@birdybeep/cli 0.2.0 → 0.3.0

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 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/agent.ts","../src/framework.ts","../src/commands/doctor.ts","../src/config.ts","../src/diagnostics.ts","../src/commands/hook.ts","../src/commands/logout.ts","../src/commands/pair.ts","../src/pairing.ts","../src/version.ts","../src/commands/queue.ts","../src/commands/report-status.ts","../src/commands/status.ts","../src/commands/test.ts","../src/commands.ts","../src/update-check.ts","../src/cli.ts"],"sourcesContent":["/**\n * @birdybeep/cli — library entry. Re-exports the side-effect-free CLI API so the\n * package ships a real `.d.ts` and is importable for testing/embedding. The\n * executable lives in `bin.ts` (the only module with a shebang + `process` side\n * effects); keeping it separate stops the shebang from leaking into this entry's\n * type declarations.\n */\nexport * from \"./cli.js\";\n","/**\n * `birdybeep agent install|uninstall [all|claude|codex|opencode]` (§7.3, §9.4) — the\n * once-per-machine setup half: detect supported harnesses and run each adapter's\n * idempotent, non-destructive install/uninstall. Adds ONLY BirdyBeep-managed entries\n * (existing config backed up + preserved), the installed config invokes\n * `birdybeep hook <harness>`, and NO durable token is ever written into harness/repo\n * config — the hook reads the token from the secure store at runtime. Prints the changed\n * files + any required user action (Codex `/hooks` trust, OpenCode restart).\n *\n * Built as a factory with an injectable adapter set so tests exercise the REAL adapter\n * installs under a temp HOME with deterministic detection.\n */\nimport type { AgentAdapter, InstallResult } from \"@birdybeep/agent-core\";\nimport { claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { codexAdapter } from \"@birdybeep/codex\";\nimport { opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { type Command, type CommandContext, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [claudeCodeAdapter, codexAdapter, opencodeAdapter];\n\n/** CLI short target name → adapter id (the CLI says `claude`, the adapter id is `claude_code`). */\nconst TARGET_TO_ID: Record<string, string> = {\n claude: \"claude_code\",\n codex: \"codex\",\n opencode: \"opencode\",\n};\n\nexport const AGENT_TARGETS: readonly string[] = [\"all\", \"claude\", \"codex\", \"opencode\"];\n\n/** Resolve a target to the adapter(s) it names, or `\"unknown\"` for a bad target. */\nexport function selectAdapters(\n target: string,\n adapters: AgentAdapter[],\n): AgentAdapter[] | \"unknown\" {\n if (target === \"all\") return adapters;\n const id = TARGET_TO_ID[target];\n if (id === undefined) return \"unknown\";\n return adapters.filter((a) => a.id === id);\n}\n\ninterface InstallOutcome {\n harness: string;\n displayName: string;\n detected: boolean;\n status?: InstallResult[\"status\"];\n changedFiles?: string[];\n backupFiles?: string[];\n requiredActions?: string[];\n}\n\nasync function installSelected(adapters: AgentAdapter[], ctx: CommandContext): Promise<number> {\n const target = ctx.args[0] ?? \"all\";\n const selected = selectAdapters(target, adapters);\n if (selected === \"unknown\") {\n ctx.io.errline(\n `birdybeep agent install: unknown target \"${target}\" (expected ${AGENT_TARGETS.join(\"|\")}).`,\n );\n return EXIT.USAGE;\n }\n\n const outcomes: InstallOutcome[] = [];\n for (const adapter of selected) {\n const detection = await adapter.detect();\n if (!detection.detected) {\n outcomes.push({ harness: adapter.id, displayName: adapter.displayName, detected: false });\n continue;\n }\n const result = await adapter.install();\n outcomes.push({\n harness: adapter.id,\n displayName: adapter.displayName,\n detected: true,\n status: result.status,\n changedFiles: result.changedFiles,\n backupFiles: result.backupFiles,\n requiredActions: result.requiredActions,\n });\n }\n\n if (ctx.flags.json) {\n ctx.io.result({ target, results: outcomes });\n return EXIT.OK;\n }\n\n if (outcomes.length === 0 || outcomes.every((o) => !o.detected)) {\n ctx.io.line(\"No supported harnesses detected — nothing to install.\");\n }\n for (const o of outcomes) {\n if (!o.detected) {\n ctx.io.line(`– ${o.displayName}: not detected (skipped)`);\n continue;\n }\n const changed = (o.changedFiles ?? []).length > 0 ? o.changedFiles!.join(\", \") : \"no changes\";\n ctx.io.line(`✓ ${o.displayName}: ${o.status} (${changed})`);\n for (const action of o.requiredActions ?? []) ctx.io.line(` → ${action}`);\n }\n return EXIT.OK;\n}\n\ninterface UninstallOutcome {\n harness: string;\n displayName: string;\n changed: boolean;\n removedFiles: string[];\n restoredFiles: string[];\n}\n\nasync function uninstallSelected(adapters: AgentAdapter[], ctx: CommandContext): Promise<number> {\n const target = ctx.args[0] ?? \"all\";\n const selected = selectAdapters(target, adapters);\n if (selected === \"unknown\") {\n ctx.io.errline(\n `birdybeep agent uninstall: unknown target \"${target}\" (expected ${AGENT_TARGETS.join(\"|\")}).`,\n );\n return EXIT.USAGE;\n }\n\n const outcomes: UninstallOutcome[] = [];\n for (const adapter of selected) {\n // Uninstall is safe + idempotent even if nothing is installed (a no-op).\n const result = await adapter.uninstall();\n outcomes.push({\n harness: adapter.id,\n displayName: adapter.displayName,\n changed: result.changed,\n removedFiles: result.removedFiles,\n restoredFiles: result.restoredFiles,\n });\n }\n\n if (ctx.flags.json) {\n ctx.io.result({ target, results: outcomes });\n return EXIT.OK;\n }\n for (const o of outcomes) {\n if (!o.changed) {\n ctx.io.line(`– ${o.displayName}: nothing to remove`);\n continue;\n }\n const touched = [...o.removedFiles, ...o.restoredFiles].join(\", \") || \"config restored\";\n ctx.io.line(`✓ ${o.displayName}: removed (${touched})`);\n }\n return EXIT.OK;\n}\n\nexport interface AgentCommandDeps {\n /** Adapter set (tests inject deterministic detection). Defaults to the three real adapters. */\n adapters?: AgentAdapter[];\n}\n\n/** Build the `agent` command group (install + uninstall, both via the adapter contract). */\nexport function createAgentCommand(deps: AgentCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n return {\n name: \"agent\",\n summary: \"Install or uninstall harness adapters\",\n usage: \"birdybeep agent <install|uninstall> [all|claude|codex|opencode]\",\n subcommands: [\n {\n name: \"install\",\n summary: \"Install adapters (all | claude | codex | opencode)\",\n usage: \"birdybeep agent install [all|claude|codex|opencode]\",\n run: (ctx) => installSelected(adapters, ctx),\n },\n {\n name: \"uninstall\",\n summary: \"Restore harness config to its pre-install state\",\n usage: \"birdybeep agent uninstall [all|claude|codex|opencode]\",\n run: (ctx) => uninstallSelected(adapters, ctx),\n },\n ],\n };\n}\n","/**\n * The CLI framework (§9.4): a small zero-dependency command dispatcher every `birdybeep`\n * command plugs into. Owns global flag parsing (`--json` / `--non-interactive` /\n * `--version` / `--help`), nested subcommand routing, help rendering, the config-dir\n * bootstrap, a json-aware output layer, and a shared exit-code convention. Network/auth,\n * adapter, and secret logic live in the individual commands — never here.\n *\n * Kept dependency-light on purpose: this code installs into developers' machines, so the\n * smaller + more auditable the surface, the better (§16.4).\n */\nimport { mkdirSync } from \"node:fs\";\n\nimport { birdyBeepConfigDir } from \"@birdybeep/agent-core\";\n\n/** Shared exit-code convention so callers (humans + agents) can branch on the result. */\nexport const EXIT = { OK: 0, ERROR: 1, USAGE: 2 } as const;\n\n/** A minimal output sink (process.stdout/stderr in prod; capturing buffers in tests). */\nexport interface Writer {\n write(s: string): void;\n}\n\nexport interface GlobalFlags {\n /** Machine-readable JSON output for agents/scripts. */\n json: boolean;\n /** Never prompt; fail fast (non-zero) when a required value is missing. */\n nonInteractive: boolean;\n help: boolean;\n version: boolean;\n}\n\n/** Json-aware output. `line`/`result` are mutually exclusive by mode so stdout stays clean. */\nexport interface Io {\n readonly json: boolean;\n /** Human line → stdout (suppressed in `--json` mode). */\n line(text: string): void;\n /** Always → stderr (errors/warnings show in both modes). */\n errline(text: string): void;\n /** Structured result → stdout as JSON (only in `--json` mode). */\n result(value: unknown): void;\n /** Emit the right one for the mode: human text, or the structured value as JSON. */\n emit(human: string, json: unknown): void;\n}\n\nexport function createIo(json: boolean, stdout: Writer, stderr: Writer): Io {\n return {\n json,\n line: (text) => {\n if (!json) stdout.write(`${text}\\n`);\n },\n errline: (text) => stderr.write(`${text}\\n`),\n result: (value) => {\n if (json) stdout.write(`${JSON.stringify(value)}\\n`);\n },\n emit: (human, value) => {\n if (json) stdout.write(`${JSON.stringify(value)}\\n`);\n else stdout.write(`${human}\\n`);\n },\n };\n}\n\nexport interface CommandContext {\n /** Positional args after the resolved command path. */\n args: string[];\n flags: GlobalFlags;\n io: Io;\n}\n\nexport interface Command {\n name: string;\n summary: string;\n /** One-line usage shown in the command's own `--help`. */\n usage?: string;\n /** Nested subcommands (e.g. `agent install` / `agent uninstall`). */\n subcommands?: Command[];\n /** Command logic; returns the intended exit code. Absent for pure command groups. */\n run?(ctx: CommandContext): Promise<number> | number;\n}\n\n/** Thrown by a command when a required value is missing under `--non-interactive`. */\nexport class MissingInputError extends Error {\n constructor(readonly field: string) {\n super(`missing required value: ${field}`);\n this.name = \"MissingInputError\";\n }\n}\n\n/**\n * Resolve a value that may require interaction. Returns `provided` when present; otherwise\n * throws {@link MissingInputError} under `--non-interactive` (so the CLI fails fast instead\n * of hanging), or returns undefined for the caller to prompt in interactive mode.\n */\nexport function requireValue<T>(ctx: CommandContext, field: string, provided: T | undefined): T {\n if (provided !== undefined) return provided;\n if (ctx.flags.nonInteractive) throw new MissingInputError(field);\n throw new MissingInputError(field); // interactive prompting is a per-command concern; default fail-fast\n}\n\nconst GLOBAL_FLAG_TOKENS = new Set([\n \"--json\",\n \"--non-interactive\",\n \"--version\",\n \"-v\",\n \"--help\",\n \"-h\",\n]);\n\n/** Split a raw argv into global flags + the remaining (command path + positional) tokens. */\nexport function parseGlobalFlags(argv: string[]): { flags: GlobalFlags; rest: string[] } {\n const flags: GlobalFlags = { json: false, nonInteractive: false, help: false, version: false };\n const rest: string[] = [];\n for (const token of argv) {\n switch (token) {\n case \"--json\":\n flags.json = true;\n break;\n case \"--non-interactive\":\n flags.nonInteractive = true;\n break;\n case \"--version\":\n case \"-v\":\n flags.version = true;\n break;\n case \"--help\":\n case \"-h\":\n flags.help = true;\n break;\n default:\n rest.push(token);\n }\n }\n return { flags, rest };\n}\n\n/** Is `token` an unknown long/short flag (after global flags were stripped)? */\nfunction isUnknownFlag(token: string): boolean {\n return token.startsWith(\"-\") && !GLOBAL_FLAG_TOKENS.has(token);\n}\n\nfunction renderRootHelp(version: string, commands: Command[]): string {\n const width = Math.max(...commands.map((c) => c.name.length));\n const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);\n return [\n `birdybeep ${version} — stream coding-agent lifecycle events to BirdyBeep.`,\n \"\",\n \"Usage:\",\n \" birdybeep <command> [options]\",\n \"\",\n \"Commands:\",\n ...lines,\n \"\",\n \"Global options:\",\n \" --json Machine-readable JSON output\",\n \" --non-interactive Never prompt; fail fast if input is required\",\n \" -h, --help Show help (root or per-command)\",\n \" -v, --version Show the CLI version\",\n ].join(\"\\n\");\n}\n\nfunction renderCommandHelp(path: string, command: Command): string {\n const lines = [\n `birdybeep ${path} — ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `birdybeep ${path} [options]`}`,\n ];\n if (command.subcommands && command.subcommands.length > 0) {\n const width = Math.max(...command.subcommands.map((c) => c.name.length));\n lines.push(\n \"\",\n \"Subcommands:\",\n ...command.subcommands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`),\n );\n }\n return lines.join(\"\\n\");\n}\n\nexport interface DispatchDeps {\n version: string;\n commands: Command[];\n stdout: Writer;\n stderr: Writer;\n /** Skip the config-dir bootstrap (tests that don't want filesystem side effects). */\n ensureConfig?: boolean;\n /**\n * Optional post-command update notifier, invoked after a command runs successfully (not for\n * help/version). The framework only invokes it — all registry/cache/semver logic lives in the\n * CLI layer (`update-check.ts`), never here — and its failure never affects the command result.\n */\n notifyUpdate?: (ctx: { command: string; flags: GlobalFlags; io: Io }) => Promise<void>;\n}\n\n/**\n * Run the CLI against an argv slice (without `node`/script path). Resolves the command\n * (with nested subcommands), handles `--help`/`--version`, and returns the exit code.\n * Never throws — command errors become a stderr message + {@link EXIT.ERROR}.\n */\nexport async function dispatch(argv: string[], deps: DispatchDeps): Promise<number> {\n const { flags, rest } = parseGlobalFlags(argv);\n const io = createIo(flags.json, deps.stdout, deps.stderr);\n\n // Config dir is created on first run (non-secret CLI config only — never a token).\n if (deps.ensureConfig !== false) {\n try {\n mkdirSync(birdyBeepConfigDir(), { recursive: true, mode: 0o700 });\n } catch {\n /* non-fatal: a read-only config dir is surfaced by `doctor`, not here */\n }\n }\n\n if (flags.version) {\n io.emit(deps.version, { version: deps.version });\n return EXIT.OK;\n }\n\n // Resolve the command path (supports one level of nested subcommands).\n let command: Command | undefined = deps.commands.find((c) => c.name === rest[0]);\n const pathParts: string[] = [];\n let argsStart = 1;\n if (command) {\n pathParts.push(command.name);\n if (command.subcommands && command.subcommands.length > 0) {\n const sub = command.subcommands.find((c) => c.name === rest[1]);\n if (sub) {\n command = sub;\n pathParts.push(sub.name);\n argsStart = 2;\n }\n }\n }\n\n if (rest.length === 0 || (flags.help && command === undefined)) {\n io.emit(renderRootHelp(deps.version, deps.commands), {\n version: deps.version,\n commands: deps.commands.map((c) => ({ name: c.name, summary: c.summary })),\n });\n return EXIT.OK;\n }\n\n if (command === undefined) {\n io.errline(`birdybeep: unknown command \"${rest[0]}\". Run \\`birdybeep --help\\`.`);\n return EXIT.USAGE;\n }\n\n const path = pathParts.join(\" \");\n if (flags.help) {\n io.emit(renderCommandHelp(path, command), {\n name: path,\n summary: command.summary,\n usage: command.usage,\n subcommands: command.subcommands?.map((c) => ({ name: c.name, summary: c.summary })),\n });\n return EXIT.OK;\n }\n\n if (command.run === undefined) {\n // A pure command group invoked without a subcommand → show its help as a usage error.\n io.errline(renderCommandHelp(path, command));\n return EXIT.USAGE;\n }\n\n const args = rest.slice(argsStart);\n const unknown = args.find(isUnknownFlag);\n if (unknown !== undefined) {\n io.errline(`birdybeep ${path}: unknown option \"${unknown}\".`);\n return EXIT.USAGE;\n }\n\n let code: number;\n try {\n code = await command.run({ args, flags, io });\n } catch (err) {\n if (err instanceof MissingInputError) {\n io.errline(\n `birdybeep ${path}: ${err.message} (re-run without --non-interactive to be prompted).`,\n );\n return EXIT.USAGE;\n }\n io.errline(`birdybeep ${path}: ${err instanceof Error ? err.message : String(err)}`);\n return EXIT.ERROR;\n }\n\n // Opportunistic, best-effort update notice (never alters the command's exit code or stdout).\n if (deps.notifyUpdate !== undefined) {\n try {\n await deps.notifyUpdate({ command: pathParts[0] ?? \"\", flags, io });\n } catch {\n /* the notifier is best-effort; a failure must not affect the command result */\n }\n }\n return code;\n}\n","/**\n * `birdybeep doctor` (§9.4, §21.1–21.2) — the self-service troubleshooter. Runs a battery\n * of checks (machine token, each adapter's doctor() incl. needs_trust/needs_restart/error,\n * local queue health, backend reachability), prints a concrete copy-pasteable fix for each\n * failure, drains the queue opportunistically, and exits non-zero when anything fails so\n * it's CI/script friendly. Read-only (never mutates harness config); never prints token\n * material or notification bodies. `--json` mirrors all findings.\n */\nimport {\n type AgentAdapter,\n createSender as defaultCreateSender,\n type Sender,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\nimport { claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { codexAdapter } from \"@birdybeep/codex\";\nimport { opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { isPaired, localQueueDepth } from \"../diagnostics\";\nimport { type Command, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [claudeCodeAdapter, codexAdapter, opencodeAdapter];\n\ninterface Check {\n name: string;\n ok: boolean;\n detail?: string;\n remedy?: string;\n}\n\n/** Best-effort backend reachability probe (HEAD; any non-5xx response = reachable). */\nasync function defaultProbeNetwork(baseUrl: string): Promise<boolean> {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 3000);\n if (typeof timer.unref === \"function\") timer.unref();\n const res = await fetch(baseUrl, { method: \"HEAD\", signal: controller.signal });\n clearTimeout(timer);\n return res.status < 500;\n } catch {\n return false;\n }\n}\n\nexport interface DoctorCommandDeps {\n adapters?: AgentAdapter[];\n createSender?: (baseUrl: string) => Sender;\n tokenOptions?: TokenStoreOptions;\n /** Backend reachability probe (tests inject reachable/unreachable). */\n probeNetwork?: (baseUrl: string) => Promise<boolean>;\n}\n\nexport function createDoctorCommand(deps: DoctorCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n const probeNetwork = deps.probeNetwork ?? defaultProbeNetwork;\n const makeSender =\n deps.createSender ??\n ((baseUrl) =>\n defaultCreateSender(\n deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl },\n ));\n\n return {\n name: \"doctor\",\n summary: \"Diagnose token, trust, restart, and offline-queue issues\",\n usage: \"birdybeep doctor [--json]\",\n run: async (ctx) => {\n const checks: Check[] = [];\n const apiUrl = resolveApiUrl();\n\n // 1. Machine token.\n const paired = await isPaired(deps.tokenOptions ?? {});\n checks.push(\n paired\n ? { name: \"Machine token\", ok: true }\n : {\n name: \"Machine token\",\n ok: false,\n detail: \"No machine token found.\",\n remedy: \"Run `birdybeep pair` to pair this machine.\",\n },\n );\n\n // 2. Each adapter's own diagnostics (detected? installed? needs_trust/needs_restart/error?).\n for (const adapter of adapters) {\n const result = await adapter.doctor();\n for (const c of result.checks) {\n checks.push({\n name: `${adapter.displayName}: ${c.name}`,\n ok: c.ok,\n ...(c.detail !== undefined ? { detail: c.detail } : {}),\n ...(c.remedy !== undefined ? { remedy: c.remedy } : {}),\n });\n }\n }\n\n // 3. Local queue: drain opportunistically, report depth.\n const depthBefore = localQueueDepth();\n const drain = await makeSender(apiUrl).drainNow();\n const depthAfter = localQueueDepth();\n checks.push({\n name: \"Local queue\",\n ok: true,\n detail: `${depthBefore} queued → ${drain.delivered} delivered, ${depthAfter} remaining`,\n });\n\n // 4. Backend reachability.\n const reachable = await probeNetwork(apiUrl);\n checks.push(\n reachable\n ? { name: \"Backend reachable\", ok: true }\n : {\n name: \"Backend reachable\",\n ok: false,\n detail: `Could not reach ${apiUrl}.`,\n remedy: \"Check your network; queued events will retry automatically.\",\n },\n );\n\n const ok = checks.every((c) => c.ok);\n\n if (ctx.flags.json) {\n ctx.io.result({\n ok,\n checks,\n queue: { depthBefore, delivered: drain.delivered, depthAfter },\n });\n } else {\n for (const c of checks) {\n ctx.io.line(`${c.ok ? \"✓\" : \"✗\"} ${c.name}${c.detail ? ` — ${c.detail}` : \"\"}`);\n if (!c.ok && c.remedy) ctx.io.line(` → ${c.remedy}`);\n }\n ctx.io.line(ok ? \"\\nAll checks passed.\" : \"\\nSome checks failed — see fixes above.\");\n }\n return ok ? EXIT.OK : EXIT.ERROR;\n },\n };\n}\n","/**\n * Non-secret CLI config (§9.4): a small `config.json` in the BirdyBeep user config dir\n * holding things like the API base URL. The machine TOKEN never lives here — it is read\n * exclusively from the secure token store (keychain / strict-perm file). Tolerant readers:\n * a missing/corrupt config falls back to defaults rather than crashing the hot path.\n */\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { birdyBeepConfigDir } from \"@birdybeep/agent-core\";\n\n/** Default backend base URL (overridable via env or `birdybeep pair`; finalized in a-release). */\nexport const DEFAULT_API_URL = \"https://api.birdybeep.com\";\nexport const CONFIG_FILE = \"config.json\";\n\nexport interface CliConfig {\n /** Backend base URL (set by `pair`); never holds a token. */\n apiUrl?: string;\n}\n\nexport function cliConfigPath(): string {\n return join(birdyBeepConfigDir(), CONFIG_FILE);\n}\n\n/** Read the CLI config; returns `{}` on a missing/unreadable/corrupt file (never throws). */\nexport function readCliConfig(): CliConfig {\n try {\n const parsed: unknown = JSON.parse(readFileSync(cliConfigPath(), \"utf8\"));\n return typeof parsed === \"object\" && parsed !== null ? parsed : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Merge + persist non-secret CLI config (strict-perm dir). Only the KNOWN non-secret keys\n * are ever written — anything else (e.g. a token someone passed by mistake) is dropped, so\n * the token can only ever live in the secure store, never here.\n */\nexport function writeCliConfig(patch: CliConfig): void {\n const current = readCliConfig();\n const merged: CliConfig = {};\n const apiUrl = patch.apiUrl ?? current.apiUrl;\n if (apiUrl !== undefined) merged.apiUrl = apiUrl;\n mkdirSync(birdyBeepConfigDir(), { recursive: true, mode: 0o700 });\n writeFileSync(cliConfigPath(), `${JSON.stringify(merged, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Resolve the backend base URL: `BIRDYBEEP_API_URL` env → CLI config → default. */\nexport function resolveApiUrl(): string {\n const env = process.env[\"BIRDYBEEP_API_URL\"];\n if (env !== undefined && env.length > 0) return env;\n return readCliConfig().apiUrl ?? DEFAULT_API_URL;\n}\n\n/** Public npm registry — where `@birdybeep/cli` is published; used by the update notifier. */\nexport const DEFAULT_REGISTRY_URL = \"https://registry.npmjs.org\";\n\n/**\n * Resolve the npm registry base URL for the passive update check: honor `npm_config_registry`\n * (which npm/pnpm/yarn export, so a private-registry user's mirror is respected) and fall back to\n * the public registry. Never carries auth or a token.\n */\nexport function resolveRegistryUrl(): string {\n const env = process.env[\"npm_config_registry\"];\n if (env !== undefined && env.length > 0) return env;\n return DEFAULT_REGISTRY_URL;\n}\n","/**\n * Shared status/queue plumbing used by `birdybeep status` and `birdybeep doctor`: gather\n * each adapter's integration status, the machine identity + pairing state, and local queue\n * depth. Read-only + privacy-safe — never prints token material or notification bodies.\n */\nimport {\n type AgentAdapter,\n getMachineIdentity,\n getToken,\n type IntegrationStatus,\n LocalEventQueue,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\n\nexport interface IntegrationState {\n harness: string;\n displayName: string;\n status: IntegrationStatus;\n}\n\n/** Each adapter's current §8.8 integration status (runs the real adapter.status()). */\nexport async function gatherIntegrations(adapters: AgentAdapter[]): Promise<IntegrationState[]> {\n return Promise.all(\n adapters.map(async (a) => ({\n harness: a.id,\n displayName: a.displayName,\n status: await a.status(),\n })),\n );\n}\n\n/** Is a machine token present in the secure store? (pairing state — never prints the token.) */\nexport async function isPaired(tokenOptions: TokenStoreOptions = {}): Promise<boolean> {\n return (await getToken(tokenOptions)) !== null;\n}\n\n/** Current local event-queue depth (fresh, non-expired entries). */\nexport function localQueueDepth(): number {\n return new LocalEventQueue().size();\n}\n\n/** Machine label + OS (the event `machine` identity). */\nexport function machineIdentity(): { label: string; os: string } {\n return getMachineIdentity();\n}\n","/**\n * `birdybeep hook <claude|codex|opencode>` (§9.2–9.3) — the hot-path entrypoint every\n * installed adapter config invokes when its harness fires a lifecycle event. It reads the\n * raw payload (from the trailing arg for Codex's notify argv, else from stdin), selects the\n * named harness's `runXHook` (normalize → redact/hash/truncate → dedup → send w/ short\n * timeout → queue-on-fail → opportunistic drain → fast return), and ALWAYS exits 0 so it\n * never errors the harness. The token is read by the sender from the secure store — never\n * from config — and notification content is never persisted (the adapters' normalizers\n * enforce that).\n *\n * Built as a factory so the sender + stdin reader are injectable: tests drive the full\n * dispatch → command → pipeline → stub-sink path hermetically, exactly like the adapter E2Es.\n */\nimport {\n createSender as defaultCreateSender,\n type HookResult,\n type Sender,\n} from \"@birdybeep/agent-core\";\nimport { runClaudeHook } from \"@birdybeep/claude-code\";\nimport { runCodexHook } from \"@birdybeep/codex\";\nimport { runOpenCodeHook } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\n\nexport type HarnessName = \"claude\" | \"codex\" | \"opencode\";\n\ntype HarnessRunner = (input: unknown, options: { sender: Sender }) => Promise<HookResult>;\n\nconst RUNNERS: Record<HarnessName, HarnessRunner> = {\n claude: runClaudeHook,\n codex: runCodexHook,\n opencode: runOpenCodeHook,\n};\n\nexport const HOOK_HARNESSES: readonly HarnessName[] = [\"claude\", \"codex\", \"opencode\"];\n\n/**\n * Hard cap on reading the payload — a misbehaving harness must never hang the hook.\n * 3s (was 2s, erm): a loaded machine can be slow to flush a pipe, and a timeout here\n * silently DROPS the event (\"skipped\"). BUDGET MATH: this cap and the sender's\n * DEFAULT_TOTAL_BUDGET_MS (5s) run SEQUENTIALLY and must sum comfortably under the 10s\n * hook timeout the adapters register, leaving headroom for Node startup — 3s + 5s + ~1s\n * startup < 10s. (5s + 5s summed to exactly the timeout: a slow start got the hook\n * SIGKILLed mid-send, which skips the queue-on-failure catch and loses the event.)\n */\nexport const STDIN_READ_TIMEOUT_MS = 3000;\n\n/** Resolve to `fallback` if `promise` does not settle within `ms` (the timer is unref'd). */\nfunction withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {\n return new Promise<T>((resolve) => {\n let settled = false;\n const finish = (value: T): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(value);\n };\n const timer = setTimeout(() => finish(fallback), ms);\n if (typeof timer.unref === \"function\") timer.unref();\n void promise.then(finish, () => finish(fallback));\n });\n}\n\nexport function isHarnessName(value: string | undefined): value is HarnessName {\n return value === \"claude\" || value === \"codex\" || value === \"opencode\";\n}\n\n/** Run one hook fire: select the harness runner and execute via the shared pipeline. */\nexport function runHookCommand(\n harness: HarnessName,\n payload: unknown,\n sender: Sender,\n): Promise<HookResult> {\n return RUNNERS[harness](payload, { sender });\n}\n\n/** Read process.stdin to EOF (the harness pipes a small JSON then closes); never throws. */\nfunction readStdinDefault(): Promise<string> {\n return new Promise((resolve) => {\n if (process.stdin.isTTY) {\n resolve(\"\");\n return;\n }\n let data = \"\";\n process.stdin.setEncoding(\"utf8\");\n process.stdin.on(\"data\", (chunk: string) => (data += chunk));\n process.stdin.on(\"end\", () => resolve(data));\n process.stdin.on(\"error\", () => resolve(\"\"));\n });\n}\n\n/** Resolve the raw payload: the trailing arg (Codex notify argv) wins, else read stdin. */\nexport async function readHookPayload(\n args: string[],\n readStdin: () => Promise<string>,\n): Promise<string> {\n return args[1] ?? (await readStdin());\n}\n\nexport interface HookCommandDeps {\n /** Build the sender (default: agent-core `createSender` with the resolved API URL). */\n createSender?: (baseUrl: string) => Sender;\n /** Read the raw payload from stdin (default: real process.stdin). */\n readStdin?: () => Promise<string>;\n /** Hard cap on the payload read (default {@link STDIN_READ_TIMEOUT_MS}); tests shrink it. */\n stdinTimeoutMs?: number;\n}\n\n/** Build the `hook` command. Pure stubs aside, this is the live event path. */\nexport function createHookCommand(deps: HookCommandDeps = {}): Command {\n const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender({ baseUrl }));\n const readStdin = deps.readStdin ?? readStdinDefault;\n const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;\n\n return {\n name: \"hook\",\n summary: \"Internal: normalize + send an event fired by a harness hook\",\n usage: \"birdybeep hook <claude|codex|opencode>\",\n run: async (ctx) => {\n const harness = ctx.args[0];\n if (!isHarnessName(harness)) {\n ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join(\"|\")}`);\n return EXIT.USAGE;\n }\n\n // Bounded read: the trailing argv payload resolves instantly; a hung/never-closing\n // stdin falls back to \"\" after the timeout so the hook ALWAYS returns fast (§9.3).\n const raw = await withTimeout(readHookPayload(ctx.args, readStdin), stdinTimeoutMs, \"\");\n let payload: unknown;\n try {\n payload = JSON.parse(raw);\n } catch {\n // Garbled/empty payload → skip silently + fast. Never error the harness.\n ctx.io.result({ harness, outcome: \"skipped\" });\n return EXIT.OK;\n }\n\n const sender = makeSender(resolveApiUrl());\n const result = await runHookCommand(harness, payload, sender);\n // Hot path: human mode is silent; --json emits the outcome for scripts/debugging.\n ctx.io.result({ harness, outcome: result.outcome, eventType: result.eventType });\n return EXIT.OK; // delivered/queued/deduped/skipped all return fast + non-erroring\n },\n };\n}\n","/**\n * `birdybeep logout` / `birdybeep unpair` (§9.4) — remove the local machine token from BOTH\n * the OS keychain and the strict-perm file fallback. `unpair` is the pairing-vocabulary twin\n * of `pair` and `logout` is the familiar sign-out verb; they are the SAME operation, so both\n * are offered. Idempotent (no error when already signed out). Does NOT touch harness\n * integration config (that is `agent uninstall`) or the local queue.\n */\nimport { clearToken, type TokenStoreOptions } from \"@birdybeep/agent-core\";\n\nimport { type Command, EXIT } from \"../framework\";\n\nexport interface LogoutCommandDeps {\n /** Token-store options (tests inject the file fallback). */\n tokenOptions?: TokenStoreOptions;\n}\n\n/**\n * Build a token-clearing command. `logout` and `unpair` share this one handler — only the\n * command name, help copy, and the human/JSON confirmation differ.\n */\nfunction createClearTokenCommand(\n spec: { name: \"logout\" | \"unpair\"; summary: string; humanMessage: string; jsonKey: string },\n deps: LogoutCommandDeps = {},\n): Command {\n return {\n name: spec.name,\n summary: spec.summary,\n usage: `birdybeep ${spec.name}`,\n run: async (ctx) => {\n await clearToken(deps.tokenOptions ?? {});\n ctx.io.emit(spec.humanMessage, { [spec.jsonKey]: true });\n return EXIT.OK;\n },\n };\n}\n\nexport function createLogoutCommand(deps: LogoutCommandDeps = {}): Command {\n return createClearTokenCommand(\n {\n name: \"logout\",\n summary: \"Remove the local machine token (same as `unpair`)\",\n humanMessage: \"Logged out — the machine token was removed.\",\n jsonKey: \"loggedOut\",\n },\n deps,\n );\n}\n\nexport function createUnpairCommand(deps: LogoutCommandDeps = {}): Command {\n return createClearTokenCommand(\n {\n name: \"unpair\",\n summary: \"Unpair this machine — remove the local machine token (same as `logout`)\",\n humanMessage: \"Unpaired — the machine token was removed.\",\n jsonKey: \"unpaired\",\n },\n deps,\n );\n}\n","/**\n * `birdybeep pair` (§7.1/§7.2/§9.4) — pair this machine via the device-code flow.\n * `POST /v1/pair/start` (machine_label derived from hostname/OS) → show a scannable\n * QR matrix + the pair link + `user_code` → poll `POST /v1/pair/token` with the device\n * code (+ stable machine fingerprint) until it returns the durable token or the\n * `expires_at` (10-min) deadline. The issued token is stored in the SECURE store only\n * (keychain / strict-perm file — never config or the QR); the non-secret apiUrl is\n * persisted. Per SPEC §11 the QR/code carries only short-lived pairing info.\n *\n * The QR matrix (birdybeep-agent-pe1) renders only on an interactive TTY — piped/CI\n * output keeps the plain link + code lines, which are ALWAYS printed as the SSH/\n * headless fallback (docs/pairing.md \"Headless and SSH machines\"). In `--json` mode\n * the pairing info is emitted as an NDJSON line up front (status \"pairing_started\")\n * so scripts/agents can read the code and approve — previously json mode printed\n * nothing until success, making scripted pairing impossible (birdybeep-agent-pe1).\n *\n * fetch/sleep/clock/QR/TTY are injectable for hermetic tests.\n */\nimport { getMachineIdentity, setToken, type TokenStoreOptions } from \"@birdybeep/agent-core\";\n// uqr is the CLI's ONLY third-party runtime dep (MIT, itself zero-dependency), pinned\n// EXACTLY in package.json: QR encoding (Reed–Solomon + masking) is too error-prone to\n// vendor, and a floating range would defeat the small-auditable-supply-chain goal (§16.4).\nimport { renderUnicodeCompact } from \"uqr\";\n\nimport { resolveApiUrl, writeCliConfig } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\nimport { pairStart, pairTokenPoll, type PairTokenResult } from \"../pairing\";\nimport { CLI_VERSION } from \"../version\";\n\n/** Default delay between `/pair/token` polls (the start response has no interval). */\nexport const DEFAULT_POLL_INTERVAL_MS = 2000;\n\n/**\n * How often to reprint a \"still waiting…\" heartbeat while polling. Without it, `pair`\n * prints the code once and then appears frozen (\"stuck doing nothing\") for the whole\n * 10-minute window — the reported bug. Time-gated on the injected clock so it never\n * fires spuriously in the fast, instant-sleep tests.\n */\nexport const HEARTBEAT_MS = 15_000;\n\n/**\n * Render the QR payload as a terminal-scannable half-block matrix. `border: 2` keeps a\n * quiet zone around the symbol (phone cameras misread flush-against-text QRs).\n */\nexport function renderQrMatrix(qrPayload: string): string {\n return renderUnicodeCompact(qrPayload, { border: 2 });\n}\n\nexport interface PairCommandDeps {\n fetchImpl?: typeof fetch;\n tokenOptions?: TokenStoreOptions;\n /** Injectable delay between polls (default real setTimeout; tests make it instant). */\n sleep?: (ms: number) => Promise<void>;\n /** Injectable clock for the expiry deadline (default Date.now). */\n now?: () => number;\n /** Render the QR payload as a matrix (default {@link renderQrMatrix} via uqr). */\n renderQr?: (qrPayload: string) => string;\n /** Whether stdout is an interactive terminal (default process.stdout.isTTY). The QR\n * matrix renders only on a TTY — piped output stays plain text. */\n isTTY?: boolean;\n pollIntervalMs?: number;\n}\n\nexport function createPairCommand(deps: PairCommandDeps = {}): Command {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n const clock = deps.now ?? (() => Date.now());\n const renderQr = deps.renderQr ?? renderQrMatrix;\n const intervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n\n return {\n name: \"pair\",\n summary: \"Pair this machine with your BirdyBeep account (QR or manual)\",\n usage: \"birdybeep pair [--json]\",\n run: async (ctx) => {\n const apiUrl = resolveApiUrl();\n const identity = getMachineIdentity(); // { label, os, fingerprintHash }\n const start = await pairStart(\n apiUrl,\n { machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION },\n fetchImpl,\n );\n\n if (ctx.flags.json) {\n // NDJSON: emit the pairing info NOW so a script/agent can surface the code for\n // approval while we poll; the final success object is a later line (pe1).\n ctx.io.result({\n status: \"pairing_started\",\n user_code: start.user_code,\n qr_payload: start.qr_payload,\n expires_at: start.expires_at,\n });\n } else {\n // Point at the RELIABLE path: the in-app scanner. Opening the https link only\n // reaches the approval screen where universal/app links are configured; scanning\n // (or typing the code) in the app always works, so lead with that.\n ctx.io.line(\n \"To pair this machine, open the BirdyBeep app, tap “pair a machine”, and scan this QR (or enter the code):\",\n );\n // The matrix is TTY-only (a piped/CI consumer wants greppable lines, and\n // half-block art garbles logs); the link + code lines below ALWAYS print.\n const isTTY = deps.isTTY ?? process.stdout.isTTY === true;\n if (isTTY) ctx.io.line(renderQr(start.qr_payload));\n ctx.io.line(` Scan or open: ${start.qr_payload}`);\n ctx.io.line(` Code: ${start.user_code}`);\n ctx.io.line(\"Waiting for you to approve this machine in the app…\");\n }\n\n // Poll /pair/token until approved (201), a TERMINAL error, or the window expires.\n const deadline = Date.parse(start.expires_at);\n const startedAt = clock();\n let lastBeat = startedAt;\n let paired: PairTokenResult | undefined;\n let terminal: Extract<PairTokenResult, { status: \"error\" }> | undefined;\n for (;;) {\n const nowMs = clock();\n if (nowMs >= deadline) break;\n await sleep(intervalMs);\n const poll = await pairTokenPoll(\n apiUrl,\n start.device_code,\n fetchImpl,\n identity.fingerprintHash,\n );\n if (poll.status === \"paired\") {\n paired = poll;\n break;\n }\n // A failure that waiting can't fix (e.g. the agent-install cap) must STOP the loop\n // and be shown — never masked as \"not approved yet\" so the prompt hangs silently.\n if (poll.status === \"error\" && !poll.retryable) {\n terminal = poll;\n break;\n }\n // Otherwise pending (not approved yet) or a transient server error → keep waiting,\n // reprinting a heartbeat so the prompt is visibly alive. Human-mode only (NDJSON\n // stays a clean two-line stream); time-gated on the clock so tests never see it.\n if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {\n ctx.io.line(\n poll.status === \"error\"\n ? ` still trying — the server is busy (${poll.message}). approve in the app when you can…`\n : \" still waiting — approve this machine in the BirdyBeep app…\",\n );\n lastBeat = nowMs;\n }\n }\n\n if (terminal !== undefined) {\n // NDJSON: a terminal result object on stderr+stdout so scripts see the reason code.\n ctx.io.result({ paired: false, reason: terminal.code });\n ctx.io.errline(`Pairing failed: ${terminal.message}`);\n return EXIT.ERROR;\n }\n\n if (paired === undefined || paired.status !== \"paired\") {\n // NDJSON contract: json mode gets a TERMINAL result object on every exit path,\n // so scripts can key off the last parseable line instead of only the exit code.\n ctx.io.result({ paired: false, reason: \"timeout\" });\n ctx.io.errline(\n \"Pairing timed out before you approved it. In the BirdyBeep app, tap “pair a machine”, scan the QR (or enter the code), then run `birdybeep pair` again.\",\n );\n return EXIT.ERROR;\n }\n\n // Durable token → secure store ONLY. Non-secret apiUrl → config. Never the reverse.\n await setToken(paired.machineToken, deps.tokenOptions ?? {});\n writeCliConfig({ apiUrl });\n\n ctx.io.emit(`✓ Paired. Run \\`birdybeep test\\` to send a test Beep.`, {\n paired: true,\n machineId: paired.machineId,\n });\n return EXIT.OK;\n },\n };\n}\n","/**\n * CLI pairing client — the device-code flow (§7.2/§13.4). `pairStart` opens a session via\n * `POST /v1/pair/start`; the CLI shows `qr_payload` + `user_code`, then polls\n * `POST /v1/pair/token` (`pairTokenPoll`) until it returns 201 `{ machine_token, machine_id }`\n * or the `expires_at` deadline. A `validation_failed`/4xx during polling means \"not approved\n * yet — keep polling\". Per SPEC §11 the QR / user code carries only short-lived pairing info,\n * NEVER a durable token. Request/response shapes are mirrored from the product (agent-core).\n */\nimport {\n type ErrorCode,\n errorEnvelopeSchema,\n type PairStartResponse,\n pairStartResponseSchema,\n pairTokenResponseSchema,\n} from \"@birdybeep/agent-core\";\n\nfunction base(apiUrl: string): string {\n return apiUrl.replace(/\\/$/, \"\");\n}\n\nexport interface PairStartInput {\n /** Required — the human machine label (derived from hostname/OS). */\n machineLabel: string;\n os?: string;\n cliVersion?: string;\n}\n\n/** Begin a pairing session (`POST /v1/pair/start`, unauthenticated). */\nexport async function pairStart(\n apiUrl: string,\n input: PairStartInput,\n fetchImpl: typeof fetch,\n): Promise<PairStartResponse> {\n const body = {\n machine_label: input.machineLabel,\n ...(input.os !== undefined ? { os: input.os } : {}),\n ...(input.cliVersion !== undefined ? { cli_version: input.cliVersion } : {}),\n };\n const res = await fetchImpl(`${base(apiUrl)}/v1/pair/start`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw new Error(`pairing could not be started (HTTP ${res.status})`);\n const parsed = pairStartResponseSchema.safeParse(await res.json());\n if (!parsed.success) throw new Error(\"pairing start returned an unexpected response shape\");\n return parsed.data;\n}\n\nexport type PairTokenResult =\n | { status: \"pending\" }\n | { status: \"paired\"; machineToken: string; machineId: string }\n /**\n * The backend returned an outcome that will NOT resolve by waiting (`retryable: false`,\n * e.g. `quota_exceeded` — the install cap is hit) or a transient server-side failure\n * (`retryable: true`, e.g. `internal_error`/5xx). Surfacing these is what stops `pair`\n * from masking a real error as \"not approved yet\" and hanging silently until timeout.\n */\n | { status: \"error\"; code: ErrorCode | \"unknown\"; message: string; retryable: boolean };\n\n/**\n * Terminal error codes on `/v1/pair/token`: waiting can never turn them into a 201, so the\n * CLI must STOP polling and show the user the reason. `quota_exceeded` (the agent-install cap)\n * is the one a real user actually hits; the auth-shaped codes should never occur on this\n * unauthenticated endpoint but are treated as terminal defensively (never loop forever).\n */\nconst TERMINAL_TOKEN_ERRORS: ReadonlySet<ErrorCode> = new Set<ErrorCode>([\n \"quota_exceeded\",\n \"unauthorized\",\n \"forbidden\",\n \"token_revoked\",\n \"not_found\",\n \"payload_too_large\",\n]);\n\n/**\n * Poll once for the device token (`POST /v1/pair/token`, unauthenticated). Outcomes:\n * - 201 with a valid token body → `paired`.\n * - `validation_failed`/4xx (the documented \"not approved yet\" signal) → `pending`, so the\n * caller keeps polling until the `expires_at` deadline.\n * - a TERMINAL error (e.g. `quota_exceeded`) → `error` with `retryable: false` — the caller\n * surfaces it and stops, instead of hanging silently on a failure waiting can't fix.\n * - `rate_limited`/`internal_error`/5xx/unparseable → `error` with `retryable: true` — the\n * caller keeps polling (transient) but can warn if it persists.\n */\nexport async function pairTokenPoll(\n apiUrl: string,\n deviceCode: string,\n fetchImpl: typeof fetch,\n machineFingerprint?: string,\n): Promise<PairTokenResult> {\n const body = {\n device_code: deviceCode,\n ...(machineFingerprint !== undefined ? { machine_fingerprint: machineFingerprint } : {}),\n };\n const res = await fetchImpl(`${base(apiUrl)}/v1/pair/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (res.ok) {\n const parsed = pairTokenResponseSchema.safeParse(await res.json());\n if (!parsed.success) return { status: \"pending\" };\n return {\n status: \"paired\",\n machineToken: parsed.data.machine_token,\n machineId: parsed.data.machine_id,\n };\n }\n\n // Non-2xx: read the typed §13.4 error envelope to tell \"not approved yet\" (keep polling)\n // apart from a real failure the user must see. A body that isn't a parseable envelope falls\n // back to the status code.\n let errBody: unknown = null;\n try {\n errBody = await res.json();\n } catch {\n /* empty / non-JSON error body → classify by status below */\n }\n const env = errorEnvelopeSchema.safeParse(errBody);\n const code = env.success ? env.data.error.code : undefined;\n\n // \"not approved yet\" is the documented benign signal → keep polling. Also treat any\n // unclassifiable 4xx (except 429) as pending, preserving the endpoint's historical\n // accept-and-keep-waiting behavior.\n if (\n code === \"validation_failed\" ||\n (code === undefined && res.status >= 400 && res.status < 500 && res.status !== 429)\n ) {\n return { status: \"pending\" };\n }\n\n const message = env.success ? env.data.error.message : `pairing failed (HTTP ${res.status})`;\n if (code !== undefined && TERMINAL_TOKEN_ERRORS.has(code)) {\n return { status: \"error\", code, message, retryable: false };\n }\n // rate_limited / internal_error / any 5xx / unrecognized → transient; safe to keep polling.\n return { status: \"error\", code: code ?? \"unknown\", message, retryable: true };\n}\n","/**\n * CLI version — single-sourced from package.json at build time (s0o7). `tsup.config.ts`\n * injects the real `@birdybeep/cli` version via the `__CLI_VERSION__` esbuild define, so\n * the shipped binary reports its true version for `--version` and the `cli_version` it\n * sends on `/pair/start` (the mobile approval sheet's machine identity). The `0.0.0`\n * fallback only applies to non-bundled runs (vitest / tsx), where the define is absent.\n */\n\n/** Build-time-replaced global; declared so source typechecks before tsup substitutes it. */\ndeclare const __CLI_VERSION__: string | undefined;\n\nexport const CLI_VERSION: string =\n typeof __CLI_VERSION__ === \"string\" && __CLI_VERSION__.length > 0 ? __CLI_VERSION__ : \"0.0.0\";\n","/**\n * `birdybeep queue clear` (§9.4) — debug maintenance: drop all locally-queued events. The\n * queue is best-effort (≤24h retention), so clearing it only discards pending retries; it\n * never touches harness config or the token. Reports how many entries were removed.\n */\nimport { LocalEventQueue } from \"@birdybeep/agent-core\";\n\nimport { type Command, EXIT } from \"../framework\";\n\nexport function createQueueCommand(): Command {\n return {\n name: \"queue\",\n summary: \"Local event-queue maintenance\",\n usage: \"birdybeep queue <clear>\",\n subcommands: [\n {\n name: \"clear\",\n summary: \"Clear the local offline event queue (debug)\",\n usage: \"birdybeep queue clear\",\n run: (ctx) => {\n const cleared = new LocalEventQueue().clear();\n ctx.io.emit(`Cleared ${cleared} queued event(s).`, { cleared });\n return EXIT.OK;\n },\n },\n ],\n };\n}\n","/**\n * `birdybeep report-status` (§7.3 step 7, §8.8, §21.2) — push each adapter's pre-event\n * integration status to the backend so the Machines/Integrations screen shows them BEFORE\n * any agent event fires. Sends ONE BATCHED `POST /v1/integrations/status` request\n * ({ integrations: [...] }, machine-token auth), parses the `{ integrations: [...] }`\n * response (surfacing the server's EFFECTIVE status, e.g. Codex → needs_trust), and parses\n * the mirrored error envelope: a 401/403 (unauthorized / forbidden / token_revoked) is\n * TERMINAL (exit non-zero), while offline / 5xx / rate_limit is \"deferred\" (surfaced, exit 0)\n * so it never blocks install.\n *\n * Request/response/error shapes are mirrored from the product (agent-core). fetch/adapters/\n * token injectable for hermetic tests; the live post is the deferred cross-repo follow-up.\n */\nimport {\n type AgentAdapter,\n errorEnvelopeSchema,\n getToken,\n type IntegrationStatusItem,\n integrationStatusResponseSchema,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\nimport { CLAUDE_CODE_ADAPTER_VERSION, claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { CODEX_ADAPTER_VERSION, codexAdapter } from \"@birdybeep/codex\";\nimport { OPENCODE_ADAPTER_VERSION, opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [claudeCodeAdapter, codexAdapter, opencodeAdapter];\n\n/** Per-harness BirdyBeep adapter version (the schema's optional `adapter_version`). */\nconst ADAPTER_VERSIONS: Record<string, string> = {\n claude_code: CLAUDE_CODE_ADAPTER_VERSION,\n codex: CODEX_ADAPTER_VERSION,\n opencode: OPENCODE_ADAPTER_VERSION,\n};\n\nconst base = (apiUrl: string): string => apiUrl.replace(/\\/$/, \"\");\n\nasync function gatherItems(adapters: AgentAdapter[]): Promise<IntegrationStatusItem[]> {\n return Promise.all(\n adapters.map(async (a) => {\n const [detection, status] = await Promise.all([a.detect(), a.status()]);\n const item: IntegrationStatusItem = { harness: a.id, status };\n if (detection.version !== undefined) item.harness_version = detection.version;\n const adapterVersion = ADAPTER_VERSIONS[a.id];\n if (adapterVersion !== undefined) item.adapter_version = adapterVersion;\n return item;\n }),\n );\n}\n\nexport interface ReportStatusCommandDeps {\n adapters?: AgentAdapter[];\n fetchImpl?: typeof fetch;\n tokenOptions?: TokenStoreOptions;\n}\n\nexport function createReportStatusCommand(deps: ReportStatusCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n const fetchImpl = deps.fetchImpl ?? fetch;\n\n return {\n name: \"report-status\",\n summary: \"Internal: report integration status to the backend\",\n usage: \"birdybeep report-status [--json]\",\n run: async (ctx) => {\n const token = await getToken(deps.tokenOptions ?? {});\n if (token === null) {\n ctx.io.errline(\"No machine token — run `birdybeep pair` first.\");\n return EXIT.ERROR;\n }\n\n const items = await gatherItems(adapters);\n if (items.length === 0) {\n ctx.io.emit(\"No integrations to report.\", { outcome: \"reported\", integrations: [] });\n return EXIT.OK;\n }\n\n // The effective per-harness status to display; defaults to what we sent, overwritten by\n // the server's response when it 200s.\n let effective = items.map((i) => ({ harness: i.harness, status: i.status }));\n let outcome: \"reported\" | \"deferred\" | \"terminal\" = \"deferred\";\n let errorCode: string | undefined;\n\n try {\n const res = await fetchImpl(`${base(resolveApiUrl())}/v1/integrations/status`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${token}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ integrations: items }),\n });\n if (res.ok) {\n outcome = \"reported\";\n const parsed = integrationStatusResponseSchema.safeParse(\n await res.json().catch(() => undefined),\n );\n if (parsed.success) {\n effective = parsed.data.integrations.map((i) => ({\n harness: i.harness,\n status: i.status,\n }));\n }\n } else {\n const env = errorEnvelopeSchema.safeParse(await res.json().catch(() => undefined));\n errorCode = env.success ? env.data.error.code : undefined;\n // The error CODE is the canonical terminal signal (auth failures); HTTP status is\n // only the fallback when the envelope didn't parse. Everything else → deferred.\n const terminal =\n errorCode !== undefined\n ? errorCode === \"unauthorized\" ||\n errorCode === \"forbidden\" ||\n errorCode === \"token_revoked\"\n : res.status === 401 || res.status === 403;\n outcome = terminal ? \"terminal\" : \"deferred\";\n }\n } catch {\n outcome = \"deferred\"; // offline / transport error → surfaced, not fatal\n }\n\n if (ctx.flags.json) {\n ctx.io.result({\n outcome,\n integrations: effective,\n ...(errorCode !== undefined ? { error: errorCode } : {}),\n });\n } else if (outcome === \"terminal\") {\n ctx.io.errline(\n `Report rejected (${errorCode ?? \"auth\"}) — your token may be revoked. Re-run \\`birdybeep pair\\`.`,\n );\n } else {\n for (const e of effective) {\n ctx.io.line(\n outcome === \"reported\"\n ? `✓ ${e.harness}: ${e.status} (reported)`\n : `• ${e.harness}: ${e.status} (deferred — backend unreachable)`,\n );\n }\n }\n\n // Terminal auth failure → non-zero; offline/deferred → 0 (must never block install).\n return outcome === \"terminal\" ? EXIT.ERROR : EXIT.OK;\n },\n };\n}\n","/**\n * `birdybeep status` (§9.3, §9.4) — a quick health snapshot: machine identity + pairing\n * state, per-harness integration status, and local queue depth, while opportunistically\n * draining the queue (best-effort, non-blocking) and reporting delivered-vs-remaining.\n * Exits non-zero when not paired so scripts can branch. `--json` mirrors everything.\n * Factory with injectable adapters/sender/token so tests run hermetically against a stub.\n */\nimport {\n type AgentAdapter,\n createSender as defaultCreateSender,\n type Sender,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\nimport { claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { codexAdapter } from \"@birdybeep/codex\";\nimport { opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { gatherIntegrations, isPaired, localQueueDepth, machineIdentity } from \"../diagnostics\";\nimport { type Command, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [claudeCodeAdapter, codexAdapter, opencodeAdapter];\n\nexport interface StatusCommandDeps {\n adapters?: AgentAdapter[];\n /** Build the drain sender (default: agent-core createSender at the resolved API URL). */\n createSender?: (baseUrl: string) => Sender;\n /** Token-store options (tests inject the file fallback). */\n tokenOptions?: TokenStoreOptions;\n}\n\nexport function createStatusCommand(deps: StatusCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n const makeSender =\n deps.createSender ??\n ((baseUrl) =>\n defaultCreateSender(\n deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl },\n ));\n\n return {\n name: \"status\",\n summary: \"Show pairing + per-harness integration status\",\n usage: \"birdybeep status [--json]\",\n run: async (ctx) => {\n const machine = machineIdentity();\n const paired = await isPaired(deps.tokenOptions ?? {});\n const integrations = await gatherIntegrations(adapters);\n const depthBefore = localQueueDepth();\n const drain = await makeSender(resolveApiUrl()).drainNow(); // opportunistic, best-effort\n const depthAfter = localQueueDepth();\n\n const report = {\n machine,\n paired,\n integrations,\n queue: { depthBefore, delivered: drain.delivered, depthAfter },\n };\n\n if (ctx.flags.json) {\n ctx.io.result(report);\n } else {\n ctx.io.line(`Machine: ${machine.label} (${machine.os})`);\n ctx.io.line(paired ? \"Paired: yes\" : \"Paired: no — run `birdybeep pair`\");\n ctx.io.line(\"Integrations:\");\n for (const i of integrations) ctx.io.line(` ${i.displayName}: ${i.status}`);\n ctx.io.line(\n `Queue: ${depthBefore} queued → ${drain.delivered} delivered, ${depthAfter} remaining`,\n );\n }\n return paired ? EXIT.OK : EXIT.ERROR; // not-paired → defined non-zero\n },\n };\n}\n","/**\n * `birdybeep test` (§7.1, §9.4) — send a representative test event through the REAL sender\n * path (normalize/redact/truncate → send w/ short timeout → queue-on-fail → opportunistic\n * drain) so a developer can confirm end-to-end delivery (and trigger a test Beep) right\n * after pairing. Not a mock — it exercises the production code path. Reports delivered vs\n * queued (offline) vs rejected; --json mirrors the outcome.\n *\n * Sends event_type \"test\" (9fh): the backend notifies it by default and exempts it from\n * the beep quota. (The old \"custom\" type is unconditionally suppressed by the §10.5\n * matrix — every test \"succeeded\" while no push could ever be sent.) The session id is\n * unique per run so back-to-back tests don't collapse in the backend's dedupe window,\n * and the CLI reports the backend's actual DECISION instead of assuming a beep.\n */\nimport { randomUUID } from \"node:crypto\";\n\nimport {\n type BirdyBeepAgentEvent,\n createSender as defaultCreateSender,\n getMachineIdentity,\n normalizeEvent,\n type NormalizeOptions,\n type Sender,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\n\n/** Build the canonical test event (event_type `test`, unique session per run). cwd is hashed by the normalizer. */\nexport function buildTestEvent(opts: NormalizeOptions = {}): BirdyBeepAgentEvent {\n const machine = getMachineIdentity();\n return normalizeEvent(\n {\n event_type: \"test\",\n status: \"running\",\n harness: \"claude_code\", // schema requires a harness; the \"test\" type distinguishes it\n // Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must\n // still beep — a constant id made the second test silently \"deduped\" (9fh).\n source_session_id: `birdybeep-cli-test-${randomUUID()}`,\n machine: { label: machine.label, os: machine.os },\n workspace: { cwd: process.cwd() },\n title: \"BirdyBeep test event\",\n body: \"If you can see this, your machine is wired up correctly.\",\n metadata: { test: true },\n },\n opts,\n );\n}\n\nexport interface TestCommandDeps {\n createSender?: (baseUrl: string) => Sender;\n tokenOptions?: TokenStoreOptions;\n}\n\nexport function createTestCommand(deps: TestCommandDeps = {}): Command {\n const makeSender =\n deps.createSender ??\n ((baseUrl) =>\n defaultCreateSender(\n deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl },\n ));\n\n return {\n name: \"test\",\n summary: \"Send a test event end-to-end\",\n usage: \"birdybeep test [--json]\",\n run: async (ctx) => {\n const event = buildTestEvent();\n const result = await makeSender(resolveApiUrl()).send(event); // real path; also drains the queue\n\n if (ctx.flags.json) {\n ctx.io.result({\n outcome: result.outcome,\n ...(result.status ? { status: result.status } : {}),\n ...(result.decision ? { decision: result.decision } : {}),\n });\n } else if (result.outcome === \"delivered\") {\n // The 202 body says what the backend DECIDED — \"delivered\" alone only means\n // \"accepted\". Claiming a beep that was suppressed is how 9fh went unnoticed.\n if (result.decision === \"notified\" || result.decision === undefined) {\n ctx.io.line(\"✓ Test event delivered — check your phone for a test Beep.\");\n } else if (result.decision === \"suppressed\") {\n ctx.io.line(\n \"⚠ The backend accepted the test event but suppressed the push — this machine \" +\n \"or integration is probably muted. Check mutes in the app, or run `birdybeep doctor`.\",\n );\n } else if (result.decision === \"deduped\") {\n ctx.io.line(\n \"⚠ The backend accepted the test event but folded it into a recent duplicate — \" +\n \"wait ~30s and run `birdybeep test` again.\",\n );\n } else {\n ctx.io.line(\n `⚠ The backend accepted the test event but decided \"${result.decision}\" — no push ` +\n \"was sent. Run `birdybeep doctor`.\",\n );\n }\n } else if (result.outcome === \"queued\") {\n ctx.io.line(\"• Offline — test event queued; it will deliver when you reconnect.\");\n } else {\n ctx.io.line(\"✗ Test event was rejected by the backend. Run `birdybeep doctor`.\");\n }\n\n // delivered + queued are non-failure (offline is by design); a hard reject is an error.\n return result.outcome === \"dropped\" ? EXIT.ERROR : EXIT.OK;\n },\n };\n}\n","/**\n * The `birdybeep` command registry (§9.4) — the command tree the framework dispatches.\n * Every command is a factory (`create*Command`) so its dependencies (adapters, sender,\n * token store, fetch, stdin) are injectable for hermetic tests; the framework (help /\n * flags / routing / config dir / exit codes) is command-independent.\n */\nimport { createAgentCommand } from \"./commands/agent\";\nimport { createDoctorCommand } from \"./commands/doctor\";\nimport { createHookCommand } from \"./commands/hook\";\nimport { createLogoutCommand, createUnpairCommand } from \"./commands/logout\";\nimport { createPairCommand } from \"./commands/pair\";\nimport { createQueueCommand } from \"./commands/queue\";\nimport { createReportStatusCommand } from \"./commands/report-status\";\nimport { createStatusCommand } from \"./commands/status\";\nimport { createTestCommand } from \"./commands/test\";\nimport { type Command } from \"./framework\";\n\n/** Build the full §9.4 command tree. */\nexport function buildCommands(): Command[] {\n return [\n createPairCommand(),\n createLogoutCommand(),\n createUnpairCommand(),\n createStatusCommand(),\n createTestCommand(),\n createDoctorCommand(),\n createAgentCommand(),\n createHookCommand(),\n createQueueCommand(),\n createReportStatusCommand(),\n ];\n}\n","/**\n * Passive update notifier (§9.4). Instead of a manual `update` command, the CLI opportunistically\n * checks the npm registry for a newer `@birdybeep/cli` and prints a subtle \"new version available\"\n * notice to **stderr** after an eligible command runs — so users learn about upgrades just by using\n * the tool. It is:\n *\n * - **Cached (TTL-gated):** the result is stored in the config dir and only refreshed from the\n * network once per {@link DEFAULT_CHECK_INTERVAL_MS}; every other run is a local file read.\n * - **Non-blocking to the hot path:** the `hook` command (which runs inside the harness and must\n * return fast) and the internal `report-status` command are skipped before any I/O.\n * - **Quiet for machines/scripts:** skipped under `--json`, `--non-interactive`, a non-TTY stderr,\n * `CI`, or the `NO_UPDATE_NOTIFIER` / `BIRDYBEEP_NO_UPDATE_NOTIFIER` opt-outs.\n * - **Best-effort & side-effect-free on the result:** it never throws, never changes stdout, and\n * never affects the command's exit code (registry/semver logic lives here, not in the framework).\n */\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { birdyBeepConfigDir } from \"@birdybeep/agent-core\";\n\nimport { resolveRegistryUrl } from \"./config\";\nimport { type GlobalFlags, type Io } from \"./framework\";\nimport { CLI_VERSION } from \"./version\";\n\n/** The published package the notice points at. */\nexport const PACKAGE_NAME = \"@birdybeep/cli\";\n/** URL-encoded scoped path for the registry `latest` dist-tag endpoint. */\nconst PACKAGE_PATH = \"@birdybeep%2Fcli\";\n/** Cache file (non-secret) in the BirdyBeep config dir. */\nexport const UPDATE_CACHE_FILE = \"update-check.json\";\n/** Refresh the registry at most once per this window; every other run reads the cache. */\nexport const DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h\n/** Best-effort timeout for the (rare) registry refresh — short so it can't stall a command. */\nconst DEFAULT_TIMEOUT_MS = 1500;\n\n/**\n * Top-level commands that must never trigger a check/notice:\n * - `hook` runs inside the harness hot path and must return fast (never block the harness);\n * - `report-status` is invoked by BirdyBeep itself, not by an interactive user.\n */\nconst SKIP_COMMANDS = new Set([\"hook\", \"report-status\"]);\n\n/** A parsed semver: numeric core + dot-separated prerelease identifiers (build metadata dropped). */\nexport interface Semver {\n major: number;\n minor: number;\n patch: number;\n /** Prerelease identifiers (e.g. `1.2.0-beta.1` → `[\"beta\", \"1\"]`); empty for a release. */\n prerelease: string[];\n}\n\n// Simplified semver.org grammar: `MAJOR.MINOR.PATCH[-prerelease][+build]`, tolerating a leading `v`.\nconst SEMVER_RE = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/;\n\n/** Parse a semver string; returns null for anything that isn't a clean `MAJOR.MINOR.PATCH[...]`. */\nexport function parseSemver(input: string): Semver | null {\n const m = SEMVER_RE.exec(input.trim());\n if (m === null) return null;\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n prerelease: m[4] !== undefined ? m[4].split(\".\") : [],\n };\n}\n\n/** Compare two prerelease identifier lists per semver §11 (a release outranks any prerelease). */\nfunction comparePrerelease(a: string[], b: string[]): number {\n if (a.length === 0 && b.length === 0) return 0;\n if (a.length === 0) return 1; // 1.2.0 > 1.2.0-beta\n if (b.length === 0) return -1;\n const len = Math.min(a.length, b.length);\n for (let i = 0; i < len; i++) {\n const ai = a[i]!;\n const bi = b[i]!;\n const aNum = /^\\d+$/.test(ai);\n const bNum = /^\\d+$/.test(bi);\n if (aNum && bNum) {\n const d = Number(ai) - Number(bi);\n if (d !== 0) return d < 0 ? -1 : 1;\n } else if (aNum) {\n return -1; // numeric identifiers rank lower than alphanumeric\n } else if (bNum) {\n return 1;\n } else if (ai !== bi) {\n return ai < bi ? -1 : 1; // ASCII lexical order\n }\n }\n if (a.length === b.length) return 0;\n return a.length < b.length ? -1 : 1; // more identifiers wins when all preceding are equal\n}\n\n/** -1 if `a < b`, 0 if equal, 1 if `a > b` (semver precedence). */\nexport function compareSemver(a: Semver, b: Semver): number {\n if (a.major !== b.major) return a.major < b.major ? -1 : 1;\n if (a.minor !== b.minor) return a.minor < b.minor ? -1 : 1;\n if (a.patch !== b.patch) return a.patch < b.patch ? -1 : 1;\n return comparePrerelease(a.prerelease, b.prerelease);\n}\n\n/** `true` when `latest` is a strictly higher version than `current` (both must parse). */\nexport function isNewer(current: string, latest: string): boolean {\n const cur = parseSemver(current);\n const lat = parseSemver(latest);\n return cur !== null && lat !== null && compareSemver(cur, lat) < 0;\n}\n\n/** Cached registry result. `latest` is the last-seen published version, or null if never fetched. */\nexport interface UpdateCache {\n /** Epoch ms of the last registry refresh attempt. */\n checkedAt: number;\n latest: string | null;\n}\n\nexport function updateCachePath(): string {\n return join(birdyBeepConfigDir(), UPDATE_CACHE_FILE);\n}\n\n/** Read the cache; returns null on a missing/unreadable/corrupt/invalid file (never throws). */\nexport function readUpdateCache(): UpdateCache | null {\n try {\n const parsed: unknown = JSON.parse(readFileSync(updateCachePath(), \"utf8\"));\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const { checkedAt, latest } = parsed as Record<string, unknown>;\n if (typeof checkedAt !== \"number\") return null;\n if (latest !== null && typeof latest !== \"string\") return null;\n return { checkedAt, latest };\n } catch {\n return null;\n }\n}\n\n/** Persist the cache (strict-perm dir + file); best-effort — a write failure is swallowed by callers. */\nexport function writeUpdateCache(cache: UpdateCache): void {\n mkdirSync(birdyBeepConfigDir(), { recursive: true, mode: 0o700 });\n writeFileSync(updateCachePath(), `${JSON.stringify(cache)}\\n`, { mode: 0o600 });\n}\n\n/** Fetch the `latest` dist-tag version from the registry, or throw a concise reason. */\nasync function fetchLatestVersion(\n registryUrl: string,\n fetchImpl: typeof fetch,\n timeoutMs: number,\n): Promise<string> {\n const url = `${registryUrl.replace(/\\/+$/, \"\")}/${PACKAGE_PATH}/latest`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n if (typeof timer.unref === \"function\") timer.unref();\n try {\n const res = await fetchImpl(url, {\n headers: { accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!res.ok) throw new Error(`registry responded ${res.status}`);\n const body = (await res.json()) as { version?: unknown };\n if (typeof body.version !== \"string\" || body.version.length === 0) {\n throw new Error(\"registry response had no version\");\n }\n return body.version;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** The two-line upgrade notice printed to stderr (lowercase/chirpy per the Perch voice). */\nfunction renderNotice(current: string, latest: string): string {\n return (\n `a new version of birdybeep is available: ${current} → ${latest}\\n` +\n `upgrade with: npm install -g ${PACKAGE_NAME}@latest`\n );\n}\n\nexport interface NotifyUpdateOptions {\n /** Resolved top-level command name (used to skip `hook` / `report-status`). */\n command?: string;\n flags: GlobalFlags;\n io: Io;\n // --- injectables (production defaults are the real registry / fs / clock / env / TTY) ---\n fetchImpl?: typeof fetch;\n currentVersion?: string;\n registryUrl?: string;\n now?: number;\n intervalMs?: number;\n timeoutMs?: number;\n /** Override the stderr-TTY gate (tests set this true to exercise the notice deterministically). */\n isTTY?: boolean;\n env?: NodeJS.ProcessEnv;\n readCache?: () => UpdateCache | null;\n writeCache?: (cache: UpdateCache) => void;\n}\n\n/**\n * The notifier entry point, invoked by the framework after an eligible command runs. Reads the\n * cache, refreshes from the registry when stale (TTL-gated, short timeout, best-effort), and prints\n * the upgrade notice to stderr when a newer version exists. Never throws.\n */\nexport async function maybeNotifyUpdate(opts: NotifyUpdateOptions): Promise<void> {\n try {\n // Hot-path / internal commands: bail before any work so the harness is never slowed.\n if (opts.command !== undefined && SKIP_COMMANDS.has(opts.command)) return;\n // Machine/script output or explicit non-interactive: no chatter on stderr.\n if (opts.flags.json || opts.flags.nonInteractive) return;\n\n const env = opts.env ?? process.env;\n if (env[\"BIRDYBEEP_NO_UPDATE_NOTIFIER\"] || env[\"NO_UPDATE_NOTIFIER\"] || env[\"CI\"]) return;\n\n const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);\n if (!isTTY) return; // don't nag in pipes/logs\n\n const current = opts.currentVersion ?? CLI_VERSION;\n const now = opts.now ?? Date.now();\n const intervalMs = opts.intervalMs ?? DEFAULT_CHECK_INTERVAL_MS;\n const readCache = opts.readCache ?? readUpdateCache;\n const writeCache = opts.writeCache ?? writeUpdateCache;\n\n let cache = readCache();\n if (cache === null || now - cache.checkedAt >= intervalMs) {\n // Refresh at most once per interval. On failure, keep the last-known `latest` (so a\n // previously-seen update still shows) but still stamp `checkedAt` to back off, never hammer.\n let latest = cache?.latest ?? null;\n try {\n latest = await fetchLatestVersion(\n opts.registryUrl ?? resolveRegistryUrl(),\n opts.fetchImpl ?? fetch,\n opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n );\n } catch {\n /* offline / registry error: fall back to last-known latest, back off for the interval */\n }\n cache = { checkedAt: now, latest };\n try {\n writeCache(cache);\n } catch {\n /* config dir not writable: notice still works this run, just won't be cached */\n }\n }\n\n if (cache.latest !== null && isNewer(current, cache.latest)) {\n opts.io.errline(renderNotice(current, cache.latest));\n }\n } catch {\n /* the notifier is best-effort — it must never break or slow a command */\n }\n}\n","/**\n * @birdybeep/cli — the public, side-effect-free CLI API. `runCli` wires the §9.4 command\n * registry into the framework dispatcher with injectable output (so it is fully unit\n * testable); the executable shell lives in `bin.ts`.\n */\nimport { buildCommands } from \"./commands\";\nimport { type Command, dispatch, type Writer } from \"./framework\";\nimport { maybeNotifyUpdate, type NotifyUpdateOptions } from \"./update-check\";\nimport { CLI_VERSION } from \"./version\";\n\nexport { buildCommands } from \"./commands\";\nexport * from \"./framework\";\nexport { CLI_VERSION } from \"./version\";\n\nexport interface RunCliDeps {\n stdout?: Writer;\n stderr?: Writer;\n /** Override the command registry (tests). Defaults to the real §9.4 tree. */\n commands?: Command[];\n /** Skip the config-dir bootstrap (tests without filesystem side effects). */\n ensureConfig?: boolean;\n /**\n * Override the passive update-notifier. `false` disables it; an object injects the registry\n * fetch / clock / TTY / cache for hermetic tests. Omitted in production → the real notifier\n * (which no-ops on a non-TTY stderr, so unit tests capturing to buffers stay offline & quiet).\n */\n updateCheck?: Partial<NotifyUpdateOptions> | false;\n}\n\n/** Run the CLI against an argv slice (without `node`/script path). Returns the exit code. */\nexport function runCli(argv: string[], deps: RunCliDeps = {}): Promise<number> {\n const notifyUpdate =\n deps.updateCheck === false\n ? undefined\n : (ctx: {\n command: string;\n flags: NotifyUpdateOptions[\"flags\"];\n io: NotifyUpdateOptions[\"io\"];\n }) => maybeNotifyUpdate({ ...ctx, ...(deps.updateCheck ?? {}) });\n\n return dispatch(argv, {\n version: CLI_VERSION,\n commands: deps.commands ?? buildCommands(),\n stdout: deps.stdout ?? process.stdout,\n stderr: deps.stderr ?? process.stderr,\n ...(notifyUpdate !== undefined ? { notifyUpdate } : {}),\n ...(deps.ensureConfig !== undefined ? { ensureConfig: deps.ensureConfig } : {}),\n });\n}\n"],"mappings":"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","names":["import_agent_core","import_claude_code","import_codex","import_opencode","import_node_fs","import_agent_core","import_agent_core","DEFAULT_ADAPTERS","defaultCreateSender","import_agent_core","import_claude_code","import_codex","import_opencode","defaultCreateSender","import_agent_core","import_agent_core","import_agent_core","import_agent_core","import_agent_core","import_claude_code","import_codex","import_opencode","DEFAULT_ADAPTERS","base","import_agent_core","import_claude_code","import_codex","import_opencode","DEFAULT_ADAPTERS","defaultCreateSender","import_agent_core","defaultCreateSender","import_node_fs","import_node_path","import_agent_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/agent.ts","../src/framework.ts","../src/commands/doctor.ts","../src/config.ts","../src/diagnostics.ts","../src/commands/hook.ts","../src/commands/logout.ts","../src/commands/pair.ts","../src/pairing.ts","../src/version.ts","../src/commands/queue.ts","../src/commands/report-status.ts","../src/commands/status.ts","../src/commands/test.ts","../src/commands.ts","../src/update-check.ts","../src/cli.ts"],"sourcesContent":["/**\n * @birdybeep/cli — library entry. Re-exports the side-effect-free CLI API so the\n * package ships a real `.d.ts` and is importable for testing/embedding. The\n * executable lives in `bin.ts` (the only module with a shebang + `process` side\n * effects); keeping it separate stops the shebang from leaking into this entry's\n * type declarations.\n */\nexport * from \"./cli.js\";\n","/**\n * `birdybeep agent install|uninstall [all|claude|codex|opencode|cursor|copilot]` (§7.3, §9.4) — the\n * once-per-machine setup half: detect supported harnesses and run each adapter's\n * idempotent, non-destructive install/uninstall. Adds ONLY BirdyBeep-managed entries\n * (existing config backed up + preserved), the installed config invokes\n * `birdybeep hook <harness>`, and NO durable token is ever written into harness/repo\n * config — the hook reads the token from the secure store at runtime. Prints the changed\n * files + any required user action (Codex `/hooks` trust, OpenCode restart).\n *\n * Built as a factory with an injectable adapter set so tests exercise the REAL adapter\n * installs under a temp HOME with deterministic detection.\n */\nimport type { AgentAdapter, InstallResult } from \"@birdybeep/agent-core\";\nimport { claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { codexAdapter } from \"@birdybeep/codex\";\nimport { copilotAdapter } from \"@birdybeep/copilot\";\nimport { cursorAdapter } from \"@birdybeep/cursor\";\nimport { opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { type Command, type CommandContext, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [\n claudeCodeAdapter,\n codexAdapter,\n opencodeAdapter,\n cursorAdapter,\n copilotAdapter,\n];\n\n/** CLI short target name → adapter id (the CLI says `claude`, the adapter id is `claude_code`). */\nconst TARGET_TO_ID: Record<string, string> = {\n claude: \"claude_code\",\n codex: \"codex\",\n opencode: \"opencode\",\n cursor: \"cursor\",\n copilot: \"copilot\",\n};\n\nexport const AGENT_TARGETS: readonly string[] = [\n \"all\",\n \"claude\",\n \"codex\",\n \"opencode\",\n \"cursor\",\n \"copilot\",\n];\n\n/** Resolve a target to the adapter(s) it names, or `\"unknown\"` for a bad target. */\nexport function selectAdapters(\n target: string,\n adapters: AgentAdapter[],\n): AgentAdapter[] | \"unknown\" {\n if (target === \"all\") return adapters;\n const id = TARGET_TO_ID[target];\n if (id === undefined) return \"unknown\";\n return adapters.filter((a) => a.id === id);\n}\n\ninterface InstallOutcome {\n harness: string;\n displayName: string;\n detected: boolean;\n status?: InstallResult[\"status\"];\n changedFiles?: string[];\n backupFiles?: string[];\n requiredActions?: string[];\n}\n\nasync function installSelected(adapters: AgentAdapter[], ctx: CommandContext): Promise<number> {\n const target = ctx.args[0] ?? \"all\";\n const selected = selectAdapters(target, adapters);\n if (selected === \"unknown\") {\n ctx.io.errline(\n `birdybeep agent install: unknown target \"${target}\" (expected ${AGENT_TARGETS.join(\"|\")}).`,\n );\n return EXIT.USAGE;\n }\n\n const outcomes: InstallOutcome[] = [];\n for (const adapter of selected) {\n const detection = await adapter.detect();\n if (!detection.detected) {\n outcomes.push({ harness: adapter.id, displayName: adapter.displayName, detected: false });\n continue;\n }\n const result = await adapter.install();\n outcomes.push({\n harness: adapter.id,\n displayName: adapter.displayName,\n detected: true,\n status: result.status,\n changedFiles: result.changedFiles,\n backupFiles: result.backupFiles,\n requiredActions: result.requiredActions,\n });\n }\n\n if (ctx.flags.json) {\n ctx.io.result({ target, results: outcomes });\n return EXIT.OK;\n }\n\n if (outcomes.length === 0 || outcomes.every((o) => !o.detected)) {\n ctx.io.line(\"No supported harnesses detected — nothing to install.\");\n }\n for (const o of outcomes) {\n if (!o.detected) {\n ctx.io.line(`– ${o.displayName}: not detected (skipped)`);\n continue;\n }\n const changed = (o.changedFiles ?? []).length > 0 ? o.changedFiles!.join(\", \") : \"no changes\";\n ctx.io.line(`✓ ${o.displayName}: ${o.status} (${changed})`);\n for (const action of o.requiredActions ?? []) ctx.io.line(` → ${action}`);\n }\n return EXIT.OK;\n}\n\ninterface UninstallOutcome {\n harness: string;\n displayName: string;\n changed: boolean;\n removedFiles: string[];\n restoredFiles: string[];\n}\n\nasync function uninstallSelected(adapters: AgentAdapter[], ctx: CommandContext): Promise<number> {\n const target = ctx.args[0] ?? \"all\";\n const selected = selectAdapters(target, adapters);\n if (selected === \"unknown\") {\n ctx.io.errline(\n `birdybeep agent uninstall: unknown target \"${target}\" (expected ${AGENT_TARGETS.join(\"|\")}).`,\n );\n return EXIT.USAGE;\n }\n\n const outcomes: UninstallOutcome[] = [];\n for (const adapter of selected) {\n // Uninstall is safe + idempotent even if nothing is installed (a no-op).\n const result = await adapter.uninstall();\n outcomes.push({\n harness: adapter.id,\n displayName: adapter.displayName,\n changed: result.changed,\n removedFiles: result.removedFiles,\n restoredFiles: result.restoredFiles,\n });\n }\n\n if (ctx.flags.json) {\n ctx.io.result({ target, results: outcomes });\n return EXIT.OK;\n }\n for (const o of outcomes) {\n if (!o.changed) {\n ctx.io.line(`– ${o.displayName}: nothing to remove`);\n continue;\n }\n const touched = [...o.removedFiles, ...o.restoredFiles].join(\", \") || \"config restored\";\n ctx.io.line(`✓ ${o.displayName}: removed (${touched})`);\n }\n return EXIT.OK;\n}\n\nexport interface AgentCommandDeps {\n /** Adapter set (tests inject deterministic detection). Defaults to all supported adapters. */\n adapters?: AgentAdapter[];\n}\n\n/** Build the `agent` command group (install + uninstall, both via the adapter contract). */\nexport function createAgentCommand(deps: AgentCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n return {\n name: \"agent\",\n summary: \"Install or uninstall harness adapters\",\n usage: \"birdybeep agent <install|uninstall> [all|claude|codex|opencode|cursor|copilot]\",\n subcommands: [\n {\n name: \"install\",\n summary: \"Install adapters (all | claude | codex | opencode | cursor | copilot)\",\n usage: \"birdybeep agent install [all|claude|codex|opencode|cursor|copilot]\",\n run: (ctx) => installSelected(adapters, ctx),\n },\n {\n name: \"uninstall\",\n summary: \"Restore harness config to its pre-install state\",\n usage: \"birdybeep agent uninstall [all|claude|codex|opencode|cursor|copilot]\",\n run: (ctx) => uninstallSelected(adapters, ctx),\n },\n ],\n };\n}\n","/**\n * The CLI framework (§9.4): a small zero-dependency command dispatcher every `birdybeep`\n * command plugs into. Owns global flag parsing (`--json` / `--non-interactive` /\n * `--version` / `--help`), nested subcommand routing, help rendering, the config-dir\n * bootstrap, a json-aware output layer, and a shared exit-code convention. Network/auth,\n * adapter, and secret logic live in the individual commands — never here.\n *\n * Kept dependency-light on purpose: this code installs into developers' machines, so the\n * smaller + more auditable the surface, the better (§16.4).\n */\nimport { mkdirSync } from \"node:fs\";\n\nimport { birdyBeepConfigDir } from \"@birdybeep/agent-core\";\n\n/** Shared exit-code convention so callers (humans + agents) can branch on the result. */\nexport const EXIT = { OK: 0, ERROR: 1, USAGE: 2 } as const;\n\n/** A minimal output sink (process.stdout/stderr in prod; capturing buffers in tests). */\nexport interface Writer {\n write(s: string): void;\n}\n\nexport interface GlobalFlags {\n /** Machine-readable JSON output for agents/scripts. */\n json: boolean;\n /** Never prompt; fail fast (non-zero) when a required value is missing. */\n nonInteractive: boolean;\n help: boolean;\n version: boolean;\n}\n\n/** Json-aware output. `line`/`result` are mutually exclusive by mode so stdout stays clean. */\nexport interface Io {\n readonly json: boolean;\n /** Human line → stdout (suppressed in `--json` mode). */\n line(text: string): void;\n /** Always → stderr (errors/warnings show in both modes). */\n errline(text: string): void;\n /** Structured result → stdout as JSON (only in `--json` mode). */\n result(value: unknown): void;\n /** Emit the right one for the mode: human text, or the structured value as JSON. */\n emit(human: string, json: unknown): void;\n}\n\nexport function createIo(json: boolean, stdout: Writer, stderr: Writer): Io {\n return {\n json,\n line: (text) => {\n if (!json) stdout.write(`${text}\\n`);\n },\n errline: (text) => stderr.write(`${text}\\n`),\n result: (value) => {\n if (json) stdout.write(`${JSON.stringify(value)}\\n`);\n },\n emit: (human, value) => {\n if (json) stdout.write(`${JSON.stringify(value)}\\n`);\n else stdout.write(`${human}\\n`);\n },\n };\n}\n\nexport interface CommandContext {\n /** Positional args after the resolved command path. */\n args: string[];\n flags: GlobalFlags;\n io: Io;\n}\n\n/** A per-command flag: its accepted spellings, optional value placeholder, and help text. */\nexport interface CommandOption {\n /** Primary flag token, e.g. `\"--expect-email\"`. */\n flag: string;\n /** Extra accepted spellings, e.g. `[\"-y\"]`. */\n aliases?: readonly string[];\n /** Value placeholder shown in help (e.g. `\"<addr>\"`); omit for boolean flags. */\n value?: string;\n summary: string;\n}\n\nexport interface Command {\n name: string;\n summary: string;\n /** One-line usage shown in the command's own `--help`. */\n usage?: string;\n /** Nested subcommands (e.g. `agent install` / `agent uninstall`). */\n subcommands?: Command[];\n /**\n * Flags this command accepts IN ADDITION to the global ones. Without this allowlist the\n * dispatcher rejects every non-global flag as an unknown option, so a command that owns\n * flags must declare them here — and declaring them also documents them in `--help`.\n * Both `--flag value` and `--flag=value` are accepted; parsing stays the command's job.\n */\n options?: readonly CommandOption[];\n /** Command logic; returns the intended exit code. Absent for pure command groups. */\n run?(ctx: CommandContext): Promise<number> | number;\n}\n\n/** Thrown by a command when a required value is missing under `--non-interactive`. */\nexport class MissingInputError extends Error {\n constructor(readonly field: string) {\n super(`missing required value: ${field}`);\n this.name = \"MissingInputError\";\n }\n}\n\n/**\n * Resolve a value that may require interaction. Returns `provided` when present; otherwise\n * throws {@link MissingInputError} under `--non-interactive` (so the CLI fails fast instead\n * of hanging), or returns undefined for the caller to prompt in interactive mode.\n */\nexport function requireValue<T>(ctx: CommandContext, field: string, provided: T | undefined): T {\n if (provided !== undefined) return provided;\n if (ctx.flags.nonInteractive) throw new MissingInputError(field);\n throw new MissingInputError(field); // interactive prompting is a per-command concern; default fail-fast\n}\n\nconst GLOBAL_FLAG_TOKENS = new Set([\n \"--json\",\n \"--non-interactive\",\n \"--version\",\n \"-v\",\n \"--help\",\n \"-h\",\n]);\n\n/** Split a raw argv into global flags + the remaining (command path + positional) tokens. */\nexport function parseGlobalFlags(argv: string[]): { flags: GlobalFlags; rest: string[] } {\n const flags: GlobalFlags = { json: false, nonInteractive: false, help: false, version: false };\n const rest: string[] = [];\n for (const token of argv) {\n switch (token) {\n case \"--json\":\n flags.json = true;\n break;\n case \"--non-interactive\":\n flags.nonInteractive = true;\n break;\n case \"--version\":\n case \"-v\":\n flags.version = true;\n break;\n case \"--help\":\n case \"-h\":\n flags.help = true;\n break;\n default:\n rest.push(token);\n }\n }\n return { flags, rest };\n}\n\n/**\n * Is `token` an unknown long/short flag (after global flags were stripped)? `allowed` carries\n * the resolved command's own {@link Command.options}.\n *\n * The GLOBAL check is EXACT, deliberately: {@link parseGlobalFlags} consumes global flags by\n * exact token, so `--json=true` is NOT a global flag — it stays in the command's args. If this\n * guard split on `=` before the global lookup, `--json=true` / `--non-interactive=1` / `-v=2`\n * would sail past it, never be consumed, and silently run the command in the WRONG MODE with\n * the token landing in the positional args. They must be rejected as usage errors instead.\n * Only a COMMAND's own options accept the `--flag=value` spelling (see {@link Command.options}).\n */\nfunction isUnknownFlag(token: string, allowed: ReadonlySet<string>): boolean {\n if (!token.startsWith(\"-\")) return false;\n if (GLOBAL_FLAG_TOKENS.has(token)) return false; // exact match only — mirrors parseGlobalFlags\n const eq = token.indexOf(\"=\");\n return !allowed.has(eq >= 0 ? token.slice(0, eq) : token);\n}\n\nfunction renderRootHelp(version: string, commands: Command[]): string {\n const width = Math.max(...commands.map((c) => c.name.length));\n const lines = commands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);\n return [\n `birdybeep ${version} — stream coding-agent lifecycle events to BirdyBeep.`,\n \"\",\n \"Usage:\",\n \" birdybeep <command> [options]\",\n \"\",\n \"Commands:\",\n ...lines,\n \"\",\n \"Global options:\",\n \" --json Machine-readable JSON output\",\n \" --non-interactive Never prompt; fail fast if input is required\",\n \" -h, --help Show help (root or per-command)\",\n \" -v, --version Show the CLI version\",\n ].join(\"\\n\");\n}\n\n/**\n * Every flag token (primary + aliases) the resolved command accepts beyond the global ones.\n * A subcommand inherits its PARENT group's options too (`...commands` unions them in), so a\n * flag declared once on a group works on every leaf under it rather than being rejected there.\n */\nfunction commandFlagTokens(...commands: (Command | undefined)[]): ReadonlySet<string> {\n const tokens = new Set<string>();\n for (const command of commands) {\n for (const option of command?.options ?? []) {\n tokens.add(option.flag);\n for (const alias of option.aliases ?? []) tokens.add(alias);\n }\n }\n return tokens;\n}\n\nfunction renderCommandHelp(path: string, command: Command): string {\n const lines = [\n `birdybeep ${path} — ${command.summary}`,\n \"\",\n \"Usage:\",\n ` ${command.usage ?? `birdybeep ${path} [options]`}`,\n ];\n if (command.options && command.options.length > 0) {\n const labels = command.options.map(\n (o) => `${[o.flag, ...(o.aliases ?? [])].join(\", \")}${o.value ? ` ${o.value}` : \"\"}`,\n );\n const width = Math.max(...labels.map((l) => l.length));\n lines.push(\n \"\",\n \"Options:\",\n ...command.options.map((o, i) => ` ${labels[i]?.padEnd(width)} ${o.summary}`),\n );\n }\n if (command.subcommands && command.subcommands.length > 0) {\n const width = Math.max(...command.subcommands.map((c) => c.name.length));\n lines.push(\n \"\",\n \"Subcommands:\",\n ...command.subcommands.map((c) => ` ${c.name.padEnd(width)} ${c.summary}`),\n );\n }\n return lines.join(\"\\n\");\n}\n\nexport interface DispatchDeps {\n version: string;\n commands: Command[];\n stdout: Writer;\n stderr: Writer;\n /** Skip the config-dir bootstrap (tests that don't want filesystem side effects). */\n ensureConfig?: boolean;\n /**\n * Optional post-command update notifier, invoked after a command runs successfully (not for\n * help/version). The framework only invokes it — all registry/cache/semver logic lives in the\n * CLI layer (`update-check.ts`), never here — and its failure never affects the command result.\n */\n notifyUpdate?: (ctx: { command: string; flags: GlobalFlags; io: Io }) => Promise<void>;\n}\n\n/**\n * Run the CLI against an argv slice (without `node`/script path). Resolves the command\n * (with nested subcommands), handles `--help`/`--version`, and returns the exit code.\n * Never throws — command errors become a stderr message + {@link EXIT.ERROR}.\n */\nexport async function dispatch(argv: string[], deps: DispatchDeps): Promise<number> {\n const { flags, rest } = parseGlobalFlags(argv);\n const io = createIo(flags.json, deps.stdout, deps.stderr);\n\n // Config dir is created on first run (non-secret CLI config only — never a token).\n if (deps.ensureConfig !== false) {\n try {\n mkdirSync(birdyBeepConfigDir(), { recursive: true, mode: 0o700 });\n } catch {\n /* non-fatal: a read-only config dir is surfaced by `doctor`, not here */\n }\n }\n\n if (flags.version) {\n io.emit(deps.version, { version: deps.version });\n return EXIT.OK;\n }\n\n // Resolve the command path (supports one level of nested subcommands).\n let command: Command | undefined = deps.commands.find((c) => c.name === rest[0]);\n /** The group a resolved subcommand sits under (undefined for a top-level command). */\n let parent: Command | undefined;\n const pathParts: string[] = [];\n let argsStart = 1;\n if (command) {\n pathParts.push(command.name);\n if (command.subcommands && command.subcommands.length > 0) {\n const sub = command.subcommands.find((c) => c.name === rest[1]);\n if (sub) {\n parent = command;\n command = sub;\n pathParts.push(sub.name);\n argsStart = 2;\n }\n }\n }\n\n if (rest.length === 0 || (flags.help && command === undefined)) {\n io.emit(renderRootHelp(deps.version, deps.commands), {\n version: deps.version,\n commands: deps.commands.map((c) => ({ name: c.name, summary: c.summary })),\n });\n return EXIT.OK;\n }\n\n if (command === undefined) {\n io.errline(`birdybeep: unknown command \"${rest[0]}\". Run \\`birdybeep --help\\`.`);\n return EXIT.USAGE;\n }\n\n const path = pathParts.join(\" \");\n if (flags.help) {\n io.emit(renderCommandHelp(path, command), {\n name: path,\n summary: command.summary,\n usage: command.usage,\n options: command.options,\n subcommands: command.subcommands?.map((c) => ({ name: c.name, summary: c.summary })),\n });\n return EXIT.OK;\n }\n\n if (command.run === undefined) {\n // A pure command group invoked without a subcommand → show its help as a usage error.\n io.errline(renderCommandHelp(path, command));\n return EXIT.USAGE;\n }\n\n const args = rest.slice(argsStart);\n const allowed = commandFlagTokens(command, parent);\n const unknown = args.find((token) => isUnknownFlag(token, allowed));\n if (unknown !== undefined) {\n io.errline(`birdybeep ${path}: unknown option \"${unknown}\".`);\n return EXIT.USAGE;\n }\n\n let code: number;\n try {\n code = await command.run({ args, flags, io });\n } catch (err) {\n if (err instanceof MissingInputError) {\n io.errline(\n `birdybeep ${path}: ${err.message} (re-run without --non-interactive to be prompted).`,\n );\n return EXIT.USAGE;\n }\n io.errline(`birdybeep ${path}: ${err instanceof Error ? err.message : String(err)}`);\n return EXIT.ERROR;\n }\n\n // Opportunistic, best-effort update notice (never alters the command's exit code or stdout).\n if (deps.notifyUpdate !== undefined) {\n try {\n await deps.notifyUpdate({ command: pathParts[0] ?? \"\", flags, io });\n } catch {\n /* the notifier is best-effort; a failure must not affect the command result */\n }\n }\n return code;\n}\n","/**\n * `birdybeep doctor` (§9.4, §21.1–21.2) — the self-service troubleshooter. Runs a battery\n * of checks (machine token, each adapter's doctor() incl. needs_trust/needs_restart/error,\n * local queue health, backend reachability), prints a concrete copy-pasteable fix for each\n * failure, drains the queue opportunistically, and exits non-zero when anything fails so\n * it's CI/script friendly. Read-only (never mutates harness config); never prints token\n * material or notification bodies. `--json` mirrors all findings.\n */\nimport {\n type AgentAdapter,\n createSender as defaultCreateSender,\n type Sender,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\nimport { claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { codexAdapter } from \"@birdybeep/codex\";\nimport { copilotAdapter } from \"@birdybeep/copilot\";\nimport { cursorAdapter } from \"@birdybeep/cursor\";\nimport { opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { isPaired, localQueueDepth } from \"../diagnostics\";\nimport { type Command, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [\n claudeCodeAdapter,\n codexAdapter,\n opencodeAdapter,\n cursorAdapter,\n copilotAdapter,\n];\n\ninterface Check {\n name: string;\n ok: boolean;\n detail?: string;\n remedy?: string;\n}\n\n/** Best-effort backend reachability probe (HEAD; any non-5xx response = reachable). */\nasync function defaultProbeNetwork(baseUrl: string): Promise<boolean> {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 3000);\n if (typeof timer.unref === \"function\") timer.unref();\n const res = await fetch(baseUrl, { method: \"HEAD\", signal: controller.signal });\n clearTimeout(timer);\n return res.status < 500;\n } catch {\n return false;\n }\n}\n\nexport interface DoctorCommandDeps {\n adapters?: AgentAdapter[];\n createSender?: (baseUrl: string) => Sender;\n tokenOptions?: TokenStoreOptions;\n /** Backend reachability probe (tests inject reachable/unreachable). */\n probeNetwork?: (baseUrl: string) => Promise<boolean>;\n}\n\nexport function createDoctorCommand(deps: DoctorCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n const probeNetwork = deps.probeNetwork ?? defaultProbeNetwork;\n const makeSender =\n deps.createSender ??\n ((baseUrl) =>\n defaultCreateSender(\n deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl },\n ));\n\n return {\n name: \"doctor\",\n summary: \"Diagnose token, trust, restart, and offline-queue issues\",\n usage: \"birdybeep doctor [--json]\",\n run: async (ctx) => {\n const checks: Check[] = [];\n const apiUrl = resolveApiUrl();\n\n // 1. Machine token.\n const paired = await isPaired(deps.tokenOptions ?? {});\n checks.push(\n paired\n ? { name: \"Machine token\", ok: true }\n : {\n name: \"Machine token\",\n ok: false,\n detail: \"No machine token found.\",\n remedy: \"Run `birdybeep pair` to pair this machine.\",\n },\n );\n\n // 2. Each adapter's own diagnostics (detected? installed? needs_trust/needs_restart/error?).\n for (const adapter of adapters) {\n const result = await adapter.doctor();\n for (const c of result.checks) {\n checks.push({\n name: `${adapter.displayName}: ${c.name}`,\n ok: c.ok,\n ...(c.detail !== undefined ? { detail: c.detail } : {}),\n ...(c.remedy !== undefined ? { remedy: c.remedy } : {}),\n });\n }\n }\n\n // 3. Local queue: drain opportunistically, report depth.\n const depthBefore = localQueueDepth();\n const drain = await makeSender(apiUrl).drainNow();\n const depthAfter = localQueueDepth();\n checks.push({\n name: \"Local queue\",\n ok: true,\n detail: `${depthBefore} queued → ${drain.delivered} delivered, ${depthAfter} remaining`,\n });\n\n // 4. Backend reachability.\n const reachable = await probeNetwork(apiUrl);\n checks.push(\n reachable\n ? { name: \"Backend reachable\", ok: true }\n : {\n name: \"Backend reachable\",\n ok: false,\n detail: `Could not reach ${apiUrl}.`,\n remedy: \"Check your network; queued events will retry automatically.\",\n },\n );\n\n const ok = checks.every((c) => c.ok);\n\n if (ctx.flags.json) {\n ctx.io.result({\n ok,\n checks,\n queue: { depthBefore, delivered: drain.delivered, depthAfter },\n });\n } else {\n for (const c of checks) {\n ctx.io.line(`${c.ok ? \"✓\" : \"✗\"} ${c.name}${c.detail ? ` — ${c.detail}` : \"\"}`);\n if (!c.ok && c.remedy) ctx.io.line(` → ${c.remedy}`);\n }\n ctx.io.line(ok ? \"\\nAll checks passed.\" : \"\\nSome checks failed — see fixes above.\");\n }\n return ok ? EXIT.OK : EXIT.ERROR;\n },\n };\n}\n","/**\n * Non-secret CLI config (§9.4): a small `config.json` in the BirdyBeep user config dir\n * holding things like the API base URL. The machine TOKEN never lives here — it is read\n * exclusively from the secure token store (keychain / strict-perm file). Tolerant readers:\n * a missing/corrupt config falls back to defaults rather than crashing the hot path.\n */\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { birdyBeepConfigDir } from \"@birdybeep/agent-core\";\n\n/** Default backend base URL (overridable via env or `birdybeep pair`; finalized in a-release). */\nexport const DEFAULT_API_URL = \"https://api.birdybeep.com\";\nexport const CONFIG_FILE = \"config.json\";\n\nexport interface CliConfig {\n /** Backend base URL (set by `pair`); never holds a token. */\n apiUrl?: string;\n /**\n * Optional identity pin for `birdybeep pair` (birdybeep-md60): the account that must have\n * approved a pairing before its token is trusted. Same effect as `--expect-email` (which\n * overrides it), for fleets/CI images that want the check baked in. Non-secret.\n */\n expectEmail?: string;\n}\n\nexport function cliConfigPath(): string {\n return join(birdyBeepConfigDir(), CONFIG_FILE);\n}\n\n/** Read the CLI config; returns `{}` on a missing/unreadable/corrupt file (never throws). */\nexport function readCliConfig(): CliConfig {\n try {\n const parsed: unknown = JSON.parse(readFileSync(cliConfigPath(), \"utf8\"));\n return typeof parsed === \"object\" && parsed !== null ? parsed : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Merge + persist non-secret CLI config (strict-perm dir). Only the KNOWN non-secret keys\n * are ever written — anything else (e.g. a token someone passed by mistake) is dropped, so\n * the token can only ever live in the secure store, never here.\n */\nexport function writeCliConfig(patch: CliConfig): void {\n const current = readCliConfig();\n const merged: CliConfig = {};\n const apiUrl = patch.apiUrl ?? current.apiUrl;\n if (apiUrl !== undefined) merged.apiUrl = apiUrl;\n const expectEmail = patch.expectEmail ?? current.expectEmail;\n if (expectEmail !== undefined) merged.expectEmail = expectEmail;\n mkdirSync(birdyBeepConfigDir(), { recursive: true, mode: 0o700 });\n writeFileSync(cliConfigPath(), `${JSON.stringify(merged, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Resolve the backend base URL: `BIRDYBEEP_API_URL` env → CLI config → default. */\nexport function resolveApiUrl(): string {\n const env = process.env[\"BIRDYBEEP_API_URL\"];\n if (env !== undefined && env.length > 0) return env;\n return readCliConfig().apiUrl ?? DEFAULT_API_URL;\n}\n\n/** Public npm registry — where `@birdybeep/cli` is published; used by the update notifier. */\nexport const DEFAULT_REGISTRY_URL = \"https://registry.npmjs.org\";\n\n/**\n * Resolve the npm registry base URL for the passive update check: honor `npm_config_registry`\n * (which npm/pnpm/yarn export, so a private-registry user's mirror is respected) and fall back to\n * the public registry. Never carries auth or a token.\n */\nexport function resolveRegistryUrl(): string {\n const env = process.env[\"npm_config_registry\"];\n if (env !== undefined && env.length > 0) return env;\n return DEFAULT_REGISTRY_URL;\n}\n","/**\n * Shared status/queue plumbing used by `birdybeep status` and `birdybeep doctor`: gather\n * each adapter's integration status, the machine identity + pairing state, and local queue\n * depth. Read-only + privacy-safe — never prints token material or notification bodies.\n */\nimport {\n type AgentAdapter,\n getMachineIdentity,\n getToken,\n type IntegrationStatus,\n LocalEventQueue,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\n\nexport interface IntegrationState {\n harness: string;\n displayName: string;\n status: IntegrationStatus;\n}\n\n/** Each adapter's current §8.8 integration status (runs the real adapter.status()). */\nexport async function gatherIntegrations(adapters: AgentAdapter[]): Promise<IntegrationState[]> {\n return Promise.all(\n adapters.map(async (a) => ({\n harness: a.id,\n displayName: a.displayName,\n status: await a.status(),\n })),\n );\n}\n\n/** Is a machine token present in the secure store? (pairing state — never prints the token.) */\nexport async function isPaired(tokenOptions: TokenStoreOptions = {}): Promise<boolean> {\n return (await getToken(tokenOptions)) !== null;\n}\n\n/** Current local event-queue depth (fresh, non-expired entries). */\nexport function localQueueDepth(): number {\n return new LocalEventQueue().size();\n}\n\n/** Machine label + OS (the event `machine` identity). */\nexport function machineIdentity(): { label: string; os: string } {\n return getMachineIdentity();\n}\n","/**\n * `birdybeep hook <claude|codex|opencode|cursor|copilot>` (§9.2–9.3) — the hot-path entrypoint every\n * installed adapter config invokes when its harness fires a lifecycle event. It reads the\n * raw payload (from the trailing arg for Codex's notify argv, else from stdin), selects the\n * named harness's `runXHook` (normalize → redact/hash/truncate → dedup → send w/ short\n * timeout → queue-on-fail → opportunistic drain → fast return), and ALWAYS exits 0 so it\n * never errors the harness. The token is read by the sender from the secure store — never\n * from config — and notification content is never persisted (the adapters' normalizers\n * enforce that).\n *\n * Built as a factory so the sender + stdin reader are injectable: tests drive the full\n * dispatch → command → pipeline → stub-sink path hermetically, exactly like the adapter E2Es.\n */\nimport { spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { closeSync, openSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\n\nimport {\n createSender as defaultCreateSender,\n type HookResult,\n resolveOnPath,\n type Sender,\n} from \"@birdybeep/agent-core\";\nimport { runClaudeHook } from \"@birdybeep/claude-code\";\nimport { runCodexHook } from \"@birdybeep/codex\";\nimport {\n type CopilotHookEventName,\n isCopilotHookEventName,\n runCopilotHook,\n} from \"@birdybeep/copilot\";\nimport { runCursorHook } from \"@birdybeep/cursor\";\nimport { runOpenCodeHook } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\n\nexport type HarnessName = \"claude\" | \"codex\" | \"opencode\" | \"cursor\" | \"copilot\";\n\ntype HarnessRunner = (input: unknown, options: { sender: Sender }) => Promise<HookResult>;\n\nconst RUNNERS: Record<Exclude<HarnessName, \"copilot\">, HarnessRunner> = {\n claude: runClaudeHook,\n codex: runCodexHook,\n opencode: runOpenCodeHook,\n cursor: runCursorHook,\n};\n\nexport const HOOK_HARNESSES: readonly HarnessName[] = [\n \"claude\",\n \"codex\",\n \"opencode\",\n \"cursor\",\n \"copilot\",\n];\n\n/**\n * Hard cap on reading the payload — a misbehaving harness must never hang the hook.\n * 3s (was 2s, erm): a loaded machine can be slow to flush a pipe, and a timeout here\n * silently DROPS the event (\"skipped\"). BUDGET MATH: this cap and the sender's\n * DEFAULT_TOTAL_BUDGET_MS (5s) run SEQUENTIALLY and must sum comfortably under the 10s\n * hook timeout the adapters register, leaving headroom for Node startup — 3s + 5s + ~1s\n * startup < 10s. (5s + 5s summed to exactly the timeout: a slow start got the hook\n * SIGKILLed mid-send, which skips the queue-on-failure catch and loses the event.)\n */\nexport const STDIN_READ_TIMEOUT_MS = 3000;\n\n/** Resolve to `fallback` if `promise` does not settle within `ms` (the timer is unref'd). */\nfunction withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {\n return new Promise<T>((resolve) => {\n let settled = false;\n const finish = (value: T): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(value);\n };\n const timer = setTimeout(() => finish(fallback), ms);\n if (typeof timer.unref === \"function\") timer.unref();\n void promise.then(finish, () => finish(fallback));\n });\n}\n\nexport function isHarnessName(value: string | undefined): value is HarnessName {\n return (\n value === \"claude\" ||\n value === \"codex\" ||\n value === \"opencode\" ||\n value === \"cursor\" ||\n value === \"copilot\"\n );\n}\n\n/** Run one hook fire: select the harness runner and execute via the shared pipeline. */\nexport function runHookCommand(\n harness: HarnessName,\n payload: unknown,\n sender: Sender,\n copilotEventName?: CopilotHookEventName,\n): Promise<HookResult> {\n if (harness === \"copilot\") {\n if (copilotEventName === undefined) return Promise.resolve({ outcome: \"skipped\" });\n return runCopilotHook(copilotEventName, payload, { sender });\n }\n return RUNNERS[harness](payload, { sender });\n}\n\n/** Read process.stdin to EOF (the harness pipes a small JSON then closes); never throws. */\nfunction readStdinDefault(): Promise<string> {\n return new Promise((resolve) => {\n if (process.stdin.isTTY) {\n resolve(\"\");\n return;\n }\n let data = \"\";\n process.stdin.setEncoding(\"utf8\");\n process.stdin.on(\"data\", (chunk: string) => (data += chunk));\n process.stdin.on(\"end\", () => resolve(data));\n process.stdin.on(\"error\", () => resolve(\"\"));\n });\n}\n\n/** Resolve the raw payload: the trailing arg (Codex notify argv) wins, else read stdin. */\nexport async function readHookPayload(\n args: string[],\n readStdin: () => Promise<string>,\n stdinOnly = false,\n): Promise<string> {\n return stdinOnly ? readStdin() : (args[1] ?? (await readStdin()));\n}\n\n/**\n * Env var carrying the temp-file path the detached notify worker deletes after reading it —\n * see {@link detachCodexNotifyWorker}. Set only on the detached worker's environment.\n */\nexport const NOTIFY_STDIN_FILE_ENV = \"BIRDYBEEP_CODEX_NOTIFY_STDIN_FILE\";\n\n/**\n * birdybeep-agent-fuf: `codex exec` (headless/one-shot) reaps the notify child's PROCESS\n * GROUP when it exits. Codex fires `notify` at turn-complete — right before it exits — so on\n * a cold/slow backend the in-line send is still in flight when the group is SIGKILLed, and\n * the event is lost before delivery *or* the queue-write finishes (the interactive `codex`\n * TUI stays alive, so it never hit this).\n *\n * The fix, scoped to the notify path only: instead of sending in-line, re-launch\n * `birdybeep hook codex` DETACHED (`detached: true` → `setsid`/new session) reading the\n * payload on stdin, then return immediately. The detached worker is NOT in the group\n * `codex exec` reaps, so it outlives the harness and completes the fast send+queue. Because\n * the worker is invoked WITHOUT a trailing argv payload it reads stdin and runs the ordinary\n * in-process path below — it is never itself re-detached, so this never recurses. Lifecycle\n * `[[hooks.X]]` events are deliberately untouched: they arrive on stdin and fire mid-session.\n *\n * The payload is delivered via a strict-perm (0o600) temp FILE handed to the worker as its\n * stdin fd — NOT a pipe this process holds. Two reasons: (1) the payload is fully written\n * before the spawn, so the worker always reads it complete even though we exit immediately;\n * (2) this process then holds NO pipe/stream to the child, so its prompt exit is DETERMINISTIC\n * on every platform — it never depends on when/whether a parent-held stdin pipe flushes and\n * closes, which is exactly the fast-return codex needs. The worker unlinks the temp file after\n * reading it (via {@link NOTIFY_STDIN_FILE_ENV}).\n *\n * Scoped to POSIX: on Windows a child is NOT killed when its parent exits (see agent-core\n * safe-spawn), so the exec-exit reap race does not arise there — we return false and the\n * caller sends in-line. We also return false when `birdybeep` can't be resolved on PATH or the\n * spawn throws; an in-line best-effort delivery still beats dropping the event outright.\n */\nexport function detachCodexNotifyWorker(payload: string): boolean {\n if (process.platform === \"win32\") return false; // no exec-exit reap race on Windows\n let file: string | undefined;\n let fd: number | undefined;\n try {\n // SECURITY (sec-review-2026-07 H1): resolve `birdybeep` to an absolute path on PATH ONLY\n // (never cwd — the harness's cwd is the attacker-controllable repo), then spawn that\n // absolute path with a trusted cwd. On POSIX `birdybeep` is a real executable (never a\n // shell shim), so no shell is involved.\n const birdybeep = resolveOnPath(\"birdybeep\");\n if (birdybeep === null) return false; // not on PATH → caller sends in-line as a fallback\n\n const tmpFile = join(tmpdir(), `birdybeep-notify-${randomBytes(16).toString(\"hex\")}.json`);\n file = tmpFile; // track for the synchronous catch cleanup path\n writeFileSync(tmpFile, payload, { mode: 0o600 }); // fully written BEFORE spawn\n fd = openSync(tmpFile, \"r\");\n const child = spawn(birdybeep, [\"hook\", \"codex\"], {\n cwd: dirname(birdybeep), // trusted dir, never the inherited/attacker cwd\n detached: true, // new session (setsid) → survives `codex exec` reaping the group\n stdio: [fd, \"ignore\", \"ignore\"], // stdin = the temp file; this process holds no pipe\n env: { ...process.env, [NOTIFY_STDIN_FILE_ENV]: tmpFile }, // worker cleans it up post-read\n windowsHide: true,\n });\n child.on(\"error\", () => {\n // `spawn` reports most launch failures (EMFILE/ENOMEM, or the binary vanishing after\n // resolveOnPath) ASYNCHRONOUSLY via 'error', after we've already returned true. The worker\n // never ran, so it can't delete its stdin temp file — clean it up here so we don't leak a\n // 0o600 file per failed fire. The event itself is lost (we already returned; there's no\n // retroactive in-line send), which is the accepted best-effort contract for detachment.\n try {\n rmSync(tmpFile, { force: true });\n } catch {\n /* the OS reclaims tmp eventually */\n }\n });\n child.unref(); // don't keep the notify process alive waiting on the worker\n return true;\n } catch {\n if (file !== undefined) {\n try {\n rmSync(file, { force: true }); // spawn failed before the worker could clean up\n } catch {\n /* the OS reclaims tmp eventually */\n }\n }\n return false; // any failure → in-line fallback (never throw into the harness)\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd); // the child holds its own dup; this process keeps no fd\n } catch {\n /* already closed / never opened */\n }\n }\n }\n}\n\nexport interface HookCommandDeps {\n /** Build the sender (default: agent-core `createSender` with the resolved API URL). */\n createSender?: (baseUrl: string) => Sender;\n /** Read the raw payload from stdin (default: real process.stdin). */\n readStdin?: () => Promise<string>;\n /** Hard cap on the payload read (default {@link STDIN_READ_TIMEOUT_MS}); tests shrink it. */\n stdinTimeoutMs?: number;\n /**\n * Detach the Codex notify send into a process that survives `codex exec` reaping its group\n * (birdybeep-agent-fuf). Default {@link detachCodexNotifyWorker}; returns whether the\n * detached worker launched (true → the notify process returns fast; false → send in-line as\n * a fallback). Injectable so tests drive the branch without spawning a real process.\n */\n detachCodexNotify?: (payload: string) => boolean;\n}\n\n/** Build the `hook` command. Pure stubs aside, this is the live event path. */\nexport function createHookCommand(deps: HookCommandDeps = {}): Command {\n const makeSender = deps.createSender ?? ((baseUrl) => defaultCreateSender({ baseUrl }));\n const readStdin = deps.readStdin ?? readStdinDefault;\n const stdinTimeoutMs = deps.stdinTimeoutMs ?? STDIN_READ_TIMEOUT_MS;\n const detachCodexNotify = deps.detachCodexNotify ?? detachCodexNotifyWorker;\n\n return {\n name: \"hook\",\n summary: \"Internal: normalize + send an event fired by a harness hook\",\n usage: \"birdybeep hook <claude|codex|opencode|cursor|copilot> [copilot-event]\",\n run: async (ctx) => {\n const harness = ctx.args[0];\n if (!isHarnessName(harness)) {\n ctx.io.errline(`birdybeep hook: expected one of ${HOOK_HARNESSES.join(\"|\")}`);\n return EXIT.USAGE;\n }\n\n // birdybeep-agent-fuf: a Codex *notify* fire (payload delivered as the trailing argv\n // arg) races `codex exec` exit, which reaps the notify process group. Re-launch the send\n // DETACHED reading the payload on stdin (see {@link detachCodexNotifyWorker}) and return\n // immediately, so it outlives the reap. Scoped to notify only — lifecycle hooks arrive on\n // stdin. If the worker can't be launched we fall through and send in-line (best-effort).\n // An empty trailing arg is not a real notify payload — fall through so it's `skipped`\n // in-line rather than spawning a worker just to read an empty file.\n const notifyPayload = ctx.args[1];\n if (\n harness === \"codex\" &&\n notifyPayload !== undefined &&\n notifyPayload.length > 0 &&\n detachCodexNotify(notifyPayload)\n ) {\n ctx.io.result({ harness, outcome: \"detached\" });\n return EXIT.OK; // the detached worker delivers; the notify process must not block codex\n }\n\n // Bounded read: the trailing argv payload resolves instantly; a hung/never-closing\n // stdin falls back to \"\" after the timeout so the hook ALWAYS returns fast (§9.3).\n // Copilot's second arg is the event name, not a JSON payload. Every Copilot payload must\n // therefore come from stdin; Codex retains its notify argv-payload behavior.\n const copilotEventName =\n harness === \"copilot\" && isCopilotHookEventName(ctx.args[1]) ? ctx.args[1] : undefined;\n const raw = await withTimeout(\n readHookPayload(ctx.args, readStdin, harness === \"copilot\"),\n stdinTimeoutMs,\n \"\",\n );\n\n // If we ARE the detached notify worker (spawned by detachCodexNotifyWorker), the payload\n // was handed to us as a strict-perm temp file used for stdin — now that it's read, delete\n // it. Guard the path (our own tmpdir prefix) before unlinking so a stray/injected env value\n // can never make a hook fire force-delete an arbitrary file. Best-effort: the OS reclaims\n // tmp anyway, and a stale file is never a correctness bug.\n const notifyStdinFile = process.env[NOTIFY_STDIN_FILE_ENV];\n if (\n notifyStdinFile !== undefined &&\n dirname(notifyStdinFile) === tmpdir() &&\n basename(notifyStdinFile).startsWith(\"birdybeep-notify-\")\n ) {\n try {\n rmSync(notifyStdinFile, { force: true });\n } catch {\n /* the OS reclaims tmp eventually */\n }\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(raw);\n } catch {\n // Garbled/empty payload → skip silently + fast. Never error the harness.\n ctx.io.result({ harness, outcome: \"skipped\" });\n return EXIT.OK;\n }\n\n const sender = makeSender(resolveApiUrl());\n const result = await runHookCommand(harness, payload, sender, copilotEventName);\n // Hot path: human mode is silent; --json emits the outcome for scripts/debugging.\n // Surface the backend's 202 decision (notified/suppressed/deduped) + HTTP status when\n // a send was attempted — the outcome alone (\"delivered\") can't distinguish a beep that\n // fired from one the backend accepted-but-suppressed, which is exactly the failure mode\n // `doctor` and delivery debugging need to see.\n ctx.io.result({\n harness,\n ...(copilotEventName !== undefined ? { event: copilotEventName } : {}),\n outcome: result.outcome,\n eventType: result.eventType,\n ...(result.send?.decision ? { decision: result.send.decision } : {}),\n ...(result.send?.status !== undefined ? { status: result.send.status } : {}),\n });\n return EXIT.OK; // delivered/queued/deduped/skipped all return fast + non-erroring\n },\n };\n}\n","/**\n * `birdybeep logout` / `birdybeep unpair` (§9.4) — remove this machine's pairing.\n *\n * They differ on purpose:\n * - `logout` is LOCAL ONLY: it removes the machine token from BOTH the OS keychain and the\n * strict-perm file fallback. It does not touch the server — use it to sign this box out\n * without revoking the installation (you can re-pair the SAME machine later).\n * - `unpair` is the true reverse of `pair`: it REVOKES the machine server-side (best-effort\n * `POST /v1/machine/revoke-self` with the machine token) so the machine disappears from the\n * app, AND then clears the local token. If the server can't be reached, it still clears the\n * local token and tells you to revoke the machine in the app so a ghost row can't linger.\n *\n * Both are idempotent (no error when already signed out) and never touch harness integration\n * config (that is `agent uninstall`) or the local queue.\n */\nimport { clearToken, getToken, type TokenStoreOptions } from \"@birdybeep/agent-core\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\n\nexport interface LogoutCommandDeps {\n /** Token-store options (tests inject the file fallback). */\n tokenOptions?: TokenStoreOptions;\n}\n\nexport interface UnpairCommandDeps extends LogoutCommandDeps {\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n /** Bound on the server round-trip so `unpair` can never hang on a dead network. Default 10s. */\n timeoutMs?: number;\n}\n\nconst base = (apiUrl: string): string => apiUrl.replace(/\\/$/, \"\");\n\nexport function createLogoutCommand(deps: LogoutCommandDeps = {}): Command {\n return {\n name: \"logout\",\n summary: \"Remove the local machine token (does NOT revoke the machine server-side)\",\n usage: \"birdybeep logout\",\n run: async (ctx) => {\n await clearToken(deps.tokenOptions ?? {});\n ctx.io.emit(\"Logged out — the machine token was removed.\", { loggedOut: true });\n return EXIT.OK;\n },\n };\n}\n\n/** Outcome of the best-effort server-side revoke during `unpair`. */\ntype RevokeOutcome =\n | \"revoked\" // server confirmed the machine is gone (2xx, or already-revoked 403)\n | \"no_token\" // nothing to revoke — there was no local token to begin with\n | \"unreachable\" // couldn't reach the server (offline / timeout) — machine may still show\n | \"rejected\"; // server answered but didn't confirm removal (e.g. 401/5xx)\n\nasync function revokeSelf(\n token: string,\n fetchImpl: typeof fetch,\n timeoutMs: number,\n): Promise<RevokeOutcome> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(`${base(resolveApiUrl())}/v1/machine/revoke-self`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${token}` },\n signal: controller.signal,\n });\n // 2xx → revoked now. 403 (token_revoked) → it was already revoked server-side, so the\n // machine is already gone: from the user's view, unpaired either way. Anything else\n // (401 invalid token, 5xx, 429) → we can't confirm removal.\n if (res.ok || res.status === 403) return \"revoked\";\n return \"rejected\";\n } catch {\n return \"unreachable\"; // offline / DNS / timeout (abort) — never fatal; local clear proceeds\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function createUnpairCommand(deps: UnpairCommandDeps = {}): Command {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const timeoutMs = deps.timeoutMs ?? 10_000;\n return {\n name: \"unpair\",\n summary: \"Unpair this machine — revoke it server-side and remove the local token\",\n usage: \"birdybeep unpair\",\n run: async (ctx) => {\n const token = await getToken(deps.tokenOptions ?? {});\n const outcome: RevokeOutcome =\n token === null ? \"no_token\" : await revokeSelf(token, fetchImpl, timeoutMs);\n\n // Always clear the local token afterward — idempotent, and `unpair` must succeed even\n // fully offline (the server revoke is best-effort). Ordered AFTER the revoke so the\n // token is still available to authenticate it.\n await clearToken(deps.tokenOptions ?? {});\n\n const serverRevoked = outcome === \"revoked\";\n const human =\n outcome === \"revoked\"\n ? \"Unpaired — the machine was revoked and removed from your account.\"\n : outcome === \"no_token\"\n ? \"Already unpaired — there was no local token to remove.\"\n : outcome === \"unreachable\"\n ? \"Unpaired locally, but the server was unreachable — the machine may still show in the app. Open BirdyBeep and revoke it there to fully remove it.\"\n : \"Unpaired locally, but the server didn't confirm removal — if the machine still shows in the app, revoke it there.\";\n ctx.io.emit(human, { unpaired: true, serverRevoked });\n return EXIT.OK;\n },\n };\n}\n","/**\n * `birdybeep pair` (§7.1/§7.2/§9.4) — pair this machine via the device-code flow.\n * `POST /v1/pair/start` (machine_label derived from hostname/OS) → show a scannable\n * QR matrix + the complete pair link + display-only `user_code` → poll `POST /v1/pair/token`\n * with the device\n * code (+ stable machine fingerprint) until it returns the durable token or the\n * `expires_at` (10-min) deadline. The issued token is stored in the SECURE store only\n * (keychain / strict-perm file — never config or the QR); the non-secret apiUrl is\n * persisted. Per SPEC §11 the QR/link carries only short-lived pairing info; its fragment\n * contains the approval secret, so the displayed user code cannot approve by itself.\n *\n * The QR matrix (birdybeep-agent-pe1) renders only on an interactive TTY — piped/CI\n * output keeps the plain link + display-code lines, which are ALWAYS printed as the SSH/\n * headless fallback (docs/pairing.md \"Headless and SSH machines\"). In `--json` mode\n * the pairing info is emitted as an NDJSON line up front (status \"pairing_started\")\n * so scripts/agents can surface the complete QR payload for approval — previously json mode printed\n * nothing until success, making scripted pairing impossible (birdybeep-agent-pe1).\n *\n * fetch/sleep/clock/QR/TTY are injectable for hermetic tests.\n */\nimport { closeSync, openSync } from \"node:fs\";\n\nimport {\n deriveCodeChallengeS256,\n generateCodeVerifier,\n getMachineIdentity,\n setToken,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\n// uqr is the CLI's ONLY third-party runtime dep (MIT, itself zero-dependency), pinned\n// EXACTLY in package.json: QR encoding (Reed–Solomon + masking) is too error-prone to\n// vendor, and a floating range would defeat the small-auditable-supply-chain goal (§16.4).\nimport { renderUnicodeCompact } from \"uqr\";\n\nimport { cliConfigPath, readCliConfig, resolveApiUrl, writeCliConfig } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\nimport { pairStart, pairTokenPoll, type PairTokenResult } from \"../pairing\";\nimport { CLI_VERSION } from \"../version\";\n\n/** Default delay between `/pair/token` polls (the start response has no interval). */\nexport const DEFAULT_POLL_INTERVAL_MS = 2000;\n\n/**\n * How often to reprint a \"still waiting…\" heartbeat while polling. Without it, `pair`\n * prints the pairing instructions once and then appears frozen (\"stuck doing nothing\") for the whole\n * 10-minute window — the reported bug. Time-gated on the injected clock so it never\n * fires spuriously in the fast, instant-sleep tests.\n */\nexport const HEARTBEAT_MS = 15_000;\n\n/**\n * Render the QR payload as a terminal-scannable half-block matrix. `border: 2` keeps a\n * quiet zone around the symbol (phone cameras misread flush-against-text QRs).\n */\nexport function renderQrMatrix(qrPayload: string): string {\n return renderUnicodeCompact(qrPayload, { border: 2 });\n}\n\n/** What the caller asked for on the command line (beyond the global flags). */\nexport interface PairFlags {\n /** `--yes` / `-y`: skip the interactive confirm (CI/headless escape hatch). */\n yes: boolean;\n /** `--expect-email <addr>`: pin the identity that must have approved this pairing. */\n expectEmail?: string;\n /** A usage problem (unknown value, stray argument) — the command exits EXIT.USAGE. */\n error?: string;\n}\n\n/**\n * Parse `pair`'s own flags out of the post-global argv. Accepts `--expect-email addr` and\n * `--expect-email=addr`; any stray positional is a usage error (pair takes none), so a\n * fat-fingered `birdybeep pair becs@example.com` can never be silently ignored while the\n * confirm gate falls back to prompting.\n */\nexport function parsePairFlags(args: string[]): PairFlags {\n const flags: PairFlags = { yes: false };\n for (let i = 0; i < args.length; i += 1) {\n const token = args[i] ?? \"\";\n if (token === \"--yes\" || token === \"-y\") {\n flags.yes = true;\n } else if (token === \"--expect-email\" || token.startsWith(\"--expect-email=\")) {\n const inline = token.startsWith(\"--expect-email=\")\n ? token.slice(\"--expect-email=\".length)\n : undefined;\n const value = inline ?? args[++i];\n if (value === undefined || value.length === 0 || value.startsWith(\"-\")) {\n return { ...flags, error: \"--expect-email requires an email address\" };\n }\n flags.expectEmail = value;\n } else {\n return { ...flags, error: `unexpected argument \"${token}\"` };\n }\n }\n return flags;\n}\n\n/**\n * The confirm gate's inputs (birdybeep-md60). `approvedByEmail` is what the server said\n * approved this pairing; everything else is how the operator invoked the CLI.\n */\nexport interface PairConfirmInput {\n /** `approved_by_email` from `/v1/pair/token` — absent on older servers. */\n approvedByEmail?: string;\n /** The pinned identity (`--expect-email`, else the `expectEmail` config key). */\n expectEmail?: string;\n /**\n * WHERE the pin came from — the remedy differs. A `--flag` pin can simply be dropped from the\n * next invocation; a `config` pin lives in a file and there is no CLI switch that ignores it,\n * so the message has to name the file instead of suggesting an impossible re-run.\n */\n expectEmailSource?: \"flag\" | \"config\";\n yes: boolean;\n nonInteractive: boolean;\n /** Whether stdin can carry an answer (a real terminal), i.e. prompting won't hang. */\n stdinIsTTY: boolean;\n /**\n * Whether the process can still reach its CONTROLLING TERMINAL (`/dev/tty`) even though stdin\n * isn't one. This is the difference between \"a human is sitting here but their shell hands us\n * pipe-backed stdio\" and \"nobody is there at all\" (a script, a CI job): only the latter may\n * fail closed, the former gets prompted on the terminal it actually has. Always false on\n * Windows — see {@link canOpenControllingTerminal}.\n */\n controllingTerminalAvailable: boolean;\n /** Platform, for platform-specific remediation in the reject text. Default `process.platform`. */\n platform?: string;\n /** Where the CLI config file lives, for the config-pin remedy. */\n configPath?: string;\n}\n\nexport type PairConfirmDecision =\n /** Trust the token without asking (a pin matched, or the operator passed `--yes`). */\n | { action: \"approve\"; reason: \"expected_email_match\" | \"yes_flag\" }\n /**\n * Ask the human. `question` is the exact prompt to write; `on` says WHERE to read the answer\n * — \"stdin\" when stdin is a terminal, \"controlling-terminal\" when it isn't but /dev/tty is\n * reachable (POSIX only).\n */\n | { action: \"prompt\"; question: string; on: \"stdin\" | \"controlling-terminal\" }\n /** Refuse: the token must NOT be persisted, and `message` says why + how to proceed. */\n | {\n action: \"reject\";\n reason: \"expected_email_mismatch\" | \"expected_email_unverifiable\" | \"non_interactive\";\n message: string;\n };\n\n/**\n * Do two addresses denote the SAME account? Trimmed + case-folded, and required to agree both\n * before AND after Unicode NFKC normalization.\n *\n * Why both: NFKC folds visually-distinct code points onto the same ASCII (fullwidth `e` → `e`,\n * ligatures, math letterforms). Comparing only normalized forms would let a server-reported\n * homoglyph address auto-approve against an ASCII pin — the exact wrong-account acceptance this\n * gate exists to stop. Comparing only raw forms would ignore normalization entirely. Requiring\n * agreement means any pair that matches on one side of normalization but not the other is\n * treated as a MISMATCH, which fails closed (the human is asked, or the pin refuses).\n */\nfunction sameEmail(a: string, b: string): boolean {\n const fold = (v: string): string => v.trim().toLowerCase();\n const rawEqual = fold(a) === fold(b);\n const nfkcEqual = fold(a.normalize(\"NFKC\")) === fold(b.normalize(\"NFKC\"));\n return rawEqual && nfkcEqual;\n}\n\n/**\n * Decide whether a freshly minted token may be trusted — the whole of the md60 gate, kept\n * pure so every branch is unit-testable and the command body stays a thin wrapper.\n *\n * Order matters:\n * 1. A pinned identity (`--expect-email`) is the strongest signal: it auto-approves on an\n * exact match and HARD-FAILS on a mismatch — a `--yes` alongside it must not override a\n * wrong account (that would turn the pin into a no-op).\n * A pin with no `approved_by_email` to check against also fails: unverifiable ≠ fine.\n * 2. `--yes` is the blunt escape hatch for CI: trust without asking.\n * 3. Otherwise ask — on stdin when it is a terminal, else on the CONTROLLING TERMINAL when one\n * is still reachable (pipe-backed stdio under a real terminal: MSYS/mintty Git Bash, some\n * wrappers). Only when there is genuinely no one to ask (`--non-interactive`, a script, CI)\n * does it fail closed — never hang, never auto-trust.\n */\nexport function decidePairConfirmation(input: PairConfirmInput): PairConfirmDecision {\n const { approvedByEmail, expectEmail } = input;\n const platform = input.platform ?? process.platform;\n\n if (expectEmail !== undefined) {\n if (approvedByEmail === undefined) {\n // The remedy depends on where the pin came from: a flag can be dropped, a config key\n // cannot (there is no --no-expect-email), so point at the file instead of a dead end.\n const remedy =\n input.expectEmailSource === \"config\"\n ? `Remove or correct the \"expectEmail\" key in ${input.configPath ?? \"the BirdyBeep CLI config\"} ` +\n \"(or upgrade the backend to one that reports the approving account) and re-run.\"\n : \"Re-run without --expect-email (and confirm interactively) if that is expected.\";\n return {\n action: \"reject\",\n reason: \"expected_email_unverifiable\",\n message:\n `Pairing refused: ${expectEmail} was pinned as the expected approving account, but the ` +\n \"server did not report which account approved this machine, so the pin could not be \" +\n `verified. The machine token was NOT stored. ${remedy}`,\n };\n }\n if (sameEmail(approvedByEmail, expectEmail)) {\n return { action: \"approve\", reason: \"expected_email_match\" };\n }\n return {\n action: \"reject\",\n reason: \"expected_email_mismatch\",\n message:\n `Pairing refused: this machine was approved by ${approvedByEmail}, but ${expectEmail} was ` +\n \"expected. The machine token was NOT stored. If you did not expect that account to approve \" +\n \"it, open BirdyBeep and revoke the machine, then re-run `birdybeep pair`.\",\n };\n }\n\n if (input.yes) return { action: \"approve\", reason: \"yes_flag\" };\n\n const question =\n approvedByEmail !== undefined\n ? `Pair this machine to ${approvedByEmail}? [y/N] `\n : \"The server did not report which account approved this machine. Pair anyway? [y/N] \";\n\n // `--non-interactive` is an explicit \"never prompt me\", so it outranks any terminal we could\n // reach. Otherwise stdin wins when it's a terminal; failing that we ask on the POSIX\n // controlling terminal, which is what makes pipe-backed shells usable. On Windows there is no\n // usable equivalent — see {@link canOpenControllingTerminal} — so it falls through to the\n // refusal below, which is fast and honest rather than a hang.\n if (!input.nonInteractive) {\n if (input.stdinIsTTY) return { action: \"prompt\", question, on: \"stdin\" };\n if (input.controllingTerminalAvailable) {\n return { action: \"prompt\", question, on: \"controlling-terminal\" };\n }\n }\n\n const who = approvedByEmail !== undefined ? ` (approved by ${approvedByEmail})` : \"\";\n // On Windows the usual cause is a non-ConPTY MSYS/mintty shell handing us pipe-backed stdio.\n // `winpty` attaches a real console — which makes stdin itself a TTY, so the ordinary prompt\n // path engages. Name it rather than leaving the user stuck.\n const winptyHint =\n platform === \"win32\" && !input.nonInteractive\n ? \" In Git Bash / MSYS, `winpty birdybeep pair` attaches a real console so the prompt can appear.\"\n : \"\";\n return {\n action: \"reject\",\n reason: \"non_interactive\",\n message:\n `Pairing needs confirmation${who}, but there is no terminal to ask on, so the machine ` +\n \"token was NOT stored. Re-run with `--expect-email <addr>` to pin the approving account \" +\n \"(recommended for CI), or `--yes` to accept whichever account approved it.\" +\n winptyHint,\n };\n}\n\n/** Is a prompt answer an explicit yes? Anything else (incl. empty/EOF) means no. */\nexport function isAffirmative(answer: string): boolean {\n return /^(y|yes)$/i.test(answer.trim());\n}\n\n/**\n * The device that reaches this process's CONTROLLING TERMINAL regardless of what stdin is wired\n * to. POSIX only — see {@link canOpenControllingTerminal} for why Windows is excluded.\n */\nexport function controllingTerminalPath(): string {\n return \"/dev/tty\";\n}\n\n/**\n * Can we open the controlling terminal for reading? Probe only — never throws, opens nothing\n * durable. Opening `/dev/tty` succeeds exactly when a terminal really is attached: in a CI job,\n * a daemon, or a detached session (`setsid`) it fails, which is the signal the gate needs.\n *\n * WINDOWS IS DELIBERATELY EXCLUDED. The obvious analogue is the `CONIN$` console device, and an\n * earlier revision used it — but measured on a windows-latest runner with fully piped stdio,\n * `CONIN$` OPENS and then READING it blocks forever. That turns the gate's \"fail closed, fast\"\n * guarantee into a 60s hang (caught by scripts/live-e2e-pair-headless.mjs before release), which\n * is strictly worse than the refusal it was meant to avoid: a script gets neither an answer nor\n * an error. Since opening it proves nothing on Windows, we don't. Windows users whose shell hands\n * the CLI pipe-backed stdio (MSYS/mintty Git Bash) run `winpty birdybeep pair`, which attaches a\n * real console — stdin then IS a TTY and the ordinary stdin path handles it, no fallback needed.\n */\nexport function canOpenControllingTerminal(\n path: string = controllingTerminalPath(),\n /** Explicit so tests can pin BOTH branches on any host, without patching process.platform. */\n platform: string = process.platform,\n): boolean {\n if (platform === \"win32\") return false;\n let fd: number | undefined;\n try {\n fd = openSync(path, \"r\");\n return true;\n } catch {\n return false; // no controlling terminal (script, CI, detached session) → caller fails closed\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* already gone */\n }\n }\n }\n}\n\n/**\n * Ask a question and read one line back. The question always goes to stderr (not stdout) so\n * `--json` output stays a clean NDJSON stream. EOF/close resolves to \"\" (a decline) so the CLI\n * can never hang waiting for an answer that will never come.\n *\n * `on: \"controlling-terminal\"` reads from /dev/tty instead of stdin — a human IS present but the\n * shell gave us pipe-backed stdio.\n *\n * WHY tty.ReadStream over an explicit fd, and not `createReadStream(\"/dev/tty\")`:\n * a plain fs read stream services reads on the libuv THREADPOOL. Once a read is issued it cannot\n * be cancelled — `destroy()` returns, but the `FSReqCallback` stays pending, and because `bin.ts`\n * sets `process.exitCode` (rather than calling `process.exit`) the event loop never drains. The\n * observable bug: after answering the prompt the CLI printed \"✓ Paired …\" and then HUNG until a\n * keypress or Ctrl-C — flatly contradicting the \"never hangs\" guarantee this gate is built on.\n * Measured: answer read at +9ms, process still alive at 20s with\n * `process._getActiveRequests() === ['FSReqCallback']`. A `tty.ReadStream` uses a poll-backed\n * handle instead, so closing the fd deterministically here ends the read and the process exits\n * (~0.06s). The fd is ours (we opened it), so we close it exactly once, on every path.\n */\nasync function promptForAnswer(\n question: string,\n on: \"stdin\" | \"controlling-terminal\",\n): Promise<string> {\n const { createInterface } = await import(\"node:readline/promises\");\n\n let ttyFd: number | undefined;\n let input: NodeJS.ReadableStream;\n if (on === \"stdin\") {\n input = process.stdin;\n } else {\n const { ReadStream } = await import(\"node:tty\");\n ttyFd = openSync(controllingTerminalPath(), \"r\");\n input = new ReadStream(ttyFd);\n }\n\n return new Promise<string>((resolve) => {\n const rl = createInterface({ input, output: process.stderr });\n let settled = false;\n const done = (value: string): void => {\n if (settled) return;\n settled = true;\n rl.close();\n if (on === \"stdin\") {\n // The interface resumed stdin; unref so a lingering TTY handle can't hold the process open.\n process.stdin.unref?.();\n } else {\n // Order matters: unref + destroy the handle, THEN close the fd. Both are wrapped because\n // destroying a tty stream may already have closed it — a double close must never throw\n // out of the prompt (that would turn a successful answer into a crash).\n try {\n (input as { unref?: () => void }).unref?.();\n (input as { destroy?: () => void }).destroy?.();\n } catch {\n /* stream already torn down */\n }\n if (ttyFd !== undefined) {\n try {\n closeSync(ttyFd);\n } catch {\n /* already closed by the stream */\n }\n }\n }\n resolve(value);\n };\n rl.question(question).then(done, () => done(\"\"));\n rl.once(\"close\", () => done(\"\"));\n input.once?.(\"error\", () => done(\"\")); // tty vanished mid-prompt → decline, never hang\n });\n}\n\nexport interface PairCommandDeps {\n fetchImpl?: typeof fetch;\n tokenOptions?: TokenStoreOptions;\n /** Injectable delay between polls (default real setTimeout; tests make it instant). */\n sleep?: (ms: number) => Promise<void>;\n /** Injectable clock for the expiry deadline (default Date.now). */\n now?: () => number;\n /** Render the QR payload as a matrix (default {@link renderQrMatrix} via uqr). */\n renderQr?: (qrPayload: string) => string;\n /** Whether stdout is an interactive terminal (default process.stdout.isTTY). The QR\n * matrix renders only on a TTY — piped output stays plain text. */\n isTTY?: boolean;\n pollIntervalMs?: number;\n /**\n * Whether stdin can answer a prompt (default process.stdin.isTTY). Drives the md60 confirm\n * gate's fail-closed branch — a piped/CI stdin never gets prompted.\n */\n isStdinTTY?: boolean;\n /**\n * Whether the controlling terminal is reachable when stdin is NOT a TTY (default: probe\n * /dev/tty; always false on Windows). Injected in tests so both branches are exercised\n * without needing a real terminal.\n */\n hasControllingTerminal?: () => boolean;\n /** Ask a question and read one line (default {@link promptForAnswer}); injected in tests. */\n promptLine?: (question: string, on: \"stdin\" | \"controlling-terminal\") => Promise<string>;\n /** The pinned identity from config (default: the `expectEmail` key of the CLI config). */\n configuredExpectEmail?: () => string | undefined;\n}\n\nexport function createPairCommand(deps: PairCommandDeps = {}): Command {\n const fetchImpl = deps.fetchImpl ?? fetch;\n const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n const clock = deps.now ?? (() => Date.now());\n const renderQr = deps.renderQr ?? renderQrMatrix;\n const intervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const promptLine = deps.promptLine ?? promptForAnswer;\n const hasControllingTerminal =\n deps.hasControllingTerminal ?? (() => canOpenControllingTerminal());\n // Tolerant read: the config file is user-editable, so a non-string/empty pin is treated as\n // \"no pin\" rather than crashing `pair` (or, worse, comparing against garbage).\n const configuredExpectEmail =\n deps.configuredExpectEmail ??\n ((): string | undefined => {\n const pinned: unknown = readCliConfig().expectEmail;\n return typeof pinned === \"string\" && pinned.trim().length > 0 ? pinned : undefined;\n });\n\n return {\n name: \"pair\",\n summary: \"Pair this machine with your BirdyBeep account (QR or manual)\",\n usage: \"birdybeep pair [--yes] [--expect-email <addr>] [--json]\",\n options: [\n {\n flag: \"--yes\",\n aliases: [\"-y\"],\n summary: \"Skip the approving-account confirmation (headless/CI)\",\n },\n {\n flag: \"--expect-email\",\n value: \"<addr>\",\n summary: \"Only trust the pairing if this account approved it (else fail)\",\n },\n ],\n run: async (ctx) => {\n const pairFlags = parsePairFlags(ctx.args);\n if (pairFlags.error !== undefined) {\n ctx.io.errline(`birdybeep pair: ${pairFlags.error}.`);\n return EXIT.USAGE;\n }\n const apiUrl = resolveApiUrl();\n const identity = getMachineIdentity(); // { label, os, fingerprintHash }\n // PKCE (dgxd): commit to a fresh random verifier by sending only its S256 challenge on\n // /pair/start; prove possession of the verifier on every /pair/token. The verifier is a\n // short-lived SECRET kept in memory for this `pair` run ONLY — never persisted to disk or\n // the token store. Binds the token mint to THIS CLI so an interceptor of the device_code\n // can't redeem it. A newer server enforces it; an older one ignores it (backward compatible).\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = deriveCodeChallengeS256(codeVerifier);\n const start = await pairStart(\n apiUrl,\n { machineLabel: identity.label, os: identity.os, cliVersion: CLI_VERSION, codeChallenge },\n fetchImpl,\n );\n\n if (ctx.flags.json) {\n // NDJSON: emit the pairing info NOW so a script/agent can surface the complete\n // qr_payload for approval while we poll; the final success object is a later line.\n ctx.io.result({\n status: \"pairing_started\",\n user_code: start.user_code,\n qr_payload: start.qr_payload,\n expires_at: start.expires_at,\n });\n } else {\n // Approval needs the high-entropy fragment secret carried by the complete QR/link.\n // The short user_code remains visible only to identify the same pending session.\n ctx.io.line(\n \"To pair this machine, open the BirdyBeep app, tap “pair a machine”, and scan this QR or open the complete link:\",\n );\n // The matrix is TTY-only (a piped/CI consumer wants greppable lines, and\n // half-block art garbles logs); the link + code lines below ALWAYS print.\n const isTTY = deps.isTTY ?? process.stdout.isTTY === true;\n if (isTTY) ctx.io.line(renderQr(start.qr_payload));\n ctx.io.line(` Scan or open: ${start.qr_payload}`);\n ctx.io.line(\n ` Session code (display only; cannot approve by itself): ${start.user_code}`,\n );\n ctx.io.line(\"Waiting for you to approve this machine in the app…\");\n }\n\n // Poll /pair/token until approved (201), a TERMINAL error, or the window expires.\n const deadline = Date.parse(start.expires_at);\n const startedAt = clock();\n let lastBeat = startedAt;\n let paired: PairTokenResult | undefined;\n let terminal: Extract<PairTokenResult, { status: \"error\" }> | undefined;\n for (;;) {\n const nowMs = clock();\n if (nowMs >= deadline) break;\n await sleep(intervalMs);\n const poll = await pairTokenPoll(\n apiUrl,\n start.device_code,\n fetchImpl,\n identity.fingerprintHash,\n codeVerifier, // PKCE proof-of-possession (dgxd) — sent on every poll\n );\n if (poll.status === \"paired\") {\n paired = poll;\n break;\n }\n // A failure that waiting can't fix (e.g. the agent-install cap) must STOP the loop\n // and be shown — never masked as \"not approved yet\" so the prompt hangs silently.\n if (poll.status === \"error\" && !poll.retryable) {\n terminal = poll;\n break;\n }\n // Otherwise pending (not approved yet) or a transient server error → keep waiting,\n // reprinting a heartbeat so the prompt is visibly alive. Human-mode only (NDJSON\n // stays a clean two-line stream); time-gated on the clock so tests never see it.\n if (!ctx.flags.json && nowMs - lastBeat >= HEARTBEAT_MS) {\n ctx.io.line(\n poll.status === \"error\"\n ? ` still trying — the server is busy (${poll.message}). approve in the app when you can…`\n : \" still waiting — approve this machine in the BirdyBeep app…\",\n );\n lastBeat = nowMs;\n }\n }\n\n if (terminal !== undefined) {\n // NDJSON: a terminal result object on stderr+stdout so scripts see the reason code.\n ctx.io.result({ paired: false, reason: terminal.code });\n ctx.io.errline(`Pairing failed: ${terminal.message}`);\n return EXIT.ERROR;\n }\n\n if (paired === undefined || paired.status !== \"paired\") {\n // NDJSON contract: json mode gets a TERMINAL result object on every exit path,\n // so scripts can key off the last parseable line instead of only the exit code.\n ctx.io.result({ paired: false, reason: \"timeout\" });\n ctx.io.errline(\n \"Pairing timed out before you approved it. In the BirdyBeep app, tap “pair a machine”, scan a fresh QR or open its complete link, then run `birdybeep pair` again.\",\n );\n return EXIT.ERROR;\n }\n\n // ── md60: the confirm gate ────────────────────────────────────────────────────\n // The token is minted but NOT yet trusted. Before it is persisted, the human (or a\n // pinned identity) must confirm the account that approved this machine — so a\n // wrong-account or hijacked approval is caught at trust time, before any event flows.\n // Nothing below this point runs unless the gate approves: no token, no config write.\n const approvedBy = paired.approvedByEmail;\n // The flag wins over the config pin, so a one-off `pair` can override a fleet default.\n const expectEmail = pairFlags.expectEmail ?? configuredExpectEmail();\n const stdinIsTTY = deps.isStdinTTY ?? process.stdin.isTTY === true;\n const decision = decidePairConfirmation({\n ...(approvedBy !== undefined ? { approvedByEmail: approvedBy } : {}),\n ...(expectEmail !== undefined ? { expectEmail } : {}),\n ...(expectEmail !== undefined\n ? { expectEmailSource: pairFlags.expectEmail !== undefined ? \"flag\" : \"config\" }\n : {}),\n yes: pairFlags.yes,\n nonInteractive: ctx.flags.nonInteractive,\n stdinIsTTY,\n // Probed ONLY when stdin can't answer — opening /dev/tty is a syscall, and when stdin is\n // already a terminal the answer is irrelevant.\n controllingTerminalAvailable: stdinIsTTY ? false : hasControllingTerminal(),\n configPath: cliConfigPath(),\n });\n\n if (decision.action === \"reject\") {\n ctx.io.result({ paired: false, reason: decision.reason });\n ctx.io.errline(decision.message);\n return EXIT.ERROR;\n }\n if (\n decision.action === \"prompt\" &&\n !isAffirmative(await promptLine(decision.question, decision.on))\n ) {\n ctx.io.result({ paired: false, reason: \"declined\" });\n ctx.io.errline(\n \"Pairing declined — the machine token was NOT stored, and this machine will send no \" +\n \"events. The machine may still appear in the BirdyBeep app; revoke it there if you \" +\n \"did not intend to pair it.\",\n );\n return EXIT.ERROR;\n }\n\n // Confirmed. Durable token → secure store ONLY. Non-secret apiUrl → config. Never the reverse.\n await setToken(paired.machineToken, deps.tokenOptions ?? {});\n writeCliConfig({ apiUrl });\n\n // Surface the approving account (dgxd) when the server reports it, so the trusted\n // identity is on the record. Additive: absent from older servers.\n const humanSuffix = approvedBy !== undefined ? ` to ${approvedBy}` : \"\";\n ctx.io.emit(`✓ Paired${humanSuffix}. Run \\`birdybeep test\\` to send a test Beep.`, {\n paired: true,\n machineId: paired.machineId,\n ...(approvedBy !== undefined ? { approvedByEmail: approvedBy } : {}),\n });\n return EXIT.OK;\n },\n };\n}\n","/**\n * CLI pairing client — the device-code flow (§7.2/§13.4). `pairStart` opens a session via\n * `POST /v1/pair/start`; the CLI shows the complete `qr_payload` plus a display-only\n * `user_code`, then polls\n * `POST /v1/pair/token` (`pairTokenPoll`) until it returns 201 `{ machine_token, machine_id }`\n * or the `expires_at` deadline. A `validation_failed`/4xx during polling means \"not approved\n * yet — keep polling\". Per SPEC §11 the QR/link carries short-lived pairing info plus its\n * approval secret, NEVER a durable token; the user code alone cannot approve. Request/response\n * shapes are mirrored from the product (agent-core).\n */\nimport {\n type ErrorCode,\n errorEnvelopeSchema,\n type PairStartResponse,\n pairStartResponseSchema,\n pairTokenResponseSchema,\n} from \"@birdybeep/agent-core\";\n\nfunction base(apiUrl: string): string {\n return apiUrl.replace(/\\/$/, \"\");\n}\n\nexport interface PairStartInput {\n /** Required — the human machine label (derived from hostname/OS). */\n machineLabel: string;\n os?: string;\n cliVersion?: string;\n /**\n * PKCE S256 challenge = base64url(sha256(codeVerifier)) (dgxd). When present, the session is\n * BOUND to this CLI: the product's `/pair/token` then requires the matching `codeVerifier`.\n * Omit to keep the legacy device_code-only path (backward compatible with older servers).\n */\n codeChallenge?: string;\n}\n\n/** Begin a pairing session (`POST /v1/pair/start`, unauthenticated). */\nexport async function pairStart(\n apiUrl: string,\n input: PairStartInput,\n fetchImpl: typeof fetch,\n): Promise<PairStartResponse> {\n const body = {\n machine_label: input.machineLabel,\n ...(input.os !== undefined ? { os: input.os } : {}),\n ...(input.cliVersion !== undefined ? { cli_version: input.cliVersion } : {}),\n ...(input.codeChallenge !== undefined ? { code_challenge: input.codeChallenge } : {}),\n };\n const res = await fetchImpl(`${base(apiUrl)}/v1/pair/start`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw new Error(`pairing could not be started (HTTP ${res.status})`);\n const parsed = pairStartResponseSchema.safeParse(await res.json());\n if (!parsed.success) throw new Error(\"pairing start returned an unexpected response shape\");\n return parsed.data;\n}\n\nexport type PairTokenResult =\n | { status: \"pending\" }\n | { status: \"paired\"; machineToken: string; machineId: string; approvedByEmail?: string }\n /**\n * The backend returned an outcome that will NOT resolve by waiting (`retryable: false`,\n * e.g. `quota_exceeded` — the install cap is hit) or a transient server-side failure\n * (`retryable: true`, e.g. `internal_error`/5xx). Surfacing these is what stops `pair`\n * from masking a real error as \"not approved yet\" and hanging silently until timeout.\n */\n | { status: \"error\"; code: ErrorCode | \"unknown\"; message: string; retryable: boolean };\n\n/**\n * Terminal error codes on `/v1/pair/token`: waiting can never turn them into a 201, so the\n * CLI must STOP polling and show the user the reason. `quota_exceeded` (the agent-install cap)\n * is the one a real user actually hits; the auth-shaped codes should never occur on this\n * unauthenticated endpoint but are treated as terminal defensively (never loop forever).\n */\nconst TERMINAL_TOKEN_ERRORS: ReadonlySet<ErrorCode> = new Set<ErrorCode>([\n \"quota_exceeded\",\n \"unauthorized\",\n \"forbidden\",\n \"token_revoked\",\n \"not_found\",\n \"payload_too_large\",\n]);\n\n/**\n * Poll once for the device token (`POST /v1/pair/token`, unauthenticated). Outcomes:\n * - 201 with a valid token body → `paired`.\n * - `validation_failed`/4xx (the documented \"not approved yet\" signal) → `pending`, so the\n * caller keeps polling until the `expires_at` deadline.\n * - a TERMINAL error (e.g. `quota_exceeded`) → `error` with `retryable: false` — the caller\n * surfaces it and stops, instead of hanging silently on a failure waiting can't fix.\n * - `rate_limited`/`internal_error`/5xx/unparseable → `error` with `retryable: true` — the\n * caller keeps polling (transient) but can warn if it persists.\n */\nexport async function pairTokenPoll(\n apiUrl: string,\n deviceCode: string,\n fetchImpl: typeof fetch,\n machineFingerprint?: string,\n /**\n * PKCE verifier (dgxd) — the secret whose sha256 was committed as `code_challenge` on\n * `/pair/start`. Sent on EVERY poll: when the session was started with a challenge the server\n * requires it and checks `sha256Base64Url(verifier) === stored challenge`. Held in memory only;\n * never written to disk. Omit for a legacy (no-challenge) session.\n */\n codeVerifier?: string,\n): Promise<PairTokenResult> {\n const body = {\n device_code: deviceCode,\n ...(machineFingerprint !== undefined ? { machine_fingerprint: machineFingerprint } : {}),\n ...(codeVerifier !== undefined ? { code_verifier: codeVerifier } : {}),\n };\n const res = await fetchImpl(`${base(apiUrl)}/v1/pair/token`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (res.ok) {\n const parsed = pairTokenResponseSchema.safeParse(await res.json());\n if (!parsed.success) return { status: \"pending\" };\n return {\n status: \"paired\",\n machineToken: parsed.data.machine_token,\n machineId: parsed.data.machine_id,\n // Only surface the key when the server reported it (exactOptionalPropertyTypes: no explicit\n // undefined). Older servers omit approved_by_email; newer ones (dgxd) include it.\n ...(parsed.data.approved_by_email !== undefined\n ? { approvedByEmail: parsed.data.approved_by_email }\n : {}),\n };\n }\n\n // Non-2xx: read the typed §13.4 error envelope to tell \"not approved yet\" (keep polling)\n // apart from a real failure the user must see. A body that isn't a parseable envelope falls\n // back to the status code.\n let errBody: unknown = null;\n try {\n errBody = await res.json();\n } catch {\n /* empty / non-JSON error body → classify by status below */\n }\n const env = errorEnvelopeSchema.safeParse(errBody);\n const code = env.success ? env.data.error.code : undefined;\n\n // \"not approved yet\" is the documented benign signal → keep polling. Also treat any\n // unclassifiable 4xx (except 429) as pending, preserving the endpoint's historical\n // accept-and-keep-waiting behavior.\n if (\n code === \"validation_failed\" ||\n (code === undefined && res.status >= 400 && res.status < 500 && res.status !== 429)\n ) {\n return { status: \"pending\" };\n }\n\n const message = env.success ? env.data.error.message : `pairing failed (HTTP ${res.status})`;\n if (code !== undefined && TERMINAL_TOKEN_ERRORS.has(code)) {\n return { status: \"error\", code, message, retryable: false };\n }\n // rate_limited / internal_error / any 5xx / unrecognized → transient; safe to keep polling.\n return { status: \"error\", code: code ?? \"unknown\", message, retryable: true };\n}\n","/**\n * CLI version — single-sourced from package.json at build time (s0o7). `tsup.config.ts`\n * injects the real `@birdybeep/cli` version via the `__CLI_VERSION__` esbuild define, so\n * the shipped binary reports its true version for `--version` and the `cli_version` it\n * sends on `/pair/start` (the mobile approval sheet's machine identity). The `0.0.0`\n * fallback only applies to non-bundled runs (vitest / tsx), where the define is absent.\n */\n\n/** Build-time-replaced global; declared so source typechecks before tsup substitutes it. */\ndeclare const __CLI_VERSION__: string | undefined;\n\nexport const CLI_VERSION: string =\n typeof __CLI_VERSION__ === \"string\" && __CLI_VERSION__.length > 0 ? __CLI_VERSION__ : \"0.0.0\";\n","/**\n * `birdybeep queue clear` (§9.4) — debug maintenance: drop all locally-queued events. The\n * queue is best-effort (≤24h retention), so clearing it only discards pending retries; it\n * never touches harness config or the token. Reports how many entries were removed.\n */\nimport { LocalEventQueue } from \"@birdybeep/agent-core\";\n\nimport { type Command, EXIT } from \"../framework\";\n\nexport function createQueueCommand(): Command {\n return {\n name: \"queue\",\n summary: \"Local event-queue maintenance\",\n usage: \"birdybeep queue <clear>\",\n subcommands: [\n {\n name: \"clear\",\n summary: \"Clear the local offline event queue (debug)\",\n usage: \"birdybeep queue clear\",\n run: (ctx) => {\n const cleared = new LocalEventQueue().clear();\n ctx.io.emit(`Cleared ${cleared} queued event(s).`, { cleared });\n return EXIT.OK;\n },\n },\n ],\n };\n}\n","/**\n * `birdybeep report-status` (§7.3 step 7, §8.8, §21.2) — push each adapter's pre-event\n * integration status to the backend so the Machines/Integrations screen shows them BEFORE\n * any agent event fires. Sends ONE BATCHED `POST /v1/integrations/status` request\n * ({ integrations: [...] }, machine-token auth), parses the `{ integrations: [...] }`\n * response (surfacing the server's EFFECTIVE status, e.g. Codex → needs_trust), and parses\n * the mirrored error envelope: a 401/403 (unauthorized / forbidden / token_revoked) is\n * TERMINAL (exit non-zero), while offline / 5xx / rate_limit is \"deferred\" (surfaced, exit 0)\n * so it never blocks install.\n *\n * Request/response/error shapes are mirrored from the product (agent-core). fetch/adapters/\n * token injectable for hermetic tests; the live post is the deferred cross-repo follow-up.\n */\nimport {\n type AgentAdapter,\n errorEnvelopeSchema,\n getToken,\n type IntegrationStatusItem,\n integrationStatusResponseSchema,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\nimport { CLAUDE_CODE_ADAPTER_VERSION, claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { CODEX_ADAPTER_VERSION, codexAdapter } from \"@birdybeep/codex\";\nimport { COPILOT_ADAPTER_VERSION, copilotAdapter } from \"@birdybeep/copilot\";\nimport { CURSOR_ADAPTER_VERSION, cursorAdapter } from \"@birdybeep/cursor\";\nimport { OPENCODE_ADAPTER_VERSION, opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [\n claudeCodeAdapter,\n codexAdapter,\n opencodeAdapter,\n cursorAdapter,\n copilotAdapter,\n];\n\n/** Per-harness BirdyBeep adapter version (the schema's optional `adapter_version`). */\nconst ADAPTER_VERSIONS: Record<string, string> = {\n claude_code: CLAUDE_CODE_ADAPTER_VERSION,\n codex: CODEX_ADAPTER_VERSION,\n opencode: OPENCODE_ADAPTER_VERSION,\n cursor: CURSOR_ADAPTER_VERSION,\n copilot: COPILOT_ADAPTER_VERSION,\n};\n\nconst base = (apiUrl: string): string => apiUrl.replace(/\\/$/, \"\");\n\nasync function gatherItems(adapters: AgentAdapter[]): Promise<IntegrationStatusItem[]> {\n return Promise.all(\n adapters.map(async (a) => {\n const [detection, status] = await Promise.all([a.detect(), a.status()]);\n const item: IntegrationStatusItem = { harness: a.id, status };\n if (detection.version !== undefined) item.harness_version = detection.version;\n const adapterVersion = ADAPTER_VERSIONS[a.id];\n if (adapterVersion !== undefined) item.adapter_version = adapterVersion;\n return item;\n }),\n );\n}\n\nexport interface ReportStatusCommandDeps {\n adapters?: AgentAdapter[];\n fetchImpl?: typeof fetch;\n tokenOptions?: TokenStoreOptions;\n}\n\nexport function createReportStatusCommand(deps: ReportStatusCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n const fetchImpl = deps.fetchImpl ?? fetch;\n\n return {\n name: \"report-status\",\n summary: \"Internal: report integration status to the backend\",\n usage: \"birdybeep report-status [--json]\",\n run: async (ctx) => {\n const token = await getToken(deps.tokenOptions ?? {});\n if (token === null) {\n ctx.io.errline(\"No machine token — run `birdybeep pair` first.\");\n return EXIT.ERROR;\n }\n\n const items = await gatherItems(adapters);\n if (items.length === 0) {\n ctx.io.emit(\"No integrations to report.\", { outcome: \"reported\", integrations: [] });\n return EXIT.OK;\n }\n\n // The effective per-harness status to display; defaults to what we sent, overwritten by\n // the server's response when it 200s.\n let effective = items.map((i) => ({ harness: i.harness, status: i.status }));\n let outcome: \"reported\" | \"deferred\" | \"terminal\" = \"deferred\";\n let errorCode: string | undefined;\n\n try {\n const res = await fetchImpl(`${base(resolveApiUrl())}/v1/integrations/status`, {\n method: \"POST\",\n headers: { authorization: `Bearer ${token}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ integrations: items }),\n });\n if (res.ok) {\n outcome = \"reported\";\n const parsed = integrationStatusResponseSchema.safeParse(\n await res.json().catch(() => undefined),\n );\n if (parsed.success) {\n effective = parsed.data.integrations.map((i) => ({\n harness: i.harness,\n status: i.status,\n }));\n }\n } else {\n const env = errorEnvelopeSchema.safeParse(await res.json().catch(() => undefined));\n errorCode = env.success ? env.data.error.code : undefined;\n // The error CODE is the canonical terminal signal (auth failures); HTTP status is\n // only the fallback when the envelope didn't parse. Everything else → deferred.\n const terminal =\n errorCode !== undefined\n ? errorCode === \"unauthorized\" ||\n errorCode === \"forbidden\" ||\n errorCode === \"token_revoked\"\n : res.status === 401 || res.status === 403;\n outcome = terminal ? \"terminal\" : \"deferred\";\n }\n } catch {\n outcome = \"deferred\"; // offline / transport error → surfaced, not fatal\n }\n\n if (ctx.flags.json) {\n ctx.io.result({\n outcome,\n integrations: effective,\n ...(errorCode !== undefined ? { error: errorCode } : {}),\n });\n } else if (outcome === \"terminal\") {\n ctx.io.errline(\n `Report rejected (${errorCode ?? \"auth\"}) — your token may be revoked. Re-run \\`birdybeep pair\\`.`,\n );\n } else {\n for (const e of effective) {\n ctx.io.line(\n outcome === \"reported\"\n ? `✓ ${e.harness}: ${e.status} (reported)`\n : `• ${e.harness}: ${e.status} (deferred — backend unreachable)`,\n );\n }\n }\n\n // Terminal auth failure → non-zero; offline/deferred → 0 (must never block install).\n return outcome === \"terminal\" ? EXIT.ERROR : EXIT.OK;\n },\n };\n}\n","/**\n * `birdybeep status` (§9.3, §9.4) — a quick health snapshot: machine identity + pairing\n * state, per-harness integration status, and local queue depth, while opportunistically\n * draining the queue (best-effort, non-blocking) and reporting delivered-vs-remaining.\n * Exits non-zero when not paired so scripts can branch. `--json` mirrors everything.\n * Factory with injectable adapters/sender/token so tests run hermetically against a stub.\n */\nimport {\n type AgentAdapter,\n createSender as defaultCreateSender,\n type Sender,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\nimport { claudeCodeAdapter } from \"@birdybeep/claude-code\";\nimport { codexAdapter } from \"@birdybeep/codex\";\nimport { copilotAdapter } from \"@birdybeep/copilot\";\nimport { cursorAdapter } from \"@birdybeep/cursor\";\nimport { opencodeAdapter } from \"@birdybeep/opencode\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { gatherIntegrations, isPaired, localQueueDepth, machineIdentity } from \"../diagnostics\";\nimport { type Command, EXIT } from \"../framework\";\n\nconst DEFAULT_ADAPTERS: AgentAdapter[] = [\n claudeCodeAdapter,\n codexAdapter,\n opencodeAdapter,\n cursorAdapter,\n copilotAdapter,\n];\n\nexport interface StatusCommandDeps {\n adapters?: AgentAdapter[];\n /** Build the drain sender (default: agent-core createSender at the resolved API URL). */\n createSender?: (baseUrl: string) => Sender;\n /** Token-store options (tests inject the file fallback). */\n tokenOptions?: TokenStoreOptions;\n}\n\nexport function createStatusCommand(deps: StatusCommandDeps = {}): Command {\n const adapters = deps.adapters ?? DEFAULT_ADAPTERS;\n const makeSender =\n deps.createSender ??\n ((baseUrl) =>\n defaultCreateSender(\n deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl },\n ));\n\n return {\n name: \"status\",\n summary: \"Show pairing + per-harness integration status\",\n usage: \"birdybeep status [--json]\",\n run: async (ctx) => {\n const machine = machineIdentity();\n const paired = await isPaired(deps.tokenOptions ?? {});\n const integrations = await gatherIntegrations(adapters);\n const depthBefore = localQueueDepth();\n const drain = await makeSender(resolveApiUrl()).drainNow(); // opportunistic, best-effort\n const depthAfter = localQueueDepth();\n\n const report = {\n machine,\n paired,\n integrations,\n queue: { depthBefore, delivered: drain.delivered, depthAfter },\n };\n\n if (ctx.flags.json) {\n ctx.io.result(report);\n } else {\n ctx.io.line(`Machine: ${machine.label} (${machine.os})`);\n ctx.io.line(paired ? \"Paired: yes\" : \"Paired: no — run `birdybeep pair`\");\n ctx.io.line(\"Integrations:\");\n for (const i of integrations) ctx.io.line(` ${i.displayName}: ${i.status}`);\n ctx.io.line(\n `Queue: ${depthBefore} queued → ${drain.delivered} delivered, ${depthAfter} remaining`,\n );\n }\n return paired ? EXIT.OK : EXIT.ERROR; // not-paired → defined non-zero\n },\n };\n}\n","/**\n * `birdybeep test` (§7.1, §9.4) — send a representative test event through the REAL sender\n * path (normalize/redact/truncate → send w/ short timeout → queue-on-fail → opportunistic\n * drain) so a developer can confirm end-to-end delivery (and trigger a test Beep) right\n * after pairing. Not a mock — it exercises the production code path. Reports delivered vs\n * queued (offline) vs rejected; --json mirrors the outcome.\n *\n * Sends event_type \"test\" (9fh): the backend notifies it by default and exempts it from\n * the beep quota. (The old \"custom\" type is unconditionally suppressed by the §10.5\n * matrix — every test \"succeeded\" while no push could ever be sent.) The session id is\n * unique per run so back-to-back tests don't collapse in the backend's dedupe window,\n * and the CLI reports the backend's actual DECISION instead of assuming a beep.\n */\nimport { randomUUID } from \"node:crypto\";\n\nimport {\n type BirdyBeepAgentEvent,\n createSender as defaultCreateSender,\n getMachineIdentity,\n normalizeEvent,\n type NormalizeOptions,\n type Sender,\n type TokenStoreOptions,\n} from \"@birdybeep/agent-core\";\n\nimport { resolveApiUrl } from \"../config\";\nimport { type Command, EXIT } from \"../framework\";\n\n/** Build the canonical test event (event_type `test`, unique session per run). cwd is hashed by the normalizer. */\nexport function buildTestEvent(opts: NormalizeOptions = {}): BirdyBeepAgentEvent {\n const machine = getMachineIdentity();\n return normalizeEvent(\n {\n event_type: \"test\",\n status: \"running\",\n harness: \"claude_code\", // schema requires a harness; the \"test\" type distinguishes it\n // Unique per run: a repeat `birdybeep test` inside the backend's dedupe window must\n // still beep — a constant id made the second test silently \"deduped\" (9fh).\n source_session_id: `birdybeep-cli-test-${randomUUID()}`,\n machine: { label: machine.label, os: machine.os },\n workspace: { cwd: process.cwd() },\n title: \"BirdyBeep test event\",\n body: \"If you can see this, your machine is wired up correctly.\",\n metadata: { test: true },\n },\n opts,\n );\n}\n\nexport interface TestCommandDeps {\n createSender?: (baseUrl: string) => Sender;\n tokenOptions?: TokenStoreOptions;\n}\n\nexport function createTestCommand(deps: TestCommandDeps = {}): Command {\n const makeSender =\n deps.createSender ??\n ((baseUrl) =>\n defaultCreateSender(\n deps.tokenOptions ? { baseUrl, tokenOptions: deps.tokenOptions } : { baseUrl },\n ));\n\n return {\n name: \"test\",\n summary: \"Send a test event end-to-end\",\n usage: \"birdybeep test [--json]\",\n run: async (ctx) => {\n const event = buildTestEvent();\n const result = await makeSender(resolveApiUrl()).send(event); // real path; also drains the queue\n\n if (ctx.flags.json) {\n ctx.io.result({\n outcome: result.outcome,\n ...(result.status ? { status: result.status } : {}),\n ...(result.decision ? { decision: result.decision } : {}),\n });\n } else if (result.outcome === \"delivered\") {\n // The 202 body says what the backend DECIDED — \"delivered\" alone only means\n // \"accepted\". Claiming a beep that was suppressed is how 9fh went unnoticed.\n if (result.decision === \"notified\" || result.decision === undefined) {\n ctx.io.line(\"✓ Test event delivered — check your phone for a test Beep.\");\n } else if (result.decision === \"suppressed\") {\n ctx.io.line(\n \"⚠ The backend accepted the test event but suppressed the push — this machine \" +\n \"or integration is probably muted. Check mutes in the app, or run `birdybeep doctor`.\",\n );\n } else if (result.decision === \"deduped\") {\n ctx.io.line(\n \"⚠ The backend accepted the test event but folded it into a recent duplicate — \" +\n \"wait ~30s and run `birdybeep test` again.\",\n );\n } else {\n ctx.io.line(\n `⚠ The backend accepted the test event but decided \"${result.decision}\" — no push ` +\n \"was sent. Run `birdybeep doctor`.\",\n );\n }\n } else if (result.outcome === \"queued\") {\n ctx.io.line(\"• Offline — test event queued; it will deliver when you reconnect.\");\n } else {\n ctx.io.line(\"✗ Test event was rejected by the backend. Run `birdybeep doctor`.\");\n }\n\n // delivered + queued are non-failure (offline is by design); a hard reject is an error.\n return result.outcome === \"dropped\" ? EXIT.ERROR : EXIT.OK;\n },\n };\n}\n","/**\n * The `birdybeep` command registry (§9.4) — the command tree the framework dispatches.\n * Every command is a factory (`create*Command`) so its dependencies (adapters, sender,\n * token store, fetch, stdin) are injectable for hermetic tests; the framework (help /\n * flags / routing / config dir / exit codes) is command-independent.\n */\nimport { createAgentCommand } from \"./commands/agent\";\nimport { createDoctorCommand } from \"./commands/doctor\";\nimport { createHookCommand } from \"./commands/hook\";\nimport { createLogoutCommand, createUnpairCommand } from \"./commands/logout\";\nimport { createPairCommand } from \"./commands/pair\";\nimport { createQueueCommand } from \"./commands/queue\";\nimport { createReportStatusCommand } from \"./commands/report-status\";\nimport { createStatusCommand } from \"./commands/status\";\nimport { createTestCommand } from \"./commands/test\";\nimport { type Command } from \"./framework\";\n\n/** Build the full §9.4 command tree. */\nexport function buildCommands(): Command[] {\n return [\n createPairCommand(),\n createLogoutCommand(),\n createUnpairCommand(),\n createStatusCommand(),\n createTestCommand(),\n createDoctorCommand(),\n createAgentCommand(),\n createHookCommand(),\n createQueueCommand(),\n createReportStatusCommand(),\n ];\n}\n","/**\n * Passive update notifier (§9.4). Instead of a manual `update` command, the CLI opportunistically\n * checks the npm registry for a newer `@birdybeep/cli` and prints a subtle \"new version available\"\n * notice to **stderr** after an eligible command runs — so users learn about upgrades just by using\n * the tool. It is:\n *\n * - **Cached (TTL-gated):** the result is stored in the config dir and only refreshed from the\n * network once per {@link DEFAULT_CHECK_INTERVAL_MS}; every other run is a local file read.\n * - **Non-blocking to the hot path:** the `hook` command (which runs inside the harness and must\n * return fast) and the internal `report-status` command are skipped before any I/O.\n * - **Quiet for machines/scripts:** skipped under `--json`, `--non-interactive`, a non-TTY stderr,\n * `CI`, or the `NO_UPDATE_NOTIFIER` / `BIRDYBEEP_NO_UPDATE_NOTIFIER` opt-outs.\n * - **Best-effort & side-effect-free on the result:** it never throws, never changes stdout, and\n * never affects the command's exit code (registry/semver logic lives here, not in the framework).\n */\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { birdyBeepConfigDir } from \"@birdybeep/agent-core\";\n\nimport { resolveRegistryUrl } from \"./config\";\nimport { type GlobalFlags, type Io } from \"./framework\";\nimport { CLI_VERSION } from \"./version\";\n\n/** The published package the notice points at. */\nexport const PACKAGE_NAME = \"@birdybeep/cli\";\n/** URL-encoded scoped path for the registry `latest` dist-tag endpoint. */\nconst PACKAGE_PATH = \"@birdybeep%2Fcli\";\n/** Cache file (non-secret) in the BirdyBeep config dir. */\nexport const UPDATE_CACHE_FILE = \"update-check.json\";\n/** Refresh the registry at most once per this window; every other run reads the cache. */\nexport const DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h\n/** Best-effort timeout for the (rare) registry refresh — short so it can't stall a command. */\nconst DEFAULT_TIMEOUT_MS = 1500;\n\n/**\n * Top-level commands that must never trigger a check/notice:\n * - `hook` runs inside the harness hot path and must return fast (never block the harness);\n * - `report-status` is invoked by BirdyBeep itself, not by an interactive user.\n */\nconst SKIP_COMMANDS = new Set([\"hook\", \"report-status\"]);\n\n/** A parsed semver: numeric core + dot-separated prerelease identifiers (build metadata dropped). */\nexport interface Semver {\n major: number;\n minor: number;\n patch: number;\n /** Prerelease identifiers (e.g. `1.2.0-beta.1` → `[\"beta\", \"1\"]`); empty for a release. */\n prerelease: string[];\n}\n\n// Simplified semver.org grammar: `MAJOR.MINOR.PATCH[-prerelease][+build]`, tolerating a leading `v`.\nconst SEMVER_RE = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/;\n\n/** Parse a semver string; returns null for anything that isn't a clean `MAJOR.MINOR.PATCH[...]`. */\nexport function parseSemver(input: string): Semver | null {\n const m = SEMVER_RE.exec(input.trim());\n if (m === null) return null;\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n prerelease: m[4] !== undefined ? m[4].split(\".\") : [],\n };\n}\n\n/** Compare two prerelease identifier lists per semver §11 (a release outranks any prerelease). */\nfunction comparePrerelease(a: string[], b: string[]): number {\n if (a.length === 0 && b.length === 0) return 0;\n if (a.length === 0) return 1; // 1.2.0 > 1.2.0-beta\n if (b.length === 0) return -1;\n const len = Math.min(a.length, b.length);\n for (let i = 0; i < len; i++) {\n const ai = a[i]!;\n const bi = b[i]!;\n const aNum = /^\\d+$/.test(ai);\n const bNum = /^\\d+$/.test(bi);\n if (aNum && bNum) {\n const d = Number(ai) - Number(bi);\n if (d !== 0) return d < 0 ? -1 : 1;\n } else if (aNum) {\n return -1; // numeric identifiers rank lower than alphanumeric\n } else if (bNum) {\n return 1;\n } else if (ai !== bi) {\n return ai < bi ? -1 : 1; // ASCII lexical order\n }\n }\n if (a.length === b.length) return 0;\n return a.length < b.length ? -1 : 1; // more identifiers wins when all preceding are equal\n}\n\n/** -1 if `a < b`, 0 if equal, 1 if `a > b` (semver precedence). */\nexport function compareSemver(a: Semver, b: Semver): number {\n if (a.major !== b.major) return a.major < b.major ? -1 : 1;\n if (a.minor !== b.minor) return a.minor < b.minor ? -1 : 1;\n if (a.patch !== b.patch) return a.patch < b.patch ? -1 : 1;\n return comparePrerelease(a.prerelease, b.prerelease);\n}\n\n/** `true` when `latest` is a strictly higher version than `current` (both must parse). */\nexport function isNewer(current: string, latest: string): boolean {\n const cur = parseSemver(current);\n const lat = parseSemver(latest);\n return cur !== null && lat !== null && compareSemver(cur, lat) < 0;\n}\n\n/** Cached registry result. `latest` is the last-seen published version, or null if never fetched. */\nexport interface UpdateCache {\n /** Epoch ms of the last registry refresh attempt. */\n checkedAt: number;\n latest: string | null;\n}\n\nexport function updateCachePath(): string {\n return join(birdyBeepConfigDir(), UPDATE_CACHE_FILE);\n}\n\n/** Read the cache; returns null on a missing/unreadable/corrupt/invalid file (never throws). */\nexport function readUpdateCache(): UpdateCache | null {\n try {\n const parsed: unknown = JSON.parse(readFileSync(updateCachePath(), \"utf8\"));\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const { checkedAt, latest } = parsed as Record<string, unknown>;\n if (typeof checkedAt !== \"number\") return null;\n if (latest !== null && typeof latest !== \"string\") return null;\n return { checkedAt, latest };\n } catch {\n return null;\n }\n}\n\n/** Persist the cache (strict-perm dir + file); best-effort — a write failure is swallowed by callers. */\nexport function writeUpdateCache(cache: UpdateCache): void {\n mkdirSync(birdyBeepConfigDir(), { recursive: true, mode: 0o700 });\n writeFileSync(updateCachePath(), `${JSON.stringify(cache)}\\n`, { mode: 0o600 });\n}\n\n/** Fetch the `latest` dist-tag version from the registry, or throw a concise reason. */\nasync function fetchLatestVersion(\n registryUrl: string,\n fetchImpl: typeof fetch,\n timeoutMs: number,\n): Promise<string> {\n const url = `${registryUrl.replace(/\\/+$/, \"\")}/${PACKAGE_PATH}/latest`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n if (typeof timer.unref === \"function\") timer.unref();\n try {\n const res = await fetchImpl(url, {\n headers: { accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!res.ok) throw new Error(`registry responded ${res.status}`);\n const body = (await res.json()) as { version?: unknown };\n if (typeof body.version !== \"string\" || body.version.length === 0) {\n throw new Error(\"registry response had no version\");\n }\n return body.version;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** The two-line upgrade notice printed to stderr (lowercase/chirpy per the Perch voice). */\nfunction renderNotice(current: string, latest: string): string {\n return (\n `a new version of birdybeep is available: ${current} → ${latest}\\n` +\n `upgrade with: npm install -g ${PACKAGE_NAME}@latest`\n );\n}\n\nexport interface NotifyUpdateOptions {\n /** Resolved top-level command name (used to skip `hook` / `report-status`). */\n command?: string;\n flags: GlobalFlags;\n io: Io;\n // --- injectables (production defaults are the real registry / fs / clock / env / TTY) ---\n fetchImpl?: typeof fetch;\n currentVersion?: string;\n registryUrl?: string;\n now?: number;\n intervalMs?: number;\n timeoutMs?: number;\n /** Override the stderr-TTY gate (tests set this true to exercise the notice deterministically). */\n isTTY?: boolean;\n env?: NodeJS.ProcessEnv;\n readCache?: () => UpdateCache | null;\n writeCache?: (cache: UpdateCache) => void;\n}\n\n/**\n * The notifier entry point, invoked by the framework after an eligible command runs. Reads the\n * cache, refreshes from the registry when stale (TTL-gated, short timeout, best-effort), and prints\n * the upgrade notice to stderr when a newer version exists. Never throws.\n */\nexport async function maybeNotifyUpdate(opts: NotifyUpdateOptions): Promise<void> {\n try {\n // Hot-path / internal commands: bail before any work so the harness is never slowed.\n if (opts.command !== undefined && SKIP_COMMANDS.has(opts.command)) return;\n // Machine/script output or explicit non-interactive: no chatter on stderr.\n if (opts.flags.json || opts.flags.nonInteractive) return;\n\n const env = opts.env ?? process.env;\n if (env[\"BIRDYBEEP_NO_UPDATE_NOTIFIER\"] || env[\"NO_UPDATE_NOTIFIER\"] || env[\"CI\"]) return;\n\n const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);\n if (!isTTY) return; // don't nag in pipes/logs\n\n const current = opts.currentVersion ?? CLI_VERSION;\n const now = opts.now ?? Date.now();\n const intervalMs = opts.intervalMs ?? DEFAULT_CHECK_INTERVAL_MS;\n const readCache = opts.readCache ?? readUpdateCache;\n const writeCache = opts.writeCache ?? writeUpdateCache;\n\n let cache = readCache();\n if (cache === null || now - cache.checkedAt >= intervalMs) {\n // Refresh at most once per interval. On failure, keep the last-known `latest` (so a\n // previously-seen update still shows) but still stamp `checkedAt` to back off, never hammer.\n let latest = cache?.latest ?? null;\n try {\n latest = await fetchLatestVersion(\n opts.registryUrl ?? resolveRegistryUrl(),\n opts.fetchImpl ?? fetch,\n opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n );\n } catch {\n /* offline / registry error: fall back to last-known latest, back off for the interval */\n }\n cache = { checkedAt: now, latest };\n try {\n writeCache(cache);\n } catch {\n /* config dir not writable: notice still works this run, just won't be cached */\n }\n }\n\n if (cache.latest !== null && isNewer(current, cache.latest)) {\n opts.io.errline(renderNotice(current, cache.latest));\n }\n } catch {\n /* the notifier is best-effort — it must never break or slow a command */\n }\n}\n","/**\n * @birdybeep/cli — the public, side-effect-free CLI API. `runCli` wires the §9.4 command\n * registry into the framework dispatcher with injectable output (so it is fully unit\n * testable); the executable shell lives in `bin.ts`.\n */\nimport { buildCommands } from \"./commands\";\nimport { type Command, dispatch, type Writer } from \"./framework\";\nimport { maybeNotifyUpdate, type NotifyUpdateOptions } from \"./update-check\";\nimport { CLI_VERSION } from \"./version\";\n\nexport { buildCommands } from \"./commands\";\nexport * from \"./framework\";\nexport { CLI_VERSION } from \"./version\";\n\nexport interface RunCliDeps {\n stdout?: Writer;\n stderr?: Writer;\n /** Override the command registry (tests). Defaults to the real §9.4 tree. */\n commands?: Command[];\n /** Skip the config-dir bootstrap (tests without filesystem side effects). */\n ensureConfig?: boolean;\n /**\n * Override the passive update-notifier. `false` disables it; an object injects the registry\n * fetch / clock / TTY / cache for hermetic tests. Omitted in production → the real notifier\n * (which no-ops on a non-TTY stderr, so unit tests capturing to buffers stay offline & quiet).\n */\n updateCheck?: Partial<NotifyUpdateOptions> | false;\n}\n\n/** Run the CLI against an argv slice (without `node`/script path). Returns the exit code. */\nexport function runCli(argv: string[], deps: RunCliDeps = {}): Promise<number> {\n const notifyUpdate =\n deps.updateCheck === false\n ? undefined\n : (ctx: {\n command: string;\n flags: NotifyUpdateOptions[\"flags\"];\n io: NotifyUpdateOptions[\"io\"];\n }) => maybeNotifyUpdate({ ...ctx, ...(deps.updateCheck ?? {}) });\n\n return dispatch(argv, {\n version: CLI_VERSION,\n commands: deps.commands ?? buildCommands(),\n stdout: deps.stdout ?? process.stdout,\n stderr: deps.stderr ?? process.stderr,\n ...(notifyUpdate !== undefined ? { notifyUpdate } : {}),\n ...(deps.ensureConfig !== undefined ? { ensureConfig: deps.ensureConfig } : {}),\n });\n}\n"],"mappings":"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","names":["import_agent_core","import_claude_code","import_codex","import_copilot","import_cursor","import_opencode","import_node_fs","import_agent_core","import_agent_core","DEFAULT_ADAPTERS","defaultCreateSender","import_node_fs","import_node_path","import_agent_core","import_claude_code","import_codex","import_copilot","import_cursor","import_opencode","defaultCreateSender","import_agent_core","import_node_fs","import_agent_core","import_agent_core","base","import_agent_core","import_agent_core","import_claude_code","import_codex","import_copilot","import_cursor","import_opencode","DEFAULT_ADAPTERS","base","import_agent_core","import_claude_code","import_codex","import_copilot","import_cursor","import_opencode","DEFAULT_ADAPTERS","defaultCreateSender","import_node_crypto","import_agent_core","defaultCreateSender","import_node_fs","import_node_path","import_agent_core"]}