summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-22 14:39:14 +0000
committerGitHub <noreply@github.com>2026-02-22 14:39:14 +0000
commit6a34a6f3c423a7c57616762eb4cea2b7da52eaf3 (patch)
tree7c596eac896918466e7ef3f149b02333fef09212
parent0523765af84492640928d571f481e17b26008b13 (diff)
downloadsoryu-6a34a6f3c423a7c57616762eb4cea2b7da52eaf3.tar.gz
soryu-6a34a6f3c423a7c57616762eb4cea2b7da52eaf3.zip
feat: Add daemon page with download binary and Cloudflare Agent setup (#77)
* feat: soryu-co/soryu - makima: Create DaemonList and DaemonDetail page components * feat: soryu-co/soryu - makima: Add daemon page routes, CSS styles, and navigation * feat: soryu-co/soryu - makima: Create daemon page with download and monitoring * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Integrate Cloudflare Agent setup into daemon page
-rw-r--r--frontend/src/components/DaemonDetail.tsx229
-rw-r--r--frontend/src/components/DaemonList.tsx125
-rw-r--r--frontend/src/components/VNInterface.tsx13
-rw-r--r--frontend/src/main.tsx10
-rw-r--r--frontend/src/styles/pc98.css59
-rw-r--r--makima/cloudflare-agent/.gitignore4
-rw-r--r--makima/cloudflare-agent/README.md237
-rw-r--r--makima/cloudflare-agent/package-lock.json2802
-rw-r--r--makima/cloudflare-agent/package.json22
-rwxr-xr-xmakima/cloudflare-agent/setup.sh331
-rw-r--r--makima/cloudflare-agent/src/agent.ts773
-rw-r--r--makima/cloudflare-agent/src/index.ts47
-rw-r--r--makima/cloudflare-agent/src/types.ts148
-rw-r--r--makima/cloudflare-agent/tsconfig.json18
-rw-r--r--makima/cloudflare-agent/wrangler.toml20
-rw-r--r--makima/frontend/src/components/NavStrip.tsx1
-rw-r--r--makima/frontend/src/hooks/useMultiTaskSubscription.ts84
-rw-r--r--makima/frontend/src/main.tsx9
-rw-r--r--makima/frontend/src/routes/daemon.tsx746
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
20 files changed, 5595 insertions, 85 deletions
diff --git a/frontend/src/components/DaemonDetail.tsx b/frontend/src/components/DaemonDetail.tsx
new file mode 100644
index 0000000..2de0997
--- /dev/null
+++ b/frontend/src/components/DaemonDetail.tsx
@@ -0,0 +1,229 @@
+import React, { useEffect, useState } from 'react'
+import { useParams, Link } from 'react-router-dom'
+
+interface Daemon {
+ id: string
+ ownerId: string
+ connectionId: string
+ hostname: string | null
+ machineId: string | null
+ maxConcurrentTasks: number
+ currentTaskCount: number
+ status: string
+ lastHeartbeatAt: string
+ connectedAt: string
+ disconnectedAt: string | null
+}
+
+interface DaemonDirectory {
+ path: string
+ label: string
+ directoryType: string
+ hostname: string | null
+ exists?: boolean
+}
+
+type Tab = 'overview' | 'directories'
+
+function statusIndicator(status: string): { color: string; label: string } {
+ switch (status.toLowerCase()) {
+ case 'connected':
+ return { color: 'green', label: 'Connected' }
+ case 'disconnected':
+ return { color: 'red', label: 'Disconnected' }
+ case 'unhealthy':
+ return { color: 'yellow', label: 'Unhealthy' }
+ default:
+ return { color: 'gray', label: status }
+ }
+}
+
+export function DaemonDetail() {
+ const { id } = useParams<{ id: string }>()
+ const [daemon, setDaemon] = useState<Daemon | null>(null)
+ const [directories, setDirectories] = useState<DaemonDirectory[]>([])
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState<string | null>(null)
+ const [activeTab, setActiveTab] = useState<Tab>('overview')
+
+ useEffect(() => {
+ async function fetchDaemon() {
+ if (!id) return
+
+ try {
+ setLoading(true)
+ const [daemonRes, dirRes] = await Promise.all([
+ fetch(`/api/v1/mesh/daemons/${id}`),
+ fetch('/api/v1/mesh/daemons/directories'),
+ ])
+
+ if (!daemonRes.ok) {
+ throw new Error(`Failed to fetch daemon: ${daemonRes.statusText}`)
+ }
+
+ const daemonData = await daemonRes.json()
+ setDaemon(daemonData)
+
+ if (dirRes.ok) {
+ const dirData = await dirRes.json()
+ setDirectories(dirData.directories || [])
+ }
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Unknown error')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchDaemon()
+
+ const interval = setInterval(async () => {
+ if (!id) return
+ try {
+ const response = await fetch(`/api/v1/mesh/daemons/${id}`)
+ if (response.ok) {
+ const daemonData = await response.json()
+ setDaemon(daemonData)
+ }
+ } catch {
+ // Silently ignore refresh errors
+ }
+ }, 10000)
+
+ return () => clearInterval(interval)
+ }, [id])
+
+ if (loading) {
+ return (
+ <div className="daemon-detail-container">
+ <div className="loading">Loading daemon...</div>
+ </div>
+ )
+ }
+
+ if (error) {
+ return (
+ <div className="daemon-detail-container">
+ <div className="error">Error: {error}</div>
+ <Link to="/daemons" className="back-link">
+ Back to Daemons
+ </Link>
+ </div>
+ )
+ }
+
+ if (!daemon) {
+ return (
+ <div className="daemon-detail-container">
+ <div className="not-found">Daemon not found</div>
+ <Link to="/daemons" className="back-link">
+ Back to Daemons
+ </Link>
+ </div>
+ )
+ }
+
+ const status = statusIndicator(daemon.status)
+ const filteredDirectories = directories.filter(
+ (dir) => dir.hostname === daemon.hostname
+ )
+
+ return (
+ <div className="daemon-detail-container">
+ <div className="daemon-detail-header">
+ <Link to="/daemons" className="back-link">
+ Back to Daemons
+ </Link>
+ <h1 className="daemon-title">{daemon.hostname || 'Unknown Host'}</h1>
+ <div className="daemon-meta">
+ <span>
+ <span
+ className="status-dot"
+ style={{ backgroundColor: status.color }}
+ />
+ {status.label}
+ </span>
+ <span>
+ Tasks: {daemon.currentTaskCount} / {daemon.maxConcurrentTasks}
+ </span>
+ </div>
+ </div>
+
+ <div className="daemon-tabs">
+ <button
+ className={`tab-button ${activeTab === 'overview' ? 'active' : ''}`}
+ onClick={() => setActiveTab('overview')}
+ >
+ Overview
+ </button>
+ <button
+ className={`tab-button ${activeTab === 'directories' ? 'active' : ''}`}
+ onClick={() => setActiveTab('directories')}
+ >
+ Directories ({filteredDirectories.length})
+ </button>
+ </div>
+
+ <div className="daemon-tab-content">
+ {activeTab === 'overview' && (
+ <div className="tab-panel">
+ <h2>Daemon Overview</h2>
+ <dl className="overview-list">
+ <dt>ID</dt>
+ <dd>{daemon.id}</dd>
+ <dt>Hostname</dt>
+ <dd>{daemon.hostname || 'N/A'}</dd>
+ <dt>Machine ID</dt>
+ <dd>{daemon.machineId || 'N/A'}</dd>
+ <dt>Status</dt>
+ <dd>
+ <span
+ className="status-dot"
+ style={{ backgroundColor: status.color }}
+ />
+ {status.label}
+ </dd>
+ <dt>Connection ID</dt>
+ <dd>{daemon.connectionId}</dd>
+ <dt>Max Concurrent Tasks</dt>
+ <dd>{daemon.maxConcurrentTasks}</dd>
+ <dt>Current Task Count</dt>
+ <dd>{daemon.currentTaskCount}</dd>
+ <dt>Connected At</dt>
+ <dd>{new Date(daemon.connectedAt).toLocaleString()}</dd>
+ <dt>Last Heartbeat At</dt>
+ <dd>{new Date(daemon.lastHeartbeatAt).toLocaleString()}</dd>
+ <dt>Disconnected At</dt>
+ <dd>
+ {daemon.disconnectedAt
+ ? new Date(daemon.disconnectedAt).toLocaleString()
+ : 'N/A'}
+ </dd>
+ </dl>
+ </div>
+ )}
+
+ {activeTab === 'directories' && (
+ <div className="tab-panel">
+ <h2>Directories</h2>
+ {filteredDirectories.length === 0 ? (
+ <p>No directories found for this daemon</p>
+ ) : (
+ <ul className="directory-list">
+ {filteredDirectories.map((dir, index) => (
+ <li key={`${dir.path}-${index}`} className="directory-item">
+ <h3>{dir.label}</h3>
+ <div className="daemon-meta">
+ <span>Path: {dir.path}</span>
+ <span>Type: {dir.directoryType}</span>
+ </div>
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
+ )}
+ </div>
+ </div>
+ )
+}
diff --git a/frontend/src/components/DaemonList.tsx b/frontend/src/components/DaemonList.tsx
new file mode 100644
index 0000000..215c790
--- /dev/null
+++ b/frontend/src/components/DaemonList.tsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useState } from 'react'
+import { Link } from 'react-router-dom'
+
+interface DaemonSummary {
+ id: string
+ ownerId: string
+ connectionId: string
+ hostname: string | null
+ machineId: string | null
+ maxConcurrentTasks: number
+ currentTaskCount: number
+ status: string
+ lastHeartbeatAt: string
+ connectedAt: string
+ disconnectedAt: string | null
+}
+
+function statusDotColor(status: string): string {
+ switch (status.toLowerCase()) {
+ case 'connected':
+ return 'green'
+ case 'disconnected':
+ return 'red'
+ case 'unhealthy':
+ return 'yellow'
+ default:
+ return 'gray'
+ }
+}
+
+export function DaemonList() {
+ const [daemons, setDaemons] = useState<DaemonSummary[]>([])
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState<string | null>(null)
+
+ useEffect(() => {
+ async function fetchDaemons() {
+ try {
+ setLoading(true)
+ const response = await fetch('/api/v1/mesh/daemons')
+ if (!response.ok) {
+ throw new Error(`Failed to fetch daemons: ${response.statusText}`)
+ }
+ const data = await response.json()
+ setDaemons(data.daemons || [])
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Unknown error')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ fetchDaemons()
+
+ const interval = setInterval(async () => {
+ try {
+ const response = await fetch('/api/v1/mesh/daemons')
+ if (response.ok) {
+ const data = await response.json()
+ setDaemons(data.daemons || [])
+ }
+ } catch {
+ // Silently ignore refresh errors
+ }
+ }, 10000)
+
+ return () => clearInterval(interval)
+ }, [])
+
+ if (loading) {
+ return (
+ <div className="daemon-list-container">
+ <div className="loading">Loading daemons...</div>
+ </div>
+ )
+ }
+
+ if (error) {
+ return (
+ <div className="daemon-list-container">
+ <div className="error">Error: {error}</div>
+ </div>
+ )
+ }
+
+ return (
+ <div className="daemon-list-container">
+ <Link to="/" className="back-link">
+ Back to Home
+ </Link>
+ <h1>Daemons</h1>
+ {daemons.length === 0 ? (
+ <p>No daemons found</p>
+ ) : (
+ <ul className="daemon-list">
+ {daemons.map((daemon) => (
+ <li key={daemon.id} className="daemon-item">
+ <Link to={`/daemons/${daemon.id}`}>
+ <h2>{daemon.hostname || 'Unknown Host'}</h2>
+ <div className="daemon-status">
+ <span
+ className="status-dot"
+ style={{ backgroundColor: statusDotColor(daemon.status) }}
+ />
+ <span>{daemon.status}</span>
+ </div>
+ <div className="daemon-meta">
+ <span>
+ Tasks: {daemon.currentTaskCount} / {daemon.maxConcurrentTasks}
+ </span>
+ <span>
+ Connected: {new Date(daemon.connectedAt).toLocaleString()}
+ </span>
+ {daemon.machineId && (
+ <span>Machine: {daemon.machineId}</span>
+ )}
+ </div>
+ </Link>
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
+ )
+}
diff --git a/frontend/src/components/VNInterface.tsx b/frontend/src/components/VNInterface.tsx
index be71d27..318a9b9 100644
--- a/frontend/src/components/VNInterface.tsx
+++ b/frontend/src/components/VNInterface.tsx
@@ -1,4 +1,5 @@
import React, { useEffect } from 'react'
+import { Link } from 'react-router-dom'
import { useStore } from '@nanostores/react'
import {
isStandbyStore,
@@ -100,6 +101,18 @@ export function VNInterface({ onLogout }: VNInterfaceProps) {
{isStandby ? 'STDBY' : 'LIVE'}
</span>
</div>
+ <div className="status-item">
+ <Link to="/daemons" style={{ color: '#66ccff', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '4px' }}>
+ <span className="info-label">Mesh:</span>
+ <span className="info-value">Daemons</span>
+ </Link>
+ </div>
+ <div className="status-item">
+ <Link to="/contracts" style={{ color: '#66ccff', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '4px' }}>
+ <span className="info-label">View:</span>
+ <span className="info-value">Contracts</span>
+ </Link>
+ </div>
</div>
</div>
</div>
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index a6eae5b..04b8cde 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -5,6 +5,8 @@ import App from './App'
import { ContractList } from './components/ContractList'
import { ContractDetail } from './components/ContractDetail'
import { FileDetail } from './components/FileDetail'
+import { DaemonList } from './components/DaemonList'
+import { DaemonDetail } from './components/DaemonDetail'
import './styles/pc98.css'
import './styles/mobile.css'
@@ -33,6 +35,14 @@ const router = createBrowserRouter([
path: '/contracts/:contractId/files/:fileId',
element: <FileDetail />,
},
+ {
+ path: '/daemons',
+ element: <DaemonList />,
+ },
+ {
+ path: '/daemons/:id',
+ element: <DaemonDetail />,
+ },
])
ReactDOM.createRoot(document.getElementById('root')!).render(
diff --git a/frontend/src/styles/pc98.css b/frontend/src/styles/pc98.css
index 4dcf15e..7ec0d1c 100644
--- a/frontend/src/styles/pc98.css
+++ b/frontend/src/styles/pc98.css
@@ -4621,3 +4621,62 @@ button:focus-visible {
display: none;
}
}
+
+/* ===== Daemon Pages ===== */
+
+.daemon-list-container { max-width: 900px; margin: 0 auto; padding: 40px 20px; font-family: 'MS Gothic', monospace; min-height: 100vh; background: var(--bg-gradient); }
+.daemon-list-container h1 { font-family: 'MS Gothic', monospace; font-size: 24px; color: #66ccff; text-transform: uppercase; letter-spacing: 2px; margin: 0 0 30px 0; border-bottom: 2px solid rgba(102, 204, 255, 0.3); padding-bottom: 10px; }
+.daemon-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 12px; }
+.daemon-item { background: rgba(0, 0, 0, 0.6); border: 1px solid rgba(102, 204, 255, 0.3); padding: 0; transition: all 0.2s ease; }
+.daemon-item:hover { border-color: #66ccff; background: rgba(0, 0, 51, 0.8); box-shadow: 0 0 15px rgba(102, 204, 255, 0.15); }
+.daemon-item a { display: block; padding: 16px 20px; color: inherit; text-decoration: none; }
+.daemon-item h2 { font-family: 'MS Gothic', monospace; font-size: 16px; color: #ffffff; margin: 0 0 8px 0; display: flex; align-items: center; gap: 10px; }
+.daemon-meta { display: flex; gap: 20px; flex-wrap: wrap; font-size: 12px; color: rgba(255, 255, 255, 0.6); }
+.daemon-meta span { display: flex; align-items: center; gap: 4px; }
+
+/* Status indicators */
+.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 4px; }
+.status-dot.connected { background: #00ff88; box-shadow: 0 0 6px rgba(0, 255, 136, 0.6); }
+.status-dot.disconnected { background: #ff4466; box-shadow: 0 0 6px rgba(255, 68, 102, 0.6); }
+.status-dot.unhealthy { background: #ffcc66; box-shadow: 0 0 6px rgba(255, 204, 102, 0.6); }
+.daemon-status { display: flex; align-items: center; font-size: 12px; text-transform: uppercase; letter-spacing: 1px; }
+.daemon-status.connected { color: #00ff88; }
+.daemon-status.disconnected { color: #ff4466; }
+.daemon-status.unhealthy { color: #ffcc66; }
+
+/* Daemon Detail */
+.daemon-detail-container { max-width: 900px; margin: 0 auto; padding: 40px 20px; font-family: 'MS Gothic', monospace; min-height: 100vh; background: var(--bg-gradient); }
+.daemon-detail-header { margin-bottom: 30px; }
+.daemon-title { font-family: 'MS Gothic', monospace; font-size: 22px; color: #ffffff; margin: 15px 0 8px; }
+
+/* Shared back links */
+.daemon-list-container .back-link, .daemon-detail-container .back-link { color: #66ccff; text-decoration: none; font-size: 13px; display: inline-flex; align-items: center; gap: 4px; opacity: 0.8; transition: opacity 0.2s ease; }
+.daemon-list-container .back-link:hover, .daemon-detail-container .back-link:hover { opacity: 1; text-decoration: underline; }
+
+/* Daemon Tabs */
+.daemon-tabs { display: flex; gap: 0; border-bottom: 2px solid rgba(102, 204, 255, 0.3); margin-bottom: 20px; }
+.daemon-tabs .tab-button { background: transparent; border: none; border-bottom: 2px solid transparent; color: rgba(255, 255, 255, 0.5); font-family: 'MS Gothic', monospace; font-size: 13px; padding: 10px 20px; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; transition: all 0.2s ease; margin-bottom: -2px; }
+.daemon-tabs .tab-button:hover { color: rgba(255, 255, 255, 0.8); background: rgba(102, 204, 255, 0.05); }
+.daemon-tabs .tab-button.active { color: #66ccff; border-bottom-color: #66ccff; }
+
+/* Tab content */
+.daemon-tab-content .tab-panel { animation: fadeIn 0.2s ease; }
+.daemon-tab-content .tab-panel h2 { font-family: 'MS Gothic', monospace; font-size: 16px; color: #66ccff; margin: 0 0 15px 0; text-transform: uppercase; letter-spacing: 1px; }
+.daemon-tab-content .overview-list { margin: 0; display: grid; grid-template-columns: 180px 1fr; gap: 0; }
+.daemon-tab-content .overview-list dt { padding: 10px 15px; color: rgba(255, 255, 255, 0.5); font-size: 12px; text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid rgba(102, 204, 255, 0.1); }
+.daemon-tab-content .overview-list dd { padding: 10px 15px; margin: 0; color: #ffffff; font-size: 13px; border-bottom: 1px solid rgba(102, 204, 255, 0.1); }
+
+/* Directory list */
+.directory-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
+.directory-item { background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(102, 204, 255, 0.2); padding: 12px 16px; }
+.directory-item h3 { font-size: 14px; color: #ffffff; margin: 0 0 4px 0; }
+.directory-item .directory-path { font-size: 12px; color: rgba(255, 255, 255, 0.5); word-break: break-all; }
+.directory-item .directory-type { display: inline-block; font-size: 10px; color: #66ccff; text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; padding: 2px 6px; border: 1px solid rgba(102, 204, 255, 0.3); }
+
+/* Capacity bar */
+.task-capacity { display: flex; align-items: center; gap: 8px; font-size: 12px; }
+.capacity-bar { width: 60px; height: 6px; background: rgba(255, 255, 255, 0.1); overflow: hidden; }
+.capacity-fill { height: 100%; background: #66ccff; transition: width 0.3s ease; }
+.capacity-fill.high { background: #ffcc66; }
+.capacity-fill.full { background: #ff4466; }
+.refresh-indicator { font-size: 11px; color: rgba(255, 255, 255, 0.3); margin-left: auto; }
diff --git a/makima/cloudflare-agent/.gitignore b/makima/cloudflare-agent/.gitignore
new file mode 100644
index 0000000..d833692
--- /dev/null
+++ b/makima/cloudflare-agent/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+.wrangler/
+.dev.vars
+dist/
diff --git a/makima/cloudflare-agent/README.md b/makima/cloudflare-agent/README.md
new file mode 100644
index 0000000..b33842a
--- /dev/null
+++ b/makima/cloudflare-agent/README.md
@@ -0,0 +1,237 @@
+# Makima Cloudflare Agent
+
+Edge relay agent for [Makima](https://github.com/soryu-co/makima) — distributed task orchestration for AI coding agents.
+
+This Cloudflare Worker acts as a **WebSocket relay** between the Makima server and native daemon instances. It runs on Cloudflare's edge network using Durable Objects for persistent state and connections.
+
+## Why an Edge Relay?
+
+The full Makima daemon requires native capabilities (process spawning, git operations, filesystem access) that aren't available on Cloudflare Workers. Instead, this agent serves as:
+
+1. **WebSocket Relay** — Bridges the Makima server with remote daemon instances via persistent WebSocket connections
+2. **Task Queue Manager** — Receives tasks from the server and dispatches them to the least-loaded downstream daemon
+3. **Status Aggregator** — Tracks daemon health and task history across edge locations
+4. **API Proxy** — Provides HTTP endpoints for monitoring and management from the edge
+
+## Architecture
+
+```
+ Cloudflare Edge
+ ┌─────────────────────────┐
+ │ │
+ Makima Server ◄──►│ MakimaAgent │◄──► Native Daemon 1
+ (wss://...) │ (Durable Object) │◄──► Native Daemon 2
+ │ │◄──► Native Daemon N
+ │ ┌───────────────────┐ │
+ │ │ SQLite State │ │
+ │ │ - Task history │ │
+ │ │ - Connection logs │ │
+ │ └───────────────────┘ │
+ │ │
+ └─────────────────────────┘
+```
+
+**Message flow:**
+
+1. The agent maintains a persistent WebSocket to the Makima server (upstream)
+2. Native daemons connect to the agent via WebSocket at `/ws/daemon` (downstream)
+3. When the server sends a `SpawnTask` command, the agent selects the least-loaded downstream daemon and forwards the task
+4. Task output, progress, and completion messages from daemons are relayed back to the server
+5. The agent sends periodic heartbeats and tracks all task dispatches in SQLite
+
+## Prerequisites
+
+- **Cloudflare Account** with Workers and Durable Objects enabled
+- **Node.js 18+** and npm
+- **Makima Server** running and accessible (default: `wss://api.makima.jp`)
+- **API Key** from your Makima server dashboard
+
+## Quick Setup
+
+```bash
+# Clone and navigate to the cloudflare-agent directory
+cd makima/cloudflare-agent
+
+# Run the interactive setup script
+chmod +x setup.sh
+./setup.sh
+```
+
+The setup script will:
+
+1. Check prerequisites (Node.js 18+, npm, npx)
+2. Prompt for your Makima server URL and API key
+3. Install npm dependencies
+4. Create `.dev.vars` with your secrets
+5. Check Cloudflare authentication (offer to log in)
+6. Optionally set production secrets
+7. Offer to deploy immediately or start a dev server
+
+## Manual Setup
+
+If you prefer manual configuration:
+
+```bash
+# 1. Install dependencies
+npm install
+
+# 2. Create local secrets file
+cat > .dev.vars <<EOF
+MAKIMA_SERVER_URL=wss://api.makima.jp
+MAKIMA_API_KEY=your-api-key-here
+MAKIMA_AGENT_NAME=my-edge-agent
+EOF
+
+# 3. Log in to Cloudflare
+npx wrangler login
+
+# 4. Set production secrets
+echo "wss://api.makima.jp" | npx wrangler secret put MAKIMA_SERVER_URL
+echo "your-api-key-here" | npx wrangler secret put MAKIMA_API_KEY
+echo "my-edge-agent" | npx wrangler secret put MAKIMA_AGENT_NAME
+
+# 5. Deploy
+npx wrangler deploy
+```
+
+## Development
+
+```bash
+# Start local dev server with hot reload
+npm run dev
+
+# Stream production logs
+npm run tail
+
+# Deploy to Cloudflare
+npm run deploy
+```
+
+## API Endpoints
+
+After deployment, the agent exposes these HTTP endpoints:
+
+| Method | Path | Description |
+|--------|---------------|------------------------------------------|
+| GET | `/` | Agent status (same as `/status`) |
+| GET | `/status` | Connection status and daemon overview |
+| GET | `/health` | Simple health check |
+| GET | `/tasks` | Task dispatch history (paginated) |
+| GET | `/logs` | Connection event logs |
+| POST | `/reconnect` | Force reconnection to upstream server |
+| WS | `/ws/daemon` | WebSocket endpoint for downstream daemons|
+
+### Query Parameters
+
+- **`/tasks`**: `?limit=50&offset=0` — Paginate task history
+- **`/logs`**: `?limit=50` — Limit log entries returned
+
+### Example Responses
+
+**GET /status**
+```json
+{
+ "status": "ok",
+ "agentName": "makima-edge",
+ "upstreamConnected": true,
+ "daemonId": "550e8400-e29b-41d4-a716-446655440000",
+ "lastHeartbeat": "2024-12-15T10:30:00.000Z",
+ "connectedDaemons": 2,
+ "activeTasks": 3,
+ "totalTasksProcessed": 142
+}
+```
+
+**GET /health**
+```json
+{
+ "healthy": true,
+ "upstreamConnected": true
+}
+```
+
+## Environment Variables
+
+| Variable | Required | Description | Default |
+|-----------------------|----------|------------------------------------------------|-----------------------|
+| `MAKIMA_SERVER_URL` | Yes | WebSocket URL of the Makima server | — |
+| `MAKIMA_API_KEY` | Yes | API key for server authentication | — |
+| `MAKIMA_AGENT_NAME` | No | Human-readable name for this agent | `makima-edge-{id}` |
+
+Set these as Cloudflare Workers secrets for production:
+```bash
+echo "value" | npx wrangler secret put VARIABLE_NAME
+```
+
+For local development, put them in `.dev.vars` (gitignored automatically).
+
+## How It Works
+
+### Upstream Connection (to Makima Server)
+
+1. On startup, the agent opens a WebSocket to `MAKIMA_SERVER_URL/ws/daemon`
+2. It authenticates using the `MAKIMA_API_KEY` with `maxConcurrentTasks: 0` (relay-only mode)
+3. Sends heartbeats every 30 seconds
+4. On disconnect, reconnects with exponential backoff (1s → 60s, max 20 attempts)
+
+### Downstream Connections (from Native Daemons)
+
+1. Native Makima daemons connect via WebSocket to `/ws/daemon`
+2. Daemons authenticate with their hostname and max concurrent task count
+3. The agent tracks each daemon's active tasks and health
+
+### Task Dispatch
+
+When the server sends a `SpawnTask` command:
+1. The agent records the task in SQLite
+2. Selects the downstream daemon with the fewest active tasks (that still has capacity)
+3. Forwards the full `SpawnTask` command
+4. If no daemons are available, the task remains pending
+
+### Persistence
+
+The agent uses Cloudflare Durable Objects with SQLite for:
+- **Task history** — All received, dispatched, completed, and failed tasks
+- **Connection logs** — Connect/disconnect/error events (last 200 entries)
+- **Agent state** — Connection status, daemon ID, active tasks
+
+The Durable Object auto-hibernates when idle and resumes on the next request, preserving all state.
+
+## Connecting Native Daemons
+
+Native Makima daemons can connect to this edge relay instead of directly to the server. Configure the daemon to point at your deployed Worker URL:
+
+```toml
+# makima-daemon.toml
+[server]
+url = "wss://makima-agent.your-account.workers.dev/ws/daemon"
+api_key = "your-api-key"
+```
+
+Or via environment variable:
+```bash
+MAKIMA_DAEMON_SERVER__URL=wss://makima-agent.your-account.workers.dev/ws/daemon \
+MAKIMA_DAEMON_SERVER__API_KEY=your-api-key \
+makima daemon
+```
+
+## Troubleshooting
+
+### Agent shows "disconnected" status
+- Verify `MAKIMA_SERVER_URL` is correct and the server is reachable
+- Check that `MAKIMA_API_KEY` is valid
+- Review connection logs at `GET /logs`
+
+### Tasks stuck in "pending"
+- No downstream daemons are connected
+- Ensure native daemons are pointing to this agent's `/ws/daemon` endpoint
+- Check `GET /status` for `connectedDaemons` count
+
+### Deployment fails
+- Ensure you're logged in: `npx wrangler whoami`
+- Ensure your account has Durable Objects enabled
+- Check `wrangler.toml` configuration
+
+## License
+
+Part of the [Makima](https://github.com/soryu-co/makima) project.
diff --git a/makima/cloudflare-agent/package-lock.json b/makima/cloudflare-agent/package-lock.json
new file mode 100644
index 0000000..256a708
--- /dev/null
+++ b/makima/cloudflare-agent/package-lock.json
@@ -0,0 +1,2802 @@
+{
+ "name": "makima-agent",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "makima-agent",
+ "version": "0.1.0",
+ "dependencies": {
+ "agents": "^0.0.67"
+ },
+ "devDependencies": {
+ "@cloudflare/workers-types": "^4.20241230.0",
+ "typescript": "^5.7.0",
+ "wrangler": "^3.99.0"
+ }
+ },
+ "node_modules/@ai-sdk/provider": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz",
+ "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==",
+ "dependencies": {
+ "json-schema": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ai-sdk/provider-utils": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz",
+ "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==",
+ "dependencies": {
+ "@ai-sdk/provider": "1.1.3",
+ "nanoid": "^3.3.8",
+ "secure-json-parse": "^2.7.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.23.8"
+ }
+ },
+ "node_modules/@ai-sdk/provider-utils/node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/@ai-sdk/react": {
+ "version": "1.2.12",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz",
+ "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "2.2.8",
+ "@ai-sdk/ui-utils": "1.2.11",
+ "swr": "^2.2.5",
+ "throttleit": "2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "zod": "^3.23.8"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/ui-utils": {
+ "version": "1.2.11",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz",
+ "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==",
+ "dependencies": {
+ "@ai-sdk/provider": "1.1.3",
+ "@ai-sdk/provider-utils": "2.2.8",
+ "zod-to-json-schema": "^3.24.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.23.8"
+ }
+ },
+ "node_modules/@cloudflare/kv-asset-handler": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz",
+ "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==",
+ "dev": true,
+ "dependencies": {
+ "mime": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.13"
+ }
+ },
+ "node_modules/@cloudflare/unenv-preset": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz",
+ "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==",
+ "dev": true,
+ "peerDependencies": {
+ "unenv": "2.0.0-rc.14",
+ "workerd": "^1.20250124.0"
+ },
+ "peerDependenciesMeta": {
+ "workerd": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-64": {
+ "version": "1.20250718.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz",
+ "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-arm64": {
+ "version": "1.20250718.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz",
+ "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-64": {
+ "version": "1.20250718.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz",
+ "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-arm64": {
+ "version": "1.20250718.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz",
+ "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-windows-64": {
+ "version": "1.20250718.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz",
+ "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workers-types": {
+ "version": "4.20260228.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260228.0.tgz",
+ "integrity": "sha512-9LfRg93ncQq6Oc4MFpqGSs+PmPhqWvg8TspXwbiYNR201IhXB4WqHR/aTSudPI0ujsf/NLc8E9fF3C+aA2g8KQ=="
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild-plugins/node-globals-polyfill": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz",
+ "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==",
+ "dev": true,
+ "peerDependencies": {
+ "esbuild": "*"
+ }
+ },
+ "node_modules/@esbuild-plugins/node-modules-polyfill": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz",
+ "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0",
+ "rollup-plugin-node-polyfills": "^0.2.1"
+ },
+ "peerDependencies": {
+ "esbuild": "*"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
+ "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
+ "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
+ "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
+ "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
+ "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
+ "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
+ "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
+ "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
+ "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
+ "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
+ "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
+ "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
+ "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
+ "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
+ "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
+ "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
+ "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
+ "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
+ "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
+ "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
+ "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
+ "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@fastify/busboy": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
+ "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.9",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
+ "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
+ "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==",
+ "dependencies": {
+ "@hono/node-server": "^1.19.9",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@types/diff-match-patch": {
+ "version": "1.0.36",
+ "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
+ "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+ "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agents": {
+ "version": "0.0.67",
+ "resolved": "https://registry.npmjs.org/agents/-/agents-0.0.67.tgz",
+ "integrity": "sha512-Bjn8w9230xcgeP/UKvWbZVvQvPuCpA1S0f8X6859xSvkviAcKWgHidnyRBUMUCniljNTzbU1ZgR8YWveJ4xbqg==",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.10.2",
+ "ai": "^4.3.9",
+ "cron-schedule": "^5.0.4",
+ "nanoid": "^5.1.5",
+ "partyserver": "^0.0.67",
+ "partysocket": "1.1.3",
+ "zod": "^3.24.3"
+ },
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
+ "node_modules/ai": {
+ "version": "4.3.19",
+ "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz",
+ "integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==",
+ "dependencies": {
+ "@ai-sdk/provider": "1.1.3",
+ "@ai-sdk/provider-utils": "2.2.8",
+ "@ai-sdk/react": "1.2.12",
+ "@ai-sdk/ui-utils": "1.2.11",
+ "@opentelemetry/api": "1.9.0",
+ "jsondiffpatch": "0.6.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "zod": "^3.23.8"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/as-table": {
+ "version": "1.0.55",
+ "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz",
+ "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==",
+ "dev": true,
+ "dependencies": {
+ "printable-characters": "^1.0.42"
+ }
+ },
+ "node_modules/blake3-wasm": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
+ "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
+ "dev": true
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cron-schedule": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-5.0.4.tgz",
+ "integrity": "sha512-nH0a49E/kSVk6BeFgKZy4uUsy6D2A16p120h5bYD9ILBhQu7o2sJFH+WI4R731TSBQ0dB1Ik7inB/dRAB4C8QQ==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz",
+ "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+ "dev": true
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/diff-match-patch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+ "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
+ "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.17.19",
+ "@esbuild/android-arm64": "0.17.19",
+ "@esbuild/android-x64": "0.17.19",
+ "@esbuild/darwin-arm64": "0.17.19",
+ "@esbuild/darwin-x64": "0.17.19",
+ "@esbuild/freebsd-arm64": "0.17.19",
+ "@esbuild/freebsd-x64": "0.17.19",
+ "@esbuild/linux-arm": "0.17.19",
+ "@esbuild/linux-arm64": "0.17.19",
+ "@esbuild/linux-ia32": "0.17.19",
+ "@esbuild/linux-loong64": "0.17.19",
+ "@esbuild/linux-mips64el": "0.17.19",
+ "@esbuild/linux-ppc64": "0.17.19",
+ "@esbuild/linux-riscv64": "0.17.19",
+ "@esbuild/linux-s390x": "0.17.19",
+ "@esbuild/linux-x64": "0.17.19",
+ "@esbuild/netbsd-x64": "0.17.19",
+ "@esbuild/openbsd-x64": "0.17.19",
+ "@esbuild/sunos-x64": "0.17.19",
+ "@esbuild/win32-arm64": "0.17.19",
+ "@esbuild/win32-ia32": "0.17.19",
+ "@esbuild/win32-x64": "0.17.19"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
+ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
+ "dev": true
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-polyfill": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz",
+ "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ=="
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
+ "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/exit-hook": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
+ "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
+ "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
+ "dependencies": {
+ "ip-address": "10.0.1"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "dev": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ]
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-source": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz",
+ "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==",
+ "dev": true,
+ "dependencies": {
+ "data-uri-to-buffer": "^2.0.0",
+ "source-map": "^0.6.1"
+ }
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.1.tgz",
+ "integrity": "sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw==",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ip-address": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
+ "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "node_modules/jose": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
+ "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="
+ },
+ "node_modules/jsondiffpatch": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
+ "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
+ "dependencies": {
+ "@types/diff-match-patch": "^1.0.36",
+ "chalk": "^5.3.0",
+ "diff-match-patch": "^1.0.5"
+ },
+ "bin": {
+ "jsondiffpatch": "bin/jsondiffpatch.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dev": true,
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "dev": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/miniflare": {
+ "version": "3.20250718.3",
+ "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250718.3.tgz",
+ "integrity": "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ==",
+ "dev": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "0.8.1",
+ "acorn": "8.14.0",
+ "acorn-walk": "8.3.2",
+ "exit-hook": "2.2.1",
+ "glob-to-regexp": "0.4.1",
+ "stoppable": "1.1.0",
+ "undici": "^5.28.5",
+ "workerd": "1.20250718.0",
+ "ws": "8.18.0",
+ "youch": "3.3.4",
+ "zod": "3.22.3"
+ },
+ "bin": {
+ "miniflare": "bootstrap.js"
+ },
+ "engines": {
+ "node": ">=16.13"
+ }
+ },
+ "node_modules/miniflare/node_modules/zod": {
+ "version": "3.22.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
+ "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "dev": true,
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
+ "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "dev": true
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/partyserver": {
+ "version": "0.0.67",
+ "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.0.67.tgz",
+ "integrity": "sha512-GQ0fjJ7n5r5LrsFHFUkGR3Bd50YdBZaDNjvRTi8PowZgI5fvCFliT/XdDpRVuwfDDk0jb9es2cXcaAh1z5GsLA==",
+ "dependencies": {
+ "nanoid": "^5.1.5"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^4.20240729.0"
+ }
+ },
+ "node_modules/partysocket": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.1.3.tgz",
+ "integrity": "sha512-87Jd/nqPoWnVfzHE6Z12WLWTJ+TAgxs0b7i2S163HfQSrVDUK5tW/FC64T5N8L5ss+gqF+EV0BwjZMWggMY3UA==",
+ "dependencies": {
+ "event-target-polyfill": "^0.0.4"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/printable-characters": {
+ "version": "1.0.42",
+ "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
+ "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
+ "dev": true
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup-plugin-inject": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
+ "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==",
+ "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.",
+ "dev": true,
+ "dependencies": {
+ "estree-walker": "^0.6.1",
+ "magic-string": "^0.25.3",
+ "rollup-pluginutils": "^2.8.1"
+ }
+ },
+ "node_modules/rollup-plugin-node-polyfills": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz",
+ "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==",
+ "dev": true,
+ "dependencies": {
+ "rollup-plugin-inject": "^3.0.0"
+ }
+ },
+ "node_modules/rollup-pluginutils": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
+ "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
+ "dev": true,
+ "dependencies": {
+ "estree-walker": "^0.6.1"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/secure-json-parse": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
+ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "deprecated": "Please use @jridgewell/sourcemap-codec instead",
+ "dev": true
+ },
+ "node_modules/stacktracey": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz",
+ "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==",
+ "dev": true,
+ "dependencies": {
+ "as-table": "^1.0.36",
+ "get-source": "^2.0.12"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stoppable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
+ "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/swr": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.0.tgz",
+ "integrity": "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==",
+ "dependencies": {
+ "dequal": "^2.0.3",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/throttleit": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
+ "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
+ "dev": true
+ },
+ "node_modules/undici": {
+ "version": "5.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
+ "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
+ "dev": true,
+ "dependencies": {
+ "@fastify/busboy": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
+ "node_modules/unenv": {
+ "version": "2.0.0-rc.14",
+ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz",
+ "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==",
+ "dev": true,
+ "dependencies": {
+ "defu": "^6.1.4",
+ "exsolve": "^1.0.1",
+ "ohash": "^2.0.10",
+ "pathe": "^2.0.3",
+ "ufo": "^1.5.4"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/workerd": {
+ "version": "1.20250718.0",
+ "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250718.0.tgz",
+ "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "workerd": "bin/workerd"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "@cloudflare/workerd-darwin-64": "1.20250718.0",
+ "@cloudflare/workerd-darwin-arm64": "1.20250718.0",
+ "@cloudflare/workerd-linux-64": "1.20250718.0",
+ "@cloudflare/workerd-linux-arm64": "1.20250718.0",
+ "@cloudflare/workerd-windows-64": "1.20250718.0"
+ }
+ },
+ "node_modules/wrangler": {
+ "version": "3.114.17",
+ "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.17.tgz",
+ "integrity": "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA==",
+ "dev": true,
+ "dependencies": {
+ "@cloudflare/kv-asset-handler": "0.3.4",
+ "@cloudflare/unenv-preset": "2.0.2",
+ "@esbuild-plugins/node-globals-polyfill": "0.2.3",
+ "@esbuild-plugins/node-modules-polyfill": "0.2.2",
+ "blake3-wasm": "2.1.5",
+ "esbuild": "0.17.19",
+ "miniflare": "3.20250718.3",
+ "path-to-regexp": "6.3.0",
+ "unenv": "2.0.0-rc.14",
+ "workerd": "1.20250718.0"
+ },
+ "bin": {
+ "wrangler": "bin/wrangler.js",
+ "wrangler2": "bin/wrangler.js"
+ },
+ "engines": {
+ "node": ">=16.17.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2",
+ "sharp": "^0.33.5"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^4.20250408.0"
+ },
+ "peerDependenciesMeta": {
+ "@cloudflare/workers-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/wrangler/node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "dev": true
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/youch": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz",
+ "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==",
+ "dev": true,
+ "dependencies": {
+ "cookie": "^0.7.1",
+ "mustache": "^4.2.0",
+ "stacktracey": "^2.1.8"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.1",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
+ "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
+ "peerDependencies": {
+ "zod": "^3.25 || ^4"
+ }
+ }
+ }
+}
diff --git a/makima/cloudflare-agent/package.json b/makima/cloudflare-agent/package.json
new file mode 100644
index 0000000..5b6a01e
--- /dev/null
+++ b/makima/cloudflare-agent/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "makima-agent",
+ "version": "0.1.0",
+ "description": "Makima edge relay agent for Cloudflare Workers - bridges the Makima server with remote daemon instances",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "wrangler dev",
+ "deploy": "wrangler deploy",
+ "setup": "bash setup.sh",
+ "tail": "wrangler tail",
+ "types": "wrangler types"
+ },
+ "dependencies": {
+ "agents": "^0.0.67"
+ },
+ "devDependencies": {
+ "@cloudflare/workers-types": "^4.20241230.0",
+ "typescript": "^5.7.0",
+ "wrangler": "^3.99.0"
+ }
+}
diff --git a/makima/cloudflare-agent/setup.sh b/makima/cloudflare-agent/setup.sh
new file mode 100755
index 0000000..acf9cd9
--- /dev/null
+++ b/makima/cloudflare-agent/setup.sh
@@ -0,0 +1,331 @@
+#!/usr/bin/env bash
+# ============================================================================
+# Makima Cloudflare Agent — Interactive Setup Script
+#
+# Sets up the Cloudflare Workers project for deploying a Makima edge relay
+# agent. This agent acts as a WebSocket bridge between the Makima server
+# and native daemon instances running on your infrastructure.
+#
+# Usage:
+# chmod +x setup.sh && ./setup.sh
+# ============================================================================
+
+set -euo pipefail
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m' # No Color
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+info() { echo -e "${BLUE}ℹ${NC} $*"; }
+success() { echo -e "${GREEN}✔${NC} $*"; }
+warn() { echo -e "${YELLOW}⚠${NC} $*"; }
+error() { echo -e "${RED}✖${NC} $*"; }
+
+prompt() {
+ local var_name="$1"
+ local prompt_text="$2"
+ local default_value="${3:-}"
+ local value
+
+ if [ -n "$default_value" ]; then
+ echo -ne "${CYAN}?${NC} ${prompt_text} ${BOLD}[${default_value}]${NC}: "
+ else
+ echo -ne "${CYAN}?${NC} ${prompt_text}: "
+ fi
+
+ read -r value
+ value="${value:-$default_value}"
+ eval "$var_name=\"$value\""
+}
+
+confirm() {
+ local prompt_text="$1"
+ local default="${2:-y}"
+ local yn
+
+ if [ "$default" = "y" ]; then
+ echo -ne "${CYAN}?${NC} ${prompt_text} ${BOLD}[Y/n]${NC}: "
+ else
+ echo -ne "${CYAN}?${NC} ${prompt_text} ${BOLD}[y/N]${NC}: "
+ fi
+
+ read -r yn
+ yn="${yn:-$default}"
+
+ case "$yn" in
+ [Yy]*) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+separator() {
+ echo ""
+ echo -e "${BLUE}──────────────────────────────────────────────────────${NC}"
+ echo ""
+}
+
+# ---------------------------------------------------------------------------
+# Header
+# ---------------------------------------------------------------------------
+
+clear 2>/dev/null || true
+echo ""
+echo -e "${BOLD}${CYAN}"
+echo " ╔══════════════════════════════════════════════════╗"
+echo " ║ Makima Cloudflare Agent Setup ║"
+echo " ║ ║"
+echo " ║ Edge relay for distributed task orchestration ║"
+echo " ╚══════════════════════════════════════════════════╝"
+echo -e "${NC}"
+echo ""
+
+# ---------------------------------------------------------------------------
+# 1. Check Prerequisites
+# ---------------------------------------------------------------------------
+
+info "Checking prerequisites..."
+echo ""
+
+missing=0
+
+# Node.js
+if command -v node &>/dev/null; then
+ node_version=$(node --version)
+ # Check if version is 18+
+ major_version=$(echo "$node_version" | sed 's/v//' | cut -d. -f1)
+ if [ "$major_version" -ge 18 ]; then
+ success "Node.js ${node_version}"
+ else
+ error "Node.js ${node_version} (requires v18+)"
+ missing=1
+ fi
+else
+ error "Node.js not found (requires v18+)"
+ missing=1
+fi
+
+# npm
+if command -v npm &>/dev/null; then
+ success "npm $(npm --version)"
+else
+ error "npm not found"
+ missing=1
+fi
+
+# npx
+if command -v npx &>/dev/null; then
+ success "npx available"
+else
+ error "npx not found"
+ missing=1
+fi
+
+if [ "$missing" -eq 1 ]; then
+ echo ""
+ error "Missing prerequisites. Please install the missing tools and try again."
+ echo ""
+ info "Install Node.js 18+: https://nodejs.org/"
+ exit 1
+fi
+
+separator
+
+# ---------------------------------------------------------------------------
+# 2. Gather Configuration
+# ---------------------------------------------------------------------------
+
+info "Let's configure your Makima edge agent."
+echo ""
+
+prompt MAKIMA_SERVER_URL "Makima server WebSocket URL" "wss://api.makima.jp"
+echo ""
+
+prompt MAKIMA_API_KEY "Makima API key (from your server dashboard)" ""
+if [ -z "$MAKIMA_API_KEY" ]; then
+ error "API key is required."
+ exit 1
+fi
+echo ""
+
+prompt MAKIMA_AGENT_NAME "Agent display name (optional)" "makima-edge"
+echo ""
+
+separator
+
+# ---------------------------------------------------------------------------
+# 3. Install Dependencies
+# ---------------------------------------------------------------------------
+
+info "Installing dependencies..."
+echo ""
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR"
+
+npm install 2>&1 | while IFS= read -r line; do
+ echo " $line"
+done
+
+echo ""
+success "Dependencies installed."
+
+separator
+
+# ---------------------------------------------------------------------------
+# 4. Create .dev.vars (local secrets for development)
+# ---------------------------------------------------------------------------
+
+info "Creating .dev.vars for local development secrets..."
+
+cat > .dev.vars <<EOF
+# Makima Cloudflare Agent — Local Development Secrets
+# These are used by 'wrangler dev' and should NOT be committed to git.
+
+MAKIMA_SERVER_URL=${MAKIMA_SERVER_URL}
+MAKIMA_API_KEY=${MAKIMA_API_KEY}
+MAKIMA_AGENT_NAME=${MAKIMA_AGENT_NAME}
+EOF
+
+success "Created .dev.vars"
+echo ""
+
+# Ensure .dev.vars is gitignored
+if [ -f .gitignore ]; then
+ if ! grep -q ".dev.vars" .gitignore 2>/dev/null; then
+ echo ".dev.vars" >> .gitignore
+ success "Added .dev.vars to .gitignore"
+ fi
+else
+ echo ".dev.vars" > .gitignore
+ echo "node_modules/" >> .gitignore
+ echo ".wrangler/" >> .gitignore
+ success "Created .gitignore"
+fi
+
+separator
+
+# ---------------------------------------------------------------------------
+# 5. Wrangler Login Check
+# ---------------------------------------------------------------------------
+
+info "Checking Cloudflare authentication..."
+echo ""
+
+if npx wrangler whoami 2>/dev/null | grep -q "You are logged in"; then
+ success "Already logged in to Cloudflare."
+else
+ warn "Not logged in to Cloudflare."
+ if confirm "Would you like to log in now?"; then
+ npx wrangler login
+ success "Logged in to Cloudflare."
+ else
+ warn "Skipping login. You'll need to run 'npx wrangler login' before deploying."
+ fi
+fi
+
+separator
+
+# ---------------------------------------------------------------------------
+# 6. Set Production Secrets
+# ---------------------------------------------------------------------------
+
+if confirm "Would you like to set production secrets now? (Required before first deploy)"; then
+ echo ""
+ info "Setting secrets via Wrangler..."
+ echo ""
+
+ echo "$MAKIMA_SERVER_URL" | npx wrangler secret put MAKIMA_SERVER_URL 2>&1 | while IFS= read -r line; do
+ echo " $line"
+ done
+ success "Set MAKIMA_SERVER_URL"
+
+ echo "$MAKIMA_API_KEY" | npx wrangler secret put MAKIMA_API_KEY 2>&1 | while IFS= read -r line; do
+ echo " $line"
+ done
+ success "Set MAKIMA_API_KEY"
+
+ if [ -n "$MAKIMA_AGENT_NAME" ]; then
+ echo "$MAKIMA_AGENT_NAME" | npx wrangler secret put MAKIMA_AGENT_NAME 2>&1 | while IFS= read -r line; do
+ echo " $line"
+ done
+ success "Set MAKIMA_AGENT_NAME"
+ fi
+fi
+
+separator
+
+# ---------------------------------------------------------------------------
+# 7. Deploy or Dev
+# ---------------------------------------------------------------------------
+
+echo -e "${BOLD}Setup complete!${NC} What would you like to do next?"
+echo ""
+echo " 1) Deploy to Cloudflare Workers (production)"
+echo " 2) Start local development server"
+echo " 3) Exit (deploy later)"
+echo ""
+
+prompt NEXT_ACTION "Choose an option" "3"
+
+case "$NEXT_ACTION" in
+ 1)
+ echo ""
+ info "Deploying to Cloudflare Workers..."
+ echo ""
+ npx wrangler deploy 2>&1 | while IFS= read -r line; do
+ echo " $line"
+ done
+ echo ""
+ success "Deployed! Your Makima edge agent is live."
+ ;;
+ 2)
+ echo ""
+ info "Starting local development server..."
+ echo ""
+ info "Press Ctrl+C to stop."
+ echo ""
+ npx wrangler dev
+ ;;
+ 3|*)
+ ;;
+esac
+
+# ---------------------------------------------------------------------------
+# 8. Next Steps
+# ---------------------------------------------------------------------------
+
+separator
+
+echo -e "${BOLD}${GREEN}🎉 Makima Cloudflare Agent is ready!${NC}"
+echo ""
+echo -e " ${BOLD}Useful commands:${NC}"
+echo ""
+echo -e " ${CYAN}npm run dev${NC} Start local development server"
+echo -e " ${CYAN}npm run deploy${NC} Deploy to Cloudflare Workers"
+echo -e " ${CYAN}npm run tail${NC} Stream production logs"
+echo ""
+echo -e " ${BOLD}API Endpoints (after deploy):${NC}"
+echo ""
+echo -e " ${CYAN}GET /status${NC} Agent connection status"
+echo -e " ${CYAN}GET /health${NC} Health check"
+echo -e " ${CYAN}GET /tasks${NC} Task dispatch history"
+echo -e " ${CYAN}GET /logs${NC} Connection event logs"
+echo -e " ${CYAN}POST /reconnect${NC} Force upstream reconnection"
+echo -e " ${CYAN}WS /ws/daemon${NC} Downstream daemon WebSocket"
+echo ""
+echo -e " ${BOLD}Architecture:${NC}"
+echo ""
+echo -e " Makima Server ←WebSocket→ ${CYAN}Edge Agent${NC} ←WebSocket→ Native Daemons"
+echo ""
+echo -e " ${BOLD}Documentation:${NC}"
+echo -e " See ${CYAN}README.md${NC} for full setup and architecture details."
+echo ""
diff --git a/makima/cloudflare-agent/src/agent.ts b/makima/cloudflare-agent/src/agent.ts
new file mode 100644
index 0000000..a5a7823
--- /dev/null
+++ b/makima/cloudflare-agent/src/agent.ts
@@ -0,0 +1,773 @@
+/**
+ * MakimaAgent — Cloudflare Durable Object Agent
+ *
+ * Acts as an edge relay between the Makima server and downstream daemon
+ * instances. It does NOT execute tasks itself (that requires native process
+ * spawning, git, filesystem access). Instead it:
+ *
+ * 1. Maintains a persistent WebSocket to the Makima server (upstream).
+ * 2. Accepts WebSocket connections from native daemons (downstream).
+ * 3. Relays messages bidirectionally between upstream and downstream.
+ * 4. Tracks task dispatch history and daemon health in SQLite.
+ * 5. Exposes an HTTP API for status and management.
+ */
+
+import { Agent } from "agents";
+import type { Connection, ConnectionContext, WSMessage } from "agents";
+import type {
+ Env,
+ AgentState,
+ DaemonMessage,
+ DaemonCommand,
+ TaskRecord,
+ DownstreamDaemon,
+ StatusResponse,
+ TaskHistoryResponse,
+} from "./types";
+
+// ---------------------------------------------------------------------------
+// Constants
+// ---------------------------------------------------------------------------
+
+const HEARTBEAT_INTERVAL_MS = 30_000; // 30s
+const RECONNECT_BASE_MS = 1_000; // 1s initial backoff
+const RECONNECT_MAX_MS = 60_000; // 60s max backoff
+const MAX_RECONNECT_ATTEMPTS = 20;
+const TASK_HISTORY_MAX = 500;
+
+// ---------------------------------------------------------------------------
+// MakimaAgent Durable Object
+// ---------------------------------------------------------------------------
+
+export class MakimaAgent extends Agent<Env, AgentState> {
+ // -- Upstream (Makima server) connection --
+ private upstreamWs: WebSocket | null = null;
+ private heartbeatInterval: ReturnType<typeof setInterval> | null = null;
+ private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
+
+ // -- Downstream (native daemon) connections --
+ private downstreamDaemons: Map<string, DownstreamDaemon> = new Map();
+
+ // -- Bookkeeping --
+ private totalTasksProcessed = 0;
+
+ // -- Default state for new agent instances --
+ initialState: AgentState = {
+ connected: false,
+ lastConnectedAt: null,
+ lastHeartbeatAt: null,
+ reconnectAttempts: 0,
+ daemonId: null,
+ activeTasks: [],
+ };
+
+ // =========================================================================
+ // Lifecycle
+ // =========================================================================
+
+ /**
+ * Called once when the Durable Object is first instantiated.
+ * Sets up the SQLite schema and establishes the upstream connection.
+ */
+ async onStart(): Promise<void> {
+ this.initDatabase();
+ this.loadStats();
+
+ // Attempt upstream connection
+ await this.connectUpstream();
+ }
+
+ // =========================================================================
+ // SQLite Schema
+ // =========================================================================
+
+ private initDatabase(): void {
+ // Use the raw storage sql for DDL since tagged template doesn't support multi-statement
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS task_history (
+ task_id TEXT PRIMARY KEY,
+ task_name TEXT NOT NULL,
+ status TEXT NOT NULL DEFAULT 'pending',
+ dispatched_to TEXT,
+ received_at TEXT NOT NULL,
+ dispatched_at TEXT,
+ completed_at TEXT,
+ error TEXT
+ )
+ `);
+ this.ctx.storage.sql.exec(`
+ CREATE TABLE IF NOT EXISTS connection_log (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ event TEXT NOT NULL,
+ timestamp TEXT NOT NULL,
+ details TEXT
+ )
+ `);
+ this.ctx.storage.sql.exec(`
+ CREATE INDEX IF NOT EXISTS idx_task_history_status
+ ON task_history(status)
+ `);
+ this.ctx.storage.sql.exec(`
+ CREATE INDEX IF NOT EXISTS idx_task_history_received
+ ON task_history(received_at DESC)
+ `);
+ }
+
+ private loadStats(): void {
+ const rows = this.sql<{ cnt: number }>`SELECT COUNT(*) as cnt FROM task_history`;
+ this.totalTasksProcessed = rows[0]?.cnt ?? 0;
+ }
+
+ // =========================================================================
+ // Upstream (Makima Server) Connection
+ // =========================================================================
+
+ private async connectUpstream(): Promise<void> {
+ const serverUrl = this.env.MAKIMA_SERVER_URL;
+ const apiKey = this.env.MAKIMA_API_KEY;
+
+ if (!serverUrl || !apiKey) {
+ this.logConnection(
+ "error",
+ "Missing MAKIMA_SERVER_URL or MAKIMA_API_KEY"
+ );
+ return;
+ }
+
+ // Normalize URL: ensure it ends with /ws/daemon
+ let wsUrl = serverUrl;
+ if (!wsUrl.endsWith("/ws/daemon")) {
+ wsUrl = wsUrl.replace(/\/$/, "") + "/ws/daemon";
+ }
+
+ try {
+ // Cloudflare Workers use the standard WebSocket API via fetch upgrade
+ const resp = await fetch(wsUrl, {
+ headers: {
+ Upgrade: "websocket",
+ },
+ });
+
+ const ws = resp.webSocket;
+ if (!ws) {
+ throw new Error(`WebSocket upgrade failed (status ${resp.status})`);
+ }
+
+ ws.accept();
+ this.upstreamWs = ws;
+
+ // Authenticate immediately
+ const authMsg: DaemonMessage = {
+ type: "authenticate",
+ apiKey,
+ machineId: `cf-agent-${this.name}`,
+ hostname: `cloudflare-edge-${this.name}`,
+ maxConcurrentTasks: 0, // Relay only — does not execute tasks directly
+ };
+ ws.send(JSON.stringify(authMsg));
+
+ // Wire up event handlers
+ ws.addEventListener("message", (event) => {
+ this.handleUpstreamMessage(event);
+ });
+
+ ws.addEventListener("close", (event) => {
+ this.handleUpstreamClose(event.code, event.reason);
+ });
+
+ ws.addEventListener("error", () => {
+ this.logConnection("error", "Upstream WebSocket error");
+ this.handleUpstreamClose(1006, "WebSocket error");
+ });
+
+ // Start heartbeat
+ this.startHeartbeat();
+
+ this.logConnection("connected", `Connected to ${wsUrl}`);
+ } catch (err) {
+ const msg = err instanceof Error ? err.message : String(err);
+ this.logConnection("error", `Failed to connect: ${msg}`);
+ this.scheduleReconnect();
+ }
+ }
+
+ private handleUpstreamMessage(event: MessageEvent): void {
+ let command: DaemonCommand;
+ try {
+ command = JSON.parse(
+ typeof event.data === "string" ? event.data : ""
+ ) as DaemonCommand;
+ } catch {
+ this.logConnection("error", "Failed to parse upstream message");
+ return;
+ }
+
+ switch (command.type) {
+ case "authenticated":
+ this.onUpstreamAuthenticated(command.daemonId);
+ break;
+
+ case "spawnTask":
+ this.onSpawnTask(command);
+ break;
+
+ case "pauseTask":
+ case "resumeTask":
+ case "interruptTask":
+ case "sendMessage":
+ // Forward task control commands to the appropriate downstream daemon
+ this.forwardToDownstream(command);
+ break;
+
+ default:
+ // Forward any unrecognized commands to all downstream daemons
+ this.broadcastToDownstream(JSON.stringify(command));
+ break;
+ }
+ }
+
+ private onUpstreamAuthenticated(daemonId: string): void {
+ this.setState({
+ ...this.state,
+ connected: true,
+ lastConnectedAt: new Date().toISOString(),
+ reconnectAttempts: 0,
+ daemonId,
+ });
+
+ this.logConnection(
+ "authenticated",
+ `Authenticated as daemon ${daemonId}`
+ );
+ }
+
+ private onSpawnTask(command: Extract<DaemonCommand, { type: "spawnTask" }>): void {
+ // Record in task history
+ const now = new Date().toISOString();
+ const taskId = command.taskId;
+ const taskName = command.taskName;
+ this.sql`INSERT OR REPLACE INTO task_history (task_id, task_name, status, received_at)
+ VALUES (${taskId}, ${taskName}, 'pending', ${now})`;
+ this.totalTasksProcessed++;
+
+ // Find the best downstream daemon to dispatch to
+ const daemon = this.selectDownstreamDaemon();
+ if (daemon) {
+ this.dispatchTaskToDownstream(daemon, command);
+ } else {
+ // No downstream daemons connected — store as pending
+ this.logConnection(
+ "warning",
+ `No downstream daemons available for task ${command.taskId}`
+ );
+ // Notify upstream that we can't handle this task right now
+ this.sendUpstream({
+ type: "taskStatusChange",
+ taskId: command.taskId,
+ oldStatus: "pending",
+ newStatus: "pending",
+ });
+ }
+ }
+
+ private dispatchTaskToDownstream(
+ daemon: DownstreamDaemon,
+ command: Extract<DaemonCommand, { type: "spawnTask" }>
+ ): void {
+ try {
+ daemon.ws.send(JSON.stringify(command));
+ daemon.activeTasks.add(command.taskId);
+
+ const now = new Date().toISOString();
+ const did = daemon.id;
+ const tid = command.taskId;
+ this.sql`UPDATE task_history
+ SET status = 'dispatched', dispatched_to = ${did}, dispatched_at = ${now}
+ WHERE task_id = ${tid}`;
+
+ this.setState({
+ ...this.state,
+ activeTasks: [...this.state.activeTasks, command.taskId],
+ });
+
+ this.logConnection(
+ "dispatch",
+ `Task ${command.taskId} dispatched to daemon ${daemon.hostname}`
+ );
+ } catch (err) {
+ this.logConnection(
+ "error",
+ `Failed to dispatch task ${command.taskId}: ${err}`
+ );
+ }
+ }
+
+ private selectDownstreamDaemon(): DownstreamDaemon | null {
+ let best: DownstreamDaemon | null = null;
+ let leastTasks = Infinity;
+
+ for (const daemon of this.downstreamDaemons.values()) {
+ if (daemon.activeTasks.size < daemon.maxConcurrentTasks) {
+ if (daemon.activeTasks.size < leastTasks) {
+ leastTasks = daemon.activeTasks.size;
+ best = daemon;
+ }
+ }
+ }
+ return best;
+ }
+
+ private forwardToDownstream(command: DaemonCommand): void {
+ // Find which downstream daemon has this task
+ const taskId = "taskId" in command ? (command as { taskId: string }).taskId : null;
+ if (!taskId) return;
+
+ for (const daemon of this.downstreamDaemons.values()) {
+ if (daemon.activeTasks.has(taskId)) {
+ daemon.ws.send(JSON.stringify(command));
+ return;
+ }
+ }
+
+ this.logConnection(
+ "warning",
+ `No downstream daemon found for task ${taskId}`
+ );
+ }
+
+ private broadcastToDownstream(message: string): void {
+ for (const daemon of this.downstreamDaemons.values()) {
+ try {
+ daemon.ws.send(message);
+ } catch {
+ // Ignore send failures on individual daemons
+ }
+ }
+ }
+
+ // =========================================================================
+ // Downstream (Native Daemon) WebSocket Handling
+ // =========================================================================
+
+ /**
+ * Called by the Agents SDK when a client connects via WebSocket.
+ * In our architecture, "clients" are native Makima daemons that connect
+ * to this edge relay to receive task dispatches.
+ */
+ onConnect(connection: Connection, ctx: ConnectionContext): void {
+ const daemonId = crypto.randomUUID();
+ const daemon: DownstreamDaemon = {
+ id: daemonId,
+ ws: connection as unknown as WebSocket,
+ hostname: "unknown",
+ maxConcurrentTasks: 10,
+ activeTasks: new Set(),
+ lastHeartbeat: new Date().toISOString(),
+ };
+ this.downstreamDaemons.set(daemonId, daemon);
+
+ this.logConnection(
+ "downstream_connect",
+ `Downstream daemon connected: ${daemonId}`
+ );
+ }
+
+ /**
+ * Called when a downstream daemon sends a message.
+ */
+ onMessage(connection: Connection, message: WSMessage): void {
+ const data =
+ typeof message === "string"
+ ? message
+ : message instanceof ArrayBuffer
+ ? new TextDecoder().decode(message)
+ : "";
+ let parsed: DaemonMessage;
+ try {
+ parsed = JSON.parse(data) as DaemonMessage;
+ } catch {
+ return;
+ }
+
+ // Find which downstream daemon sent this
+ const daemon = this.findDaemonByConnection(connection);
+
+ // Handle authenticate from downstream daemons
+ if (parsed.type === "authenticate" && daemon) {
+ daemon.hostname = (parsed as Extract<DaemonMessage, { type: "authenticate" }>).hostname;
+ daemon.maxConcurrentTasks = (parsed as Extract<DaemonMessage, { type: "authenticate" }>).maxConcurrentTasks;
+ // Send back a synthetic authentication confirmation
+ connection.send(
+ JSON.stringify({
+ type: "authenticated",
+ daemonId: daemon.id,
+ })
+ );
+ return;
+ }
+
+ // Handle heartbeats from downstream
+ if (parsed.type === "heartbeat" && daemon) {
+ daemon.lastHeartbeat = new Date().toISOString();
+ return;
+ }
+
+ // Handle task completion from downstream
+ if (parsed.type === "taskComplete" && daemon) {
+ const tc = parsed as Extract<DaemonMessage, { type: "taskComplete" }>;
+ daemon.activeTasks.delete(tc.taskId);
+
+ const status = tc.success ? "completed" : "failed";
+ const now = new Date().toISOString();
+ const errMsg = tc.error ?? null;
+ const tid = tc.taskId;
+ this.sql`UPDATE task_history
+ SET status = ${status}, completed_at = ${now}, error = ${errMsg}
+ WHERE task_id = ${tid}`;
+
+ this.setState({
+ ...this.state,
+ activeTasks: this.state.activeTasks.filter((id) => id !== tc.taskId),
+ });
+ }
+
+ // Forward all daemon messages to the upstream server
+ this.sendUpstream(parsed);
+ }
+
+ /**
+ * Called when a downstream daemon disconnects.
+ */
+ onClose(connection: Connection, code: number, reason: string, wasClean: boolean): void {
+ const daemon = this.findDaemonByConnection(connection);
+ if (daemon) {
+ this.logConnection(
+ "downstream_disconnect",
+ `Downstream daemon ${daemon.hostname} disconnected: code=${code} reason=${reason} clean=${wasClean}`
+ );
+ this.downstreamDaemons.delete(daemon.id);
+ }
+ }
+
+ onError(connectionOrError: Connection | unknown, error?: unknown): void {
+ // Handle the overloaded signature: (connection, error) or (error)
+ if (error !== undefined) {
+ const connection = connectionOrError as Connection;
+ const daemon = this.findDaemonByConnection(connection);
+ if (daemon) {
+ this.logConnection(
+ "error",
+ `Downstream daemon ${daemon.hostname} error: ${error}`
+ );
+ }
+ } else {
+ this.logConnection("error", `Agent error: ${connectionOrError}`);
+ }
+ }
+
+ private findDaemonByConnection(connection: Connection): DownstreamDaemon | undefined {
+ for (const daemon of this.downstreamDaemons.values()) {
+ if (daemon.ws === (connection as unknown as WebSocket)) {
+ return daemon;
+ }
+ }
+ return undefined;
+ }
+
+ // =========================================================================
+ // Heartbeat
+ // =========================================================================
+
+ private startHeartbeat(): void {
+ this.stopHeartbeat();
+ this.heartbeatInterval = setInterval(() => {
+ this.sendHeartbeat();
+ }, HEARTBEAT_INTERVAL_MS);
+ }
+
+ private stopHeartbeat(): void {
+ if (this.heartbeatInterval) {
+ clearInterval(this.heartbeatInterval);
+ this.heartbeatInterval = null;
+ }
+ }
+
+ private sendHeartbeat(): void {
+ const activeTasks = this.state.activeTasks;
+ this.sendUpstream({
+ type: "heartbeat",
+ activeTasks,
+ });
+ this.setState({
+ ...this.state,
+ lastHeartbeatAt: new Date().toISOString(),
+ });
+ }
+
+ // =========================================================================
+ // Upstream Reconnection
+ // =========================================================================
+
+ private handleUpstreamClose(code: number, reason: string): void {
+ this.upstreamWs = null;
+ this.stopHeartbeat();
+
+ this.setState({
+ ...this.state,
+ connected: false,
+ });
+
+ this.logConnection(
+ "disconnected",
+ `Upstream disconnected: code=${code} reason=${reason}`
+ );
+
+ this.scheduleReconnect();
+ }
+
+ private scheduleReconnect(): void {
+ if (this.reconnectTimeout) {
+ clearTimeout(this.reconnectTimeout);
+ }
+
+ const attempts = this.state.reconnectAttempts;
+ if (attempts >= MAX_RECONNECT_ATTEMPTS) {
+ this.logConnection(
+ "error",
+ `Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached — giving up`
+ );
+ return;
+ }
+
+ // Exponential backoff with jitter
+ const delay = Math.min(
+ RECONNECT_BASE_MS * Math.pow(2, attempts) +
+ Math.random() * RECONNECT_BASE_MS,
+ RECONNECT_MAX_MS
+ );
+
+ this.setState({
+ ...this.state,
+ reconnectAttempts: attempts + 1,
+ });
+
+ this.logConnection(
+ "reconnecting",
+ `Scheduling reconnect in ${Math.round(delay)}ms (attempt ${attempts + 1})`
+ );
+
+ this.reconnectTimeout = setTimeout(() => {
+ this.connectUpstream();
+ }, delay);
+ }
+
+ // =========================================================================
+ // Upstream Send Helper
+ // =========================================================================
+
+ private sendUpstream(message: DaemonMessage): void {
+ if (!this.upstreamWs) return;
+ try {
+ this.upstreamWs.send(JSON.stringify(message));
+ } catch {
+ this.logConnection("error", "Failed to send upstream message");
+ }
+ }
+
+ // =========================================================================
+ // Connection Logging
+ // =========================================================================
+
+ private logConnection(event: string, details: string): void {
+ try {
+ const now = new Date().toISOString();
+ this.sql`INSERT INTO connection_log (event, timestamp, details)
+ VALUES (${event}, ${now}, ${details})`;
+
+ // Prune old logs (keep last 200)
+ this.ctx.storage.sql.exec(`
+ DELETE FROM connection_log
+ WHERE id NOT IN (
+ SELECT id FROM connection_log ORDER BY id DESC LIMIT 200
+ )
+ `);
+ } catch {
+ // Swallow — logging should never break the agent
+ }
+ }
+
+ // =========================================================================
+ // RPC Methods (callable from Agent SDK clients)
+ // =========================================================================
+
+ /** Returns the current status of the edge relay agent. */
+ getStatus(): StatusResponse {
+ const agentName = this.env.MAKIMA_AGENT_NAME ?? `makima-edge-${this.name}`;
+
+ return {
+ status: this.state.connected
+ ? this.downstreamDaemons.size > 0
+ ? "ok"
+ : "degraded"
+ : "disconnected",
+ agentName,
+ upstreamConnected: this.state.connected,
+ daemonId: this.state.daemonId,
+ lastHeartbeat: this.state.lastHeartbeatAt,
+ connectedDaemons: this.downstreamDaemons.size,
+ activeTasks: this.state.activeTasks.length,
+ totalTasksProcessed: this.totalTasksProcessed,
+ };
+ }
+
+ /** Returns paginated task history from the SQLite store. */
+ getTaskHistory(limit = 50, offset = 0): TaskHistoryResponse {
+ const clampedLimit = Math.min(Math.max(1, limit), TASK_HISTORY_MAX);
+
+ const rows = this.sql<TaskRecord>`
+ SELECT task_id as taskId, task_name as taskName, status,
+ dispatched_to as dispatchedTo, received_at as receivedAt,
+ dispatched_at as dispatchedAt, completed_at as completedAt, error
+ FROM task_history
+ ORDER BY received_at DESC
+ LIMIT ${clampedLimit} OFFSET ${offset}
+ `;
+
+ const totalRows = this.sql<{ cnt: number }>`SELECT COUNT(*) as cnt FROM task_history`;
+
+ return {
+ tasks: rows,
+ total: totalRows[0]?.cnt ?? 0,
+ limit: clampedLimit,
+ offset,
+ };
+ }
+
+ /** Force a reconnection to the upstream Makima server. */
+ reconnect(): { success: boolean; message: string } {
+ // Close existing connection if any
+ if (this.upstreamWs) {
+ try {
+ this.upstreamWs.close(1000, "Manual reconnect requested");
+ } catch {
+ // Ignore
+ }
+ this.upstreamWs = null;
+ }
+
+ this.stopHeartbeat();
+
+ // Reset reconnect counter
+ this.setState({
+ ...this.state,
+ reconnectAttempts: 0,
+ connected: false,
+ });
+
+ // Start fresh connection
+ this.connectUpstream();
+
+ return {
+ success: true,
+ message: "Reconnection initiated",
+ };
+ }
+
+ // =========================================================================
+ // HTTP Request Handler
+ // =========================================================================
+
+ /**
+ * Handles HTTP requests routed to this Durable Object.
+ * Provides a REST API for management and status.
+ */
+ async onRequest(request: Request): Promise<Response> {
+ const url = new URL(request.url);
+ const path = url.pathname;
+
+ // CORS headers for browser-based clients
+ const corsHeaders: Record<string, string> = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
+ };
+
+ if (request.method === "OPTIONS") {
+ return new Response(null, { status: 204, headers: corsHeaders });
+ }
+
+ const jsonHeaders: Record<string, string> = {
+ "Content-Type": "application/json",
+ ...corsHeaders,
+ };
+
+ try {
+ // GET /status — Agent status
+ if (path === "/status" || path === "/") {
+ return new Response(JSON.stringify(this.getStatus()), {
+ headers: jsonHeaders,
+ });
+ }
+
+ // GET /tasks — Task history
+ if (path === "/tasks" && request.method === "GET") {
+ const limit = parseInt(url.searchParams.get("limit") ?? "50", 10);
+ const offset = parseInt(url.searchParams.get("offset") ?? "0", 10);
+ return new Response(
+ JSON.stringify(this.getTaskHistory(limit, offset)),
+ { headers: jsonHeaders }
+ );
+ }
+
+ // POST /reconnect — Force reconnection
+ if (path === "/reconnect" && request.method === "POST") {
+ return new Response(JSON.stringify(this.reconnect()), {
+ headers: jsonHeaders,
+ });
+ }
+
+ // GET /logs — Connection logs
+ if (path === "/logs" && request.method === "GET") {
+ const logLimit = Math.min(
+ parseInt(url.searchParams.get("limit") ?? "50", 10),
+ 200
+ );
+ const rows = this.sql<{
+ id: number;
+ event: string;
+ timestamp: string;
+ details: string | null;
+ }>`SELECT id, event, timestamp, details
+ FROM connection_log
+ ORDER BY id DESC
+ LIMIT ${logLimit}`;
+ return new Response(JSON.stringify({ logs: rows }), {
+ headers: jsonHeaders,
+ });
+ }
+
+ // GET /health — Simple health check
+ if (path === "/health") {
+ return new Response(
+ JSON.stringify({
+ healthy: true,
+ upstreamConnected: this.state.connected,
+ }),
+ { headers: jsonHeaders }
+ );
+ }
+
+ return new Response(JSON.stringify({ error: "Not found" }), {
+ status: 404,
+ headers: jsonHeaders,
+ });
+ } catch (err) {
+ return new Response(
+ JSON.stringify({
+ error: err instanceof Error ? err.message : "Internal error",
+ }),
+ { status: 500, headers: jsonHeaders }
+ );
+ }
+ }
+}
diff --git a/makima/cloudflare-agent/src/index.ts b/makima/cloudflare-agent/src/index.ts
new file mode 100644
index 0000000..0b64af4
--- /dev/null
+++ b/makima/cloudflare-agent/src/index.ts
@@ -0,0 +1,47 @@
+/**
+ * Makima Cloudflare Agent — Worker Entry Point
+ *
+ * Routes incoming requests to the MakimaAgent Durable Object which manages
+ * the upstream WebSocket connection to the Makima server and relays tasks
+ * to downstream native daemon instances.
+ *
+ * Routes:
+ * GET / → Agent status (JSON)
+ * GET /status → Agent status (JSON)
+ * GET /health → Health check
+ * GET /tasks → Task history (paginated)
+ * GET /logs → Connection logs
+ * POST /reconnect → Force upstream reconnection
+ * * /ws → WebSocket upgrade for downstream daemons
+ * * /agent/* → Forwarded to Agent's onRequest handler
+ */
+
+import { MakimaAgent } from "./agent";
+import type { Env } from "./types";
+
+export { MakimaAgent };
+
+export default {
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
+ const url = new URL(request.url);
+
+ // Use a singleton Durable Object — all traffic routes to one agent
+ // instance identified by a fixed name. This ensures a single persistent
+ // upstream WebSocket to the Makima server.
+ const agentId = env.MAKIMA_AGENT.idFromName("makima-relay-primary");
+ const agent = env.MAKIMA_AGENT.get(agentId);
+
+ // WebSocket upgrade path — downstream daemons connect here
+ if (url.pathname === "/ws" || url.pathname === "/ws/daemon") {
+ const upgradeHeader = request.headers.get("Upgrade");
+ if (upgradeHeader !== "websocket") {
+ return new Response("Expected WebSocket upgrade", { status: 426 });
+ }
+ // Forward the WebSocket upgrade to the Durable Object
+ return agent.fetch(request);
+ }
+
+ // All other routes are forwarded to the Durable Object's onRequest handler
+ return agent.fetch(request);
+ },
+};
diff --git a/makima/cloudflare-agent/src/types.ts b/makima/cloudflare-agent/src/types.ts
new file mode 100644
index 0000000..d2ab20a
--- /dev/null
+++ b/makima/cloudflare-agent/src/types.ts
@@ -0,0 +1,148 @@
+/**
+ * Type definitions for the Makima Cloudflare Agent.
+ *
+ * These types mirror the daemon-server WebSocket protocol defined in
+ * makima/src/daemon/ws/protocol.rs. Only the subset relevant to the
+ * edge relay is included here.
+ */
+
+// ---------------------------------------------------------------------------
+// Worker Environment
+// ---------------------------------------------------------------------------
+
+export interface Env {
+ /** Durable Object binding for MakimaAgent instances. */
+ MAKIMA_AGENT: DurableObjectNamespace;
+ /** Makima server WebSocket URL (e.g. wss://api.makima.jp). */
+ MAKIMA_SERVER_URL: string;
+ /** API key for authenticating with the Makima server. */
+ MAKIMA_API_KEY: string;
+ /** Optional: human-readable name for this edge agent. */
+ MAKIMA_AGENT_NAME?: string;
+}
+
+// ---------------------------------------------------------------------------
+// Agent State (persisted in Durable Object SQLite)
+// ---------------------------------------------------------------------------
+
+export interface AgentState {
+ /** Whether the upstream WebSocket is currently connected. */
+ connected: boolean;
+ /** ISO-8601 timestamp of last successful connection. */
+ lastConnectedAt: string | null;
+ /** ISO-8601 timestamp of last heartbeat sent. */
+ lastHeartbeatAt: string | null;
+ /** Number of consecutive reconnect failures. */
+ reconnectAttempts: number;
+ /** Daemon ID assigned by the server after authentication. */
+ daemonId: string | null;
+ /** Active task IDs being tracked through this relay. */
+ activeTasks: string[];
+}
+
+export const DEFAULT_AGENT_STATE: AgentState = {
+ connected: false,
+ lastConnectedAt: null,
+ lastHeartbeatAt: null,
+ reconnectAttempts: 0,
+ daemonId: null,
+ activeTasks: [],
+};
+
+// ---------------------------------------------------------------------------
+// Daemon → Server Messages (subset used by the relay)
+// ---------------------------------------------------------------------------
+
+export type DaemonMessage =
+ | { type: "authenticate"; apiKey: string; machineId: string; hostname: string; maxConcurrentTasks: number }
+ | { type: "heartbeat"; activeTasks: string[] }
+ | { type: "taskOutput"; taskId: string; output: string; isPartial: boolean }
+ | { type: "taskStatusChange"; taskId: string; oldStatus: string; newStatus: string }
+ | { type: "taskProgress"; taskId: string; summary: string }
+ | { type: "taskComplete"; taskId: string; success: boolean; error?: string };
+
+// ---------------------------------------------------------------------------
+// Server → Daemon Commands (subset relevant to the relay)
+// ---------------------------------------------------------------------------
+
+export type DaemonCommand =
+ | { type: "authenticated"; daemonId: string }
+ | { type: "spawnTask"; taskId: string; taskName: string; plan: string; repoUrl?: string; baseBranch?: string; targetBranch?: string; contractId?: string; depth: number; isOrchestrator: boolean }
+ | { type: "pauseTask"; taskId: string }
+ | { type: "resumeTask"; taskId: string }
+ | { type: "interruptTask"; taskId: string; graceful: boolean }
+ | { type: "sendMessage"; taskId: string; message: string };
+
+// ---------------------------------------------------------------------------
+// Task History Record (stored in SQLite)
+// ---------------------------------------------------------------------------
+
+export interface TaskRecord {
+ /** UUID of the task. */
+ taskId: string;
+ /** Human-readable task name. */
+ taskName: string;
+ /** Current status: pending | dispatched | completed | failed. */
+ status: string;
+ /** Which downstream daemon received this task. */
+ dispatchedTo: string | null;
+ /** ISO-8601 timestamp when the task was received. */
+ receivedAt: string;
+ /** ISO-8601 timestamp when the task was dispatched downstream. */
+ dispatchedAt: string | null;
+ /** ISO-8601 timestamp when the task completed. */
+ completedAt: string | null;
+ /** Error message if failed. */
+ error: string | null;
+}
+
+// ---------------------------------------------------------------------------
+// Connected Downstream Daemon
+// ---------------------------------------------------------------------------
+
+export interface DownstreamDaemon {
+ /** Unique ID for this downstream connection. */
+ id: string;
+ /** WebSocket connection to this daemon. */
+ ws: WebSocket;
+ /** Human-readable hostname. */
+ hostname: string;
+ /** Maximum concurrent tasks this daemon supports. */
+ maxConcurrentTasks: number;
+ /** Currently active task IDs on this daemon. */
+ activeTasks: Set<string>;
+ /** ISO-8601 timestamp of last heartbeat received. */
+ lastHeartbeat: string;
+}
+
+// ---------------------------------------------------------------------------
+// HTTP API Response Types
+// ---------------------------------------------------------------------------
+
+export interface StatusResponse {
+ status: "ok" | "degraded" | "disconnected";
+ agentName: string;
+ upstreamConnected: boolean;
+ daemonId: string | null;
+ lastHeartbeat: string | null;
+ connectedDaemons: number;
+ activeTasks: number;
+ totalTasksProcessed: number;
+}
+
+export interface TaskHistoryResponse {
+ tasks: TaskRecord[];
+ total: number;
+ limit: number;
+ offset: number;
+}
+
+// ---------------------------------------------------------------------------
+// RPC method types for the Agent
+// ---------------------------------------------------------------------------
+
+export interface AgentRPC {
+ getStatus: () => StatusResponse;
+ getTaskHistory: (limit?: number, offset?: number) => TaskHistoryResponse;
+ reconnect: () => { success: boolean; message: string };
+}
diff --git a/makima/cloudflare-agent/tsconfig.json b/makima/cloudflare-agent/tsconfig.json
new file mode 100644
index 0000000..ed1dd65
--- /dev/null
+++ b/makima/cloudflare-agent/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "lib": ["ES2022"],
+ "types": ["@cloudflare/workers-types"],
+ "strict": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/makima/cloudflare-agent/wrangler.toml b/makima/cloudflare-agent/wrangler.toml
new file mode 100644
index 0000000..11f5c66
--- /dev/null
+++ b/makima/cloudflare-agent/wrangler.toml
@@ -0,0 +1,20 @@
+name = "makima-agent"
+main = "src/index.ts"
+compatibility_date = "2024-12-01"
+compatibility_flags = ["nodejs_compat"]
+
+[observability]
+enabled = true
+
+[vars]
+# Non-secret configuration — override via .dev.vars for local dev
+# MAKIMA_SERVER_URL = "wss://api.makima.jp"
+
+[durable_objects]
+bindings = [
+ { name = "MAKIMA_AGENT", class_name = "MakimaAgent" }
+]
+
+[[migrations]]
+tag = "v1"
+new_sqlite_classes = ["MakimaAgent"]
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx
index 9556458..1bd0891 100644
--- a/makima/frontend/src/components/NavStrip.tsx
+++ b/makima/frontend/src/components/NavStrip.tsx
@@ -17,6 +17,7 @@ const NAV_LINKS: NavLink[] = [
{ label: "Mesh", href: "/mesh", requiresAuth: true },
{ label: "Daemons", href: "/daemons", requiresAuth: true },
{ label: "History", href: "/history", requiresAuth: true },
+ { label: "Daemon", href: "/daemon", requiresAuth: true },
];
export function NavStrip() {
diff --git a/makima/frontend/src/hooks/useMultiTaskSubscription.ts b/makima/frontend/src/hooks/useMultiTaskSubscription.ts
index 41489c7..b229e90 100644
--- a/makima/frontend/src/hooks/useMultiTaskSubscription.ts
+++ b/makima/frontend/src/hooks/useMultiTaskSubscription.ts
@@ -31,8 +31,6 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption
const backfilledTasksRef = useRef<Set<string>>(new Set());
const taskMapRef = useRef(taskMap);
const enabledRef = useRef(enabled);
- /** Track which task IDs have already been backfilled to avoid re-fetching */
- const backfilledTasksRef = useRef<Set<string>>(new Set());
// Keep refs in sync
useEffect(() => {
@@ -43,88 +41,6 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption
enabledRef.current = enabled;
}, [enabled]);
- /** Max number of historical events to backfill per task */
- const MAX_BACKFILL_PER_TASK = 200;
-
- /**
- * Convert a TaskEvent (from the REST API) into a MultiTaskOutputEntry.
- * Only converts events with event_type === 'output'.
- */
- const convertTaskEventToEntry = useCallback(
- (event: TaskEvent): MultiTaskOutputEntry | null => {
- if (event.eventType !== "output") return null;
- const data = event.eventData;
- if (!data) return null;
-
- return {
- taskId: event.taskId,
- messageType: (data.messageType as string) || "system",
- content: (data.content as string) || "",
- toolName: data.toolName as string | undefined,
- toolInput: data.toolInput as Record<string, unknown> | undefined,
- isError: data.isError as boolean | undefined,
- costUsd: data.costUsd as number | undefined,
- durationMs: data.durationMs as number | undefined,
- isPartial: false,
- taskLabel:
- taskMapRef.current.get(event.taskId) || event.taskId,
- receivedAt: new Date(event.createdAt).getTime(),
- isBackfill: true,
- };
- },
- []
- );
-
- /**
- * Backfill historical log entries for a task from the REST API.
- * Only fetches once per task ID (tracked in backfilledTasksRef).
- */
- const backfillTask = useCallback(
- async (taskId: string) => {
- if (backfilledTasksRef.current.has(taskId)) return;
- backfilledTasksRef.current.add(taskId);
-
- try {
- const response = await listTaskEvents(taskId);
- const events = response.events;
-
- // The API returns events in DESC order; reverse to get chronological ASC
- const chronologicalEvents = [...events].reverse();
-
- // Filter to output events and convert, limiting to MAX_BACKFILL_PER_TASK
- const backfillEntries: MultiTaskOutputEntry[] = [];
- for (const event of chronologicalEvents) {
- const entry = convertTaskEventToEntry(event);
- if (entry) {
- backfillEntries.push(entry);
- if (backfillEntries.length >= MAX_BACKFILL_PER_TASK) break;
- }
- }
-
- if (backfillEntries.length === 0) return;
-
- // Prepend historical entries before any existing live entries for this task,
- // maintaining overall chronological order across all tasks
- setEntries((prev) => {
- // Merge backfill entries with existing entries, maintaining chronological order
- const merged = [...backfillEntries, ...prev];
- // Sort by receivedAt to ensure proper chronological ordering
- merged.sort((a, b) => a.receivedAt - b.receivedAt);
- // Trim to maxEntries
- if (merged.length > maxEntries) {
- return merged.slice(merged.length - maxEntries);
- }
- return merged;
- });
- } catch (e) {
- console.error(`Failed to backfill task events for ${taskId}:`, e);
- // Remove from backfilled set so it can be retried
- backfilledTasksRef.current.delete(taskId);
- }
- },
- [convertTaskEventToEntry, maxEntries]
- );
-
// Derive task IDs from the map, stabilized to avoid unnecessary effect triggers
const taskIdsKey = useMemo(() => Array.from(taskMap.keys()).sort().join(","), [taskMap]);
const taskIds = useMemo(() => Array.from(taskMap.keys()), [taskIdsKey]); // eslint-disable-line react-hooks/exhaustive-deps
diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx
index 32c05ba..a75d6a0 100644
--- a/makima/frontend/src/main.tsx
+++ b/makima/frontend/src/main.tsx
@@ -21,6 +21,7 @@ import SettingsPage from "./routes/settings";
import ContractFilePage from "./routes/contract-file";
import SpeakPage from "./routes/speak";
import DirectivesPage from "./routes/directives";
+import DaemonPage from "./routes/daemon";
createRoot(document.getElementById("root")!).render(
<StrictMode>
@@ -162,6 +163,14 @@ createRoot(document.getElementById("root")!).render(
}
/>
<Route
+ path="/daemon"
+ element={
+ <ProtectedRoute>
+ <DaemonPage />
+ </ProtectedRoute>
+ }
+ />
+ <Route
path="/speak"
element={
<ProtectedRoute>
diff --git a/makima/frontend/src/routes/daemon.tsx b/makima/frontend/src/routes/daemon.tsx
new file mode 100644
index 0000000..66154ad
--- /dev/null
+++ b/makima/frontend/src/routes/daemon.tsx
@@ -0,0 +1,746 @@
+import { useState, useEffect, useCallback } from "react";
+import { useNavigate } from "react-router";
+import { Masthead } from "../components/Masthead";
+import { useAuth } from "../contexts/AuthContext";
+import {
+ listDaemons,
+ restartDaemon,
+ type Daemon,
+} from "../lib/api";
+
+// =============================================================================
+// Types
+// =============================================================================
+
+interface GitHubAsset {
+ name: string;
+ browser_download_url: string;
+ size: number;
+}
+
+interface GitHubRelease {
+ tag_name: string;
+ name: string;
+ published_at: string;
+ html_url: string;
+ assets: GitHubAsset[];
+}
+
+interface PlatformDownload {
+ label: string;
+ arch: string;
+ pattern: string;
+ asset: GitHubAsset | null;
+ recommended: boolean;
+}
+
+// =============================================================================
+// Helpers
+// =============================================================================
+
+function detectPlatform(): string {
+ const ua = navigator.userAgent.toLowerCase();
+ const platform = navigator.platform?.toLowerCase() || "";
+
+ if (platform.includes("mac") || ua.includes("macintosh")) {
+ // Check for Apple Silicon
+ // navigator.platform is "MacIntel" even on ARM for some browsers,
+ // but we can check userAgent for hints or default to arm64 for modern Macs
+ if (
+ ua.includes("arm") ||
+ ua.includes("aarch64") ||
+ // Chrome 93+ on ARM Macs reports this
+ (typeof navigator !== "undefined" &&
+ "userAgentData" in navigator &&
+ // @ts-expect-error -- userAgentData may not be typed
+ navigator.userAgentData?.platform === "macOS")
+ ) {
+ return "macos-arm64";
+ }
+ return "macos-arm64"; // Default to ARM64 for modern Macs
+ }
+
+ if (platform.includes("linux") || ua.includes("linux")) {
+ return "linux-x86_64";
+ }
+
+ return "linux-x86_64"; // fallback
+}
+
+function formatBytes(bytes: number): string {
+ if (bytes === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
+}
+
+function formatDate(dateStr: string): string {
+ return new Date(dateStr).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ });
+}
+
+// =============================================================================
+// Sub-components
+// =============================================================================
+
+function SectionHeader({ children }: { children: React.ReactNode }) {
+ return (
+ <h2 className="text-[11px] font-mono uppercase tracking-wide text-[#8899aa] mb-3 pb-2 border-b border-[rgba(117,170,252,0.15)]">
+ {children}
+ </h2>
+ );
+}
+
+function ErrorAlert({ children }: { children: React.ReactNode }) {
+ return (
+ <div className="border border-red-700/50 bg-red-900/20 text-red-400 px-3 py-2 mb-4 font-mono text-xs">
+ {children}
+ </div>
+ );
+}
+
+function CodeBlock({ children }: { children: React.ReactNode }) {
+ return (
+ <code className="block bg-black/50 px-3 py-2 text-[11px] font-mono text-green-400 mb-2 overflow-x-auto">
+ {children}
+ </code>
+ );
+}
+
+function StepNumber({ n }: { n: number }) {
+ return (
+ <span className="inline-flex items-center justify-center w-5 h-5 border border-[rgba(117,170,252,0.35)] text-[10px] font-mono text-[#75aafc] mr-2 shrink-0">
+ {n}
+ </span>
+ );
+}
+
+// =============================================================================
+// Download Section
+// =============================================================================
+
+function DownloadSection() {
+ const [release, setRelease] = useState<GitHubRelease | null>(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+ const [userPlatform] = useState(detectPlatform);
+
+ useEffect(() => {
+ const fetchRelease = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const res = await fetch(
+ "https://api.github.com/repos/soryu-co/makima/releases/latest"
+ );
+ if (!res.ok) {
+ throw new Error(`GitHub API returned ${res.status}`);
+ }
+ const data: GitHubRelease = await res.json();
+ setRelease(data);
+ } catch (err) {
+ setError(
+ err instanceof Error ? err.message : "Failed to fetch release info"
+ );
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetchRelease();
+ }, []);
+
+ const platforms: PlatformDownload[] = [
+ {
+ label: "Linux x86_64",
+ arch: "linux-x86_64",
+ pattern: "linux-x86_64.tar.gz",
+ asset: null,
+ recommended: userPlatform === "linux-x86_64",
+ },
+ {
+ label: "macOS Intel (x86_64)",
+ arch: "macos-x86_64",
+ pattern: "macos-x86_64.tar.gz",
+ asset: null,
+ recommended: userPlatform === "macos-x86_64",
+ },
+ {
+ label: "macOS Apple Silicon (ARM64)",
+ arch: "macos-arm64",
+ pattern: "macos-arm64.tar.gz",
+ asset: null,
+ recommended: userPlatform === "macos-arm64",
+ },
+ ];
+
+ // Match assets to platforms
+ if (release) {
+ for (const p of platforms) {
+ p.asset =
+ release.assets.find((a) => a.name.includes(p.pattern)) || null;
+ }
+ }
+
+ // Sort recommended first
+ const sortedPlatforms = [...platforms].sort(
+ (a, b) => (b.recommended ? 1 : 0) - (a.recommended ? 1 : 0)
+ );
+
+ return (
+ <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4">
+ <SectionHeader>Download Daemon</SectionHeader>
+
+ {loading && (
+ <p className="text-[#7788aa] font-mono text-xs">
+ Fetching latest release...
+ </p>
+ )}
+
+ {error && <ErrorAlert>Failed to load release: {error}</ErrorAlert>}
+
+ {release && (
+ <>
+ <div className="flex items-center justify-between mb-4">
+ <div className="flex items-center gap-3">
+ <span className="text-[#9bc3ff] font-mono text-sm">
+ {release.tag_name}
+ </span>
+ <span className="text-[#556677] font-mono text-[10px]">
+ {formatDate(release.published_at)}
+ </span>
+ </div>
+ <a
+ href="https://github.com/soryu-co/makima/releases"
+ target="_blank"
+ rel="noopener noreferrer"
+ className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
+ >
+ All Releases &rarr;
+ </a>
+ </div>
+
+ <div className="space-y-2">
+ {sortedPlatforms.map((p) => (
+ <div
+ key={p.arch}
+ className={`border p-3 flex items-center justify-between ${
+ p.recommended
+ ? "border-[rgba(117,170,252,0.5)] bg-[#0a1628]"
+ : "border-[rgba(117,170,252,0.15)] bg-[#0a1525]"
+ }`}
+ >
+ <div className="flex items-center gap-3">
+ <span className="font-mono text-xs text-[#9bc3ff]">
+ {p.label}
+ </span>
+ {p.recommended && (
+ <span className="text-[9px] font-mono uppercase px-1.5 py-0.5 border border-[rgba(117,170,252,0.4)] text-[#75aafc] bg-[rgba(117,170,252,0.08)]">
+ Detected
+ </span>
+ )}
+ {p.asset && (
+ <span className="text-[10px] font-mono text-[#556677]">
+ {formatBytes(p.asset.size)}
+ </span>
+ )}
+ </div>
+ {p.asset ? (
+ <a
+ href={p.asset.browser_download_url}
+ className={`text-[10px] font-mono uppercase px-3 py-1.5 border transition-colors ${
+ p.recommended
+ ? "text-[#9bc3ff] border-[rgba(117,170,252,0.5)] bg-[rgba(117,170,252,0.1)] hover:bg-[rgba(117,170,252,0.2)]"
+ : "text-[#75aafc] border-[rgba(117,170,252,0.25)] hover:bg-[rgba(117,170,252,0.1)]"
+ }`}
+ download
+ >
+ Download
+ </a>
+ ) : (
+ <span className="text-[10px] font-mono text-[#556677]">
+ Not available
+ </span>
+ )}
+ </div>
+ ))}
+ </div>
+ </>
+ )}
+ </section>
+ );
+}
+
+// =============================================================================
+// Setup Instructions Section
+// =============================================================================
+
+function SetupSection() {
+ const [showConfig, setShowConfig] = useState(false);
+
+ return (
+ <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4">
+ <SectionHeader>Setup Instructions</SectionHeader>
+ <div className="space-y-3">
+ <div className="flex items-start">
+ <StepNumber n={1} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Download the binary for your platform above
+ </p>
+ </div>
+ </div>
+
+ <div className="flex items-start">
+ <StepNumber n={2} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Extract the archive
+ </p>
+ <CodeBlock>tar xzf makima-*.tar.gz</CodeBlock>
+ </div>
+ </div>
+
+ <div className="flex items-start">
+ <StepNumber n={3} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Move to PATH
+ </p>
+ <CodeBlock>sudo mv makima /usr/local/bin/</CodeBlock>
+ </div>
+ </div>
+
+ <div className="flex items-start">
+ <StepNumber n={4} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Set your API key (
+ <a
+ href="/settings"
+ className="text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
+ >
+ generate one in Settings
+ </a>
+ )
+ </p>
+ <CodeBlock>export MAKIMA_API_KEY="your-key"</CodeBlock>
+ </div>
+ </div>
+
+ <div className="flex items-start">
+ <StepNumber n={5} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Set server URL
+ </p>
+ <CodeBlock>
+ export MAKIMA_DAEMON_SERVER_URL="ws://your-server:8080"
+ </CodeBlock>
+ </div>
+ </div>
+
+ <div className="flex items-start">
+ <StepNumber n={6} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Run the daemon
+ </p>
+ <CodeBlock>makima daemon</CodeBlock>
+ </div>
+ </div>
+ </div>
+
+ {/* Config file alternative */}
+ <div className="mt-4 pt-3 border-t border-[rgba(117,170,252,0.1)]">
+ <button
+ onClick={() => setShowConfig(!showConfig)}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors bg-transparent border-none cursor-pointer p-0"
+ >
+ {showConfig ? "- Hide" : "+"} Config file alternative
+ </button>
+ {showConfig && (
+ <div className="mt-3">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-2">
+ Create <code className="text-green-400">makima-daemon.toml</code>{" "}
+ in the working directory:
+ </p>
+ <code className="block bg-black/50 px-3 py-2 text-[11px] font-mono text-green-400 whitespace-pre overflow-x-auto">
+ {`[daemon]
+api_key = "your-key"
+server_url = "ws://your-server:8080"
+max_concurrent_tasks = 4`}
+ </code>
+ </div>
+ )}
+ </div>
+ </section>
+ );
+}
+
+// =============================================================================
+// Cloudflare Edge Deployment Section
+// =============================================================================
+
+function CloudflareAgentSection() {
+ const [showSetup, setShowSetup] = useState(false);
+
+ const benefits = [
+ {
+ label: "Global edge presence",
+ desc: "Lower latency from 300+ Cloudflare locations worldwide",
+ },
+ {
+ label: "Auto-scaling & hibernation",
+ desc: "Cost-efficient — only runs when needed",
+ },
+ {
+ label: "WebSocket relay",
+ desc: "Coordinate remote daemon instances through persistent connections",
+ },
+ {
+ label: "Durable Objects",
+ desc: "Built on Cloudflare's stateful edge compute primitives",
+ },
+ ];
+
+ return (
+ <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4">
+ <SectionHeader>Edge Deployment</SectionHeader>
+
+ <p className="text-[#7788aa] font-mono text-[10px] mb-4 leading-relaxed">
+ Deploy a lightweight Makima relay agent on Cloudflare's edge network for
+ global, low-latency daemon coordination. Ideal for distributed teams or
+ production deployments requiring high availability.
+ </p>
+
+ {/* Benefits */}
+ <div className="space-y-2 mb-4">
+ {benefits.map((b) => (
+ <div
+ key={b.label}
+ className="flex items-start gap-2 text-[10px] font-mono"
+ >
+ <span className="text-[#75aafc] mt-px shrink-0">&#x25B8;</span>
+ <div>
+ <span className="text-[#9bc3ff]">{b.label}</span>
+ <span className="text-[#556677] ml-1">— {b.desc}</span>
+ </div>
+ </div>
+ ))}
+ </div>
+
+ {/* Quick Setup */}
+ <div className="border-t border-[rgba(117,170,252,0.1)] pt-3">
+ <button
+ onClick={() => setShowSetup(!showSetup)}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors bg-transparent border-none cursor-pointer p-0"
+ >
+ {showSetup ? "- Hide" : "+"} Quick setup
+ </button>
+ {showSetup && (
+ <div className="mt-3 space-y-3">
+ <div className="flex items-start">
+ <StepNumber n={1} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Navigate to the Cloudflare agent directory
+ </p>
+ <CodeBlock>cd makima/cloudflare-agent</CodeBlock>
+ </div>
+ </div>
+ <div className="flex items-start">
+ <StepNumber n={2} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Run the setup script
+ </p>
+ <CodeBlock>./setup.sh</CodeBlock>
+ </div>
+ </div>
+ <div className="flex items-start">
+ <StepNumber n={3} />
+ <div className="flex-1">
+ <p className="text-[#7788aa] font-mono text-[10px] mb-1">
+ Deploy to Cloudflare
+ </p>
+ <CodeBlock>npx wrangler deploy</CodeBlock>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+
+ {/* Link to repo */}
+ <div className="mt-4 pt-3 border-t border-[rgba(117,170,252,0.1)] flex items-center justify-between">
+ <span className="text-[10px] font-mono text-[#556677]">
+ Full documentation & source
+ </span>
+ <a
+ href="https://github.com/soryu-co/soryu/tree/master/makima/cloudflare-agent"
+ target="_blank"
+ rel="noopener noreferrer"
+ className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] transition-colors px-3 py-1.5 border border-[rgba(117,170,252,0.25)] hover:bg-[rgba(117,170,252,0.1)]"
+ >
+ View on GitHub &rarr;
+ </a>
+ </div>
+ </section>
+ );
+}
+
+// =============================================================================
+// Connected Daemons Section
+// =============================================================================
+
+function ConnectedDaemonsSection() {
+ const [daemons, setDaemons] = useState<Daemon[]>([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+ const [restartingDaemonId, setRestartingDaemonId] = useState<string | null>(
+ null
+ );
+ const [restartConfirmDaemonId, setRestartConfirmDaemonId] = useState<
+ string | null
+ >(null);
+
+ const loadDaemons = useCallback(async () => {
+ try {
+ setError(null);
+ const response = await listDaemons();
+ setDaemons(response.daemons);
+ } catch (err) {
+ setError(
+ err instanceof Error ? err.message : "Failed to load daemons"
+ );
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ loadDaemons();
+ }, [loadDaemons]);
+
+ // Auto-refresh every 30 seconds
+ useEffect(() => {
+ const interval = setInterval(() => {
+ loadDaemons();
+ }, 30000);
+ return () => clearInterval(interval);
+ }, [loadDaemons]);
+
+ const handleRestartDaemon = async (id: string) => {
+ try {
+ setRestartingDaemonId(id);
+ setError(null);
+ await restartDaemon(id);
+ setRestartConfirmDaemonId(null);
+ // Daemon will restart, so refresh the list after a short delay
+ setTimeout(() => {
+ loadDaemons();
+ }, 2000);
+ } catch (err) {
+ setError(
+ err instanceof Error ? err.message : "Failed to restart daemon"
+ );
+ } finally {
+ setRestartingDaemonId(null);
+ }
+ };
+
+ return (
+ <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4">
+ <div className="flex items-center justify-between mb-3 pb-2 border-b border-[rgba(117,170,252,0.15)]">
+ <div className="flex items-center gap-2">
+ <h2 className="text-[11px] font-mono uppercase tracking-wide text-[#8899aa]">
+ Connected Daemons
+ </h2>
+ {daemons.length > 0 && (
+ <span className="text-[10px] font-mono text-[#556677]">
+ ({daemons.filter((d) => d.status === "connected").length}{" "}
+ connected / {daemons.length} total)
+ </span>
+ )}
+ </div>
+ <button
+ onClick={loadDaemons}
+ disabled={loading}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] disabled:opacity-50 bg-transparent border-none cursor-pointer p-0"
+ title="Refresh"
+ >
+ {loading ? "..." : "\u21BB"}
+ </button>
+ </div>
+
+ {error && <ErrorAlert>{error}</ErrorAlert>}
+
+ {loading && daemons.length === 0 ? (
+ <p className="text-[#7788aa] font-mono text-xs">Loading...</p>
+ ) : daemons.length === 0 ? (
+ <div className="text-center py-6">
+ <p className="text-[#7788aa] font-mono text-xs mb-2">
+ No daemons connected
+ </p>
+ <p className="text-[#556677] font-mono text-[10px]">
+ Follow the setup instructions above to connect a daemon
+ </p>
+ </div>
+ ) : (
+ <div className="space-y-2">
+ {daemons.map((daemon) => (
+ <div
+ key={daemon.id}
+ className="border border-[rgba(117,170,252,0.15)] bg-[#0a1525] p-3"
+ >
+ <div className="flex items-center justify-between mb-2">
+ <span className="font-mono text-xs text-[#9bc3ff]">
+ {daemon.hostname || "Unknown Host"}
+ </span>
+ <div className="flex items-center gap-2">
+ <span
+ className={`text-[10px] font-mono uppercase px-2 py-0.5 border ${
+ daemon.status === "connected"
+ ? "text-green-400 border-green-700/50 bg-green-900/20"
+ : daemon.status === "unhealthy"
+ ? "text-yellow-400 border-yellow-700/50 bg-yellow-900/20"
+ : "text-[#8899aa] border-[rgba(117,170,252,0.25)]"
+ }`}
+ >
+ {daemon.status}
+ </span>
+ </div>
+ </div>
+ <div className="font-mono text-[10px] text-[#7788aa] space-y-1">
+ <div className="flex justify-between">
+ <span>Tasks</span>
+ <span className="text-[#9bc3ff]">
+ {daemon.currentTaskCount} / {daemon.maxConcurrentTasks}
+ </span>
+ </div>
+ <div className="flex justify-between">
+ <span>Connected</span>
+ <span className="text-[#75aafc]">
+ {new Date(daemon.connectedAt).toLocaleString()}
+ </span>
+ </div>
+ {daemon.machineId && (
+ <div className="flex justify-between">
+ <span>Machine</span>
+ <span
+ className="text-[#556677] truncate ml-2"
+ title={daemon.machineId}
+ >
+ {daemon.machineId.substring(0, 16)}...
+ </span>
+ </div>
+ )}
+ </div>
+ {/* Restart Section */}
+ {daemon.status === "connected" && (
+ <div className="mt-3 pt-2 border-t border-[rgba(117,170,252,0.1)]">
+ {restartConfirmDaemonId === daemon.id ? (
+ <div className="flex items-center justify-between gap-2">
+ <span className="text-[10px] font-mono text-yellow-400">
+ Restart daemon? Running tasks will be interrupted.
+ </span>
+ <div className="flex gap-2">
+ <button
+ onClick={() => setRestartConfirmDaemonId(null)}
+ className="text-[10px] font-mono text-[#7788aa] hover:text-[#9bc3ff] px-2 py-1 bg-transparent border-none cursor-pointer"
+ disabled={restartingDaemonId === daemon.id}
+ >
+ Cancel
+ </button>
+ <button
+ onClick={() => handleRestartDaemon(daemon.id)}
+ disabled={restartingDaemonId === daemon.id}
+ className="text-[10px] font-mono text-red-400 hover:text-red-300 px-2 py-1 border border-red-700/50 bg-red-900/20 disabled:opacity-50 cursor-pointer"
+ >
+ {restartingDaemonId === daemon.id
+ ? "Restarting..."
+ : "Confirm"}
+ </button>
+ </div>
+ </div>
+ ) : (
+ <button
+ onClick={() => setRestartConfirmDaemonId(daemon.id)}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] bg-transparent border-none cursor-pointer p-0"
+ >
+ &#x27F3; Restart Daemon
+ </button>
+ )}
+ </div>
+ )}
+ </div>
+ ))}
+ </div>
+ )}
+ </section>
+ );
+}
+
+// =============================================================================
+// Main Page
+// =============================================================================
+
+export default function DaemonPage() {
+ const {
+ isAuthenticated,
+ isAuthConfigured,
+ isLoading: authLoading,
+ } = useAuth();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (!authLoading && isAuthConfigured && !isAuthenticated) {
+ navigate("/login");
+ }
+ }, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
+
+ if (authLoading) {
+ return (
+ <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
+ <Masthead showNav />
+ <main className="flex-1 flex items-center justify-center">
+ <p className="text-[#7788aa] font-mono text-sm">Loading...</p>
+ </main>
+ </div>
+ );
+ }
+
+ return (
+ <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
+ <Masthead showNav />
+ <main className="flex-1 px-4 py-6 max-w-4xl mx-auto w-full">
+ {/* Page header */}
+ <div className="mb-6">
+ <h1 className="text-sm font-mono uppercase tracking-wide text-[#9bc3ff] mb-1">
+ Daemon Management
+ </h1>
+ <p className="text-[#556677] font-mono text-[10px]">
+ Download, configure, and monitor Makima daemons
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+ {/* Left Column: Downloads & Setup */}
+ <div className="space-y-6">
+ <DownloadSection />
+ <SetupSection />
+ </div>
+
+ {/* Right Column: Edge Deployment & Connected Daemons */}
+ <div className="space-y-6">
+ <ConnectedDaemonsSection />
+ <CloudflareAgentSection />
+ </div>
+ </div>
+ </main>
+ </div>
+ );
+}
diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo
index 68f2eac..410931d 100644
--- a/makima/frontend/tsconfig.tsbuildinfo
+++ b/makima/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/stepnode.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file
+{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/orchestratorstepnode.tsx","./src/components/directives/stepnode.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/orders/orderdetail.tsx","./src/components/orders/orderlist.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectives.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.ts","./src/hooks/useorders.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/daemon.tsx","./src/routes/directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/orders.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/types/messages.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file