zidane 5.0.2 → 5.0.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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"turn-operations-5aQu4dJg.js","names":["findGitRoot","findGitRoot","findGitRoot"],"sources":["../src/chat/agents.ts","../src/chat/providers.ts","../src/chat/credentials.ts","../src/chat/auth.ts","../src/chat/completion.ts","../src/chat/completion-files.ts","../src/chat/completion-skills.ts","../src/chat/project-root.ts","../src/chat/store.ts","../src/chat/user-config.ts","../src/chat/config.ts","../src/chat/config-context.tsx","../src/chat/themes/catppuccin.ts","../src/chat/themes/vaporwave.ts","../src/chat/theme.ts","../src/chat/settings-context.tsx","../src/chat/enabled-toggle-set.ts","../src/chat/files-discovery.ts","../src/chat/format.ts","../src/chat/generate-title.ts","../src/chat/project-user-paths.ts","../src/chat/mcps-discovery.ts","../src/chat/oauth.ts","../src/chat/prompt-segments.ts","../src/chat/safe-mode.ts","../src/chat/safe-mode-context.tsx","../src/chat/session-export.ts","../src/chat/skills-discovery.ts","../src/chat/streaming.ts","../src/chat/theme-context.tsx","../src/chat/turn-operations.ts"],"sourcesContent":["/**\n * Agent profiles — named variants of the chat agent (Build, Plan, …).\n *\n * Each profile bundles a {@link Preset} with display metadata (label,\n * description, accent color) so the TUI can render a picker, persist the\n * user's choice, and rebuild the active agent on switch.\n *\n * The host's {@link ChatOptions.preset} is the single-agent legacy\n * shorthand; for multi-profile setups, pass `agents` (an {@link AgentRegistry})\n * directly. Built-ins ship as {@link BUILTIN_AGENTS} = `{ build, plan }`.\n *\n * No OpenTUI dependency — this module is pure data so a GUI host can\n * consume the same registry.\n */\nimport type { Preset } from '../presets'\nimport { definePreset } from '../presets'\nimport { edit, glob, grep, listFiles, multiEdit, readFile, shell, writeFile } from '../tools'\nimport { createSpawnTool } from '../tools/spawn'\n\n/**\n * Theme color token used to accent the active profile in the UI (footer\n * badge, picker highlight). Resolved against `ThemeColors` via\n * `useColors()`; falls back to `accent` when omitted.\n */\nexport type AgentAccent = 'brand' | 'accent' | 'warn' | 'model'\n\nexport interface AgentProfile {\n /** Stable identifier persisted in `TuiState.lastAgent` and shown in keybindings. */\n id: string\n /** Human-readable label rendered in the picker and footer badge. */\n label: string\n /** One-line description shown next to the label in the picker. */\n description: string\n /**\n * Preset applied to `createAgent()` when this profile is active. Profiles\n * are self-contained: the host's chat-level `preset` (if any) is NOT\n * merged underneath. Hosts that want a shared base across profiles\n * should compose it explicitly in each profile's preset.\n */\n preset: Preset\n /** Theme token used by the picker / footer badge. Defaults to `'accent'`. */\n accent?: AgentAccent\n}\n\nexport type AgentRegistry = Readonly<Record<string, AgentProfile>>\n\n/** Read-only tool slice shared by the Plan profile and any host-built read-only variant. */\nconst READ_ONLY_TOOLS = { readFile, listFiles, glob, grep }\n\n/** Full build-mode tool slice — read + write + shell + spawn. */\nconst BUILD_TOOLS = { shell, readFile, writeFile, listFiles, edit, multiEdit, glob, grep }\n\n/**\n * Build agent — the default profile. Full read/write/shell access plus\n * subagent spawning. Mirrors the legacy `basic` preset so existing TUI\n * behavior is preserved when `agents` is omitted.\n */\nexport const BUILD_AGENT: AgentProfile = {\n id: 'build',\n label: 'Build',\n description: 'full tool access — read, write, edit, shell, and spawn subagents',\n accent: 'accent',\n preset: definePreset({\n name: 'build',\n system: 'You are a helpful coding assistant with full access to the workspace: shell, file reading, file writing, surgical and multi-edit tools, directory listing, codebase search, and sub-agent spawning. Prefer `edit` / `multi_edit` for in-place changes and `write_file` for full file overwrites. Spawn subagents for parallelizable work.',\n // `persist: true` shares the parent's session with every child agent so a\n // reloaded TUI can reconstruct the subagent tree from `session.turns`.\n tools: { ...BUILD_TOOLS, spawn: createSpawnTool({ persist: true }) },\n }),\n}\n\n/**\n * Plan agent — read-only exploration mode. Locked down to file reading and\n * codebase search; no shell, no edits, no spawning. The system prompt\n * frames the conversation as planning rather than execution so the model\n * outputs proposals instead of attempting mutations.\n */\nexport const PLAN_AGENT: AgentProfile = {\n id: 'plan',\n label: 'Plan',\n description: 'read-only — explore, analyze, and propose without modifying anything',\n accent: 'model',\n preset: definePreset({\n name: 'plan',\n system: 'You are in PLAN mode. You can read files, list directories, and search the codebase — but you CANNOT modify files, run shell commands, or spawn subagents. Use this mode to investigate, understand the problem, and propose a clear plan in detail. The user will switch to Build mode to execute it.',\n tools: READ_ONLY_TOOLS,\n }),\n}\n\n/**\n * Default registry shipped with `zidane/tui`. Insertion order = picker order.\n * Hosts that want only one profile can pass `{ agents: { build: BUILD_AGENT } }`,\n * or a fully custom registry of their own profiles.\n */\nexport const BUILTIN_AGENTS: AgentRegistry = {\n build: BUILD_AGENT,\n plan: PLAN_AGENT,\n}\n\n/** Id of the profile activated on first launch when nothing is persisted. */\nexport const DEFAULT_AGENT_ID = 'build'\n\n/**\n * Resolve an agent id against a registry with sensible fallbacks: the\n * requested id wins when present, then `defaultId`, then the first key.\n * Returns `null` when the registry is empty (host misconfiguration —\n * the caller should surface this rather than silently failing).\n */\nexport function resolveAgentId(\n registry: AgentRegistry,\n requestedId: string | undefined,\n defaultId: string | undefined,\n): string | null {\n if (requestedId && registry[requestedId])\n return requestedId\n if (defaultId && registry[defaultId])\n return defaultId\n const first = Object.keys(registry)[0]\n return first ?? null\n}\n\n/**\n * Wrap a legacy single `Preset` into a single-profile registry. Used by\n * `resolveConfig` when the host passed `preset` without `agents`, so older\n * call sites keep working — they get a one-entry registry whose picker is\n * a no-op (only one option).\n */\nexport function singleAgentRegistry(preset: Preset): AgentRegistry {\n return {\n default: {\n id: 'default',\n label: typeof preset.name === 'string' && preset.name.length > 0 ? preset.name : 'Default',\n description: 'host-provided preset',\n preset,\n accent: 'accent',\n },\n }\n}\n","/**\n * Provider registry — the customization seam for the TUI.\n *\n * A {@link ProviderDescriptor} carries everything the TUI needs to know about\n * a provider in one place: how to instantiate it, what to show in the UI,\n * how to detect credentials, how to OAuth (if applicable), and where to look\n * up its models. Hosts can ship their own descriptors instead of the built-in\n * four — see {@link BUILTIN_PROVIDERS}.\n */\n\nimport type { OAuthProviderInterface } from '@mariozechner/pi-ai/oauth'\nimport type { Provider } from '../providers'\nimport { getModel, getModels } from '@mariozechner/pi-ai'\nimport { anthropicOAuthProvider, openaiCodexOAuthProvider } from '@mariozechner/pi-ai/oauth'\nimport { anthropic, cerebras, openai, openrouter } from '../providers'\n\n/**\n * Structural model metadata — compatible with pi-ai's `Model` interface but\n * not coupled to it, so hosts can pass either pi-ai-shaped objects or a\n * custom registry of their own.\n *\n * Deliberately a structural duplicate of pi-ai's `Model` rather than a\n * re-export: keeps the public API stable when pi-ai bumps shape, and lets\n * hosts implement their own registry without depending on pi-ai's types.\n *\n * Lives here (rather than `./config`) because `ProviderDescriptor.models`\n * needs it — pushing it to `config.tsx` created an import cycle.\n */\nexport interface ModelInfo {\n id: string\n name?: string\n contextWindow: number\n maxTokens?: number\n reasoning?: boolean\n input?: readonly ('text' | 'image')[]\n cost?: { input: number, output: number, cacheRead?: number, cacheWrite?: number }\n provider?: string\n}\n\nexport interface ProviderDescriptor {\n /**\n * Unique identifier. Used as the registry key, persisted in `state.json`\n * as the resumed provider, and (by default) as the credential-file key.\n */\n key: string\n /** Display name shown in the TUI's provider picker and labels. */\n label: string\n /**\n * Factory that builds a fresh `Provider` instance. Called on every session\n * activation. Some factories (e.g. zidane's built-in `anthropic`) eagerly\n * resolve credentials at construction time and throw when none are\n * configured — set {@link defaultModel} on the descriptor to avoid the TUI\n * calling the factory before the user has picked + authed a provider.\n */\n factory: () => Provider\n /**\n * Default model id to seed the picker with. When omitted, the TUI falls\n * back to `descriptor.factory().meta.defaultModel`, which constructs the\n * provider — fine for lazy-credential factories, but breaks for factories\n * that throw on missing credentials before the wizard runs. Setting this\n * eagerly lets the TUI render the auth wizard without ever instantiating\n * the provider.\n */\n defaultModel?: string\n /**\n * Env var checked when detecting whether the user has an API key\n * configured for this provider. Optional — set to `undefined` when the\n * provider only supports OAuth (or only credentials via a custom path).\n */\n envKey?: string\n /**\n * Key under which credentials live in `credentials.json`. Defaults to\n * `key`. The only built-in that overrides this is OpenAI Codex\n * (`openai` → `openai-codex`) to stay compatible with the harness\n * provider's existing lookup.\n */\n credentialFileKey?: string\n /** Placeholder shown in the wizard's API-key input. */\n apiKeyPlaceholder?: string\n /**\n * pi-ai (or compat) OAuth provider. When present, the wizard offers an\n * OAuth option in addition to API key.\n */\n oauthProvider?: OAuthProviderInterface\n /**\n * Optional copy appended to the wizard's \"OAuth\" method description.\n * Use to communicate why a user might pick OAuth (e.g. subscription tier,\n * org-wide SSO). Shown after `browser-based sign-in`. Skip the leading\n * space — the wizard adds it. Example: `'Claude Pro/Max subscription'`.\n */\n oauthHint?: string\n /**\n * pi-ai provider id used to look up models in pi-ai's built-in registry.\n * Defaults to `key`. Only Codex differs (`openai` → `openai-codex`).\n */\n piProviderId?: string\n /**\n * Override the model list returned for this provider's picker. When set,\n * skips pi-ai's registry entirely. Useful for hosts maintaining their\n * own model catalogue or for custom providers pi-ai doesn't know about.\n */\n models?: readonly ModelInfo[]\n}\n\n/** Convenience accessor — returns `credentialFileKey ?? key`. */\nexport function credKeyOf(desc: ProviderDescriptor): string {\n return desc.credentialFileKey ?? desc.key\n}\n\n/** Convenience accessor — returns `piProviderId ?? key`. */\nexport function piIdOf(desc: ProviderDescriptor): string {\n return desc.piProviderId ?? desc.key\n}\n\n// ---------------------------------------------------------------------------\n// Built-in descriptors\n// ---------------------------------------------------------------------------\n\nexport const anthropicDescriptor: ProviderDescriptor = {\n key: 'anthropic',\n label: 'Anthropic',\n factory: anthropic,\n defaultModel: 'claude-opus-4-7',\n envKey: 'ANTHROPIC_API_KEY',\n apiKeyPlaceholder: 'sk-ant-…',\n oauthProvider: anthropicOAuthProvider,\n oauthHint: 'Claude Pro/Max subscription',\n}\n\nexport const openaiDescriptor: ProviderDescriptor = {\n key: 'openai',\n label: 'OpenAI Codex',\n factory: openai,\n defaultModel: 'gpt-5.4',\n envKey: 'OPENAI_CODEX_API_KEY',\n credentialFileKey: 'openai-codex',\n piProviderId: 'openai-codex',\n apiKeyPlaceholder: 'sk-… or eyJ… (Codex)',\n oauthProvider: openaiCodexOAuthProvider,\n}\n\nexport const openrouterDescriptor: ProviderDescriptor = {\n key: 'openrouter',\n label: 'OpenRouter',\n factory: openrouter,\n defaultModel: 'anthropic/claude-sonnet-4-6',\n envKey: 'OPENROUTER_API_KEY',\n apiKeyPlaceholder: 'sk-or-…',\n}\n\nexport const cerebrasDescriptor: ProviderDescriptor = {\n key: 'cerebras',\n label: 'Cerebras',\n factory: cerebras,\n defaultModel: 'zai-glm-4.7',\n envKey: 'CEREBRAS_API_KEY',\n apiKeyPlaceholder: 'csk-…',\n}\n\n/**\n * Default provider registry. Passed verbatim when `runTui` is invoked without\n * an explicit `providers` option. Hosts that want to override per-provider\n * metadata can spread this and replace specific entries:\n *\n * ```ts\n * runTui({ providers: { ...BUILTIN_PROVIDERS, anthropic: myOwnAnthropicDescriptor } })\n * ```\n */\nexport const BUILTIN_PROVIDERS: Readonly<Record<string, ProviderDescriptor>> = {\n anthropic: anthropicDescriptor,\n openai: openaiDescriptor,\n openrouter: openrouterDescriptor,\n cerebras: cerebrasDescriptor,\n}\n\n// ---------------------------------------------------------------------------\n// Model registry helper\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the model list for a given provider. Honors `descriptor.models`\n * when set; otherwise queries pi-ai via `descriptor.piProviderId`. Returns\n * `[]` for descriptors with no known mapping (custom providers without a\n * model list) — callers should hide the model picker in that case.\n */\nexport function modelsForDescriptor(descriptor: ProviderDescriptor): readonly ModelInfo[] {\n if (descriptor.models)\n return descriptor.models\n try {\n return getModels(piIdOf(descriptor) as never) as readonly ModelInfo[]\n }\n catch {\n return []\n }\n}\n\n/**\n * Look up the model's max context window via the descriptor's model source.\n * Returns `null` when the model isn't known (custom slugs, providers without\n * a registry); callers should hide the context indicator in that case.\n */\nexport function getContextWindow(descriptor: ProviderDescriptor, modelId: string): number | null {\n // Prefer the descriptor's own list — host overrides win.\n if (descriptor.models) {\n const found = descriptor.models.find(m => m.id === modelId)\n return found?.contextWindow ?? null\n }\n try {\n const model = getModel(piIdOf(descriptor) as never, modelId as never)\n return model?.contextWindow ?? null\n }\n catch {\n return null\n }\n}\n","/**\n * Credential storage for the TUI.\n *\n * Lives at `<dataDir>/credentials.json` (default `~/.zidane/credentials.json`,\n * overridable via `ZIDANE_STORAGE_DIR`). Owner-only (0o600) so refresh tokens\n * and API keys don't leak on shared boxes.\n *\n * Two credential kinds:\n * - `apikey` — a plain API key entered by the user (e.g. via the setup wizard).\n * - `oauth` — tokens obtained via a provider's OAuth flow (refresh-aware).\n *\n * **Schema note.** File keys come from {@link ProviderDescriptor.credentialFileKey}\n * (falling back to `descriptor.key`). They must match what the harness providers\n * look up at runtime — for built-ins this is `anthropic`, `openai-codex`,\n * `openrouter`, `cerebras`. Hosts that ship custom providers control the file\n * key via the descriptor.\n *\n * The legacy `cwd/.credentials.json` (written by `bun run auth` in older\n * versions) is migrated on first read if the new file doesn't exist yet. The\n * legacy file is left in place — we don't delete user data behind their back.\n */\n\nimport type { ProviderDescriptor } from './providers'\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { credKeyOf } from './providers'\n\n/** POSIX mode for the credentials file. Ignored on Windows. */\nconst FILE_MODE = 0o600\n\nexport interface ApiKeyCredential {\n kind: 'apikey'\n value: string\n}\nexport interface OAuthCredential {\n kind: 'oauth'\n access: string\n refresh?: string\n expires?: number\n /** Provider-specific extras (e.g. OpenAI Codex `accountId`). */\n [extra: string]: unknown\n}\nexport type ProviderCredential = ApiKeyCredential | OAuthCredential\n\n/** Top-level shape of `credentials.json` — keys are credential-file keys. */\nexport type CredentialsFile = Record<string, ProviderCredential>\n\n/**\n * Resolve the credentials file path given the resolved TUI data directory\n * (typically `~/.zidane`, i.e. `config.paths.dir`).\n *\n * Matches the convention used elsewhere in the TUI (sessions.db, state.json)\n * so a single `ZIDANE_STORAGE_DIR` override moves the entire data root.\n */\nexport function credentialsPath(dataDir: string): string {\n return resolve(dataDir, 'credentials.json')\n}\n\n/**\n * Read credentials from disk.\n *\n * Returns `{}` when the file is missing or corrupt (last-ditch tolerance —\n * a hand-edit gone wrong shouldn't lock the user out of re-authing). On first\n * call with no file present, attempts a migration from `cwd/.credentials.json`\n * (the legacy location used by `bun run auth`).\n */\nexport function readCredentials(dataDir: string): CredentialsFile {\n const path = credentialsPath(dataDir)\n\n if (!existsSync(path)) {\n const migrated = migrateLegacyFile(path)\n if (migrated)\n return migrated\n return {}\n }\n\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n return parsed as CredentialsFile\n }\n catch {\n return {}\n }\n}\n\n/** Read a single provider's credential (translating via the descriptor). */\nexport function readProviderCredential(dataDir: string, descriptor: ProviderDescriptor): ProviderCredential | undefined {\n return readCredentials(dataDir)[credKeyOf(descriptor)]\n}\n\n/**\n * Write credentials atomically (write-then-rename) with mode 0o600.\n *\n * Atomic on the same filesystem — readers either see the previous file or the\n * new one, never a half-written intermediate. Creates the parent dir if needed\n * (first launch on a fresh machine: `~/.zidane/` may not exist yet).\n */\nexport function writeCredentials(dataDir: string, creds: CredentialsFile): void {\n const path = credentialsPath(dataDir)\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.${process.pid}.${Date.now()}.tmp`\n writeFileSync(tmp, `${JSON.stringify(creds, null, 2)}\\n`, { mode: FILE_MODE })\n renameSync(tmp, path)\n}\n\nexport function setProviderCredential(\n dataDir: string,\n descriptor: ProviderDescriptor,\n cred: ProviderCredential,\n): void {\n const all = readCredentials(dataDir)\n all[credKeyOf(descriptor)] = cred\n writeCredentials(dataDir, all)\n}\n\nexport function removeProviderCredential(dataDir: string, descriptor: ProviderDescriptor): void {\n const all = readCredentials(dataDir)\n const fileKey = credKeyOf(descriptor)\n if (!(fileKey in all))\n return\n delete all[fileKey]\n writeCredentials(dataDir, all)\n}\n\n/**\n * Inject API-key credentials into `process.env` so the harness providers pick\n * them up via their existing env-var resolution. Called once at TUI launch\n * after the credentials file has been resolved. OAuth credentials are NOT\n * injected — those reach providers via `ZIDANE_CREDENTIALS_PATH` + the file\n * reader in `src/providers/oauth.ts`.\n *\n * Does not overwrite env vars that are already set — explicit user-provided\n * env values win over stored API keys.\n *\n * Descriptors without an `envKey` (OAuth-only providers, custom providers\n * that bypass env-var resolution) are skipped silently.\n */\nexport function applyApiKeyEnv(\n dataDir: string,\n registry: Readonly<Record<string, ProviderDescriptor>>,\n): void {\n const creds = readCredentials(dataDir)\n for (const descriptor of Object.values(registry)) {\n if (!descriptor.envKey || process.env[descriptor.envKey])\n continue\n const cred = creds[credKeyOf(descriptor)]\n if (cred?.kind === 'apikey' && cred.value)\n process.env[descriptor.envKey] = cred.value\n }\n}\n\n// ---------------------------------------------------------------------------\n// Legacy migration\n// ---------------------------------------------------------------------------\n\n/**\n * `bun run auth` (pre-TUI) wrote `cwd/.credentials.json` with an entry per\n * provider mapping directly to an OAuthCredentials payload, e.g.:\n *\n * {\n * \"anthropic\": { \"access\": \"...\", \"refresh\": \"...\", \"expires\": 123 },\n * \"openai-codex\": { \"access\": \"...\", \"refresh\": \"...\", \"expires\": 123, \"accountId\": \"...\" }\n * }\n *\n * We don't delete the legacy file — it might still be used by a host that\n * imports the harness directly. We just copy its contents into the new\n * location under the kind-tagged shape so the TUI picks them up.\n *\n * Migration is provider-agnostic: any top-level entry with an `access` field\n * is preserved verbatim (extras included), under the same key. The TUI's\n * detection then looks them up via the matching descriptor's `credentialFileKey`.\n *\n * Returns the migrated credentials when the migration ran, or `null` when\n * there's no legacy file to migrate.\n */\nfunction migrateLegacyFile(targetPath: string): CredentialsFile | null {\n const legacyPath = resolve(process.cwd(), '.credentials.json')\n if (!existsSync(legacyPath))\n return null\n\n let legacy: Record<string, unknown>\n try {\n legacy = JSON.parse(readFileSync(legacyPath, 'utf-8'))\n }\n catch {\n return null\n }\n if (!legacy || typeof legacy !== 'object' || Array.isArray(legacy))\n return null\n\n const migrated: CredentialsFile = {}\n for (const [fileKey, value] of Object.entries(legacy)) {\n if (!isOAuthLegacy(value))\n continue\n const { access, refresh, expires, ...extras } = value\n migrated[fileKey] = {\n kind: 'oauth',\n access,\n ...(typeof refresh === 'string' ? { refresh } : {}),\n ...(typeof expires === 'number' ? { expires } : {}),\n ...extras,\n }\n }\n\n if (Object.keys(migrated).length === 0)\n return null\n\n // Persist immediately so subsequent reads use the new file directly.\n mkdirSync(dirname(targetPath), { recursive: true })\n const tmp = `${targetPath}.${process.pid}.${Date.now()}.tmp`\n writeFileSync(tmp, `${JSON.stringify(migrated, null, 2)}\\n`, { mode: FILE_MODE })\n renameSync(tmp, targetPath)\n\n return migrated\n}\n\nfunction isOAuthLegacy(value: unknown): value is { access: string, refresh?: string, expires?: number, [extra: string]: unknown } {\n return (\n typeof value === 'object'\n && value !== null\n && 'access' in value\n && typeof (value as { access: unknown }).access === 'string'\n )\n}\n","import type { ProviderCredential } from './credentials'\nimport type { ProviderDescriptor } from './providers'\nimport { readCredentials } from './credentials'\nimport { credKeyOf } from './providers'\n\n/**\n * Provider identifier as known to the TUI. Built-in keys are `anthropic`,\n * `openai`, `openrouter`, `cerebras`, but hosts can register additional\n * keys via {@link ProviderDescriptor} — so the type is open.\n */\nexport type ProviderKey = string\n\nexport interface AuthMethod {\n source: 'env' | 'oauth' | 'apikey'\n /** Human-readable detail (env var name, expiry timestamp, …). */\n detail: string\n}\n\nexport interface ProviderAuth {\n key: ProviderKey\n label: string\n /** True when at least one credential source is present. */\n available: boolean\n methods: AuthMethod[]\n}\n\n/**\n * Detect available auth for every registered provider.\n *\n * Resolution order per provider (a method appears in `methods` for each\n * layer that has a credential — the agent itself resolves them in the same\n * order via its provider factories):\n *\n * 1. `kind: 'apikey'` from `credentials.json` (injected into env at TUI launch)\n * 2. explicit env var (descriptor's `envKey`)\n * 3. `kind: 'oauth'` from `credentials.json` (or legacy `cwd/.credentials.json`)\n *\n * Pure read — never refreshes or rewrites the credentials file.\n */\nexport function detectAuth(\n dataDir: string,\n registry: Readonly<Record<string, ProviderDescriptor>>,\n env: Record<string, string | undefined> = process.env,\n): ProviderAuth[] {\n const creds = readCredentials(dataDir)\n\n return Object.values(registry).map((descriptor) => {\n const methods: AuthMethod[] = []\n const fileEntry = creds[credKeyOf(descriptor)] as ProviderCredential | undefined\n\n // 1. Stored API key\n if (fileEntry?.kind === 'apikey' && fileEntry.value)\n methods.push({ source: 'apikey', detail: 'credentials.json' })\n\n // 2. Env var (may be set by `applyApiKeyEnv` from a stored key, or by the\n // user's shell. Either way, treated as the same observable signal.)\n if (descriptor.envKey && env[descriptor.envKey])\n methods.push({ source: 'env', detail: descriptor.envKey })\n\n // 3. Stored OAuth\n if (fileEntry?.kind === 'oauth' && fileEntry.access) {\n const detail = typeof fileEntry.expires === 'number'\n ? `oauth · expires ${new Date(fileEntry.expires).toLocaleString()}`\n : 'oauth · credentials.json'\n methods.push({ source: 'oauth', detail })\n }\n\n return {\n key: descriptor.key,\n label: descriptor.label,\n available: methods.length > 0,\n methods,\n }\n })\n}\n","/**\n * Prompt autocompletion framework.\n *\n * Renderer-agnostic. Providers plug in by registering a `trigger` character\n * (e.g. `/` for skills, `@` for files) and exposing two operations:\n *\n * 1. `suggest(query)` — return ranked items for the live query.\n * 2. `parseReferences(text)` — find all references to the provider's\n * items in arbitrary text. Used to highlight in-prompt mentions and\n * drive submit-time side effects (activate the skill, attach the\n * file, …).\n *\n * The TUI consumes `useCompletion()` to drive a popover above the textarea.\n * A future GUI consumes the same hook to drive a dropdown. The popup\n * component itself only reads `label` + `description` on each item, so\n * provider-specific typing (`TItem`) stays at the registration boundary\n * and never leaks into the renderer.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A discoverable, selectable thing — one row in the autocomplete popover.\n * `TItem` is provider-specific; consumers can inspect `data` when they\n * need the originating payload (e.g. the full `SkillConfig` for tooltips).\n */\nexport interface CompletionItem<TItem = unknown> {\n /** Stable identifier within the provider. Used as React key + selection equality. */\n id: string\n /** User-visible primary string (\"research\"). */\n label: string\n /** Optional one-line secondary string (\"In-depth research with citations\"). */\n description?: string\n /**\n * Text that replaces the active span (trigger + query) on commit. Usually\n * `${trigger}${label}` with a trailing space so the cursor lands ready\n * for the next token.\n */\n insertText: string\n /** Original provider-specific payload. */\n data: TItem\n}\n\n/**\n * A reference to a provider item inside arbitrary prompt text. Producers:\n * `provider.parseReferences(text)`. Consumers: the TUI for highlighting,\n * the run flow for \"activate every referenced skill before agent.run()\".\n *\n * Spans are half-open `[start, end)` codepoint offsets into the source\n * string. Overlapping spans from the same or different providers are\n * caller-resolved — the helpers below ship a \"first wins\" merger.\n */\nexport interface CompletionReference<TItem = unknown> {\n providerId: string\n start: number\n end: number\n itemId: string\n data: TItem\n}\n\n/**\n * Provider contract. Implementations decide their own ranking, fuzzy-match\n * rules, async loading behavior, and reference grammar.\n */\nexport interface CompletionProvider<TItem = unknown> {\n /** Stable id used for tagging references + React keys. */\n id: string\n /**\n * Single character that activates this provider. The engine considers a\n * trigger \"active\" when it appears at the start of the buffer or\n * immediately after whitespace, and is not closed by whitespace before\n * the cursor (so a trigger plus query is a contiguous token).\n */\n trigger: string\n /** Human-readable name. Reserved for future multi-provider popover headers. */\n label: string\n /**\n * Returns items for the active `query` (the text between the trigger and\n * the cursor, excluding the trigger itself). Synchronous return is the\n * common case; promises let providers paginate or hit a backend.\n *\n * `signal` is aborted when the query changes or the popover closes —\n * use it to cancel in-flight network calls.\n */\n suggest: (\n query: string,\n ctx: CompletionContext,\n signal: AbortSignal,\n ) => CompletionItem<TItem>[] | Promise<CompletionItem<TItem>[]>\n /**\n * Find every reference to this provider's items in `text`. Pure: must not\n * mutate ctx. The TUI calls this on every keystroke for highlighting, so\n * keep it cheap (linear scan, no I/O).\n */\n parseReferences: (\n text: string,\n ctx: CompletionContext,\n ) => CompletionReference<TItem>[]\n}\n\n/**\n * Read-only view of the active prompt buffer + cursor passed to provider\n * callbacks. Kept minimal so providers stay portable across renderers.\n */\nexport interface CompletionContext {\n /** Full prompt text. */\n text: string\n /** Codepoint offset of the cursor (0-based). */\n cursor: number\n}\n\n/**\n * Identified active trigger span. Returned by `findActiveTrigger` so\n * callers can show the popover, query the provider, and on commit replace\n * the span with the selected item's `insertText`.\n */\nexport interface ActiveTrigger<TItem = unknown> {\n provider: CompletionProvider<TItem>\n /** Substring after the trigger, up to the cursor. Empty if cursor sits right after the trigger. */\n query: string\n /** `[start, end)` — span covered by the trigger + query in the source. */\n span: { start: number, end: number }\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers — exported for unit tests + alternate renderers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the provider trigger active at `cursor`, or `null` when none fits.\n *\n * Rules:\n * - The trigger character must sit at position 0 of the buffer OR be\n * preceded by whitespace. This prevents `http://` from triggering the\n * `/`-bound skills provider mid-URL.\n * - The cursor must be at or past the trigger position.\n * - Nothing between the trigger and the cursor may be whitespace (the\n * query is one contiguous token).\n * - The query length is bounded — `maxQueryLength` defaults to 64 — so\n * a runaway buffer scan can't pin the renderer.\n */\nexport function findActiveTrigger<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTrigger<TItem> | null {\n if (providers.length === 0)\n return null\n const max = options.maxQueryLength ?? 64\n const safeCursor = Math.max(0, Math.min(cursor, text.length))\n // Whitespace test mirrors regex `\\s` so this surface agrees with the\n // built-in providers' `parseReferences` regex (which uses `\\s`). Without\n // this, pasting `text\\u00a0@README.md` would parse as a ref but the popover\n // wouldn't reopen on retype because NBSP wasn't recognized as a boundary.\n const isWhitespace = (ch: string | undefined) => ch === undefined ? false : /\\s/.test(ch)\n // Walk backward from the cursor to the nearest trigger char. Bail when\n // we hit whitespace (query token closed) or exceed `max`.\n for (let i = safeCursor - 1; i >= 0 && safeCursor - i <= max + 1; i--) {\n const ch = text[i]\n if (isWhitespace(ch))\n return null\n const provider = providers.find(p => p.trigger === ch)\n if (!provider)\n continue\n // Trigger must be at start of buffer or follow whitespace.\n const before = i > 0 ? text[i - 1] : ''\n if (before !== '' && !isWhitespace(before))\n continue\n const query = text.slice(i + 1, safeCursor)\n return {\n provider,\n query,\n span: { start: i, end: safeCursor },\n }\n }\n return null\n}\n\n/**\n * Replace `[span.start, span.end)` in `text` with `insertText`. Returns the\n * mutated text and the new cursor position (end of insertion).\n */\nexport function applyInsert(\n text: string,\n span: { start: number, end: number },\n insertText: string,\n): { text: string, cursor: number } {\n const next = text.slice(0, span.start) + insertText + text.slice(span.end)\n return { text: next, cursor: span.start + insertText.length }\n}\n\n/**\n * Merge reference lists from multiple providers into one ordered list with\n * earlier-start-wins disambiguation when spans overlap. Ties broken by\n * insertion order. Spans are sorted ascending so renderers can walk them\n * sequentially with a cursor through the source string.\n */\nexport function mergeReferences<TItem>(\n refs: readonly CompletionReference<TItem>[],\n): CompletionReference<TItem>[] {\n const sorted = [...refs].sort((a, b) => a.start - b.start)\n const merged: CompletionReference<TItem>[] = []\n let lastEnd = -1\n for (const ref of sorted) {\n if (ref.start < lastEnd)\n continue\n merged.push(ref)\n lastEnd = ref.end\n }\n return merged\n}\n\n/**\n * Collect every provider's references in one pass. Convenience wrapper —\n * the TUI textarea component calls this on every keystroke to highlight\n * in-prompt mentions.\n */\nexport function collectReferences<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n cursor = text.length,\n): CompletionReference<TItem>[] {\n const ctx: CompletionContext = { text, cursor }\n const refs: CompletionReference<TItem>[] = []\n for (const p of providers) {\n for (const ref of p.parseReferences(text, ctx))\n refs.push(ref)\n }\n return mergeReferences(refs)\n}\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 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; the engine picks at most one active provider per cursor\n * position.\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 */\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 `${text.length}:${cursor}` — the moment the\n // user types or moves the cursor we re-activate; until then we honor a\n // manual close (Esc).\n const [dismissedSignature, setDismissedSignature] = useState<string | null>(null)\n\n const active = useMemo(() => {\n const a = findActiveTrigger(text, cursor, providers, { maxQueryLength })\n if (!a)\n return null\n const sig = `${text.length}:${cursor}`\n if (dismissedSignature === sig)\n return null\n return a\n }, [text, cursor, providers, maxQueryLength, dismissedSignature])\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 (!active) {\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 out = active.provider.suggest(active.query, ctx, controller.signal)\n if (Array.isArray(out)) {\n setItems(out)\n setSelectedIndex(0)\n setLoading(false)\n return\n }\n setLoading(true)\n void out.then(\n (next) => {\n if (cancelled)\n return\n setItems(next)\n setSelectedIndex(0)\n setLoading(false)\n },\n () => {\n if (cancelled)\n return\n setItems([])\n setLoading(false)\n },\n )\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [active, 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 // `cursor` is intentionally omitted from the dep array: neither built-in\n // provider's `parseReferences` reads `ctx.cursor`, so re-walking on every\n // arrow-key press would waste work. Forwarded into the call so future\n // cursor-aware providers can opt in; they're then responsible for their\n // own memoization.\n const references = useMemo(\n () => collectReferences(text, providers, cursor),\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 setDismissedSignature(`${text.length}:${cursor}`)\n }, [text.length, cursor])\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 that exposes the discovered project file catalog as\n * `@`-prefixed mentions. Trigger: `@`. Filtering: substring against\n * `name + path`, case-insensitive. Inserted text is `@{path} ` so the\n * cursor lands ready for the next token.\n *\n * Provider is pure UI — file *attachment* (reading bytes, injecting into\n * the prompt) happens at the submission boundary, where the host walks\n * `state.references` and decides what to do with each `FileEntry`.\n */\n\nimport type {\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion'\nimport type { FileEntry } from './files-discovery'\n\n/** Trigger character — `@` is the conventional file-mention prefix in chat UIs. */\nexport const FILES_TRIGGER = '@'\n\n/** Cap on returned items. Keeps the popover compact + render-cheap. */\nconst DEFAULT_RESULT_LIMIT = 50\n\n/**\n * Build an `@`-prefixed files completion provider against a *live* catalog.\n *\n * The factory captures a getter so the catalog can be re-scanned (cwd\n * change, manual refresh) without re-instantiating the provider — the\n * App keeps one provider for the lifetime of the prompt block and just\n * mutates the underlying state.\n *\n * `limit` caps the result list so the popover stays bounded on huge\n * monorepos. Filtering is substring on `path` + `name`, case-insensitive;\n * ranking prefers (in order): exact name match, name prefix, name\n * substring, path substring, alphabetical.\n */\nexport function createFilesCompletionProvider(opts: {\n /** Live file catalog. Re-evaluated per call so refreshes take effect immediately. */\n getCatalog: () => readonly FileEntry[]\n /** Max items returned to the popover. Default: 50. */\n limit?: number\n}): CompletionProvider<FileEntry> {\n const limit = opts.limit ?? DEFAULT_RESULT_LIMIT\n return {\n id: 'files',\n trigger: FILES_TRIGGER,\n label: 'Files',\n suggest(query) {\n const catalog = opts.getCatalog()\n const q = query.trim().toLowerCase()\n const scored: { entry: FileEntry, rank: number }[] = []\n for (const file of catalog) {\n const name = file.name.toLowerCase()\n const path = file.path.toLowerCase()\n if (q.length === 0) {\n scored.push({ entry: file, rank: 4 })\n continue\n }\n if (name === q) {\n scored.push({ entry: file, rank: 0 })\n continue\n }\n if (name.startsWith(q)) {\n scored.push({ entry: file, rank: 1 })\n continue\n }\n if (name.includes(q)) {\n scored.push({ entry: file, rank: 2 })\n continue\n }\n if (path.includes(q)) {\n scored.push({ entry: file, rank: 3 })\n continue\n }\n }\n scored.sort((a, b) => {\n if (a.rank !== b.rank)\n return a.rank - b.rank\n return a.entry.path.localeCompare(b.entry.path)\n })\n return scored.slice(0, limit).map<CompletionItem<FileEntry>>(({ entry }) => ({\n id: entry.path,\n label: entry.name,\n // Description is the parent directory — gives the user disambiguation\n // signal when multiple files share a basename (`index.ts` × 12).\n description: parentDir(entry.path),\n insertText: `${FILES_TRIGGER}${entry.path} `,\n data: entry,\n }))\n },\n parseReferences(text, _ctx: CompletionContext) {\n const catalog = opts.getCatalog()\n if (catalog.length === 0)\n return []\n const byPath = new Map<string, FileEntry>()\n for (const file of catalog) byPath.set(file.path, file)\n const refs: CompletionReference<FileEntry>[] = []\n // Match `@<path>` at start-of-buffer or after whitespace. Paths are\n // non-whitespace sequences — files contain `/`, `.`, `-`, etc., so a\n // word-boundary rule won't do. We greedy-match `\\S+` then trim\n // trailing punctuation that's almost never part of the intended\n // reference (sentence end). The trimmed candidate must exist in the\n // catalog for the span to be highlighted — typos don't produce\n // false-positive references.\n // Capture groups: 1 = leading boundary (start-of-buffer or whitespace),\n // 2 = path body without the `@` sigil. `@` itself is matched but not\n // captured because the consumer only needs the raw path to look up\n // the catalog entry.\n const rx = /(^|\\s)@(\\S+)/g\n let m: RegExpExecArray | null\n // eslint-disable-next-line no-cond-assign\n while ((m = rx.exec(text)) !== null) {\n const rawCandidate = m[2]\n // Strip trailing `.`, `,`, `;`, `:`, `)` , `]`, `}`, `!`, `?` — when\n // the exact candidate isn't in the catalog but a shorter prefix is,\n // the user almost certainly mentioned the file in regular prose.\n // Single regex replace + slice instead of a per-char loop keeps this\n // O(n) in candidate length rather than O(n²) on pathological tails.\n const stripped = byPath.has(rawCandidate)\n ? rawCandidate\n : rawCandidate.replace(/[.,;:)\\]}!?]+$/, '')\n const file = byPath.get(stripped)\n if (!file)\n continue\n const start = m.index + m[1].length\n // `trimmed` matches the visible \"@<stripped>\" span — recompute its\n // length from `stripped` so we don't over-paint on the trailing punct.\n const trimmedLen = 1 + stripped.length // `@` + path\n refs.push({\n providerId: 'files',\n start,\n end: start + trimmedLen,\n itemId: file.path,\n data: file,\n })\n }\n return refs\n },\n }\n}\n\n/** Return the parent directory of a forward-slashed path, or `''` for root entries. */\nfunction parentDir(path: string): string {\n const lastSlash = path.lastIndexOf('/')\n return lastSlash <= 0 ? '' : path.slice(0, lastSlash)\n}\n\n/**\n * Walk a reference list and return the deduplicated set of files in\n * first-mention order — input to \"attach these files to the prompt\"\n * downstream logic.\n */\nexport function uniqueFilesFromReferences(\n references: readonly CompletionReference<unknown>[],\n): FileEntry[] {\n const out: FileEntry[] = []\n const seen = new Set<string>()\n for (const ref of references) {\n if (ref.providerId !== 'files')\n continue\n if (seen.has(ref.itemId))\n continue\n seen.add(ref.itemId)\n out.push(ref.data as FileEntry)\n }\n return out\n}\n","/**\n * Completion provider that exposes the discovered skills catalog as\n * slash-commands. Trigger: `/`. Filtering: substring against `name +\n * description`, case-insensitive. Inserted text is `/{name} ` so the\n * cursor lands ready for the next token.\n *\n * Skill activation is **not** performed here — the provider is pure UI.\n * Submission flow at the host (TUI `onSubmitPrompt`) walks the prompt for\n * skill references via `parseReferences` and calls `agent.activateSkill`\n * before `agent.run`.\n */\n\nimport type { SkillConfig } from '../skills'\nimport type {\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion'\n\n/** Trigger character — slash-commands convention. */\nexport const SKILLS_TRIGGER = '/'\n\n/** Valid skill-name shape (matches the parser): lowercase alnum + dashes. */\nconst SKILL_NAME_RX = /^[a-z0-9][a-z0-9-]*$/\n\n/**\n * Build a slash-command completion provider against a *live* skills\n * catalog. The factory captures a getter so the catalog can change across\n * renders (toggles, reload) without re-instantiating the provider.\n *\n * Pass `getEnabled` to additionally hide skills the user has toggled off\n * — when undefined, every catalog entry is offered.\n */\nexport function createSkillsCompletionProvider(opts: {\n /** Live catalog. Re-evaluated per call so toggles take effect immediately. */\n getCatalog: () => readonly SkillConfig[]\n /** Optional enable-set filter; when undefined every catalog skill is offered. */\n getEnabled?: () => readonly string[] | undefined\n}): CompletionProvider<SkillConfig> {\n const visible = (): SkillConfig[] => {\n const all = opts.getCatalog()\n const enabled = opts.getEnabled?.()\n if (enabled === undefined)\n return [...all]\n const allow = new Set(enabled)\n return all.filter(s => allow.has(s.name))\n }\n return {\n id: 'skills',\n trigger: SKILLS_TRIGGER,\n label: 'Skills',\n suggest(query) {\n const q = query.trim().toLowerCase()\n const items = visible()\n .filter(skill => SKILL_NAME_RX.test(skill.name))\n .filter((skill) => {\n if (q.length === 0)\n return true\n return (\n skill.name.toLowerCase().includes(q)\n || skill.description.toLowerCase().includes(q)\n )\n })\n // Prefix matches first, then substring matches, then alphabetical.\n .sort((a, b) => {\n const an = a.name.toLowerCase()\n const bn = b.name.toLowerCase()\n if (q) {\n const aPrefix = an.startsWith(q)\n const bPrefix = bn.startsWith(q)\n if (aPrefix !== bPrefix)\n return aPrefix ? -1 : 1\n }\n return an.localeCompare(bn)\n })\n return items.map<CompletionItem<SkillConfig>>(skill => ({\n id: skill.name,\n label: skill.name,\n description: skill.description,\n insertText: `${SKILLS_TRIGGER}${skill.name} `,\n data: skill,\n }))\n },\n parseReferences(text, _ctx: CompletionContext) {\n const catalog = visible()\n if (catalog.length === 0)\n return []\n const byName = new Map<string, SkillConfig>()\n for (const skill of catalog) byName.set(skill.name, skill)\n const refs: CompletionReference<SkillConfig>[] = []\n // Match `/name` at start-of-buffer or after whitespace. The lookbehind\n // is implemented manually (no regex `\\b` because `/` isn't a word\n // character) so we work in environments that lack lookbehind support.\n // Grammar must match `SKILL_NAME_RX` — `[a-z0-9-]` only, no `_`. Using\n // `\\w` here would parse `/foo_bar` as a candidate, only for the catalog\n // lookup to drop it; aligning the two grammars keeps the parser honest.\n const rx = /(^|\\s)(\\/([a-z0-9][a-z0-9-]*))/g\n let m: RegExpExecArray | null\n // eslint-disable-next-line no-cond-assign\n while ((m = rx.exec(text)) !== null) {\n const name = m[3]\n const skill = byName.get(name)\n if (!skill)\n continue\n const start = m.index + m[1].length\n refs.push({\n providerId: 'skills',\n start,\n end: start + m[2].length,\n itemId: skill.name,\n data: skill,\n })\n }\n return refs\n },\n }\n}\n\n/**\n * Walk a parsed prompt for skill references and return the deduplicated\n * list of skill names — input to `agent.activateSkill(name)` calls on\n * submit.\n */\nexport function uniqueSkillNamesFromReferences(\n references: readonly CompletionReference<unknown>[],\n): string[] {\n const out: string[] = []\n const seen = new Set<string>()\n for (const ref of references) {\n if (ref.providerId !== 'skills')\n continue\n if (seen.has(ref.itemId))\n continue\n seen.add(ref.itemId)\n out.push(ref.itemId)\n }\n return out\n}\n","/**\n * Git root detection — walks parents from `cwd` looking for a `.git`\n * entry, returning the absolute path of the repo root or `null` when\n * the search reaches the filesystem root.\n *\n * Why parent-walk over `git rev-parse --show-toplevel`:\n *\n * - Zero dependencies (no shell-out, no git binary requirement,\n * works even when `git` is missing from `PATH`).\n * - Pure-sync, deterministic, easy to test against tmp dirs.\n * - Equally fast in practice — most lookups are a single `existsSync`.\n *\n * Recognizes `.git` as either:\n *\n * - A directory (standard repo layout).\n * - A file containing `gitdir: …` (worktrees + submodules).\n *\n * Bare repos (no working tree) are intentionally NOT detected here —\n * the TUI's data dir contract is \"project working tree\", and a bare\n * repo has no working files to scope sessions against.\n */\n\nimport { existsSync, statSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/**\n * Walk parents of `cwd` looking for a `.git` entry. Returns the\n * absolute path of the directory containing `.git`, or `null` when no\n * git repo is found above `cwd`.\n *\n * Stops at the filesystem root (`/`) — no infinite loop on `dirname`'s\n * idempotent root case (`dirname('/')` returns `/`).\n */\nexport function findGitRoot(cwd: string = process.cwd()): string | null {\n let dir = resolve(cwd)\n // Guard against pathological inputs (junction loops, mount cycles)\n // by bounding the walk. 64 levels is well past anything sane and\n // avoids a pathological infinite loop on misconfigured volumes.\n for (let depth = 0; depth < 64; depth++) {\n if (hasGitMarker(dir))\n return dir\n const parent = dirname(dir)\n if (parent === dir)\n return null // reached filesystem root\n dir = parent\n }\n return null\n}\n\n/**\n * `.git` exists as a directory (standard repo) OR as a file containing\n * a `gitdir:` pointer (worktree / submodule). Tolerant on stat failures\n * — a permission error on a parent directory shouldn't crash the walk.\n */\nfunction hasGitMarker(dir: string): boolean {\n const candidate = resolve(dir, '.git')\n try {\n if (!existsSync(candidate))\n return false\n const stat = statSync(candidate)\n return stat.isDirectory() || stat.isFile()\n }\n catch {\n return false\n }\n}\n","import type { SessionRun, SessionStore } from '../session'\nimport type { SessionTurn, ToolResultContent } from '../types'\nimport type { ProviderKey } from './auth'\nimport type { SessionMeta, Settings, StreamEvent } from './types'\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { formatTokenUsage } from '../stats'\nimport { toolResultToText } from '../types'\n\n// NOTE: `createTuiStore` moved to `src/session/sqlite.ts` so this module —\n// the public `zidane/chat` entry — does NOT statically import `bun:sqlite`.\n// A future GUI consumer that imports `zidane/chat` no longer transitively\n// pulls in the Bun-only sqlite binding. Hosts construct their own store\n// (e.g. via `createTuiStore` from `zidane/session/sqlite` for terminal hosts)\n// and pass it through `runTui({ store })` / `resolveConfig({ store })`.\n\nfunction ensureStateDir(path: string): void {\n const dir = dirname(path)\n if (existsSync(dir))\n return\n try {\n mkdirSync(dir, { recursive: true })\n }\n catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Could not create TUI state directory at \"${dir}\". `\n + `Override the location via \\`runTui({ storageDir, prefix })\\` or the `\n + `\\`ZIDANE_STORAGE_DIR\\` env var. Original error: ${message}`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Persisted UI state — what to resume on next launch.\n//\n// `StateStoreApi` is a thin facade that binds load/save to a specific JSON\n// path, so the rest of the UI can persist state without knowing the layout.\n// ---------------------------------------------------------------------------\n\nexport interface TuiState {\n lastProvider?: ProviderKey\n lastSessionId?: string\n /** Per-provider last-used model id. */\n lastModelByProvider?: Partial<Record<ProviderKey, string>>\n /**\n * Last-active agent profile id (see {@link AgentRegistry}). Resumed on\n * launch so a returning user lands back in the same mode (Build / Plan /\n * a host profile). Falls back to the host's `defaultAgent` then the first\n * registry key if the id is no longer registered.\n */\n lastAgent?: string\n /** User-toggled transcript filters. Persisted so they outlive a launch. */\n settings?: Partial<Settings>\n}\n\nexport interface StateStoreApi {\n load: () => TuiState\n save: (state: TuiState) => void\n}\n\nexport function createStateStore(path: string): StateStoreApi {\n return {\n load: () => loadState(path),\n save: state => saveState(path, state),\n }\n}\n\nexport function loadState(path: string): TuiState {\n if (!existsSync(path))\n return {}\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed as TuiState\n }\n catch {\n // Corrupt state file → treat as absent so the user can reach the auth screen.\n }\n return {}\n}\n\nexport function saveState(path: string, state: TuiState): void {\n ensureStateDir(path)\n // Atomic write via tmp + rename so a crash mid-write never leaves a half file.\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(state, null, 2))\n renameSync(tmp, path)\n}\n\n// Re-export the model-info shape used by views below so callers don't have to\n// pull from `./config` for transcript helpers alone.\nexport type { ModelInfo } from './config'\n\n// ---------------------------------------------------------------------------\n// Session view helpers — derive UI-friendly shapes from persisted turns.\n// ---------------------------------------------------------------------------\n\n/**\n * Load every session and project it to the compact `SessionMeta` shape used by\n * the picker. Sorted by recency via the underlying store's `list()` contract\n * (sqlite store returns by `updated_at DESC`).\n *\n * Robust to per-row failures: `Promise.allSettled` so a single corrupt or\n * unreadable row (malformed JSON, partial write, transient I/O error)\n * doesn't take down the entire picker. Failed rows are silently skipped;\n * a stderr line is emitted under `ZIDANE_DEBUG` for diagnosis.\n */\nexport async function listSessionMeta(\n store: SessionStore,\n filter?: {\n /**\n * Restrict to sessions belonging to this project. Omit to ignore\n * the axis; pass `null` to ask for untagged (legacy) sessions\n * specifically. The TUI defaults to passing the current git root /\n * cwd here so each project sees only its own conversations.\n */\n projectRoot?: string | null\n },\n): Promise<SessionMeta[]> {\n const ids = await store.list(filter)\n const settled = await Promise.allSettled(ids.map(async (id) => {\n const data = await store.load(id)\n if (!data)\n return null\n return {\n id,\n title: deriveSessionTitle(data.turns, data.metadata),\n turnCount: data.turns.length,\n userMessageCount: data.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: data.runs.length,\n ...(data.projectRoot ? { projectRoot: data.projectRoot } : {}),\n updatedAt: data.updatedAt,\n }\n }))\n const metas: SessionMeta[] = []\n for (let i = 0; i < settled.length; i++) {\n const result = settled[i]\n if (result.status === 'fulfilled' && result.value) {\n metas.push(result.value)\n }\n else if (result.status === 'rejected' && process.env.ZIDANE_DEBUG) {\n const cause = result.reason instanceof Error ? result.reason.message : String(result.reason)\n process.stderr.write(`[zidane/chat] failed to load session \"${ids[i]}\": ${cause}\\n`)\n }\n }\n return metas\n}\n\n/** Derive a short title from the first user message — returns null when empty. */\nexport function titleFromTurns(turns: SessionTurn[]): string | null {\n const first = turns.find(t => t.role === 'user')\n if (!first)\n return null\n for (const block of first.content) {\n if (block.type === 'text' && block.text.trim()) {\n const oneLine = block.text.replace(/\\s+/g, ' ').trim()\n return oneLine.length > 60 ? `${oneLine.slice(0, 60)}…` : oneLine\n }\n }\n return null\n}\n\n/**\n * Display title for a session — preferred sources, in order:\n *\n * 1. `metadata.title` if it's a non-empty string (typically set by\n * the session-details modal's \"generate title\" action).\n * 2. {@link titleFromTurns} — the first user message, one-line + clipped.\n * 3. The literal `'untitled'`.\n *\n * Total / pure / defensive on non-string metadata values.\n */\nexport function deriveSessionTitle(\n turns: SessionTurn[],\n metadata?: Record<string, unknown>,\n): string {\n const stored = typeof metadata?.title === 'string' ? metadata.title.trim() : ''\n if (stored.length > 0)\n return stored\n return titleFromTurns(turns) ?? 'untitled'\n}\n\n/**\n * Replay persisted turns as a viewable transcript. Mirrors the event shape\n * produced live by the agent hooks so loaded and streaming history render\n * identically — including subagent ancestry when `runs` is supplied.\n *\n * Subagent reconstruction:\n * - Every turn carries a `runId`. We look that up in `runs` to get the\n * run's `depth` and tag the resulting events with `{ depth, childId }`\n * — the same shape the live `child:*` bubble hooks produce.\n * - We synthesize `spawn-start` / `spawn-end` markers at each child-run\n * boundary so the transcript reads the same as a live run did\n * (`🌱 [run-id] task` … child events … `🌳 [run-id] done · tokens`).\n * - For child runs (`depth > 0`), the user-role \"task\" text is suppressed\n * because `spawn-start` already shows it.\n *\n * Without `runs` (legacy callers / tests), the function falls back to the\n * old behavior: depth-0 events with no subagent grouping.\n */\nexport function eventsFromTurns(\n turns: SessionTurn[],\n runs: readonly SessionRun[] = [],\n): StreamEvent[] {\n const runById = new Map<string, SessionRun>()\n for (const run of runs)\n runById.set(run.id, run)\n\n // Build a chronological mapping from the persisted `runId` (e.g. `run_2`) to\n // a friendly `child-N` label that mirrors what the spawn tool emits live\n // (see `src/tools/spawn.ts`'s local counter). Without this the reloaded\n // transcript labels its subagent block `[run_2]` while the persisted tool\n // result text inside that very block says `[sub-agent child-1]`.\n const childLabelByRunId = new Map<string, string>()\n const childRuns = runs\n .filter(r => (r.depth ?? 0) > 0)\n .slice()\n .sort((a, b) => a.startedAt - b.startedAt)\n childRuns.forEach((r, i) => childLabelByRunId.set(r.id, `child-${i + 1}`))\n const labelFor = (runId: string) => childLabelByRunId.get(runId) ?? runId\n\n // Build a `callId → toolName` map from the assistant `tool_call` blocks so\n // we can tag each user-role `tool_result` event with the tool it answers.\n // The renderer uses this for tool-aware filters (e.g. hiding the spawn\n // tool's result when `hideSubagentOutput` is on).\n const toolByCallId = new Map<string, string>()\n for (const turn of turns) {\n if (turn.role !== 'assistant')\n continue\n for (const block of turn.content) {\n if (block.type === 'tool_call')\n toolByCallId.set(block.id, block.name)\n }\n }\n\n // Ancestry chain for a turn's run — `[root child, …, the turn's own run]`,\n // excluding any depth-0 (top-level) ancestor since top-level runs don't\n // render spawn-start/spawn-end markers. Used by the stack-based\n // transition logic below so depth ≥ 2 (grandchild) sequences don't\n // emit duplicate `spawn-start` / premature `spawn-end` markers when\n // walking back up the tree. parent→A→B→A→parent walks cleanly:\n // T1 ancestry [] → no transition\n // T3 ancestry [A] → open A\n // T5 ancestry [A, B] → open B (A stays)\n // T7 ancestry [A] → close B (A stays)\n // T9 ancestry [] → close A\n const ancestryOf = (turnRunId: string | undefined): SessionRun[] => {\n if (!turnRunId)\n return []\n const chain: SessionRun[] = []\n let cursor: SessionRun | undefined = runById.get(turnRunId)\n const seen = new Set<string>()\n while (cursor) {\n // Defensive cycle guard — a corrupt run row pointing at its own\n // ancestor would otherwise loop forever.\n if (seen.has(cursor.id))\n break\n seen.add(cursor.id)\n if ((cursor.depth ?? 0) > 0)\n chain.unshift(cursor)\n cursor = cursor.parentRunId ? runById.get(cursor.parentRunId) : undefined\n }\n return chain\n }\n\n const events: StreamEvent[] = []\n /** Currently-open child runs, root-of-tree first, innermost last. */\n const openStack: SessionRun[] = []\n /** True iff anything has been emitted at the active depth-0 level. Used to decide separators between top-level turns. */\n let lastDepthAtEmission = -1\n\n const pushSpawnEnd = (run: SessionRun) => {\n const tag = run.status === 'aborted' || run.status === 'error' ? run.status : 'done'\n // Same caching gotcha as the live path: `tokensIn` mirrors\n // `AgentStats.totalIn` (new uncached only). `formatTokenUsage` folds\n // cache reads/creations into the headline `in` number.\n const usage = formatTokenUsage({\n totalIn: run.tokensIn ?? run.totalUsage?.input ?? 0,\n totalOut: run.tokensOut ?? run.totalUsage?.output ?? 0,\n totalCacheRead: run.totalUsage?.cacheRead ?? 0,\n totalCacheCreation: run.totalUsage?.cacheCreation ?? 0,\n })\n events.push({\n kind: 'spawn-end',\n text: `${tag} ${usage}`,\n childId: labelFor(run.id),\n depth: run.depth ?? 1,\n })\n }\n\n const pushSpawnStart = (run: SessionRun) => {\n const taskPreview = run.prompt.length > 80 ? `${run.prompt.slice(0, 80)}…` : run.prompt\n events.push({\n kind: 'spawn-start',\n text: taskPreview,\n childId: labelFor(run.id),\n depth: run.depth ?? 1,\n })\n }\n\n for (let i = 0; i < turns.length; i++) {\n const turn = turns[i]\n const targetChain = ancestryOf(turn.runId)\n const depth = targetChain.length // 0 for top-level, otherwise innermost depth\n\n // Diff the open stack against the turn's ancestry chain. Common\n // prefix stays; anything beyond in the stack is closed, anything\n // beyond in the chain is opened. Single-shot for depth = 1 (matches\n // the old behavior); multi-shot for depth ≥ 2 (the case the prior\n // implementation got wrong).\n let common = 0\n while (\n common < openStack.length\n && common < targetChain.length\n && openStack[common].id === targetChain[common].id\n ) {\n common++\n }\n while (openStack.length > common) {\n const run = openStack.pop()!\n pushSpawnEnd(run)\n }\n while (openStack.length < targetChain.length) {\n const run = targetChain[openStack.length]\n pushSpawnStart(run)\n openStack.push(run)\n }\n\n // Top-level separators — only when both prev and current turns are\n // at depth 0 (parent → parent). Keeps subagent blocks tight and\n // doesn't double up around spawn-start/spawn-end markers.\n if (depth === 0 && lastDepthAtEmission === 0)\n events.push({ kind: 'separator', text: '' })\n\n const subagentTag = depth > 0 && turn.runId ? { childId: labelFor(turn.runId), depth } : undefined\n // `turnId` tags every event so the TUI's select-turn mode can highlight\n // all events emitted by a single SessionTurn. Subagent ancestry stays in\n // a separate object so the spread order keeps existing tag overrides\n // (childId/depth) wins-last semantics intact.\n const tag = { turnId: turn.id, ...subagentTag }\n\n if (turn.role === 'user') {\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim()) {\n // For child runs the \"user\" text is the spawn task itself, already\n // rendered as the `spawn-start` marker above. Drop the duplicate.\n //\n // `user-prompt` carries the **raw** text; the renderer prepends the\n // `❯ ` chevron. No prefix in the payload means `refs` offsets stay\n // raw (no shift), and consumers building user-message indices or\n // history can read `event.text` directly.\n if (depth === 0)\n events.push({ kind: 'user-prompt', text: block.text, turnId: turn.id })\n }\n else if (block.type === 'tool_result') {\n const tool = toolByCallId.get(block.callId)\n // Spawn tool-results duplicate the spawn-end marker's `Tokens: …`\n // line — and on pre-fix sessions the legacy \"N in / M out\" shape\n // disagrees with the cache-aware spawn-end. Strip it at the\n // display layer; the persisted output stays intact.\n const raw = toolResultText(block.output)\n const text = tool === 'spawn' ? stripSpawnTokensLine(raw) : raw\n events.push({\n kind: 'tool-result',\n text,\n ...(tool ? { tool } : {}),\n ...tag,\n })\n }\n }\n lastDepthAtEmission = depth\n continue\n }\n\n if (turn.role === 'assistant') {\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim()) {\n // Persisted turns are finalized — `streaming: false` so the markdown\n // renderer commits to the final layout (no heal pass needed).\n events.push({ kind: 'markdown', text: block.text, streaming: false, ...tag })\n }\n else if (block.type === 'tool_call') {\n events.push({\n kind: 'tool',\n text: toolCallPreview(block.name, block.input),\n tool: block.name,\n ...tag,\n })\n }\n }\n lastDepthAtEmission = depth\n }\n }\n\n // Drain any trailing open child runs (e.g. session ended mid-spawn). Pop\n // innermost-first so depth ≥ 2 chains emit `spawn-end` in the right order.\n while (openStack.length > 0) {\n const run = openStack.pop()!\n pushSpawnEnd(run)\n }\n\n return events\n}\n\n/** Shared formatter for the `↳ name(args)` line shown on tool calls. */\nexport function toolCallPreview(name: string, input: Record<string, unknown>): string {\n const args = JSON.stringify(input)\n return args && args !== '{}' ? `${name}(${args})` : name\n}\n\n/** Render tool output as plain text, whether it's a string or structured content. */\nexport function toolResultText(output: string | ToolResultContent[]): string {\n return typeof output === 'string' ? output : toolResultToText(output)\n}\n\n/**\n * Strip the `Tokens: …` line from a spawn tool-result. The spawn-end marker\n * displayed right above already shows the same stats; keeping the line in the\n * rendered tool-result body just produces a visible duplicate (and, on\n * reloaded pre-fix sessions, an *inconsistent* duplicate — the persisted line\n * uses the old `13 in / 4075 out` shape while the freshly synthesized\n * spawn-end uses the cache-aware `in 92615 (cache 92602) / 4075 out` shape).\n *\n * Display-only: the persisted tool_result content is untouched, so the LLM\n * still sees the full string in its context window. Anchored to start-of-line\n * and matches both `Tokens: 13 in / 4075 out` (legacy) and `Tokens: in 13 …`\n * (post-`formatTokenUsage`) shapes.\n */\nexport function stripSpawnTokensLine(text: string): string {\n // Strict: only strip when the `Tokens: …` line is **immediately preceded\n // by** the spawn-tool header (`[sub-agent <id>] (Completed|Aborted|Failed|Timed) …`).\n // The previous `^Tokens:` multi-line regex would eat any body line that\n // happened to start with `Tokens:` — e.g. a model summarizing\n // \"Tokens: …\" inside its response. Anchored matching keeps the strip\n // local to the spawn header even if the model's response continues with\n // arbitrary content.\n //\n // `m` + `g` so every header in a multi-spawn result is handled, not just\n // the one at index 0. Today every spawn tool-result is single-spawn so\n // `g` is a no-op; the multiline anchor lets a future feature concatenating\n // multiple spawn outputs into one body strip every header's tokens line.\n return text.replace(\n /(^\\[sub-agent [^\\n]+\\n)Tokens:[^\\n]*\\n?/gm,\n '$1',\n )\n}\n\n/**\n * Deduplicated, in-order list of **parent-conversation** turn ids that appear\n * in a rendered transcript — the navigation index for the TUI's select-turn\n * mode. Subagent (`childId` set) events are deliberately skipped:\n *\n * - With `hideSubagentOutput: true` (default), child events are filtered\n * out of the transcript by `isVisible`, so allowing the user to \"select\"\n * them would highlight nothing and scroll to nowhere.\n * - With `hideSubagentOutput: false`, child events are visible but they're\n * nested execution detail; the user's mental model of a \"message\" is\n * the conversational exchange, not each spawn turn.\n *\n * Synthetic events (separator, spawn-start, spawn-end) have no `turnId` and\n * are skipped naturally. Pure function over the event stream — no settings\n * dependency, no host concerns — so it composes the same in TUI / SDK\n * consumers.\n */\nexport function selectableTurnIds(events: readonly StreamEvent[]): string[] {\n const seen = new Set<string>()\n const ordered: string[] = []\n for (const e of events) {\n if (!e.turnId)\n continue\n if (e.childId)\n continue // subagent turn — not part of the conversational flow\n if (seen.has(e.turnId))\n continue\n seen.add(e.turnId)\n ordered.push(e.turnId)\n }\n return ordered\n}\n\n/** Effective context size of the most recent assistant turn — drives the footer indicator. */\n/**\n * Walk from the end of `turns` and return the cache-aware input-token total\n * of the most recent **parent** (depth-0) assistant turn. Subagent turns\n * are skipped because their context window is the child's, not the parent's\n * — surfacing a subagent's `usage.input` as the footer's \"context used\"\n * after a session ends mid-spawn would be misleading. The next prompt\n * feeds the parent, so the parent's last view is what matters.\n *\n * `runs` is optional for backwards compatibility (older call sites and\n * tests that don't have ancestry info). Without it, this falls back to the\n * pre-fix behavior — depth is unknown so every assistant turn qualifies.\n */\nexport function lastContextSizeFromTurns(\n turns: SessionTurn[],\n runs: readonly SessionRun[] = [],\n): number {\n const childRunIds = new Set<string>()\n for (const run of runs) {\n if ((run.depth ?? 0) > 0)\n childRunIds.add(run.id)\n }\n for (let i = turns.length - 1; i >= 0; i--) {\n const turn = turns[i]\n if (turn.role !== 'assistant' || !turn.usage)\n continue\n if (turn.runId && childRunIds.has(turn.runId))\n continue\n return (turn.usage.input ?? 0)\n + (turn.usage.cacheRead ?? 0)\n + (turn.usage.cacheCreation ?? 0)\n }\n return 0\n}\n","/**\n * User-level config at `~/.{prefix}/config.json`. Holds global flags\n * that need to persist across projects — distinct from `state.json`\n * which can live PER-PROJECT under `<git-root>/.{prefix}/`.\n *\n * Single field today (`projectDb` opt-out); easy to grow without\n * threading every new flag through `runTui()`'s call site.\n *\n * Tolerant on disk errors: missing file → empty config (defaults\n * apply), malformed JSON → empty config (logged under `ZIDANE_DEBUG`).\n * Never throws on read — the host's bootstrap path can't afford it.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nexport interface UserConfig {\n /**\n * Master switch for project-scoped storage. When `true` (default),\n * launching inside a git repo auto-creates `<git-root>/.{prefix}/`\n * and stores `sessions.db` + `state.json` there. When `false`,\n * everything stays at the user level (`~/.{prefix}/`) regardless of\n * git presence — the classic single-store behavior.\n */\n projectDb?: boolean\n}\n\n/** Default user config — currently `{ projectDb: true }` (opt-in by default). */\nexport const DEFAULT_USER_CONFIG: Readonly<UserConfig> = Object.freeze({\n projectDb: true,\n})\n\n/**\n * Build the absolute path to `~/.{prefix}/config.json` given a user\n * directory (`<storageDir>/<prefix>`). Exported so callers debugging a\n * \"why isn't my override taking effect?\" can print the resolved path.\n */\nexport function userConfigPath(userDir: string): string {\n return resolve(userDir, 'config.json')\n}\n\n/**\n * Read + parse the user config file. Returns an empty config (defaults\n * apply) on any failure — the function never throws. Set `ZIDANE_DEBUG`\n * to surface parse failures to stderr.\n */\nexport function loadUserConfig(userDir: string): UserConfig {\n const path = userConfigPath(userDir)\n if (!existsSync(path))\n return {}\n try {\n const raw = readFileSync(path, 'utf8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n return normalizeUserConfig(parsed as Record<string, unknown>)\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[zidane/chat] user-config: failed to read \"${path}\": ${msg}\\n`)\n }\n return {}\n }\n}\n\n/**\n * Narrow an arbitrary JSON object to the `UserConfig` shape. Drops\n * unknown keys (so a future zidane version's config field doesn't\n * leak into the typed surface) and validates types per-field.\n */\nfunction normalizeUserConfig(raw: Record<string, unknown>): UserConfig {\n const out: UserConfig = {}\n if (typeof raw.projectDb === 'boolean')\n out.projectDb = raw.projectDb\n return out\n}\n","import type { Preset } from '../presets'\nimport type { SessionStore } from '../session'\nimport type { AgentRegistry } from './agents'\nimport type { ProviderAuth, ProviderKey } from './auth'\nimport type { ModelInfo, ProviderDescriptor } from './providers'\nimport type { StateStoreApi, TuiState } from './store'\nimport type { Picked, Settings } from './types'\nimport { existsSync, mkdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { resolve } from 'node:path'\nimport { BUILTIN_AGENTS, DEFAULT_AGENT_ID, resolveAgentId, singleAgentRegistry } from './agents'\nimport { detectAuth } from './auth'\nimport { applyApiKeyEnv, credentialsPath } from './credentials'\nimport { findGitRoot } from './project-root'\nimport { BUILTIN_PROVIDERS, modelsForDescriptor } from './providers'\nimport { createStateStore } from './store'\nimport { loadUserConfig } from './user-config'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Re-exported for callers who want `ModelInfo` from the same module as `ChatOptions`. */\nexport type { ModelInfo }\n\n/**\n * Provider registry — a map keyed by `descriptor.key`. Passed verbatim to the\n * chat engine; there is no implicit merge with built-ins. Hosts that want the\n * four default providers spread {@link BUILTIN_PROVIDERS}; hosts that only\n * want their own pass exactly their own.\n */\nexport type ProviderRegistry = Readonly<Record<string, ProviderDescriptor>>\n\n/**\n * Options accepted by `resolveConfig()` (and, transitively, by `runTui()` and\n * any future GUI launcher). Every field is optional — defaults boot a working\n * chat against the built-in providers with sessions stored under `~/.zidane/`.\n */\nexport interface ChatOptions {\n /**\n * Directory name used under `storageDir` for sessions + state.\n * Default: `'.zidane'` → `~/.zidane/sessions.db` + `~/.zidane/state.json`.\n * Pass e.g. `'.myapp'` for `~/.myapp/...`, or `'myapp'` for a visible dir.\n */\n prefix?: string\n /**\n * Storage root. Defaults to the user's home directory. Combined with\n * `prefix` to form the data directory.\n */\n storageDir?: string\n /**\n * Provider registry. Each value is a {@link ProviderDescriptor} carrying\n * label + factory + credential metadata. **No automatic merge** — hosts\n * pass exactly what they want.\n *\n * - Omitted → {@link BUILTIN_PROVIDERS} (anthropic + openai + openrouter + cerebras).\n * - `providers: BUILTIN_PROVIDERS` → same as omitted, but explicit.\n * - `providers: { anthropic: anthropicDescriptor }` → only Anthropic.\n * - `providers: { ...BUILTIN_PROVIDERS, mine: customDescriptor }` → built-ins + custom.\n * - `providers: { mine: customDescriptor }` → custom-only, **no built-ins**.\n */\n providers?: ProviderRegistry\n /**\n * Legacy single-agent preset (tools, system prompt, behavior). When set\n * **without** {@link ChatOptions.agents}, the resolver wraps this preset\n * into a one-entry {@link AgentRegistry} keyed `default` so existing\n * callers keep working and the picker is effectively hidden.\n *\n * Prefer `agents` for any multi-profile setup. Passing both throws.\n */\n preset?: Preset\n /**\n * Agent profile registry — keyed by `profile.id`. Each profile bundles a\n * preset with display metadata (label, description, accent). The TUI\n * shows a picker (Ctrl+A) when more than one profile is registered.\n *\n * - Omitted (and `preset` omitted) → {@link BUILTIN_AGENTS} (Build + Plan).\n * - Omitted but `preset` provided → single-entry registry around the preset.\n * - Provided → host owns the registry; pass exactly what you want.\n *\n * Hosts mixing built-ins with their own typically spread:\n * `agents: { ...BUILTIN_AGENTS, debug: myDebugProfile }`.\n */\n agents?: AgentRegistry\n /**\n * Id of the profile activated on first launch. Resolved against `agents`;\n * unknown ids fall through to the registry's first key. Persisted\n * `state.lastAgent` overrides this on subsequent launches.\n */\n defaultAgent?: string\n /**\n * Session store — either an instance or a factory called with the\n * resolved storage paths. **Required** at the chat layer so this module\n * stays platform-agnostic (no `bun:sqlite` baked in). Terminal hosts use\n * `createTuiStore` from `zidane/session/sqlite`; a future GUI host can\n * supply a different adapter (IndexedDB, remote API, in-memory) without\n * touching this layer.\n *\n * ```ts\n * import { createTuiStore } from 'zidane/session/sqlite'\n *\n * // Either pass a factory…\n * resolveConfig({ store: paths => createTuiStore(paths.db) })\n *\n * // …or an instance built ahead of time.\n * const store = createTuiStore('/path/to/sessions.db')\n * resolveConfig({ store })\n * ```\n */\n store: SessionStore | ((paths: ResolvedPaths) => SessionStore)\n /**\n * Project-scoped session storage. When `true` (default), launching\n * inside a git repo auto-creates `<git-root>/.{prefix}/` and stores\n * `sessions.db` + `state.json` there — so sessions, mcps.json, and\n * skills all live alongside the project they belong to. Credentials\n * and the cross-project safelist stay at `~/.{prefix}/` regardless.\n *\n * Precedence: `options.projectDb` > `~/.{prefix}/config.json`\n * `projectDb` > `true`. The env var `ZIDANE_PROJECT_DB=0` flips it\n * off too, for one-shot CLI invocations.\n *\n * When `false`, OR when not inside a git repo, everything lives at\n * `~/.{prefix}/` — the classic single-store layout.\n */\n projectDb?: boolean\n /**\n * Override for the project root. Defaults to `process.cwd()`. Hosts\n * embedding the TUI from a non-CLI context (tests, daemons) can pin\n * the cwd here so the git-root walk produces a stable result. Has no\n * effect when `projectDb` resolves to `false`.\n */\n cwd?: string\n}\n\n/** Resolved on-disk layout produced by {@link resolveConfig}. */\nexport interface ResolvedPaths {\n /**\n * Effective data root — equals `userDir` when project mode is off,\n * `projectDir` when it's on. Kept for callers that wrote against the\n * pre-project-db API; new code should reference `userDir` /\n * `projectDir` / the explicit `db` / `state` paths instead.\n */\n dir: string\n /**\n * User-scoped data root. Always `<storageDir>/<prefix>` (default\n * `~/.zidane/`). Credentials, the project-keyed safelist, and the\n * user-level `config.json` live here regardless of project mode —\n * they MUST NOT leak into a checked-in project directory.\n */\n userDir: string\n /**\n * Project-scoped data root. Set to `<git-root>/<prefix>` when project\n * mode resolved to ON and a git root was found. `null` otherwise.\n */\n projectDir: string | null\n /** Resolved `sessions.db` path. Lives under `projectDir` when active, else `userDir`. */\n db: string\n /** Resolved `state.json` path. Same scoping rule as `db`. */\n state: string\n}\n\n// ---------------------------------------------------------------------------\n// Resolved config\n// ---------------------------------------------------------------------------\n\nexport interface ResolvedConfig {\n prefix: string\n storageDir: string\n paths: ResolvedPaths\n providers: ProviderRegistry\n /**\n * Resolved profile registry. Always non-empty; the resolver throws when\n * the host passes an empty `agents` map (same contract as `providers`).\n */\n agents: AgentRegistry\n /** Id of the profile to activate on mount. Guaranteed to key into `agents`. */\n initialAgentId: string\n store: SessionStore\n stateStore: StateStoreApi\n /** Lookup by `ProviderKey` returning the available models for the picker. */\n modelsFor: (key: ProviderKey) => readonly ModelInfo[]\n initialState: TuiState\n initialSettings: Partial<Settings>\n resumeProvider: ProviderAuth | null\n initialPicked: Picked | null\n}\n\n/** Resolve user options into a fully-bound runtime config. Pure aside from disk reads. */\nexport function resolveConfig(options: ChatOptions): ResolvedConfig {\n const prefix = options.prefix ?? process.env.ZIDANE_PREFIX ?? '.zidane'\n const storageDir = options.storageDir ?? process.env.ZIDANE_STORAGE_DIR ?? homedir()\n const paths = resolveStoragePaths({\n prefix,\n storageDir,\n cwd: options.cwd ?? process.cwd(),\n projectDb: options.projectDb,\n })\n\n // `options.store` is required at the chat layer — the renderer (TUI, GUI)\n // injects an adapter so this module stays free of platform-specific\n // session-storage code. Accepts an instance or a factory; the factory\n // form lets callers wire their adapter to the resolved storage paths\n // without duplicating the prefix/storageDir logic above.\n if (!options.store)\n throw new Error('resolveConfig: `store` is required. Pass a `SessionStore` instance, or a factory — terminal hosts can use `createTuiStore` from `zidane/session/sqlite`.')\n const store: SessionStore = typeof options.store === 'function'\n ? options.store(paths)\n : options.store\n const stateStore = createStateStore(paths.state)\n const initialState = stateStore.load()\n\n // No implicit merge — host controls the registry entirely. Default is the\n // four built-ins; an explicit empty `{}` would mean \"no providers\" (the\n // wizard would have nothing to offer and the AuthScreen would refuse).\n const providers: ProviderRegistry = options.providers ?? BUILTIN_PROVIDERS\n\n // Agent registry resolution. Three paths, mutually exclusive:\n // - `agents` + `preset` both set → throw. The legacy `preset` would\n // get silently ignored otherwise and the host wouldn't notice.\n // - `agents` set → use verbatim (host owns the picker contents).\n // - `preset` set without `agents` → wrap into a single-entry registry\n // so the legacy single-agent path keeps working without code change.\n // - neither set → BUILTIN_AGENTS (Build + Plan).\n if (options.agents && options.preset) {\n throw new Error(\n 'resolveConfig: pass either `preset` (single-agent legacy) or `agents` '\n + '(multi-profile registry), not both. Migrate `preset` into an entry '\n + 'inside `agents` if you want both worlds.',\n )\n }\n const agents: AgentRegistry = options.agents\n ?? (options.preset ? singleAgentRegistry(options.preset) : BUILTIN_AGENTS)\n if (Object.keys(agents).length === 0) {\n throw new Error('resolveConfig: `agents` registry is empty — at least one profile is required.')\n }\n\n // Wire credentials *before* anything that might read them (resume detection,\n // any factory call). Sets `ZIDANE_CREDENTIALS_PATH` so the harness providers\n // find the file the chat engine manages, and injects stored API keys into\n // env so factories that resolve credentials eagerly (like `anthropic()`)\n // see them. Credentials ALWAYS live under `userDir` — they're host-wide\n // secrets, not per-project data, and must never leak into a checked-in\n // project `.{prefix}/` directory.\n process.env.ZIDANE_CREDENTIALS_PATH = credentialsPath(paths.userDir)\n applyApiKeyEnv(paths.userDir, providers)\n\n const modelsFor = makeModelsResolver(providers)\n\n const resumeProvider = resolveResumeProvider(initialState, providers, paths.userDir)\n const initialPicked = resumeProvider ? pickInitial(resumeProvider, providers, initialState) : null\n\n // Prefer persisted `lastAgent`, then the host's `defaultAgent`, then\n // `DEFAULT_AGENT_ID` (= 'build'), then the first registry key. The\n // last fallback is hit when the host's `defaultAgent` was renamed /\n // removed since the state file was written.\n const initialAgentId = resolveAgentId(\n agents,\n initialState.lastAgent,\n options.defaultAgent ?? DEFAULT_AGENT_ID,\n )\n if (!initialAgentId) {\n // Defensive — the empty-check above already guards, so this is unreachable.\n throw new Error('resolveConfig: failed to resolve an initial agent id from a non-empty registry.')\n }\n\n return {\n prefix,\n storageDir,\n paths,\n providers,\n agents,\n initialAgentId,\n store,\n stateStore,\n modelsFor,\n initialState,\n initialSettings: initialState.settings ?? {},\n resumeProvider,\n initialPicked,\n }\n}\n\n/**\n * Compute the on-disk layout for a single TUI session. Public so hosts\n * (CLI tools, tests, scripted bootstraps) can ask \"where will sessions\n * land?\" without going through `resolveConfig`'s full machinery.\n *\n * Decision logic for project mode (least → most specific):\n *\n * 1. Default: ON (`projectDb: true`).\n * 2. `~/.{prefix}/config.json` `projectDb` flag (when present).\n * 3. `ZIDANE_PROJECT_DB` env var (`'0'` / `'false'` / `'off'` → off,\n * anything else true if set).\n * 4. `options.projectDb` (host call site).\n *\n * Project mode activates only when the resolution above lands on\n * `true` AND a git repo is found above `cwd`. Outside git or with the\n * flag off, sessions + state stay at the user dir.\n *\n * Auto-creates the project `.{prefix}/` directory when mode resolves\n * to ON (silent — mirrors `ensureStateDir`). User dir is created\n * lazily by the state-store + credentials writers; we don't pre-touch\n * it here so a read-only invocation stays read-only.\n */\nexport function resolveStoragePaths(opts: {\n prefix: string\n storageDir: string\n cwd: string\n /** Host override for project mode. Beats the user-config + env-var sources. */\n projectDb?: boolean\n}): ResolvedPaths {\n const userDir = resolve(opts.storageDir, opts.prefix)\n\n // User-config + env-var are loaded eagerly so the precedence chain\n // is observable from a single call to this function; resolution is\n // cheap (one fs.existsSync + readFileSync at most).\n const userConfig = loadUserConfig(userDir)\n const envFlag = parseEnvFlag(process.env.ZIDANE_PROJECT_DB)\n // Defaults to OFF — the day-one experience stores everything at\n // `~/.{prefix}/` and uses `SessionData.projectRoot` tags to scope\n // the sessions list per-project. Per-project `.{prefix}/sessions.db`\n // (the `projectDb: true` path) is opt-in for callers who want a\n // checked-in / shareable session history.\n const projectDbEnabled = opts.projectDb\n ?? envFlag\n ?? userConfig.projectDb\n ?? false\n\n const gitRoot = projectDbEnabled ? findGitRoot(opts.cwd) : null\n const projectDir = gitRoot ? resolve(gitRoot, opts.prefix) : null\n\n if (projectDir && !existsSync(projectDir)) {\n try {\n mkdirSync(projectDir, { recursive: true })\n }\n catch (err) {\n // Don't crash the bootstrap on mkdir failure (permissions, R/O\n // FS) — fall back to user-scoped storage. Surface the cause\n // under ZIDANE_DEBUG so the operator can diagnose if curious.\n if (process.env.ZIDANE_DEBUG) {\n const cause = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[zidane/chat] project-db: mkdir \"${projectDir}\" failed: ${cause}\\n`)\n }\n return userOnlyPaths(userDir)\n }\n }\n\n const effective = projectDir ?? userDir\n return {\n dir: effective,\n userDir,\n projectDir,\n db: resolve(effective, 'sessions.db'),\n state: resolve(effective, 'state.json'),\n }\n}\n\nfunction userOnlyPaths(userDir: string): ResolvedPaths {\n return {\n dir: userDir,\n userDir,\n projectDir: null,\n db: resolve(userDir, 'sessions.db'),\n state: resolve(userDir, 'state.json'),\n }\n}\n\n/** Tri-state parse for env-var bools — explicit `null` keeps absence from forcing a default. */\nfunction parseEnvFlag(raw: string | undefined): boolean | undefined {\n if (raw === undefined)\n return undefined\n const v = raw.trim().toLowerCase()\n if (v === '0' || v === 'false' || v === 'off' || v === 'no')\n return false\n if (v === '1' || v === 'true' || v === 'on' || v === 'yes')\n return true\n return undefined\n}\n\nfunction makeModelsResolver(registry: ProviderRegistry): (key: ProviderKey) => readonly ModelInfo[] {\n return (key) => {\n const descriptor = registry[key]\n return descriptor ? modelsForDescriptor(descriptor) : []\n }\n}\n\nfunction resolveResumeProvider(\n state: TuiState,\n providers: ProviderRegistry,\n storageDir: string,\n): ProviderAuth | null {\n if (!state.lastProvider)\n return null\n if (!providers[state.lastProvider])\n return null\n return detectAuth(storageDir, providers).find(p => p.key === state.lastProvider && p.available) ?? null\n}\n\nfunction pickInitial(auth: ProviderAuth, providers: ProviderRegistry, state: TuiState): Picked | null {\n const descriptor = providers[auth.key]\n if (!descriptor)\n return null\n const remembered = state.lastModelByProvider?.[auth.key]\n // Prefer the remembered model, then the descriptor's declared default.\n // Only construct the provider as a last resort — some factories throw on\n // missing credentials, and we never want that to block the wizard.\n const model = remembered ?? descriptor.defaultModel ?? safeFactoryDefault(descriptor)\n return model ? { provider: auth, model } : null\n}\n\nfunction safeFactoryDefault(descriptor: ProviderDescriptor): string | undefined {\n try {\n return descriptor.factory().meta.defaultModel\n }\n catch {\n return undefined\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\nconst ConfigContext = createContext<ResolvedConfig | null>(null)\n\nexport function ConfigProvider({ config, children }: { config: ResolvedConfig, children: ReactNode }) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n}\n\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","import type { Theme } from '../theme'\n\n// ---------------------------------------------------------------------------\n// Catppuccin — four flavors built from the official palette.\n//\n// Hex values mirror catppuccin/palette v1.8.0 verbatim. The palette is the\n// data; `catppuccinTheme()` is the mapping into our `Theme` shape — accent\n// → role colors, semantic UI tokens (mantle = modal), and Tree-sitter syntax\n// token mappings that line up with the official Catppuccin styleguide.\n//\n// `Latte` is a light flavor (`base = #eff1f5`, `text = #4c4f69`) and only\n// looks correct in a light terminal — same caveat as the upstream VSCode\n// theme. The other three are dark.\n// ---------------------------------------------------------------------------\n\n/**\n * One Catppuccin flavor as a flat hex map. Mirrors the structure of\n * `catppuccin/palette` so the builder below maps named colors → roles\n * without any per-flavor branching.\n */\ninterface CatppuccinPalette {\n rosewater: string\n flamingo: string\n pink: string\n mauve: string\n red: string\n maroon: string\n peach: string\n yellow: string\n green: string\n teal: string\n sky: string\n sapphire: string\n blue: string\n lavender: string\n text: string\n subtext1: string\n subtext0: string\n overlay2: string\n overlay1: string\n overlay0: string\n surface2: string\n surface1: string\n surface0: string\n base: string\n mantle: string\n crust: string\n}\n\nconst LATTE: CatppuccinPalette = {\n rosewater: '#dc8a78',\n flamingo: '#dd7878',\n pink: '#ea76cb',\n mauve: '#8839ef',\n red: '#d20f39',\n maroon: '#e64553',\n peach: '#fe640b',\n yellow: '#df8e1d',\n green: '#40a02b',\n teal: '#179299',\n sky: '#04a5e5',\n sapphire: '#209fb5',\n blue: '#1e66f5',\n lavender: '#7287fd',\n text: '#4c4f69',\n subtext1: '#5c5f77',\n subtext0: '#6c6f85',\n overlay2: '#7c7f93',\n overlay1: '#8c8fa1',\n overlay0: '#9ca0b0',\n surface2: '#acb0be',\n surface1: '#bcc0cc',\n surface0: '#ccd0da',\n base: '#eff1f5',\n mantle: '#e6e9ef',\n crust: '#dce0e8',\n}\n\nconst FRAPPE: CatppuccinPalette = {\n rosewater: '#f2d5cf',\n flamingo: '#eebebe',\n pink: '#f4b8e4',\n mauve: '#ca9ee6',\n red: '#e78284',\n maroon: '#ea999c',\n peach: '#ef9f76',\n yellow: '#e5c890',\n green: '#a6d189',\n teal: '#81c8be',\n sky: '#99d1db',\n sapphire: '#85c1dc',\n blue: '#8caaee',\n lavender: '#babbf1',\n text: '#c6d0f5',\n subtext1: '#b5bfe2',\n subtext0: '#a5adce',\n overlay2: '#949cbb',\n overlay1: '#838ba7',\n overlay0: '#737994',\n surface2: '#626880',\n surface1: '#51576d',\n surface0: '#414559',\n base: '#303446',\n mantle: '#292c3c',\n crust: '#232634',\n}\n\nconst MACCHIATO: CatppuccinPalette = {\n rosewater: '#f4dbd6',\n flamingo: '#f0c6c6',\n pink: '#f5bde6',\n mauve: '#c6a0f6',\n red: '#ed8796',\n maroon: '#ee99a0',\n peach: '#f5a97f',\n yellow: '#eed49f',\n green: '#a6da95',\n teal: '#8bd5ca',\n sky: '#91d7e3',\n sapphire: '#7dc4e4',\n blue: '#8aadf4',\n lavender: '#b7bdf8',\n text: '#cad3f5',\n subtext1: '#b8c0e0',\n subtext0: '#a5adcb',\n overlay2: '#939ab7',\n overlay1: '#8087a2',\n overlay0: '#6e738d',\n surface2: '#5b6078',\n surface1: '#494d64',\n surface0: '#363a4f',\n base: '#24273a',\n mantle: '#1e2030',\n crust: '#181926',\n}\n\nconst MOCHA: CatppuccinPalette = {\n rosewater: '#f5e0dc',\n flamingo: '#f2cdcd',\n pink: '#f5c2e7',\n mauve: '#cba6f7',\n red: '#f38ba8',\n maroon: '#eba0ac',\n peach: '#fab387',\n yellow: '#f9e2af',\n green: '#a6e3a1',\n teal: '#94e2d5',\n sky: '#89dceb',\n sapphire: '#74c7ec',\n blue: '#89b4fa',\n lavender: '#b4befe',\n text: '#cdd6f4',\n subtext1: '#bac2de',\n subtext0: '#a6adc8',\n overlay2: '#9399b2',\n overlay1: '#7f849c',\n overlay0: '#6c7086',\n surface2: '#585b70',\n surface1: '#45475a',\n surface0: '#313244',\n base: '#1e1e2e',\n mantle: '#181825',\n crust: '#11111b',\n}\n\n/**\n * Compose a `Theme` from a Catppuccin palette flavor.\n *\n * Role-color picks follow the upstream Catppuccin styleguide:\n * - `mauve` is the canonical accent — used here as the brand color.\n * - `green` / `red` / `yellow` keep their universal semantic meaning.\n * - `blue` carries function / model identity (matches the VSCode plugin's\n * `support.function` mapping).\n * - `subtext1` / `overlay0` form the dim/mute pair (one step apart so the\n * two tiers stay visually distinct on every flavor).\n * - `surface1` / `overlay0` give the resting/active border pair.\n *\n * Syntax token mappings line up with the official Catppuccin token rules\n * (keyword = mauve, string = green, function = blue, type = yellow, …) so\n * code fences match what users see in their editor.\n */\nfunction catppuccinTheme(id: string, label: string, p: CatppuccinPalette): Theme {\n return {\n id,\n label,\n colors: {\n brand: p.mauve,\n accent: p.green,\n model: p.blue,\n warn: p.yellow,\n error: p.red,\n dim: p.subtext1,\n mute: p.overlay0,\n border: p.surface1,\n borderActive: p.overlay0,\n },\n select: {\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: p.mauve,\n textColor: p.subtext1,\n descriptionColor: p.overlay0,\n selectedDescriptionColor: p.subtext0,\n },\n surfaces: {\n // `mantle` is one shade darker than `base`, mirroring how the upstream\n // VSCode theme paints the sidebar against the editor — a subtle but\n // present elevation cue for the modal panel.\n modal: p.mantle,\n // Chip pills — skills on `mauve` (the canonical Catppuccin accent,\n // matches the brand role) and files on `blue` (function/identifier\n // accent in the upstream styleguide, so file mentions visually\n // rhyme with code references). Foreground is `base` for all\n // pills so dark flavors read light-on-dark and Latte's mauve/blue\n // are dark enough that `base` (`#eff1f5`) contrasts.\n chips: {\n default: { bg: p.mauve, fg: p.base },\n skills: { bg: p.mauve, fg: p.base },\n files: { bg: p.blue, fg: p.base },\n },\n // `surface0` is Catppuccin's canonical \"row highlight\" tier — one\n // step above `base`, designed for subtle elevation. Works on every\n // flavor including Latte (where it darkens against the light base).\n selection: p.surface0,\n },\n syntax: {\n 'default': { fg: p.text },\n\n // ---- markdown structure ----\n 'markup.heading': { fg: p.mauve, bold: true },\n 'markup.heading.1': { fg: p.mauve, bold: true },\n 'markup.heading.2': { fg: p.lavender, bold: true },\n 'markup.heading.3': { fg: p.blue, bold: true },\n 'markup.bold': { fg: p.text, bold: true },\n 'markup.strong': { fg: p.text, bold: true },\n 'markup.italic': { fg: p.text, italic: true },\n 'markup.link': { fg: p.sky, underline: true },\n 'markup.link.url': { fg: p.sky, underline: true },\n 'markup.list': { fg: p.peach },\n 'markup.raw': { fg: p.green },\n 'markup.raw.block': { fg: p.green },\n 'markup.quote': { fg: p.overlay2, italic: true },\n\n // ---- code ----\n 'keyword': { fg: p.mauve, bold: true },\n 'keyword.import': { fg: p.pink, bold: true },\n 'keyword.operator': { fg: p.sky },\n 'string': { fg: p.green },\n 'string.escape': { fg: p.pink, bold: true },\n 'character': { fg: p.teal },\n 'comment': { fg: p.overlay1, italic: true },\n 'number': { fg: p.peach },\n 'boolean': { fg: p.peach },\n 'constant': { fg: p.peach },\n 'constant.builtin': { fg: p.peach },\n 'function': { fg: p.blue },\n 'function.call': { fg: p.blue },\n 'function.method': { fg: p.blue },\n 'function.method.call': { fg: p.blue },\n 'function.builtin': { fg: p.blue },\n 'function.macro': { fg: p.teal },\n 'type': { fg: p.yellow },\n 'type.builtin': { fg: p.yellow },\n 'constructor': { fg: p.yellow },\n 'attribute': { fg: p.yellow },\n 'tag': { fg: p.lavender },\n 'variable': { fg: p.text },\n 'variable.builtin': { fg: p.red },\n 'variable.parameter': { fg: p.maroon, italic: true },\n 'variable.member': { fg: p.text },\n 'property': { fg: p.lavender },\n 'operator': { fg: p.sky },\n 'punctuation': { fg: p.overlay2 },\n 'punctuation.bracket': { fg: p.overlay2 },\n 'punctuation.delimiter': { fg: p.overlay2 },\n 'label': { fg: p.sapphire },\n },\n }\n}\n\nexport const CATPPUCCIN_MOCHA = catppuccinTheme('catppuccin-mocha', 'Catppuccin Mocha', MOCHA)\nexport const CATPPUCCIN_MACCHIATO = catppuccinTheme('catppuccin-macchiato', 'Catppuccin Macchiato', MACCHIATO)\nexport const CATPPUCCIN_FRAPPE = catppuccinTheme('catppuccin-frappe', 'Catppuccin Frappé', FRAPPE)\n// Latte assumes a *light* terminal background (`base = #eff1f5`,\n// `text = #4c4f69`). The label is suffixed so the picker reads correctly\n// regardless of where in the cycle it lands.\nexport const CATPPUCCIN_LATTE = catppuccinTheme('catppuccin-latte', 'Catppuccin Latte (light)', LATTE)\n","import type { Theme } from '../theme'\n\n// ---------------------------------------------------------------------------\n// Vaporwave — neon-on-dark, ported from this-fifo/vaporwave-theme-vscode.\n//\n// Hex values come straight from the upstream `vaporwave-color-theme.json`\n// (terminal ANSI table + tokenColors). The signature pink (#E95378) carries\n// the brand role; the bright pink (#FF71CE) is reserved for the keyword\n// token so headings and keywords visually rhyme without competing.\n// ---------------------------------------------------------------------------\n\nconst PALETTE = {\n pink: '#E95378',\n pinkBright: '#FF71CE',\n cyan: '#01CDFE',\n cyanBright: '#59E1E3',\n blue: '#94D0FF',\n green: '#29D398',\n greenBright: '#09F7A0',\n yellow: '#FFFB96',\n red: '#F43E5C',\n text: '#D5D8DA',\n /**\n * ~70% of `text` — used for captions / helper / \"dim\" text so that\n * secondary copy is visually distinct from primary copy. Matches the\n * upstream theme's sidebar foreground (`#b7c5d3`) tonality.\n */\n textDim: '#B7C5D3',\n textBright: '#EEFFFF',\n comment: '#BBBBBB',\n muted: '#6C6F93',\n surface: '#2E303E',\n panel: '#232530',\n}\n\nexport const VAPORWAVE_THEME: Theme = {\n id: 'vaporwave',\n label: 'Vaporwave',\n colors: {\n brand: PALETTE.pink,\n accent: PALETTE.greenBright,\n model: PALETTE.blue,\n warn: PALETTE.yellow,\n error: PALETTE.red,\n dim: PALETTE.textDim,\n mute: PALETTE.muted,\n border: PALETTE.surface,\n borderActive: PALETTE.muted,\n },\n select: {\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: PALETTE.pink,\n textColor: PALETTE.text,\n descriptionColor: PALETTE.muted,\n selectedDescriptionColor: PALETTE.text,\n },\n surfaces: {\n // Upstream uses `#232530` for editor widgets / dropdowns — same elevation\n // role as our modal panels.\n modal: PALETTE.panel,\n // Chip pills — skills on the signature vaporwave pink (brand role)\n // and files on cyan (the upstream's secondary neon accent). Both\n // read unmistakably against the dark panel foreground.\n chips: {\n default: { bg: PALETTE.pink, fg: PALETTE.panel },\n skills: { bg: PALETTE.pink, fg: PALETTE.panel },\n files: { bg: PALETTE.cyan, fg: PALETTE.panel },\n },\n // `surface` is one shade above `panel` — a quiet purple-grey lift\n // that reads as \"active row\" without clashing with the neon pink.\n selection: PALETTE.surface,\n },\n syntax: {\n 'default': { fg: PALETTE.text },\n\n // ---- markdown structure ----\n 'markup.heading': { fg: PALETTE.pink, bold: true },\n 'markup.heading.1': { fg: PALETTE.pink, bold: true },\n 'markup.heading.2': { fg: PALETTE.pinkBright, bold: true },\n 'markup.heading.3': { fg: PALETTE.blue, bold: true },\n 'markup.bold': { fg: PALETTE.textBright, bold: true },\n 'markup.strong': { fg: PALETTE.textBright, bold: true },\n 'markup.italic': { fg: PALETTE.greenBright, italic: true },\n 'markup.link': { fg: PALETTE.yellow, underline: true },\n 'markup.link.url': { fg: PALETTE.yellow, underline: true },\n 'markup.list': { fg: PALETTE.textBright },\n 'markup.raw': { fg: PALETTE.yellow },\n 'markup.raw.block': { fg: PALETTE.yellow },\n 'markup.quote': { fg: PALETTE.yellow, italic: true },\n\n // ---- code (mirrors the upstream tokenColors mapping) ----\n 'keyword': { fg: PALETTE.pinkBright, bold: true },\n 'keyword.import': { fg: PALETTE.pinkBright, bold: true },\n 'keyword.operator': { fg: PALETTE.comment },\n 'string': { fg: PALETTE.yellow },\n 'string.escape': { fg: PALETTE.greenBright, bold: true },\n 'character': { fg: PALETTE.yellow },\n 'comment': { fg: PALETTE.comment, italic: true },\n 'number': { fg: PALETTE.textBright },\n 'boolean': { fg: PALETTE.textBright },\n 'constant': { fg: PALETTE.textBright },\n 'constant.builtin': { fg: PALETTE.textBright },\n 'function': { fg: PALETTE.greenBright },\n 'function.call': { fg: PALETTE.greenBright },\n 'function.method': { fg: PALETTE.greenBright },\n 'function.method.call': { fg: PALETTE.greenBright },\n 'function.builtin': { fg: PALETTE.greenBright },\n 'function.macro': { fg: PALETTE.greenBright },\n 'type': { fg: PALETTE.greenBright },\n 'type.builtin': { fg: PALETTE.greenBright },\n 'constructor': { fg: PALETTE.greenBright },\n 'attribute': { fg: PALETTE.yellow },\n 'tag': { fg: PALETTE.blue },\n 'variable': { fg: PALETTE.blue },\n 'variable.builtin': { fg: PALETTE.cyanBright },\n 'variable.parameter': { fg: PALETTE.text },\n 'variable.member': { fg: PALETTE.blue },\n 'property': { fg: PALETTE.blue },\n 'operator': { fg: PALETTE.comment },\n 'punctuation': { fg: PALETTE.comment },\n 'punctuation.bracket': { fg: PALETTE.textBright },\n 'punctuation.delimiter': { fg: PALETTE.comment },\n 'label': { fg: PALETTE.cyan },\n },\n}\n","/**\n * Renderer-agnostic theme system.\n *\n * A `Theme` bundles every variable that can change visually between\n * \"themes\" — colors, select-row styling, code/markdown syntax highlight\n * tokens, panel backgrounds. Plain JSON: no OpenTUI dependency, no React,\n * no functions. The TUI consumes the theme by reading from `useTheme()`\n * and converting hex strings into OpenTUI's `RGBA`/`SyntaxStyle`; a future\n * GUI consumes the same theme by converting into CSS variables or Tailwind\n * tokens.\n *\n * Components should always read theme values through `useTheme()` /\n * `useColors()` so a runtime theme switch (Settings → Theme) re-paints\n * the whole tree. Importing a raw theme object directly bypasses the\n * context and pins the component to a single look.\n *\n * Built-in flavors (Catppuccin, Vaporwave) live in `./themes/`. This file\n * just defines the shape + the default theme + the registry.\n */\n\nimport {\n CATPPUCCIN_FRAPPE,\n CATPPUCCIN_LATTE,\n CATPPUCCIN_MACCHIATO,\n CATPPUCCIN_MOCHA,\n} from './themes/catppuccin'\nimport { VAPORWAVE_THEME } from './themes/vaporwave'\n\n/** Role-named color palette. Reused throughout the UI for text, borders, accents. */\nexport interface ThemeColors {\n /** Primary accent — selected text, brand emphasis (e.g. `▶` markers). */\n brand: string\n /** Success / progress accent — used sparingly. */\n accent: string\n /** Model badge accent (provider/model name in the footer, links). */\n model: string\n /** Warnings — busy spinner, approval picker border, esc hints. */\n warn: string\n /** Errors — `✗` prefix on error events, validation messages. */\n error: string\n /** Secondary text — captions, helper text, dim transcript lines. */\n dim: string\n /** Tertiary text — separators, descriptions, the `┃` tool-result bar. */\n mute: string\n /** Resting border color. */\n border: string\n /** Border color on focused / active elements. */\n borderActive: string\n}\n\n/** Select-component styling. Plain prop shape; both OpenTUI's `<select>` and any GUI equivalent can consume this. */\nexport interface ThemeSelect {\n backgroundColor: string\n focusedBackgroundColor: string\n selectedBackgroundColor: string\n selectedTextColor: string\n textColor: string\n descriptionColor: string\n selectedDescriptionColor: string\n}\n\n/** Background + foreground pair for a completion-reference chip pill. */\nexport interface ChipColor {\n /** Background paint — the pill itself. High-saturation by convention. */\n bg: string\n /** Foreground paint — the chip text. Pre-paired with {@link ChipColor.bg} for legibility. */\n fg: string\n}\n\n/**\n * Chip colors keyed by completion-provider id (`'skills'`, `'files'`, …).\n * `default` is the required fallback used by any provider id without an\n * explicit entry — keeps host-registered providers themable out of the\n * box. Use {@link resolveChipColor} to perform the kind-specific → default\n * lookup rather than indexing the map directly.\n */\n/**\n * Map of provider id → chip color pair. `default` is required so callers\n * always have a fallback; every other key is optional so direct indexing\n * (`chips['unknown']`) surfaces as `ChipColor | undefined` and nudges\n * consumers toward {@link resolveChipColor}, which encodes the fallback.\n */\nexport type ChipColorMap = { default: ChipColor } & Partial<Record<string, ChipColor>>\n\n/**\n * Look up the chip color pair for a provider id, falling back to\n * `chips.default` when the theme has no kind-specific entry. Mirrors\n * `resolveChipStyleId` so both rendering surfaces (submitted echo +\n * live textarea) share one canonical lookup rule.\n */\nexport function resolveChipColor(chips: ChipColorMap, providerId: string): ChipColor {\n return chips[providerId] ?? chips.default\n}\n\n/** Panel / surface backgrounds. */\nexport interface ThemeSurfaces {\n /** Background of an overlaid modal panel (settings, model picker, …). */\n modal: string\n /**\n * Completion-chip color pairs keyed by provider id. Built-in themes\n * ship distinct `skills` + `files` tones so the two reference kinds\n * read differently in both the submitted echo and the live textarea.\n */\n chips: ChipColorMap\n /**\n * Background paint for the selected turn's events in select-turn mode.\n * Subtle, low-saturation lift from the terminal default so the whole\n * span of the selected turn reads as one continuous highlighted block\n * without overpowering the foreground text. Inverse-tinted on the\n * light flavor (Latte) so the same visual role works there.\n */\n selection: string\n}\n\n/**\n * One entry in a `SyntaxStyles` map — what OpenTUI's `SyntaxStyle.fromStyles`\n * accepts, minus the `RGBA` conversion. Renderer-agnostic JSON.\n */\nexport interface SyntaxTokenStyle {\n fg?: string\n bg?: string\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n}\n\n/**\n * Map of Tree-sitter / markdown capture group → token style.\n *\n * Two flavours of keys live in the same table:\n * - `markup.*` — the markdown structure captures (heading, bold, italic,\n * list, quote, raw inline + block, link). OpenTUI's markdown parser\n * emits these for the markdown text itself.\n * - bare token names (`keyword`, `string`, `function`, …) — emitted by\n * the embedded language Tree-sitter grammars when a code fence\n * declares a language. OpenTUI passes the same `SyntaxStyle` down to\n * the fenced-code renderable, so this map drives both surfaces.\n */\nexport type SyntaxStyles = Record<string, SyntaxTokenStyle>\n\n/** Full theme bundle. */\nexport interface Theme {\n /** Stable identifier, used as the key in `BUILTIN_THEMES` and `Settings.theme`. */\n id: string\n /** Human-readable label shown in the settings picker. */\n label: string\n colors: ThemeColors\n select: ThemeSelect\n surfaces: ThemeSurfaces\n syntax: SyntaxStyles\n}\n\n// ---------------------------------------------------------------------------\n// Default theme — Bolt palette mapped to our role-named slots\n//\n// Source: Bolt design-system primitives + dark-theme semantic tokens. We\n// pick the dark variants because the TUI almost always renders on dark\n// terminals; the few light-terminal users get the same hues a step\n// brighter / cooler than the comparable light-theme tokens, which still\n// reads as \"brand blue / orange / red / green\" and remains accessible.\n//\n// Color-role mapping. Every \"accent\" slot stays inside the Bolt brand\n// cascade (brand-300 → brand-400 → brand-500 → brand-600) so the\n// chrome reads as one cool family rather than a mosaic of warm + cool\n// dots. Warm tones (orange, red) are reserved for genuine error /\n// danger contexts only:\n//\n// brand-600 borderActive #1488fc focused / active rims\n// brand-500 brand #2ba6ff signature accent — session title, ▶\n// brand-400 warn #53c4ff shortcut keys, spinner, accent numbers\n// brand-300 model #8adaff model id, links, soft brand sibling\n// green-500 accent #22c55e success — spawn markers, ✓\n// red-500 error #ef4444 errors, ✗ glyphs\n// neutral-400 dim #a3a3ac secondary text\n// neutral-600 mute #525258 separators, `┃` bar\n// neutral-700 border #3c3c41 resting borders\n//\n// `warn` lives in the brand cascade rather than orange so the hint\n// rows (which use it for every shortcut key) don't strafe orange dots\n// across an otherwise cool surface. The spinner + approval picker\n// pick up the same brand-tinted accent — reads as \"working / attention\"\n// without competing with the red of `error`.\n//\n// Defined as a local constant first so `BUILTIN_THEMES` references it\n// by name, and so the markdown-structure entries can interpolate the\n// role-named colors without redeclaring them.\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_COLORS: ThemeColors = {\n brand: '#2ba6ff', // bolt brand-500\n accent: '#22c55e', // bolt green-500 (success)\n model: '#8adaff', // bolt brand-300 (soft sky)\n warn: '#53c4ff', // bolt brand-400 (cool accent — keys, spinner, \"attention\")\n error: '#ef4444', // bolt red-500 (danger)\n dim: '#a3a3ac', // bolt neutral-400\n mute: '#525258', // bolt neutral-600\n border: '#3c3c41', // bolt neutral-700\n borderActive: '#1488fc', // bolt brand-600\n}\n\nexport const DEFAULT_THEME: Theme = {\n id: 'default',\n label: 'Default',\n colors: DEFAULT_COLORS,\n select: {\n // Transparent backgrounds keep the highlight bar from filling with a\n // different color than the surrounding box. The `▶` marker + brand-\n // colored selected text carry the focus affordance instead.\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: DEFAULT_COLORS.brand,\n textColor: DEFAULT_COLORS.dim,\n descriptionColor: DEFAULT_COLORS.mute,\n selectedDescriptionColor: DEFAULT_COLORS.dim,\n },\n surfaces: {\n // Bolt's `--surface` dark — neutral-950.\n modal: '#111114',\n // Skills (slash-commands, user verbs) get the signature bold-brand\n // pill — saturated `brand-500` background with very light brand\n // text on top: primary action, attention-getting. Files (resource\n // references) get the *inverted* sibling — bright `brand-300`\n // background with deep `brand-950` text. Both pills are vibrant\n // and clearly in the brand family, but they sit on opposite ends\n // of the saturation/lightness axis, so they read as deliberately\n // paired counterparts rather than two pills competing for the\n // same visual role.\n chips: {\n default: { bg: DEFAULT_COLORS.brand, fg: '#eef9ff' },\n skills: { bg: DEFAULT_COLORS.brand, fg: '#eef9ff' },\n files: { bg: '#8adaff', fg: '#122f59' },\n },\n // Translucent brand on dark surface — Bolt's `--brandContainer`\n // (dark) is `#2ba6ff1a` (10% over surface). The terminal can't\n // alpha-blend, so we pre-mix ~15% brand-500 over neutral-950 and\n // bake the result as a solid hex. Reads as \"this turn is active\"\n // without overpowering the text foreground.\n selection: '#142737',\n },\n syntax: {\n // ---- markdown structure ----\n // Headings cascade through brand-500 → brand-400 → brand-300 so the\n // outline reads as one related family rather than three random\n // accents. Body text rides `--textPrimary` (`#fefeff`).\n 'default': { fg: '#fefeff' },\n 'markup.heading': { fg: '#2ba6ff', bold: true },\n 'markup.heading.1': { fg: '#2ba6ff', bold: true },\n 'markup.heading.2': { fg: '#53c4ff', bold: true },\n 'markup.heading.3': { fg: '#8adaff', bold: true },\n 'markup.bold': { fg: '#ffffff', bold: true },\n 'markup.strong': { fg: '#ffffff', bold: true },\n 'markup.italic': { fg: '#fefeff', italic: true },\n 'markup.link': { fg: DEFAULT_COLORS.model, underline: true },\n 'markup.link.url': { fg: DEFAULT_COLORS.model, underline: true },\n // Bullet markers in markdown rides the warm `orange-400` rather\n // than `warn` (which is now cool brand-400). Lists are dense and\n // benefit from a hue that stands apart from headings + body text,\n // and warm tones are appropriate inside the rendered-markdown\n // surface where they don't accumulate into the chrome.\n 'markup.list': { fg: '#fb923c' },\n // Inline / block `code` rides brand-200 — readable on dark, distinct\n // from regular text + headings without leaving the brand family.\n 'markup.raw': { fg: '#bae7ff' },\n 'markup.raw.block': { fg: '#bae7ff' },\n 'markup.quote': { fg: DEFAULT_COLORS.dim, italic: true },\n\n // ---- code (Tree-sitter language tokens inside fenced blocks) ----\n // Bolt has no purple/violet step, so we lean on green for functions\n // (callable / \"alive\") and reserve orange for types + parameters.\n // String constants live in brand-200, numeric/property tokens in\n // brand-300 so they read as one cool family against the warm types.\n 'keyword': { fg: '#f87171', bold: true }, // dangerHighlight\n 'keyword.import': { fg: '#f87171', bold: true },\n 'keyword.operator': { fg: '#f87171' },\n 'string': { fg: '#bae7ff' }, // brand-200\n 'string.escape': { fg: '#bae7ff', bold: true },\n 'character': { fg: '#bae7ff' },\n 'comment': { fg: '#73737b', italic: true }, // neutral-500\n 'number': { fg: '#8adaff' }, // brand-300\n 'boolean': { fg: '#8adaff' },\n 'constant': { fg: '#8adaff' },\n 'constant.builtin': { fg: '#8adaff' },\n 'function': { fg: '#86efac' }, // green-300\n 'function.call': { fg: '#86efac' },\n 'function.method': { fg: '#86efac' },\n 'function.method.call': { fg: '#86efac' },\n 'function.builtin': { fg: '#86efac' },\n 'function.macro': { fg: '#86efac' },\n 'type': { fg: '#fb923c' }, // warningHighlight (orange-400)\n 'type.builtin': { fg: '#fb923c' },\n 'constructor': { fg: '#fb923c' },\n 'attribute': { fg: '#fb923c' },\n 'tag': { fg: '#4ade80' }, // green-400\n 'variable': { fg: '#fefeff' },\n 'variable.builtin': { fg: '#8adaff' },\n 'variable.parameter': { fg: '#fb923c' },\n 'variable.member': { fg: '#8adaff' },\n 'property': { fg: '#8adaff' },\n 'operator': { fg: '#f87171' },\n 'punctuation': { fg: DEFAULT_COLORS.mute },\n 'punctuation.bracket': { fg: '#fefeff' },\n 'punctuation.delimiter': { fg: '#d4d4dd' }, // neutral-300\n 'label': { fg: '#8adaff' },\n },\n}\n\n/**\n * Built-in theme registry, keyed by `theme.id`. The TUI looks up the active\n * theme here using `Settings.theme`; unknown ids fall back to\n * `DEFAULT_THEME`. Hosts can extend this by passing additional themes to a\n * future `runTui({ themes })` option (not yet wired).\n *\n * Insertion order is the picker cycle order — keep `default` first so a\n * fresh install (no `theme` in `state.json`) sees the familiar yellow theme\n * before the others.\n */\nexport const BUILTIN_THEMES: Readonly<Record<string, Theme>> = {\n [DEFAULT_THEME.id]: DEFAULT_THEME,\n [CATPPUCCIN_MOCHA.id]: CATPPUCCIN_MOCHA,\n [CATPPUCCIN_MACCHIATO.id]: CATPPUCCIN_MACCHIATO,\n [CATPPUCCIN_FRAPPE.id]: CATPPUCCIN_FRAPPE,\n [CATPPUCCIN_LATTE.id]: CATPPUCCIN_LATTE,\n [VAPORWAVE_THEME.id]: VAPORWAVE_THEME,\n}\n\n/** Resolve a theme id to its full `Theme`, falling back to default on unknown ids. */\nexport function resolveTheme(id: string | undefined): Theme {\n if (id && BUILTIN_THEMES[id])\n return BUILTIN_THEMES[id]\n return DEFAULT_THEME\n}\n","import type { ReactNode } from 'react'\nimport type { Settings } from './types'\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport { BUILTIN_THEMES, DEFAULT_THEME } from './theme'\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_SETTINGS: Settings = {\n showThinking: true,\n showToolCalls: true,\n showToolResults: true,\n safeMode: true,\n hideSubagentOutput: true,\n theme: DEFAULT_THEME.id,\n // Off by default — the sessions list filters to the current project\n // (git root / cwd) so the user only sees the conversations relevant\n // to where they're working. Toggle on to inspect or jump across\n // projects, plus see pre-tagging legacy sessions.\n showAllProjects: false,\n // `enabledSkills` / `enabledMcps` deliberately default to `undefined`\n // (NOT `[]`): every discovered skill + server is enabled until the user\n // explicitly toggles. The Settings modal seeds the allowlist with the\n // current discovery on first toggle so future additions don't auto-opt\n // out.\n}\n\n// ---------------------------------------------------------------------------\n// SettingsProvider — single source of truth for the transcript filters.\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\ninterface 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}\n\nconst SettingsContext = createContext<SettingsContextValue | null>(null)\n\nexport function SettingsProvider({\n initial,\n onChange,\n children,\n}: {\n initial: Settings\n onChange?: (settings: Settings) => void\n children: ReactNode\n}) {\n const [settings, setSettings] = useState<Settings>(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 => ({ ...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 : { ...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\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/**\n * Keys of `Settings` whose value type is exactly `boolean`. Used to type\n * the toggle table so `SETTINGS_TOGGLES.key` only ever points to a\n * boolean-valued setting — string- and array-valued settings (theme,\n * enabledSkills, …) live in `SETTINGS_CHOICES` or actions.\n *\n * `-?` strips optionality from the mapped output so `Settings[K]` doesn't\n * silently broaden into `T | undefined` and short-circuit the `extends\n * boolean` check via `undefined`.\n */\nexport type BooleanSettingKey = {\n [K in keyof Settings]-?: Settings[K] extends boolean ? K : never\n}[keyof Settings]\n\n/**\n * Static description of every togglable setting, in display order. The TUI's\n * `SettingsModal` and any future GUI settings panel build their row list\n * from this so labels/descriptions stay in one place.\n */\nexport interface SettingsToggle {\n key: BooleanSettingKey\n label: string\n description: string\n}\n\nexport const SETTINGS_TOGGLES: readonly SettingsToggle[] = [\n { key: 'safeMode', label: 'Safe mode', description: 'prompt before each tool call (unless safelisted)' },\n { key: 'hideSubagentOutput', label: 'Hide subagent output', description: 'collapse subagent runs to start/done markers' },\n { key: 'showAllProjects', label: 'All projects', description: 'list sessions from every project (off = current only)' },\n { key: 'showThinking', label: 'Thinking blocks', description: 'agent reasoning shown inline' },\n { key: 'showToolCalls', label: 'Tool calls', description: 'the ↳ name(args) lines' },\n { key: 'showToolResults', label: 'Tool outputs', description: 'the ┃ result blocks under tool calls' },\n]\n\n/**\n * String-valued settings that pick between a known set of options. The\n * `SettingsModal` renders these as one row showing the active label, where\n * `enter` cycles to the next option (wrapping back to the first). Same\n * shape any GUI settings panel can consume — render the options array as a\n * dropdown or segmented control.\n *\n * Keep `options` in stable display order; cycling depends on it.\n */\nexport interface SettingsChoice<K extends keyof Settings = keyof Settings> {\n key: K\n label: string\n description: string\n options: readonly { value: Settings[K], label: string }[]\n}\n\nexport const SETTINGS_CHOICES: readonly SettingsChoice[] = [\n {\n key: 'theme',\n label: 'Theme',\n description: 'colors + markdown / syntax styles',\n options: Object.values(BUILTIN_THEMES).map(t => ({ value: t.id, label: t.label })),\n },\n]\n","/**\n * Renderer-agnostic state machine for an \"enabled allowlist\" — the shape\n * used by `settings.enabledSkills` / `settings.enabledMcps` (and any future\n * `enabledX` toggle backed by a `Settings` field).\n *\n * Semantics, kept identical across all three call sites:\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'\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 */\nexport function useEnabledToggleSet<T>(opts: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n}): EnabledToggleSet {\n const { settings, setSetting } = useSettings()\n const { catalog, keyOf, settingKey } = 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.\n const catalogKeys = useMemo(\n () => new Set(catalog.map(keyOf)),\n [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 correctness for free + sub-second performance on\n * even huge monorepos. Falls back to a depth-limited filesystem walk when\n * git isn't available or the directory isn't a repo.\n *\n * Pure: 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'\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. */\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\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 /** Cap on returned entries. Default: 10,000. Bigger lists fall back to a truncated set. */\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`. Tries `git ls-files` first;\n * on failure (no git, not a repo, abort) walks the fs with a hand-rolled\n * 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 */\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 const cause = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[zidane/chat] git ls-files failed (${cause}) — falling back to fs walk\\n`)\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 const cause = fsErr instanceof Error ? fsErr.message : String(fsErr)\n process.stderr.write(`[zidane/chat] fs walk failed: ${cause}\\n`)\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`, the\n * user's global excludes file, AND skip files marked `assume-unchanged`.\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\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 })\n if (out.length >= maxFiles)\n break\n }\n return out\n}\n","import { homedir } from 'node:os'\n\n/** Compact token formatter — 12_415 → \"12.4k\", 1_234_567 → \"1.23M\". */\nexport function fmtTokens(n: number): string {\n if (n < 1000)\n return String(n)\n if (n < 1_000_000)\n return `${(n / 1000).toFixed(n < 10_000 ? 2 : 1)}k`\n return `${(n / 1_000_000).toFixed(2)}M`\n}\n\n/** Compact relative-time formatter — \"just now / 5m / 3h / 2d\". */\nexport function ageString(ts: number, now: number = Date.now()): string {\n const m = Math.floor((now - ts) / 60_000)\n if (m < 1)\n return 'just now'\n if (m < 60)\n return `${m}m ago`\n const h = Math.floor(m / 60)\n if (h < 24)\n return `${h}h ago`\n return `${Math.floor(h / 24)}d ago`\n}\n\n/** Six-char short form of a session id for headers and lists. */\nexport function shortId(id: string): string {\n return id.replace(/-/g, '').slice(0, 6)\n}\n\n/**\n * Compact an absolute path for display: replace the user's `$HOME`\n * prefix with `~` (so `/Users/yael/Code/zidane` → `~/Code/zidane`),\n * and optionally left-truncate with an ellipsis when the result\n * still exceeds `maxWidth` (so the path's *tail* — the part the user\n * recognizes — stays visible: `…/zidane` rather than `/Users/yaeluil…`).\n *\n * `maxWidth` is the maximum *display width* in cells. Omit to skip\n * truncation. Paths outside `$HOME` are returned verbatim modulo\n * truncation. The ellipsis (`…`) counts as one cell.\n *\n * `home` overrides `os.homedir()` for tests; production callers leave\n * it undefined and pay the cheap one-syscall lookup per call.\n */\nexport function compactPath(path: string, maxWidth?: number, home?: string): string {\n const h = home ?? homedir()\n let display = path\n if (h) {\n if (path === h)\n display = '~'\n else if (path.startsWith(`${h}/`))\n display = `~${path.slice(h.length)}`\n }\n if (maxWidth !== undefined && maxWidth > 1 && display.length > maxWidth) {\n // Left-truncate: keep the rightmost `maxWidth - 1` chars and\n // prepend `…`. The right side of a path is the distinctive\n // bit (repo name, leaf folder); the left side is the boilerplate\n // (home, common parents) we're happy to drop.\n return `…${display.slice(display.length - maxWidth + 1)}`\n }\n return display\n}\n","/**\n * One-shot title generation for an existing session — feeds the last N\n * turns to the provider with a \"write me a title\" system prompt and\n * returns the cleaned-up result.\n *\n * Provider-agnostic: works with anything implementing the renderer-\n * shared `Provider` interface. No tools, no session mutation, no\n * caching — this is a cheap, isolated completion that should be safe to\n * fire even mid-session without touching the live agent loop.\n */\n\nimport type { Provider } from '../providers'\nimport type { SessionTurn } from '../types'\n\n/** Hard cap on the result length. Anything longer is truncated client-side. */\nconst TITLE_MAX_CHARS = 60\n\n/** Default turn count fed to the model. 10 covers most exchanges without bloat. */\nconst DEFAULT_MAX_TURNS = 10\n\n/** Tokens budgeted for the model's reply. 64 fits any sane title with headroom. */\nconst TITLE_RESPONSE_MAX_TOKENS = 64\n\n/** Per-turn body cap when assembling the prompt — keeps the request payload tight. */\nconst PROMPT_TURN_CHAR_MAX = 2000\n\nexport interface GenerateSessionTitleOptions {\n provider: Provider\n /** Model id used for the call. */\n model: string\n /** Conversation history to summarize. Empty → throws synchronously. */\n turns: readonly SessionTurn[]\n /** Cap on how many trailing turns to feed the model. Defaults to 10. */\n maxTurns?: number\n /** Optional cancellation signal — forwarded to the provider's stream. */\n signal?: AbortSignal\n}\n\n/**\n * Drive the provider's `stream()` once and return a concise title for\n * the conversation represented by `turns`. Throws when:\n * - `turns` contains no text-bearing content (nothing to summarize),\n * - the provider stream completes with no output text,\n * - `signal` is aborted (rethrown verbatim).\n *\n * Output is trimmed, single-line, with surrounding quotes / trailing\n * punctuation stripped, and truncated to `TITLE_MAX_CHARS`.\n */\nexport async function generateSessionTitle({\n provider,\n model,\n turns,\n maxTurns = DEFAULT_MAX_TURNS,\n signal,\n}: GenerateSessionTitleOptions): Promise<string> {\n const slice = turns.slice(-Math.max(1, maxTurns))\n const transcript = renderTurnsForPrompt(slice)\n if (!transcript)\n throw new Error('No text content in the recent turns to summarize.')\n\n const system = [\n 'You generate concise, descriptive titles for chat conversations.',\n 'Reply with ONLY the title — no quotes, no markdown, no trailing punctuation,',\n 'no preamble (do not say \"Title:\" or similar).',\n `Aim for 3 to 7 words and stay under ${TITLE_MAX_CHARS} characters.`,\n ].join(' ')\n\n const userPrompt = `Generate a title for this conversation:\\n\\n${transcript}`\n\n let text = ''\n const result = await provider.stream(\n {\n model,\n system,\n tools: [],\n messages: [provider.userMessage(userPrompt)],\n maxTokens: TITLE_RESPONSE_MAX_TOKENS,\n ...(signal ? { signal } : {}),\n },\n {\n onText: (delta) => { text += delta },\n },\n )\n // Prefer the streamed-text accumulator (driven by deltas) but fall\n // back to the provider's already-concatenated `result.text` for\n // adapters that batch on the final frame instead of emitting deltas.\n const raw = text.trim().length > 0 ? text : result.text\n return cleanTitle(raw)\n}\n\n/**\n * Compact a turn list into a transcript-style prompt:\n *\n * user: …\n * assistant: …\n * user: …\n *\n * Tool calls and tool results are summarized to keep the request\n * payload small — the model needs the rough flow of the conversation,\n * not full JSON blobs. Per-turn text is clipped at `PROMPT_TURN_CHAR_MAX`\n * so a single huge code dump doesn't blow the budget.\n *\n * Returns an empty string when the slice has no text-bearing content\n * (e.g. a turn list that's only tool plumbing).\n */\nfunction renderTurnsForPrompt(turns: readonly SessionTurn[]): string {\n const lines: string[] = []\n for (const turn of turns) {\n const body = textBodyOf(turn)\n if (!body)\n continue\n const role = turn.role === 'assistant' ? 'assistant' : turn.role === 'user' ? 'user' : 'system'\n lines.push(`${role}: ${clip(body, PROMPT_TURN_CHAR_MAX)}`)\n }\n return lines.join('\\n\\n')\n}\n\nfunction textBodyOf(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim())\n parts.push(block.text.trim())\n else if (block.type === 'tool_call')\n parts.push(`[tool: ${block.name}]`)\n else if (block.type === 'tool_result')\n parts.push(`[tool result]`)\n }\n return parts.join(' ')\n}\n\n/**\n * Normalize a model-generated title into the shape we want to persist:\n *\n * - Collapse internal whitespace + take the first line only (some\n * models emit \"Title: foo\\n\\nReason: …\" despite instructions).\n * - Strip surrounding quote characters (`\"foo\"`, `'foo'`, `` `foo` ``).\n * - Strip a leading `Title:` / `title -` prefix if the model added one.\n * - Strip trailing punctuation (`.`, `!`, `?`) — titles read cleaner\n * without it.\n * - Truncate to `TITLE_MAX_CHARS` with a trailing `…` when over.\n *\n * Exported as `cleanTitle` so tests can pin the normalization rules\n * without going through a mock provider.\n */\nexport function cleanTitle(raw: string): string {\n let s = raw.split('\\n')[0]?.trim() ?? ''\n // Strip a \"Title:\" / \"Title -\" preamble the model sometimes adds.\n s = s.replace(/^\\s*title\\s*[:\\-–—]\\s*/i, '')\n // Strip surrounding matched quotes.\n if (s.length >= 2) {\n const first = s.charAt(0)\n const last = s.charAt(s.length - 1)\n const matched = (first === '\"' && last === '\"')\n || (first === '\\'' && last === '\\'')\n || (first === '`' && last === '`')\n || (first === '“' && last === '”')\n if (matched)\n s = s.slice(1, -1).trim()\n }\n // Trim trailing terminal punctuation — titles don't take periods.\n s = s.replace(/[.!?]+$/, '').trim()\n if (s.length === 0)\n throw new Error('Model returned an empty title.')\n return s.length > TITLE_MAX_CHARS ? `${s.slice(0, TITLE_MAX_CHARS - 1)}…` : s\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max)}…` : text\n}\n","/**\n * Shared search-path builder for project + user config discovery.\n *\n * Both Skills (`skills/<name>/SKILL.md`) and MCP servers (`mcps.json`)\n * follow the same first-found-wins order:\n *\n * 1. `{project-root}/.agents/<sub>` — project, agnostic convention\n * 2. `{project-root}/.{prefix}/<sub>` — project, zidane (`.zidane` default)\n * 3. `~/.agents/<sub>` — user, agnostic\n * 4. `~/.{prefix}/<sub>` — user, zidane\n *\n * `{project-root}` is the git root walked up from `cwd` when present,\n * else `cwd` itself. This pairs with the project-scoped session\n * storage (`resolveStoragePaths`) so all three project-aware surfaces\n * — sessions, mcps, skills — anchor at the same `.{prefix}/`.\n *\n * Centralizing the pattern keeps the four paths in one place — a third\n * discovery surface (hooks, rules, …) gets the same convention for free\n * and stays in lock-step on a prefix bump.\n */\n\nimport { homedir } from 'node:os'\nimport { resolve } from 'node:path'\nimport { findGitRoot } from './project-root'\n\n/** A discovered path tagged with whether it lives in the project or the user dir. */\nexport interface ProjectUserPath {\n path: string\n source: 'project' | 'user'\n}\n\n/**\n * Resolve the four search paths for a given subdirectory (`skills`,\n * `mcps.json`, …). Returns absolute paths regardless of whether they\n * exist — the caller chooses to `existsSync`-filter or feed straight\n * into a tolerant discovery routine.\n */\nexport function projectUserPaths(opts: {\n /** Subdirectory or filename relative to each search root. E.g. `'skills'`, `'mcps.json'`. */\n subPath: string\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** Storage prefix. Leading dot tolerated. Default: `'.zidane'`. */\n prefix?: string\n}): ProjectUserPath[] {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n // Anchor the project search at the git root when one exists — so a\n // subdir invocation (e.g. `cd repo/packages/foo`) still finds the\n // repo-level `.zidane/mcps.json`. Falls back to `cwd` outside git.\n const projectRoot = findGitRoot(cwd) ?? cwd\n // `prefix` arrives as `.zidane` in `ResolvedConfig`; trim the leading dot\n // for the symmetric `.agents` / `.zidane` convention.\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n return [\n { path: resolve(projectRoot, `.agents/${opts.subPath}`), source: 'project' },\n { path: resolve(projectRoot, `.${prefix}/${opts.subPath}`), source: 'project' },\n { path: resolve(home, `.agents/${opts.subPath}`), source: 'user' },\n { path: resolve(home, `.${prefix}/${opts.subPath}`), source: 'user' },\n ]\n}\n","/**\n * MCP server discovery from project + user config files.\n *\n * Reads `mcps.json` (a list of `McpServerConfig` shapes the SDK accepts)\n * from the project + user dirs, normalizes via the same path the agent\n * uses (`normalizeMcpServers`), and folds the user's enabled-toggle list\n * into a server array the agent picks up at construction.\n *\n * Pattern mirrors `skills-discovery.ts` — the chat layer owns discovery +\n * UI, the agent receives the same `McpServerConfig[]` it always did.\n */\n\nimport type { McpServerConfig } from '../mcp'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { normalizeMcpServers } from '../mcp'\nimport { projectUserPaths } from './project-user-paths'\n\n/**\n * One entry in the discovered list. `path` is the source file so a Settings\n * picker can show \"Defined in ~/.zidane/mcps.json\" tooltips.\n */\nexport interface DiscoveredMcp {\n config: McpServerConfig\n source: 'project' | 'user'\n path: string\n}\n\n/**\n * Search order for `mcps.json` — see {@link projectUserPaths}. Project\n * beats user; the first file found for each (name) wins. Non-existent\n * files are skipped without error.\n */\nexport function defaultMcpsConfigPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): { path: string, source: 'project' | 'user' }[] {\n return projectUserPaths({ subPath: 'mcps.json', ...opts })\n}\n\n/**\n * Parse one `mcps.json` file. Accepts:\n * - A raw `McpServerConfig[]` array — host-SDK convention.\n * - An object with `{ \"mcpServers\": { name: {...} } }` — Claude Desktop\n * + many client wrappers. The `mcpServers` value is unwrapped before\n * normalization (otherwise `normalizeMcpServers` would treat the whole\n * wrapper as a single server).\n * - A name-keyed map (`{ fs: {...}, github: {...} }`) — host-SDK\n * convenience shape.\n *\n * Validation flows through `normalizeMcpServers` so the result matches\n * what `createAgent` accepts. Throws on malformed JSON; the caller decides\n * whether to surface or swallow.\n */\nexport function parseMcpsFile(text: string): McpServerConfig[] {\n const raw = JSON.parse(text) as unknown\n // Unwrap the Claude-Desktop wrapper before handing off — the helper\n // doesn't recognize the `{ mcpServers: ... }` envelope.\n const target: unknown = raw && typeof raw === 'object' && !Array.isArray(raw) && 'mcpServers' in raw\n ? (raw as { mcpServers: unknown }).mcpServers\n : raw\n // `normalizeMcpServers` accepts `unknown` — no cast needed; it handles\n // array / map / object-with-`name` shapes and throws on malformed input.\n return normalizeMcpServers(target)\n}\n\n/**\n * Walk the default config paths, parse every file that exists, and return\n * `DiscoveredMcp[]` in source-priority order. Project entries appear\n * before user entries; first occurrence of a `name` wins (later\n * duplicates dropped).\n *\n * Parse errors are logged under `ZIDANE_DEBUG` and the file skipped so a\n * single malformed `mcps.json` can't take down the picker.\n */\nexport function discoverProjectMcps(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): DiscoveredMcp[] {\n const paths = defaultMcpsConfigPaths(opts).filter(p => existsSync(p.path))\n const seen = new Set<string>()\n const out: DiscoveredMcp[] = []\n for (const { path, source } of paths) {\n let configs: McpServerConfig[]\n try {\n configs = parseMcpsFile(readFileSync(path, 'utf8'))\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG) {\n const cause = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[zidane/chat] failed to parse \"${path}\": ${cause}\\n`)\n }\n continue\n }\n for (const config of configs) {\n if (seen.has(config.name))\n continue\n seen.add(config.name)\n out.push({ config, source, path })\n }\n }\n return out\n}\n\n/**\n * Map a user-toggled enable list onto the `mcpServers` array the agent\n * receives. Conventions match `buildSkillsConfig`:\n *\n * - `enabled === undefined` → every discovered server enabled (default).\n * - `enabled === []` → no servers (the agent runs MCP-less).\n * - `enabled === [names]` → allowlist; servers not in the list are\n * dropped.\n *\n * Returns a fresh array — callers can mutate without affecting the\n * underlying discovery list.\n */\nexport function buildMcpServers(opts: {\n discovered: readonly DiscoveredMcp[]\n enabled?: readonly string[]\n}): McpServerConfig[] {\n if (opts.enabled === undefined)\n return opts.discovered.map(d => d.config)\n if (opts.enabled.length === 0)\n return []\n const allow = new Set(opts.enabled)\n return opts.discovered.filter(d => allow.has(d.config.name)).map(d => d.config)\n}\n","/**\n * OAuth login flow exposed to the TUI.\n *\n * Driven by each {@link ProviderDescriptor}'s `oauthProvider` field. Built-in\n * descriptors wire pi-ai's `anthropicOAuthProvider` and `openaiCodexOAuthProvider`,\n * but hosts can pass any object conforming to `OAuthProviderInterface` — the\n * TUI is implementation-agnostic past the descriptor boundary.\n */\n\nimport type { OAuthCredentials, OAuthLoginCallbacks } from '@mariozechner/pi-ai/oauth'\nimport type { ProviderDescriptor } from './providers'\nimport { spawn } from 'node:child_process'\n\nexport function supportsOAuth(descriptor: ProviderDescriptor): boolean {\n return descriptor.oauthProvider !== undefined\n}\n\nexport interface OAuthFlowOptions {\n /** Called when the provider emits its login URL — typically right after the callback server starts. */\n onUrl: (url: string, instructions?: string) => void\n /** Called when the provider needs a code entered manually (rare; only when callback server fails). */\n onCodeRequest?: () => Promise<string>\n /** Called with each progress message from the OAuth flow (token exchange, etc.). */\n onProgress?: (message: string) => void\n /** Abort the in-flight login (e.g. user pressed esc). */\n signal?: AbortSignal\n}\n\n/**\n * Run the OAuth login flow for a provider.\n *\n * Returns the OAuth credentials on success; caller persists them via\n * `setProviderCredential(dataDir, descriptor, { kind: 'oauth', ...credentials })`.\n * Throws when the descriptor has no `oauthProvider` configured.\n */\nexport async function runOAuthLogin(\n descriptor: ProviderDescriptor,\n options: OAuthFlowOptions,\n): Promise<OAuthCredentials> {\n if (!descriptor.oauthProvider) {\n throw new Error(\n `OAuth not supported for ${descriptor.label} (${descriptor.key}) — use an API key instead.`,\n )\n }\n\n const callbacks: OAuthLoginCallbacks = {\n onAuth: (info) => {\n options.onUrl(info.url, info.instructions)\n // Best-effort browser launch. If `open`/`xdg-open` isn't available, the\n // user can still click the URL from the TUI — the callback server is\n // already listening either way.\n void tryOpenBrowser(info.url)\n },\n onPrompt: async () => {\n if (!options.onCodeRequest)\n throw new Error('OAuth flow requires manual code input but no handler is wired.')\n return options.onCodeRequest()\n },\n onProgress: options.onProgress,\n signal: options.signal,\n }\n\n return descriptor.oauthProvider.login(callbacks)\n}\n\n/**\n * Best-effort cross-platform browser open. macOS uses `open`, Linux uses\n * `xdg-open`, Windows uses `start`. Failures are swallowed — the callback\n * server is already listening, and the URL is displayed in the TUI for\n * manual click.\n *\n * Uses `spawn` (not `exec`) so the URL is passed as an argv element rather\n * than interpolated into a shell command — no need to think about quoting\n * URLs that contain `&`, `?`, `\"` or other shell metacharacters.\n */\nfunction tryOpenBrowser(url: string): void {\n const [cmd, ...args] = (() => {\n if (process.platform === 'darwin')\n return ['open', url]\n if (process.platform === 'win32')\n return ['cmd', '/c', 'start', '', url]\n return ['xdg-open', url]\n })()\n try {\n const child = spawn(cmd, args, { stdio: 'ignore', detached: true })\n child.on('error', () => {}) // ENOENT etc. — silently fall through\n child.unref()\n }\n catch {\n // spawn() itself failed (e.g. invalid binary path). Swallow.\n }\n}\n","/**\n * Pure string + reference math for rendering a submitted prompt with chip\n * pills around completion references. Renderer-agnostic — the TUI walks\n * the segments into OpenTUI `<text>` nodes, a GUI walks them into JSX\n * spans + `<span class=\"chip\">` pills. No layout engine assumptions.\n */\n\n/**\n * Highlight span — half-open `[start, end)` over the source string.\n *\n * Offsets are JS string indices (UTF-16 code units) — the same convention used\n * everywhere else in the chat layer (`text[i]`, `m.index`, `String.slice`).\n * Surrogate pairs that straddle a span boundary would render malformed, but\n * realistic prompts in this surface (terminal text + ASCII triggers) keep that\n * risk theoretical; callers feeding emoji-heavy content should normalize to\n * codepoint walks upstream.\n */\nexport interface PromptSegmentRef {\n start: number\n end: number\n /** Provider id tagging this span (`'skills'`, `'files'`, …). Free-form. */\n providerId: string\n}\n\n/**\n * One atomic unit emitted by {@link splitPromptSegments}. Plain segments\n * are word-sized so wraps land cleanly between words; chip segments\n * carry the source `providerId` (`'skills'`, `'files'`, …) so renderers\n * can pick per-kind colors without re-walking the ref list.\n */\nexport type PromptSegment\n = | { kind: 'plain', text: string }\n | { kind: 'chip', text: string, providerId: string }\n\n/**\n * Split a prompt buffer into word-sized atomic segments suitable for a\n * flex-row + flex-wrap renderer (TUI) or a `display: inline` flow with\n * inline-block chips (GUI). Each chip becomes one segment (atomic —\n * never broken across rows); each plain run is split into \"word +\n * trailing space\" units so wraps land at clean word boundaries.\n *\n * Robust to:\n * - Overlapping refs — sorted by start; later refs that overlap are\n * dropped via the first-wins rule.\n * - Out-of-bounds refs — dropped entirely when `end > text.length` or\n * `start >= text.length`. Partial clipping would silently truncate\n * a chip's label; the caller is in a better position to surface the\n * mismatch (typically a stale `refs` array referencing a previous text).\n * - Whitespace-only plain runs — emitted as their own plain segment\n * so chip-adjacent-to-chip cases keep the original spacing.\n *\n * Word splitter rationale: `\\S+\\s*` keeps trailing whitespace attached\n * to its preceding word so wrap boundaries land between words (cleanly).\n * A leading-whitespace-only segment is captured by `\\s+` so we don't\n * drop it entirely when the plain run starts with a space.\n */\nexport function splitPromptSegments(\n text: string,\n refs: readonly PromptSegmentRef[],\n): PromptSegment[] {\n const sorted = [...refs]\n .filter(r => r.end > r.start && r.start < text.length && r.end <= text.length)\n .sort((a, b) => a.start - b.start)\n const out: PromptSegment[] = []\n let cursor = 0\n for (const ref of sorted) {\n if (ref.start < cursor)\n continue // overlapping with a previous ref — drop\n if (ref.start > cursor) {\n const matches = text.slice(cursor, ref.start).match(/\\S+\\s*|\\s+/g) ?? []\n for (const m of matches)\n out.push({ kind: 'plain', text: m })\n }\n out.push({ kind: 'chip', text: text.slice(ref.start, ref.end), providerId: ref.providerId })\n cursor = ref.end\n }\n if (cursor < text.length) {\n const matches = text.slice(cursor).match(/\\S+\\s*|\\s+/g) ?? []\n for (const m of matches)\n out.push({ kind: 'plain', text: m })\n }\n return out\n}\n","/**\n * Safe-mode storage + matching for the TUI.\n *\n * Lives at `<dataDir>/projects.json` (default `~/.zidane/projects.json`). Each\n * top-level key is an absolute project directory; the value carries that\n * project's persisted tool-call `safelist`.\n *\n * ```json\n * {\n * \"/Users/me/proj-a\": { \"safelist\": [\"read_file\", \"shell:git:*\"] }\n * }\n * ```\n *\n * Two granularities for safelist entries:\n * - **bare tool name** — `\"read_file\"` matches every `read_file` call.\n * - **tool + first-arg token + wildcard** — `\"shell:git:*\"` matches `shell`\n * calls whose primary string argument starts with the token `git`\n * (followed by whitespace or end-of-string). Modelled on Claude Code's\n * `Bash(git:*)` syntax.\n *\n * A short list of read-only tools is **implicitly safe** without being\n * persisted — see {@link IMPLICITLY_SAFE_TOOLS}.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n// ---------------------------------------------------------------------------\n// Persistence\n// ---------------------------------------------------------------------------\n\nexport interface ProjectEntry {\n safelist?: string[]\n}\n\nexport type ProjectsFile = Record<string, ProjectEntry>\n\n/** Resolve `projects.json`'s on-disk path given the TUI data directory. */\nexport function projectsFilePath(dataDir: string): string {\n return resolve(dataDir, 'projects.json')\n}\n\nexport function readProjects(dataDir: string): ProjectsFile {\n const path = projectsFilePath(dataDir)\n if (!existsSync(path))\n return {}\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed as ProjectsFile\n }\n catch {\n // Corrupt file → treat as empty so the user can re-bootstrap.\n }\n return {}\n}\n\nfunction ensureDir(path: string): void {\n const dir = dirname(path)\n if (!existsSync(dir))\n mkdirSync(dir, { recursive: true })\n}\n\n/** Atomic write — tmp + rename so a crash never leaves a half-file. */\nexport function writeProjects(dataDir: string, file: ProjectsFile): void {\n const path = projectsFilePath(dataDir)\n ensureDir(path)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(file, null, 2))\n renameSync(tmp, path)\n}\n\n/**\n * Append `entry` to the safelist for `projectDir`, dedup-aware. Returns the\n * updated entry list (post-write) so callers can render it without re-reading.\n */\nexport function addToSafelist(\n dataDir: string,\n projectDir: string,\n entry: string,\n): readonly string[] {\n const file = readProjects(dataDir)\n const existing = file[projectDir]?.safelist ?? []\n if (existing.includes(entry))\n return existing\n const next = [...existing, entry]\n file[projectDir] = { ...file[projectDir], safelist: next }\n writeProjects(dataDir, file)\n return next\n}\n\n/** Read the safelist for one project. Returns `[]` for unknown projects. */\nexport function getSafelist(dataDir: string, projectDir: string): readonly string[] {\n return readProjects(dataDir)[projectDir]?.safelist ?? []\n}\n\n// ---------------------------------------------------------------------------\n// Matching\n// ---------------------------------------------------------------------------\n\n/**\n * Tools that always pass without prompting — pure file/dir reads with no\n * side effects. Users who want to gate them must disable safe-mode entirely\n * (or fork this list in their own embedding).\n */\nexport const IMPLICITLY_SAFE_TOOLS: readonly string[] = [\n 'read_file',\n 'list_files',\n 'glob',\n 'grep',\n]\n\n/** Common input keys carrying the \"primary argument\" we scope safelists on. */\nconst PRIMARY_ARG_KEYS = ['command', 'path', 'pattern', 'query'] as const\n\nfunction primaryArgValue(input: Record<string, unknown>): string {\n for (const key of PRIMARY_ARG_KEYS) {\n const v = input[key]\n if (typeof v === 'string' && v.length > 0)\n return v\n }\n return ''\n}\n\n/** Extract the first whitespace-delimited token of the primary arg. */\nfunction primaryArgToken(input: Record<string, unknown>): string {\n return primaryArgValue(input).split(/\\s+/)[0] ?? ''\n}\n\n/**\n * Shell metacharacters that turn a single command into a compound: pipes,\n * sequencing, redirects, substitutions, line breaks, subshells. A `shell:git:*`\n * entry is meant to greenlight \"any git invocation\" — without this guard,\n * `git status && rm -rf /` would tokenize to `git` and pass the safelist\n * unchallenged. Reject any command that's not a single program call.\n *\n * The regex is intentionally generous: false positives (e.g. `echo \"hi & bye\"`)\n * just prompt the user again, which is the safe failure mode.\n */\nconst SHELL_COMPOUND_RE = /[;&|<>`$\\n\\r()]/\n\nfunction isCompoundShellCommand(command: string): boolean {\n return SHELL_COMPOUND_RE.test(command)\n}\n\n/**\n * Test whether a `{ tool, input }` pair is covered by one safelist entry.\n *\n * Supported entry shapes:\n * - `\"<tool>\"` — broad match on tool name. For `shell` this still requires\n * a single-program command (compound forms always prompt).\n * - `\"<tool>:<token>:*\"` — match when the primary arg's first token equals\n * `<token>`. For `shell`, also requires the command to be free of\n * metacharacters (`;`, `&&`, `||`, `|`, `$(`, backticks, `>`, `<`,\n * newlines, subshells) — otherwise a `shell:git:*` entry would silently\n * greenlight `git status && rm -rf /`.\n *\n * Entries that don't fit either shape are ignored (forward-compat for future\n * pattern syntax — readers shouldn't choke on entries written by a newer\n * version of the TUI).\n */\nexport function matchesSafelistEntry(\n entry: string,\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n // Shell entries — bare or scoped — only match single-program commands. We\n // refuse to interpret the safelist for any command containing chaining or\n // redirection metacharacters.\n if (tool === 'shell') {\n const cmd = typeof input.command === 'string' ? input.command : ''\n if (isCompoundShellCommand(cmd))\n return false\n }\n if (entry === tool)\n return true\n const sep = entry.indexOf(':')\n if (sep <= 0)\n return false\n if (entry.slice(0, sep) !== tool)\n return false\n const scope = entry.slice(sep + 1)\n if (scope.endsWith(':*'))\n return primaryArgToken(input) === scope.slice(0, -2)\n return false\n}\n\n/** True when a call matches ANY entry in the project's safelist (or is implicitly safe). */\nexport function isOnSafelist(\n entries: readonly string[],\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n if (IMPLICITLY_SAFE_TOOLS.includes(tool))\n return true\n return entries.some(e => matchesSafelistEntry(e, tool, input))\n}\n\n/**\n * Suggest the safelist entry to write when the user picks \"accept and\n * remember\" for a `{ tool, input }`. Heuristic:\n *\n * - `shell` → scope by first command token (`shell:git:*`).\n * - anything else → bare tool name (broad).\n *\n * Returning a string ensures the UI always has a concrete entry to display\n * as the button label.\n */\nexport function suggestSafelistEntry(\n tool: string,\n input: Record<string, unknown>,\n): string {\n if (tool === 'shell') {\n const token = primaryArgToken(input)\n if (token)\n return `${tool}:${token}:*`\n }\n return tool\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 `app.tsx` 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 }` 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 { createContext, useCallback, useContext, useRef, useState } from 'react'\n\nexport type ApprovalDecision = 'accept-once' | 'accept-safelist' | 'deny'\n\nexport interface ApprovalRequest {\n id: string\n tool: string\n input: Record<string, unknown>\n resolve: (decision: ApprovalDecision) => void\n}\n\n/** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */\nexport type RequestApproval = (tool: string, input: Record<string, unknown>) => Promise<ApprovalDecision>\n\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\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 */\nexport function SafeModeProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly ApprovalRequest[]>([])\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) => new Promise<ApprovalDecision>((resolve) => {\n setQueue(prev => [...prev, { id: nextApprovalId(), tool, input, resolve }])\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 // 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 }\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\nexport function useSafeModeQueue(): readonly ApprovalRequest[] {\n return useContext(SafeModeQueueContext)\n}\n\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 (or user's) `.{prefix}/sessions/`\n * directory.\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 home directory 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, future GUI, SDK\n * scripts) can either call `writeSessionExport` for the canned\n * \"render + write\" combo or compose `renderSession` with a different\n * sink (clipboard, network, S3) without dragging in `node:fs`.\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 { fmtTokens, shortId } from './format'\nimport { deriveSessionTitle } from './store'\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 (`~/.{prefix}/sessions`)\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\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`.\n *\n * @throws RangeError when `sessionId` would resolve to an empty / `..`\n * filename — defensive guard against malicious or buggy callers\n * crossing into a sibling directory via the id.\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 root = repoRoot ?? home\n const dir = resolve(root, `.${prefix}`, '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 · created · turns` summary,\n * a stats section (turns / runs / tokens / cost / status), then a\n * `## Conversation` block where each turn renders as `### N. role ·\n * run_X · ISO-date` with text, thinking, tool calls, and tool results\n * 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 */\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 */\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 })\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 recorded 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 · run_1 · 2026-05-13T01:48:29.895Z` */\nfunction renderTurnHeader(turn: SessionTurn, index: number): string {\n const date = new Date(turn.createdAt).toISOString()\n const runFragment = turn.runId ? ` · \\`${turn.runId}\\`` : ''\n const usageFragment = turn.usage ? ` · ${formatTurnUsage(turn.usage)}` : ''\n return `### ${index}. ${turn.role}${runFragment} · ${date}${usageFragment}`\n}\n\n/** `in 12 · out 48 · cached 1.2k` — only emits 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 const parts: string[] = []\n for (const block of turn.content) {\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 return block.text.trim() ? block.text.trim() : ''\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 result** ✗ 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 `_[image · ${block.mediaType}]_`\n default:\n return ''\n }\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(`_[image · ${piece.mediaType}]_`)\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(`Refusing to export session — invalid id \"${sessionId}\"`)\n return `${cleaned}.${ext}`\n}\n\n// ---------------------------------------------------------------------------\n// Usage aggregation (shared with the details modal in spirit but duplicated\n// here so the renderer stays self-contained — pulling it in from the modal\n// would invert the dependency direction)\n// ---------------------------------------------------------------------------\n\ninterface AggregatedUsage {\n input: number\n output: number\n cacheRead: number\n total: number\n cost: number\n}\n\nfunction 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","/**\n * Skills discovery + persisted enabled-state for the chat layer.\n *\n * Walks project + storage-dir scan paths, returns each skill's parsed\n * metadata, and folds the user's enabled-toggle list into a `SkillsConfig`\n * the agent picks up at construction time.\n *\n * Pattern mirrors what `createAgent` already supports (`SkillsConfig.scan`\n * + `enabled`), so the agent receives the same shape it always did — the\n * chat layer just owns the discovery + UI plumbing.\n */\n\nimport type { SkillConfig, SkillsConfig, SourcedScanPath } from '../skills'\nimport { existsSync } from 'node:fs'\nimport { discoverSkills } from '../skills'\nimport { projectUserPaths } from './project-user-paths'\n\n/**\n * Resolve the default skill scan paths for a project. First-found wins on\n * collision; earlier entries take precedence.\n *\n * Search order — see {@link projectUserPaths}. Non-existent paths are\n * returned so downstream code can choose whether to create them;\n * `discoverSkills` itself skips missing dirs without error.\n */\nexport function defaultSkillScanPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): SourcedScanPath[] {\n return projectUserPaths({ subPath: 'skills', ...opts })\n}\n\n/**\n * Discover every skill reachable from the default scan paths. Returns\n * parsed `SkillConfig`s with `name`, `description`, frontmatter, and\n * lenient-load diagnostics. Pure I/O — does not activate or write.\n *\n * `signal` is forwarded to `discoverSkills` so the TUI's directory-watch\n * effect can cancel a long scan when the user switches `cwd` rapidly.\n *\n * Errors during parse are surfaced as `diagnostics` on the returned\n * skill, not thrown — keeps the picker usable even when a single\n * SKILL.md is malformed.\n */\nexport async function discoverProjectSkills(opts: {\n cwd?: string\n home?: string\n prefix?: string\n signal?: AbortSignal\n} = {}): Promise<SkillConfig[]> {\n const paths = defaultSkillScanPaths(opts).filter(p => existsSync(p.path))\n return discoverSkills(paths, opts.signal)\n}\n\n/**\n * Map a user-toggled enable list onto the format `createAgent` expects.\n *\n * Conventions:\n * - `enabled === undefined` → all discovered skills enabled (default).\n * - `enabled === []` → fully off; the agent will not scan or\n * inject the skills tools.\n * - `enabled === [names]` → allowlist; skills not in the list are\n * left out of the catalog.\n *\n * `scan` is the resolved scan-path list (passed through verbatim). Pass\n * `defaultSkillScanPaths()` for the standard project + user paths, or\n * supply a host-specific list.\n */\nexport function buildSkillsConfig(opts: {\n scan: SourcedScanPath[]\n enabled?: readonly string[]\n}): SkillsConfig {\n const scan = opts.scan.map(p => p.path)\n // Empty allowlist must disable the system outright — the loop checks for\n // `enabled === false || enabled.length === 0` to short-circuit catalog\n // assembly. Passing `[]` here used to leak through as \"no allowlist\n // entries, so include nothing\" which the loop treats as \"off\" anyway,\n // but being explicit prevents future loop refactors from drifting.\n if (opts.enabled !== undefined && opts.enabled.length === 0) {\n return { scan, enabled: false }\n }\n return {\n scan,\n ...(opts.enabled ? { enabled: [...opts.enabled] } : {}),\n }\n}\n","import type { Dispatch, SetStateAction } from 'react'\nimport type { TurnUsage } from '../types'\nimport type { Owner, StreamEvent } from './types'\nimport { useCallback, useMemo, useRef } from 'react'\n\n/**\n * Streaming flush cadence. Set to roughly half the renderer's 30fps target\n * (~16fps deltas) so OpenTUI's `MarkdownRenderable` has a full paint frame\n * between content updates. The trailing markdown block is `destroyRecursively`\n * + recreated on every content change while `streaming: true` (parser\n * marks the last 2 blocks unstable); landing those mutations one-per-frame\n * paints partial layout states, which renders as flicker. Pacing at ~60ms\n * gives the renderer time to settle without sacrificing live feel — tokens\n * still appear faster than the user can read.\n */\nconst FLUSH_INTERVAL_MS = 60\n\nconst PARENT_OWNER: Owner = 'parent'\n\n// ---------------------------------------------------------------------------\n// Streaming-delta merging\n//\n// Each event is owned by either the parent agent or a specific subagent\n// (`childId`). Same-owner same-kind text/thinking chunks coalesce into one\n// growing event so React allocates one renderable per stream, not per delta.\n// ---------------------------------------------------------------------------\n\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\nfunction emptyBucket(owner: Owner, depth: number): DeltaBucket {\n return { markdown: '', thinking: '', owner, depth }\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\nfunction appendMarkdownDelta(prev: StreamEvent[], delta: string, owner: Owner, depth: number, turnId: string | undefined): StreamEvent[] {\n const last = prev[prev.length - 1]\n if (last && last.kind === 'markdown' && last.streaming && ownerOf(last) === owner) {\n const next = prev.slice(0, -1)\n next.push({ ...last, text: last.text + delta })\n return next\n }\n return [\n ...prev,\n tagEvent({ kind: 'markdown', text: delta, streaming: true }, owner, depth, turnId),\n ]\n}\n\nfunction appendThinkingLines(prev: StreamEvent[], delta: string, owner: Owner, depth: number, turnId: string | undefined): StreamEvent[] {\n const lines = delta.split('\\n')\n const result = [...prev]\n const last = result[result.length - 1]\n\n if (last && last.kind === 'thinking' && ownerOf(last) === owner) {\n result[result.length - 1] = { ...last, text: last.text + lines[0] }\n }\n else if (lines[0] || lines.length > 1) {\n result.push(tagEvent({ kind: 'thinking', text: lines[0] }, owner, depth, turnId))\n }\n\n for (let i = 1; i < lines.length; i++)\n result.push(tagEvent({ kind: 'thinking', text: lines[i] }, owner, depth, turnId))\n\n return result\n}\n\nfunction ownerOf(evt: StreamEvent): Owner {\n return evt.childId ?? PARENT_OWNER\n}\n\n/**\n * Stamp owner (parent vs subagent) + depth + optional `turnId` onto a\n * freshly-minted event so consumers can identify the producer and the\n * source turn. Parent-owned events leave `childId` / `depth` off to match\n * the prior shape; `turnId` is added whenever the caller provided one.\n */\nfunction tagEvent(evt: StreamEvent, owner: Owner, depth: number, turnId: string | undefined): StreamEvent {\n const withTurn = turnId ? { ...evt, turnId } : evt\n if (owner === PARENT_OWNER)\n return withTurn\n return { ...withTurn, childId: owner, depth }\n}\n\n/** Flip any trailing streaming markdown blocks (any owner) to finalized. */\nexport function finalizeStreamingMarkdown(events: StreamEvent[]): StreamEvent[] {\n let changed = false\n const next = events.map((e) => {\n if (e.kind === 'markdown' && e.streaming) {\n changed = true\n return { ...e, streaming: false }\n }\n return e\n })\n return changed ? next : events\n}\n\n/** Flip the trailing streaming markdown block for one specific owner. */\nexport function finalizeStreamingMarkdownForOwner(events: StreamEvent[], owner: Owner): StreamEvent[] {\n for (let i = events.length - 1; i >= 0; i--) {\n const e = events[i]\n if (e.kind !== 'markdown')\n continue\n if (!e.streaming)\n continue\n if (ownerOf(e) !== owner)\n continue\n const next = events.slice()\n next[i] = { ...e, streaming: false }\n return next\n }\n return events\n}\n\n// ---------------------------------------------------------------------------\n// Context-window math.\n// ---------------------------------------------------------------------------\n\n/**\n * Effective context size for a single turn.\n *\n * `usage.input` is misleading on its own when prompt caching is active: providers\n * (Anthropic, OpenRouter→Anthropic, Gemini) report `input` as the *new uncached*\n * tokens only — the cached prefix shows up in `cacheRead`, and newly-cached\n * tokens in `cacheCreation`. The model still saw all three buckets, so the real\n * context-window utilization is their sum.\n *\n * Non-caching providers leave `cacheRead`/`cacheCreation` undefined, so this\n * collapses to plain `input` for them.\n */\nexport function turnContextSize(usage: TurnUsage | undefined): number {\n if (!usage)\n return 0\n return (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheCreation ?? 0)\n}\n\n// ---------------------------------------------------------------------------\n// useStreamBuffer — throttled stream-event accumulator with per-owner lanes.\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 flush at most once per ~33ms.\n// ---------------------------------------------------------------------------\n\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\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\nexport function useStreamBuffer(setEvents: Dispatch<SetStateAction<StreamEvent[]>>): StreamBuffer {\n const bucketsRef = useRef<Map<Owner, DeltaBucket>>(new Map())\n const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const drainPendingInto = useCallback((updater?: (events: StreamEvent[]) => StreamEvent[]) => {\n if (flushTimerRef.current) {\n clearTimeout(flushTimerRef.current)\n flushTimerRef.current = null\n }\n const buckets = Array.from(bucketsRef.current.values())\n bucketsRef.current.clear()\n const hasDeltas = buckets.some(b => b.markdown.length > 0 || b.thinking.length > 0)\n if (!hasDeltas && !updater)\n return\n setEvents((prev) => {\n let merged = prev\n for (const bucket of buckets)\n merged = applyBucket(merged, bucket)\n return updater ? updater(merged) : merged\n })\n }, [setEvents])\n\n const flush = useCallback(() => drainPendingInto(), [drainPendingInto])\n\n const flushAndUpdate = useCallback(\n (update: (events: StreamEvent[]) => StreamEvent[]) => drainPendingInto(update),\n [drainPendingInto],\n )\n\n const appendImmediate = useCallback(\n (evt: StreamEvent) => drainPendingInto(events => [...events, evt]),\n [drainPendingInto],\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 timer) tag the new event.\n if (source?.turnId)\n bucket.turnId = source.turnId\n if (!flushTimerRef.current)\n flushTimerRef.current = setTimeout(flush, FLUSH_INTERVAL_MS)\n }, [flush])\n\n const reset = useCallback(() => {\n if (flushTimerRef.current) {\n clearTimeout(flushTimerRef.current)\n flushTimerRef.current = null\n }\n bucketsRef.current.clear()\n }, [])\n\n // CRITICAL: stabilize the returned object. All five 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 }),\n [queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset],\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 — the rename-friendly shape lets a component\n// keep its body unchanged when migrating from the static `COLOR.brand`\n// pattern to `const COLOR = useColors(); COLOR.brand`.\n// ---------------------------------------------------------------------------\n\nconst ThemeContext = createContext<Theme>(DEFAULT_THEME)\n\nexport function ThemeProvider({ theme, children }: { theme: Theme, children: ReactNode }) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>\n}\n\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`. */\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 * Pure transforms on `SessionTurn[]` used by the TUI's turn-selection\n * modal actions (fork / delete / copy). Each helper is renderer- and\n * store-agnostic so it composes the same in TUI, SDK, or test code.\n *\n * Tool-call protocol invariant the helpers preserve:\n *\n * Every `tool_call` block emitted by an assistant turn MUST be matched\n * by a `tool_result` block (same `callId`) in a later turn before the\n * next assistant turn. Providers reject histories that violate this —\n * so any operation that mutates the turn list must either keep pairs\n * intact or strip both sides.\n */\n\nimport type { SessionContentBlock, SessionTurn } from '../types'\n\n/**\n * Fork — keep every turn up to and including `turnId`, then strip any\n * `tool_call` blocks left without a matching `tool_result` in the slice.\n *\n * Semantics:\n * - Include the selected turn (\"branch from HERE\" mental model — the\n * user wants the selected message to be the latest in the fork).\n * - If the selected turn is an assistant turn with unresolved\n * `tool_call` blocks (their `tool_result`s live in turns AFTER the\n * slice), strip those calls. Otherwise the fork would post an\n * assistant turn with no matching tool results, breaking the next\n * provider call.\n * - Drop turns that become empty (all blocks stripped).\n *\n * Returns `null` when `turnId` doesn't exist in `turns` — caller should\n * surface a \"turn not found\" error rather than silently no-op.\n */\nexport function truncateTurnsAt(turns: readonly SessionTurn[], turnId: string): SessionTurn[] | null {\n const idx = turns.findIndex(t => t.id === turnId)\n if (idx === -1)\n return null\n const slice = turns.slice(0, idx + 1)\n return stripOrphanToolBlocks(slice)\n}\n\n/**\n * Delete — remove the turn with `turnId` and any tool blocks left\n * orphaned by the removal. Returns `null` when `turnId` doesn't exist.\n *\n * Strategy:\n * 1. Drop the target turn.\n * 2. Scan the remaining turns for `tool_call`s without a matching\n * `tool_result` (orphaned by removing the user turn that carried\n * the result), and `tool_result`s without a matching `tool_call`\n * (orphaned by removing the assistant turn that issued the call).\n * Strip both sides.\n * 3. Drop turns whose content is now empty.\n *\n * This guarantees the resulting history is protocol-clean — a follow-up\n * `agent.run()` against the modified session can post turns without the\n * provider rejecting the history.\n */\nexport function deleteTurnSafely(turns: readonly SessionTurn[], turnId: string): SessionTurn[] | null {\n const idx = turns.findIndex(t => t.id === turnId)\n if (idx === -1)\n return null\n const without = [...turns.slice(0, idx), ...turns.slice(idx + 1)]\n return stripOrphanToolBlocks(without)\n}\n\n/**\n * Walk a turn list and remove any tool blocks whose counterpart is\n * missing. Drops turns left empty. Used by `truncateTurnsAt` (which can\n * leave `tool_call`s orphaned when their results are past the cut) and\n * `deleteTurnSafely` (which can orphan either side of a pair).\n *\n * Pure / total: returns a new array; never throws.\n */\nfunction stripOrphanToolBlocks(turns: readonly SessionTurn[]): SessionTurn[] {\n const callIds = new Set<string>()\n const resultIds = new Set<string>()\n for (const turn of turns) {\n for (const block of turn.content) {\n if (block.type === 'tool_call')\n callIds.add(block.id)\n else if (block.type === 'tool_result')\n resultIds.add(block.callId)\n }\n }\n const result: SessionTurn[] = []\n for (const turn of turns) {\n const filtered: SessionContentBlock[] = []\n for (const block of turn.content) {\n if (block.type === 'tool_call') {\n if (!resultIds.has(block.id))\n continue // call lost its result\n }\n else if (block.type === 'tool_result') {\n if (!callIds.has(block.callId))\n continue // result lost its call\n }\n filtered.push(block)\n }\n if (filtered.length === 0)\n continue // turn is now empty — drop it\n result.push(filtered.length === turn.content.length ? turn : { ...turn, content: filtered })\n }\n return result\n}\n\n/**\n * Serialize a turn's content to a clean text representation suited for\n * the clipboard. Joins text + thinking blocks verbatim; tool calls and\n * tool results get bracketed labels so the user can paste a readable\n * record of what happened without losing structure.\n *\n * Empty turns return `''`.\n */\nexport function turnAsText(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim())\n parts.push(block.text)\n else if (block.type === 'thinking' && block.text.trim())\n parts.push(`[thinking]\\n${block.text}`)\n else if (block.type === 'tool_call')\n parts.push(`[tool call · ${block.name}]\\n${stringifyArgs(block.input)}`)\n else if (block.type === 'tool_result')\n parts.push(`[tool result]\\n${typeof block.output === 'string' ? block.output : JSON.stringify(block.output, null, 2)}`)\n }\n return parts.join('\\n\\n')\n}\n\nfunction stringifyArgs(input: Record<string, unknown>): string {\n try {\n return JSON.stringify(input, null, 2)\n }\n catch {\n return String(input)\n }\n}\n\n/**\n * Count turns before / after the one identified by `turnId` in the\n * given list. Returns `null` when the id is missing. Used to label the\n * turn-details modal with `N before · M after`.\n */\nexport function countNeighbors(\n turnIds: readonly string[],\n turnId: string,\n): { before: number, after: number } | null {\n const idx = turnIds.indexOf(turnId)\n if (idx === -1)\n return null\n return { before: idx, after: turnIds.length - 1 - idx }\n}\n"],"mappings":"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"}