zidane 8.2.3 → 8.2.4
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.
- package/dist/{acp-oYRjlh71.js → acp-BIInKhVI.js} +6 -6
- package/dist/{acp-oYRjlh71.js.map → acp-BIInKhVI.js.map} +1 -1
- package/dist/acp-cli.js +4 -4
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +1 -1
- package/dist/{agent-CBQglXtH.js → agent-CmhCOmyO.js} +10 -5
- package/dist/{agent-CBQglXtH.js.map → agent-CmhCOmyO.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/chat/pure.d.ts +4 -4
- package/dist/chat/pure.js +7 -7
- package/dist/chat.d.ts +3 -3
- package/dist/chat.js +16 -16
- package/dist/{cli-shared-E5m_pI8A.js → cli-shared-GOehTeqk.js} +2 -2
- package/dist/{cli-shared-E5m_pI8A.js.map → cli-shared-GOehTeqk.js.map} +1 -1
- package/dist/{client-core-EEEX_u6D.d.ts → client-core-Dz8hELs4.d.ts} +15 -5
- package/dist/client-core-Dz8hELs4.d.ts.map +1 -0
- package/dist/codemode-worker.js +1 -1
- package/dist/codemode.js +1 -1
- package/dist/{connection-loop-CpgfUmuw.d.ts → connection-loop-dm_TN-QD.d.ts} +3 -3
- package/dist/{connection-loop-CpgfUmuw.d.ts.map → connection-loop-dm_TN-QD.d.ts.map} +1 -1
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/{credentials-B9DfBJP9.js → credentials-DxOQmset.js} +2 -2
- package/dist/{credentials-B9DfBJP9.js.map → credentials-DxOQmset.js.map} +1 -1
- package/dist/daemon-cli.js +2 -2
- package/dist/{dispatcher-Bv9-c6I2.js → dispatcher-Dclv6HXn.js} +22 -18
- package/dist/dispatcher-Dclv6HXn.js.map +1 -0
- package/dist/{engine-XmoPPB6U.js → engine-C_U6kDfA.js} +6 -6
- package/dist/{engine-XmoPPB6U.js.map → engine-C_U6kDfA.js.map} +1 -1
- package/dist/{eval-CfQlBR81.js → eval-CIHIF_Ns.js} +3 -3
- package/dist/{eval-CfQlBR81.js.map → eval-CIHIF_Ns.js.map} +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/{executor-BhthPE1q.js → executor-BFaR2nfc.js} +3 -3
- package/dist/{executor-BhthPE1q.js.map → executor-BFaR2nfc.js.map} +1 -1
- package/dist/{extensions-BgBwogRW.js → extensions-DIbpUj5R.js} +3 -3
- package/dist/{extensions-BgBwogRW.js.map → extensions-DIbpUj5R.js.map} +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/{fold-rules-DSypi36k.d.ts → fold-rules-CMpWNPqk.d.ts} +2 -2
- package/dist/{fold-rules-DSypi36k.d.ts.map → fold-rules-CMpWNPqk.d.ts.map} +1 -1
- package/dist/{headless-R4pbpTkm.js → headless-Bs9Lb4vs.js} +4 -4
- package/dist/{headless-R4pbpTkm.js.map → headless-Bs9Lb4vs.js.map} +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/history-worker.js +1 -1
- package/dist/{host-scoped-settings-C-Nmb6vr.js → host-scoped-settings-DxxI_yRo.js} +2 -2
- package/dist/{host-scoped-settings-C-Nmb6vr.js.map → host-scoped-settings-DxxI_yRo.js.map} +1 -1
- package/dist/{host-signals-BFudPyCT.js → host-signals-CQ2jZR-2.js} +5 -5
- package/dist/{host-signals-BFudPyCT.js.map → host-signals-CQ2jZR-2.js.map} +1 -1
- package/dist/{index-DyQyDLX1.d.ts → index-4mDRyTGJ.d.ts} +1722 -1691
- package/dist/index-4mDRyTGJ.d.ts.map +1 -0
- package/dist/{index-CDS7YdUC.d.ts → index-Dlmu8HnG.d.ts} +5 -5
- package/dist/{index-CDS7YdUC.d.ts.map → index-Dlmu8HnG.d.ts.map} +1 -1
- package/dist/{index-AqRZ3gEz.d.ts → index-QsAN9yjZ.d.ts} +3 -3
- package/dist/{index-AqRZ3gEz.d.ts.map → index-QsAN9yjZ.d.ts.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -8
- package/dist/ipc/remote.d.ts +5 -5
- package/dist/ipc/remote.js +3 -3
- package/dist/ipc.d.ts +5 -5
- package/dist/ipc.js +9 -9
- package/dist/{links-rFaWqS2Y.js → links-D5itfH8P.js} +10 -10
- package/dist/{links-rFaWqS2Y.js.map → links-D5itfH8P.js.map} +1 -1
- package/dist/{mcp-allowlist-CLXweyxk.js → mcp-allowlist-BbnTBKNN.js} +2 -2
- package/dist/{mcp-allowlist-CLXweyxk.js.map → mcp-allowlist-BbnTBKNN.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/{oauth-7_48zR7n.js → oauth-Ck3wQX4Q.js} +66 -32
- package/dist/oauth-Ck3wQX4Q.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/{prelude-HzY_JEGq.js → prelude-qjPUkw4f.js} +26 -2
- package/dist/prelude-qjPUkw4f.js.map +1 -0
- package/dist/{presets-DXeHsd24.js → presets-C4hkykN-.js} +2 -2
- package/dist/{presets-DXeHsd24.js.map → presets-C4hkykN-.js.map} +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/{protocol-BIv1Yuvl.js → protocol-Do_i6Ras.js} +3 -1
- package/dist/protocol-Do_i6Ras.js.map +1 -0
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/neuralwatt.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/{registry-eourRLY2.js → registry-DxPP_tAt.js} +3 -3
- package/dist/{registry-eourRLY2.js.map → registry-DxPP_tAt.js.map} +1 -1
- package/dist/{remote-hosts-DY2J07ut.js → remote-hosts-BkOwRszu.js} +4 -4
- package/dist/{remote-hosts-DY2J07ut.js.map → remote-hosts-BkOwRszu.js.map} +1 -1
- package/dist/restate.d.ts +1 -1
- package/dist/{secret-shapes-BjW4l6U0.js → secret-shapes-HvaYVzwc.js} +10 -2
- package/dist/secret-shapes-HvaYVzwc.js.map +1 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-7TUHs42_.js → session-CCsKXID6.js} +6 -2
- package/dist/session-CCsKXID6.js.map +1 -0
- package/dist/{session-factory-CMwIXE-7.js → session-factory-Ct7J9CK1.js} +6 -6
- package/dist/{session-factory-CMwIXE-7.js.map → session-factory-Ct7J9CK1.js.map} +1 -1
- package/dist/session.d.ts +2 -2
- package/dist/session.js +1 -1
- package/dist/{settings-keys-CPdmkTz7.js → settings-keys-QoGJ5n3e.js} +4 -4
- package/dist/{settings-keys-CPdmkTz7.js.map → settings-keys-QoGJ5n3e.js.map} +1 -1
- package/dist/{shell-path-B8b3EOtK.js → shell-path-BXXtRmEA.js} +4 -4
- package/dist/{shell-path-B8b3EOtK.js.map → shell-path-BXXtRmEA.js.map} +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/{spaces-CyjMh5Zf.js → spaces-C_x2bvEl.js} +14 -4
- package/dist/spaces-C_x2bvEl.js.map +1 -0
- package/dist/{store-L01W0gGI.js → store-BpkWIaSH.js} +10 -30
- package/dist/store-BpkWIaSH.js.map +1 -0
- package/dist/{todo-edits-BERpCRPj.js → todo-edits-DKoxu7Ss.js} +3 -3
- package/dist/{todo-edits-BERpCRPj.js.map → todo-edits-DKoxu7Ss.js.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/{tools-pZVFIJ_K.js → tools-FHEIKGkh.js} +2 -2
- package/dist/{tools-pZVFIJ_K.js.map → tools-FHEIKGkh.js.map} +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +2 -2
- package/dist/{transcript-anchors-jHrg-r-H.js → transcript-anchors-D8jWLpe5.js} +10 -10
- package/dist/{transcript-anchors-jHrg-r-H.js.map → transcript-anchors-D8jWLpe5.js.map} +1 -1
- package/dist/{transcript-cache-DXdgVuoy.js → transcript-cache-CdHKtx9y.js} +4 -4
- package/dist/{transcript-cache-DXdgVuoy.js.map → transcript-cache-CdHKtx9y.js.map} +1 -1
- package/dist/{transcript-reconcile-CPdrNbbR.js → transcript-reconcile-D_SWw6dX.js} +11 -3
- package/dist/transcript-reconcile-D_SWw6dX.js.map +1 -0
- package/dist/tui.d.ts +7 -7
- package/dist/tui.js +24 -23
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-DuGtKuc_.d.ts → turn-operations-Dbk3xPKu.d.ts} +11 -3
- package/dist/{turn-operations-DuGtKuc_.d.ts.map → turn-operations-Dbk3xPKu.d.ts.map} +1 -1
- package/dist/{types-C0PJmBaO.d.ts → types-CMXR7A0b.d.ts} +2 -2
- package/dist/{types-C0PJmBaO.d.ts.map → types-CMXR7A0b.d.ts.map} +1 -1
- package/dist/types-CzmghX7C.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{url-D8KZ7Edw.js → url-D4vG7lzO.js} +2 -2
- package/dist/{url-D8KZ7Edw.js.map → url-D4vG7lzO.js.map} +1 -1
- package/dist/{version-bd3DQJkc.js → version-DDRBX7K6.js} +2 -2
- package/dist/{version-bd3DQJkc.js.map → version-DDRBX7K6.js.map} +1 -1
- package/dist/{workspace-exec-stream-CyfYgqAh.js → workspace-exec-stream-iHv9cooF.js} +5 -5
- package/dist/{workspace-exec-stream-CyfYgqAh.js.map → workspace-exec-stream-iHv9cooF.js.map} +1 -1
- package/dist/{write-file-QYrcDdl3.js → write-file-ChtRPdEL.js} +3 -3
- package/dist/{write-file-QYrcDdl3.js.map → write-file-ChtRPdEL.js.map} +1 -1
- package/dist/{ws-CURQ2igk.js → ws-B2HB87nz.js} +6 -6
- package/dist/{ws-CURQ2igk.js.map → ws-B2HB87nz.js.map} +1 -1
- package/dist/{ws-client-BVljYbdb.js → ws-client-DPC9ZJHr.js} +4 -4
- package/dist/{ws-client-BVljYbdb.js.map → ws-client-DPC9ZJHr.js.map} +1 -1
- package/docs/CODEMODE.md +6 -0
- package/docs/IPC.md +2 -0
- package/docs/ONBOARDING.md +1 -1
- package/package.json +1 -1
- package/dist/client-core-EEEX_u6D.d.ts.map +0 -1
- package/dist/dispatcher-Bv9-c6I2.js.map +0 -1
- package/dist/index-DyQyDLX1.d.ts.map +0 -1
- package/dist/oauth-7_48zR7n.js.map +0 -1
- package/dist/prelude-HzY_JEGq.js.map +0 -1
- package/dist/protocol-BIv1Yuvl.js.map +0 -1
- package/dist/secret-shapes-BjW4l6U0.js.map +0 -1
- package/dist/session-7TUHs42_.js.map +0 -1
- package/dist/spaces-CyjMh5Zf.js.map +0 -1
- package/dist/store-L01W0gGI.js.map +0 -1
- package/dist/transcript-reconcile-CPdrNbbR.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript-anchors-jHrg-r-H.js","names":["OPT_OUT_VARS","findGitRoot","findGitRoot"],"sources":["../src/chat/artifacts-view.ts","../src/chat/env-opt-out.ts","../src/chat/auto-update.ts","../src/chat/auto-update-companion.ts","../src/chat/auto-update-restart.ts","../src/chat/auto-update-hook.ts","../src/chat/boot-profiler.ts","../src/chat/claude-import.ts","../src/chat/completion.ts","../src/chat/completion-artifacts.ts","../src/chat/config-context.tsx","../src/chat/discovery-context.tsx","../src/chat/discovery-slot.ts","../src/chat/editor-command.ts","../src/chat/settings-context.tsx","../src/chat/enabled-toggle-set.ts","../src/chat/files-discovery.ts","../src/chat/footer-hints.ts","../src/chat/github-usage.ts","../src/chat/hints.ts","../src/chat/host/onboarding/providers.ts","../src/chat/host/onboarding/detect.ts","../src/chat/interactions.tsx","../src/chat/mcp-auth-context.tsx","../src/chat/mcp-rows.ts","../src/chat/mcp-tool-toggle.ts","../src/chat/oauth-redirect.ts","../src/chat/path-display.ts","../src/chat/provider-usage.ts","../src/chat/running-calls.ts","../src/chat/safe-mode-context.tsx","../src/chat/session-export.ts","../src/chat/streaming.ts","../src/chat/theme-context.tsx","../src/chat/transcript-anchors.ts"],"sourcesContent":["/**\n * Renderer-agnostic shaping of session artifacts for display — grouping\n * and row presentation. Shared by the TUI rail + modal and the GUI rail, so\n * every surface agrees on what an artifact looks like.\n */\n\nimport type { ExtensionArtifactRow, ExtensionRegistry } from '../extensions'\nimport type { SessionArtifact } from '../session'\nimport { artifactKindLabel, renderArtifactRow } from '../extensions/builtin-artifact-kinds'\n\n/** Whether artifacts are listed for the current session or the whole project. */\nexport type ArtifactScope = 'session' | 'project'\n\n/** One artifact paired with its rendered row. */\nexport interface ArtifactViewRow {\n /** The session artifact. */\n artifact: SessionArtifact\n /** Rendered row for the artifact. */\n row: ExtensionArtifactRow\n}\n\n/** Artifacts of one kind, ready to display. */\nexport interface ArtifactViewGroup {\n /** Artifact kind id. */\n kind: string\n /** Human kind label (`Pull request`), raw kind when unregistered. */\n label: string\n /** Rows of this kind, in the store's order. */\n rows: ArtifactViewRow[]\n}\n\ntype KindRegistry = Pick<ExtensionRegistry, 'artifactKinds'> | undefined\n\n/** Display order for the built-in kinds; anything else follows alphabetically. */\nconst KIND_ORDER = ['pull_request', 'branch', 'commit', 'file']\n\n/**\n * Group by kind (built-ins first in a fixed order, then extension kinds\n * alphabetically); rows inside a group keep the store's newest-first order.\n *\n * @param artifacts - Session artifacts to group.\n * @param registry - Extension registry that supplies kind labels and row renderers.\n */\nexport function groupArtifacts(artifacts: readonly SessionArtifact[], registry?: KindRegistry): ArtifactViewGroup[] {\n const byKind = new Map<string, ArtifactViewRow[]>()\n for (const artifact of artifacts) {\n const list = byKind.get(artifact.kind) ?? []\n list.push({ artifact, row: renderArtifactRow(artifact, registry) })\n byKind.set(artifact.kind, list)\n }\n const kinds = [...byKind.keys()].sort((a, b) => {\n const ai = KIND_ORDER.indexOf(a)\n const bi = KIND_ORDER.indexOf(b)\n if (ai !== -1 || bi !== -1)\n return (ai === -1 ? KIND_ORDER.length : ai) - (bi === -1 ? KIND_ORDER.length : bi)\n return a.localeCompare(b)\n })\n return kinds.map(kind => ({ kind, label: artifactKindLabel(kind, registry), rows: byKind.get(kind)! }))\n}\n\n/**\n * One count per kind, a badge each: `['3 pull requests', '2 files']`; empty\n * for none.\n *\n * @param artifacts - Session artifacts to count.\n * @param registry - Extension registry that supplies kind labels.\n */\nexport function summarizeArtifactCounts(artifacts: readonly SessionArtifact[], registry?: KindRegistry): string[] {\n return groupArtifacts(artifacts, registry)\n .map(g => `${g.rows.length} ${pluralizeKind(g.label, g.rows.length)}`)\n}\n\nfunction pluralizeKind(label: string, count: number): string {\n const lower = label.toLowerCase()\n if (count === 1)\n return lower\n return /(?:s|x|ch|sh)$/.test(lower) ? `${lower}es` : `${lower}s`\n}\n","/** Whether any of `vars` is set to a truthy value (anything but unset, `''`, `0`, `false`). */\nexport function envOptedOut(vars: readonly string[], env: NodeJS.ProcessEnv = process.env): boolean {\n for (const key of vars) {\n const v = env[key]\n if (v !== undefined && v !== '' && v !== '0' && v.toLowerCase() !== 'false')\n return true\n }\n return false\n}\n","// ---------------------------------------------------------------------------\n// Auto-update — renderer-agnostic core.\n//\n// Three layers, all reusable by any consumer that wraps `zidane/tui`:\n// 1. `checkForUpdate` — fetches the latest version from an npm-compatible\n// registry, persisted on disk with a TTL so the hot path costs zero\n// network on a warm cache.\n// 2. `performSelfUpdate` — spawns the host package manager (npm, pnpm,\n// yarn, bun, volta) and tells it to re-install the package globally.\n// 3. `performInPlaceSelfUpdate` — downloads the platform-binary tarball\n// from the registry, extracts the binary, atomically swaps it over\n// the live one. Used when no package manager is reachable (offline\n// install path, restricted shells) or when the user wants speed.\n//\n// Pure data + Node-builtin I/O. No React, no OpenTUI, no external deps —\n// hosts replacing the runtime can re-implement just the layer they need.\n// ---------------------------------------------------------------------------\n\nimport type { Buffer } from 'node:buffer'\nimport { spawn } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport {\n chmodSync,\n createReadStream,\n createWriteStream,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n realpathSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from 'node:fs'\nimport { homedir, tmpdir } from 'node:os'\nimport { dirname, join, resolve, sep } from 'node:path'\nimport { createGunzip } from 'node:zlib'\nimport { envOptedOut } from './env-opt-out'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Options for {@link checkForUpdate}. */\nexport interface CheckForUpdateOptions {\n /** Full npm package name to query. e.g. `'zidane-tui'`. */\n packageName: string\n /** Current installed version. Inlined at build time from the consumer's `package.json`. */\n currentVersion: string\n /**\n * Registry base URL. Defaults to `https://registry.npmjs.org`. Strip the\n * trailing slash; the helper appends `/${packageName}/${channel}`.\n */\n registry?: string\n /**\n * dist-tag to read. Defaults to `'latest'`. Set to `'next'` (or any\n * custom tag) for prerelease channels — the comparison still goes through\n * `compareSemver`, so `5.6.0-beta.2 > 5.5.0` evaluates correctly.\n */\n channel?: string\n /** Directory used for the on-disk TTL cache. Skipped when omitted. */\n cacheDir?: string\n /**\n * Cache lifetime. Defaults to 24 h. Zero or negative never counts the cache\n * as fresh, so every check hits the registry. The cache is still written\n * and still serves as the fallback when the registry fails.\n */\n cacheTtlMs?: number\n /** Network timeout. Defaults to 3 000 ms. */\n timeoutMs?: number\n /** Abort signal — wins over the internal timeout. */\n signal?: AbortSignal\n /**\n * When `true`, ignore env-var opt-outs (`CI`, `NO_UPDATE_NOTIFIER`,\n * `ZIDANE_NO_UPDATE`) AND bypass the on-disk TTL cache (always hit\n * the registry). Used by the explicit `zidane upgrade` path where the\n * user typed the command — they expect fresh data, not a 24-hour-old\n * cached value. The cache is still WRITTEN with the fresh result so\n * subsequent background checks benefit; cache reads only feed the\n * etag into the next request so the registry can short-circuit with\n * 304 when nothing changed.\n */\n force?: boolean\n /** Inject for tests. Defaults to `globalThis.fetch`. */\n fetcher?: typeof fetch\n /** Inject for tests. Defaults to `Date.now`. */\n now?: () => number\n}\n\n/** Result of {@link checkForUpdate}. */\nexport interface UpdateStatus {\n /** Always present — echoes `currentVersion`. */\n current: string\n /** `null` when the lookup failed / was skipped / cache was empty. */\n latest: string | null\n /** `true` when `latest` is strictly greater than `current` by semver. */\n hasUpdate: boolean\n /**\n * - `'fresh'` — fetched from the registry this call.\n * - `'cached'` — served from the on-disk cache, including a stale entry\n * when the registry fetch fails.\n * - `'skipped'` — opt-out env var, or no `fetch` and no cache.\n * - `'error'` — registry returned non-2xx or threw; details on `error`.\n */\n source: 'fresh' | 'cached' | 'skipped' | 'error'\n /** Error message when `source === 'error'`. */\n error?: string\n /** When the registry was last queried (ms since epoch). `null` when never. */\n checkedAt: number | null\n /** dist-tag used for the lookup — handy when surfacing \"v5.6.0 (next)\". */\n channel: string\n}\n\n/** A package manager install command. */\nexport interface PackageManagerCommand {\n /** Stable identifier. `npm` is the fallback when no other signal fires. */\n id: PackageManagerId\n /** Argv that, when execve'd, installs `packageName@channel` globally. */\n argv: string[]\n /**\n * Free-form note for the user — explains *why* this command was chosen\n * (e.g. \"Volta-managed install detected via ~/.volta/bin\").\n */\n note?: string\n}\n\n/** Identifier of a supported package manager. */\nexport type PackageManagerId = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'volta'\n\n/** Options for {@link detectPackageManager}. Every field overrides a value read from the running process, which keeps detection testable. */\nexport interface DetectPackageManagerOptions {\n /** Override the resolved binary path (defaults to `process.execPath`). */\n binaryPath?: string\n /** Override the user-agent string (defaults to `process.env.npm_config_user_agent`). */\n userAgent?: string | undefined\n /** Override env. Used in tests. */\n env?: Readonly<Record<string, string | undefined>>\n /** Override `homedir()`. Used in tests. */\n home?: string\n /** Override `process.platform`. Used in tests. */\n platform?: NodeJS.Platform\n /** Package to install. Defaults to `'zidane-tui'`. */\n packageName?: string\n /** dist-tag. Defaults to `'latest'`. */\n channel?: string\n}\n\n// ---------------------------------------------------------------------------\n// Env-var opt-outs\n//\n// `NO_UPDATE_NOTIFIER` is the de-facto npm-ecosystem convention (yeoman,\n// update-notifier). `ZIDANE_NO_UPDATE` is the project-specific knob so\n// hosts wrapping the TUI can wire their own without colliding.\n// `CI=true` is set by every major CI runner; skipping there avoids\n// surprise registry chatter from build pipelines.\n// ---------------------------------------------------------------------------\n\nconst OPT_OUT_VARS = ['ZIDANE_NO_UPDATE', 'NO_UPDATE_NOTIFIER', 'CI'] as const\n\nfunction isOptedOut(env: NodeJS.ProcessEnv = process.env): boolean {\n return envOptedOut(OPT_OUT_VARS, env)\n}\n\n// ---------------------------------------------------------------------------\n// Semver compare — tiny self-contained implementation.\n//\n// We support the subset of semver the npm registry actually returns:\n// `MAJOR.MINOR.PATCH` plus optional `-prerelease[.N…]` and `+build`. Build\n// metadata is ignored per spec; prereleases compare via the standard rule\n// (numeric vs string, longer chain wins after equal prefix).\n//\n// Inlining this beats pulling in `semver` (~70 KB) just to compare two\n// strings. Tested in `test/chat/auto-update.test.ts`.\n// ---------------------------------------------------------------------------\n\n/**\n * Parses a semantic version string into its parts.\n * Accepts an optional leading `v`, a prerelease and build metadata. Returns `null` when the input is not a valid version.\n *\n * @param input - Version string such as `1.2.3` or `v1.2.3-rc.1`.\n */\nexport function parseSemver(input: string): { major: number, minor: number, patch: number, prerelease: string[] } | null {\n const m = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([\\w.-]+))?(?:\\+[\\w.-]+)?$/.exec(input.trim())\n if (!m)\n return null\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n prerelease: m[4] ? m[4].split('.') : [],\n }\n}\n\n/**\n * Returns `-1 / 0 / 1`. Unparseable inputs sort as `0` (assume equal).\n *\n * @param a - First version string.\n * @param b - Second version string.\n */\nexport function compareSemver(a: string, b: string): -1 | 0 | 1 {\n const A = parseSemver(a)\n const B = parseSemver(b)\n if (!A || !B)\n return 0\n for (const k of ['major', 'minor', 'patch'] as const) {\n if (A[k] !== B[k])\n return A[k] < B[k] ? -1 : 1\n }\n // A release version is greater than its prereleases (1.0.0 > 1.0.0-rc.1).\n if (A.prerelease.length === 0 && B.prerelease.length > 0)\n return 1\n if (A.prerelease.length > 0 && B.prerelease.length === 0)\n return -1\n const len = Math.max(A.prerelease.length, B.prerelease.length)\n for (let i = 0; i < len; i++) {\n const ai = A.prerelease[i]\n const bi = B.prerelease[i]\n if (ai === undefined)\n return -1\n if (bi === undefined)\n return 1\n const an = /^\\d+$/.test(ai) ? Number(ai) : null\n const bn = /^\\d+$/.test(bi) ? Number(bi) : null\n if (an !== null && bn !== null) {\n if (an !== bn)\n return an < bn ? -1 : 1\n }\n else if (an !== null) {\n return -1 // numeric < alphanumeric per spec\n }\n else if (bn !== null) {\n return 1\n }\n else if (ai !== bi) {\n return ai < bi ? -1 : 1\n }\n }\n return 0\n}\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\ninterface UpdateCheckFile {\n packageName: string\n channel: string\n latest: string\n checkedAt: number\n /** Last-seen registry ETag — flows through If-None-Match on the next check. */\n etag?: string\n}\n\nfunction cacheFilePath(cacheDir: string, packageName: string, channel: string): string {\n // Embed package + channel in the filename so a host running multiple\n // checks (e.g. against `latest` and `next`) doesn't trample state.\n const slug = `${packageName.replaceAll('/', '__')}@${channel}`\n return resolve(cacheDir, `update-check-${slug}.json`)\n}\n\nfunction readCache(path: string): UpdateCheckFile | null {\n try {\n const raw = readFileSync(path, 'utf8')\n const parsed = JSON.parse(raw) as UpdateCheckFile\n if (typeof parsed.latest !== 'string' || typeof parsed.checkedAt !== 'number')\n return null\n return parsed\n }\n catch {\n return null\n }\n}\n\nfunction writeCache(path: string, entry: UpdateCheckFile): void {\n try {\n mkdirSync(dirname(path), { recursive: true })\n // Format with a trailing newline so hand-inspection in a terminal\n // doesn't render the prompt glued to the closing brace.\n writeFileSync(path, `${JSON.stringify(entry, null, 2)}\\n`)\n }\n catch {\n // Cache failures are non-fatal — the next call just re-fetches.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registry fetch\n// ---------------------------------------------------------------------------\n\n/** Strip a trailing slash so URL assembly is uniform. */\nfunction trimSlash(s: string): string {\n return s.endsWith('/') ? s.slice(0, -1) : s\n}\n\ninterface FetchedRelease {\n latest: string\n etag: string | null\n}\n\nasync function fetchLatestRelease(opts: {\n registry: string\n packageName: string\n channel: string\n etag: string | undefined\n fetcher: typeof fetch\n signal: AbortSignal\n}): Promise<FetchedRelease> {\n // Use the `<pkg>/<dist-tag>` endpoint which returns a single packument\n // (the smallest payload). Falls back to the abbreviated metadata mime\n // type so npm proxies (Verdaccio, JFrog) that don't honor it still\n // return JSON.\n const url = `${trimSlash(opts.registry)}/${encodeURIComponent(opts.packageName)}/${encodeURIComponent(opts.channel)}`\n const headers: Record<string, string> = {\n 'accept': 'application/vnd.npm.install-v1+json, application/json',\n 'user-agent': 'zidane-update-check/1',\n }\n if (opts.etag)\n headers['if-none-match'] = opts.etag\n const res = await opts.fetcher(url, { headers, signal: opts.signal, redirect: 'follow' })\n if (res.status === 304) {\n // Caller falls back to the cached entry when this throws — keep the\n // signal explicit instead of overloading the return shape.\n throw new NotModifiedError()\n }\n if (!res.ok)\n throw new Error(`registry returned ${res.status}`)\n const body = await res.json() as { version?: string, name?: string }\n if (!body.version)\n throw new Error('registry payload missing `version`')\n return { latest: body.version, etag: res.headers.get('etag') }\n}\n\nclass NotModifiedError extends Error {\n constructor() { super('not-modified') }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: checkForUpdate\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the latest available version of `packageName`, honoring an\n * on-disk TTL cache so the warm path costs zero network. Never throws —\n * registry failures degrade to `{ source: 'error', latest: null }`.\n *\n * Designed for boot-path usage: short timeout, swallows errors, opt-outs\n * for CI / restricted shells. Consumers driving the explicit `upgrade`\n * subcommand should set `force: true` to ignore env opt-outs.\n *\n * @param options - Package, current version and cache, network and opt-out settings for the check.\n */\nexport async function checkForUpdate(options: CheckForUpdateOptions): Promise<UpdateStatus> {\n const channel = options.channel ?? 'latest'\n const now = options.now ?? Date.now\n const fetcher = options.fetcher ?? globalThis.fetch\n const ttl = options.cacheTtlMs ?? 24 * 60 * 60 * 1000\n const timeoutMs = options.timeoutMs ?? 3000\n\n if (!options.force && isOptedOut())\n return skipped(options.currentVersion, channel)\n\n const cachePath = options.cacheDir\n ? cacheFilePath(options.cacheDir, options.packageName, channel)\n : null\n\n // Fast-path the warm cache — but only when the cached payload still\n // matches `packageName + channel`. The slug guards against an\n // older zidane writing a different package's cache file at the same\n // path (host rename scenarios). `force` skips the freshness short-\n // circuit entirely: the user-typed `upgrade` path always wants the\n // truth from the registry, never a 24-hour-old cached \"no update\".\n const cached = cachePath ? readCache(cachePath) : null\n if (!options.force && cached && cached.packageName === options.packageName && cached.channel === channel) {\n const fresh = ttl > 0 && (now() - cached.checkedAt) < ttl\n if (fresh) {\n const hasUpdate = compareSemver(cached.latest, options.currentVersion) > 0\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n }\n\n // Fetch is required for the cold path. Falling back to a stale cache is\n // checked further below, but the contract for `skipped` is \"we didn't\n // even try\" — without a fetcher we can't try.\n if (typeof fetcher !== 'function') {\n if (cached) {\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n return skipped(options.currentVersion, channel)\n }\n\n // Tie the user's signal to our timeout so abort propagates. The\n // controller is local to this call — we never leak it to callers.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs)\n if (options.signal) {\n if (options.signal.aborted)\n controller.abort(options.signal.reason as Error)\n else\n options.signal.addEventListener('abort', () => controller.abort(options.signal!.reason as Error), { once: true })\n }\n\n try {\n const release = await fetchLatestRelease({\n registry: options.registry ?? 'https://registry.npmjs.org',\n packageName: options.packageName,\n channel,\n etag: cached?.etag,\n fetcher,\n signal: controller.signal,\n })\n const checkedAt = now()\n if (cachePath) {\n writeCache(cachePath, {\n packageName: options.packageName,\n channel,\n latest: release.latest,\n checkedAt,\n ...(release.etag ? { etag: release.etag } : {}),\n })\n }\n return {\n current: options.currentVersion,\n latest: release.latest,\n hasUpdate: compareSemver(release.latest, options.currentVersion) > 0,\n source: 'fresh',\n checkedAt,\n channel,\n }\n }\n catch (err) {\n if (err instanceof NotModifiedError && cached) {\n // 304 — extend the cache freshness window so we don't re-poll\n // every boot when the registry agrees we're current.\n const checkedAt = now()\n if (cachePath) {\n writeCache(cachePath, {\n ...cached,\n checkedAt,\n })\n }\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt,\n channel,\n }\n }\n if (cached) {\n // Fall back to a stale cache rather than reporting `null` when we\n // have data — the user still sees something useful.\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n return {\n current: options.currentVersion,\n latest: null,\n hasUpdate: false,\n source: 'error',\n error: err instanceof Error ? err.message : String(err),\n checkedAt: null,\n channel,\n }\n }\n finally {\n clearTimeout(timer)\n }\n}\n\nfunction skipped(current: string, channel: string): UpdateStatus {\n return { current, latest: null, hasUpdate: false, source: 'skipped', checkedAt: null, channel }\n}\n\n// ---------------------------------------------------------------------------\n// Package-manager detection\n//\n// Layered heuristics — strongest signal wins:\n// 1. Resolved binary path. Volta lives under `~/.volta`, bun under\n// `~/.bun`, pnpm under `~/.local/share/pnpm`, `~/Library/pnpm` or a\n// `pnpm` path segment, classic yarn under `~/.yarn`.\n// 2. `npm_config_user_agent` env var — set during `npm/pnpm/yarn/bun\n// install` runs, so this still helps when the user is mid-install.\n// 3. Env vars `VOLTA_HOME`, `BUN_INSTALL` and `PNPM_HOME`, trusted less\n// than the path because they can be set for unrelated tooling.\n// 4. Default to npm. Universally available; users on more exotic PMs\n// have to pass `--package-manager` (or set up their env so the path\n// heuristic fires).\n//\n// Volta is treated as a first-class PM here even though it wraps another\n// (typically npm) — `volta install` is the only correct way to update a\n// Volta-pinned tool. Writing through the inner PM would leave Volta's\n// catalog stale and re-pin the old version on the next shell.\n// ---------------------------------------------------------------------------\n\nfunction normalizePath(path: string): string {\n // Resolve symlinks so a `/usr/local/bin/zidane` → `/opt/homebrew/...`\n // chain still matches the inner location. `realpathSync` throws on\n // broken links; fall back to the literal path then.\n try {\n return realpathSync(path)\n }\n catch {\n return path\n }\n}\n\n/**\n * The command to show for a manual reinstall. Volta treats `install` of an\n * already-recorded version as a no-op, so the only way to replace a broken\n * install is to remove it first.\n */\nexport function reinstallHint(command: PackageManagerCommand, packageName: string, channel: string): string {\n const cmd = command.argv.join(' ')\n return command.id === 'volta' ? `volta uninstall ${packageName} && volta install ${packageName}@${channel}` : cmd\n}\n\n/**\n * Chooses the package manager command that installs the package globally.\n * Checks the binary path, then the npm user agent, then environment hints such as `VOLTA_HOME`, and falls back to `npm`.\n *\n * @param opts - Optional overrides for the binary path, environment, platform, package name and channel.\n */\nexport function detectPackageManager(opts: DetectPackageManagerOptions = {}): PackageManagerCommand {\n const env = opts.env ?? process.env\n const home = opts.home ?? homedir()\n const platform = opts.platform ?? process.platform\n const channel = opts.channel ?? 'latest'\n const packageName = opts.packageName ?? 'zidane-tui'\n const binaryPath = opts.binaryPath ?? process.execPath\n const resolvedBinary = normalizePath(binaryPath)\n const userAgent = opts.userAgent ?? env.npm_config_user_agent\n\n // The argv contract:\n // - `argv[0]` is the binary we exec — needs to be on PATH or absolute.\n // - Remaining tokens are the install command.\n // We rely on PATH resolution so users with shimmed wrappers (volta,\n // mise, asdf) still hit their managed binary.\n const target = `${packageName}@${channel}`\n\n // 1. Path-based heuristics ------------------------------------------------\n //\n // Match path SEGMENTS, not substrings — `/Users/me/.volta-old/bin/x` must\n // NOT match `/Users/me/.volta`. We anchor the comparison to a separator\n // on both sides so the directory name has to be exact.\n\n const containsSegment = (path: string, segment: string) => {\n // `segment` is `<home>/.<name>` (no trailing separator). To match, the\n // path must contain `<segment><sep>` OR end with `<segment>`.\n const tail = `${segment}${sep}`\n return path.includes(tail) || path.endsWith(segment)\n }\n const inPath = (segment: string) =>\n containsSegment(resolvedBinary, segment) || containsSegment(binaryPath, segment)\n const homeSeg = (rest: string) => `${home}${sep}${rest}`\n\n // Binary-path check — strongest signal. A binary living under `~/.volta/`\n // was DEFINITELY installed by Volta; same logic for the others.\n if (inPath(homeSeg('.volta'))) {\n return {\n id: 'volta',\n argv: ['volta', 'install', target],\n note: 'Volta-managed install detected, refreshing via `volta install`.',\n }\n }\n if (inPath(homeSeg('.bun'))) {\n return {\n id: 'bun',\n argv: ['bun', 'add', '--global', target],\n note: 'Bun global install detected.',\n }\n }\n if (\n inPath(homeSeg('.local/share/pnpm'))\n || inPath(homeSeg('Library/pnpm'))\n || resolvedBinary.includes(`${sep}pnpm${sep}`)\n ) {\n return {\n id: 'pnpm',\n argv: ['pnpm', 'add', '--global', target],\n note: 'pnpm global install detected.',\n }\n }\n if (inPath(homeSeg('.yarn'))) {\n return {\n id: 'yarn',\n argv: ['yarn', 'global', 'add', target],\n note: 'Yarn classic global install detected.',\n }\n }\n\n // 2. User-agent heuristic (mid-install or recently-installed shells) ------\n // `npm_config_user_agent` looks like `npm/10.2.3 node/v22.0.0 darwin arm64`\n // or `pnpm/8.15.0 npm/? node/v22.0.0 darwin arm64`. This fires when the\n // binary lives in an unrecognized prefix (corporate npm proxy, custom\n // global root, …) but the calling shell still identifies a PM.\n if (userAgent) {\n const ua = userAgent.toLowerCase()\n if (ua.startsWith('pnpm/'))\n return { id: 'pnpm', argv: ['pnpm', 'add', '--global', target] }\n if (ua.startsWith('yarn/'))\n return { id: 'yarn', argv: ['yarn', 'global', 'add', target] }\n if (ua.startsWith('bun/'))\n return { id: 'bun', argv: ['bun', 'add', '--global', target] }\n if (ua.startsWith('npm/'))\n return { id: 'npm', argv: ['npm', 'install', '--global', target] }\n }\n\n // 3. Env-var fallback — only fires when both the path and the user-agent\n // failed to identify a PM. We trust these LESS than the binary path\n // because a user can install zidane outside their main PM (e.g. via\n // npm) while still having $VOLTA_HOME / $BUN_INSTALL set for unrelated\n // tooling.\n if (env.VOLTA_HOME)\n return { id: 'volta', argv: ['volta', 'install', target], note: '$VOLTA_HOME set, using `volta install`. If the install is not Volta-managed, pass `--package-manager npm`.' }\n if (env.BUN_INSTALL)\n return { id: 'bun', argv: ['bun', 'add', '--global', target] }\n if (env.PNPM_HOME)\n return { id: 'pnpm', argv: ['pnpm', 'add', '--global', target] }\n\n // 4. Default to npm — universally available.\n\n void platform // Reserved for Windows-specific tweaks (none yet).\n return {\n id: 'npm',\n argv: ['npm', 'install', '--global', target],\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: tarball readiness\n// ---------------------------------------------------------------------------\n\n/** Options for {@link checkTarballAvailable}. */\nexport interface TarballAvailabilityOptions {\n /** Full npm package name to probe. */\n packageName: string\n /** Exact version whose tarball is probed. */\n version: string\n /** Registry base URL. Defaults to `https://registry.npmjs.org`. */\n registry?: string\n /** Timeout for the whole probe. Defaults to 8 000 ms. */\n timeoutMs?: number\n /** Inject for tests. */\n fetcher?: typeof fetch\n}\n\n/**\n * Whether `<packageName>@<version>` can actually be DOWNLOADED right now, not\n * merely resolved. Right after a publish the registry serves the new\n * manifest minutes before the tarball reaches the CDN; a package manager\n * that hits that window on an `optionalDependencies` platform binary\n * silently skips it and leaves a bin with nothing behind it. `upgrade` asks\n * this before spawning the PM so it can wait instead of breaking the install.\n *\n * - `'ok'` tarball responds 2xx\n * - `'missing'` manifest or tarball 404 (published, not propagated)\n * - `'unknown'` network / registry error; the caller decides\n *\n * @param opts - Package, version and registry settings for the availability probe.\n */\nexport async function checkTarballAvailable(opts: TarballAvailabilityOptions): Promise<'ok' | 'missing' | 'unknown'> {\n const fetcher = opts.fetcher ?? globalThis.fetch\n const registry = trimSlash(opts.registry ?? 'https://registry.npmjs.org')\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? 8000)\n try {\n const manifest = await fetcher(\n `${registry}/${encodeURIComponent(opts.packageName)}/${encodeURIComponent(opts.version)}`,\n { headers: { 'accept': 'application/json', 'user-agent': 'zidane-update-check/1' }, signal: ac.signal, redirect: 'follow' },\n )\n if (manifest.status === 404)\n return 'missing'\n if (!manifest.ok)\n return 'unknown'\n const body = await manifest.json() as { dist?: { tarball?: string } }\n const tarball = body.dist?.tarball\n if (!tarball)\n return 'unknown'\n const head = await fetcher(tarball, { method: 'HEAD', signal: ac.signal, redirect: 'follow' })\n if (head.status === 404)\n return 'missing'\n return head.ok ? 'ok' : 'unknown'\n }\n catch {\n return 'unknown'\n }\n finally {\n clearTimeout(timer)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: performSelfUpdate (PM-spawned)\n// ---------------------------------------------------------------------------\n\n/** Options for {@link performSelfUpdate}. */\nexport interface PerformSelfUpdateOptions {\n /** Full npm package name to install. */\n packageName: string\n /** dist-tag to install. Defaults to `'latest'`. */\n channel?: string\n /** Force a specific PM instead of auto-detect. */\n packageManager?: PackageManagerId | 'auto'\n /** Don't actually spawn — return the command we WOULD run. */\n dryRun?: boolean\n /** When set, write `pm stdout/stderr` to these streams instead of stdio inherit. */\n stdout?: NodeJS.WritableStream\n /** Stream that receives the package manager's stderr instead of inheriting stdio. */\n stderr?: NodeJS.WritableStream\n /** Abort the install. */\n signal?: AbortSignal\n /** Override env (e.g. set `npm_config_loglevel=error`). */\n env?: Readonly<Record<string, string>>\n /** Inject for tests. */\n spawn?: typeof spawn\n /** Inject for tests. */\n detect?: typeof detectPackageManager\n}\n\n/** Outcome of {@link performSelfUpdate}. */\nexport interface SelfUpdateResult {\n /** The package manager command that was, or would be, run. */\n command: PackageManagerCommand\n /** PM exit code. `null` when `dryRun` or the process was killed. */\n exitCode: number | null\n /** Set when the spawn itself failed (`ENOENT`, `EACCES`). */\n spawnError?: string\n}\n\n/**\n * Re-install the package globally via the host's package manager. Resolves\n * with the spawned command's exit code. It does not reject: a spawn failure\n * comes back as `spawnError`, an abort of the caller's `signal` kills the\n * child (`exitCode: null`), and PM failures surface as a non-zero `exitCode`.\n * The caller decides whether to retry or fall back to in-place.\n *\n * @param options - Package, channel, package manager and stream settings for the install.\n */\nexport async function performSelfUpdate(options: PerformSelfUpdateOptions): Promise<SelfUpdateResult> {\n const detect = options.detect ?? detectPackageManager\n const command = options.packageManager && options.packageManager !== 'auto'\n ? overrideManager(options.packageManager, options.packageName, options.channel ?? 'latest')\n : detect({ packageName: options.packageName, channel: options.channel ?? 'latest' })\n\n if (options.dryRun)\n return { command, exitCode: null }\n\n const useSpawn = options.spawn ?? spawn\n return await new Promise<SelfUpdateResult>((resolveP) => {\n const [bin, ...args] = command.argv\n const child = useSpawn(bin!, args, {\n stdio: options.stdout || options.stderr\n ? ['ignore', 'pipe', 'pipe']\n : 'inherit',\n env: { ...process.env, ...options.env },\n })\n if (options.stdout && child.stdout)\n child.stdout.pipe(options.stdout)\n if (options.stderr && child.stderr)\n child.stderr.pipe(options.stderr)\n child.on('error', (err) => {\n // ENOENT here means the PM binary isn't on PATH — surface a\n // clear message rather than the bare error.\n resolveP({ command, exitCode: null, spawnError: err.message })\n })\n child.on('exit', (code) => {\n resolveP({ command, exitCode: code })\n })\n if (options.signal) {\n const onAbort = () => child.kill()\n if (options.signal.aborted)\n onAbort()\n else\n options.signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n\n/** The global-install command that pins `<packageName>@<channel>` for package manager `id`. */\nexport function overrideManager(id: PackageManagerId, packageName: string, channel: string): PackageManagerCommand {\n const target = `${packageName}@${channel}`\n switch (id) {\n case 'pnpm': return { id, argv: ['pnpm', 'add', '--global', target] }\n case 'yarn': return { id, argv: ['yarn', 'global', 'add', target] }\n case 'bun': return { id, argv: ['bun', 'add', '--global', target] }\n case 'volta': return { id, argv: ['volta', 'install', target] }\n case 'npm':\n default: return { id: 'npm', argv: ['npm', 'install', '--global', target] }\n }\n}\n\n// ---------------------------------------------------------------------------\n// In-place self-update\n//\n// Downloads `<packageName>@<channel>` from the npm registry, verifies its\n// integrity digest, untars the `bin/<filename>` entry, and atomic-renames it\n// over the live binary. The host needs to know:\n// - the platform-specific package name to fetch (e.g. `zidane-tui-darwin-arm64`)\n// - the path inside that tarball where the binary lives (defaults to\n// `bin/<binaryName>`, matched as `package/bin/<binaryName>`, which fits the\n// project layout under `tui/npm/<key>/bin/`)\n// - the path of the running binary, so we know where to write\n//\n// Volta-managed installs are explicitly refused — Volta's catalog needs\n// the PM path. The caller should detect via `detectPackageManager` and\n// fall through to `performSelfUpdate` instead.\n// ---------------------------------------------------------------------------\n\n/** Options for {@link performInPlaceSelfUpdate}. */\nexport interface PerformInPlaceUpdateOptions {\n /** Platform-specific tarball package, e.g. `'zidane-tui-darwin-arm64'`. */\n packageName: string\n /** dist-tag to install. Defaults to `'latest'`. */\n channel?: string\n /** Resolved path of the running binary. Defaults to `process.execPath`. */\n binaryPath?: string\n /** Inside the tarball — usually `bin/<name>`. Defaults to `bin/<basename(binaryPath)>`. */\n tarballBinaryPath?: string\n /** Registry base URL. Defaults to `https://registry.npmjs.org`. */\n registry?: string\n /** Network timeout for the whole update, metadata and download. Defaults to 30 000 ms — tarballs are MB-scale. */\n timeoutMs?: number\n /** Abort signal for the download. */\n signal?: AbortSignal\n /** Don't replace — just download, verify and extract. */\n dryRun?: boolean\n /** Fetch implementation. Defaults to `globalThis.fetch`. */\n fetcher?: typeof fetch\n /** When set, refuse the swap if the resolved binary lives under one of these prefixes. */\n refuseUnderPrefixes?: readonly string[]\n}\n\n/** Outcome of {@link performInPlaceSelfUpdate}. */\nexport interface InPlaceUpdateResult {\n /** `'success' | 'refused' | 'failed'`. */\n status: 'success' | 'refused' | 'failed'\n /** Version that was installed (from the tarball's package.json). */\n installedVersion?: string\n /** Why we bailed. Always present on `refused` / `failed`. */\n reason?: string\n /** Resolved binary path after the swap. */\n binaryPath?: string\n}\n\n/**\n * Replace the running binary with the latest tarball-shipped one. Works on\n * macOS + Linux (POSIX unlink-while-running); refuses on Windows. Returns\n * a result object instead of throwing — callers usually want to print\n * `result.reason` and fall back to a PM-driven update.\n *\n * @param options - Platform package, binary path, registry and swap restrictions.\n */\nexport async function performInPlaceSelfUpdate(options: PerformInPlaceUpdateOptions): Promise<InPlaceUpdateResult> {\n if (process.platform === 'win32')\n return { status: 'refused', reason: 'in-place update not supported on Windows; use `zidane upgrade` instead.' }\n\n const binaryPath = options.binaryPath ?? process.execPath\n const resolved = normalizePath(binaryPath)\n const refused = options.refuseUnderPrefixes\n if (refused?.some(p => resolved.startsWith(p))) {\n return { status: 'refused', reason: `binary at ${resolved} sits under a managed prefix; use \\`zidane upgrade\\` to update via your package manager.` }\n }\n // Volta is the canonical \"managed prefix\" — refuse by default so users\n // don't end up with a stale catalog after the swap.\n if (resolved.includes(`${homedir()}${sep}.volta${sep}`)) {\n return { status: 'refused', reason: 'Volta-managed install detected. Use `zidane upgrade` (runs `volta install`) instead.' }\n }\n\n const fetcher = options.fetcher ?? globalThis.fetch\n if (typeof fetcher !== 'function')\n return { status: 'failed', reason: 'global fetch unavailable; rebuild with Node ≥ 18 or Bun.' }\n\n const channel = options.channel ?? 'latest'\n const registry = options.registry ?? 'https://registry.npmjs.org'\n const timeoutMs = options.timeoutMs ?? 30_000\n\n // 1. Resolve dist-tag → packument entry for the exact version + tarball URL.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs)\n if (options.signal) {\n if (options.signal.aborted)\n controller.abort(options.signal.reason as Error)\n else\n options.signal.addEventListener('abort', () => controller.abort(options.signal!.reason as Error), { once: true })\n }\n\n try {\n const metaUrl = `${trimSlash(registry)}/${encodeURIComponent(options.packageName)}/${encodeURIComponent(channel)}`\n const metaRes = await fetcher(metaUrl, {\n headers: { 'accept': 'application/json', 'user-agent': 'zidane-update-check/1' },\n signal: controller.signal,\n })\n if (!metaRes.ok)\n return { status: 'failed', reason: `registry returned ${metaRes.status} for ${options.packageName}@${channel}` }\n const meta = await metaRes.json() as {\n version?: string\n dist?: { tarball?: string, integrity?: string, shasum?: string }\n }\n if (!meta.version || !meta.dist?.tarball)\n return { status: 'failed', reason: `registry payload missing version or tarball for ${options.packageName}@${channel}` }\n // Defense-in-depth: the npm registry serves over HTTPS, but a user\n // running with `--registry <custom>` might point at a less-trusted\n // mirror. Refuse to overwrite the live binary if the registry didn't\n // ship at least one integrity field for us to verify.\n if (!meta.dist.integrity && !meta.dist.shasum)\n return { status: 'failed', reason: `registry payload missing integrity / shasum for ${options.packageName}@${channel}; refusing to overwrite the binary.` }\n\n // 2. Stage adjacent to the target binary's directory so the final\n // `rename` stays on a single filesystem (avoids EXDEV on Linux where\n // /tmp is often tmpfs). Falls back to the OS tmpdir if the target's\n // parent is non-writable.\n const targetDir = dirname(binaryPath)\n let stagingDir: string\n try {\n stagingDir = mkdtempSync(join(targetDir, '.zidane-update-'))\n }\n catch {\n stagingDir = mkdtempSync(join(tmpdir(), 'zidane-update-'))\n }\n const tarballPath = join(stagingDir, 'package.tgz')\n\n try {\n const tgzRes = await fetcher(meta.dist.tarball, {\n headers: { 'user-agent': 'zidane-update-check/1' },\n signal: controller.signal,\n })\n if (!tgzRes.ok || !tgzRes.body)\n return { status: 'failed', reason: `tarball download failed: ${tgzRes.status}` }\n const digests = await streamToFile(tgzRes.body, tarballPath)\n\n // 3. Verify integrity. Prefer SRI (sha512) when present; fall back\n // to the legacy `shasum` (SHA-1) which every package on the registry\n // still carries. A mismatch is a hard refusal — never overwrite the\n // live binary with content we can't authenticate.\n const integrityCheck = verifyIntegrity(meta.dist, digests)\n if (!integrityCheck.ok)\n return { status: 'failed', reason: `integrity check failed: ${integrityCheck.reason}` }\n\n // 4. Extract just the binary entry.\n const tarballBinaryPath = options.tarballBinaryPath\n ?? `bin/${binaryNameFrom(binaryPath)}`\n const extractedPath = await extractEntryFromTarball(tarballPath, tarballBinaryPath, stagingDir)\n if (!extractedPath)\n return { status: 'failed', reason: `tarball did not contain ${tarballBinaryPath}` }\n\n if (options.dryRun) {\n return { status: 'success', installedVersion: meta.version, binaryPath: extractedPath }\n }\n\n // 5. Mark executable + atomic rename. POSIX permits unlink-while-running.\n chmodSync(extractedPath, 0o755)\n const targetForSwap = binaryPath\n try {\n renameSync(extractedPath, targetForSwap)\n }\n catch (err) {\n return { status: 'failed', reason: `failed to write ${targetForSwap}: ${err instanceof Error ? err.message : String(err)}` }\n }\n return { status: 'success', installedVersion: meta.version, binaryPath: targetForSwap }\n }\n finally {\n try { rmSync(stagingDir, { recursive: true, force: true }) }\n catch { /* best-effort */ }\n }\n }\n catch (err) {\n return { status: 'failed', reason: err instanceof Error ? err.message : String(err) }\n }\n finally {\n clearTimeout(timer)\n }\n}\n\nfunction binaryNameFrom(path: string): string {\n const last = path.split(/[\\\\/]/).at(-1) ?? ''\n return last.endsWith('.exe') ? last.slice(0, -4) : last\n}\n\ninterface StreamDigests {\n sha1: string\n sha512Base64: string\n size: number\n}\n\n/**\n * Stream `body` to `path` while computing integrity digests on the fly —\n * we never load the whole tarball into a Buffer just to hash it. Returns\n * SHA-1 (hex, for the legacy `dist.shasum`) and SHA-512 (base64, for SRI's\n * `sha512-<base64>`).\n */\nasync function streamToFile(body: ReadableStream<Uint8Array>, path: string): Promise<StreamDigests> {\n const file = createWriteStream(path)\n const sha1 = createHash('sha1')\n const sha512 = createHash('sha512')\n let size = 0\n const reader = body.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done)\n break\n if (value && value.byteLength > 0) {\n sha1.update(value)\n sha512.update(value)\n size += value.byteLength\n await new Promise<void>((res, rej) => file.write(value, err => err ? rej(err) : res()))\n }\n }\n }\n finally {\n await new Promise<void>(res => file.end(() => res()))\n }\n return {\n sha1: sha1.digest('hex'),\n sha512Base64: sha512.digest('base64'),\n size,\n }\n}\n\n/**\n * Validate the downloaded tarball's digest against the registry's\n * `dist.integrity` (SRI: only `sha512-<base64>` can match) or, when the\n * registry ships no SRI, the legacy `dist.shasum` (hex SHA-1). We don't\n * accept \"neither provided\" because that's what `performInPlaceSelfUpdate`\n * already short-circuited above.\n */\nfunction verifyIntegrity(\n dist: { integrity?: string, shasum?: string },\n digests: StreamDigests,\n): { ok: true } | { ok: false, reason: string } {\n if (dist.integrity) {\n // SRI may carry multiple space-separated hashes — any match wins.\n const entries = dist.integrity.split(/\\s+/).filter(Boolean)\n for (const entry of entries) {\n const m = /^(sha(?:256|384|512))-(.+)$/.exec(entry)\n if (!m)\n continue\n const algo = m[1]!\n const expected = m[2]!\n if (algo === 'sha512' && digests.sha512Base64 === expected)\n return { ok: true }\n // A non-512 SRI is unusual but legal. We do not recompute other\n // algorithms, so fail closed and refuse rather than skip the check.\n if (algo !== 'sha512')\n return { ok: false, reason: `unsupported SRI algorithm \\`${algo}\\`. Re-run via your package manager.` }\n }\n return { ok: false, reason: 'SRI mismatch: tarball does not match the registry\\'s `dist.integrity`.' }\n }\n if (dist.shasum) {\n if (digests.sha1 === dist.shasum.toLowerCase())\n return { ok: true }\n return { ok: false, reason: 'SHA-1 mismatch: tarball does not match the registry\\'s `dist.shasum`.' }\n }\n return { ok: false, reason: 'no integrity field shipped by the registry.' }\n}\n\n// ---------------------------------------------------------------------------\n// Minimal tar extractor — POSIX ustar format. Only handles the entries\n// produced by `npm pack`: filenames < 100 bytes, no PAX extended headers\n// (npm only emits them for paths > 100 chars; we already root the binary\n// at `bin/<name>` which stays well under that), no GNU long-name records.\n// ---------------------------------------------------------------------------\n\nasync function extractEntryFromTarball(\n tarballPath: string,\n entryRelPath: string,\n outDir: string,\n): Promise<string | null> {\n const matchSuffix = `/${entryRelPath}`\n const out = join(outDir, 'binary.bin')\n\n // Read the whole tarball into memory — platform packages are tens of MB,\n // well within the budget for a temp buffer.\n const chunks: Uint8Array[] = []\n const gunzip = createGunzip()\n const input = createReadStream(tarballPath)\n await new Promise<void>((res, rej) => {\n gunzip.on('data', (c: Buffer) => chunks.push(c))\n gunzip.on('end', () => res())\n gunzip.on('error', rej)\n input.on('error', rej)\n input.pipe(gunzip)\n })\n const tar = concatChunks(chunks)\n let offset = 0\n while (offset + 512 <= tar.length) {\n const header = tar.subarray(offset, offset + 512)\n if (header.every(b => b === 0))\n break\n const name = readNulString(header, 0, 100)\n const sizeOct = readNulString(header, 124, 12).trim()\n const size = sizeOct === '' ? 0 : Number.parseInt(sizeOct, 8)\n const dataStart = offset + 512\n const dataEnd = dataStart + size\n if (name && (name === entryRelPath || name.endsWith(matchSuffix))) {\n writeFileSync(out, tar.subarray(dataStart, dataEnd))\n return out\n }\n // 512-byte aligned padding.\n offset = dataEnd + ((512 - (size % 512)) % 512)\n }\n return null\n}\n\nfunction readNulString(buf: Uint8Array, start: number, len: number): string {\n const end = start + len\n let stop = end\n for (let i = start; i < end; i++) {\n if (buf[i] === 0) { stop = i; break }\n }\n return new TextDecoder('utf8').decode(buf.subarray(start, stop))\n}\n\nfunction concatChunks(chunks: Uint8Array[]): Uint8Array {\n let total = 0\n for (const c of chunks) total += c.byteLength\n const out = new Uint8Array(total)\n let offset = 0\n for (const c of chunks) {\n out.set(c, offset)\n offset += c.byteLength\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Resolve the platform-specific tarball package\n//\n// Mirrors the resolution table in `tui/bin/zidane.mjs` — `darwin/arm64`,\n// `darwin/x64`, `linux/x64` (glibc), `linux/arm64` (glibc). Returns\n// `null` for unsupported targets so the caller can print a clear message.\n// ---------------------------------------------------------------------------\n\n/** Options for {@link resolvePlatformPackage}. */\nexport interface PlatformTarballOptions {\n /** Override `process.platform`. */\n platform?: NodeJS.Platform\n /** Override `process.arch`. */\n arch?: NodeJS.Architecture | string\n /** Override the libc check (returned from `detectLibc`). */\n libc?: 'glibc' | 'musl'\n /** Prefix joined to the platform key. Defaults to `'zidane-tui-'`. */\n prefix?: string\n}\n\n/**\n * Resolves the platform-specific tarball package name for the current machine.\n * Supports macOS on arm64 and x64, and Linux on arm64 and x64 with glibc. Returns `null` for other targets.\n *\n * @param opts - Optional overrides for platform, architecture, libc and name prefix.\n */\nexport function resolvePlatformPackage(opts: PlatformTarballOptions = {}): string | null {\n const platform = opts.platform ?? process.platform\n const arch = opts.arch ?? process.arch\n const libc = opts.libc ?? detectLibc()\n const prefix = opts.prefix ?? 'zidane-tui-'\n if (platform === 'darwin') {\n if (arch === 'arm64')\n return `${prefix}darwin-arm64`\n if (arch === 'x64')\n return `${prefix}darwin-x64`\n return null\n }\n if (platform === 'linux') {\n if (libc !== 'glibc')\n return null\n if (arch === 'arm64')\n return `${prefix}linux-arm64`\n if (arch === 'x64')\n return `${prefix}linux-x64`\n return null\n }\n return null\n}\n\n/** Mirrors `tui/bin/zidane.mjs#detectLibc`. Reports `'glibc'` off Linux and `'musl'` when `ldd --version` fails or names musl. */\nexport function detectLibc(): 'glibc' | 'musl' {\n if (process.platform !== 'linux')\n return 'glibc'\n try {\n // eslint-disable-next-line ts/no-require-imports\n const { execSync } = require('node:child_process') as typeof import('node:child_process')\n const out = execSync('ldd --version 2>&1', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }) as string\n return out.toLowerCase().includes('musl') ? 'musl' : 'glibc'\n }\n catch {\n return 'musl'\n }\n}\n\n// ---------------------------------------------------------------------------\n// `_` re-exports for tests that don't want to mock through the module\n// boundary. Not part of the public surface; keep names underscored.\n// ---------------------------------------------------------------------------\n\nexport const _internal = {\n cacheFilePath,\n readCache,\n writeCache,\n binaryNameFrom,\n extractEntryFromTarball,\n}\n","// ---------------------------------------------------------------------------\n// Companion packages — sibling globals that `zidane upgrade` keeps in step\n// with the TUI. Today that is `zidane-gui`: same version line, same npm\n// release, but installed separately (and possibly with a different package\n// manager). This module answers one question — \"is <package> installed on\n// this machine, at what version, and where?\" — so the CLI can check the\n// registry for it and re-run the install through the PM that owns it.\n//\n// Discovery is by the package's bin on PATH, not by asking a PM: `npm ls -g`\n// is slow, differs per manager, and lies under Volta. The bin is what the\n// user actually invokes, so it is also the most honest signal of \"installed\".\n// ---------------------------------------------------------------------------\n\nimport { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { delimiter, dirname, join, sep } from 'node:path'\n\n/** Identifies a companion package and its executable. */\nexport interface CompanionSpec {\n /** npm package name, e.g. `'zidane-gui'`. */\n packageName: string\n /** Executable name on PATH. Defaults to `packageName`. */\n binName?: string\n}\n\n/** A companion package found on disk. */\nexport interface InstalledCompanion {\n /** npm package name. */\n packageName: string\n /** Version from the installed `package.json`. */\n version: string\n /** Root of the installed package. */\n packageDir: string\n /**\n * The bin as found on PATH (NOT resolved). `detectPackageManager` wants\n * this: `~/.volta/bin/<name>` identifies Volta even though its realpath is\n * the generic `volta-shim`.\n */\n binaryPath: string\n}\n\nexport interface FindCompanionOptions {\n env?: Readonly<Record<string, string | undefined>>\n home?: string\n platform?: NodeJS.Platform\n}\n\n/**\n * Locate a globally installed companion. `null` when its bin is not on PATH\n * or no `package.json` with that name can be found behind it.\n *\n * @param spec - The package and its bin name.\n * @param opts - Optional overrides for the environment, home directory and platform.\n */\nexport function findInstalledCompanion(spec: CompanionSpec, opts: FindCompanionOptions = {}): InstalledCompanion | null {\n const env = opts.env ?? process.env\n const home = opts.home ?? homedir()\n const platform = opts.platform ?? process.platform\n const binName = spec.binName ?? spec.packageName\n\n const binaryPath = findOnPath(binName, env.PATH ?? '', platform)\n if (!binaryPath)\n return null\n\n const packageDir = resolvePackageDir(spec.packageName, binaryPath, home)\n if (!packageDir)\n return null\n\n const version = readPackageVersion(packageDir, spec.packageName)\n if (!version)\n return null\n\n return { packageName: spec.packageName, version, packageDir, binaryPath }\n}\n\nfunction findOnPath(binName: string, pathVar: string, platform: NodeJS.Platform): string | null {\n const candidates = platform === 'win32' ? [binName, `${binName}.cmd`, `${binName}.exe`] : [binName]\n for (const dir of pathVar.split(delimiter)) {\n if (!dir)\n continue\n for (const name of candidates) {\n const full = join(dir, name)\n try {\n if (statSync(full).isFile())\n return full\n }\n catch {\n // not here\n }\n }\n }\n return null\n}\n\n/**\n * From the bin to the package root. Two strategies:\n * 1. Follow the symlink (`<prefix>/bin/zidane-gui` → `…/node_modules/\n * zidane-gui/bin/zidane-gui.mjs`) and climb until a `package.json`\n * carries the expected name. Covers npm, pnpm, yarn and bun.\n * 2. Volta: the bin is a shim whose realpath is `volta-shim`, so nothing to\n * climb. Its global packages live at a fixed layout under `$VOLTA_HOME`\n * (the same one the GUI installer maps staging paths to).\n */\nfunction resolvePackageDir(packageName: string, binaryPath: string, home: string): string | null {\n let resolved = binaryPath\n try {\n resolved = realpathSync(binaryPath)\n }\n catch {\n // dangling link — fall through to the Volta layout check\n }\n\n const climbed = climbToPackage(dirname(resolved), packageName)\n if (climbed)\n return climbed\n\n const voltaHome = voltaHomeFor(binaryPath, resolved, home)\n if (voltaHome) {\n const dir = join(voltaHome, 'tools', 'image', 'packages', packageName, 'lib', 'node_modules', packageName)\n if (existsSync(join(dir, 'package.json')))\n return dir\n }\n return null\n}\n\nfunction climbToPackage(start: string, packageName: string): string | null {\n let dir = start\n for (let depth = 0; depth < 6; depth++) {\n if (readPackageVersion(dir, packageName))\n return dir\n const parent = dirname(dir)\n if (parent === dir)\n break\n dir = parent\n }\n return null\n}\n\nfunction voltaHomeFor(binaryPath: string, resolved: string, home: string): string | null {\n const marker = `${sep}.volta${sep}`\n for (const p of [binaryPath, resolved]) {\n const idx = p.indexOf(marker)\n if (idx !== -1)\n return p.slice(0, idx + marker.length - 1)\n }\n const fallback = join(home, '.volta')\n return existsSync(fallback) ? fallback : null\n}\n\nfunction readPackageVersion(dir: string, packageName: string): string | null {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name?: unknown, version?: unknown }\n if (pkg.name !== packageName || typeof pkg.version !== 'string')\n return null\n return pkg.version\n }\n catch {\n return null\n }\n}\n","// ---------------------------------------------------------------------------\n// \"A newer version is on disk — restart to use it.\"\n//\n// Two things live here:\n//\n// 1. The APPLIED MARKER — `<cacheDir>/update-applied.json`, written by every\n// path that installs a release (`zidane upgrade`, the GUI's in-app\n// update). It is a wake-up call, not the truth: running surfaces watch\n// the file and re-probe the disk when it changes.\n//\n// 2. The INSTALLED-VERSION WATCHER — compares the version the running\n// process was built with against the version its package has on disk\n// (resolved from the bin on PATH, the same way `zidane upgrade` finds\n// companions). `restartRequired` when disk > running. Re-probes on the\n// marker, on a slow poll (an upgrade run from another machine's shell\n// over the same home dir writes no local marker), and on demand\n// (`check()` — the GUI calls it on window focus).\n//\n// Renderer-agnostic: Node `fs` only, no React except the hook at the bottom.\n// ---------------------------------------------------------------------------\n\nimport type { FSWatcher } from 'node:fs'\nimport type { CompanionSpec, FindCompanionOptions, InstalledCompanion } from './auto-update-companion'\nimport { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { useEffect, useState } from 'react'\nimport { compareSemver } from './auto-update'\nimport { findInstalledCompanion } from './auto-update-companion'\n\n/** File name of the update-applied marker inside the cache directory. */\nexport const UPDATE_APPLIED_MARKER = 'update-applied.json'\n\n/** Record written after an install lands, so running processes can notice it before the next poll. */\nexport interface UpdateAppliedMarker {\n /** ms since epoch. */\n at: number\n /** Every package the run installed, with the version now on disk. */\n packages: Array<{ packageName: string, version: string }>\n}\n\n/**\n * Returns the path of the update-applied marker file.\n *\n * @param cacheDir - Directory that holds the marker file.\n */\nexport function updateAppliedMarkerPath(cacheDir: string): string {\n return join(cacheDir, UPDATE_APPLIED_MARKER)\n}\n\n/**\n * Record that an install just landed. Best-effort: a failure to write the\n * marker must never fail the upgrade that succeeded — the poll catches up.\n *\n * @param cacheDir - Directory that holds the marker file. Created when missing.\n * @param marker - The record to write.\n */\nexport function writeUpdateAppliedMarker(cacheDir: string, marker: UpdateAppliedMarker): boolean {\n try {\n mkdirSync(cacheDir, { recursive: true })\n writeFileSync(updateAppliedMarkerPath(cacheDir), `${JSON.stringify(marker, null, 2)}\\n`)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Reads the update-applied marker.\n * Returns `null` when the file is missing, unreadable or malformed. Package entries with a non-string name or version are dropped.\n *\n * @param cacheDir - Directory that holds the marker file.\n */\nexport function readUpdateAppliedMarker(cacheDir: string): UpdateAppliedMarker | null {\n try {\n const raw = JSON.parse(readFileSync(updateAppliedMarkerPath(cacheDir), 'utf8')) as unknown\n if (!raw || typeof raw !== 'object')\n return null\n const { at, packages } = raw as Partial<UpdateAppliedMarker>\n if (typeof at !== 'number' || !Array.isArray(packages))\n return null\n return {\n at,\n packages: packages.filter(p => p && typeof p.packageName === 'string' && typeof p.version === 'string'),\n }\n }\n catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Installed-version watcher\n// ---------------------------------------------------------------------------\n\n/** Snapshot of the running version against the version installed on disk. */\nexport interface InstalledVersionState {\n /** Version the running process was built with. */\n running: string\n /** Version found on disk for the package; `null` when the probe found nothing (dev checkout, PATH without the bin). */\n installed: string | null\n /** `installed` is strictly newer than `running` — the process is stale. */\n restartRequired: boolean\n /** When the disk was last probed (ms since epoch). */\n checkedAt: number\n}\n\n/** Options for {@link watchInstalledVersion}. */\nexport interface WatchInstalledVersionOptions {\n /** The running package, e.g. `{ packageName: 'zidane-tui', binName: 'zidane' }`. */\n spec: CompanionSpec\n /** Inlined at build time. An empty string disables the watcher (dev runs). */\n currentVersion: string\n /** Where the applied marker lives. Omit to rely on the poll alone. */\n cacheDir?: string\n /** Poll interval. Defaults to 5 min; `0` disables the poll. */\n pollMs?: number\n /** Debounce for marker events (installs touch the file more than once). Defaults to 500 ms. */\n debounceMs?: number\n /** Called with the new state when the installed version or restart flag changes. */\n onChange: (state: InstalledVersionState) => void\n /** Injected for tests. */\n probe?: (spec: CompanionSpec, opts?: FindCompanionOptions) => InstalledCompanion | null\n /** Injected for tests. */\n now?: () => number\n}\n\n/** Handle returned by {@link watchInstalledVersion}. */\nexport interface InstalledVersionWatcher {\n /** Re-probe now. Returns the fresh state (also delivered to `onChange` when it differs). */\n check: () => InstalledVersionState\n /** Last delivered state. */\n current: () => InstalledVersionState\n /** Stops the watcher. Later checks return the last state and `onChange` no longer fires. */\n close: () => void\n}\n\n/**\n * Compute the state once — the pure heart of the watcher, exported so a host\n * without a lifecycle (a one-shot IPC handler) can answer \"is a restart\n * pending?\" without holding a watcher.\n *\n * @param spec - The running package and its binary name.\n * @param currentVersion - Version the process was built with. An empty string yields no installed version.\n * @param probe - Function that finds the installed package on disk. Defaults to `findInstalledCompanion`.\n * @param now - Clock used for `checkedAt`. Defaults to `Date.now`.\n */\nexport function probeInstalledVersion(\n spec: CompanionSpec,\n currentVersion: string,\n probe: WatchInstalledVersionOptions['probe'] = findInstalledCompanion,\n now: () => number = Date.now,\n): InstalledVersionState {\n const installed = currentVersion ? probe(spec)?.version ?? null : null\n return {\n running: currentVersion,\n installed,\n restartRequired: installed !== null && compareSemver(installed, currentVersion) > 0,\n checkedAt: now(),\n }\n}\n\n/**\n * Starts watching the disk for a newer install of the running package.\n * The watcher probes once on creation, then on every poll and on changes to the applied marker. It calls `onChange` only when the installed version or the restart flag changes. It stays idle when `currentVersion` is empty.\n *\n * @param options - Package spec, running version, callbacks and timing settings.\n */\nexport function watchInstalledVersion(options: WatchInstalledVersionOptions): InstalledVersionWatcher {\n const probe = options.probe ?? findInstalledCompanion\n const now = options.now ?? Date.now\n const enabled = options.currentVersion.length > 0\n let state = probeInstalledVersion(options.spec, options.currentVersion, probe, now)\n let closed = false\n\n const deliver = (next: InstalledVersionState) => {\n const changed = next.installed !== state.installed || next.restartRequired !== state.restartRequired\n state = next\n if (changed && !closed)\n options.onChange(next)\n }\n const check = (): InstalledVersionState => {\n if (!enabled || closed)\n return state\n deliver(probeInstalledVersion(options.spec, options.currentVersion, probe, now))\n return state\n }\n\n let watcher: FSWatcher | null = null\n let debounce: ReturnType<typeof setTimeout> | null = null\n let poll: ReturnType<typeof setInterval> | null = null\n\n if (enabled) {\n if (options.cacheDir) {\n // Watch the DIRECTORY: the marker may not exist yet, and installers\n // replace files (rename) rather than rewrite them in place.\n try {\n mkdirSync(options.cacheDir, { recursive: true })\n watcher = watch(options.cacheDir, (_event, filename) => {\n if (filename && filename.toString() !== UPDATE_APPLIED_MARKER)\n return\n if (debounce)\n clearTimeout(debounce)\n debounce = setTimeout(() => {\n debounce = null\n check()\n }, options.debounceMs ?? 500)\n })\n watcher.on('error', () => {\n // A vanished cache dir or an exhausted inotify budget must not\n // take the host down; the poll still covers us.\n watcher?.close()\n watcher = null\n })\n }\n catch {\n watcher = null\n }\n }\n const pollMs = options.pollMs ?? 5 * 60_000\n if (pollMs > 0) {\n poll = setInterval(check, pollMs)\n // Never keep a process alive for this.\n if (typeof poll === 'object' && 'unref' in poll)\n poll.unref()\n }\n }\n\n return {\n check,\n current: () => state,\n close: () => {\n closed = true\n watcher?.close()\n if (debounce)\n clearTimeout(debounce)\n if (poll)\n clearInterval(poll)\n },\n }\n}\n\n/**\n * Quick existence check so hosts can decide whether to read the marker at all.\n *\n * @param cacheDir - Directory that holds the marker file.\n */\nexport function hasUpdateAppliedMarker(cacheDir: string): boolean {\n return existsSync(updateAppliedMarkerPath(cacheDir))\n}\n\n// ---------------------------------------------------------------------------\n// React hook — the TUI's consumer. The GUI runs the watcher in its main\n// process and forwards the state over IPC instead.\n// ---------------------------------------------------------------------------\n\n/** Options for {@link useRestartRequired}. */\nexport interface UseRestartRequiredOptions {\n /** The running package. `null` disables the hook. */\n spec: CompanionSpec | null\n /** Version the process was built with. */\n currentVersion: string\n /** Directory that holds the applied marker. Omit to rely on polling alone. */\n cacheDir?: string\n /** Master switch — mirrors `Settings.checkForUpdates`. */\n enabled: boolean\n /** Poll interval in milliseconds. Defaults to 5 minutes. `0` disables polling. */\n pollMs?: number\n}\n\n/**\n * `null` until the first probe (or when disabled / no spec), then the live\n * state. Flipping `enabled` off tears the watcher down and clears the state\n * so no stale \"restart\" chip lingers.\n *\n * @param options - Package spec, running version, cache directory, poll interval and the master `enabled` switch.\n */\nexport function useRestartRequired(options: UseRestartRequiredOptions): InstalledVersionState | null {\n const [state, setState] = useState<InstalledVersionState | null>(null)\n const { spec, currentVersion, cacheDir, enabled, pollMs } = options\n const packageName = spec?.packageName\n const binName = spec?.binName\n\n useEffect(() => {\n if (!enabled || !packageName || !currentVersion) {\n setState(null)\n return\n }\n const watcher = watchInstalledVersion({\n spec: binName ? { packageName, binName } : { packageName },\n currentVersion,\n cacheDir,\n pollMs,\n onChange: setState,\n })\n setState(watcher.current())\n return () => watcher.close()\n }, [enabled, packageName, binName, currentVersion, cacheDir, pollMs])\n\n return state\n}\n","// ---------------------------------------------------------------------------\n// `useUpdateCheck` — renderer-agnostic React hook driving the auto-update\n// footer chip + any banner a host wants to wire.\n//\n// Lives in `zidane/chat` (not `zidane/tui`) so a GUI shell can consume the\n// same status. Pure React + the renderer-agnostic core in\n// `./auto-update`; no OpenTUI imports.\n// ---------------------------------------------------------------------------\n\nimport type { UpdateStatus } from './auto-update'\nimport type { Hint } from './hints'\nimport { useEffect, useRef, useState } from 'react'\nimport { checkForUpdate } from './auto-update'\n\n/** Options for {@link useUpdateCheck}. */\nexport interface UseUpdateCheckOptions {\n /** npm package name. e.g. `'zidane-tui'`. */\n packageName: string\n /** Inlined at build time. Skips the check if empty/falsy. */\n currentVersion: string\n /**\n * Master enable flag — usually `Settings.checkForUpdates`. When `false`\n * the hook short-circuits without scheduling any work AND clears the\n * last-known status so a stale chip doesn't linger after the user\n * disabled the feature.\n */\n enabled: boolean\n /** Directory under which the on-disk cache lives. Usually `paths.userDir`. */\n cacheDir?: string\n /** Registry base URL. Defaults to `https://registry.npmjs.org`. */\n registry?: string\n /** dist-tag to read. Defaults to `'latest'`. */\n channel?: string\n /**\n * Cache lifetime for the read-through. **Defaults to `0`** (always\n * fetch fresh) — the TUI chip is the user's source of truth for \"what\n * version of zidane is on npm right now\"; we'd rather pay one HTTPS\n * call per session than tell them yesterday's news for up to 24 hours.\n *\n * The on-disk cache is still WRITTEN with each fresh result (so\n * `zidane upgrade` and other tooling reads benefit) and still READ to\n * feed the registry's ETag (so the cold path can short-circuit on 304\n * — meaningful bandwidth win when nothing changed). Pass a positive\n * value (e.g. `3_600_000` for 1 h) to enable read-through caching for\n * a GUI shell that mounts the hook in many places.\n */\n cacheTtlMs?: number\n /**\n * Wall-clock delay before the first check fires. Defaults to 1 500 ms so\n * we never compete with the boot path's first paint. The check itself\n * is async, so this only affects WHEN the chip first appears — never\n * blocks anything else.\n */\n delayMs?: number\n /** Injected for tests. */\n now?: () => number\n /** Injected for tests. */\n fetcher?: typeof fetch\n}\n\n/**\n * Returns the current update status. The check fires:\n * - Once per mount (every TUI launch).\n * - Every time `enabled` flips from `false` → `true` (user re-enabling\n * the setting expects an immediate refresh).\n *\n * The check is async and deferred by `delayMs` so it never competes with\n * first paint. Result is `null` until the first check resolves; flipping\n * `enabled` to `false` resets to `null` so any chip rendered against\n * `hasUpdate` disappears immediately.\n *\n * The hook owns its own AbortController so an unmount cancels any in-flight\n * registry request cleanly.\n *\n * @param options - Package, current version, enable flag and cache settings for the check.\n */\nexport function useUpdateCheck(options: UseUpdateCheckOptions): UpdateStatus | null {\n const [status, setStatus] = useState<UpdateStatus | null>(null)\n // Ref-mirror so the effect doesn't re-run on every keystroke when the\n // host wraps the result into derived hint state.\n const optsRef = useRef(options)\n optsRef.current = options\n\n useEffect(() => {\n if (!options.enabled || !options.currentVersion) {\n // Disabling the setting must hide a chip that was already painted\n // — otherwise the user sees a stale `↑ vX.Y.Z` linger until the\n // next launch. React bails on identical-state updates so the\n // initial-mount case (status already null) costs nothing.\n setStatus(null)\n return\n }\n const controller = new AbortController()\n const timer = setTimeout(() => {\n void checkForUpdate({\n packageName: optsRef.current.packageName,\n currentVersion: optsRef.current.currentVersion,\n cacheDir: optsRef.current.cacheDir,\n registry: optsRef.current.registry,\n channel: optsRef.current.channel,\n // Default to TTL=0 (always fetch fresh). See `cacheTtlMs` docs.\n cacheTtlMs: optsRef.current.cacheTtlMs ?? 0,\n signal: controller.signal,\n now: optsRef.current.now,\n fetcher: optsRef.current.fetcher,\n }).then((s) => {\n if (!controller.signal.aborted)\n setStatus(s)\n })\n }, options.delayMs ?? 1500)\n return () => {\n clearTimeout(timer)\n controller.abort()\n }\n // Re-run when the master enable / package / version flip. Other\n // option changes stay inside `optsRef.current`; re-running on every\n // option mutation would cause render storms when the host wraps\n // the result into derived hint state.\n }, [options.enabled, options.packageName, options.currentVersion])\n\n return status\n}\n\n// ---------------------------------------------------------------------------\n// Footer chip helper — pure function so any renderer (TUI Footer, future\n// GUI status bar) can compose against the same `Hint`. The chip is\n// suppressed when there's no update; callers spread the result into their\n// hint array unconditionally.\n//\n// Color contract: the chip rides whatever accent the host passes (typically\n// `theme.colors.accent`). Renderers that want a \"fresh check\" badge can\n// inspect `status.source === 'fresh'` themselves — this helper sticks to\n// the one-line discoverability surface.\n// ---------------------------------------------------------------------------\n\n/** Display options for the update and restart hints. */\nexport interface BuildUpdateHintOptions {\n /** Foreground color for the `↑` glyph + version label. */\n color: string\n /**\n * When true (default), the chip renders the literal version (`↑ v5.6.0`).\n * When false, just the glyph + `update` label — useful when the row is\n * tight and the version would push higher-priority chips off the right.\n */\n showVersion?: boolean\n /** Override the leading glyph. Defaults to `'↑'`. */\n glyph?: string\n}\n\n/**\n * Builds the `↑ vX.Y.Z` hint for an available update.\n * Returns `null` when there is no newer version.\n *\n * @param status - Result of an update check, or `null` before the first check.\n * @param options - Color and label settings for the hint.\n */\nexport function buildUpdateHint(status: UpdateStatus | null, options: BuildUpdateHintOptions): Hint | null {\n if (!status?.hasUpdate || !status.latest)\n return null\n const label = options.showVersion === false\n ? 'update'\n : `v${stripVPrefix(status.latest)}`\n return {\n key: options.glyph ?? '↑',\n keyColor: options.color,\n label,\n labelColor: options.color,\n }\n}\n\n/**\n * `↻ v7.0.31` — a newer build is on disk than the one running (this\n * process is stale; see `auto-update-restart.ts`). Takes precedence over\n * the `↑` chip: once the release is installed, \"available\" is old news and\n * \"restart\" is the one action left. Same color contract as `buildUpdateHint`.\n *\n * @param state - Installed-version state, or `null` when unknown.\n * @param options - Color and label settings for the hint.\n */\nexport function buildRestartHint(\n state: { restartRequired: boolean, installed: string | null } | null,\n options: BuildUpdateHintOptions,\n): Hint | null {\n if (!state?.restartRequired || !state.installed)\n return null\n return {\n key: options.glyph ?? '↻',\n keyColor: options.color,\n label: options.showVersion === false ? 'restart' : `v${stripVPrefix(state.installed)}`,\n labelColor: options.color,\n }\n}\n\nfunction stripVPrefix(v: string): string {\n return v.startsWith('v') ? v.slice(1) : v\n}\n","// ---------------------------------------------------------------------------\n// Boot-time wall-clock profiler.\n//\n// Sprinkle `bootTick('<label>')` at strategic points along the startup\n// chain (cli.ts → runTui → registerTreeSitterParsers → resolveConfig →\n// createCliRenderer → React mount → discovery effects → session\n// resume). When `ZIDANE_BOOT_PROFILE=1` is set, each call emits one\n// line on stderr with the delta since the previous tick and the total\n// since module load:\n//\n// $ ZIDANE_BOOT_PROFILE=1 zidane\n// [boot] +0.0ms / total 0.0ms / cli:enter\n// [boot] +12.3ms / total 12.3ms / cli:after-setupBundledTreeSitterAssets\n// [boot] +47.8ms / total 60.1ms / runTui:after-registerTreeSitterParsers\n// ...\n//\n// Renderer-agnostic on purpose — lives in `zidane/chat` so a future GUI\n// shell can wire it into its own boot path with the same env var and\n// the same output format. Writes to stderr, which OpenTUI's renderer\n// leaves alone (stdout is the frame buffer surface).\n// ---------------------------------------------------------------------------\n\nconst enabled = !!process.env.ZIDANE_BOOT_PROFILE\n/**\n * High-resolution origin for the running profile. Captured at module\n * load time so the first {@link bootTick} call inside the same process\n * reflects \"time since the boot profiler was first reached\", which for\n * the standard `cli.ts → runTui` path is effectively \"time since the\n * TUI binary started executing user code\".\n */\nconst start = performance.now()\nlet last = start\n\n/**\n * Record a checkpoint. No-op unless `ZIDANE_BOOT_PROFILE` is truthy in\n * the environment.\n *\n * The leading delta is the time since the PREVIOUS tick (so a long\n * delta highlights the immediately-preceding work); the trailing total\n * is the time since the profiler started. Both round to one decimal of\n * a millisecond.\n *\n * @param label - Name of the boot step that just finished.\n */\nexport function bootTick(label: string): void {\n if (!enabled)\n return\n const now = performance.now()\n const delta = now - last\n const total = now - start\n last = now\n // Delta and total are padded to 7 characters so the columns stay aligned\n // until a value widens past that. The label is not padded.\n process.stderr.write(\n `[boot] +${delta.toFixed(1).padStart(7)}ms / total ${total.toFixed(1).padStart(7)}ms / ${label}\\n`,\n )\n}\n\n/**\n * Returns `true` when `ZIDANE_BOOT_PROFILE` is set. Useful for guarding\n * heavier instrumentation (e.g. wrapping a costly call in a span) that\n * you don't want paying its own cost in the default path.\n */\nexport function bootProfileEnabled(): boolean {\n return enabled\n}\n","/**\n * One-shot IMPORT from an existing Claude Code setup — the materializing\n * counterpart to `foreign-configs.ts`'s read-only discovery.\n *\n * Four pieces, each independently skippable:\n *\n * - Sessions — `~/.claude/projects/<slug>/*.jsonl` transcripts convert\n * to zidane `SessionData` and land in the session store. Idempotent:\n * the zidane session REUSES the Claude session UUID, so a re-import\n * skips everything the store already has.\n * - MCP servers — Claude's `.mcp.json` + `~/.claude.json` entries are\n * COPIED into zidane-owned `mcps.json` files (via `upsertMcpServer`).\n * Names already present in any native zidane/agents config are\n * skipped — imports never shadow what the user configured natively.\n * - Skills — `.claude/skills/<name>` directories are copied into the\n * corresponding `.{prefix}/skills`, skipping names that already\n * exist in any native scan path.\n * - Commands and subagents — `.claude/commands/**.md` and\n * `.claude/agents/**.md` are copied into `.{prefix}/commands` and\n * `.{prefix}/agents`, keeping their folders and skipping names a native\n * root already defines. Copied, they load without the\n * `importForeignConfigs` toggle that otherwise reads them in place.\n *\n * Claude's `settings.json` (permissions / hooks / plugins) has no zidane\n * equivalent and `CLAUDE.md` is already read natively (`agents-md.ts`),\n * so neither is imported — the UI reports that instead of pretending.\n */\n\nimport type { MarkdownDefFile } from '../markdown-defs/types'\nimport type { SessionData, SessionStore } from '../session'\nimport type { McpServerConfig, SessionContentBlock, SessionTurn, ToolResultContent, TurnUsage } from '../types'\nimport type { OnboardingImportCategory, OnboardingImportPreview } from './onboarding/types'\nimport { Buffer } from 'node:buffer'\nimport { closeSync, cpSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join, relative, resolve } from 'node:path'\nimport { markdownDefRoots } from '../markdown-defs/roots'\nimport { listMarkdownDefFiles } from '../markdown-defs/scan'\nimport { parseClaudeState } from './foreign-configs'\nimport { defaultMcpsConfigPaths, parseJsonTolerant, parseMcpsFile } from './mcps-discovery'\nimport { mcpsWritePath, upsertMcpServer } from './mcps-write'\nimport { findGitRoot, projectRootOf } from './project-root'\nimport { projectUserPaths } from './project-user-paths'\n\n/** Options for locating and importing Claude Code data. */\nexport interface ClaudeImportOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** Storage prefix for write targets. Default: `'.zidane'`. */\n prefix?: string\n /** Import sessions from every Claude project dir, not just the current project's. */\n allProjects?: boolean\n}\n\nfunction roots(opts: ClaudeImportOptions): { projectRoot: string, home: string } {\n const cwd = opts.cwd ?? process.cwd()\n return {\n projectRoot: findGitRoot(cwd) ?? cwd,\n home: opts.home ?? homedir(),\n }\n}\n\n/**\n * Whether `~/.claude` exists. The setup wizard uses it to detect Claude Code.\n *\n * @param opts - Discovery options. Only `home` is used.\n */\nexport function hasClaudeCodeSetup(opts: ClaudeImportOptions = {}): boolean {\n return existsSync(resolve(roots(opts).home, '.claude'))\n}\n\n// ---------------------------------------------------------------------------\n// Sessions\n// ---------------------------------------------------------------------------\n\n/**\n * Claude Code names each `~/.claude/projects/` subdirectory after the\n * project path with every non-alphanumeric character flattened to `-`\n * (`/Users/me/.claude/wt` → `-Users-me--claude-wt`). Lossy, so matching\n * goes slug → slug rather than trying to reverse it.\n */\nexport function claudeProjectSlug(path: string): string {\n return path.replace(/[^a-z0-9]/gi, '-')\n}\n\nexport interface ClaudeSessionFile {\n path: string\n /** The `~/.claude/projects/<slug>` directory the file came from. */\n projectDir: string\n}\n\n/**\n * Session transcripts for the current project, or every project when\n * `allProjects` is set.\n *\n * Claude Code slugs by LAUNCH directory, so a session started in a\n * monorepo subdir (`repo/packages/foo`) lands in a different dir than\n * one started at the repo root. Scoped listing therefore accepts both\n * the exact slug of the resolved git root (realpath variants included)\n * AND any dir whose slug extends it — but because slugs are lossy\n * (`repo/foo` and a sibling `repo-foo` collide), every file in those dirs\n * is confirmed against the transcript's recorded `cwd` (resolved to its\n * git root), mirroring how the converted session's `projectRoot` is\n * derived. A transcript with no `cwd` passes only in the exact-slug dir.\n */\nexport function listClaudeSessionFiles(opts: ClaudeImportOptions = {}): ClaudeSessionFile[] {\n const out: ClaudeSessionFile[] = []\n walkClaudeSessionFiles(opts, opts.allProjects\n ? file => out.push(file)\n : (file, scoped) => {\n if (scoped())\n out.push(file)\n })\n return out\n}\n\n/**\n * Detection-preview counts — scoped and all-projects figures from ONE\n * directory walk (the naive two-listing approach reads every project dir\n * twice). The scoped classification (with its per-file cwd peek for\n * candidate dirs) runs at most once per file via the lazy `scoped()`\n * thunk.\n *\n * @param opts - Discovery options. Only `cwd`, `home` and the project scope are used.\n */\nexport function countClaudeSessionFiles(opts: ClaudeImportOptions = {}): { project: number, all: number } {\n let project = 0\n let all = 0\n walkClaudeSessionFiles({ ...opts, allProjects: false }, (_file, scoped) => {\n all++\n if (scoped())\n project++\n }, /* visitAllDirs */ true)\n return { project, all }\n}\n\n/**\n * Shared walk under `~/.claude/projects`. `visit` receives every\n * transcript of every CANDIDATE dir (scoped mode) or every dir\n * (`allProjects` / `visitAllDirs`), plus a lazy `scoped()` thunk that\n * answers \"does this file belong to the current project?\" — lazy because\n * the answer can cost a `transcriptProjectRoot` peek (up to the whole\n * file), which pure all-projects listings never need.\n */\nfunction walkClaudeSessionFiles(\n opts: ClaudeImportOptions,\n visit: (file: ClaudeSessionFile, scoped: () => boolean) => void,\n visitAllDirs = false,\n): void {\n const { projectRoot, home } = roots(opts)\n const projectsDir = resolve(home, '.claude/projects')\n if (!existsSync(projectsDir))\n return\n const slugs = [claudeProjectSlug(projectRoot), claudeProjectSlug(realpathOr(projectRoot))]\n const exact = new Set(slugs)\n const prefixes = [...new Set(slugs.map(s => `${s}-`))]\n const wantedRoots = new Set([projectRoot, realpathOr(projectRoot)])\n for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {\n if (!entry.isDirectory())\n continue\n const exactMatch = exact.has(entry.name)\n const candidate = exactMatch || prefixes.some(p => entry.name.startsWith(p))\n if (!opts.allProjects && !visitAllDirs && !candidate)\n continue\n const projectDir = join(projectsDir, entry.name)\n for (const file of readdirSync(projectDir)) {\n if (!file.endsWith('.jsonl'))\n continue\n const path = join(projectDir, file)\n // Slugs are lossy even on exact match (`/a/b-c` and `/a/b/c` both\n // slug to `-a-b-c`), so EVERY scoped file is confirmed against its\n // recorded cwd. A transcript with no cwd at all (torn/empty file)\n // gets the benefit of the doubt only in the exact-slug dir —\n // prefix matches must prove their subdir claim.\n const scoped = (): boolean => {\n if (!candidate)\n return false\n const root = transcriptProjectRoot(path)\n return root === null ? exactMatch : wantedRoots.has(root) || wantedRoots.has(realpathOr(root))\n }\n visit({ path, projectDir }, scoped)\n }\n }\n}\n\n/**\n * Peek a transcript's project root: scan the first chunk for the first\n * record carrying a `cwd` and resolve it the same way\n * `convertClaudeSession` does. When the chunk was truncated (giant first\n * records — e.g. pasted base64 images) and held no cwd, fall back to the\n * full file: the import reads it in full anyway, so correctness beats the\n * shortcut. Null when no cwd exists at all (empty/torn file) or the file\n * is unreadable.\n */\nfunction transcriptProjectRoot(path: string): string | null {\n let text: string\n let truncated = false\n try {\n const fd = openSync(path, 'r')\n try {\n const buf = Buffer.alloc(256 * 1024)\n const n = readSync(fd, buf, 0, buf.length, 0)\n text = buf.subarray(0, n).toString('utf8')\n truncated = n === buf.length\n }\n finally {\n closeSync(fd)\n }\n }\n catch {\n return null\n }\n const fromChunk = scanForCwd(text)\n if (fromChunk !== null || !truncated)\n return fromChunk\n try {\n return scanForCwd(readFileSync(path, 'utf8'))\n }\n catch {\n return null\n }\n}\n\nfunction scanForCwd(text: string): string | null {\n for (const line of text.split('\\n')) {\n if (!line.trim())\n continue\n try {\n const record = JSON.parse(line) as { cwd?: unknown }\n if (typeof record.cwd === 'string')\n return findGitRoot(record.cwd) ?? record.cwd\n }\n catch {\n continue // torn chunk boundary / artifact line\n }\n }\n return null\n}\n\nfunction realpathOr(path: string): string {\n try {\n return realpathSync(path)\n }\n catch {\n return path\n }\n}\n\nexport interface ConvertedClaudeSession {\n session: SessionData\n claudeSessionId: string\n}\n\ninterface ClaudeRecord {\n type?: string\n uuid?: string\n timestamp?: string\n isSidechain?: boolean\n isMeta?: boolean\n cwd?: string\n sessionId?: string\n customTitle?: string\n summary?: string\n message?: {\n id?: string\n role?: string\n content?: unknown\n usage?: {\n input_tokens?: number\n output_tokens?: number\n cache_creation_input_tokens?: number\n cache_read_input_tokens?: number\n }\n model?: string\n }\n}\n\n/**\n * Convert one Claude Code session transcript (JSONL text) to a zidane\n * `SessionData`. Returns null when no conversational turns survive the\n * filter (hook noise, sidechains, queue bookkeeping).\n *\n * Kept-vs-dropped:\n * - `user` / `assistant` records with a `message` → turns. Consecutive\n * assistant records sharing a `message.id` merge into ONE turn (Claude\n * writes text and tool_use as separate records of the same API message).\n * - Consecutive tool_result-only user records merge into ONE user turn:\n * Claude writes one record per result of a parallel tool batch, but\n * zidane persists a batch as a single user turn and\n * `ensureToolResultPairing` enforces strict adjacency — split turns\n * would be \"repaired\" into orphan markers + error placeholders on resume.\n * - `custom-title` / `summary` records → `metadata.title`.\n * - Sidechains (subagent transcripts), `isMeta` records, `attachment`,\n * `system`, `queue-operation`, `last-prompt` → dropped.\n * - Thinking blocks → dropped (provider-bound signatures, not replayable).\n */\nexport function convertClaudeSession(jsonl: string, opts: { fallbackId?: string } = {}): ConvertedClaudeSession | null {\n const turns: SessionTurn[] = []\n let title: string | undefined\n let claudeSessionId: string | undefined\n let cwd: string | undefined\n let lastAssistantMessageId: string | undefined\n\n for (const line of jsonl.split('\\n')) {\n if (!line.trim())\n continue\n let record: ClaudeRecord\n try {\n record = JSON.parse(line) as ClaudeRecord\n }\n catch {\n continue // torn tail line, editor artifact — skip, don't fail the file\n }\n if (record.type === 'custom-title' && typeof record.customTitle === 'string') {\n title = record.customTitle\n continue\n }\n if (record.type === 'summary' && typeof record.summary === 'string') {\n title ??= record.summary\n continue\n }\n if (record.type !== 'user' && record.type !== 'assistant')\n continue\n if (record.isSidechain || record.isMeta || !record.message)\n continue\n claudeSessionId ??= record.sessionId\n cwd ??= record.cwd\n // Malformed timestamps parse to NaN — don't let that poison\n // createdAt/updatedAt (sort keys in the sessions list).\n const parsedTs = record.timestamp ? Date.parse(record.timestamp) : Number.NaN\n const createdAt = Number.isNaN(parsedTs) ? Date.now() : parsedTs\n\n if (record.type === 'assistant') {\n const content = claudeBlocksToContent(record.message.content)\n if (content.length === 0)\n continue\n const messageId = record.message.id\n const previous = turns[turns.length - 1]\n // Merge continuation records of the same API message into the open\n // turn. Claude repeats the SAME usage object on every record of a\n // message, so the latest snapshot replaces — summing would N-count\n // the message's tokens.\n if (messageId && messageId === lastAssistantMessageId && previous?.role === 'assistant') {\n previous.content.push(...content)\n const usage = claudeUsage(record.message.usage, record.message.model)\n if (usage)\n previous.usage = usage\n continue\n }\n lastAssistantMessageId = messageId\n const usage = claudeUsage(record.message.usage, record.message.model)\n turns.push({\n id: record.uuid ?? crypto.randomUUID(),\n role: 'assistant',\n content,\n createdAt,\n ...(usage ? { usage } : {}),\n })\n continue\n }\n\n lastAssistantMessageId = undefined\n const content = claudeBlocksToContent(record.message.content)\n if (content.length === 0)\n continue\n // One record per tool_result of a parallel batch → one zidane user\n // turn — see the doc comment (ensureToolResultPairing adjacency).\n const previous = turns[turns.length - 1]\n if (previous?.role === 'user' && isToolResultOnly(content) && isToolResultOnly(previous.content)) {\n previous.content.push(...content)\n continue\n }\n turns.push({ id: record.uuid ?? crypto.randomUUID(), role: 'user', content, createdAt })\n }\n\n if (turns.length === 0)\n return null\n // Identity PREFERS the caller's `fallbackId` (the file basename —\n // Claude's own per-file uniqueness key) over the records' `sessionId`:\n // resume/fork lineages copy prior records verbatim, so a forked\n // transcript's early records still carry the ORIGINAL session's id —\n // keying on that would dedupe the fork against its ancestor and\n // silently never import the continuation.\n const id = opts.fallbackId ?? claudeSessionId ?? crypto.randomUUID()\n // Identity, like every session: a Claude transcript recorded in a linked\n // worktree belongs to the main checkout's project.\n const projectRoot = cwd ? projectRootOf(cwd) : undefined\n const session: SessionData = {\n id,\n ...(projectRoot ? { projectRoot } : {}),\n turns,\n runs: [],\n status: 'idle',\n metadata: {\n ...(title ? { title } : {}),\n importedFrom: 'claude-code',\n claudeSessionId: id,\n // Lineage breadcrumb: differs from `claudeSessionId` on forked /\n // resumed transcripts whose records carry the ancestor's id.\n ...(claudeSessionId && claudeSessionId !== id ? { claudeLineageId: claudeSessionId } : {}),\n },\n createdAt: turns[0]!.createdAt,\n updatedAt: turns[turns.length - 1]!.createdAt,\n }\n return { session, claudeSessionId: id }\n}\n\n/** Map Claude/Anthropic content blocks to zidane `SessionContentBlock`s. */\nfunction claudeBlocksToContent(content: unknown): SessionContentBlock[] {\n if (typeof content === 'string')\n return content.trim() ? [{ type: 'text', text: content }] : []\n if (!Array.isArray(content))\n return []\n const out: SessionContentBlock[] = []\n for (const raw of content) {\n if (!raw || typeof raw !== 'object')\n continue\n const block = raw as Record<string, unknown>\n switch (block.type) {\n case 'text':\n if (typeof block.text === 'string' && block.text.trim())\n out.push({ type: 'text', text: block.text })\n break\n case 'tool_use':\n if (typeof block.id === 'string' && typeof block.name === 'string') {\n const input = block.input && typeof block.input === 'object' && !Array.isArray(block.input)\n ? block.input as Record<string, unknown>\n : {}\n out.push({ type: 'tool_call', id: block.id, name: block.name, input })\n }\n break\n case 'tool_result':\n if (typeof block.tool_use_id === 'string') {\n out.push({\n type: 'tool_result',\n callId: block.tool_use_id,\n output: claudeToolResultOutput(block.content),\n ...(block.is_error === true ? { isError: true } : {}),\n })\n }\n break\n case 'image': {\n const image = claudeImage(block)\n if (image)\n out.push(image)\n break\n }\n // thinking / redacted_thinking: provider-bound signatures — dropped.\n }\n }\n return out\n}\n\nfunction claudeImage(block: Record<string, unknown>): SessionContentBlock | null {\n const source = block.source as Record<string, unknown> | undefined\n if (!source || source.type !== 'base64' || typeof source.media_type !== 'string' || typeof source.data !== 'string')\n return null\n return { type: 'image', mediaType: source.media_type, data: source.data }\n}\n\nfunction claudeToolResultOutput(content: unknown): string | ToolResultContent[] {\n if (typeof content === 'string')\n return content\n if (!Array.isArray(content))\n return ''\n const parts: ToolResultContent[] = []\n for (const raw of content) {\n if (!raw || typeof raw !== 'object')\n continue\n const block = raw as Record<string, unknown>\n if (block.type === 'text' && typeof block.text === 'string') {\n parts.push({ type: 'text', text: block.text })\n }\n else if (block.type === 'image') {\n const image = claudeImage(block)\n if (image?.type === 'image' && image.data)\n parts.push({ type: 'image', mediaType: image.mediaType, data: image.data })\n }\n }\n // Collapse the common all-text case to a plain string.\n if (parts.every(p => p.type === 'text'))\n return parts.map(p => (p as { text: string }).text).join('\\n')\n return parts\n}\n\ntype ClaudeUsage = NonNullable<NonNullable<ClaudeRecord['message']>['usage']>\n\nfunction claudeUsage(usage: ClaudeUsage | undefined, model?: string): TurnUsage | undefined {\n if (!usage || typeof usage !== 'object')\n return undefined\n return {\n input: usage.input_tokens ?? 0,\n output: usage.output_tokens ?? 0,\n ...(usage.cache_creation_input_tokens ? { cacheCreation: usage.cache_creation_input_tokens } : {}),\n ...(usage.cache_read_input_tokens ? { cacheRead: usage.cache_read_input_tokens } : {}),\n ...(model ? { modelId: model } : {}),\n }\n}\n\nfunction isToolResultOnly(blocks: readonly SessionContentBlock[]): boolean {\n return blocks.length > 0 && blocks.every(b => b.type === 'tool_result')\n}\n\n/** Result of importing Claude Code session transcripts. */\nexport interface ClaudeSessionImportResult {\n /** Sessions written to the store. */\n imported: number\n /** Already in the store (same Claude session UUID) — idempotent re-run. */\n skipped: number\n /** Nothing conversational in the file (hook noise, queue bookkeeping only). */\n empty: number\n /** Files that failed to parse/convert, with the reason. */\n failed: { path: string, error: string }[]\n}\n\n/** Import Claude Code session transcripts into `store`. */\nexport async function importClaudeSessions(store: SessionStore, opts: ClaudeImportOptions = {}): Promise<ClaudeSessionImportResult> {\n const result: ClaudeSessionImportResult = { imported: 0, skipped: 0, empty: 0, failed: [] }\n for (const file of listClaudeSessionFiles(opts)) {\n try {\n // Session id = the file's basename (Claude names each transcript\n // after ITS session UUID). `convertClaudeSession` prefers this over\n // the records' `sessionId`, which on forked/resumed lineages still\n // carries the ancestor's id — see the identity comment there.\n const fallbackId = basename(file.path, '.jsonl')\n // Idempotence: a store hit on the basename means this transcript\n // already imported — checked BEFORE reading/converting, so a\n // re-import doesn't re-parse the whole Claude history just to\n // count skips.\n if (await store.load(fallbackId)) {\n result.skipped++\n continue\n }\n const converted = convertClaudeSession(readFileSync(file.path, 'utf8'), { fallbackId })\n if (!converted) {\n result.empty++\n continue\n }\n await store.save(converted.session)\n result.imported++\n }\n catch (err) {\n result.failed.push({ path: file.path, error: err instanceof Error ? err.message : String(err) })\n }\n }\n return result\n}\n\n// ---------------------------------------------------------------------------\n// MCP servers\n// ---------------------------------------------------------------------------\n\n/** Result of copying one category of Claude Code definitions into zidane. */\nexport interface ClaudeCopyResult {\n /** Names that were written. */\n copied: string[]\n /** Names already present natively — never overwritten. */\n skipped: string[]\n /** Names that could not be written, with the reason. */\n failed: { name: string, error: string }[]\n /**\n * Names that appeared in MORE THAN ONE Claude source (e.g. `.mcp.json`\n * AND `~/.claude.json`) — the first occurrence (highest-precedence\n * source, matching Claude's own project → local → user order) was\n * processed, later ones dropped. Surfaced so a differently-configured\n * duplicate doesn't vanish from the accounting. MCP import only.\n */\n duplicates?: string[]\n}\n\n/**\n * Copy Claude Code's MCP servers into zidane-owned `mcps.json` files.\n * The repo-shared `.mcp.json` writes to `<root>/.{prefix}/mcps.json`;\n * BOTH `~/.claude.json` scopes write to `~/.{prefix}/mcps.json` —\n * Claude's \"local\" scope (the per-project map inside the user state\n * file) is private-by-design and routinely carries tokens in\n * `env`/`headers`, so materializing it into a committable project file\n * would leak credentials. User scope loses the per-project activation\n * (zidane has no user-private per-project config), which is the safer\n * direction to err. Collisions with names in ANY native config path are\n * skipped.\n */\nexport function importClaudeMcps(opts: ClaudeImportOptions = {}): ClaudeCopyResult {\n const { home } = roots(opts)\n const { native: nativeNames, sources, failed } = claudeMcpCandidates(opts)\n const result: ClaudeCopyResult = { copied: [], skipped: [], failed: [...failed], duplicates: [] }\n\n const seen = new Set<string>()\n for (const { servers, scope } of sources) {\n for (const server of servers) {\n if (seen.has(server.name)) {\n // Same name in a lower-precedence source — report, don't vanish:\n // its config may differ from the occurrence that won.\n result.duplicates!.push(server.name)\n continue\n }\n seen.add(server.name)\n if (nativeNames.has(server.name)) {\n result.skipped.push(server.name)\n continue\n }\n try {\n const target = mcpsWritePath(scope, { ...(opts.cwd ? { cwd: opts.cwd } : {}), home, ...(opts.prefix ? { prefix: opts.prefix } : {}) })\n const { action } = upsertMcpServer(target, server)\n if (action === 'updated') {\n // Unreachable while `nativeNames` raw-walks every candidate file\n // (the write targets included) — but if a concurrent writer or a\n // future shape drift ever slips past the guard, say so honestly\n // instead of counting an overwrite as a copy.\n result.failed.push({ name: server.name, error: `overwrote an existing entry in ${target}. Restore it from version control if unintended` })\n continue\n }\n result.copied.push(server.name)\n }\n catch (err) {\n result.failed.push({ name: server.name, error: err instanceof Error ? err.message : String(err) })\n }\n }\n }\n return result\n}\n\n/** Claude Code's MCP servers by source, and the names zidane already has. */\ninterface ClaudeMcpCandidates {\n /** Raw names in every native config file: the import never overwrites them. */\n native: Set<string>\n /** Claude's servers, highest-precedence source first, with the scope each one writes to. */\n sources: { servers: McpServerConfig[], scope: 'project' | 'user' }[]\n /** Claude files that could not be read. */\n failed: { name: string, error: string }[]\n}\n\n/** What {@link importClaudeMcps} and {@link previewClaudeImport} work from: nothing is written. */\nfunction claudeMcpCandidates(opts: ClaudeImportOptions): ClaudeMcpCandidates {\n const { projectRoot, home } = roots(opts)\n const failed: { name: string, error: string }[] = []\n\n // Collision guard — collected from the RAW file shape, NOT from\n // `parseMcpsFile`: the normalized view drops `enabled: false` entries\n // and can reject a file over one invalid entry, while `upsertMcpServer`\n // walks the raw JSON. Guarding on the normalized view would let the\n // import overwrite a native entry the user disabled (or one sitting\n // next to a malformed sibling). Raw names = exactly what upsert sees,\n // so an import can only ever ADD.\n const native = new Set<string>()\n for (const candidate of defaultMcpsConfigPaths({ ...(opts.cwd ? { cwd: opts.cwd } : {}), home, ...(opts.prefix ? { prefix: opts.prefix } : {}) })) {\n if (!existsSync(candidate.path))\n continue\n for (const name of rawMcpNames(readFileSync(candidate.path, 'utf8')))\n native.add(name)\n }\n\n const sources: { servers: McpServerConfig[], scope: 'project' | 'user' }[] = []\n const mcpJson = resolve(projectRoot, '.mcp.json')\n if (existsSync(mcpJson)) {\n try {\n sources.push({ servers: parseMcpsFile(readFileSync(mcpJson, 'utf8')), scope: 'project' })\n }\n catch (err) {\n failed.push({ name: mcpJson, error: err instanceof Error ? err.message : String(err) })\n }\n }\n const claudeState = resolve(home, '.claude.json')\n if (existsSync(claudeState)) {\n try {\n const state: unknown = JSON.parse(readFileSync(claudeState, 'utf8'))\n // User scope on purpose — see the doc comment (credential leak).\n sources.push({ servers: parseClaudeState(state, 'claude-project-state', projectRoot), scope: 'user' })\n sources.push({ servers: parseClaudeState(state, 'claude-user-state'), scope: 'user' })\n }\n catch (err) {\n failed.push({ name: claudeState, error: err instanceof Error ? err.message : String(err) })\n }\n }\n return { native, sources, failed }\n}\n\n/**\n * Names physically present in an mcps config file, from the raw parsed\n * shape — mirrors `upsertMcpServer`'s walk (flat array `name`s, wrapper\n * `mcpServers` keys, bare-map keys) with none of discovery's filtering.\n * Unreadable/malformed files yield no names; `upsertMcpServer` throws on\n * those before writing, so they still can't be overwritten.\n */\nfunction rawMcpNames(text: string): string[] {\n let raw: unknown\n try {\n raw = parseJsonTolerant(text)\n }\n catch {\n return []\n }\n if (Array.isArray(raw)) {\n return raw.flatMap(entry =>\n entry && typeof entry === 'object' && typeof (entry as { name?: unknown }).name === 'string'\n ? [(entry as { name: string }).name]\n : [])\n }\n if (raw && typeof raw === 'object') {\n const record = raw as Record<string, unknown>\n const map = record.mcpServers && typeof record.mcpServers === 'object' && !Array.isArray(record.mcpServers)\n ? record.mcpServers as Record<string, unknown>\n : record\n return Object.keys(map)\n }\n return []\n}\n\n// ---------------------------------------------------------------------------\n// Skills\n// ---------------------------------------------------------------------------\n\n/**\n * Copy `.claude/skills/<name>` directories (project + user) into the\n * corresponding `.{prefix}/skills`. A skill \"exists natively\" when a\n * directory of the same name sits in any native scan path — those are\n * skipped, not overwritten.\n */\nexport function importClaudeSkills(opts: ClaudeImportOptions = {}): ClaudeCopyResult {\n const result: ClaudeCopyResult = { copied: [], skipped: [], failed: [] }\n const { native: nativeNames, pairs } = claudeSkillCandidates(opts)\n for (const { from, to, names } of pairs) {\n for (const name of names) {\n if (nativeNames.has(name)) {\n result.skipped.push(name)\n continue\n }\n try {\n cpSync(join(from, name), join(to, name), { recursive: true, errorOnExist: true, force: false })\n nativeNames.add(name)\n result.copied.push(name)\n }\n catch (err) {\n result.failed.push({ name, error: err instanceof Error ? err.message : String(err) })\n }\n }\n }\n return result\n}\n\n/** Claude Code's skill folders, per source and target, and the skill names zidane already has. */\nfunction claudeSkillCandidates(opts: ClaudeImportOptions): { native: Set<string>, pairs: { from: string, to: string, names: string[] }[] } {\n const { projectRoot, home } = roots(opts)\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n const native = new Set<string>()\n for (const scan of projectUserPaths({ subPath: 'skills', ...(opts.cwd ? { cwd: opts.cwd } : {}), home, ...(opts.prefix ? { prefix: opts.prefix } : {}) })) {\n if (!existsSync(scan.path))\n continue\n for (const entry of readdirSync(scan.path, { withFileTypes: true })) {\n if (entry.isDirectory())\n native.add(entry.name)\n }\n }\n const pairs = [\n { from: resolve(projectRoot, '.claude/skills'), to: resolve(projectRoot, `.${prefix}/skills`) },\n { from: resolve(home, '.claude/skills'), to: resolve(home, `.${prefix}/skills`) },\n ].map(({ from, to }) => ({\n from,\n to,\n // A folder without a SKILL.md is not a skill.\n names: existsSync(from)\n ? readdirSync(from, { withFileTypes: true }).filter(entry => entry.isDirectory() && existsSync(join(from, entry.name, 'SKILL.md'))).map(entry => entry.name)\n : [],\n }))\n return { native, pairs }\n}\n\n// ---------------------------------------------------------------------------\n// Commands and subagents\n// ---------------------------------------------------------------------------\n\n/**\n * Copy `.claude/<kind>` markdown files (project + user) into the matching\n * `.{prefix}/<kind>`, keeping nested folders (`git/commit.md` stays\n * `/git:commit`). A name any native root already defines is skipped, never\n * overwritten. Names come from the file path, as the loader derives them; an\n * agent renamed by its `name:` frontmatter is compared by file name.\n */\nexport function importClaudeMarkdownDefs(kind: 'commands' | 'agents', opts: ClaudeImportOptions = {}): ClaudeCopyResult {\n const result: ClaudeCopyResult = { copied: [], skipped: [], failed: [] }\n const { native: nativeNames, pairs } = claudeMarkdownDefCandidates(kind, opts)\n for (const { from, to, files } of pairs) {\n for (const file of files) {\n if (nativeNames.has(file.name)) {\n result.skipped.push(file.name)\n continue\n }\n try {\n const target = join(to, relative(from, file.path))\n mkdirSync(dirname(target), { recursive: true })\n cpSync(file.path, target, { errorOnExist: true, force: false })\n nativeNames.add(file.name)\n result.copied.push(file.name)\n }\n catch (err) {\n result.failed.push({ name: file.name, error: err instanceof Error ? err.message : String(err) })\n }\n }\n }\n return result\n}\n\n/** Claude Code's command or subagent files, per source and target, and the names zidane already defines. */\nfunction claudeMarkdownDefCandidates(kind: 'commands' | 'agents', opts: ClaudeImportOptions): { native: Set<string>, pairs: { from: string, to: string, files: MarkdownDefFile[] }[] } {\n const { projectRoot, home } = roots(opts)\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n const nativeRoots = markdownDefRoots({ kind, ...(opts.cwd ? { cwd: opts.cwd } : {}), home, prefix: `.${prefix}`, includeForeign: false })\n const native = new Set(listMarkdownDefFiles(nativeRoots).map(file => file.name))\n const pairs = ([\n { from: resolve(projectRoot, `.claude/${kind}`), to: resolve(projectRoot, `.${prefix}/${kind}`), source: 'project' },\n { from: resolve(home, `.claude/${kind}`), to: resolve(home, `.${prefix}/${kind}`), source: 'user' },\n ] as const).map(({ from, to, source }) => ({ from, to, files: listMarkdownDefFiles([{ path: from, source, foreign: true }]) }))\n return { native, pairs }\n}\n\n// ---------------------------------------------------------------------------\n// Combined runner\n// ---------------------------------------------------------------------------\n\n/** What an import from Claude Code deliberately leaves alone, spelled out for the UI. */\nexport const CLAUDE_IMPORT_NOTES: readonly string[] = [\n 'zidane already reads CLAUDE.md files, so there is nothing to copy.',\n 'zidane does not copy Claude Code permissions, hooks or plugins, because it has nothing that matches them.',\n]\n\n/** What {@link importFromClaudeCode} imported. A category is absent when it was switched off. */\nexport interface ClaudeImportSummary {\n /**\n * Where the import landed: always this computer (its session store and\n * its `.zidane` files). Claude Code's data is read from this machine, so\n * the import never targets a remote host.\n */\n target: 'this-computer'\n /** Session import result. */\n sessions?: ClaudeSessionImportResult\n /** MCP server import result. */\n mcps?: ClaudeCopyResult\n /** Skill import result. */\n skills?: ClaudeCopyResult\n /** Command import result. */\n commands?: ClaudeCopyResult\n /** Subagent import result. */\n agents?: ClaudeCopyResult\n /** What is deliberately NOT imported, spelled out for the UI. */\n notes: string[]\n}\n\n/** The host a window is using, as far as the import note cares. */\nexport interface ClaudeImportActiveHost {\n /** Where the host runs: in this process, a local daemon or a remote server. */\n kind: 'in-process' | 'daemon' | 'server'\n /** Display name of the host. */\n name: string\n}\n\n/**\n * What to tell the user when the window's sessions live on a remote host:\n * the import went to this computer, so the host's list won't show it.\n * `null` for a host on this machine (it shares the store).\n *\n * @param activeHost - The host the window uses, or `null` or `undefined` when unknown.\n */\nexport function claudeImportHostNote(activeHost: ClaudeImportActiveHost | null | undefined): string | null {\n if (activeHost?.kind !== 'server')\n return null\n return `Imports go to this computer, not to ${activeHost.name}. Find them in this computer's session list.`\n}\n\n/**\n * Import Claude Code data from THIS machine into THIS machine: `store` must\n * be the local session store (never a remote host's), MCP servers, skills,\n * commands and subagents go to this machine's `.zidane` files. Pass `activeHost` (the\n * window's host) to get the remote-host note in `notes`.\n *\n * @param store - The local session store that receives imported sessions.\n * @param opts - Discovery options plus per-category switches (`sessions`, `mcps`, `skills`, `commands`, `agents`), each on unless set to `false`, and the optional `activeHost`.\n * @example\n * ```ts\n * import { importFromClaudeCode } from 'zidane/chat'\n * import type { SessionStore } from 'zidane'\n *\n * declare const store: SessionStore\n *\n * const summary = await importFromClaudeCode(store, { mcps: false })\n * console.log(summary.sessions?.imported, summary.notes)\n * ```\n */\nexport async function importFromClaudeCode(\n store: SessionStore,\n opts: ClaudeImportOptions & { sessions?: boolean, mcps?: boolean, skills?: boolean, commands?: boolean, agents?: boolean, activeHost?: ClaudeImportActiveHost | null } = {},\n): Promise<ClaudeImportSummary> {\n const hostNote = claudeImportHostNote(opts.activeHost)\n const summary: ClaudeImportSummary = {\n target: 'this-computer',\n notes: [...(hostNote ? [hostNote] : []), ...CLAUDE_IMPORT_NOTES],\n }\n if (opts.sessions !== false)\n summary.sessions = await importClaudeSessions(store, opts)\n if (opts.mcps !== false)\n summary.mcps = importClaudeMcps(opts)\n if (opts.skills !== false)\n summary.skills = importClaudeSkills(opts)\n if (opts.commands !== false)\n summary.commands = importClaudeMarkdownDefs('commands', opts)\n if (opts.agents !== false)\n summary.agents = importClaudeMarkdownDefs('agents', opts)\n return summary\n}\n\n/** The names a copy would bring over and skip, assuming each copy succeeds, as the importers walk them. */\nfunction previewNames(native: ReadonlySet<string>, names: Iterable<string>): OnboardingImportCategory {\n const known = new Set(native)\n const copy: string[] = []\n const skip: string[] = []\n for (const name of names) {\n if (known.has(name)) {\n skip.push(name)\n continue\n }\n known.add(name)\n copy.push(name)\n }\n return { copy, skip }\n}\n\n/**\n * What {@link importFromClaudeCode} would bring over and skip, per piece,\n * without writing anything: the setup wizard's Bring your setup step shows\n * it before the user picks. Names already in zidane are skipped, as the\n * import skips them; a server listed in several Claude files counts once.\n *\n * @param opts - Discovery options: `cwd`, `home` and `prefix`.\n * @example\n * ```ts\n * import { previewClaudeImport } from 'zidane/chat'\n *\n * const preview = previewClaudeImport()\n * console.log(preview.sessions.all, preview.mcps.copy, preview.skills.skip)\n * ```\n */\nexport function previewClaudeImport(opts: ClaudeImportOptions = {}): OnboardingImportPreview {\n const mcps = claudeMcpCandidates(opts)\n const mcpNames = [...new Set(mcps.sources.flatMap(source => source.servers.map(server => server.name)))]\n const skills = claudeSkillCandidates(opts)\n const commands = claudeMarkdownDefCandidates('commands', opts)\n const agents = claudeMarkdownDefCandidates('agents', opts)\n return {\n sessions: countClaudeSessionFiles(opts),\n mcps: previewNames(mcps.native, mcpNames),\n skills: previewNames(skills.native, skills.pairs.flatMap(pair => pair.names)),\n commands: previewNames(commands.native, commands.pairs.flatMap(pair => pair.files.map(file => file.name))),\n agents: previewNames(agents.native, agents.pairs.flatMap(pair => pair.files.map(file => file.name))),\n notes: CLAUDE_IMPORT_NOTES,\n }\n}\n","/**\n * Prompt autocompletion framework — React hook.\n *\n * The provider contract types + pure span/reference helpers live in\n * `./completion-core` (node-free + react-free, re-exported here). This\n * module adds the `useCompletion()` React hook on top.\n *\n * The TUI consumes `useCompletion()` to drive a popover above the textarea.\n * A GUI consumes the same hook to drive a dropdown. The popup component\n * itself only reads `label` + `description` on each item, so provider-\n * specific typing (`TItem`) stays at the registration boundary and never\n * leaks into the renderer.\n */\n\nimport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion-core'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { applyInsert, collectReferences, findActiveTriggers, mergeSuggestions } from './completion-core'\n\nexport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n CompletionSubmitContext,\n} from './completion-core'\nexport { applyInsert, collectReferences, findActiveTrigger, findActiveTriggers, mergeReferences, mergeSuggestions, resolveCompletionSubmit } from './completion-core'\n\n/**\n * Shared sentinel returned by `useCompletion` when no references are\n * present in the buffer. Stable identity lets consumers' `useEffect`\n * dep arrays bail out on chip-less keystrokes — see `useChipHighlights`.\n */\nconst EMPTY_REFS: readonly CompletionReference<unknown>[] = Object.freeze([])\n\n// ---------------------------------------------------------------------------\n// React hook\n// ---------------------------------------------------------------------------\n\n/** State surface returned by `useCompletion()`. */\nexport interface CompletionState<TItem = unknown> {\n /** Active trigger, or null when the cursor doesn't sit in a completable span. */\n active: ActiveTrigger<TItem> | null\n /** Items for the active query. Empty when no trigger is active. */\n items: readonly CompletionItem<TItem>[]\n /** True while the provider's `suggest` promise is pending. */\n loading: boolean\n /** Index of the highlighted item inside `items`. */\n selectedIndex: number\n /** Move the highlight. Wraps at the ends. No-op when `items` is empty. */\n selectNext: () => void\n /** Moves the highlight to the previous item. Wraps at the ends. No-op when `items` is empty. */\n selectPrev: () => void\n /**\n * Commit the highlighted item. Returns the new text + cursor pair, or\n * `null` when there's nothing to commit (no active trigger, empty list).\n */\n commit: () => { text: string, cursor: number } | null\n /** Force-close the popover until the user types again. */\n dismiss: () => void\n /** All references in the current buffer, regardless of provider. */\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Drive a prompt-completion popover from React state.\n *\n * Inputs are pull-style — the caller owns `text` + `cursor` (typically\n * mirroring an `OpenTUI TextareaRenderable` or a `<textarea>` element) and\n * passes them in each render. Providers are matched by their `trigger`\n * character; every provider that shares the trigger at the cursor is bound\n * and their suggestions merge into one list.\n *\n * Asynchronous suggest calls are aborted when the query changes or the\n * popover closes, so providers that hit a backend don't leak.\n *\n * @param input - Current buffer text and cursor offset.\n * @param providers - Providers that offer suggestions.\n * @param options - Options. `maxQueryLength` bounds the trigger query scan.\n */\nexport function useCompletion<TItem = unknown>(\n input: { text: string, cursor: number },\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): CompletionState<TItem> {\n const { text, cursor } = input\n // Destructure primitives up front so the `useMemo` dep array reads them\n // by value (not by `options` reference, which is a fresh object literal\n // on every call from most hosts).\n const { maxQueryLength } = options\n // `dismissed` is keyed by the trigger's `span.start` — the buffer\n // position of the `@` / `/` glyph that opened the popover. Staying\n // dismissed *while the user keeps typing into the same trigger span*\n // is the whole point of Esc; a stricter `${text.length}:${cursor}`\n // key would re-open on the very next keystroke, which would make the\n // \"esc dismiss\" hint a lie. The effect below resets this when the trigger\n // goes away entirely (user backspaced past `@`) so retyping `@` at\n // the same position works.\n const [dismissedTriggerStart, setDismissedTriggerStart] = useState<number | null>(null)\n\n const rawTrigger = useMemo(\n () => findActiveTriggers(text, cursor, providers, { maxQueryLength }),\n [text, cursor, providers, maxQueryLength],\n )\n\n const activeMulti = useMemo(() => {\n if (!rawTrigger)\n return null\n if (rawTrigger.span.start === dismissedTriggerStart)\n return null\n return rawTrigger\n }, [rawTrigger, dismissedTriggerStart])\n\n // `active` stays a single-provider ActiveTrigger (first bound provider) so\n // the popup's title/commit contract is unchanged; multi-provider merging\n // happens in the suggest effect below and surfaces as `item.section`.\n const active = useMemo<ActiveTrigger<TItem> | null>(\n () => activeMulti\n ? { provider: activeMulti.providers[0], query: activeMulti.query, span: activeMulti.span }\n : null,\n [activeMulti],\n )\n\n // Forget the dismissal once the trigger disappears — otherwise a\n // fresh `@` typed at the same byte position would inherit the prior\n // dismiss and the popover would never come back without scrolling\n // the trigger elsewhere first.\n useEffect(() => {\n if (!rawTrigger)\n setDismissedTriggerStart(null)\n }, [rawTrigger])\n\n const [items, setItems] = useState<readonly CompletionItem<TItem>[]>([])\n const [loading, setLoading] = useState(false)\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n // Cancel any in-flight suggest when the query changes / popover closes.\n const abortRef = useRef<AbortController | null>(null)\n\n useEffect(() => {\n abortRef.current?.abort()\n if (!activeMulti) {\n setItems([])\n setLoading(false)\n setSelectedIndex(0)\n return\n }\n const controller = new AbortController()\n abortRef.current = controller\n const ctx: CompletionContext = { text, cursor }\n let cancelled = false\n const outs = activeMulti.providers.map(provider => ({\n provider,\n out: provider.suggest(activeMulti.query, ctx, controller.signal),\n }))\n // Sync fast path — every provider returned synchronously (the common\n // single-provider case, e.g. `@` files). Merge without a loading flash.\n if (outs.every(o => Array.isArray(o.out))) {\n setItems(mergeSuggestions(outs.map(o => ({ provider: o.provider, items: o.out as CompletionItem<TItem>[] })), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n return\n }\n setLoading(true)\n // allSettled, not all: with several providers on one trigger, a single\n // provider whose async suggest rejects (e.g. a failed catalog scan) must\n // not blank its siblings' results. Fulfilled groups merge; rejected ones\n // are dropped. (All rejecting → empty items.)\n void Promise.allSettled(outs.map(async o => ({ provider: o.provider, items: await o.out }))).then(\n (results) => {\n if (cancelled)\n return\n setItems(mergeSuggestions(results.flatMap(r => r.status === 'fulfilled' ? [r.value] : []), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n },\n )\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [activeMulti, text, cursor])\n\n // References don't depend on `cursor` — both built-in providers ignore\n // `ctx.cursor` in `parseReferences`, and the consumers (chip highlighting,\n // submit-time activation) only care about positions of references, not\n // about where the caret happens to sit. Dropping `cursor` from the dep\n // array avoids re-walking every provider regex on each arrow-key press.\n // `cursor` is still forwarded so a future provider that wants the cursor\n // can opt in — at the cost of choosing its own memoization strategy.\n //\n // Empty results collapse to a shared `EMPTY_REFS` constant so the prompt\n // textarea's `useChipHighlights` effect doesn't fire on every keystroke\n // of a chip-less buffer (each keystroke would otherwise allocate a fresh\n // `[]`, change identity, and trigger a `clearAllHighlights` repaint).\n const references = useMemo(\n () => {\n const refs = collectReferences(text, providers, cursor)\n return refs.length === 0 ? (EMPTY_REFS as readonly CompletionReference<TItem>[]) : refs\n },\n [text, providers],\n )\n\n const selectNext = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i + 1) % items.length))\n }, [items.length])\n const selectPrev = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i - 1 + items.length) % items.length))\n }, [items.length])\n const dismiss = useCallback(() => {\n // Pull from `rawTrigger`, not `active`, so dismiss-while-dismissed\n // (a no-op now but reasonable to support) keeps the right anchor.\n if (rawTrigger)\n setDismissedTriggerStart(rawTrigger.span.start)\n }, [rawTrigger])\n const commit = useCallback((): { text: string, cursor: number } | null => {\n if (!active || items.length === 0)\n return null\n const item = items[Math.min(selectedIndex, items.length - 1)]\n return applyInsert(text, active.span, item.insertText)\n }, [active, items, selectedIndex, text])\n\n // Memoize the returned bag so consumers placing it in a `useEffect`\n // dep array don't observe a fresh reference every render. Same pattern\n // (and rationale) as `useStreamBuffer.return` — see streaming.ts.\n return useMemo<CompletionState<TItem>>(\n () => ({ active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references }),\n [active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references],\n )\n}\n","/**\n * Completion provider for session artifacts. Trigger: `#` (shared with the\n * chats provider — the renderer groups both under one popup).\n *\n * `#pr` → \"acme/widgets#42\" (pull request, open) → inserts the PR URL. Unlike chats there\n * is no token grammar: the inserted text IS the locator (URL, `git:…`,\n * path), which the model can act on directly and a human can read. No\n * `parseReferences` — a pasted URL is not a reference to resolve.\n *\n * Catalog getter is host-owned and sync; the host keeps it fresh off the\n * `artifact:recorded` / `artifact:changed` signals the same way it keeps\n * the chats catalog. `ensureCatalog` covers the cold first `#` keystroke.\n */\n\nimport type { ExtensionArtifactRow } from '../extensions/types'\nimport type { SessionArtifact } from '../session/artifacts'\nimport type { CompletionItem, CompletionProvider } from './completion-core'\nimport { Fzf } from 'fzf'\nimport { ageString } from './format'\n\n/** Id of the artifacts completion provider. */\nexport const ARTIFACTS_COMPLETION_PROVIDER_ID = 'artifacts'\n\n/** An artifact that can be referenced from a prompt. */\nexport interface ArtifactReferenceEntry {\n /** The session artifact. */\n artifact: SessionArtifact\n /** Pre-rendered row (`renderArtifactRow`) — kind registry stays host-side. */\n row: ExtensionArtifactRow\n /** Human kind label for the description (`Pull request`). */\n kindLabel: string\n}\n\n/** Provider payload for an artifact reference. */\nexport interface ArtifactReferenceData {\n /** The referenced artifact. */\n entry: ArtifactReferenceEntry\n /** What gets inserted: `row.href` when present, else the raw `uri`. */\n locator: string\n}\n\nconst DEFAULT_RESULT_LIMIT = 30\n\n/** Kind → short query aliases so `#pr` and `#branch` hit without typing the label. */\nconst KIND_ALIASES: Record<string, readonly string[]> = {\n pull_request: ['pr', 'pull', 'mr'],\n branch: ['branch', 'br'],\n commit: ['commit', 'sha'],\n file: ['file', 'path'],\n}\n\nfunction locatorFor(entry: ArtifactReferenceEntry): string {\n return entry.row.href ?? entry.artifact.uri\n}\n\nfunction haystack(entry: ArtifactReferenceEntry): string {\n const aliases = KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]\n return [entry.row.title, entry.row.subtitle ?? '', entry.artifact.label, entry.kindLabel, ...aliases].join(' ')\n}\n\nfunction descriptionFor(entry: ArtifactReferenceEntry): string {\n const bits = [entry.kindLabel]\n if (entry.row.status)\n bits.push(entry.row.status)\n bits.push(ageString(entry.artifact.updatedAt))\n const loc = locatorFor(entry)\n if (loc !== entry.row.title)\n bits.push(loc)\n return bits.join(', ')\n}\n\n/**\n * Filters and ranks artifacts for a query.\n * An empty query returns every artifact. Otherwise it matches the kind aliases, kind label and a fuzzy text match.\n *\n * @param catalog - Artifacts to search.\n * @param query - Text typed after the trigger.\n * @param limit - Maximum number of items returned.\n */\nexport function scoreArtifacts(\n catalog: readonly ArtifactReferenceEntry[],\n query: string,\n limit = DEFAULT_RESULT_LIMIT,\n): CompletionItem<ArtifactReferenceData>[] {\n const q = query.trim().toLowerCase()\n const fuzzy = q.length > 0\n ? new Set(new Fzf(catalog, { selector: haystack, casing: 'case-insensitive' }).find(q).map(r => r.item))\n : null\n const scored: { entry: ArtifactReferenceEntry, rank: number }[] = []\n for (const entry of catalog) {\n if (q.length === 0) {\n scored.push({ entry, rank: 5 })\n continue\n }\n const title = entry.row.title.toLowerCase()\n const aliases = (KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]).map(a => a.toLowerCase())\n const kindLabel = entry.kindLabel.toLowerCase()\n if (aliases.some(a => a === q || a.startsWith(q)) || kindLabel.startsWith(q)) {\n scored.push({ entry, rank: 0 })\n continue\n }\n if (title.startsWith(q)) {\n scored.push({ entry, rank: 1 })\n continue\n }\n if (title.includes(q) || locatorFor(entry).toLowerCase().includes(q)) {\n scored.push({ entry, rank: 2 })\n continue\n }\n if (fuzzy?.has(entry))\n scored.push({ entry, rank: 4 })\n }\n scored.sort((a, b) => a.rank - b.rank || b.entry.artifact.updatedAt - a.entry.artifact.updatedAt)\n return scored.slice(0, limit).map<CompletionItem<ArtifactReferenceData>>(({ entry }) => {\n const locator = locatorFor(entry)\n return {\n id: `artifact:${entry.artifact.id}`,\n label: entry.row.title,\n description: descriptionFor(entry),\n insertText: `${locator} `,\n data: { entry, locator },\n }\n })\n}\n\n/**\n * Creates a completion provider for `#` artifact references.\n * The provider only suggests. It inserts the artifact's locator as plain text, so it parses no references back.\n *\n * @param opts - Catalog getter, an optional async catalog loader and a result limit (default 30).\n */\nexport function createArtifactsCompletionProvider(opts: {\n getCatalog: () => readonly ArtifactReferenceEntry[]\n ensureCatalog?: () => Promise<readonly ArtifactReferenceEntry[]>\n limit?: number\n}): CompletionProvider<ArtifactReferenceData> {\n const limit = opts.limit ?? DEFAULT_RESULT_LIMIT\n return {\n id: ARTIFACTS_COMPLETION_PROVIDER_ID,\n trigger: '#',\n label: 'Artifacts',\n suggest(query) {\n const current = opts.getCatalog()\n if (current.length > 0 || !opts.ensureCatalog)\n return scoreArtifacts(current, query, limit)\n return opts.ensureCatalog().then(loaded => scoreArtifacts(loaded, query, limit))\n },\n // Inserted text is a plain locator, not a token: nothing to parse back.\n parseReferences: () => [],\n }\n}\n","import type { ReactNode } from 'react'\nimport type { ResolvedConfig } from './config'\nimport { createContext, useContext } from 'react'\n\n// ---------------------------------------------------------------------------\n// ConfigContext — renderer-agnostic React context that hands a fully-resolved\n// chat config down to deep consumers (modals, pickers, screens). Kept in its\n// own file so callers that only want `resolveConfig()` don't pay the\n// `import 'react'` cost.\n// ---------------------------------------------------------------------------\n\n/** Exported for hooks that read the config when there is one (`useModelName`). */\nexport const ConfigContext = createContext<ResolvedConfig | null>(null)\n\n/**\n * Provides a {@link ResolvedConfig} to the components below it.\n *\n * @param props - Props with the resolved `config` and the `children` to render.\n */\nexport function ConfigProvider({ config, children }: { config: ResolvedConfig, children: ReactNode }) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n}\n\n/** Reads the {@link ResolvedConfig} from the nearest {@link ConfigProvider}. Throws when used outside one. */\nexport function useConfig(): ResolvedConfig {\n const ctx = useContext(ConfigContext)\n if (!ctx)\n throw new Error('useConfig must be used inside <ConfigProvider>')\n return ctx\n}\n","/**\n * Live catalogs for files / skills / MCPs surfaced through React\n * context.\n *\n * **Why context, not props.** The modal layer stores its open node in\n * `useState`. React's element-identity reconciliation skips re-rendering\n * a subtree when the element reference hasn't changed — so passing\n * `skillsCatalog` as a prop into `modal.open(<SettingsModal …>)` bakes\n * the snapshot at open time and any subsequent `setSkillsCatalog`\n * doesn't reach the modal. Context propagation IS forwarded through\n * the modal layer regardless of element identity (same path the MCP\n * auth state uses), so reading via `useDiscovery()` gives the modal\n * live state.\n *\n * **Shape.** The provider exposes three groups:\n *\n * - **Catalogs** — the current state. Consumers re-render on every\n * update.\n * - **Ensure thunks** — kick the first load (idempotent). Wired into\n * completion providers; also called by the settings modal on mount\n * to fire-load lazy catalogs (currently `files`) on demand.\n * - **Refresh thunks** — force a rescan, bypassing the SWR throttle.\n * Wired into the settings modal's `ctrl+R`.\n *\n * Renderer-agnostic by design: the context only carries data + thunks.\n * The discovery work itself lives in `*-discovery.ts` modules; the\n * stale-while-revalidate machinery lives in `discovery-slot.ts`. The\n * provider's caller (AppShell for the TUI) owns where the slots live\n * and how their state propagates to `<DiscoveryProvider value>`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpToolSchema } from '../mcp'\nimport type { SkillConfig } from '../skills'\nimport type { FileEntry } from './files-discovery'\nimport type { DiscoveredMcp, DiscoveryError } from './mcps-discovery'\nimport type { DiscoveredPlugin } from './plugins-discovery'\nimport { createContext, useContext } from 'react'\n\n/** Skills, plugins and MCP servers found on disk, shared with the UI. */\nexport interface DiscoveryContextValue {\n /** Parsed `SkillConfig[]` from project + user scan paths. */\n skillsCatalog: readonly SkillConfig[]\n /**\n * Agent Plugins loaded from `.agents/plugins` / `.{prefix}/plugins`\n * (project + user). Includes rejected plugins — they render as read-only\n * diagnostics rather than vanishing.\n */\n pluginsCatalog: readonly DiscoveredPlugin[]\n /** Parsed `mcps.json` entries. */\n mcpsCatalog: readonly DiscoveredMcp[]\n /** Non-fatal parse errors from `mcps.json` lookups. */\n mcpsErrors: readonly DiscoveryError[]\n /**\n * Per-server full tool catalog from the on-disk cache (see\n * `chat/mcp-tools-cache.ts`). Updated as MCP servers bootstrap and\n * fire the `mcp:tools:list` hook. Missing key → no cache yet; the\n * settings UI surfaces an empty-state row under the expanded server.\n */\n mcpToolsByServer: Record<string, readonly McpToolSchema[]>\n /** Current file walk results — empty until lazy-load lands. */\n filesCatalog: readonly FileEntry[]\n\n /** Force a fresh skills rescan. Resolves once state is committed. */\n refreshSkills: () => Promise<void>\n /** Force a fresh plugin rescan. Resolves once state is committed. */\n refreshPlugins: () => Promise<void>\n /**\n * Re-parse `mcps.json`. Sync-shaped under the hood, async for symmetry.\n * Resolves `true` when the catalog actually changed (a publish that\n * schedules downstream agent rebuilds), `false` on a fingerprint no-op\n * or scan failure — callers that arm rebuild-coupled state (e.g. the\n * add-MCP auto-login) disarm on `false`.\n */\n refreshMcps: () => Promise<boolean>\n /** Force a fresh file walk. No UI affordance calls it yet. */\n refreshFiles: () => Promise<void>\n\n /** Idempotent first-load trigger for skills (used by `/` popover + modal mount). */\n ensureSkills: () => Promise<readonly SkillConfig[]>\n /** Idempotent first-load trigger for files (used by `@` popover). */\n ensureFiles: () => Promise<readonly FileEntry[]>\n}\n\nconst DiscoveryContext = createContext<DiscoveryContextValue | null>(null)\n\n/**\n * Provides a {@link DiscoveryContextValue} to the components below it.\n *\n * @param props - Props with the discovery `value` and the `children` to render.\n */\nexport function DiscoveryProvider({\n value,\n children,\n}: {\n value: DiscoveryContextValue\n children: ReactNode\n}) {\n return (\n <DiscoveryContext.Provider value={value}>\n {children}\n </DiscoveryContext.Provider>\n )\n}\n\n/**\n * Read live discovery state + actions. Throws if used outside a\n * `<DiscoveryProvider>` — discovery is a load-bearing dependency for\n * settings + completion popovers; bailing loud here surfaces wiring\n * mistakes at mount instead of producing empty catalogs at first\n * keystroke.\n */\nexport function useDiscovery(): DiscoveryContextValue {\n const ctx = useContext(DiscoveryContext)\n if (!ctx)\n throw new Error('useDiscovery must be used inside <DiscoveryProvider>')\n return ctx\n}\n\n/**\n * Non-throwing variant — returns `null` when no provider is mounted.\n * Used by composable modals (`<SettingsModal>`, …) that accept catalog\n * props as a fallback for embedders who don't wire the full TUI\n * shell. The in-app flow always mounts `<DiscoveryProvider>` so the\n * modal sees live state; standalone embeds get the prop snapshot.\n */\nexport function useDiscoveryOptional(): DiscoveryContextValue | null {\n return useContext(DiscoveryContext)\n}\n","/**\n * Stale-while-revalidate catalog slot.\n *\n * One slot per (catalog kind, projectDir) pin. Holds the first-load\n * promise + a `lastScannedAt` timestamp + an in-flight `refreshing`\n * promise. Idempotent across concurrent callers, safe to rotate when\n * the project changes (the slot's `abort` controller cancels in-flight\n * walks and the `.then` guards drop stale results).\n *\n * Renderer-agnostic. AppShell wraps it in React refs so React closures\n * stay stable; a non-TUI shell can use it directly against its own\n * state propagation.\n *\n * Contract:\n * - `ensure()` — kick the first load if not started; return the\n * cached promise. On subsequent calls, if `Date.now() -\n * lastScannedAt >= throttleMs` and no refresh is in flight, kick\n * a background revalidate that calls `onLoad(fresh)` when it\n * resolves. The original first-load promise still resolves with\n * the first walk's contents — consumers reading live state\n * elsewhere (React state) see the refreshed catalog.\n * - `refresh()` — force a rescan now, bypassing the throttle.\n * De-duplicates: if a refresh is already in flight, returns the\n * same promise.\n * - `isRefreshing()` — whether a refresh is running now.\n * - `abort()` — cancel any in-flight walk and detach. After this\n * the slot is dead; callers should rotate to a new slot.\n *\n * No state is exposed except through promises + the host's `onLoad`\n * callback; the slot doesn't try to be a store, only a coordinator.\n */\n\n/** Walk function — takes an abort signal, returns the discovered list. */\nexport type Walk<T> = (signal: AbortSignal) => Promise<readonly T[]>\n\n/** Notification: a fresh walk has resolved. Host is expected to commit to its own state store. */\nexport type Commit<T> = (items: readonly T[]) => void\n\n/** Options for {@link createDiscoverySlot}. */\nexport interface DiscoverySlotOptions<T> {\n /** Discover function — runs the actual FS / network walk. */\n walk: Walk<T>\n /** Commit hook — receives every successful walk's result (first + refreshes). */\n onLoad: Commit<T>\n /**\n * Min interval between background refreshes triggered by `ensure()`.\n * Default: 3_000ms. `refresh()` ignores this — it's the user's\n * explicit-intent path.\n */\n throttleMs?: number\n /** Optional error sink. Defaults to no-op; useful for `debugLog`. */\n onError?: (err: unknown, phase: 'first-load' | 'refresh') => void\n}\n\n/** Handle returned by {@link createDiscoverySlot}. */\nexport interface DiscoverySlot<T> {\n /**\n * Kick the first load (idempotent). Returns the first-load promise\n * once it exists. Schedules a background refresh if past throttle.\n */\n ensure: () => Promise<readonly T[]>\n /** Force-refresh now. De-duped across concurrent callers. */\n refresh: () => Promise<void>\n /** True if a refresh is currently running. */\n isRefreshing: () => boolean\n /** Cancel in-flight work + detach. Idempotent. */\n abort: () => void\n}\n\n/**\n * Creates a slot that loads a list lazily and keeps it fresh.\n * `ensure()` starts the first walk once and schedules throttled background refreshes. `refresh()` forces a walk. Every successful walk result goes to `onLoad`.\n *\n * @param options - The walk function, commit hook and optional throttle and error sink.\n */\nexport function createDiscoverySlot<T>(options: DiscoverySlotOptions<T>): DiscoverySlot<T> {\n const throttleMs = options.throttleMs ?? 3_000\n const abortCtrl = new AbortController()\n let firstLoad: Promise<readonly T[]> | null = null\n let refreshing: Promise<void> | null = null\n let lastScannedAt = 0\n let aborted = false\n\n const handleError = (err: unknown, phase: 'first-load' | 'refresh'): void => {\n options.onError?.(err, phase)\n }\n\n const startRefresh = (): Promise<void> => {\n lastScannedAt = Date.now()\n const run = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return\n options.onLoad(items)\n })\n .catch((err) => {\n if (aborted)\n return\n handleError(err, 'refresh')\n })\n .finally(() => {\n if (refreshing === run)\n refreshing = null\n })\n refreshing = run\n return run\n }\n\n const slot: DiscoverySlot<T> = {\n ensure() {\n if (aborted)\n return Promise.resolve([] as readonly T[])\n if (!firstLoad) {\n lastScannedAt = Date.now()\n firstLoad = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return [] as readonly T[]\n options.onLoad(items)\n return items\n })\n .catch((err) => {\n if (!aborted)\n handleError(err, 'first-load')\n return [] as readonly T[]\n })\n return firstLoad\n }\n // SWR: revalidate in the background if stale and not already running.\n if (!refreshing && Date.now() - lastScannedAt >= throttleMs)\n void startRefresh()\n return firstLoad\n },\n refresh() {\n if (aborted)\n return Promise.resolve()\n if (refreshing)\n return refreshing\n // Forced refresh from a user-initiated path (e.g. settings\n // modal `ctrl+R`). If the first load hasn't happened yet,\n // bootstrap through `ensure` so subsequent ensure callers see\n // the right `firstLoad` promise.\n if (!firstLoad)\n return slot.ensure().then(() => {})\n return startRefresh()\n },\n isRefreshing() {\n return refreshing !== null\n },\n abort() {\n if (aborted)\n return\n aborted = true\n abortCtrl.abort()\n },\n }\n return slot\n}\n","/**\n * `$EDITOR` / `$VISUAL` command parsing.\n *\n * These variables conventionally hold a command LINE, not a bare executable —\n * `code --wait`, `cursor --wait`, `subl -w`, `emacsclient -nw` are all normal\n * values. Spawning the whole string as argv[0] looks for a file literally named\n * `cursor --wait` and fails with ENOENT, so the flags have to be split off.\n *\n * Kept out of the TUI (and free of `node:child_process`) so the\n * tokenizer is unit-testable without pulling in the TUI.\n */\n\n/**\n * Split an editor command line into `[executable, ...args]`.\n *\n * Whitespace-separated, with single and double quotes honored so an editor\n * living under a path with spaces still works\n * (`\"/Applications/My App/bin/ed\" -w`). Quotes group and are stripped;\n * backslash escaping is deliberately NOT implemented, because on Windows\n * backslash is the path separator and treating `C:\\tools\\ed` as escapes would\n * corrupt far more command lines than it would rescue.\n *\n * Returns `[]` for an empty or whitespace-only spec, so callers can fall back\n * to the platform opener rather than spawning `''`.\n *\n * @param spec - Raw editor command line, such as the value of `$EDITOR`.\n * @example\n * ```ts\n * import { parseEditorCommand } from 'zidane/chat'\n *\n * const [cmd, ...args] = parseEditorCommand('code --wait')\n * // cmd === 'code', args === ['--wait']\n * ```\n */\nexport function parseEditorCommand(spec: string): string[] {\n const tokens: string[] = []\n let current = ''\n // The quote character we're inside, or null at top level. Tracked (rather\n // than a boolean) so a double quote inside single quotes stays literal.\n let quote: '\"' | '\\'' | null = null\n // Distinct from `current !== ''` so `\"\"` yields an empty-string token instead\n // of vanishing — an explicitly quoted empty argument was still an argument.\n let quoted = false\n\n for (const ch of spec) {\n if (quote) {\n if (ch === quote)\n quote = null\n else\n current += ch\n continue\n }\n if (ch === '\"' || ch === '\\'') {\n quote = ch\n quoted = true\n continue\n }\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n if (current !== '' || quoted) {\n tokens.push(current)\n current = ''\n quoted = false\n }\n continue\n }\n current += ch\n }\n if (current !== '' || quoted)\n tokens.push(current)\n return tokens\n}\n\n/**\n * Re-quote a single argv token for a shell that will re-parse the joined command\n * line — i.e. `spawn(..., { shell: true })`, which Node uses on Windows so that\n * `.cmd` / `.bat` wrappers (how `code`, `cursor` and friends ship there) resolve.\n *\n * Node does NOT re-quote when `shell` is set: it joins `[cmd, ...args]` with\n * spaces and hands the string over. Since {@link parseEditorCommand} has already\n * STRIPPED the user's quotes, handing tokens back unquoted would split\n * `C:\\Program Files\\ed.exe` at the space and try to run `C:\\Program`. This puts\n * the quotes back.\n *\n * Only tokens that need it are quoted, so a plain `code` stays `code`. A literal\n * double quote is doubled, which is how `cmd.exe` escapes one inside a quoted\n * run.\n *\n * The test covers `cmd.exe` METACHARACTERS, not just whitespace: `&`, `|`, `^`,\n * `<`, `>` and friends carry none of the obvious \"needs quoting\" signals, so a\n * repo cloned into `C:\\work\\a&b` would otherwise hand `C:\\work\\a&b\\AGENTS.md`\n * over bare, and cmd would split it at the `&` and run the tail as a second\n * command.\n *\n * `%` and `!` are in the class so such a token is quoted rather than passed\n * bare — but be clear about the limit: quoting does NOT stop cmd.exe expanding\n * `%VAR%`, nor `!VAR!` where delayed expansion is on. Double quotes suppress\n * splitting and other syntax, not expansion. That is a property of the shell\n * which no amount of quoting can close; the worst case is a wrong path, not an\n * injected command.\n *\n * @param token - One argv token, already stripped of its original quotes.\n */\nexport function quoteForShell(token: string): string {\n if (token === '')\n return '\"\"'\n if (!/[\\s\"&|^<>%()!]/.test(token))\n return token\n return `\"${token.replace(/\"/g, '\"\"')}\"`\n}\n","import type { ReactNode } from 'react'\nimport type { BooleanSettingKey } from './settings-catalog'\nimport type { Settings } from './types'\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport { enforceSettingsInvariants } from './settings-catalog'\n\n// The React-free catalog lives in `settings-catalog.ts`.\nexport * from './settings-catalog'\n\n// ---------------------------------------------------------------------------\n// SettingsProvider — single source of truth for the settings.\n//\n// `onChange` is called with the latest settings whenever a row toggles, so\n// the host can persist them. Persistence pathing lives outside this module\n// to keep the settings layer portable across renderers (TUI, GUI, …).\n// ---------------------------------------------------------------------------\n\nexport interface SettingsContextValue {\n settings: Settings\n /** Flip a boolean setting in place. Restricted at the type level to boolean-valued keys. */\n toggle: (key: BooleanSettingKey) => void\n /** Write any setting key to a new value. Used by string-valued choice rows. */\n setSetting: <K extends keyof Settings>(key: K, value: Settings[K]) => void\n /** One line the Settings modal shows (a server refused the last write); cleared by the next change. */\n status?: string | null\n /**\n * Settings sync markers, while the modal edits a server another computer\n * syncs to (`docs/SERVER.md` § Settings sync): which setting came from\n * which device, which was edited on the server since (an override), and\n * dropping an override. Absent elsewhere.\n */\n sync?: SettingsSyncMarks\n}\n\n/** A setting's sync state on the server the modal edits. */\nexport interface SettingsSyncMarks {\n marker: (key: string) => { kind: 'synced' | 'override', device: string } | null\n /** Drop the override on `key`; false when it has none. */\n reset: (key: string) => boolean\n}\n\nconst SettingsContext = createContext<SettingsContextValue | null>(null)\n\n/**\n * Holds the settings state, applies {@link enforceSettingsInvariants} on every write and reports each change to `onChange`.\n *\n * @param props - `initial` settings, an optional `onChange` callback and the `children` to render.\n */\nexport function SettingsProvider({\n initial,\n onChange,\n children,\n}: {\n initial: Settings\n onChange?: (settings: Settings) => void\n children: ReactNode\n}) {\n // Normalized on load too: an older build (or a hand edit) may have persisted\n // a dependent toggle without its requirement.\n const [settings, setSettings] = useState<Settings>(() => enforceSettingsInvariants(initial))\n\n // `onChange` runs as a side-effect *after* commit, not inside the state\n // updater. Two reasons:\n // 1. React 18 strict-mode dev runs invoke state updaters twice for\n // every transition; a `disk.save(...)` inside the updater would\n // double-fire there.\n // 2. Pure-update is the React contract — updaters must be free of\n // observable side-effects. Persisting from inside breaks the\n // contract regardless of strict mode.\n //\n // A ref keeps the latest `onChange` reachable from the effect without\n // forcing the effect to re-run on every render the parent re-renders.\n // The `initialMount` ref skips the very first commit (we don't want to\n // persist the initial state we just loaded from disk).\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const initialMount = useRef(true)\n useEffect(() => {\n if (initialMount.current) {\n initialMount.current = false\n return\n }\n onChangeRef.current?.(settings)\n }, [settings])\n\n const toggle = useCallback((key: BooleanSettingKey) => {\n setSettings(prev => enforceSettingsInvariants({ ...prev, [key]: !prev[key] }))\n }, [])\n\n const setSetting = useCallback(<K extends keyof Settings>(key: K, value: Settings[K]) => {\n setSettings(prev => (prev[key] === value ? prev : enforceSettingsInvariants({ ...prev, [key]: value })))\n }, [])\n\n const value = useMemo<SettingsContextValue>(\n () => ({ settings, toggle, setSetting }),\n [settings, toggle, setSetting],\n )\n\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/**\n * Re-provides settings to a subtree with other values and writers: a\n * surface that edits somewhere else than this window (the TUI's Settings\n * modal while it edits a remote host) reuses every settings-reading\n * component unchanged.\n */\nexport function SettingsOverride({ value, children }: { value: SettingsContextValue, children: ReactNode }) {\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/** Returns the settings context value. Throws when used outside {@link SettingsProvider}. */\nexport function useSettings(): SettingsContextValue {\n const ctx = useContext(SettingsContext)\n if (!ctx)\n throw new Error('useSettings must be used inside <SettingsProvider>')\n return ctx\n}\n","/**\n * Renderer-agnostic state machine for an \"enabled allowlist\" — the shape\n * used by `settings.enabledSkills`, `settings.enabledMcps`,\n * `settings.enabledExtensions` and `settings.enabledPlugins` (see\n * {@link EnabledAllowlistKey}).\n *\n * Semantics, kept identical across every call site:\n * - `undefined` → every catalog entry is enabled (default — user has\n * never opened the picker; new entries flow in).\n * - `[]` → the whole subsystem is off.\n * - `[names]` → explicit allowlist; the persisted shape stays a\n * concrete array once the user has toggled anything.\n *\n * The hook exposes a live `enabledSet` (Set<string>) + a `toggle(name)`\n * callback that persists the result through `useSettings().setSetting`.\n * The first toggle seeds the persisted allowlist from the current catalog\n * so newly-added entries don't silently drop on the next launch.\n *\n * Renderer-neutral: works for the TUI's `<ToggleListModal>`, a GUI\n * checkbox list, a CLI flag tool. Hook tests live alongside the chat\n * suite; the rendering layer is tested where it lives.\n */\n\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Settings keys whose value type is `readonly string[] | undefined`. */\nexport type EnabledAllowlistKey = 'enabledSkills' | 'enabledMcps' | 'enabledExtensions' | 'enabledPlugins'\n\n/**\n * Reactive handle over a persisted enabled-names allowlist, returned by {@link useEnabledToggleSet}.\n */\nexport interface EnabledToggleSet {\n /** Live set of enabled names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Toggle one name on/off; persists immediately via `setSetting`. */\n toggle: (name: string) => void\n}\n\n/**\n * Bind an \"enabled allowlist\" setting to a discovered catalog.\n *\n * `keyOf` extracts the persisted identity from a catalog entry (skill name,\n * MCP server name, …). Pass a stable, collision-free key — the persisted\n * allowlist is keyed against it forever.\n *\n * @param opts - Catalog, key extractor, setting key, and optional extra keys to keep when pruning the allowlist.\n */\nexport function useEnabledToggleSet<T>(opts: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n /**\n * Names that survive the persisted-allowlist GC even when absent from\n * `catalog`. Use when the catalog deliberately hides entries that are\n * still known to exist — e.g. errored / untrusted extensions are kept\n * out of the toggle list but must not be dropped from the allowlist\n * by an unrelated toggle. Defaults to the catalog keys.\n */\n gcKeys?: readonly string[]\n}): EnabledToggleSet {\n const { settings, setSetting } = useSettings()\n const { catalog, keyOf, settingKey, gcKeys } = opts\n const persisted = settings[settingKey]\n\n const enabledSet = useMemo<ReadonlySet<string>>(() => {\n if (persisted === undefined)\n return new Set(catalog.map(keyOf))\n return new Set(persisted)\n }, [persisted, catalog, keyOf])\n\n // Live catalog membership — used to garbage-collect stale persisted\n // names on every write. Without this, a skill / MCP that was once\n // enabled and then removed from disk lingers in the JSON forever\n // (the next toggle round-trips it back into the array). Functionally\n // harmless today (the agent ignores unknown names) but it lets the\n // persisted state drift away from reality. `gcKeys` widens the\n // \"known\" set beyond the rendered catalog (see its JSDoc).\n const catalogKeys = useMemo(\n () => new Set(gcKeys ?? catalog.map(keyOf)),\n [gcKeys, catalog, keyOf],\n )\n\n const toggle = useCallback((name: string) => {\n // Seed from the current `enabledSet` (covers both the seeded-from-catalog\n // and explicit-allowlist branches). Persist as a sorted array filtered\n // against the live catalog so stale names — entries deleted from disk\n // between launches — don't linger forever in the persisted allowlist.\n // `undefined` is reserved for \"fresh install, accept new discoveries\";\n // once the user toggles once the persistence shape stays a concrete list.\n const next = new Set(enabledSet)\n if (next.has(name))\n next.delete(name)\n else\n next.add(name)\n const persisted = [...next]\n .filter(n => catalogKeys.has(n))\n .sort()\n setSetting(settingKey, persisted)\n }, [enabledSet, catalogKeys, settingKey, setSetting])\n\n return { enabledSet, toggle }\n}\n","/**\n * Project file discovery for the `@`-prefixed files completion provider.\n *\n * Primary strategy — `git ls-files --cached --others --exclude-standard`.\n * Git already understands `.gitignore`, `.git/info/exclude`, and global\n * excludes, so we get its ignore rules without reimplementing them and one\n * process call instead of a full walk. Falls back to a filesystem walk (no\n * depth limit, capped by `maxFiles`) when git isn't available or the\n * directory isn't a repo.\n *\n * Stateless: callers cache the returned list (e.g. in App state) and pass it\n * into the provider's `getCatalog`. The list is a snapshot — refresh on\n * cwd change or via a user-driven action.\n */\n\nimport type { Dirent, Stats } from 'node:fs'\nimport { spawn } from 'node:child_process'\nimport { readdir, stat } from 'node:fs/promises'\nimport { resolve, sep } from 'node:path'\nimport { errorMessage } from '../errors'\n\n/** One row in the project file catalog. `path` is forward-slashed + relative to `cwd`. */\nexport interface FileEntry {\n /** Forward-slashed relative path from the discovery cwd. Stable across OSes. No trailing slash for dirs. */\n path: string\n /** Basename — used for prefix ranking in the completion provider. */\n name: string\n /** Source — `git` when listed via `git ls-files`, `fs` from the walk fallback. */\n source: 'git' | 'fs'\n /**\n * Entry kind. Directories are derived from the discovered file paths\n * (every ancestor of a listed file), so only directories that contain\n * at least one non-ignored file appear — mirroring `git ls-files`\n * semantics, which never lists empty or fully-ignored dirs.\n */\n kind: 'file' | 'dir'\n}\n\n/**\n * Hard cap on the discovered file count. Bigger than any reasonable repo's\n * source tree, small enough that a runaway scan can't pin the renderer's\n * filter. Provider's substring match runs over the full list per\n * keystroke; 10k entries × cheap `.includes()` is well under one frame.\n */\nconst DEFAULT_MAX_FILES = 10_000\n\n/**\n * Names skipped during the fs-walk fallback. Mirrors what `git ls-files`\n * would exclude via the default `.gitignore` shipped in most repos —\n * `node_modules`, `dist`, build caches — plus the `.git` dir itself.\n * Best-effort; the git path is the authoritative one.\n */\nconst FS_WALK_SKIP = new Set<string>([\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n '.next',\n '.nuxt',\n '.cache',\n '.turbo',\n '.parcel-cache',\n '.vercel',\n '.svelte-kit',\n '.expo',\n '.gradle',\n 'target',\n '__pycache__',\n '.pytest_cache',\n '.venv',\n 'venv',\n '.idea',\n '.vscode-test',\n])\n\n/** Options for `listProjectFiles`. */\nexport interface ListProjectFilesOptions {\n /** Discovery root. Default: `process.cwd()`. */\n cwd?: string\n /**\n * Cap on discovered files. Default: 10,000. Bigger lists fall back to\n * a truncated set. Derived directory entries are appended on top with\n * their own equal cap, so the returned array can exceed `maxFiles`\n * (bounded by 2×).\n */\n maxFiles?: number\n /** Aborts the scan early. Useful when re-running on rapid project switches. */\n signal?: AbortSignal\n}\n\n/**\n * Discover every non-ignored file under `cwd`, plus the directories that\n * contain them (derived from the file paths, `kind: 'dir'`). Tries\n * `git ls-files` first; on failure (no git, not a repo, abort) walks the\n * fs with a hand-rolled skip list.\n *\n * Errors are not thrown — the function always returns an array (possibly\n * empty). Callers wanting failure diagnostics can opt into them via\n * `ZIDANE_DEBUG`.\n *\n * @param opts - Working directory, file cap, and abort signal. All optional.\n */\nexport async function listProjectFiles(opts: ListProjectFilesOptions = {}): Promise<FileEntry[]> {\n const cwd = resolve(opts.cwd ?? process.cwd())\n const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES\n const signal = opts.signal\n\n try {\n const paths = await listViaGit(cwd, signal)\n return toEntries(paths, 'git', maxFiles)\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] git ls-files failed (${errorMessage(err)}), walking the file system instead\\n`)\n try {\n const paths = await listViaFs(cwd, maxFiles, signal)\n return toEntries(paths, 'fs', maxFiles)\n }\n catch (fsErr) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] fs walk failed: ${errorMessage(fsErr)}\\n`)\n return []\n }\n }\n}\n\n/**\n * Run `git ls-files --cached --others --exclude-standard` and return paths.\n *\n * - `--cached` — tracked files.\n * - `--others` — untracked but not ignored.\n * - `--exclude-standard` — apply `.gitignore`, `.git/info/exclude`, and\n * the user's global excludes file.\n *\n * Throws on non-zero exit (not a repo / no git) so the fallback path can\n * pick up. Aborts cleanly on `signal.aborted`.\n */\nasync function listViaGit(cwd: string, signal: AbortSignal | undefined): Promise<string[]> {\n return new Promise<string[]>((resolveP, rejectP) => {\n const child = spawn('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n // Buffered as chunk arrays — joining once at the end keeps GC quiet on\n // huge repos. A `let stdout += chunk` accumulator reallocates the whole\n // buffer per chunk; with a ~10MB `git ls-files` output that's quadratic\n // in chunk count.\n const stdoutChunks: string[] = []\n const stderrChunks: string[] = []\n const onAbort = () => child.kill('SIGTERM')\n if (signal) {\n if (signal.aborted) {\n child.kill('SIGTERM')\n rejectP(new Error('aborted'))\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n child.stdout.setEncoding('utf8')\n child.stderr.setEncoding('utf8')\n child.stdout.on('data', chunk => stdoutChunks.push(chunk))\n child.stderr.on('data', chunk => stderrChunks.push(chunk))\n child.on('error', (err) => {\n signal?.removeEventListener('abort', onAbort)\n rejectP(err)\n })\n child.on('close', (code) => {\n signal?.removeEventListener('abort', onAbort)\n if (signal?.aborted) {\n rejectP(new Error('aborted'))\n return\n }\n if (code !== 0) {\n rejectP(new Error(`git ls-files exited ${code}: ${stderrChunks.join('').trim()}`))\n return\n }\n // `-z` separates with NUL so paths containing newlines stay intact.\n const paths = stdoutChunks.join('').split('\\0').filter(p => p.length > 0)\n resolveP(paths)\n })\n })\n}\n\n/**\n * Depth-first fs walk fallback. Used when git is unavailable. Honors a\n * hard-coded skip list; doesn't read `.gitignore`. Acceptable for hosts\n * outside a git checkout (e.g. a freshly-`unzip`ped project).\n */\nasync function listViaFs(\n cwd: string,\n maxFiles: number,\n signal: AbortSignal | undefined,\n): Promise<string[]> {\n const out: string[] = []\n const stack: string[] = ['.']\n while (stack.length > 0) {\n if (signal?.aborted)\n throw new Error('aborted')\n const rel = stack.pop()!\n const abs = rel === '.' ? cwd : resolve(cwd, rel)\n let entries: Dirent[]\n try {\n entries = await readdir(abs, { withFileTypes: true })\n }\n catch {\n // Unreadable dir — skip silently. EACCES / ENOENT during a walk\n // shouldn't fail the whole discovery.\n continue\n }\n for (const e of entries) {\n if (FS_WALK_SKIP.has(e.name))\n continue\n const childRel = rel === '.' ? e.name : `${rel}${sep}${e.name}`\n if (e.isDirectory()) {\n stack.push(childRel)\n }\n else if (e.isFile()) {\n out.push(toForwardSlash(childRel))\n if (out.length >= maxFiles)\n return out\n }\n else if (e.isSymbolicLink()) {\n // Resolve once; ignore broken links and follow only if it points\n // to a regular file. We don't recurse into symlinked dirs to\n // avoid cycles.\n let st: Stats\n try {\n st = await stat(resolve(abs, e.name))\n }\n catch {\n continue\n }\n if (st.isFile()) {\n out.push(toForwardSlash(childRel))\n if (out.length >= maxFiles)\n return out\n }\n }\n }\n }\n return out\n}\n\nfunction toForwardSlash(p: string): string {\n return sep === '/' ? p : p.replaceAll(sep, '/')\n}\n\n/**\n * File entries (plus their ancestor dirs) from relative paths — what\n * {@link listProjectFiles} returns, for lists produced elsewhere (a remote\n * sandbox's `git ls-files`).\n *\n * @param paths - Relative file paths.\n * @param source - Where the paths came from. Defaults to `'git'`.\n * @param maxFiles - Maximum number of entries to return. Defaults to the same cap as {@link listProjectFiles}.\n */\nexport function fileEntriesFromPaths(paths: readonly string[], source: 'git' | 'fs' = 'git', maxFiles = DEFAULT_MAX_FILES): FileEntry[] {\n return toEntries(paths, source, maxFiles)\n}\n\nfunction toEntries(paths: readonly string[], source: 'git' | 'fs', maxFiles: number): FileEntry[] {\n const seen = new Set<string>()\n const out: FileEntry[] = []\n for (const raw of paths) {\n const path = toForwardSlash(raw)\n if (path.length === 0 || seen.has(path))\n continue\n seen.add(path)\n const lastSlash = path.lastIndexOf('/')\n const name = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n out.push({ path, name, source, kind: 'file' })\n if (out.length >= maxFiles)\n break\n }\n // Directory pass — derive every ancestor dir of the (possibly capped)\n // file list so `@` can also reference directories. Appended after the\n // files with its own `maxFiles` cap (deep trees can yield more dirs\n // than files) so a truncated file list never crowds out the dirs and\n // the total stays bounded at 2×`maxFiles`.\n const dirs = new Set<string>()\n for (const entry of out) {\n let p = entry.path\n for (let idx = p.lastIndexOf('/'); idx > 0; idx = p.lastIndexOf('/')) {\n p = p.slice(0, idx)\n if (dirs.has(p))\n break // ancestors of an already-seen dir are seen too\n dirs.add(p)\n }\n }\n let dirCount = 0\n for (const path of dirs) {\n const lastSlash = path.lastIndexOf('/')\n const name = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n out.push({ path, name, source, kind: 'dir' })\n if (++dirCount >= maxFiles)\n break\n }\n return out\n}\n","// ---------------------------------------------------------------------------\n// Footer hint composition.\n//\n// Pure presentation logic — given the current screen, run state, and a\n// resolved keybinding map, produce the ordered list of {@link Hint}s\n// the bottom-bar Footer should render. Lives in `zidane/chat` (not\n// `zidane/tui`) because it's renderer-agnostic: the only types involved\n// are `Hint`, `Screen`, `SessionMeta`, `Settings['uiMode']`, and `KeyBindings`. A GUI\n// shell can consume the same list and paint it however it likes.\n//\n// Priority order is intentional and load-bearing:\n//\n// pending approval > live pending interaction > resumed pending\n// interaction > busy (streaming) > auth screen > onboarding screen (no\n// hints) > sessions screen > chat screen (idle).\n//\n// The first four return state-specific hint sets unconditionally —\n// `uiMode` is ignored there because those affordances (approve / abort\n// / navigate) are not the noise the minimal mode is targeting. Minimal\n// mode only collapses the chat-idle row.\n// ---------------------------------------------------------------------------\n\nimport type { Hint } from './hints'\nimport type { KeyBindings } from './keybindings'\nimport type { Screen, SessionMeta, Settings } from './types'\nimport { formatBindingForDisplay } from './keybindings'\n\n/**\n * Options bag for {@link buildHints}. Flat shape so call sites read as a\n * checklist of \"what state do I have to surface\" — easy to thread\n * through React `useMemo` deps and to construct fresh in tests.\n */\nexport interface BuildHintsOptions {\n /** Screen the footer is shown on. */\n screen: Screen\n /** True while the agent is running a turn. */\n busy: boolean\n /** True while a safe-mode approval prompt is pending. */\n pending: boolean\n /**\n * True when a LIVE pending interaction is on screen (agent is mid-run,\n * awaiting the tool's Promise). Same nav hints as the safe-mode\n * approval picker — `abortRun` aborts the run.\n */\n pendingInteractionLive: boolean\n /**\n * True when the chat screen is showing a resumed (not live) pending\n * interaction. The agent isn't running; esc leaves the form without\n * persisting a response, so the hint reads \"leave for later\" — the\n * session re-enqueues the same interaction on next activation.\n */\n pendingInteractionResumed: boolean\n /** Active session, or `null` when none is open. */\n currentSession: SessionMeta | null\n /** True when more than one agent profile is available, so the agent switch hint is useful. */\n hasMultipleAgents: boolean\n /**\n * Chat-screen chrome density — `'minimal'` collapses the idle chat\n * hint row to just `agent / model / settings`. Busy and pending\n * states keep their full hint sets because those\n * affordances are load-bearing (abort, navigate, approve).\n */\n uiMode: Settings['uiMode']\n /** Label of the active model, or `null` when none is selected. */\n modelLabel: string | null\n /** Foreground color for the model label. */\n modelColor: string\n /** Current reasoning effort label, or `null` when the model has no reasoning knob. */\n effortLabel: string | null\n /** Foreground color for the effort label. */\n effortColor: string\n /**\n * Foreground for the `/l` chord that introduces the effort label. Unset, it\n * takes the color of the model key it extends, so the pair reads as one binding.\n */\n effortKeyColor?: string\n /**\n * Comma-joined labels of the enabled model options (e.g. `'fast'`), or `null`\n * when none are on. Appended after the effort label in the model chord — when\n * the model has no reasoning knob, it rides the chord on its own.\n */\n modelOptionsLabel?: string | null\n /** Label of the active agent profile. */\n agentLabel: string\n /** Foreground color for the agent label. */\n agentColor: string\n /**\n * Resolved keybindings — every hint key string flows through this so\n * a user-level rebind (e.g. `\"openSettings\": \"ctrl+p\"`) shows up live\n * in the bottom bar without us hard-coding the default `'ctrl+o'`.\n */\n keybindings: KeyBindings\n /**\n * Number of currently-dispatching tool calls. Drives the\n * `<cancelToolCall> cancel` chip in the `busy` hint row and the\n * `cancel task` chip on the idle chat row. Without\n * it, the binding stays inert and there's no point advertising the\n * shortcut. Falls back to \"no chip\" when no tools are in flight\n * (model is mid-stream but hasn't dispatched yet).\n */\n inFlightToolCount: number\n /**\n * Number of currently-active skills. Drives the footer's\n * `✦ N skill(s)` chip on the chat screen — purely informational,\n * a passive surface for \"tool restrictions are in effect\". Hidden\n * when `0` so the row stays uncluttered for the common case.\n */\n activeSkillCount: number\n /** Foreground for the skills chip (typically `COLOR.brand`). */\n skillsChipColor: string\n /**\n * Progress label for the todo chip (`\"2/5\"` from `todoProgressLabel`), or\n * `null` to hide it.\n *\n * As a `Hint` it inherits the row's click-to-keypress\n * contract for free (`dispatchHintKey`), so clicking it opens the list the\n * same way `ctrl+t` does — and it sits below the prompt like every other\n * control.\n */\n todoProgress: string | null\n /**\n * Pre-built `↑ vX.Y.Z` chip from `buildUpdateHint(useUpdateCheck())`,\n * or `null` when no update is available. Always last in the chat-screen\n * row so it's the first to drop when the terminal narrows.\n */\n updateHint: Hint | null\n /**\n * Pre-built prompt-cache toast, non-null only for a few seconds after a\n * `'cache:break'` hook fired (the host owns the visibility timer and the\n * severity split): warn-colored `⚡ cache break in <culprit>: +Nk tok, ~$0.48`\n * for attributable breaks, dim `⚡ cache expired: +Nk tok, ~$0.48` for\n * byte-stable TTL expiry — routine across human think-time, so it must not\n * cry wolf. Sits beside `updateHint` at the droppable end of the row: a\n * transient cost signal, not an action.\n */\n cacheBreakHint?: Hint | null\n /**\n * Sessions-screen list scope — `true` when the list spans all\n * projects. Drives the `tab` hint's label, which advertises the scope\n * the toggle switches TO. Optional so non-sessions callers can omit it.\n */\n sessionsScopeAll?: boolean\n /** Sessions screen: plain ←/→ switch spaces (Spaces on, 2+ spaces). Adds the `←→ space` hint. */\n sessionsSpaceSwitch?: boolean\n}\n\n/**\n * Build the footer's shortcut hints for the current screen. On the chat\n * screen the model id rides next to its `ctrl+m` shortcut and the agent\n * label rides next to `shift+tab`, each in its accent color — the bar\n * doubles as the status display without needing separate badges. When\n * the active model exposes reasoning, the `ctrl+m` hint grows a\n * secondary `/l` chord with the current effort label, surfacing the\n * effort picker as a discoverable, in-place affordance.\n *\n * @param options - Screen, run state, labels, colors, and keybindings that decide which hints appear.\n */\nexport function buildHints(options: BuildHintsOptions): Hint[] {\n const {\n screen,\n busy,\n pending,\n pendingInteractionLive,\n pendingInteractionResumed,\n currentSession,\n hasMultipleAgents,\n uiMode,\n modelLabel,\n modelColor,\n effortLabel,\n effortColor,\n effortKeyColor,\n modelOptionsLabel,\n agentLabel,\n agentColor,\n keybindings,\n inFlightToolCount,\n activeSkillCount,\n skillsChipColor,\n todoProgress,\n updateHint,\n cacheBreakHint = null,\n sessionsScopeAll,\n sessionsSpaceSwitch,\n } = options\n\n // Todo progress chip. Built BEFORE the priority branches below because it is\n // STATUS, not a shortcut: it has to survive every one of them. It reports\n // todo progress independent of `busy`, and an `in_progress` todo is set\n // precisely while the agent is working and calling `todowrite`. Building\n // it after the early returns would hide it for the whole window it exists\n // to cover.\n //\n // Appended LAST in each branch so `clipHintsToWidth` (which drops from the\n // right) sheds it before the abort/navigate affordances, which must stay.\n const todosChip: Hint | null = todoProgress !== null\n ? { key: keybindings.openTodos, label: `${todoProgress} todos` }\n : null\n const withTodos = (hints: Hint[]): Hint[] => todosChip ? [...hints, todosChip] : hints\n\n // Priority: safe-mode approval > live interaction > resumed\n // interaction > plain streaming. All three pending shapes share the\n // ↑↓ / ↵ nav prefix; only the esc semantics differ.\n if (pending)\n return withTodos([{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: formatBindingForDisplay(keybindings.abortRun), label: 'abort run' }])\n if (pendingInteractionLive)\n return withTodos([{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: formatBindingForDisplay(keybindings.abortRun), label: 'abort run' }])\n if (pendingInteractionResumed)\n return withTodos([{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'leave for later' }])\n if (busy) {\n // The run-wide abort, then the way out. The per-call cancel\n // (`cancelToolCall`) stays bound but unadvertised mid-run: the abort\n // covers the common case, and the chip only crowded the row.\n const baseBusyHints: Hint[] = []\n baseBusyHints.push({ key: formatBindingForDisplay(keybindings.abortRun), label: 'abort' })\n baseBusyHints.push({ key: 'esc', label: 'sessions' })\n // The model stays in view during the run it serves (a fallback shows in\n // its warn colour exactly then); no key, since the picker waits for idle.\n if (modelLabel)\n baseBusyHints.push({ key: '', label: modelLabel, labelColor: modelColor })\n return withTodos(baseBusyHints)\n }\n if (screen === 'auth')\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'exit' }]\n // The setup wizard shows the keys of each step inside its frame.\n if (screen === 'onboarding')\n return []\n if (screen === 'sessions') {\n return [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\n { key: 'tab', label: sessionsScopeAll ? 'current project' : 'all projects' },\n ...(sessionsSpaceSwitch ? [{ key: '←→', label: 'space' }] : []),\n { key: keybindings.openSessionDetails, label: 'session' },\n { key: keybindings.openSettings, label: 'settings' },\n { key: 'esc', label: currentSession ? 'back' : 'exit' },\n ]\n }\n // Chat screen. Prompt-input shortcuts (`↵ send`, `shift+↵ newline`,\n // `↑↓ history`) live in the prompt box's overlay title — the bottom\n // bar only carries the most common agent / model / global affordances. The\n // `cycleAgent` binding shows the active label inline; the\n // `openModelPicker` binding shows the active model id inline; etc.\n // `cycleAgent` is suppressed when there's only one profile and\n // `openSessionDetails` only renders once a session exists.\n //\n // Minimal UI mode: strip the chat-idle bottom bar to just `agent`\n // (when there's >1 profile), `model/effort`, and `settings`. The\n // full keybinding catalog stays reachable from inside settings.\n // Busy / pending / select-turn / queue branches above already exited\n // before this point — minimal only affects the resting chat row.\n //\n // Compose the model hint. When the active model supports reasoning,\n // the primary `<openModelPicker> model` pair grows a secondary\n // `/l effort` chord that doubles as the discoverability hint for the\n // effort picker (`openEffortPicker`) and the status display for the\n // current effort. The chord shares the model accent so the pair\n // reads as one keyboard binding across every theme.\n // The `/l` chord (from `openEffortPicker`) carries reasoning effort plus any enabled model options,\n // all space-separated so they read as one model-info run (e.g.\n // `claude-opus-4-8 medium fast`). When the model has no reasoning knob, the\n // options ride the chord on their own.\n const chordLabel = [effortLabel, modelOptionsLabel].filter(Boolean).join(' ') || null\n const modelHint: Hint | null = modelLabel\n ? {\n key: keybindings.openModelPicker,\n label: modelLabel,\n labelColor: modelColor,\n ...(chordLabel\n ? { extra: { key: shortChord(keybindings.openEffortPicker), keySpec: keybindings.openEffortPicker, ...(effortKeyColor ? { keyColor: effortKeyColor } : {}), label: chordLabel, labelColor: effortColor } }\n : {}),\n }\n : null\n // Skills chip — purely informational, no shortcut wired. We re-use\n // the Hint shape's `key` slot to render the glyph (`✦`) so the row\n // layout (key + label, padded breakpoints) stays consistent with\n // every other entry. The chip is omitted when no skills are active\n // so the row stays uncluttered for the common case.\n const skillsChip: Hint | null = activeSkillCount > 0\n ? {\n key: '✦',\n keyColor: skillsChipColor,\n label: activeSkillCount === 1 ? '1 skill' : `${activeSkillCount} skills`,\n labelColor: skillsChipColor,\n }\n : null\n // Background-task chip — fires when the user is idle (not `busy`)\n // but a task is still running. Gives them a discoverable way to\n // reach `ctrl+k` for kill. The `busy` branch above already covers\n // the dispatching case; this one is for \"agent finished its turn,\n // task still chugging\".\n const cancelTaskChip: Hint | null = inFlightToolCount > 0\n ? {\n key: keybindings.cancelToolCall,\n label: inFlightToolCount === 1 ? 'cancel task' : `cancel task (${inFlightToolCount})`,\n }\n : null\n // Minimal mode short-circuits the full composition. Same agent / model\n // hints as the full row (so a multi-agent setup still cycles, and the\n // model + effort chord stays visible), plus `settings` as the\n // discoverability anchor for everything we just dropped (keybindings\n // included — they have a tab in there).\n if (uiMode === 'minimal') {\n return [\n ...(hasMultipleAgents ? [{ key: keybindings.cycleAgent, label: agentLabel, labelColor: agentColor }] : []),\n ...(modelHint ? [modelHint] : []),\n { key: keybindings.openSettings, label: 'settings' },\n ]\n }\n return [\n ...(hasMultipleAgents ? [{ key: keybindings.cycleAgent, label: agentLabel, labelColor: agentColor }] : []),\n ...(modelHint ? [modelHint] : []),\n ...(skillsChip ? [skillsChip] : []),\n ...(todosChip ? [todosChip] : []),\n ...(cancelTaskChip ? [cancelTaskChip] : []),\n ...(currentSession ? [{ key: keybindings.openSessionDetails, label: 'session' }] : []),\n { key: keybindings.openSettings, label: 'settings' },\n { key: 'esc', label: 'sessions' },\n // Update chip lives at the right-most position so `clipHintsToWidth`\n // drops it first on narrow terminals — discoverability is nice but\n // the action shortcuts must stay.\n ...(cacheBreakHint ? [cacheBreakHint] : []),\n ...(updateHint ? [updateHint] : []),\n ]\n}\n\n/**\n * Shorten a binding spec for display as a \"chord continuation\" — the\n * `/l` after `ctrl+m model` in the chat footer. We only strip the\n * `ctrl+` prefix so user-customized chords (`alt+n`, `meta+shift+n`)\n * still render in full. Falls back to the verbatim spec when no\n * `ctrl+` prefix is found, which keeps the visual contract honest:\n * the rendered key always matches the bound trigger.\n *\n * @param spec - Binding spec, such as `ctrl+n`.\n */\nexport function shortChord(spec: string): string {\n return spec.startsWith('ctrl+') ? `/${spec.slice('ctrl+'.length)}` : spec\n}\n","/**\n * GitHub API limits as usage snapshots, for the Usage panel next to the\n * model providers' subscription windows.\n *\n * One snapshot per GitHub host zidane talks to (github.com first, then any\n * Enterprise host the shared state has seen), for the token each host uses\n * now. Windows are GitHub's quotas: `core` (REST), `graphql`, `search`.\n *\n * Two sources:\n * - the shared state every zidane process writes from the `x-ratelimit-*`\n * headers of its answers, so the section shows something with probes off;\n * - with `probe`, `GET /rate_limit` (free: it doesn't spend any quota), plus\n * `GET /user` once per token for the login (one REST request, never\n * below the quota's reserve).\n *\n * The balance is GitHub's own count per token, so it includes what the `gh`\n * CLI and the agent's `gh` commands spent with the same token.\n */\nimport type { GithubClient } from '../github/client'\nimport type { ProviderUsageSnapshot, ProviderUsageWindow } from './provider-usage-format'\nimport { clockSkewMs, githubClient, normalizeGithubHost } from '../github/client'\nimport { envOptedOut } from './env-opt-out'\n\nconst RESOURCES: readonly { id: string, label: string }[] = [\n { id: 'core', label: 'REST' },\n { id: 'graphql', label: 'GraphQL' },\n { id: 'search', label: 'Search' },\n]\n\n/** Options for {@link fetchGithubUsage}. */\nexport interface FetchGithubUsageOptions {\n /** Ask GitHub (`/rate_limit`); otherwise report what zidane observed. Ignored when the env opts out. */\n probe?: boolean\n /** GitHub client to read state and identity from. Defaults to the shared `githubClient()`. */\n client?: GithubClient\n /** Clock in epoch milliseconds. Defaults to `Date.now`. */\n now?: () => number\n /** How long a probe may take before the observed state is returned instead. Default 4 s. */\n probeDeadlineMs?: number\n}\n\n/**\n * How long a probe may hold the panel. It shares the client's request queue\n * with PR reads, whose own timeouts only start once they get a slot, so a busy\n * queue could otherwise stall the panel for the sum of everyone's timeouts.\n * Whatever lands after the deadline is still recorded for the next look.\n */\nconst PROBE_DEADLINE_MS = 4_000\n\nasync function probeHost(client: GithubClient, host: string, now: number): Promise<boolean> {\n const ident = await client.identity(host)\n const answer = await client.request({ host, path: 'rate_limit', interactive: true, conditional: false, timeoutMs: PROBE_DEADLINE_MS })\n if (answer.kind !== 'ok')\n return false\n try {\n const body = JSON.parse(answer.text) as { resources?: Record<string, { limit?: number, remaining?: number, used?: number, reset?: number }> }\n const skew = clockSkewMs(answer.headers, now)\n for (const { id } of RESOURCES) {\n const r = body.resources?.[id]\n if (r && typeof r.limit === 'number' && r.limit > 0 && typeof r.remaining === 'number' && typeof r.reset === 'number')\n client.observeQuota(host, ident.fingerprint, id, { limit: r.limit, remaining: r.remaining, resetAt: r.reset * 1000 - skew, ...(typeof r.used === 'number' ? { used: r.used } : {}) })\n }\n }\n catch {\n return false\n }\n client.observeAccount(host, ident.fingerprint, { source: ident.source })\n if (!ident.anonymous && !ident.login) {\n // Background: refused below the reserve, so the panel never spends a user's last requests.\n const user = await client.request({ host, path: 'user', timeoutMs: PROBE_DEADLINE_MS })\n if (user.kind === 'ok') {\n try {\n const login = (JSON.parse(user.text) as { login?: unknown }).login\n if (typeof login === 'string' && /^[\\w.-]+$/.test(login))\n client.observeAccount(host, ident.fingerprint, { login })\n }\n catch { /* no login, still a usable snapshot */ }\n }\n }\n return true\n}\n\n/**\n * GitHub's limits per host, in the shape the Usage panel renders. Never rejects.\n *\n * @param opts - Probe toggle, client, clock, and probe deadline.\n */\nexport async function fetchGithubUsage(opts: FetchGithubUsageOptions = {}): Promise<ProviderUsageSnapshot[]> {\n const client = opts.client ?? githubClient()\n const now = opts.now ?? Date.now\n const before = client.snapshot()\n const hosts = [...new Set(['github.com', ...before.quotas.map(q => q.host), ...before.accounts.map(a => a.host)].map(normalizeGithubHost))]\n const probe = opts.probe === true && !envOptedOut(['ZIDANE_NO_USAGE_CHECK', 'CI'])\n const idents = new Map(await Promise.all(hosts.map(async host => [host, await client.identity(host).catch(() => null)] as const)))\n /**\n * Without a token, GitHub only shows up once zidane has used it (a PR chip,\n * an artifact): someone who never touches GitHub features gets no row and no\n * probe. With a token (`gh auth login`, `GH_TOKEN`), it always shows.\n */\n const used = (host: string) => {\n const ident = idents.get(host)\n if (!ident)\n return false\n if (!ident.anonymous)\n return true\n return before.quotas.some(q => q.host === host && q.fingerprint === ident.fingerprint)\n || before.pauses.some(p => p.host === host && p.fingerprint === ident.fingerprint)\n }\n const shown = hosts.filter(used)\n const probed = new Map<string, boolean>()\n if (probe) {\n await Promise.all(shown.map(async (host) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const deadline = new Promise<boolean>((resolve) => {\n timer = setTimeout(resolve, opts.probeDeadlineMs ?? PROBE_DEADLINE_MS, false)\n })\n const ok = await Promise.race([probeHost(client, host, now()).catch(() => false), deadline])\n clearTimeout(timer)\n probed.set(host, ok)\n }))\n }\n\n const state = client.snapshot()\n const t = now()\n const out: ProviderUsageSnapshot[] = []\n for (const host of shown) {\n // Again: the probe may have just learned the login.\n const ident = await client.identity(host).catch(() => null)\n if (!ident)\n continue\n const quotas = state.quotas.filter(q => q.host === host && q.fingerprint === ident.fingerprint && q.resetAt > t)\n const pauses = state.pauses.filter(p => p.host === host && p.fingerprint === ident.fingerprint && p.retryAt > t)\n // Enterprise hosts nobody has used with this token say nothing worth a row.\n if (host !== 'github.com' && quotas.length === 0 && pauses.length === 0)\n continue\n const windows: ProviderUsageWindow[] = []\n for (const { id, label } of RESOURCES) {\n const q = quotas.find(x => x.resource === id)\n const paused = pauses.find(p => p.resource === id)\n if (!q && !paused)\n continue\n const usedPercent = paused ? 100 : q && q.limit > 0 ? Math.round(((q.limit - q.remaining) / q.limit) * 1000) / 10 : 0\n windows.push({ id, label, usedPercent, resetsAt: Math.max(q?.resetAt ?? 0, paused?.retryAt ?? 0) || null })\n }\n const hostPause = pauses.find(p => p.resource === '*')\n if (hostPause)\n windows.push({ id: 'paused', label: 'paused', usedPercent: 100, resetsAt: hostPause.retryAt })\n const plan = ident.login\n ? `@${ident.login}`\n : ident.anonymous ? 'anonymous' : 'signed in'\n out.push({\n providerKey: 'github',\n label: host === 'github.com' ? 'GitHub' : `GitHub (${host})`,\n credentialKey: `github:${host}:${ident.fingerprint}`,\n account: null,\n activeCredential: true,\n status: windows.length > 0 ? 'ok' : 'unavailable',\n plan: ident.anonymous || ident.source === 'custom' || ident.source === 'explicit' ? plan : `${plan} via ${ident.source}`,\n windows,\n credits: null,\n fetchedAt: Math.max(0, ...quotas.map(q => q.observedAt)) || t,\n error: windows.length > 0 ? null : probe && probed.get(host) === false ? 'GitHub did not answer' : 'No GitHub requests yet. Refresh to check.',\n ...(ident.anonymous ? { note: 'Anonymous: 60 requests/h. Run `gh auth login` for 5,000/h.' } : {}),\n })\n }\n return out\n}\n","// ---------------------------------------------------------------------------\n// Footer / title hint primitives.\n//\n// Pure data + width-estimation helpers consumed by the TUI's bottom-bar\n// footer, the prompt-box overlay, the sessions header, and any future\n// GUI shell that wants to render the same hint rows: `<key> <label>`, two\n// blank columns between hints. Plain-text math only — no renderer primitives, no colour\n// resolution, no React. Width estimates are intentionally `.length`-\n// based (assumes ASCII / single-cell glyphs); the consumer's renderer\n// is responsible for the actual painting and any wide-glyph accounting\n// it needs.\n// ---------------------------------------------------------------------------\n\n/**\n * Single hint entry — `<key> <label>`, optionally followed by an inline\n * secondary chord (`<key><extra.key> <label> <extra.label>`) for compound\n * shortcuts like `ctrl+m/l model effort`.\n *\n * Colour fields are advisory hex strings (resolved against the active\n * theme by the renderer); leaving them undefined lets the renderer pick\n * its conventional fallback (e.g. `warn` for keys, `dim` for labels; the\n * secondary chord takes its key's color).\n */\nexport interface Hint {\n /** Key or chord shown for the hint, such as `ctrl+l`. */\n key: string\n /** Short action text shown after the key. */\n label: string\n /** Optional override for the key color. Renderer default: warn. */\n keyColor?: string\n /** Optional override for the label color. Renderer default: dim. */\n labelColor?: string\n /**\n * Optional secondary chord + value rendered inline after the primary\n * pair: `<key><extra.key> <label> <extra.label>`. Lets one hint advertise\n * a compound shortcut (e.g. `ctrl+m/l model effort`) without forcing\n * callers into a parallel struct or a custom renderer.\n */\n extra?: {\n key: string\n label: string\n /**\n * Dispatchable binding spec behind `extra.key` when the displayed\n * chord is an abbreviation (`/l` for `ctrl+l` — see `shortChord`).\n * The abbreviation itself doesn't parse as a keypress, so a renderer\n * that makes the secondary chord independently clickable needs the\n * full spec. Defaults to `extra.key` when omitted.\n */\n keySpec?: string\n /** Defaults to the primary key's color on the renderer side: `ctrl+m/l` reads as one binding. */\n keyColor?: string\n /** Defaults to dim on the renderer side. */\n labelColor?: string\n }\n}\n\n/**\n * Truncate `text` to at most `max` characters, replacing the trailing\n * overflow with `…`. Edge cases:\n * - `max <= 0` → empty string (no room to render at all).\n * - `max === 1` → just the ellipsis glyph.\n * - `text.length <= max` → unchanged.\n *\n * Trailing-style truncation matches the natural read order of titles:\n * the prefix carries enough signal to identify the surface.\n *\n * @param text - Text to shorten.\n * @param max - Maximum length in characters, ellipsis included.\n */\nexport function truncateTrailing(text: string, max: number): string {\n if (max <= 0)\n return ''\n if (text.length <= max)\n return text\n if (max === 1)\n return '…'\n return `${text.slice(0, max - 1)}…`\n}\n\n// ---------------------------------------------------------------------------\n// Plain-text width estimates for responsive tiering.\n//\n// We approximate with `.length` instead of `stringWidth` because every glyph\n// involved (hint keys, provider names, model ids, token counts) is either\n// ASCII or a 1-cell symbol. The estimates only need to be precise enough to\n// pick a layout tier — being off by a column at the breakpoint is harmless.\n// ---------------------------------------------------------------------------\n\n/** Blank columns between two hints in a row. */\nexport const HINT_GAP = 2\n\n/**\n * Column layout of one hint, from its first cell:\n *\n * `ctrl+m/l` ` gpt-5` ` high fast`\n * └ keys ──┘ └label─┘ └extra label┘\n *\n * `key` then `extra.key`, a space, the label; an empty key with no extra\n * drops the space (a label-only hint). The TUI's click zones and the width\n * math below both read this, so they can't drift apart.\n */\nexport interface HintLayout {\n /** Columns `[extraKeyStart, extraKeyEnd)` hold `extra.key` (empty range without one). */\n extraKeyStart: number\n extraKeyEnd: number\n /** First column of the space before `extra.label`; equals `width` without an extra. */\n extraLabelStart: number\n /** Total columns. */\n width: number\n}\n\n/**\n * Layout of a single hint, see {@link HintLayout}. Pure / total.\n *\n * @param h - Hint to measure.\n */\nexport function hintLayout(h: Hint): HintLayout {\n const extraKeyStart = h.key.length\n const extraKeyEnd = extraKeyStart + (h.extra?.key.length ?? 0)\n const labelEnd = extraKeyEnd + (extraKeyEnd > 0 && h.label !== '' ? 1 : 0) + h.label.length\n return {\n extraKeyStart,\n extraKeyEnd,\n extraLabelStart: labelEnd,\n width: labelEnd + (h.extra ? 1 + h.extra.label.length : 0),\n }\n}\n\n/**\n * Plain-text width estimate for a list of {@link Hint}s rendered as a\n * hint row (keys, labels, {@link HINT_GAP} between hints). Exported so\n * prompt-box overlays can run the same responsive math as the bottom-bar\n * footer when deciding whether trigger hints fit. Pure / total.\n *\n * @param hints - Hints to measure.\n */\nexport function hintsLength(hints: readonly Hint[]): number {\n return hints.reduce((sum, h, i) => sum + hintLayout(h).width + (i > 0 ? HINT_GAP : 0), 0)\n}\n\n/**\n * Stable empty list so callers can compare by reference. Exported so\n * any consumer that needs to feed \"no primary hints\" into the same\n * `clipHintsToWidth` / `renderHintSpans` pipeline (e.g. the TUI's\n * minimal UI mode, where the prompt overlay shows only `@` / `/`\n * triggers) reuses the same frozen array instead of allocating a new\n * one each render.\n */\nexport const EMPTY_HINTS: readonly Hint[] = Object.freeze([])\n\n/**\n * Return the longest prefix of `hints` whose rendered width fits within\n * `budget`. Used to degrade hint rows gracefully at narrow terminal\n * widths instead of letting an absolutely-positioned hint row wrap\n * mid-segment (which paints the overflow over surrounding borders and\n * looks like garbled glyphs in a TUI).\n *\n * Prefix-only (no reordering, no last-hint priority) so the survivors\n * keep their authored order — the user's muscle memory for \"leftmost\n * hint = primary action\" stays intact as the terminal shrinks.\n *\n * @param hints - Hints in display order.\n * @param budget - Available width in characters.\n */\nexport function clipHintsToWidth(hints: readonly Hint[], budget: number): readonly Hint[] {\n if (budget <= 0 || hints.length === 0)\n return EMPTY_HINTS\n const out: Hint[] = []\n let used = 0\n for (let i = 0; i < hints.length; i++) {\n const h = hints[i]!\n const cost = (i > 0 ? HINT_GAP : 0) + hintLayout(h).width\n if (used + cost > budget)\n break\n out.push(h)\n used += cost\n }\n if (out.length === 0)\n return EMPTY_HINTS\n return out.length === hints.length ? hints : out\n}\n","/**\n * The provider list of the Connect step. It comes from the provider registry\n * of the host and from what `detectAuth` found. It says what each provider is,\n * how it signs in, and whether a credential is ready. Provider extensions that\n * are not installed yet come last, so Connect can install one and sign in.\n */\nimport type { ProviderAuth } from '../../auth'\nimport type { OnboardingCredentialSource, OnboardingProvider } from '../../onboarding/types'\nimport type { ProviderDescriptor } from '../../providers'\nimport { supportsOAuth } from '../../oauth'\nimport { providerBrand } from '../../onboarding/brands'\nimport { NATIVE_EXTENSIONS } from '../../onboarding/native-extensions'\n\n/** Providers shown first in Connect. The rest sit under \"More\". */\nexport const FEATURED_PROVIDERS: readonly string[] = ['anthropic', 'openai', 'google', 'openrouter', 'xai', 'local']\n\nfunction sourceOf(descriptor: ProviderDescriptor, auth: ProviderAuth | undefined): OnboardingCredentialSource | undefined {\n const method = auth?.available ? auth.methods[0] : undefined\n if (!method)\n return undefined\n // A provider without an API key (a local server) is ready once its fields are stored.\n if (method.source === 'apikey' && !descriptor.envKey && descriptor.customFields?.length)\n return 'configured'\n return method.source\n}\n\n/**\n * The providers Connect lists, featured ones first, then the provider\n * extensions that are not installed.\n *\n * @param registry - The host's provider descriptors.\n * @param auth - What `detectAuth` found for them.\n */\nexport function onboardingProviders(registry: Readonly<Record<string, ProviderDescriptor>>, auth: readonly ProviderAuth[]): OnboardingProvider[] {\n const listed: OnboardingProvider[] = Object.values(registry).map((descriptor) => {\n const found = auth.find(entry => entry.key === descriptor.key)\n const source = sourceOf(descriptor, found)\n const brand = providerBrand(descriptor.key)\n return {\n key: descriptor.key,\n label: descriptor.label,\n ...(descriptor.description ? { description: descriptor.description } : {}),\n ...(descriptor.signInLabel ? { signInLabel: descriptor.signInLabel } : {}),\n featured: FEATURED_PROVIDERS.includes(descriptor.key),\n oauth: supportsOAuth(descriptor),\n ...(descriptor.oauthHint ? { oauthHint: descriptor.oauthHint } : {}),\n apiKey: Boolean(descriptor.envKey),\n ...(descriptor.apiKeyPlaceholder ? { apiKeyPlaceholder: descriptor.apiKeyPlaceholder } : {}),\n customFields: (descriptor.customFields ?? []).map(({ key, label, placeholder, hint, required }) => ({\n key,\n label,\n ...(placeholder ? { placeholder } : {}),\n ...(hint ? { hint } : {}),\n ...(required ? { required } : {}),\n })),\n ready: Boolean(found?.available),\n ...(source ? { source } : {}),\n ...(source === 'env' && descriptor.envKey ? { envKey: descriptor.envKey } : {}),\n ...(brand ? { brand } : {}),\n }\n })\n const featured = FEATURED_PROVIDERS.flatMap(key => listed.filter(provider => provider.key === key))\n const rest = listed.filter(provider => !provider.featured)\n const viaExtension: OnboardingProvider[] = NATIVE_EXTENSIONS\n .filter(entry => entry.provider && !registry[entry.provider])\n .map(entry => ({ key: entry.provider!, label: entry.label, description: entry.description, featured: false, oauth: true, apiKey: false, customFields: [], ready: false, viaExtension: entry.name }))\n return [...featured, ...rest, ...viaExtension]\n}\n","/**\n * What the setup wizard finds on this computer before its first step. It finds\n * the providers and their credentials, the setups of other coding agents, the\n * GitHub login and local model servers. It also finds the signs of an install\n * that existed before the wizard. It changes nothing.\n */\nimport type { GithubIdentity } from '../../../github/client'\nimport type { OnboardingAgentId, OnboardingDetection, OnboardingGithubIdentity, OnboardingInstallSignals, OnboardingLocalServer } from '../../onboarding/types'\nimport type { ProviderDescriptor } from '../../providers'\nimport type { TuiState } from '../../store'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { githubClient, githubTokenSource } from '../../../github/client'\nimport { detectAuth } from '../../auth'\nimport { hasClaudeCodeSetup } from '../../claude-import'\nimport { readCredentials } from '../../credentials'\nimport { foreignMcpConfigFiles, foreignSkillScanPaths } from '../../foreign-configs'\nimport { findGitRoot } from '../../project-root'\nimport { settingsFromStored } from '../../settings-defaults'\nimport { onboardingProviders } from './providers'\n\nconst AGENT_ORDER: readonly OnboardingAgentId[] = ['claude-code', 'codex', 'pi']\n\nfunction agentOfPath(path: string): OnboardingAgentId | undefined {\n if (/[\\\\/]\\.codex[\\\\/]/.test(path))\n return 'codex'\n if (/[\\\\/]\\.pi[\\\\/]/.test(path))\n return 'pi'\n if (/[\\\\/]\\.claude(?:\\.json$|[\\\\/])/.test(path))\n return 'claude-code'\n return undefined\n}\n\n/** Where to look for other agents' configuration. */\nexport interface DetectOtherAgentsOptions {\n /** Project directory. Default: `process.cwd()`. */\n cwd?: string\n /** Home directory. Default: `os.homedir()`. */\n home?: string\n}\n\n/**\n * The coding agents whose setup exists on this computer, for the user or for\n * the current project. They are Claude Code (`~/.claude`, `.claude/`), Codex\n * (`.codex/`) and Pi (`.pi/`).\n *\n * @param opts - Project and home directories.\n */\nexport function detectOtherAgents(opts: DetectOtherAgentsOptions = {}): OnboardingAgentId[] {\n const found = new Set<OnboardingAgentId>()\n if (hasClaudeCodeSetup(opts))\n found.add('claude-code')\n const cwd = opts.cwd ?? process.cwd()\n if (existsSync(resolve(findGitRoot(cwd) ?? cwd, '.claude')))\n found.add('claude-code')\n for (const candidate of [...foreignMcpConfigFiles(opts), ...foreignSkillScanPaths(opts)]) {\n const agent = agentOfPath(candidate.path)\n if (agent && existsSync(candidate.path))\n found.add(agent)\n }\n return AGENT_ORDER.filter(agent => found.has(agent))\n}\n\nconst LOCAL_SERVERS: readonly { kind: OnboardingLocalServer['kind'], probe: string, url: string }[] = [\n { kind: 'ollama', probe: 'http://127.0.0.1:11434/api/tags', url: 'http://localhost:11434/v1' },\n { kind: 'lm-studio', probe: 'http://127.0.0.1:1234/v1/models', url: 'http://localhost:1234/v1' },\n { kind: 'vllm', probe: 'http://127.0.0.1:8000/v1/models', url: 'http://localhost:8000/v1' },\n]\n\n/** How to probe for local model servers. */\nexport interface ProbeLocalModelServersOptions {\n /** Fetch implementation. Default: the global `fetch`. */\n fetch?: typeof fetch\n /** How long each server gets to answer, in milliseconds. Default 300. */\n timeoutMs?: number\n}\n\n/**\n * The local model servers answering on their default ports (Ollama,\n * LM Studio, vLLM), with the base URL the Local LLM provider takes.\n *\n * @param opts - Fetch implementation and timeout.\n */\nexport async function probeLocalModelServers(opts: ProbeLocalModelServersOptions = {}): Promise<OnboardingLocalServer[]> {\n const fetchImpl = opts.fetch ?? fetch\n const timeoutMs = opts.timeoutMs ?? 300\n const answers = await Promise.all(LOCAL_SERVERS.map(async (server) => {\n try {\n const res = await fetchImpl(server.probe, { signal: AbortSignal.timeout(timeoutMs) })\n // Only the status counts: an unread body keeps the connection until it is collected.\n await res.body?.cancel().catch(() => undefined)\n return res.ok ? { kind: server.kind, url: server.url } : null\n }\n catch {\n return null\n }\n }))\n return answers.filter(answer => answer !== null)\n}\n\n/** What {@link onboardingInstallSignals} reads. */\nexport interface OnboardingInstallSignalsInput {\n /** Directory of `credentials.json`. */\n dataDir: string\n /** The state file's content. */\n state: TuiState\n /** The session store holds at least one session. */\n sessions: boolean\n /** This app has never run on this machine. */\n surfaceFirstRun: boolean\n}\n\n/**\n * The signs of an install that predates the wizard.\n *\n * @param input - The credentials directory, the state file and what the app knows.\n */\nexport function onboardingInstallSignals(input: OnboardingInstallSignalsInput): OnboardingInstallSignals {\n return {\n storedSettings: Object.keys(settingsFromStored(input.state.settings ?? {})).length > 0,\n credentials: Object.keys(readCredentials(input.dataDir)).length > 0,\n sessions: input.sessions,\n lastProvider: Boolean(input.state.lastProvider),\n surfaceFirstRun: input.surfaceFirstRun,\n }\n}\n\n/** What {@link detectOnboardingEnvironment} reads. */\nexport interface DetectOnboardingOptions extends OnboardingInstallSignalsInput, DetectOtherAgentsOptions {\n /** The host's provider descriptors. */\n registry: Readonly<Record<string, ProviderDescriptor>>\n /** Environment to look for provider keys in. Default: `process.env`. */\n env?: Record<string, string | undefined>\n /** Fetch implementation for the local server probe. Default: the global `fetch`. */\n fetch?: typeof fetch\n /** The GitHub identity lookup. Default: the process's GitHub client. */\n githubIdentity?: () => Promise<GithubIdentity>\n}\n\nasync function githubFor(lookup: () => Promise<GithubIdentity>): Promise<OnboardingGithubIdentity | undefined> {\n try {\n const identity = await lookup()\n if (identity.anonymous)\n return undefined\n return { source: identity.source, ...(identity.login ? { login: identity.login } : {}) }\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Everything the wizard shows it found: providers and their credentials,\n * other agents, the GitHub login, local model servers and the install\n * signals. The GitHub lookup and the server probe run in parallel.\n *\n * @param opts - The host's registry and paths, the state file and what the app knows.\n * @example\n * ```ts\n * import { BUILTIN_PROVIDERS, detectOnboardingEnvironment } from 'zidane/chat'\n *\n * const found = await detectOnboardingEnvironment({\n * dataDir: '/home/me/.zidane',\n * registry: BUILTIN_PROVIDERS,\n * state: {},\n * sessions: false,\n * surfaceFirstRun: true,\n * })\n * console.log(found.providers.filter(p => p.ready).map(p => p.key), found.agents)\n * ```\n */\nexport async function detectOnboardingEnvironment(opts: DetectOnboardingOptions): Promise<OnboardingDetection> {\n const auth = detectAuth(opts.dataDir, opts.registry, opts.env ?? process.env)\n const [github, localServers] = await Promise.all([\n githubFor(opts.githubIdentity ?? (() => githubClient().identity())),\n probeLocalModelServers(opts.fetch ? { fetch: opts.fetch } : {}),\n ])\n return {\n providers: onboardingProviders(opts.registry, auth),\n agents: detectOtherAgents(opts),\n ...(github ? { github } : {}),\n localServers,\n signals: onboardingInstallSignals(opts),\n }\n}\n\n/**\n * A GitHub token zidane already knows (`GH_TOKEN`, `GITHUB_TOKEN` or the\n * `gh` CLI), for the GitHub connector's \"Use my gh login\". `null` when there\n * is none.\n */\nexport async function connectorGithubToken(): Promise<{ token: string, source: string } | null> {\n return githubTokenSource.resolveWithSource()\n}\n","/**\n * The React side of interactions: a FIFO queue of pending requests with\n * stable actions, shared by the TUI and GUI. The protocol (tools, requests,\n * serialization, pending-state derivation) lives in `interactions-protocol.ts`\n * and is re-exported here.\n */\nimport type { ReactNode } from 'react'\nimport type { InteractionResponse, InteractionsActions, PendingInteractionEntry } from './interactions-protocol'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\nexport * from './interactions-protocol'\n\n// ---------------------------------------------------------------------------\n// React context — FIFO queue of pending requests + resolver callbacks.\n//\n// Two contexts on purpose (mirrors `safe-mode-context.tsx`):\n// - `InteractionsQueueContext` carries the live queue. Consumers re-render\n// on every push/pop.\n// - `InteractionsActionsContext` carries `{ enqueue, resolveHead, … }` and\n// never changes identity, so callbacks that depend on these don't\n// re-bind on every queue churn.\n// ---------------------------------------------------------------------------\n\nconst InteractionsQueueContext = createContext<readonly PendingInteractionEntry[]>([])\nconst InteractionsActionsContext = createContext<InteractionsActions | null>(null)\n\n/**\n * React provider that holds the FIFO queue of pending interactions. Pending entries are cancelled when it unmounts.\n *\n * @param props - Component props.\n * @param props.children - Subtree that can read the queue and actions.\n */\nexport function InteractionsProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly PendingInteractionEntry[]>([])\n\n // Mirror the live queue so the unmount cleanup can settle whatever is\n // pending without re-subscribing the effect on every queue change.\n const queueRef = useRef<readonly PendingInteractionEntry[]>([])\n queueRef.current = queue\n\n // Provider unmount is a teardown path too: React drops the queue state\n // outright, so any in-flight `requestInteraction` Promise would hang\n // forever (its tool body never settles → the run's `running` gate never\n // clears). Cancel every pending entry on unmount so the resolver can't be\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const entry of queueRef.current)\n entry.cancel('Interactions provider unmounted')\n }, [])\n\n const enqueue = useCallback((entry: PendingInteractionEntry) => {\n setQueue(prev => [...prev, entry])\n }, [])\n\n const resolveHead = useCallback((response: InteractionResponse) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(response)\n return rest\n })\n }, [])\n\n const cancelHead = useCallback((reason?: string) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.cancel(reason)\n return rest\n })\n }, [])\n\n const cancelAll = useCallback((reason?: string) => {\n setQueue((prev) => {\n for (const entry of prev) entry.cancel(reason)\n return []\n })\n }, [])\n\n const dropById = useCallback((requestId: string) => {\n // Remove WITHOUT settling: the daemon already resolved this in the\n // window that answered it. Neither resolve nor cancel — both would\n // reach back toward the (no-op) daemon callback needlessly.\n setQueue(prev => prev.filter(entry => entry.request.id !== requestId))\n }, [])\n\n // Stable actions identity — every member is `useCallback`'d with `[]`\n // deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useInteractionsActions()`\n // never re-render purely because of queue state.\n const actionsRef = useRef<InteractionsActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { enqueue, resolveHead, cancelHead, cancelAll, dropById }\n\n return (\n <InteractionsActionsContext.Provider value={actionsRef.current}>\n <InteractionsQueueContext.Provider value={queue}>\n {children}\n </InteractionsQueueContext.Provider>\n </InteractionsActionsContext.Provider>\n )\n}\n\n/** Read the live queue (re-renders on every push/pop). */\nexport function useInteractionsQueue(): readonly PendingInteractionEntry[] {\n return useContext(InteractionsQueueContext)\n}\n\n/** Read the stable actions object. */\nexport function useInteractionsActions(): InteractionsActions {\n const ctx = useContext(InteractionsActionsContext)\n if (!ctx)\n throw new Error('useInteractionsActions must be used inside <InteractionsProvider>')\n return ctx\n}\n","/**\n * React provider for the MCP per-server OAuth state map.\n *\n * `ModalRoot.open(node)` captures the modal contents as a snapshot — naive\n * prop-passing would freeze the auth badge at whatever status existed when\n * the user opened the picker, and a `mcp:auth:url` arriving mid-login\n * would never reach the rendered modal. Routing the state through a\n * context provider mounted ABOVE `ModalRoot` fixes that: a state update\n * re-renders the provider, which re-renders `ModalRoot`, which re-renders\n * the (referentially-stable) modal child node — and consumers calling\n * `useMcpAuthState()` see the fresh value.\n *\n * Two contexts on purpose:\n *\n * - `StateContext` carries the live map. Consumers re-render on every\n * auth event.\n * - `DispatchContext` carries the reducer entry point. Identity is stable\n * across renders, so AppShell's hook handlers don't re-bind when the\n * state changes — they grab `dispatch` once and fire events for the\n * lifetime of the agent.\n *\n * Actions (login / logout / cancel) deliberately do NOT live in context —\n * they depend on AppShell-scoped resources (credential store, catalog ref,\n * agent ref), close over those via refs, and stay stable across renders.\n * Passing them as modal props at `modal.open` time is fine because the\n * captured reference reads from refs internally; the snapshot doesn't go\n * stale even though it was taken at open time.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpAuthEvent, McpAuthStateMap } from './mcp-auth-state'\nimport { createContext, useCallback, useContext, useState } from 'react'\nimport { reduceMcpAuth } from './mcp-auth-state'\n\nconst StateContext = createContext<McpAuthStateMap>({})\nconst DispatchContext = createContext<((event: McpAuthEvent) => void) | null>(null)\n\n/**\n * Mount once near the top of the React tree (above `ModalRoot`). Children\n * use `useMcpAuthState()` to read and `useMcpAuthDispatch()` to fire events.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function McpAuthProvider({ children }: { children: ReactNode }) {\n const [state, setState] = useState<McpAuthStateMap>({})\n const dispatch = useCallback((event: McpAuthEvent) => {\n setState(prev => reduceMcpAuth(prev, event))\n }, [])\n return (\n <StateContext.Provider value={state}>\n <DispatchContext.Provider value={dispatch}>\n {children}\n </DispatchContext.Provider>\n </StateContext.Provider>\n )\n}\n\n/** Returns the current auth state map, keyed by server name. */\nexport function useMcpAuthState(): McpAuthStateMap {\n return useContext(StateContext)\n}\n\n/** Returns the function that applies an {@link McpAuthEvent} to the auth state. Throws when used outside {@link McpAuthProvider}. */\nexport function useMcpAuthDispatch(): (event: McpAuthEvent) => void {\n const dispatch = useContext(DispatchContext)\n if (!dispatch)\n throw new Error('useMcpAuthDispatch must be used inside <McpAuthProvider>')\n return dispatch\n}\n","/**\n * Renderer-agnostic data model for the MCP settings list when it\n * supports per-server expand / collapse with nested tool rows.\n *\n * Lives in `chat/` (not `tui/`) because both the standalone\n * `McpsSettingsModal` and the inline `<McpsList>` inside the unified\n * `SettingsModal` consume the exact same flattening + cursor\n * arithmetic — re-deriving it in two places would let the two\n * surfaces drift on edge cases (empty tool catalogs, freshly-removed\n * servers, stale expansion state).\n *\n * The model is a flat `VisibleMcpRow[]` so renderers can iterate once,\n * and the cursor is a single integer that walks both kinds (server\n * headers AND tool checkboxes) without separate \"which server am I\n * on / which tool inside it\" state.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { DiscoveredMcp } from './mcps-discovery'\n\n/**\n * One row in the flattened MCP settings list. Three kinds:\n *\n * - `'server'` — server header with the chevron + checkbox +\n * transport detail. Always present.\n * - `'tool'` — checkbox + description for a tool advertised\n * by the parent server. Present only when the\n * server is expanded AND its tool cache is\n * populated.\n * - `'tool-empty'` — placeholder shown when an expanded server\n * has no cached tool list yet (first launch\n * before any session has bootstrapped the\n * server). Lets the renderer surface a\n * one-line \"send a prompt to populate\" hint\n * WITHOUT changing the server's expand\n * affordance.\n */\nexport type VisibleMcpRow\n = | { kind: 'server', entry: DiscoveredMcp }\n | { kind: 'tool', serverName: string, tool: McpToolSchema }\n | { kind: 'tool-empty', serverName: string }\n\n/**\n * Flatten a server catalog plus the per-server expanded set into the\n * visible-row list a renderer cycles through.\n *\n * - `catalog` — filtered / ordered server list (post-search,\n * post-allow-list).\n * - `expanded` — names of servers whose tool list is open.\n * Servers not in this set render as a single\n * server-header row.\n * - `toolsByServer` — per-server full tool catalog (from\n * `chat/mcp-tools-cache.ts`). Missing key →\n * `'tool-empty'` placeholder under the\n * expanded server.\n *\n * @param catalog - Discovered servers in display order.\n * @param expanded - Names of servers whose tool rows are shown.\n * @param toolsByServer - Cached tool lists keyed by server name. A missing or empty list yields a `tool-empty` row.\n */\nexport function buildVisibleMcpRows(\n catalog: readonly DiscoveredMcp[],\n expanded: ReadonlySet<string>,\n toolsByServer: Record<string, readonly McpToolSchema[]>,\n): VisibleMcpRow[] {\n const out: VisibleMcpRow[] = []\n for (const entry of catalog) {\n out.push({ kind: 'server', entry })\n if (!expanded.has(entry.config.name))\n continue\n const tools = toolsByServer[entry.config.name] ?? []\n if (tools.length === 0) {\n out.push({ kind: 'tool-empty', serverName: entry.config.name })\n continue\n }\n for (const tool of tools)\n out.push({ kind: 'tool', serverName: entry.config.name, tool })\n }\n return out\n}\n\n/**\n * Server name a row \"belongs to\" for catalog / toggle / auth lookups.\n * For a server row that's its own name; for tool / tool-empty rows\n * it's the parent server's name.\n *\n * @param row - Any visible row.\n */\nexport function parentServerName(row: VisibleMcpRow): string {\n return row.kind === 'server' ? row.entry.config.name : row.serverName\n}\n\n/**\n * Index in `rows` of the server header for the given parent name. `-1`\n * when not present. Used by collapse handlers to snap the cursor up\n * to the parent BEFORE shrinking the row list — otherwise the cursor\n * would land on whichever sibling row now occupies the same flat\n * index.\n *\n * @param rows - The visible rows.\n * @param parentName - Server name to look for.\n */\nexport function indexOfServerRow(rows: readonly VisibleMcpRow[], parentName: string): number {\n return rows.findIndex(\n r => r.kind === 'server' && r.entry.config.name === parentName,\n )\n}\n","/**\n * Renderer-agnostic per-tool toggle for MCP servers. Sibling of\n * {@link useEnabledToggleSet} — same shape, opposite polarity:\n *\n * - `useEnabledToggleSet` is an **allowlist** (`enabledMcps` / etc.),\n * `undefined` = everything enabled, toggle moves to an explicit list.\n * - `useMcpToolToggleSet` is a **deny-list** (`disabledMcpTools`),\n * missing entry = everything enabled, toggle moves to an explicit\n * list of NAMES TO HIDE.\n *\n * The polarity flip is intentional and documented on the setting itself\n * (`Settings.disabledMcpTools`): when an MCP server ships a new tool in\n * a later release, a user who has already opted out of `big_tool` still\n * gets the new one — opposite preference from server-level toggles\n * where a new server should NOT silently appear in the catalog.\n *\n * Two hook flavors:\n *\n * - {@link useMcpToolToggleSet} — one server. Convenience for hosts\n * that render a single server's tool list at a time.\n * - {@link useMcpToolToggleMap} — every server's toggle handler in\n * one call. Required by hosts that dispatch toggles from a shared\n * keyboard handler at the top of the tree (`mcps-settings.tsx`)\n * where calling the single-server hook in a loop would violate\n * rules-of-hooks the moment a server is added or removed.\n *\n * Both flavors route through the same {@link buildToolToggle} factory\n * so semantics stay byte-identical regardless of entry point.\n *\n * Garbage-collects on every write: stale names (tools that disappeared\n * from the server's catalog between launches) are dropped so the\n * persisted deny-list doesn't grow unbounded. Same invariant as the\n * sibling hook for `enabledX` allowlists.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { Settings } from './types'\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Per-tool enable state and toggle handler for one MCP server. */\nexport interface McpToolToggleSet {\n /** Live set of ENABLED tool names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Flip one tool on/off; persists immediately via `setSetting`. */\n toggle: (toolName: string) => void\n}\n\n/**\n * Bind a single MCP server's per-tool toggle to\n * `Settings.disabledMcpTools[serverName]`.\n *\n * `catalog` is the FULL list of tools the server advertises (i.e. the\n * cached `listTools()` payload from `chat/mcp-tools-cache.ts`). The\n * hook needs it both to render `enabledSet` (every catalog tool not in\n * the persisted deny-list) and to garbage-collect stale deny-list\n * entries on every write.\n *\n * @param opts - The server name and its full tool catalog.\n */\nexport function useMcpToolToggleSet(opts: {\n serverName: string\n catalog: readonly McpToolSchema[]\n}): McpToolToggleSet {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n\n const catalogNames = useMemo(() => opts.catalog.map(t => t.name), [opts.catalog])\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(\n () => buildToolToggle({\n serverName: opts.serverName,\n catalogNames,\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n }),\n [opts.serverName, catalogNames, persistedDenyAll, setDisabled],\n )\n}\n\n/**\n * Bind every server in `catalogByServer` to its own per-tool toggle in\n * a single hook call. Returns a `serverName → McpToolToggleSet` map.\n *\n * Use this when a host needs handlers for multiple servers at once\n * (the TUI's MCP settings panel keyboard dispatcher is the canonical\n * example) — calling {@link useMcpToolToggleSet} in a `for` / `.map`\n * over a dynamic catalog would change the hook-call order whenever a\n * server is added or removed, violating React's rules-of-hooks. This\n * hook calls `useMemo` exactly once regardless of server count.\n *\n * Servers absent from `catalogByServer` get no entry in the result.\n * The persisted deny-list still keeps their entries on disk — they\n * just stay invisible to this consumer until the catalog re-includes\n * them.\n *\n * @param opts - Tool catalogs keyed by server name.\n */\nexport function useMcpToolToggleMap(opts: {\n catalogByServer: Record<string, readonly McpToolSchema[]>\n}): Record<string, McpToolToggleSet> {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(() => {\n const out: Record<string, McpToolToggleSet> = {}\n for (const [serverName, catalog] of Object.entries(opts.catalogByServer)) {\n out[serverName] = buildToolToggle({\n serverName,\n catalogNames: catalog.map(t => t.name),\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n })\n }\n return out\n }, [opts.catalogByServer, persistedDenyAll, setDisabled])\n}\n\n/**\n * Pure factory shared by both hook flavors. Captures `persistedDenyAll`\n * + `setDisabledMcpTools` at the call site so toggle handlers stay\n * referentially stable across renders that don't touch the underlying\n * settings entry — important for `useKeyboard` to avoid re-binding on\n * every catalog change.\n *\n * Exported for direct use by non-React consumers (CLI, GUI shells)\n * that want the same write semantics without the hook plumbing.\n *\n * @param args - Server name, the server's catalog tool names, the persisted deny-list for all servers and the setter that writes it.\n */\nexport function buildToolToggle(args: {\n serverName: string\n catalogNames: readonly string[]\n persistedDenyAll: Settings['disabledMcpTools']\n setDisabledMcpTools: (next: Settings['disabledMcpTools']) => void\n}): McpToolToggleSet {\n const { serverName, catalogNames, persistedDenyAll, setDisabledMcpTools } = args\n const persistedDeny = persistedDenyAll?.[serverName]\n const catalogSet = new Set(catalogNames)\n\n const enabledSet: ReadonlySet<string> = (() => {\n if (!persistedDeny || persistedDeny.length === 0)\n return new Set(catalogNames)\n const deny = new Set(persistedDeny)\n return new Set(catalogNames.filter(name => !deny.has(name)))\n })()\n\n const toggle = (toolName: string): void => {\n // Compute the next deny-list against the persisted state (NOT\n // `enabledSet`) so we never accidentally promote \"implicit-enabled-\n // because-no-entry\" tools into an explicit list on the first\n // toggle. GC stale names at the same time so the persisted file\n // tracks reality.\n const currentDeny = new Set(persistedDeny ?? [])\n if (currentDeny.has(toolName))\n currentDeny.delete(toolName)\n else\n currentDeny.add(toolName)\n\n const nextForServer = [...currentDeny]\n .filter(n => catalogSet.has(n))\n .sort()\n\n const prevMap = persistedDenyAll ?? {}\n const nextMap: Record<string, readonly string[]> = { ...prevMap }\n if (nextForServer.length === 0) {\n // Empty entry collapses back to \"no customization\" so the\n // persisted file stays tidy when a user re-enables every tool\n // they previously disabled.\n delete nextMap[serverName]\n }\n else {\n nextMap[serverName] = nextForServer\n }\n\n // Top-level `undefined` represents \"no per-tool prefs anywhere\".\n // Restore that when the last server entry empties out so the\n // diff against defaults stays minimal.\n const next: Settings['disabledMcpTools']\n = Object.keys(nextMap).length === 0 ? undefined : nextMap\n setDisabledMcpTools(next)\n }\n\n return { enabledSet, toggle }\n}\n","/**\n * Manual OAuth redirect-URL handoff.\n *\n * Users who run zidane over SSH (or behind a proxy / firewall that blocks\n * loopback) can't have the local browser hit the in-process callback server.\n * They CAN, however, copy the URL their browser was redirected to —\n * `http://127.0.0.1:<port>/callback?code=...&state=...` — and paste it back\n * into the TUI.\n *\n * The trick: that URL IS the callback our local server is listening on. We\n * just `fetch()` it ourselves. The server runs in the same process; the\n * request hits its handler exactly as if a real browser had arrived, the\n * OAuth promise (`waitForCode` for pi-ai providers, `startOAuthCallback` for\n * MCP) resolves through the normal happy path, and the upstream flow\n * continues uninterrupted. No new code path inside the OAuth state machine.\n *\n * Defense in depth: we reject anything that isn't a loopback URL — fetching\n * an arbitrary user-pasted URL from inside the agent process would be a\n * trivial SSRF.\n */\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]'])\n\n/**\n * The `redirect_uri` the authorization server was told to come back to,\n * read out of the authorization URL itself. That's our loopback callback\n * (`http://127.0.0.1:<port>/callback`) — the UI shows it so an SSH user\n * knows which address to fish out of the browser's URL bar, and\n * {@link normalizeRedirectPaste} uses it to rebuild a full callback URL\n * from a partial paste.\n *\n * Returns `null` when the URL doesn't parse or carries no usable\n * `redirect_uri` — callers degrade to \"paste the whole URL\".\n *\n * @param authUrl - The authorization URL to read.\n */\nexport function redirectUriFromAuthUrl(authUrl: string): URL | null {\n let parsed: URL\n try {\n parsed = new URL(authUrl)\n }\n catch {\n return null\n }\n const redirect = parsed.searchParams.get('redirect_uri')\n if (!redirect)\n return null\n try {\n return new URL(redirect)\n }\n catch {\n return null\n }\n}\n\n/**\n * Widen what counts as an acceptable paste. The happy path is the full\n * redirect URL out of the browser's address bar, but users on SSH\n * routinely end up holding less than that: some authorization servers\n * render the code on the page instead of redirecting, and copying from a\n * \"can't reach this site\" error page loses everything but the query.\n *\n * Accepted forms, given the authorization URL for context:\n *\n * - `http://127.0.0.1:5123/callback?code=x&state=y` — passthrough (any\n * absolute URL is returned untouched; the loopback guard in\n * {@link fetchOAuthRedirect} still has final say, so a pasted\n * `https://evil.test` is rejected there, not silently rewritten).\n * - `/callback?code=x&state=y` — path + query onto the callback origin.\n * - `?code=x&state=y` / `code=x&state=y` — query onto the callback URL.\n * - `x` — a bare authorization code; `state` is taken from the\n * authorization URL when it carried one (the loopback server forwards\n * `state` verbatim and the SDK owns verification, so a missing one is\n * not fatal). Anything with whitespace inside is prose (an error page\n * fragment), not a code — refused with a clearer error than a server\n * 400 would give.\n *\n * Throws when the input can't be turned into a URL — no `authUrl` context\n * for a partial paste, or an unusable authorization URL.\n *\n * @param pasted - What the user pasted: a full URL, a path, a query string or a bare code.\n * @param authUrl - The authorization URL on screen. Required to complete anything shorter than a full URL.\n */\nexport function normalizeRedirectPaste(pasted: string, authUrl?: string): string {\n const trimmed = pasted.trim()\n if (!trimmed)\n throw new Error('Paste the redirect URL from your browser.')\n // Absolute URL → caller's problem to validate. Checked by scheme rather\n // than `new URL()` success so a bare code like `abc` (which URL happily\n // rejects) and a scheme-ful `mcp://…` are told apart deterministically.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed))\n return trimmed\n\n const callback = authUrl ? redirectUriFromAuthUrl(authUrl) : null\n if (!callback) {\n throw new Error(\n 'That doesn\\'t look like a URL. Paste the full address your browser tried to reach (starting with http://127.0.0.1).',\n )\n }\n\n // `/callback?code=…` — keep the pasted path, it may differ from the one\n // we registered (a server that normalized a trailing slash, say). Same\n // outcome guard as the query branch below: a bare `/callback` carries no\n // OAuth result, and fetching it would only produce an opaque server 400\n // where a targeted message tells the user exactly what to re-copy.\n if (trimmed.startsWith('/')) {\n const merged = new URL(trimmed, callback)\n if (!merged.searchParams.has('code') && !merged.searchParams.has('error'))\n throw new Error('That path has no \"code\" parameter. Paste the full address from your browser\\'s URL bar.')\n return merged.toString()\n }\n\n const target = new URL(callback.toString())\n const query = trimmed.startsWith('?') ? trimmed.slice(1) : trimmed\n // A bare authorization code is a single URL-legal token — including\n // base64 padding, so trailing `=` stays code-like (`abc==`). Any OTHER\n // placement of `=` means key/value pairs (`foo=bar`, `state=…&code=…`):\n // treat as a query string, never as a code — URL-encoding `foo=bar` into\n // `code=foo%3Dbar` would fire a doomed request where a targeted error\n // tells the user what to re-copy.\n const codeLike = /^[^=\\s]+=*$/.test(query)\n if (!codeLike && query.includes('=')) {\n const params = new URLSearchParams(query)\n if (!params.has('code') && !params.has('error'))\n throw new Error('That fragment has no \"code\" parameter. Paste the full address from your browser, or just the code.')\n for (const [key, value] of params)\n target.searchParams.set(key, value)\n return target.toString()\n }\n\n // Bare code. Whitespace-bearing pastes never reach here (`codeLike`\n // excludes them) except as pure prose without `=`, so keep the explicit\n // reject for those: sending `code=access%20denied` to the callback\n // server would 400 without explaining anything.\n if (/\\s/.test(query))\n throw new Error('That doesn\\'t look like an authorization code. Paste the full address from your browser, or just the code itself.')\n target.searchParams.set('code', query)\n const state = new URL(authUrl!).searchParams.get('state')\n if (state)\n target.searchParams.set('state', state)\n return target.toString()\n}\n\n/** Outcome of calling the loopback callback server. */\nexport interface FetchOAuthRedirectResult {\n /** Status code from the callback server. 2xx = success. */\n status: number\n /** Best-effort message extracted from the response body (HTML title or trimmed text). */\n message?: string\n}\n\n/**\n * Treat `pasted` as a callback-URL paste. Validates it's a loopback URL,\n * fires a GET, returns the status.\n *\n * Pass `authUrl` (the authorization URL currently on screen) to accept\n * partial pastes — a bare code, a query string, a path — via\n * {@link normalizeRedirectPaste}. Without it only a full absolute URL is\n * accepted.\n *\n * Throws when:\n * - `pasted` doesn't parse as a URL (and can't be completed from `authUrl`).\n * - The URL host isn't loopback (rejects SSRF).\n * - The fetch errors out (network, timeout).\n *\n * Returns success/failure for non-2xx responses; the caller decides whether\n * a 4xx is fatal (state mismatch is a 400 the user can retry from).\n *\n * @param pasted - What the user pasted: a callback URL, or a partial one when `options.authUrl` is set.\n * @param options - Abort signal, request timeout in milliseconds (`5000` by default) and the authorization URL used to complete partial pastes.\n */\nexport async function fetchOAuthRedirect(\n pasted: string,\n options: { signal?: AbortSignal, timeoutMs?: number, authUrl?: string } = {},\n): Promise<FetchOAuthRedirectResult> {\n const trimmed = normalizeRedirectPaste(pasted, options.authUrl)\n\n let url: URL\n try {\n url = new URL(trimmed)\n }\n catch {\n throw new Error('That doesn\\'t look like a URL. Paste the full address from your browser.')\n }\n\n // Strip brackets that some browsers add around IPv6 hosts so the\n // comparison hits LOOPBACK_HOSTS regardless of textual form.\n const host = url.hostname.replace(/^\\[|\\]$/g, '')\n if (!LOOPBACK_HOSTS.has(host)) {\n throw new Error(\n `Expected a loopback URL (127.0.0.1 / localhost), got \"${url.hostname}\". The browser should have redirected to a localhost address.`,\n )\n }\n\n // Short timeout: the callback server is in-process, so any meaningful\n // delay means it's gone (closed by a prior abort) or never started.\n const timeoutMs = options.timeoutMs ?? 5000\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n options.signal?.addEventListener('abort', () => ac.abort(), { once: true })\n\n let response: Response\n try {\n response = await fetch(url.toString(), { signal: ac.signal })\n }\n catch (err) {\n if ((err as Error).name === 'AbortError')\n throw new Error('No response from the local callback server. Was the login cancelled?')\n throw new Error(`Could not reach the local callback server: ${(err as Error).message}`)\n }\n finally {\n clearTimeout(timer)\n }\n\n const bodyText = await response.text().catch(() => '')\n return { status: response.status, message: extractMessage(bodyText) }\n}\n\n/**\n * Pick the most useful one-line message out of the callback server's HTML\n * response. pi-ai uses `<h1>` for the heading; the MCP callback uses\n * a leading `<p>`. We try both, then fall back to a stripped snippet.\n */\nfunction extractMessage(html: string): string | undefined {\n if (!html)\n return undefined\n const h1 = /<h1[^>]*>([\\s\\S]*?)<\\/h1>/i.exec(html)?.[1]\n if (h1)\n return stripTags(h1).trim() || undefined\n const p = /<p[^>]*>([\\s\\S]*?)<\\/p>/i.exec(html)?.[1]\n if (p)\n return stripTags(p).trim() || undefined\n return undefined\n}\n\nfunction stripTags(s: string): string {\n return s.replace(/<[^>]*>/g, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '\"').replace(/'/g, '\\'')\n}\n","/**\n * @-completion path display formatter.\n *\n * The `@`-popover walks the project from the git root so the user sees\n * the whole tree regardless of where they launched the TUI. The\n * agent's CWD-resolving tools (`read_file`, `glob`, `edit`, …) read\n * relative paths against `process.cwd()`, which is frequently a\n * subdirectory of that root. Emitting the discovery's raw\n * project-root-relative path into the prompt produces a `<cwd>/<rel>`\n * miss — the bug this helper exists to close.\n *\n * Rule:\n *\n * - File at `cwd` itself → `.`.\n * - File under `cwd` (subtree) → forward-slashed CWD-relative,\n * no `..`.\n * - File above `cwd` but still inside the project → CWD-relative\n * with `..` traversal (e.g. `../EDIT_THIS.md`). Shorter and more\n * scannable than absolute, and the agent's tools resolve both.\n * - File outside the project (input was passed absolute) → returned\n * verbatim. Inputs that arrive as project-root-relative strings\n * are always inside the project by construction, so this branch\n * only kicks in when a direct caller hands the function an\n * absolute target.\n *\n * Pure, no I/O, deterministic.\n */\n\nimport { isAbsolute, posix, relative, resolve, sep } from 'node:path'\n\n/**\n * Convert a project-root-relative path (as emitted by `listProjectFiles`)\n * into the form the agent's tools — and the user — will actually find\n * given the current `cwd`.\n *\n * Inputs:\n * - `projectRelativePath` — forward-slashed, no leading slash. Returned\n * verbatim when it's empty or already absolute (defensive — the\n * absolute-input branch is the de-facto \"outside the project\"\n * escape; the catalog itself never emits absolute paths).\n * - `projectRoot` — absolute path of the discovery anchor.\n * - `cwd` — absolute path of `process.cwd()` at the moment of\n * insertion.\n *\n * Output is always either an absolute path (only when the input arrived\n * absolute) OR a CWD-relative path. The relative form may include\n * `../` segments when the target sits above `cwd` within the project,\n * which keeps cross-tree references short and scannable\n * (e.g. `../EDIT_THIS.md` instead of `/Users/.../zidane/EDIT_THIS.md`).\n *\n * @param projectRelativePath - Forward-slashed path relative to `projectRoot`.\n * @param projectRoot - Absolute path of the discovery anchor.\n * @param cwd - Absolute working directory the result is relative to.\n */\nexport function formatPathForCwd(\n projectRelativePath: string,\n projectRoot: string,\n cwd: string,\n): string {\n if (projectRelativePath.length === 0)\n return projectRelativePath\n if (isAbsolute(projectRelativePath))\n return projectRelativePath\n const target = resolve(projectRoot, projectRelativePath)\n const here = resolve(cwd)\n if (target === here)\n return '.'\n const rel = relative(here, target)\n if (rel.length === 0)\n return '.'\n return sep === '/' ? rel : rel.split(sep).join(posix.sep)\n}\n","/**\n * Subscription usage for the providers we authenticate via OAuth, plus\n * OpenRouter's credits endpoint.\n *\n * Probes are keyed by CREDENTIAL FILE KEY, so any descriptor storing its tokens\n * under a known key resolves the right one:\n * - `anthropic` `GET api.anthropic.com/api/oauth/usage`\n * (OAuth bearer + `anthropic-beta: oauth-2025-04-20`, and\n * the `user:profile` scope the login flow requests)\n * - `openai-codex` `GET chatgpt.com/backend-api/wham/usage`\n * (Codex OAuth bearer + `ChatGPT-Account-Id`)\n * - `xai-oauth` `POST grok.com` gRPC-web billing probe. Best-effort: xAI\n * has no token-auth usage API and this endpoint is\n * cookie-oriented, so it often refuses a bearer.\n * - `openrouter` `GET openrouter.ai/api/v1/credits` (official, API key)\n *\n * A provider without a built-in probe brings its own\n * (`ProviderDescriptor.usage`, e.g. an extension's), and runs through the same\n * renewal, cache and failure handling.\n *\n * Only the OpenRouter endpoint is official; the others can change without\n * notice. So every probe is read-only and never throws: failures normalize into\n * a snapshot with `status: 'unauthorized' | 'unavailable'` and a short `error`,\n * which callers render as a state rather than handle as an error.\n *\n * An expired access token is refreshed through the run path's own helper\n * (`resolveOAuthApiKey` in `providers/oauth.ts`), targeting the exact slot:\n * same cross-process lock, same in-process coalescing, same generation check\n * before persisting. Refresh tokens are single-use, so this is the only safe\n * way to do it. It also matters for parked accounts: a run never uses them,\n * so nothing else would renew them and they would read `token expired` for\n * good. A failed refresh reports `'unauthorized'` without issuing the usage\n * request.\n *\n * Consent is two-tiered, mirroring the update check: hosts gate automatic\n * probes on `Settings.subscriptionUsage`, and {@link usageChecksOptedOut}\n * (`ZIDANE_NO_USAGE_CHECK` / `CI`) disables every probe here, explicit\n * refreshes included.\n */\n\nimport type { ProviderCredential } from './credentials'\nimport type { ProviderUsageSnapshot, ProviderUsageWindow } from './provider-usage-format'\nimport type { ProviderDescriptor } from './providers'\nimport { resolveOAuthApiKey } from '../providers/oauth'\nimport { credentialsPath, oauthAccountLabel, readCredentials, storedCredentialsOf } from './credentials'\nimport { envOptedOut } from './env-opt-out'\nimport { credKeyOf } from './providers'\n\n// Wire types + presentation helpers live in the node-free sibling so the\n// Electron renderer can share them; re-exported here to keep one import path.\nexport type { ProviderUsageCredits, ProviderUsageSnapshot, ProviderUsageStatus, ProviderUsageWindow } from './provider-usage-format'\nexport { activeSnapshotFor, formatBalance, formatCredits, formatUsagePercent, mostConstrainedWindow, resetLabel, showsAccount, updatedLabel, type UsageTone, usageTone } from './provider-usage-format'\n\n/** Per-request timeout. Generous, since these endpoints are occasionally slow. */\nconst FETCH_TIMEOUT_MS = 15_000\n\n/** How long a snapshot stays fresh. Usage windows move in minutes, not seconds. */\nexport const USAGE_CACHE_TTL_MS = 60_000\n/** TTL after a failed probe kept the previous good snapshot: retry soon, not in a minute. */\nconst USAGE_RETRY_TTL_MS = 10_000\n\n/** Which probe a credential key maps to. `descriptor`: the provider's own (`ProviderDescriptor.usage`). */\ntype UsageSource = 'claude' | 'codex' | 'grok' | 'openrouter' | 'descriptor'\ntype BuiltinUsageSource = Exclude<UsageSource, 'descriptor'>\n\n/**\n * Credential-file key to probe. Keyed by credential key rather than provider\n * key so a custom descriptor reusing a builtin credential slot\n * (`credentialFileKey: 'anthropic'`) gets usage for free.\n */\nconst USAGE_SOURCE_BY_CRED_KEY: Readonly<Record<string, BuiltinUsageSource>> = {\n 'anthropic': 'claude',\n 'openai-codex': 'codex',\n 'xai-oauth': 'grok',\n 'openrouter': 'openrouter',\n}\n\n/** Credential kind each probe authenticates with. */\nconst SOURCE_CRED_KIND: Readonly<Record<BuiltinUsageSource, ProviderCredential['kind']>> = {\n claude: 'oauth',\n codex: 'oauth',\n grok: 'oauth',\n openrouter: 'apikey',\n}\n\n/**\n * The probe for a descriptor, or `null` when it has no usage surface.\n *\n * @param descriptor - The provider to look up.\n */\nexport function usageSourceFor(descriptor: ProviderDescriptor): UsageSource | null {\n return USAGE_SOURCE_BY_CRED_KEY[credKeyOf(descriptor)] ?? (typeof descriptor.usage?.fetch === 'function' ? 'descriptor' : null)\n}\n\n/** Credential kind a probe authenticates with. */\nfunction credentialKindOf(source: UsageSource, descriptor: ProviderDescriptor): ProviderCredential['kind'] {\n return source === 'descriptor' ? (descriptor.usage?.credentialKind ?? 'oauth') : SOURCE_CRED_KIND[source]\n}\n\n/**\n * Whether this provider can report subscription usage at all.\n *\n * @param descriptor - The provider to check.\n */\nexport function providerUsageSupported(descriptor: ProviderDescriptor): boolean {\n return usageSourceFor(descriptor) !== null\n}\n\n// ---------------------------------------------------------------------------\n// Cache + entry points\n// ---------------------------------------------------------------------------\n\ninterface CacheEntry {\n expiresAt: number\n snapshot: ProviderUsageSnapshot\n}\n\n/** Keyed by credential slot + token: two descriptors sharing tokens share a snapshot. */\nconst cache = new Map<string, CacheEntry>()\n/** In-flight probes, so a footer read and a panel open share one request. */\nconst inFlight = new Map<string, Promise<ProviderUsageSnapshot>>()\n/**\n * Newest probe issued per cache key, as a monotonic ticket. `refresh`\n * skips both maps above, so two probes for one key can be outstanding at once\n * (panel refresh while the footer's post-run refresh is still in flight); only\n * the newest may write the cache, or a slow older probe would pin stale\n * numbers for a full TTL. Clearing the map also invalidates every outstanding\n * ticket, so a probe that spans `clearProviderUsageCache()` cannot repopulate\n * the cache it was meant to drop.\n */\nconst latestProbe = new Map<string, number>()\nlet probeTicket = 0\n\n/**\n * Drop every cached snapshot. Entries are keyed by credential KEY, not by the\n * token itself, so signing out and back in (or re-authing to another account)\n * would otherwise keep serving the previous account's numbers for the rest of\n * the TTL. The TUI calls this when it re-reads credentials.\n */\nexport function clearProviderUsageCache(): void {\n cache.clear()\n inFlight.clear()\n latestProbe.clear()\n}\n\n// ---------------------------------------------------------------------------\n// Env opt-outs\n//\n// Same shape as the update check's (see `chat/auto-update.ts`): these probes\n// are the only other background network calls the TUI makes, and they talk to\n// hosts that are not the ones inference goes to, so an air-gapped, CI, or\n// privacy-sensitive environment must be able to switch them off without\n// touching settings. `ZIDANE_NO_USAGE_CHECK` is the project knob; `CI` is set\n// by every major runner and skipping there avoids surprise chatter from build\n// pipelines. Unlike the setting, this is absolute: it also blocks the panel's\n// explicit refresh.\n// ---------------------------------------------------------------------------\n\nconst OPT_OUT_VARS = ['ZIDANE_NO_USAGE_CHECK', 'CI'] as const\n\n/**\n * Whether the environment forbids usage probes outright.\n *\n * @param env - Environment to read. Opt-out variables are `ZIDANE_NO_USAGE_CHECK` and `CI`.\n */\nexport function usageChecksOptedOut(env: NodeJS.ProcessEnv = process.env): boolean {\n return envOptedOut(OPT_OUT_VARS, env)\n}\n\n/** Options for the provider usage fetchers. */\nexport interface FetchProviderUsageOptions {\n /** Ignore (and replace) a still-fresh cached snapshot. */\n refresh?: boolean\n}\n\n/** One credential a probe can authenticate with, and the slot it came from. */\ninterface UsageCredential {\n /** Credentials-file key (`anthropic`, `anthropic#account:1`, …). */\n key: string\n cred: ProviderCredential\n /** Canonical slot, i.e. the credential requests use. */\n active: boolean\n}\n\n/**\n * Usage for one provider, probed with the credential requests use — or, when\n * that one is not the kind the probe needs (an API key in use, OAuth accounts\n * parked beside it), the first stored one that is. What the footer shows.\n *\n * `null` means there is nothing to show: the provider has no usage surface,\n * no credential of the kind its probe needs (file or, for API keys, env), or\n * the environment opted out (see {@link usageChecksOptedOut}).\n *\n * Never rejects. Transport and parse failures land in the snapshot.\n *\n * @param dataDir - Directory that holds the credentials files.\n * @param descriptor - The provider to probe.\n * @param opts - Fetch options, such as forcing a refresh.\n */\nexport async function fetchProviderUsage(\n dataDir: string,\n descriptor: ProviderDescriptor,\n opts: FetchProviderUsageOptions = {},\n): Promise<ProviderUsageSnapshot | null> {\n const source = usageSourceFor(descriptor)\n if (!source || usageChecksOptedOut())\n return null\n const [preferred] = resolveUsageCredentials(dataDir, descriptor, source)\n if (!preferred)\n return null\n return probeRenewed(dataDir, source, descriptor, preferred, opts)\n}\n\n/**\n * Usage for EVERY credential of a provider the probe can use — one snapshot\n * per stored OAuth account, the active one first. What the panel renders.\n * Empty under the same conditions {@link fetchProviderUsage} returns `null`.\n *\n * @param dataDir - Directory that holds the credentials files.\n * @param descriptor - The provider to probe.\n * @param opts - Fetch options, such as forcing a refresh.\n */\nexport async function fetchProviderUsageAccounts(\n dataDir: string,\n descriptor: ProviderDescriptor,\n opts: FetchProviderUsageOptions = {},\n): Promise<readonly ProviderUsageSnapshot[]> {\n const source = usageSourceFor(descriptor)\n if (!source || usageChecksOptedOut())\n return []\n const credentials = resolveUsageCredentials(dataDir, descriptor, source)\n return Promise.all(credentials.map(c => probeRenewed(dataDir, source, descriptor, c, opts)))\n}\n\n/**\n * {@link probeCached}, after renewing an expired OAuth token in its slot.\n * Stays synchronous up to the request when no renewal is needed, so callers\n * that start two probes back to back still see both requests issued at once.\n */\nfunction probeRenewed(\n dataDir: string,\n source: UsageSource,\n descriptor: ProviderDescriptor,\n credential: UsageCredential,\n opts: FetchProviderUsageOptions,\n): Promise<ProviderUsageSnapshot> {\n if (!needsRenewal(credential.cred))\n return probeCached(source, descriptor, credential, opts)\n return renewExpired(dataDir, descriptor, credential).then(renewed =>\n 'status' in renewed\n ? stampIdentity(renewed, descriptor, credential)\n : probeCached(source, descriptor, renewed, opts))\n}\n\n/**\n * An expired OAuth token that carries a refresh token. One without cannot be\n * renewed and reports `token expired` from the probe itself.\n */\nfunction needsRenewal(cred: ProviderCredential): boolean {\n return cred.kind === 'oauth' && typeof cred.expires === 'number' && cred.expires <= Date.now() && !!cred.refresh\n}\n\n/**\n * Rotate an expired OAuth token through the run path's refresh helper, keyed\n * to this credential's own slot, and hand back the credential as now stored.\n * A failed refresh becomes an `unauthorized` snapshot.\n */\nasync function renewExpired(\n dataDir: string,\n descriptor: ProviderDescriptor,\n credential: UsageCredential,\n): Promise<UsageCredential | ProviderUsageSnapshot> {\n try {\n await resolveOAuthApiKey({\n provider: descriptor.key,\n providerId: credKeyOf(descriptor),\n credentialKey: credential.key,\n credentialsPath: credentialsPath(dataDir),\n missingError: 'not signed in',\n refreshError: reason => reason,\n })\n }\n catch {\n // The reason is already surfaced by the next run (its refresh fails the\n // same way, with the full message); the panel just needs the state.\n return snapshot(\n { providerKey: descriptor.key, label: descriptor.label },\n { status: 'unauthorized', error: 'refresh failed' },\n )\n }\n // Re-read rather than trust a callback: another process may have rotated\n // the slot moments earlier, in which case the helper found a fresh token\n // and fired no refresh event, and the file is the only source of truth.\n const stored = readCredentials(dataDir)[credential.key]\n return stored?.kind === 'oauth' ? { ...credential, cred: stored } : credential\n}\n\n/** Probe one credential through the cache / in-flight coalescing. */\nfunction probeCached(\n source: UsageSource,\n descriptor: ProviderDescriptor,\n credential: UsageCredential,\n opts: FetchProviderUsageOptions,\n): Promise<ProviderUsageSnapshot> {\n // Keyed by the token, not the slot: promoting a parked account moves tokens\n // between slots, and a slot-keyed cache would then serve one account's\n // numbers under the other's name for the rest of the TTL.\n const cacheKey = `${credential.key}\\0${credential.cred.kind === 'oauth' ? credential.cred.access : credential.cred.value}`\n // Identity is re-stamped on every return path: a snapshot can come back from\n // a probe another descriptor started, and it must still name the provider\n // (and slot) the caller asked about.\n const identify = (snapshot: ProviderUsageSnapshot): ProviderUsageSnapshot => stampIdentity(snapshot, descriptor, credential)\n\n if (!opts.refresh) {\n const cached = cache.get(cacheKey)\n if (cached && cached.expiresAt > Date.now())\n return Promise.resolve(identify(cached.snapshot))\n const pending = inFlight.get(cacheKey)\n if (pending)\n return pending.then(identify)\n }\n\n const ticket = ++probeTicket\n latestProbe.set(cacheKey, ticket)\n const previous = cache.get(cacheKey)?.snapshot\n const probe = runProbe(source, descriptor, credential.cred)\n .then((fresh) => {\n // A transient failure (timeout, 5xx, parse error) must not replace\n // numbers that were fine a minute ago: the panel would flap between\n // real windows and \"unavailable\" on every hiccup. Keep the last good\n // snapshot, but only for a short while so the next probe retries soon.\n // `unauthorized` is a real state change and always shows.\n const keepPrevious = fresh.status === 'unavailable' && previous?.status === 'ok'\n const snapshot = keepPrevious ? previous : fresh\n if (latestProbe.get(cacheKey) === ticket)\n cache.set(cacheKey, { expiresAt: Date.now() + (keepPrevious ? USAGE_RETRY_TTL_MS : USAGE_CACHE_TTL_MS), snapshot })\n return snapshot\n })\n .finally(() => {\n if (inFlight.get(cacheKey) === probe)\n inFlight.delete(cacheKey)\n })\n inFlight.set(cacheKey, probe)\n return probe.then(identify)\n}\n\n/** Name the provider, slot, and account a snapshot belongs to. */\nfunction stampIdentity(snapshot: ProviderUsageSnapshot, descriptor: ProviderDescriptor, credential: UsageCredential): ProviderUsageSnapshot {\n return {\n ...snapshot,\n providerKey: descriptor.key,\n label: descriptor.label,\n credentialKey: credential.key,\n ...(typeof credential.cred.id === 'string' && credential.cred.id ? { credentialId: credential.cred.id } : {}),\n account: credential.cred.kind === 'oauth' ? oauthAccountLabel(credential.cred) : null,\n activeCredential: credential.active,\n }\n}\n\n/**\n * Every credential a probe can authenticate with, active slot first, then in\n * storage order. Empty when the provider isn't authenticated in a way the\n * probe can use.\n *\n * An API key is taken from the environment when the file has none: `detectAuth`\n * marks a provider available on a shell-set `envKey` alone, and the launcher\n * copies stored keys INTO env, so the two are the same signal. Reading only the\n * file would leave an env-authed user with no row at all, under an empty state\n * telling them they aren't signed in. OAuth tokens have no env equivalent, so\n * those probes stay file-only.\n */\nfunction resolveUsageCredentials(\n dataDir: string,\n descriptor: ProviderDescriptor,\n source: UsageSource,\n): UsageCredential[] {\n const kind = credentialKindOf(source, descriptor)\n const credKey = credKeyOf(descriptor)\n // A stored API key with an empty value is a customFields-only credential\n // (see `ApiKeyCredential.value`), so fall through to env rather than probing\n // with nothing.\n const stored = storedCredentialsOf(readCredentials(dataDir), credKey)\n .filter(s => s.credential.kind === kind && (s.credential.kind !== 'apikey' || s.credential.value))\n .map(s => ({ key: s.key, cred: s.credential, active: s.active }))\n if (stored.length > 0)\n return stored\n if (kind === 'apikey' && descriptor.envKey) {\n const fromEnv = process.env[descriptor.envKey]\n if (fromEnv)\n return [{ key: credKey, cred: { kind: 'apikey', value: fromEnv }, active: true }]\n }\n return []\n}\n\n/**\n * Usage for every registered provider that has both a usage surface and a\n * usable credential, in registry order, every account included.\n *\n * @param dataDir - Directory that holds the credentials files.\n * @param registry - Provider descriptors keyed by provider key.\n * @param opts - Fetch options, such as forcing a refresh.\n */\nexport async function fetchAllProviderUsage(\n dataDir: string,\n registry: Readonly<Record<string, ProviderDescriptor>>,\n opts: FetchProviderUsageOptions = {},\n): Promise<readonly ProviderUsageSnapshot[]> {\n const candidates = Object.values(registry).filter(providerUsageSupported)\n const settled = await Promise.all(\n candidates.map(descriptor => fetchProviderUsageAccounts(dataDir, descriptor, opts)),\n )\n return settled.flat()\n}\n\n// ---------------------------------------------------------------------------\n// Probes\n// ---------------------------------------------------------------------------\n\nasync function runProbe(\n source: UsageSource,\n descriptor: ProviderDescriptor,\n cred: ProviderCredential,\n): Promise<ProviderUsageSnapshot> {\n const base = { providerKey: descriptor.key, label: descriptor.label }\n try {\n switch (source) {\n case 'claude':\n return await fetchClaudeUsage(base, cred)\n case 'codex':\n return await fetchCodexUsage(base, cred)\n case 'grok':\n return await fetchGrokUsage(base, cred)\n case 'openrouter':\n return await fetchOpenRouterUsage(base, cred)\n case 'descriptor':\n return await fetchDescriptorUsage(base, descriptor, cred)\n }\n }\n catch (err) {\n // Covers `AbortSignal.timeout` firing. A slow endpoint is unavailable, not\n // an error the caller should handle.\n return snapshot(base, { error: err instanceof Error ? err.message : 'request failed' })\n }\n}\n\n/** Identity half of a snapshot, resolved once per probe. */\ntype SnapshotBase = Pick<ProviderUsageSnapshot, 'providerKey' | 'label'>\n\n/**\n * Snapshot factory. Defaults to `unavailable` so failure paths stay one-liners.\n * The credential identity fields are placeholders here; `probeCached` stamps\n * the real ones on every return path.\n */\nfunction snapshot(base: SnapshotBase, partial: Partial<ProviderUsageSnapshot> = {}): ProviderUsageSnapshot {\n return {\n ...base,\n credentialKey: base.providerKey,\n account: null,\n activeCredential: true,\n status: 'unavailable',\n plan: null,\n windows: [],\n credits: null,\n fetchedAt: Date.now(),\n error: null,\n ...partial,\n }\n}\n\n/**\n * OAuth access token, or an `unauthorized` snapshot explaining why there isn't\n * one. Expiry is checked without refreshing: `probeRenewed` already renewed\n * any expired token that carries a refresh token, so an expired one here has\n * none.\n */\nfunction accessTokenOr(\n base: SnapshotBase,\n cred: ProviderCredential,\n): { token: string } | { fail: ProviderUsageSnapshot } {\n if (cred.kind !== 'oauth' || !cred.access)\n return { fail: snapshot(base, { status: 'unauthorized', error: 'not signed in' }) }\n if (typeof cred.expires === 'number' && cred.expires <= Date.now())\n return { fail: snapshot(base, { status: 'unauthorized', error: 'token expired' }) }\n return { token: cred.access }\n}\n\n/**\n * 401/403 to unauthorized, other non-2xx to unavailable. `null` when the\n * response is fine.\n *\n * A rejected token reads in the same register as the local checks (`not signed\n * in`, `token expired`): it is the row a real user hits most, and the status\n * code tells them nothing. Other codes keep the number, where it is the useful\n * detail.\n */\nfunction httpFailure(base: SnapshotBase, res: Response): ProviderUsageSnapshot | null {\n if (res.status === 401 || res.status === 403)\n return snapshot(base, { status: 'unauthorized', error: 'token rejected' })\n if (!res.ok)\n return snapshot(base, { error: `HTTP ${res.status}` })\n return null\n}\n\n// --- A provider's own probe ------------------------------------------------\n\n/**\n * Run `ProviderDescriptor.usage` (extension code: its answer is checked,\n * and a probe ignoring its signal still yields at the deadline).\n */\nasync function fetchDescriptorUsage(base: SnapshotBase, descriptor: ProviderDescriptor, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n if (cred.kind === 'oauth') {\n const token = accessTokenOr(base, cred)\n if ('fail' in token)\n return token.fail\n }\n const probe = descriptor.usage!\n const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS)\n const deadline = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => reject(new Error('usage probe timed out')), { once: true })\n })\n const report = await Promise.race([probe.fetch(cred, signal), deadline])\n const status = report?.status === 'unauthorized' || report?.status === 'unavailable' ? report.status : 'ok'\n const windows = (Array.isArray(report?.windows) ? report.windows : [])\n .filter(w => w && typeof w.id === 'string' && typeof w.label === 'string' && Number.isFinite(w.usedPercent))\n .map(w => ({\n id: w.id,\n label: w.label,\n usedPercent: clampPercent(w.usedPercent),\n resetsAt: typeof w.resetsAt === 'number' && Number.isFinite(w.resetsAt) ? w.resetsAt : null,\n }))\n return snapshot(base, {\n status,\n plan: typeof report?.plan === 'string' ? report.plan : null,\n windows,\n credits: report?.credits && Number.isFinite(report.credits.remaining) ? report.credits : null,\n error: typeof report?.error === 'string' ? report.error : null,\n ...(typeof report?.note === 'string' && report.note ? { note: report.note } : {}),\n })\n}\n\n// --- Claude ----------------------------------------------------------------\n\ninterface ClaudeWindow { utilization?: number, resets_at?: string | null }\n\nconst CLAUDE_WINDOWS: readonly { key: string, id: string, label: string }[] = [\n { key: 'five_hour', id: 'five_hour', label: '5h' },\n { key: 'seven_day', id: 'seven_day', label: 'weekly' },\n { key: 'seven_day_opus', id: 'seven_day_opus', label: 'opus weekly' },\n { key: 'seven_day_sonnet', id: 'seven_day_sonnet', label: 'sonnet weekly' },\n]\n\nasync function fetchClaudeUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n const auth = accessTokenOr(base, cred)\n if ('fail' in auth)\n return auth.fail\n\n const res = await fetch('https://api.anthropic.com/api/oauth/usage', {\n headers: {\n 'Authorization': `Bearer ${auth.token}`,\n 'anthropic-beta': 'oauth-2025-04-20',\n 'Accept': 'application/json',\n },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n const data = await res.json().catch(() => null) as Record<string, unknown> | null\n if (!data)\n return snapshot(base, { error: 'invalid response' })\n\n const windows: ProviderUsageWindow[] = []\n for (const { key, id, label } of CLAUDE_WINDOWS) {\n const win = data[key] as ClaudeWindow | undefined\n if (win && typeof win.utilization === 'number') {\n windows.push({\n id,\n label,\n usedPercent: clampPercent(win.utilization),\n resetsAt: isoToEpoch(win.resets_at),\n })\n }\n }\n if (windows.length === 0)\n return snapshot(base, { error: 'no usage data' })\n return snapshot(base, { status: 'ok', windows })\n}\n\n// --- OpenAI Codex ----------------------------------------------------------\n\ninterface CodexWindow { used_percent?: number, reset_at?: number, limit_window_seconds?: number }\n\n/** Codex reports window length, not a name, so derive `5h` / `weekly` / `Nd`. */\nfunction codexWindowLabel(win: CodexWindow, fallback: string): string {\n const seconds = win.limit_window_seconds\n if (!seconds)\n return fallback\n if (seconds < 86_400)\n return `${Math.round(seconds / 3600)}h`\n const days = Math.round(seconds / 86_400)\n return days === 7 ? 'weekly' : `${days}d`\n}\n\nasync function fetchCodexUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n const auth = accessTokenOr(base, cred)\n if ('fail' in auth)\n return auth.fail\n const accountId = cred.kind === 'oauth' && typeof cred.accountId === 'string' ? cred.accountId : null\n\n const res = await fetch('https://chatgpt.com/backend-api/wham/usage', {\n headers: {\n 'Authorization': `Bearer ${auth.token}`,\n ...(accountId ? { 'ChatGPT-Account-Id': accountId } : {}),\n 'User-Agent': 'codex-cli',\n 'Accept': 'application/json',\n },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n const data = await res.json().catch(() => null) as {\n plan_type?: string\n rate_limit?: { primary_window?: CodexWindow, secondary_window?: CodexWindow }\n /** Some responses use the plural key. Both are accepted. */\n rate_limits?: { primary_window?: CodexWindow, secondary_window?: CodexWindow }\n credits?: { has_credits?: boolean, unlimited?: boolean, balance?: number }\n } | null\n if (!data)\n return snapshot(base, { error: 'invalid response' })\n\n const rateLimit = data.rate_limit ?? data.rate_limits\n const windows: ProviderUsageWindow[] = []\n const push = (win: CodexWindow | undefined, id: string, fallbackLabel: string) => {\n if (win && typeof win.used_percent === 'number') {\n windows.push({\n id,\n label: codexWindowLabel(win, fallbackLabel),\n usedPercent: clampPercent(win.used_percent),\n resetsAt: typeof win.reset_at === 'number' ? win.reset_at * 1000 : null,\n })\n }\n }\n push(rateLimit?.primary_window, 'primary', '5h')\n push(rateLimit?.secondary_window, 'secondary', 'weekly')\n\n const credits = data.credits && typeof data.credits.balance === 'number' && !data.credits.unlimited\n ? { remaining: data.credits.balance, total: null }\n : null\n\n if (windows.length === 0 && !credits)\n return snapshot(base, { error: 'no usage data' })\n return snapshot(base, {\n status: 'ok',\n plan: typeof data.plan_type === 'string' ? data.plan_type : null,\n windows,\n credits,\n })\n}\n\n// --- OpenRouter ------------------------------------------------------------\n\nasync function fetchOpenRouterUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n if (cred.kind !== 'apikey' || !cred.value)\n return snapshot(base, { status: 'unauthorized', error: 'no API key stored' })\n\n const res = await fetch('https://openrouter.ai/api/v1/credits', {\n headers: { Authorization: `Bearer ${cred.value}`, Accept: 'application/json' },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n const body = await res.json().catch(() => null) as { data?: { total_credits?: number, total_usage?: number } } | null\n const totals = body?.data\n if (!totals || typeof totals.total_credits !== 'number' || typeof totals.total_usage !== 'number')\n return snapshot(base, { error: 'invalid response' })\n\n const usedPercent = totals.total_credits > 0\n ? clampPercent((totals.total_usage / totals.total_credits) * 100)\n : 0\n return snapshot(base, {\n status: 'ok',\n windows: [{ id: 'credits', label: 'credits', usedPercent, resetsAt: null }],\n credits: { remaining: totals.total_credits - totals.total_usage, total: totals.total_credits },\n })\n}\n\n// --- Grok ------------------------------------------------------------------\n\nconst GROK_BILLING_URL = 'https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig'\nconst GROK_AUTH_FAILURE_RE = /access token|unauthenticated|expired/i\n\nasync function fetchGrokUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n const auth = accessTokenOr(base, cred)\n if ('fail' in auth)\n return auth.fail\n\n const res = await fetch(GROK_BILLING_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${auth.token}`,\n 'Content-Type': 'application/grpc-web+proto',\n 'x-grpc-web': '1',\n 'x-user-agent': 'connect-es/2.1.1',\n 'Origin': 'https://grok.com',\n 'Referer': 'https://grok.com/?_s=usage',\n 'Accept': '*/*',\n },\n // Empty gRPC-web frame: flags byte + u32 length 0.\n body: new Uint8Array([0, 0, 0, 0, 0]),\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n // gRPC carries its own status out-of-band, so HTTP 200 with `grpc-status: 16`\n // is an auth rejection. That is the common answer for a bearer here.\n const grpcStatus = res.headers.get('grpc-status')\n if (grpcStatus && grpcStatus !== '0') {\n const message = res.headers.get('grpc-message') ?? ''\n const unauthorized = grpcStatus === '16' || (grpcStatus === '7' && GROK_AUTH_FAILURE_RE.test(message))\n // Plain words for the expected case, the raw status for the rest: this\n // endpoint wants a session cookie, so a rejected bearer is routine, while\n // any other status is worth reporting verbatim.\n return unauthorized\n ? snapshot(base, { status: 'unauthorized', error: 'token not accepted' })\n : snapshot(base, { error: `gRPC ${grpcStatus}${message ? `: ${message}` : ''}` })\n }\n\n const parsed = parseGrokBillingResponse(new Uint8Array(await res.arrayBuffer()))\n if (!parsed)\n return snapshot(base, { error: 'unreadable response' })\n return snapshot(base, {\n status: 'ok',\n windows: [{\n id: 'credits',\n label: 'credits',\n usedPercent: clampPercent(parsed.usedPercent),\n resetsAt: parsed.resetsAt ? parsed.resetsAt.getTime() : null,\n }],\n })\n}\n\n// ---------------------------------------------------------------------------\n// Grok payload: tolerant protobuf scan\n//\n// The billing response is a protobuf whose schema we don't have. Rather than\n// vendor a generated client for an endpoint xAI can change at will, walk the\n// wire format and recover the two fields we need by shape. A wrong-shaped\n// payload yields `null`, which surfaces as an `unavailable` row.\n// ---------------------------------------------------------------------------\n\ninterface ScannedField { path: number[], order: number, value: number }\ninterface ProtobufScan { fixed32: ScannedField[], varint: ScannedField[] }\n\n/** Split a gRPC-web body into data frames. Flag bit 0x80 marks trailers. */\nexport function grpcWebDataFrames(bytes: Uint8Array): Uint8Array[] {\n const frames: Uint8Array[] = []\n let index = 0\n while (index < bytes.length) {\n if (index + 5 > bytes.length)\n return []\n const flags = bytes[index]!\n const length = (bytes[index + 1]! << 24) | (bytes[index + 2]! << 16) | (bytes[index + 3]! << 8) | bytes[index + 4]!\n const start = index + 5\n const end = start + length\n if (length < 0 || end > bytes.length)\n return []\n if ((flags & 0x80) === 0)\n frames.push(bytes.subarray(start, end))\n index = end\n }\n return frames\n}\n\nfunction looksLikeProtobuf(bytes: Uint8Array): boolean {\n if (bytes.length === 0)\n return false\n const fieldNumber = bytes[0]! >> 3\n const wireType = bytes[0]! & 0x07\n return fieldNumber > 0 && (wireType === 0 || wireType === 1 || wireType === 2 || wireType === 5)\n}\n\nfunction readVarint(bytes: Uint8Array, offset: number): { value: number, next: number } | null {\n let value = 0\n let shift = 0\n let index = offset\n while (index < bytes.length && shift < 64) {\n const byte = bytes[index]!\n // Float math is fine here: the values we care about (epoch seconds, small\n // enums) sit far below 2^53.\n value += (byte & 0x7F) * 2 ** shift\n index += 1\n if ((byte & 0x80) === 0)\n return { value, next: index }\n shift += 7\n }\n return null\n}\n\nfunction scanProtobuf(bytes: Uint8Array, path: number[], scan: ProtobufScan, orderRef: { n: number }, depth: number): void {\n if (depth > 6)\n return\n let index = 0\n while (index < bytes.length) {\n const tag = readVarint(bytes, index)\n if (!tag)\n return\n const fieldNumber = Math.floor(tag.value / 8)\n const wireType = tag.value % 8\n if (fieldNumber === 0)\n return\n index = tag.next\n const fieldPath = [...path, fieldNumber]\n switch (wireType) {\n case 0: { // varint\n const v = readVarint(bytes, index)\n if (!v)\n return\n scan.varint.push({ path: fieldPath, order: orderRef.n++, value: v.value })\n index = v.next\n break\n }\n case 1: { // fixed64, nothing we need is a double\n if (index + 8 > bytes.length)\n return\n index += 8\n break\n }\n case 5: { // fixed32 (float), where the used percent lives\n if (index + 4 > bytes.length)\n return\n const view = new DataView(bytes.buffer, bytes.byteOffset + index, 4)\n scan.fixed32.push({ path: fieldPath, order: orderRef.n++, value: view.getFloat32(0, true) })\n index += 4\n break\n }\n case 2: { // length-delimited, recurse when it looks like a nested message\n const len = readVarint(bytes, index)\n if (!len || len.next + len.value > bytes.length)\n return\n const sub = bytes.subarray(len.next, len.next + len.value)\n if (looksLikeProtobuf(sub))\n scanProtobuf(sub, fieldPath, scan, orderRef, depth + 1)\n index = len.next + len.value\n break\n }\n default:\n return // groups and unknown wire types: bail rather than misparse\n }\n }\n}\n\n/**\n * Recover `{ usedPercent, resetsAt }` from the billing protobuf without a\n * schema. The used percent is a fixed32 float in 0-100 whose path ends in field\n * 1 (shallowest, earliest wins); the reset is the nearest future epoch-second\n * varint, preferring path `[1,5,1]` (billing-cycle end).\n */\nexport function parseGrokBillingResponse(\n bytes: Uint8Array,\n now = new Date(),\n): { usedPercent: number, resetsAt: Date | null } | null {\n let frames = grpcWebDataFrames(bytes)\n if (frames.length === 0 && looksLikeProtobuf(bytes))\n frames = [bytes]\n if (frames.length === 0)\n return null\n\n const scan: ProtobufScan = { fixed32: [], varint: [] }\n const orderRef = { n: 0 }\n for (const frame of frames)\n scanProtobuf(frame, [], scan, orderRef, 0)\n\n const percentField = scan.fixed32\n .filter(f => f.path.at(-1) === 1 && Number.isFinite(f.value) && f.value >= 0 && f.value <= 100)\n .sort((a, b) => a.path.length === b.path.length ? a.order - b.order : a.path.length - b.path.length)[0]\n\n const epochFields = scan.varint\n .filter(f => f.value >= 1_700_000_000 && f.value <= 2_100_000_000)\n .map(f => ({ path: f.path, date: new Date(f.value * 1000) }))\n .filter(f => f.date > now)\n const preferred = epochFields.filter(\n f => f.path.length === 3 && f.path[0] === 1 && f.path[1] === 5 && f.path[2] === 1,\n )\n const resetsAt = (preferred.length > 0 ? preferred : epochFields)\n .map(f => f.date)\n .sort((a, b) => a.getTime() - b.getTime())[0] ?? null\n\n // Zero-usage periods omit the proto3 default percent field, so a billing\n // period with a future reset reads as 0% used.\n const hasUsagePeriod = scan.varint.some(f =>\n (f.path.length >= 2 && f.path[0] === 1 && f.path[1] === 6)\n || (f.path.length === 3 && f.path[0] === 1 && f.path[1] === 8 && f.path[2] === 1 && (f.value === 1 || f.value === 2)),\n )\n if (!percentField) {\n if (scan.fixed32.length === 0 && resetsAt && hasUsagePeriod)\n return { usedPercent: 0, resetsAt }\n return null\n }\n return { usedPercent: percentField.value, resetsAt }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction clampPercent(value: number): number {\n if (!Number.isFinite(value))\n return 0\n return Math.min(100, Math.max(0, Math.round(value * 10) / 10))\n}\n\nfunction isoToEpoch(value: string | null | undefined): number | null {\n if (typeof value !== 'string')\n return null\n const ts = new Date(value).getTime()\n return Number.isFinite(ts) ? ts : null\n}\n","/**\n * The calls still running in a transcript read from disk. The assistant turn\n * holding a step's calls is saved before they run, their results (with\n * `durationMs`) once all of them end, so a page read mid-call shows a call\n * with neither a result nor a start. The host's running work\n * (`chat.listWork`, with `children` for the subagents' calls) has the\n * start: stamped back on, the call's row counts from when it began, not\n * from when the page was read.\n *\n * Shared by the TUI's attach and the desktop app's transcript pages.\n */\nimport type { RunningWorkItem } from './host/signals'\nimport type { StreamEvent } from './types'\n\n/** What these read off an event: a `StreamEvent`, or a client's copy of one. */\ntype CallEvent = Pick<StreamEvent, 'kind' | 'callId' | 'childId' | 'startedAt'>\n\n/** A call by its agent and id: ids are only unique within one agent. */\nfunction callKey(callId: string, childId: string | undefined): string {\n return `${childId ?? ''}\\u0000${callId}`\n}\n\n/**\n * Whether a call, a subagent's included, has neither a result nor a start: a\n * call that may still be running, untimed. A page without one needs no\n * running work.\n *\n * @param events - The page's events.\n */\nexport function hasUntimedOpenCalls(events: readonly CallEvent[]): boolean {\n const done = new Set(events.flatMap(e => (e.kind === 'tool-result' && e.callId ? [callKey(e.callId, e.childId)] : [])))\n return events.some(e => e.kind === 'tool' && e.callId !== undefined && e.startedAt === undefined && !done.has(callKey(e.callId, e.childId)))\n}\n\n/**\n * `events` with each running call's start from the host's running work, a\n * subagent's call matched by its `childId`. `events` itself when nothing\n * is stamped.\n *\n * @param events - The page's events.\n * @param work - The session's running work (`chat.listWork`).\n */\nexport function withRunningCallStarts<E extends CallEvent>(events: E[], work: readonly Pick<RunningWorkItem, 'kind' | 'id' | 'startedAt' | 'childId'>[]): E[] {\n const starts = new Map(work.flatMap(item => (item.kind === 'tool' ? [[callKey(item.id, item.childId), item.startedAt] as const] : [])))\n if (starts.size === 0)\n return events\n let stamped = false\n const out = events.map((event) => {\n const startedAt = event.kind === 'tool' && event.callId ? starts.get(callKey(event.callId, event.childId)) : undefined\n if (startedAt === undefined || event.startedAt !== undefined)\n return event\n stamped = true\n return { ...event, startedAt }\n })\n return stamped ? out : events\n}\n","/**\n * Safe-mode React context — bridges the agent's `tool:gate` / `mcp:tool:gate`\n * hooks (which run inside the harness loop) with the TUI's React tree (which\n * renders the approval picker).\n *\n * Flow:\n * 1. Hook handler in `tui/host/host-session-callbacks.ts` calls `requestApproval(tool, input)`.\n * 2. Context appends a pending entry to the queue, returns a Promise.\n * 3. Picker renders the queue's head, calls `resolve(decision)` on pick.\n * 4. Head is shifted off the queue; hook handler proceeds with the decision.\n *\n * The queue is FIFO so parallel tool calls prompt in arrival order.\n *\n * Two contexts on purpose:\n * - `SafeModeQueueContext` carries the live queue. Consumers re-render on\n * every push/pop.\n * - `SafeModeActionsContext` carries `{ requestApproval, resolveHead,\n * denyAll, dropByCorrelation }` and never changes identity, so callbacks that depend on\n * these don't re-bind every time a tool call enters or leaves the queue.\n */\n\nimport type { ReactNode } from 'react'\nimport type { ModelApproval } from './types'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\n/**\n * Outcome of an approval prompt. Four bulk decisions match the original\n * single-edit modal contract; `partial` is the multi-edit branch — the\n * user accepted a subset of an `edit` / `multi_edit` / `write_file` call.\n *\n * `partial.mask` is 1:1 with the call's hunks (in input order): `true`\n * means apply, `false` means deny. The gate handler walks the mask to\n * build per-hunk outcomes; the TUI rebinds `ctx.input.edits` to the\n * approved subset and the renderer reconstructs the full per-hunk view\n * from the `<edit-outcomes>` annotation appended to the tool result.\n */\nexport type ApprovalDecision\n = | 'accept-once'\n | 'accept-session'\n | 'accept-safelist'\n | 'deny'\n | { kind: 'partial', mask: readonly boolean[] }\n\n/**\n * Identifies the caller behind an approval prompt — the parent agent or\n * a specific subagent (`child-N`). The modal's right-side title pins the\n * label so the user knows which agent issued the call when subagents\n * bubble their gates up through the parent's hook bus.\n */\nexport type ApprovalOriginator\n = | { kind: 'parent' }\n | { kind: 'child', label: string }\n\n/** A tool call waiting for the user's decision. */\nexport interface ApprovalRequest {\n /** Unique id of the request. */\n id: string\n /** Name of the tool awaiting approval. */\n tool: string\n /** The tool call's input. */\n input: Record<string, unknown>\n /** Settles the request with the user's decision. */\n resolve: (decision: ApprovalDecision) => void\n /** Caller attribution. Absent ≡ `{ kind: 'parent' }`. */\n originator?: ApprovalOriginator\n /** Model decision approvals: the decision model's verdict when it handed this call to you (`modelConcern`). */\n concern?: ModelApproval\n /**\n * Cross-window correlation id (the daemon's approval request id) for\n * entries surfaced by the daemon runtime. Lets `dropByCorrelation`\n * retract this entry when ANOTHER window resolves the same shared\n * approval, without re-sending a decision. Absent for local-runtime\n * approvals.\n */\n correlationId?: string\n}\n\n/** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */\nexport type RequestApproval = (\n tool: string,\n input: Record<string, unknown>,\n originator?: ApprovalOriginator,\n correlationId?: string,\n) => Promise<ApprovalDecision>\n\n/** Functions that drive the approval queue. */\nexport interface SafeModeActions {\n /** Request a decision; resolves once the user picks. */\n requestApproval: RequestApproval\n /** Resolve the head and shift the queue forward. */\n resolveHead: (decision: ApprovalDecision) => void\n /** Resolve all pending with `deny`. Used on abort / hard exit. */\n denyAll: () => void\n /**\n * Remove the entry whose `correlationId` matches, WITHOUT resolving it\n * (the daemon already did, in another window). No-op when absent.\n */\n dropByCorrelation: (correlationId: string) => void\n}\n\nconst SafeModeQueueContext = createContext<readonly ApprovalRequest[]>([])\nconst SafeModeActionsContext = createContext<SafeModeActions | null>(null)\n\nlet approvalIdCounter = 0\nfunction nextApprovalId(): string {\n // Monotonic id is enough — uniqueness is scoped to a single TUI session\n // and the value never leaves the React tree. Avoids Date.now/Math.random\n // collisions inside tight parallel-tool batches.\n approvalIdCounter += 1\n return `approval-${approvalIdCounter}`\n}\n\n/**\n * Owns the queue + actions. Splits the value across two contexts so a queue\n * change doesn't invalidate every callback memo that closes over the actions.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function SafeModeProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly ApprovalRequest[]>([])\n\n // Mirror the live queue so the unmount cleanup settles pending approvals\n // without re-subscribing on every queue change.\n const queueRef = useRef<readonly ApprovalRequest[]>([])\n queueRef.current = queue\n\n // Provider unmount drops the queue state — a tool body parked on\n // `requestApproval` would then hang forever (its run's `running` gate\n // never clears). Deny every pending request on unmount so no resolver is\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const p of queueRef.current)\n p.resolve('deny')\n }, [])\n\n // `setQueue` is stable, so `useCallback([])` here gives genuine identity\n // stability — `requestApproval` is the same function reference for the\n // lifetime of the provider, which is what hook handlers need.\n const requestApproval = useCallback<RequestApproval>(\n (tool, input, originator, correlationId) => new Promise<ApprovalDecision>((resolve) => {\n setQueue(prev => [\n ...prev,\n {\n id: nextApprovalId(),\n tool,\n input,\n resolve,\n ...(originator ? { originator } : {}),\n ...(correlationId ? { correlationId } : {}),\n },\n ])\n }),\n [],\n )\n\n const resolveHead = useCallback((decision: ApprovalDecision) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(decision)\n return rest\n })\n }, [])\n\n const denyAll = useCallback(() => {\n setQueue((prev) => {\n for (const p of prev) p.resolve('deny')\n return []\n })\n }, [])\n\n const dropByCorrelation = useCallback((correlationId: string) => {\n // Remove WITHOUT resolving: the daemon already settled this approval\n // in the window that answered it. Resolving here would double-answer\n // (harmless deny) AND fire the RPC guard path unnecessarily.\n setQueue(prev => prev.filter(p => p.correlationId !== correlationId))\n }, [])\n\n // Actions object is stable across renders — its members are all `useCallback`'d\n // with `[]` deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useSafeModeActions()` never\n // re-render purely because of safe-mode state.\n const actionsRef = useRef<SafeModeActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { requestApproval, resolveHead, denyAll, dropByCorrelation }\n\n return (\n <SafeModeActionsContext.Provider value={actionsRef.current}>\n <SafeModeQueueContext.Provider value={queue}>\n {children}\n </SafeModeQueueContext.Provider>\n </SafeModeActionsContext.Provider>\n )\n}\n\n/** Returns the pending approval requests, oldest first. */\nexport function useSafeModeQueue(): readonly ApprovalRequest[] {\n return useContext(SafeModeQueueContext)\n}\n\n/** Returns the approval actions. Throws when used outside {@link SafeModeProvider}. */\nexport function useSafeModeActions(): SafeModeActions {\n const ctx = useContext(SafeModeActionsContext)\n if (!ctx)\n throw new Error('useSafeModeActions must be used inside <SafeModeProvider>')\n return ctx\n}\n","/**\n * Session export — render a `SessionData` as a clean Markdown or JSON\n * document and write it under the project's `.{prefix}/sessions/` directory,\n * or the user's `<dataDir>/sessions/`.\n *\n * Anchor resolution mirrors the read-side `projectUserPaths` convention,\n * but for *writes* we pick a single canonical destination instead of a\n * fallback list: the closest enclosing git repository (walking up from\n * `cwd`), and the user's storage data dir as the final fallback. This keeps\n * exports out of an unrelated project's `.{prefix}/` directory when the\n * user runs the TUI from a non-repo folder.\n *\n * Renderer-agnostic by design: every consumer (TUI, GUI, SDK scripts) can\n * either call `writeSessionExport` for the canned \"render + write\" combo\n * or compose `renderSession`, which only returns a string, with a different\n * sink (clipboard, network, S3).\n */\n\nimport type { SessionData, SessionRun } from '../session'\nimport type { SessionContentBlock, SessionTurn, ToolResultContent, TurnUsage } from '../types'\nimport { existsSync, mkdirSync } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { mergeReplyText } from '../session/reply-text'\nimport { fmtTokens, shortId } from './format'\nimport { deriveSessionTitle } from './store'\nimport { resolveStorageDirs } from './xdg'\n\n/** File format supported by the exporter. */\nexport type SessionExportFormat = 'markdown' | 'json'\n\n/**\n * Anchor for the resolved destination directory:\n * - `project` — the nearest enclosing git repository (`<repoRoot>/.{prefix}/sessions`)\n * - `home` — fallback when no repo was found (`<dataDir>/sessions`; `~/.{prefix}/sessions`\n * outside an XDG layout)\n */\nexport type SessionExportAnchor = 'project' | 'home'\n\n/** Resolved file destination for a session export. */\nexport interface SessionExportTarget {\n /** Absolute directory holding the file. May not exist yet — `writeSessionExport` creates it on demand. */\n dir: string\n /** Absolute path of the file (`dir/{id}.{ext}`). */\n filepath: string\n /** Where the destination was anchored. */\n anchor: SessionExportAnchor\n}\n\n/** Options shared by every resolve / write entry-point. */\ninterface ResolveOptions {\n /** Discovery cwd. Defaults to `process.cwd()`. */\n cwd?: string\n /** User home directory. Defaults to `os.homedir()`. */\n home?: string\n /**\n * Storage prefix. Leading dot tolerated (`zidane` and `.zidane` are\n * equivalent). Defaults to `'.zidane'`.\n */\n prefix?: string\n /**\n * User storage data dir; home-anchored exports land in `<dataDir>/sessions`.\n * Defaults to `resolveStorageDirs({ prefix, home }).dataDir`. Never a literal\n * `~/.{prefix}`: writing there would flip an XDG install to legacy storage.\n */\n dataDir?: string\n}\n\nconst DEFAULT_PREFIX = '.zidane'\n\n/**\n * Resolve the export target for a session id + format. Pure: no fs\n * write happens here, the caller decides whether to `existsSync` the\n * directory or hand the path to `writeSessionExport`.\n *\n * Anchor selection walks upward from `cwd` looking for a `.git` entry;\n * the first hit anchors to that repo's root (`project`). When the walk\n * exits without finding `.git`, the destination anchors to `home`: the\n * user's storage data dir.\n *\n * @throws RangeError when `sessionId` is empty, `.`, `..` or holds a path\n * separator — defensive guard against malicious or buggy callers\n * crossing into a sibling directory via the id.\n *\n * @param opts - Session id, format and optional overrides for the working directory, home directory, prefix and data directory.\n */\nexport function resolveSessionExportTarget(opts: {\n sessionId: string\n format: SessionExportFormat\n} & ResolveOptions): SessionExportTarget {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n const prefix = normalizePrefix(opts.prefix)\n const filename = exportFilename(opts.sessionId, opts.format)\n\n const repoRoot = findGitRoot(cwd)\n const anchor: SessionExportAnchor = repoRoot ? 'project' : 'home'\n const dir = repoRoot\n ? resolve(repoRoot, `.${prefix}`, 'sessions')\n : resolve(opts.dataDir ?? resolveStorageDirs({ prefix: `.${prefix}`, home }).dataDir, 'sessions')\n return { dir, filepath: join(dir, filename), anchor }\n}\n\n/**\n * Render a session into a string in the requested format.\n *\n * `markdown` produces a human-readable transcript: a YAML-free header\n * block with the title and a one-line `id, turns, runs` summary,\n * a `## Metadata` list (id, created, updated, status, turns, runs, tokens,\n * cost), then a `## Conversation` block where each turn renders as `### N. role` over an\n * italic `ISO-date, run run_X, tokens …` line, with text, thinking, tool\n * calls, and tool results formatted as appropriate fenced blocks. Useful for sharing a session\n * with a teammate or pasting into an issue tracker.\n *\n * `json` returns a 2-space-indented, deterministic dump of the full\n * `SessionData` blob. Useful for re-importing into the store or for\n * post-hoc analysis tooling.\n *\n * @param session - The session to render.\n * @param format - `json` for the pretty-printed session data, `markdown` for a transcript.\n */\nexport function renderSession(session: SessionData, format: SessionExportFormat): string {\n if (format === 'json')\n return `${JSON.stringify(session, null, 2)}\\n`\n return renderMarkdown(session)\n}\n\n/**\n * Render `session` and write the resulting bytes to disk. Returns the\n * resolved target so the caller can show the user where the file\n * landed. The parent directory is created on demand (`recursive: true`)\n * — first-time exports don't need any pre-flight setup.\n *\n * @param opts - The session, the format and optional overrides for the working directory, home directory, prefix and data directory.\n */\nexport async function writeSessionExport(\n opts: { session: SessionData, format: SessionExportFormat } & ResolveOptions,\n): Promise<SessionExportTarget> {\n const target = resolveSessionExportTarget({\n sessionId: opts.session.id,\n format: opts.format,\n cwd: opts.cwd,\n home: opts.home,\n prefix: opts.prefix,\n dataDir: opts.dataDir,\n })\n const body = renderSession(opts.session, opts.format)\n // `mkdirSync` is synchronous to avoid two awaits on a hot path that's\n // I/O-bound anyway; `recursive: true` makes it a no-op when the dir\n // already exists, so we don't pay an `existsSync` round-trip.\n mkdirSync(target.dir, { recursive: true })\n await writeFile(target.filepath, body, 'utf8')\n return target\n}\n\n// ---------------------------------------------------------------------------\n// Markdown renderer\n// ---------------------------------------------------------------------------\n\n/**\n * Render a `SessionData` as a clean markdown transcript. Stable across\n * runs (no timestamps or random ids leak into the output beyond what\n * the session itself carries) — diffing two exports of the same\n * session is a no-op.\n */\nfunction renderMarkdown(session: SessionData): string {\n const title = deriveSessionTitle(session.turns, session.metadata)\n const userTurns = session.turns.filter(t => t.role === 'user').length\n const assistantTurns = session.turns.filter(t => t.role === 'assistant').length\n const usage = aggregateUsage(session.runs)\n const lines: string[] = []\n lines.push(`# ${escapeInline(title)}`)\n lines.push('')\n lines.push(`> Session \\`${shortId(session.id)}\\`, ${session.turns.length} turn${session.turns.length === 1 ? '' : 's'}, ${session.runs.length} run${session.runs.length === 1 ? '' : 's'}`)\n lines.push('')\n lines.push('## Metadata')\n lines.push('')\n lines.push(`- **id**: \\`${session.id}\\``)\n lines.push(`- **created**: ${new Date(session.createdAt).toISOString()}`)\n lines.push(`- **updated**: ${new Date(session.updatedAt).toISOString()}`)\n lines.push(`- **status**: ${session.status}`)\n lines.push(`- **turns**: ${session.turns.length} (${userTurns} user, ${assistantTurns} assistant)`)\n lines.push(`- **runs**: ${session.runs.length}`)\n if (usage.total > 0) {\n const tokenBits: string[] = [\n `in ${fmtTokens(usage.input)}`,\n `out ${fmtTokens(usage.output)}`,\n ]\n if (usage.cacheRead > 0)\n tokenBits.push(`cached ${fmtTokens(usage.cacheRead)}`)\n lines.push(`- **tokens**: ${fmtTokens(usage.total)} (${tokenBits.join(', ')})`)\n if (usage.cost > 0)\n lines.push(`- **cost**: $${usage.cost.toFixed(usage.cost < 0.01 ? 4 : 2)}`)\n }\n lines.push('')\n lines.push('## Conversation')\n lines.push('')\n\n if (session.turns.length === 0) {\n lines.push('_No turns yet._')\n lines.push('')\n return `${lines.join('\\n')}\\n`\n }\n\n session.turns.forEach((turn, idx) => {\n lines.push(renderTurnHeader(turn, idx + 1))\n lines.push('')\n const body = renderTurnBody(turn)\n if (body) {\n lines.push(body)\n lines.push('')\n }\n })\n\n return `${lines.join('\\n').replace(/\\n+$/, '\\n')}\\n`\n}\n\n/** `### 1. user` over `_2026-05-13T01:48:29.895Z, run \\`run_1\\`, tokens in 12, out 48_`. */\nfunction renderTurnHeader(turn: SessionTurn, index: number): string {\n const meta = [new Date(turn.createdAt).toISOString()]\n if (turn.runId)\n meta.push(`run \\`${turn.runId}\\``)\n const usage = turn.usage ? formatTurnUsage(turn.usage) : ''\n if (usage)\n meta.push(`tokens ${usage}`)\n return `### ${index}. ${turn.role}\\n\\n_${meta.join(', ')}_`\n}\n\n/** `in 12, out 48, cached 1.2k`: only the non-zero buckets. */\nfunction formatTurnUsage(usage: TurnUsage): string {\n const parts: string[] = []\n if (usage.input > 0)\n parts.push(`in ${fmtTokens(usage.input)}`)\n if (usage.output > 0)\n parts.push(`out ${fmtTokens(usage.output)}`)\n if ((usage.cacheRead ?? 0) > 0)\n parts.push(`cached ${fmtTokens(usage.cacheRead ?? 0)}`)\n return parts.join(', ')\n}\n\n/**\n * Render all blocks of a single turn into a markdown fragment.\n * Block ordering matches the on-disk turn so resume / replay semantics\n * are preserved: tool_call → tool_result pairs stay adjacent, thinking\n * blocks land right before the assistant text they precede.\n */\nfunction renderTurnBody(turn: SessionTurn): string {\n // A frozen system prompt is harness plumbing, kilobytes of doctrine.\n if (turn.systemSnapshot)\n return ''\n const parts: string[] = []\n // A cited Claude reply renders whole (`mergeReplyText`), not as one\n // trimmed paragraph per cited span.\n for (const block of mergeReplyText(turn)) {\n const chunk = renderBlock(block)\n if (chunk)\n parts.push(chunk)\n }\n return parts.join('\\n\\n')\n}\n\nfunction renderBlock(block: SessionContentBlock): string {\n switch (block.type) {\n case 'text': {\n const text = block.text.trim()\n if (!block.systemNote || !text)\n return text\n // Quoted like thinking, so a note never reads as the user's words.\n const quoted = text.split('\\n').map(l => `> ${l}`).join('\\n')\n return `> **note${block.systemNote.label ? `: ${block.systemNote.label}` : ''}**\\n>\\n${quoted}`\n }\n case 'thinking': {\n const text = block.text.trim()\n if (!text)\n return ''\n // Blockquote prefix on every line so consumers can fold / hide\n // thinking sections without breaking the surrounding markdown.\n const quoted = text.split('\\n').map(l => `> ${l}`).join('\\n')\n return `> **thinking**\\n>\\n${quoted}`\n }\n case 'tool_call': {\n const args = JSON.stringify(block.input, null, 2)\n return [\n `**Tool call** \\`${block.name}\\` (id \\`${block.id}\\`)`,\n '',\n '```json',\n args,\n '```',\n ].join('\\n')\n }\n case 'tool_result': {\n const header = block.isError\n ? `**Tool error** (id \\`${block.callId}\\`)`\n : `**Tool result** (id \\`${block.callId}\\`)`\n const output = renderToolOutput(block.output)\n return [header, '', output].join('\\n')\n }\n case 'image':\n // Image data could be megabytes of base64; reference it by media type\n // rather than inline. A future viewer that wants to round-trip can\n // pull the original bytes from the on-disk session.\n return mediaPlaceholder('image', block.mediaType)\n case 'audio':\n case 'video':\n case 'document':\n return mediaPlaceholder(block.type, block.mediaType, block.name)\n case 'compact-summary': {\n // Boundary card in the markdown export — mirrors the TUI's\n // CompactSummaryBlock so a reader sees what happened at the\n // compaction point. The detailed turn-by-turn history before the\n // marker is still in the export (compaction never deletes from\n // disk), so the reader can compare summary vs source.\n const count = block.replacesTurnIds.length\n return [\n `**Compaction summary** (${count} turn${count === 1 ? '' : 's'} compacted, model \\`${block.model}\\`)`,\n '',\n block.summary.trim(),\n ].join('\\n')\n }\n default:\n return ''\n }\n}\n\n/** `_[image: image/png]_`, or `_[document: notes.pdf (application/pdf)]_` when the block is named. */\nfunction mediaPlaceholder(kind: 'image' | 'audio' | 'video' | 'document', mediaType: string, name?: string): string {\n return `_[${kind}: ${name ? `${name} (${mediaType})` : mediaType}]_`\n}\n\n/**\n * Tool output is either a flat string (the common case) or a structured\n * array of `ToolResultContent` (multimodal tools — screenshots, charts).\n * Strings render as a fenced code block; structured arrays render\n * their text blocks inline with `[image: …]` placeholders for the rest.\n */\nfunction renderToolOutput(output: string | ToolResultContent[]): string {\n if (typeof output === 'string') {\n const fence = pickFence(output)\n return `${fence}\\n${output}\\n${fence}`\n }\n const segments: string[] = []\n for (const piece of output) {\n if (piece.type === 'text') {\n const fence = pickFence(piece.text)\n segments.push(`${fence}\\n${piece.text}\\n${fence}`)\n }\n else {\n segments.push(mediaPlaceholder(piece.type, piece.mediaType, piece.type === 'document' ? piece.name : undefined))\n }\n }\n return segments.join('\\n\\n')\n}\n\n/**\n * Pick a fence (`\\`\\`\\`` or longer) that doesn't collide with any\n * backtick run inside `content`. The CommonMark rule is \"a fenced\n * block ends at a fence of the same length or longer made of the same\n * char\" — choosing a fence strictly longer than the longest run inside\n * the body avoids accidental termination.\n */\nfunction pickFence(content: string): string {\n let longestRun = 0\n let current = 0\n for (const ch of content) {\n if (ch === '`') {\n current++\n if (current > longestRun)\n longestRun = current\n }\n else {\n current = 0\n }\n }\n const len = Math.max(3, longestRun + 1)\n return '`'.repeat(len)\n}\n\n/** Strip control characters from a single-line inline (e.g. the title). */\nfunction escapeInline(s: string): string {\n return s.replace(/[\\r\\n]+/g, ' ').trim()\n}\n\n// ---------------------------------------------------------------------------\n// Path resolution helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Walk parents of `start` looking for a `.git` entry (file or\n * directory — `.git` is a file inside git worktrees). Returns the\n * directory holding `.git`, or `null` at the filesystem root.\n */\nfunction findGitRoot(start: string): string | null {\n let dir = resolve(start)\n // Hard cap as an escape hatch on pathological cwd values\n // (`mount --bind` cycles, etc.); 64 levels is well above what any\n // real-world checkout produces.\n for (let i = 0; i < 64; i++) {\n if (existsSync(join(dir, '.git')))\n return dir\n const parent = dirname(dir)\n if (parent === dir)\n return null\n dir = parent\n }\n return null\n}\n\n/** Trim a leading dot from `prefix`. */\nfunction normalizePrefix(prefix: string | undefined): string {\n return (prefix ?? DEFAULT_PREFIX).replace(/^\\./, '')\n}\n\n/**\n * Compose the on-disk filename for `(sessionId, format)`. Rejects\n * empty ids and path-traversal-ish values so the caller can't escape\n * `.{prefix}/sessions/`.\n */\nfunction exportFilename(sessionId: string, format: SessionExportFormat): string {\n const ext = format === 'json' ? 'json' : 'md'\n const cleaned = sessionId.trim()\n if (!cleaned || cleaned.includes('/') || cleaned.includes('\\\\') || cleaned === '.' || cleaned === '..')\n throw new RangeError(`Cannot export session: invalid id \"${sessionId}\"`)\n return `${cleaned}.${ext}`\n}\n\n// ---------------------------------------------------------------------------\n// Usage aggregation (also read by the TUI session details modal)\n// ---------------------------------------------------------------------------\n\n/** Token and cost totals over a list of runs, as built by {@link aggregateUsage}. */\nexport interface AggregatedUsage {\n /** Input tokens, cache reads excluded. */\n input: number\n /** Output tokens. */\n output: number\n /** Cache-read tokens. */\n cacheRead: number\n /** `input + output`. */\n total: number\n /** Cost in USD. */\n cost: number\n}\n\n/**\n * Sum token + cost figures across every {@link SessionRun}. Preference\n * order for a run's tally is `totalUsage` → per-`turnUsage[]` sum.\n * The session sets `totalUsage` from `turnUsage` when a run finishes, so the\n * fallback covers records that carry only `turnUsage`.\n *\n * Returns an aggregate of zeroes when no usage data is available; the\n * caller decides whether to render the tokens row at all.\n */\nexport function aggregateUsage(runs: readonly SessionRun[]): AggregatedUsage {\n const acc = { input: 0, output: 0, cacheRead: 0, cost: 0 }\n for (const run of runs) {\n if (run.totalUsage) {\n acc.input += run.totalUsage.input ?? 0\n acc.output += run.totalUsage.output ?? 0\n acc.cacheRead += run.totalUsage.cacheRead ?? 0\n }\n else if (run.turnUsage) {\n for (const u of run.turnUsage) {\n acc.input += u.input ?? 0\n acc.output += u.output ?? 0\n acc.cacheRead += u.cacheRead ?? 0\n }\n }\n if (run.cost)\n acc.cost += run.cost\n }\n return { ...acc, total: acc.input + acc.output }\n}\n","import type { Dispatch, MutableRefObject, SetStateAction } from 'react'\nimport type { TranscriptWeight } from './streaming-pure'\nimport type { Owner, StreamEvent } from './types'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { appendMarkdownDelta, appendThinkingLines, smoothCharsForTick, stepTranscriptCap, takeChars } from './streaming-pure'\n\n// Pure delta-merge / finalize reducers + context-window math live in the\n// node-free `./streaming-pure` leaf (importable from `zidane/chat/pure`, and\n// shared with the GUI renderer). Re-exported here so existing `zidane/chat`\n// consumers keep their import paths.\nexport { appendMarkdownDelta, appendThinkingLines, capTranscriptEvents, finalizeStreamingMarkdown, finalizeStreamingMarkdownForOwner, hiddenEarlierTranscriptCount, MAX_TRANSCRIPT_EVENTS, mergeTargetIndex, replaceTranscriptTurnEvents, revealEarlierTranscriptEvents, smoothCpsForBacklog, stepTranscriptCap, TRANSCRIPT_TRIM_STEP_EVENTS, TRANSCRIPT_TRIM_TO_EVENTS, turnContextSize } from './streaming-pure'\n\n/**\n * Default tick interval for the continuous drain loop. ~16ms aligns with\n * the 60fps renderer target so the typewriter cadence reveals 1 portion of\n * content per paint frame (a host whose frame rate differs passes\n * {@link StreamBufferOptions.getTickIntervalMs}). The bucket-derived\n * `chars-per-tick` decides how much actually lands per tick — see\n * {@link smoothCharsForTick}.\n */\nconst TICK_INTERVAL_MS = 16\n\n/**\n * Safety bound for {@link StreamBuffer.whenDrained} — per window of NO\n * drain progress. A long response legitimately types out for longer\n * than any fixed deadline (the ramp bottoms out at\n * the base rate, `SMOOTH_BASE_CPS` in `./streaming-pure`), so each committed tick re-arms the window;\n * only a genuinely wedged ticker — this long with nothing committed —\n * fires the escape hatch, and convergence (transcript resync) is never\n * blocked forever.\n */\nexport const WHEN_DRAINED_TIMEOUT_MS = 3000\n\nconst PARENT_OWNER: Owner = 'parent'\n\n/** One owner's (parent or a subagent) not-yet-committed deltas. */\ninterface DeltaBucket {\n markdown: string\n thinking: string\n owner: Owner\n depth: number\n /** Last turnId seen for this bucket. Tagged onto new events when the bucket flushes. */\n turnId?: string\n /**\n * Fractional chars carried over from the previous smooth tick. The\n * tick computes an exact float (`cps * dt`), takes the integer part,\n * and stashes the leftover here so the next tick adds it back in.\n * This makes the long-run rate match the target CPS exactly instead\n * of being inflated by `Math.ceil` rounding on every tick.\n */\n smoothCarry: number\n}\n\nfunction emptyBucket(owner: Owner, depth: number): DeltaBucket {\n return { markdown: '', thinking: '', owner, depth, smoothCarry: 0 }\n}\n\nfunction applyBucket(prev: StreamEvent[], bucket: DeltaBucket): StreamEvent[] {\n let result = prev\n if (bucket.thinking)\n result = appendThinkingLines(result, bucket.thinking, bucket.owner, bucket.depth, bucket.turnId)\n if (bucket.markdown)\n result = appendMarkdownDelta(result, bucket.markdown, bucket.owner, bucket.depth, bucket.turnId)\n return result\n}\n\n// ---------------------------------------------------------------------------\n// useStreamBuffer — per-owner stream-event accumulator with two drain modes.\n//\n// Provider deltas arrive at ~50Hz; flushing each one through React causes the\n// ScrollBox to recompute layout faster than the terminal can clear and\n// repaint, producing overdraw artifacts during scroll. We buffer per owner\n// (parent + each active subagent) and drain via a continuous ticker:\n//\n// - **smooth mode** (default — gated on {@link Settings.smoothStreaming}):\n// drip-feed text character-by-character at the base rate (`./streaming-pure`), with\n// an adaptive burst when the buffer falls behind. Reads as a typewriter\n// effect and masks bursty provider chunks behind a steady cadence.\n// While markdown backlog remains, turn-boundary calls (`appendImmediate`\n// / `flushAndUpdate`) queue behind it and `flush` leaves the ticker to\n// finish. They apply once the backlog has typed out.\n// - **batched mode** (off): drain everything per tick.\n//\n// `getSmooth` is called on every tick so toggling the user setting at\n// runtime takes effect on the next drain without rebuilding the buffer.\n// ---------------------------------------------------------------------------\n\n/** Which agent and turn a streaming delta came from. */\nexport interface StreamSource {\n /** Pass `undefined` / omit for parent-agent events. */\n childId?: string\n /** Nesting depth — 0 for parent, ≥ 1 for subagents. */\n depth?: number\n /**\n * `SessionTurn.id` this delta belongs to. Tagged onto new events so the\n * TUI's select-turn mode can group every event from one turn. Stored on\n * the active bucket and refreshed on every delta so a turn boundary\n * (`turn:after` flushes the bucket) doesn't carry stale ids forward.\n */\n turnId?: string\n}\n\n/** Handle returned by {@link useStreamBuffer}. */\nexport interface StreamBuffer {\n /** Queue a streaming delta for the next flush tick. */\n queueStreamDelta: (kind: 'markdown' | 'thinking', delta: string, source?: StreamSource) => void\n /** Drain pending deltas immediately, then append a non-streaming event. */\n appendImmediate: (evt: StreamEvent) => void\n /** Drain pending deltas immediately, then transform the event list. */\n flushAndUpdate: (update: (events: StreamEvent[]) => StreamEvent[]) => void\n /** Drain pending deltas without further transformation. */\n flush: () => void\n /** Cancel any pending flush and drop buffered deltas (on session teardown). */\n reset: () => void\n /**\n * Resolves once the pacer is idle — no bucket holds queued content and\n * no post-drain updater is pending. Resolves immediately when already\n * idle (the batched / `smoothStreaming: false` path never queues, so\n * this is zero-cost there). A safety timeout (default\n * {@link WHEN_DRAINED_TIMEOUT_MS}) resolves anyway if the ticker\n * wedges — measured as a full window with NO drain progress, so a\n * long response that keeps typing re-arms it and never snaps — and\n * callers gating a transcript convergence on the drain can never\n * dead-lock. `reset()` also releases every waiter — the backlog it\n * drops would otherwise never drain.\n */\n whenDrained: (timeoutMs?: number) => Promise<void>\n}\n\n/** Options for {@link useStreamBuffer}. */\nexport interface StreamBufferOptions {\n /**\n * Returns `true` to drip-feed buffered text at the smooth typewriter\n * cadence, `false` to drain everything per tick (batched mode). Called on every tick, so flipping the user setting\n * takes effect on the next drain.\n */\n getSmooth?: () => boolean\n /**\n * Display-memory bound applied on every drain commit (see\n * {@link capTranscriptEvents}): above this many events the oldest are\n * trimmed at a turn boundary and replaced with a cumulative\n * \"… N earlier items hidden\" marker event, at most\n * {@link TRANSCRIPT_TRIM_STEP_EVENTS} per drain ({@link stepTranscriptCap}),\n * so a large overflow never freezes one frame. Rendered events hold live\n * renderables/native buffers, so an unbounded buffer OOMs long\n * sessions (~0.13MB RSS per event measured in the TUI).\n *\n * Default {@link MAX_TRANSCRIPT_EVENTS}. Pass `null` to disable and\n * receive the full, unbounded event array, for example for\n * a host that renders a virtualized list or exports events verbatim.\n */\n maxTranscriptEvents?: number | null\n /**\n * Live override of {@link maxTranscriptEvents}, read inside each drain\n * commit. Use it when the cap changes between renders: a pending drain\n * updater runs during the host's next `useState` evaluation, BEFORE this\n * hook re-mirrors the option, so a render-mirrored value is one commit\n * stale (a history reveal would be re-trimmed by that commit).\n */\n getMaxTranscriptEvents?: () => number | null | undefined\n /**\n * What each event weighs on screen ({@link TranscriptWeight}), read at\n * each drain commit like {@link getMaxTranscriptEvents}: the cap then\n * counts rows, not events. Without it every event is a row.\n */\n getTranscriptWeight?: () => TranscriptWeight | undefined\n /**\n * Drain tick interval in ms, read each time the ticker starts. Match it to\n * the host's frame interval: ticking faster than frames paint only adds\n * commits nobody sees. The smooth cadence measures real elapsed time, so\n * the character rate does not depend on it. Default 16 (60 fps).\n */\n getTickIntervalMs?: () => number | undefined\n}\n\n/**\n * Take this tick's portion off every live bucket (mutating the buckets):\n * all of its thinking, and its markdown at the smooth cadence or in full.\n * `stillHasContent` = some bucket kept markdown for a later tick.\n */\nfunction takeTickPortions(\n buckets: Map<Owner, DeltaBucket>,\n smooth: boolean,\n dtMs: number,\n): { portions: DeltaBucket[], stillHasContent: boolean } {\n const portions: DeltaBucket[] = []\n let stillHasContent = false\n for (const bucket of buckets.values()) {\n const thinking = bucket.thinking\n bucket.thinking = ''\n\n let taken = ''\n if (bucket.markdown.length > 0) {\n if (smooth) {\n const { take, carry } = smoothCharsForTick(\n bucket.markdown.length,\n bucket.smoothCarry,\n dtMs,\n )\n bucket.smoothCarry = carry\n if (take > 0) {\n const result = takeChars(bucket.markdown, take)\n taken = result.taken\n bucket.markdown = result.rest\n if (bucket.markdown.length === 0)\n bucket.smoothCarry = 0\n }\n }\n else {\n taken = bucket.markdown\n bucket.markdown = ''\n bucket.smoothCarry = 0\n }\n }\n\n if (taken || thinking) {\n portions.push({\n markdown: taken,\n thinking,\n owner: bucket.owner,\n depth: bucket.depth,\n turnId: bucket.turnId,\n smoothCarry: 0,\n })\n }\n if (bucket.markdown.length > 0)\n stillHasContent = true\n }\n return { portions, stillHasContent }\n}\n\n/** No queued post-drain updater and no bucket holding content. */\nfunction bufferIdle(buckets: Map<Owner, DeltaBucket>, pendingUpdaters: number): boolean {\n let idle = pendingUpdaters === 0\n if (idle) {\n for (const bucket of buckets.values()) {\n if (bucket.markdown.length > 0 || bucket.thinking.length > 0) {\n idle = false\n break\n }\n }\n }\n return idle\n}\n\ninterface DrainWaiter { resolve: () => void, timer: ReturnType<typeof setTimeout> }\n\n/** Register a `whenDrained` waiter, with its no-progress safety timer armed. */\nfunction addDrainWaiter(\n resolve: () => void,\n drainProgressRef: MutableRefObject<number>,\n drainWaitersRef: MutableRefObject<DrainWaiter[]>,\n timeoutMs: number,\n): void {\n // Safety valve: a wedged ticker (cleared interval, starved event\n // loop) must not block the caller forever — resolve anyway and\n // drop the waiter so a late drain doesn't double-resolve. Armed\n // per window of NO progress: a tick that committed content since\n // the last check proves the drain is alive (a long response types\n // for longer than any fixed deadline), so re-arm instead of\n // snapping the tail into the caller's convergence.\n let seenProgress = drainProgressRef.current\n const waiter = {\n resolve,\n timer: undefined as unknown as ReturnType<typeof setTimeout>,\n }\n const arm = (): ReturnType<typeof setTimeout> => setTimeout(() => {\n if (drainProgressRef.current !== seenProgress) {\n seenProgress = drainProgressRef.current\n waiter.timer = arm()\n return\n }\n drainWaitersRef.current = drainWaitersRef.current.filter(w => w !== waiter)\n resolve()\n }, timeoutMs)\n waiter.timer = arm()\n drainWaitersRef.current.push(waiter)\n}\n\n/**\n * Buffers streaming deltas and commits them to the transcript on a ticker, in smooth or batched mode.\n *\n * @param setEvents - State setter for the transcript events.\n * @param options - Cadence and transcript cap settings.\n */\nexport function useStreamBuffer(\n setEvents: Dispatch<SetStateAction<StreamEvent[]>>,\n options?: StreamBufferOptions,\n): StreamBuffer {\n const bucketsRef = useRef<Map<Owner, DeltaBucket>>(new Map())\n const tickerRef = useRef<ReturnType<typeof setInterval> | null>(null)\n // Real wall-clock timestamp of the last tick. Feeds the dt argument\n // to `smoothCharsForTick` so the drain rate stays accurate even when\n // `setInterval` is late (event-loop congestion, GC pauses); without\n // this a 50ms-late tick would only commit ~16ms-worth of chars and\n // the visible cadence would stall.\n const lastTickMsRef = useRef<number>(0)\n // Pin the latest `getSmooth` callback inside a ref so the ticker closure\n // always sees the current setting without recreating the timer.\n const getSmoothRef = useRef<(() => boolean) | undefined>(options?.getSmooth)\n getSmoothRef.current = options?.getSmooth\n /** Live mirror so drains read the current cap without re-memoizing. */\n const maxEventsRef = useRef<number | null | undefined>(options?.maxTranscriptEvents)\n maxEventsRef.current = options?.maxTranscriptEvents\n const getMaxEventsRef = useRef(options?.getMaxTranscriptEvents)\n getMaxEventsRef.current = options?.getMaxTranscriptEvents\n const getWeightRef = useRef(options?.getTranscriptWeight)\n getWeightRef.current = options?.getTranscriptWeight\n const getTickIntervalRef = useRef(options?.getTickIntervalMs)\n getTickIntervalRef.current = options?.getTickIntervalMs\n /** Interval of the running ticker (the nominal first-tick dt and the dt clamp). */\n const tickIntervalRef = useRef(TICK_INTERVAL_MS)\n const capForCommit = useCallback((events: StreamEvent[]): StreamEvent[] => {\n const cap = getMaxEventsRef.current ? getMaxEventsRef.current() : maxEventsRef.current\n // Stepped: a big overflow (a reveal handed back) trims over several drains.\n return cap === null ? events : stepTranscriptCap(events, cap ?? undefined, undefined, getWeightRef.current?.())\n }, [])\n\n /**\n * Updaters queued while smooth-streaming was still draining backlog —\n * tool-call appends, finalize-markdown transforms, error events.\n * Applied in FIFO order at the END of `tick()` once every bucket is\n * empty, so the trailing typewriter characters land BEFORE the\n * follow-up event instead of being clobbered by an immediate drain.\n *\n * The buffer treats markdown deltas as the authoritative ordering\n * source: a tool call queued behind 100 buffered chars surfaces after\n * those 100 chars have typed out, preserving the visual stream order\n * the user is reading.\n */\n const pendingUpdatersRef = useRef<((events: StreamEvent[]) => StreamEvent[])[]>([])\n\n /**\n * Pending `whenDrained` promises. Released (with their safety timers\n * cleared) whenever the buffer reaches the idle state: the tick that\n * empties the last bucket, a synchronous `drainNow`, or a `reset`.\n */\n const drainWaitersRef = useRef<DrainWaiter[]>([])\n\n /**\n * Monotonic count of commits (ticks that landed content or updaters).\n * `whenDrained` waiters compare it across their safety window: movement\n * means the ticker is alive — re-arm instead of firing the escape hatch.\n */\n const drainProgressRef = useRef(0)\n\n const releaseDrainWaiters = useCallback(() => {\n if (drainWaitersRef.current.length === 0)\n return\n const waiters = drainWaitersRef.current\n drainWaitersRef.current = []\n for (const waiter of waiters) {\n clearTimeout(waiter.timer)\n waiter.resolve()\n }\n }, [])\n\n const stopTicker = useCallback(() => {\n if (tickerRef.current) {\n clearInterval(tickerRef.current)\n tickerRef.current = null\n }\n // Forget the last-tick stamp so the next ticker run starts from\n // the nominal interval instead of a stale (potentially seconds-old)\n // delta that would burst-drain the first frame.\n lastTickMsRef.current = 0\n }, [])\n\n /**\n * Has at least one bucket got unflushed *markdown* content?\n *\n * Thinking content is excluded: it always flushes in full on every\n * tick (see `tick()`), so its presence in a bucket only matters until\n * the next 16 ms cycle — never long enough to justify deferring an\n * end-of-turn updater.\n */\n const hasMarkdownBacklog = useCallback((): boolean => {\n for (const bucket of bucketsRef.current.values()) {\n if (bucket.markdown.length > 0)\n return true\n }\n return false\n }, [])\n\n /**\n * Drain every bucket in full and synchronously run any queued\n * post-drain updaters plus the caller-provided one. Used by the\n * fast paths below: `flush`, and `appendImmediate` /\n * `flushAndUpdate` in batched mode or when no markdown backlog remains.\n */\n const drainNow = useCallback((updater?: (events: StreamEvent[]) => StreamEvent[]) => {\n stopTicker()\n const buckets = Array.from(bucketsRef.current.values())\n bucketsRef.current.clear()\n const queued = pendingUpdatersRef.current\n pendingUpdatersRef.current = []\n const hasDeltas = buckets.some(b => b.markdown.length > 0 || b.thinking.length > 0)\n if (!hasDeltas && !updater && queued.length === 0) {\n releaseDrainWaiters()\n return\n }\n setEvents((prev) => {\n let merged = prev\n for (const bucket of buckets)\n merged = applyBucket(merged, bucket)\n for (const u of queued)\n merged = u(merged)\n if (updater)\n merged = updater(merged)\n // Memory bound: identity under the cap, turn-boundary head trim\n // above it (see capTranscriptEvents). Session turns stay complete.\n return capForCommit(merged)\n })\n releaseDrainWaiters()\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n /**\n * One tick of the continuous drain loop. Walks every live bucket and\n * commits a portion of its content based on the current mode. Once\n * every bucket empties out, fires any post-drain updaters that were\n * queued while smooth streaming was still in progress (turn\n * finalize, tool calls, errors) and stops the ticker.\n *\n * `thinking` content always flushes immediately even in smooth mode —\n * it's an internal-reasoning surface, surfaced for transparency rather\n * than for reading flow, and users expect it to keep up with the model.\n */\n const tick = useCallback(() => {\n const smooth = getSmoothRef.current?.() ?? true\n const buckets = bucketsRef.current\n\n // Real elapsed time since the last tick. Falls back to the nominal\n // interval on the first tick (when there's nothing to measure\n // against) and clamps to a sane upper bound so a one-off 500ms\n // event-loop stall doesn't burst-drain the entire backlog.\n const now = Date.now()\n const interval = tickIntervalRef.current\n const dtMs = lastTickMsRef.current === 0\n ? interval\n : Math.min(interval * 4, Math.max(1, now - lastTickMsRef.current))\n lastTickMsRef.current = now\n\n const { portions, stillHasContent } = takeTickPortions(buckets, smooth, dtMs)\n\n // Fold post-drain updaters into the same commit as the trailing\n // portions so the resulting transcript has at most one paint per\n // tick — and so the LAST portion of a smooth stream and its\n // companion finalize-markdown / tool-call event land atomically.\n const drainQueued = !stillHasContent && pendingUpdatersRef.current.length > 0\n const queued = drainQueued\n ? pendingUpdatersRef.current.splice(0, pendingUpdatersRef.current.length)\n : []\n\n if (portions.length > 0 || queued.length > 0) {\n drainProgressRef.current += 1\n setEvents((prev) => {\n let next = prev\n for (const portion of portions)\n next = applyBucket(next, portion)\n for (const u of queued)\n next = u(next)\n // Same memory bound as `drainNow`: deferred updaters append\n // tool-call / error events too, so this commit path must not\n // become a cap-bypass window. Identity under the cap.\n return capForCommit(next)\n })\n }\n\n if (!stillHasContent) {\n stopTicker()\n // Buckets and queued updaters both landed in this commit — the\n // buffer is idle, so end-of-stream convergence can proceed.\n releaseDrainWaiters()\n }\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n const ensureTicker = useCallback(() => {\n if (tickerRef.current)\n return\n const requested = getTickIntervalRef.current?.()\n tickIntervalRef.current = requested !== undefined && Number.isFinite(requested) && requested > 0 ? requested : TICK_INTERVAL_MS\n tickerRef.current = setInterval(tick, tickIntervalRef.current)\n }, [tick])\n\n /**\n * Decide between fast-drain and deferred-drain for an\n * updater-bearing call (`appendImmediate`, `flushAndUpdate`).\n *\n * Smooth mode + markdown backlog → ENQUEUE the updater behind the\n * trailing characters; the ticker picks it up once buckets empty.\n * Anything else → fall through to `drainNow` so the call retains\n * its synchronous semantics.\n */\n const drainOrDefer = useCallback((updater: (events: StreamEvent[]) => StreamEvent[]) => {\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n pendingUpdatersRef.current.push(updater)\n ensureTicker()\n return\n }\n drainNow(updater)\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flush = useCallback(() => {\n // In smooth mode with backlog, the ticker is already draining at\n // the typewriter cadence — explicit `flush()` shouldn't yank the\n // remaining characters into a single frame. The ticker is\n // self-stopping once empty, so this becomes a no-op there.\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n ensureTicker()\n return\n }\n drainNow()\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flushAndUpdate = useCallback(\n (update: (events: StreamEvent[]) => StreamEvent[]) => drainOrDefer(update),\n [drainOrDefer],\n )\n\n const appendImmediate = useCallback(\n (evt: StreamEvent) => drainOrDefer(events => [...events, evt]),\n [drainOrDefer],\n )\n\n const queueStreamDelta = useCallback((\n kind: 'markdown' | 'thinking',\n delta: string,\n source?: StreamSource,\n ) => {\n if (!delta)\n return\n const owner: Owner = source?.childId ?? PARENT_OWNER\n const depth = source?.depth ?? 0\n let bucket = bucketsRef.current.get(owner)\n if (!bucket) {\n bucket = emptyBucket(owner, depth)\n bucketsRef.current.set(owner, bucket)\n }\n bucket[kind] += delta\n // Refresh on every delta — `turn:after` drains the bucket between turns,\n // so a turnId only ever applies to the deltas of one turn. Keeping the\n // latest also lets a turn that starts mid-bucket (rare but theoretically\n // possible if two hooks race the flush ticker) tag the new event.\n if (source?.turnId)\n bucket.turnId = source.turnId\n ensureTicker()\n }, [ensureTicker])\n\n const reset = useCallback(() => {\n stopTicker()\n bucketsRef.current.clear()\n pendingUpdatersRef.current = []\n // The dropped backlog will never drain — release waiters instead of\n // stranding them until the safety timeout.\n releaseDrainWaiters()\n }, [releaseDrainWaiters, stopTicker])\n\n const whenDrained = useCallback((timeoutMs: number = WHEN_DRAINED_TIMEOUT_MS): Promise<void> => {\n if (bufferIdle(bucketsRef.current, pendingUpdatersRef.current.length))\n return Promise.resolve()\n return new Promise(resolve => addDrainWaiter(resolve, drainProgressRef, drainWaitersRef, timeoutMs))\n }, [])\n\n // Unmount hygiene: the interval ticker outlives the render otherwise and\n // fires `setEvents` against an unmounted host. `reset` is stable (all\n // deps are useCallback'd), so this registers exactly once and never\n // perturbs the memoized return below.\n useEffect(() => reset, [reset])\n\n // CRITICAL: stabilize the returned object. All six callbacks are useCallback'd\n // (stable across renders), but a bare object-literal return would be a fresh\n // reference every call, which cascades through every `useCallback`/`useEffect`\n // that depends on `stream` and turns the resume effect into an unbounded\n // destroy/rebuild loop — the exact source of the terminal-host memory leak.\n return useMemo(\n () => ({ queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained }),\n [queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained],\n )\n}\n","import type { ReactNode } from 'react'\nimport type { SyntaxStyles, Theme, ThemeColors, ThemeSelect, ThemeSurfaces } from './theme'\nimport { createContext, useContext } from 'react'\nimport { DEFAULT_THEME } from './theme'\n\n// ---------------------------------------------------------------------------\n// ThemeContext\n//\n// The TUI / GUI mounts `<ThemeProvider theme={…}>` near the root of its tree,\n// resolving the active theme from `Settings.theme`. Every component reads\n// theme values through a hook so a runtime theme switch (Settings → Theme)\n// triggers an immediate re-paint of the whole tree.\n//\n// Sub-hooks (`useColors`, `useSelectStyle`, …) exist so consumers can grab\n// just the slice they need, for example `const COLOR = useColors(); COLOR.brand`.\n// ---------------------------------------------------------------------------\n\nconst ThemeContext = createContext<Theme>(DEFAULT_THEME)\n\n/**\n * Provides `theme` to the subtree.\n *\n * @param props - The `theme` to provide and the `children` to render.\n */\nexport function ThemeProvider({ theme, children }: { theme: Theme, children: ReactNode }) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>\n}\n\n/** Returns the current theme. Falls back to {@link DEFAULT_THEME} outside a {@link ThemeProvider}. */\nexport function useTheme(): Theme {\n return useContext(ThemeContext)\n}\n\n/** Color palette only — equivalent to `useTheme().colors`. */\nexport function useColors(): ThemeColors {\n return useContext(ThemeContext).colors\n}\n\n/** Select-row styling — equivalent to `useTheme().select`: the `▶` marker and accent text carry the cursor. */\nexport function useSelectStyle(): ThemeSelect {\n return useContext(ThemeContext).select\n}\n\n/** Panel / surface backgrounds — equivalent to `useTheme().surfaces`. */\nexport function useSurfaces(): ThemeSurfaces {\n return useContext(ThemeContext).surfaces\n}\n\n/** Raw syntax style table — `useTheme().syntax`. Renderer converts to its native style type. */\nexport function useSyntaxStyles(): SyntaxStyles {\n return useContext(ThemeContext).syntax\n}\n","// ---------------------------------------------------------------------------\n// Transcript scroll-anchor computation.\n//\n// Walks a list of partitioned transcript items (one entry per row that\n// will be rendered) and assigns a stable scroll-anchor id to the FIRST\n// event of each turn — `turn-anchor-<turnId>`. The renderer applies\n// these ids to whichever container hosts the first event of the turn;\n// scroll-into-view (TUI scrollbox or GUI element.scrollIntoView) then\n// resolves the id to a known target.\n//\n// Pure data-in / data-out so the same logic powers the TUI today and\n// any future GUI without dragging OpenTUI specifics into the chat\n// layer.\n// ---------------------------------------------------------------------------\n\nimport type { StreamEvent } from './types'\n\n/**\n * One entry in the partitioned transcript-row list. Single events\n * render as a normal row; a `child-run` groups ONE subagent's events\n * from a consecutive run of subagent output into one bordered box\n * (concurrent siblings get a `child-run` each, so their rows never\n * braid together inside a shared frame); `tool-group`s\n * collect ALL parent-owned tool/tool-result events of one exchange\n * into a single collapsible summary row (`Settings.groupToolCalls`);\n * an `exchange-fold` goes further (`Settings.transcriptDensity:\n * 'compact'`) and takes every thinking, tool and worker event of the\n * exchange, leaving only the prompt and the assistant's prose in place.\n * `key` is stable across re-partitions of the same stream (derived\n * from the first grouped event) so expand state survives appends.\n *\n * Currently produced by the TUI's `partitionTranscript`; a GUI shell\n * grouping subagent runs the same way can produce the same shape.\n */\nexport type TranscriptItem\n = | { kind: 'event', event: StreamEvent, previous?: StreamEvent }\n | { kind: 'child-run', events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'tool-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'exchange-fold', key: string, events: StreamEvent[], previous?: StreamEvent }\n /** Compact density: an exchange's earlier messages, ahead of its last one. */\n | { kind: 'message-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n\n/**\n * Per-item anchor ids for auto-scroll. Walks `items` in render order\n * and, for each event, returns either:\n * - `'turn-anchor-<turnId>'` — the first event of this turn (the\n * scroll target).\n * - `undefined` — later event of an already-tagged turn (or a\n * synthetic event with no `turnId`).\n *\n * `ids[i]` is a tuple per item: length 1 for plain events, length N\n * for subagent runs (one entry per inner event). `idByTurn` is the\n * inverse lookup used by the scroll effect. `lastTurnId` is the\n * most-recently-rendered turn — the scroll effect special-cases it to\n * snap to bottom rather than scroll the anchor into view (which would\n * stop short of the actual tail).\n *\n * Exported so the anchor-tagging matrix can be unit-tested without\n * rendering anything.\n *\n * @param items - Transcript items in render order.\n */\nexport function computeTurnAnchors(items: readonly TranscriptItem[]): {\n idByTurn: ReadonlyMap<string, string>\n ids: readonly (readonly (string | undefined)[])[]\n lastTurnId: string | undefined\n} {\n const idByTurn = new Map<string, string>()\n let lastTurnId: string | undefined\n const tag = (turnId: string | undefined): string | undefined => {\n if (!turnId)\n return undefined\n lastTurnId = turnId\n if (idByTurn.has(turnId))\n return undefined\n const id = `turn-anchor-${turnId}`\n idByTurn.set(turnId, id)\n return id\n }\n const ids: (string | undefined)[][] = []\n for (const item of items) {\n if (item.kind === 'event')\n ids.push([tag(item.event.turnId)])\n else\n ids.push(item.events.map(e => tag(e.turnId)))\n }\n return { idByTurn, ids, lastTurnId }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transcript-anchors-D8jWLpe5.js","names":["OPT_OUT_VARS","findGitRoot","findGitRoot"],"sources":["../src/chat/artifacts-view.ts","../src/chat/env-opt-out.ts","../src/chat/auto-update.ts","../src/chat/auto-update-companion.ts","../src/chat/auto-update-restart.ts","../src/chat/auto-update-hook.ts","../src/chat/boot-profiler.ts","../src/chat/claude-import.ts","../src/chat/completion.ts","../src/chat/completion-artifacts.ts","../src/chat/config-context.tsx","../src/chat/discovery-context.tsx","../src/chat/discovery-slot.ts","../src/chat/editor-command.ts","../src/chat/settings-context.tsx","../src/chat/enabled-toggle-set.ts","../src/chat/files-discovery.ts","../src/chat/footer-hints.ts","../src/chat/github-usage.ts","../src/chat/hints.ts","../src/chat/host/onboarding/providers.ts","../src/chat/host/onboarding/detect.ts","../src/chat/interactions.tsx","../src/chat/mcp-auth-context.tsx","../src/chat/mcp-rows.ts","../src/chat/mcp-tool-toggle.ts","../src/chat/oauth-redirect.ts","../src/chat/path-display.ts","../src/chat/provider-usage.ts","../src/chat/running-calls.ts","../src/chat/safe-mode-context.tsx","../src/chat/session-export.ts","../src/chat/streaming.ts","../src/chat/theme-context.tsx","../src/chat/transcript-anchors.ts"],"sourcesContent":["/**\n * Renderer-agnostic shaping of session artifacts for display — grouping\n * and row presentation. Shared by the TUI rail + modal and the GUI rail, so\n * every surface agrees on what an artifact looks like.\n */\n\nimport type { ExtensionArtifactRow, ExtensionRegistry } from '../extensions'\nimport type { SessionArtifact } from '../session'\nimport { artifactKindLabel, renderArtifactRow } from '../extensions/builtin-artifact-kinds'\n\n/** Whether artifacts are listed for the current session or the whole project. */\nexport type ArtifactScope = 'session' | 'project'\n\n/** One artifact paired with its rendered row. */\nexport interface ArtifactViewRow {\n /** The session artifact. */\n artifact: SessionArtifact\n /** Rendered row for the artifact. */\n row: ExtensionArtifactRow\n}\n\n/** Artifacts of one kind, ready to display. */\nexport interface ArtifactViewGroup {\n /** Artifact kind id. */\n kind: string\n /** Human kind label (`Pull request`), raw kind when unregistered. */\n label: string\n /** Rows of this kind, in the store's order. */\n rows: ArtifactViewRow[]\n}\n\ntype KindRegistry = Pick<ExtensionRegistry, 'artifactKinds'> | undefined\n\n/** Display order for the built-in kinds; anything else follows alphabetically. */\nconst KIND_ORDER = ['pull_request', 'branch', 'commit', 'file']\n\n/**\n * Group by kind (built-ins first in a fixed order, then extension kinds\n * alphabetically); rows inside a group keep the store's newest-first order.\n *\n * @param artifacts - Session artifacts to group.\n * @param registry - Extension registry that supplies kind labels and row renderers.\n */\nexport function groupArtifacts(artifacts: readonly SessionArtifact[], registry?: KindRegistry): ArtifactViewGroup[] {\n const byKind = new Map<string, ArtifactViewRow[]>()\n for (const artifact of artifacts) {\n const list = byKind.get(artifact.kind) ?? []\n list.push({ artifact, row: renderArtifactRow(artifact, registry) })\n byKind.set(artifact.kind, list)\n }\n const kinds = [...byKind.keys()].sort((a, b) => {\n const ai = KIND_ORDER.indexOf(a)\n const bi = KIND_ORDER.indexOf(b)\n if (ai !== -1 || bi !== -1)\n return (ai === -1 ? KIND_ORDER.length : ai) - (bi === -1 ? KIND_ORDER.length : bi)\n return a.localeCompare(b)\n })\n return kinds.map(kind => ({ kind, label: artifactKindLabel(kind, registry), rows: byKind.get(kind)! }))\n}\n\n/**\n * One count per kind, a badge each: `['3 pull requests', '2 files']`; empty\n * for none.\n *\n * @param artifacts - Session artifacts to count.\n * @param registry - Extension registry that supplies kind labels.\n */\nexport function summarizeArtifactCounts(artifacts: readonly SessionArtifact[], registry?: KindRegistry): string[] {\n return groupArtifacts(artifacts, registry)\n .map(g => `${g.rows.length} ${pluralizeKind(g.label, g.rows.length)}`)\n}\n\nfunction pluralizeKind(label: string, count: number): string {\n const lower = label.toLowerCase()\n if (count === 1)\n return lower\n return /(?:s|x|ch|sh)$/.test(lower) ? `${lower}es` : `${lower}s`\n}\n","/** Whether any of `vars` is set to a truthy value (anything but unset, `''`, `0`, `false`). */\nexport function envOptedOut(vars: readonly string[], env: NodeJS.ProcessEnv = process.env): boolean {\n for (const key of vars) {\n const v = env[key]\n if (v !== undefined && v !== '' && v !== '0' && v.toLowerCase() !== 'false')\n return true\n }\n return false\n}\n","// ---------------------------------------------------------------------------\n// Auto-update — renderer-agnostic core.\n//\n// Three layers, all reusable by any consumer that wraps `zidane/tui`:\n// 1. `checkForUpdate` — fetches the latest version from an npm-compatible\n// registry, persisted on disk with a TTL so the hot path costs zero\n// network on a warm cache.\n// 2. `performSelfUpdate` — spawns the host package manager (npm, pnpm,\n// yarn, bun, volta) and tells it to re-install the package globally.\n// 3. `performInPlaceSelfUpdate` — downloads the platform-binary tarball\n// from the registry, extracts the binary, atomically swaps it over\n// the live one. Used when no package manager is reachable (offline\n// install path, restricted shells) or when the user wants speed.\n//\n// Pure data + Node-builtin I/O. No React, no OpenTUI, no external deps —\n// hosts replacing the runtime can re-implement just the layer they need.\n// ---------------------------------------------------------------------------\n\nimport type { Buffer } from 'node:buffer'\nimport { spawn } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport {\n chmodSync,\n createReadStream,\n createWriteStream,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n realpathSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from 'node:fs'\nimport { homedir, tmpdir } from 'node:os'\nimport { dirname, join, resolve, sep } from 'node:path'\nimport { createGunzip } from 'node:zlib'\nimport { envOptedOut } from './env-opt-out'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Options for {@link checkForUpdate}. */\nexport interface CheckForUpdateOptions {\n /** Full npm package name to query. e.g. `'zidane-tui'`. */\n packageName: string\n /** Current installed version. Inlined at build time from the consumer's `package.json`. */\n currentVersion: string\n /**\n * Registry base URL. Defaults to `https://registry.npmjs.org`. Strip the\n * trailing slash; the helper appends `/${packageName}/${channel}`.\n */\n registry?: string\n /**\n * dist-tag to read. Defaults to `'latest'`. Set to `'next'` (or any\n * custom tag) for prerelease channels — the comparison still goes through\n * `compareSemver`, so `5.6.0-beta.2 > 5.5.0` evaluates correctly.\n */\n channel?: string\n /** Directory used for the on-disk TTL cache. Skipped when omitted. */\n cacheDir?: string\n /**\n * Cache lifetime. Defaults to 24 h. Zero or negative never counts the cache\n * as fresh, so every check hits the registry. The cache is still written\n * and still serves as the fallback when the registry fails.\n */\n cacheTtlMs?: number\n /** Network timeout. Defaults to 3 000 ms. */\n timeoutMs?: number\n /** Abort signal — wins over the internal timeout. */\n signal?: AbortSignal\n /**\n * When `true`, ignore env-var opt-outs (`CI`, `NO_UPDATE_NOTIFIER`,\n * `ZIDANE_NO_UPDATE`) AND bypass the on-disk TTL cache (always hit\n * the registry). Used by the explicit `zidane upgrade` path where the\n * user typed the command — they expect fresh data, not a 24-hour-old\n * cached value. The cache is still WRITTEN with the fresh result so\n * subsequent background checks benefit; cache reads only feed the\n * etag into the next request so the registry can short-circuit with\n * 304 when nothing changed.\n */\n force?: boolean\n /** Inject for tests. Defaults to `globalThis.fetch`. */\n fetcher?: typeof fetch\n /** Inject for tests. Defaults to `Date.now`. */\n now?: () => number\n}\n\n/** Result of {@link checkForUpdate}. */\nexport interface UpdateStatus {\n /** Always present — echoes `currentVersion`. */\n current: string\n /** `null` when the lookup failed / was skipped / cache was empty. */\n latest: string | null\n /** `true` when `latest` is strictly greater than `current` by semver. */\n hasUpdate: boolean\n /**\n * - `'fresh'` — fetched from the registry this call.\n * - `'cached'` — served from the on-disk cache, including a stale entry\n * when the registry fetch fails.\n * - `'skipped'` — opt-out env var, or no `fetch` and no cache.\n * - `'error'` — registry returned non-2xx or threw; details on `error`.\n */\n source: 'fresh' | 'cached' | 'skipped' | 'error'\n /** Error message when `source === 'error'`. */\n error?: string\n /** When the registry was last queried (ms since epoch). `null` when never. */\n checkedAt: number | null\n /** dist-tag used for the lookup — handy when surfacing \"v5.6.0 (next)\". */\n channel: string\n}\n\n/** A package manager install command. */\nexport interface PackageManagerCommand {\n /** Stable identifier. `npm` is the fallback when no other signal fires. */\n id: PackageManagerId\n /** Argv that, when execve'd, installs `packageName@channel` globally. */\n argv: string[]\n /**\n * Free-form note for the user — explains *why* this command was chosen\n * (e.g. \"Volta-managed install detected via ~/.volta/bin\").\n */\n note?: string\n}\n\n/** Identifier of a supported package manager. */\nexport type PackageManagerId = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'volta'\n\n/** Options for {@link detectPackageManager}. Every field overrides a value read from the running process, which keeps detection testable. */\nexport interface DetectPackageManagerOptions {\n /** Override the resolved binary path (defaults to `process.execPath`). */\n binaryPath?: string\n /** Override the user-agent string (defaults to `process.env.npm_config_user_agent`). */\n userAgent?: string | undefined\n /** Override env. Used in tests. */\n env?: Readonly<Record<string, string | undefined>>\n /** Override `homedir()`. Used in tests. */\n home?: string\n /** Override `process.platform`. Used in tests. */\n platform?: NodeJS.Platform\n /** Package to install. Defaults to `'zidane-tui'`. */\n packageName?: string\n /** dist-tag. Defaults to `'latest'`. */\n channel?: string\n}\n\n// ---------------------------------------------------------------------------\n// Env-var opt-outs\n//\n// `NO_UPDATE_NOTIFIER` is the de-facto npm-ecosystem convention (yeoman,\n// update-notifier). `ZIDANE_NO_UPDATE` is the project-specific knob so\n// hosts wrapping the TUI can wire their own without colliding.\n// `CI=true` is set by every major CI runner; skipping there avoids\n// surprise registry chatter from build pipelines.\n// ---------------------------------------------------------------------------\n\nconst OPT_OUT_VARS = ['ZIDANE_NO_UPDATE', 'NO_UPDATE_NOTIFIER', 'CI'] as const\n\nfunction isOptedOut(env: NodeJS.ProcessEnv = process.env): boolean {\n return envOptedOut(OPT_OUT_VARS, env)\n}\n\n// ---------------------------------------------------------------------------\n// Semver compare — tiny self-contained implementation.\n//\n// We support the subset of semver the npm registry actually returns:\n// `MAJOR.MINOR.PATCH` plus optional `-prerelease[.N…]` and `+build`. Build\n// metadata is ignored per spec; prereleases compare via the standard rule\n// (numeric vs string, longer chain wins after equal prefix).\n//\n// Inlining this beats pulling in `semver` (~70 KB) just to compare two\n// strings. Tested in `test/chat/auto-update.test.ts`.\n// ---------------------------------------------------------------------------\n\n/**\n * Parses a semantic version string into its parts.\n * Accepts an optional leading `v`, a prerelease and build metadata. Returns `null` when the input is not a valid version.\n *\n * @param input - Version string such as `1.2.3` or `v1.2.3-rc.1`.\n */\nexport function parseSemver(input: string): { major: number, minor: number, patch: number, prerelease: string[] } | null {\n const m = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([\\w.-]+))?(?:\\+[\\w.-]+)?$/.exec(input.trim())\n if (!m)\n return null\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n prerelease: m[4] ? m[4].split('.') : [],\n }\n}\n\n/**\n * Returns `-1 / 0 / 1`. Unparseable inputs sort as `0` (assume equal).\n *\n * @param a - First version string.\n * @param b - Second version string.\n */\nexport function compareSemver(a: string, b: string): -1 | 0 | 1 {\n const A = parseSemver(a)\n const B = parseSemver(b)\n if (!A || !B)\n return 0\n for (const k of ['major', 'minor', 'patch'] as const) {\n if (A[k] !== B[k])\n return A[k] < B[k] ? -1 : 1\n }\n // A release version is greater than its prereleases (1.0.0 > 1.0.0-rc.1).\n if (A.prerelease.length === 0 && B.prerelease.length > 0)\n return 1\n if (A.prerelease.length > 0 && B.prerelease.length === 0)\n return -1\n const len = Math.max(A.prerelease.length, B.prerelease.length)\n for (let i = 0; i < len; i++) {\n const ai = A.prerelease[i]\n const bi = B.prerelease[i]\n if (ai === undefined)\n return -1\n if (bi === undefined)\n return 1\n const an = /^\\d+$/.test(ai) ? Number(ai) : null\n const bn = /^\\d+$/.test(bi) ? Number(bi) : null\n if (an !== null && bn !== null) {\n if (an !== bn)\n return an < bn ? -1 : 1\n }\n else if (an !== null) {\n return -1 // numeric < alphanumeric per spec\n }\n else if (bn !== null) {\n return 1\n }\n else if (ai !== bi) {\n return ai < bi ? -1 : 1\n }\n }\n return 0\n}\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\ninterface UpdateCheckFile {\n packageName: string\n channel: string\n latest: string\n checkedAt: number\n /** Last-seen registry ETag — flows through If-None-Match on the next check. */\n etag?: string\n}\n\nfunction cacheFilePath(cacheDir: string, packageName: string, channel: string): string {\n // Embed package + channel in the filename so a host running multiple\n // checks (e.g. against `latest` and `next`) doesn't trample state.\n const slug = `${packageName.replaceAll('/', '__')}@${channel}`\n return resolve(cacheDir, `update-check-${slug}.json`)\n}\n\nfunction readCache(path: string): UpdateCheckFile | null {\n try {\n const raw = readFileSync(path, 'utf8')\n const parsed = JSON.parse(raw) as UpdateCheckFile\n if (typeof parsed.latest !== 'string' || typeof parsed.checkedAt !== 'number')\n return null\n return parsed\n }\n catch {\n return null\n }\n}\n\nfunction writeCache(path: string, entry: UpdateCheckFile): void {\n try {\n mkdirSync(dirname(path), { recursive: true })\n // Format with a trailing newline so hand-inspection in a terminal\n // doesn't render the prompt glued to the closing brace.\n writeFileSync(path, `${JSON.stringify(entry, null, 2)}\\n`)\n }\n catch {\n // Cache failures are non-fatal — the next call just re-fetches.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registry fetch\n// ---------------------------------------------------------------------------\n\n/** Strip a trailing slash so URL assembly is uniform. */\nfunction trimSlash(s: string): string {\n return s.endsWith('/') ? s.slice(0, -1) : s\n}\n\ninterface FetchedRelease {\n latest: string\n etag: string | null\n}\n\nasync function fetchLatestRelease(opts: {\n registry: string\n packageName: string\n channel: string\n etag: string | undefined\n fetcher: typeof fetch\n signal: AbortSignal\n}): Promise<FetchedRelease> {\n // Use the `<pkg>/<dist-tag>` endpoint which returns a single packument\n // (the smallest payload). Falls back to the abbreviated metadata mime\n // type so npm proxies (Verdaccio, JFrog) that don't honor it still\n // return JSON.\n const url = `${trimSlash(opts.registry)}/${encodeURIComponent(opts.packageName)}/${encodeURIComponent(opts.channel)}`\n const headers: Record<string, string> = {\n 'accept': 'application/vnd.npm.install-v1+json, application/json',\n 'user-agent': 'zidane-update-check/1',\n }\n if (opts.etag)\n headers['if-none-match'] = opts.etag\n const res = await opts.fetcher(url, { headers, signal: opts.signal, redirect: 'follow' })\n if (res.status === 304) {\n // Caller falls back to the cached entry when this throws — keep the\n // signal explicit instead of overloading the return shape.\n throw new NotModifiedError()\n }\n if (!res.ok)\n throw new Error(`registry returned ${res.status}`)\n const body = await res.json() as { version?: string, name?: string }\n if (!body.version)\n throw new Error('registry payload missing `version`')\n return { latest: body.version, etag: res.headers.get('etag') }\n}\n\nclass NotModifiedError extends Error {\n constructor() { super('not-modified') }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: checkForUpdate\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the latest available version of `packageName`, honoring an\n * on-disk TTL cache so the warm path costs zero network. Never throws —\n * registry failures degrade to `{ source: 'error', latest: null }`.\n *\n * Designed for boot-path usage: short timeout, swallows errors, opt-outs\n * for CI / restricted shells. Consumers driving the explicit `upgrade`\n * subcommand should set `force: true` to ignore env opt-outs.\n *\n * @param options - Package, current version and cache, network and opt-out settings for the check.\n */\nexport async function checkForUpdate(options: CheckForUpdateOptions): Promise<UpdateStatus> {\n const channel = options.channel ?? 'latest'\n const now = options.now ?? Date.now\n const fetcher = options.fetcher ?? globalThis.fetch\n const ttl = options.cacheTtlMs ?? 24 * 60 * 60 * 1000\n const timeoutMs = options.timeoutMs ?? 3000\n\n if (!options.force && isOptedOut())\n return skipped(options.currentVersion, channel)\n\n const cachePath = options.cacheDir\n ? cacheFilePath(options.cacheDir, options.packageName, channel)\n : null\n\n // Fast-path the warm cache — but only when the cached payload still\n // matches `packageName + channel`. The slug guards against an\n // older zidane writing a different package's cache file at the same\n // path (host rename scenarios). `force` skips the freshness short-\n // circuit entirely: the user-typed `upgrade` path always wants the\n // truth from the registry, never a 24-hour-old cached \"no update\".\n const cached = cachePath ? readCache(cachePath) : null\n if (!options.force && cached && cached.packageName === options.packageName && cached.channel === channel) {\n const fresh = ttl > 0 && (now() - cached.checkedAt) < ttl\n if (fresh) {\n const hasUpdate = compareSemver(cached.latest, options.currentVersion) > 0\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n }\n\n // Fetch is required for the cold path. Falling back to a stale cache is\n // checked further below, but the contract for `skipped` is \"we didn't\n // even try\" — without a fetcher we can't try.\n if (typeof fetcher !== 'function') {\n if (cached) {\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n return skipped(options.currentVersion, channel)\n }\n\n // Tie the user's signal to our timeout so abort propagates. The\n // controller is local to this call — we never leak it to callers.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs)\n if (options.signal) {\n if (options.signal.aborted)\n controller.abort(options.signal.reason as Error)\n else\n options.signal.addEventListener('abort', () => controller.abort(options.signal!.reason as Error), { once: true })\n }\n\n try {\n const release = await fetchLatestRelease({\n registry: options.registry ?? 'https://registry.npmjs.org',\n packageName: options.packageName,\n channel,\n etag: cached?.etag,\n fetcher,\n signal: controller.signal,\n })\n const checkedAt = now()\n if (cachePath) {\n writeCache(cachePath, {\n packageName: options.packageName,\n channel,\n latest: release.latest,\n checkedAt,\n ...(release.etag ? { etag: release.etag } : {}),\n })\n }\n return {\n current: options.currentVersion,\n latest: release.latest,\n hasUpdate: compareSemver(release.latest, options.currentVersion) > 0,\n source: 'fresh',\n checkedAt,\n channel,\n }\n }\n catch (err) {\n if (err instanceof NotModifiedError && cached) {\n // 304 — extend the cache freshness window so we don't re-poll\n // every boot when the registry agrees we're current.\n const checkedAt = now()\n if (cachePath) {\n writeCache(cachePath, {\n ...cached,\n checkedAt,\n })\n }\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt,\n channel,\n }\n }\n if (cached) {\n // Fall back to a stale cache rather than reporting `null` when we\n // have data — the user still sees something useful.\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n return {\n current: options.currentVersion,\n latest: null,\n hasUpdate: false,\n source: 'error',\n error: err instanceof Error ? err.message : String(err),\n checkedAt: null,\n channel,\n }\n }\n finally {\n clearTimeout(timer)\n }\n}\n\nfunction skipped(current: string, channel: string): UpdateStatus {\n return { current, latest: null, hasUpdate: false, source: 'skipped', checkedAt: null, channel }\n}\n\n// ---------------------------------------------------------------------------\n// Package-manager detection\n//\n// Layered heuristics — strongest signal wins:\n// 1. Resolved binary path. Volta lives under `~/.volta`, bun under\n// `~/.bun`, pnpm under `~/.local/share/pnpm`, `~/Library/pnpm` or a\n// `pnpm` path segment, classic yarn under `~/.yarn`.\n// 2. `npm_config_user_agent` env var — set during `npm/pnpm/yarn/bun\n// install` runs, so this still helps when the user is mid-install.\n// 3. Env vars `VOLTA_HOME`, `BUN_INSTALL` and `PNPM_HOME`, trusted less\n// than the path because they can be set for unrelated tooling.\n// 4. Default to npm. Universally available; users on more exotic PMs\n// have to pass `--package-manager` (or set up their env so the path\n// heuristic fires).\n//\n// Volta is treated as a first-class PM here even though it wraps another\n// (typically npm) — `volta install` is the only correct way to update a\n// Volta-pinned tool. Writing through the inner PM would leave Volta's\n// catalog stale and re-pin the old version on the next shell.\n// ---------------------------------------------------------------------------\n\nfunction normalizePath(path: string): string {\n // Resolve symlinks so a `/usr/local/bin/zidane` → `/opt/homebrew/...`\n // chain still matches the inner location. `realpathSync` throws on\n // broken links; fall back to the literal path then.\n try {\n return realpathSync(path)\n }\n catch {\n return path\n }\n}\n\n/**\n * The command to show for a manual reinstall. Volta treats `install` of an\n * already-recorded version as a no-op, so the only way to replace a broken\n * install is to remove it first.\n */\nexport function reinstallHint(command: PackageManagerCommand, packageName: string, channel: string): string {\n const cmd = command.argv.join(' ')\n return command.id === 'volta' ? `volta uninstall ${packageName} && volta install ${packageName}@${channel}` : cmd\n}\n\n/**\n * Chooses the package manager command that installs the package globally.\n * Checks the binary path, then the npm user agent, then environment hints such as `VOLTA_HOME`, and falls back to `npm`.\n *\n * @param opts - Optional overrides for the binary path, environment, platform, package name and channel.\n */\nexport function detectPackageManager(opts: DetectPackageManagerOptions = {}): PackageManagerCommand {\n const env = opts.env ?? process.env\n const home = opts.home ?? homedir()\n const platform = opts.platform ?? process.platform\n const channel = opts.channel ?? 'latest'\n const packageName = opts.packageName ?? 'zidane-tui'\n const binaryPath = opts.binaryPath ?? process.execPath\n const resolvedBinary = normalizePath(binaryPath)\n const userAgent = opts.userAgent ?? env.npm_config_user_agent\n\n // The argv contract:\n // - `argv[0]` is the binary we exec — needs to be on PATH or absolute.\n // - Remaining tokens are the install command.\n // We rely on PATH resolution so users with shimmed wrappers (volta,\n // mise, asdf) still hit their managed binary.\n const target = `${packageName}@${channel}`\n\n // 1. Path-based heuristics ------------------------------------------------\n //\n // Match path SEGMENTS, not substrings — `/Users/me/.volta-old/bin/x` must\n // NOT match `/Users/me/.volta`. We anchor the comparison to a separator\n // on both sides so the directory name has to be exact.\n\n const containsSegment = (path: string, segment: string) => {\n // `segment` is `<home>/.<name>` (no trailing separator). To match, the\n // path must contain `<segment><sep>` OR end with `<segment>`.\n const tail = `${segment}${sep}`\n return path.includes(tail) || path.endsWith(segment)\n }\n const inPath = (segment: string) =>\n containsSegment(resolvedBinary, segment) || containsSegment(binaryPath, segment)\n const homeSeg = (rest: string) => `${home}${sep}${rest}`\n\n // Binary-path check — strongest signal. A binary living under `~/.volta/`\n // was DEFINITELY installed by Volta; same logic for the others.\n if (inPath(homeSeg('.volta'))) {\n return {\n id: 'volta',\n argv: ['volta', 'install', target],\n note: 'Volta-managed install detected, refreshing via `volta install`.',\n }\n }\n if (inPath(homeSeg('.bun'))) {\n return {\n id: 'bun',\n argv: ['bun', 'add', '--global', target],\n note: 'Bun global install detected.',\n }\n }\n if (\n inPath(homeSeg('.local/share/pnpm'))\n || inPath(homeSeg('Library/pnpm'))\n || resolvedBinary.includes(`${sep}pnpm${sep}`)\n ) {\n return {\n id: 'pnpm',\n argv: ['pnpm', 'add', '--global', target],\n note: 'pnpm global install detected.',\n }\n }\n if (inPath(homeSeg('.yarn'))) {\n return {\n id: 'yarn',\n argv: ['yarn', 'global', 'add', target],\n note: 'Yarn classic global install detected.',\n }\n }\n\n // 2. User-agent heuristic (mid-install or recently-installed shells) ------\n // `npm_config_user_agent` looks like `npm/10.2.3 node/v22.0.0 darwin arm64`\n // or `pnpm/8.15.0 npm/? node/v22.0.0 darwin arm64`. This fires when the\n // binary lives in an unrecognized prefix (corporate npm proxy, custom\n // global root, …) but the calling shell still identifies a PM.\n if (userAgent) {\n const ua = userAgent.toLowerCase()\n if (ua.startsWith('pnpm/'))\n return { id: 'pnpm', argv: ['pnpm', 'add', '--global', target] }\n if (ua.startsWith('yarn/'))\n return { id: 'yarn', argv: ['yarn', 'global', 'add', target] }\n if (ua.startsWith('bun/'))\n return { id: 'bun', argv: ['bun', 'add', '--global', target] }\n if (ua.startsWith('npm/'))\n return { id: 'npm', argv: ['npm', 'install', '--global', target] }\n }\n\n // 3. Env-var fallback — only fires when both the path and the user-agent\n // failed to identify a PM. We trust these LESS than the binary path\n // because a user can install zidane outside their main PM (e.g. via\n // npm) while still having $VOLTA_HOME / $BUN_INSTALL set for unrelated\n // tooling.\n if (env.VOLTA_HOME)\n return { id: 'volta', argv: ['volta', 'install', target], note: '$VOLTA_HOME set, using `volta install`. If the install is not Volta-managed, pass `--package-manager npm`.' }\n if (env.BUN_INSTALL)\n return { id: 'bun', argv: ['bun', 'add', '--global', target] }\n if (env.PNPM_HOME)\n return { id: 'pnpm', argv: ['pnpm', 'add', '--global', target] }\n\n // 4. Default to npm — universally available.\n\n void platform // Reserved for Windows-specific tweaks (none yet).\n return {\n id: 'npm',\n argv: ['npm', 'install', '--global', target],\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: tarball readiness\n// ---------------------------------------------------------------------------\n\n/** Options for {@link checkTarballAvailable}. */\nexport interface TarballAvailabilityOptions {\n /** Full npm package name to probe. */\n packageName: string\n /** Exact version whose tarball is probed. */\n version: string\n /** Registry base URL. Defaults to `https://registry.npmjs.org`. */\n registry?: string\n /** Timeout for the whole probe. Defaults to 8 000 ms. */\n timeoutMs?: number\n /** Inject for tests. */\n fetcher?: typeof fetch\n}\n\n/**\n * Whether `<packageName>@<version>` can actually be DOWNLOADED right now, not\n * merely resolved. Right after a publish the registry serves the new\n * manifest minutes before the tarball reaches the CDN; a package manager\n * that hits that window on an `optionalDependencies` platform binary\n * silently skips it and leaves a bin with nothing behind it. `upgrade` asks\n * this before spawning the PM so it can wait instead of breaking the install.\n *\n * - `'ok'` tarball responds 2xx\n * - `'missing'` manifest or tarball 404 (published, not propagated)\n * - `'unknown'` network / registry error; the caller decides\n *\n * @param opts - Package, version and registry settings for the availability probe.\n */\nexport async function checkTarballAvailable(opts: TarballAvailabilityOptions): Promise<'ok' | 'missing' | 'unknown'> {\n const fetcher = opts.fetcher ?? globalThis.fetch\n const registry = trimSlash(opts.registry ?? 'https://registry.npmjs.org')\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? 8000)\n try {\n const manifest = await fetcher(\n `${registry}/${encodeURIComponent(opts.packageName)}/${encodeURIComponent(opts.version)}`,\n { headers: { 'accept': 'application/json', 'user-agent': 'zidane-update-check/1' }, signal: ac.signal, redirect: 'follow' },\n )\n if (manifest.status === 404)\n return 'missing'\n if (!manifest.ok)\n return 'unknown'\n const body = await manifest.json() as { dist?: { tarball?: string } }\n const tarball = body.dist?.tarball\n if (!tarball)\n return 'unknown'\n const head = await fetcher(tarball, { method: 'HEAD', signal: ac.signal, redirect: 'follow' })\n if (head.status === 404)\n return 'missing'\n return head.ok ? 'ok' : 'unknown'\n }\n catch {\n return 'unknown'\n }\n finally {\n clearTimeout(timer)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: performSelfUpdate (PM-spawned)\n// ---------------------------------------------------------------------------\n\n/** Options for {@link performSelfUpdate}. */\nexport interface PerformSelfUpdateOptions {\n /** Full npm package name to install. */\n packageName: string\n /** dist-tag to install. Defaults to `'latest'`. */\n channel?: string\n /** Force a specific PM instead of auto-detect. */\n packageManager?: PackageManagerId | 'auto'\n /** Don't actually spawn — return the command we WOULD run. */\n dryRun?: boolean\n /** When set, write `pm stdout/stderr` to these streams instead of stdio inherit. */\n stdout?: NodeJS.WritableStream\n /** Stream that receives the package manager's stderr instead of inheriting stdio. */\n stderr?: NodeJS.WritableStream\n /** Abort the install. */\n signal?: AbortSignal\n /** Override env (e.g. set `npm_config_loglevel=error`). */\n env?: Readonly<Record<string, string>>\n /** Inject for tests. */\n spawn?: typeof spawn\n /** Inject for tests. */\n detect?: typeof detectPackageManager\n}\n\n/** Outcome of {@link performSelfUpdate}. */\nexport interface SelfUpdateResult {\n /** The package manager command that was, or would be, run. */\n command: PackageManagerCommand\n /** PM exit code. `null` when `dryRun` or the process was killed. */\n exitCode: number | null\n /** Set when the spawn itself failed (`ENOENT`, `EACCES`). */\n spawnError?: string\n}\n\n/**\n * Re-install the package globally via the host's package manager. Resolves\n * with the spawned command's exit code. It does not reject: a spawn failure\n * comes back as `spawnError`, an abort of the caller's `signal` kills the\n * child (`exitCode: null`), and PM failures surface as a non-zero `exitCode`.\n * The caller decides whether to retry or fall back to in-place.\n *\n * @param options - Package, channel, package manager and stream settings for the install.\n */\nexport async function performSelfUpdate(options: PerformSelfUpdateOptions): Promise<SelfUpdateResult> {\n const detect = options.detect ?? detectPackageManager\n const command = options.packageManager && options.packageManager !== 'auto'\n ? overrideManager(options.packageManager, options.packageName, options.channel ?? 'latest')\n : detect({ packageName: options.packageName, channel: options.channel ?? 'latest' })\n\n if (options.dryRun)\n return { command, exitCode: null }\n\n const useSpawn = options.spawn ?? spawn\n return await new Promise<SelfUpdateResult>((resolveP) => {\n const [bin, ...args] = command.argv\n const child = useSpawn(bin!, args, {\n stdio: options.stdout || options.stderr\n ? ['ignore', 'pipe', 'pipe']\n : 'inherit',\n env: { ...process.env, ...options.env },\n })\n if (options.stdout && child.stdout)\n child.stdout.pipe(options.stdout)\n if (options.stderr && child.stderr)\n child.stderr.pipe(options.stderr)\n child.on('error', (err) => {\n // ENOENT here means the PM binary isn't on PATH — surface a\n // clear message rather than the bare error.\n resolveP({ command, exitCode: null, spawnError: err.message })\n })\n child.on('exit', (code) => {\n resolveP({ command, exitCode: code })\n })\n if (options.signal) {\n const onAbort = () => child.kill()\n if (options.signal.aborted)\n onAbort()\n else\n options.signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n\n/** The global-install command that pins `<packageName>@<channel>` for package manager `id`. */\nexport function overrideManager(id: PackageManagerId, packageName: string, channel: string): PackageManagerCommand {\n const target = `${packageName}@${channel}`\n switch (id) {\n case 'pnpm': return { id, argv: ['pnpm', 'add', '--global', target] }\n case 'yarn': return { id, argv: ['yarn', 'global', 'add', target] }\n case 'bun': return { id, argv: ['bun', 'add', '--global', target] }\n case 'volta': return { id, argv: ['volta', 'install', target] }\n case 'npm':\n default: return { id: 'npm', argv: ['npm', 'install', '--global', target] }\n }\n}\n\n// ---------------------------------------------------------------------------\n// In-place self-update\n//\n// Downloads `<packageName>@<channel>` from the npm registry, verifies its\n// integrity digest, untars the `bin/<filename>` entry, and atomic-renames it\n// over the live binary. The host needs to know:\n// - the platform-specific package name to fetch (e.g. `zidane-tui-darwin-arm64`)\n// - the path inside that tarball where the binary lives (defaults to\n// `bin/<binaryName>`, matched as `package/bin/<binaryName>`, which fits the\n// project layout under `tui/npm/<key>/bin/`)\n// - the path of the running binary, so we know where to write\n//\n// Volta-managed installs are explicitly refused — Volta's catalog needs\n// the PM path. The caller should detect via `detectPackageManager` and\n// fall through to `performSelfUpdate` instead.\n// ---------------------------------------------------------------------------\n\n/** Options for {@link performInPlaceSelfUpdate}. */\nexport interface PerformInPlaceUpdateOptions {\n /** Platform-specific tarball package, e.g. `'zidane-tui-darwin-arm64'`. */\n packageName: string\n /** dist-tag to install. Defaults to `'latest'`. */\n channel?: string\n /** Resolved path of the running binary. Defaults to `process.execPath`. */\n binaryPath?: string\n /** Inside the tarball — usually `bin/<name>`. Defaults to `bin/<basename(binaryPath)>`. */\n tarballBinaryPath?: string\n /** Registry base URL. Defaults to `https://registry.npmjs.org`. */\n registry?: string\n /** Network timeout for the whole update, metadata and download. Defaults to 30 000 ms — tarballs are MB-scale. */\n timeoutMs?: number\n /** Abort signal for the download. */\n signal?: AbortSignal\n /** Don't replace — just download, verify and extract. */\n dryRun?: boolean\n /** Fetch implementation. Defaults to `globalThis.fetch`. */\n fetcher?: typeof fetch\n /** When set, refuse the swap if the resolved binary lives under one of these prefixes. */\n refuseUnderPrefixes?: readonly string[]\n}\n\n/** Outcome of {@link performInPlaceSelfUpdate}. */\nexport interface InPlaceUpdateResult {\n /** `'success' | 'refused' | 'failed'`. */\n status: 'success' | 'refused' | 'failed'\n /** Version that was installed (from the tarball's package.json). */\n installedVersion?: string\n /** Why we bailed. Always present on `refused` / `failed`. */\n reason?: string\n /** Resolved binary path after the swap. */\n binaryPath?: string\n}\n\n/**\n * Replace the running binary with the latest tarball-shipped one. Works on\n * macOS + Linux (POSIX unlink-while-running); refuses on Windows. Returns\n * a result object instead of throwing — callers usually want to print\n * `result.reason` and fall back to a PM-driven update.\n *\n * @param options - Platform package, binary path, registry and swap restrictions.\n */\nexport async function performInPlaceSelfUpdate(options: PerformInPlaceUpdateOptions): Promise<InPlaceUpdateResult> {\n if (process.platform === 'win32')\n return { status: 'refused', reason: 'in-place update not supported on Windows; use `zidane upgrade` instead.' }\n\n const binaryPath = options.binaryPath ?? process.execPath\n const resolved = normalizePath(binaryPath)\n const refused = options.refuseUnderPrefixes\n if (refused?.some(p => resolved.startsWith(p))) {\n return { status: 'refused', reason: `binary at ${resolved} sits under a managed prefix; use \\`zidane upgrade\\` to update via your package manager.` }\n }\n // Volta is the canonical \"managed prefix\" — refuse by default so users\n // don't end up with a stale catalog after the swap.\n if (resolved.includes(`${homedir()}${sep}.volta${sep}`)) {\n return { status: 'refused', reason: 'Volta-managed install detected. Use `zidane upgrade` (runs `volta install`) instead.' }\n }\n\n const fetcher = options.fetcher ?? globalThis.fetch\n if (typeof fetcher !== 'function')\n return { status: 'failed', reason: 'global fetch unavailable; rebuild with Node ≥ 18 or Bun.' }\n\n const channel = options.channel ?? 'latest'\n const registry = options.registry ?? 'https://registry.npmjs.org'\n const timeoutMs = options.timeoutMs ?? 30_000\n\n // 1. Resolve dist-tag → packument entry for the exact version + tarball URL.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs)\n if (options.signal) {\n if (options.signal.aborted)\n controller.abort(options.signal.reason as Error)\n else\n options.signal.addEventListener('abort', () => controller.abort(options.signal!.reason as Error), { once: true })\n }\n\n try {\n const metaUrl = `${trimSlash(registry)}/${encodeURIComponent(options.packageName)}/${encodeURIComponent(channel)}`\n const metaRes = await fetcher(metaUrl, {\n headers: { 'accept': 'application/json', 'user-agent': 'zidane-update-check/1' },\n signal: controller.signal,\n })\n if (!metaRes.ok)\n return { status: 'failed', reason: `registry returned ${metaRes.status} for ${options.packageName}@${channel}` }\n const meta = await metaRes.json() as {\n version?: string\n dist?: { tarball?: string, integrity?: string, shasum?: string }\n }\n if (!meta.version || !meta.dist?.tarball)\n return { status: 'failed', reason: `registry payload missing version or tarball for ${options.packageName}@${channel}` }\n // Defense-in-depth: the npm registry serves over HTTPS, but a user\n // running with `--registry <custom>` might point at a less-trusted\n // mirror. Refuse to overwrite the live binary if the registry didn't\n // ship at least one integrity field for us to verify.\n if (!meta.dist.integrity && !meta.dist.shasum)\n return { status: 'failed', reason: `registry payload missing integrity / shasum for ${options.packageName}@${channel}; refusing to overwrite the binary.` }\n\n // 2. Stage adjacent to the target binary's directory so the final\n // `rename` stays on a single filesystem (avoids EXDEV on Linux where\n // /tmp is often tmpfs). Falls back to the OS tmpdir if the target's\n // parent is non-writable.\n const targetDir = dirname(binaryPath)\n let stagingDir: string\n try {\n stagingDir = mkdtempSync(join(targetDir, '.zidane-update-'))\n }\n catch {\n stagingDir = mkdtempSync(join(tmpdir(), 'zidane-update-'))\n }\n const tarballPath = join(stagingDir, 'package.tgz')\n\n try {\n const tgzRes = await fetcher(meta.dist.tarball, {\n headers: { 'user-agent': 'zidane-update-check/1' },\n signal: controller.signal,\n })\n if (!tgzRes.ok || !tgzRes.body)\n return { status: 'failed', reason: `tarball download failed: ${tgzRes.status}` }\n const digests = await streamToFile(tgzRes.body, tarballPath)\n\n // 3. Verify integrity. Prefer SRI (sha512) when present; fall back\n // to the legacy `shasum` (SHA-1) which every package on the registry\n // still carries. A mismatch is a hard refusal — never overwrite the\n // live binary with content we can't authenticate.\n const integrityCheck = verifyIntegrity(meta.dist, digests)\n if (!integrityCheck.ok)\n return { status: 'failed', reason: `integrity check failed: ${integrityCheck.reason}` }\n\n // 4. Extract just the binary entry.\n const tarballBinaryPath = options.tarballBinaryPath\n ?? `bin/${binaryNameFrom(binaryPath)}`\n const extractedPath = await extractEntryFromTarball(tarballPath, tarballBinaryPath, stagingDir)\n if (!extractedPath)\n return { status: 'failed', reason: `tarball did not contain ${tarballBinaryPath}` }\n\n if (options.dryRun) {\n return { status: 'success', installedVersion: meta.version, binaryPath: extractedPath }\n }\n\n // 5. Mark executable + atomic rename. POSIX permits unlink-while-running.\n chmodSync(extractedPath, 0o755)\n const targetForSwap = binaryPath\n try {\n renameSync(extractedPath, targetForSwap)\n }\n catch (err) {\n return { status: 'failed', reason: `failed to write ${targetForSwap}: ${err instanceof Error ? err.message : String(err)}` }\n }\n return { status: 'success', installedVersion: meta.version, binaryPath: targetForSwap }\n }\n finally {\n try { rmSync(stagingDir, { recursive: true, force: true }) }\n catch { /* best-effort */ }\n }\n }\n catch (err) {\n return { status: 'failed', reason: err instanceof Error ? err.message : String(err) }\n }\n finally {\n clearTimeout(timer)\n }\n}\n\nfunction binaryNameFrom(path: string): string {\n const last = path.split(/[\\\\/]/).at(-1) ?? ''\n return last.endsWith('.exe') ? last.slice(0, -4) : last\n}\n\ninterface StreamDigests {\n sha1: string\n sha512Base64: string\n size: number\n}\n\n/**\n * Stream `body` to `path` while computing integrity digests on the fly —\n * we never load the whole tarball into a Buffer just to hash it. Returns\n * SHA-1 (hex, for the legacy `dist.shasum`) and SHA-512 (base64, for SRI's\n * `sha512-<base64>`).\n */\nasync function streamToFile(body: ReadableStream<Uint8Array>, path: string): Promise<StreamDigests> {\n const file = createWriteStream(path)\n const sha1 = createHash('sha1')\n const sha512 = createHash('sha512')\n let size = 0\n const reader = body.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done)\n break\n if (value && value.byteLength > 0) {\n sha1.update(value)\n sha512.update(value)\n size += value.byteLength\n await new Promise<void>((res, rej) => file.write(value, err => err ? rej(err) : res()))\n }\n }\n }\n finally {\n await new Promise<void>(res => file.end(() => res()))\n }\n return {\n sha1: sha1.digest('hex'),\n sha512Base64: sha512.digest('base64'),\n size,\n }\n}\n\n/**\n * Validate the downloaded tarball's digest against the registry's\n * `dist.integrity` (SRI: only `sha512-<base64>` can match) or, when the\n * registry ships no SRI, the legacy `dist.shasum` (hex SHA-1). We don't\n * accept \"neither provided\" because that's what `performInPlaceSelfUpdate`\n * already short-circuited above.\n */\nfunction verifyIntegrity(\n dist: { integrity?: string, shasum?: string },\n digests: StreamDigests,\n): { ok: true } | { ok: false, reason: string } {\n if (dist.integrity) {\n // SRI may carry multiple space-separated hashes — any match wins.\n const entries = dist.integrity.split(/\\s+/).filter(Boolean)\n for (const entry of entries) {\n const m = /^(sha(?:256|384|512))-(.+)$/.exec(entry)\n if (!m)\n continue\n const algo = m[1]!\n const expected = m[2]!\n if (algo === 'sha512' && digests.sha512Base64 === expected)\n return { ok: true }\n // A non-512 SRI is unusual but legal. We do not recompute other\n // algorithms, so fail closed and refuse rather than skip the check.\n if (algo !== 'sha512')\n return { ok: false, reason: `unsupported SRI algorithm \\`${algo}\\`. Re-run via your package manager.` }\n }\n return { ok: false, reason: 'SRI mismatch: tarball does not match the registry\\'s `dist.integrity`.' }\n }\n if (dist.shasum) {\n if (digests.sha1 === dist.shasum.toLowerCase())\n return { ok: true }\n return { ok: false, reason: 'SHA-1 mismatch: tarball does not match the registry\\'s `dist.shasum`.' }\n }\n return { ok: false, reason: 'no integrity field shipped by the registry.' }\n}\n\n// ---------------------------------------------------------------------------\n// Minimal tar extractor — POSIX ustar format. Only handles the entries\n// produced by `npm pack`: filenames < 100 bytes, no PAX extended headers\n// (npm only emits them for paths > 100 chars; we already root the binary\n// at `bin/<name>` which stays well under that), no GNU long-name records.\n// ---------------------------------------------------------------------------\n\nasync function extractEntryFromTarball(\n tarballPath: string,\n entryRelPath: string,\n outDir: string,\n): Promise<string | null> {\n const matchSuffix = `/${entryRelPath}`\n const out = join(outDir, 'binary.bin')\n\n // Read the whole tarball into memory — platform packages are tens of MB,\n // well within the budget for a temp buffer.\n const chunks: Uint8Array[] = []\n const gunzip = createGunzip()\n const input = createReadStream(tarballPath)\n await new Promise<void>((res, rej) => {\n gunzip.on('data', (c: Buffer) => chunks.push(c))\n gunzip.on('end', () => res())\n gunzip.on('error', rej)\n input.on('error', rej)\n input.pipe(gunzip)\n })\n const tar = concatChunks(chunks)\n let offset = 0\n while (offset + 512 <= tar.length) {\n const header = tar.subarray(offset, offset + 512)\n if (header.every(b => b === 0))\n break\n const name = readNulString(header, 0, 100)\n const sizeOct = readNulString(header, 124, 12).trim()\n const size = sizeOct === '' ? 0 : Number.parseInt(sizeOct, 8)\n const dataStart = offset + 512\n const dataEnd = dataStart + size\n if (name && (name === entryRelPath || name.endsWith(matchSuffix))) {\n writeFileSync(out, tar.subarray(dataStart, dataEnd))\n return out\n }\n // 512-byte aligned padding.\n offset = dataEnd + ((512 - (size % 512)) % 512)\n }\n return null\n}\n\nfunction readNulString(buf: Uint8Array, start: number, len: number): string {\n const end = start + len\n let stop = end\n for (let i = start; i < end; i++) {\n if (buf[i] === 0) { stop = i; break }\n }\n return new TextDecoder('utf8').decode(buf.subarray(start, stop))\n}\n\nfunction concatChunks(chunks: Uint8Array[]): Uint8Array {\n let total = 0\n for (const c of chunks) total += c.byteLength\n const out = new Uint8Array(total)\n let offset = 0\n for (const c of chunks) {\n out.set(c, offset)\n offset += c.byteLength\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Resolve the platform-specific tarball package\n//\n// Mirrors the resolution table in `tui/bin/zidane.mjs` — `darwin/arm64`,\n// `darwin/x64`, `linux/x64` (glibc), `linux/arm64` (glibc). Returns\n// `null` for unsupported targets so the caller can print a clear message.\n// ---------------------------------------------------------------------------\n\n/** Options for {@link resolvePlatformPackage}. */\nexport interface PlatformTarballOptions {\n /** Override `process.platform`. */\n platform?: NodeJS.Platform\n /** Override `process.arch`. */\n arch?: NodeJS.Architecture | string\n /** Override the libc check (returned from `detectLibc`). */\n libc?: 'glibc' | 'musl'\n /** Prefix joined to the platform key. Defaults to `'zidane-tui-'`. */\n prefix?: string\n}\n\n/**\n * Resolves the platform-specific tarball package name for the current machine.\n * Supports macOS on arm64 and x64, and Linux on arm64 and x64 with glibc. Returns `null` for other targets.\n *\n * @param opts - Optional overrides for platform, architecture, libc and name prefix.\n */\nexport function resolvePlatformPackage(opts: PlatformTarballOptions = {}): string | null {\n const platform = opts.platform ?? process.platform\n const arch = opts.arch ?? process.arch\n const libc = opts.libc ?? detectLibc()\n const prefix = opts.prefix ?? 'zidane-tui-'\n if (platform === 'darwin') {\n if (arch === 'arm64')\n return `${prefix}darwin-arm64`\n if (arch === 'x64')\n return `${prefix}darwin-x64`\n return null\n }\n if (platform === 'linux') {\n if (libc !== 'glibc')\n return null\n if (arch === 'arm64')\n return `${prefix}linux-arm64`\n if (arch === 'x64')\n return `${prefix}linux-x64`\n return null\n }\n return null\n}\n\n/** Mirrors `tui/bin/zidane.mjs#detectLibc`. Reports `'glibc'` off Linux and `'musl'` when `ldd --version` fails or names musl. */\nexport function detectLibc(): 'glibc' | 'musl' {\n if (process.platform !== 'linux')\n return 'glibc'\n try {\n // eslint-disable-next-line ts/no-require-imports\n const { execSync } = require('node:child_process') as typeof import('node:child_process')\n const out = execSync('ldd --version 2>&1', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }) as string\n return out.toLowerCase().includes('musl') ? 'musl' : 'glibc'\n }\n catch {\n return 'musl'\n }\n}\n\n// ---------------------------------------------------------------------------\n// `_` re-exports for tests that don't want to mock through the module\n// boundary. Not part of the public surface; keep names underscored.\n// ---------------------------------------------------------------------------\n\nexport const _internal = {\n cacheFilePath,\n readCache,\n writeCache,\n binaryNameFrom,\n extractEntryFromTarball,\n}\n","// ---------------------------------------------------------------------------\n// Companion packages — sibling globals that `zidane upgrade` keeps in step\n// with the TUI. Today that is `zidane-gui`: same version line, same npm\n// release, but installed separately (and possibly with a different package\n// manager). This module answers one question — \"is <package> installed on\n// this machine, at what version, and where?\" — so the CLI can check the\n// registry for it and re-run the install through the PM that owns it.\n//\n// Discovery is by the package's bin on PATH, not by asking a PM: `npm ls -g`\n// is slow, differs per manager, and lies under Volta. The bin is what the\n// user actually invokes, so it is also the most honest signal of \"installed\".\n// ---------------------------------------------------------------------------\n\nimport { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { delimiter, dirname, join, sep } from 'node:path'\n\n/** Identifies a companion package and its executable. */\nexport interface CompanionSpec {\n /** npm package name, e.g. `'zidane-gui'`. */\n packageName: string\n /** Executable name on PATH. Defaults to `packageName`. */\n binName?: string\n}\n\n/** A companion package found on disk. */\nexport interface InstalledCompanion {\n /** npm package name. */\n packageName: string\n /** Version from the installed `package.json`. */\n version: string\n /** Root of the installed package. */\n packageDir: string\n /**\n * The bin as found on PATH (NOT resolved). `detectPackageManager` wants\n * this: `~/.volta/bin/<name>` identifies Volta even though its realpath is\n * the generic `volta-shim`.\n */\n binaryPath: string\n}\n\nexport interface FindCompanionOptions {\n env?: Readonly<Record<string, string | undefined>>\n home?: string\n platform?: NodeJS.Platform\n}\n\n/**\n * Locate a globally installed companion. `null` when its bin is not on PATH\n * or no `package.json` with that name can be found behind it.\n *\n * @param spec - The package and its bin name.\n * @param opts - Optional overrides for the environment, home directory and platform.\n */\nexport function findInstalledCompanion(spec: CompanionSpec, opts: FindCompanionOptions = {}): InstalledCompanion | null {\n const env = opts.env ?? process.env\n const home = opts.home ?? homedir()\n const platform = opts.platform ?? process.platform\n const binName = spec.binName ?? spec.packageName\n\n const binaryPath = findOnPath(binName, env.PATH ?? '', platform)\n if (!binaryPath)\n return null\n\n const packageDir = resolvePackageDir(spec.packageName, binaryPath, home)\n if (!packageDir)\n return null\n\n const version = readPackageVersion(packageDir, spec.packageName)\n if (!version)\n return null\n\n return { packageName: spec.packageName, version, packageDir, binaryPath }\n}\n\nfunction findOnPath(binName: string, pathVar: string, platform: NodeJS.Platform): string | null {\n const candidates = platform === 'win32' ? [binName, `${binName}.cmd`, `${binName}.exe`] : [binName]\n for (const dir of pathVar.split(delimiter)) {\n if (!dir)\n continue\n for (const name of candidates) {\n const full = join(dir, name)\n try {\n if (statSync(full).isFile())\n return full\n }\n catch {\n // not here\n }\n }\n }\n return null\n}\n\n/**\n * From the bin to the package root. Two strategies:\n * 1. Follow the symlink (`<prefix>/bin/zidane-gui` → `…/node_modules/\n * zidane-gui/bin/zidane-gui.mjs`) and climb until a `package.json`\n * carries the expected name. Covers npm, pnpm, yarn and bun.\n * 2. Volta: the bin is a shim whose realpath is `volta-shim`, so nothing to\n * climb. Its global packages live at a fixed layout under `$VOLTA_HOME`\n * (the same one the GUI installer maps staging paths to).\n */\nfunction resolvePackageDir(packageName: string, binaryPath: string, home: string): string | null {\n let resolved = binaryPath\n try {\n resolved = realpathSync(binaryPath)\n }\n catch {\n // dangling link — fall through to the Volta layout check\n }\n\n const climbed = climbToPackage(dirname(resolved), packageName)\n if (climbed)\n return climbed\n\n const voltaHome = voltaHomeFor(binaryPath, resolved, home)\n if (voltaHome) {\n const dir = join(voltaHome, 'tools', 'image', 'packages', packageName, 'lib', 'node_modules', packageName)\n if (existsSync(join(dir, 'package.json')))\n return dir\n }\n return null\n}\n\nfunction climbToPackage(start: string, packageName: string): string | null {\n let dir = start\n for (let depth = 0; depth < 6; depth++) {\n if (readPackageVersion(dir, packageName))\n return dir\n const parent = dirname(dir)\n if (parent === dir)\n break\n dir = parent\n }\n return null\n}\n\nfunction voltaHomeFor(binaryPath: string, resolved: string, home: string): string | null {\n const marker = `${sep}.volta${sep}`\n for (const p of [binaryPath, resolved]) {\n const idx = p.indexOf(marker)\n if (idx !== -1)\n return p.slice(0, idx + marker.length - 1)\n }\n const fallback = join(home, '.volta')\n return existsSync(fallback) ? fallback : null\n}\n\nfunction readPackageVersion(dir: string, packageName: string): string | null {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name?: unknown, version?: unknown }\n if (pkg.name !== packageName || typeof pkg.version !== 'string')\n return null\n return pkg.version\n }\n catch {\n return null\n }\n}\n","// ---------------------------------------------------------------------------\n// \"A newer version is on disk — restart to use it.\"\n//\n// Two things live here:\n//\n// 1. The APPLIED MARKER — `<cacheDir>/update-applied.json`, written by every\n// path that installs a release (`zidane upgrade`, the GUI's in-app\n// update). It is a wake-up call, not the truth: running surfaces watch\n// the file and re-probe the disk when it changes.\n//\n// 2. The INSTALLED-VERSION WATCHER — compares the version the running\n// process was built with against the version its package has on disk\n// (resolved from the bin on PATH, the same way `zidane upgrade` finds\n// companions). `restartRequired` when disk > running. Re-probes on the\n// marker, on a slow poll (an upgrade run from another machine's shell\n// over the same home dir writes no local marker), and on demand\n// (`check()` — the GUI calls it on window focus).\n//\n// Renderer-agnostic: Node `fs` only, no React except the hook at the bottom.\n// ---------------------------------------------------------------------------\n\nimport type { FSWatcher } from 'node:fs'\nimport type { CompanionSpec, FindCompanionOptions, InstalledCompanion } from './auto-update-companion'\nimport { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { useEffect, useState } from 'react'\nimport { compareSemver } from './auto-update'\nimport { findInstalledCompanion } from './auto-update-companion'\n\n/** File name of the update-applied marker inside the cache directory. */\nexport const UPDATE_APPLIED_MARKER = 'update-applied.json'\n\n/** Record written after an install lands, so running processes can notice it before the next poll. */\nexport interface UpdateAppliedMarker {\n /** ms since epoch. */\n at: number\n /** Every package the run installed, with the version now on disk. */\n packages: Array<{ packageName: string, version: string }>\n}\n\n/**\n * Returns the path of the update-applied marker file.\n *\n * @param cacheDir - Directory that holds the marker file.\n */\nexport function updateAppliedMarkerPath(cacheDir: string): string {\n return join(cacheDir, UPDATE_APPLIED_MARKER)\n}\n\n/**\n * Record that an install just landed. Best-effort: a failure to write the\n * marker must never fail the upgrade that succeeded — the poll catches up.\n *\n * @param cacheDir - Directory that holds the marker file. Created when missing.\n * @param marker - The record to write.\n */\nexport function writeUpdateAppliedMarker(cacheDir: string, marker: UpdateAppliedMarker): boolean {\n try {\n mkdirSync(cacheDir, { recursive: true })\n writeFileSync(updateAppliedMarkerPath(cacheDir), `${JSON.stringify(marker, null, 2)}\\n`)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Reads the update-applied marker.\n * Returns `null` when the file is missing, unreadable or malformed. Package entries with a non-string name or version are dropped.\n *\n * @param cacheDir - Directory that holds the marker file.\n */\nexport function readUpdateAppliedMarker(cacheDir: string): UpdateAppliedMarker | null {\n try {\n const raw = JSON.parse(readFileSync(updateAppliedMarkerPath(cacheDir), 'utf8')) as unknown\n if (!raw || typeof raw !== 'object')\n return null\n const { at, packages } = raw as Partial<UpdateAppliedMarker>\n if (typeof at !== 'number' || !Array.isArray(packages))\n return null\n return {\n at,\n packages: packages.filter(p => p && typeof p.packageName === 'string' && typeof p.version === 'string'),\n }\n }\n catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Installed-version watcher\n// ---------------------------------------------------------------------------\n\n/** Snapshot of the running version against the version installed on disk. */\nexport interface InstalledVersionState {\n /** Version the running process was built with. */\n running: string\n /** Version found on disk for the package; `null` when the probe found nothing (dev checkout, PATH without the bin). */\n installed: string | null\n /** `installed` is strictly newer than `running` — the process is stale. */\n restartRequired: boolean\n /** When the disk was last probed (ms since epoch). */\n checkedAt: number\n}\n\n/** Options for {@link watchInstalledVersion}. */\nexport interface WatchInstalledVersionOptions {\n /** The running package, e.g. `{ packageName: 'zidane-tui', binName: 'zidane' }`. */\n spec: CompanionSpec\n /** Inlined at build time. An empty string disables the watcher (dev runs). */\n currentVersion: string\n /** Where the applied marker lives. Omit to rely on the poll alone. */\n cacheDir?: string\n /** Poll interval. Defaults to 5 min; `0` disables the poll. */\n pollMs?: number\n /** Debounce for marker events (installs touch the file more than once). Defaults to 500 ms. */\n debounceMs?: number\n /** Called with the new state when the installed version or restart flag changes. */\n onChange: (state: InstalledVersionState) => void\n /** Injected for tests. */\n probe?: (spec: CompanionSpec, opts?: FindCompanionOptions) => InstalledCompanion | null\n /** Injected for tests. */\n now?: () => number\n}\n\n/** Handle returned by {@link watchInstalledVersion}. */\nexport interface InstalledVersionWatcher {\n /** Re-probe now. Returns the fresh state (also delivered to `onChange` when it differs). */\n check: () => InstalledVersionState\n /** Last delivered state. */\n current: () => InstalledVersionState\n /** Stops the watcher. Later checks return the last state and `onChange` no longer fires. */\n close: () => void\n}\n\n/**\n * Compute the state once — the pure heart of the watcher, exported so a host\n * without a lifecycle (a one-shot IPC handler) can answer \"is a restart\n * pending?\" without holding a watcher.\n *\n * @param spec - The running package and its binary name.\n * @param currentVersion - Version the process was built with. An empty string yields no installed version.\n * @param probe - Function that finds the installed package on disk. Defaults to `findInstalledCompanion`.\n * @param now - Clock used for `checkedAt`. Defaults to `Date.now`.\n */\nexport function probeInstalledVersion(\n spec: CompanionSpec,\n currentVersion: string,\n probe: WatchInstalledVersionOptions['probe'] = findInstalledCompanion,\n now: () => number = Date.now,\n): InstalledVersionState {\n const installed = currentVersion ? probe(spec)?.version ?? null : null\n return {\n running: currentVersion,\n installed,\n restartRequired: installed !== null && compareSemver(installed, currentVersion) > 0,\n checkedAt: now(),\n }\n}\n\n/**\n * Starts watching the disk for a newer install of the running package.\n * The watcher probes once on creation, then on every poll and on changes to the applied marker. It calls `onChange` only when the installed version or the restart flag changes. It stays idle when `currentVersion` is empty.\n *\n * @param options - Package spec, running version, callbacks and timing settings.\n */\nexport function watchInstalledVersion(options: WatchInstalledVersionOptions): InstalledVersionWatcher {\n const probe = options.probe ?? findInstalledCompanion\n const now = options.now ?? Date.now\n const enabled = options.currentVersion.length > 0\n let state = probeInstalledVersion(options.spec, options.currentVersion, probe, now)\n let closed = false\n\n const deliver = (next: InstalledVersionState) => {\n const changed = next.installed !== state.installed || next.restartRequired !== state.restartRequired\n state = next\n if (changed && !closed)\n options.onChange(next)\n }\n const check = (): InstalledVersionState => {\n if (!enabled || closed)\n return state\n deliver(probeInstalledVersion(options.spec, options.currentVersion, probe, now))\n return state\n }\n\n let watcher: FSWatcher | null = null\n let debounce: ReturnType<typeof setTimeout> | null = null\n let poll: ReturnType<typeof setInterval> | null = null\n\n if (enabled) {\n if (options.cacheDir) {\n // Watch the DIRECTORY: the marker may not exist yet, and installers\n // replace files (rename) rather than rewrite them in place.\n try {\n mkdirSync(options.cacheDir, { recursive: true })\n watcher = watch(options.cacheDir, (_event, filename) => {\n if (filename && filename.toString() !== UPDATE_APPLIED_MARKER)\n return\n if (debounce)\n clearTimeout(debounce)\n debounce = setTimeout(() => {\n debounce = null\n check()\n }, options.debounceMs ?? 500)\n })\n watcher.on('error', () => {\n // A vanished cache dir or an exhausted inotify budget must not\n // take the host down; the poll still covers us.\n watcher?.close()\n watcher = null\n })\n }\n catch {\n watcher = null\n }\n }\n const pollMs = options.pollMs ?? 5 * 60_000\n if (pollMs > 0) {\n poll = setInterval(check, pollMs)\n // Never keep a process alive for this.\n if (typeof poll === 'object' && 'unref' in poll)\n poll.unref()\n }\n }\n\n return {\n check,\n current: () => state,\n close: () => {\n closed = true\n watcher?.close()\n if (debounce)\n clearTimeout(debounce)\n if (poll)\n clearInterval(poll)\n },\n }\n}\n\n/**\n * Quick existence check so hosts can decide whether to read the marker at all.\n *\n * @param cacheDir - Directory that holds the marker file.\n */\nexport function hasUpdateAppliedMarker(cacheDir: string): boolean {\n return existsSync(updateAppliedMarkerPath(cacheDir))\n}\n\n// ---------------------------------------------------------------------------\n// React hook — the TUI's consumer. The GUI runs the watcher in its main\n// process and forwards the state over IPC instead.\n// ---------------------------------------------------------------------------\n\n/** Options for {@link useRestartRequired}. */\nexport interface UseRestartRequiredOptions {\n /** The running package. `null` disables the hook. */\n spec: CompanionSpec | null\n /** Version the process was built with. */\n currentVersion: string\n /** Directory that holds the applied marker. Omit to rely on polling alone. */\n cacheDir?: string\n /** Master switch — mirrors `Settings.checkForUpdates`. */\n enabled: boolean\n /** Poll interval in milliseconds. Defaults to 5 minutes. `0` disables polling. */\n pollMs?: number\n}\n\n/**\n * `null` until the first probe (or when disabled / no spec), then the live\n * state. Flipping `enabled` off tears the watcher down and clears the state\n * so no stale \"restart\" chip lingers.\n *\n * @param options - Package spec, running version, cache directory, poll interval and the master `enabled` switch.\n */\nexport function useRestartRequired(options: UseRestartRequiredOptions): InstalledVersionState | null {\n const [state, setState] = useState<InstalledVersionState | null>(null)\n const { spec, currentVersion, cacheDir, enabled, pollMs } = options\n const packageName = spec?.packageName\n const binName = spec?.binName\n\n useEffect(() => {\n if (!enabled || !packageName || !currentVersion) {\n setState(null)\n return\n }\n const watcher = watchInstalledVersion({\n spec: binName ? { packageName, binName } : { packageName },\n currentVersion,\n cacheDir,\n pollMs,\n onChange: setState,\n })\n setState(watcher.current())\n return () => watcher.close()\n }, [enabled, packageName, binName, currentVersion, cacheDir, pollMs])\n\n return state\n}\n","// ---------------------------------------------------------------------------\n// `useUpdateCheck` — renderer-agnostic React hook driving the auto-update\n// footer chip + any banner a host wants to wire.\n//\n// Lives in `zidane/chat` (not `zidane/tui`) so a GUI shell can consume the\n// same status. Pure React + the renderer-agnostic core in\n// `./auto-update`; no OpenTUI imports.\n// ---------------------------------------------------------------------------\n\nimport type { UpdateStatus } from './auto-update'\nimport type { Hint } from './hints'\nimport { useEffect, useRef, useState } from 'react'\nimport { checkForUpdate } from './auto-update'\n\n/** Options for {@link useUpdateCheck}. */\nexport interface UseUpdateCheckOptions {\n /** npm package name. e.g. `'zidane-tui'`. */\n packageName: string\n /** Inlined at build time. Skips the check if empty/falsy. */\n currentVersion: string\n /**\n * Master enable flag — usually `Settings.checkForUpdates`. When `false`\n * the hook short-circuits without scheduling any work AND clears the\n * last-known status so a stale chip doesn't linger after the user\n * disabled the feature.\n */\n enabled: boolean\n /** Directory under which the on-disk cache lives. Usually `paths.userDir`. */\n cacheDir?: string\n /** Registry base URL. Defaults to `https://registry.npmjs.org`. */\n registry?: string\n /** dist-tag to read. Defaults to `'latest'`. */\n channel?: string\n /**\n * Cache lifetime for the read-through. **Defaults to `0`** (always\n * fetch fresh) — the TUI chip is the user's source of truth for \"what\n * version of zidane is on npm right now\"; we'd rather pay one HTTPS\n * call per session than tell them yesterday's news for up to 24 hours.\n *\n * The on-disk cache is still WRITTEN with each fresh result (so\n * `zidane upgrade` and other tooling reads benefit) and still READ to\n * feed the registry's ETag (so the cold path can short-circuit on 304\n * — meaningful bandwidth win when nothing changed). Pass a positive\n * value (e.g. `3_600_000` for 1 h) to enable read-through caching for\n * a GUI shell that mounts the hook in many places.\n */\n cacheTtlMs?: number\n /**\n * Wall-clock delay before the first check fires. Defaults to 1 500 ms so\n * we never compete with the boot path's first paint. The check itself\n * is async, so this only affects WHEN the chip first appears — never\n * blocks anything else.\n */\n delayMs?: number\n /** Injected for tests. */\n now?: () => number\n /** Injected for tests. */\n fetcher?: typeof fetch\n}\n\n/**\n * Returns the current update status. The check fires:\n * - Once per mount (every TUI launch).\n * - Every time `enabled` flips from `false` → `true` (user re-enabling\n * the setting expects an immediate refresh).\n *\n * The check is async and deferred by `delayMs` so it never competes with\n * first paint. Result is `null` until the first check resolves; flipping\n * `enabled` to `false` resets to `null` so any chip rendered against\n * `hasUpdate` disappears immediately.\n *\n * The hook owns its own AbortController so an unmount cancels any in-flight\n * registry request cleanly.\n *\n * @param options - Package, current version, enable flag and cache settings for the check.\n */\nexport function useUpdateCheck(options: UseUpdateCheckOptions): UpdateStatus | null {\n const [status, setStatus] = useState<UpdateStatus | null>(null)\n // Ref-mirror so the effect doesn't re-run on every keystroke when the\n // host wraps the result into derived hint state.\n const optsRef = useRef(options)\n optsRef.current = options\n\n useEffect(() => {\n if (!options.enabled || !options.currentVersion) {\n // Disabling the setting must hide a chip that was already painted\n // — otherwise the user sees a stale `↑ vX.Y.Z` linger until the\n // next launch. React bails on identical-state updates so the\n // initial-mount case (status already null) costs nothing.\n setStatus(null)\n return\n }\n const controller = new AbortController()\n const timer = setTimeout(() => {\n void checkForUpdate({\n packageName: optsRef.current.packageName,\n currentVersion: optsRef.current.currentVersion,\n cacheDir: optsRef.current.cacheDir,\n registry: optsRef.current.registry,\n channel: optsRef.current.channel,\n // Default to TTL=0 (always fetch fresh). See `cacheTtlMs` docs.\n cacheTtlMs: optsRef.current.cacheTtlMs ?? 0,\n signal: controller.signal,\n now: optsRef.current.now,\n fetcher: optsRef.current.fetcher,\n }).then((s) => {\n if (!controller.signal.aborted)\n setStatus(s)\n })\n }, options.delayMs ?? 1500)\n return () => {\n clearTimeout(timer)\n controller.abort()\n }\n // Re-run when the master enable / package / version flip. Other\n // option changes stay inside `optsRef.current`; re-running on every\n // option mutation would cause render storms when the host wraps\n // the result into derived hint state.\n }, [options.enabled, options.packageName, options.currentVersion])\n\n return status\n}\n\n// ---------------------------------------------------------------------------\n// Footer chip helper — pure function so any renderer (TUI Footer, future\n// GUI status bar) can compose against the same `Hint`. The chip is\n// suppressed when there's no update; callers spread the result into their\n// hint array unconditionally.\n//\n// Color contract: the chip rides whatever accent the host passes (typically\n// `theme.colors.accent`). Renderers that want a \"fresh check\" badge can\n// inspect `status.source === 'fresh'` themselves — this helper sticks to\n// the one-line discoverability surface.\n// ---------------------------------------------------------------------------\n\n/** Display options for the update and restart hints. */\nexport interface BuildUpdateHintOptions {\n /** Foreground color for the `↑` glyph + version label. */\n color: string\n /**\n * When true (default), the chip renders the literal version (`↑ v5.6.0`).\n * When false, just the glyph + `update` label — useful when the row is\n * tight and the version would push higher-priority chips off the right.\n */\n showVersion?: boolean\n /** Override the leading glyph. Defaults to `'↑'`. */\n glyph?: string\n}\n\n/**\n * Builds the `↑ vX.Y.Z` hint for an available update.\n * Returns `null` when there is no newer version.\n *\n * @param status - Result of an update check, or `null` before the first check.\n * @param options - Color and label settings for the hint.\n */\nexport function buildUpdateHint(status: UpdateStatus | null, options: BuildUpdateHintOptions): Hint | null {\n if (!status?.hasUpdate || !status.latest)\n return null\n const label = options.showVersion === false\n ? 'update'\n : `v${stripVPrefix(status.latest)}`\n return {\n key: options.glyph ?? '↑',\n keyColor: options.color,\n label,\n labelColor: options.color,\n }\n}\n\n/**\n * `↻ v7.0.31` — a newer build is on disk than the one running (this\n * process is stale; see `auto-update-restart.ts`). Takes precedence over\n * the `↑` chip: once the release is installed, \"available\" is old news and\n * \"restart\" is the one action left. Same color contract as `buildUpdateHint`.\n *\n * @param state - Installed-version state, or `null` when unknown.\n * @param options - Color and label settings for the hint.\n */\nexport function buildRestartHint(\n state: { restartRequired: boolean, installed: string | null } | null,\n options: BuildUpdateHintOptions,\n): Hint | null {\n if (!state?.restartRequired || !state.installed)\n return null\n return {\n key: options.glyph ?? '↻',\n keyColor: options.color,\n label: options.showVersion === false ? 'restart' : `v${stripVPrefix(state.installed)}`,\n labelColor: options.color,\n }\n}\n\nfunction stripVPrefix(v: string): string {\n return v.startsWith('v') ? v.slice(1) : v\n}\n","// ---------------------------------------------------------------------------\n// Boot-time wall-clock profiler.\n//\n// Sprinkle `bootTick('<label>')` at strategic points along the startup\n// chain (cli.ts → runTui → registerTreeSitterParsers → resolveConfig →\n// createCliRenderer → React mount → discovery effects → session\n// resume). When `ZIDANE_BOOT_PROFILE=1` is set, each call emits one\n// line on stderr with the delta since the previous tick and the total\n// since module load:\n//\n// $ ZIDANE_BOOT_PROFILE=1 zidane\n// [boot] +0.0ms / total 0.0ms / cli:enter\n// [boot] +12.3ms / total 12.3ms / cli:after-setupBundledTreeSitterAssets\n// [boot] +47.8ms / total 60.1ms / runTui:after-registerTreeSitterParsers\n// ...\n//\n// Renderer-agnostic on purpose — lives in `zidane/chat` so a future GUI\n// shell can wire it into its own boot path with the same env var and\n// the same output format. Writes to stderr, which OpenTUI's renderer\n// leaves alone (stdout is the frame buffer surface).\n// ---------------------------------------------------------------------------\n\nconst enabled = !!process.env.ZIDANE_BOOT_PROFILE\n/**\n * High-resolution origin for the running profile. Captured at module\n * load time so the first {@link bootTick} call inside the same process\n * reflects \"time since the boot profiler was first reached\", which for\n * the standard `cli.ts → runTui` path is effectively \"time since the\n * TUI binary started executing user code\".\n */\nconst start = performance.now()\nlet last = start\n\n/**\n * Record a checkpoint. No-op unless `ZIDANE_BOOT_PROFILE` is truthy in\n * the environment.\n *\n * The leading delta is the time since the PREVIOUS tick (so a long\n * delta highlights the immediately-preceding work); the trailing total\n * is the time since the profiler started. Both round to one decimal of\n * a millisecond.\n *\n * @param label - Name of the boot step that just finished.\n */\nexport function bootTick(label: string): void {\n if (!enabled)\n return\n const now = performance.now()\n const delta = now - last\n const total = now - start\n last = now\n // Delta and total are padded to 7 characters so the columns stay aligned\n // until a value widens past that. The label is not padded.\n process.stderr.write(\n `[boot] +${delta.toFixed(1).padStart(7)}ms / total ${total.toFixed(1).padStart(7)}ms / ${label}\\n`,\n )\n}\n\n/**\n * Returns `true` when `ZIDANE_BOOT_PROFILE` is set. Useful for guarding\n * heavier instrumentation (e.g. wrapping a costly call in a span) that\n * you don't want paying its own cost in the default path.\n */\nexport function bootProfileEnabled(): boolean {\n return enabled\n}\n","/**\n * One-shot IMPORT from an existing Claude Code setup — the materializing\n * counterpart to `foreign-configs.ts`'s read-only discovery.\n *\n * Four pieces, each independently skippable:\n *\n * - Sessions — `~/.claude/projects/<slug>/*.jsonl` transcripts convert\n * to zidane `SessionData` and land in the session store. Idempotent:\n * the zidane session REUSES the Claude session UUID, so a re-import\n * skips everything the store already has.\n * - MCP servers — Claude's `.mcp.json` + `~/.claude.json` entries are\n * COPIED into zidane-owned `mcps.json` files (via `upsertMcpServer`).\n * Names already present in any native zidane/agents config are\n * skipped — imports never shadow what the user configured natively.\n * - Skills — `.claude/skills/<name>` directories are copied into the\n * corresponding `.{prefix}/skills`, skipping names that already\n * exist in any native scan path.\n * - Commands and subagents — `.claude/commands/**.md` and\n * `.claude/agents/**.md` are copied into `.{prefix}/commands` and\n * `.{prefix}/agents`, keeping their folders and skipping names a native\n * root already defines. Copied, they load without the\n * `importForeignConfigs` toggle that otherwise reads them in place.\n *\n * Claude's `settings.json` (permissions / hooks / plugins) has no zidane\n * equivalent and `CLAUDE.md` is already read natively (`agents-md.ts`),\n * so neither is imported — the UI reports that instead of pretending.\n */\n\nimport type { MarkdownDefFile } from '../markdown-defs/types'\nimport type { SessionData, SessionStore } from '../session'\nimport type { McpServerConfig, SessionContentBlock, SessionTurn, ToolResultContent, TurnUsage } from '../types'\nimport type { OnboardingImportCategory, OnboardingImportPreview } from './onboarding/types'\nimport { Buffer } from 'node:buffer'\nimport { closeSync, cpSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join, relative, resolve } from 'node:path'\nimport { markdownDefRoots } from '../markdown-defs/roots'\nimport { listMarkdownDefFiles } from '../markdown-defs/scan'\nimport { parseClaudeState } from './foreign-configs'\nimport { defaultMcpsConfigPaths, parseJsonTolerant, parseMcpsFile } from './mcps-discovery'\nimport { mcpsWritePath, upsertMcpServer } from './mcps-write'\nimport { findGitRoot, projectRootOf } from './project-root'\nimport { projectUserPaths } from './project-user-paths'\n\n/** Options for locating and importing Claude Code data. */\nexport interface ClaudeImportOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** Storage prefix for write targets. Default: `'.zidane'`. */\n prefix?: string\n /** Import sessions from every Claude project dir, not just the current project's. */\n allProjects?: boolean\n}\n\nfunction roots(opts: ClaudeImportOptions): { projectRoot: string, home: string } {\n const cwd = opts.cwd ?? process.cwd()\n return {\n projectRoot: findGitRoot(cwd) ?? cwd,\n home: opts.home ?? homedir(),\n }\n}\n\n/**\n * Whether `~/.claude` exists. The setup wizard uses it to detect Claude Code.\n *\n * @param opts - Discovery options. Only `home` is used.\n */\nexport function hasClaudeCodeSetup(opts: ClaudeImportOptions = {}): boolean {\n return existsSync(resolve(roots(opts).home, '.claude'))\n}\n\n// ---------------------------------------------------------------------------\n// Sessions\n// ---------------------------------------------------------------------------\n\n/**\n * Claude Code names each `~/.claude/projects/` subdirectory after the\n * project path with every non-alphanumeric character flattened to `-`\n * (`/Users/me/.claude/wt` → `-Users-me--claude-wt`). Lossy, so matching\n * goes slug → slug rather than trying to reverse it.\n */\nexport function claudeProjectSlug(path: string): string {\n return path.replace(/[^a-z0-9]/gi, '-')\n}\n\nexport interface ClaudeSessionFile {\n path: string\n /** The `~/.claude/projects/<slug>` directory the file came from. */\n projectDir: string\n}\n\n/**\n * Session transcripts for the current project, or every project when\n * `allProjects` is set.\n *\n * Claude Code slugs by LAUNCH directory, so a session started in a\n * monorepo subdir (`repo/packages/foo`) lands in a different dir than\n * one started at the repo root. Scoped listing therefore accepts both\n * the exact slug of the resolved git root (realpath variants included)\n * AND any dir whose slug extends it — but because slugs are lossy\n * (`repo/foo` and a sibling `repo-foo` collide), every file in those dirs\n * is confirmed against the transcript's recorded `cwd` (resolved to its\n * git root), mirroring how the converted session's `projectRoot` is\n * derived. A transcript with no `cwd` passes only in the exact-slug dir.\n */\nexport function listClaudeSessionFiles(opts: ClaudeImportOptions = {}): ClaudeSessionFile[] {\n const out: ClaudeSessionFile[] = []\n walkClaudeSessionFiles(opts, opts.allProjects\n ? file => out.push(file)\n : (file, scoped) => {\n if (scoped())\n out.push(file)\n })\n return out\n}\n\n/**\n * Detection-preview counts — scoped and all-projects figures from ONE\n * directory walk (the naive two-listing approach reads every project dir\n * twice). The scoped classification (with its per-file cwd peek for\n * candidate dirs) runs at most once per file via the lazy `scoped()`\n * thunk.\n *\n * @param opts - Discovery options. Only `cwd`, `home` and the project scope are used.\n */\nexport function countClaudeSessionFiles(opts: ClaudeImportOptions = {}): { project: number, all: number } {\n let project = 0\n let all = 0\n walkClaudeSessionFiles({ ...opts, allProjects: false }, (_file, scoped) => {\n all++\n if (scoped())\n project++\n }, /* visitAllDirs */ true)\n return { project, all }\n}\n\n/**\n * Shared walk under `~/.claude/projects`. `visit` receives every\n * transcript of every CANDIDATE dir (scoped mode) or every dir\n * (`allProjects` / `visitAllDirs`), plus a lazy `scoped()` thunk that\n * answers \"does this file belong to the current project?\" — lazy because\n * the answer can cost a `transcriptProjectRoot` peek (up to the whole\n * file), which pure all-projects listings never need.\n */\nfunction walkClaudeSessionFiles(\n opts: ClaudeImportOptions,\n visit: (file: ClaudeSessionFile, scoped: () => boolean) => void,\n visitAllDirs = false,\n): void {\n const { projectRoot, home } = roots(opts)\n const projectsDir = resolve(home, '.claude/projects')\n if (!existsSync(projectsDir))\n return\n const slugs = [claudeProjectSlug(projectRoot), claudeProjectSlug(realpathOr(projectRoot))]\n const exact = new Set(slugs)\n const prefixes = [...new Set(slugs.map(s => `${s}-`))]\n const wantedRoots = new Set([projectRoot, realpathOr(projectRoot)])\n for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {\n if (!entry.isDirectory())\n continue\n const exactMatch = exact.has(entry.name)\n const candidate = exactMatch || prefixes.some(p => entry.name.startsWith(p))\n if (!opts.allProjects && !visitAllDirs && !candidate)\n continue\n const projectDir = join(projectsDir, entry.name)\n for (const file of readdirSync(projectDir)) {\n if (!file.endsWith('.jsonl'))\n continue\n const path = join(projectDir, file)\n // Slugs are lossy even on exact match (`/a/b-c` and `/a/b/c` both\n // slug to `-a-b-c`), so EVERY scoped file is confirmed against its\n // recorded cwd. A transcript with no cwd at all (torn/empty file)\n // gets the benefit of the doubt only in the exact-slug dir —\n // prefix matches must prove their subdir claim.\n const scoped = (): boolean => {\n if (!candidate)\n return false\n const root = transcriptProjectRoot(path)\n return root === null ? exactMatch : wantedRoots.has(root) || wantedRoots.has(realpathOr(root))\n }\n visit({ path, projectDir }, scoped)\n }\n }\n}\n\n/**\n * Peek a transcript's project root: scan the first chunk for the first\n * record carrying a `cwd` and resolve it the same way\n * `convertClaudeSession` does. When the chunk was truncated (giant first\n * records — e.g. pasted base64 images) and held no cwd, fall back to the\n * full file: the import reads it in full anyway, so correctness beats the\n * shortcut. Null when no cwd exists at all (empty/torn file) or the file\n * is unreadable.\n */\nfunction transcriptProjectRoot(path: string): string | null {\n let text: string\n let truncated = false\n try {\n const fd = openSync(path, 'r')\n try {\n const buf = Buffer.alloc(256 * 1024)\n const n = readSync(fd, buf, 0, buf.length, 0)\n text = buf.subarray(0, n).toString('utf8')\n truncated = n === buf.length\n }\n finally {\n closeSync(fd)\n }\n }\n catch {\n return null\n }\n const fromChunk = scanForCwd(text)\n if (fromChunk !== null || !truncated)\n return fromChunk\n try {\n return scanForCwd(readFileSync(path, 'utf8'))\n }\n catch {\n return null\n }\n}\n\nfunction scanForCwd(text: string): string | null {\n for (const line of text.split('\\n')) {\n if (!line.trim())\n continue\n try {\n const record = JSON.parse(line) as { cwd?: unknown }\n if (typeof record.cwd === 'string')\n return findGitRoot(record.cwd) ?? record.cwd\n }\n catch {\n continue // torn chunk boundary / artifact line\n }\n }\n return null\n}\n\nfunction realpathOr(path: string): string {\n try {\n return realpathSync(path)\n }\n catch {\n return path\n }\n}\n\nexport interface ConvertedClaudeSession {\n session: SessionData\n claudeSessionId: string\n}\n\ninterface ClaudeRecord {\n type?: string\n uuid?: string\n timestamp?: string\n isSidechain?: boolean\n isMeta?: boolean\n cwd?: string\n sessionId?: string\n customTitle?: string\n summary?: string\n message?: {\n id?: string\n role?: string\n content?: unknown\n usage?: {\n input_tokens?: number\n output_tokens?: number\n cache_creation_input_tokens?: number\n cache_read_input_tokens?: number\n }\n model?: string\n }\n}\n\n/**\n * Convert one Claude Code session transcript (JSONL text) to a zidane\n * `SessionData`. Returns null when no conversational turns survive the\n * filter (hook noise, sidechains, queue bookkeeping).\n *\n * Kept-vs-dropped:\n * - `user` / `assistant` records with a `message` → turns. Consecutive\n * assistant records sharing a `message.id` merge into ONE turn (Claude\n * writes text and tool_use as separate records of the same API message).\n * - Consecutive tool_result-only user records merge into ONE user turn:\n * Claude writes one record per result of a parallel tool batch, but\n * zidane persists a batch as a single user turn and\n * `ensureToolResultPairing` enforces strict adjacency — split turns\n * would be \"repaired\" into orphan markers + error placeholders on resume.\n * - `custom-title` / `summary` records → `metadata.title`.\n * - Sidechains (subagent transcripts), `isMeta` records, `attachment`,\n * `system`, `queue-operation`, `last-prompt` → dropped.\n * - Thinking blocks → dropped (provider-bound signatures, not replayable).\n */\nexport function convertClaudeSession(jsonl: string, opts: { fallbackId?: string } = {}): ConvertedClaudeSession | null {\n const turns: SessionTurn[] = []\n let title: string | undefined\n let claudeSessionId: string | undefined\n let cwd: string | undefined\n let lastAssistantMessageId: string | undefined\n\n for (const line of jsonl.split('\\n')) {\n if (!line.trim())\n continue\n let record: ClaudeRecord\n try {\n record = JSON.parse(line) as ClaudeRecord\n }\n catch {\n continue // torn tail line, editor artifact — skip, don't fail the file\n }\n if (record.type === 'custom-title' && typeof record.customTitle === 'string') {\n title = record.customTitle\n continue\n }\n if (record.type === 'summary' && typeof record.summary === 'string') {\n title ??= record.summary\n continue\n }\n if (record.type !== 'user' && record.type !== 'assistant')\n continue\n if (record.isSidechain || record.isMeta || !record.message)\n continue\n claudeSessionId ??= record.sessionId\n cwd ??= record.cwd\n // Malformed timestamps parse to NaN — don't let that poison\n // createdAt/updatedAt (sort keys in the sessions list).\n const parsedTs = record.timestamp ? Date.parse(record.timestamp) : Number.NaN\n const createdAt = Number.isNaN(parsedTs) ? Date.now() : parsedTs\n\n if (record.type === 'assistant') {\n const content = claudeBlocksToContent(record.message.content)\n if (content.length === 0)\n continue\n const messageId = record.message.id\n const previous = turns[turns.length - 1]\n // Merge continuation records of the same API message into the open\n // turn. Claude repeats the SAME usage object on every record of a\n // message, so the latest snapshot replaces — summing would N-count\n // the message's tokens.\n if (messageId && messageId === lastAssistantMessageId && previous?.role === 'assistant') {\n previous.content.push(...content)\n const usage = claudeUsage(record.message.usage, record.message.model)\n if (usage)\n previous.usage = usage\n continue\n }\n lastAssistantMessageId = messageId\n const usage = claudeUsage(record.message.usage, record.message.model)\n turns.push({\n id: record.uuid ?? crypto.randomUUID(),\n role: 'assistant',\n content,\n createdAt,\n ...(usage ? { usage } : {}),\n })\n continue\n }\n\n lastAssistantMessageId = undefined\n const content = claudeBlocksToContent(record.message.content)\n if (content.length === 0)\n continue\n // One record per tool_result of a parallel batch → one zidane user\n // turn — see the doc comment (ensureToolResultPairing adjacency).\n const previous = turns[turns.length - 1]\n if (previous?.role === 'user' && isToolResultOnly(content) && isToolResultOnly(previous.content)) {\n previous.content.push(...content)\n continue\n }\n turns.push({ id: record.uuid ?? crypto.randomUUID(), role: 'user', content, createdAt })\n }\n\n if (turns.length === 0)\n return null\n // Identity PREFERS the caller's `fallbackId` (the file basename —\n // Claude's own per-file uniqueness key) over the records' `sessionId`:\n // resume/fork lineages copy prior records verbatim, so a forked\n // transcript's early records still carry the ORIGINAL session's id —\n // keying on that would dedupe the fork against its ancestor and\n // silently never import the continuation.\n const id = opts.fallbackId ?? claudeSessionId ?? crypto.randomUUID()\n // Identity, like every session: a Claude transcript recorded in a linked\n // worktree belongs to the main checkout's project.\n const projectRoot = cwd ? projectRootOf(cwd) : undefined\n const session: SessionData = {\n id,\n ...(projectRoot ? { projectRoot } : {}),\n turns,\n runs: [],\n status: 'idle',\n metadata: {\n ...(title ? { title } : {}),\n importedFrom: 'claude-code',\n claudeSessionId: id,\n // Lineage breadcrumb: differs from `claudeSessionId` on forked /\n // resumed transcripts whose records carry the ancestor's id.\n ...(claudeSessionId && claudeSessionId !== id ? { claudeLineageId: claudeSessionId } : {}),\n },\n createdAt: turns[0]!.createdAt,\n updatedAt: turns[turns.length - 1]!.createdAt,\n }\n return { session, claudeSessionId: id }\n}\n\n/** Map Claude/Anthropic content blocks to zidane `SessionContentBlock`s. */\nfunction claudeBlocksToContent(content: unknown): SessionContentBlock[] {\n if (typeof content === 'string')\n return content.trim() ? [{ type: 'text', text: content }] : []\n if (!Array.isArray(content))\n return []\n const out: SessionContentBlock[] = []\n for (const raw of content) {\n if (!raw || typeof raw !== 'object')\n continue\n const block = raw as Record<string, unknown>\n switch (block.type) {\n case 'text':\n if (typeof block.text === 'string' && block.text.trim())\n out.push({ type: 'text', text: block.text })\n break\n case 'tool_use':\n if (typeof block.id === 'string' && typeof block.name === 'string') {\n const input = block.input && typeof block.input === 'object' && !Array.isArray(block.input)\n ? block.input as Record<string, unknown>\n : {}\n out.push({ type: 'tool_call', id: block.id, name: block.name, input })\n }\n break\n case 'tool_result':\n if (typeof block.tool_use_id === 'string') {\n out.push({\n type: 'tool_result',\n callId: block.tool_use_id,\n output: claudeToolResultOutput(block.content),\n ...(block.is_error === true ? { isError: true } : {}),\n })\n }\n break\n case 'image': {\n const image = claudeImage(block)\n if (image)\n out.push(image)\n break\n }\n // thinking / redacted_thinking: provider-bound signatures — dropped.\n }\n }\n return out\n}\n\nfunction claudeImage(block: Record<string, unknown>): SessionContentBlock | null {\n const source = block.source as Record<string, unknown> | undefined\n if (!source || source.type !== 'base64' || typeof source.media_type !== 'string' || typeof source.data !== 'string')\n return null\n return { type: 'image', mediaType: source.media_type, data: source.data }\n}\n\nfunction claudeToolResultOutput(content: unknown): string | ToolResultContent[] {\n if (typeof content === 'string')\n return content\n if (!Array.isArray(content))\n return ''\n const parts: ToolResultContent[] = []\n for (const raw of content) {\n if (!raw || typeof raw !== 'object')\n continue\n const block = raw as Record<string, unknown>\n if (block.type === 'text' && typeof block.text === 'string') {\n parts.push({ type: 'text', text: block.text })\n }\n else if (block.type === 'image') {\n const image = claudeImage(block)\n if (image?.type === 'image' && image.data)\n parts.push({ type: 'image', mediaType: image.mediaType, data: image.data })\n }\n }\n // Collapse the common all-text case to a plain string.\n if (parts.every(p => p.type === 'text'))\n return parts.map(p => (p as { text: string }).text).join('\\n')\n return parts\n}\n\ntype ClaudeUsage = NonNullable<NonNullable<ClaudeRecord['message']>['usage']>\n\nfunction claudeUsage(usage: ClaudeUsage | undefined, model?: string): TurnUsage | undefined {\n if (!usage || typeof usage !== 'object')\n return undefined\n return {\n input: usage.input_tokens ?? 0,\n output: usage.output_tokens ?? 0,\n ...(usage.cache_creation_input_tokens ? { cacheCreation: usage.cache_creation_input_tokens } : {}),\n ...(usage.cache_read_input_tokens ? { cacheRead: usage.cache_read_input_tokens } : {}),\n ...(model ? { modelId: model } : {}),\n }\n}\n\nfunction isToolResultOnly(blocks: readonly SessionContentBlock[]): boolean {\n return blocks.length > 0 && blocks.every(b => b.type === 'tool_result')\n}\n\n/** Result of importing Claude Code session transcripts. */\nexport interface ClaudeSessionImportResult {\n /** Sessions written to the store. */\n imported: number\n /** Already in the store (same Claude session UUID) — idempotent re-run. */\n skipped: number\n /** Nothing conversational in the file (hook noise, queue bookkeeping only). */\n empty: number\n /** Files that failed to parse/convert, with the reason. */\n failed: { path: string, error: string }[]\n}\n\n/** Import Claude Code session transcripts into `store`. */\nexport async function importClaudeSessions(store: SessionStore, opts: ClaudeImportOptions = {}): Promise<ClaudeSessionImportResult> {\n const result: ClaudeSessionImportResult = { imported: 0, skipped: 0, empty: 0, failed: [] }\n for (const file of listClaudeSessionFiles(opts)) {\n try {\n // Session id = the file's basename (Claude names each transcript\n // after ITS session UUID). `convertClaudeSession` prefers this over\n // the records' `sessionId`, which on forked/resumed lineages still\n // carries the ancestor's id — see the identity comment there.\n const fallbackId = basename(file.path, '.jsonl')\n // Idempotence: a store hit on the basename means this transcript\n // already imported — checked BEFORE reading/converting, so a\n // re-import doesn't re-parse the whole Claude history just to\n // count skips.\n if (await store.load(fallbackId)) {\n result.skipped++\n continue\n }\n const converted = convertClaudeSession(readFileSync(file.path, 'utf8'), { fallbackId })\n if (!converted) {\n result.empty++\n continue\n }\n await store.save(converted.session)\n result.imported++\n }\n catch (err) {\n result.failed.push({ path: file.path, error: err instanceof Error ? err.message : String(err) })\n }\n }\n return result\n}\n\n// ---------------------------------------------------------------------------\n// MCP servers\n// ---------------------------------------------------------------------------\n\n/** Result of copying one category of Claude Code definitions into zidane. */\nexport interface ClaudeCopyResult {\n /** Names that were written. */\n copied: string[]\n /** Names already present natively — never overwritten. */\n skipped: string[]\n /** Names that could not be written, with the reason. */\n failed: { name: string, error: string }[]\n /**\n * Names that appeared in MORE THAN ONE Claude source (e.g. `.mcp.json`\n * AND `~/.claude.json`) — the first occurrence (highest-precedence\n * source, matching Claude's own project → local → user order) was\n * processed, later ones dropped. Surfaced so a differently-configured\n * duplicate doesn't vanish from the accounting. MCP import only.\n */\n duplicates?: string[]\n}\n\n/**\n * Copy Claude Code's MCP servers into zidane-owned `mcps.json` files.\n * The repo-shared `.mcp.json` writes to `<root>/.{prefix}/mcps.json`;\n * BOTH `~/.claude.json` scopes write to `~/.{prefix}/mcps.json` —\n * Claude's \"local\" scope (the per-project map inside the user state\n * file) is private-by-design and routinely carries tokens in\n * `env`/`headers`, so materializing it into a committable project file\n * would leak credentials. User scope loses the per-project activation\n * (zidane has no user-private per-project config), which is the safer\n * direction to err. Collisions with names in ANY native config path are\n * skipped.\n */\nexport function importClaudeMcps(opts: ClaudeImportOptions = {}): ClaudeCopyResult {\n const { home } = roots(opts)\n const { native: nativeNames, sources, failed } = claudeMcpCandidates(opts)\n const result: ClaudeCopyResult = { copied: [], skipped: [], failed: [...failed], duplicates: [] }\n\n const seen = new Set<string>()\n for (const { servers, scope } of sources) {\n for (const server of servers) {\n if (seen.has(server.name)) {\n // Same name in a lower-precedence source — report, don't vanish:\n // its config may differ from the occurrence that won.\n result.duplicates!.push(server.name)\n continue\n }\n seen.add(server.name)\n if (nativeNames.has(server.name)) {\n result.skipped.push(server.name)\n continue\n }\n try {\n const target = mcpsWritePath(scope, { ...(opts.cwd ? { cwd: opts.cwd } : {}), home, ...(opts.prefix ? { prefix: opts.prefix } : {}) })\n const { action } = upsertMcpServer(target, server)\n if (action === 'updated') {\n // Unreachable while `nativeNames` raw-walks every candidate file\n // (the write targets included) — but if a concurrent writer or a\n // future shape drift ever slips past the guard, say so honestly\n // instead of counting an overwrite as a copy.\n result.failed.push({ name: server.name, error: `overwrote an existing entry in ${target}. Restore it from version control if unintended` })\n continue\n }\n result.copied.push(server.name)\n }\n catch (err) {\n result.failed.push({ name: server.name, error: err instanceof Error ? err.message : String(err) })\n }\n }\n }\n return result\n}\n\n/** Claude Code's MCP servers by source, and the names zidane already has. */\ninterface ClaudeMcpCandidates {\n /** Raw names in every native config file: the import never overwrites them. */\n native: Set<string>\n /** Claude's servers, highest-precedence source first, with the scope each one writes to. */\n sources: { servers: McpServerConfig[], scope: 'project' | 'user' }[]\n /** Claude files that could not be read. */\n failed: { name: string, error: string }[]\n}\n\n/** What {@link importClaudeMcps} and {@link previewClaudeImport} work from: nothing is written. */\nfunction claudeMcpCandidates(opts: ClaudeImportOptions): ClaudeMcpCandidates {\n const { projectRoot, home } = roots(opts)\n const failed: { name: string, error: string }[] = []\n\n // Collision guard — collected from the RAW file shape, NOT from\n // `parseMcpsFile`: the normalized view drops `enabled: false` entries\n // and can reject a file over one invalid entry, while `upsertMcpServer`\n // walks the raw JSON. Guarding on the normalized view would let the\n // import overwrite a native entry the user disabled (or one sitting\n // next to a malformed sibling). Raw names = exactly what upsert sees,\n // so an import can only ever ADD.\n const native = new Set<string>()\n for (const candidate of defaultMcpsConfigPaths({ ...(opts.cwd ? { cwd: opts.cwd } : {}), home, ...(opts.prefix ? { prefix: opts.prefix } : {}) })) {\n if (!existsSync(candidate.path))\n continue\n for (const name of rawMcpNames(readFileSync(candidate.path, 'utf8')))\n native.add(name)\n }\n\n const sources: { servers: McpServerConfig[], scope: 'project' | 'user' }[] = []\n const mcpJson = resolve(projectRoot, '.mcp.json')\n if (existsSync(mcpJson)) {\n try {\n sources.push({ servers: parseMcpsFile(readFileSync(mcpJson, 'utf8')), scope: 'project' })\n }\n catch (err) {\n failed.push({ name: mcpJson, error: err instanceof Error ? err.message : String(err) })\n }\n }\n const claudeState = resolve(home, '.claude.json')\n if (existsSync(claudeState)) {\n try {\n const state: unknown = JSON.parse(readFileSync(claudeState, 'utf8'))\n // User scope on purpose — see the doc comment (credential leak).\n sources.push({ servers: parseClaudeState(state, 'claude-project-state', projectRoot), scope: 'user' })\n sources.push({ servers: parseClaudeState(state, 'claude-user-state'), scope: 'user' })\n }\n catch (err) {\n failed.push({ name: claudeState, error: err instanceof Error ? err.message : String(err) })\n }\n }\n return { native, sources, failed }\n}\n\n/**\n * Names physically present in an mcps config file, from the raw parsed\n * shape — mirrors `upsertMcpServer`'s walk (flat array `name`s, wrapper\n * `mcpServers` keys, bare-map keys) with none of discovery's filtering.\n * Unreadable/malformed files yield no names; `upsertMcpServer` throws on\n * those before writing, so they still can't be overwritten.\n */\nfunction rawMcpNames(text: string): string[] {\n let raw: unknown\n try {\n raw = parseJsonTolerant(text)\n }\n catch {\n return []\n }\n if (Array.isArray(raw)) {\n return raw.flatMap(entry =>\n entry && typeof entry === 'object' && typeof (entry as { name?: unknown }).name === 'string'\n ? [(entry as { name: string }).name]\n : [])\n }\n if (raw && typeof raw === 'object') {\n const record = raw as Record<string, unknown>\n const map = record.mcpServers && typeof record.mcpServers === 'object' && !Array.isArray(record.mcpServers)\n ? record.mcpServers as Record<string, unknown>\n : record\n return Object.keys(map)\n }\n return []\n}\n\n// ---------------------------------------------------------------------------\n// Skills\n// ---------------------------------------------------------------------------\n\n/**\n * Copy `.claude/skills/<name>` directories (project + user) into the\n * corresponding `.{prefix}/skills`. A skill \"exists natively\" when a\n * directory of the same name sits in any native scan path — those are\n * skipped, not overwritten.\n */\nexport function importClaudeSkills(opts: ClaudeImportOptions = {}): ClaudeCopyResult {\n const result: ClaudeCopyResult = { copied: [], skipped: [], failed: [] }\n const { native: nativeNames, pairs } = claudeSkillCandidates(opts)\n for (const { from, to, names } of pairs) {\n for (const name of names) {\n if (nativeNames.has(name)) {\n result.skipped.push(name)\n continue\n }\n try {\n cpSync(join(from, name), join(to, name), { recursive: true, errorOnExist: true, force: false })\n nativeNames.add(name)\n result.copied.push(name)\n }\n catch (err) {\n result.failed.push({ name, error: err instanceof Error ? err.message : String(err) })\n }\n }\n }\n return result\n}\n\n/** Claude Code's skill folders, per source and target, and the skill names zidane already has. */\nfunction claudeSkillCandidates(opts: ClaudeImportOptions): { native: Set<string>, pairs: { from: string, to: string, names: string[] }[] } {\n const { projectRoot, home } = roots(opts)\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n const native = new Set<string>()\n for (const scan of projectUserPaths({ subPath: 'skills', ...(opts.cwd ? { cwd: opts.cwd } : {}), home, ...(opts.prefix ? { prefix: opts.prefix } : {}) })) {\n if (!existsSync(scan.path))\n continue\n for (const entry of readdirSync(scan.path, { withFileTypes: true })) {\n if (entry.isDirectory())\n native.add(entry.name)\n }\n }\n const pairs = [\n { from: resolve(projectRoot, '.claude/skills'), to: resolve(projectRoot, `.${prefix}/skills`) },\n { from: resolve(home, '.claude/skills'), to: resolve(home, `.${prefix}/skills`) },\n ].map(({ from, to }) => ({\n from,\n to,\n // A folder without a SKILL.md is not a skill.\n names: existsSync(from)\n ? readdirSync(from, { withFileTypes: true }).filter(entry => entry.isDirectory() && existsSync(join(from, entry.name, 'SKILL.md'))).map(entry => entry.name)\n : [],\n }))\n return { native, pairs }\n}\n\n// ---------------------------------------------------------------------------\n// Commands and subagents\n// ---------------------------------------------------------------------------\n\n/**\n * Copy `.claude/<kind>` markdown files (project + user) into the matching\n * `.{prefix}/<kind>`, keeping nested folders (`git/commit.md` stays\n * `/git:commit`). A name any native root already defines is skipped, never\n * overwritten. Names come from the file path, as the loader derives them; an\n * agent renamed by its `name:` frontmatter is compared by file name.\n */\nexport function importClaudeMarkdownDefs(kind: 'commands' | 'agents', opts: ClaudeImportOptions = {}): ClaudeCopyResult {\n const result: ClaudeCopyResult = { copied: [], skipped: [], failed: [] }\n const { native: nativeNames, pairs } = claudeMarkdownDefCandidates(kind, opts)\n for (const { from, to, files } of pairs) {\n for (const file of files) {\n if (nativeNames.has(file.name)) {\n result.skipped.push(file.name)\n continue\n }\n try {\n const target = join(to, relative(from, file.path))\n mkdirSync(dirname(target), { recursive: true })\n cpSync(file.path, target, { errorOnExist: true, force: false })\n nativeNames.add(file.name)\n result.copied.push(file.name)\n }\n catch (err) {\n result.failed.push({ name: file.name, error: err instanceof Error ? err.message : String(err) })\n }\n }\n }\n return result\n}\n\n/** Claude Code's command or subagent files, per source and target, and the names zidane already defines. */\nfunction claudeMarkdownDefCandidates(kind: 'commands' | 'agents', opts: ClaudeImportOptions): { native: Set<string>, pairs: { from: string, to: string, files: MarkdownDefFile[] }[] } {\n const { projectRoot, home } = roots(opts)\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n const nativeRoots = markdownDefRoots({ kind, ...(opts.cwd ? { cwd: opts.cwd } : {}), home, prefix: `.${prefix}`, includeForeign: false })\n const native = new Set(listMarkdownDefFiles(nativeRoots).map(file => file.name))\n const pairs = ([\n { from: resolve(projectRoot, `.claude/${kind}`), to: resolve(projectRoot, `.${prefix}/${kind}`), source: 'project' },\n { from: resolve(home, `.claude/${kind}`), to: resolve(home, `.${prefix}/${kind}`), source: 'user' },\n ] as const).map(({ from, to, source }) => ({ from, to, files: listMarkdownDefFiles([{ path: from, source, foreign: true }]) }))\n return { native, pairs }\n}\n\n// ---------------------------------------------------------------------------\n// Combined runner\n// ---------------------------------------------------------------------------\n\n/** What an import from Claude Code deliberately leaves alone, spelled out for the UI. */\nexport const CLAUDE_IMPORT_NOTES: readonly string[] = [\n 'zidane already reads CLAUDE.md files, so there is nothing to copy.',\n 'zidane does not copy Claude Code permissions, hooks or plugins, because it has nothing that matches them.',\n]\n\n/** What {@link importFromClaudeCode} imported. A category is absent when it was switched off. */\nexport interface ClaudeImportSummary {\n /**\n * Where the import landed: always this computer (its session store and\n * its `.zidane` files). Claude Code's data is read from this machine, so\n * the import never targets a remote host.\n */\n target: 'this-computer'\n /** Session import result. */\n sessions?: ClaudeSessionImportResult\n /** MCP server import result. */\n mcps?: ClaudeCopyResult\n /** Skill import result. */\n skills?: ClaudeCopyResult\n /** Command import result. */\n commands?: ClaudeCopyResult\n /** Subagent import result. */\n agents?: ClaudeCopyResult\n /** What is deliberately NOT imported, spelled out for the UI. */\n notes: string[]\n}\n\n/** The host a window is using, as far as the import note cares. */\nexport interface ClaudeImportActiveHost {\n /** Where the host runs: in this process, a local daemon or a remote server. */\n kind: 'in-process' | 'daemon' | 'server'\n /** Display name of the host. */\n name: string\n}\n\n/**\n * What to tell the user when the window's sessions live on a remote host:\n * the import went to this computer, so the host's list won't show it.\n * `null` for a host on this machine (it shares the store).\n *\n * @param activeHost - The host the window uses, or `null` or `undefined` when unknown.\n */\nexport function claudeImportHostNote(activeHost: ClaudeImportActiveHost | null | undefined): string | null {\n if (activeHost?.kind !== 'server')\n return null\n return `Imports go to this computer, not to ${activeHost.name}. Find them in this computer's session list.`\n}\n\n/**\n * Import Claude Code data from THIS machine into THIS machine: `store` must\n * be the local session store (never a remote host's), MCP servers, skills,\n * commands and subagents go to this machine's `.zidane` files. Pass `activeHost` (the\n * window's host) to get the remote-host note in `notes`.\n *\n * @param store - The local session store that receives imported sessions.\n * @param opts - Discovery options plus per-category switches (`sessions`, `mcps`, `skills`, `commands`, `agents`), each on unless set to `false`, and the optional `activeHost`.\n * @example\n * ```ts\n * import { importFromClaudeCode } from 'zidane/chat'\n * import type { SessionStore } from 'zidane'\n *\n * declare const store: SessionStore\n *\n * const summary = await importFromClaudeCode(store, { mcps: false })\n * console.log(summary.sessions?.imported, summary.notes)\n * ```\n */\nexport async function importFromClaudeCode(\n store: SessionStore,\n opts: ClaudeImportOptions & { sessions?: boolean, mcps?: boolean, skills?: boolean, commands?: boolean, agents?: boolean, activeHost?: ClaudeImportActiveHost | null } = {},\n): Promise<ClaudeImportSummary> {\n const hostNote = claudeImportHostNote(opts.activeHost)\n const summary: ClaudeImportSummary = {\n target: 'this-computer',\n notes: [...(hostNote ? [hostNote] : []), ...CLAUDE_IMPORT_NOTES],\n }\n if (opts.sessions !== false)\n summary.sessions = await importClaudeSessions(store, opts)\n if (opts.mcps !== false)\n summary.mcps = importClaudeMcps(opts)\n if (opts.skills !== false)\n summary.skills = importClaudeSkills(opts)\n if (opts.commands !== false)\n summary.commands = importClaudeMarkdownDefs('commands', opts)\n if (opts.agents !== false)\n summary.agents = importClaudeMarkdownDefs('agents', opts)\n return summary\n}\n\n/** The names a copy would bring over and skip, assuming each copy succeeds, as the importers walk them. */\nfunction previewNames(native: ReadonlySet<string>, names: Iterable<string>): OnboardingImportCategory {\n const known = new Set(native)\n const copy: string[] = []\n const skip: string[] = []\n for (const name of names) {\n if (known.has(name)) {\n skip.push(name)\n continue\n }\n known.add(name)\n copy.push(name)\n }\n return { copy, skip }\n}\n\n/**\n * What {@link importFromClaudeCode} would bring over and skip, per piece,\n * without writing anything: the setup wizard's Bring your setup step shows\n * it before the user picks. Names already in zidane are skipped, as the\n * import skips them; a server listed in several Claude files counts once.\n *\n * @param opts - Discovery options: `cwd`, `home` and `prefix`.\n * @example\n * ```ts\n * import { previewClaudeImport } from 'zidane/chat'\n *\n * const preview = previewClaudeImport()\n * console.log(preview.sessions.all, preview.mcps.copy, preview.skills.skip)\n * ```\n */\nexport function previewClaudeImport(opts: ClaudeImportOptions = {}): OnboardingImportPreview {\n const mcps = claudeMcpCandidates(opts)\n const mcpNames = [...new Set(mcps.sources.flatMap(source => source.servers.map(server => server.name)))]\n const skills = claudeSkillCandidates(opts)\n const commands = claudeMarkdownDefCandidates('commands', opts)\n const agents = claudeMarkdownDefCandidates('agents', opts)\n return {\n sessions: countClaudeSessionFiles(opts),\n mcps: previewNames(mcps.native, mcpNames),\n skills: previewNames(skills.native, skills.pairs.flatMap(pair => pair.names)),\n commands: previewNames(commands.native, commands.pairs.flatMap(pair => pair.files.map(file => file.name))),\n agents: previewNames(agents.native, agents.pairs.flatMap(pair => pair.files.map(file => file.name))),\n notes: CLAUDE_IMPORT_NOTES,\n }\n}\n","/**\n * Prompt autocompletion framework — React hook.\n *\n * The provider contract types + pure span/reference helpers live in\n * `./completion-core` (node-free + react-free, re-exported here). This\n * module adds the `useCompletion()` React hook on top.\n *\n * The TUI consumes `useCompletion()` to drive a popover above the textarea.\n * A GUI consumes the same hook to drive a dropdown. The popup component\n * itself only reads `label` + `description` on each item, so provider-\n * specific typing (`TItem`) stays at the registration boundary and never\n * leaks into the renderer.\n */\n\nimport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion-core'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { applyInsert, collectReferences, findActiveTriggers, mergeSuggestions } from './completion-core'\n\nexport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n CompletionSubmitContext,\n} from './completion-core'\nexport { applyInsert, collectReferences, findActiveTrigger, findActiveTriggers, mergeReferences, mergeSuggestions, resolveCompletionSubmit } from './completion-core'\n\n/**\n * Shared sentinel returned by `useCompletion` when no references are\n * present in the buffer. Stable identity lets consumers' `useEffect`\n * dep arrays bail out on chip-less keystrokes — see `useChipHighlights`.\n */\nconst EMPTY_REFS: readonly CompletionReference<unknown>[] = Object.freeze([])\n\n// ---------------------------------------------------------------------------\n// React hook\n// ---------------------------------------------------------------------------\n\n/** State surface returned by `useCompletion()`. */\nexport interface CompletionState<TItem = unknown> {\n /** Active trigger, or null when the cursor doesn't sit in a completable span. */\n active: ActiveTrigger<TItem> | null\n /** Items for the active query. Empty when no trigger is active. */\n items: readonly CompletionItem<TItem>[]\n /** True while the provider's `suggest` promise is pending. */\n loading: boolean\n /** Index of the highlighted item inside `items`. */\n selectedIndex: number\n /** Move the highlight. Wraps at the ends. No-op when `items` is empty. */\n selectNext: () => void\n /** Moves the highlight to the previous item. Wraps at the ends. No-op when `items` is empty. */\n selectPrev: () => void\n /**\n * Commit the highlighted item. Returns the new text + cursor pair, or\n * `null` when there's nothing to commit (no active trigger, empty list).\n */\n commit: () => { text: string, cursor: number } | null\n /** Force-close the popover until the user types again. */\n dismiss: () => void\n /** All references in the current buffer, regardless of provider. */\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Drive a prompt-completion popover from React state.\n *\n * Inputs are pull-style — the caller owns `text` + `cursor` (typically\n * mirroring an `OpenTUI TextareaRenderable` or a `<textarea>` element) and\n * passes them in each render. Providers are matched by their `trigger`\n * character; every provider that shares the trigger at the cursor is bound\n * and their suggestions merge into one list.\n *\n * Asynchronous suggest calls are aborted when the query changes or the\n * popover closes, so providers that hit a backend don't leak.\n *\n * @param input - Current buffer text and cursor offset.\n * @param providers - Providers that offer suggestions.\n * @param options - Options. `maxQueryLength` bounds the trigger query scan.\n */\nexport function useCompletion<TItem = unknown>(\n input: { text: string, cursor: number },\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): CompletionState<TItem> {\n const { text, cursor } = input\n // Destructure primitives up front so the `useMemo` dep array reads them\n // by value (not by `options` reference, which is a fresh object literal\n // on every call from most hosts).\n const { maxQueryLength } = options\n // `dismissed` is keyed by the trigger's `span.start` — the buffer\n // position of the `@` / `/` glyph that opened the popover. Staying\n // dismissed *while the user keeps typing into the same trigger span*\n // is the whole point of Esc; a stricter `${text.length}:${cursor}`\n // key would re-open on the very next keystroke, which would make the\n // \"esc dismiss\" hint a lie. The effect below resets this when the trigger\n // goes away entirely (user backspaced past `@`) so retyping `@` at\n // the same position works.\n const [dismissedTriggerStart, setDismissedTriggerStart] = useState<number | null>(null)\n\n const rawTrigger = useMemo(\n () => findActiveTriggers(text, cursor, providers, { maxQueryLength }),\n [text, cursor, providers, maxQueryLength],\n )\n\n const activeMulti = useMemo(() => {\n if (!rawTrigger)\n return null\n if (rawTrigger.span.start === dismissedTriggerStart)\n return null\n return rawTrigger\n }, [rawTrigger, dismissedTriggerStart])\n\n // `active` stays a single-provider ActiveTrigger (first bound provider) so\n // the popup's title/commit contract is unchanged; multi-provider merging\n // happens in the suggest effect below and surfaces as `item.section`.\n const active = useMemo<ActiveTrigger<TItem> | null>(\n () => activeMulti\n ? { provider: activeMulti.providers[0], query: activeMulti.query, span: activeMulti.span }\n : null,\n [activeMulti],\n )\n\n // Forget the dismissal once the trigger disappears — otherwise a\n // fresh `@` typed at the same byte position would inherit the prior\n // dismiss and the popover would never come back without scrolling\n // the trigger elsewhere first.\n useEffect(() => {\n if (!rawTrigger)\n setDismissedTriggerStart(null)\n }, [rawTrigger])\n\n const [items, setItems] = useState<readonly CompletionItem<TItem>[]>([])\n const [loading, setLoading] = useState(false)\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n // Cancel any in-flight suggest when the query changes / popover closes.\n const abortRef = useRef<AbortController | null>(null)\n\n useEffect(() => {\n abortRef.current?.abort()\n if (!activeMulti) {\n setItems([])\n setLoading(false)\n setSelectedIndex(0)\n return\n }\n const controller = new AbortController()\n abortRef.current = controller\n const ctx: CompletionContext = { text, cursor }\n let cancelled = false\n const outs = activeMulti.providers.map(provider => ({\n provider,\n out: provider.suggest(activeMulti.query, ctx, controller.signal),\n }))\n // Sync fast path — every provider returned synchronously (the common\n // single-provider case, e.g. `@` files). Merge without a loading flash.\n if (outs.every(o => Array.isArray(o.out))) {\n setItems(mergeSuggestions(outs.map(o => ({ provider: o.provider, items: o.out as CompletionItem<TItem>[] })), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n return\n }\n setLoading(true)\n // allSettled, not all: with several providers on one trigger, a single\n // provider whose async suggest rejects (e.g. a failed catalog scan) must\n // not blank its siblings' results. Fulfilled groups merge; rejected ones\n // are dropped. (All rejecting → empty items.)\n void Promise.allSettled(outs.map(async o => ({ provider: o.provider, items: await o.out }))).then(\n (results) => {\n if (cancelled)\n return\n setItems(mergeSuggestions(results.flatMap(r => r.status === 'fulfilled' ? [r.value] : []), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n },\n )\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [activeMulti, text, cursor])\n\n // References don't depend on `cursor` — both built-in providers ignore\n // `ctx.cursor` in `parseReferences`, and the consumers (chip highlighting,\n // submit-time activation) only care about positions of references, not\n // about where the caret happens to sit. Dropping `cursor` from the dep\n // array avoids re-walking every provider regex on each arrow-key press.\n // `cursor` is still forwarded so a future provider that wants the cursor\n // can opt in — at the cost of choosing its own memoization strategy.\n //\n // Empty results collapse to a shared `EMPTY_REFS` constant so the prompt\n // textarea's `useChipHighlights` effect doesn't fire on every keystroke\n // of a chip-less buffer (each keystroke would otherwise allocate a fresh\n // `[]`, change identity, and trigger a `clearAllHighlights` repaint).\n const references = useMemo(\n () => {\n const refs = collectReferences(text, providers, cursor)\n return refs.length === 0 ? (EMPTY_REFS as readonly CompletionReference<TItem>[]) : refs\n },\n [text, providers],\n )\n\n const selectNext = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i + 1) % items.length))\n }, [items.length])\n const selectPrev = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i - 1 + items.length) % items.length))\n }, [items.length])\n const dismiss = useCallback(() => {\n // Pull from `rawTrigger`, not `active`, so dismiss-while-dismissed\n // (a no-op now but reasonable to support) keeps the right anchor.\n if (rawTrigger)\n setDismissedTriggerStart(rawTrigger.span.start)\n }, [rawTrigger])\n const commit = useCallback((): { text: string, cursor: number } | null => {\n if (!active || items.length === 0)\n return null\n const item = items[Math.min(selectedIndex, items.length - 1)]\n return applyInsert(text, active.span, item.insertText)\n }, [active, items, selectedIndex, text])\n\n // Memoize the returned bag so consumers placing it in a `useEffect`\n // dep array don't observe a fresh reference every render. Same pattern\n // (and rationale) as `useStreamBuffer.return` — see streaming.ts.\n return useMemo<CompletionState<TItem>>(\n () => ({ active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references }),\n [active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references],\n )\n}\n","/**\n * Completion provider for session artifacts. Trigger: `#` (shared with the\n * chats provider — the renderer groups both under one popup).\n *\n * `#pr` → \"acme/widgets#42\" (pull request, open) → inserts the PR URL. Unlike chats there\n * is no token grammar: the inserted text IS the locator (URL, `git:…`,\n * path), which the model can act on directly and a human can read. No\n * `parseReferences` — a pasted URL is not a reference to resolve.\n *\n * Catalog getter is host-owned and sync; the host keeps it fresh off the\n * `artifact:recorded` / `artifact:changed` signals the same way it keeps\n * the chats catalog. `ensureCatalog` covers the cold first `#` keystroke.\n */\n\nimport type { ExtensionArtifactRow } from '../extensions/types'\nimport type { SessionArtifact } from '../session/artifacts'\nimport type { CompletionItem, CompletionProvider } from './completion-core'\nimport { Fzf } from 'fzf'\nimport { ageString } from './format'\n\n/** Id of the artifacts completion provider. */\nexport const ARTIFACTS_COMPLETION_PROVIDER_ID = 'artifacts'\n\n/** An artifact that can be referenced from a prompt. */\nexport interface ArtifactReferenceEntry {\n /** The session artifact. */\n artifact: SessionArtifact\n /** Pre-rendered row (`renderArtifactRow`) — kind registry stays host-side. */\n row: ExtensionArtifactRow\n /** Human kind label for the description (`Pull request`). */\n kindLabel: string\n}\n\n/** Provider payload for an artifact reference. */\nexport interface ArtifactReferenceData {\n /** The referenced artifact. */\n entry: ArtifactReferenceEntry\n /** What gets inserted: `row.href` when present, else the raw `uri`. */\n locator: string\n}\n\nconst DEFAULT_RESULT_LIMIT = 30\n\n/** Kind → short query aliases so `#pr` and `#branch` hit without typing the label. */\nconst KIND_ALIASES: Record<string, readonly string[]> = {\n pull_request: ['pr', 'pull', 'mr'],\n branch: ['branch', 'br'],\n commit: ['commit', 'sha'],\n file: ['file', 'path'],\n}\n\nfunction locatorFor(entry: ArtifactReferenceEntry): string {\n return entry.row.href ?? entry.artifact.uri\n}\n\nfunction haystack(entry: ArtifactReferenceEntry): string {\n const aliases = KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]\n return [entry.row.title, entry.row.subtitle ?? '', entry.artifact.label, entry.kindLabel, ...aliases].join(' ')\n}\n\nfunction descriptionFor(entry: ArtifactReferenceEntry): string {\n const bits = [entry.kindLabel]\n if (entry.row.status)\n bits.push(entry.row.status)\n bits.push(ageString(entry.artifact.updatedAt))\n const loc = locatorFor(entry)\n if (loc !== entry.row.title)\n bits.push(loc)\n return bits.join(', ')\n}\n\n/**\n * Filters and ranks artifacts for a query.\n * An empty query returns every artifact. Otherwise it matches the kind aliases, kind label and a fuzzy text match.\n *\n * @param catalog - Artifacts to search.\n * @param query - Text typed after the trigger.\n * @param limit - Maximum number of items returned.\n */\nexport function scoreArtifacts(\n catalog: readonly ArtifactReferenceEntry[],\n query: string,\n limit = DEFAULT_RESULT_LIMIT,\n): CompletionItem<ArtifactReferenceData>[] {\n const q = query.trim().toLowerCase()\n const fuzzy = q.length > 0\n ? new Set(new Fzf(catalog, { selector: haystack, casing: 'case-insensitive' }).find(q).map(r => r.item))\n : null\n const scored: { entry: ArtifactReferenceEntry, rank: number }[] = []\n for (const entry of catalog) {\n if (q.length === 0) {\n scored.push({ entry, rank: 5 })\n continue\n }\n const title = entry.row.title.toLowerCase()\n const aliases = (KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]).map(a => a.toLowerCase())\n const kindLabel = entry.kindLabel.toLowerCase()\n if (aliases.some(a => a === q || a.startsWith(q)) || kindLabel.startsWith(q)) {\n scored.push({ entry, rank: 0 })\n continue\n }\n if (title.startsWith(q)) {\n scored.push({ entry, rank: 1 })\n continue\n }\n if (title.includes(q) || locatorFor(entry).toLowerCase().includes(q)) {\n scored.push({ entry, rank: 2 })\n continue\n }\n if (fuzzy?.has(entry))\n scored.push({ entry, rank: 4 })\n }\n scored.sort((a, b) => a.rank - b.rank || b.entry.artifact.updatedAt - a.entry.artifact.updatedAt)\n return scored.slice(0, limit).map<CompletionItem<ArtifactReferenceData>>(({ entry }) => {\n const locator = locatorFor(entry)\n return {\n id: `artifact:${entry.artifact.id}`,\n label: entry.row.title,\n description: descriptionFor(entry),\n insertText: `${locator} `,\n data: { entry, locator },\n }\n })\n}\n\n/**\n * Creates a completion provider for `#` artifact references.\n * The provider only suggests. It inserts the artifact's locator as plain text, so it parses no references back.\n *\n * @param opts - Catalog getter, an optional async catalog loader and a result limit (default 30).\n */\nexport function createArtifactsCompletionProvider(opts: {\n getCatalog: () => readonly ArtifactReferenceEntry[]\n ensureCatalog?: () => Promise<readonly ArtifactReferenceEntry[]>\n limit?: number\n}): CompletionProvider<ArtifactReferenceData> {\n const limit = opts.limit ?? DEFAULT_RESULT_LIMIT\n return {\n id: ARTIFACTS_COMPLETION_PROVIDER_ID,\n trigger: '#',\n label: 'Artifacts',\n suggest(query) {\n const current = opts.getCatalog()\n if (current.length > 0 || !opts.ensureCatalog)\n return scoreArtifacts(current, query, limit)\n return opts.ensureCatalog().then(loaded => scoreArtifacts(loaded, query, limit))\n },\n // Inserted text is a plain locator, not a token: nothing to parse back.\n parseReferences: () => [],\n }\n}\n","import type { ReactNode } from 'react'\nimport type { ResolvedConfig } from './config'\nimport { createContext, useContext } from 'react'\n\n// ---------------------------------------------------------------------------\n// ConfigContext — renderer-agnostic React context that hands a fully-resolved\n// chat config down to deep consumers (modals, pickers, screens). Kept in its\n// own file so callers that only want `resolveConfig()` don't pay the\n// `import 'react'` cost.\n// ---------------------------------------------------------------------------\n\n/** Exported for hooks that read the config when there is one (`useModelName`). */\nexport const ConfigContext = createContext<ResolvedConfig | null>(null)\n\n/**\n * Provides a {@link ResolvedConfig} to the components below it.\n *\n * @param props - Props with the resolved `config` and the `children` to render.\n */\nexport function ConfigProvider({ config, children }: { config: ResolvedConfig, children: ReactNode }) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n}\n\n/** Reads the {@link ResolvedConfig} from the nearest {@link ConfigProvider}. Throws when used outside one. */\nexport function useConfig(): ResolvedConfig {\n const ctx = useContext(ConfigContext)\n if (!ctx)\n throw new Error('useConfig must be used inside <ConfigProvider>')\n return ctx\n}\n","/**\n * Live catalogs for files / skills / MCPs surfaced through React\n * context.\n *\n * **Why context, not props.** The modal layer stores its open node in\n * `useState`. React's element-identity reconciliation skips re-rendering\n * a subtree when the element reference hasn't changed — so passing\n * `skillsCatalog` as a prop into `modal.open(<SettingsModal …>)` bakes\n * the snapshot at open time and any subsequent `setSkillsCatalog`\n * doesn't reach the modal. Context propagation IS forwarded through\n * the modal layer regardless of element identity (same path the MCP\n * auth state uses), so reading via `useDiscovery()` gives the modal\n * live state.\n *\n * **Shape.** The provider exposes three groups:\n *\n * - **Catalogs** — the current state. Consumers re-render on every\n * update.\n * - **Ensure thunks** — kick the first load (idempotent). Wired into\n * completion providers; also called by the settings modal on mount\n * to fire-load lazy catalogs (currently `files`) on demand.\n * - **Refresh thunks** — force a rescan, bypassing the SWR throttle.\n * Wired into the settings modal's `ctrl+R`.\n *\n * Renderer-agnostic by design: the context only carries data + thunks.\n * The discovery work itself lives in `*-discovery.ts` modules; the\n * stale-while-revalidate machinery lives in `discovery-slot.ts`. The\n * provider's caller (AppShell for the TUI) owns where the slots live\n * and how their state propagates to `<DiscoveryProvider value>`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpToolSchema } from '../mcp'\nimport type { SkillConfig } from '../skills'\nimport type { FileEntry } from './files-discovery'\nimport type { DiscoveredMcp, DiscoveryError } from './mcps-discovery'\nimport type { DiscoveredPlugin } from './plugins-discovery'\nimport { createContext, useContext } from 'react'\n\n/** Skills, plugins and MCP servers found on disk, shared with the UI. */\nexport interface DiscoveryContextValue {\n /** Parsed `SkillConfig[]` from project + user scan paths. */\n skillsCatalog: readonly SkillConfig[]\n /**\n * Agent Plugins loaded from `.agents/plugins` / `.{prefix}/plugins`\n * (project + user). Includes rejected plugins — they render as read-only\n * diagnostics rather than vanishing.\n */\n pluginsCatalog: readonly DiscoveredPlugin[]\n /** Parsed `mcps.json` entries. */\n mcpsCatalog: readonly DiscoveredMcp[]\n /** Non-fatal parse errors from `mcps.json` lookups. */\n mcpsErrors: readonly DiscoveryError[]\n /**\n * Per-server full tool catalog from the on-disk cache (see\n * `chat/mcp-tools-cache.ts`). Updated as MCP servers bootstrap and\n * fire the `mcp:tools:list` hook. Missing key → no cache yet; the\n * settings UI surfaces an empty-state row under the expanded server.\n */\n mcpToolsByServer: Record<string, readonly McpToolSchema[]>\n /** Current file walk results — empty until lazy-load lands. */\n filesCatalog: readonly FileEntry[]\n\n /** Force a fresh skills rescan. Resolves once state is committed. */\n refreshSkills: () => Promise<void>\n /** Force a fresh plugin rescan. Resolves once state is committed. */\n refreshPlugins: () => Promise<void>\n /**\n * Re-parse `mcps.json`. Sync-shaped under the hood, async for symmetry.\n * Resolves `true` when the catalog actually changed (a publish that\n * schedules downstream agent rebuilds), `false` on a fingerprint no-op\n * or scan failure — callers that arm rebuild-coupled state (e.g. the\n * add-MCP auto-login) disarm on `false`.\n */\n refreshMcps: () => Promise<boolean>\n /** Force a fresh file walk. No UI affordance calls it yet. */\n refreshFiles: () => Promise<void>\n\n /** Idempotent first-load trigger for skills (used by `/` popover + modal mount). */\n ensureSkills: () => Promise<readonly SkillConfig[]>\n /** Idempotent first-load trigger for files (used by `@` popover). */\n ensureFiles: () => Promise<readonly FileEntry[]>\n}\n\nconst DiscoveryContext = createContext<DiscoveryContextValue | null>(null)\n\n/**\n * Provides a {@link DiscoveryContextValue} to the components below it.\n *\n * @param props - Props with the discovery `value` and the `children` to render.\n */\nexport function DiscoveryProvider({\n value,\n children,\n}: {\n value: DiscoveryContextValue\n children: ReactNode\n}) {\n return (\n <DiscoveryContext.Provider value={value}>\n {children}\n </DiscoveryContext.Provider>\n )\n}\n\n/**\n * Read live discovery state + actions. Throws if used outside a\n * `<DiscoveryProvider>` — discovery is a load-bearing dependency for\n * settings + completion popovers; bailing loud here surfaces wiring\n * mistakes at mount instead of producing empty catalogs at first\n * keystroke.\n */\nexport function useDiscovery(): DiscoveryContextValue {\n const ctx = useContext(DiscoveryContext)\n if (!ctx)\n throw new Error('useDiscovery must be used inside <DiscoveryProvider>')\n return ctx\n}\n\n/**\n * Non-throwing variant — returns `null` when no provider is mounted.\n * Used by composable modals (`<SettingsModal>`, …) that accept catalog\n * props as a fallback for embedders who don't wire the full TUI\n * shell. The in-app flow always mounts `<DiscoveryProvider>` so the\n * modal sees live state; standalone embeds get the prop snapshot.\n */\nexport function useDiscoveryOptional(): DiscoveryContextValue | null {\n return useContext(DiscoveryContext)\n}\n","/**\n * Stale-while-revalidate catalog slot.\n *\n * One slot per (catalog kind, projectDir) pin. Holds the first-load\n * promise + a `lastScannedAt` timestamp + an in-flight `refreshing`\n * promise. Idempotent across concurrent callers, safe to rotate when\n * the project changes (the slot's `abort` controller cancels in-flight\n * walks and the `.then` guards drop stale results).\n *\n * Renderer-agnostic. AppShell wraps it in React refs so React closures\n * stay stable; a non-TUI shell can use it directly against its own\n * state propagation.\n *\n * Contract:\n * - `ensure()` — kick the first load if not started; return the\n * cached promise. On subsequent calls, if `Date.now() -\n * lastScannedAt >= throttleMs` and no refresh is in flight, kick\n * a background revalidate that calls `onLoad(fresh)` when it\n * resolves. The original first-load promise still resolves with\n * the first walk's contents — consumers reading live state\n * elsewhere (React state) see the refreshed catalog.\n * - `refresh()` — force a rescan now, bypassing the throttle.\n * De-duplicates: if a refresh is already in flight, returns the\n * same promise.\n * - `isRefreshing()` — whether a refresh is running now.\n * - `abort()` — cancel any in-flight walk and detach. After this\n * the slot is dead; callers should rotate to a new slot.\n *\n * No state is exposed except through promises + the host's `onLoad`\n * callback; the slot doesn't try to be a store, only a coordinator.\n */\n\n/** Walk function — takes an abort signal, returns the discovered list. */\nexport type Walk<T> = (signal: AbortSignal) => Promise<readonly T[]>\n\n/** Notification: a fresh walk has resolved. Host is expected to commit to its own state store. */\nexport type Commit<T> = (items: readonly T[]) => void\n\n/** Options for {@link createDiscoverySlot}. */\nexport interface DiscoverySlotOptions<T> {\n /** Discover function — runs the actual FS / network walk. */\n walk: Walk<T>\n /** Commit hook — receives every successful walk's result (first + refreshes). */\n onLoad: Commit<T>\n /**\n * Min interval between background refreshes triggered by `ensure()`.\n * Default: 3_000ms. `refresh()` ignores this — it's the user's\n * explicit-intent path.\n */\n throttleMs?: number\n /** Optional error sink. Defaults to no-op; useful for `debugLog`. */\n onError?: (err: unknown, phase: 'first-load' | 'refresh') => void\n}\n\n/** Handle returned by {@link createDiscoverySlot}. */\nexport interface DiscoverySlot<T> {\n /**\n * Kick the first load (idempotent). Returns the first-load promise\n * once it exists. Schedules a background refresh if past throttle.\n */\n ensure: () => Promise<readonly T[]>\n /** Force-refresh now. De-duped across concurrent callers. */\n refresh: () => Promise<void>\n /** True if a refresh is currently running. */\n isRefreshing: () => boolean\n /** Cancel in-flight work + detach. Idempotent. */\n abort: () => void\n}\n\n/**\n * Creates a slot that loads a list lazily and keeps it fresh.\n * `ensure()` starts the first walk once and schedules throttled background refreshes. `refresh()` forces a walk. Every successful walk result goes to `onLoad`.\n *\n * @param options - The walk function, commit hook and optional throttle and error sink.\n */\nexport function createDiscoverySlot<T>(options: DiscoverySlotOptions<T>): DiscoverySlot<T> {\n const throttleMs = options.throttleMs ?? 3_000\n const abortCtrl = new AbortController()\n let firstLoad: Promise<readonly T[]> | null = null\n let refreshing: Promise<void> | null = null\n let lastScannedAt = 0\n let aborted = false\n\n const handleError = (err: unknown, phase: 'first-load' | 'refresh'): void => {\n options.onError?.(err, phase)\n }\n\n const startRefresh = (): Promise<void> => {\n lastScannedAt = Date.now()\n const run = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return\n options.onLoad(items)\n })\n .catch((err) => {\n if (aborted)\n return\n handleError(err, 'refresh')\n })\n .finally(() => {\n if (refreshing === run)\n refreshing = null\n })\n refreshing = run\n return run\n }\n\n const slot: DiscoverySlot<T> = {\n ensure() {\n if (aborted)\n return Promise.resolve([] as readonly T[])\n if (!firstLoad) {\n lastScannedAt = Date.now()\n firstLoad = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return [] as readonly T[]\n options.onLoad(items)\n return items\n })\n .catch((err) => {\n if (!aborted)\n handleError(err, 'first-load')\n return [] as readonly T[]\n })\n return firstLoad\n }\n // SWR: revalidate in the background if stale and not already running.\n if (!refreshing && Date.now() - lastScannedAt >= throttleMs)\n void startRefresh()\n return firstLoad\n },\n refresh() {\n if (aborted)\n return Promise.resolve()\n if (refreshing)\n return refreshing\n // Forced refresh from a user-initiated path (e.g. settings\n // modal `ctrl+R`). If the first load hasn't happened yet,\n // bootstrap through `ensure` so subsequent ensure callers see\n // the right `firstLoad` promise.\n if (!firstLoad)\n return slot.ensure().then(() => {})\n return startRefresh()\n },\n isRefreshing() {\n return refreshing !== null\n },\n abort() {\n if (aborted)\n return\n aborted = true\n abortCtrl.abort()\n },\n }\n return slot\n}\n","/**\n * `$EDITOR` / `$VISUAL` command parsing.\n *\n * These variables conventionally hold a command LINE, not a bare executable —\n * `code --wait`, `cursor --wait`, `subl -w`, `emacsclient -nw` are all normal\n * values. Spawning the whole string as argv[0] looks for a file literally named\n * `cursor --wait` and fails with ENOENT, so the flags have to be split off.\n *\n * Kept out of the TUI (and free of `node:child_process`) so the\n * tokenizer is unit-testable without pulling in the TUI.\n */\n\n/**\n * Split an editor command line into `[executable, ...args]`.\n *\n * Whitespace-separated, with single and double quotes honored so an editor\n * living under a path with spaces still works\n * (`\"/Applications/My App/bin/ed\" -w`). Quotes group and are stripped;\n * backslash escaping is deliberately NOT implemented, because on Windows\n * backslash is the path separator and treating `C:\\tools\\ed` as escapes would\n * corrupt far more command lines than it would rescue.\n *\n * Returns `[]` for an empty or whitespace-only spec, so callers can fall back\n * to the platform opener rather than spawning `''`.\n *\n * @param spec - Raw editor command line, such as the value of `$EDITOR`.\n * @example\n * ```ts\n * import { parseEditorCommand } from 'zidane/chat'\n *\n * const [cmd, ...args] = parseEditorCommand('code --wait')\n * // cmd === 'code', args === ['--wait']\n * ```\n */\nexport function parseEditorCommand(spec: string): string[] {\n const tokens: string[] = []\n let current = ''\n // The quote character we're inside, or null at top level. Tracked (rather\n // than a boolean) so a double quote inside single quotes stays literal.\n let quote: '\"' | '\\'' | null = null\n // Distinct from `current !== ''` so `\"\"` yields an empty-string token instead\n // of vanishing — an explicitly quoted empty argument was still an argument.\n let quoted = false\n\n for (const ch of spec) {\n if (quote) {\n if (ch === quote)\n quote = null\n else\n current += ch\n continue\n }\n if (ch === '\"' || ch === '\\'') {\n quote = ch\n quoted = true\n continue\n }\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n if (current !== '' || quoted) {\n tokens.push(current)\n current = ''\n quoted = false\n }\n continue\n }\n current += ch\n }\n if (current !== '' || quoted)\n tokens.push(current)\n return tokens\n}\n\n/**\n * Re-quote a single argv token for a shell that will re-parse the joined command\n * line — i.e. `spawn(..., { shell: true })`, which Node uses on Windows so that\n * `.cmd` / `.bat` wrappers (how `code`, `cursor` and friends ship there) resolve.\n *\n * Node does NOT re-quote when `shell` is set: it joins `[cmd, ...args]` with\n * spaces and hands the string over. Since {@link parseEditorCommand} has already\n * STRIPPED the user's quotes, handing tokens back unquoted would split\n * `C:\\Program Files\\ed.exe` at the space and try to run `C:\\Program`. This puts\n * the quotes back.\n *\n * Only tokens that need it are quoted, so a plain `code` stays `code`. A literal\n * double quote is doubled, which is how `cmd.exe` escapes one inside a quoted\n * run.\n *\n * The test covers `cmd.exe` METACHARACTERS, not just whitespace: `&`, `|`, `^`,\n * `<`, `>` and friends carry none of the obvious \"needs quoting\" signals, so a\n * repo cloned into `C:\\work\\a&b` would otherwise hand `C:\\work\\a&b\\AGENTS.md`\n * over bare, and cmd would split it at the `&` and run the tail as a second\n * command.\n *\n * `%` and `!` are in the class so such a token is quoted rather than passed\n * bare — but be clear about the limit: quoting does NOT stop cmd.exe expanding\n * `%VAR%`, nor `!VAR!` where delayed expansion is on. Double quotes suppress\n * splitting and other syntax, not expansion. That is a property of the shell\n * which no amount of quoting can close; the worst case is a wrong path, not an\n * injected command.\n *\n * @param token - One argv token, already stripped of its original quotes.\n */\nexport function quoteForShell(token: string): string {\n if (token === '')\n return '\"\"'\n if (!/[\\s\"&|^<>%()!]/.test(token))\n return token\n return `\"${token.replace(/\"/g, '\"\"')}\"`\n}\n","import type { ReactNode } from 'react'\nimport type { BooleanSettingKey } from './settings-catalog'\nimport type { Settings } from './types'\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport { enforceSettingsInvariants } from './settings-catalog'\n\n// The React-free catalog lives in `settings-catalog.ts`.\nexport * from './settings-catalog'\n\n// ---------------------------------------------------------------------------\n// SettingsProvider — single source of truth for the settings.\n//\n// `onChange` is called with the latest settings whenever a row toggles, so\n// the host can persist them. Persistence pathing lives outside this module\n// to keep the settings layer portable across renderers (TUI, GUI, …).\n// ---------------------------------------------------------------------------\n\nexport interface SettingsContextValue {\n settings: Settings\n /** Flip a boolean setting in place. Restricted at the type level to boolean-valued keys. */\n toggle: (key: BooleanSettingKey) => void\n /** Write any setting key to a new value. Used by string-valued choice rows. */\n setSetting: <K extends keyof Settings>(key: K, value: Settings[K]) => void\n /** One line the Settings modal shows (a server refused the last write); cleared by the next change. */\n status?: string | null\n /**\n * Settings sync markers, while the modal edits a server another computer\n * syncs to (`docs/SERVER.md` § Settings sync): which setting came from\n * which device, which was edited on the server since (an override), and\n * dropping an override. Absent elsewhere.\n */\n sync?: SettingsSyncMarks\n}\n\n/** A setting's sync state on the server the modal edits. */\nexport interface SettingsSyncMarks {\n marker: (key: string) => { kind: 'synced' | 'override', device: string } | null\n /** Drop the override on `key`; false when it has none. */\n reset: (key: string) => boolean\n}\n\nconst SettingsContext = createContext<SettingsContextValue | null>(null)\n\n/**\n * Holds the settings state, applies {@link enforceSettingsInvariants} on every write and reports each change to `onChange`.\n *\n * @param props - `initial` settings, an optional `onChange` callback and the `children` to render.\n */\nexport function SettingsProvider({\n initial,\n onChange,\n children,\n}: {\n initial: Settings\n onChange?: (settings: Settings) => void\n children: ReactNode\n}) {\n // Normalized on load too: an older build (or a hand edit) may have persisted\n // a dependent toggle without its requirement.\n const [settings, setSettings] = useState<Settings>(() => enforceSettingsInvariants(initial))\n\n // `onChange` runs as a side-effect *after* commit, not inside the state\n // updater. Two reasons:\n // 1. React 18 strict-mode dev runs invoke state updaters twice for\n // every transition; a `disk.save(...)` inside the updater would\n // double-fire there.\n // 2. Pure-update is the React contract — updaters must be free of\n // observable side-effects. Persisting from inside breaks the\n // contract regardless of strict mode.\n //\n // A ref keeps the latest `onChange` reachable from the effect without\n // forcing the effect to re-run on every render the parent re-renders.\n // The `initialMount` ref skips the very first commit (we don't want to\n // persist the initial state we just loaded from disk).\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const initialMount = useRef(true)\n useEffect(() => {\n if (initialMount.current) {\n initialMount.current = false\n return\n }\n onChangeRef.current?.(settings)\n }, [settings])\n\n const toggle = useCallback((key: BooleanSettingKey) => {\n setSettings(prev => enforceSettingsInvariants({ ...prev, [key]: !prev[key] }))\n }, [])\n\n const setSetting = useCallback(<K extends keyof Settings>(key: K, value: Settings[K]) => {\n setSettings(prev => (prev[key] === value ? prev : enforceSettingsInvariants({ ...prev, [key]: value })))\n }, [])\n\n const value = useMemo<SettingsContextValue>(\n () => ({ settings, toggle, setSetting }),\n [settings, toggle, setSetting],\n )\n\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/**\n * Re-provides settings to a subtree with other values and writers: a\n * surface that edits somewhere else than this window (the TUI's Settings\n * modal while it edits a remote host) reuses every settings-reading\n * component unchanged.\n */\nexport function SettingsOverride({ value, children }: { value: SettingsContextValue, children: ReactNode }) {\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/** Returns the settings context value. Throws when used outside {@link SettingsProvider}. */\nexport function useSettings(): SettingsContextValue {\n const ctx = useContext(SettingsContext)\n if (!ctx)\n throw new Error('useSettings must be used inside <SettingsProvider>')\n return ctx\n}\n","/**\n * Renderer-agnostic state machine for an \"enabled allowlist\" — the shape\n * used by `settings.enabledSkills`, `settings.enabledMcps`,\n * `settings.enabledExtensions` and `settings.enabledPlugins` (see\n * {@link EnabledAllowlistKey}).\n *\n * Semantics, kept identical across every call site:\n * - `undefined` → every catalog entry is enabled (default — user has\n * never opened the picker; new entries flow in).\n * - `[]` → the whole subsystem is off.\n * - `[names]` → explicit allowlist; the persisted shape stays a\n * concrete array once the user has toggled anything.\n *\n * The hook exposes a live `enabledSet` (Set<string>) + a `toggle(name)`\n * callback that persists the result through `useSettings().setSetting`.\n * The first toggle seeds the persisted allowlist from the current catalog\n * so newly-added entries don't silently drop on the next launch.\n *\n * Renderer-neutral: works for the TUI's `<ToggleListModal>`, a GUI\n * checkbox list, a CLI flag tool. Hook tests live alongside the chat\n * suite; the rendering layer is tested where it lives.\n */\n\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Settings keys whose value type is `readonly string[] | undefined`. */\nexport type EnabledAllowlistKey = 'enabledSkills' | 'enabledMcps' | 'enabledExtensions' | 'enabledPlugins'\n\n/**\n * Reactive handle over a persisted enabled-names allowlist, returned by {@link useEnabledToggleSet}.\n */\nexport interface EnabledToggleSet {\n /** Live set of enabled names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Toggle one name on/off; persists immediately via `setSetting`. */\n toggle: (name: string) => void\n}\n\n/**\n * Bind an \"enabled allowlist\" setting to a discovered catalog.\n *\n * `keyOf` extracts the persisted identity from a catalog entry (skill name,\n * MCP server name, …). Pass a stable, collision-free key — the persisted\n * allowlist is keyed against it forever.\n *\n * @param opts - Catalog, key extractor, setting key, and optional extra keys to keep when pruning the allowlist.\n */\nexport function useEnabledToggleSet<T>(opts: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n /**\n * Names that survive the persisted-allowlist GC even when absent from\n * `catalog`. Use when the catalog deliberately hides entries that are\n * still known to exist — e.g. errored / untrusted extensions are kept\n * out of the toggle list but must not be dropped from the allowlist\n * by an unrelated toggle. Defaults to the catalog keys.\n */\n gcKeys?: readonly string[]\n}): EnabledToggleSet {\n const { settings, setSetting } = useSettings()\n const { catalog, keyOf, settingKey, gcKeys } = opts\n const persisted = settings[settingKey]\n\n const enabledSet = useMemo<ReadonlySet<string>>(() => {\n if (persisted === undefined)\n return new Set(catalog.map(keyOf))\n return new Set(persisted)\n }, [persisted, catalog, keyOf])\n\n // Live catalog membership — used to garbage-collect stale persisted\n // names on every write. Without this, a skill / MCP that was once\n // enabled and then removed from disk lingers in the JSON forever\n // (the next toggle round-trips it back into the array). Functionally\n // harmless today (the agent ignores unknown names) but it lets the\n // persisted state drift away from reality. `gcKeys` widens the\n // \"known\" set beyond the rendered catalog (see its JSDoc).\n const catalogKeys = useMemo(\n () => new Set(gcKeys ?? catalog.map(keyOf)),\n [gcKeys, catalog, keyOf],\n )\n\n const toggle = useCallback((name: string) => {\n // Seed from the current `enabledSet` (covers both the seeded-from-catalog\n // and explicit-allowlist branches). Persist as a sorted array filtered\n // against the live catalog so stale names — entries deleted from disk\n // between launches — don't linger forever in the persisted allowlist.\n // `undefined` is reserved for \"fresh install, accept new discoveries\";\n // once the user toggles once the persistence shape stays a concrete list.\n const next = new Set(enabledSet)\n if (next.has(name))\n next.delete(name)\n else\n next.add(name)\n const persisted = [...next]\n .filter(n => catalogKeys.has(n))\n .sort()\n setSetting(settingKey, persisted)\n }, [enabledSet, catalogKeys, settingKey, setSetting])\n\n return { enabledSet, toggle }\n}\n","/**\n * Project file discovery for the `@`-prefixed files completion provider.\n *\n * Primary strategy — `git ls-files --cached --others --exclude-standard`.\n * Git already understands `.gitignore`, `.git/info/exclude`, and global\n * excludes, so we get its ignore rules without reimplementing them and one\n * process call instead of a full walk. Falls back to a filesystem walk (no\n * depth limit, capped by `maxFiles`) when git isn't available or the\n * directory isn't a repo.\n *\n * Stateless: callers cache the returned list (e.g. in App state) and pass it\n * into the provider's `getCatalog`. The list is a snapshot — refresh on\n * cwd change or via a user-driven action.\n */\n\nimport type { Dirent, Stats } from 'node:fs'\nimport { spawn } from 'node:child_process'\nimport { readdir, stat } from 'node:fs/promises'\nimport { resolve, sep } from 'node:path'\nimport { errorMessage } from '../errors'\n\n/** One row in the project file catalog. `path` is forward-slashed + relative to `cwd`. */\nexport interface FileEntry {\n /** Forward-slashed relative path from the discovery cwd. Stable across OSes. No trailing slash for dirs. */\n path: string\n /** Basename — used for prefix ranking in the completion provider. */\n name: string\n /** Source — `git` when listed via `git ls-files`, `fs` from the walk fallback. */\n source: 'git' | 'fs'\n /**\n * Entry kind. Directories are derived from the discovered file paths\n * (every ancestor of a listed file), so only directories that contain\n * at least one non-ignored file appear — mirroring `git ls-files`\n * semantics, which never lists empty or fully-ignored dirs.\n */\n kind: 'file' | 'dir'\n}\n\n/**\n * Hard cap on the discovered file count. Bigger than any reasonable repo's\n * source tree, small enough that a runaway scan can't pin the renderer's\n * filter. Provider's substring match runs over the full list per\n * keystroke; 10k entries × cheap `.includes()` is well under one frame.\n */\nconst DEFAULT_MAX_FILES = 10_000\n\n/**\n * Names skipped during the fs-walk fallback. Mirrors what `git ls-files`\n * would exclude via the default `.gitignore` shipped in most repos —\n * `node_modules`, `dist`, build caches — plus the `.git` dir itself.\n * Best-effort; the git path is the authoritative one.\n */\nconst FS_WALK_SKIP = new Set<string>([\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n '.next',\n '.nuxt',\n '.cache',\n '.turbo',\n '.parcel-cache',\n '.vercel',\n '.svelte-kit',\n '.expo',\n '.gradle',\n 'target',\n '__pycache__',\n '.pytest_cache',\n '.venv',\n 'venv',\n '.idea',\n '.vscode-test',\n])\n\n/** Options for `listProjectFiles`. */\nexport interface ListProjectFilesOptions {\n /** Discovery root. Default: `process.cwd()`. */\n cwd?: string\n /**\n * Cap on discovered files. Default: 10,000. Bigger lists fall back to\n * a truncated set. Derived directory entries are appended on top with\n * their own equal cap, so the returned array can exceed `maxFiles`\n * (bounded by 2×).\n */\n maxFiles?: number\n /** Aborts the scan early. Useful when re-running on rapid project switches. */\n signal?: AbortSignal\n}\n\n/**\n * Discover every non-ignored file under `cwd`, plus the directories that\n * contain them (derived from the file paths, `kind: 'dir'`). Tries\n * `git ls-files` first; on failure (no git, not a repo, abort) walks the\n * fs with a hand-rolled skip list.\n *\n * Errors are not thrown — the function always returns an array (possibly\n * empty). Callers wanting failure diagnostics can opt into them via\n * `ZIDANE_DEBUG`.\n *\n * @param opts - Working directory, file cap, and abort signal. All optional.\n */\nexport async function listProjectFiles(opts: ListProjectFilesOptions = {}): Promise<FileEntry[]> {\n const cwd = resolve(opts.cwd ?? process.cwd())\n const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES\n const signal = opts.signal\n\n try {\n const paths = await listViaGit(cwd, signal)\n return toEntries(paths, 'git', maxFiles)\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] git ls-files failed (${errorMessage(err)}), walking the file system instead\\n`)\n try {\n const paths = await listViaFs(cwd, maxFiles, signal)\n return toEntries(paths, 'fs', maxFiles)\n }\n catch (fsErr) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] fs walk failed: ${errorMessage(fsErr)}\\n`)\n return []\n }\n }\n}\n\n/**\n * Run `git ls-files --cached --others --exclude-standard` and return paths.\n *\n * - `--cached` — tracked files.\n * - `--others` — untracked but not ignored.\n * - `--exclude-standard` — apply `.gitignore`, `.git/info/exclude`, and\n * the user's global excludes file.\n *\n * Throws on non-zero exit (not a repo / no git) so the fallback path can\n * pick up. Aborts cleanly on `signal.aborted`.\n */\nasync function listViaGit(cwd: string, signal: AbortSignal | undefined): Promise<string[]> {\n return new Promise<string[]>((resolveP, rejectP) => {\n const child = spawn('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n // Buffered as chunk arrays — joining once at the end keeps GC quiet on\n // huge repos. A `let stdout += chunk` accumulator reallocates the whole\n // buffer per chunk; with a ~10MB `git ls-files` output that's quadratic\n // in chunk count.\n const stdoutChunks: string[] = []\n const stderrChunks: string[] = []\n const onAbort = () => child.kill('SIGTERM')\n if (signal) {\n if (signal.aborted) {\n child.kill('SIGTERM')\n rejectP(new Error('aborted'))\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n child.stdout.setEncoding('utf8')\n child.stderr.setEncoding('utf8')\n child.stdout.on('data', chunk => stdoutChunks.push(chunk))\n child.stderr.on('data', chunk => stderrChunks.push(chunk))\n child.on('error', (err) => {\n signal?.removeEventListener('abort', onAbort)\n rejectP(err)\n })\n child.on('close', (code) => {\n signal?.removeEventListener('abort', onAbort)\n if (signal?.aborted) {\n rejectP(new Error('aborted'))\n return\n }\n if (code !== 0) {\n rejectP(new Error(`git ls-files exited ${code}: ${stderrChunks.join('').trim()}`))\n return\n }\n // `-z` separates with NUL so paths containing newlines stay intact.\n const paths = stdoutChunks.join('').split('\\0').filter(p => p.length > 0)\n resolveP(paths)\n })\n })\n}\n\n/**\n * Depth-first fs walk fallback. Used when git is unavailable. Honors a\n * hard-coded skip list; doesn't read `.gitignore`. Acceptable for hosts\n * outside a git checkout (e.g. a freshly-`unzip`ped project).\n */\nasync function listViaFs(\n cwd: string,\n maxFiles: number,\n signal: AbortSignal | undefined,\n): Promise<string[]> {\n const out: string[] = []\n const stack: string[] = ['.']\n while (stack.length > 0) {\n if (signal?.aborted)\n throw new Error('aborted')\n const rel = stack.pop()!\n const abs = rel === '.' ? cwd : resolve(cwd, rel)\n let entries: Dirent[]\n try {\n entries = await readdir(abs, { withFileTypes: true })\n }\n catch {\n // Unreadable dir — skip silently. EACCES / ENOENT during a walk\n // shouldn't fail the whole discovery.\n continue\n }\n for (const e of entries) {\n if (FS_WALK_SKIP.has(e.name))\n continue\n const childRel = rel === '.' ? e.name : `${rel}${sep}${e.name}`\n if (e.isDirectory()) {\n stack.push(childRel)\n }\n else if (e.isFile()) {\n out.push(toForwardSlash(childRel))\n if (out.length >= maxFiles)\n return out\n }\n else if (e.isSymbolicLink()) {\n // Resolve once; ignore broken links and follow only if it points\n // to a regular file. We don't recurse into symlinked dirs to\n // avoid cycles.\n let st: Stats\n try {\n st = await stat(resolve(abs, e.name))\n }\n catch {\n continue\n }\n if (st.isFile()) {\n out.push(toForwardSlash(childRel))\n if (out.length >= maxFiles)\n return out\n }\n }\n }\n }\n return out\n}\n\nfunction toForwardSlash(p: string): string {\n return sep === '/' ? p : p.replaceAll(sep, '/')\n}\n\n/**\n * File entries (plus their ancestor dirs) from relative paths — what\n * {@link listProjectFiles} returns, for lists produced elsewhere (a remote\n * sandbox's `git ls-files`).\n *\n * @param paths - Relative file paths.\n * @param source - Where the paths came from. Defaults to `'git'`.\n * @param maxFiles - Maximum number of entries to return. Defaults to the same cap as {@link listProjectFiles}.\n */\nexport function fileEntriesFromPaths(paths: readonly string[], source: 'git' | 'fs' = 'git', maxFiles = DEFAULT_MAX_FILES): FileEntry[] {\n return toEntries(paths, source, maxFiles)\n}\n\nfunction toEntries(paths: readonly string[], source: 'git' | 'fs', maxFiles: number): FileEntry[] {\n const seen = new Set<string>()\n const out: FileEntry[] = []\n for (const raw of paths) {\n const path = toForwardSlash(raw)\n if (path.length === 0 || seen.has(path))\n continue\n seen.add(path)\n const lastSlash = path.lastIndexOf('/')\n const name = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n out.push({ path, name, source, kind: 'file' })\n if (out.length >= maxFiles)\n break\n }\n // Directory pass — derive every ancestor dir of the (possibly capped)\n // file list so `@` can also reference directories. Appended after the\n // files with its own `maxFiles` cap (deep trees can yield more dirs\n // than files) so a truncated file list never crowds out the dirs and\n // the total stays bounded at 2×`maxFiles`.\n const dirs = new Set<string>()\n for (const entry of out) {\n let p = entry.path\n for (let idx = p.lastIndexOf('/'); idx > 0; idx = p.lastIndexOf('/')) {\n p = p.slice(0, idx)\n if (dirs.has(p))\n break // ancestors of an already-seen dir are seen too\n dirs.add(p)\n }\n }\n let dirCount = 0\n for (const path of dirs) {\n const lastSlash = path.lastIndexOf('/')\n const name = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n out.push({ path, name, source, kind: 'dir' })\n if (++dirCount >= maxFiles)\n break\n }\n return out\n}\n","// ---------------------------------------------------------------------------\n// Footer hint composition.\n//\n// Pure presentation logic — given the current screen, run state, and a\n// resolved keybinding map, produce the ordered list of {@link Hint}s\n// the bottom-bar Footer should render. Lives in `zidane/chat` (not\n// `zidane/tui`) because it's renderer-agnostic: the only types involved\n// are `Hint`, `Screen`, `SessionMeta`, `Settings['uiMode']`, and `KeyBindings`. A GUI\n// shell can consume the same list and paint it however it likes.\n//\n// Priority order is intentional and load-bearing:\n//\n// pending approval > live pending interaction > resumed pending\n// interaction > busy (streaming) > auth screen > onboarding screen (no\n// hints) > sessions screen > chat screen (idle).\n//\n// The first four return state-specific hint sets unconditionally —\n// `uiMode` is ignored there because those affordances (approve / abort\n// / navigate) are not the noise the minimal mode is targeting. Minimal\n// mode only collapses the chat-idle row.\n// ---------------------------------------------------------------------------\n\nimport type { Hint } from './hints'\nimport type { KeyBindings } from './keybindings'\nimport type { Screen, SessionMeta, Settings } from './types'\nimport { formatBindingForDisplay } from './keybindings'\n\n/**\n * Options bag for {@link buildHints}. Flat shape so call sites read as a\n * checklist of \"what state do I have to surface\" — easy to thread\n * through React `useMemo` deps and to construct fresh in tests.\n */\nexport interface BuildHintsOptions {\n /** Screen the footer is shown on. */\n screen: Screen\n /** True while the agent is running a turn. */\n busy: boolean\n /** True while a safe-mode approval prompt is pending. */\n pending: boolean\n /**\n * True when a LIVE pending interaction is on screen (agent is mid-run,\n * awaiting the tool's Promise). Same nav hints as the safe-mode\n * approval picker — `abortRun` aborts the run.\n */\n pendingInteractionLive: boolean\n /**\n * True when the chat screen is showing a resumed (not live) pending\n * interaction. The agent isn't running; esc leaves the form without\n * persisting a response, so the hint reads \"leave for later\" — the\n * session re-enqueues the same interaction on next activation.\n */\n pendingInteractionResumed: boolean\n /** Active session, or `null` when none is open. */\n currentSession: SessionMeta | null\n /** True when more than one agent profile is available, so the agent switch hint is useful. */\n hasMultipleAgents: boolean\n /**\n * Chat-screen chrome density — `'minimal'` collapses the idle chat\n * hint row to just `agent / model / settings`. Busy and pending\n * states keep their full hint sets because those\n * affordances are load-bearing (abort, navigate, approve).\n */\n uiMode: Settings['uiMode']\n /** Label of the active model, or `null` when none is selected. */\n modelLabel: string | null\n /** Foreground color for the model label. */\n modelColor: string\n /** Current reasoning effort label, or `null` when the model has no reasoning knob. */\n effortLabel: string | null\n /** Foreground color for the effort label. */\n effortColor: string\n /**\n * Foreground for the `/l` chord that introduces the effort label. Unset, it\n * takes the color of the model key it extends, so the pair reads as one binding.\n */\n effortKeyColor?: string\n /**\n * Comma-joined labels of the enabled model options (e.g. `'fast'`), or `null`\n * when none are on. Appended after the effort label in the model chord — when\n * the model has no reasoning knob, it rides the chord on its own.\n */\n modelOptionsLabel?: string | null\n /** Label of the active agent profile. */\n agentLabel: string\n /** Foreground color for the agent label. */\n agentColor: string\n /**\n * Resolved keybindings — every hint key string flows through this so\n * a user-level rebind (e.g. `\"openSettings\": \"ctrl+p\"`) shows up live\n * in the bottom bar without us hard-coding the default `'ctrl+o'`.\n */\n keybindings: KeyBindings\n /**\n * Number of currently-dispatching tool calls. Drives the\n * `<cancelToolCall> cancel` chip in the `busy` hint row and the\n * `cancel task` chip on the idle chat row. Without\n * it, the binding stays inert and there's no point advertising the\n * shortcut. Falls back to \"no chip\" when no tools are in flight\n * (model is mid-stream but hasn't dispatched yet).\n */\n inFlightToolCount: number\n /**\n * Number of currently-active skills. Drives the footer's\n * `✦ N skill(s)` chip on the chat screen — purely informational,\n * a passive surface for \"tool restrictions are in effect\". Hidden\n * when `0` so the row stays uncluttered for the common case.\n */\n activeSkillCount: number\n /** Foreground for the skills chip (typically `COLOR.brand`). */\n skillsChipColor: string\n /**\n * Progress label for the todo chip (`\"2/5\"` from `todoProgressLabel`), or\n * `null` to hide it.\n *\n * As a `Hint` it inherits the row's click-to-keypress\n * contract for free (`dispatchHintKey`), so clicking it opens the list the\n * same way `ctrl+t` does — and it sits below the prompt like every other\n * control.\n */\n todoProgress: string | null\n /**\n * Pre-built `↑ vX.Y.Z` chip from `buildUpdateHint(useUpdateCheck())`,\n * or `null` when no update is available. Always last in the chat-screen\n * row so it's the first to drop when the terminal narrows.\n */\n updateHint: Hint | null\n /**\n * Pre-built prompt-cache toast, non-null only for a few seconds after a\n * `'cache:break'` hook fired (the host owns the visibility timer and the\n * severity split): warn-colored `⚡ cache break in <culprit>: +Nk tok, ~$0.48`\n * for attributable breaks, dim `⚡ cache expired: +Nk tok, ~$0.48` for\n * byte-stable TTL expiry — routine across human think-time, so it must not\n * cry wolf. Sits beside `updateHint` at the droppable end of the row: a\n * transient cost signal, not an action.\n */\n cacheBreakHint?: Hint | null\n /**\n * Sessions-screen list scope — `true` when the list spans all\n * projects. Drives the `tab` hint's label, which advertises the scope\n * the toggle switches TO. Optional so non-sessions callers can omit it.\n */\n sessionsScopeAll?: boolean\n /** Sessions screen: plain ←/→ switch spaces (Spaces on, 2+ spaces). Adds the `←→ space` hint. */\n sessionsSpaceSwitch?: boolean\n}\n\n/**\n * Build the footer's shortcut hints for the current screen. On the chat\n * screen the model id rides next to its `ctrl+m` shortcut and the agent\n * label rides next to `shift+tab`, each in its accent color — the bar\n * doubles as the status display without needing separate badges. When\n * the active model exposes reasoning, the `ctrl+m` hint grows a\n * secondary `/l` chord with the current effort label, surfacing the\n * effort picker as a discoverable, in-place affordance.\n *\n * @param options - Screen, run state, labels, colors, and keybindings that decide which hints appear.\n */\nexport function buildHints(options: BuildHintsOptions): Hint[] {\n const {\n screen,\n busy,\n pending,\n pendingInteractionLive,\n pendingInteractionResumed,\n currentSession,\n hasMultipleAgents,\n uiMode,\n modelLabel,\n modelColor,\n effortLabel,\n effortColor,\n effortKeyColor,\n modelOptionsLabel,\n agentLabel,\n agentColor,\n keybindings,\n inFlightToolCount,\n activeSkillCount,\n skillsChipColor,\n todoProgress,\n updateHint,\n cacheBreakHint = null,\n sessionsScopeAll,\n sessionsSpaceSwitch,\n } = options\n\n // Todo progress chip. Built BEFORE the priority branches below because it is\n // STATUS, not a shortcut: it has to survive every one of them. It reports\n // todo progress independent of `busy`, and an `in_progress` todo is set\n // precisely while the agent is working and calling `todowrite`. Building\n // it after the early returns would hide it for the whole window it exists\n // to cover.\n //\n // Appended LAST in each branch so `clipHintsToWidth` (which drops from the\n // right) sheds it before the abort/navigate affordances, which must stay.\n const todosChip: Hint | null = todoProgress !== null\n ? { key: keybindings.openTodos, label: `${todoProgress} todos` }\n : null\n const withTodos = (hints: Hint[]): Hint[] => todosChip ? [...hints, todosChip] : hints\n\n // Priority: safe-mode approval > live interaction > resumed\n // interaction > plain streaming. All three pending shapes share the\n // ↑↓ / ↵ nav prefix; only the esc semantics differ.\n if (pending)\n return withTodos([{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: formatBindingForDisplay(keybindings.abortRun), label: 'abort run' }])\n if (pendingInteractionLive)\n return withTodos([{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: formatBindingForDisplay(keybindings.abortRun), label: 'abort run' }])\n if (pendingInteractionResumed)\n return withTodos([{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'leave for later' }])\n if (busy) {\n // The run-wide abort, then the way out. The per-call cancel\n // (`cancelToolCall`) stays bound but unadvertised mid-run: the abort\n // covers the common case, and the chip only crowded the row.\n const baseBusyHints: Hint[] = []\n baseBusyHints.push({ key: formatBindingForDisplay(keybindings.abortRun), label: 'abort' })\n baseBusyHints.push({ key: 'esc', label: 'sessions' })\n // The model stays in view during the run it serves (a fallback shows in\n // its warn colour exactly then); no key, since the picker waits for idle.\n if (modelLabel)\n baseBusyHints.push({ key: '', label: modelLabel, labelColor: modelColor })\n return withTodos(baseBusyHints)\n }\n if (screen === 'auth')\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'exit' }]\n // The setup wizard shows the keys of each step inside its frame.\n if (screen === 'onboarding')\n return []\n if (screen === 'sessions') {\n return [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\n { key: 'tab', label: sessionsScopeAll ? 'current project' : 'all projects' },\n ...(sessionsSpaceSwitch ? [{ key: '←→', label: 'space' }] : []),\n { key: keybindings.openSessionDetails, label: 'session' },\n { key: keybindings.openSettings, label: 'settings' },\n { key: 'esc', label: currentSession ? 'back' : 'exit' },\n ]\n }\n // Chat screen. Prompt-input shortcuts (`↵ send`, `shift+↵ newline`,\n // `↑↓ history`) live in the prompt box's overlay title — the bottom\n // bar only carries the most common agent / model / global affordances. The\n // `cycleAgent` binding shows the active label inline; the\n // `openModelPicker` binding shows the active model id inline; etc.\n // `cycleAgent` is suppressed when there's only one profile and\n // `openSessionDetails` only renders once a session exists.\n //\n // Minimal UI mode: strip the chat-idle bottom bar to just `agent`\n // (when there's >1 profile), `model/effort`, and `settings`. The\n // full keybinding catalog stays reachable from inside settings.\n // Busy / pending / select-turn / queue branches above already exited\n // before this point — minimal only affects the resting chat row.\n //\n // Compose the model hint. When the active model supports reasoning,\n // the primary `<openModelPicker> model` pair grows a secondary\n // `/l effort` chord that doubles as the discoverability hint for the\n // effort picker (`openEffortPicker`) and the status display for the\n // current effort. The chord shares the model accent so the pair\n // reads as one keyboard binding across every theme.\n // The `/l` chord (from `openEffortPicker`) carries reasoning effort plus any enabled model options,\n // all space-separated so they read as one model-info run (e.g.\n // `claude-opus-4-8 medium fast`). When the model has no reasoning knob, the\n // options ride the chord on their own.\n const chordLabel = [effortLabel, modelOptionsLabel].filter(Boolean).join(' ') || null\n const modelHint: Hint | null = modelLabel\n ? {\n key: keybindings.openModelPicker,\n label: modelLabel,\n labelColor: modelColor,\n ...(chordLabel\n ? { extra: { key: shortChord(keybindings.openEffortPicker), keySpec: keybindings.openEffortPicker, ...(effortKeyColor ? { keyColor: effortKeyColor } : {}), label: chordLabel, labelColor: effortColor } }\n : {}),\n }\n : null\n // Skills chip — purely informational, no shortcut wired. We re-use\n // the Hint shape's `key` slot to render the glyph (`✦`) so the row\n // layout (key + label, padded breakpoints) stays consistent with\n // every other entry. The chip is omitted when no skills are active\n // so the row stays uncluttered for the common case.\n const skillsChip: Hint | null = activeSkillCount > 0\n ? {\n key: '✦',\n keyColor: skillsChipColor,\n label: activeSkillCount === 1 ? '1 skill' : `${activeSkillCount} skills`,\n labelColor: skillsChipColor,\n }\n : null\n // Background-task chip — fires when the user is idle (not `busy`)\n // but a task is still running. Gives them a discoverable way to\n // reach `ctrl+k` for kill. The `busy` branch above already covers\n // the dispatching case; this one is for \"agent finished its turn,\n // task still chugging\".\n const cancelTaskChip: Hint | null = inFlightToolCount > 0\n ? {\n key: keybindings.cancelToolCall,\n label: inFlightToolCount === 1 ? 'cancel task' : `cancel task (${inFlightToolCount})`,\n }\n : null\n // Minimal mode short-circuits the full composition. Same agent / model\n // hints as the full row (so a multi-agent setup still cycles, and the\n // model + effort chord stays visible), plus `settings` as the\n // discoverability anchor for everything we just dropped (keybindings\n // included — they have a tab in there).\n if (uiMode === 'minimal') {\n return [\n ...(hasMultipleAgents ? [{ key: keybindings.cycleAgent, label: agentLabel, labelColor: agentColor }] : []),\n ...(modelHint ? [modelHint] : []),\n { key: keybindings.openSettings, label: 'settings' },\n ]\n }\n return [\n ...(hasMultipleAgents ? [{ key: keybindings.cycleAgent, label: agentLabel, labelColor: agentColor }] : []),\n ...(modelHint ? [modelHint] : []),\n ...(skillsChip ? [skillsChip] : []),\n ...(todosChip ? [todosChip] : []),\n ...(cancelTaskChip ? [cancelTaskChip] : []),\n ...(currentSession ? [{ key: keybindings.openSessionDetails, label: 'session' }] : []),\n { key: keybindings.openSettings, label: 'settings' },\n { key: 'esc', label: 'sessions' },\n // Update chip lives at the right-most position so `clipHintsToWidth`\n // drops it first on narrow terminals — discoverability is nice but\n // the action shortcuts must stay.\n ...(cacheBreakHint ? [cacheBreakHint] : []),\n ...(updateHint ? [updateHint] : []),\n ]\n}\n\n/**\n * Shorten a binding spec for display as a \"chord continuation\" — the\n * `/l` after `ctrl+m model` in the chat footer. We only strip the\n * `ctrl+` prefix so user-customized chords (`alt+n`, `meta+shift+n`)\n * still render in full. Falls back to the verbatim spec when no\n * `ctrl+` prefix is found, which keeps the visual contract honest:\n * the rendered key always matches the bound trigger.\n *\n * @param spec - Binding spec, such as `ctrl+n`.\n */\nexport function shortChord(spec: string): string {\n return spec.startsWith('ctrl+') ? `/${spec.slice('ctrl+'.length)}` : spec\n}\n","/**\n * GitHub API limits as usage snapshots, for the Usage panel next to the\n * model providers' subscription windows.\n *\n * One snapshot per GitHub host zidane talks to (github.com first, then any\n * Enterprise host the shared state has seen), for the token each host uses\n * now. Windows are GitHub's quotas: `core` (REST), `graphql`, `search`.\n *\n * Two sources:\n * - the shared state every zidane process writes from the `x-ratelimit-*`\n * headers of its answers, so the section shows something with probes off;\n * - with `probe`, `GET /rate_limit` (free: it doesn't spend any quota), plus\n * `GET /user` once per token for the login (one REST request, never\n * below the quota's reserve).\n *\n * The balance is GitHub's own count per token, so it includes what the `gh`\n * CLI and the agent's `gh` commands spent with the same token.\n */\nimport type { GithubClient } from '../github/client'\nimport type { ProviderUsageSnapshot, ProviderUsageWindow } from './provider-usage-format'\nimport { clockSkewMs, githubClient, normalizeGithubHost } from '../github/client'\nimport { envOptedOut } from './env-opt-out'\n\nconst RESOURCES: readonly { id: string, label: string }[] = [\n { id: 'core', label: 'REST' },\n { id: 'graphql', label: 'GraphQL' },\n { id: 'search', label: 'Search' },\n]\n\n/** Options for {@link fetchGithubUsage}. */\nexport interface FetchGithubUsageOptions {\n /** Ask GitHub (`/rate_limit`); otherwise report what zidane observed. Ignored when the env opts out. */\n probe?: boolean\n /** GitHub client to read state and identity from. Defaults to the shared `githubClient()`. */\n client?: GithubClient\n /** Clock in epoch milliseconds. Defaults to `Date.now`. */\n now?: () => number\n /** How long a probe may take before the observed state is returned instead. Default 4 s. */\n probeDeadlineMs?: number\n}\n\n/**\n * How long a probe may hold the panel. It shares the client's request queue\n * with PR reads, whose own timeouts only start once they get a slot, so a busy\n * queue could otherwise stall the panel for the sum of everyone's timeouts.\n * Whatever lands after the deadline is still recorded for the next look.\n */\nconst PROBE_DEADLINE_MS = 4_000\n\nasync function probeHost(client: GithubClient, host: string, now: number): Promise<boolean> {\n const ident = await client.identity(host)\n const answer = await client.request({ host, path: 'rate_limit', interactive: true, conditional: false, timeoutMs: PROBE_DEADLINE_MS })\n if (answer.kind !== 'ok')\n return false\n try {\n const body = JSON.parse(answer.text) as { resources?: Record<string, { limit?: number, remaining?: number, used?: number, reset?: number }> }\n const skew = clockSkewMs(answer.headers, now)\n for (const { id } of RESOURCES) {\n const r = body.resources?.[id]\n if (r && typeof r.limit === 'number' && r.limit > 0 && typeof r.remaining === 'number' && typeof r.reset === 'number')\n client.observeQuota(host, ident.fingerprint, id, { limit: r.limit, remaining: r.remaining, resetAt: r.reset * 1000 - skew, ...(typeof r.used === 'number' ? { used: r.used } : {}) })\n }\n }\n catch {\n return false\n }\n client.observeAccount(host, ident.fingerprint, { source: ident.source })\n if (!ident.anonymous && !ident.login) {\n // Background: refused below the reserve, so the panel never spends a user's last requests.\n const user = await client.request({ host, path: 'user', timeoutMs: PROBE_DEADLINE_MS })\n if (user.kind === 'ok') {\n try {\n const login = (JSON.parse(user.text) as { login?: unknown }).login\n if (typeof login === 'string' && /^[\\w.-]+$/.test(login))\n client.observeAccount(host, ident.fingerprint, { login })\n }\n catch { /* no login, still a usable snapshot */ }\n }\n }\n return true\n}\n\n/**\n * GitHub's limits per host, in the shape the Usage panel renders. Never rejects.\n *\n * @param opts - Probe toggle, client, clock, and probe deadline.\n */\nexport async function fetchGithubUsage(opts: FetchGithubUsageOptions = {}): Promise<ProviderUsageSnapshot[]> {\n const client = opts.client ?? githubClient()\n const now = opts.now ?? Date.now\n const before = client.snapshot()\n const hosts = [...new Set(['github.com', ...before.quotas.map(q => q.host), ...before.accounts.map(a => a.host)].map(normalizeGithubHost))]\n const probe = opts.probe === true && !envOptedOut(['ZIDANE_NO_USAGE_CHECK', 'CI'])\n const idents = new Map(await Promise.all(hosts.map(async host => [host, await client.identity(host).catch(() => null)] as const)))\n /**\n * Without a token, GitHub only shows up once zidane has used it (a PR chip,\n * an artifact): someone who never touches GitHub features gets no row and no\n * probe. With a token (`gh auth login`, `GH_TOKEN`), it always shows.\n */\n const used = (host: string) => {\n const ident = idents.get(host)\n if (!ident)\n return false\n if (!ident.anonymous)\n return true\n return before.quotas.some(q => q.host === host && q.fingerprint === ident.fingerprint)\n || before.pauses.some(p => p.host === host && p.fingerprint === ident.fingerprint)\n }\n const shown = hosts.filter(used)\n const probed = new Map<string, boolean>()\n if (probe) {\n await Promise.all(shown.map(async (host) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const deadline = new Promise<boolean>((resolve) => {\n timer = setTimeout(resolve, opts.probeDeadlineMs ?? PROBE_DEADLINE_MS, false)\n })\n const ok = await Promise.race([probeHost(client, host, now()).catch(() => false), deadline])\n clearTimeout(timer)\n probed.set(host, ok)\n }))\n }\n\n const state = client.snapshot()\n const t = now()\n const out: ProviderUsageSnapshot[] = []\n for (const host of shown) {\n // Again: the probe may have just learned the login.\n const ident = await client.identity(host).catch(() => null)\n if (!ident)\n continue\n const quotas = state.quotas.filter(q => q.host === host && q.fingerprint === ident.fingerprint && q.resetAt > t)\n const pauses = state.pauses.filter(p => p.host === host && p.fingerprint === ident.fingerprint && p.retryAt > t)\n // Enterprise hosts nobody has used with this token say nothing worth a row.\n if (host !== 'github.com' && quotas.length === 0 && pauses.length === 0)\n continue\n const windows: ProviderUsageWindow[] = []\n for (const { id, label } of RESOURCES) {\n const q = quotas.find(x => x.resource === id)\n const paused = pauses.find(p => p.resource === id)\n if (!q && !paused)\n continue\n const usedPercent = paused ? 100 : q && q.limit > 0 ? Math.round(((q.limit - q.remaining) / q.limit) * 1000) / 10 : 0\n windows.push({ id, label, usedPercent, resetsAt: Math.max(q?.resetAt ?? 0, paused?.retryAt ?? 0) || null })\n }\n const hostPause = pauses.find(p => p.resource === '*')\n if (hostPause)\n windows.push({ id: 'paused', label: 'paused', usedPercent: 100, resetsAt: hostPause.retryAt })\n const plan = ident.login\n ? `@${ident.login}`\n : ident.anonymous ? 'anonymous' : 'signed in'\n out.push({\n providerKey: 'github',\n label: host === 'github.com' ? 'GitHub' : `GitHub (${host})`,\n credentialKey: `github:${host}:${ident.fingerprint}`,\n account: null,\n activeCredential: true,\n status: windows.length > 0 ? 'ok' : 'unavailable',\n plan: ident.anonymous || ident.source === 'custom' || ident.source === 'explicit' ? plan : `${plan} via ${ident.source}`,\n windows,\n credits: null,\n fetchedAt: Math.max(0, ...quotas.map(q => q.observedAt)) || t,\n error: windows.length > 0 ? null : probe && probed.get(host) === false ? 'GitHub did not answer' : 'No GitHub requests yet. Refresh to check.',\n ...(ident.anonymous ? { note: 'Anonymous: 60 requests/h. Run `gh auth login` for 5,000/h.' } : {}),\n })\n }\n return out\n}\n","// ---------------------------------------------------------------------------\n// Footer / title hint primitives.\n//\n// Pure data + width-estimation helpers consumed by the TUI's bottom-bar\n// footer, the prompt-box overlay, the sessions header, and any future\n// GUI shell that wants to render the same hint rows: `<key> <label>`, two\n// blank columns between hints. Plain-text math only — no renderer primitives, no colour\n// resolution, no React. Width estimates are intentionally `.length`-\n// based (assumes ASCII / single-cell glyphs); the consumer's renderer\n// is responsible for the actual painting and any wide-glyph accounting\n// it needs.\n// ---------------------------------------------------------------------------\n\n/**\n * Single hint entry — `<key> <label>`, optionally followed by an inline\n * secondary chord (`<key><extra.key> <label> <extra.label>`) for compound\n * shortcuts like `ctrl+m/l model effort`.\n *\n * Colour fields are advisory hex strings (resolved against the active\n * theme by the renderer); leaving them undefined lets the renderer pick\n * its conventional fallback (e.g. `warn` for keys, `dim` for labels; the\n * secondary chord takes its key's color).\n */\nexport interface Hint {\n /** Key or chord shown for the hint, such as `ctrl+l`. */\n key: string\n /** Short action text shown after the key. */\n label: string\n /** Optional override for the key color. Renderer default: warn. */\n keyColor?: string\n /** Optional override for the label color. Renderer default: dim. */\n labelColor?: string\n /**\n * Optional secondary chord + value rendered inline after the primary\n * pair: `<key><extra.key> <label> <extra.label>`. Lets one hint advertise\n * a compound shortcut (e.g. `ctrl+m/l model effort`) without forcing\n * callers into a parallel struct or a custom renderer.\n */\n extra?: {\n key: string\n label: string\n /**\n * Dispatchable binding spec behind `extra.key` when the displayed\n * chord is an abbreviation (`/l` for `ctrl+l` — see `shortChord`).\n * The abbreviation itself doesn't parse as a keypress, so a renderer\n * that makes the secondary chord independently clickable needs the\n * full spec. Defaults to `extra.key` when omitted.\n */\n keySpec?: string\n /** Defaults to the primary key's color on the renderer side: `ctrl+m/l` reads as one binding. */\n keyColor?: string\n /** Defaults to dim on the renderer side. */\n labelColor?: string\n }\n}\n\n/**\n * Truncate `text` to at most `max` characters, replacing the trailing\n * overflow with `…`. Edge cases:\n * - `max <= 0` → empty string (no room to render at all).\n * - `max === 1` → just the ellipsis glyph.\n * - `text.length <= max` → unchanged.\n *\n * Trailing-style truncation matches the natural read order of titles:\n * the prefix carries enough signal to identify the surface.\n *\n * @param text - Text to shorten.\n * @param max - Maximum length in characters, ellipsis included.\n */\nexport function truncateTrailing(text: string, max: number): string {\n if (max <= 0)\n return ''\n if (text.length <= max)\n return text\n if (max === 1)\n return '…'\n return `${text.slice(0, max - 1)}…`\n}\n\n// ---------------------------------------------------------------------------\n// Plain-text width estimates for responsive tiering.\n//\n// We approximate with `.length` instead of `stringWidth` because every glyph\n// involved (hint keys, provider names, model ids, token counts) is either\n// ASCII or a 1-cell symbol. The estimates only need to be precise enough to\n// pick a layout tier — being off by a column at the breakpoint is harmless.\n// ---------------------------------------------------------------------------\n\n/** Blank columns between two hints in a row. */\nexport const HINT_GAP = 2\n\n/**\n * Column layout of one hint, from its first cell:\n *\n * `ctrl+m/l` ` gpt-5` ` high fast`\n * └ keys ──┘ └label─┘ └extra label┘\n *\n * `key` then `extra.key`, a space, the label; an empty key with no extra\n * drops the space (a label-only hint). The TUI's click zones and the width\n * math below both read this, so they can't drift apart.\n */\nexport interface HintLayout {\n /** Columns `[extraKeyStart, extraKeyEnd)` hold `extra.key` (empty range without one). */\n extraKeyStart: number\n extraKeyEnd: number\n /** First column of the space before `extra.label`; equals `width` without an extra. */\n extraLabelStart: number\n /** Total columns. */\n width: number\n}\n\n/**\n * Layout of a single hint, see {@link HintLayout}. Pure / total.\n *\n * @param h - Hint to measure.\n */\nexport function hintLayout(h: Hint): HintLayout {\n const extraKeyStart = h.key.length\n const extraKeyEnd = extraKeyStart + (h.extra?.key.length ?? 0)\n const labelEnd = extraKeyEnd + (extraKeyEnd > 0 && h.label !== '' ? 1 : 0) + h.label.length\n return {\n extraKeyStart,\n extraKeyEnd,\n extraLabelStart: labelEnd,\n width: labelEnd + (h.extra ? 1 + h.extra.label.length : 0),\n }\n}\n\n/**\n * Plain-text width estimate for a list of {@link Hint}s rendered as a\n * hint row (keys, labels, {@link HINT_GAP} between hints). Exported so\n * prompt-box overlays can run the same responsive math as the bottom-bar\n * footer when deciding whether trigger hints fit. Pure / total.\n *\n * @param hints - Hints to measure.\n */\nexport function hintsLength(hints: readonly Hint[]): number {\n return hints.reduce((sum, h, i) => sum + hintLayout(h).width + (i > 0 ? HINT_GAP : 0), 0)\n}\n\n/**\n * Stable empty list so callers can compare by reference. Exported so\n * any consumer that needs to feed \"no primary hints\" into the same\n * `clipHintsToWidth` / `renderHintSpans` pipeline (e.g. the TUI's\n * minimal UI mode, where the prompt overlay shows only `@` / `/`\n * triggers) reuses the same frozen array instead of allocating a new\n * one each render.\n */\nexport const EMPTY_HINTS: readonly Hint[] = Object.freeze([])\n\n/**\n * Return the longest prefix of `hints` whose rendered width fits within\n * `budget`. Used to degrade hint rows gracefully at narrow terminal\n * widths instead of letting an absolutely-positioned hint row wrap\n * mid-segment (which paints the overflow over surrounding borders and\n * looks like garbled glyphs in a TUI).\n *\n * Prefix-only (no reordering, no last-hint priority) so the survivors\n * keep their authored order — the user's muscle memory for \"leftmost\n * hint = primary action\" stays intact as the terminal shrinks.\n *\n * @param hints - Hints in display order.\n * @param budget - Available width in characters.\n */\nexport function clipHintsToWidth(hints: readonly Hint[], budget: number): readonly Hint[] {\n if (budget <= 0 || hints.length === 0)\n return EMPTY_HINTS\n const out: Hint[] = []\n let used = 0\n for (let i = 0; i < hints.length; i++) {\n const h = hints[i]!\n const cost = (i > 0 ? HINT_GAP : 0) + hintLayout(h).width\n if (used + cost > budget)\n break\n out.push(h)\n used += cost\n }\n if (out.length === 0)\n return EMPTY_HINTS\n return out.length === hints.length ? hints : out\n}\n","/**\n * The provider list of the Connect step. It comes from the provider registry\n * of the host and from what `detectAuth` found. It says what each provider is,\n * how it signs in, and whether a credential is ready. Provider extensions that\n * are not installed yet come last, so Connect can install one and sign in.\n */\nimport type { ProviderAuth } from '../../auth'\nimport type { OnboardingCredentialSource, OnboardingProvider } from '../../onboarding/types'\nimport type { ProviderDescriptor } from '../../providers'\nimport { supportsOAuth } from '../../oauth'\nimport { providerBrand } from '../../onboarding/brands'\nimport { NATIVE_EXTENSIONS } from '../../onboarding/native-extensions'\n\n/** Providers shown first in Connect. The rest sit under \"More\". */\nexport const FEATURED_PROVIDERS: readonly string[] = ['anthropic', 'openai', 'google', 'openrouter', 'xai', 'local']\n\nfunction sourceOf(descriptor: ProviderDescriptor, auth: ProviderAuth | undefined): OnboardingCredentialSource | undefined {\n const method = auth?.available ? auth.methods[0] : undefined\n if (!method)\n return undefined\n // A provider without an API key (a local server) is ready once its fields are stored.\n if (method.source === 'apikey' && !descriptor.envKey && descriptor.customFields?.length)\n return 'configured'\n return method.source\n}\n\n/**\n * The providers Connect lists, featured ones first, then the provider\n * extensions that are not installed.\n *\n * @param registry - The host's provider descriptors.\n * @param auth - What `detectAuth` found for them.\n */\nexport function onboardingProviders(registry: Readonly<Record<string, ProviderDescriptor>>, auth: readonly ProviderAuth[]): OnboardingProvider[] {\n const listed: OnboardingProvider[] = Object.values(registry).map((descriptor) => {\n const found = auth.find(entry => entry.key === descriptor.key)\n const source = sourceOf(descriptor, found)\n const brand = providerBrand(descriptor.key)\n return {\n key: descriptor.key,\n label: descriptor.label,\n ...(descriptor.description ? { description: descriptor.description } : {}),\n ...(descriptor.signInLabel ? { signInLabel: descriptor.signInLabel } : {}),\n featured: FEATURED_PROVIDERS.includes(descriptor.key),\n oauth: supportsOAuth(descriptor),\n ...(descriptor.oauthHint ? { oauthHint: descriptor.oauthHint } : {}),\n apiKey: Boolean(descriptor.envKey),\n ...(descriptor.apiKeyPlaceholder ? { apiKeyPlaceholder: descriptor.apiKeyPlaceholder } : {}),\n customFields: (descriptor.customFields ?? []).map(({ key, label, placeholder, hint, required }) => ({\n key,\n label,\n ...(placeholder ? { placeholder } : {}),\n ...(hint ? { hint } : {}),\n ...(required ? { required } : {}),\n })),\n ready: Boolean(found?.available),\n ...(source ? { source } : {}),\n ...(source === 'env' && descriptor.envKey ? { envKey: descriptor.envKey } : {}),\n ...(brand ? { brand } : {}),\n }\n })\n const featured = FEATURED_PROVIDERS.flatMap(key => listed.filter(provider => provider.key === key))\n const rest = listed.filter(provider => !provider.featured)\n const viaExtension: OnboardingProvider[] = NATIVE_EXTENSIONS\n .filter(entry => entry.provider && !registry[entry.provider])\n .map(entry => ({ key: entry.provider!, label: entry.label, description: entry.description, featured: false, oauth: true, apiKey: false, customFields: [], ready: false, viaExtension: entry.name }))\n return [...featured, ...rest, ...viaExtension]\n}\n","/**\n * What the setup wizard finds on this computer before its first step. It finds\n * the providers and their credentials, the setups of other coding agents, the\n * GitHub login and local model servers. It also finds the signs of an install\n * that existed before the wizard. It changes nothing.\n */\nimport type { GithubIdentity } from '../../../github/client'\nimport type { OnboardingAgentId, OnboardingDetection, OnboardingGithubIdentity, OnboardingInstallSignals, OnboardingLocalServer } from '../../onboarding/types'\nimport type { ProviderDescriptor } from '../../providers'\nimport type { TuiState } from '../../store'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { githubClient, githubTokenSource } from '../../../github/client'\nimport { detectAuth } from '../../auth'\nimport { hasClaudeCodeSetup } from '../../claude-import'\nimport { readCredentials } from '../../credentials'\nimport { foreignMcpConfigFiles, foreignSkillScanPaths } from '../../foreign-configs'\nimport { findGitRoot } from '../../project-root'\nimport { settingsFromStored } from '../../settings-defaults'\nimport { onboardingProviders } from './providers'\n\nconst AGENT_ORDER: readonly OnboardingAgentId[] = ['claude-code', 'codex', 'pi']\n\nfunction agentOfPath(path: string): OnboardingAgentId | undefined {\n if (/[\\\\/]\\.codex[\\\\/]/.test(path))\n return 'codex'\n if (/[\\\\/]\\.pi[\\\\/]/.test(path))\n return 'pi'\n if (/[\\\\/]\\.claude(?:\\.json$|[\\\\/])/.test(path))\n return 'claude-code'\n return undefined\n}\n\n/** Where to look for other agents' configuration. */\nexport interface DetectOtherAgentsOptions {\n /** Project directory. Default: `process.cwd()`. */\n cwd?: string\n /** Home directory. Default: `os.homedir()`. */\n home?: string\n}\n\n/**\n * The coding agents whose setup exists on this computer, for the user or for\n * the current project. They are Claude Code (`~/.claude`, `.claude/`), Codex\n * (`.codex/`) and Pi (`.pi/`).\n *\n * @param opts - Project and home directories.\n */\nexport function detectOtherAgents(opts: DetectOtherAgentsOptions = {}): OnboardingAgentId[] {\n const found = new Set<OnboardingAgentId>()\n if (hasClaudeCodeSetup(opts))\n found.add('claude-code')\n const cwd = opts.cwd ?? process.cwd()\n if (existsSync(resolve(findGitRoot(cwd) ?? cwd, '.claude')))\n found.add('claude-code')\n for (const candidate of [...foreignMcpConfigFiles(opts), ...foreignSkillScanPaths(opts)]) {\n const agent = agentOfPath(candidate.path)\n if (agent && existsSync(candidate.path))\n found.add(agent)\n }\n return AGENT_ORDER.filter(agent => found.has(agent))\n}\n\nconst LOCAL_SERVERS: readonly { kind: OnboardingLocalServer['kind'], probe: string, url: string }[] = [\n { kind: 'ollama', probe: 'http://127.0.0.1:11434/api/tags', url: 'http://localhost:11434/v1' },\n { kind: 'lm-studio', probe: 'http://127.0.0.1:1234/v1/models', url: 'http://localhost:1234/v1' },\n { kind: 'vllm', probe: 'http://127.0.0.1:8000/v1/models', url: 'http://localhost:8000/v1' },\n]\n\n/** How to probe for local model servers. */\nexport interface ProbeLocalModelServersOptions {\n /** Fetch implementation. Default: the global `fetch`. */\n fetch?: typeof fetch\n /** How long each server gets to answer, in milliseconds. Default 300. */\n timeoutMs?: number\n}\n\n/**\n * The local model servers answering on their default ports (Ollama,\n * LM Studio, vLLM), with the base URL the Local LLM provider takes.\n *\n * @param opts - Fetch implementation and timeout.\n */\nexport async function probeLocalModelServers(opts: ProbeLocalModelServersOptions = {}): Promise<OnboardingLocalServer[]> {\n const fetchImpl = opts.fetch ?? fetch\n const timeoutMs = opts.timeoutMs ?? 300\n const answers = await Promise.all(LOCAL_SERVERS.map(async (server) => {\n try {\n const res = await fetchImpl(server.probe, { signal: AbortSignal.timeout(timeoutMs) })\n // Only the status counts: an unread body keeps the connection until it is collected.\n await res.body?.cancel().catch(() => undefined)\n return res.ok ? { kind: server.kind, url: server.url } : null\n }\n catch {\n return null\n }\n }))\n return answers.filter(answer => answer !== null)\n}\n\n/** What {@link onboardingInstallSignals} reads. */\nexport interface OnboardingInstallSignalsInput {\n /** Directory of `credentials.json`. */\n dataDir: string\n /** The state file's content. */\n state: TuiState\n /** The session store holds at least one session. */\n sessions: boolean\n /** This app has never run on this machine. */\n surfaceFirstRun: boolean\n}\n\n/**\n * The signs of an install that predates the wizard.\n *\n * @param input - The credentials directory, the state file and what the app knows.\n */\nexport function onboardingInstallSignals(input: OnboardingInstallSignalsInput): OnboardingInstallSignals {\n return {\n storedSettings: Object.keys(settingsFromStored(input.state.settings ?? {})).length > 0,\n credentials: Object.keys(readCredentials(input.dataDir)).length > 0,\n sessions: input.sessions,\n lastProvider: Boolean(input.state.lastProvider),\n surfaceFirstRun: input.surfaceFirstRun,\n }\n}\n\n/** What {@link detectOnboardingEnvironment} reads. */\nexport interface DetectOnboardingOptions extends OnboardingInstallSignalsInput, DetectOtherAgentsOptions {\n /** The host's provider descriptors. */\n registry: Readonly<Record<string, ProviderDescriptor>>\n /** Environment to look for provider keys in. Default: `process.env`. */\n env?: Record<string, string | undefined>\n /** Fetch implementation for the local server probe. Default: the global `fetch`. */\n fetch?: typeof fetch\n /** The GitHub identity lookup. Default: the process's GitHub client. */\n githubIdentity?: () => Promise<GithubIdentity>\n}\n\nasync function githubFor(lookup: () => Promise<GithubIdentity>): Promise<OnboardingGithubIdentity | undefined> {\n try {\n const identity = await lookup()\n if (identity.anonymous)\n return undefined\n return { source: identity.source, ...(identity.login ? { login: identity.login } : {}) }\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Everything the wizard shows it found: providers and their credentials,\n * other agents, the GitHub login, local model servers and the install\n * signals. The GitHub lookup and the server probe run in parallel.\n *\n * @param opts - The host's registry and paths, the state file and what the app knows.\n * @example\n * ```ts\n * import { BUILTIN_PROVIDERS, detectOnboardingEnvironment } from 'zidane/chat'\n *\n * const found = await detectOnboardingEnvironment({\n * dataDir: '/home/me/.zidane',\n * registry: BUILTIN_PROVIDERS,\n * state: {},\n * sessions: false,\n * surfaceFirstRun: true,\n * })\n * console.log(found.providers.filter(p => p.ready).map(p => p.key), found.agents)\n * ```\n */\nexport async function detectOnboardingEnvironment(opts: DetectOnboardingOptions): Promise<OnboardingDetection> {\n const auth = detectAuth(opts.dataDir, opts.registry, opts.env ?? process.env)\n const [github, localServers] = await Promise.all([\n githubFor(opts.githubIdentity ?? (() => githubClient().identity())),\n probeLocalModelServers(opts.fetch ? { fetch: opts.fetch } : {}),\n ])\n return {\n providers: onboardingProviders(opts.registry, auth),\n agents: detectOtherAgents(opts),\n ...(github ? { github } : {}),\n localServers,\n signals: onboardingInstallSignals(opts),\n }\n}\n\n/**\n * A GitHub token zidane already knows (`GH_TOKEN`, `GITHUB_TOKEN` or the\n * `gh` CLI), for the GitHub connector's \"Use my gh login\". `null` when there\n * is none.\n */\nexport async function connectorGithubToken(): Promise<{ token: string, source: string } | null> {\n return githubTokenSource.resolveWithSource()\n}\n","/**\n * The React side of interactions: a FIFO queue of pending requests with\n * stable actions, shared by the TUI and GUI. The protocol (tools, requests,\n * serialization, pending-state derivation) lives in `interactions-protocol.ts`\n * and is re-exported here.\n */\nimport type { ReactNode } from 'react'\nimport type { InteractionResponse, InteractionsActions, PendingInteractionEntry } from './interactions-protocol'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\nexport * from './interactions-protocol'\n\n// ---------------------------------------------------------------------------\n// React context — FIFO queue of pending requests + resolver callbacks.\n//\n// Two contexts on purpose (mirrors `safe-mode-context.tsx`):\n// - `InteractionsQueueContext` carries the live queue. Consumers re-render\n// on every push/pop.\n// - `InteractionsActionsContext` carries `{ enqueue, resolveHead, … }` and\n// never changes identity, so callbacks that depend on these don't\n// re-bind on every queue churn.\n// ---------------------------------------------------------------------------\n\nconst InteractionsQueueContext = createContext<readonly PendingInteractionEntry[]>([])\nconst InteractionsActionsContext = createContext<InteractionsActions | null>(null)\n\n/**\n * React provider that holds the FIFO queue of pending interactions. Pending entries are cancelled when it unmounts.\n *\n * @param props - Component props.\n * @param props.children - Subtree that can read the queue and actions.\n */\nexport function InteractionsProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly PendingInteractionEntry[]>([])\n\n // Mirror the live queue so the unmount cleanup can settle whatever is\n // pending without re-subscribing the effect on every queue change.\n const queueRef = useRef<readonly PendingInteractionEntry[]>([])\n queueRef.current = queue\n\n // Provider unmount is a teardown path too: React drops the queue state\n // outright, so any in-flight `requestInteraction` Promise would hang\n // forever (its tool body never settles → the run's `running` gate never\n // clears). Cancel every pending entry on unmount so the resolver can't be\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const entry of queueRef.current)\n entry.cancel('Interactions provider unmounted')\n }, [])\n\n const enqueue = useCallback((entry: PendingInteractionEntry) => {\n setQueue(prev => [...prev, entry])\n }, [])\n\n const resolveHead = useCallback((response: InteractionResponse) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(response)\n return rest\n })\n }, [])\n\n const cancelHead = useCallback((reason?: string) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.cancel(reason)\n return rest\n })\n }, [])\n\n const cancelAll = useCallback((reason?: string) => {\n setQueue((prev) => {\n for (const entry of prev) entry.cancel(reason)\n return []\n })\n }, [])\n\n const dropById = useCallback((requestId: string) => {\n // Remove WITHOUT settling: the daemon already resolved this in the\n // window that answered it. Neither resolve nor cancel — both would\n // reach back toward the (no-op) daemon callback needlessly.\n setQueue(prev => prev.filter(entry => entry.request.id !== requestId))\n }, [])\n\n // Stable actions identity — every member is `useCallback`'d with `[]`\n // deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useInteractionsActions()`\n // never re-render purely because of queue state.\n const actionsRef = useRef<InteractionsActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { enqueue, resolveHead, cancelHead, cancelAll, dropById }\n\n return (\n <InteractionsActionsContext.Provider value={actionsRef.current}>\n <InteractionsQueueContext.Provider value={queue}>\n {children}\n </InteractionsQueueContext.Provider>\n </InteractionsActionsContext.Provider>\n )\n}\n\n/** Read the live queue (re-renders on every push/pop). */\nexport function useInteractionsQueue(): readonly PendingInteractionEntry[] {\n return useContext(InteractionsQueueContext)\n}\n\n/** Read the stable actions object. */\nexport function useInteractionsActions(): InteractionsActions {\n const ctx = useContext(InteractionsActionsContext)\n if (!ctx)\n throw new Error('useInteractionsActions must be used inside <InteractionsProvider>')\n return ctx\n}\n","/**\n * React provider for the MCP per-server OAuth state map.\n *\n * `ModalRoot.open(node)` captures the modal contents as a snapshot — naive\n * prop-passing would freeze the auth badge at whatever status existed when\n * the user opened the picker, and a `mcp:auth:url` arriving mid-login\n * would never reach the rendered modal. Routing the state through a\n * context provider mounted ABOVE `ModalRoot` fixes that: a state update\n * re-renders the provider, which re-renders `ModalRoot`, which re-renders\n * the (referentially-stable) modal child node — and consumers calling\n * `useMcpAuthState()` see the fresh value.\n *\n * Two contexts on purpose:\n *\n * - `StateContext` carries the live map. Consumers re-render on every\n * auth event.\n * - `DispatchContext` carries the reducer entry point. Identity is stable\n * across renders, so AppShell's hook handlers don't re-bind when the\n * state changes — they grab `dispatch` once and fire events for the\n * lifetime of the agent.\n *\n * Actions (login / logout / cancel) deliberately do NOT live in context —\n * they depend on AppShell-scoped resources (credential store, catalog ref,\n * agent ref), close over those via refs, and stay stable across renders.\n * Passing them as modal props at `modal.open` time is fine because the\n * captured reference reads from refs internally; the snapshot doesn't go\n * stale even though it was taken at open time.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpAuthEvent, McpAuthStateMap } from './mcp-auth-state'\nimport { createContext, useCallback, useContext, useState } from 'react'\nimport { reduceMcpAuth } from './mcp-auth-state'\n\nconst StateContext = createContext<McpAuthStateMap>({})\nconst DispatchContext = createContext<((event: McpAuthEvent) => void) | null>(null)\n\n/**\n * Mount once near the top of the React tree (above `ModalRoot`). Children\n * use `useMcpAuthState()` to read and `useMcpAuthDispatch()` to fire events.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function McpAuthProvider({ children }: { children: ReactNode }) {\n const [state, setState] = useState<McpAuthStateMap>({})\n const dispatch = useCallback((event: McpAuthEvent) => {\n setState(prev => reduceMcpAuth(prev, event))\n }, [])\n return (\n <StateContext.Provider value={state}>\n <DispatchContext.Provider value={dispatch}>\n {children}\n </DispatchContext.Provider>\n </StateContext.Provider>\n )\n}\n\n/** Returns the current auth state map, keyed by server name. */\nexport function useMcpAuthState(): McpAuthStateMap {\n return useContext(StateContext)\n}\n\n/** Returns the function that applies an {@link McpAuthEvent} to the auth state. Throws when used outside {@link McpAuthProvider}. */\nexport function useMcpAuthDispatch(): (event: McpAuthEvent) => void {\n const dispatch = useContext(DispatchContext)\n if (!dispatch)\n throw new Error('useMcpAuthDispatch must be used inside <McpAuthProvider>')\n return dispatch\n}\n","/**\n * Renderer-agnostic data model for the MCP settings list when it\n * supports per-server expand / collapse with nested tool rows.\n *\n * Lives in `chat/` (not `tui/`) because both the standalone\n * `McpsSettingsModal` and the inline `<McpsList>` inside the unified\n * `SettingsModal` consume the exact same flattening + cursor\n * arithmetic — re-deriving it in two places would let the two\n * surfaces drift on edge cases (empty tool catalogs, freshly-removed\n * servers, stale expansion state).\n *\n * The model is a flat `VisibleMcpRow[]` so renderers can iterate once,\n * and the cursor is a single integer that walks both kinds (server\n * headers AND tool checkboxes) without separate \"which server am I\n * on / which tool inside it\" state.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { DiscoveredMcp } from './mcps-discovery'\n\n/**\n * One row in the flattened MCP settings list. Three kinds:\n *\n * - `'server'` — server header with the chevron + checkbox +\n * transport detail. Always present.\n * - `'tool'` — checkbox + description for a tool advertised\n * by the parent server. Present only when the\n * server is expanded AND its tool cache is\n * populated.\n * - `'tool-empty'` — placeholder shown when an expanded server\n * has no cached tool list yet (first launch\n * before any session has bootstrapped the\n * server). Lets the renderer surface a\n * one-line \"send a prompt to populate\" hint\n * WITHOUT changing the server's expand\n * affordance.\n */\nexport type VisibleMcpRow\n = | { kind: 'server', entry: DiscoveredMcp }\n | { kind: 'tool', serverName: string, tool: McpToolSchema }\n | { kind: 'tool-empty', serverName: string }\n\n/**\n * Flatten a server catalog plus the per-server expanded set into the\n * visible-row list a renderer cycles through.\n *\n * - `catalog` — filtered / ordered server list (post-search,\n * post-allow-list).\n * - `expanded` — names of servers whose tool list is open.\n * Servers not in this set render as a single\n * server-header row.\n * - `toolsByServer` — per-server full tool catalog (from\n * `chat/mcp-tools-cache.ts`). Missing key →\n * `'tool-empty'` placeholder under the\n * expanded server.\n *\n * @param catalog - Discovered servers in display order.\n * @param expanded - Names of servers whose tool rows are shown.\n * @param toolsByServer - Cached tool lists keyed by server name. A missing or empty list yields a `tool-empty` row.\n */\nexport function buildVisibleMcpRows(\n catalog: readonly DiscoveredMcp[],\n expanded: ReadonlySet<string>,\n toolsByServer: Record<string, readonly McpToolSchema[]>,\n): VisibleMcpRow[] {\n const out: VisibleMcpRow[] = []\n for (const entry of catalog) {\n out.push({ kind: 'server', entry })\n if (!expanded.has(entry.config.name))\n continue\n const tools = toolsByServer[entry.config.name] ?? []\n if (tools.length === 0) {\n out.push({ kind: 'tool-empty', serverName: entry.config.name })\n continue\n }\n for (const tool of tools)\n out.push({ kind: 'tool', serverName: entry.config.name, tool })\n }\n return out\n}\n\n/**\n * Server name a row \"belongs to\" for catalog / toggle / auth lookups.\n * For a server row that's its own name; for tool / tool-empty rows\n * it's the parent server's name.\n *\n * @param row - Any visible row.\n */\nexport function parentServerName(row: VisibleMcpRow): string {\n return row.kind === 'server' ? row.entry.config.name : row.serverName\n}\n\n/**\n * Index in `rows` of the server header for the given parent name. `-1`\n * when not present. Used by collapse handlers to snap the cursor up\n * to the parent BEFORE shrinking the row list — otherwise the cursor\n * would land on whichever sibling row now occupies the same flat\n * index.\n *\n * @param rows - The visible rows.\n * @param parentName - Server name to look for.\n */\nexport function indexOfServerRow(rows: readonly VisibleMcpRow[], parentName: string): number {\n return rows.findIndex(\n r => r.kind === 'server' && r.entry.config.name === parentName,\n )\n}\n","/**\n * Renderer-agnostic per-tool toggle for MCP servers. Sibling of\n * {@link useEnabledToggleSet} — same shape, opposite polarity:\n *\n * - `useEnabledToggleSet` is an **allowlist** (`enabledMcps` / etc.),\n * `undefined` = everything enabled, toggle moves to an explicit list.\n * - `useMcpToolToggleSet` is a **deny-list** (`disabledMcpTools`),\n * missing entry = everything enabled, toggle moves to an explicit\n * list of NAMES TO HIDE.\n *\n * The polarity flip is intentional and documented on the setting itself\n * (`Settings.disabledMcpTools`): when an MCP server ships a new tool in\n * a later release, a user who has already opted out of `big_tool` still\n * gets the new one — opposite preference from server-level toggles\n * where a new server should NOT silently appear in the catalog.\n *\n * Two hook flavors:\n *\n * - {@link useMcpToolToggleSet} — one server. Convenience for hosts\n * that render a single server's tool list at a time.\n * - {@link useMcpToolToggleMap} — every server's toggle handler in\n * one call. Required by hosts that dispatch toggles from a shared\n * keyboard handler at the top of the tree (`mcps-settings.tsx`)\n * where calling the single-server hook in a loop would violate\n * rules-of-hooks the moment a server is added or removed.\n *\n * Both flavors route through the same {@link buildToolToggle} factory\n * so semantics stay byte-identical regardless of entry point.\n *\n * Garbage-collects on every write: stale names (tools that disappeared\n * from the server's catalog between launches) are dropped so the\n * persisted deny-list doesn't grow unbounded. Same invariant as the\n * sibling hook for `enabledX` allowlists.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { Settings } from './types'\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Per-tool enable state and toggle handler for one MCP server. */\nexport interface McpToolToggleSet {\n /** Live set of ENABLED tool names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Flip one tool on/off; persists immediately via `setSetting`. */\n toggle: (toolName: string) => void\n}\n\n/**\n * Bind a single MCP server's per-tool toggle to\n * `Settings.disabledMcpTools[serverName]`.\n *\n * `catalog` is the FULL list of tools the server advertises (i.e. the\n * cached `listTools()` payload from `chat/mcp-tools-cache.ts`). The\n * hook needs it both to render `enabledSet` (every catalog tool not in\n * the persisted deny-list) and to garbage-collect stale deny-list\n * entries on every write.\n *\n * @param opts - The server name and its full tool catalog.\n */\nexport function useMcpToolToggleSet(opts: {\n serverName: string\n catalog: readonly McpToolSchema[]\n}): McpToolToggleSet {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n\n const catalogNames = useMemo(() => opts.catalog.map(t => t.name), [opts.catalog])\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(\n () => buildToolToggle({\n serverName: opts.serverName,\n catalogNames,\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n }),\n [opts.serverName, catalogNames, persistedDenyAll, setDisabled],\n )\n}\n\n/**\n * Bind every server in `catalogByServer` to its own per-tool toggle in\n * a single hook call. Returns a `serverName → McpToolToggleSet` map.\n *\n * Use this when a host needs handlers for multiple servers at once\n * (the TUI's MCP settings panel keyboard dispatcher is the canonical\n * example) — calling {@link useMcpToolToggleSet} in a `for` / `.map`\n * over a dynamic catalog would change the hook-call order whenever a\n * server is added or removed, violating React's rules-of-hooks. This\n * hook calls `useMemo` exactly once regardless of server count.\n *\n * Servers absent from `catalogByServer` get no entry in the result.\n * The persisted deny-list still keeps their entries on disk — they\n * just stay invisible to this consumer until the catalog re-includes\n * them.\n *\n * @param opts - Tool catalogs keyed by server name.\n */\nexport function useMcpToolToggleMap(opts: {\n catalogByServer: Record<string, readonly McpToolSchema[]>\n}): Record<string, McpToolToggleSet> {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(() => {\n const out: Record<string, McpToolToggleSet> = {}\n for (const [serverName, catalog] of Object.entries(opts.catalogByServer)) {\n out[serverName] = buildToolToggle({\n serverName,\n catalogNames: catalog.map(t => t.name),\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n })\n }\n return out\n }, [opts.catalogByServer, persistedDenyAll, setDisabled])\n}\n\n/**\n * Pure factory shared by both hook flavors. Captures `persistedDenyAll`\n * + `setDisabledMcpTools` at the call site so toggle handlers stay\n * referentially stable across renders that don't touch the underlying\n * settings entry — important for `useKeyboard` to avoid re-binding on\n * every catalog change.\n *\n * Exported for direct use by non-React consumers (CLI, GUI shells)\n * that want the same write semantics without the hook plumbing.\n *\n * @param args - Server name, the server's catalog tool names, the persisted deny-list for all servers and the setter that writes it.\n */\nexport function buildToolToggle(args: {\n serverName: string\n catalogNames: readonly string[]\n persistedDenyAll: Settings['disabledMcpTools']\n setDisabledMcpTools: (next: Settings['disabledMcpTools']) => void\n}): McpToolToggleSet {\n const { serverName, catalogNames, persistedDenyAll, setDisabledMcpTools } = args\n const persistedDeny = persistedDenyAll?.[serverName]\n const catalogSet = new Set(catalogNames)\n\n const enabledSet: ReadonlySet<string> = (() => {\n if (!persistedDeny || persistedDeny.length === 0)\n return new Set(catalogNames)\n const deny = new Set(persistedDeny)\n return new Set(catalogNames.filter(name => !deny.has(name)))\n })()\n\n const toggle = (toolName: string): void => {\n // Compute the next deny-list against the persisted state (NOT\n // `enabledSet`) so we never accidentally promote \"implicit-enabled-\n // because-no-entry\" tools into an explicit list on the first\n // toggle. GC stale names at the same time so the persisted file\n // tracks reality.\n const currentDeny = new Set(persistedDeny ?? [])\n if (currentDeny.has(toolName))\n currentDeny.delete(toolName)\n else\n currentDeny.add(toolName)\n\n const nextForServer = [...currentDeny]\n .filter(n => catalogSet.has(n))\n .sort()\n\n const prevMap = persistedDenyAll ?? {}\n const nextMap: Record<string, readonly string[]> = { ...prevMap }\n if (nextForServer.length === 0) {\n // Empty entry collapses back to \"no customization\" so the\n // persisted file stays tidy when a user re-enables every tool\n // they previously disabled.\n delete nextMap[serverName]\n }\n else {\n nextMap[serverName] = nextForServer\n }\n\n // Top-level `undefined` represents \"no per-tool prefs anywhere\".\n // Restore that when the last server entry empties out so the\n // diff against defaults stays minimal.\n const next: Settings['disabledMcpTools']\n = Object.keys(nextMap).length === 0 ? undefined : nextMap\n setDisabledMcpTools(next)\n }\n\n return { enabledSet, toggle }\n}\n","/**\n * Manual OAuth redirect-URL handoff.\n *\n * Users who run zidane over SSH (or behind a proxy / firewall that blocks\n * loopback) can't have the local browser hit the in-process callback server.\n * They CAN, however, copy the URL their browser was redirected to —\n * `http://127.0.0.1:<port>/callback?code=...&state=...` — and paste it back\n * into the TUI.\n *\n * The trick: that URL IS the callback our local server is listening on. We\n * just `fetch()` it ourselves. The server runs in the same process; the\n * request hits its handler exactly as if a real browser had arrived, the\n * OAuth promise (`waitForCode` for pi-ai providers, `startOAuthCallback` for\n * MCP) resolves through the normal happy path, and the upstream flow\n * continues uninterrupted. No new code path inside the OAuth state machine.\n *\n * Defense in depth: we reject anything that isn't a loopback URL — fetching\n * an arbitrary user-pasted URL from inside the agent process would be a\n * trivial SSRF.\n */\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]'])\n\n/**\n * The `redirect_uri` the authorization server was told to come back to,\n * read out of the authorization URL itself. That's our loopback callback\n * (`http://127.0.0.1:<port>/callback`) — the UI shows it so an SSH user\n * knows which address to fish out of the browser's URL bar, and\n * {@link normalizeRedirectPaste} uses it to rebuild a full callback URL\n * from a partial paste.\n *\n * Returns `null` when the URL doesn't parse or carries no usable\n * `redirect_uri` — callers degrade to \"paste the whole URL\".\n *\n * @param authUrl - The authorization URL to read.\n */\nexport function redirectUriFromAuthUrl(authUrl: string): URL | null {\n let parsed: URL\n try {\n parsed = new URL(authUrl)\n }\n catch {\n return null\n }\n const redirect = parsed.searchParams.get('redirect_uri')\n if (!redirect)\n return null\n try {\n return new URL(redirect)\n }\n catch {\n return null\n }\n}\n\n/**\n * Widen what counts as an acceptable paste. The happy path is the full\n * redirect URL out of the browser's address bar, but users on SSH\n * routinely end up holding less than that: some authorization servers\n * render the code on the page instead of redirecting, and copying from a\n * \"can't reach this site\" error page loses everything but the query.\n *\n * Accepted forms, given the authorization URL for context:\n *\n * - `http://127.0.0.1:5123/callback?code=x&state=y` — passthrough (any\n * absolute URL is returned untouched; the loopback guard in\n * {@link fetchOAuthRedirect} still has final say, so a pasted\n * `https://evil.test` is rejected there, not silently rewritten).\n * - `/callback?code=x&state=y` — path + query onto the callback origin.\n * - `?code=x&state=y` / `code=x&state=y` — query onto the callback URL.\n * - `x` — a bare authorization code; `state` is taken from the\n * authorization URL when it carried one (the loopback server forwards\n * `state` verbatim and the SDK owns verification, so a missing one is\n * not fatal). Anything with whitespace inside is prose (an error page\n * fragment), not a code — refused with a clearer error than a server\n * 400 would give.\n *\n * Throws when the input can't be turned into a URL — no `authUrl` context\n * for a partial paste, or an unusable authorization URL.\n *\n * @param pasted - What the user pasted: a full URL, a path, a query string or a bare code.\n * @param authUrl - The authorization URL on screen. Required to complete anything shorter than a full URL.\n */\nexport function normalizeRedirectPaste(pasted: string, authUrl?: string): string {\n const trimmed = pasted.trim()\n if (!trimmed)\n throw new Error('Paste the redirect URL from your browser.')\n // Absolute URL → caller's problem to validate. Checked by scheme rather\n // than `new URL()` success so a bare code like `abc` (which URL happily\n // rejects) and a scheme-ful `mcp://…` are told apart deterministically.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed))\n return trimmed\n\n const callback = authUrl ? redirectUriFromAuthUrl(authUrl) : null\n if (!callback) {\n throw new Error(\n 'That doesn\\'t look like a URL. Paste the full address your browser tried to reach (starting with http://127.0.0.1).',\n )\n }\n\n // `/callback?code=…` — keep the pasted path, it may differ from the one\n // we registered (a server that normalized a trailing slash, say). Same\n // outcome guard as the query branch below: a bare `/callback` carries no\n // OAuth result, and fetching it would only produce an opaque server 400\n // where a targeted message tells the user exactly what to re-copy.\n if (trimmed.startsWith('/')) {\n const merged = new URL(trimmed, callback)\n if (!merged.searchParams.has('code') && !merged.searchParams.has('error'))\n throw new Error('That path has no \"code\" parameter. Paste the full address from your browser\\'s URL bar.')\n return merged.toString()\n }\n\n const target = new URL(callback.toString())\n const query = trimmed.startsWith('?') ? trimmed.slice(1) : trimmed\n // A bare authorization code is a single URL-legal token — including\n // base64 padding, so trailing `=` stays code-like (`abc==`). Any OTHER\n // placement of `=` means key/value pairs (`foo=bar`, `state=…&code=…`):\n // treat as a query string, never as a code — URL-encoding `foo=bar` into\n // `code=foo%3Dbar` would fire a doomed request where a targeted error\n // tells the user what to re-copy.\n const codeLike = /^[^=\\s]+=*$/.test(query)\n if (!codeLike && query.includes('=')) {\n const params = new URLSearchParams(query)\n if (!params.has('code') && !params.has('error'))\n throw new Error('That fragment has no \"code\" parameter. Paste the full address from your browser, or just the code.')\n for (const [key, value] of params)\n target.searchParams.set(key, value)\n return target.toString()\n }\n\n // Bare code. Whitespace-bearing pastes never reach here (`codeLike`\n // excludes them) except as pure prose without `=`, so keep the explicit\n // reject for those: sending `code=access%20denied` to the callback\n // server would 400 without explaining anything.\n if (/\\s/.test(query))\n throw new Error('That doesn\\'t look like an authorization code. Paste the full address from your browser, or just the code itself.')\n target.searchParams.set('code', query)\n const state = new URL(authUrl!).searchParams.get('state')\n if (state)\n target.searchParams.set('state', state)\n return target.toString()\n}\n\n/** Outcome of calling the loopback callback server. */\nexport interface FetchOAuthRedirectResult {\n /** Status code from the callback server. 2xx = success. */\n status: number\n /** Best-effort message extracted from the response body (HTML title or trimmed text). */\n message?: string\n}\n\n/**\n * Treat `pasted` as a callback-URL paste. Validates it's a loopback URL,\n * fires a GET, returns the status.\n *\n * Pass `authUrl` (the authorization URL currently on screen) to accept\n * partial pastes — a bare code, a query string, a path — via\n * {@link normalizeRedirectPaste}. Without it only a full absolute URL is\n * accepted.\n *\n * Throws when:\n * - `pasted` doesn't parse as a URL (and can't be completed from `authUrl`).\n * - The URL host isn't loopback (rejects SSRF).\n * - The fetch errors out (network, timeout).\n *\n * Returns success/failure for non-2xx responses; the caller decides whether\n * a 4xx is fatal (state mismatch is a 400 the user can retry from).\n *\n * @param pasted - What the user pasted: a callback URL, or a partial one when `options.authUrl` is set.\n * @param options - Abort signal, request timeout in milliseconds (`5000` by default) and the authorization URL used to complete partial pastes.\n */\nexport async function fetchOAuthRedirect(\n pasted: string,\n options: { signal?: AbortSignal, timeoutMs?: number, authUrl?: string } = {},\n): Promise<FetchOAuthRedirectResult> {\n const trimmed = normalizeRedirectPaste(pasted, options.authUrl)\n\n let url: URL\n try {\n url = new URL(trimmed)\n }\n catch {\n throw new Error('That doesn\\'t look like a URL. Paste the full address from your browser.')\n }\n\n // Strip brackets that some browsers add around IPv6 hosts so the\n // comparison hits LOOPBACK_HOSTS regardless of textual form.\n const host = url.hostname.replace(/^\\[|\\]$/g, '')\n if (!LOOPBACK_HOSTS.has(host)) {\n throw new Error(\n `Expected a loopback URL (127.0.0.1 / localhost), got \"${url.hostname}\". The browser should have redirected to a localhost address.`,\n )\n }\n\n // Short timeout: the callback server is in-process, so any meaningful\n // delay means it's gone (closed by a prior abort) or never started.\n const timeoutMs = options.timeoutMs ?? 5000\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n options.signal?.addEventListener('abort', () => ac.abort(), { once: true })\n\n let response: Response\n try {\n response = await fetch(url.toString(), { signal: ac.signal })\n }\n catch (err) {\n if ((err as Error).name === 'AbortError')\n throw new Error('No response from the local callback server. Was the login cancelled?')\n throw new Error(`Could not reach the local callback server: ${(err as Error).message}`)\n }\n finally {\n clearTimeout(timer)\n }\n\n const bodyText = await response.text().catch(() => '')\n return { status: response.status, message: extractMessage(bodyText) }\n}\n\n/**\n * Pick the most useful one-line message out of the callback server's HTML\n * response. pi-ai uses `<h1>` for the heading; the MCP callback uses\n * a leading `<p>`. We try both, then fall back to a stripped snippet.\n */\nfunction extractMessage(html: string): string | undefined {\n if (!html)\n return undefined\n const h1 = /<h1[^>]*>([\\s\\S]*?)<\\/h1>/i.exec(html)?.[1]\n if (h1)\n return stripTags(h1).trim() || undefined\n const p = /<p[^>]*>([\\s\\S]*?)<\\/p>/i.exec(html)?.[1]\n if (p)\n return stripTags(p).trim() || undefined\n return undefined\n}\n\nfunction stripTags(s: string): string {\n return s.replace(/<[^>]*>/g, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '\"').replace(/'/g, '\\'')\n}\n","/**\n * @-completion path display formatter.\n *\n * The `@`-popover walks the project from the git root so the user sees\n * the whole tree regardless of where they launched the TUI. The\n * agent's CWD-resolving tools (`read_file`, `glob`, `edit`, …) read\n * relative paths against `process.cwd()`, which is frequently a\n * subdirectory of that root. Emitting the discovery's raw\n * project-root-relative path into the prompt produces a `<cwd>/<rel>`\n * miss — the bug this helper exists to close.\n *\n * Rule:\n *\n * - File at `cwd` itself → `.`.\n * - File under `cwd` (subtree) → forward-slashed CWD-relative,\n * no `..`.\n * - File above `cwd` but still inside the project → CWD-relative\n * with `..` traversal (e.g. `../EDIT_THIS.md`). Shorter and more\n * scannable than absolute, and the agent's tools resolve both.\n * - File outside the project (input was passed absolute) → returned\n * verbatim. Inputs that arrive as project-root-relative strings\n * are always inside the project by construction, so this branch\n * only kicks in when a direct caller hands the function an\n * absolute target.\n *\n * Pure, no I/O, deterministic.\n */\n\nimport { isAbsolute, posix, relative, resolve, sep } from 'node:path'\n\n/**\n * Convert a project-root-relative path (as emitted by `listProjectFiles`)\n * into the form the agent's tools — and the user — will actually find\n * given the current `cwd`.\n *\n * Inputs:\n * - `projectRelativePath` — forward-slashed, no leading slash. Returned\n * verbatim when it's empty or already absolute (defensive — the\n * absolute-input branch is the de-facto \"outside the project\"\n * escape; the catalog itself never emits absolute paths).\n * - `projectRoot` — absolute path of the discovery anchor.\n * - `cwd` — absolute path of `process.cwd()` at the moment of\n * insertion.\n *\n * Output is always either an absolute path (only when the input arrived\n * absolute) OR a CWD-relative path. The relative form may include\n * `../` segments when the target sits above `cwd` within the project,\n * which keeps cross-tree references short and scannable\n * (e.g. `../EDIT_THIS.md` instead of `/Users/.../zidane/EDIT_THIS.md`).\n *\n * @param projectRelativePath - Forward-slashed path relative to `projectRoot`.\n * @param projectRoot - Absolute path of the discovery anchor.\n * @param cwd - Absolute working directory the result is relative to.\n */\nexport function formatPathForCwd(\n projectRelativePath: string,\n projectRoot: string,\n cwd: string,\n): string {\n if (projectRelativePath.length === 0)\n return projectRelativePath\n if (isAbsolute(projectRelativePath))\n return projectRelativePath\n const target = resolve(projectRoot, projectRelativePath)\n const here = resolve(cwd)\n if (target === here)\n return '.'\n const rel = relative(here, target)\n if (rel.length === 0)\n return '.'\n return sep === '/' ? rel : rel.split(sep).join(posix.sep)\n}\n","/**\n * Subscription usage for the providers we authenticate via OAuth, plus\n * OpenRouter's credits endpoint.\n *\n * Probes are keyed by CREDENTIAL FILE KEY, so any descriptor storing its tokens\n * under a known key resolves the right one:\n * - `anthropic` `GET api.anthropic.com/api/oauth/usage`\n * (OAuth bearer + `anthropic-beta: oauth-2025-04-20`, and\n * the `user:profile` scope the login flow requests)\n * - `openai-codex` `GET chatgpt.com/backend-api/wham/usage`\n * (Codex OAuth bearer + `ChatGPT-Account-Id`)\n * - `xai-oauth` `POST grok.com` gRPC-web billing probe. Best-effort: xAI\n * has no token-auth usage API and this endpoint is\n * cookie-oriented, so it often refuses a bearer.\n * - `openrouter` `GET openrouter.ai/api/v1/credits` (official, API key)\n *\n * A provider without a built-in probe brings its own\n * (`ProviderDescriptor.usage`, e.g. an extension's), and runs through the same\n * renewal, cache and failure handling.\n *\n * Only the OpenRouter endpoint is official; the others can change without\n * notice. So every probe is read-only and never throws: failures normalize into\n * a snapshot with `status: 'unauthorized' | 'unavailable'` and a short `error`,\n * which callers render as a state rather than handle as an error.\n *\n * An expired access token is refreshed through the run path's own helper\n * (`resolveOAuthApiKey` in `providers/oauth.ts`), targeting the exact slot:\n * same cross-process lock, same in-process coalescing, same generation check\n * before persisting. Refresh tokens are single-use, so this is the only safe\n * way to do it. It also matters for parked accounts: a run never uses them,\n * so nothing else would renew them and they would read `token expired` for\n * good. A failed refresh reports `'unauthorized'` without issuing the usage\n * request.\n *\n * Consent is two-tiered, mirroring the update check: hosts gate automatic\n * probes on `Settings.subscriptionUsage`, and {@link usageChecksOptedOut}\n * (`ZIDANE_NO_USAGE_CHECK` / `CI`) disables every probe here, explicit\n * refreshes included.\n */\n\nimport type { ProviderCredential } from './credentials'\nimport type { ProviderUsageSnapshot, ProviderUsageWindow } from './provider-usage-format'\nimport type { ProviderDescriptor } from './providers'\nimport { resolveOAuthApiKey } from '../providers/oauth'\nimport { credentialsPath, oauthAccountLabel, readCredentials, storedCredentialsOf } from './credentials'\nimport { envOptedOut } from './env-opt-out'\nimport { credKeyOf } from './providers'\n\n// Wire types + presentation helpers live in the node-free sibling so the\n// Electron renderer can share them; re-exported here to keep one import path.\nexport type { ProviderUsageCredits, ProviderUsageSnapshot, ProviderUsageStatus, ProviderUsageWindow } from './provider-usage-format'\nexport { activeSnapshotFor, formatBalance, formatCredits, formatUsagePercent, mostConstrainedWindow, resetLabel, showsAccount, updatedLabel, type UsageTone, usageTone } from './provider-usage-format'\n\n/** Per-request timeout. Generous, since these endpoints are occasionally slow. */\nconst FETCH_TIMEOUT_MS = 15_000\n\n/** How long a snapshot stays fresh. Usage windows move in minutes, not seconds. */\nexport const USAGE_CACHE_TTL_MS = 60_000\n/** TTL after a failed probe kept the previous good snapshot: retry soon, not in a minute. */\nconst USAGE_RETRY_TTL_MS = 10_000\n\n/** Which probe a credential key maps to. `descriptor`: the provider's own (`ProviderDescriptor.usage`). */\ntype UsageSource = 'claude' | 'codex' | 'grok' | 'openrouter' | 'descriptor'\ntype BuiltinUsageSource = Exclude<UsageSource, 'descriptor'>\n\n/**\n * Credential-file key to probe. Keyed by credential key rather than provider\n * key so a custom descriptor reusing a builtin credential slot\n * (`credentialFileKey: 'anthropic'`) gets usage for free.\n */\nconst USAGE_SOURCE_BY_CRED_KEY: Readonly<Record<string, BuiltinUsageSource>> = {\n 'anthropic': 'claude',\n 'openai-codex': 'codex',\n 'xai-oauth': 'grok',\n 'openrouter': 'openrouter',\n}\n\n/** Credential kind each probe authenticates with. */\nconst SOURCE_CRED_KIND: Readonly<Record<BuiltinUsageSource, ProviderCredential['kind']>> = {\n claude: 'oauth',\n codex: 'oauth',\n grok: 'oauth',\n openrouter: 'apikey',\n}\n\n/**\n * The probe for a descriptor, or `null` when it has no usage surface.\n *\n * @param descriptor - The provider to look up.\n */\nexport function usageSourceFor(descriptor: ProviderDescriptor): UsageSource | null {\n return USAGE_SOURCE_BY_CRED_KEY[credKeyOf(descriptor)] ?? (typeof descriptor.usage?.fetch === 'function' ? 'descriptor' : null)\n}\n\n/** Credential kind a probe authenticates with. */\nfunction credentialKindOf(source: UsageSource, descriptor: ProviderDescriptor): ProviderCredential['kind'] {\n return source === 'descriptor' ? (descriptor.usage?.credentialKind ?? 'oauth') : SOURCE_CRED_KIND[source]\n}\n\n/**\n * Whether this provider can report subscription usage at all.\n *\n * @param descriptor - The provider to check.\n */\nexport function providerUsageSupported(descriptor: ProviderDescriptor): boolean {\n return usageSourceFor(descriptor) !== null\n}\n\n// ---------------------------------------------------------------------------\n// Cache + entry points\n// ---------------------------------------------------------------------------\n\ninterface CacheEntry {\n expiresAt: number\n snapshot: ProviderUsageSnapshot\n}\n\n/** Keyed by credential slot + token: two descriptors sharing tokens share a snapshot. */\nconst cache = new Map<string, CacheEntry>()\n/** In-flight probes, so a footer read and a panel open share one request. */\nconst inFlight = new Map<string, Promise<ProviderUsageSnapshot>>()\n/**\n * Newest probe issued per cache key, as a monotonic ticket. `refresh`\n * skips both maps above, so two probes for one key can be outstanding at once\n * (panel refresh while the footer's post-run refresh is still in flight); only\n * the newest may write the cache, or a slow older probe would pin stale\n * numbers for a full TTL. Clearing the map also invalidates every outstanding\n * ticket, so a probe that spans `clearProviderUsageCache()` cannot repopulate\n * the cache it was meant to drop.\n */\nconst latestProbe = new Map<string, number>()\nlet probeTicket = 0\n\n/**\n * Drop every cached snapshot. Entries are keyed by credential KEY, not by the\n * token itself, so signing out and back in (or re-authing to another account)\n * would otherwise keep serving the previous account's numbers for the rest of\n * the TTL. The TUI calls this when it re-reads credentials.\n */\nexport function clearProviderUsageCache(): void {\n cache.clear()\n inFlight.clear()\n latestProbe.clear()\n}\n\n// ---------------------------------------------------------------------------\n// Env opt-outs\n//\n// Same shape as the update check's (see `chat/auto-update.ts`): these probes\n// are the only other background network calls the TUI makes, and they talk to\n// hosts that are not the ones inference goes to, so an air-gapped, CI, or\n// privacy-sensitive environment must be able to switch them off without\n// touching settings. `ZIDANE_NO_USAGE_CHECK` is the project knob; `CI` is set\n// by every major runner and skipping there avoids surprise chatter from build\n// pipelines. Unlike the setting, this is absolute: it also blocks the panel's\n// explicit refresh.\n// ---------------------------------------------------------------------------\n\nconst OPT_OUT_VARS = ['ZIDANE_NO_USAGE_CHECK', 'CI'] as const\n\n/**\n * Whether the environment forbids usage probes outright.\n *\n * @param env - Environment to read. Opt-out variables are `ZIDANE_NO_USAGE_CHECK` and `CI`.\n */\nexport function usageChecksOptedOut(env: NodeJS.ProcessEnv = process.env): boolean {\n return envOptedOut(OPT_OUT_VARS, env)\n}\n\n/** Options for the provider usage fetchers. */\nexport interface FetchProviderUsageOptions {\n /** Ignore (and replace) a still-fresh cached snapshot. */\n refresh?: boolean\n}\n\n/** One credential a probe can authenticate with, and the slot it came from. */\ninterface UsageCredential {\n /** Credentials-file key (`anthropic`, `anthropic#account:1`, …). */\n key: string\n cred: ProviderCredential\n /** Canonical slot, i.e. the credential requests use. */\n active: boolean\n}\n\n/**\n * Usage for one provider, probed with the credential requests use — or, when\n * that one is not the kind the probe needs (an API key in use, OAuth accounts\n * parked beside it), the first stored one that is. What the footer shows.\n *\n * `null` means there is nothing to show: the provider has no usage surface,\n * no credential of the kind its probe needs (file or, for API keys, env), or\n * the environment opted out (see {@link usageChecksOptedOut}).\n *\n * Never rejects. Transport and parse failures land in the snapshot.\n *\n * @param dataDir - Directory that holds the credentials files.\n * @param descriptor - The provider to probe.\n * @param opts - Fetch options, such as forcing a refresh.\n */\nexport async function fetchProviderUsage(\n dataDir: string,\n descriptor: ProviderDescriptor,\n opts: FetchProviderUsageOptions = {},\n): Promise<ProviderUsageSnapshot | null> {\n const source = usageSourceFor(descriptor)\n if (!source || usageChecksOptedOut())\n return null\n const [preferred] = resolveUsageCredentials(dataDir, descriptor, source)\n if (!preferred)\n return null\n return probeRenewed(dataDir, source, descriptor, preferred, opts)\n}\n\n/**\n * Usage for EVERY credential of a provider the probe can use — one snapshot\n * per stored OAuth account, the active one first. What the panel renders.\n * Empty under the same conditions {@link fetchProviderUsage} returns `null`.\n *\n * @param dataDir - Directory that holds the credentials files.\n * @param descriptor - The provider to probe.\n * @param opts - Fetch options, such as forcing a refresh.\n */\nexport async function fetchProviderUsageAccounts(\n dataDir: string,\n descriptor: ProviderDescriptor,\n opts: FetchProviderUsageOptions = {},\n): Promise<readonly ProviderUsageSnapshot[]> {\n const source = usageSourceFor(descriptor)\n if (!source || usageChecksOptedOut())\n return []\n const credentials = resolveUsageCredentials(dataDir, descriptor, source)\n return Promise.all(credentials.map(c => probeRenewed(dataDir, source, descriptor, c, opts)))\n}\n\n/**\n * {@link probeCached}, after renewing an expired OAuth token in its slot.\n * Stays synchronous up to the request when no renewal is needed, so callers\n * that start two probes back to back still see both requests issued at once.\n */\nfunction probeRenewed(\n dataDir: string,\n source: UsageSource,\n descriptor: ProviderDescriptor,\n credential: UsageCredential,\n opts: FetchProviderUsageOptions,\n): Promise<ProviderUsageSnapshot> {\n if (!needsRenewal(credential.cred))\n return probeCached(source, descriptor, credential, opts)\n return renewExpired(dataDir, descriptor, credential).then(renewed =>\n 'status' in renewed\n ? stampIdentity(renewed, descriptor, credential)\n : probeCached(source, descriptor, renewed, opts))\n}\n\n/**\n * An expired OAuth token that carries a refresh token. One without cannot be\n * renewed and reports `token expired` from the probe itself.\n */\nfunction needsRenewal(cred: ProviderCredential): boolean {\n return cred.kind === 'oauth' && typeof cred.expires === 'number' && cred.expires <= Date.now() && !!cred.refresh\n}\n\n/**\n * Rotate an expired OAuth token through the run path's refresh helper, keyed\n * to this credential's own slot, and hand back the credential as now stored.\n * A failed refresh becomes an `unauthorized` snapshot.\n */\nasync function renewExpired(\n dataDir: string,\n descriptor: ProviderDescriptor,\n credential: UsageCredential,\n): Promise<UsageCredential | ProviderUsageSnapshot> {\n try {\n await resolveOAuthApiKey({\n provider: descriptor.key,\n providerId: credKeyOf(descriptor),\n credentialKey: credential.key,\n credentialsPath: credentialsPath(dataDir),\n missingError: 'not signed in',\n refreshError: reason => reason,\n })\n }\n catch {\n // The reason is already surfaced by the next run (its refresh fails the\n // same way, with the full message); the panel just needs the state.\n return snapshot(\n { providerKey: descriptor.key, label: descriptor.label },\n { status: 'unauthorized', error: 'refresh failed' },\n )\n }\n // Re-read rather than trust a callback: another process may have rotated\n // the slot moments earlier, in which case the helper found a fresh token\n // and fired no refresh event, and the file is the only source of truth.\n const stored = readCredentials(dataDir)[credential.key]\n return stored?.kind === 'oauth' ? { ...credential, cred: stored } : credential\n}\n\n/** Probe one credential through the cache / in-flight coalescing. */\nfunction probeCached(\n source: UsageSource,\n descriptor: ProviderDescriptor,\n credential: UsageCredential,\n opts: FetchProviderUsageOptions,\n): Promise<ProviderUsageSnapshot> {\n // Keyed by the token, not the slot: promoting a parked account moves tokens\n // between slots, and a slot-keyed cache would then serve one account's\n // numbers under the other's name for the rest of the TTL.\n const cacheKey = `${credential.key}\\0${credential.cred.kind === 'oauth' ? credential.cred.access : credential.cred.value}`\n // Identity is re-stamped on every return path: a snapshot can come back from\n // a probe another descriptor started, and it must still name the provider\n // (and slot) the caller asked about.\n const identify = (snapshot: ProviderUsageSnapshot): ProviderUsageSnapshot => stampIdentity(snapshot, descriptor, credential)\n\n if (!opts.refresh) {\n const cached = cache.get(cacheKey)\n if (cached && cached.expiresAt > Date.now())\n return Promise.resolve(identify(cached.snapshot))\n const pending = inFlight.get(cacheKey)\n if (pending)\n return pending.then(identify)\n }\n\n const ticket = ++probeTicket\n latestProbe.set(cacheKey, ticket)\n const previous = cache.get(cacheKey)?.snapshot\n const probe = runProbe(source, descriptor, credential.cred)\n .then((fresh) => {\n // A transient failure (timeout, 5xx, parse error) must not replace\n // numbers that were fine a minute ago: the panel would flap between\n // real windows and \"unavailable\" on every hiccup. Keep the last good\n // snapshot, but only for a short while so the next probe retries soon.\n // `unauthorized` is a real state change and always shows.\n const keepPrevious = fresh.status === 'unavailable' && previous?.status === 'ok'\n const snapshot = keepPrevious ? previous : fresh\n if (latestProbe.get(cacheKey) === ticket)\n cache.set(cacheKey, { expiresAt: Date.now() + (keepPrevious ? USAGE_RETRY_TTL_MS : USAGE_CACHE_TTL_MS), snapshot })\n return snapshot\n })\n .finally(() => {\n if (inFlight.get(cacheKey) === probe)\n inFlight.delete(cacheKey)\n })\n inFlight.set(cacheKey, probe)\n return probe.then(identify)\n}\n\n/** Name the provider, slot, and account a snapshot belongs to. */\nfunction stampIdentity(snapshot: ProviderUsageSnapshot, descriptor: ProviderDescriptor, credential: UsageCredential): ProviderUsageSnapshot {\n return {\n ...snapshot,\n providerKey: descriptor.key,\n label: descriptor.label,\n credentialKey: credential.key,\n ...(typeof credential.cred.id === 'string' && credential.cred.id ? { credentialId: credential.cred.id } : {}),\n account: credential.cred.kind === 'oauth' ? oauthAccountLabel(credential.cred) : null,\n activeCredential: credential.active,\n }\n}\n\n/**\n * Every credential a probe can authenticate with, active slot first, then in\n * storage order. Empty when the provider isn't authenticated in a way the\n * probe can use.\n *\n * An API key is taken from the environment when the file has none: `detectAuth`\n * marks a provider available on a shell-set `envKey` alone, and the launcher\n * copies stored keys INTO env, so the two are the same signal. Reading only the\n * file would leave an env-authed user with no row at all, under an empty state\n * telling them they aren't signed in. OAuth tokens have no env equivalent, so\n * those probes stay file-only.\n */\nfunction resolveUsageCredentials(\n dataDir: string,\n descriptor: ProviderDescriptor,\n source: UsageSource,\n): UsageCredential[] {\n const kind = credentialKindOf(source, descriptor)\n const credKey = credKeyOf(descriptor)\n // A stored API key with an empty value is a customFields-only credential\n // (see `ApiKeyCredential.value`), so fall through to env rather than probing\n // with nothing.\n const stored = storedCredentialsOf(readCredentials(dataDir), credKey)\n .filter(s => s.credential.kind === kind && (s.credential.kind !== 'apikey' || s.credential.value))\n .map(s => ({ key: s.key, cred: s.credential, active: s.active }))\n if (stored.length > 0)\n return stored\n if (kind === 'apikey' && descriptor.envKey) {\n const fromEnv = process.env[descriptor.envKey]\n if (fromEnv)\n return [{ key: credKey, cred: { kind: 'apikey', value: fromEnv }, active: true }]\n }\n return []\n}\n\n/**\n * Usage for every registered provider that has both a usage surface and a\n * usable credential, in registry order, every account included.\n *\n * @param dataDir - Directory that holds the credentials files.\n * @param registry - Provider descriptors keyed by provider key.\n * @param opts - Fetch options, such as forcing a refresh.\n */\nexport async function fetchAllProviderUsage(\n dataDir: string,\n registry: Readonly<Record<string, ProviderDescriptor>>,\n opts: FetchProviderUsageOptions = {},\n): Promise<readonly ProviderUsageSnapshot[]> {\n const candidates = Object.values(registry).filter(providerUsageSupported)\n const settled = await Promise.all(\n candidates.map(descriptor => fetchProviderUsageAccounts(dataDir, descriptor, opts)),\n )\n return settled.flat()\n}\n\n// ---------------------------------------------------------------------------\n// Probes\n// ---------------------------------------------------------------------------\n\nasync function runProbe(\n source: UsageSource,\n descriptor: ProviderDescriptor,\n cred: ProviderCredential,\n): Promise<ProviderUsageSnapshot> {\n const base = { providerKey: descriptor.key, label: descriptor.label }\n try {\n switch (source) {\n case 'claude':\n return await fetchClaudeUsage(base, cred)\n case 'codex':\n return await fetchCodexUsage(base, cred)\n case 'grok':\n return await fetchGrokUsage(base, cred)\n case 'openrouter':\n return await fetchOpenRouterUsage(base, cred)\n case 'descriptor':\n return await fetchDescriptorUsage(base, descriptor, cred)\n }\n }\n catch (err) {\n // Covers `AbortSignal.timeout` firing. A slow endpoint is unavailable, not\n // an error the caller should handle.\n return snapshot(base, { error: err instanceof Error ? err.message : 'request failed' })\n }\n}\n\n/** Identity half of a snapshot, resolved once per probe. */\ntype SnapshotBase = Pick<ProviderUsageSnapshot, 'providerKey' | 'label'>\n\n/**\n * Snapshot factory. Defaults to `unavailable` so failure paths stay one-liners.\n * The credential identity fields are placeholders here; `probeCached` stamps\n * the real ones on every return path.\n */\nfunction snapshot(base: SnapshotBase, partial: Partial<ProviderUsageSnapshot> = {}): ProviderUsageSnapshot {\n return {\n ...base,\n credentialKey: base.providerKey,\n account: null,\n activeCredential: true,\n status: 'unavailable',\n plan: null,\n windows: [],\n credits: null,\n fetchedAt: Date.now(),\n error: null,\n ...partial,\n }\n}\n\n/**\n * OAuth access token, or an `unauthorized` snapshot explaining why there isn't\n * one. Expiry is checked without refreshing: `probeRenewed` already renewed\n * any expired token that carries a refresh token, so an expired one here has\n * none.\n */\nfunction accessTokenOr(\n base: SnapshotBase,\n cred: ProviderCredential,\n): { token: string } | { fail: ProviderUsageSnapshot } {\n if (cred.kind !== 'oauth' || !cred.access)\n return { fail: snapshot(base, { status: 'unauthorized', error: 'not signed in' }) }\n if (typeof cred.expires === 'number' && cred.expires <= Date.now())\n return { fail: snapshot(base, { status: 'unauthorized', error: 'token expired' }) }\n return { token: cred.access }\n}\n\n/**\n * 401/403 to unauthorized, other non-2xx to unavailable. `null` when the\n * response is fine.\n *\n * A rejected token reads in the same register as the local checks (`not signed\n * in`, `token expired`): it is the row a real user hits most, and the status\n * code tells them nothing. Other codes keep the number, where it is the useful\n * detail.\n */\nfunction httpFailure(base: SnapshotBase, res: Response): ProviderUsageSnapshot | null {\n if (res.status === 401 || res.status === 403)\n return snapshot(base, { status: 'unauthorized', error: 'token rejected' })\n if (!res.ok)\n return snapshot(base, { error: `HTTP ${res.status}` })\n return null\n}\n\n// --- A provider's own probe ------------------------------------------------\n\n/**\n * Run `ProviderDescriptor.usage` (extension code: its answer is checked,\n * and a probe ignoring its signal still yields at the deadline).\n */\nasync function fetchDescriptorUsage(base: SnapshotBase, descriptor: ProviderDescriptor, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n if (cred.kind === 'oauth') {\n const token = accessTokenOr(base, cred)\n if ('fail' in token)\n return token.fail\n }\n const probe = descriptor.usage!\n const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS)\n const deadline = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => reject(new Error('usage probe timed out')), { once: true })\n })\n const report = await Promise.race([probe.fetch(cred, signal), deadline])\n const status = report?.status === 'unauthorized' || report?.status === 'unavailable' ? report.status : 'ok'\n const windows = (Array.isArray(report?.windows) ? report.windows : [])\n .filter(w => w && typeof w.id === 'string' && typeof w.label === 'string' && Number.isFinite(w.usedPercent))\n .map(w => ({\n id: w.id,\n label: w.label,\n usedPercent: clampPercent(w.usedPercent),\n resetsAt: typeof w.resetsAt === 'number' && Number.isFinite(w.resetsAt) ? w.resetsAt : null,\n }))\n return snapshot(base, {\n status,\n plan: typeof report?.plan === 'string' ? report.plan : null,\n windows,\n credits: report?.credits && Number.isFinite(report.credits.remaining) ? report.credits : null,\n error: typeof report?.error === 'string' ? report.error : null,\n ...(typeof report?.note === 'string' && report.note ? { note: report.note } : {}),\n })\n}\n\n// --- Claude ----------------------------------------------------------------\n\ninterface ClaudeWindow { utilization?: number, resets_at?: string | null }\n\nconst CLAUDE_WINDOWS: readonly { key: string, id: string, label: string }[] = [\n { key: 'five_hour', id: 'five_hour', label: '5h' },\n { key: 'seven_day', id: 'seven_day', label: 'weekly' },\n { key: 'seven_day_opus', id: 'seven_day_opus', label: 'opus weekly' },\n { key: 'seven_day_sonnet', id: 'seven_day_sonnet', label: 'sonnet weekly' },\n]\n\nasync function fetchClaudeUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n const auth = accessTokenOr(base, cred)\n if ('fail' in auth)\n return auth.fail\n\n const res = await fetch('https://api.anthropic.com/api/oauth/usage', {\n headers: {\n 'Authorization': `Bearer ${auth.token}`,\n 'anthropic-beta': 'oauth-2025-04-20',\n 'Accept': 'application/json',\n },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n const data = await res.json().catch(() => null) as Record<string, unknown> | null\n if (!data)\n return snapshot(base, { error: 'invalid response' })\n\n const windows: ProviderUsageWindow[] = []\n for (const { key, id, label } of CLAUDE_WINDOWS) {\n const win = data[key] as ClaudeWindow | undefined\n if (win && typeof win.utilization === 'number') {\n windows.push({\n id,\n label,\n usedPercent: clampPercent(win.utilization),\n resetsAt: isoToEpoch(win.resets_at),\n })\n }\n }\n if (windows.length === 0)\n return snapshot(base, { error: 'no usage data' })\n return snapshot(base, { status: 'ok', windows })\n}\n\n// --- OpenAI Codex ----------------------------------------------------------\n\ninterface CodexWindow { used_percent?: number, reset_at?: number, limit_window_seconds?: number }\n\n/** Codex reports window length, not a name, so derive `5h` / `weekly` / `Nd`. */\nfunction codexWindowLabel(win: CodexWindow, fallback: string): string {\n const seconds = win.limit_window_seconds\n if (!seconds)\n return fallback\n if (seconds < 86_400)\n return `${Math.round(seconds / 3600)}h`\n const days = Math.round(seconds / 86_400)\n return days === 7 ? 'weekly' : `${days}d`\n}\n\nasync function fetchCodexUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n const auth = accessTokenOr(base, cred)\n if ('fail' in auth)\n return auth.fail\n const accountId = cred.kind === 'oauth' && typeof cred.accountId === 'string' ? cred.accountId : null\n\n const res = await fetch('https://chatgpt.com/backend-api/wham/usage', {\n headers: {\n 'Authorization': `Bearer ${auth.token}`,\n ...(accountId ? { 'ChatGPT-Account-Id': accountId } : {}),\n 'User-Agent': 'codex-cli',\n 'Accept': 'application/json',\n },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n const data = await res.json().catch(() => null) as {\n plan_type?: string\n rate_limit?: { primary_window?: CodexWindow, secondary_window?: CodexWindow }\n /** Some responses use the plural key. Both are accepted. */\n rate_limits?: { primary_window?: CodexWindow, secondary_window?: CodexWindow }\n credits?: { has_credits?: boolean, unlimited?: boolean, balance?: number }\n } | null\n if (!data)\n return snapshot(base, { error: 'invalid response' })\n\n const rateLimit = data.rate_limit ?? data.rate_limits\n const windows: ProviderUsageWindow[] = []\n const push = (win: CodexWindow | undefined, id: string, fallbackLabel: string) => {\n if (win && typeof win.used_percent === 'number') {\n windows.push({\n id,\n label: codexWindowLabel(win, fallbackLabel),\n usedPercent: clampPercent(win.used_percent),\n resetsAt: typeof win.reset_at === 'number' ? win.reset_at * 1000 : null,\n })\n }\n }\n push(rateLimit?.primary_window, 'primary', '5h')\n push(rateLimit?.secondary_window, 'secondary', 'weekly')\n\n const credits = data.credits && typeof data.credits.balance === 'number' && !data.credits.unlimited\n ? { remaining: data.credits.balance, total: null }\n : null\n\n if (windows.length === 0 && !credits)\n return snapshot(base, { error: 'no usage data' })\n return snapshot(base, {\n status: 'ok',\n plan: typeof data.plan_type === 'string' ? data.plan_type : null,\n windows,\n credits,\n })\n}\n\n// --- OpenRouter ------------------------------------------------------------\n\nasync function fetchOpenRouterUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n if (cred.kind !== 'apikey' || !cred.value)\n return snapshot(base, { status: 'unauthorized', error: 'no API key stored' })\n\n const res = await fetch('https://openrouter.ai/api/v1/credits', {\n headers: { Authorization: `Bearer ${cred.value}`, Accept: 'application/json' },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n const body = await res.json().catch(() => null) as { data?: { total_credits?: number, total_usage?: number } } | null\n const totals = body?.data\n if (!totals || typeof totals.total_credits !== 'number' || typeof totals.total_usage !== 'number')\n return snapshot(base, { error: 'invalid response' })\n\n const usedPercent = totals.total_credits > 0\n ? clampPercent((totals.total_usage / totals.total_credits) * 100)\n : 0\n return snapshot(base, {\n status: 'ok',\n windows: [{ id: 'credits', label: 'credits', usedPercent, resetsAt: null }],\n credits: { remaining: totals.total_credits - totals.total_usage, total: totals.total_credits },\n })\n}\n\n// --- Grok ------------------------------------------------------------------\n\nconst GROK_BILLING_URL = 'https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig'\nconst GROK_AUTH_FAILURE_RE = /access token|unauthenticated|expired/i\n\nasync function fetchGrokUsage(base: SnapshotBase, cred: ProviderCredential): Promise<ProviderUsageSnapshot> {\n const auth = accessTokenOr(base, cred)\n if ('fail' in auth)\n return auth.fail\n\n const res = await fetch(GROK_BILLING_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${auth.token}`,\n 'Content-Type': 'application/grpc-web+proto',\n 'x-grpc-web': '1',\n 'x-user-agent': 'connect-es/2.1.1',\n 'Origin': 'https://grok.com',\n 'Referer': 'https://grok.com/?_s=usage',\n 'Accept': '*/*',\n },\n // Empty gRPC-web frame: flags byte + u32 length 0.\n body: new Uint8Array([0, 0, 0, 0, 0]),\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n })\n const failure = httpFailure(base, res)\n if (failure)\n return failure\n\n // gRPC carries its own status out-of-band, so HTTP 200 with `grpc-status: 16`\n // is an auth rejection. That is the common answer for a bearer here.\n const grpcStatus = res.headers.get('grpc-status')\n if (grpcStatus && grpcStatus !== '0') {\n const message = res.headers.get('grpc-message') ?? ''\n const unauthorized = grpcStatus === '16' || (grpcStatus === '7' && GROK_AUTH_FAILURE_RE.test(message))\n // Plain words for the expected case, the raw status for the rest: this\n // endpoint wants a session cookie, so a rejected bearer is routine, while\n // any other status is worth reporting verbatim.\n return unauthorized\n ? snapshot(base, { status: 'unauthorized', error: 'token not accepted' })\n : snapshot(base, { error: `gRPC ${grpcStatus}${message ? `: ${message}` : ''}` })\n }\n\n const parsed = parseGrokBillingResponse(new Uint8Array(await res.arrayBuffer()))\n if (!parsed)\n return snapshot(base, { error: 'unreadable response' })\n return snapshot(base, {\n status: 'ok',\n windows: [{\n id: 'credits',\n label: 'credits',\n usedPercent: clampPercent(parsed.usedPercent),\n resetsAt: parsed.resetsAt ? parsed.resetsAt.getTime() : null,\n }],\n })\n}\n\n// ---------------------------------------------------------------------------\n// Grok payload: tolerant protobuf scan\n//\n// The billing response is a protobuf whose schema we don't have. Rather than\n// vendor a generated client for an endpoint xAI can change at will, walk the\n// wire format and recover the two fields we need by shape. A wrong-shaped\n// payload yields `null`, which surfaces as an `unavailable` row.\n// ---------------------------------------------------------------------------\n\ninterface ScannedField { path: number[], order: number, value: number }\ninterface ProtobufScan { fixed32: ScannedField[], varint: ScannedField[] }\n\n/** Split a gRPC-web body into data frames. Flag bit 0x80 marks trailers. */\nexport function grpcWebDataFrames(bytes: Uint8Array): Uint8Array[] {\n const frames: Uint8Array[] = []\n let index = 0\n while (index < bytes.length) {\n if (index + 5 > bytes.length)\n return []\n const flags = bytes[index]!\n const length = (bytes[index + 1]! << 24) | (bytes[index + 2]! << 16) | (bytes[index + 3]! << 8) | bytes[index + 4]!\n const start = index + 5\n const end = start + length\n if (length < 0 || end > bytes.length)\n return []\n if ((flags & 0x80) === 0)\n frames.push(bytes.subarray(start, end))\n index = end\n }\n return frames\n}\n\nfunction looksLikeProtobuf(bytes: Uint8Array): boolean {\n if (bytes.length === 0)\n return false\n const fieldNumber = bytes[0]! >> 3\n const wireType = bytes[0]! & 0x07\n return fieldNumber > 0 && (wireType === 0 || wireType === 1 || wireType === 2 || wireType === 5)\n}\n\nfunction readVarint(bytes: Uint8Array, offset: number): { value: number, next: number } | null {\n let value = 0\n let shift = 0\n let index = offset\n while (index < bytes.length && shift < 64) {\n const byte = bytes[index]!\n // Float math is fine here: the values we care about (epoch seconds, small\n // enums) sit far below 2^53.\n value += (byte & 0x7F) * 2 ** shift\n index += 1\n if ((byte & 0x80) === 0)\n return { value, next: index }\n shift += 7\n }\n return null\n}\n\nfunction scanProtobuf(bytes: Uint8Array, path: number[], scan: ProtobufScan, orderRef: { n: number }, depth: number): void {\n if (depth > 6)\n return\n let index = 0\n while (index < bytes.length) {\n const tag = readVarint(bytes, index)\n if (!tag)\n return\n const fieldNumber = Math.floor(tag.value / 8)\n const wireType = tag.value % 8\n if (fieldNumber === 0)\n return\n index = tag.next\n const fieldPath = [...path, fieldNumber]\n switch (wireType) {\n case 0: { // varint\n const v = readVarint(bytes, index)\n if (!v)\n return\n scan.varint.push({ path: fieldPath, order: orderRef.n++, value: v.value })\n index = v.next\n break\n }\n case 1: { // fixed64, nothing we need is a double\n if (index + 8 > bytes.length)\n return\n index += 8\n break\n }\n case 5: { // fixed32 (float), where the used percent lives\n if (index + 4 > bytes.length)\n return\n const view = new DataView(bytes.buffer, bytes.byteOffset + index, 4)\n scan.fixed32.push({ path: fieldPath, order: orderRef.n++, value: view.getFloat32(0, true) })\n index += 4\n break\n }\n case 2: { // length-delimited, recurse when it looks like a nested message\n const len = readVarint(bytes, index)\n if (!len || len.next + len.value > bytes.length)\n return\n const sub = bytes.subarray(len.next, len.next + len.value)\n if (looksLikeProtobuf(sub))\n scanProtobuf(sub, fieldPath, scan, orderRef, depth + 1)\n index = len.next + len.value\n break\n }\n default:\n return // groups and unknown wire types: bail rather than misparse\n }\n }\n}\n\n/**\n * Recover `{ usedPercent, resetsAt }` from the billing protobuf without a\n * schema. The used percent is a fixed32 float in 0-100 whose path ends in field\n * 1 (shallowest, earliest wins); the reset is the nearest future epoch-second\n * varint, preferring path `[1,5,1]` (billing-cycle end).\n */\nexport function parseGrokBillingResponse(\n bytes: Uint8Array,\n now = new Date(),\n): { usedPercent: number, resetsAt: Date | null } | null {\n let frames = grpcWebDataFrames(bytes)\n if (frames.length === 0 && looksLikeProtobuf(bytes))\n frames = [bytes]\n if (frames.length === 0)\n return null\n\n const scan: ProtobufScan = { fixed32: [], varint: [] }\n const orderRef = { n: 0 }\n for (const frame of frames)\n scanProtobuf(frame, [], scan, orderRef, 0)\n\n const percentField = scan.fixed32\n .filter(f => f.path.at(-1) === 1 && Number.isFinite(f.value) && f.value >= 0 && f.value <= 100)\n .sort((a, b) => a.path.length === b.path.length ? a.order - b.order : a.path.length - b.path.length)[0]\n\n const epochFields = scan.varint\n .filter(f => f.value >= 1_700_000_000 && f.value <= 2_100_000_000)\n .map(f => ({ path: f.path, date: new Date(f.value * 1000) }))\n .filter(f => f.date > now)\n const preferred = epochFields.filter(\n f => f.path.length === 3 && f.path[0] === 1 && f.path[1] === 5 && f.path[2] === 1,\n )\n const resetsAt = (preferred.length > 0 ? preferred : epochFields)\n .map(f => f.date)\n .sort((a, b) => a.getTime() - b.getTime())[0] ?? null\n\n // Zero-usage periods omit the proto3 default percent field, so a billing\n // period with a future reset reads as 0% used.\n const hasUsagePeriod = scan.varint.some(f =>\n (f.path.length >= 2 && f.path[0] === 1 && f.path[1] === 6)\n || (f.path.length === 3 && f.path[0] === 1 && f.path[1] === 8 && f.path[2] === 1 && (f.value === 1 || f.value === 2)),\n )\n if (!percentField) {\n if (scan.fixed32.length === 0 && resetsAt && hasUsagePeriod)\n return { usedPercent: 0, resetsAt }\n return null\n }\n return { usedPercent: percentField.value, resetsAt }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction clampPercent(value: number): number {\n if (!Number.isFinite(value))\n return 0\n return Math.min(100, Math.max(0, Math.round(value * 10) / 10))\n}\n\nfunction isoToEpoch(value: string | null | undefined): number | null {\n if (typeof value !== 'string')\n return null\n const ts = new Date(value).getTime()\n return Number.isFinite(ts) ? ts : null\n}\n","/**\n * The calls still running in a transcript read from disk. The assistant turn\n * holding a step's calls is saved before they run, their results (with\n * `durationMs`) once all of them end, so a page read mid-call shows a call\n * with neither a result nor a start. The host's running work\n * (`chat.listWork`, with `children` for the subagents' calls) has the\n * start: stamped back on, the call's row counts from when it began, not\n * from when the page was read.\n *\n * Shared by the TUI's attach and the desktop app's transcript pages.\n */\nimport type { RunningWorkItem } from './host/signals'\nimport type { StreamEvent } from './types'\n\n/** What these read off an event: a `StreamEvent`, or a client's copy of one. */\ntype CallEvent = Pick<StreamEvent, 'kind' | 'callId' | 'childId' | 'startedAt'>\n\n/** A call by its agent and id: ids are only unique within one agent. */\nfunction callKey(callId: string, childId: string | undefined): string {\n return `${childId ?? ''}\\u0000${callId}`\n}\n\n/**\n * Whether a call, a subagent's included, has neither a result nor a start: a\n * call that may still be running, untimed. A page without one needs no\n * running work.\n *\n * @param events - The page's events.\n */\nexport function hasUntimedOpenCalls(events: readonly CallEvent[]): boolean {\n const done = new Set(events.flatMap(e => (e.kind === 'tool-result' && e.callId ? [callKey(e.callId, e.childId)] : [])))\n return events.some(e => e.kind === 'tool' && e.callId !== undefined && e.startedAt === undefined && !done.has(callKey(e.callId, e.childId)))\n}\n\n/**\n * `events` with each running call's start from the host's running work, a\n * subagent's call matched by its `childId`. `events` itself when nothing\n * is stamped.\n *\n * @param events - The page's events.\n * @param work - The session's running work (`chat.listWork`).\n */\nexport function withRunningCallStarts<E extends CallEvent>(events: E[], work: readonly Pick<RunningWorkItem, 'kind' | 'id' | 'startedAt' | 'childId'>[]): E[] {\n const starts = new Map(work.flatMap(item => (item.kind === 'tool' ? [[callKey(item.id, item.childId), item.startedAt] as const] : [])))\n if (starts.size === 0)\n return events\n let stamped = false\n const out = events.map((event) => {\n const startedAt = event.kind === 'tool' && event.callId ? starts.get(callKey(event.callId, event.childId)) : undefined\n if (startedAt === undefined || event.startedAt !== undefined)\n return event\n stamped = true\n return { ...event, startedAt }\n })\n return stamped ? out : events\n}\n","/**\n * Safe-mode React context — bridges the agent's `tool:gate` / `mcp:tool:gate`\n * hooks (which run inside the harness loop) with the TUI's React tree (which\n * renders the approval picker).\n *\n * Flow:\n * 1. Hook handler in `tui/host/host-session-callbacks.ts` calls `requestApproval(tool, input)`.\n * 2. Context appends a pending entry to the queue, returns a Promise.\n * 3. Picker renders the queue's head, calls `resolve(decision)` on pick.\n * 4. Head is shifted off the queue; hook handler proceeds with the decision.\n *\n * The queue is FIFO so parallel tool calls prompt in arrival order.\n *\n * Two contexts on purpose:\n * - `SafeModeQueueContext` carries the live queue. Consumers re-render on\n * every push/pop.\n * - `SafeModeActionsContext` carries `{ requestApproval, resolveHead,\n * denyAll, dropByCorrelation }` and never changes identity, so callbacks that depend on\n * these don't re-bind every time a tool call enters or leaves the queue.\n */\n\nimport type { ReactNode } from 'react'\nimport type { ModelApproval } from './types'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\n/**\n * Outcome of an approval prompt. Four bulk decisions match the original\n * single-edit modal contract; `partial` is the multi-edit branch — the\n * user accepted a subset of an `edit` / `multi_edit` / `write_file` call.\n *\n * `partial.mask` is 1:1 with the call's hunks (in input order): `true`\n * means apply, `false` means deny. The gate handler walks the mask to\n * build per-hunk outcomes; the TUI rebinds `ctx.input.edits` to the\n * approved subset and the renderer reconstructs the full per-hunk view\n * from the `<edit-outcomes>` annotation appended to the tool result.\n */\nexport type ApprovalDecision\n = | 'accept-once'\n | 'accept-session'\n | 'accept-safelist'\n | 'deny'\n | { kind: 'partial', mask: readonly boolean[] }\n\n/**\n * Identifies the caller behind an approval prompt — the parent agent or\n * a specific subagent (`child-N`). The modal's right-side title pins the\n * label so the user knows which agent issued the call when subagents\n * bubble their gates up through the parent's hook bus.\n */\nexport type ApprovalOriginator\n = | { kind: 'parent' }\n | { kind: 'child', label: string }\n\n/** A tool call waiting for the user's decision. */\nexport interface ApprovalRequest {\n /** Unique id of the request. */\n id: string\n /** Name of the tool awaiting approval. */\n tool: string\n /** The tool call's input. */\n input: Record<string, unknown>\n /** Settles the request with the user's decision. */\n resolve: (decision: ApprovalDecision) => void\n /** Caller attribution. Absent ≡ `{ kind: 'parent' }`. */\n originator?: ApprovalOriginator\n /** Model decision approvals: the decision model's verdict when it handed this call to you (`modelConcern`). */\n concern?: ModelApproval\n /**\n * Cross-window correlation id (the daemon's approval request id) for\n * entries surfaced by the daemon runtime. Lets `dropByCorrelation`\n * retract this entry when ANOTHER window resolves the same shared\n * approval, without re-sending a decision. Absent for local-runtime\n * approvals.\n */\n correlationId?: string\n}\n\n/** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */\nexport type RequestApproval = (\n tool: string,\n input: Record<string, unknown>,\n originator?: ApprovalOriginator,\n correlationId?: string,\n) => Promise<ApprovalDecision>\n\n/** Functions that drive the approval queue. */\nexport interface SafeModeActions {\n /** Request a decision; resolves once the user picks. */\n requestApproval: RequestApproval\n /** Resolve the head and shift the queue forward. */\n resolveHead: (decision: ApprovalDecision) => void\n /** Resolve all pending with `deny`. Used on abort / hard exit. */\n denyAll: () => void\n /**\n * Remove the entry whose `correlationId` matches, WITHOUT resolving it\n * (the daemon already did, in another window). No-op when absent.\n */\n dropByCorrelation: (correlationId: string) => void\n}\n\nconst SafeModeQueueContext = createContext<readonly ApprovalRequest[]>([])\nconst SafeModeActionsContext = createContext<SafeModeActions | null>(null)\n\nlet approvalIdCounter = 0\nfunction nextApprovalId(): string {\n // Monotonic id is enough — uniqueness is scoped to a single TUI session\n // and the value never leaves the React tree. Avoids Date.now/Math.random\n // collisions inside tight parallel-tool batches.\n approvalIdCounter += 1\n return `approval-${approvalIdCounter}`\n}\n\n/**\n * Owns the queue + actions. Splits the value across two contexts so a queue\n * change doesn't invalidate every callback memo that closes over the actions.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function SafeModeProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly ApprovalRequest[]>([])\n\n // Mirror the live queue so the unmount cleanup settles pending approvals\n // without re-subscribing on every queue change.\n const queueRef = useRef<readonly ApprovalRequest[]>([])\n queueRef.current = queue\n\n // Provider unmount drops the queue state — a tool body parked on\n // `requestApproval` would then hang forever (its run's `running` gate\n // never clears). Deny every pending request on unmount so no resolver is\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const p of queueRef.current)\n p.resolve('deny')\n }, [])\n\n // `setQueue` is stable, so `useCallback([])` here gives genuine identity\n // stability — `requestApproval` is the same function reference for the\n // lifetime of the provider, which is what hook handlers need.\n const requestApproval = useCallback<RequestApproval>(\n (tool, input, originator, correlationId) => new Promise<ApprovalDecision>((resolve) => {\n setQueue(prev => [\n ...prev,\n {\n id: nextApprovalId(),\n tool,\n input,\n resolve,\n ...(originator ? { originator } : {}),\n ...(correlationId ? { correlationId } : {}),\n },\n ])\n }),\n [],\n )\n\n const resolveHead = useCallback((decision: ApprovalDecision) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(decision)\n return rest\n })\n }, [])\n\n const denyAll = useCallback(() => {\n setQueue((prev) => {\n for (const p of prev) p.resolve('deny')\n return []\n })\n }, [])\n\n const dropByCorrelation = useCallback((correlationId: string) => {\n // Remove WITHOUT resolving: the daemon already settled this approval\n // in the window that answered it. Resolving here would double-answer\n // (harmless deny) AND fire the RPC guard path unnecessarily.\n setQueue(prev => prev.filter(p => p.correlationId !== correlationId))\n }, [])\n\n // Actions object is stable across renders — its members are all `useCallback`'d\n // with `[]` deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useSafeModeActions()` never\n // re-render purely because of safe-mode state.\n const actionsRef = useRef<SafeModeActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { requestApproval, resolveHead, denyAll, dropByCorrelation }\n\n return (\n <SafeModeActionsContext.Provider value={actionsRef.current}>\n <SafeModeQueueContext.Provider value={queue}>\n {children}\n </SafeModeQueueContext.Provider>\n </SafeModeActionsContext.Provider>\n )\n}\n\n/** Returns the pending approval requests, oldest first. */\nexport function useSafeModeQueue(): readonly ApprovalRequest[] {\n return useContext(SafeModeQueueContext)\n}\n\n/** Returns the approval actions. Throws when used outside {@link SafeModeProvider}. */\nexport function useSafeModeActions(): SafeModeActions {\n const ctx = useContext(SafeModeActionsContext)\n if (!ctx)\n throw new Error('useSafeModeActions must be used inside <SafeModeProvider>')\n return ctx\n}\n","/**\n * Session export — render a `SessionData` as a clean Markdown or JSON\n * document and write it under the project's `.{prefix}/sessions/` directory,\n * or the user's `<dataDir>/sessions/`.\n *\n * Anchor resolution mirrors the read-side `projectUserPaths` convention,\n * but for *writes* we pick a single canonical destination instead of a\n * fallback list: the closest enclosing git repository (walking up from\n * `cwd`), and the user's storage data dir as the final fallback. This keeps\n * exports out of an unrelated project's `.{prefix}/` directory when the\n * user runs the TUI from a non-repo folder.\n *\n * Renderer-agnostic by design: every consumer (TUI, GUI, SDK scripts) can\n * either call `writeSessionExport` for the canned \"render + write\" combo\n * or compose `renderSession`, which only returns a string, with a different\n * sink (clipboard, network, S3).\n */\n\nimport type { SessionData, SessionRun } from '../session'\nimport type { SessionContentBlock, SessionTurn, ToolResultContent, TurnUsage } from '../types'\nimport { existsSync, mkdirSync } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { mergeReplyText } from '../session/reply-text'\nimport { fmtTokens, shortId } from './format'\nimport { deriveSessionTitle } from './store'\nimport { resolveStorageDirs } from './xdg'\n\n/** File format supported by the exporter. */\nexport type SessionExportFormat = 'markdown' | 'json'\n\n/**\n * Anchor for the resolved destination directory:\n * - `project` — the nearest enclosing git repository (`<repoRoot>/.{prefix}/sessions`)\n * - `home` — fallback when no repo was found (`<dataDir>/sessions`; `~/.{prefix}/sessions`\n * outside an XDG layout)\n */\nexport type SessionExportAnchor = 'project' | 'home'\n\n/** Resolved file destination for a session export. */\nexport interface SessionExportTarget {\n /** Absolute directory holding the file. May not exist yet — `writeSessionExport` creates it on demand. */\n dir: string\n /** Absolute path of the file (`dir/{id}.{ext}`). */\n filepath: string\n /** Where the destination was anchored. */\n anchor: SessionExportAnchor\n}\n\n/** Options shared by every resolve / write entry-point. */\ninterface ResolveOptions {\n /** Discovery cwd. Defaults to `process.cwd()`. */\n cwd?: string\n /** User home directory. Defaults to `os.homedir()`. */\n home?: string\n /**\n * Storage prefix. Leading dot tolerated (`zidane` and `.zidane` are\n * equivalent). Defaults to `'.zidane'`.\n */\n prefix?: string\n /**\n * User storage data dir; home-anchored exports land in `<dataDir>/sessions`.\n * Defaults to `resolveStorageDirs({ prefix, home }).dataDir`. Never a literal\n * `~/.{prefix}`: writing there would flip an XDG install to legacy storage.\n */\n dataDir?: string\n}\n\nconst DEFAULT_PREFIX = '.zidane'\n\n/**\n * Resolve the export target for a session id + format. Pure: no fs\n * write happens here, the caller decides whether to `existsSync` the\n * directory or hand the path to `writeSessionExport`.\n *\n * Anchor selection walks upward from `cwd` looking for a `.git` entry;\n * the first hit anchors to that repo's root (`project`). When the walk\n * exits without finding `.git`, the destination anchors to `home`: the\n * user's storage data dir.\n *\n * @throws RangeError when `sessionId` is empty, `.`, `..` or holds a path\n * separator — defensive guard against malicious or buggy callers\n * crossing into a sibling directory via the id.\n *\n * @param opts - Session id, format and optional overrides for the working directory, home directory, prefix and data directory.\n */\nexport function resolveSessionExportTarget(opts: {\n sessionId: string\n format: SessionExportFormat\n} & ResolveOptions): SessionExportTarget {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n const prefix = normalizePrefix(opts.prefix)\n const filename = exportFilename(opts.sessionId, opts.format)\n\n const repoRoot = findGitRoot(cwd)\n const anchor: SessionExportAnchor = repoRoot ? 'project' : 'home'\n const dir = repoRoot\n ? resolve(repoRoot, `.${prefix}`, 'sessions')\n : resolve(opts.dataDir ?? resolveStorageDirs({ prefix: `.${prefix}`, home }).dataDir, 'sessions')\n return { dir, filepath: join(dir, filename), anchor }\n}\n\n/**\n * Render a session into a string in the requested format.\n *\n * `markdown` produces a human-readable transcript: a YAML-free header\n * block with the title and a one-line `id, turns, runs` summary,\n * a `## Metadata` list (id, created, updated, status, turns, runs, tokens,\n * cost), then a `## Conversation` block where each turn renders as `### N. role` over an\n * italic `ISO-date, run run_X, tokens …` line, with text, thinking, tool\n * calls, and tool results formatted as appropriate fenced blocks. Useful for sharing a session\n * with a teammate or pasting into an issue tracker.\n *\n * `json` returns a 2-space-indented, deterministic dump of the full\n * `SessionData` blob. Useful for re-importing into the store or for\n * post-hoc analysis tooling.\n *\n * @param session - The session to render.\n * @param format - `json` for the pretty-printed session data, `markdown` for a transcript.\n */\nexport function renderSession(session: SessionData, format: SessionExportFormat): string {\n if (format === 'json')\n return `${JSON.stringify(session, null, 2)}\\n`\n return renderMarkdown(session)\n}\n\n/**\n * Render `session` and write the resulting bytes to disk. Returns the\n * resolved target so the caller can show the user where the file\n * landed. The parent directory is created on demand (`recursive: true`)\n * — first-time exports don't need any pre-flight setup.\n *\n * @param opts - The session, the format and optional overrides for the working directory, home directory, prefix and data directory.\n */\nexport async function writeSessionExport(\n opts: { session: SessionData, format: SessionExportFormat } & ResolveOptions,\n): Promise<SessionExportTarget> {\n const target = resolveSessionExportTarget({\n sessionId: opts.session.id,\n format: opts.format,\n cwd: opts.cwd,\n home: opts.home,\n prefix: opts.prefix,\n dataDir: opts.dataDir,\n })\n const body = renderSession(opts.session, opts.format)\n // `mkdirSync` is synchronous to avoid two awaits on a hot path that's\n // I/O-bound anyway; `recursive: true` makes it a no-op when the dir\n // already exists, so we don't pay an `existsSync` round-trip.\n mkdirSync(target.dir, { recursive: true })\n await writeFile(target.filepath, body, 'utf8')\n return target\n}\n\n// ---------------------------------------------------------------------------\n// Markdown renderer\n// ---------------------------------------------------------------------------\n\n/**\n * Render a `SessionData` as a clean markdown transcript. Stable across\n * runs (no timestamps or random ids leak into the output beyond what\n * the session itself carries) — diffing two exports of the same\n * session is a no-op.\n */\nfunction renderMarkdown(session: SessionData): string {\n const title = deriveSessionTitle(session.turns, session.metadata)\n const userTurns = session.turns.filter(t => t.role === 'user').length\n const assistantTurns = session.turns.filter(t => t.role === 'assistant').length\n const usage = aggregateUsage(session.runs)\n const lines: string[] = []\n lines.push(`# ${escapeInline(title)}`)\n lines.push('')\n lines.push(`> Session \\`${shortId(session.id)}\\`, ${session.turns.length} turn${session.turns.length === 1 ? '' : 's'}, ${session.runs.length} run${session.runs.length === 1 ? '' : 's'}`)\n lines.push('')\n lines.push('## Metadata')\n lines.push('')\n lines.push(`- **id**: \\`${session.id}\\``)\n lines.push(`- **created**: ${new Date(session.createdAt).toISOString()}`)\n lines.push(`- **updated**: ${new Date(session.updatedAt).toISOString()}`)\n lines.push(`- **status**: ${session.status}`)\n lines.push(`- **turns**: ${session.turns.length} (${userTurns} user, ${assistantTurns} assistant)`)\n lines.push(`- **runs**: ${session.runs.length}`)\n if (usage.total > 0) {\n const tokenBits: string[] = [\n `in ${fmtTokens(usage.input)}`,\n `out ${fmtTokens(usage.output)}`,\n ]\n if (usage.cacheRead > 0)\n tokenBits.push(`cached ${fmtTokens(usage.cacheRead)}`)\n lines.push(`- **tokens**: ${fmtTokens(usage.total)} (${tokenBits.join(', ')})`)\n if (usage.cost > 0)\n lines.push(`- **cost**: $${usage.cost.toFixed(usage.cost < 0.01 ? 4 : 2)}`)\n }\n lines.push('')\n lines.push('## Conversation')\n lines.push('')\n\n if (session.turns.length === 0) {\n lines.push('_No turns yet._')\n lines.push('')\n return `${lines.join('\\n')}\\n`\n }\n\n session.turns.forEach((turn, idx) => {\n lines.push(renderTurnHeader(turn, idx + 1))\n lines.push('')\n const body = renderTurnBody(turn)\n if (body) {\n lines.push(body)\n lines.push('')\n }\n })\n\n return `${lines.join('\\n').replace(/\\n+$/, '\\n')}\\n`\n}\n\n/** `### 1. user` over `_2026-05-13T01:48:29.895Z, run \\`run_1\\`, tokens in 12, out 48_`. */\nfunction renderTurnHeader(turn: SessionTurn, index: number): string {\n const meta = [new Date(turn.createdAt).toISOString()]\n if (turn.runId)\n meta.push(`run \\`${turn.runId}\\``)\n const usage = turn.usage ? formatTurnUsage(turn.usage) : ''\n if (usage)\n meta.push(`tokens ${usage}`)\n return `### ${index}. ${turn.role}\\n\\n_${meta.join(', ')}_`\n}\n\n/** `in 12, out 48, cached 1.2k`: only the non-zero buckets. */\nfunction formatTurnUsage(usage: TurnUsage): string {\n const parts: string[] = []\n if (usage.input > 0)\n parts.push(`in ${fmtTokens(usage.input)}`)\n if (usage.output > 0)\n parts.push(`out ${fmtTokens(usage.output)}`)\n if ((usage.cacheRead ?? 0) > 0)\n parts.push(`cached ${fmtTokens(usage.cacheRead ?? 0)}`)\n return parts.join(', ')\n}\n\n/**\n * Render all blocks of a single turn into a markdown fragment.\n * Block ordering matches the on-disk turn so resume / replay semantics\n * are preserved: tool_call → tool_result pairs stay adjacent, thinking\n * blocks land right before the assistant text they precede.\n */\nfunction renderTurnBody(turn: SessionTurn): string {\n // A frozen system prompt is harness plumbing, kilobytes of doctrine.\n if (turn.systemSnapshot)\n return ''\n const parts: string[] = []\n // A cited Claude reply renders whole (`mergeReplyText`), not as one\n // trimmed paragraph per cited span.\n for (const block of mergeReplyText(turn)) {\n const chunk = renderBlock(block)\n if (chunk)\n parts.push(chunk)\n }\n return parts.join('\\n\\n')\n}\n\nfunction renderBlock(block: SessionContentBlock): string {\n switch (block.type) {\n case 'text': {\n const text = block.text.trim()\n if (!block.systemNote || !text)\n return text\n // Quoted like thinking, so a note never reads as the user's words.\n const quoted = text.split('\\n').map(l => `> ${l}`).join('\\n')\n return `> **note${block.systemNote.label ? `: ${block.systemNote.label}` : ''}**\\n>\\n${quoted}`\n }\n case 'thinking': {\n const text = block.text.trim()\n if (!text)\n return ''\n // Blockquote prefix on every line so consumers can fold / hide\n // thinking sections without breaking the surrounding markdown.\n const quoted = text.split('\\n').map(l => `> ${l}`).join('\\n')\n return `> **thinking**\\n>\\n${quoted}`\n }\n case 'tool_call': {\n const args = JSON.stringify(block.input, null, 2)\n return [\n `**Tool call** \\`${block.name}\\` (id \\`${block.id}\\`)`,\n '',\n '```json',\n args,\n '```',\n ].join('\\n')\n }\n case 'tool_result': {\n const header = block.isError\n ? `**Tool error** (id \\`${block.callId}\\`)`\n : `**Tool result** (id \\`${block.callId}\\`)`\n const output = renderToolOutput(block.output)\n return [header, '', output].join('\\n')\n }\n case 'image':\n // Image data could be megabytes of base64; reference it by media type\n // rather than inline. A future viewer that wants to round-trip can\n // pull the original bytes from the on-disk session.\n return mediaPlaceholder('image', block.mediaType)\n case 'audio':\n case 'video':\n case 'document':\n return mediaPlaceholder(block.type, block.mediaType, block.name)\n case 'compact-summary': {\n // Boundary card in the markdown export — mirrors the TUI's\n // CompactSummaryBlock so a reader sees what happened at the\n // compaction point. The detailed turn-by-turn history before the\n // marker is still in the export (compaction never deletes from\n // disk), so the reader can compare summary vs source.\n const count = block.replacesTurnIds.length\n return [\n `**Compaction summary** (${count} turn${count === 1 ? '' : 's'} compacted, model \\`${block.model}\\`)`,\n '',\n block.summary.trim(),\n ].join('\\n')\n }\n default:\n return ''\n }\n}\n\n/** `_[image: image/png]_`, or `_[document: notes.pdf (application/pdf)]_` when the block is named. */\nfunction mediaPlaceholder(kind: 'image' | 'audio' | 'video' | 'document', mediaType: string, name?: string): string {\n return `_[${kind}: ${name ? `${name} (${mediaType})` : mediaType}]_`\n}\n\n/**\n * Tool output is either a flat string (the common case) or a structured\n * array of `ToolResultContent` (multimodal tools — screenshots, charts).\n * Strings render as a fenced code block; structured arrays render\n * their text blocks inline with `[image: …]` placeholders for the rest.\n */\nfunction renderToolOutput(output: string | ToolResultContent[]): string {\n if (typeof output === 'string') {\n const fence = pickFence(output)\n return `${fence}\\n${output}\\n${fence}`\n }\n const segments: string[] = []\n for (const piece of output) {\n if (piece.type === 'text') {\n const fence = pickFence(piece.text)\n segments.push(`${fence}\\n${piece.text}\\n${fence}`)\n }\n else {\n segments.push(mediaPlaceholder(piece.type, piece.mediaType, piece.type === 'document' ? piece.name : undefined))\n }\n }\n return segments.join('\\n\\n')\n}\n\n/**\n * Pick a fence (`\\`\\`\\`` or longer) that doesn't collide with any\n * backtick run inside `content`. The CommonMark rule is \"a fenced\n * block ends at a fence of the same length or longer made of the same\n * char\" — choosing a fence strictly longer than the longest run inside\n * the body avoids accidental termination.\n */\nfunction pickFence(content: string): string {\n let longestRun = 0\n let current = 0\n for (const ch of content) {\n if (ch === '`') {\n current++\n if (current > longestRun)\n longestRun = current\n }\n else {\n current = 0\n }\n }\n const len = Math.max(3, longestRun + 1)\n return '`'.repeat(len)\n}\n\n/** Strip control characters from a single-line inline (e.g. the title). */\nfunction escapeInline(s: string): string {\n return s.replace(/[\\r\\n]+/g, ' ').trim()\n}\n\n// ---------------------------------------------------------------------------\n// Path resolution helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Walk parents of `start` looking for a `.git` entry (file or\n * directory — `.git` is a file inside git worktrees). Returns the\n * directory holding `.git`, or `null` at the filesystem root.\n */\nfunction findGitRoot(start: string): string | null {\n let dir = resolve(start)\n // Hard cap as an escape hatch on pathological cwd values\n // (`mount --bind` cycles, etc.); 64 levels is well above what any\n // real-world checkout produces.\n for (let i = 0; i < 64; i++) {\n if (existsSync(join(dir, '.git')))\n return dir\n const parent = dirname(dir)\n if (parent === dir)\n return null\n dir = parent\n }\n return null\n}\n\n/** Trim a leading dot from `prefix`. */\nfunction normalizePrefix(prefix: string | undefined): string {\n return (prefix ?? DEFAULT_PREFIX).replace(/^\\./, '')\n}\n\n/**\n * Compose the on-disk filename for `(sessionId, format)`. Rejects\n * empty ids and path-traversal-ish values so the caller can't escape\n * `.{prefix}/sessions/`.\n */\nfunction exportFilename(sessionId: string, format: SessionExportFormat): string {\n const ext = format === 'json' ? 'json' : 'md'\n const cleaned = sessionId.trim()\n if (!cleaned || cleaned.includes('/') || cleaned.includes('\\\\') || cleaned === '.' || cleaned === '..')\n throw new RangeError(`Cannot export session: invalid id \"${sessionId}\"`)\n return `${cleaned}.${ext}`\n}\n\n// ---------------------------------------------------------------------------\n// Usage aggregation (also read by the TUI session details modal)\n// ---------------------------------------------------------------------------\n\n/** Token and cost totals over a list of runs, as built by {@link aggregateUsage}. */\nexport interface AggregatedUsage {\n /** Input tokens, cache reads excluded. */\n input: number\n /** Output tokens. */\n output: number\n /** Cache-read tokens. */\n cacheRead: number\n /** `input + output`. */\n total: number\n /** Cost in USD. */\n cost: number\n}\n\n/**\n * Sum token + cost figures across every {@link SessionRun}. Preference\n * order for a run's tally is `totalUsage` → per-`turnUsage[]` sum.\n * The session sets `totalUsage` from `turnUsage` when a run finishes, so the\n * fallback covers records that carry only `turnUsage`.\n *\n * Returns an aggregate of zeroes when no usage data is available; the\n * caller decides whether to render the tokens row at all.\n */\nexport function aggregateUsage(runs: readonly SessionRun[]): AggregatedUsage {\n const acc = { input: 0, output: 0, cacheRead: 0, cost: 0 }\n for (const run of runs) {\n if (run.totalUsage) {\n acc.input += run.totalUsage.input ?? 0\n acc.output += run.totalUsage.output ?? 0\n acc.cacheRead += run.totalUsage.cacheRead ?? 0\n }\n else if (run.turnUsage) {\n for (const u of run.turnUsage) {\n acc.input += u.input ?? 0\n acc.output += u.output ?? 0\n acc.cacheRead += u.cacheRead ?? 0\n }\n }\n if (run.cost)\n acc.cost += run.cost\n }\n return { ...acc, total: acc.input + acc.output }\n}\n","import type { Dispatch, MutableRefObject, SetStateAction } from 'react'\nimport type { TranscriptWeight } from './streaming-pure'\nimport type { Owner, StreamEvent } from './types'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { appendMarkdownDelta, appendThinkingLines, smoothCharsForTick, stepTranscriptCap, takeChars } from './streaming-pure'\n\n// Pure delta-merge / finalize reducers + context-window math live in the\n// node-free `./streaming-pure` leaf (importable from `zidane/chat/pure`, and\n// shared with the GUI renderer). Re-exported here so existing `zidane/chat`\n// consumers keep their import paths.\nexport { appendMarkdownDelta, appendThinkingLines, capTranscriptEvents, finalizeStreamingMarkdown, finalizeStreamingMarkdownForOwner, hiddenEarlierTranscriptCount, MAX_TRANSCRIPT_EVENTS, mergeTargetIndex, replaceTranscriptTurnEvents, revealEarlierTranscriptEvents, smoothCpsForBacklog, stepTranscriptCap, TRANSCRIPT_TRIM_STEP_EVENTS, TRANSCRIPT_TRIM_TO_EVENTS, turnContextSize } from './streaming-pure'\n\n/**\n * Default tick interval for the continuous drain loop. ~16ms aligns with\n * the 60fps renderer target so the typewriter cadence reveals 1 portion of\n * content per paint frame (a host whose frame rate differs passes\n * {@link StreamBufferOptions.getTickIntervalMs}). The bucket-derived\n * `chars-per-tick` decides how much actually lands per tick — see\n * {@link smoothCharsForTick}.\n */\nconst TICK_INTERVAL_MS = 16\n\n/**\n * Safety bound for {@link StreamBuffer.whenDrained} — per window of NO\n * drain progress. A long response legitimately types out for longer\n * than any fixed deadline (the ramp bottoms out at\n * the base rate, `SMOOTH_BASE_CPS` in `./streaming-pure`), so each committed tick re-arms the window;\n * only a genuinely wedged ticker — this long with nothing committed —\n * fires the escape hatch, and convergence (transcript resync) is never\n * blocked forever.\n */\nexport const WHEN_DRAINED_TIMEOUT_MS = 3000\n\nconst PARENT_OWNER: Owner = 'parent'\n\n/** One owner's (parent or a subagent) not-yet-committed deltas. */\ninterface DeltaBucket {\n markdown: string\n thinking: string\n owner: Owner\n depth: number\n /** Last turnId seen for this bucket. Tagged onto new events when the bucket flushes. */\n turnId?: string\n /**\n * Fractional chars carried over from the previous smooth tick. The\n * tick computes an exact float (`cps * dt`), takes the integer part,\n * and stashes the leftover here so the next tick adds it back in.\n * This makes the long-run rate match the target CPS exactly instead\n * of being inflated by `Math.ceil` rounding on every tick.\n */\n smoothCarry: number\n}\n\nfunction emptyBucket(owner: Owner, depth: number): DeltaBucket {\n return { markdown: '', thinking: '', owner, depth, smoothCarry: 0 }\n}\n\nfunction applyBucket(prev: StreamEvent[], bucket: DeltaBucket): StreamEvent[] {\n let result = prev\n if (bucket.thinking)\n result = appendThinkingLines(result, bucket.thinking, bucket.owner, bucket.depth, bucket.turnId)\n if (bucket.markdown)\n result = appendMarkdownDelta(result, bucket.markdown, bucket.owner, bucket.depth, bucket.turnId)\n return result\n}\n\n// ---------------------------------------------------------------------------\n// useStreamBuffer — per-owner stream-event accumulator with two drain modes.\n//\n// Provider deltas arrive at ~50Hz; flushing each one through React causes the\n// ScrollBox to recompute layout faster than the terminal can clear and\n// repaint, producing overdraw artifacts during scroll. We buffer per owner\n// (parent + each active subagent) and drain via a continuous ticker:\n//\n// - **smooth mode** (default — gated on {@link Settings.smoothStreaming}):\n// drip-feed text character-by-character at the base rate (`./streaming-pure`), with\n// an adaptive burst when the buffer falls behind. Reads as a typewriter\n// effect and masks bursty provider chunks behind a steady cadence.\n// While markdown backlog remains, turn-boundary calls (`appendImmediate`\n// / `flushAndUpdate`) queue behind it and `flush` leaves the ticker to\n// finish. They apply once the backlog has typed out.\n// - **batched mode** (off): drain everything per tick.\n//\n// `getSmooth` is called on every tick so toggling the user setting at\n// runtime takes effect on the next drain without rebuilding the buffer.\n// ---------------------------------------------------------------------------\n\n/** Which agent and turn a streaming delta came from. */\nexport interface StreamSource {\n /** Pass `undefined` / omit for parent-agent events. */\n childId?: string\n /** Nesting depth — 0 for parent, ≥ 1 for subagents. */\n depth?: number\n /**\n * `SessionTurn.id` this delta belongs to. Tagged onto new events so the\n * TUI's select-turn mode can group every event from one turn. Stored on\n * the active bucket and refreshed on every delta so a turn boundary\n * (`turn:after` flushes the bucket) doesn't carry stale ids forward.\n */\n turnId?: string\n}\n\n/** Handle returned by {@link useStreamBuffer}. */\nexport interface StreamBuffer {\n /** Queue a streaming delta for the next flush tick. */\n queueStreamDelta: (kind: 'markdown' | 'thinking', delta: string, source?: StreamSource) => void\n /** Drain pending deltas immediately, then append a non-streaming event. */\n appendImmediate: (evt: StreamEvent) => void\n /** Drain pending deltas immediately, then transform the event list. */\n flushAndUpdate: (update: (events: StreamEvent[]) => StreamEvent[]) => void\n /** Drain pending deltas without further transformation. */\n flush: () => void\n /** Cancel any pending flush and drop buffered deltas (on session teardown). */\n reset: () => void\n /**\n * Resolves once the pacer is idle — no bucket holds queued content and\n * no post-drain updater is pending. Resolves immediately when already\n * idle (the batched / `smoothStreaming: false` path never queues, so\n * this is zero-cost there). A safety timeout (default\n * {@link WHEN_DRAINED_TIMEOUT_MS}) resolves anyway if the ticker\n * wedges — measured as a full window with NO drain progress, so a\n * long response that keeps typing re-arms it and never snaps — and\n * callers gating a transcript convergence on the drain can never\n * dead-lock. `reset()` also releases every waiter — the backlog it\n * drops would otherwise never drain.\n */\n whenDrained: (timeoutMs?: number) => Promise<void>\n}\n\n/** Options for {@link useStreamBuffer}. */\nexport interface StreamBufferOptions {\n /**\n * Returns `true` to drip-feed buffered text at the smooth typewriter\n * cadence, `false` to drain everything per tick (batched mode). Called on every tick, so flipping the user setting\n * takes effect on the next drain.\n */\n getSmooth?: () => boolean\n /**\n * Display-memory bound applied on every drain commit (see\n * {@link capTranscriptEvents}): above this many events the oldest are\n * trimmed at a turn boundary and replaced with a cumulative\n * \"… N earlier items hidden\" marker event, at most\n * {@link TRANSCRIPT_TRIM_STEP_EVENTS} per drain ({@link stepTranscriptCap}),\n * so a large overflow never freezes one frame. Rendered events hold live\n * renderables/native buffers, so an unbounded buffer OOMs long\n * sessions (~0.13MB RSS per event measured in the TUI).\n *\n * Default {@link MAX_TRANSCRIPT_EVENTS}. Pass `null` to disable and\n * receive the full, unbounded event array, for example for\n * a host that renders a virtualized list or exports events verbatim.\n */\n maxTranscriptEvents?: number | null\n /**\n * Live override of {@link maxTranscriptEvents}, read inside each drain\n * commit. Use it when the cap changes between renders: a pending drain\n * updater runs during the host's next `useState` evaluation, BEFORE this\n * hook re-mirrors the option, so a render-mirrored value is one commit\n * stale (a history reveal would be re-trimmed by that commit).\n */\n getMaxTranscriptEvents?: () => number | null | undefined\n /**\n * What each event weighs on screen ({@link TranscriptWeight}), read at\n * each drain commit like {@link getMaxTranscriptEvents}: the cap then\n * counts rows, not events. Without it every event is a row.\n */\n getTranscriptWeight?: () => TranscriptWeight | undefined\n /**\n * Drain tick interval in ms, read each time the ticker starts. Match it to\n * the host's frame interval: ticking faster than frames paint only adds\n * commits nobody sees. The smooth cadence measures real elapsed time, so\n * the character rate does not depend on it. Default 16 (60 fps).\n */\n getTickIntervalMs?: () => number | undefined\n}\n\n/**\n * Take this tick's portion off every live bucket (mutating the buckets):\n * all of its thinking, and its markdown at the smooth cadence or in full.\n * `stillHasContent` = some bucket kept markdown for a later tick.\n */\nfunction takeTickPortions(\n buckets: Map<Owner, DeltaBucket>,\n smooth: boolean,\n dtMs: number,\n): { portions: DeltaBucket[], stillHasContent: boolean } {\n const portions: DeltaBucket[] = []\n let stillHasContent = false\n for (const bucket of buckets.values()) {\n const thinking = bucket.thinking\n bucket.thinking = ''\n\n let taken = ''\n if (bucket.markdown.length > 0) {\n if (smooth) {\n const { take, carry } = smoothCharsForTick(\n bucket.markdown.length,\n bucket.smoothCarry,\n dtMs,\n )\n bucket.smoothCarry = carry\n if (take > 0) {\n const result = takeChars(bucket.markdown, take)\n taken = result.taken\n bucket.markdown = result.rest\n if (bucket.markdown.length === 0)\n bucket.smoothCarry = 0\n }\n }\n else {\n taken = bucket.markdown\n bucket.markdown = ''\n bucket.smoothCarry = 0\n }\n }\n\n if (taken || thinking) {\n portions.push({\n markdown: taken,\n thinking,\n owner: bucket.owner,\n depth: bucket.depth,\n turnId: bucket.turnId,\n smoothCarry: 0,\n })\n }\n if (bucket.markdown.length > 0)\n stillHasContent = true\n }\n return { portions, stillHasContent }\n}\n\n/** No queued post-drain updater and no bucket holding content. */\nfunction bufferIdle(buckets: Map<Owner, DeltaBucket>, pendingUpdaters: number): boolean {\n let idle = pendingUpdaters === 0\n if (idle) {\n for (const bucket of buckets.values()) {\n if (bucket.markdown.length > 0 || bucket.thinking.length > 0) {\n idle = false\n break\n }\n }\n }\n return idle\n}\n\ninterface DrainWaiter { resolve: () => void, timer: ReturnType<typeof setTimeout> }\n\n/** Register a `whenDrained` waiter, with its no-progress safety timer armed. */\nfunction addDrainWaiter(\n resolve: () => void,\n drainProgressRef: MutableRefObject<number>,\n drainWaitersRef: MutableRefObject<DrainWaiter[]>,\n timeoutMs: number,\n): void {\n // Safety valve: a wedged ticker (cleared interval, starved event\n // loop) must not block the caller forever — resolve anyway and\n // drop the waiter so a late drain doesn't double-resolve. Armed\n // per window of NO progress: a tick that committed content since\n // the last check proves the drain is alive (a long response types\n // for longer than any fixed deadline), so re-arm instead of\n // snapping the tail into the caller's convergence.\n let seenProgress = drainProgressRef.current\n const waiter = {\n resolve,\n timer: undefined as unknown as ReturnType<typeof setTimeout>,\n }\n const arm = (): ReturnType<typeof setTimeout> => setTimeout(() => {\n if (drainProgressRef.current !== seenProgress) {\n seenProgress = drainProgressRef.current\n waiter.timer = arm()\n return\n }\n drainWaitersRef.current = drainWaitersRef.current.filter(w => w !== waiter)\n resolve()\n }, timeoutMs)\n waiter.timer = arm()\n drainWaitersRef.current.push(waiter)\n}\n\n/**\n * Buffers streaming deltas and commits them to the transcript on a ticker, in smooth or batched mode.\n *\n * @param setEvents - State setter for the transcript events.\n * @param options - Cadence and transcript cap settings.\n */\nexport function useStreamBuffer(\n setEvents: Dispatch<SetStateAction<StreamEvent[]>>,\n options?: StreamBufferOptions,\n): StreamBuffer {\n const bucketsRef = useRef<Map<Owner, DeltaBucket>>(new Map())\n const tickerRef = useRef<ReturnType<typeof setInterval> | null>(null)\n // Real wall-clock timestamp of the last tick. Feeds the dt argument\n // to `smoothCharsForTick` so the drain rate stays accurate even when\n // `setInterval` is late (event-loop congestion, GC pauses); without\n // this a 50ms-late tick would only commit ~16ms-worth of chars and\n // the visible cadence would stall.\n const lastTickMsRef = useRef<number>(0)\n // Pin the latest `getSmooth` callback inside a ref so the ticker closure\n // always sees the current setting without recreating the timer.\n const getSmoothRef = useRef<(() => boolean) | undefined>(options?.getSmooth)\n getSmoothRef.current = options?.getSmooth\n /** Live mirror so drains read the current cap without re-memoizing. */\n const maxEventsRef = useRef<number | null | undefined>(options?.maxTranscriptEvents)\n maxEventsRef.current = options?.maxTranscriptEvents\n const getMaxEventsRef = useRef(options?.getMaxTranscriptEvents)\n getMaxEventsRef.current = options?.getMaxTranscriptEvents\n const getWeightRef = useRef(options?.getTranscriptWeight)\n getWeightRef.current = options?.getTranscriptWeight\n const getTickIntervalRef = useRef(options?.getTickIntervalMs)\n getTickIntervalRef.current = options?.getTickIntervalMs\n /** Interval of the running ticker (the nominal first-tick dt and the dt clamp). */\n const tickIntervalRef = useRef(TICK_INTERVAL_MS)\n const capForCommit = useCallback((events: StreamEvent[]): StreamEvent[] => {\n const cap = getMaxEventsRef.current ? getMaxEventsRef.current() : maxEventsRef.current\n // Stepped: a big overflow (a reveal handed back) trims over several drains.\n return cap === null ? events : stepTranscriptCap(events, cap ?? undefined, undefined, getWeightRef.current?.())\n }, [])\n\n /**\n * Updaters queued while smooth-streaming was still draining backlog —\n * tool-call appends, finalize-markdown transforms, error events.\n * Applied in FIFO order at the END of `tick()` once every bucket is\n * empty, so the trailing typewriter characters land BEFORE the\n * follow-up event instead of being clobbered by an immediate drain.\n *\n * The buffer treats markdown deltas as the authoritative ordering\n * source: a tool call queued behind 100 buffered chars surfaces after\n * those 100 chars have typed out, preserving the visual stream order\n * the user is reading.\n */\n const pendingUpdatersRef = useRef<((events: StreamEvent[]) => StreamEvent[])[]>([])\n\n /**\n * Pending `whenDrained` promises. Released (with their safety timers\n * cleared) whenever the buffer reaches the idle state: the tick that\n * empties the last bucket, a synchronous `drainNow`, or a `reset`.\n */\n const drainWaitersRef = useRef<DrainWaiter[]>([])\n\n /**\n * Monotonic count of commits (ticks that landed content or updaters).\n * `whenDrained` waiters compare it across their safety window: movement\n * means the ticker is alive — re-arm instead of firing the escape hatch.\n */\n const drainProgressRef = useRef(0)\n\n const releaseDrainWaiters = useCallback(() => {\n if (drainWaitersRef.current.length === 0)\n return\n const waiters = drainWaitersRef.current\n drainWaitersRef.current = []\n for (const waiter of waiters) {\n clearTimeout(waiter.timer)\n waiter.resolve()\n }\n }, [])\n\n const stopTicker = useCallback(() => {\n if (tickerRef.current) {\n clearInterval(tickerRef.current)\n tickerRef.current = null\n }\n // Forget the last-tick stamp so the next ticker run starts from\n // the nominal interval instead of a stale (potentially seconds-old)\n // delta that would burst-drain the first frame.\n lastTickMsRef.current = 0\n }, [])\n\n /**\n * Has at least one bucket got unflushed *markdown* content?\n *\n * Thinking content is excluded: it always flushes in full on every\n * tick (see `tick()`), so its presence in a bucket only matters until\n * the next 16 ms cycle — never long enough to justify deferring an\n * end-of-turn updater.\n */\n const hasMarkdownBacklog = useCallback((): boolean => {\n for (const bucket of bucketsRef.current.values()) {\n if (bucket.markdown.length > 0)\n return true\n }\n return false\n }, [])\n\n /**\n * Drain every bucket in full and synchronously run any queued\n * post-drain updaters plus the caller-provided one. Used by the\n * fast paths below: `flush`, and `appendImmediate` /\n * `flushAndUpdate` in batched mode or when no markdown backlog remains.\n */\n const drainNow = useCallback((updater?: (events: StreamEvent[]) => StreamEvent[]) => {\n stopTicker()\n const buckets = Array.from(bucketsRef.current.values())\n bucketsRef.current.clear()\n const queued = pendingUpdatersRef.current\n pendingUpdatersRef.current = []\n const hasDeltas = buckets.some(b => b.markdown.length > 0 || b.thinking.length > 0)\n if (!hasDeltas && !updater && queued.length === 0) {\n releaseDrainWaiters()\n return\n }\n setEvents((prev) => {\n let merged = prev\n for (const bucket of buckets)\n merged = applyBucket(merged, bucket)\n for (const u of queued)\n merged = u(merged)\n if (updater)\n merged = updater(merged)\n // Memory bound: identity under the cap, turn-boundary head trim\n // above it (see capTranscriptEvents). Session turns stay complete.\n return capForCommit(merged)\n })\n releaseDrainWaiters()\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n /**\n * One tick of the continuous drain loop. Walks every live bucket and\n * commits a portion of its content based on the current mode. Once\n * every bucket empties out, fires any post-drain updaters that were\n * queued while smooth streaming was still in progress (turn\n * finalize, tool calls, errors) and stops the ticker.\n *\n * `thinking` content always flushes immediately even in smooth mode —\n * it's an internal-reasoning surface, surfaced for transparency rather\n * than for reading flow, and users expect it to keep up with the model.\n */\n const tick = useCallback(() => {\n const smooth = getSmoothRef.current?.() ?? true\n const buckets = bucketsRef.current\n\n // Real elapsed time since the last tick. Falls back to the nominal\n // interval on the first tick (when there's nothing to measure\n // against) and clamps to a sane upper bound so a one-off 500ms\n // event-loop stall doesn't burst-drain the entire backlog.\n const now = Date.now()\n const interval = tickIntervalRef.current\n const dtMs = lastTickMsRef.current === 0\n ? interval\n : Math.min(interval * 4, Math.max(1, now - lastTickMsRef.current))\n lastTickMsRef.current = now\n\n const { portions, stillHasContent } = takeTickPortions(buckets, smooth, dtMs)\n\n // Fold post-drain updaters into the same commit as the trailing\n // portions so the resulting transcript has at most one paint per\n // tick — and so the LAST portion of a smooth stream and its\n // companion finalize-markdown / tool-call event land atomically.\n const drainQueued = !stillHasContent && pendingUpdatersRef.current.length > 0\n const queued = drainQueued\n ? pendingUpdatersRef.current.splice(0, pendingUpdatersRef.current.length)\n : []\n\n if (portions.length > 0 || queued.length > 0) {\n drainProgressRef.current += 1\n setEvents((prev) => {\n let next = prev\n for (const portion of portions)\n next = applyBucket(next, portion)\n for (const u of queued)\n next = u(next)\n // Same memory bound as `drainNow`: deferred updaters append\n // tool-call / error events too, so this commit path must not\n // become a cap-bypass window. Identity under the cap.\n return capForCommit(next)\n })\n }\n\n if (!stillHasContent) {\n stopTicker()\n // Buckets and queued updaters both landed in this commit — the\n // buffer is idle, so end-of-stream convergence can proceed.\n releaseDrainWaiters()\n }\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n const ensureTicker = useCallback(() => {\n if (tickerRef.current)\n return\n const requested = getTickIntervalRef.current?.()\n tickIntervalRef.current = requested !== undefined && Number.isFinite(requested) && requested > 0 ? requested : TICK_INTERVAL_MS\n tickerRef.current = setInterval(tick, tickIntervalRef.current)\n }, [tick])\n\n /**\n * Decide between fast-drain and deferred-drain for an\n * updater-bearing call (`appendImmediate`, `flushAndUpdate`).\n *\n * Smooth mode + markdown backlog → ENQUEUE the updater behind the\n * trailing characters; the ticker picks it up once buckets empty.\n * Anything else → fall through to `drainNow` so the call retains\n * its synchronous semantics.\n */\n const drainOrDefer = useCallback((updater: (events: StreamEvent[]) => StreamEvent[]) => {\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n pendingUpdatersRef.current.push(updater)\n ensureTicker()\n return\n }\n drainNow(updater)\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flush = useCallback(() => {\n // In smooth mode with backlog, the ticker is already draining at\n // the typewriter cadence — explicit `flush()` shouldn't yank the\n // remaining characters into a single frame. The ticker is\n // self-stopping once empty, so this becomes a no-op there.\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n ensureTicker()\n return\n }\n drainNow()\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flushAndUpdate = useCallback(\n (update: (events: StreamEvent[]) => StreamEvent[]) => drainOrDefer(update),\n [drainOrDefer],\n )\n\n const appendImmediate = useCallback(\n (evt: StreamEvent) => drainOrDefer(events => [...events, evt]),\n [drainOrDefer],\n )\n\n const queueStreamDelta = useCallback((\n kind: 'markdown' | 'thinking',\n delta: string,\n source?: StreamSource,\n ) => {\n if (!delta)\n return\n const owner: Owner = source?.childId ?? PARENT_OWNER\n const depth = source?.depth ?? 0\n let bucket = bucketsRef.current.get(owner)\n if (!bucket) {\n bucket = emptyBucket(owner, depth)\n bucketsRef.current.set(owner, bucket)\n }\n bucket[kind] += delta\n // Refresh on every delta — `turn:after` drains the bucket between turns,\n // so a turnId only ever applies to the deltas of one turn. Keeping the\n // latest also lets a turn that starts mid-bucket (rare but theoretically\n // possible if two hooks race the flush ticker) tag the new event.\n if (source?.turnId)\n bucket.turnId = source.turnId\n ensureTicker()\n }, [ensureTicker])\n\n const reset = useCallback(() => {\n stopTicker()\n bucketsRef.current.clear()\n pendingUpdatersRef.current = []\n // The dropped backlog will never drain — release waiters instead of\n // stranding them until the safety timeout.\n releaseDrainWaiters()\n }, [releaseDrainWaiters, stopTicker])\n\n const whenDrained = useCallback((timeoutMs: number = WHEN_DRAINED_TIMEOUT_MS): Promise<void> => {\n if (bufferIdle(bucketsRef.current, pendingUpdatersRef.current.length))\n return Promise.resolve()\n return new Promise(resolve => addDrainWaiter(resolve, drainProgressRef, drainWaitersRef, timeoutMs))\n }, [])\n\n // Unmount hygiene: the interval ticker outlives the render otherwise and\n // fires `setEvents` against an unmounted host. `reset` is stable (all\n // deps are useCallback'd), so this registers exactly once and never\n // perturbs the memoized return below.\n useEffect(() => reset, [reset])\n\n // CRITICAL: stabilize the returned object. All six callbacks are useCallback'd\n // (stable across renders), but a bare object-literal return would be a fresh\n // reference every call, which cascades through every `useCallback`/`useEffect`\n // that depends on `stream` and turns the resume effect into an unbounded\n // destroy/rebuild loop — the exact source of the terminal-host memory leak.\n return useMemo(\n () => ({ queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained }),\n [queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained],\n )\n}\n","import type { ReactNode } from 'react'\nimport type { SyntaxStyles, Theme, ThemeColors, ThemeSelect, ThemeSurfaces } from './theme'\nimport { createContext, useContext } from 'react'\nimport { DEFAULT_THEME } from './theme'\n\n// ---------------------------------------------------------------------------\n// ThemeContext\n//\n// The TUI / GUI mounts `<ThemeProvider theme={…}>` near the root of its tree,\n// resolving the active theme from `Settings.theme`. Every component reads\n// theme values through a hook so a runtime theme switch (Settings → Theme)\n// triggers an immediate re-paint of the whole tree.\n//\n// Sub-hooks (`useColors`, `useSelectStyle`, …) exist so consumers can grab\n// just the slice they need, for example `const COLOR = useColors(); COLOR.brand`.\n// ---------------------------------------------------------------------------\n\nconst ThemeContext = createContext<Theme>(DEFAULT_THEME)\n\n/**\n * Provides `theme` to the subtree.\n *\n * @param props - The `theme` to provide and the `children` to render.\n */\nexport function ThemeProvider({ theme, children }: { theme: Theme, children: ReactNode }) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>\n}\n\n/** Returns the current theme. Falls back to {@link DEFAULT_THEME} outside a {@link ThemeProvider}. */\nexport function useTheme(): Theme {\n return useContext(ThemeContext)\n}\n\n/** Color palette only — equivalent to `useTheme().colors`. */\nexport function useColors(): ThemeColors {\n return useContext(ThemeContext).colors\n}\n\n/** Select-row styling — equivalent to `useTheme().select`: the `▶` marker and accent text carry the cursor. */\nexport function useSelectStyle(): ThemeSelect {\n return useContext(ThemeContext).select\n}\n\n/** Panel / surface backgrounds — equivalent to `useTheme().surfaces`. */\nexport function useSurfaces(): ThemeSurfaces {\n return useContext(ThemeContext).surfaces\n}\n\n/** Raw syntax style table — `useTheme().syntax`. Renderer converts to its native style type. */\nexport function useSyntaxStyles(): SyntaxStyles {\n return useContext(ThemeContext).syntax\n}\n","// ---------------------------------------------------------------------------\n// Transcript scroll-anchor computation.\n//\n// Walks a list of partitioned transcript items (one entry per row that\n// will be rendered) and assigns a stable scroll-anchor id to the FIRST\n// event of each turn — `turn-anchor-<turnId>`. The renderer applies\n// these ids to whichever container hosts the first event of the turn;\n// scroll-into-view (TUI scrollbox or GUI element.scrollIntoView) then\n// resolves the id to a known target.\n//\n// Pure data-in / data-out so the same logic powers the TUI today and\n// any future GUI without dragging OpenTUI specifics into the chat\n// layer.\n// ---------------------------------------------------------------------------\n\nimport type { StreamEvent } from './types'\n\n/**\n * One entry in the partitioned transcript-row list. Single events\n * render as a normal row; a `child-run` groups ONE subagent's events\n * from a consecutive run of subagent output into one bordered box\n * (concurrent siblings get a `child-run` each, so their rows never\n * braid together inside a shared frame); `tool-group`s\n * collect ALL parent-owned tool/tool-result events of one exchange\n * into a single collapsible summary row (`Settings.groupToolCalls`);\n * an `exchange-fold` goes further (`Settings.transcriptDensity:\n * 'compact'`) and takes every thinking, tool and worker event of the\n * exchange, leaving only the prompt and the assistant's prose in place.\n * `key` is stable across re-partitions of the same stream (derived\n * from the first grouped event) so expand state survives appends.\n *\n * Currently produced by the TUI's `partitionTranscript`; a GUI shell\n * grouping subagent runs the same way can produce the same shape.\n */\nexport type TranscriptItem\n = | { kind: 'event', event: StreamEvent, previous?: StreamEvent }\n | { kind: 'child-run', events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'tool-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'exchange-fold', key: string, events: StreamEvent[], previous?: StreamEvent }\n /** Compact density: an exchange's earlier messages, ahead of its last one. */\n | { kind: 'message-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n\n/**\n * Per-item anchor ids for auto-scroll. Walks `items` in render order\n * and, for each event, returns either:\n * - `'turn-anchor-<turnId>'` — the first event of this turn (the\n * scroll target).\n * - `undefined` — later event of an already-tagged turn (or a\n * synthetic event with no `turnId`).\n *\n * `ids[i]` is a tuple per item: length 1 for plain events, length N\n * for subagent runs (one entry per inner event). `idByTurn` is the\n * inverse lookup used by the scroll effect. `lastTurnId` is the\n * most-recently-rendered turn — the scroll effect special-cases it to\n * snap to bottom rather than scroll the anchor into view (which would\n * stop short of the actual tail).\n *\n * Exported so the anchor-tagging matrix can be unit-tested without\n * rendering anything.\n *\n * @param items - Transcript items in render order.\n */\nexport function computeTurnAnchors(items: readonly TranscriptItem[]): {\n idByTurn: ReadonlyMap<string, string>\n ids: readonly (readonly (string | undefined)[])[]\n lastTurnId: string | undefined\n} {\n const idByTurn = new Map<string, string>()\n let lastTurnId: string | undefined\n const tag = (turnId: string | undefined): string | undefined => {\n if (!turnId)\n return undefined\n lastTurnId = turnId\n if (idByTurn.has(turnId))\n return undefined\n const id = `turn-anchor-${turnId}`\n idByTurn.set(turnId, id)\n return id\n }\n const ids: (string | undefined)[][] = []\n for (const item of items) {\n if (item.kind === 'event')\n ids.push([tag(item.event.turnId)])\n else\n ids.push(item.events.map(e => tag(e.turnId)))\n }\n return { idByTurn, ids, lastTurnId }\n}\n"],"mappings":"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"}
|