@madarco/agentbox 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/{_cloud-attach-2DGI6FUA.js → _cloud-attach-45ECDTRL.js} +4 -4
  3. package/dist/{chunk-MTVI44DW.js → chunk-ECLLV5JH.js} +6 -3
  4. package/dist/chunk-ECLLV5JH.js.map +1 -0
  5. package/dist/{chunk-M2UWJKFA.js → chunk-MXXXKJYS.js} +5 -5
  6. package/dist/chunk-MXXXKJYS.js.map +1 -0
  7. package/dist/{chunk-I7NOGCL4.js → chunk-PZ2TJF2U.js} +23 -11
  8. package/dist/chunk-PZ2TJF2U.js.map +1 -0
  9. package/dist/{chunk-I24B6AXR.js → chunk-R5XIDQFR.js} +6 -3
  10. package/dist/chunk-R5XIDQFR.js.map +1 -0
  11. package/dist/{chunk-PWUVHPN6.js → chunk-SNTHHWKY.js} +7 -3
  12. package/dist/chunk-SNTHHWKY.js.map +1 -0
  13. package/dist/{chunk-LEV3KICD.js → chunk-ZGVMN54V.js} +6 -3
  14. package/dist/{chunk-LEV3KICD.js.map → chunk-ZGVMN54V.js.map} +1 -1
  15. package/dist/{chunk-CDKVD6UO.js → chunk-ZJXTIH6C.js} +119 -87
  16. package/dist/chunk-ZJXTIH6C.js.map +1 -0
  17. package/dist/{dist-SBCQVFCE.js → dist-ASLPRUQR.js} +3 -3
  18. package/dist/{dist-BD5QJRDC.js → dist-PTJ6CEQY.js} +5 -5
  19. package/dist/{dist-SJHY3HYN.js → dist-RAZP76VX.js} +5 -5
  20. package/dist/{dist-BNI5PQYK.js → dist-WMQDMTWS.js} +5 -5
  21. package/dist/index.js +619 -546
  22. package/dist/index.js.map +1 -1
  23. package/dist/{prepared-state-MQHD3M5F-O5M4NIN4.js → prepared-state-MQHD3M5F-KE4DT3GX.js} +2 -2
  24. package/package.json +4 -4
  25. package/runtime/docker/packages/ctl/dist/bin.cjs +5 -2
  26. package/runtime/hetzner/ctl.cjs +5 -2
  27. package/runtime/relay/bin.cjs +37 -19
  28. package/runtime/vercel/ctl.cjs +5 -2
  29. package/runtime/vercel/scripts/provision.sh +20 -0
  30. package/dist/chunk-CDKVD6UO.js.map +0 -1
  31. package/dist/chunk-I24B6AXR.js.map +0 -1
  32. package/dist/chunk-I7NOGCL4.js.map +0 -1
  33. package/dist/chunk-M2UWJKFA.js.map +0 -1
  34. package/dist/chunk-MTVI44DW.js.map +0 -1
  35. package/dist/chunk-PWUVHPN6.js.map +0 -1
  36. /package/dist/{_cloud-attach-2DGI6FUA.js.map → _cloud-attach-45ECDTRL.js.map} +0 -0
  37. /package/dist/{dist-SBCQVFCE.js.map → dist-ASLPRUQR.js.map} +0 -0
  38. /package/dist/{dist-BD5QJRDC.js.map → dist-PTJ6CEQY.js.map} +0 -0
  39. /package/dist/{dist-SJHY3HYN.js.map → dist-RAZP76VX.js.map} +0 -0
  40. /package/dist/{dist-BNI5PQYK.js.map → dist-WMQDMTWS.js.map} +0 -0
  41. /package/dist/{prepared-state-MQHD3M5F-O5M4NIN4.js.map → prepared-state-MQHD3M5F-KE4DT3GX.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/sandbox-hetzner/src/env-loader.ts","../../../packages/sandbox-hetzner/src/client.ts","../../../packages/sandbox-hetzner/src/credentials.ts","../../../packages/sandbox-hetzner/src/egress-ip.ts","../../../packages/sandbox-hetzner/src/retry.ts","../../../packages/sandbox-hetzner/src/firewall.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\n/**\n * Hetzner env auto-loader — mirrors `ensureDaytonaEnvLoaded()`. The Hetzner\n * REST client reads `HCLOUD_TOKEN` from `process.env`. We pull it in from\n * `~/.agentbox/secrets.env` so the client Just Works after the user runs\n * `agentbox hetzner login` once.\n *\n * Lookup order (first wins; process.env is never overwritten):\n * 1. `process.env` (already set in the shell).\n * 2. `~/.agentbox/secrets.env` — written by `agentbox hetzner login`.\n *\n * Project-level `.env` / `.env.local` are intentionally NOT consulted: those\n * files belong to the app code being developed, and a `HCLOUD_TOKEN` there\n * is typically meant for in-box infrastructure work, not for the host CLI to\n * harvest and provision VPSes with.\n *\n * Only Hetzner-prefixed keys are imported. Idempotent + side-effect-free\n * after the first call.\n */\nconst HETZNER_KEYS = ['HCLOUD_TOKEN', 'HCLOUD_ENDPOINT'] as const;\n\nlet loaded = false;\n\nexport function ensureHetznerEnvLoaded(): void {\n if (loaded) return;\n loaded = true;\n importHetznerFromFile(resolve(homedir(), '.agentbox', 'secrets.env'));\n}\n\nfunction importHetznerFromFile(path: string): void {\n if (!existsSync(path)) return;\n let body: string;\n try {\n body = readFileSync(path, 'utf8');\n } catch {\n return;\n }\n const parsed = parseEnvFile(body);\n for (const key of HETZNER_KEYS) {\n if (process.env[key] !== undefined) continue;\n const value = parsed[key];\n if (typeof value === 'string') {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * Minimal `.env` parser: handles `KEY=value`, `KEY=\"value with spaces\"`,\n * `KEY='value with $special chars'`, `export KEY=value`, blank lines, and\n * `#` comments. Same shape as the daytona env-loader's parser — kept local\n * here rather than imported across packages to avoid the cycle (daytona\n * doesn't import from hetzner and shouldn't start now).\n */\nexport function parseEnvFile(body: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith('#')) continue;\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) continue;\n const key = stripped.slice(0, eq).trim();\n let value = stripped.slice(eq + 1).trim();\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n","/**\n * Hetzner Cloud REST API client — hand-rolled fetch wrapper.\n *\n * Why not an SDK: the Hetzner SDK options are limited (no official JS SDK\n * with strict types at the time of writing), and the subset of the API we\n * need is small (servers, images, firewalls, plus a handful of read-only\n * lookups). A hand-rolled client gives us strict typing of just the fields\n * we touch, no heavy dep tree, and full control over the retry wrapper.\n *\n * Auth: bearer token in `HCLOUD_TOKEN` env. The env-loader pulls it from\n * `~/.agentbox/secrets.env` so the user only sets it once via\n * `agentbox hetzner login`.\n *\n * Errors: REST responses get unwrapped into typed `HetznerApiError`s that\n * carry the response `status` + the API's `error.code` / `error.message`.\n * Network failures bubble up as raw `Error`s with a `code` property\n * (ECONNRESET, ETIMEDOUT, …) — the retry wrapper classifies both shapes.\n */\n\nimport { ensureHetznerEnvLoaded } from './env-loader.js';\n\nexport const DEFAULT_HCLOUD_ENDPOINT = 'https://api.hetzner.cloud/v1';\n\n/**\n * Coarse Hetzner Cloud Server lifecycle states we care about. Hetzner has a\n * dozen finer-grained ones (`initializing`, `migrating`, `rebuilding`, …);\n * we map them in `backend.ts` to the four-value `CloudState` everyone else\n * consumes. Listed here so the client return types stay narrow.\n */\nexport type HetznerServerStatus =\n | 'running'\n | 'initializing'\n | 'starting'\n | 'stopping'\n | 'off'\n | 'deleting'\n | 'migrating'\n | 'rebuilding'\n | 'unknown';\n\nexport interface HetznerServer {\n id: number;\n name: string;\n status: HetznerServerStatus;\n created: string;\n public_net: {\n ipv4: { ip: string; blocked: boolean } | null;\n ipv6: { ip: string; blocked: boolean } | null;\n };\n server_type: { name: string; cores: number; memory: number; disk: number };\n image: { id: number; name?: string; description?: string; type: string } | null;\n labels: Record<string, string>;\n}\n\nexport interface HetznerAction {\n id: number;\n command: string;\n status: 'running' | 'success' | 'error';\n progress: number;\n error?: { code: string; message: string };\n}\n\nexport interface HetznerImage {\n id: number;\n type: 'system' | 'snapshot' | 'backup' | 'app';\n status: 'available' | 'creating' | 'unavailable';\n name?: string;\n description: string;\n image_size?: number;\n disk_size: number;\n created: string;\n labels: Record<string, string>;\n bound_to?: number;\n}\n\nexport interface HetznerFirewall {\n id: number;\n name: string;\n rules: HetznerFirewallRule[];\n applied_to: Array<{ type: 'server'; server: { id: number } }>;\n}\n\nexport interface HetznerFirewallRule {\n direction: 'in' | 'out';\n protocol: 'tcp' | 'udp' | 'icmp' | 'esp' | 'gre';\n port?: string;\n source_ips?: string[];\n destination_ips?: string[];\n description?: string;\n}\n\nexport interface HetznerSshKey {\n id: number;\n name: string;\n fingerprint: string;\n public_key: string;\n labels: Record<string, string>;\n}\n\nexport interface CreateServerRequest {\n name: string;\n server_type: string;\n image: string | number;\n location?: string;\n datacenter?: string;\n user_data?: string;\n ssh_keys?: Array<string | number>;\n firewalls?: Array<{ firewall: number }>;\n labels?: Record<string, string>;\n start_after_create?: boolean;\n public_net?: {\n enable_ipv4?: boolean;\n enable_ipv6?: boolean;\n };\n}\n\nexport interface CreateFirewallRequest {\n name: string;\n rules: HetznerFirewallRule[];\n labels?: Record<string, string>;\n apply_to?: Array<{ type: 'server'; server: { id: number } }>;\n}\n\n/**\n * Strongly-typed Hetzner API error. The Hetzner API consistently returns\n * `{ error: { code, message, details? } }` for 4xx/5xx (https://docs.hetzner.cloud/#errors).\n * We unwrap that into this class so callers can do `instanceof\n * HetznerApiError` and inspect `.code` / `.statusCode` without parsing the\n * body again.\n */\nexport class HetznerApiError extends Error {\n readonly statusCode: number;\n readonly code: string;\n readonly details?: unknown;\n constructor(statusCode: number, code: string, message: string, details?: unknown) {\n super(`hetzner ${String(statusCode)} ${code}: ${message}`);\n this.name = 'HetznerApiError';\n this.statusCode = statusCode;\n this.code = code;\n this.details = details;\n }\n}\n\n/**\n * Subset of the Hetzner Cloud API the agentbox provider talks to. Methods\n * map 1:1 to REST endpoints; each operation is small + idempotent-where-the-\n * API-is-idempotent. The retry wrapper around the provider methods handles\n * transient 5xx / connection failures.\n */\nexport interface HetznerClient {\n /** GET /servers/{id}. Returns null on 404 so callers don't have to try/catch. */\n getServer(id: number): Promise<HetznerServer | null>;\n /** POST /servers. Returns the created server + the create action handle. */\n createServer(req: CreateServerRequest): Promise<{ server: HetznerServer; action: HetznerAction }>;\n /** GET /servers (with optional label selector). */\n listServers(opts?: { label_selector?: string }): Promise<HetznerServer[]>;\n /** DELETE /servers/{id}. Returns the action handle. Idempotent on 404. */\n deleteServer(id: number): Promise<HetznerAction | null>;\n /** POST /servers/{id}/actions/poweron. */\n powerOn(id: number): Promise<HetznerAction>;\n /** POST /servers/{id}/actions/poweroff. */\n powerOff(id: number): Promise<HetznerAction>;\n /** POST /servers/{id}/actions/shutdown — graceful, sends ACPI. */\n shutdown(id: number): Promise<HetznerAction>;\n /** POST /servers/{id}/actions/create_image — snapshot of the live disk. */\n createImage(\n id: number,\n body: { type: 'snapshot' | 'backup'; description?: string; labels?: Record<string, string> },\n ): Promise<{ image: HetznerImage; action: HetznerAction }>;\n /** GET /images/{id}. Returns null on 404. */\n getImage(id: number): Promise<HetznerImage | null>;\n /** GET /images (filterable). */\n listImages(opts?: {\n type?: 'system' | 'snapshot' | 'backup' | 'app';\n label_selector?: string;\n name?: string;\n }): Promise<HetznerImage[]>;\n /** DELETE /images/{id}. Idempotent on 404. */\n deleteImage(id: number): Promise<void>;\n /** POST /firewalls. */\n createFirewall(req: CreateFirewallRequest): Promise<HetznerFirewall>;\n /** POST /firewalls/{id}/actions/set_rules. Replaces the entire rule set. */\n setFirewallRules(id: number, rules: HetznerFirewallRule[]): Promise<HetznerAction[]>;\n /** GET /firewalls/{id}. Returns null on 404. */\n getFirewall(id: number): Promise<HetznerFirewall | null>;\n /** DELETE /firewalls/{id}. Idempotent on 404. */\n deleteFirewall(id: number): Promise<void>;\n /**\n * GET /locations — used by `agentbox hetzner login` to validate the token\n * with a cheap unauthenticated-shape call (the endpoint requires a valid\n * token but returns a small, stable response).\n */\n listLocations(): Promise<Array<{ id: number; name: string; city: string; country: string }>>;\n}\n\ninterface MakeClientOptions {\n /** Override the bearer token (else read from `HCLOUD_TOKEN`). */\n token?: string;\n /** Override the API base URL (else read from `HCLOUD_ENDPOINT` or use the default). */\n endpoint?: string;\n /** Per-request fetch impl (tests inject this). */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Build a Hetzner Cloud client bound to the current `HCLOUD_TOKEN`. The token\n * is resolved at construction time, so re-running `agentbox hetzner login` in\n * the middle of a long-lived process won't pick up the new token without a\n * fresh `makeHetznerClient()` call (we accept this — the CLI re-imports the\n * provider on each invocation).\n */\nexport function makeHetznerClient(opts: MakeClientOptions = {}): HetznerClient {\n ensureHetznerEnvLoaded();\n const rawToken = opts.token ?? process.env.HCLOUD_TOKEN;\n if (!rawToken || rawToken.trim().length === 0) {\n throw new Error(\n 'Hetzner credentials not configured: HCLOUD_TOKEN is empty.\\n' +\n 'Run `agentbox hetzner login` interactively, or set HCLOUD_TOKEN in the environment.',\n );\n }\n // Bind to a const so the type narrows for the closures below — without\n // this the `req()` closure sees the original `string | undefined` shape.\n const token: string = rawToken.trim();\n const endpoint = (opts.endpoint ?? process.env.HCLOUD_ENDPOINT ?? DEFAULT_HCLOUD_ENDPOINT).replace(/\\/$/, '');\n const fetchImpl = opts.fetchImpl ?? fetch;\n\n async function req<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n path: string,\n body?: unknown,\n ): Promise<T | null> {\n const url = `${endpoint}${path}`;\n const init: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n };\n const res = await fetchImpl(url, init);\n if (res.status === 204) return null;\n if (res.status === 404) return null;\n if (!res.ok) {\n let parsed: { error?: { code?: string; message?: string; details?: unknown } } = {};\n try {\n parsed = (await res.json()) as typeof parsed;\n } catch {\n // body wasn't json\n }\n const code = parsed.error?.code ?? `http_${String(res.status)}`;\n const msg = parsed.error?.message ?? res.statusText ?? 'unknown error';\n throw new HetznerApiError(res.status, code, msg, parsed.error?.details);\n }\n const text = await res.text();\n if (text.length === 0) return null;\n return JSON.parse(text) as T;\n }\n\n async function reqExpect<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n path: string,\n body?: unknown,\n ): Promise<T> {\n const out = await req<T>(method, path, body);\n if (out === null) {\n throw new HetznerApiError(0, 'empty_response', `expected a body from ${method} ${path}`);\n }\n return out;\n }\n\n return {\n async getServer(id) {\n const r = await req<{ server: HetznerServer }>('GET', `/servers/${String(id)}`);\n return r?.server ?? null;\n },\n async createServer(reqBody) {\n const r = await reqExpect<{ server: HetznerServer; action: HetznerAction }>(\n 'POST',\n '/servers',\n reqBody,\n );\n return { server: r.server, action: r.action };\n },\n async listServers(opts) {\n const params = new URLSearchParams();\n if (opts?.label_selector) params.set('label_selector', opts.label_selector);\n params.set('per_page', '50');\n const all: HetznerServer[] = [];\n let pageNum = 1;\n while (true) {\n params.set('page', String(pageNum));\n const r = await reqExpect<{\n servers: HetznerServer[];\n meta?: { pagination?: { next_page?: number | null } };\n }>('GET', `/servers?${params.toString()}`);\n all.push(...r.servers);\n const next = r.meta?.pagination?.next_page;\n if (typeof next !== 'number') break;\n pageNum = next;\n }\n return all;\n },\n async deleteServer(id) {\n const r = await req<{ action: HetznerAction }>('DELETE', `/servers/${String(id)}`);\n return r?.action ?? null;\n },\n async powerOn(id) {\n const r = await reqExpect<{ action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/poweron`,\n );\n return r.action;\n },\n async powerOff(id) {\n const r = await reqExpect<{ action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/poweroff`,\n );\n return r.action;\n },\n async shutdown(id) {\n const r = await reqExpect<{ action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/shutdown`,\n );\n return r.action;\n },\n async createImage(id, body) {\n const r = await reqExpect<{ image: HetznerImage; action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/create_image`,\n body,\n );\n return { image: r.image, action: r.action };\n },\n async getImage(id) {\n const r = await req<{ image: HetznerImage }>('GET', `/images/${String(id)}`);\n return r?.image ?? null;\n },\n async listImages(opts) {\n const params = new URLSearchParams();\n if (opts?.type) params.set('type', opts.type);\n if (opts?.label_selector) params.set('label_selector', opts.label_selector);\n if (opts?.name) params.set('name', opts.name);\n params.set('per_page', '50');\n const all: HetznerImage[] = [];\n let pageNum = 1;\n while (true) {\n params.set('page', String(pageNum));\n const r = await reqExpect<{\n images: HetznerImage[];\n meta?: { pagination?: { next_page?: number | null } };\n }>('GET', `/images?${params.toString()}`);\n all.push(...r.images);\n const next = r.meta?.pagination?.next_page;\n if (typeof next !== 'number') break;\n pageNum = next;\n }\n return all;\n },\n async deleteImage(id) {\n await req<unknown>('DELETE', `/images/${String(id)}`);\n },\n async createFirewall(reqBody) {\n const r = await reqExpect<{ firewall: HetznerFirewall }>('POST', '/firewalls', reqBody);\n return r.firewall;\n },\n async setFirewallRules(id, rules) {\n const r = await reqExpect<{ actions: HetznerAction[] }>(\n 'POST',\n `/firewalls/${String(id)}/actions/set_rules`,\n { rules },\n );\n return r.actions;\n },\n async getFirewall(id) {\n const r = await req<{ firewall: HetznerFirewall }>('GET', `/firewalls/${String(id)}`);\n return r?.firewall ?? null;\n },\n async deleteFirewall(id) {\n await req<unknown>('DELETE', `/firewalls/${String(id)}`);\n },\n async listLocations() {\n const r = await reqExpect<{\n locations: Array<{ id: number; name: string; city: string; country: string }>;\n }>('GET', '/locations');\n return r.locations;\n },\n };\n}\n","import { spawnSync } from 'node:child_process';\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, resolve } from 'node:path';\nimport { confirm, isCancel, intro, log, note, outro, password, spinner } from '@clack/prompts';\nimport { makeHetznerClient } from './client.js';\nimport { ensureHetznerEnvLoaded } from './env-loader.js';\n\nconst DASHBOARD_KEYS_URL = 'https://console.hetzner.cloud/projects';\n\n/**\n * Keys we manage in `~/.agentbox/secrets.env`. When the user reconfigures\n * we strip prior values before appending so the file never accumulates\n * duplicates. `HCLOUD_ENDPOINT` is honored but we don't prompt for it\n * (default endpoint covers 100% of users).\n */\nconst MANAGED_KEYS = ['HCLOUD_TOKEN', 'HCLOUD_ENDPOINT'] as const;\ntype ManagedKey = (typeof MANAGED_KEYS)[number];\n\nexport interface EnsureHetznerCredentialsOptions {\n /** Re-prompt even when valid credentials are already present (used by `agentbox hetzner login`). */\n force?: boolean;\n}\n\n/**\n * First-run interactive setup for Hetzner credentials. Walks the user\n * through creating a project API token, pasting it, validating, and\n * persisting to `~/.agentbox/secrets.env`.\n *\n * No-op when credentials are already configured (env var or our secrets\n * file). Silent no-op when stdin isn't a TTY so scripted/CI callers get\n * the API \"401 unauthorized\" error instead of a hung prompt.\n *\n * Mirrors `ensureDaytonaCredentials()` in shape so the registry's first-\n * run gate stays uniform across providers.\n */\nexport async function ensureHetznerCredentials(\n opts: EnsureHetznerCredentialsOptions = {},\n): Promise<void> {\n ensureHetznerEnvLoaded();\n\n if (!opts.force && hasUsableCredentials()) return;\n if (!process.stdin.isTTY) return;\n\n intro('Hetzner Cloud setup');\n note(\n `AgentBox needs a Hetzner Cloud API token (project-scoped) to provision VPSes.\\n\\n` +\n `1. Open ${DASHBOARD_KEYS_URL}\\n` +\n `2. Pick a project (or create one).\\n` +\n `3. Security → API Tokens → Generate API Token (Read + Write).`,\n 'API token required',\n );\n\n const open = await confirm({\n message: `Open ${DASHBOARD_KEYS_URL} in your browser?`,\n initialValue: true,\n });\n if (isCancel(open)) {\n log.warn('Hetzner setup cancelled — re-run `agentbox hetzner login` when ready.');\n return;\n }\n if (open) openDashboard();\n\n // One retry on auth failure (typos / expired token are the common case).\n for (let attempt = 0; attempt < 2; attempt++) {\n const creds = await promptForCredentials();\n if (creds === null) return;\n\n const result = await validateCredentials(creds);\n if (result.ok) {\n persistCredentials(creds);\n log.success(`Hetzner credentials saved to ${secretsPath()}`);\n outro('Setup complete.');\n return;\n }\n if (result.kind === 'auth' && attempt === 0) {\n log.error(`That token was rejected by Hetzner: ${result.message}`);\n log.info('Try again, or press Ctrl-C to cancel.');\n continue;\n }\n if (result.kind === 'network') {\n log.warn(`Could not reach Hetzner to validate (${result.message}) — saving anyway.`);\n persistCredentials(creds);\n log.success(`Hetzner credentials saved to ${secretsPath()}`);\n outro('Setup complete (unvalidated).');\n return;\n }\n throw new Error(`Hetzner credentials rejected: ${result.message}`);\n }\n}\n\nfunction hasUsableCredentials(): boolean {\n return typeof process.env.HCLOUD_TOKEN === 'string' && process.env.HCLOUD_TOKEN.length > 0;\n}\n\ninterface Credentials {\n token: string;\n endpoint?: string;\n}\n\nasync function promptForCredentials(): Promise<Credentials | null> {\n const token = await password({\n message: 'Paste your Hetzner Cloud API token',\n validate(v) {\n if (!v || v.trim().length === 0) return 'Cannot be empty';\n return undefined;\n },\n });\n if (isCancel(token)) {\n log.warn('Hetzner setup cancelled.');\n return null;\n }\n return { token: token.trim() };\n}\n\ntype ValidationResult =\n | { ok: true }\n | { ok: false; kind: 'auth'; message: string }\n | { ok: false; kind: 'network'; message: string };\n\nasync function validateCredentials(creds: Credentials): Promise<ValidationResult> {\n const s = spinner();\n s.start('Validating credentials with Hetzner');\n\n try {\n const client = makeHetznerClient({ token: creds.token, endpoint: creds.endpoint });\n // `listLocations()` is a cheap, deterministic call that exercises auth +\n // basic API reachability without provisioning anything.\n await client.listLocations();\n s.stop('Hetzner credentials accepted');\n return { ok: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n s.stop('Hetzner credentials check failed');\n if (/401|403|unauthor|forbidden|invalid|token/i.test(message)) {\n return { ok: false, kind: 'auth', message };\n }\n return { ok: false, kind: 'network', message };\n }\n}\n\nfunction persistCredentials(creds: Credentials): void {\n process.env.HCLOUD_TOKEN = creds.token;\n if (creds.endpoint) process.env.HCLOUD_ENDPOINT = creds.endpoint;\n const path = secretsPath();\n mkdirSync(dirname(path), { recursive: true });\n\n let existing = '';\n if (existsSync(path)) {\n try {\n existing = readFileSync(path, 'utf8');\n } catch {\n existing = '';\n }\n }\n\n const kept = existing\n .split(/\\r?\\n/)\n .filter((line) => {\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) return true;\n const key = stripped.slice(0, eq).trim();\n return !(MANAGED_KEYS as readonly string[]).includes(key);\n })\n .join('\\n')\n .replace(/\\s+$/u, '');\n\n const lines: string[] = [`HCLOUD_TOKEN=${creds.token}`];\n if (creds.endpoint) lines.push(`HCLOUD_ENDPOINT=${creds.endpoint}`);\n\n const body = (kept ? `${kept}\\n` : '') + lines.join('\\n') + '\\n';\n\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, body, { mode: 0o600 });\n try {\n chmodSync(tmp, 0o600);\n } catch {\n // chmod best-effort; writeFileSync mode already covers most filesystems.\n }\n renameSync(tmp, path);\n try {\n chmodSync(path, 0o600);\n } catch {\n // ignore — already attempted above.\n }\n}\n\nfunction openDashboard(): void {\n try {\n const r = spawnSync('open', [DASHBOARD_KEYS_URL], { stdio: 'ignore' });\n if (r.status !== 0) {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_KEYS_URL} manually.`);\n }\n } catch {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_KEYS_URL} manually.`);\n }\n}\n\nexport function secretsPath(): string {\n return resolve(homedir(), '.agentbox', 'secrets.env');\n}\n\nexport interface HetznerCredStatus {\n token?: string;\n endpoint?: string;\n source: 'env' | 'secrets.env' | 'none';\n}\n\nexport function readHetznerCredStatus(): HetznerCredStatus {\n const shellHadToken = !!process.env.HCLOUD_TOKEN;\n ensureHetznerEnvLoaded();\n const token = process.env.HCLOUD_TOKEN;\n const endpoint = process.env.HCLOUD_ENDPOINT;\n if (!token) return { source: 'none' };\n return {\n token,\n endpoint,\n source: shellHadToken ? 'env' : 'secrets.env',\n };\n}\n\nexport function maskKey(value: string): string {\n if (value.length <= 8) return '*'.repeat(value.length);\n return `${value.slice(0, 4)}…${'*'.repeat(8)}${value.slice(-4)}`;\n}\n\n/** Snapshot of the managed env keys (used by tests around `applyToEnv`). */\nexport function snapshotManagedEnv(): Record<ManagedKey, string | undefined> {\n const out = {} as Record<ManagedKey, string | undefined>;\n for (const k of MANAGED_KEYS) out[k] = process.env[k];\n return out;\n}\n\nexport function restoreManagedEnv(snap: Record<ManagedKey, string | undefined>): void {\n for (const k of MANAGED_KEYS) {\n if (snap[k] === undefined) delete process.env[k];\n else process.env[k] = snap[k];\n }\n}\n","/**\n * Host egress-IP detection for the Hetzner firewall lock-down. Probes three\n * independent providers in sequence; first 3s success wins. Fails loud\n * (throws) if all three fail — we do **not** silently fall back to\n * `0.0.0.0/0`, because that would defeat the safe-by-default firewall.\n *\n * The user can always override the auto-detect via\n * `--firewall-source <cidr>` (or `--firewall-source 0.0.0.0/0` for the\n * explicit dynamic-IP opt-in).\n */\n\nconst PROBES = [\n 'https://api.ipify.org',\n 'https://ifconfig.io/ip',\n 'https://icanhazip.com',\n] as const;\n\nconst TIMEOUT_MS = 3_000;\n\nconst IPV4_RE = /^(?:\\d{1,3}\\.){3}\\d{1,3}$/;\nconst IPV6_RE = /^[0-9a-fA-F:]+$/;\n\nexport interface DetectEgressIpOptions {\n /** Override the probe list (tests inject this). */\n probes?: readonly string[];\n /** Per-probe timeout in ms (default 3_000). */\n timeoutMs?: number;\n /** Override `fetch` (tests inject this). */\n fetchImpl?: typeof fetch;\n /** Best-effort logger for probe attempts. */\n onLog?: (line: string) => void;\n}\n\n/**\n * Detect the host's egress IP. Returns the bare IP string (no `/32`); the\n * caller composes the CIDR.\n *\n * Throws when no probe responded. The error message lists each probe that\n * was tried so the user can see whether their network is blocking a\n * specific provider.\n */\nexport async function detectEgressIp(opts: DetectEgressIpOptions = {}): Promise<string> {\n const probes = opts.probes ?? PROBES;\n const timeout = opts.timeoutMs ?? TIMEOUT_MS;\n const fetchImpl = opts.fetchImpl ?? fetch;\n const errors: string[] = [];\n\n for (const url of probes) {\n try {\n const ip = await raceTimeout(probe(url, fetchImpl), timeout);\n if (ip) {\n opts.onLog?.(`egress-ip: detected ${ip} via ${url}`);\n return ip;\n }\n errors.push(`${url}: empty/invalid response`);\n } catch (err) {\n errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n throw new Error(\n `could not auto-detect the host's egress IP — all ${String(probes.length)} probes failed:\\n` +\n errors.map((e) => ` - ${e}`).join('\\n') +\n `\\nOverride with --firewall-source <cidr> (e.g. --firewall-source 0.0.0.0/0 for the explicit-open opt-in).`,\n );\n}\n\nasync function probe(url: string, fetchImpl: typeof fetch): Promise<string | null> {\n const res = await fetchImpl(url, { method: 'GET' });\n if (!res.ok) return null;\n const body = (await res.text()).trim();\n if (IPV4_RE.test(body)) {\n // Cheap sanity: each octet in 0–255.\n const parts = body.split('.').map((p) => Number.parseInt(p, 10));\n if (parts.every((p) => p >= 0 && p <= 255)) return body;\n return null;\n }\n // We do not currently use IPv6 for firewall rules (Hetzner accepts them\n // but the rest of the provider talks IPv4), but accept the probe answer\n // so a v6-only network surfaces an actionable error rather than a silent\n // empty result. Composing the CIDR is the caller's job.\n if (IPV6_RE.test(body) && body.includes(':')) return body;\n return null;\n}\n\nasync function raceTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new Error(`probe timed out after ${String(ms)}ms`)), ms);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n","/**\n * Bounded retry wrapper for Hetzner Cloud API calls — mirrors\n * `withDaytonaRetry` in shape and intent. Hetzner is generally well-behaved\n * but the public API does rate-limit (429) and occasionally returns 502/504\n * during regional incidents; without bounded retries those propagate as\n * wedges in the calling lifecycle code.\n *\n * Non-idempotent ops (`provision`, `createImage`) pass\n * `retryOnAmbiguous: false` so a 504 after the request reached the origin\n * doesn't create a duplicate billable resource.\n */\n\nimport { HetznerApiError } from './client.js';\n\nexport interface WithRetryOptions {\n /** Method name, used in retry log lines. */\n method: string;\n /** Per-attempt timeout (ms). Default 30_000. */\n attemptTimeoutMs?: number;\n /** Backoff before attempts 2, 3, … (ms). Default [1000, 2000, 4000]. */\n backoffMs?: readonly number[];\n /**\n * Whether to retry on errors where we can't be sure the server applied\n * the request — connection failures, per-attempt timeouts, and 5xx\n * responses. Set false for non-idempotent operations (e.g. `provision`,\n * `createImage`) where a retry could create a duplicate resource.\n */\n retryOnAmbiguous: boolean;\n /** Override the default `process.stderr` retry sink (used by tests). */\n onRetry?: (line: string) => void;\n}\n\nconst DEFAULT_BACKOFF: readonly number[] = [1000, 2000, 4000];\nconst DEFAULT_ATTEMPT_TIMEOUT_MS = 30_000;\n\nclass AttemptTimeoutError extends Error {\n constructor(method: string, ms: number) {\n super(`hetzner ${method}: per-attempt timeout after ${String(ms)}ms`);\n this.name = 'AttemptTimeoutError';\n }\n}\n\nexport function isAttemptTimeout(err: unknown): err is AttemptTimeoutError {\n return err instanceof AttemptTimeoutError;\n}\n\n/**\n * Classify an error as retriable or not. `allowAmbiguous` gates the cases\n * where the server may or may not have applied the request — the caller\n * decides based on idempotency.\n */\nexport function isRetriable(err: unknown, allowAmbiguous: boolean): boolean {\n if (err instanceof HetznerApiError) {\n // Rate limit: always back off — the server told us to.\n if (err.statusCode === 429 || err.code === 'rate_limit_exceeded') return true;\n // 5xx: ambiguous (the API may or may not have applied the change).\n if (err.statusCode >= 500 && err.statusCode <= 599) return allowAmbiguous;\n // Hetzner conflict / locked errors: the API tells us to wait — same as\n // rate-limit semantically. `conflict` is what `delete_server` returns\n // when another action (e.g. our own poweroff) is still in flight.\n if (err.code === 'locked' || err.code === 'conflict') return true;\n // Everything else is a permanent client error (auth, validation, not_found).\n return false;\n }\n\n if (err instanceof AttemptTimeoutError) return allowAmbiguous;\n\n // Raw fetch / undici errors. The Node fetch impl wraps low-level errors in\n // `{ cause }`; we check both shapes for portability.\n if (err && typeof err === 'object') {\n const candidates: unknown[] = [err, (err as { cause?: unknown }).cause];\n for (const c of candidates) {\n if (!c || typeof c !== 'object') continue;\n const code = (c as { code?: unknown }).code;\n if (\n code === 'ECONNRESET' ||\n code === 'ETIMEDOUT' ||\n code === 'ECONNABORTED' ||\n code === 'EAI_AGAIN' ||\n code === 'ECONNREFUSED' ||\n code === 'ENOTFOUND' ||\n code === 'UND_ERR_SOCKET' ||\n code === 'UND_ERR_CONNECT_TIMEOUT'\n ) {\n return allowAmbiguous;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Run `fn`, retrying on transient failures with capped exponential backoff.\n * Each attempt is bounded by `attemptTimeoutMs` via Promise.race; total\n * wall-clock = sum(backoffMs) + maxAttempts * attemptTimeoutMs.\n */\nexport async function withHetznerRetry<T>(\n opts: WithRetryOptions,\n fn: () => Promise<T>,\n): Promise<T> {\n const backoff = opts.backoffMs ?? DEFAULT_BACKOFF;\n const maxAttempts = backoff.length + 1;\n const timeoutMs = opts.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS;\n const log = opts.onRetry ?? defaultRetryLog;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await raceTimeout(fn(), timeoutMs, opts.method);\n } catch (err) {\n const last = attempt === maxAttempts;\n if (last || !isRetriable(err, opts.retryOnAmbiguous)) throw err;\n const delay = backoff[attempt - 1] ?? backoff[backoff.length - 1] ?? 4000;\n log(\n `hetzner ${opts.method}: attempt ${String(attempt)} failed (${errorSummary(err)}); retrying in ${String(delay)}ms`,\n );\n await sleep(delay);\n }\n }\n throw new Error(`withHetznerRetry: exhausted attempts for ${opts.method}`);\n}\n\nfunction defaultRetryLog(line: string): void {\n process.stderr.write(`\\n[hetzner-retry] ${line}\\n`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function raceTimeout<T>(p: Promise<T>, ms: number, method: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new AttemptTimeoutError(method, ms)), ms);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\nfunction errorSummary(err: unknown): string {\n if (err instanceof HetznerApiError) {\n return `HetznerApiError ${String(err.statusCode)} ${err.code}: ${truncate(err.message)}`;\n }\n if (err instanceof Error) {\n const code = (err as { code?: unknown }).code;\n return code !== undefined\n ? `${err.name}(${String(code)}): ${truncate(err.message)}`\n : `${err.name}: ${truncate(err.message)}`;\n }\n return truncate(String(err));\n}\n\nfunction truncate(s: string, max = 160): string {\n return s.length > max ? `${s.slice(0, max)}…` : s;\n}\n","/**\n * Hetzner Cloud Firewall provisioning + drift sync.\n *\n * Defense-in-depth model (recapped from\n * ~/.claude/plans/how-to-safely-create-parallel-pebble.md §\"The safety model\"):\n *\n * 1. In-VPS services bind to loopback (the load-bearing layer).\n * 2. Hetzner Cloud Firewall locks SSH to the host's egress IP — applied\n * here at provision time, before the VPS first boots. Everything else\n * is denied inbound; outbound is unrestricted.\n * 3. sshd hardening (PasswordAuthentication no, AllowUsers vscode, …)\n * written by cloud-init at first boot.\n *\n * Layer 2 is what this module provisions. The firewall is per-box (1:1 with\n * the VPS) so an egress-IP-drift on one box doesn't affect siblings, and a\n * destroy cleanly removes everything we created.\n */\n\nimport { HetznerApiError, type HetznerClient, type HetznerFirewall, type HetznerFirewallRule } from './client.js';\nimport { withHetznerRetry } from './retry.js';\n\n/**\n * Build the SSH-only inbound rule for a given source CIDR. Outbound is\n * left unrestricted (empty rules array = \"no inbound besides this one\").\n */\nexport function sshOnlyInboundRule(sourceCidr: string): HetznerFirewallRule[] {\n return [\n {\n direction: 'in',\n protocol: 'tcp',\n port: '22',\n source_ips: [sourceCidr],\n description: 'agentbox: SSH from host egress IP only',\n },\n ];\n}\n\nexport interface CreateFirewallOptions {\n /** Human-readable name persisted with the firewall (visible in the Hetzner dashboard). */\n name: string;\n /** Source CIDR (e.g. `1.2.3.4/32`). The caller is responsible for normalizing the suffix. */\n sourceCidr: string;\n /** Labels merged onto the firewall (we always add `agentbox.managed=true`). */\n labels?: Record<string, string>;\n}\n\n/**\n * Provision a fresh per-box firewall locked to the given source CIDR.\n * Returns the created `HetznerFirewall` so the caller can persist\n * `firewallId` on the box record.\n */\nexport async function createPerBoxFirewall(\n client: HetznerClient,\n opts: CreateFirewallOptions,\n): Promise<HetznerFirewall> {\n return withHetznerRetry(\n { method: 'createFirewall', retryOnAmbiguous: false, attemptTimeoutMs: 60_000 },\n () =>\n client.createFirewall({\n name: opts.name,\n rules: sshOnlyInboundRule(opts.sourceCidr),\n labels: {\n 'agentbox.managed': 'true',\n 'agentbox.role': 'box',\n ...opts.labels,\n },\n }),\n );\n}\n\n/**\n * Re-detect the egress IP and replace the firewall's rule set with the new\n * source. Used by `agentbox hetzner firewall sync <box>` after the host\n * laptop moves networks. Cheap operation — no VPS restart involved.\n *\n * Idempotent on the API: setting the same rules again is a no-op from the\n * user's point of view (the API still returns an action handle, but it\n * resolves instantly).\n */\nexport async function syncFirewallSource(\n client: HetznerClient,\n firewallId: number,\n sourceCidr: string,\n): Promise<void> {\n await withHetznerRetry(\n { method: 'setFirewallRules', retryOnAmbiguous: true, attemptTimeoutMs: 60_000 },\n () => client.setFirewallRules(firewallId, sshOnlyInboundRule(sourceCidr)),\n );\n}\n\n/**\n * Delete a per-box firewall. Idempotent on 404 (the API surfaces it as a\n * `not_found` error which the retry classifier won't retry; we swallow it\n * here so destroy paths don't need a special-case).\n *\n * Hetzner returns 409 `conflict` if the firewall is still attached to a\n * server when we try to delete it — `deleteServer()` returns as soon as the\n * delete action is *enqueued*, not after the server's firewall attachment\n * is torn down, so a quick subsequent `deleteFirewall()` will collide.\n * We poll for a short window (default 60s, intervals doubled to 8s) to\n * cover the typical 5–15s detach lag before giving up.\n */\nexport async function deletePerBoxFirewall(\n client: HetznerClient,\n firewallId: number,\n opts: { detachWaitMs?: number } = {},\n): Promise<void> {\n const deadline = Date.now() + (opts.detachWaitMs ?? 60_000);\n let interval = 1_000;\n while (true) {\n try {\n await withHetznerRetry(\n { method: 'deleteFirewall', retryOnAmbiguous: true, attemptTimeoutMs: 30_000 },\n () => client.deleteFirewall(firewallId),\n );\n return;\n } catch (err) {\n if (err instanceof HetznerApiError && (err.statusCode === 404 || err.code === 'not_found')) {\n return;\n }\n const stillAttached =\n err instanceof HetznerApiError &&\n (err.statusCode === 409 ||\n err.code === 'conflict' ||\n err.code === 'resource_in_use');\n if (stillAttached && Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, interval));\n interval = Math.min(interval * 2, 8_000);\n continue;\n }\n throw err;\n }\n }\n}\n\n/**\n * Normalize a source spec into a CIDR. Accepts:\n * - bare IPv4 → appends `/32`\n * - bare IPv6 → appends `/128`\n * - already-CIDR (anything with `/`) → returned as-is\n *\n * Whitespace is trimmed. Does **not** validate the address itself — that's\n * either the API's job (it'll reject bad CIDRs with a clear `validation`\n * error) or `detectEgressIp`'s job (it only returns valid IPv4/IPv6).\n */\nexport function normalizeSourceCidr(raw: string): string {\n const trimmed = raw.trim();\n if (trimmed.includes('/')) return trimmed;\n if (trimmed.includes(':')) return `${trimmed}/128`;\n return `${trimmed}/32`;\n}\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","resolve","opts","log","raceTimeout"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/_cloud-attach.ts","../src/provider/registry.ts","../src/wrapped-pty/run.ts","../src/pty/pty-backend.ts","../src/terminal/host.ts","../src/terminal/title.ts","../src/wrapped-pty/input-router.ts","../src/dashboard/sidebar.ts","../src/wrapped-pty/footer.ts","../src/wrapped-pty/prompt-client.ts","../src/lib/paste-image.ts","../src/lib/host-clipboard.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { appendFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { spinner } from '@clack/prompts';\nimport { DEFAULT_RELAY_PORT } from '@agentbox/sandbox-docker';\nimport type { BoxRecord } from '@agentbox/core';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { providerForBox } from '../provider/registry.js';\nimport { runWrappedAttach } from '../wrapped-pty/index.js';\nimport { pasteHostClipboardImage } from '../lib/paste-image.js';\nimport { clipboardCaptureAvailable } from '../lib/host-clipboard.js';\n\nconst RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;\n/** Give up reconnecting a dropped attach after this long (box likely gone). */\nconst RECONNECT_TIMEOUT_MS = 5 * 60_000;\n\n/** setTimeout that also resolves early if the signal aborts (no rejection). */\nfunction abortableSleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const t = setTimeout(resolve, ms);\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(t);\n resolve();\n },\n { once: true },\n );\n });\n}\n\n/**\n * Attach to (or create) a tmux session inside a cloud sandbox over SSH and\n * run an agent CLI inside it. Shared between `agentbox claude`/`codex`/\n * `opencode` so the SSH + tmux mechanics live in one place.\n *\n * The inner command tmux runs is `bash -lc 'exec <binary>'`:\n * - login shell so `/home/vscode/.local/bin` is on PATH and `/etc/profile.d/\n * agentbox.sh` exports `AGENTBOX_BOX_*` env;\n * - `exec` so the agent gets PID 2 (Ctrl-c in the agent kills the session\n * cleanly rather than dropping to bash).\n *\n * When `extraArgs` is non-empty, we base64-encode the argv (one arg per line)\n * and hand the inner shell a small `mapfile`-based launcher that reconstructs\n * the array — see `buildCloudAttachInnerCommand`. Base64 is alphanumeric+`/+=`\n * so it survives every shell-quoting layer (host single-quote, SSH, tmux,\n * bash) untouched, which avoids the 3-layer escaping mess the literal form\n * would otherwise require.\n */\nexport interface CloudAgentAttachArgs {\n box: BoxRecord;\n /** In-sandbox binary path or name (`claude`, `codex`, `opencode`). */\n binary: string;\n /** Tmux session name (e.g. `claude`). */\n sessionName: string;\n /** Mode label for the wrapper's footer. */\n mode: 'claude' | 'codex' | 'opencode';\n /**\n * Extra args the user typed after `--`. Passed through to the in-box agent\n * verbatim via a base64-encoded launcher. Limitation: args containing\n * literal `\\n` aren't supported (none of claude/codex/opencode flags do).\n */\n extraArgs?: string[];\n /**\n * Where to open the attached session in the host's terminal (`split`/`window`/\n * `tab`/`same`). Forwarded to `runWrappedAttach`. Daytona attaches are forced\n * to `same` for now because `provider.buildAttach()` may return a `cleanup`\n * that tears down per-call SSH tunnels — running cleanup while a detached\n * new pane still holds the connection would kill the pane. Hetzner's\n * ControlMaster is per-box-lifetime so spawn-and-detach is safe there.\n */\n openIn?: AttachOpenIn;\n}\n\n/**\n * Render the inner shell command tmux runs inside the cloud sandbox. Exported\n * so unit tests can exercise the base64 round-trip without spinning up SSH.\n *\n * Empty `extraArgs` keeps the no-args path identical to the pre-args\n * behaviour — `bash -lc 'exec <binary>'` with a backslash-space so the outer\n * shell-quoting layers don't split `exec` from the binary name.\n */\nexport function buildCloudAttachInnerCommand(binary: string, extraArgs?: string[]): string {\n if (!extraArgs || extraArgs.length === 0) {\n return `bash -lc exec\\\\ ${binary}`;\n }\n // One arg per line, base64-encoded. The launcher runs `mapfile -t A` against\n // the decoded stream, then `exec <binary> \"${A[@]}\"` so each arg lands as\n // its own argv element — quotes/spaces inside an arg are preserved exactly\n // because base64 is opaque to every outer shell quoting pass.\n const blob = Buffer.from(extraArgs.join('\\n'), 'utf8').toString('base64');\n // The decode feeds `mapfile` via a **here-string**, NOT process substitution\n // (`< <(…)`). Process substitution needs `/dev/fd/N`, and the Vercel Sandbox\n // (Firecracker microVM, AL2023) has no `/dev/fd` — so `mapfile -t A < <(…)`\n // fails with `/dev/fd/63: No such file or directory`, A stays empty, and the\n // agent launches with no args (the wizard's initial setup prompt is silently\n // dropped). A here-string is backed by a temp file, needs no `/dev/fd`, and\n // works on every backend (docker/daytona/hetzner unaffected). `$(…)` strips\n // the trailing newline `<<<` re-adds, so mapfile -t yields one element per\n // arg exactly as the join produced them.\n //\n // **bash -lc body MUST be single-quoted, not double-quoted.** When tmux\n // launches the session command, it goes through `/bin/sh -c <cmd>`. If we\n // double-quote, sh's parser sees `\"${A[@]}\"` and expands it eagerly —\n // before mapfile ever runs — to the empty string, so claude is invoked as\n // `claude \"\"` and the wizard's initial prompt is silently dropped. Single\n // quotes are inert in sh's parser: the literal `${A[@]}` (and `$(…)`) reach\n // bash, which runs them AFTER the outer sh layer. The outer shellSingle wrap\n // in renderInnerCommand re-escapes any internal `'` as `'\\''`; this body has\n // no single quotes (it uses double quotes around the here-string), so it\n // composes fine.\n return `bash -lc 'mapfile -t A <<< \"$(echo ${blob} | base64 -d)\"; exec ${binary} \"\\${A[@]}\"'`;\n}\n\nexport async function cloudAgentAttach(args: CloudAgentAttachArgs): Promise<void> {\n const provider = await providerForBox(args.box);\n if (!provider.buildAttach) {\n throw new Error(`provider '${provider.name}' does not support interactive attach`);\n }\n // Captured for the reconnect closure (TS won't preserve the narrowing above\n // inside a later-invoked callback).\n const buildAttach = provider.buildAttach.bind(provider);\n // Ensure the box is running before we attach. A cloud box can be stopped\n // out from under an attach — most notably `checkpoint --set-default`, which\n // snapshots and stops the sandbox. Without this, buildAttach runs against a\n // dead sandbox and the relay poller 502s (\"not listening on the requested\n // port\") forever while the user stares at \"Waiting for connection...\".\n // `provider.start` auto-resumes from snapshot and returns the record with\n // refreshed preview URLs / relay tokens, which we must use downstream.\n // Mirrors the docker attach path (unpause/start) and `checkpoint create`.\n let box = args.box;\n const state = await provider.probeState(box);\n if (state === 'missing') {\n throw new Error(`cloud sandbox for ${box.name} is missing; was it destroyed?`);\n }\n if (state !== 'running') {\n const s = spinner();\n s.start(state === 'paused' ? 'resuming box' : 'starting box');\n box = await provider.start(box);\n s.stop('box running');\n }\n const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);\n // Daytona-only: force inline attach. `spec.cleanup` would otherwise run as\n // soon as the host process returns from the spawn (before the new pane has\n // released the per-call SSH tunnel), breaking the detached attach.\n const safeOpenIn: AttachOpenIn | undefined =\n box.provider === 'daytona' ? 'same' : args.openIn;\n\n // New-terminal attaches (tab/window/split) re-invoke `agentbox <agent> attach`\n // in the fresh pane, and that re-invocation carries NO `extraArgs` — so for a\n // resume/teleport launch (`claude --resume <id>`, etc.) the session would\n // otherwise be created fresh, dropping the resumed session. Pre-create the\n // session detached here with the full command; the re-invoked attach then\n // finds it via `tmux has-session` and just attaches. (Inline attach runs the\n // full command itself, so it doesn't need this.)\n if (safeOpenIn && safeOpenIn !== 'same' && args.extraArgs && args.extraArgs.length > 0) {\n const pre = await provider.buildAttach(box, 'agent', {\n sessionName: args.sessionName,\n command,\n detached: true,\n });\n try {\n await runDetached(pre.argv, pre.env);\n } finally {\n if (pre.cleanup) await pre.cleanup();\n }\n }\n\n let spec = await provider.buildAttach(box, 'agent', {\n sessionName: args.sessionName,\n command,\n });\n // claude only, and only when this host can capture a clipboard image (macOS,\n // or a Linux desktop with xclip/wl-paste). Otherwise Ctrl+V forwards verbatim.\n const canPaste =\n args.mode === 'claude' && (await clipboardCaptureAvailable());\n\n // Re-establish the attach after the wrapper decides the box dropped (a vercel\n // checkpoint reboot, or a connection blip). Keep trying `provider.start` —\n // which resumes a stopped box and no-ops a running one — until it succeeds or\n // the deadline lapses. We deliberately DON'T bail on consecutive failures: a\n // box mid-snapshot rejects `start` for the whole (multi-minute) capture, and\n // that's indistinguishable from a destroyed box by error alone — so we lean on\n // the time budget (and the user's Ctrl+C, which aborts the signal) instead. On\n // a reboot this lands in a freshly-created tmux session (the snapshot is\n // filesystem-only); a blip on a still-running box re-attaches the same live\n // session. Returns null to give up (cancelled or timed out).\n const reconnect = async (\n signal: AbortSignal,\n ): Promise<{ command: string; argv: string[]; env?: Record<string, string> } | null> => {\n const deadline = Date.now() + RECONNECT_TIMEOUT_MS;\n let backoff = 500;\n for (;;) {\n if (signal.aborted || Date.now() > deadline) return null;\n try {\n box = await provider.start(box);\n break;\n } catch {\n await abortableSleep(backoff, signal);\n backoff = Math.min(backoff * 2, 5000);\n }\n }\n if (signal.aborted) return null;\n // Mint the fresh attach FIRST, then release the previous one's per-call\n // resources (SSH tunnel / token). Order matters: if buildAttach throws,\n // `spec` still points at the old spec (uncleaned), so the outer `finally`\n // cleans it exactly once — building-then-cleaning avoids the double-cleanup\n // that the reverse order would cause on a buildAttach failure.\n const prev = spec;\n spec = await buildAttach(box, 'agent', { sessionName: args.sessionName, command });\n if (prev.cleanup) {\n try {\n await prev.cleanup();\n } catch {\n // best-effort\n }\n }\n return { command: spec.argv[0]!, argv: spec.argv.slice(1), env: spec.env };\n };\n\n try {\n const code = await runWrappedAttach({\n container: box.name,\n command: spec.argv[0],\n dockerArgv: spec.argv.slice(1),\n env: spec.env,\n relayBaseUrl: RELAY_HOST_URL,\n boxId: box.id,\n boxName: box.name,\n projectIndex: box.projectIndex,\n mode: args.mode,\n detachable: true,\n openIn: safeOpenIn,\n reconnect,\n onError: (msg) => {\n // Non-fatal wrapper diagnostics (reconnect failures, give-ups, etc.) —\n // logged to a file because writing to stderr would corrupt the PTY.\n try {\n appendFileSync(\n join(homedir(), '.agentbox', 'logs', 'attach.log'),\n `${new Date().toISOString()} [${box.name}] ${msg}\\n`,\n );\n } catch {\n // best-effort\n }\n },\n onPasteImage: canPaste\n ? () => pasteHostClipboardImage(provider, box)\n : undefined,\n });\n process.exit(code);\n } finally {\n if (spec.cleanup) await spec.cleanup();\n }\n}\n\n/**\n * Run an attach-style argv non-interactively to completion (used for the\n * `detached` session pre-start). stdio is ignored — the remote command only\n * creates + configures the tmux session and exits; there's nothing to show.\n * Resolves on exit regardless of code (a non-zero here shouldn't block the\n * subsequent attach, which surfaces any real failure to the user).\n */\nfunction runDetached(argv: string[], env?: Record<string, string>): Promise<void> {\n return new Promise((resolve) => {\n const child = spawn(argv[0]!, argv.slice(1), {\n stdio: 'ignore',\n env: env ? { ...process.env, ...env } : process.env,\n });\n child.on('error', () => resolve());\n child.on('exit', () => resolve());\n });\n}\n","/**\n * Provider registry — resolves a `Provider` for either an existing box (from\n * its `provider` discriminator) or a fresh `create` (from --provider flag /\n * config / default). Lazy `import()` keeps the Daytona SDK out of the Docker\n * hot path.\n */\n\nimport type { EffectiveConfig } from '@agentbox/config';\nimport type { BoxRecord, Provider, ProviderName } from '@agentbox/core';\n\nexport type KnownProviderName = 'docker' | 'daytona' | 'hetzner' | 'vercel';\n\nconst KNOWN: readonly KnownProviderName[] = ['docker', 'daytona', 'hetzner', 'vercel'];\n\nexport function isKnownProvider(name: string): name is KnownProviderName {\n return (KNOWN as readonly string[]).includes(name);\n}\n\nexport async function getProvider(name: ProviderName): Promise<Provider> {\n switch (name) {\n case 'docker': {\n const mod = await import('@agentbox/sandbox-docker');\n return mod.dockerProvider;\n }\n case 'daytona': {\n // Single lazy import covers both the first-run prompt gate and the\n // provider itself — keeps the Daytona SDK off the Docker hot path.\n // The prompt is a no-op when env is already configured or stdin isn't\n // a TTY (scripted callers get the SDK's \"not configured\" error instead\n // of a hung prompt).\n const mod = await import('@agentbox/sandbox-daytona');\n await mod.ensureDaytonaCredentials();\n return mod.daytonaProvider;\n }\n case 'hetzner': {\n // Same lazy-import pattern as daytona. `ensureHetznerCredentials` walks\n // the user through `agentbox hetzner login` on first use. The base-\n // snapshot gate (`ensureHetznerBaseSnapshot`) is deliberately *not*\n // called here: it would chicken-and-egg `agentbox prepare --provider\n // hetzner` (which exists precisely to BUILD the snapshot). The gate\n // lives inside `backend.provision` instead — `prepare` calls the REST\n // client directly, never `provision`, so it slips past the gate while\n // `create`/`claude`/etc. still trip it.\n const mod = await import('@agentbox/sandbox-hetzner');\n await mod.ensureHetznerCredentials();\n return mod.hetznerProvider;\n }\n case 'vercel': {\n // Same lazy-import pattern. `ensureVercelCredentials` walks the user\n // through `agentbox vercel login` (OIDC or token trio) on first use. The\n // base-snapshot gate lives inside `backend.provision` (so `prepare` can\n // build it without tripping the gate), matching the hetzner shape.\n const mod = await import('@agentbox/sandbox-vercel');\n await mod.ensureVercelCredentials();\n return mod.vercelProvider;\n }\n default:\n throw new Error(`unknown sandbox provider: ${String(name)}`);\n }\n}\n\n/** Provider for an existing box record. Defaults to 'docker' for legacy records. */\nexport async function providerForBox(box: BoxRecord): Promise<Provider> {\n return getProvider(box.provider ?? 'docker');\n}\n\nexport interface CreateProviderChoice {\n /** Explicit --provider flag, if the command exposed one. */\n flag?: string;\n /** Effective config (carries box.provider for the layered default). */\n config: EffectiveConfig;\n}\n\n/**\n * Provider for a fresh `agentbox create`. Precedence: --provider flag >\n * box.provider config > 'docker'. Throws if the resolved name isn't registered.\n */\nexport async function providerForCreate(choice: CreateProviderChoice): Promise<Provider> {\n const flag = choice.flag?.trim();\n const name = (flag && flag.length > 0 ? flag : choice.config.box.provider) as ProviderName;\n if (typeof name !== 'string' || name.length === 0 || !isKnownProvider(name)) {\n throw new Error(\n `unknown sandbox provider \"${String(name)}\" (known: ${KNOWN.join(', ')})`,\n );\n }\n return getProvider(name);\n}\n","import { spawn, spawnSync } from 'node:child_process';\nimport { readBoxStatus } from '@agentbox/sandbox-docker';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { loadPtyBackend } from '../pty/pty-backend.js';\nimport { detectHostTerminal, spawnInNewTerminal } from '../terminal/host.js';\nimport { popTerminalTitle, pushTerminalTitle, setTerminalTitle } from '../terminal/title.js';\nimport {\n createInputRouter,\n type InputRouter,\n type LeaderAction,\n} from './input-router.js';\nimport {\n ALERT_BAND_ROWS,\n CURSOR_RESTORE,\n CURSOR_SAVE,\n cursorMoveTo,\n renderAlertBand,\n renderFooter,\n SYNC_BEGIN,\n SYNC_END,\n type AlertBandState,\n type FooterState,\n} from './footer.js';\nimport { postAnswer, subscribePrompts, type PromptStream } from './prompt-client.js';\nimport type { BoxNoticeEvent, PromptAskEvent } from '@agentbox/relay';\nimport type { ClaudeQuestionPayload } from '@agentbox/ctl';\n\nexport interface WrappedAttachOptions {\n /** Docker container name (only used for log lines). */\n container: string;\n /** Full docker argv (e.g. result of buildClaudeAttachArgv). */\n dockerArgv: string[];\n /**\n * The program to spawn for the PTY. Defaults to `'docker'` (the historical\n * behavior; `dockerArgv` is then the docker subcommand argv). Cloud boxes\n * pass `'ssh'` with the Daytona SSH argv instead.\n */\n command?: string;\n /** Extra env merged over `process.env` for the spawned child (e.g. the\n * Vercel provider's `VERCEL_AUTH_TOKEN` for the `sbx` CLI). */\n env?: Record<string, string>;\n /** Relay base URL — http://127.0.0.1:8787 in normal use. */\n relayBaseUrl: string;\n boxId: string;\n /** Friendly box name; rendered in the idle footer. */\n boxName: string;\n /** Per-project box index (BoxRecord.projectIndex). Used together with\n * boxId/boxName to read the per-box status.json for the live session\n * title. Pre-feature boxes lack it; absent is fine. */\n projectIndex?: number;\n /** Mode label affects the idle footer state label only. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the inner session can be detached (tmux-backed). Drives the\n * `Ctrl+a d` detach chord + footer hint. Defaults to `mode === 'claude'`\n * (claude is always tmux-backed); a tmux-backed `agentbox shell` passes\n * `true`, a `--no-tmux` shell leaves it false. */\n detachable?: boolean;\n /** Optional notice printed to stdout *after* the pty exits with code 0\n * (mirrors today's `formatDetachNotice` for `agentbox claude`). */\n detachNotice?: string;\n /** Optional sink for non-fatal errors that we'd otherwise swallow (Ctrl+a\n * action spawn failures, status-poll failures, unexpected prompt-capture\n * rejections). Callers wire this to their command log so post-mortem\n * inspection isn't blind. */\n onError?: (msg: string) => void;\n /** Where to open the attached session. When set to anything other than\n * `same` (or undefined) and the host shell is running inside tmux or iTerm2,\n * the attach runs in a fresh pane/tab/window and this function returns 0\n * without taking over the current terminal. Outside tmux/iTerm2 it falls\n * back to inline attach (the original behavior). */\n openIn?: AttachOpenIn;\n /** Optional host→box clipboard image paste, invoked when the user presses\n * Ctrl+V (wired for claude only). Ships the host clipboard image into the\n * box and loads it into the box's X11 clipboard; resolves with the outcome\n * so the footer can flash a result. The input router re-emits Ctrl+V after\n * this settles, so Claude Code reads the now-loaded clipboard. Omitted →\n * Ctrl+V forwards verbatim. */\n onPasteImage?: () => Promise<'pasted' | 'no-image' | 'error'>;\n /**\n * Re-establish the connection after the inner PTY drops. The wrapper decides\n * *whether* to call this (a checkpoint reboot — signalled by an active\n * `checkpoint` notice — or a non-zero exit; a clean exit-0 on a healthy box,\n * or an explicit `agentbox stop`, ends the wrapper instead). The callback then\n * resumes the box if needed and returns a fresh spawn spec to re-attach with,\n * or `null` to give up (box destroyed, cancelled, or timed out). `signal`\n * aborts if the user hits Ctrl+C while reconnecting. Only cloud attaches wire\n * this; docker omits it (exit-on-drop, unchanged).\n */\n reconnect?: (\n signal: AbortSignal,\n exitCode: number,\n ) => Promise<{ command: string; argv: string[]; env?: Record<string, string> } | null>;\n}\n\nconst FOOTER_ROWS = 1;\n/** Min visible inner-PTY rows below which we collapse the band back into the\n * one-line footer (today's behavior). Keeps a tiny terminal usable instead of\n * driving the inner program to a 0-row pane. */\nconst MIN_INNER_ROWS = 5;\nconst STATUS_POLL_INTERVAL_MS = 3000;\n/** Spinner advance cadence while a `notice` footer is active. */\nconst SPINNER_INTERVAL_MS = 120;\n/** How long the post-action confirmation flash stays in the footer. */\nconst FLASH_DURATION_MS = 2000;\n/** A respawned session that dies within this window counts as a rapid failure. */\nconst RAPID_RECONNECT_MS = 8000;\n/** Give up reconnecting after this many consecutive rapid failures (crash loop). */\nconst MAX_RAPID_RECONNECTS = 3;\n/** A drop within this long of a checkpoint notice clearing is still treated as\n * a reboot — only to bridge a tiny SSE-vs-pty event race (the box stops, and\n * thus the pty drops, while the notice is still active, so this rarely fires).\n * Kept short so a clean exit shortly after a checkpoint isn't misread. */\nconst CHECKPOINT_DROP_GRACE_MS = 4000;\n\n/** Per-action confirmation text shown in the footer flash. */\nconst ACTION_FLASH: Record<Exclude<LeaderAction, 'detach'>, string> = {\n screen: 'Opening noVNC viewer…',\n code: 'Launching VS Code / Cursor…',\n url: 'Opening box URL…',\n};\n\n/** Per-action `agentbox` subcommand: `<sub> <boxId> <...flags>`. */\nconst ACTION_CMD: Record<\n Exclude<LeaderAction, 'detach'>,\n { sub: string; flags: string[] }\n> = {\n screen: { sub: 'screen', flags: [] },\n // --no-wait: don't block on `wait-ready` — the box is already running.\n code: { sub: 'code', flags: ['--no-wait'] },\n url: { sub: 'url', flags: [] },\n};\n\n/** Recursive `agentbox <agent> attach <box> --attach-in same` argv for the\n * new-pane re-entry. Returns null for modes that don't have an `attach`\n * subcommand (notably `shell`), so the caller can skip new-pane spawning. */\nfunction buildAgentboxAttachArgv(\n mode: WrappedAttachOptions['mode'],\n boxName: string,\n): string[] | null {\n if (mode !== 'claude' && mode !== 'codex' && mode !== 'opencode') return null;\n return [mode, 'attach', boxName, '--attach-in', 'same'];\n}\n\n/**\n * Replace `spawnSync('docker', argv, { stdio: 'inherit' })` with a\n * node-pty wrapper that reserves the bottom row for a permission-prompt\n * footer. Falls back transparently to today's spawnSync behavior when\n * node-pty isn't available (optional dep missing), or when stdin/stdout\n * isn't a TTY (piping / non-interactive use).\n *\n * Returns the pty's exit code; caller `process.exit`s with it.\n */\nexport async function runWrappedAttach(opts: WrappedAttachOptions): Promise<number> {\n const command = opts.command ?? 'docker';\n const logErr = (msg: string): void => {\n opts.onError?.(msg);\n };\n\n // Open-in-new-terminal short-circuit: if the user asked for split/window/tab\n // and we're inside tmux or iTerm2, re-invoke `agentbox <agent> attach <box>\n // --attach-in same` in a fresh pane so the new pane runs the full wrapper\n // (footer + prompt channel) against the already-prepared session — same UX\n // as inline, just in a new pane. The host process then exits 0. Unknown\n // hosts, shell mode (no attach subcommand to recurse into), and spawn\n // failures fall through to the inline attach below.\n const openIn = opts.openIn ?? 'same';\n if (openIn !== 'same') {\n const subArgv = buildAgentboxAttachArgv(opts.mode, opts.boxName);\n const host = subArgv ? detectHostTerminal() : 'unknown';\n if (subArgv && host !== 'unknown' && process.argv[1]) {\n const r = await spawnInNewTerminal({\n host,\n mode: openIn,\n argv: [process.execPath, process.argv[1], ...subArgv],\n cwd: process.cwd(),\n title: opts.boxName,\n });\n if (r.launched) {\n process.stdout.write(r.note + '\\n');\n return 0;\n }\n if (r.error) logErr(r.error);\n // fall through to inline attach\n }\n }\n\n if (!process.stdout.isTTY || !process.stdin.isTTY) {\n // Non-interactive path: piping / scripts. Don't wrap — preserves\n // machine-readable stdout, no footer corruption.\n return runFallback(command, opts.dockerArgv, opts.env);\n }\n const backend = await loadPtyBackend();\n if (!backend) {\n // One-line stderr notice; preserves current behavior bit-for-bit.\n process.stderr.write(\n 'agentbox: permission prompts disabled (node-pty backend unavailable)\\n',\n );\n return runFallback(command, opts.dockerArgv, opts.env);\n }\n\n const cols = process.stdout.columns ?? 80;\n const rows = process.stdout.rows ?? 24;\n const innerRows = Math.max(1, rows - FOOTER_ROWS);\n\n let pty = backend.ptySpawn(command, opts.dockerArgv, {\n name: 'xterm-256color',\n cols,\n rows: innerRows,\n env: opts.env ? { ...process.env, ...opts.env } : process.env,\n });\n // When the current pty was (re)spawned — feeds the crash-loop guard below.\n let lastSpawnAt = Date.now();\n // Resize the current pty, tolerating a dead one: during a reconnect the old\n // pty has exited and the new one isn't spawned yet, and node-pty throws on a\n // closed pty. A throw here used to propagate out of the band-relayout in\n // reconnectFlow's finally and silently kill the wrapper. The next spawn uses\n // the correct size regardless.\n const resizePty = (c: number, r: number): void => {\n try {\n pty.resize(c, r);\n } catch {\n // pty exited / mid-respawn\n }\n };\n\n // Mirror the agent's session title to the host terminal/tab title (iTerm2\n // etc.). tmux swallows the inner OSC title (set-titles off), so the host\n // never sees it; we re-emit it ourselves from the polled status below. Save\n // the user's current title first so teardown can restore it. Seed with the\n // box name so the tab is named immediately, before the first status poll.\n pushTerminalTitle();\n let lastEmittedTitle = opts.boxName;\n setTerminalTitle(lastEmittedTitle);\n\n // claude is always tmux-backed; a tmux-backed `agentbox shell` opts in via\n // `detachable: true`, a `--no-tmux` shell leaves it false (nothing to detach).\n const detachable = opts.detachable ?? opts.mode === 'claude';\n\n // Idle footer = dashboard's statusLine() with a single hint (`Control+a:\n // Actions`, expanding to the chord menu while the leader is open). Session\n // title + claude activity come from the per-box status.json polled below.\n let leaderActive = false;\n const buildIdle = (sessionTitle?: string, claudeActivity?: string): FooterState => ({\n kind: 'idle',\n boxName: opts.boxName,\n sessionTitle,\n claudeActivity,\n mode: opts.mode,\n detachable,\n leaderActive,\n });\n let footerState: FooterState = buildIdle();\n let lastSessionTitle: string | undefined;\n let lastActivity: string | undefined;\n // Prompt + notice + question feed the alert band above the footer; flash +\n // leader stay in the footer. `recomputeFooter` keeps the footer at idle/flash;\n // `recomputeBand` derives the band visibility from prompt > notice > question.\n let capturingPrompt: PromptAskEvent | null = null;\n let activeNotice: BoxNoticeEvent | null = null;\n // The \"box rebooting — reconnecting…\" banner, owned solely by reconnectFlow.\n // Kept separate from `activeNotice` (which the SSE notice callbacks mutate) so\n // a real notice-set/clear arriving mid-reconnect can't clobber or prematurely\n // dismiss it. Takes precedence over everything while a reconnect is in flight.\n let reconnectBanner: string | null = null;\n let noticeFrame = 0;\n let questionPayload: ClaudeQuestionPayload | null = null;\n let bandState: AlertBandState | null = null;\n let bandReservedRows = 0; // 0 or ALERT_BAND_ROWS depending on band visibility\n let spinnerTimer: ReturnType<typeof setInterval> | null = null;\n // Transient confirmation shown after a Ctrl+a action fires.\n let flashMessage: string | null = null;\n let flashTimer: ReturnType<typeof setTimeout> | null = null;\n // True while the inner pty has dropped and we're re-establishing it. Stdin is\n // swallowed (no live pty to write to) except Ctrl+C, which aborts the wait.\n let reconnecting = false;\n let reconnectAbort: AbortController | null = null;\n // Set when the user deliberately detaches (Ctrl+a d) — that exit must end the\n // wrapper, never trigger a reconnect.\n let userDetached = false;\n // When a `checkpoint` notice last set / cleared (epoch ms; 0 = never). A pty\n // drop while one is active, or within CHECKPOINT_DROP_GRACE_MS of it clearing,\n // is a box reboot (snapshot stopped it) → reconnect; otherwise an exit-0 drop\n // on a healthy box is a clean session end → exit. Plain numbers so the loop's\n // check doesn't trip TS's null-narrowing on `activeNotice` (assigned only in a\n // callback).\n let checkpointNoticeAt = 0;\n let checkpointNoticeClearedAt = 0;\n\n /** Reserved rows above the inner pty: footer (always 1) + band (3 or 0). */\n const reservedRows = (): number => FOOTER_ROWS + bandReservedRows;\n /** Whether the current terminal has room for the band without collapsing\n * the inner pty below `MIN_INNER_ROWS`; gates the band on tiny terminals. */\n const bandFits = (): boolean => {\n const rs = process.stdout.rows ?? rows;\n return rs - FOOTER_ROWS - ALERT_BAND_ROWS >= MIN_INNER_ROWS;\n };\n\n // Lazy SGR mirror: when the inner pty's most recent attribute is bright\n // bold, our footer paint won't reset it correctly via the inner program's\n // next byte. We always end the chrome with SGR reset, but the inner program\n // may be in the middle of a graphics run when the redraw happens — wrap the\n // redraw in cursor save/restore + sync output so the inner program never\n // sees our cursor moves and the user sees one atomic frame.\n const redrawChrome = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const footerLine = renderFooter(footerState, cs);\n let payload = SYNC_BEGIN + CURSOR_SAVE;\n if (bandReservedRows > 0 && bandState) {\n const bandLines = renderAlertBand(bandState, cs, bandReservedRows);\n for (let i = 0; i < bandLines.length; i++) {\n const row = rs - FOOTER_ROWS - (bandLines.length - i);\n payload += cursorMoveTo(row + 1, 1) + bandLines[i];\n }\n }\n payload += cursorMoveTo(rs, 1) + footerLine + CURSOR_RESTORE + SYNC_END;\n process.stdout.write(payload);\n };\n\n // Derive `footerState` from flash > leader/idle. Prompt/notice/question are\n // surfaced in the alert band above the footer (see `recomputeBand`); the\n // footer keeps showing the calm status bar so the user always has context.\n // **Min-size fallback**: when the band collapses on a tiny terminal\n // (`bandReservedRows === 0` while `bandState != null`), prompt and notice\n // fall back to the pre-band footer-replacement so they're not lost (the\n // question state has no one-line footer renderer — sidebar marker only).\n const recomputeFooter = (): void => {\n const collapsed = bandState !== null && bandReservedRows === 0;\n if (collapsed && reconnectBanner) {\n footerState = { kind: 'notice', message: reconnectBanner, frame: noticeFrame };\n } else if (collapsed && capturingPrompt) {\n footerState = { kind: 'prompt', prompt: capturingPrompt };\n } else if (collapsed && activeNotice) {\n footerState = { kind: 'notice', message: activeNotice.message, frame: noticeFrame };\n } else if (flashMessage) {\n footerState = { kind: 'flash', message: flashMessage };\n } else {\n footerState = buildIdle(lastSessionTitle, lastActivity);\n }\n };\n\n // Derive the band's content + visibility from prompt > notice > question.\n // Priority chain: a relay prompt hard-blocks an in-box RPC (most urgent);\n // a notice means the box is frozen for a snapshot (loud animated banner);\n // a question is the agent waiting for the user. When nothing is active the\n // band collapses entirely.\n const recomputeBand = (): void => {\n if (reconnectBanner) {\n // While reconnecting nothing else is actionable (stdin is swallowed, the\n // box is down), so the banner outranks prompt/notice/question.\n bandState = { kind: 'notice', message: reconnectBanner, frame: noticeFrame };\n } else if (capturingPrompt) {\n bandState = { kind: 'prompt', prompt: capturingPrompt };\n } else if (activeNotice) {\n bandState = { kind: 'notice', message: activeNotice.message, frame: noticeFrame };\n } else if (questionPayload) {\n bandState = { kind: 'question', question: questionPayload };\n } else {\n bandState = null;\n }\n };\n\n /** Resize the inner pty + reapply the scroll region for the current reserved\n * rows, then clear any rows that just changed ownership (the freed region\n * when the band collapses; the band area itself when it appears). Called\n * after `recomputeBand` whenever the band visibility flips. */\n const relayoutForBand = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const inner = Math.max(1, rs - reservedRows());\n resizePty(cs, inner);\n process.stdout.write(`\\x1b[1;${String(inner)}r`);\n // Clear the chrome area (band + footer rows) so stale agent output left\n // over from the previous scroll region doesn't show through under the\n // newly painted band/footer.\n let clear = SYNC_BEGIN + CURSOR_SAVE;\n for (let r = inner + 1; r <= rs; r++) clear += cursorMoveTo(r, 1) + '\\x1b[2K';\n clear += CURSOR_RESTORE + SYNC_END;\n process.stdout.write(clear);\n };\n\n /** Re-derive the band state and, if visibility changed, resize + reflow.\n * Always finishes with a chrome redraw so the band/footer are repainted.\n * Also re-derives the footer so the min-size fallback (prompt/notice in\n * the footer when the band collapses) keeps in sync. */\n const applyBandChange = (): void => {\n recomputeBand();\n const wantRows = bandState && bandFits() ? ALERT_BAND_ROWS : 0;\n if (wantRows !== bandReservedRows) {\n bandReservedRows = wantRows;\n relayoutForBand();\n }\n recomputeFooter();\n redrawChrome();\n };\n\n const startSpinner = (): void => {\n if (spinnerTimer) return;\n spinnerTimer = setInterval(() => {\n noticeFrame++;\n // Advance the spinner frame whenever a notice is the live band; if the\n // notice was outranked by a prompt the frame still advances so it\n // resumes mid-animation when the prompt clears.\n if (bandState?.kind === 'notice') {\n bandState = { kind: 'notice', message: bandState.message, frame: noticeFrame };\n // When the band is collapsed on a tiny terminal the notice renders\n // through `footerState` instead, so re-derive it to pick up the new\n // frame — otherwise the footer-fallback spinner glyph freezes.\n if (bandReservedRows === 0) recomputeFooter();\n redrawChrome();\n }\n }, SPINNER_INTERVAL_MS);\n if (typeof spinnerTimer.unref === 'function') spinnerTimer.unref();\n };\n const stopSpinner = (): void => {\n if (spinnerTimer) {\n clearInterval(spinnerTimer);\n spinnerTimer = null;\n }\n };\n\n // Wire pty -> stdout. The inner program writes raw bytes; we forward as-is.\n // The outer terminal has `rows` real rows, but the pty thinks it has `innerRows`.\n // The inner program's writes can still physically touch row `rows` (our footer\n // row) via: (1) scroll when its bottom line emits a newline — the terminal\n // scrolls the whole screen and row `rows` gets cleared; (2) clear-screen\n // sequences like `\\x1b[2J`; (3) alt-screen entry `\\x1b[?1049h`; (4) column\n // wraparound from the inner program's last row. The scroll-region setup\n // below limits (1); always-repaint here handles the rest. Each redraw is\n // wrapped in synchronized output (DECSET 2026) so the user never sees a\n // half-painted frame on terminals that support it (iTerm2/WezTerm/kitty/\n // Apple Terminal/Ghostty).\n // Wire the inner pty's output -> stdout. Factored out so a respawned pty\n // (after a reconnect) can be re-wired the same way.\n const wireOutput = (): void => {\n pty.onData((d: string) => {\n process.stdout.write(d);\n redrawChrome();\n });\n };\n wireOutput();\n\n // Ctrl+a leader chord map — keys mirror the dashboard's (`c`/`s`/`u`).\n // A detachable (tmux-backed) session also gets `d: detach`; a plain\n // `--no-tmux` shell has nothing to detach from.\n const leaderChords: Record<string, LeaderAction> = detachable\n ? { c: 'code', s: 'screen', u: 'url', d: 'detach' }\n : { c: 'code', s: 'screen', u: 'url' };\n\n // Run a Ctrl+a leader action. `detach` writes the tmux detach sequence to\n // the pty (`\\x02` = Ctrl+b, tmux's secondary prefix; `d` = detach-client) —\n // the attach process then exits 0 and teardown runs normally. The other\n // actions shell out to the real `agentbox` subcommand, detached, so the\n // long-running open/launch never blocks (or corrupts) this terminal.\n const runAction = (name: LeaderAction): void => {\n if (name === 'detach') {\n if (!reconnecting) {\n userDetached = true;\n pty.write('\\x02d');\n }\n return;\n }\n const cliEntry = process.argv[1];\n if (typeof cliEntry === 'string' && cliEntry.length > 0) {\n const cmd = ACTION_CMD[name];\n try {\n spawn(\n process.execPath,\n [cliEntry, cmd.sub, opts.boxId, ...cmd.flags],\n { detached: true, stdio: 'ignore' },\n ).unref();\n } catch (e) {\n // Best-effort — the footer flash still shows. Surface for inspection.\n logErr(`leader-action spawn (${name}) failed: ${(e as Error).message}`);\n }\n }\n flashMessage = ACTION_FLASH[name];\n if (flashTimer) clearTimeout(flashTimer);\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawChrome();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawChrome();\n };\n\n // Ctrl+V image paste: hold a \"Pasting image…\" notice in the footer while the\n // host clipboard image is shipped into the box, then flash the outcome. The\n // input router re-emits the Ctrl+V once this resolves, so Claude reads the\n // now-loaded box clipboard. Never throws — failures degrade to a flash.\n const handlePasteImage = async (): Promise<void> => {\n if (!opts.onPasteImage) return;\n if (flashTimer) {\n clearTimeout(flashTimer);\n flashTimer = null;\n }\n flashMessage = 'Pasting image…';\n recomputeFooter();\n redrawChrome();\n let result: 'pasted' | 'no-image' | 'error' = 'error';\n try {\n result = await opts.onPasteImage();\n } catch (e) {\n logErr(`paste-image failed: ${(e as Error).message}`);\n }\n flashMessage =\n result === 'pasted'\n ? 'Image pasted'\n : result === 'no-image'\n ? 'No image in clipboard'\n : 'Image paste failed';\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawChrome();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawChrome();\n };\n\n // Wire stdin -> pty (through the router so prompts + the leader can intercept).\n const router: InputRouter = createInputRouter({\n onForward: (b) => {\n // While reconnecting there's no live pty to write to. Swallow input, but\n // let a bare Ctrl+C (a lone 0x03 byte) abort the reconnect wait so the\n // user can bail out. Match exactly — a pasted/coalesced buffer that merely\n // contains 0x03 must not trip the abort.\n if (reconnecting) {\n if (b.length === 1 && b[0] === 0x03) reconnectAbort?.abort();\n return;\n }\n // node-pty wants utf8 strings; stdin is binary safe via Buffer.\n pty.write(b.toString('utf8'));\n },\n onAnswer: (body) => {\n // Fire-and-forget; the relay-side route is idempotent. We don't\n // block the input flow on the network roundtrip.\n void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });\n capturingPrompt = null;\n applyBandChange();\n },\n leaderChords,\n onLeaderChange: (open) => {\n leaderActive = open;\n recomputeFooter();\n redrawChrome();\n },\n onAction: (name) => {\n runAction(name);\n },\n onPasteImage: opts.onPasteImage ? handlePasteImage : undefined,\n });\n\n if (process.stdin.isTTY) process.stdin.setRawMode(true);\n process.stdin.resume();\n const onStdinData = (chunk: Buffer): void => {\n router.feed(chunk);\n };\n process.stdin.on('data', onStdinData);\n\n // Resize: keep the pty `reservedRows` shorter than the host terminal; the\n // footer owns the last row directly and the band (when active) owns the 3\n // rows above it. Re-apply the scroll region too — most terminals reset\n // DECSTBM on resize. The bandFits() check downgrades band → 0 if the new\n // size is too small to host both.\n const onResize = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n // Re-evaluate band visibility against the new size first; a now-too-small\n // terminal collapses the band, a now-big-enough one re-opens it. Refresh\n // the footer so the collapsed-band fallback (prompt/notice in the footer)\n // tracks the new reserve.\n bandReservedRows = bandState && bandFits() ? ALERT_BAND_ROWS : 0;\n const inner = Math.max(1, rs - reservedRows());\n resizePty(cs, inner);\n process.stdout.write(`\\x1b[1;${String(inner)}r`);\n recomputeFooter();\n redrawChrome();\n };\n process.stdout.on('resize', onResize);\n\n // SSE: subscribe to the relay's prompt stream for this box.\n const stream: PromptStream = subscribePrompts({\n relayBaseUrl: opts.relayBaseUrl,\n boxId: opts.boxId,\n onPrompt: (ev: PromptAskEvent) => {\n capturingPrompt = ev;\n applyBandChange();\n // capture() returns a Promise that resolves with the answer body; the\n // input-router's onAnswer callback already POSTs and resets the band.\n // We just need to await so unhandled rejections (router.abort) don't\n // crash the process.\n router.capture(ev).catch((e: unknown) => {\n // Expected reasons: sibling answered ('resolved-elsewhere'), pty exit.\n // Anything else is a real bug worth surfacing.\n const msg = e instanceof Error ? e.message : String(e);\n if (msg !== 'resolved-elsewhere') {\n logErr(`prompt capture rejected: ${msg}`);\n }\n });\n },\n onResolved: (id: string) => {\n // Clear band if it's still showing this id (sibling wrapper won).\n if (capturingPrompt && capturingPrompt.id === id) {\n capturingPrompt = null;\n router.abort('resolved-elsewhere');\n applyBandChange();\n }\n },\n onNotice: (ev: BoxNoticeEvent) => {\n if (ev.kind === 'checkpoint') checkpointNoticeAt = Date.now();\n activeNotice = ev;\n startSpinner();\n applyBandChange();\n },\n onNoticeCleared: (id: string) => {\n if (activeNotice && activeNotice.id === id) {\n if (activeNotice.kind === 'checkpoint') checkpointNoticeClearedAt = Date.now();\n activeNotice = null;\n stopSpinner();\n applyBandChange();\n }\n },\n });\n\n // Poll the box's status.json for `claude.sessionTitle` so the idle\n // footer can show what claude set as its terminal title (mirrors the\n // dashboard's sidebar entry). Best-effort — paused/stopped boxes and\n // pre-status-feature boxes return null and we just keep the previous\n // title (or no title).\n const pollStatus = async (): Promise<void> => {\n try {\n const status = await readBoxStatus({\n id: opts.boxId,\n name: opts.boxName,\n projectIndex: opts.projectIndex,\n });\n // Read the title/activity from the body of the agent we attached to;\n // shell mode has no agent session so it keeps the box-name title.\n const body =\n opts.mode === 'codex'\n ? status?.codex\n : opts.mode === 'opencode'\n ? status?.opencode\n : opts.mode === 'shell'\n ? undefined\n : status?.claude;\n const nextTitle = body?.sessionTitle?.trim() || undefined;\n const nextActivity = body?.state || undefined;\n // Mirror the live title to the host terminal/tab, falling back to the box\n // name until the agent sets one. Deduped so we don't spam the terminal.\n const desiredTitle = nextTitle ?? opts.boxName;\n if (desiredTitle !== lastEmittedTitle) {\n lastEmittedTitle = desiredTitle;\n setTerminalTitle(desiredTitle);\n }\n // Surface claude's AskUserQuestion payload to the band when the agent\n // is in `question` state; clear it on any other state. Only meaningful\n // for claude mode (codex/opencode have no question payload). The band's\n // priority chain (`recomputeBand`) demotes question below prompt/notice,\n // so it only shows when nothing more urgent is pending.\n const nextQuestion =\n opts.mode === 'claude' && status?.claude.state === 'question'\n ? (status.claude.question ?? null)\n : null;\n const questionChanged =\n (nextQuestion?.capturedAt ?? null) !== (questionPayload?.capturedAt ?? null);\n if (questionChanged) {\n questionPayload = nextQuestion;\n applyBandChange();\n }\n if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;\n lastSessionTitle = nextTitle;\n lastActivity = nextActivity;\n if (footerState.kind === 'idle') {\n recomputeFooter();\n redrawChrome();\n }\n } catch (e) {\n // readBoxStatus already swallows the common cases (paused/stopped/pre-feature);\n // anything reaching here is unexpected and worth a log line.\n logErr(`status poll failed: ${(e as Error).message}`);\n }\n };\n void pollStatus();\n const statusTimer = setInterval(() => {\n void pollStatus();\n }, STATUS_POLL_INTERVAL_MS);\n if (typeof statusTimer.unref === 'function') statusTimer.unref();\n\n // Restrict the outer terminal's scroll region to rows 1..innerRows so the\n // inner program's natural scrolling (bottom-line newline) doesn't push\n // content into our footer row. DECSTBM also resets the cursor to (1,1) on\n // some terminals, so we follow it with a cursor restore. Reverted in\n // teardown via `\\x1b[r` (clear scroll region -> full screen).\n process.stdout.write(`\\x1b[1;${String(innerRows)}r`);\n\n // Plain shell (`--no-tmux`): bash doesn't enter alt-screen, so without help\n // the user's pre-shell host-terminal content stays visible above bash's\n // freshly drawn prompt. Clear the visible screen + home the cursor before\n // the pty's first write. We don't touch scrollback (`\\x1b[3J`) — the user's\n // pre-shell context stays scroll-up-able. Claude and the tmux-backed shell\n // skip this: they enter their own alt-screen on init and would just\n // overpaint anyway (clearing first would only flicker).\n if (opts.mode === 'shell' && !detachable) {\n process.stdout.write('\\x1b[H\\x1b[2J');\n }\n\n // Initial paint so the idle footer appears immediately.\n redrawChrome();\n\n /**\n * Keep the wrapper open across a drop: show the \"box rebooting — reconnecting…\"\n * band and let `opts.reconnect` resume the box + hand back a fresh spawn spec\n * (or null to give up). Only called once the loop has decided the drop is a\n * reboot/blip, so the band always shows here.\n */\n const reconnectFlow = async (\n code: number,\n ): Promise<{ command: string; argv: string[]; env?: Record<string, string> } | null> => {\n const controller = new AbortController();\n reconnecting = true; // swallow stdin (no live pty) while we re-establish\n reconnectAbort = controller;\n reconnectBanner = 'box rebooting — reconnecting…';\n startSpinner();\n applyBandChange();\n let spec: { command: string; argv: string[]; env?: Record<string, string> } | null = null;\n try {\n spec = (await opts.reconnect?.(controller.signal, code)) ?? null;\n } catch (e) {\n logErr(`reconnect failed: ${(e as Error).message}`);\n } finally {\n reconnecting = false;\n reconnectAbort = null;\n reconnectBanner = null;\n // A real checkpoint notice may have arrived via SSE during the reconnect;\n // keep the spinner running if so, only stop it when nothing animates.\n if (!activeNotice) stopSpinner();\n applyBandChange();\n }\n if (spec) {\n flashMessage = 'reconnected';\n if (flashTimer) clearTimeout(flashTimer);\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawChrome();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawChrome();\n }\n return spec;\n };\n\n // Wait for the pty to exit. Reconnect only on a real drop: a checkpoint reboot\n // (an active/just-cleared `checkpoint` notice — vercel's snapshot stops the\n // box and its `sbx exec` exits 0, so the notice is the signal, not the code)\n // or a non-zero exit (connection blip). A clean exit-0 on a healthy box (agent\n // exited) or an explicit `agentbox stop` (no notice) ends the wrapper. A\n // crash-loop guard bails if respawned sessions keep dying immediately.\n let exitCode = 0;\n let rapidFails = 0;\n for (;;) {\n const code = await new Promise<number>((resolve) => {\n pty.onExit(({ exitCode }) => resolve(exitCode));\n });\n if (userDetached || !opts.reconnect) {\n exitCode = code;\n break;\n }\n // Checkpoint notice currently active (set more recently than cleared) or\n // cleared within the grace window → this drop is a box reboot.\n const checkpointing =\n checkpointNoticeAt > checkpointNoticeClearedAt ||\n Date.now() - checkpointNoticeClearedAt < CHECKPOINT_DROP_GRACE_MS;\n if (!checkpointing && code === 0) {\n exitCode = code; // clean session end on a healthy box\n break;\n }\n rapidFails = Date.now() - lastSpawnAt < RAPID_RECONNECT_MS ? rapidFails + 1 : 0;\n if (rapidFails >= MAX_RAPID_RECONNECTS) {\n logErr('giving up reconnect after repeated rapid failures');\n exitCode = code;\n break;\n }\n const next = await reconnectFlow(code);\n if (!next) {\n exitCode = code;\n break;\n }\n const rsNow = process.stdout.rows ?? rows;\n const innerNow = Math.max(1, rsNow - reservedRows());\n pty = backend.ptySpawn(next.command, next.argv, {\n name: 'xterm-256color',\n cols: process.stdout.columns ?? cols,\n rows: innerNow,\n env: next.env ? { ...process.env, ...next.env } : process.env,\n });\n wireOutput();\n lastSpawnAt = Date.now();\n // The checkpoint that caused this drop is consumed — clear its tracking so a\n // clean exit in the reconnected session isn't misclassified as another\n // reboot (a fresh checkpoint sets these again via onNotice).\n checkpointNoticeAt = 0;\n checkpointNoticeClearedAt = 0;\n // Re-assert the scroll region (the fresh session repaints into it) and\n // repaint chrome so the footer/band survive the respawn.\n process.stdout.write(`\\x1b[1;${String(innerNow)}r`);\n redrawChrome();\n }\n\n // Teardown order: stop reading stdin, restore cooked mode, drop SSE,\n // dispose the router (rejects any in-flight capture), clear the footer\n // row so the shell prompt below doesn't sit on top of our bar.\n process.stdin.off('data', onStdinData);\n process.stdout.off('resize', onResize);\n clearInterval(statusTimer);\n stopSpinner();\n if (flashTimer) clearTimeout(flashTimer);\n if (process.stdin.isTTY) process.stdin.setRawMode(false);\n process.stdin.pause();\n stream.close();\n router.dispose();\n const rsFinal = process.stdout.rows ?? rows;\n const csFinal = process.stdout.columns ?? cols;\n // Clear the scroll region first so the cursor moves below can reach row N\n // without the terminal trying to keep them inside the smaller region.\n // Then erase every row owned by chrome (band + footer) so a stale band\n // doesn't sit above the next shell prompt; return the cursor afterwards.\n let teardownPaint = '\\x1b[r';\n for (let r = rsFinal - bandReservedRows; r <= rsFinal; r++) {\n if (r >= 1) teardownPaint += cursorMoveTo(r, 1) + '\\x1b[2K';\n }\n teardownPaint += cursorMoveTo(rsFinal, csFinal);\n process.stdout.write(teardownPaint);\n // Restore the host terminal/tab title we saved at attach time.\n popTerminalTitle();\n\n if (exitCode === 0 && opts.detachNotice) {\n // Match the cosmetic of the old attachClaudeSession: overwrite tmux's\n // own `[detached]` line if it's visible, then print the reattach hint.\n process.stdout.write('\\x1b[1A\\x1b[2K\\r' + opts.detachNotice + '\\n');\n }\n return exitCode;\n}\n\n/**\n * Fallback when node-pty is unavailable or stdio isn't a TTY. Identical to\n * today's call: blocking spawnSync with inherited stdio.\n */\nfunction runFallback(command: string, argv: string[], env?: Record<string, string>): number {\n const child = spawnSync(command, argv, {\n stdio: 'inherit',\n env: env ? { ...process.env, ...env } : process.env,\n });\n return child.status ?? 0;\n}\n","import type { Terminal as XtermTerminal } from '@xterm/headless';\n\n/**\n * The `@xterm/headless` `Terminal` class. Injected (not imported) because\n * @xterm/headless is CJS — a static ESM named import breaks Node's loader for\n * the whole CLI, so callers dynamic-import it and pass the ctor through.\n *\n * Used by the dashboard (for its xterm-headless screen-state mirror). The\n * wrapped-pty wrapper does not need it — the inner program writes raw bytes\n * directly to the user's terminal, no parsing required.\n */\nexport type TerminalCtor = new (opts: {\n cols: number;\n rows: number;\n allowProposedApi: boolean;\n scrollback: number;\n convertEol: boolean;\n}) => XtermTerminal;\n\n/**\n * Minimal shape of a node-pty IPty (avoids a hard type dep on the optional\n * module — node-pty is in optionalDependencies, may not be installed).\n */\nexport interface IPtyLike {\n onData(cb: (d: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n write(d: string): void;\n resize(cols: number, rows: number): void;\n kill(): void;\n}\n\nexport type PtySpawn = (\n file: string,\n args: string[],\n opts: { name: string; cols: number; rows: number; env: NodeJS.ProcessEnv },\n) => IPtyLike;\n\nexport interface PtyBackend {\n ptySpawn: PtySpawn;\n /** Present for callers that also need the xterm headless ctor (dashboard). */\n termCtor: TerminalCtor;\n}\n\n/**\n * Dynamic-load the optional pty + xterm/headless backends. Returns null\n * when either prebuild is missing (we don't throw — callers decide how to\n * degrade). Centralized here so the dashboard and the wrapped-pty wrapper\n * use the same exact load dance.\n */\nexport async function loadPtyBackend(): Promise<PtyBackend | null> {\n try {\n const ptyMod = (await import('@homebridge/node-pty-prebuilt-multiarch')) as Record<\n string,\n unknown\n >;\n const xtermMod = (await import('@xterm/headless')) as Record<string, unknown>;\n const spawn =\n (ptyMod['spawn'] as unknown) ??\n (ptyMod['default'] as Record<string, unknown> | undefined)?.['spawn'];\n const Terminal =\n (xtermMod['Terminal'] as unknown) ??\n (xtermMod['default'] as Record<string, unknown> | undefined)?.['Terminal'];\n if (typeof spawn !== 'function' || typeof Terminal !== 'function') {\n return null;\n }\n return {\n ptySpawn: spawn as unknown as PtySpawn,\n termCtor: Terminal as unknown as TerminalCtor,\n };\n } catch {\n return null;\n }\n}\n","import { spawn } from 'node:child_process';\nimport type { AttachOpenIn } from '@agentbox/config';\n\nexport type HostTerminal = 'tmux' | 'iterm2' | 'unknown';\n\n/**\n * Identify the user's host terminal from env vars. tmux wins over iTerm2 even\n * when nested — when `TMUX` is set, the tmux CLI is the right primitive (it can\n * split the current pane / open a new window without going through AppleScript).\n *\n * macOS-only by design: the CLI itself is macOS-only (see CLAUDE.md), so we\n * don't try to recognize gnome-terminal / alacritty / Windows Terminal.\n */\nexport function detectHostTerminal(env: NodeJS.ProcessEnv = process.env): HostTerminal {\n const tmux = env['TMUX'];\n if (tmux && tmux.length > 0) return 'tmux';\n const termProgram = env['TERM_PROGRAM'];\n if (termProgram === 'iTerm.app') return 'iterm2';\n return 'unknown';\n}\n\n/** Single-quote a string so it survives a shell parse intact. */\nfunction shellQuote(s: string): string {\n if (s.length === 0) return \"''\";\n // Replace any internal `'` with the four-byte sequence `'\\''` (close, escaped\n // quote, reopen). Cheaper than picking double-quotes — no $/`/\\ to worry about.\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Escape a string for embedding in a double-quoted AppleScript literal. */\nfunction appleScriptEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/** Join an argv into a single shell-safe command line. */\nfunction shellJoin(argv: string[]): string {\n return argv.map(shellQuote).join(' ');\n}\n\nexport interface SpawnInNewTerminalArgs {\n host: Exclude<HostTerminal, 'unknown'>;\n /** Where to open the session in that host's terminology. `'same'` is rejected\n * by the caller — we never produce `same` here. */\n mode: Exclude<AttachOpenIn, 'same'>;\n /** Full argv to run in the new pane: `[program, ...args]`. The first element\n * is the binary; the rest are passed verbatim. */\n argv: string[];\n /** Working directory for the new pane. Passed to tmux via `-c` and prepended\n * to the iTerm2 command as `cd <cwd> && exec …`. */\n cwd: string;\n /** Short title for the new tmux window / iTerm2 tab when applicable. */\n title: string;\n}\n\nexport interface SpawnInNewTerminalResult {\n launched: boolean;\n /** One-line user-facing message printed to the host's stdout on success.\n * Empty string when `launched` is false. */\n note: string;\n /** stderr captured from the spawner, when `launched` is false. Used only for\n * the command log; not surfaced to the user. */\n error?: string;\n}\n\n/**\n * Open a fresh tmux pane / iTerm2 split-tab-window and run `<command> <argv...>`\n * there. Returns synchronously after the new pane is requested — the inner\n * command runs in its own terminal and is no longer this process's child.\n *\n * On failure (tmux/osascript exits non-zero, or wasn't found), the caller is\n * expected to fall back to inline attach.\n */\nexport async function spawnInNewTerminal(\n args: SpawnInNewTerminalArgs,\n): Promise<SpawnInNewTerminalResult> {\n if (args.host === 'tmux') return spawnInTmux(args);\n return spawnInITerm2(args);\n}\n\nasync function spawnInTmux(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // `-c <cwd>` drops the new pane in the host pane's directory so the\n // recursive `agentbox` invocation can resolve project-scoped refs (and so\n // any commands the user runs after detaching start somewhere sensible).\n // The command is passed as a single shell-quoted positional after `--`;\n // tmux hands it to /bin/sh -c, which is why each argv element needs\n // single-quoting.\n const cmdStr = shellJoin(args.argv);\n let tmuxArgv: string[];\n let noteKind: string;\n if (args.mode === 'split') {\n tmuxArgv = ['split-window', '-h', '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux split';\n } else {\n // `window` and `tab` both map to tmux's only \"another full screen\" primitive.\n tmuxArgv = ['new-window', '-n', args.title, '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux window';\n }\n const r = await runQuiet('tmux', tmuxArgv);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `tmux ${tmuxArgv.join(' ')} exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind} — Ctrl+a d to detach the box's tmux session.`,\n };\n}\n\nasync function spawnInITerm2(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // iTerm2 launches `command` through a shell, but doesn't honor a starting\n // directory parameter on its AppleScript verbs. Prepend `cd <cwd> && exec`\n // so the new tab/window/split lands in the host pane's cwd and replaces\n // the launching shell with the agentbox process.\n const inner = shellJoin(args.argv);\n const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${inner}`;\n const cmdLit = `\"${appleScriptEscape(cmdLine)}\"`;\n\n // Always create the tab/window/split first, then `write text` into its\n // session. The `... with default profile command \"<cmd>\"` parameter form is\n // unreliable on iTerm 3.7 betas — it fails (returns `missing value`) and the\n // command bounces to Terminal.app instead of running in iTerm. The\n // create-then-write-text form is the supported path and works across\n // versions, so every mode uses it.\n let lines: string[];\n let noteKind: string;\n switch (args.mode) {\n case 'split':\n lines = [\n 'tell application \"iTerm\"',\n ' tell current session of current window to set _s to (split vertically with default profile)',\n ` tell _s to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 split';\n break;\n case 'tab':\n lines = [\n 'tell application \"iTerm\"',\n ' tell current window to set _t to (create tab with default profile)',\n ` tell current session of _t to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 tab';\n break;\n case 'window':\n lines = [\n 'tell application \"iTerm\"',\n ' set _w to (create window with default profile)',\n ` tell current session of _w to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 window';\n break;\n }\n\n const r = await runQuiet('osascript', ['-e', lines.join('\\n')]);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `osascript exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind} — Ctrl+a d to detach the box's tmux session.`,\n };\n}\n\ninterface QuietResult {\n code: number;\n stderr: string;\n}\n\n/** Spawn `cmd argv...`, capture stderr, ignore stdout. Resolves on exit. */\nfunction runQuiet(cmd: string, argv: string[]): Promise<QuietResult> {\n return new Promise((resolve) => {\n const child = spawn(cmd, argv, { stdio: ['ignore', 'ignore', 'pipe'] });\n let stderr = '';\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('error', (err) => {\n resolve({ code: 127, stderr: err.message });\n });\n child.on('exit', (code) => {\n resolve({ code: typeof code === 'number' ? code : 1, stderr });\n });\n });\n}\n","const ESC = '\\x1b';\nconst BEL = '\\x07';\n\n/** Replace control chars that would otherwise break the OSC string (the BEL\n * terminator, or any C0 byte) and trim — a stray newline in the agent's\n * session title must not corrupt the host terminal. */\nfunction sanitize(title: string): string {\n return title.replace(/[\\x00-\\x1f\\x7f]/g, ' ').trim();\n}\n\n/**\n * Set the host terminal's title via OSC 0 (`ESC ] 0 ; <title> BEL`), which sets\n * both the window and icon title — the same sequence Claude Code emits. Guarded\n * by `isTTY` so piped / redirected output stays clean.\n */\nexport function setTerminalTitle(\n title: string,\n stream: NodeJS.WriteStream = process.stdout,\n): void {\n if (!stream.isTTY) return;\n stream.write(`${ESC}]0;${sanitize(title)}${BEL}`);\n}\n\n/**\n * Push the terminal's current title onto its title stack (XTPUSHTITLE,\n * `CSI 22 ; 2 t`). Pair with {@link popTerminalTitle} on exit so the user's\n * original tab title is restored. Terminals without title-stack support ignore\n * the unknown CSI.\n */\nexport function pushTerminalTitle(stream: NodeJS.WriteStream = process.stdout): void {\n if (!stream.isTTY) return;\n stream.write(`${ESC}[22;2t`);\n}\n\n/** Pop the title saved by {@link pushTerminalTitle} (XTPOPTITLE, `CSI 23 ; 2 t`). */\nexport function popTerminalTitle(stream: NodeJS.WriteStream = process.stdout): void {\n if (!stream.isTTY) return;\n stream.write(`${ESC}[23;2t`);\n}\n","import type { PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * Steady-state input forwarder + active-prompt capture + Ctrl+a leader.\n *\n * In steady state every byte goes to the pty unmodified — *unless* a\n * `leaderChords` map is supplied, in which case `Ctrl+a` (0x01) opens the\n * actions menu (leader-only: a literal Ctrl+a needs a double-press).\n *\n * Only when `capture()` is awaiting does the router intercept the next\n * keystroke and resolve the prompt with a y/n/cancel answer. Anything else\n * the user types while a prompt is active is dropped (not forwarded) — the\n * inner program doesn't see partial keys.\n */\nexport interface InputRouter {\n /** True while a prompt is being captured. Used by the run loop to know\n * whether to redraw the footer eagerly. */\n readonly capturing: boolean;\n /** Feed raw bytes from process.stdin. Forwards or captures internally. */\n feed(buf: Buffer): void;\n /** Activate prompt capture. Resolves with the answer body. Subsequent\n * capture() calls before resolution overwrite the previous prompt (the\n * newer one wins — relay broadcast order is canonical). */\n capture(p: PromptAskEvent): Promise<PromptAnswerBody>;\n /** Reject the in-flight capture (pty exit, sibling-wrapper answered). */\n abort(reason: 'pty-exit' | 'resolved-elsewhere'): void;\n dispose(): void;\n}\n\ninterface ActivePrompt {\n ev: PromptAskEvent;\n resolve: (b: PromptAnswerBody) => void;\n reject: (e: Error) => void;\n}\n\n/** Actions reachable from the Ctrl+a leader menu. */\nexport type LeaderAction = 'screen' | 'code' | 'url' | 'detach';\n\nconst KEY_ENTER = 0x0d;\nconst KEY_LF = 0x0a;\nconst KEY_ESC = 0x1b;\nconst KEY_CTRL_C = 0x03;\nconst KEY_Y_LOW = 0x79;\nconst KEY_Y_UP = 0x59;\nconst KEY_N_LOW = 0x6e;\nconst KEY_N_UP = 0x4e;\nconst KEY_LEADER = 0x01; // Ctrl-a\nconst KEY_CTRL_V = 0x16; // Ctrl-v — Claude Code's \"paste image from clipboard\"\nconst KEY_A_LOW = 0x61; // 'a'\n\n/**\n * A key decoded from an enhanced-keyboard escape sequence. TUIs like Claude Code\n * can switch the terminal into the kitty keyboard protocol or xterm\n * modifyOtherKeys, in which `Ctrl+a` and even plain letters arrive not as raw\n * bytes but as escape sequences. The leader must decode those so the Actions\n * footer keeps working under those modes.\n */\ninterface CsiKey {\n /** Total byte length of the sequence (so the caller can advance past it). */\n len: number;\n /** Base unicode codepoint of the key (e.g. 97 for 'a'). */\n code: number;\n /** Whether Ctrl was held. */\n ctrl: boolean;\n}\n\n/**\n * Parse a kitty keyboard (`ESC [ <code> ; <mods> u`) or xterm modifyOtherKeys\n * (`ESC [ 27 ; <mods> ; <code> ~`) key sequence at `buf[i]`. Returns the base\n * keycode + Ctrl flag, or null when `buf[i]` isn't one of those (a plain byte,\n * an arrow/function/mouse sequence, or an incomplete sequence split across\n * reads — all of which just forward unchanged). Precise on purpose: only the\n * exact `…u` / `CSI 27 … ~` key shapes match, so cursor/mouse CSI sequences\n * (`ESC [ A`, `ESC [ < … M`) fall through.\n */\nfunction parseCsiKey(buf: Buffer, i: number): CsiKey | null {\n if (buf[i] !== KEY_ESC || buf[i + 1] !== 0x5b /* [ */) return null;\n const params: number[] = [];\n let val = -1;\n for (let j = i + 2; j < buf.length; j++) {\n const b = buf[j];\n if (b !== undefined && b >= 0x30 && b <= 0x39) {\n val = (val < 0 ? 0 : val) * 10 + (b - 0x30);\n continue;\n }\n if (b === 0x3b /* ; */) {\n params.push(val);\n val = -1;\n continue;\n }\n if (b === 0x3a /* : */) {\n // Sub-parameters (kitty event types / alternate keys): record the param,\n // then skip to the next ';' or final byte.\n params.push(val);\n val = -1;\n while (\n j + 1 < buf.length &&\n buf[j + 1] !== 0x3b &&\n buf[j + 1] !== 0x75 &&\n buf[j + 1] !== 0x7e\n ) {\n j++;\n }\n continue;\n }\n if (b === 0x75 /* u */ || b === 0x7e /* ~ */) {\n if (val >= 0) params.push(val);\n const len = j - i + 1;\n const modsToCtrl = (m: number): boolean => ((m - 1) & 4) !== 0;\n if (b === 0x75) {\n // kitty: CSI <code> ; <mods> u\n const code = params[0];\n if (code === undefined || code < 0) return null;\n return { len, code, ctrl: modsToCtrl(params[1] ?? 1) };\n }\n // modifyOtherKeys: CSI 27 ; <mods> ; <code> ~ (other '~' seqs aren't keys)\n if (params[0] !== 27) return null;\n const code = params[2];\n if (code === undefined || code < 0) return null;\n return { len, code, ctrl: modsToCtrl(params[1] ?? 1) };\n }\n return null; // any other byte → not a CSI-u / modifyOtherKeys key\n }\n return null; // incomplete (split across reads) — forward as-is\n}\n\nconst DEFAULT_LEADER_TIMEOUT_MS = 2000;\n\nexport interface InputRouterOptions {\n onForward: (b: Buffer) => void;\n /** Called when a prompt's capture is resolved — the run loop POSTs the answer. */\n onAnswer: (body: PromptAnswerBody) => void;\n /** Ctrl+a leader chord map: a single lowercase character → action. When\n * omitted or empty the leader is disabled and `Ctrl+a` forwards verbatim. */\n leaderChords?: Readonly<Record<string, LeaderAction>>;\n /** Fired when the leader menu opens (true) / closes (false). */\n onLeaderChange?: (active: boolean) => void;\n /** Fired when a recognized chord key resolves the leader. */\n onAction?: (name: LeaderAction) => void;\n /**\n * When set, a lone `Ctrl+V` (0x16) in steady state is intercepted instead of\n * forwarded: the router awaits this hook (which loads the host clipboard\n * image into the box), then re-emits the `Ctrl+V` so the inner program's own\n * paste handler reads the now-populated box clipboard. Presses while one is\n * in flight are dropped (debounced). When omitted, `Ctrl+V` forwards\n * verbatim. Used for claude image paste; other modes don't pass it. */\n onPasteImage?: () => Promise<unknown>;\n /** ms the leader menu stays open with no key before auto-closing (default 2000). */\n leaderTimeoutMs?: number;\n /** Injected for unit tests; defaults to global timers. */\n setTimer?: (ms: number, fn: () => void) => unknown;\n clearTimer?: (h: unknown) => void;\n}\n\nexport function createInputRouter(opts: InputRouterOptions): InputRouter {\n let active: ActivePrompt | null = null;\n let disposed = false;\n\n const leaderChords = opts.leaderChords ?? {};\n const leaderEnabled = Object.keys(leaderChords).length > 0;\n const onPasteImage = opts.onPasteImage;\n const pasteEnabled = typeof onPasteImage === 'function';\n let pasteInFlight = false;\n const leaderTimeoutMs = opts.leaderTimeoutMs ?? DEFAULT_LEADER_TIMEOUT_MS;\n const setTimer = opts.setTimer ?? ((ms, fn) => setTimeout(fn, ms) as unknown);\n const clearTimer =\n opts.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>));\n let leader = false;\n let leaderTimer: unknown = null;\n\n const disarmLeader = (): void => {\n if (leaderTimer != null) {\n clearTimer(leaderTimer);\n leaderTimer = null;\n }\n };\n\n const exitLeader = (): void => {\n if (!leader) return;\n leader = false;\n disarmLeader();\n opts.onLeaderChange?.(false);\n };\n\n const enterLeader = (): void => {\n leader = true;\n disarmLeader();\n // Leader-only: a lone Ctrl+a just times the menu out — it is never\n // auto-forwarded. A literal Ctrl+a is sent via a double-press.\n leaderTimer = setTimer(leaderTimeoutMs, () => {\n leaderTimer = null;\n exitLeader();\n });\n opts.onLeaderChange?.(true);\n };\n\n // The leader is open and `b` is the chord byte that resolves it.\n const resolveLeaderByte = (b: number): void => {\n if (b === KEY_LEADER) {\n // Double Ctrl+a → one literal Ctrl+a to the inner program.\n exitLeader();\n opts.onForward(Buffer.from([KEY_LEADER]));\n return;\n }\n if (b === KEY_ESC) {\n // Esc dismisses the menu; nothing forwarded.\n exitLeader();\n return;\n }\n const action = leaderChords[String.fromCharCode(b).toLowerCase()];\n if (action) {\n exitLeader();\n opts.onAction?.(action);\n return;\n }\n // Unrecognized chord: close the menu, forward the key so typing isn't lost.\n exitLeader();\n opts.onForward(Buffer.from([b]));\n };\n\n const settle = (\n answer: PromptAnswerBody['answer'],\n cancelled?: boolean,\n ): void => {\n if (!active) return;\n const body: PromptAnswerBody = {\n id: active.ev.id,\n answer,\n ...(cancelled ? { cancelled: true } : {}),\n };\n const p = active;\n active = null;\n p.resolve(body);\n opts.onAnswer(body);\n };\n\n const handleCapturedByte = (b: number): void => {\n if (!active) return;\n if (b === KEY_Y_LOW || b === KEY_Y_UP) {\n settle('y');\n return;\n }\n if (b === KEY_N_LOW || b === KEY_N_UP) {\n settle('n');\n return;\n }\n if (b === KEY_ESC || b === KEY_CTRL_C) {\n settle('n', true);\n return;\n }\n if (b === KEY_ENTER || b === KEY_LF) {\n // Enter accepts the default answer.\n const def = active.ev.defaultAnswer ?? 'n';\n settle(def);\n return;\n }\n // Anything else: ignored (not forwarded, not consumed).\n };\n\n // Intercepted Ctrl+V: run the host→box image-paste hook, then re-emit the\n // Ctrl+V so the inner program reads the (now-loaded) box clipboard. A press\n // while one is in flight is dropped — the Ctrl+V was already swallowed by the\n // caller, so there's nothing to forward.\n const triggerPaste = (): void => {\n if (pasteInFlight) return;\n pasteInFlight = true;\n const done = (): void => {\n pasteInFlight = false;\n if (!disposed) opts.onForward(Buffer.from([KEY_CTRL_V]));\n };\n void Promise.resolve()\n .then(() => onPasteImage?.())\n .then(done, done);\n };\n\n // Leader-aware steady-state forwarding: scan bytes, batching plain runs into\n // a single onForward call, and intercept `Ctrl+a` chords + `Ctrl+V` paste.\n const feedSteady = (buf: Buffer): void => {\n let chunkStart = 0;\n const flushChunk = (end: number): void => {\n if (end > chunkStart) opts.onForward(buf.subarray(chunkStart, end));\n chunkStart = end;\n };\n let i = 0;\n while (i < buf.length) {\n const byte = buf[i];\n if (byte === undefined) {\n i++;\n continue;\n }\n\n if (leader) {\n // Resolve the chord. The key may be a raw byte, or — when the inner app\n // enabled an enhanced keyboard protocol — a CSI-u / modifyOtherKeys\n // sequence (e.g. 'c' as `ESC [ 99 u`).\n const k = parseCsiKey(buf, i);\n if (k) {\n if (k.ctrl && k.code === KEY_A_LOW) {\n // Double Ctrl+a → one literal Ctrl+a to the inner program.\n exitLeader();\n opts.onForward(Buffer.from([KEY_LEADER]));\n } else {\n const action = leaderChords[String.fromCharCode(k.code).toLowerCase()];\n exitLeader();\n if (action) opts.onAction?.(action);\n else opts.onForward(buf.subarray(i, i + k.len)); // unknown: don't lose it\n }\n i += k.len;\n chunkStart = i;\n continue;\n }\n resolveLeaderByte(byte);\n i += 1;\n chunkStart = i;\n continue;\n }\n\n if (leaderEnabled && byte === KEY_LEADER) {\n flushChunk(i); // forward everything typed before the Ctrl+a\n enterLeader();\n i += 1;\n chunkStart = i;\n continue;\n }\n // Ctrl+a re-encoded by an enhanced keyboard protocol (kitty / modifyOtherKeys).\n if (leaderEnabled && byte === KEY_ESC) {\n const k = parseCsiKey(buf, i);\n if (k && k.ctrl && k.code === KEY_A_LOW) {\n flushChunk(i);\n enterLeader();\n i += k.len;\n chunkStart = i;\n continue;\n }\n }\n if (pasteEnabled && byte === KEY_CTRL_V) {\n flushChunk(i); // forward everything typed before the Ctrl+V\n i += 1;\n chunkStart = i; // swallow it; triggerPaste re-emits after the load\n triggerPaste();\n continue;\n }\n i += 1;\n }\n flushChunk(buf.length);\n };\n\n return {\n get capturing(): boolean {\n return active !== null;\n },\n feed(buf: Buffer): void {\n if (disposed) return;\n if (active) {\n // A multi-byte read starting with ESC is a CSI/SS3/OSC escape\n // sequence — mouse click (`\\x1b[<…M/m`), arrow / function key,\n // window-focus event, bracketed-paste markers, etc. Drop the\n // whole chunk: the user pressed something we don't model as a\n // confirmation key, and they'd be (correctly) surprised if a stray\n // mouse click registered as \"deny\". A *real* Esc keypress arrives\n // as a single byte in its own read, which still cancels below.\n if (buf.length > 1 && buf[0] === KEY_ESC) return;\n // Process bytes one at a time so a paste of \"yes\\n\" is handled\n // sanely: the 'y' settles, the rest is dropped — we don't want\n // stray bytes leaking to the pty after the prompt closed mid-buf.\n // (After settle, `active` is null; remaining bytes fall through to\n // forward path below.)\n for (let i = 0; i < buf.length; i++) {\n const byte = buf[i];\n if (byte === undefined) continue;\n if (active) {\n handleCapturedByte(byte);\n } else {\n // Active became null mid-buffer (settled). Forward the rest as\n // a normal keystroke chunk.\n opts.onForward(buf.subarray(i));\n return;\n }\n }\n return;\n }\n if (!leaderEnabled && !pasteEnabled) {\n opts.onForward(buf);\n return;\n }\n feedSteady(buf);\n },\n capture(ev: PromptAskEvent): Promise<PromptAnswerBody> {\n return new Promise<PromptAnswerBody>((resolve, reject) => {\n // A relay prompt outranks the actions menu — close the leader first.\n if (leader) exitLeader();\n if (active) {\n // A new prompt arrived before the old one was answered — abort\n // the old one (treated as cancelled) and switch to the new one.\n // The relay already broadcast `prompt-ask` for both; we owe the\n // first an answer or it'll stay pending forever.\n settle('n', true);\n }\n active = { ev, resolve, reject };\n });\n },\n abort(reason): void {\n if (!active) return;\n const p = active;\n active = null;\n const msg = reason === 'pty-exit' ? 'pty exited' : 'resolved by sibling wrapper';\n p.reject(new Error(msg));\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n disarmLeader();\n if (active) {\n const p = active;\n active = null;\n p.reject(new Error('input router disposed'));\n }\n },\n };\n}\n","import type { ClaudeQuestionPayload } from '@agentbox/ctl';\n\nexport interface SidebarBox {\n id: string;\n name: string;\n /** Container state: 'running' | 'paused' | 'stopped' | 'missing' | … */\n state: string;\n /** Activity of the agent this box runs (claude / codex) — 'working' | 'idle'\n * | 'waiting' | 'unknown' | undefined. Resolved from whichever agent is\n * active (see `resolveAgent` in commands/dashboard.ts). */\n activity?: string;\n /** The in-box terminal/session title the active agent set, or undefined. */\n sessionTitle?: string;\n /** 1-based per-project box number, shown as `[N]`; undefined for\n * pre-feature boxes and the synthetic \"+ New box\" entry. */\n index?: number;\n /** Absolute project root; used to group boxes under a project header.\n * Undefined for pre-feature boxes and the synthetic \"+ New box\" entry. */\n project?: string;\n /** This box has an unanswered relay `prompt-ask` event (e.g. agentbox-ctl\n * git push / cp / download waiting for user confirmation). The compositor\n * injects this flag from its in-memory map of active prompts. Overrides\n * the activity cell — `▲ prompt` reads more urgent than `● working`. */\n pendingPrompt?: boolean;\n /** This box has an active relay notice (currently: a checkpoint is being\n * captured, freezing the box). Injected by the compositor; shown as\n * `◆ checkpoint` in the activity cell. Outranked by `pendingPrompt`. */\n checkpointing?: boolean;\n /** Last `AskUserQuestion` payload from the box's claude status; populated\n * only while `activity === 'question'`. The compositor renders it into the\n * alert band above the footer for the selected box. */\n claudeQuestion?: ClaudeQuestionPayload;\n}\n\n/** Per-row ownership + styling map returned alongside the rendered lines so\n * the compositor can highlight the selected box and style headers without\n * re-deriving the (now non-uniform) layout. */\nexport interface SidebarRender {\n lines: string[];\n /** boxId rendered on row `i`, else null (banner / group header / blank). */\n rowOwner: (string | null)[];\n /** true for the banner and project-header rows (styled like the banner). */\n headerRows: boolean[];\n}\n\n/** Truncate to `max` printable chars, appending `…` when it had to cut\n * (keeps the head). */\nfunction ellipsize(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return s.slice(0, max - 1) + '…';\n}\n\n/** Truncate keeping the *tail* (the distinguishing part of a box name like\n * `…-78b94c78`), prepending `…` when it had to cut. */\nfunction ellipsizeHead(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return '…' + s.slice(s.length - (max - 1));\n}\n\nexport function activityCell(b: SidebarBox): string {\n // Pending relay prompt outranks every other state — the user needs to\n // act before whatever the box is doing can continue.\n if (b.pendingPrompt) return '▲ prompt';\n // A checkpoint freezes the box; surface it over the activity state.\n if (b.checkpointing) return '◆ checkpoint';\n if (b.state !== 'running') return `[${b.state}]`;\n switch (b.activity) {\n case 'working':\n return '● working';\n case 'idle':\n return '○ idle';\n case 'waiting':\n return '◐ waiting';\n default:\n return '? unknown';\n }\n}\n\n/** Synthetic sidebar entry pinned at the top: selecting it opens the create\n * menu. Carried in the compositor's box list like a real box (sentinel id),\n * so selection/switch/highlight need no special-casing. */\nexport const NEW_BOX_ID = '__agentbox_new__';\nexport const NEW_BOX_LABEL = '+ New box';\n\n/** Sidebar banner label (rendered into the top border). */\nexport const SIDEBAR_HEADER = 'AgentBox';\n\n/** Top border: a flat line on the top + a rounded corner into the right edge\n * only (no left/bottom, to save space): `──── AgentBox ─────…` filling\n * exactly `w`. The left end is a straight line; the matching rounded\n * top-right corner (`╮`) is drawn by the compositor at the sidebar separator\n * column. */\nfunction topBorder(label: string, w: number): string {\n const lead = `──── ${label} `;\n if (lead.length >= w) return lead.slice(0, w);\n return lead + '─'.repeat(w - lead.length);\n}\n/** Lines `sidebarLines` reserves before the box rows (banner + blank). The\n * compositor uses this to locate the selected box row for highlighting. */\nexport const SIDEBAR_HEADER_LINES = 2;\n\nfunction fit(s: string, w: number): string {\n if (s.length === w) return s;\n if (s.length > w) return s.slice(0, w);\n return s + ' '.repeat(w - s.length);\n}\n\n/** `s` centered in a field of `w` columns (truncated if it doesn't fit). */\nfunction center(s: string, w: number): string {\n if (s.length >= w) return s.slice(0, w);\n const pad = w - s.length;\n const leftPad = Math.floor(pad / 2);\n return ' '.repeat(leftPad) + s + ' '.repeat(pad - leftPad);\n}\n\n/** `basename` of an absolute project root, for the group header label. */\nfunction projectLabel(project: string | undefined): string {\n if (!project) return '(no project)';\n const parts = project.split('/').filter(Boolean);\n return parts[parts.length - 1] ?? project;\n}\n\n/** Strip the leading decoration Claude prepends to its terminal title (the\n * spinner glyph, e.g. `✳ `) plus any leading symbols/asterisks/space, so the\n * sidebar shows just the words. Falls back to the trimmed original if the\n * title is all decoration. */\nexport function stripTitleGlyph(s: string): string {\n const t = s.replace(/^[\\s\\p{S}*·]+/u, '');\n return t.length > 0 ? t : s.trim();\n}\n\n/**\n * Render one box row: `marker<num> <title|name> <status>`. The number and\n * the status are width-protected; the middle (title, else the box name with\n * its meaningful tail kept) flexes and ellipsizes so the status is never\n * eaten. Compact: no brackets, no glyph, single-char marker.\n */\nfunction boxRow(b: SidebarBox, marker: string, w: number): string {\n const numStr = b.index != null ? `${b.index} ` : '';\n const status = activityCell(b);\n const left = `${marker}${numStr}`;\n // -2: 1 gap before status, 1 margin after so the label doesn't touch the\n // sidebar's right border.\n const room = w - left.length - status.length - 2;\n if (room <= 0) return fit(`${left}${status}`, w);\n const middle =\n b.state === 'running' && b.sessionTitle\n ? ellipsize(stripTitleGlyph(b.sessionTitle), room)\n : ellipsizeHead(b.name, room);\n // Left segment padded so the status sits one column in from the right edge,\n // with a trailing space as the right margin.\n return fit(`${left}${middle}`, w - status.length - 1) + status + ' ';\n}\n\n/**\n * The sidebar region as exactly `h` lines, each exactly `w` columns, plus a\n * per-row ownership/style map. Pure — no ANSI positioning (the compositor\n * places it). Boxes are grouped under a ` ── <project> ── ` header (callers\n * pass them pre-sorted by project).\n */\nexport function sidebarLines(\n boxes: SidebarBox[],\n selectedId: string,\n w: number,\n h: number,\n): SidebarRender {\n const lines: string[] = [topBorder(SIDEBAR_HEADER, w), fit('', w)];\n const rowOwner: (string | null)[] = [null, null];\n const headerRows: boolean[] = [true, false];\n const push = (line: string, owner: string | null, header: boolean): void => {\n lines.push(fit(line, w));\n rowOwner.push(owner);\n headerRows.push(header);\n };\n\n let prevProject: string | undefined;\n let seenGroup = false;\n for (const b of boxes) {\n const marker = b.id === selectedId ? '▸' : ' ';\n if (b.id === NEW_BOX_ID) {\n push(`${marker}${NEW_BOX_LABEL}`, b.id, false);\n continue;\n }\n if (!seenGroup || b.project !== prevProject) {\n push(center(` ── ${projectLabel(b.project)} ── `, w), null, true);\n prevProject = b.project;\n seenGroup = true;\n }\n push(boxRow(b, marker, w), b.id, false);\n }\n if (boxes.length === 0) push(' (no boxes)', null, false);\n while (lines.length < h) push('', null, false);\n return {\n lines: lines.slice(0, h),\n rowOwner: rowOwner.slice(0, h),\n headerRows: headerRows.slice(0, h),\n };\n}\n\n/**\n * Centered action menu for a running box with no Claude session.\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function menuLines(boxName: string, w: number, h: number): string[] {\n const body = [\n '',\n ` No agent session in ${boxName}.`,\n '',\n ' [c] Start Claude',\n ' [x] Start Codex',\n ' [o] Start OpenCode',\n ' [s] Open a shell',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then c/s/u/q (code/screen/url/quit)',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered action menu for a non-running box (paused/stopped): resume +\n * destroy, with a two-step destroy confirm (the TUI can't show a prompt).\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function lifecycleMenuLines(\n boxName: string,\n state: 'paused' | 'stopped',\n confirmDestroy: boolean,\n w: number,\n h: number,\n): string[] {\n const body = confirmDestroy\n ? [\n '',\n ` Destroy ${boxName}?`,\n ' This removes the container and its volumes.',\n '',\n ' [y] Yes, destroy',\n ' [any other key] Cancel',\n ]\n : [\n '',\n ` Box ${boxName} is ${state}.`,\n '',\n state === 'paused' ? ' [u] Unpause' : ' [s] Start',\n ' [d] Destroy',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered menu for the synthetic \"+ New box\" entry. Exactly `h` lines, each\n * exactly `w` columns. Pure.\n */\nexport function createMenuLines(where: string, w: number, h: number): string[] {\n const body = [\n '',\n ' Create a new box',\n '',\n ' [c] Create + launch Claude',\n ' [x] Create + launch Codex',\n ' [o] Create + launch OpenCode',\n ' [n] Create only',\n '',\n ` in ${where}`,\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n// Status-bar palette — matches the in-box tmux footer\n// (`buildTmuxSessionArgs`): dark bar, blue brand block, dim-grey hints\n// with white key chords.\n/** The footer/sidebar background gray. Truecolor (not palette index 236) so\n * it pins an exact RGB — terminals can remap/shade indexed colors per\n * context, which made the sidebar and status bar look like different grays.\n * Single source so the two regions can't drift. */\nexport const BAR_BG = '\\x1b[48;2;48;48;48m';\nconst BAR_BASE = BAR_BG + '\\x1b[38;5;250m';\nconst BAR_BRAND = '\\x1b[48;5;39m\\x1b[38;5;16m'; // blue block (not bold)\nconst BRAND_BOLD = '\\x1b[1m'; // box name only\nconst BRAND_NOBOLD = '\\x1b[22m';\nconst HINT_KEY = '\\x1b[38;5;255m'; // white: the key chord\nconst HINT_TXT = '\\x1b[38;5;245m'; // gray: labels + separators\nconst BAR_RESET = '\\x1b[0m';\n\n// [key chord, label]. Modifiers spelled out (no ⌥/^ glyphs); arrows use the\n// ↑/↓ glyphs. Rendered as `KEYS: label` with the chord white, label gray.\nconst SWITCH_HINT: readonly [string, string] = ['Control+Option+↑/↓', 'switch'];\nconst HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a c', 'code'],\n ['Control+a s', 'screen'],\n ['Control+a u', 'url'],\n ['Control+a q', 'quit'],\n];\n\n/** Minimal hint tier when the bar is too narrow for the full `HINT_GROUPS`:\n * box switching (always important) + the leader. Pressing `Ctrl-a` then\n * expands to `ADVANCED_HINT_GROUPS` (the compositor swaps while the leader is\n * active). */\nexport const COLLAPSED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a', 'more'],\n];\n\n/** The expanded \"which-key\" chord menu shown while the Ctrl-a leader is\n * pending — every chord, compact (`KEY: label`), reverts on the next key. */\nexport const ADVANCED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['t', 'stop'],\n ['p', 'pause'],\n ['d', 'destroy'],\n ['q', 'quit'],\n];\n\n/**\n * Status line, exactly `w` printable columns, colored to match the in-box tmux\n * footer (dark bar, blue ` agentbox ▸ … ` brand block on the left, dim-grey\n * shortcut hints on the right). `stateLabel` overrides the box's activity text\n * (used for `shell` / `menu` panes where the box `activity` would otherwise\n * show a misleading `unknown`). `fallbackGroups`, when given, is the narrow-bar tier\n * tried before brand-core-only — used to keep one essential chord pinned\n * (instead of the default dashboard `COLLAPSED_HINT_GROUPS`).\n */\nexport function statusLine(\n box: SidebarBox | undefined,\n w: number,\n stateLabel?: string,\n groups: ReadonlyArray<readonly [string, string]> = HINT_GROUPS,\n fallbackGroups?: ReadonlyArray<readonly [string, string]>,\n): string {\n const state =\n stateLabel ?? (box ? (box.state === 'running' ? (box.activity ?? 'unknown') : box.state) : '');\n // \"agentbox ▸ \" stays normal weight; only the box name + state are bold.\n const brandPrefix = box ? ' agentbox ▸ ' : ' agentbox ';\n // Brand *core* (no title) — the width-protected segment. The title is the\n // lowest-priority segment: it only fills space left after brand + hints.\n const base = box ? `${box.name} (${state})` : '';\n const coreMain = box ? `${base} ` : '';\n const corePlain = brandPrefix + coreMain;\n\n const SEP = ' │ ';\n const renderHints = (\n g: ReadonlyArray<readonly [string, string]>,\n ): { plain: string; styled: string } => ({\n plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + ' ',\n styled:\n g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + ' ',\n });\n\n // Hint tier: shortcuts beat the title. Try the requested groups; if the\n // brand core + those hints overflow, fall back to the narrow-bar tier\n // (`fallbackGroups` if supplied, else the dashboard's minimal leader hint);\n // if even that overflows, render brand-core-only (title can never push the\n // box name off-screen).\n let hints: { plain: string; styled: string } | null = null;\n for (const g of [groups, fallbackGroups ?? COLLAPSED_HINT_GROUPS]) {\n const h = renderHints(g);\n if (corePlain.length + h.plain.length + 1 <= w) {\n hints = h;\n break;\n }\n }\n if (!hints) {\n return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;\n }\n\n // Title fills only the leftover, ellipsized; dropped entirely when there's\n // no meaningful room (≈ ` — ` + a few chars). Capped at 40 cols as before.\n const room = w - corePlain.length - hints.plain.length - 1;\n let titleSeg = '';\n if (box?.sessionTitle && room >= 7) {\n titleSeg = ` — ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;\n }\n\n const leftPlain = brandPrefix + base + titleSeg + (box ? ' ' : '');\n const leftStyled =\n BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? ' ' : '') + BRAND_NOBOLD;\n const gap = w - leftPlain.length - hints.plain.length;\n // brand block (name + title bold) → base bar → gap → white/gray hints.\n return (\n BAR_BASE +\n leftStyled +\n BAR_BASE +\n ' '.repeat(gap) +\n hints.styled +\n BAR_RESET\n );\n}\n","import { BAR_BG, statusLine, type SidebarBox } from '../dashboard/sidebar.js';\nimport type { PromptAskEvent } from '@agentbox/relay';\nimport type { ClaudeQuestionPayload } from '@agentbox/ctl';\n\n/**\n * Footer rendering state. `idle` reuses the dashboard's `statusLine` shape\n * (brand chip + box name + optional session title + right-aligned hint);\n * `prompt` is shown while a `prompt-ask` event is being captured; `notice`\n * is an animated informational warning (e.g. checkpoint in progress);\n * `flash` is a transient confirmation after a Ctrl+a action fires.\n */\nexport type FooterState =\n | {\n kind: 'idle';\n boxName: string;\n /** Claude's tmux pane title (from BoxStatus.claude.sessionTitle).\n * Undefined until the first status poll completes (or in shell mode). */\n sessionTitle?: string;\n /** Claude activity hint shown in `(<state>)` after the name. Same field\n * the dashboard sidebar uses (`working` / `idle` / `waiting` / etc.). */\n claudeActivity?: string;\n /** Mode drives the state label: claude shows claude activity, the\n * others show `(shell)` / `(codex)` / `(opencode)`. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the session can be detached (tmux-backed). Drives the\n * expanded leader menu + the pinned `Control+a d: detach` hint. */\n detachable?: boolean;\n /** True while the Ctrl+a leader menu is open — swaps the collapsed\n * `Control+a: Actions` hint for the expanded chord list. */\n leaderActive?: boolean;\n }\n | { kind: 'prompt'; prompt: PromptAskEvent }\n | {\n kind: 'notice';\n /** Warning text, e.g. \"Checkpoint in progress — …\". */\n message: string;\n /** Monotonic counter; the spinner glyph is `SPINNER_FRAMES[frame % len]`. */\n frame: number;\n }\n | {\n kind: 'flash';\n /** Transient confirmation text, e.g. \"Opening noVNC viewer…\". */\n message: string;\n };\n\n/**\n * Spinner cycle for the `notice` footer. Solid half-filled circles, not\n * braille: braille glyphs read as a faint dot cluster on the yellow banner\n * (set vs unset dots are hard to tell apart), so the motion gets lost. The\n * rotating black half of these is unambiguous.\n */\nexport const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'] as const;\n\nconst URGENT = '\\x1b[38;5;220m\\x1b[1m'; // bright yellow + bold (active prompt)\nconst TITLE = '\\x1b[1m\\x1b[38;5;253m'; // bold near-white (prompt band title)\nconst TXT = '\\x1b[38;5;250m'; // dim gray body text\nconst SUBTLE = '\\x1b[38;5;245m'; // very dim (detail / Y/N hint)\nconst RESET = '\\x1b[0m';\nconst UNDERLINE = '\\x1b[4m'; // emphasizes the default answer inside the chip\nconst NO_UNDERLINE = '\\x1b[24m'; // ends underline without dropping the chip bg\n// Agent-question accent: cyan + bold, matching the dashboard sidebar's\n// \"awaiting\" hue — distinct from URGENT (relay prompt) so the two readings\n// don't collide when both could in principle stack.\nconst QUESTION_ACCENT = '\\x1b[38;5;51m\\x1b[1m';\n// Notice footer = a full-width warning banner: bright yellow background with\n// near-black bold text. High contrast so the \"box is frozen\" state is\n// unmissable — deliberately louder than the dim-on-dark idle/prompt bars.\nconst NOTICE_BG = '\\x1b[48;5;220m'; // bright yellow background\nconst NOTICE_FG = '\\x1b[38;5;16m\\x1b[1m'; // near-black + bold text\n// Flash footer = a calm one-line confirmation on the normal dark bar.\nconst FLASH_FG = '\\x1b[38;5;150m\\x1b[1m'; // soft green + bold\n\n/** Collapsed idle hint (plain `--no-tmux` shell) — the leader is hidden\n * behind one chord. */\nconst COLLAPSED_HINTS_PLAIN: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n];\n/** Collapsed idle hint (detachable session) — the detach chord stays pinned\n * on the right even while the actions menu is closed. */\nconst COLLAPSED_HINTS_DETACHABLE: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n ['Control+a d', 'detach'],\n];\n/** Narrow-bar fallback for a detachable session: drop the `Actions` hint\n * first, but never the detach chord. */\nconst DETACH_PIN_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['Control+a d', 'detach'],\n];\n/** Expanded which-key menu shown while the Ctrl+a leader is open. A\n * detachable (tmux-backed) session also gets `d: detach`; a plain shell\n * has nothing to detach from. */\nconst DETACHABLE_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['d', 'detach'],\n];\nconst PLAIN_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n];\n\n/**\n * Truncate `s` to exactly `width` visible columns, padding with spaces when\n * shorter. ANSI SGR sequences must NOT be present in the input.\n */\nfunction padTo(visible: string, width: number): string {\n if (visible.length === width) return visible;\n if (visible.length > width) {\n if (width <= 1) return visible.slice(0, width);\n return visible.slice(0, width - 1) + '…';\n }\n return visible + ' '.repeat(width - visible.length);\n}\n\n/**\n * High-contrast answer chip for a confirm prompt: the keys spelled out on a\n * bright-yellow background (the same NOTICE treatment used for the \"box\n * frozen\" banner) so the y/N choice is unmissable. The default answer is\n * underlined. Returns the styled string plus its visible column width — the\n * ANSI codes don't count toward layout, so callers need the plain width.\n */\nfunction answerChip(defaultAnswer: 'y' | 'n' | undefined): { ansi: string; width: number } {\n const yesKey = 'y Yes';\n const noKey = 'n No';\n const sep = ' · ';\n const yesIsDefault = defaultAnswer === 'y';\n const yes = yesIsDefault ? `${UNDERLINE}${yesKey}${NO_UNDERLINE}` : yesKey;\n const no = yesIsDefault ? noKey : `${UNDERLINE}${noKey}${NO_UNDERLINE}`;\n const ansi = `${NOTICE_BG}${NOTICE_FG} ${yes}${sep}${no} ${RESET}`;\n // Width derived from the plain (underline-free) shape so it stays correct\n // if the wording changes.\n const width = ` ${yesKey}${sep}${noKey} `.length;\n return { ansi, width };\n}\n\n/**\n * Render the footer row as a single ANSI string. Caller positions the\n * cursor at the last row, col 0 before writing, and restores it afterwards.\n * Always ends with SGR reset so the inner pty's next byte starts clean.\n */\nexport function renderFooter(state: FooterState, cols: number): string {\n if (cols <= 0) return '';\n if (state.kind === 'idle') {\n const sidebarBox: SidebarBox = {\n id: '', // unused by statusLine\n name: state.boxName,\n state: 'running', // we're attached, so the container is up\n activity: state.claudeActivity,\n sessionTitle: state.sessionTitle,\n };\n const isClaude = state.mode === 'claude';\n const detachable = state.detachable ?? isClaude;\n // Shell/codex modes have no claude activity to surface — passing\n // `stateLabel` overrides statusLine's default (which would otherwise show\n // `(unknown)` because `claudeActivity` is undefined and the container is\n // running).\n const stateLabel = isClaude ? undefined : state.mode === 'shell' ? 'shell' : state.mode;\n if (state.leaderActive) {\n const leaderHints = detachable ? DETACHABLE_LEADER_HINTS : PLAIN_LEADER_HINTS;\n return statusLine(sidebarBox, cols, stateLabel, leaderHints);\n }\n // Collapsed: a detachable session keeps the detach chord pinned on the\n // right (its narrow-bar fallback drops `Actions` first, never `detach`).\n const collapsed = detachable ? COLLAPSED_HINTS_DETACHABLE : COLLAPSED_HINTS_PLAIN;\n const fallback = detachable ? DETACH_PIN_HINTS : undefined;\n return statusLine(sidebarBox, cols, stateLabel, collapsed, fallback);\n }\n if (state.kind === 'flash') {\n // Flash state: a brief \"<arrow> <message>\" confirmation on the dark bar.\n const prefix = ' ▸ '; // ▸\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${BAR_BG}${FLASH_FG}${prefix}${TXT}${message}${RESET}`;\n }\n if (state.kind === 'notice') {\n // Notice state: \"<spinner> <message>\" rendered as a full-width\n // high-contrast yellow warning banner. The spinner reassures the user\n // the box is busy, not stuck.\n const spinner = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length]!;\n const prefix = ` ${spinner} `;\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${NOTICE_BG}${NOTICE_FG}${prefix}${message}${RESET}`;\n }\n // Prompt state (narrow-terminal fallback): \"[!] <message> [detail] <chip>\".\n // The answer chip is suffixed; we squeeze the message+detail into the space\n // left over (truncating message first, then detail).\n const chip = answerChip(state.prompt.defaultAnswer);\n const tag = ' [!] ';\n const sep = ' ';\n const inner = Math.max(0, cols - tag.length - chip.width);\n const detailRaw = state.prompt.detail ?? '';\n let message = state.prompt.message;\n let detail = detailRaw;\n const messageBudget = Math.max(8, inner - (detail.length > 0 ? sep.length + 8 : 0));\n if (message.length > messageBudget) {\n message = message.slice(0, Math.max(0, messageBudget - 1)) + '…';\n }\n const usedByMessage = message.length;\n const detailBudget = Math.max(0, inner - usedByMessage - sep.length);\n if (detail.length > detailBudget) {\n detail = detailBudget <= 1 ? '' : detail.slice(0, detailBudget - 1) + '…';\n }\n const middlePlain = detail.length > 0 ? `${message}${sep}${detail}` : message;\n const padded = padTo(middlePlain, inner);\n return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${RESET}${chip.ansi}`;\n}\n\n/**\n * ANSI sequence to move the cursor to (row, col) — 1-based, terminal convention.\n */\nexport function cursorMoveTo(row: number, col: number): string {\n return `\\x1b[${String(row)};${String(col)}H`;\n}\n\nexport const CURSOR_SAVE = '\\x1b7';\nexport const CURSOR_RESTORE = '\\x1b8';\n\n/**\n * Synchronized output toggles (DECSET/DECRST 2026). Wrap a multi-write\n * footer paint so terminals that support it commit one atomic frame.\n */\nexport const SYNC_BEGIN = '\\x1b[?2026h';\nexport const SYNC_END = '\\x1b[?2026l';\n\n/**\n * Alert-band state: the surface shown directly above the (idle) footer when\n * a box needs the user's attention. The band is fixed at 3 rows; the inner\n * PTY is resized down by 3 rows so the band never overlaps agent output.\n *\n * - `prompt`: a relay confirm prompt (hard-blocks an in-box RPC).\n * - `notice`: an informational warning (checkpoint/snapshot in progress);\n * `frame` advances the spinner glyph.\n * - `question`: the agent's `AskUserQuestion` payload (claude.state ===\n * 'question'); shown as header + question text + option labels.\n */\nexport type AlertBandState =\n | { kind: 'prompt'; prompt: PromptAskEvent }\n | { kind: 'notice'; message: string; frame: number }\n | { kind: 'question'; question: ClaudeQuestionPayload };\n\n/** Default band height; both TUIs reserve this many rows above the footer. */\nexport const ALERT_BAND_ROWS = 3;\n\nfunction blankBar(cols: number, bg: string): string {\n return `${bg}${' '.repeat(Math.max(0, cols))}${RESET}`;\n}\n\nfunction renderPromptBand(prompt: PromptAskEvent, cols: number, rows: number): string[] {\n const tag = ' [!] ';\n const indent = ' '.repeat(tag.length);\n const contW = Math.max(0, cols - indent.length);\n\n // Row 1: \"[!] TITLE ............ <chip>\". The bold title (the relay action,\n // e.g. GIT PUSH) flags what needs approval; the high-contrast answer chip\n // sits right next to it so the keys are spotted immediately — not stranded\n // dim in the bottom-right corner.\n const chip = answerChip(prompt.defaultAnswer);\n const title = (prompt.context?.command ?? 'confirm').toUpperCase();\n const titleW = Math.max(0, cols - tag.length - chip.width);\n const titlePadded = padTo(title, titleW);\n const line1 = `${BAR_BG}${URGENT}${tag}${TITLE}${titlePadded}${RESET}${chip.ansi}`;\n\n // Row 2: the question itself, full width.\n const line2 = `${BAR_BG}${TXT}${indent}${padTo(prompt.message, contW)}${RESET}`;\n\n // Row 3: optional detail/sub-message, dimmer.\n const line3 = `${BAR_BG}${SUBTLE}${indent}${padTo(prompt.detail ?? '', contW)}${RESET}`;\n\n return [line1, line2, line3].slice(0, rows);\n}\n\nfunction renderNoticeBand(\n message: string,\n frame: number,\n cols: number,\n rows: number,\n): string[] {\n const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length]!;\n const prefix = ` ${spinner} `;\n const indent = ' '.repeat(prefix.length);\n const firstW = Math.max(0, cols - prefix.length);\n const contW = Math.max(0, cols - indent.length);\n\n const out: string[] = [];\n let i = 0;\n for (let r = 0; r < rows; r++) {\n const isLast = r === rows - 1;\n const w = r === 0 ? firstW : contW;\n let cell: string;\n if (i >= message.length) {\n cell = ' '.repeat(w);\n } else if (isLast) {\n cell = padTo(message.slice(i), w);\n i = message.length;\n } else {\n cell = message.slice(i, i + w).padEnd(w);\n i += w;\n }\n const lead = r === 0 ? prefix : indent;\n out.push(`${NOTICE_BG}${NOTICE_FG}${lead}${cell}${RESET}`);\n }\n return out;\n}\n\nfunction renderQuestionBand(\n payload: ClaudeQuestionPayload,\n cols: number,\n rows: number,\n): string[] {\n const q = payload.questions[0];\n if (!q) return Array.from({ length: rows }, () => blankBar(cols, BAR_BG));\n\n const tag = ' [?] ';\n const indent = ' '.repeat(tag.length);\n const innerW = Math.max(0, cols - tag.length);\n const contW = Math.max(0, cols - indent.length);\n\n const header = q.header && q.header.trim().length > 0 ? q.header : 'Question';\n const headerPadded = padTo(header, innerW);\n const line1 = `${BAR_BG}${QUESTION_ACCENT}${tag}${TXT}${headerPadded}${RESET}`;\n\n const questionText = padTo(q.question, contW);\n const line2 = `${BAR_BG}${TXT}${indent}${questionText}${RESET}`;\n\n const optLabels = q.options.map((o) => o.label).join(' · ');\n const optsLine = optLabels.length > 0 ? `options: ${optLabels}` : '';\n const optsPadded = padTo(optsLine, contW);\n const line3 = `${BAR_BG}${SUBTLE}${indent}${optsPadded}${RESET}`;\n\n return [line1, line2, line3].slice(0, rows);\n}\n\n/**\n * Render the 3-row alert band as an array of `rows` ANSI strings. Each\n * element is a full-width painted row (background tint reset at EOL).\n * Callers position the cursor at each row's column 1 and write the line\n * inside the same synchronized-output wrap as the footer.\n */\nexport function renderAlertBand(\n state: AlertBandState,\n cols: number,\n rows: number = ALERT_BAND_ROWS,\n): string[] {\n if (cols <= 0 || rows <= 0) return Array.from({ length: Math.max(0, rows) }, () => '');\n if (state.kind === 'prompt') return renderPromptBand(state.prompt, cols, rows);\n if (state.kind === 'notice') return renderNoticeBand(state.message, state.frame, cols, rows);\n return renderQuestionBand(state.question, cols, rows);\n}\n","import { request as httpRequest, type IncomingMessage } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport type { BoxNoticeEvent, PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * SSE subscription back to the relay's `GET /admin/prompts/stream`. The\n * relay pushes:\n * - `event: prompt-ask` data: PromptAskEvent (with id)\n * - `event: prompt-resolved` data: { id }\n * - `event: notice-set` data: BoxNoticeEvent (with id)\n * - `event: notice-clear` data: { id }\n * - `event: ping` data: { ts }\n *\n * We reconnect with exponential backoff on any error or close — the only\n * way to know the relay is back is to keep trying. Subscribers are\n * loopback-only so latency is sub-ms.\n */\nexport interface PromptStream {\n /** Stop subscribing; aborts any in-flight reconnect attempt. */\n close(): void;\n}\n\nexport interface SubscribeOptions {\n relayBaseUrl: string;\n boxId: string;\n onPrompt: (ev: PromptAskEvent) => void;\n /** Server-driven: a sibling wrapper (or this one) answered; the run loop\n * clears the footer for stale ids it didn't originate. */\n onResolved: (id: string) => void;\n /** A box-level informational notice was set (e.g. checkpoint in progress). */\n onNotice?: (ev: BoxNoticeEvent) => void;\n /** A previously-set notice was cleared (explicitly or via its TTL). */\n onNoticeCleared?: (id: string) => void;\n onError?: (err: Error) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 200;\nconst MAX_BACKOFF_MS = 5_000;\n\nexport function subscribePrompts(opts: SubscribeOptions): PromptStream {\n let closed = false;\n let req: ReturnType<typeof httpRequest> | null = null;\n let res: IncomingMessage | null = null;\n let reconnectTimer: NodeJS.Timeout | null = null;\n let backoffMs = INITIAL_BACKOFF_MS;\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch (err) {\n if (opts.onError) opts.onError(err instanceof Error ? err : new Error(String(err)));\n return { close: () => {} };\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n\n function scheduleReconnect(): void {\n if (closed) return;\n const delay = backoffMs;\n backoffMs = Math.min(MAX_BACKOFF_MS, backoffMs * 2);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n if (typeof reconnectTimer.unref === 'function') reconnectTimer.unref();\n }\n\n /**\n * SSE message parser: server sends `event: <type>\\n` then `data: <json>\\n\\n`.\n * The relay never splits an event across writes (one chunk per dispatch),\n * but we still buffer by message boundary `\\n\\n` so a mid-message slice\n * doesn't corrupt parsing.\n */\n let buffer = '';\n function consumeMessages(): void {\n let idx = buffer.indexOf('\\n\\n');\n while (idx !== -1) {\n const raw = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n idx = buffer.indexOf('\\n\\n');\n // Drop the SSE comment line we send on connect (`: connected`).\n if (raw.startsWith(':')) continue;\n let event = '';\n let dataLine = '';\n for (const line of raw.split('\\n')) {\n if (line.startsWith('event:')) event = line.slice('event:'.length).trim();\n else if (line.startsWith('data:')) dataLine = line.slice('data:'.length).trim();\n }\n if (event === 'prompt-ask' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as PromptAskEvent;\n if (ev && typeof ev.id === 'string') opts.onPrompt(ev);\n } catch {\n /* malformed; relay should never send this — ignore rather than die */\n }\n } else if (event === 'prompt-resolved' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onResolved(payload.id);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-set' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as BoxNoticeEvent;\n if (ev && typeof ev.id === 'string') opts.onNotice?.(ev);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-clear' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onNoticeCleared?.(payload.id);\n } catch {\n /* malformed; ignore */\n }\n }\n // 'ping' has no caller-visible side effect — its purpose is to keep\n // the socket from going idle and to let the wrapper detect dead links\n // via socket-level errors. No-op here.\n }\n }\n\n function connect(): void {\n if (closed) return;\n req = transport({\n host: url.hostname,\n port,\n method: 'GET',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/stream?boxId=${encodeURIComponent(opts.boxId)}`,\n headers: { Accept: 'text/event-stream' },\n });\n req.on('response', (r) => {\n res = r;\n if (r.statusCode !== 200) {\n // 400/403 — relay says \"no for you\"; bail without retrying since\n // these are config errors (no boxId, not loopback) that won't fix\n // themselves.\n if (opts.onError) opts.onError(new Error(`SSE stream returned ${String(r.statusCode)}`));\n r.resume();\n close();\n return;\n }\n backoffMs = INITIAL_BACKOFF_MS; // reset on a healthy connect\n r.setEncoding('utf8');\n r.on('data', (chunk: string) => {\n buffer += chunk;\n consumeMessages();\n });\n r.on('end', () => {\n if (!closed) scheduleReconnect();\n });\n r.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n });\n req.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n req.end();\n }\n\n function close(): void {\n if (closed) return;\n closed = true;\n if (reconnectTimer) clearTimeout(reconnectTimer);\n try {\n res?.destroy();\n } catch {\n /* best-effort */\n }\n try {\n req?.destroy();\n } catch {\n /* best-effort */\n }\n }\n\n connect();\n return { close };\n}\n\n/**\n * POST a PromptAnswerBody to /admin/prompts/answer. Fire-and-(mostly)-\n * forget: we don't retry on failure because the relay's `prompts.resolve`\n * is idempotent and a double-resolve returns 404. If the relay was dead,\n * the SSE reconnect loop will repush any prompts that are still pending.\n */\nexport interface PostAnswerOptions {\n relayBaseUrl: string;\n body: PromptAnswerBody;\n}\n\nexport interface PostAnswerResult {\n ok: boolean;\n status: number;\n}\n\nexport function postAnswer(opts: PostAnswerOptions): Promise<PostAnswerResult> {\n return new Promise<PostAnswerResult>((resolve) => {\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch {\n resolve({ ok: false, status: 0 });\n return;\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n const json = JSON.stringify(opts.body);\n const req = transport(\n {\n host: url.hostname,\n port,\n method: 'POST',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/answer`,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json).toString(),\n },\n timeout: 3000,\n },\n (res) => {\n res.resume();\n const status = res.statusCode ?? 0;\n // 204 = accepted; 404 = already answered (idempotent). Both are \"done\".\n resolve({ ok: status === 204 || status === 404, status });\n },\n );\n req.on('error', () => resolve({ ok: false, status: 0 }));\n req.on('timeout', () => {\n req.destroy();\n resolve({ ok: false, status: 0 });\n });\n req.write(json);\n req.end();\n });\n}\n","/**\n * Host→box clipboard image paste, cross-provider.\n *\n * Wired into the attach wrapper's Ctrl+V hook (`wrapped-pty/run.ts`). When the\n * user pastes while attached to an in-box Claude Code session we:\n * 1. grab the image off the host clipboard (`captureClipboardImage`),\n * 2. make sure the box's X server (`DISPLAY=:1`) is up,\n * 3. ship the PNG into the box (`Provider.uploadPath`),\n * 4. load it into the box's X11 CLIPBOARD via `xclip -t image/png`.\n * The wrapper then forwards the literal Ctrl+V so Claude Code's own\n * \"paste image from clipboard\" binding reads the now-populated selection.\n *\n * All steps go through the provider-neutral `Provider` seam (`uploadPath` +\n * `exec`), so it works identically on docker / daytona / hetzner.\n */\n\nimport { rm } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport type { BoxRecord, Provider } from '@agentbox/core';\nimport { captureClipboardImage } from './host-clipboard.js';\n\nexport type PasteImageResult = 'pasted' | 'no-image' | 'error';\n\n/** Box-side load: ensure Xvnc is up, wait for the X socket, then hand the PNG\n * to a detached `xclip` that keeps owning the CLIPBOARD selection until Claude\n * reads it. `setsid … &` so it survives `exec` returning. The path is a\n * CLI-generated `/tmp` name (no shell metacharacters), so inlining is safe. */\nfunction loadClipboardScript(boxPngPath: string): string {\n return [\n 'pgrep -x Xvnc >/dev/null 2>&1 || /usr/local/bin/agentbox-vnc-start >/dev/null 2>&1 || true',\n 'for _ in $(seq 1 30); do [ -S /tmp/.X11-unix/X1 ] && break; sleep 0.2; done',\n `setsid sh -c 'DISPLAY=:1 xclip -selection clipboard -t image/png -i ${boxPngPath}' </dev/null >/dev/null 2>&1 &`,\n ].join('; ');\n}\n\nexport async function pasteHostClipboardImage(\n provider: Provider,\n box: BoxRecord,\n): Promise<PasteImageResult> {\n if (typeof provider.uploadPath !== 'function') return 'error';\n\n const hostPng = await captureClipboardImage();\n if (!hostPng) return 'no-image';\n\n const boxPng = `/tmp/agentbox-clip-${String(Date.now())}.png`;\n try {\n await provider.uploadPath(box, hostPng, boxPng);\n await provider.exec(box, ['sh', '-lc', loadClipboardScript(boxPng)], {\n user: 'vscode',\n });\n return 'pasted';\n } catch {\n return 'error';\n } finally {\n await rm(dirname(hostPng), { recursive: true, force: true }).catch(() => {});\n }\n}\n","/**\n * Capture an image off the host clipboard to a temp PNG.\n *\n * Used by the Ctrl+V paste path (`paste-image.ts`): when the user pastes while\n * attached to an in-box Claude Code session, we grab whatever image they copied\n * on the host and ship it into the box. Supported hosts:\n * - macOS: `osascript` coerces the clipboard to PNG (TIFF screenshots are\n * converted with `sips`). Both ship with the OS.\n * - Linux (X11 / Wayland desktop): `xclip` / `wl-paste` read the `image/png`\n * clipboard target. These aren't always installed, so capture degrades to\n * `null` when the tool (or a display) is missing.\n * Every other platform returns `null`, so the caller cleanly forwards Ctrl+V\n * unchanged.\n */\n\nimport { mkdtemp, rm, stat, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\n\n/**\n * Grab the current clipboard image into a host temp PNG. Returns the file path,\n * or `null` when the clipboard holds no image (unsupported platform, missing\n * tool, or capture failed). The caller owns the returned file and should delete\n * it (and its parent dir) after delivery.\n */\nexport async function captureClipboardImage(): Promise<string | null> {\n if (process.platform !== 'darwin' && process.platform !== 'linux') return null;\n\n const dir = await mkdtemp(join(tmpdir(), 'agentbox-clip-'));\n const pngPath = join(dir, 'clip.png');\n const ok =\n process.platform === 'darwin'\n ? await captureDarwin(dir, pngPath)\n : await captureLinux(pngPath);\n\n if (ok) return pngPath;\n await rm(dir, { recursive: true, force: true }).catch(() => {});\n return null;\n}\n\n/**\n * True when this host has a clipboard-image capture path. Call sites use it to\n * decide whether to wire the Ctrl+V hook at all — so a host with no clipboard\n * tool (or no display) leaves Ctrl+V forwarding verbatim instead of\n * intercepting it for a guaranteed-empty paste.\n */\nexport async function clipboardCaptureAvailable(): Promise<boolean> {\n if (process.platform === 'darwin') return true;\n if (process.platform === 'linux') return (await linuxClipboardTool()) !== null;\n return false;\n}\n\n// ---- macOS ----\n\n/** AppleScript that writes the clipboard to `<path>` as PNG, falling back to\n * TIFF. Prints `PNG`, `TIFF`, or `NONE`. Returns the flattened `-e <line> …`\n * argv for `osascript`. */\nfunction captureScriptArgs(pngPath: string, tiffPath: string): string[] {\n return [\n 'try',\n ' set theData to (the clipboard as «class PNGf»)',\n ` set fh to open for access (POSIX file ${JSON.stringify(pngPath)}) with write permission`,\n ' set eof fh to 0',\n ' write theData to fh',\n ' close access fh',\n ' return \"PNG\"',\n 'on error',\n ' try',\n ' set theData to (the clipboard as «class TIFF»)',\n ` set fh to open for access (POSIX file ${JSON.stringify(tiffPath)}) with write permission`,\n ' set eof fh to 0',\n ' write theData to fh',\n ' close access fh',\n ' return \"TIFF\"',\n ' on error',\n ' return \"NONE\"',\n ' end try',\n 'end try',\n ]\n .map((line) => ['-e', line])\n .flat();\n}\n\nasync function captureDarwin(dir: string, pngPath: string): Promise<boolean> {\n const tiffPath = join(dir, 'clip.tiff');\n const res = await execa('osascript', captureScriptArgs(pngPath, tiffPath), {\n reject: false,\n });\n const kind = res.stdout.trim();\n\n if (kind === 'PNG') return fileHasBytes(pngPath);\n\n if (kind === 'TIFF' && (await fileHasBytes(tiffPath))) {\n // Screenshots land on the clipboard as TIFF; convert to PNG with sips.\n const conv = await execa(\n 'sips',\n ['-s', 'format', 'png', tiffPath, '--out', pngPath],\n { reject: false },\n );\n if (conv.exitCode === 0) return fileHasBytes(pngPath);\n }\n return false;\n}\n\n// ---- Linux (X11 / Wayland) ----\n\n/** Which clipboard tool to use on this Linux host, or `null` when none is\n * usable (no display, or the binary isn't installed). Wayland wins when a\n * Wayland session is present. */\nasync function linuxClipboardTool(): Promise<'wayland' | 'x11' | null> {\n if (process.env['WAYLAND_DISPLAY'] && (await hasCmd('wl-paste'))) return 'wayland';\n if (process.env['DISPLAY'] && (await hasCmd('xclip'))) return 'x11';\n return null;\n}\n\nasync function captureLinux(pngPath: string): Promise<boolean> {\n const tool = await linuxClipboardTool();\n if (!tool) return false;\n\n let buf: Buffer | null = null;\n if (tool === 'wayland') {\n const types = await execa('wl-paste', ['--list-types'], { reject: false });\n if (types.exitCode !== 0 || !/image\\/png/i.test(types.stdout)) return false;\n const r = await execa('wl-paste', ['--type', 'image/png'], {\n encoding: 'buffer',\n reject: false,\n });\n if (r.exitCode === 0) buf = asBuffer(r.stdout);\n } else {\n const sel = ['-selection', 'clipboard'];\n const targets = await execa('xclip', [...sel, '-t', 'TARGETS', '-o'], {\n reject: false,\n });\n if (targets.exitCode !== 0 || !/image\\/png/i.test(targets.stdout)) return false;\n const r = await execa('xclip', [...sel, '-t', 'image/png', '-o'], {\n encoding: 'buffer',\n reject: false,\n });\n if (r.exitCode === 0) buf = asBuffer(r.stdout);\n }\n\n if (!buf || !isPng(buf)) return false;\n await writeFile(pngPath, buf);\n return true;\n}\n\n// ---- helpers ----\n\n/** `command -v <cmd>` — true when the binary is on PATH. `cmd` is a fixed\n * literal at every call site (no injection surface). */\nasync function hasCmd(cmd: string): Promise<boolean> {\n const r = await execa('sh', ['-c', `command -v ${cmd}`], { reject: false });\n return r.exitCode === 0;\n}\n\nfunction asBuffer(out: unknown): Buffer | null {\n if (Buffer.isBuffer(out)) return out;\n if (out instanceof Uint8Array) return Buffer.from(out);\n return null;\n}\n\nfunction isPng(buf: Buffer): boolean {\n return (\n buf.length >= 8 &&\n buf[0] === 0x89 &&\n buf[1] === 0x50 &&\n buf[2] === 0x4e &&\n buf[3] === 0x47\n );\n}\n\nasync function fileHasBytes(path: string): Promise<boolean> {\n try {\n const s = await stat(path);\n return s.isFile() && s.size > 0;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;AAAA,SAAS,SAAAA,cAAa;AACtB,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,eAAe;;;ACQxB,IAAM,QAAsC,CAAC,UAAU,WAAW,WAAW,QAAQ;AAE9E,SAAS,gBAAgB,MAAyC;AACvE,SAAQ,MAA4B,SAAS,IAAI;AACnD;AAEA,eAAsB,YAAY,MAAuC;AACvE,UAAQ,MAAM;AAAA,IACZ,KAAK,UAAU;AACb,YAAM,MAAM,MAAM,OAAO,oBAA0B;AACnD,aAAO,IAAI;AAAA,IACb;AAAA,IACA,KAAK,WAAW;AAMd,YAAM,MAAM,MAAM,OAAO,oBAA2B;AACpD,YAAM,IAAI,yBAAyB;AACnC,aAAO,IAAI;AAAA,IACb;AAAA,IACA,KAAK,WAAW;AASd,YAAM,MAAM,MAAM,OAAO,oBAA2B;AACpD,YAAM,IAAI,yBAAyB;AACnC,aAAO,IAAI;AAAA,IACb;AAAA,IACA,KAAK,UAAU;AAKb,YAAM,MAAM,MAAM,OAAO,oBAA0B;AACnD,YAAM,IAAI,wBAAwB;AAClC,aAAO,IAAI;AAAA,IACb;AAAA,IACA;AACE,YAAM,IAAI,MAAM,6BAA6B,OAAO,IAAI,CAAC,EAAE;AAAA,EAC/D;AACF;AAGA,eAAsB,eAAe,KAAmC;AACtE,SAAO,YAAY,IAAI,YAAY,QAAQ;AAC7C;AAaA,eAAsB,kBAAkB,QAAiD;AACvF,QAAM,OAAO,OAAO,MAAM,KAAK;AAC/B,QAAM,OAAQ,QAAQ,KAAK,SAAS,IAAI,OAAO,OAAO,OAAO,IAAI;AACjE,MAAI,OAAO,SAAS,YAAY,KAAK,WAAW,KAAK,CAAC,gBAAgB,IAAI,GAAG;AAC3E,UAAM,IAAI;AAAA,MACR,6BAA6B,OAAO,IAAI,CAAC,aAAa,MAAM,KAAK,IAAI,CAAC;AAAA,IACxE;AAAA,EACF;AACA,SAAO,YAAY,IAAI;AACzB;;;ACtFA,SAAS,SAAAC,QAAO,iBAAiB;;;ACiDjC,eAAsB,iBAA6C;AACjE,MAAI;AACF,UAAM,SAAU,MAAM,OAAO,yCAAyC;AAItE,UAAM,WAAY,MAAM,OAAO,iBAAiB;AAChD,UAAMC,SACH,OAAO,OAAO,KACd,OAAO,SAAS,IAA4C,OAAO;AACtE,UAAM,WACH,SAAS,UAAU,KACnB,SAAS,SAAS,IAA4C,UAAU;AAC3E,QAAI,OAAOA,WAAU,cAAc,OAAO,aAAa,YAAY;AACjE,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,UAAUA;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACxEA,SAAS,aAAa;AAaf,SAAS,mBAAmB,MAAyB,QAAQ,KAAmB;AACrF,QAAM,OAAO,IAAI,MAAM;AACvB,MAAI,QAAQ,KAAK,SAAS,EAAG,QAAO;AACpC,QAAM,cAAc,IAAI,cAAc;AACtC,MAAI,gBAAgB,YAAa,QAAO;AACxC,SAAO;AACT;AAGA,SAAS,WAAW,GAAmB;AACrC,MAAI,EAAE,WAAW,EAAG,QAAO;AAG3B,SAAO,MAAM,EAAE,QAAQ,MAAM,OAAO,IAAI;AAC1C;AAGA,SAAS,kBAAkB,GAAmB;AAC5C,SAAO,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AACrD;AAGA,SAAS,UAAU,MAAwB;AACzC,SAAO,KAAK,IAAI,UAAU,EAAE,KAAK,GAAG;AACtC;AAmCA,eAAsB,mBACpB,MACmC;AACnC,MAAI,KAAK,SAAS,OAAQ,QAAO,YAAY,IAAI;AACjD,SAAO,cAAc,IAAI;AAC3B;AAEA,eAAe,YAAY,MAAiE;AAO1F,QAAM,SAAS,UAAU,KAAK,IAAI;AAClC,MAAI;AACJ,MAAI;AACJ,MAAI,KAAK,SAAS,SAAS;AACzB,eAAW,CAAC,gBAAgB,MAAM,MAAM,KAAK,KAAK,MAAM,MAAM;AAC9D,eAAW;AAAA,EACb,OAAO;AAEL,eAAW,CAAC,cAAc,MAAM,KAAK,OAAO,MAAM,KAAK,KAAK,MAAM,MAAM;AACxE,eAAW;AAAA,EACb;AACA,QAAM,IAAI,MAAM,SAAS,QAAQ,QAAQ;AACzC,MAAI,EAAE,SAAS,GAAG;AAChB,WAAO;AAAA,MACL,UAAU;AAAA,MACV,MAAM;AAAA,MACN,OAAO,QAAQ,SAAS,KAAK,GAAG,CAAC,WAAW,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,IAChF;AAAA,EACF;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,MAAM,mBAAmB,QAAQ;AAAA,EACnC;AACF;AAEA,eAAe,cAAc,MAAiE;AAK5F,QAAM,QAAQ,UAAU,KAAK,IAAI;AACjC,QAAM,UAAU,MAAM,WAAW,KAAK,GAAG,CAAC,YAAY,KAAK;AAC3D,QAAM,SAAS,IAAI,kBAAkB,OAAO,CAAC;AAQ7C,MAAI;AACJ,MAAI;AACJ,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA,2BAA2B,MAAM;AAAA,QACjC;AAAA,MACF;AACA,iBAAW;AACX;AAAA,IACF,KAAK;AACH,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA,8CAA8C,MAAM;AAAA,QACpD;AAAA,MACF;AACA,iBAAW;AACX;AAAA,IACF,KAAK;AACH,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA,8CAA8C,MAAM;AAAA,QACpD;AAAA,MACF;AACA,iBAAW;AACX;AAAA,EACJ;AAEA,QAAM,IAAI,MAAM,SAAS,aAAa,CAAC,MAAM,MAAM,KAAK,IAAI,CAAC,CAAC;AAC9D,MAAI,EAAE,SAAS,GAAG;AAChB,WAAO;AAAA,MACL,UAAU;AAAA,MACV,MAAM;AAAA,MACN,OAAO,oBAAoB,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,MAAM,mBAAmB,QAAQ;AAAA,EACnC;AACF;AAQA,SAAS,SAAS,KAAa,MAAsC;AACnE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,KAAK,MAAM,EAAE,OAAO,CAAC,UAAU,UAAU,MAAM,EAAE,CAAC;AACtE,QAAI,SAAS;AACb,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC1C,gBAAU,MAAM,SAAS,MAAM;AAAA,IACjC,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,cAAQ,EAAE,MAAM,KAAK,QAAQ,IAAI,QAAQ,CAAC;AAAA,IAC5C,CAAC;AACD,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,cAAQ,EAAE,MAAM,OAAO,SAAS,WAAW,OAAO,GAAG,OAAO,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC;AACH;;;AChMA,IAAM,MAAM;AACZ,IAAM,MAAM;AAKZ,SAAS,SAAS,OAAuB;AACvC,SAAO,MAAM,QAAQ,oBAAoB,GAAG,EAAE,KAAK;AACrD;AAOO,SAAS,iBACd,OACA,SAA6B,QAAQ,QAC/B;AACN,MAAI,CAAC,OAAO,MAAO;AACnB,SAAO,MAAM,GAAG,GAAG,MAAM,SAAS,KAAK,CAAC,GAAG,GAAG,EAAE;AAClD;AAQO,SAAS,kBAAkB,SAA6B,QAAQ,QAAc;AACnF,MAAI,CAAC,OAAO,MAAO;AACnB,SAAO,MAAM,GAAG,GAAG,QAAQ;AAC7B;AAGO,SAAS,iBAAiB,SAA6B,QAAQ,QAAc;AAClF,MAAI,CAAC,OAAO,MAAO;AACnB,SAAO,MAAM,GAAG,GAAG,QAAQ;AAC7B;;;ACAA,IAAM,YAAY;AAClB,IAAM,SAAS;AACf,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,YAAY;AAClB,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,WAAW;AACjB,IAAM,aAAa;AACnB,IAAM,aAAa;AACnB,IAAM,YAAY;AA2BlB,SAAS,YAAY,KAAa,GAA0B;AAC1D,MAAI,IAAI,CAAC,MAAM,WAAW,IAAI,IAAI,CAAC,MAAM,GAAc,QAAO;AAC9D,QAAM,SAAmB,CAAC;AAC1B,MAAI,MAAM;AACV,WAAS,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACvC,UAAM,IAAI,IAAI,CAAC;AACf,QAAI,MAAM,UAAa,KAAK,MAAQ,KAAK,IAAM;AAC7C,aAAO,MAAM,IAAI,IAAI,OAAO,MAAM,IAAI;AACtC;AAAA,IACF;AACA,QAAI,MAAM,IAAc;AACtB,aAAO,KAAK,GAAG;AACf,YAAM;AACN;AAAA,IACF;AACA,QAAI,MAAM,IAAc;AAGtB,aAAO,KAAK,GAAG;AACf,YAAM;AACN,aACE,IAAI,IAAI,IAAI,UACZ,IAAI,IAAI,CAAC,MAAM,MACf,IAAI,IAAI,CAAC,MAAM,OACf,IAAI,IAAI,CAAC,MAAM,KACf;AACA;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,MAAM,OAAgB,MAAM,KAAc;AAC5C,UAAI,OAAO,EAAG,QAAO,KAAK,GAAG;AAC7B,YAAM,MAAM,IAAI,IAAI;AACpB,YAAM,aAAa,CAAC,OAAyB,IAAI,IAAK,OAAO;AAC7D,UAAI,MAAM,KAAM;AAEd,cAAMC,QAAO,OAAO,CAAC;AACrB,YAAIA,UAAS,UAAaA,QAAO,EAAG,QAAO;AAC3C,eAAO,EAAE,KAAK,MAAAA,OAAM,MAAM,WAAW,OAAO,CAAC,KAAK,CAAC,EAAE;AAAA,MACvD;AAEA,UAAI,OAAO,CAAC,MAAM,GAAI,QAAO;AAC7B,YAAM,OAAO,OAAO,CAAC;AACrB,UAAI,SAAS,UAAa,OAAO,EAAG,QAAO;AAC3C,aAAO,EAAE,KAAK,MAAM,MAAM,WAAW,OAAO,CAAC,KAAK,CAAC,EAAE;AAAA,IACvD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,IAAM,4BAA4B;AA4B3B,SAAS,kBAAkB,MAAuC;AACvE,MAAI,SAA8B;AAClC,MAAI,WAAW;AAEf,QAAM,eAAe,KAAK,gBAAgB,CAAC;AAC3C,QAAM,gBAAgB,OAAO,KAAK,YAAY,EAAE,SAAS;AACzD,QAAM,eAAe,KAAK;AAC1B,QAAM,eAAe,OAAO,iBAAiB;AAC7C,MAAI,gBAAgB;AACpB,QAAM,kBAAkB,KAAK,mBAAmB;AAChD,QAAM,WAAW,KAAK,aAAa,CAAC,IAAI,OAAO,WAAW,IAAI,EAAE;AAChE,QAAM,aACJ,KAAK,eAAe,CAAC,MAAM,aAAa,CAAkC;AAC5E,MAAI,SAAS;AACb,MAAI,cAAuB;AAE3B,QAAM,eAAe,MAAY;AAC/B,QAAI,eAAe,MAAM;AACvB,iBAAW,WAAW;AACtB,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,aAAa,MAAY;AAC7B,QAAI,CAAC,OAAQ;AACb,aAAS;AACT,iBAAa;AACb,SAAK,iBAAiB,KAAK;AAAA,EAC7B;AAEA,QAAM,cAAc,MAAY;AAC9B,aAAS;AACT,iBAAa;AAGb,kBAAc,SAAS,iBAAiB,MAAM;AAC5C,oBAAc;AACd,iBAAW;AAAA,IACb,CAAC;AACD,SAAK,iBAAiB,IAAI;AAAA,EAC5B;AAGA,QAAM,oBAAoB,CAAC,MAAoB;AAC7C,QAAI,MAAM,YAAY;AAEpB,iBAAW;AACX,WAAK,UAAU,OAAO,KAAK,CAAC,UAAU,CAAC,CAAC;AACxC;AAAA,IACF;AACA,QAAI,MAAM,SAAS;AAEjB,iBAAW;AACX;AAAA,IACF;AACA,UAAM,SAAS,aAAa,OAAO,aAAa,CAAC,EAAE,YAAY,CAAC;AAChE,QAAI,QAAQ;AACV,iBAAW;AACX,WAAK,WAAW,MAAM;AACtB;AAAA,IACF;AAEA,eAAW;AACX,SAAK,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAAA,EACjC;AAEA,QAAM,SAAS,CACb,QACA,cACS;AACT,QAAI,CAAC,OAAQ;AACb,UAAM,OAAyB;AAAA,MAC7B,IAAI,OAAO,GAAG;AAAA,MACd;AAAA,MACA,GAAI,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;AAAA,IACzC;AACA,UAAM,IAAI;AACV,aAAS;AACT,MAAE,QAAQ,IAAI;AACd,SAAK,SAAS,IAAI;AAAA,EACpB;AAEA,QAAM,qBAAqB,CAAC,MAAoB;AAC9C,QAAI,CAAC,OAAQ;AACb,QAAI,MAAM,aAAa,MAAM,UAAU;AACrC,aAAO,GAAG;AACV;AAAA,IACF;AACA,QAAI,MAAM,aAAa,MAAM,UAAU;AACrC,aAAO,GAAG;AACV;AAAA,IACF;AACA,QAAI,MAAM,WAAW,MAAM,YAAY;AACrC,aAAO,KAAK,IAAI;AAChB;AAAA,IACF;AACA,QAAI,MAAM,aAAa,MAAM,QAAQ;AAEnC,YAAM,MAAM,OAAO,GAAG,iBAAiB;AACvC,aAAO,GAAG;AACV;AAAA,IACF;AAAA,EAEF;AAMA,QAAM,eAAe,MAAY;AAC/B,QAAI,cAAe;AACnB,oBAAgB;AAChB,UAAM,OAAO,MAAY;AACvB,sBAAgB;AAChB,UAAI,CAAC,SAAU,MAAK,UAAU,OAAO,KAAK,CAAC,UAAU,CAAC,CAAC;AAAA,IACzD;AACA,SAAK,QAAQ,QAAQ,EAClB,KAAK,MAAM,eAAe,CAAC,EAC3B,KAAK,MAAM,IAAI;AAAA,EACpB;AAIA,QAAM,aAAa,CAAC,QAAsB;AACxC,QAAI,aAAa;AACjB,UAAM,aAAa,CAAC,QAAsB;AACxC,UAAI,MAAM,WAAY,MAAK,UAAU,IAAI,SAAS,YAAY,GAAG,CAAC;AAClE,mBAAa;AAAA,IACf;AACA,QAAI,IAAI;AACR,WAAO,IAAI,IAAI,QAAQ;AACrB,YAAM,OAAO,IAAI,CAAC;AAClB,UAAI,SAAS,QAAW;AACtB;AACA;AAAA,MACF;AAEA,UAAI,QAAQ;AAIV,cAAM,IAAI,YAAY,KAAK,CAAC;AAC5B,YAAI,GAAG;AACL,cAAI,EAAE,QAAQ,EAAE,SAAS,WAAW;AAElC,uBAAW;AACX,iBAAK,UAAU,OAAO,KAAK,CAAC,UAAU,CAAC,CAAC;AAAA,UAC1C,OAAO;AACL,kBAAM,SAAS,aAAa,OAAO,aAAa,EAAE,IAAI,EAAE,YAAY,CAAC;AACrE,uBAAW;AACX,gBAAI,OAAQ,MAAK,WAAW,MAAM;AAAA,gBAC7B,MAAK,UAAU,IAAI,SAAS,GAAG,IAAI,EAAE,GAAG,CAAC;AAAA,UAChD;AACA,eAAK,EAAE;AACP,uBAAa;AACb;AAAA,QACF;AACA,0BAAkB,IAAI;AACtB,aAAK;AACL,qBAAa;AACb;AAAA,MACF;AAEA,UAAI,iBAAiB,SAAS,YAAY;AACxC,mBAAW,CAAC;AACZ,oBAAY;AACZ,aAAK;AACL,qBAAa;AACb;AAAA,MACF;AAEA,UAAI,iBAAiB,SAAS,SAAS;AACrC,cAAM,IAAI,YAAY,KAAK,CAAC;AAC5B,YAAI,KAAK,EAAE,QAAQ,EAAE,SAAS,WAAW;AACvC,qBAAW,CAAC;AACZ,sBAAY;AACZ,eAAK,EAAE;AACP,uBAAa;AACb;AAAA,QACF;AAAA,MACF;AACA,UAAI,gBAAgB,SAAS,YAAY;AACvC,mBAAW,CAAC;AACZ,aAAK;AACL,qBAAa;AACb,qBAAa;AACb;AAAA,MACF;AACA,WAAK;AAAA,IACP;AACA,eAAW,IAAI,MAAM;AAAA,EACvB;AAEA,SAAO;AAAA,IACL,IAAI,YAAqB;AACvB,aAAO,WAAW;AAAA,IACpB;AAAA,IACA,KAAK,KAAmB;AACtB,UAAI,SAAU;AACd,UAAI,QAAQ;AAQV,YAAI,IAAI,SAAS,KAAK,IAAI,CAAC,MAAM,QAAS;AAM1C,iBAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,gBAAM,OAAO,IAAI,CAAC;AAClB,cAAI,SAAS,OAAW;AACxB,cAAI,QAAQ;AACV,+BAAmB,IAAI;AAAA,UACzB,OAAO;AAGL,iBAAK,UAAU,IAAI,SAAS,CAAC,CAAC;AAC9B;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI,CAAC,iBAAiB,CAAC,cAAc;AACnC,aAAK,UAAU,GAAG;AAClB;AAAA,MACF;AACA,iBAAW,GAAG;AAAA,IAChB;AAAA,IACA,QAAQ,IAA+C;AACrD,aAAO,IAAI,QAA0B,CAAC,SAAS,WAAW;AAExD,YAAI,OAAQ,YAAW;AACvB,YAAI,QAAQ;AAKV,iBAAO,KAAK,IAAI;AAAA,QAClB;AACA,iBAAS,EAAE,IAAI,SAAS,OAAO;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,IACA,MAAM,QAAc;AAClB,UAAI,CAAC,OAAQ;AACb,YAAM,IAAI;AACV,eAAS;AACT,YAAM,MAAM,WAAW,aAAa,eAAe;AACnD,QAAE,OAAO,IAAI,MAAM,GAAG,CAAC;AAAA,IACzB;AAAA,IACA,UAAgB;AACd,UAAI,SAAU;AACd,iBAAW;AACX,mBAAa;AACb,UAAI,QAAQ;AACV,cAAM,IAAI;AACV,iBAAS;AACT,UAAE,OAAO,IAAI,MAAM,uBAAuB,CAAC;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AACF;;;ACpXA,SAAS,UAAU,GAAW,KAAqB;AACjD,MAAI,OAAO,EAAG,QAAO;AACrB,MAAI,EAAE,UAAU,IAAK,QAAO;AAC5B,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI;AAC/B;AAIA,SAAS,cAAc,GAAW,KAAqB;AACrD,MAAI,OAAO,EAAG,QAAO;AACrB,MAAI,EAAE,UAAU,IAAK,QAAO;AAC5B,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,WAAM,EAAE,MAAM,EAAE,UAAU,MAAM,EAAE;AAC3C;AAEO,SAAS,aAAa,GAAuB;AAGlD,MAAI,EAAE,cAAe,QAAO;AAE5B,MAAI,EAAE,cAAe,QAAO;AAC5B,MAAI,EAAE,UAAU,UAAW,QAAO,IAAI,EAAE,KAAK;AAC7C,UAAQ,EAAE,UAAU;AAAA,IAClB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKO,IAAM,aAAa;AACnB,IAAM,gBAAgB;AAGtB,IAAM,iBAAiB;AAO9B,SAAS,UAAU,OAAe,GAAmB;AACnD,QAAM,OAAO,4BAAQ,KAAK;AAC1B,MAAI,KAAK,UAAU,EAAG,QAAO,KAAK,MAAM,GAAG,CAAC;AAC5C,SAAO,OAAO,SAAI,OAAO,IAAI,KAAK,MAAM;AAC1C;AAKA,SAAS,IAAI,GAAW,GAAmB;AACzC,MAAI,EAAE,WAAW,EAAG,QAAO;AAC3B,MAAI,EAAE,SAAS,EAAG,QAAO,EAAE,MAAM,GAAG,CAAC;AACrC,SAAO,IAAI,IAAI,OAAO,IAAI,EAAE,MAAM;AACpC;AAGA,SAAS,OAAO,GAAW,GAAmB;AAC5C,MAAI,EAAE,UAAU,EAAG,QAAO,EAAE,MAAM,GAAG,CAAC;AACtC,QAAM,MAAM,IAAI,EAAE;AAClB,QAAM,UAAU,KAAK,MAAM,MAAM,CAAC;AAClC,SAAO,IAAI,OAAO,OAAO,IAAI,IAAI,IAAI,OAAO,MAAM,OAAO;AAC3D;AAGA,SAAS,aAAa,SAAqC;AACzD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/C,SAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AACpC;AAMO,SAAS,gBAAgB,GAAmB;AACjD,QAAM,IAAI,EAAE,QAAQ,kBAAkB,EAAE;AACxC,SAAO,EAAE,SAAS,IAAI,IAAI,EAAE,KAAK;AACnC;AAQA,SAAS,OAAO,GAAe,QAAgB,GAAmB;AAChE,QAAM,SAAS,EAAE,SAAS,OAAO,GAAG,EAAE,KAAK,MAAM;AACjD,QAAM,SAAS,aAAa,CAAC;AAC7B,QAAM,OAAO,GAAG,MAAM,GAAG,MAAM;AAG/B,QAAM,OAAO,IAAI,KAAK,SAAS,OAAO,SAAS;AAC/C,MAAI,QAAQ,EAAG,QAAO,IAAI,GAAG,IAAI,GAAG,MAAM,IAAI,CAAC;AAC/C,QAAM,SACJ,EAAE,UAAU,aAAa,EAAE,eACvB,UAAU,gBAAgB,EAAE,YAAY,GAAG,IAAI,IAC/C,cAAc,EAAE,MAAM,IAAI;AAGhC,SAAO,IAAI,GAAG,IAAI,GAAG,MAAM,IAAI,IAAI,OAAO,SAAS,CAAC,IAAI,SAAS;AACnE;AAQO,SAAS,aACd,OACA,YACA,GACA,GACe;AACf,QAAM,QAAkB,CAAC,UAAU,gBAAgB,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;AACjE,QAAM,WAA8B,CAAC,MAAM,IAAI;AAC/C,QAAM,aAAwB,CAAC,MAAM,KAAK;AAC1C,QAAM,OAAO,CAAC,MAAc,OAAsB,WAA0B;AAC1E,UAAM,KAAK,IAAI,MAAM,CAAC,CAAC;AACvB,aAAS,KAAK,KAAK;AACnB,eAAW,KAAK,MAAM;AAAA,EACxB;AAEA,MAAI;AACJ,MAAI,YAAY;AAChB,aAAW,KAAK,OAAO;AACrB,UAAM,SAAS,EAAE,OAAO,aAAa,WAAM;AAC3C,QAAI,EAAE,OAAO,YAAY;AACvB,WAAK,GAAG,MAAM,GAAG,aAAa,IAAI,EAAE,IAAI,KAAK;AAC7C;AAAA,IACF;AACA,QAAI,CAAC,aAAa,EAAE,YAAY,aAAa;AAC3C,WAAK,OAAO,iBAAO,aAAa,EAAE,OAAO,CAAC,kBAAQ,CAAC,GAAG,MAAM,IAAI;AAChE,oBAAc,EAAE;AAChB,kBAAY;AAAA,IACd;AACA,SAAK,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,KAAK;AAAA,EACxC;AACA,MAAI,MAAM,WAAW,EAAG,MAAK,eAAe,MAAM,KAAK;AACvD,SAAO,MAAM,SAAS,EAAG,MAAK,IAAI,MAAM,KAAK;AAC7C,SAAO;AAAA,IACL,OAAO,MAAM,MAAM,GAAG,CAAC;AAAA,IACvB,UAAU,SAAS,MAAM,GAAG,CAAC;AAAA,IAC7B,YAAY,WAAW,MAAM,GAAG,CAAC;AAAA,EACnC;AACF;AAMO,SAAS,UAAU,SAAiB,GAAW,GAAqB;AACzE,QAAM,OAAO;AAAA,IACX;AAAA,IACA,yBAAyB,OAAO;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC,CAAC;AACzD,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,GAAG,IAAK,KAAI,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC;AAChE,SAAO;AACT;AAOO,SAAS,mBACd,SACA,OACA,gBACA,GACA,GACU;AACV,QAAM,OAAO,iBACT;AAAA,IACE;AAAA,IACA,aAAa,OAAO;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IACA;AAAA,IACE;AAAA,IACA,SAAS,OAAO,OAAO,KAAK;AAAA,IAC5B;AAAA,IACA,UAAU,WAAW,oBAAoB;AAAA,IACzC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACJ,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC,CAAC;AACzD,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,GAAG,IAAK,KAAI,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC;AAChE,SAAO;AACT;AAMO,SAAS,gBAAgB,OAAe,GAAW,GAAqB;AAC7E,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,KAAK;AAAA,IACb;AAAA,IACA;AAAA,EACF;AACA,QAAM,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC,CAAC;AACzD,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,GAAG,IAAK,KAAI,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC;AAChE,SAAO;AACT;AASO,IAAM,SAAS;AACtB,IAAM,WAAW,SAAS;AAC1B,IAAM,YAAY;AAClB,IAAM,aAAa;AACnB,IAAM,eAAe;AACrB,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,YAAY;AAIlB,IAAM,cAAyC,CAAC,gCAAsB,QAAQ;AAC9E,IAAM,cAAwD;AAAA,EAC5D;AAAA,EACA,CAAC,eAAe,MAAM;AAAA,EACtB,CAAC,eAAe,QAAQ;AAAA,EACxB,CAAC,eAAe,KAAK;AAAA,EACrB,CAAC,eAAe,MAAM;AACxB;AAMO,IAAM,wBAAkE;AAAA,EAC7E;AAAA,EACA,CAAC,aAAa,MAAM;AACtB;AAIO,IAAM,uBAAiE;AAAA,EAC5E,CAAC,KAAK,MAAM;AAAA,EACZ,CAAC,KAAK,QAAQ;AAAA,EACd,CAAC,KAAK,KAAK;AAAA,EACX,CAAC,KAAK,MAAM;AAAA,EACZ,CAAC,KAAK,OAAO;AAAA,EACb,CAAC,KAAK,SAAS;AAAA,EACf,CAAC,KAAK,MAAM;AACd;AAWO,SAAS,WACd,KACA,GACA,YACA,SAAmD,aACnD,gBACQ;AACR,QAAM,QACJ,eAAe,MAAO,IAAI,UAAU,YAAa,IAAI,YAAY,YAAa,IAAI,QAAS;AAE7F,QAAM,cAAc,MAAM,sBAAiB;AAG3C,QAAM,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,KAAK,MAAM;AAC9C,QAAM,WAAW,MAAM,GAAG,IAAI,MAAM;AACpC,QAAM,YAAY,cAAc;AAEhC,QAAM,MAAM;AACZ,QAAM,cAAc,CAClB,OACuC;AAAA,IACvC,OAAO,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI;AAAA,IACnD,QACE,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,QAAQ,GAAG,CAAC,GAAG,QAAQ,KAAK,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,GAAG,EAAE,IAAI;AAAA,EACtF;AAOA,MAAI,QAAkD;AACtD,aAAW,KAAK,CAAC,QAAQ,kBAAkB,qBAAqB,GAAG;AACjE,UAAM,IAAI,YAAY,CAAC;AACvB,QAAI,UAAU,SAAS,EAAE,MAAM,SAAS,KAAK,GAAG;AAC9C,cAAQ;AACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,OAAO;AACV,WAAO,WAAW,YAAY,IAAI,WAAW,CAAC,IAAI;AAAA,EACpD;AAIA,QAAM,OAAO,IAAI,UAAU,SAAS,MAAM,MAAM,SAAS;AACzD,MAAI,WAAW;AACf,MAAI,KAAK,gBAAgB,QAAQ,GAAG;AAClC,eAAW,WAAM,UAAU,IAAI,cAAc,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC;AAAA,EACtE;AAEA,QAAM,YAAY,cAAc,OAAO,YAAY,MAAM,MAAM;AAC/D,QAAM,aACJ,YAAY,cAAc,aAAa,OAAO,YAAY,MAAM,MAAM,MAAM;AAC9E,QAAM,MAAM,IAAI,UAAU,SAAS,MAAM,MAAM;AAE/C,SACE,WACA,aACA,WACA,IAAI,OAAO,GAAG,IACd,MAAM,SACN;AAEJ;;;ACnWO,IAAM,iBAAiB,CAAC,UAAK,UAAK,UAAK,QAAG;AAEjD,IAAM,SAAS;AACf,IAAM,QAAQ;AACd,IAAM,MAAM;AACZ,IAAM,SAAS;AACf,IAAM,QAAQ;AACd,IAAM,YAAY;AAClB,IAAM,eAAe;AAIrB,IAAM,kBAAkB;AAIxB,IAAM,YAAY;AAClB,IAAM,YAAY;AAElB,IAAM,WAAW;AAIjB,IAAM,wBAAkE;AAAA,EACtE,CAAC,aAAa,SAAS;AACzB;AAGA,IAAM,6BAAuE;AAAA,EAC3E,CAAC,aAAa,SAAS;AAAA,EACvB,CAAC,eAAe,QAAQ;AAC1B;AAGA,IAAM,mBAA6D;AAAA,EACjE,CAAC,eAAe,QAAQ;AAC1B;AAIA,IAAM,0BAAoE;AAAA,EACxE,CAAC,KAAK,MAAM;AAAA,EACZ,CAAC,KAAK,QAAQ;AAAA,EACd,CAAC,KAAK,KAAK;AAAA,EACX,CAAC,KAAK,QAAQ;AAChB;AACA,IAAM,qBAA+D;AAAA,EACnE,CAAC,KAAK,MAAM;AAAA,EACZ,CAAC,KAAK,QAAQ;AAAA,EACd,CAAC,KAAK,KAAK;AACb;AAMA,SAAS,MAAM,SAAiB,OAAuB;AACrD,MAAI,QAAQ,WAAW,MAAO,QAAO;AACrC,MAAI,QAAQ,SAAS,OAAO;AAC1B,QAAI,SAAS,EAAG,QAAO,QAAQ,MAAM,GAAG,KAAK;AAC7C,WAAO,QAAQ,MAAM,GAAG,QAAQ,CAAC,IAAI;AAAA,EACvC;AACA,SAAO,UAAU,IAAI,OAAO,QAAQ,QAAQ,MAAM;AACpD;AASA,SAAS,WAAW,eAAuE;AACzF,QAAM,SAAS;AACf,QAAM,QAAQ;AACd,QAAM,MAAM;AACZ,QAAM,eAAe,kBAAkB;AACvC,QAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,KAAK;AACpE,QAAM,KAAK,eAAe,QAAQ,GAAG,SAAS,GAAG,KAAK,GAAG,YAAY;AACrE,QAAM,OAAO,GAAG,SAAS,GAAG,SAAS,IAAI,GAAG,GAAG,GAAG,GAAG,EAAE,IAAI,KAAK;AAGhE,QAAM,QAAQ,IAAI,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI;AAC1C,SAAO,EAAE,MAAM,MAAM;AACvB;AAOO,SAAS,aAAa,OAAoB,MAAsB;AACrE,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,MAAM,SAAS,QAAQ;AACzB,UAAM,aAAyB;AAAA,MAC7B,IAAI;AAAA;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,OAAO;AAAA;AAAA,MACP,UAAU,MAAM;AAAA,MAChB,cAAc,MAAM;AAAA,IACtB;AACA,UAAM,WAAW,MAAM,SAAS;AAChC,UAAM,aAAa,MAAM,cAAc;AAKvC,UAAM,aAAa,WAAW,SAAY,MAAM,SAAS,UAAU,UAAU,MAAM;AACnF,QAAI,MAAM,cAAc;AACtB,YAAM,cAAc,aAAa,0BAA0B;AAC3D,aAAO,WAAW,YAAY,MAAM,YAAY,WAAW;AAAA,IAC7D;AAGA,UAAM,YAAY,aAAa,6BAA6B;AAC5D,UAAM,WAAW,aAAa,mBAAmB;AACjD,WAAO,WAAW,YAAY,MAAM,YAAY,WAAW,QAAQ;AAAA,EACrE;AACA,MAAI,MAAM,SAAS,SAAS;AAE1B,UAAM,SAAS;AACf,UAAMC,SAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,MAAM;AAC9C,UAAMC,WAAU,MAAM,MAAM,SAASD,MAAK;AAC1C,WAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,GAAG,GAAGC,QAAO,GAAG,KAAK;AAAA,EAC9D;AACA,MAAI,MAAM,SAAS,UAAU;AAI3B,UAAMC,WAAU,eAAe,MAAM,QAAQ,eAAe,MAAM;AAClE,UAAM,SAAS,IAAIA,QAAO;AAC1B,UAAMF,SAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,MAAM;AAC9C,UAAMC,WAAU,MAAM,MAAM,SAASD,MAAK;AAC1C,WAAO,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAGC,QAAO,GAAG,KAAK;AAAA,EAC5D;AAIA,QAAM,OAAO,WAAW,MAAM,OAAO,aAAa;AAClD,QAAM,MAAM;AACZ,QAAM,MAAM;AACZ,QAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,IAAI,SAAS,KAAK,KAAK;AACxD,QAAM,YAAY,MAAM,OAAO,UAAU;AACzC,MAAI,UAAU,MAAM,OAAO;AAC3B,MAAI,SAAS;AACb,QAAM,gBAAgB,KAAK,IAAI,GAAG,SAAS,OAAO,SAAS,IAAI,IAAI,SAAS,IAAI,EAAE;AAClF,MAAI,QAAQ,SAAS,eAAe;AAClC,cAAU,QAAQ,MAAM,GAAG,KAAK,IAAI,GAAG,gBAAgB,CAAC,CAAC,IAAI;AAAA,EAC/D;AACA,QAAM,gBAAgB,QAAQ;AAC9B,QAAM,eAAe,KAAK,IAAI,GAAG,QAAQ,gBAAgB,IAAI,MAAM;AACnE,MAAI,OAAO,SAAS,cAAc;AAChC,aAAS,gBAAgB,IAAI,KAAK,OAAO,MAAM,GAAG,eAAe,CAAC,IAAI;AAAA,EACxE;AACA,QAAM,cAAc,OAAO,SAAS,IAAI,GAAG,OAAO,GAAG,GAAG,GAAG,MAAM,KAAK;AACtE,QAAM,SAAS,MAAM,aAAa,KAAK;AACvC,SAAO,GAAG,MAAM,GAAG,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,IAAI;AACpE;AAKO,SAAS,aAAa,KAAa,KAAqB;AAC7D,SAAO,QAAQ,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC;AAC3C;AAEO,IAAM,cAAc;AACpB,IAAM,iBAAiB;AAMvB,IAAM,aAAa;AACnB,IAAM,WAAW;AAmBjB,IAAM,kBAAkB;AAE/B,SAAS,SAAS,MAAc,IAAoB;AAClD,SAAO,GAAG,EAAE,GAAG,IAAI,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK;AACtD;AAEA,SAAS,iBAAiB,QAAwB,MAAc,MAAwB;AACtF,QAAM,MAAM;AACZ,QAAM,SAAS,IAAI,OAAO,IAAI,MAAM;AACpC,QAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,MAAM;AAM9C,QAAM,OAAO,WAAW,OAAO,aAAa;AAC5C,QAAM,SAAS,OAAO,SAAS,WAAW,WAAW,YAAY;AACjE,QAAM,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,SAAS,KAAK,KAAK;AACzD,QAAM,cAAc,MAAM,OAAO,MAAM;AACvC,QAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,GAAG,KAAK,IAAI;AAGhF,QAAM,QAAQ,GAAG,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,MAAM,OAAO,SAAS,KAAK,CAAC,GAAG,KAAK;AAG7E,QAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,OAAO,UAAU,IAAI,KAAK,CAAC,GAAG,KAAK;AAErF,SAAO,CAAC,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG,IAAI;AAC5C;AAEA,SAAS,iBACP,SACA,OACA,MACA,MACU;AACV,QAAMC,WAAU,eAAe,QAAQ,eAAe,MAAM;AAC5D,QAAM,SAAS,IAAIA,QAAO;AAC1B,QAAM,SAAS,IAAI,OAAO,OAAO,MAAM;AACvC,QAAM,SAAS,KAAK,IAAI,GAAG,OAAO,OAAO,MAAM;AAC/C,QAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,MAAM;AAE9C,QAAM,MAAgB,CAAC;AACvB,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,UAAM,SAAS,MAAM,OAAO;AAC5B,UAAM,IAAI,MAAM,IAAI,SAAS;AAC7B,QAAI;AACJ,QAAI,KAAK,QAAQ,QAAQ;AACvB,aAAO,IAAI,OAAO,CAAC;AAAA,IACrB,WAAW,QAAQ;AACjB,aAAO,MAAM,QAAQ,MAAM,CAAC,GAAG,CAAC;AAChC,UAAI,QAAQ;AAAA,IACd,OAAO;AACL,aAAO,QAAQ,MAAM,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC;AACvC,WAAK;AAAA,IACP;AACA,UAAM,OAAO,MAAM,IAAI,SAAS;AAChC,QAAI,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE;AAAA,EAC3D;AACA,SAAO;AACT;AAEA,SAAS,mBACP,SACA,MACA,MACU;AACV,QAAM,IAAI,QAAQ,UAAU,CAAC;AAC7B,MAAI,CAAC,EAAG,QAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,GAAG,MAAM,SAAS,MAAM,MAAM,CAAC;AAExE,QAAM,MAAM;AACZ,QAAM,SAAS,IAAI,OAAO,IAAI,MAAM;AACpC,QAAM,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,MAAM;AAC5C,QAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,OAAO,MAAM;AAE9C,QAAM,SAAS,EAAE,UAAU,EAAE,OAAO,KAAK,EAAE,SAAS,IAAI,EAAE,SAAS;AACnE,QAAM,eAAe,MAAM,QAAQ,MAAM;AACzC,QAAM,QAAQ,GAAG,MAAM,GAAG,eAAe,GAAG,GAAG,GAAG,GAAG,GAAG,YAAY,GAAG,KAAK;AAE5E,QAAM,eAAe,MAAM,EAAE,UAAU,KAAK;AAC5C,QAAM,QAAQ,GAAG,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,YAAY,GAAG,KAAK;AAE7D,QAAM,YAAY,EAAE,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,QAAK;AAC1D,QAAM,WAAW,UAAU,SAAS,IAAI,YAAY,SAAS,KAAK;AAClE,QAAM,aAAa,MAAM,UAAU,KAAK;AACxC,QAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK;AAE9D,SAAO,CAAC,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG,IAAI;AAC5C;AAQO,SAAS,gBACd,OACA,MACA,OAAe,iBACL;AACV,MAAI,QAAQ,KAAK,QAAQ,EAAG,QAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI,EAAE,GAAG,MAAM,EAAE;AACrF,MAAI,MAAM,SAAS,SAAU,QAAO,iBAAiB,MAAM,QAAQ,MAAM,IAAI;AAC7E,MAAI,MAAM,SAAS,SAAU,QAAO,iBAAiB,MAAM,SAAS,MAAM,OAAO,MAAM,IAAI;AAC3F,SAAO,mBAAmB,MAAM,UAAU,MAAM,IAAI;AACtD;;;AC9VA,SAAS,WAAW,mBAAyC;AAC7D,SAAS,WAAW,oBAAoB;AAmCxC,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB;AAEhB,SAAS,iBAAiB,MAAsC;AACrE,MAAI,SAAS;AACb,MAAI,MAA6C;AACjD,MAAI,MAA8B;AAClC,MAAI,iBAAwC;AAC5C,MAAI,YAAY;AAChB,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,KAAK,YAAY;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,KAAK,QAAS,MAAK,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAClF,WAAO,EAAE,OAAO,MAAM;AAAA,IAAC,EAAE;AAAA,EAC3B;AACA,QAAM,UAAU,IAAI,aAAa;AACjC,QAAM,YAAY,UAAU,eAAe;AAC3C,QAAM,OAAO,IAAI,KAAK,SAAS,IAAI,OAAO,SAAS,IAAI,MAAM,EAAE,IAAI,UAAU,MAAM;AAEnF,WAAS,oBAA0B;AACjC,QAAI,OAAQ;AACZ,UAAM,QAAQ;AACd,gBAAY,KAAK,IAAI,gBAAgB,YAAY,CAAC;AAClD,qBAAiB,WAAW,MAAM;AAChC,uBAAiB;AACjB,cAAQ;AAAA,IACV,GAAG,KAAK;AACR,QAAI,OAAO,eAAe,UAAU,WAAY,gBAAe,MAAM;AAAA,EACvE;AAQA,MAAI,SAAS;AACb,WAAS,kBAAwB;AAC/B,QAAI,MAAM,OAAO,QAAQ,MAAM;AAC/B,WAAO,QAAQ,IAAI;AACjB,YAAM,MAAM,OAAO,MAAM,GAAG,GAAG;AAC/B,eAAS,OAAO,MAAM,MAAM,CAAC;AAC7B,YAAM,OAAO,QAAQ,MAAM;AAE3B,UAAI,IAAI,WAAW,GAAG,EAAG;AACzB,UAAI,QAAQ;AACZ,UAAI,WAAW;AACf,iBAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,YAAI,KAAK,WAAW,QAAQ,EAAG,SAAQ,KAAK,MAAM,SAAS,MAAM,EAAE,KAAK;AAAA,iBAC/D,KAAK,WAAW,OAAO,EAAG,YAAW,KAAK,MAAM,QAAQ,MAAM,EAAE,KAAK;AAAA,MAChF;AACA,UAAI,UAAU,gBAAgB,SAAS,SAAS,GAAG;AACjD,YAAI;AACF,gBAAM,KAAK,KAAK,MAAM,QAAQ;AAC9B,cAAI,MAAM,OAAO,GAAG,OAAO,SAAU,MAAK,SAAS,EAAE;AAAA,QACvD,QAAQ;AAAA,QAER;AAAA,MACF,WAAW,UAAU,qBAAqB,SAAS,SAAS,GAAG;AAC7D,YAAI;AACF,gBAAM,UAAU,KAAK,MAAM,QAAQ;AACnC,cAAI,WAAW,OAAO,QAAQ,OAAO,SAAU,MAAK,WAAW,QAAQ,EAAE;AAAA,QAC3E,QAAQ;AAAA,QAER;AAAA,MACF,WAAW,UAAU,gBAAgB,SAAS,SAAS,GAAG;AACxD,YAAI;AACF,gBAAM,KAAK,KAAK,MAAM,QAAQ;AAC9B,cAAI,MAAM,OAAO,GAAG,OAAO,SAAU,MAAK,WAAW,EAAE;AAAA,QACzD,QAAQ;AAAA,QAER;AAAA,MACF,WAAW,UAAU,kBAAkB,SAAS,SAAS,GAAG;AAC1D,YAAI;AACF,gBAAM,UAAU,KAAK,MAAM,QAAQ;AACnC,cAAI,WAAW,OAAO,QAAQ,OAAO,SAAU,MAAK,kBAAkB,QAAQ,EAAE;AAAA,QAClF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IAIF;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,QAAI,OAAQ;AACZ,UAAM,UAAU;AAAA,MACd,MAAM,IAAI;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,GAAG,IAAI,SAAS,QAAQ,OAAO,EAAE,CAAC,+BAA+B,mBAAmB,KAAK,KAAK,CAAC;AAAA,MACrG,SAAS,EAAE,QAAQ,oBAAoB;AAAA,IACzC,CAAC;AACD,QAAI,GAAG,YAAY,CAAC,MAAM;AACxB,YAAM;AACN,UAAI,EAAE,eAAe,KAAK;AAIxB,YAAI,KAAK,QAAS,MAAK,QAAQ,IAAI,MAAM,uBAAuB,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;AACvF,UAAE,OAAO;AACT,cAAM;AACN;AAAA,MACF;AACA,kBAAY;AACZ,QAAE,YAAY,MAAM;AACpB,QAAE,GAAG,QAAQ,CAAC,UAAkB;AAC9B,kBAAU;AACV,wBAAgB;AAAA,MAClB,CAAC;AACD,QAAE,GAAG,OAAO,MAAM;AAChB,YAAI,CAAC,OAAQ,mBAAkB;AAAA,MACjC,CAAC;AACD,QAAE,GAAG,SAAS,MAAM;AAClB,YAAI,CAAC,OAAQ,mBAAkB;AAAA,MACjC,CAAC;AAAA,IACH,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AACpB,UAAI,CAAC,OAAQ,mBAAkB;AAAA,IACjC,CAAC;AACD,QAAI,IAAI;AAAA,EACV;AAEA,WAAS,QAAc;AACrB,QAAI,OAAQ;AACZ,aAAS;AACT,QAAI,eAAgB,cAAa,cAAc;AAC/C,QAAI;AACF,WAAK,QAAQ;AAAA,IACf,QAAQ;AAAA,IAER;AACA,QAAI;AACF,WAAK,QAAQ;AAAA,IACf,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,UAAQ;AACR,SAAO,EAAE,MAAM;AACjB;AAkBO,SAAS,WAAW,MAAoD;AAC7E,SAAO,IAAI,QAA0B,CAAC,YAAY;AAChD,QAAI;AACJ,QAAI;AACF,YAAM,IAAI,IAAI,KAAK,YAAY;AAAA,IACjC,QAAQ;AACN,cAAQ,EAAE,IAAI,OAAO,QAAQ,EAAE,CAAC;AAChC;AAAA,IACF;AACA,UAAM,UAAU,IAAI,aAAa;AACjC,UAAM,YAAY,UAAU,eAAe;AAC3C,UAAM,OAAO,IAAI,KAAK,SAAS,IAAI,OAAO,SAAS,IAAI,MAAM,EAAE,IAAI,UAAU,MAAM;AACnF,UAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,UAAM,MAAM;AAAA,MACV;AAAA,QACE,MAAM,IAAI;AAAA,QACV;AAAA,QACA,QAAQ;AAAA,QACR,MAAM,GAAG,IAAI,SAAS,QAAQ,OAAO,EAAE,CAAC;AAAA,QACxC,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,kBAAkB,OAAO,WAAW,IAAI,EAAE,SAAS;AAAA,QACrD;AAAA,QACA,SAAS;AAAA,MACX;AAAA,MACA,CAAC,QAAQ;AACP,YAAI,OAAO;AACX,cAAM,SAAS,IAAI,cAAc;AAEjC,gBAAQ,EAAE,IAAI,WAAW,OAAO,WAAW,KAAK,OAAO,CAAC;AAAA,MAC1D;AAAA,IACF;AACA,QAAI,GAAG,SAAS,MAAM,QAAQ,EAAE,IAAI,OAAO,QAAQ,EAAE,CAAC,CAAC;AACvD,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,cAAQ,EAAE,IAAI,OAAO,QAAQ,EAAE,CAAC;AAAA,IAClC,CAAC;AACD,QAAI,MAAM,IAAI;AACd,QAAI,IAAI;AAAA,EACV,CAAC;AACH;;;APhJA,IAAM,cAAc;AAIpB,IAAM,iBAAiB;AACvB,IAAM,0BAA0B;AAEhC,IAAM,sBAAsB;AAE5B,IAAM,oBAAoB;AAE1B,IAAM,qBAAqB;AAE3B,IAAM,uBAAuB;AAK7B,IAAM,2BAA2B;AAGjC,IAAM,eAAgE;AAAA,EACpE,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,KAAK;AACP;AAGA,IAAM,aAGF;AAAA,EACF,QAAQ,EAAE,KAAK,UAAU,OAAO,CAAC,EAAE;AAAA;AAAA,EAEnC,MAAM,EAAE,KAAK,QAAQ,OAAO,CAAC,WAAW,EAAE;AAAA,EAC1C,KAAK,EAAE,KAAK,OAAO,OAAO,CAAC,EAAE;AAC/B;AAKA,SAAS,wBACP,MACA,SACiB;AACjB,MAAI,SAAS,YAAY,SAAS,WAAW,SAAS,WAAY,QAAO;AACzE,SAAO,CAAC,MAAM,UAAU,SAAS,eAAe,MAAM;AACxD;AAWA,eAAsB,iBAAiB,MAA6C;AAClF,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,SAAS,CAAC,QAAsB;AACpC,SAAK,UAAU,GAAG;AAAA,EACpB;AASA,QAAM,SAAS,KAAK,UAAU;AAC9B,MAAI,WAAW,QAAQ;AACrB,UAAM,UAAU,wBAAwB,KAAK,MAAM,KAAK,OAAO;AAC/D,UAAM,OAAO,UAAU,mBAAmB,IAAI;AAC9C,QAAI,WAAW,SAAS,aAAa,QAAQ,KAAK,CAAC,GAAG;AACpD,YAAM,IAAI,MAAM,mBAAmB;AAAA,QACjC;AAAA,QACA,MAAM;AAAA,QACN,MAAM,CAAC,QAAQ,UAAU,QAAQ,KAAK,CAAC,GAAG,GAAG,OAAO;AAAA,QACpD,KAAK,QAAQ,IAAI;AAAA,QACjB,OAAO,KAAK;AAAA,MACd,CAAC;AACD,UAAI,EAAE,UAAU;AACd,gBAAQ,OAAO,MAAM,EAAE,OAAO,IAAI;AAClC,eAAO;AAAA,MACT;AACA,UAAI,EAAE,MAAO,QAAO,EAAE,KAAK;AAAA,IAE7B;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,OAAO,SAAS,CAAC,QAAQ,MAAM,OAAO;AAGjD,WAAO,YAAY,SAAS,KAAK,YAAY,KAAK,GAAG;AAAA,EACvD;AACA,QAAM,UAAU,MAAM,eAAe;AACrC,MAAI,CAAC,SAAS;AAEZ,YAAQ,OAAO;AAAA,MACb;AAAA,IACF;AACA,WAAO,YAAY,SAAS,KAAK,YAAY,KAAK,GAAG;AAAA,EACvD;AAEA,QAAM,OAAO,QAAQ,OAAO,WAAW;AACvC,QAAM,OAAO,QAAQ,OAAO,QAAQ;AACpC,QAAM,YAAY,KAAK,IAAI,GAAG,OAAO,WAAW;AAEhD,MAAI,MAAM,QAAQ,SAAS,SAAS,KAAK,YAAY;AAAA,IACnD,MAAM;AAAA,IACN;AAAA,IACA,MAAM;AAAA,IACN,KAAK,KAAK,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,KAAK,IAAI,IAAI,QAAQ;AAAA,EAC5D,CAAC;AAED,MAAI,cAAc,KAAK,IAAI;AAM3B,QAAM,YAAY,CAAC,GAAW,MAAoB;AAChD,QAAI;AACF,UAAI,OAAO,GAAG,CAAC;AAAA,IACjB,QAAQ;AAAA,IAER;AAAA,EACF;AAOA,oBAAkB;AAClB,MAAI,mBAAmB,KAAK;AAC5B,mBAAiB,gBAAgB;AAIjC,QAAM,aAAa,KAAK,cAAc,KAAK,SAAS;AAKpD,MAAI,eAAe;AACnB,QAAM,YAAY,CAAC,cAAuB,oBAA0C;AAAA,IAClF,MAAM;AAAA,IACN,SAAS,KAAK;AAAA,IACd;AAAA,IACA;AAAA,IACA,MAAM,KAAK;AAAA,IACX;AAAA,IACA;AAAA,EACF;AACA,MAAI,cAA2B,UAAU;AACzC,MAAI;AACJ,MAAI;AAIJ,MAAI,kBAAyC;AAC7C,MAAI,eAAsC;AAK1C,MAAI,kBAAiC;AACrC,MAAI,cAAc;AAClB,MAAI,kBAAgD;AACpD,MAAI,YAAmC;AACvC,MAAI,mBAAmB;AACvB,MAAI,eAAsD;AAE1D,MAAI,eAA8B;AAClC,MAAI,aAAmD;AAGvD,MAAI,eAAe;AACnB,MAAI,iBAAyC;AAG7C,MAAI,eAAe;AAOnB,MAAI,qBAAqB;AACzB,MAAI,4BAA4B;AAGhC,QAAM,eAAe,MAAc,cAAc;AAGjD,QAAM,WAAW,MAAe;AAC9B,UAAM,KAAK,QAAQ,OAAO,QAAQ;AAClC,WAAO,KAAK,cAAc,mBAAmB;AAAA,EAC/C;AAQA,QAAM,eAAe,MAAY;AAC/B,UAAM,KAAK,QAAQ,OAAO,WAAW;AACrC,UAAM,KAAK,QAAQ,OAAO,QAAQ;AAClC,UAAM,aAAa,aAAa,aAAa,EAAE;AAC/C,QAAI,UAAU,aAAa;AAC3B,QAAI,mBAAmB,KAAK,WAAW;AACrC,YAAM,YAAY,gBAAgB,WAAW,IAAI,gBAAgB;AACjE,eAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,cAAM,MAAM,KAAK,eAAe,UAAU,SAAS;AACnD,mBAAW,aAAa,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC;AAAA,MACnD;AAAA,IACF;AACA,eAAW,aAAa,IAAI,CAAC,IAAI,aAAa,iBAAiB;AAC/D,YAAQ,OAAO,MAAM,OAAO;AAAA,EAC9B;AASA,QAAM,kBAAkB,MAAY;AAClC,UAAM,YAAY,cAAc,QAAQ,qBAAqB;AAC7D,QAAI,aAAa,iBAAiB;AAChC,oBAAc,EAAE,MAAM,UAAU,SAAS,iBAAiB,OAAO,YAAY;AAAA,IAC/E,WAAW,aAAa,iBAAiB;AACvC,oBAAc,EAAE,MAAM,UAAU,QAAQ,gBAAgB;AAAA,IAC1D,WAAW,aAAa,cAAc;AACpC,oBAAc,EAAE,MAAM,UAAU,SAAS,aAAa,SAAS,OAAO,YAAY;AAAA,IACpF,WAAW,cAAc;AACvB,oBAAc,EAAE,MAAM,SAAS,SAAS,aAAa;AAAA,IACvD,OAAO;AACL,oBAAc,UAAU,kBAAkB,YAAY;AAAA,IACxD;AAAA,EACF;AAOA,QAAM,gBAAgB,MAAY;AAChC,QAAI,iBAAiB;AAGnB,kBAAY,EAAE,MAAM,UAAU,SAAS,iBAAiB,OAAO,YAAY;AAAA,IAC7E,WAAW,iBAAiB;AAC1B,kBAAY,EAAE,MAAM,UAAU,QAAQ,gBAAgB;AAAA,IACxD,WAAW,cAAc;AACvB,kBAAY,EAAE,MAAM,UAAU,SAAS,aAAa,SAAS,OAAO,YAAY;AAAA,IAClF,WAAW,iBAAiB;AAC1B,kBAAY,EAAE,MAAM,YAAY,UAAU,gBAAgB;AAAA,IAC5D,OAAO;AACL,kBAAY;AAAA,IACd;AAAA,EACF;AAMA,QAAM,kBAAkB,MAAY;AAClC,UAAM,KAAK,QAAQ,OAAO,WAAW;AACrC,UAAM,KAAK,QAAQ,OAAO,QAAQ;AAClC,UAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,aAAa,CAAC;AAC7C,cAAU,IAAI,KAAK;AACnB,YAAQ,OAAO,MAAM,UAAU,OAAO,KAAK,CAAC,GAAG;AAI/C,QAAI,QAAQ,aAAa;AACzB,aAAS,IAAI,QAAQ,GAAG,KAAK,IAAI,IAAK,UAAS,aAAa,GAAG,CAAC,IAAI;AACpE,aAAS,iBAAiB;AAC1B,YAAQ,OAAO,MAAM,KAAK;AAAA,EAC5B;AAMA,QAAM,kBAAkB,MAAY;AAClC,kBAAc;AACd,UAAM,WAAW,aAAa,SAAS,IAAI,kBAAkB;AAC7D,QAAI,aAAa,kBAAkB;AACjC,yBAAmB;AACnB,sBAAgB;AAAA,IAClB;AACA,oBAAgB;AAChB,iBAAa;AAAA,EACf;AAEA,QAAM,eAAe,MAAY;AAC/B,QAAI,aAAc;AAClB,mBAAe,YAAY,MAAM;AAC/B;AAIA,UAAI,WAAW,SAAS,UAAU;AAChC,oBAAY,EAAE,MAAM,UAAU,SAAS,UAAU,SAAS,OAAO,YAAY;AAI7E,YAAI,qBAAqB,EAAG,iBAAgB;AAC5C,qBAAa;AAAA,MACf;AAAA,IACF,GAAG,mBAAmB;AACtB,QAAI,OAAO,aAAa,UAAU,WAAY,cAAa,MAAM;AAAA,EACnE;AACA,QAAM,cAAc,MAAY;AAC9B,QAAI,cAAc;AAChB,oBAAc,YAAY;AAC1B,qBAAe;AAAA,IACjB;AAAA,EACF;AAeA,QAAM,aAAa,MAAY;AAC7B,QAAI,OAAO,CAAC,MAAc;AACxB,cAAQ,OAAO,MAAM,CAAC;AACtB,mBAAa;AAAA,IACf,CAAC;AAAA,EACH;AACA,aAAW;AAKX,QAAM,eAA6C,aAC/C,EAAE,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,IAChD,EAAE,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM;AAOvC,QAAM,YAAY,CAAC,SAA6B;AAC9C,QAAI,SAAS,UAAU;AACrB,UAAI,CAAC,cAAc;AACjB,uBAAe;AACf,YAAI,MAAM,IAAO;AAAA,MACnB;AACA;AAAA,IACF;AACA,UAAM,WAAW,QAAQ,KAAK,CAAC;AAC/B,QAAI,OAAO,aAAa,YAAY,SAAS,SAAS,GAAG;AACvD,YAAM,MAAM,WAAW,IAAI;AAC3B,UAAI;AACF,QAAAC;AAAA,UACE,QAAQ;AAAA,UACR,CAAC,UAAU,IAAI,KAAK,KAAK,OAAO,GAAG,IAAI,KAAK;AAAA,UAC5C,EAAE,UAAU,MAAM,OAAO,SAAS;AAAA,QACpC,EAAE,MAAM;AAAA,MACV,SAAS,GAAG;AAEV,eAAO,wBAAwB,IAAI,aAAc,EAAY,OAAO,EAAE;AAAA,MACxE;AAAA,IACF;AACA,mBAAe,aAAa,IAAI;AAChC,QAAI,WAAY,cAAa,UAAU;AACvC,iBAAa,WAAW,MAAM;AAC5B,mBAAa;AACb,qBAAe;AACf,sBAAgB;AAChB,mBAAa;AAAA,IACf,GAAG,iBAAiB;AACpB,QAAI,OAAO,WAAW,UAAU,WAAY,YAAW,MAAM;AAC7D,oBAAgB;AAChB,iBAAa;AAAA,EACf;AAMA,QAAM,mBAAmB,YAA2B;AAClD,QAAI,CAAC,KAAK,aAAc;AACxB,QAAI,YAAY;AACd,mBAAa,UAAU;AACvB,mBAAa;AAAA,IACf;AACA,mBAAe;AACf,oBAAgB;AAChB,iBAAa;AACb,QAAI,SAA0C;AAC9C,QAAI;AACF,eAAS,MAAM,KAAK,aAAa;AAAA,IACnC,SAAS,GAAG;AACV,aAAO,uBAAwB,EAAY,OAAO,EAAE;AAAA,IACtD;AACA,mBACE,WAAW,WACP,iBACA,WAAW,aACT,0BACA;AACR,iBAAa,WAAW,MAAM;AAC5B,mBAAa;AACb,qBAAe;AACf,sBAAgB;AAChB,mBAAa;AAAA,IACf,GAAG,iBAAiB;AACpB,QAAI,OAAO,WAAW,UAAU,WAAY,YAAW,MAAM;AAC7D,oBAAgB;AAChB,iBAAa;AAAA,EACf;AAGA,QAAM,SAAsB,kBAAkB;AAAA,IAC5C,WAAW,CAAC,MAAM;AAKhB,UAAI,cAAc;AAChB,YAAI,EAAE,WAAW,KAAK,EAAE,CAAC,MAAM,EAAM,iBAAgB,MAAM;AAC3D;AAAA,MACF;AAEA,UAAI,MAAM,EAAE,SAAS,MAAM,CAAC;AAAA,IAC9B;AAAA,IACA,UAAU,CAAC,SAAS;AAGlB,WAAK,WAAW,EAAE,cAAc,KAAK,cAAc,KAAK,CAAC;AACzD,wBAAkB;AAClB,sBAAgB;AAAA,IAClB;AAAA,IACA;AAAA,IACA,gBAAgB,CAAC,SAAS;AACxB,qBAAe;AACf,sBAAgB;AAChB,mBAAa;AAAA,IACf;AAAA,IACA,UAAU,CAAC,SAAS;AAClB,gBAAU,IAAI;AAAA,IAChB;AAAA,IACA,cAAc,KAAK,eAAe,mBAAmB;AAAA,EACvD,CAAC;AAED,MAAI,QAAQ,MAAM,MAAO,SAAQ,MAAM,WAAW,IAAI;AACtD,UAAQ,MAAM,OAAO;AACrB,QAAM,cAAc,CAAC,UAAwB;AAC3C,WAAO,KAAK,KAAK;AAAA,EACnB;AACA,UAAQ,MAAM,GAAG,QAAQ,WAAW;AAOpC,QAAM,WAAW,MAAY;AAC3B,UAAM,KAAK,QAAQ,OAAO,WAAW;AACrC,UAAM,KAAK,QAAQ,OAAO,QAAQ;AAKlC,uBAAmB,aAAa,SAAS,IAAI,kBAAkB;AAC/D,UAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,aAAa,CAAC;AAC7C,cAAU,IAAI,KAAK;AACnB,YAAQ,OAAO,MAAM,UAAU,OAAO,KAAK,CAAC,GAAG;AAC/C,oBAAgB;AAChB,iBAAa;AAAA,EACf;AACA,UAAQ,OAAO,GAAG,UAAU,QAAQ;AAGpC,QAAM,SAAuB,iBAAiB;AAAA,IAC5C,cAAc,KAAK;AAAA,IACnB,OAAO,KAAK;AAAA,IACZ,UAAU,CAAC,OAAuB;AAChC,wBAAkB;AAClB,sBAAgB;AAKhB,aAAO,QAAQ,EAAE,EAAE,MAAM,CAAC,MAAe;AAGvC,cAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,YAAI,QAAQ,sBAAsB;AAChC,iBAAO,4BAA4B,GAAG,EAAE;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,YAAY,CAAC,OAAe;AAE1B,UAAI,mBAAmB,gBAAgB,OAAO,IAAI;AAChD,0BAAkB;AAClB,eAAO,MAAM,oBAAoB;AACjC,wBAAgB;AAAA,MAClB;AAAA,IACF;AAAA,IACA,UAAU,CAAC,OAAuB;AAChC,UAAI,GAAG,SAAS,aAAc,sBAAqB,KAAK,IAAI;AAC5D,qBAAe;AACf,mBAAa;AACb,sBAAgB;AAAA,IAClB;AAAA,IACA,iBAAiB,CAAC,OAAe;AAC/B,UAAI,gBAAgB,aAAa,OAAO,IAAI;AAC1C,YAAI,aAAa,SAAS,aAAc,6BAA4B,KAAK,IAAI;AAC7E,uBAAe;AACf,oBAAY;AACZ,wBAAgB;AAAA,MAClB;AAAA,IACF;AAAA,EACF,CAAC;AAOD,QAAM,aAAa,YAA2B;AAC5C,QAAI;AACF,YAAM,SAAS,MAAM,cAAc;AAAA,QACjC,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,QACX,cAAc,KAAK;AAAA,MACrB,CAAC;AAGD,YAAM,OACJ,KAAK,SAAS,UACV,QAAQ,QACR,KAAK,SAAS,aACZ,QAAQ,WACR,KAAK,SAAS,UACZ,SACA,QAAQ;AAClB,YAAM,YAAY,MAAM,cAAc,KAAK,KAAK;AAChD,YAAM,eAAe,MAAM,SAAS;AAGpC,YAAM,eAAe,aAAa,KAAK;AACvC,UAAI,iBAAiB,kBAAkB;AACrC,2BAAmB;AACnB,yBAAiB,YAAY;AAAA,MAC/B;AAMA,YAAM,eACJ,KAAK,SAAS,YAAY,QAAQ,OAAO,UAAU,aAC9C,OAAO,OAAO,YAAY,OAC3B;AACN,YAAM,mBACH,cAAc,cAAc,WAAW,iBAAiB,cAAc;AACzE,UAAI,iBAAiB;AACnB,0BAAkB;AAClB,wBAAgB;AAAA,MAClB;AACA,UAAI,cAAc,oBAAoB,iBAAiB,aAAc;AACrE,yBAAmB;AACnB,qBAAe;AACf,UAAI,YAAY,SAAS,QAAQ;AAC/B,wBAAgB;AAChB,qBAAa;AAAA,MACf;AAAA,IACF,SAAS,GAAG;AAGV,aAAO,uBAAwB,EAAY,OAAO,EAAE;AAAA,IACtD;AAAA,EACF;AACA,OAAK,WAAW;AAChB,QAAM,cAAc,YAAY,MAAM;AACpC,SAAK,WAAW;AAAA,EAClB,GAAG,uBAAuB;AAC1B,MAAI,OAAO,YAAY,UAAU,WAAY,aAAY,MAAM;AAO/D,UAAQ,OAAO,MAAM,UAAU,OAAO,SAAS,CAAC,GAAG;AASnD,MAAI,KAAK,SAAS,WAAW,CAAC,YAAY;AACxC,YAAQ,OAAO,MAAM,eAAe;AAAA,EACtC;AAGA,eAAa;AAQb,QAAM,gBAAgB,OACpB,SACsF;AACtF,UAAM,aAAa,IAAI,gBAAgB;AACvC,mBAAe;AACf,qBAAiB;AACjB,sBAAkB;AAClB,iBAAa;AACb,oBAAgB;AAChB,QAAI,OAAiF;AACrF,QAAI;AACF,aAAQ,MAAM,KAAK,YAAY,WAAW,QAAQ,IAAI,KAAM;AAAA,IAC9D,SAAS,GAAG;AACV,aAAO,qBAAsB,EAAY,OAAO,EAAE;AAAA,IACpD,UAAE;AACA,qBAAe;AACf,uBAAiB;AACjB,wBAAkB;AAGlB,UAAI,CAAC,aAAc,aAAY;AAC/B,sBAAgB;AAAA,IAClB;AACA,QAAI,MAAM;AACR,qBAAe;AACf,UAAI,WAAY,cAAa,UAAU;AACvC,mBAAa,WAAW,MAAM;AAC5B,qBAAa;AACb,uBAAe;AACf,wBAAgB;AAChB,qBAAa;AAAA,MACf,GAAG,iBAAiB;AACpB,UAAI,OAAO,WAAW,UAAU,WAAY,YAAW,MAAM;AAC7D,sBAAgB;AAChB,mBAAa;AAAA,IACf;AACA,WAAO;AAAA,EACT;AAQA,MAAI,WAAW;AACf,MAAI,aAAa;AACjB,aAAS;AACP,UAAM,OAAO,MAAM,IAAI,QAAgB,CAAC,YAAY;AAClD,UAAI,OAAO,CAAC,EAAE,UAAAC,UAAS,MAAM,QAAQA,SAAQ,CAAC;AAAA,IAChD,CAAC;AACD,QAAI,gBAAgB,CAAC,KAAK,WAAW;AACnC,iBAAW;AACX;AAAA,IACF;AAGA,UAAM,gBACJ,qBAAqB,6BACrB,KAAK,IAAI,IAAI,4BAA4B;AAC3C,QAAI,CAAC,iBAAiB,SAAS,GAAG;AAChC,iBAAW;AACX;AAAA,IACF;AACA,iBAAa,KAAK,IAAI,IAAI,cAAc,qBAAqB,aAAa,IAAI;AAC9E,QAAI,cAAc,sBAAsB;AACtC,aAAO,mDAAmD;AAC1D,iBAAW;AACX;AAAA,IACF;AACA,UAAM,OAAO,MAAM,cAAc,IAAI;AACrC,QAAI,CAAC,MAAM;AACT,iBAAW;AACX;AAAA,IACF;AACA,UAAM,QAAQ,QAAQ,OAAO,QAAQ;AACrC,UAAM,WAAW,KAAK,IAAI,GAAG,QAAQ,aAAa,CAAC;AACnD,UAAM,QAAQ,SAAS,KAAK,SAAS,KAAK,MAAM;AAAA,MAC9C,MAAM;AAAA,MACN,MAAM,QAAQ,OAAO,WAAW;AAAA,MAChC,MAAM;AAAA,MACN,KAAK,KAAK,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,KAAK,IAAI,IAAI,QAAQ;AAAA,IAC5D,CAAC;AACD,eAAW;AACX,kBAAc,KAAK,IAAI;AAIvB,yBAAqB;AACrB,gCAA4B;AAG5B,YAAQ,OAAO,MAAM,UAAU,OAAO,QAAQ,CAAC,GAAG;AAClD,iBAAa;AAAA,EACf;AAKA,UAAQ,MAAM,IAAI,QAAQ,WAAW;AACrC,UAAQ,OAAO,IAAI,UAAU,QAAQ;AACrC,gBAAc,WAAW;AACzB,cAAY;AACZ,MAAI,WAAY,cAAa,UAAU;AACvC,MAAI,QAAQ,MAAM,MAAO,SAAQ,MAAM,WAAW,KAAK;AACvD,UAAQ,MAAM,MAAM;AACpB,SAAO,MAAM;AACb,SAAO,QAAQ;AACf,QAAM,UAAU,QAAQ,OAAO,QAAQ;AACvC,QAAM,UAAU,QAAQ,OAAO,WAAW;AAK1C,MAAI,gBAAgB;AACpB,WAAS,IAAI,UAAU,kBAAkB,KAAK,SAAS,KAAK;AAC1D,QAAI,KAAK,EAAG,kBAAiB,aAAa,GAAG,CAAC,IAAI;AAAA,EACpD;AACA,mBAAiB,aAAa,SAAS,OAAO;AAC9C,UAAQ,OAAO,MAAM,aAAa;AAElC,mBAAiB;AAEjB,MAAI,aAAa,KAAK,KAAK,cAAc;AAGvC,YAAQ,OAAO,MAAM,qBAAqB,KAAK,eAAe,IAAI;AAAA,EACpE;AACA,SAAO;AACT;AAMA,SAAS,YAAY,SAAiB,MAAgB,KAAsC;AAC1F,QAAM,QAAQ,UAAU,SAAS,MAAM;AAAA,IACrC,OAAO;AAAA,IACP,KAAK,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,IAAI,IAAI,QAAQ;AAAA,EAClD,CAAC;AACD,SAAO,MAAM,UAAU;AACzB;;;AQ/0BA,SAAS,MAAAC,WAAU;AACnB,SAAS,eAAe;;;ACFxB,SAAS,SAAS,IAAI,MAAM,iBAAiB;AAC7C,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,aAAa;AAQtB,eAAsB,wBAAgD;AACpE,MAAI,QAAQ,aAAa,YAAY,QAAQ,aAAa,QAAS,QAAO;AAE1E,QAAM,MAAM,MAAM,QAAQ,KAAK,OAAO,GAAG,gBAAgB,CAAC;AAC1D,QAAM,UAAU,KAAK,KAAK,UAAU;AACpC,QAAM,KACJ,QAAQ,aAAa,WACjB,MAAM,cAAc,KAAK,OAAO,IAChC,MAAM,aAAa,OAAO;AAEhC,MAAI,GAAI,QAAO;AACf,QAAM,GAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAC9D,SAAO;AACT;AAQA,eAAsB,4BAA8C;AAClE,MAAI,QAAQ,aAAa,SAAU,QAAO;AAC1C,MAAI,QAAQ,aAAa,QAAS,QAAQ,MAAM,mBAAmB,MAAO;AAC1E,SAAO;AACT;AAOA,SAAS,kBAAkB,SAAiB,UAA4B;AACtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,2CAA2C,KAAK,UAAU,OAAO,CAAC;AAAA,IAClE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,6CAA6C,KAAK,UAAU,QAAQ,CAAC;AAAA,IACrE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EACG,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,EAC1B,KAAK;AACV;AAEA,eAAe,cAAc,KAAa,SAAmC;AAC3E,QAAM,WAAW,KAAK,KAAK,WAAW;AACtC,QAAM,MAAM,MAAM,MAAM,aAAa,kBAAkB,SAAS,QAAQ,GAAG;AAAA,IACzE,QAAQ;AAAA,EACV,CAAC;AACD,QAAM,OAAO,IAAI,OAAO,KAAK;AAE7B,MAAI,SAAS,MAAO,QAAO,aAAa,OAAO;AAE/C,MAAI,SAAS,UAAW,MAAM,aAAa,QAAQ,GAAI;AAErD,UAAM,OAAO,MAAM;AAAA,MACjB;AAAA,MACA,CAAC,MAAM,UAAU,OAAO,UAAU,SAAS,OAAO;AAAA,MAClD,EAAE,QAAQ,MAAM;AAAA,IAClB;AACA,QAAI,KAAK,aAAa,EAAG,QAAO,aAAa,OAAO;AAAA,EACtD;AACA,SAAO;AACT;AAOA,eAAe,qBAAwD;AACrE,MAAI,QAAQ,IAAI,iBAAiB,KAAM,MAAM,OAAO,UAAU,EAAI,QAAO;AACzE,MAAI,QAAQ,IAAI,SAAS,KAAM,MAAM,OAAO,OAAO,EAAI,QAAO;AAC9D,SAAO;AACT;AAEA,eAAe,aAAa,SAAmC;AAC7D,QAAM,OAAO,MAAM,mBAAmB;AACtC,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,MAAqB;AACzB,MAAI,SAAS,WAAW;AACtB,UAAM,QAAQ,MAAM,MAAM,YAAY,CAAC,cAAc,GAAG,EAAE,QAAQ,MAAM,CAAC;AACzE,QAAI,MAAM,aAAa,KAAK,CAAC,cAAc,KAAK,MAAM,MAAM,EAAG,QAAO;AACtE,UAAM,IAAI,MAAM,MAAM,YAAY,CAAC,UAAU,WAAW,GAAG;AAAA,MACzD,UAAU;AAAA,MACV,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,EAAE,aAAa,EAAG,OAAM,SAAS,EAAE,MAAM;AAAA,EAC/C,OAAO;AACL,UAAM,MAAM,CAAC,cAAc,WAAW;AACtC,UAAM,UAAU,MAAM,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,WAAW,IAAI,GAAG;AAAA,MACpE,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,QAAQ,aAAa,KAAK,CAAC,cAAc,KAAK,QAAQ,MAAM,EAAG,QAAO;AAC1E,UAAM,IAAI,MAAM,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,aAAa,IAAI,GAAG;AAAA,MAChE,UAAU;AAAA,MACV,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,EAAE,aAAa,EAAG,OAAM,SAAS,EAAE,MAAM;AAAA,EAC/C;AAEA,MAAI,CAAC,OAAO,CAAC,MAAM,GAAG,EAAG,QAAO;AAChC,QAAM,UAAU,SAAS,GAAG;AAC5B,SAAO;AACT;AAMA,eAAe,OAAO,KAA+B;AACnD,QAAM,IAAI,MAAM,MAAM,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC1E,SAAO,EAAE,aAAa;AACxB;AAEA,SAAS,SAAS,KAA6B;AAC7C,MAAI,OAAO,SAAS,GAAG,EAAG,QAAO;AACjC,MAAI,eAAe,WAAY,QAAO,OAAO,KAAK,GAAG;AACrD,SAAO;AACT;AAEA,SAAS,MAAM,KAAsB;AACnC,SACE,IAAI,UAAU,KACd,IAAI,CAAC,MAAM,OACX,IAAI,CAAC,MAAM,MACX,IAAI,CAAC,MAAM,MACX,IAAI,CAAC,MAAM;AAEf;AAEA,eAAe,aAAa,MAAgC;AAC1D,MAAI;AACF,UAAM,IAAI,MAAM,KAAK,IAAI;AACzB,WAAO,EAAE,OAAO,KAAK,EAAE,OAAO;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADxJA,SAAS,oBAAoB,YAA4B;AACvD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,uEAAuE,UAAU;AAAA,EACnF,EAAE,KAAK,IAAI;AACb;AAEA,eAAsB,wBACpB,UACA,KAC2B;AAC3B,MAAI,OAAO,SAAS,eAAe,WAAY,QAAO;AAEtD,QAAM,UAAU,MAAM,sBAAsB;AAC5C,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,sBAAsB,OAAO,KAAK,IAAI,CAAC,CAAC;AACvD,MAAI;AACF,UAAM,SAAS,WAAW,KAAK,SAAS,MAAM;AAC9C,UAAM,SAAS,KAAK,KAAK,CAAC,MAAM,OAAO,oBAAoB,MAAM,CAAC,GAAG;AAAA,MACnE,MAAM;AAAA,IACR,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,UAAMC,IAAG,QAAQ,OAAO,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC7E;AACF;;;AV3CA,IAAM,iBAAiB,oBAAoB,OAAO,kBAAkB,CAAC;AAErE,IAAM,uBAAuB,IAAI;AAGjC,SAAS,eAAe,IAAY,QAAoC;AACtE,SAAO,IAAI,QAAc,CAAC,YAAY;AACpC,QAAI,OAAO,SAAS;AAClB,cAAQ;AACR;AAAA,IACF;AACA,UAAM,IAAI,WAAW,SAAS,EAAE;AAChC,WAAO;AAAA,MACL;AAAA,MACA,MAAM;AACJ,qBAAa,CAAC;AACd,gBAAQ;AAAA,MACV;AAAA,MACA,EAAE,MAAM,KAAK;AAAA,IACf;AAAA,EACF,CAAC;AACH;AAqDO,SAAS,6BAA6B,QAAgB,WAA8B;AACzF,MAAI,CAAC,aAAa,UAAU,WAAW,GAAG;AACxC,WAAO,mBAAmB,MAAM;AAAA,EAClC;AAKA,QAAM,OAAO,OAAO,KAAK,UAAU,KAAK,IAAI,GAAG,MAAM,EAAE,SAAS,QAAQ;AAqBxE,SAAO,sCAAsC,IAAI,wBAAwB,MAAM;AACjF;AAEA,eAAsB,iBAAiB,MAA2C;AAChF,QAAM,WAAW,MAAM,eAAe,KAAK,GAAG;AAC9C,MAAI,CAAC,SAAS,aAAa;AACzB,UAAM,IAAI,MAAM,aAAa,SAAS,IAAI,uCAAuC;AAAA,EACnF;AAGA,QAAM,cAAc,SAAS,YAAY,KAAK,QAAQ;AAStD,MAAI,MAAM,KAAK;AACf,QAAM,QAAQ,MAAM,SAAS,WAAW,GAAG;AAC3C,MAAI,UAAU,WAAW;AACvB,UAAM,IAAI,MAAM,qBAAqB,IAAI,IAAI,gCAAgC;AAAA,EAC/E;AACA,MAAI,UAAU,WAAW;AACvB,UAAM,IAAI,QAAQ;AAClB,MAAE,MAAM,UAAU,WAAW,iBAAiB,cAAc;AAC5D,UAAM,MAAM,SAAS,MAAM,GAAG;AAC9B,MAAE,KAAK,aAAa;AAAA,EACtB;AACA,QAAM,UAAU,6BAA6B,KAAK,QAAQ,KAAK,SAAS;AAIxE,QAAM,aACJ,IAAI,aAAa,YAAY,SAAS,KAAK;AAS7C,MAAI,cAAc,eAAe,UAAU,KAAK,aAAa,KAAK,UAAU,SAAS,GAAG;AACtF,UAAM,MAAM,MAAM,SAAS,YAAY,KAAK,SAAS;AAAA,MACnD,aAAa,KAAK;AAAA,MAClB;AAAA,MACA,UAAU;AAAA,IACZ,CAAC;AACD,QAAI;AACF,YAAM,YAAY,IAAI,MAAM,IAAI,GAAG;AAAA,IACrC,UAAE;AACA,UAAI,IAAI,QAAS,OAAM,IAAI,QAAQ;AAAA,IACrC;AAAA,EACF;AAEA,MAAI,OAAO,MAAM,SAAS,YAAY,KAAK,SAAS;AAAA,IAClD,aAAa,KAAK;AAAA,IAClB;AAAA,EACF,CAAC;AAGD,QAAM,WACJ,KAAK,SAAS,YAAa,MAAM,0BAA0B;AAY7D,QAAM,YAAY,OAChB,WACsF;AACtF,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAI,UAAU;AACd,eAAS;AACP,UAAI,OAAO,WAAW,KAAK,IAAI,IAAI,SAAU,QAAO;AACpD,UAAI;AACF,cAAM,MAAM,SAAS,MAAM,GAAG;AAC9B;AAAA,MACF,QAAQ;AACN,cAAM,eAAe,SAAS,MAAM;AACpC,kBAAU,KAAK,IAAI,UAAU,GAAG,GAAI;AAAA,MACtC;AAAA,IACF;AACA,QAAI,OAAO,QAAS,QAAO;AAM3B,UAAM,OAAO;AACb,WAAO,MAAM,YAAY,KAAK,SAAS,EAAE,aAAa,KAAK,aAAa,QAAQ,CAAC;AACjF,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,cAAM,KAAK,QAAQ;AAAA,MACrB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO,EAAE,SAAS,KAAK,KAAK,CAAC,GAAI,MAAM,KAAK,KAAK,MAAM,CAAC,GAAG,KAAK,KAAK,IAAI;AAAA,EAC3E;AAEA,MAAI;AACF,UAAM,OAAO,MAAM,iBAAiB;AAAA,MAClC,WAAW,IAAI;AAAA,MACf,SAAS,KAAK,KAAK,CAAC;AAAA,MACpB,YAAY,KAAK,KAAK,MAAM,CAAC;AAAA,MAC7B,KAAK,KAAK;AAAA,MACV,cAAc;AAAA,MACd,OAAO,IAAI;AAAA,MACX,SAAS,IAAI;AAAA,MACb,cAAc,IAAI;AAAA,MAClB,MAAM,KAAK;AAAA,MACX,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR;AAAA,MACA,SAAS,CAAC,QAAQ;AAGhB,YAAI;AACF;AAAA,YACEC,MAAK,QAAQ,GAAG,aAAa,QAAQ,YAAY;AAAA,YACjD,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC,KAAK,IAAI,IAAI,KAAK,GAAG;AAAA;AAAA,UAClD;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,MACA,cAAc,WACV,MAAM,wBAAwB,UAAU,GAAG,IAC3C;AAAA,IACN,CAAC;AACD,YAAQ,KAAK,IAAI;AAAA,EACnB,UAAE;AACA,QAAI,KAAK,QAAS,OAAM,KAAK,QAAQ;AAAA,EACvC;AACF;AASA,SAAS,YAAY,MAAgB,KAA6C;AAChF,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQC,OAAM,KAAK,CAAC,GAAI,KAAK,MAAM,CAAC,GAAG;AAAA,MAC3C,OAAO;AAAA,MACP,KAAK,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,IAAI,IAAI,QAAQ;AAAA,IAClD,CAAC;AACD,UAAM,GAAG,SAAS,MAAM,QAAQ,CAAC;AACjC,UAAM,GAAG,QAAQ,MAAM,QAAQ,CAAC;AAAA,EAClC,CAAC;AACH;","names":["spawn","join","spawn","spawn","code","inner","message","spinner","spawn","exitCode","rm","rm","join","spawn"]}