olakai-cli 0.7.0 → 0.8.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.
- package/dist/{chunk-GXKHWBGO.js → chunk-B44Y3ZQP.js} +71 -4
- package/dist/{chunk-GXKHWBGO.js.map → chunk-B44Y3ZQP.js.map} +1 -1
- package/dist/{chunk-75YQWZ4Q.js → chunk-E33XD5CO.js} +1385 -19
- package/dist/chunk-E33XD5CO.js.map +1 -0
- package/dist/{doctor-27VDFNP7.js → doctor-TIVMQBE3.js} +3 -3
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/{repair-WSBWAW2B.js → repair-JYRH2ES4.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-75YQWZ4Q.js.map +0 -1
- /package/dist/{doctor-27VDFNP7.js.map → doctor-TIVMQBE3.js.map} +0 -0
- /package/dist/{repair-WSBWAW2B.js.map → repair-JYRH2ES4.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/monitor/plugin.ts","../src/monitor/registry.ts","../src/monitor/plugins/claude-code/index.ts","../src/commands/monitor-state.ts","../src/monitor/plugins/claude-code/install.ts","../src/monitor/self-monitor-provision.ts","../src/lib/git.ts","../src/monitor/prompt.ts","../src/monitor/plugins/claude-code/hook.ts","../src/commands/monitor-transcript.ts","../src/monitor/plugins/codex/index.ts","../src/monitor/plugins/codex/install.ts","../src/monitor/plugins/codex/paths.ts","../src/monitor/plugins/codex/hooks.ts","../src/monitor/plugins/codex/config.ts","../src/monitor/plugins/codex/status.ts","../src/monitor/plugins/codex/transcript.ts","../src/monitor/plugins/codex/hook.ts","../src/monitor/plugins/cursor/index.ts","../src/monitor/plugins/cursor/install.ts","../src/monitor/plugins/cursor/config.ts","../src/monitor/plugins/cursor/paths.ts","../src/monitor/plugins/cursor/hooks-config.ts","../src/monitor/plugins/cursor/status.ts","../src/monitor/plugins/cursor/hook.ts","../src/monitor/plugins/cursor/pairing-state.ts","../src/monitor/plugins/gemini-cli/index.ts","../src/monitor/plugins/gemini-cli/install.ts","../src/monitor/plugins/gemini-cli/config.ts","../src/monitor/plugins/gemini-cli/paths.ts","../src/monitor/plugins/gemini-cli/settings.ts","../src/monitor/plugins/gemini-cli/status.ts","../src/monitor/plugins/gemini-cli/pairing-state.ts","../src/monitor/plugins/gemini-cli/hook.ts","../src/monitor/plugins/antigravity/index.ts","../src/monitor/plugins/antigravity/install.ts","../src/monitor/plugins/antigravity/config.ts","../src/monitor/plugins/antigravity/paths.ts","../src/monitor/plugins/antigravity/settings.ts","../src/monitor/plugins/antigravity/status.ts","../src/monitor/plugins/antigravity/hook.ts","../src/monitor/plugins/antigravity/transcript.ts","../src/monitor/install.ts"],"sourcesContent":["/**\n * Plugin contract for \"Local Coding Agents\" monitoring (D-001).\n *\n * Each supported tool (Claude Code, OpenAI Codex CLI, Cursor) provides a\n * `ToolMonitorPlugin` that knows how to install hooks, parse hook\n * payloads, and report status for that tool. The CLI dispatches via the\n * registry below — `monitor.ts` is intentionally tool-agnostic so adding\n * a new tool is a matter of dropping a plugin module under\n * `plugins/<tool>/index.ts` and registering it.\n *\n * Stage 2 ships the Claude Code plugin and stubs for codex/cursor.\n * Stages 3 and 4 fill in the stubs.\n */\nimport type { Command } from \"commander\";\n\nexport type ToolId =\n | \"claude-code\"\n | \"codex\"\n | \"cursor\"\n | \"gemini-cli\"\n | \"antigravity\";\n\nexport const TOOL_IDS: readonly ToolId[] = [\n \"claude-code\",\n \"codex\",\n \"cursor\",\n \"gemini-cli\",\n \"antigravity\",\n] as const;\n\n/**\n * Inputs to `install()`. `projectRoot` defaults to `process.cwd()` when\n * the caller doesn't override it (e.g. tests). `interactive` toggles\n * `prompt`-driven flows; non-interactive mode (CI, scripts) must error\n * cleanly when required inputs are missing.\n */\nexport interface InstallOpts {\n projectRoot?: string;\n interactive?: boolean;\n}\n\nexport interface InstallResult {\n agentId: string;\n agentName: string;\n source: string;\n monitoringEndpoint: string;\n}\n\nexport interface UninstallOpts {\n projectRoot?: string;\n keepConfig?: boolean;\n}\n\n/**\n * Per-tool snapshot returned by `status()`. Designed so a future\n * \"show all installed tools\" view can fan out across plugins and\n * collect the same shape.\n */\nexport interface StatusReport {\n toolId: ToolId;\n configured: boolean;\n hooksConfigured: boolean;\n agentId?: string;\n agentName?: string;\n source?: string;\n apiKeyMasked?: string;\n monitoringEndpoint?: string;\n configuredAt?: string;\n /** Path to the per-tool config file, when known. */\n configPath?: string;\n /** Free-form one-line message for human display when not in JSON mode. */\n notes?: string[];\n}\n\n/**\n * Canonical monitoring payload. Mirrors the existing Claude Code POST\n * shape against `/api/monitoring/prompt`. New tools must produce the\n * same shape so the backend doesn't need per-tool branches.\n *\n * Kept loose (`Record<string, unknown>` for `customData`) because each\n * tool emits a different feature set; the backend tolerates extra keys.\n */\nexport interface MonitoringPayload {\n prompt: string;\n response: string;\n chatId: string;\n source: string;\n modelName?: string;\n tokens: number;\n customData: Record<string, unknown>;\n}\n\n/**\n * Resolved transport metadata returned alongside the payload from\n * `handleHook`. The plugin already walked the filesystem and loaded\n * the per-tool config to build/dedup the payload — passing the\n * resolved values through to the dispatcher prevents a second resolve\n * (and the stale-config race that would create) and avoids any chance\n * the dispatcher's CWD-based lookup disagrees with the plugin's.\n */\nexport interface HookTransport {\n /** Outbound endpoint for the canonical monitoring POST. */\n endpoint: string;\n /** Bearer/x-api-key for the POST. Plugin owns secret resolution. */\n apiKey: string;\n /** Configured workspace root the plugin matched the event to. */\n projectRoot: string;\n}\n\n/**\n * Result of a plugin's `handleHook`. Payload is the canonical shape\n * the dispatcher will POST; transport carries the resolved endpoint\n * and credentials so the dispatcher does not re-walk the filesystem\n * or re-load the config (which would race against `monitor disable`).\n */\nexport interface HookResult {\n payload: MonitoringPayload;\n transport: HookTransport;\n}\n\nexport interface ToolMonitorPlugin {\n id: ToolId;\n displayName: string;\n install(opts: InstallOpts): Promise<InstallResult>;\n uninstall(opts: UninstallOpts): Promise<void>;\n status(opts?: { projectRoot?: string }): Promise<StatusReport>;\n /**\n * Transform an inbound hook event into a canonical MonitoringPayload\n * plus the resolved transport (endpoint + apiKey + projectRoot).\n * Returning `null` signals \"nothing to report for this event\" (e.g.\n * an unknown event name) — the dispatcher will silent-exit.\n *\n * The plugin owns workspace + config resolution because it already\n * walked the filesystem to dedup the turn; threading the resolved\n * values through to the dispatcher prevents a stale-config race\n * (e.g. `olakai monitor disable` mid-flight) and CWD disagreement.\n */\n handleHook(\n eventName: string,\n payloadJson: unknown,\n opts?: { projectRoot?: string },\n ): Promise<HookResult | null>;\n /**\n * Whether this tool is detectably installed on the host. Powers the\n * D-002 auto-detection path inside the interactive `init` prompt.\n * MUST be cheap and non-throwing — return `false` on any uncertainty.\n */\n detectInstalled(opts?: { projectRoot?: string }): Promise<boolean>;\n /**\n * Optional per-tool subcommand registration on the parent `monitor`\n * Commander group. Plugins can use this to expose tool-specific\n * helpers (e.g. `olakai monitor claude-code transcript`). The base\n * `init`/`status`/`disable`/`hook` commands are owned by `monitor.ts`.\n */\n registerCommands?: (parent: Command) => void;\n}\n\nexport class NotImplementedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NotImplementedError\";\n }\n}\n\nconst registry = new Map<ToolId, ToolMonitorPlugin>();\n\nexport function registerPlugin(plugin: ToolMonitorPlugin): void {\n registry.set(plugin.id, plugin);\n}\n\nexport function getPlugin(id: string): ToolMonitorPlugin {\n if (!isToolId(id)) {\n throw new Error(\n `Unknown tool: \"${id}\". Supported tools: ${TOOL_IDS.join(\", \")}`,\n );\n }\n const plugin = registry.get(id);\n if (!plugin) {\n throw new Error(\n `Tool \"${id}\" is not registered. This is a CLI bug — please report it.`,\n );\n }\n return plugin;\n}\n\nexport function tryGetPlugin(id: string): ToolMonitorPlugin | null {\n if (!isToolId(id)) return null;\n return registry.get(id) ?? null;\n}\n\nexport function listPlugins(): ToolMonitorPlugin[] {\n return Array.from(registry.values());\n}\n\nexport function isToolId(value: string): value is ToolId {\n return (TOOL_IDS as readonly string[]).includes(value);\n}\n\n/**\n * Test/integration hook — clears the registry. Production code must not\n * call this; only the test harness uses it to keep registrations\n * deterministic across files.\n */\nexport function __resetRegistryForTests(): void {\n registry.clear();\n}\n","/**\n * Machine-level monitor registry (D-001).\n *\n * `olakai monitor init` writes a per-workspace config under\n * `<root>/.olakai/monitor-<tool>.json`, but nothing records installs\n * machine-wide. `olakai monitor status` only inspects `process.cwd()`,\n * so there is no answer to \"what am I monitoring on this machine, and\n * where?\". The backend has no scope model (Agents are account-scoped\n * only), so \"where am I monitoring?\" is a purely client-side fact.\n *\n * This module persists that fact at `~/.olakai/registry.json` (0600),\n * a sibling of the existing `~/.olakai/monitor-state/` dir. It is the\n * **machine lens** source of truth that `monitor list` (this stage) and\n * `monitor doctor`/`repair` (later stages) read from.\n *\n * Design contract:\n * - All reads are tolerant: a missing OR corrupt file yields an empty\n * registry; readRegistry NEVER throws.\n * - Writes are atomic (temp file + rename) and chmod 0600.\n * - Entries are keyed by the composite (path, tool); upsert replaces.\n * - Callers (init/disable) treat registry failures as non-fatal — a\n * registry write must never fail an install/uninstall.\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport {\n findConfiguredWorkspace,\n getLegacyClaudeMonitorConfigPath,\n getMonitorConfigPath,\n} from \"./paths.js\";\nimport { TOOL_IDS, type ToolId } from \"./plugin.js\";\n\nexport const REGISTRY_VERSION = 1 as const;\n\nconst OLAKAI_HOME_DIRNAME = \".olakai\";\nconst REGISTRY_FILENAME = \"registry.json\";\n\n/** Where each tool's hooks live, and whether that surface is per-workspace\n * or per-user/global. Centralized so the registry, reconcile, and the\n * install wire-in all agree. */\nexport type RegistryScope = \"workspace\" | \"global\";\n\n/**\n * A single monitored (workspace, tool) pairing on this machine.\n *\n * `lastVerifiedAt`/`lastEventAt` are populated by `monitor doctor` in a\n * later stage; they're optional here and never written by this module.\n *\n * SECURITY: this type is serialized verbatim to `registry.json` and to\n * stdout by `monitor list --json`. NEVER add a secret field (API key,\n * token, etc.) here — secrets live only in the 0600 per-workspace\n * `.olakai/monitor-<tool>.json` config, never in the registry. Later\n * stages (doctor/repair) must uphold this invariant.\n */\nexport interface RegistryEntry {\n /** Absolute workspace root (the dir containing `.olakai/`). */\n path: string;\n tool: ToolId;\n /** claude-code hooks are per-workspace; codex/cursor hooks are global. */\n scope: RegistryScope;\n agentId: string;\n agentName: string;\n /** Backend AgentSource string (CLAUDE_CODE | CODEX | CURSOR | GEMINI_CLI | ANTIGRAVITY). */\n source: string;\n /** Absolute path to `<root>/.olakai/monitor-<tool>.json`. */\n configPath: string;\n /** Absolute path to the tool's hooks file (settings.json / config.toml / hooks.json). */\n hooksPath: string;\n monitoringEndpoint: string;\n createdAt: string;\n /** Set by `monitor doctor` (S2). */\n lastVerifiedAt?: string;\n /** Set by `monitor doctor` (S2). */\n lastEventAt?: string;\n}\n\nexport interface Registry {\n version: typeof REGISTRY_VERSION;\n workspaces: RegistryEntry[];\n}\n\n/** Per-tool scope + hooks-path descriptor. Single source of truth for\n * deriving scope/hooksPath from a tool id + workspace root, used by both\n * the install wire-in and reconcile's adoption path. */\ninterface ToolRegistryDescriptor {\n scope: RegistryScope;\n /** Resolve the hooks file path for this tool given a workspace root. */\n hooksPath(projectRoot: string, homeDir: string): string;\n source: string;\n}\n\nconst TOOL_DESCRIPTORS: Record<ToolId, ToolRegistryDescriptor> = {\n \"claude-code\": {\n scope: \"workspace\",\n hooksPath: (projectRoot) =>\n path.join(projectRoot, \".claude\", \"settings.json\"),\n source: \"CLAUDE_CODE\",\n },\n codex: {\n scope: \"global\",\n hooksPath: (_projectRoot, homeDir) =>\n path.join(homeDir, \".codex\", \"config.toml\"),\n source: \"CODEX\",\n },\n cursor: {\n scope: \"global\",\n hooksPath: (_projectRoot, homeDir) =>\n path.join(homeDir, \".cursor\", \"hooks.json\"),\n source: \"CURSOR\",\n },\n \"gemini-cli\": {\n scope: \"global\",\n hooksPath: (_projectRoot, homeDir) =>\n path.join(homeDir, \".gemini\", \"settings.json\"),\n source: \"GEMINI_CLI\",\n },\n antigravity: {\n scope: \"global\",\n hooksPath: (_projectRoot, homeDir) =>\n path.join(homeDir, \".gemini\", \"config\", \"hooks.json\"),\n source: \"ANTIGRAVITY\",\n },\n};\n\n/**\n * Resolve scope + hooksPath for a tool. Exposed so the install wire-in\n * derives these the same way reconcile does (single source of truth).\n */\nexport function getToolScope(toolId: ToolId): RegistryScope {\n return TOOL_DESCRIPTORS[toolId].scope;\n}\n\nexport function getToolHooksPath(\n toolId: ToolId,\n projectRoot: string,\n homeDir: string = os.homedir(),\n): string {\n return TOOL_DESCRIPTORS[toolId].hooksPath(projectRoot, homeDir);\n}\n\n/**\n * Map a tool id to its backend `AgentSource` enum string\n * (`claude-code` → `CLAUDE_CODE`, `codex` → `CODEX`, `cursor` → `CURSOR`).\n * Single source of truth so `agents mine --source <tool>` and the install\n * wire-in agree on the same enum value.\n */\nexport function getToolSource(toolId: ToolId): string {\n return TOOL_DESCRIPTORS[toolId].source;\n}\n\nfunction getOlakaiHomeDir(homeDir: string): string {\n return path.join(homeDir, OLAKAI_HOME_DIRNAME);\n}\n\n/**\n * Absolute path to the machine registry file. `homeDir` override exists\n * for tests so they never touch the real `~/.olakai`.\n */\nexport function getRegistryPath(homeDir: string = os.homedir()): string {\n return path.join(getOlakaiHomeDir(homeDir), REGISTRY_FILENAME);\n}\n\nfunction emptyRegistry(): Registry {\n return { version: REGISTRY_VERSION, workspaces: [] };\n}\n\nfunction isRegistryEntry(value: unknown): value is RegistryEntry {\n if (typeof value !== \"object\" || value === null) return false;\n const e = value as Record<string, unknown>;\n return (\n typeof e.path === \"string\" &&\n typeof e.tool === \"string\" &&\n (TOOL_IDS as readonly string[]).includes(e.tool) &&\n (e.scope === \"workspace\" || e.scope === \"global\") &&\n typeof e.agentId === \"string\" &&\n typeof e.agentName === \"string\" &&\n typeof e.source === \"string\" &&\n typeof e.configPath === \"string\" &&\n typeof e.hooksPath === \"string\" &&\n typeof e.monitoringEndpoint === \"string\" &&\n typeof e.createdAt === \"string\"\n );\n}\n\n/**\n * Read the registry. Tolerant of a missing file, unreadable file, or\n * malformed JSON — any of those yield an empty registry. NEVER throws.\n * Individual malformed entries are dropped so one bad row can't poison\n * the whole list.\n */\nexport function readRegistry(homeDir: string = os.homedir()): Registry {\n const filePath = getRegistryPath(homeDir);\n try {\n if (!fs.existsSync(filePath)) return emptyRegistry();\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as unknown;\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !Array.isArray((parsed as { workspaces?: unknown }).workspaces)\n ) {\n return emptyRegistry();\n }\n const workspaces = (parsed as { workspaces: unknown[] }).workspaces.filter(\n isRegistryEntry,\n );\n return { version: REGISTRY_VERSION, workspaces };\n } catch {\n // Missing/corrupt/unreadable → empty. Never throw.\n return emptyRegistry();\n }\n}\n\n/**\n * Atomically persist the registry. Ensures `~/.olakai` exists, writes to\n * a temp file then renames over the target (so a crash mid-write can't\n * truncate the registry), and chmods 0600. Throws on hard FS failures —\n * callers in init/disable wrap this in try/catch to keep it best-effort.\n */\nexport function writeRegistry(\n registry: Registry,\n homeDir: string = os.homedir(),\n): void {\n const dir = getOlakaiHomeDir(homeDir);\n const filePath = getRegistryPath(homeDir);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n const body = JSON.stringify(registry, null, 2) + \"\\n\";\n // Unique temp name (pid + time) so concurrent writers don't collide on\n // the temp path before the atomic rename.\n const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;\n try {\n fs.writeFileSync(tmpPath, body, { encoding: \"utf-8\", mode: 0o600 });\n fs.renameSync(tmpPath, filePath);\n } catch (err) {\n // Best-effort cleanup of the temp file so a failed write doesn't\n // litter ~/.olakai.\n try {\n if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);\n } catch {\n // ignore cleanup failure\n }\n throw err;\n }\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // chmod failures are non-fatal — Windows / unusual filesystems.\n }\n}\n\nfunction sameKey(a: RegistryEntry, path_: string, tool: ToolId): boolean {\n return a.path === path_ && a.tool === tool;\n}\n\n/**\n * Insert or replace the entry keyed by (path, tool). Returns the updated\n * registry (also persisted). Throws only if the underlying write fails —\n * callers keep this best-effort.\n */\nexport function upsertEntry(\n entry: RegistryEntry,\n homeDir: string = os.homedir(),\n): Registry {\n const registry = readRegistry(homeDir);\n const idx = registry.workspaces.findIndex((e) =>\n sameKey(e, entry.path, entry.tool),\n );\n if (idx >= 0) {\n registry.workspaces[idx] = entry;\n } else {\n registry.workspaces.push(entry);\n }\n writeRegistry(registry, homeDir);\n return registry;\n}\n\n/**\n * Remove the entry keyed by (path, tool). No-op (still persists) when the\n * entry is absent. Returns the updated registry.\n */\nexport function removeEntry(\n entryPath: string,\n tool: ToolId,\n homeDir: string = os.homedir(),\n): Registry {\n const registry = readRegistry(homeDir);\n const next = registry.workspaces.filter(\n (e) => !sameKey(e, entryPath, tool),\n );\n if (next.length !== registry.workspaces.length) {\n registry.workspaces = next;\n writeRegistry(registry, homeDir);\n }\n return registry;\n}\n\n/** On-disk per-tool config shape shared by all three plugins. */\ninterface OnDiskMonitorConfig {\n agentId: string;\n agentName: string;\n source: string;\n monitoringEndpoint: string;\n createdAt?: string;\n}\n\nfunction readOnDiskConfig(filePath: string): OnDiskMonitorConfig | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<OnDiskMonitorConfig>;\n if (\n typeof parsed?.agentId !== \"string\" ||\n typeof parsed?.agentName !== \"string\" ||\n typeof parsed?.source !== \"string\" ||\n typeof parsed?.monitoringEndpoint !== \"string\"\n ) {\n return null;\n }\n return {\n agentId: parsed.agentId,\n agentName: parsed.agentName,\n source: parsed.source,\n monitoringEndpoint: parsed.monitoringEndpoint,\n createdAt:\n typeof parsed.createdAt === \"string\" ? parsed.createdAt : undefined,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Adoption path for installs that predate the registry. Walks up from\n * `cwd` via `findConfiguredWorkspace` to the configured workspace root,\n * then for every tool whose `.olakai/monitor-<tool>.json` exists on disk\n * but has no registry entry, reads that config and backfills an entry.\n *\n * Returns the registry entries for the resolved workspace (after any\n * backfill). Returns an empty array when `cwd` is not inside a configured\n * workspace. Never throws — registry write failures are swallowed so a\n * read-only `monitor list` still renders what it can.\n */\nexport function reconcileCurrentWorkspace(\n cwd: string,\n homeDir: string = os.homedir(),\n): RegistryEntry[] {\n const root = findConfiguredWorkspace(cwd, TOOL_IDS);\n if (!root) return [];\n\n let registry = readRegistry(homeDir);\n let mutated = false;\n\n for (const toolId of TOOL_IDS) {\n const configPath = getMonitorConfigPath(root, toolId);\n const alreadyRegistered = registry.workspaces.some((e) =>\n sameKey(e, root, toolId),\n );\n if (alreadyRegistered) continue;\n\n const config = readOnDiskConfig(configPath);\n if (!config) continue;\n\n const descriptor = TOOL_DESCRIPTORS[toolId];\n const entry: RegistryEntry = {\n path: root,\n tool: toolId,\n scope: descriptor.scope,\n agentId: config.agentId,\n agentName: config.agentName,\n source: config.source,\n configPath,\n hooksPath: descriptor.hooksPath(root, homeDir),\n monitoringEndpoint: config.monitoringEndpoint,\n createdAt: config.createdAt ?? new Date().toISOString(),\n };\n registry.workspaces.push(entry);\n mutated = true;\n }\n\n // Legacy Claude Code config (`.claude/olakai-monitor.json`) — adopt it\n // for claude-code if the new-path config didn't already cover that key.\n const legacyClaudePath = getLegacyClaudeMonitorConfigPath(root);\n const claudeRegistered = registry.workspaces.some((e) =>\n sameKey(e, root, \"claude-code\"),\n );\n if (!claudeRegistered) {\n const legacyConfig = readOnDiskConfig(legacyClaudePath);\n if (legacyConfig) {\n const descriptor = TOOL_DESCRIPTORS[\"claude-code\"];\n registry.workspaces.push({\n path: root,\n tool: \"claude-code\",\n scope: descriptor.scope,\n agentId: legacyConfig.agentId,\n agentName: legacyConfig.agentName,\n source: legacyConfig.source,\n configPath: getMonitorConfigPath(root, \"claude-code\"),\n hooksPath: descriptor.hooksPath(root, homeDir),\n monitoringEndpoint: legacyConfig.monitoringEndpoint,\n createdAt: legacyConfig.createdAt ?? new Date().toISOString(),\n });\n mutated = true;\n }\n }\n\n let persisted = true;\n if (mutated) {\n try {\n writeRegistry(registry, homeDir);\n } catch {\n // Backfill is best-effort; a read-only `monitor list` must still\n // render the adopted entries even if we couldn't persist them.\n persisted = false;\n }\n }\n\n // On success (or when nothing changed) re-read for the canonical\n // persisted view. On write failure, keep the in-memory registry — it\n // holds the adopted entries — so the caller still sees them this run;\n // the next reconcile retries the write.\n if (persisted) {\n registry = readRegistry(homeDir);\n }\n return registry.workspaces.filter((e) => e.path === root);\n}\n\n/**\n * Format the registry as a human-readable, grouped-by-tool report.\n * Pure (no I/O beyond the injected `configExists` probe) so it's unit\n * testable. Flags drift inline: an entry whose config file no longer\n * exists on disk is suffixed with `(config missing)`.\n */\nexport function formatRegistryTable(\n registry: Registry,\n configExists: (configPath: string) => boolean = (p) => fs.existsSync(p),\n): string {\n if (registry.workspaces.length === 0) {\n return \"No monitored workspaces recorded on this machine.\\nRun 'olakai monitor init --tool <tool>' to start monitoring.\";\n }\n\n const byTool = new Map<ToolId, RegistryEntry[]>();\n for (const entry of registry.workspaces) {\n const list = byTool.get(entry.tool) ?? [];\n list.push(entry);\n byTool.set(entry.tool, list);\n }\n\n const lines: string[] = [];\n // Iterate in canonical tool order for stable output.\n for (const toolId of TOOL_IDS) {\n const entries = byTool.get(toolId);\n if (!entries || entries.length === 0) continue;\n lines.push(`${toolId} (${entries.length}):`);\n for (const entry of entries) {\n const missing = configExists(entry.configPath)\n ? \"\"\n : \" (config missing)\";\n const verified = entry.lastVerifiedAt\n ? ` · verified ${entry.lastVerifiedAt}`\n : \"\";\n lines.push(\n ` • ${entry.agentName} [${entry.scope}] — ${entry.path}${verified}${missing}`,\n );\n }\n lines.push(\"\");\n }\n return lines.join(\"\\n\").trimEnd();\n}\n","import * as fs from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport {\n registerPlugin,\n type HookResult,\n type InstallOpts,\n type InstallResult,\n type StatusReport,\n type ToolMonitorPlugin,\n type UninstallOpts,\n} from \"../../plugin.js\";\nimport {\n loadSessionState,\n saveSessionState,\n setDebugLogger,\n shouldReportTurn,\n} from \"../../../commands/monitor-state.js\";\nimport { installClaudeCode, uninstallClaudeCode } from \"./install.js\";\nimport { getClaudeCodeStatus } from \"./status.js\";\nimport {\n buildClaudeCodePayload,\n extractFromTranscript,\n type ClaudeHookEvent,\n} from \"./hook.js\";\nimport { loadClaudeCodeConfig } from \"./config.js\";\nimport {\n findConfiguredWorkspace,\n getLegacyClaudeMonitorConfigPath,\n getMonitorConfigPath,\n} from \"../../paths.js\";\n\nconst TOOL_ID = \"claude-code\" as const;\nconst SUPPORTED_HOOK_EVENTS = new Set([\"stop\", \"subagent-stop\"]);\n\n/**\n * Debug logger. Mirrors the original `monitor.ts` behavior: writes\n * only when OLAKAI_MONITOR_DEBUG=1, and never throws.\n */\nfunction debugLog(label: string, data: unknown): void {\n if (process.env.OLAKAI_MONITOR_DEBUG !== \"1\") return;\n try {\n const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;\n const line = `[${new Date().toISOString()}] ${label}: ${\n typeof data === \"string\" ? data : JSON.stringify(data, null, 2)\n }\\n`;\n fs.appendFileSync(logPath, line, \"utf-8\");\n } catch {\n // Ignore debug-log failures\n }\n}\n\n/**\n * Resolve the configured project root from a Claude Code hook payload,\n * falling back to `process.cwd()` when the payload doesn't carry a\n * usable `cwd` field. Returns null when no configured workspace is in\n * the ancestry, letting the hook dispatcher silent-exit.\n *\n * Background (INV-002): Claude Code sometimes invokes Stop /\n * SubagentStop hooks from a CWD that isn't under the workspace (e.g.\n * `/tmp`). We seed the ancestor walk with `payload.cwd` when present,\n * and look for an actual monitor config file (new or legacy path) so\n * stray `.claude/` dirs in subrepos don't trap the search.\n */\nexport function resolveProjectRootFromPayload(\n eventData: ClaudeHookEvent,\n fallbackCwd: string,\n): string | null {\n const payloadCwd =\n typeof eventData.cwd === \"string\" && eventData.cwd.trim()\n ? eventData.cwd\n : fallbackCwd;\n return findConfiguredWorkspace(payloadCwd, [TOOL_ID]);\n}\n\nconst claudeCodePlugin: ToolMonitorPlugin = {\n id: TOOL_ID,\n displayName: \"Claude Code\",\n\n install(opts: InstallOpts): Promise<InstallResult> {\n return installClaudeCode(opts);\n },\n\n uninstall(opts: UninstallOpts): Promise<void> {\n return uninstallClaudeCode(opts);\n },\n\n status(opts): Promise<StatusReport> {\n return getClaudeCodeStatus(opts);\n },\n\n async handleHook(\n eventName,\n payloadJson,\n ): Promise<HookResult | null> {\n setDebugLogger(debugLog);\n\n const event = eventName.trim();\n if (!SUPPORTED_HOOK_EVENTS.has(event)) {\n debugLog(\"hook-unknown-event\", event);\n return null;\n }\n\n const eventData = (payloadJson ?? {}) as ClaudeHookEvent;\n debugLog(\"event-parsed\", { event, eventData });\n\n const projectRoot = resolveProjectRootFromPayload(\n eventData,\n process.cwd(),\n );\n if (!projectRoot) {\n debugLog(\"config-not-found\", {\n startDir:\n typeof eventData.cwd === \"string\" && eventData.cwd.trim()\n ? eventData.cwd\n : process.cwd(),\n });\n return null;\n }\n\n const config = loadClaudeCodeConfig(projectRoot, () => {\n // Silent in the hook path — stderr would interleave with Claude\n // Code's own output. Migrations still happen; just no notice.\n });\n if (!config) {\n debugLog(\"config-load-failed\", { projectRoot });\n return null;\n }\n\n const payload = buildClaudeCodePayload(event, eventData, config);\n if (!payload) return null;\n\n debugLog(\"payload-built\", payload);\n\n // Per-session turn dedup (INV-004). Re-extract so we key on the\n // exact timestamp that drove the payload. Reading the transcript\n // again is cheap (<10ms in practice) and avoids threading parser\n // state through the plugin contract.\n const sessionId =\n (typeof eventData.session_id === \"string\" && eventData.session_id) ||\n undefined;\n const extracted = extractFromTranscript(\n eventData.transcript_path,\n debugLog,\n );\n const userTurnTimestamp = extracted.userTurnTimestamp;\n\n // Empty-parse silent-exit guard.\n //\n // If the transcript parse yielded no prompt, no response, AND no\n // turns, we have nothing to monitor — emitting a noop event would\n // just add noise to the user's activity feed and inflate counts.\n // This protects against unexpected/unparseable payloads being\n // silently emitted as empty events (e.g., a Cursor stop event\n // leaking onto the claude-code path with a transcript file the\n // parser doesn't recognize).\n //\n // All three conditions are required because token counts of 0 are\n // legitimate on fully-cached turns — only the combination of empty\n // prompt + empty response + zero turns reliably means \"the parser\n // saw nothing actionable\".\n if (\n extracted.prompt.trim() === \"\" &&\n extracted.response.trim() === \"\" &&\n extracted.numTurns === 0\n ) {\n debugLog(\"empty-parse-skip\", {\n event,\n sessionId,\n transcriptPath: eventData.transcript_path,\n });\n return null;\n }\n\n if (sessionId) {\n const existingState = loadSessionState(sessionId);\n if (!shouldReportTurn(existingState, userTurnTimestamp)) {\n debugLog(\"turn-dedup-skip\", { sessionId, userTurnTimestamp });\n return null;\n }\n }\n\n // Save dedup state before the dispatcher POSTs: a POST failure must\n // not retry the same turn (INV-004). Saving here — not after the\n // network call — guarantees retry storms during outages cannot\n // re-fire the same payload.\n if (sessionId && userTurnTimestamp) {\n saveSessionState(sessionId, {\n lastUserTimestamp: userTurnTimestamp,\n lastReportedAt: new Date().toISOString(),\n numTurnsAtLastReport: extracted.numTurns,\n });\n debugLog(\"state-saved\", {\n sessionId,\n userTurnTimestamp,\n numTurns: extracted.numTurns,\n });\n }\n\n return {\n payload,\n transport: {\n endpoint: config.monitoringEndpoint,\n apiKey: config.apiKey,\n projectRoot,\n },\n };\n },\n\n async detectInstalled(opts): Promise<boolean> {\n // Two independent signals — either is enough to surface\n // \"claude-code\" in the interactive `init` selector:\n // 1. The user already ran `olakai monitor init --tool claude-code`\n // here (workspace-local monitor config exists, new or legacy\n // path), OR\n // 2. The Claude Code CLI itself is installed on this host\n // (`claude` binary on PATH).\n //\n // We deliberately do NOT check for `~/.claude/` directory presence:\n // that directory is shared with Claude Desktop, so it would\n // false-positive for users who run the desktop app but never the\n // CLI. The `claude` binary on PATH is the precise signal for the\n // Claude Code CLI.\n //\n // Matches the structure of `codex.detectInstalled` so the three\n // plugins behave consistently for first-run users (Xavier session,\n // 2026-05-14 — first-run users were seeing \"No coding agents\n // detected\" even when Claude Code was installed because this\n // function only checked for an Olakai marker file).\n const projectRoot = opts?.projectRoot ?? process.cwd();\n try {\n if (fs.existsSync(getMonitorConfigPath(projectRoot, TOOL_ID))) {\n return true;\n }\n if (fs.existsSync(getLegacyClaudeMonitorConfigPath(projectRoot))) {\n return true;\n }\n } catch {\n // Fall through to the binary probe.\n }\n return detectClaudeBinaryOnPath();\n },\n};\n\n/**\n * Synchronous `which claude` / `where claude` probe. Cheap (<10ms in\n * practice) and any failure is treated as \"not installed\". Modeled on\n * `detectCodexBinaryOnPath` in `../codex/index.ts`.\n */\nfunction detectClaudeBinaryOnPath(): boolean {\n try {\n const probe = spawnSync(\n process.platform === \"win32\" ? \"where\" : \"which\",\n [\"claude\"],\n {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n },\n );\n if (probe.status === 0 && probe.stdout && probe.stdout.toString().trim()) {\n return true;\n }\n } catch {\n // Treat any failure as \"not detectable\"\n }\n return false;\n}\n\nregisterPlugin(claudeCodePlugin);\n\nexport default claudeCodePlugin;\nexport {\n buildClaudeCodePayload,\n type ClaudeHookEvent,\n} from \"./hook.js\";\nexport { mergeHooksSettings, type HookMatcherEntry } from \"./settings.js\";\nexport { type MonitorConfig } from \"./config.js\";\n","/**\n * Per-session dedup state for the Claude Code Stop / SubagentStop hook.\n *\n * Problem (INV-004): Claude Code occasionally fires the Stop hook twice\n * for a single user turn, a few seconds apart. Each firing calls the\n * CLI, which parses the transcript and emits the LATEST user+assistant\n * pair — so two firings on one turn produce two PromptRequests with an\n * identical `prompt` and a slightly different `response`.\n *\n * Fix: keep a small sidecar file per session id under\n * `~/.olakai/monitor-state/<sessionId>.json`, keyed by the current\n * turn's user-message timestamp. If the current Stop firing sees the\n * same user-turn timestamp as the last one we reported, skip the POST.\n *\n * The module is pure w.r.t. process state — callers pass `sessionId`\n * and (optionally) a `homeDir` override for testability. All filesystem\n * errors are swallowed and surfaced via `debugLog` callbacks supplied\n * by the caller; the hook MUST NOT break Claude Code.\n */\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\n/**\n * State persisted per session id. `numTurnsAtLastReport` is informational\n * only — dedup decisions use `lastUserTimestamp`.\n */\nexport interface SessionMonitorState {\n lastUserTimestamp: string;\n lastReportedAt: string;\n numTurnsAtLastReport: number;\n}\n\nconst STATE_DIR_SEGMENTS = [\".olakai\", \"monitor-state\"];\n\n/**\n * Resolve the directory that holds per-session state files. Exposed\n * indirectly through the `homeDir` parameter on the public API so tests\n * can point it at a tmp dir.\n */\nfunction getStateDir(homeDir: string): string {\n return path.join(homeDir, ...STATE_DIR_SEGMENTS);\n}\n\nfunction getStateFile(sessionId: string, homeDir: string): string {\n return path.join(getStateDir(homeDir), `${sessionId}.json`);\n}\n\n/**\n * Optional debug logger. Kept as a module-level mutable hook so the\n * caller (monitor.ts) can wire in its own `debugLog` without this file\n * importing it (avoiding circular deps).\n */\ntype DebugLogger = (label: string, data: unknown) => void;\nlet debugLogger: DebugLogger | null = null;\n\nexport function setDebugLogger(logger: DebugLogger | null): void {\n debugLogger = logger;\n}\n\nfunction log(label: string, data: unknown): void {\n if (debugLogger) {\n try {\n debugLogger(label, data);\n } catch {\n // Debug logging must never break the hook.\n }\n }\n}\n\n/**\n * Load persisted state for `sessionId`. Returns null when the file is\n * absent, unreadable, or contains malformed JSON. Never throws.\n */\nexport function loadSessionState(\n sessionId: string,\n homeDir: string = os.homedir(),\n): SessionMonitorState | null {\n if (!sessionId) return null;\n const filePath = getStateFile(sessionId, homeDir);\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<SessionMonitorState>;\n if (\n typeof parsed?.lastUserTimestamp !== \"string\" ||\n typeof parsed?.lastReportedAt !== \"string\" ||\n typeof parsed?.numTurnsAtLastReport !== \"number\"\n ) {\n // Malformed shape — treat as missing so we don't skip a real\n // report based on garbage state.\n return null;\n }\n return {\n lastUserTimestamp: parsed.lastUserTimestamp,\n lastReportedAt: parsed.lastReportedAt,\n numTurnsAtLastReport: parsed.numTurnsAtLastReport,\n };\n } catch (err) {\n log(\"state-load-failed\", {\n sessionId,\n error: (err as Error).message,\n });\n return null;\n }\n}\n\n/**\n * Persist state for `sessionId`. Creates the state directory on first\n * write. Filesystem errors are logged via `debugLog` and swallowed.\n */\nexport function saveSessionState(\n sessionId: string,\n state: SessionMonitorState,\n homeDir: string = os.homedir(),\n): void {\n if (!sessionId) return;\n const dir = getStateDir(homeDir);\n const filePath = getStateFile(sessionId, homeDir);\n try {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + \"\\n\", \"utf-8\");\n } catch (err) {\n log(\"state-save-failed\", {\n sessionId,\n error: (err as Error).message,\n });\n }\n}\n\n/**\n * Decide whether to report the current turn given the last-reported\n * state and the current turn's user timestamp.\n *\n * Rules:\n * - currentUserTimestamp missing/empty → report (can't dedup).\n * - no existing state → report (first turn for this session).\n * - currentUserTimestamp === existing.lastUserTimestamp → SKIP.\n * - otherwise → report (a new user turn).\n */\nexport function shouldReportTurn(\n existing: SessionMonitorState | null,\n currentUserTimestamp: string | undefined,\n): boolean {\n if (!currentUserTimestamp) return true;\n if (!existing) return true;\n return existing.lastUserTimestamp !== currentUserTimestamp;\n}\n","import * as fs from \"node:fs\";\nimport { type Agent } from \"../../../lib/api.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport type {\n InstallOpts,\n InstallResult,\n UninstallOpts,\n} from \"../../plugin.js\";\nimport { provisionSelfMonitorAgent } from \"../../self-monitor-provision.js\";\nimport {\n CLAUDE_DIR,\n OLAKAI_HOOK_MARKER,\n SETTINGS_FILE,\n getClaudeDir,\n getSettingsPath,\n mergeHooksSettings,\n readJsonFile,\n writeJsonFile,\n type ClaudeSettings,\n type HookMatcherEntry,\n} from \"./settings.js\";\nimport {\n deleteClaudeCodeConfig,\n getClaudeCodeConfigPath,\n writeClaudeCodeConfig,\n type MonitorConfig,\n} from \"./config.js\";\nimport { OLAKAI_DIR } from \"../../paths.js\";\nimport path from \"node:path\";\n\nconst CLAUDE_CODE_SOURCE = \"claude-code\";\nconst CLAUDE_CODE_AGENT_SOURCE = \"CLAUDE_CODE\";\nconst CLAUDE_CODE_AGENT_CATEGORY = \"CODING\";\n\nexport async function installClaudeCode(\n opts: InstallOpts,\n): Promise<InstallResult> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n\n const token = getValidToken();\n if (!token) {\n console.error(\"Not logged in. Run 'olakai login' first.\");\n process.exit(1);\n }\n\n console.log(\"Setting up Claude Code monitoring for this workspace...\\n\");\n\n // Provision (or reuse) the self-monitor agent for this user +\n // workspace. The helper handles:\n // - default name = `<workspace>-<email-localpart>` (multi-user\n // shared-monorepo collision-safe)\n // - cross-workspace reuse via /agents/mine + regenerate-api-key\n // - 409 name-collision retry with a user-provided alternative\n // - 403 / role-aware error messages bubble up from createAgent\n const agent: Agent = await provisionSelfMonitorAgent({\n projectRoot,\n source: CLAUDE_CODE_AGENT_SOURCE,\n displayName: \"Claude Code\",\n category: CLAUDE_CODE_AGENT_CATEGORY,\n });\n\n const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;\n\n const apiKey = agent.apiKey?.key;\n if (!apiKey) {\n // Should not happen: both the create and the regenerate paths\n // return the plaintext key in the response. Surface as a clear\n // error rather than silently writing a bad config.\n console.error(\n \"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'.\",\n );\n process.exit(1);\n }\n\n const claudeDir = getClaudeDir(projectRoot);\n if (!fs.existsSync(claudeDir)) {\n fs.mkdirSync(claudeDir, { recursive: true });\n }\n\n const settingsPath = getSettingsPath(projectRoot);\n const existingSettings = readJsonFile<ClaudeSettings>(settingsPath) ?? {};\n const mergedHooks = mergeHooksSettings(existingSettings.hooks);\n const updatedSettings: ClaudeSettings = {\n ...existingSettings,\n hooks: mergedHooks,\n };\n writeJsonFile(settingsPath, updatedSettings);\n\n const monitorConfig: MonitorConfig = {\n agentId: agent.id,\n apiKey,\n agentName: agent.name,\n source: CLAUDE_CODE_SOURCE,\n createdAt: new Date().toISOString(),\n monitoringEndpoint,\n };\n writeClaudeCodeConfig(projectRoot, monitorConfig);\n\n const configPath = getClaudeCodeConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n\n console.log(\"\");\n console.log(`✓ Agent \"${agent.name}\" configured (ID: ${agent.id})`);\n if (agent.apiKey?.key) {\n console.log(\"✓ API key generated\");\n }\n console.log(\n `✓ Claude Code hooks configured in ${CLAUDE_DIR}/${SETTINGS_FILE}`,\n );\n console.log(`✓ Monitor config saved to ${configRel}`);\n console.log(\"\");\n console.log(\n \"Congrats! Monitoring is now active. Claude Code will report activity to Olakai\",\n );\n console.log(`on each turn.`);\n console.log(\"\");\n console.log(\n `⚠ Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key)`,\n );\n console.log(\"\");\n console.log(\"To check status: olakai monitor status --tool claude-code\");\n console.log(\"To disable: olakai monitor disable --tool claude-code\");\n\n return {\n agentId: agent.id,\n agentName: agent.name,\n source: CLAUDE_CODE_SOURCE,\n monitoringEndpoint,\n };\n}\n\nexport async function uninstallClaudeCode(opts: UninstallOpts): Promise<void> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n\n const settingsPath = getSettingsPath(projectRoot);\n const settings = readJsonFile<ClaudeSettings>(settingsPath);\n\n if (settings?.hooks) {\n const cleanedHooks: Record<string, HookMatcherEntry[]> = {};\n for (const [event, entries] of Object.entries(settings.hooks)) {\n const filtered = entries.filter(\n (e) => !e.hooks.some((h) => h.command.includes(OLAKAI_HOOK_MARKER)),\n );\n if (filtered.length > 0) {\n cleanedHooks[event] = filtered;\n }\n }\n if (Object.keys(cleanedHooks).length > 0) {\n settings.hooks = cleanedHooks;\n } else {\n delete settings.hooks;\n }\n writeJsonFile(settingsPath, settings);\n console.log(`✓ Olakai hooks removed from ${CLAUDE_DIR}/${SETTINGS_FILE}`);\n } else {\n console.log(\"No hooks found in settings.json.\");\n }\n\n if (!opts.keepConfig) {\n const configPath = getClaudeCodeConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n if (deleteClaudeCodeConfig(projectRoot)) {\n console.log(`✓ Monitor config removed (${configRel})`);\n }\n } else {\n const configPath = getClaudeCodeConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n console.log(`Monitor config retained at ${configRel}`);\n }\n\n console.log(\"\");\n console.log(\n \"Monitoring disabled. Run 'olakai monitor init --tool claude-code' to re-enable.\",\n );\n}\n","/**\n * Shared self-monitor agent provisioning for the coding-agent\n * plugins (claude-code, codex, cursor).\n *\n * Replaces the old \"Create new / use existing?\" two-branch prompt with\n * a deterministic flow:\n *\n * 1. Fetch the caller's identity from `/api/user/me`.\n * 2. Compose a default agent name that scopes to the workspace AND\n * the caller's email local-part (`<workspace>-<localpart>`). This\n * avoids the shared-monorepo collision where two devs on the same\n * `~/team-repo` would both default to `team-repo` and the second\n * to run would 409 on the existing-unique constraint.\n * 3. Try `GET /api/config/agents/mine?source=X&name=Y` to detect an\n * agent the user has already provisioned (cross-workspace\n * recovery, or same workspace after a local config wipe).\n * 4. If found, prompt the user to rotate the API key — they'll get\n * a fresh plaintext key for the local config, and any other\n * workspace using the old key starts 401-ing until it re-inits.\n * 5. If not found, accept a user-overridable name and create the\n * agent. Backend enforces the strict whitelist + per-user rate\n * limit + tier ceiling.\n *\n * The \"use an existing one (pick from list)\" branch is gone — the\n * backend doesn't let non-ADMIN users list arbitrary account agents\n * anyway, and self-monitor agents are intrinsically owned per-user.\n */\n\nimport path from \"node:path\";\nimport {\n createAgent,\n getCurrentUser,\n listMyAgents,\n regenerateAgentApiKey,\n type Agent,\n} from \"../lib/api.js\";\nimport { getGitConfigEmail } from \"../lib/git.js\";\nimport { promptUser } from \"./prompt.js\";\n\nexport interface ProvisionOptions {\n projectRoot: string;\n /** Backend AgentSource enum value. */\n source: \"CLAUDE_CODE\" | \"CODEX\" | \"CURSOR\" | \"GEMINI_CLI\" | \"ANTIGRAVITY\";\n /** Human-readable name for prompts and descriptions (\"Claude Code\"). */\n displayName: string;\n /** Category sent on create (\"CODING\" for all current coding agents). */\n category: string;\n}\n\nexport async function provisionSelfMonitorAgent(\n opts: ProvisionOptions,\n): Promise<Agent> {\n // One API call for the whole flow — used for both the default name\n // template and (in future) any role-aware messaging.\n const me = await getCurrentUser();\n const localPart = (me.email.split(\"@\")[0] ?? \"user\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n const workspaceName = path.basename(opts.projectRoot).toLowerCase();\n const defaultName = `${workspaceName}-${localPart}`;\n\n // Check for an existing self-monitor agent owned by this user.\n // Filtering by name on the server side keeps the round-trip cheap\n // even for users with many agents.\n let existing: Awaited<ReturnType<typeof listMyAgents>> = [];\n try {\n existing = await listMyAgents({\n source: opts.source,\n name: defaultName,\n });\n } catch (err) {\n // Old backend (pre-self-monitor) — falls through to the create\n // path, which surfaces the appropriate 403 message via\n // `createAgent`'s role-aware error mapping.\n const message = err instanceof Error ? err.message : String(err);\n if (!message.includes(\"Failed to list your agents\")) {\n // Re-throw unexpected errors; only the \"backend doesn't know\n // this endpoint yet\" case should fall through silently.\n }\n }\n\n if (existing.length > 0) {\n const found = existing[0];\n console.log(\n `\\nFound your existing ${opts.displayName} agent \"${found.name}\".`,\n );\n console.log(\n \"Re-using it requires rotating its API key. Any other workspace currently using this agent will start failing on the next monitor request until it re-runs 'olakai monitor init'.\",\n );\n const ack = await promptUser(\"Rotate the API key and reuse? [y/N]: \");\n if (ack.trim().toLowerCase() !== \"y\") {\n console.error(\n \"Cancelled. To use this workspace without affecting the other workspace, run 'olakai monitor init' again and pick a different agent name when prompted.\",\n );\n process.exit(1);\n }\n const rotated = await regenerateAgentApiKey(found.id);\n return {\n id: found.id,\n name: found.name,\n description: found.description,\n role: found.role,\n // `source` on the listMyAgents response is the AgentSource enum\n // string; the Agent type narrows it more loosely. Cast through\n // the shared union.\n source: found.source as Agent[\"source\"],\n apiKey: {\n id: rotated.id,\n key: rotated.key,\n keyMasked: rotated.keyMasked,\n isActive: rotated.isActive,\n },\n workflowId: null,\n category: opts.category,\n };\n }\n\n // No existing self-monitor agent — create one. The user can override\n // the default name (e.g. on a 409 name-collision retry with a\n // pre-existing admin-created agent of the same name).\n const nameInput = await promptUser(\n `Enter a descriptive name for this agent or accept the recommended name [${defaultName}]: `,\n );\n const agentName = nameInput.trim() || defaultName;\n\n // NOTE: we intentionally do NOT send `category` here. The backend's\n // self-monitor branch (POST /api/config/agents) uses a `.strict()`\n // schema that rejects unknown fields with\n // `self_monitor_admin_field_forbidden`, and `category` is not (and\n // has never been) a column on the Agent table — the admin path\n // silently strips it via Zod's default. `opts.category` stays in\n // `ProvisionOptions` to keep the plugin contract stable in case we\n // ever add a real category column.\n //\n // Pre-seed the developer's VCS identity by sending `vcsEmailHint` if\n // git is configured. The backend uses this to create an\n // `ExternalDeveloperIdentity` row mapping this email → the requesting\n // User, so when GitHub/Bitbucket PR ingest later finds a PR authored\n // under the same email, it auto-links to the same person. Earns its\n // keep when the user's git email differs from their Olakai login\n // (e.g. work email vs SSO email). Returns null silently if git isn't\n // installed or `user.email` isn't set — agent creation continues.\n const vcsEmailHint = getGitConfigEmail() ?? undefined;\n try {\n return await createAgent({\n name: agentName,\n description: `${opts.displayName} local agent for ${agentName}`,\n role: \"WORKER\",\n createApiKey: true,\n source: opts.source,\n ...(vcsEmailHint ? { vcsEmailHint } : {}),\n });\n } catch (err) {\n // On name collision, give the user one retry chance with a new\n // name instead of forcing them through `monitor uninstall` +\n // re-run.\n const message = err instanceof Error ? err.message : String(err);\n if (\n message.toLowerCase().includes(\"already exists\") ||\n message.toLowerCase().includes(\"conflict\")\n ) {\n console.log(\n `\\nAn agent named \"${agentName}\" already exists on this account (created by someone else or an admin).`,\n );\n const retry = await promptUser(\"Try a different name: \");\n if (!retry.trim()) {\n console.error(\"No name provided. Aborting.\");\n process.exit(1);\n }\n return createAgent({\n name: retry.trim(),\n description: `${opts.displayName} local agent for ${retry.trim()}`,\n role: \"WORKER\",\n createApiKey: true,\n source: opts.source,\n ...(vcsEmailHint ? { vcsEmailHint } : {}),\n });\n }\n throw err;\n }\n}\n","import { spawnSync } from \"node:child_process\";\n\n/**\n * Read the user's globally-configured git commit email\n * (`git config --global user.email`). Returns `null` if git isn't\n * installed, the config isn't set, or the value doesn't look like an\n * email.\n *\n * Used by `monitor init` to forward the developer's commit identity as\n * `vcsEmailHint` on agent creation. This pre-seeds an\n * `ExternalDeveloperIdentity` row on the server so the developer's later\n * GitHub/Bitbucket PRs (which use this same email as `authorEmail`) can\n * be auto-linked to their Olakai User — even when their work git email\n * differs from their Olakai login email.\n *\n * Soft-fails on every error path. The agent-create flow must not break\n * just because git isn't installed or the user hasn't run\n * `git config --global user.email \"...\"` yet.\n */\nexport function getGitConfigEmail(): string | null {\n let result: ReturnType<typeof spawnSync>;\n try {\n result = spawnSync(\"git\", [\"config\", \"--global\", \"user.email\"], {\n encoding: \"utf8\",\n // Cap stdout. A normal `user.email` value is well under 320 bytes\n // (RFC 5321 max email length); 64KB leaves headroom for git config\n // wrappers that print a banner without ever truncating real\n // values. spawnSync sets `result.error` (ENOBUFS) on overflow\n // rather than truncating silently — we check it below.\n maxBuffer: 65536,\n // Don't pipe stderr to our process; we treat any error as \"no email\".\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n } catch {\n return null;\n }\n\n // Treat ENOBUFS, signal termination (status === null), and any non-zero\n // exit as \"no email\". The downstream regex would reject a truncated\n // ENOBUFS payload anyway, but failing explicitly here keeps the\n // intent obvious to future readers.\n if (result.error) return null;\n if (result.status !== 0) return null;\n const raw = (result.stdout || \"\").toString().trim();\n if (!raw) return null;\n\n // Pre-flight check so we never POST clearly-invalid garbage that the\n // server would just reject. Approximates Zod's `.email()`:\n // - no whitespace anywhere\n // - exactly one `@`\n // - local part and domain labels can't be empty, start, or end with `.`\n // - TLD is ≥ 2 alphabetic characters (rejects `a@b.c` and trailing dots)\n // This is intentionally not a perfect copy of Zod's regex — the server\n // remains the source of truth and soft-fails if it disagrees. Goal here\n // is just \"don't send obvious junk on the wire.\"\n const EMAIL_RE =\n /^[^\\s@.]+(?:\\.[^\\s@.]+)*@[^\\s@.]+(?:\\.[^\\s@.]+)*\\.[a-zA-Z]{2,}$/;\n if (!EMAIL_RE.test(raw)) return null;\n if (raw.length > 320) return null;\n\n return raw;\n}\n","/**\n * Shared interactive prompt helper. Lives at the monitor module level\n * so plugin install flows don't each maintain their own copy.\n */\nimport * as readline from \"node:readline\";\n\nexport function promptUser(question: string): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\n/**\n * Whether the current process is running interactively (both stdin and\n * stdout are TTYs). Used to decide whether to prompt for missing\n * `--tool` selection or default with a deprecation notice.\n */\nexport function isInteractive(): boolean {\n return Boolean(process.stdin.isTTY && process.stdout.isTTY);\n}\n","/**\n * Claude Code Stop / SubagentStop hook adapter. Splits cleanly into two\n * pieces:\n *\n * - `buildClaudeCodePayload` (pure) — canonical MonitoringPayload\n * given the parsed event and resolved monitor config. Unit-testable\n * without filesystem.\n *\n * - `handleClaudeCodeHook` (effectful) — the plugin's `handleHook`\n * implementation. Resolves the workspace, parses stdin, dedups\n * against per-session state, and emits the payload. Returns the\n * payload (or null) so the dispatcher can do the actual POST.\n *\n * The split exists because the original `monitor.ts` did POST + state\n * save inside the hook handler. To keep the plugin contract clean, the\n * dispatcher (`commands/monitor.ts`) now owns the network and dedup\n * persistence; the plugin produces a payload candidate and lets the\n * dispatcher decide what to do with it.\n */\nimport * as fs from \"node:fs\";\nimport {\n parseTranscript,\n type ExtractedTranscriptData,\n} from \"./transcript.js\";\nimport type { MonitoringPayload } from \"../../plugin.js\";\nimport type { MonitorConfig } from \"./config.js\";\n\n/**\n * Claude Code hook event payloads.\n *\n * The Stop / SubagentStop hooks send ONLY the fields below — the\n * conversation data (prompt, response, tokens, model, etc.) is NOT in\n * the hook JSON. To get that data we read the transcript file at\n * `transcript_path`.\n *\n * The exact field that carries the subagent name is not formally\n * documented, so we accept several candidates (`agent_name`,\n * `agent_type`, `subagent_type`) and fall back to the `tool_input`\n * shape that the Agent tool_use block uses.\n */\nexport interface ClaudeHookEvent {\n session_id?: string;\n transcript_path?: string;\n hook_event_name?: string;\n stop_hook_active?: boolean;\n cwd?: string;\n agent_name?: string;\n agent_type?: string;\n subagent_type?: string;\n tool_input?: {\n subagent_type?: string;\n description?: string;\n };\n /**\n * Authoritative final assistant text from Claude Code's in-memory\n * state at hook-fire time. Preferred over transcript-file parsing\n * for the response — see the original docstring in `monitor.ts`.\n */\n last_assistant_message?: string;\n}\n\nexport type DebugLogger = (label: string, data: unknown) => void;\n\nconst noopDebug: DebugLogger = () => {};\n\n/**\n * Load a transcript JSONL from disk and extract the data we want to\n * report. Any failure (missing file, malformed JSON, unexpected shape)\n * is swallowed and we return a best-effort empty result. Parsing\n * itself lives in `transcript.ts` so it can be unit-tested.\n */\nexport function extractFromTranscript(\n transcriptPath: string | undefined,\n debugLog: DebugLogger = noopDebug,\n): ExtractedTranscriptData {\n const empty: ExtractedTranscriptData = {\n prompt: \"\",\n response: \"\",\n tokens: 0,\n inputTokens: 0,\n outputTokens: 0,\n modelName: null,\n numTurns: 0,\n toolCallCount: 0,\n filesEditedCount: 0,\n bashCommandCount: 0,\n };\n\n if (!transcriptPath) return empty;\n\n let raw: string;\n try {\n raw = fs.readFileSync(transcriptPath, \"utf-8\");\n } catch (err) {\n debugLog(\"transcript-read-failed\", {\n transcriptPath,\n error: (err as Error).message,\n });\n return empty;\n }\n\n return parseTranscript(raw);\n}\n\nfunction extractSubagentName(event: ClaudeHookEvent): string | undefined {\n const candidates = [\n event.agent_name,\n event.subagent_type,\n event.agent_type,\n event.tool_input?.subagent_type,\n ];\n for (const value of candidates) {\n if (typeof value === \"string\" && value.trim()) {\n return value.trim();\n }\n }\n return undefined;\n}\n\n/**\n * Build a MonitoringPayload from a Claude Code hook event.\n *\n * For Stop / SubagentStop events, the hook JSON contains only\n * session_id + transcript_path — the actual conversation data is\n * extracted from the transcript JSONL file. Other event names return\n * null (silent-exit at the dispatcher).\n */\nexport function buildClaudeCodePayload(\n event: string,\n eventData: ClaudeHookEvent,\n config: MonitorConfig,\n): MonitoringPayload | null {\n const sessionId = eventData.session_id ?? `claude-code-${Date.now()}`;\n\n switch (event) {\n case \"stop\":\n case \"subagent-stop\": {\n const extracted = extractFromTranscript(eventData.transcript_path);\n const isSubagent = event === \"subagent-stop\";\n\n const customData: Record<string, unknown> = {\n hookEvent:\n eventData.hook_event_name ??\n (isSubagent ? \"SubagentStop\" : \"Stop\"),\n sessionId,\n transcriptPath: eventData.transcript_path ?? \"\",\n cwd: eventData.cwd ?? \"\",\n stopHookActive: eventData.stop_hook_active ?? false,\n inputTokens: extracted.inputTokens,\n outputTokens: extracted.outputTokens,\n numTurns: extracted.numTurns,\n // Per-turn work signals for the Claude Code classifier (D-027).\n // Always emitted as JSON numbers — the backend classifier and\n // KPI formulas must see numeric 0, not missing keys or strings.\n toolCallCount: extracted.toolCallCount,\n filesEditedCount: extracted.filesEditedCount,\n bashCommandCount: extracted.bashCommandCount,\n };\n\n if (typeof extracted.latencyMs === \"number\") {\n customData.latencyMs = extracted.latencyMs;\n }\n\n if (isSubagent) {\n const subagent = extractSubagentName(eventData);\n if (subagent) {\n customData.subagent = subagent;\n }\n } else if (extracted.skill) {\n // Skill detection only applies to main-agent Stop events.\n customData.skill = extracted.skill;\n }\n\n // Prefer the hook payload's last_assistant_message — Claude Code\n // ships it from in-memory state at hook-fire time, so it\n // reflects the final text block of the turn even when the\n // transcript JSONL hasn't been flushed to disk yet.\n const payloadAssistant = eventData.last_assistant_message;\n const response =\n typeof payloadAssistant === \"string\" && payloadAssistant.trim()\n ? payloadAssistant\n : extracted.response;\n\n return {\n prompt: extracted.prompt,\n response,\n chatId: sessionId,\n source: config.source,\n modelName: extracted.modelName ?? undefined,\n tokens: extracted.tokens,\n customData,\n };\n }\n\n default:\n return null;\n }\n}\n","/**\n * Transcript parsing for Claude Code Stop-hook monitoring.\n *\n * The Stop hook passes only a `transcript_path` pointing at a JSONL file.\n * This module is the single place we extract the per-turn data we report\n * to Olakai (prompt, response, tokens, model, latency, skill invocation).\n *\n * The code here is intentionally pure and exports named helpers so it can\n * be unit-tested without touching the filesystem or HTTP layer.\n */\n\n/**\n * Shape of a single line in a Claude Code transcript JSONL file.\n * Each line is a standalone JSON object. We only care about `user` and\n * `assistant` entries; other types (`system`, `file-history-snapshot`,\n * `progress`, compaction markers, ...) are ignored.\n */\nexport interface TranscriptLine {\n type?: string;\n subtype?: string;\n isMeta?: boolean;\n isSidechain?: boolean;\n timestamp?: string;\n message?: {\n role?: string;\n model?: string;\n content?: string | TranscriptContentBlock[];\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n };\n}\n\nexport interface TranscriptContentBlock {\n type?: string;\n text?: string;\n /**\n * For `tool_use` blocks. Name of the tool the assistant invoked\n * (e.g. `\"Edit\"`, `\"Write\"`, `\"MultiEdit\"`, `\"Bash\"`).\n */\n name?: string;\n /**\n * For `tool_use` blocks. Tool-specific input object. We read\n * `input.file_path` for `Edit`/`Write`/`MultiEdit` to count\n * distinct edited files. Typed as `unknown` because each tool\n * uses a different shape and we only probe `file_path` defensively.\n */\n input?: unknown;\n}\n\nexport interface ExtractedTranscriptData {\n prompt: string;\n response: string;\n tokens: number;\n inputTokens: number;\n outputTokens: number;\n modelName: string | null;\n numTurns: number;\n /**\n * Latency in milliseconds between the current turn's user message and\n * its final assistant response. Undefined when latency could not be\n * computed (first turn, missing timestamps, no user predecessor).\n */\n latencyMs?: number;\n /**\n * Leading slash-command captured from the current turn's user message\n * (e.g. `\"olakai-create-agent\"` for `/olakai-create-agent do X`).\n * Undefined when the user message did not start with a slash-command.\n */\n skill?: string;\n /**\n * ISO 8601 timestamp of the current turn's user message (the last\n * non-meta, non-sidechain user entry in the transcript). Used by the\n * hook layer to dedup duplicate Stop firings for the same user turn.\n * Undefined when no usable user entry is present or the timestamp\n * is missing/unparseable.\n */\n userTurnTimestamp?: string;\n /**\n * Count of `tool_use` content blocks in the current turn's assistant\n * messages. Used as a signal for the Claude Code work classifier\n * (D-027). Always an integer; `0` when the turn contained no tool\n * calls (explicit zero beats missing key). Per-turn, not per-session —\n * the chat decorator aggregates across turns server-side.\n */\n toolCallCount: number;\n /**\n * Count of distinct `file_path` values across `Edit`, `Write`, and\n * `MultiEdit` `tool_use` blocks in the current turn. Counted as the\n * size of a `Set<string>` so duplicate paths within the same turn\n * collapse to one. File paths themselves are NOT emitted — only the\n * cardinality is privacy-safe to report.\n */\n filesEditedCount: number;\n /**\n * Count of `tool_use` blocks with `name === \"Bash\"` in the current\n * turn. Bash command strings are NOT emitted — only the count.\n */\n bashCommandCount: number;\n}\n\n/**\n * Tool names whose invocations we count toward `filesEditedCount`.\n * Kept as a typed constant so both the per-block switch and any\n * future backend-side mirror can reference the same list.\n */\nconst FILE_EDITING_TOOL_NAMES = new Set<string>([\n \"Edit\",\n \"Write\",\n \"MultiEdit\",\n]);\n\n/**\n * Tool-name marker for Bash invocations. Hoisted to a constant so we\n * don't sprinkle the string literal through the parser.\n */\nconst BASH_TOOL_NAME = \"Bash\";\n\n/**\n * Match a slash-command at the start of a message. Leading whitespace is\n * trimmed before matching. The captured group is the skill name.\n *\n * Examples (match):\n * \"/foo\" -> \"foo\"\n * \"/foo-bar\" -> \"foo-bar\"\n * \"/foo some args\" -> \"foo\"\n *\n * Examples (no match):\n * \"Not a skill: /path/file\" — slash is not at the start\n * \"/path/file\" — second segment, name contains a slash\n * \"\" — empty\n */\nconst SKILL_REGEX = /^\\/([\\w-]+)(?:\\s|$)/;\n\nexport function detectSkill(userMessage: string | undefined): string | undefined {\n if (typeof userMessage !== \"string\") return undefined;\n const trimmed = userMessage.trimStart();\n if (!trimmed) return undefined;\n const match = SKILL_REGEX.exec(trimmed);\n if (!match) return undefined;\n return match[1];\n}\n\n/**\n * Extract plain text from a transcript message's `content` field.\n * Content may be a string or an array of content blocks.\n * Only `text` blocks contribute — `thinking`, `tool_use`, `tool_result`,\n * and `image` blocks are ignored.\n */\nexport function extractTextContent(\n content: string | TranscriptContentBlock[] | undefined,\n): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n const parts: string[] = [];\n for (const block of content) {\n if (block?.type === \"text\" && typeof block.text === \"string\") {\n parts.push(block.text);\n }\n }\n return parts.join(\"\\n\").trim();\n}\n\n/**\n * Heuristic: is this a \"meta\" user message that should not be treated\n * as a real prompt? These include slash-command invocations and local\n * command caveats injected by Claude Code itself. Note that these are\n * separate `type: \"system\"` entries — regular `type: \"user\"` entries\n * carrying a typed slash-command still come through here and are NOT\n * skipped.\n */\nexport function isMetaUserMessage(line: TranscriptLine, text: string): boolean {\n if (line.isMeta === true) return true;\n if (!text) return true;\n return (\n text.includes(\"<command-name>\") ||\n text.includes(\"<local-command-caveat>\") ||\n text.includes(\"<command-message>\")\n );\n}\n\n/**\n * Parse an ISO 8601 timestamp to epoch milliseconds.\n * Returns NaN for invalid/undefined input.\n */\nfunction parseTimestamp(ts: string | undefined): number {\n if (typeof ts !== \"string\" || !ts) return NaN;\n return Date.parse(ts);\n}\n\n/**\n * Compaction markers interleaved in the transcript that should be\n * skipped when computing per-turn latency. The exact subtype strings\n * come from Claude Code's own PreCompact / PostCompact hook events\n * (recorded as `type: \"system\"` entries in the transcript).\n */\nfunction isCompactionEntry(line: TranscriptLine): boolean {\n if (line.type !== \"system\") return false;\n const subtype = line.subtype ?? \"\";\n return (\n subtype === \"compact_boundary\" ||\n subtype === \"pre_compact\" ||\n subtype === \"post_compact\" ||\n /compact/i.test(subtype)\n );\n}\n\n/**\n * Parse the transcript JSONL contents and extract the data we want to\n * report: last user prompt, last assistant response, usage totals for\n * the most recent assistant turn, model name, number of assistant\n * turns, latency for the most recent turn, and slash-command (skill)\n * detected on the current turn's user message.\n *\n * `raw` is the full UTF-8 contents of the transcript file. This entry\n * point is pure — no filesystem I/O — so it's trivial to unit test.\n */\nexport function parseTranscript(raw: string): ExtractedTranscriptData {\n const empty: ExtractedTranscriptData = {\n prompt: \"\",\n response: \"\",\n tokens: 0,\n inputTokens: 0,\n outputTokens: 0,\n modelName: null,\n numTurns: 0,\n toolCallCount: 0,\n filesEditedCount: 0,\n bashCommandCount: 0,\n };\n\n if (!raw) return empty;\n\n const lines = raw.split(\"\\n\");\n let lastUserText = \"\";\n let lastUserTimestamp: number = NaN;\n // Original ISO string of the current turn's user message, preserved so\n // the hook layer can use it as a per-session dedup key across\n // duplicate Stop firings. Kept separate from the numeric timestamp\n // because round-tripping Date.parse back through new Date().toISOString()\n // drops sub-ms precision and normalizes timezone formatting.\n let lastUserTimestampRaw: string | undefined;\n let lastAssistantText = \"\";\n let lastAssistantTimestamp: number = NaN;\n let lastAssistantModel: string | null = null;\n let lastAssistantInputTokens = 0;\n let lastAssistantOutputTokens = 0;\n let numTurns = 0;\n // Tracks whether we have seen a new user message since the last\n // assistant-latency update. Ensures latency is always measured\n // relative to the user that preceded the final assistant in the\n // current turn — not across turns.\n let currentTurnUserTimestamp: number = NaN;\n\n // Per-turn work-signal accumulators (D-027 / D.S2). Reset every time\n // we cross a new top-level user message so counts reflect only the\n // current turn window — the assistant messages between the last user\n // message and Stop. Aggregation across turns happens server-side in\n // the chat decorator, not here.\n let toolCallCount = 0;\n let bashCommandCount = 0;\n let editedFilePaths = new Set<string>();\n\n for (const rawLine of lines) {\n if (!rawLine) continue;\n let parsed: TranscriptLine;\n try {\n parsed = JSON.parse(rawLine) as TranscriptLine;\n } catch {\n continue; // skip malformed lines\n }\n\n // Skip compaction markers so they don't break the user->assistant\n // pairing used for latency.\n if (isCompactionEntry(parsed)) continue;\n\n // Skip sidechain (subagent) entries — we want the top-level session\n // conversation. If a transcript only has sidechain entries, the\n // loop below will leave us with empty strings.\n if (parsed.isSidechain === true) continue;\n\n if (parsed.type === \"user\" && parsed.message) {\n const text = extractTextContent(parsed.message.content);\n if (isMetaUserMessage(parsed, text)) continue;\n lastUserText = text;\n lastUserTimestamp = parseTimestamp(parsed.timestamp);\n currentTurnUserTimestamp = lastUserTimestamp;\n // Reset per-turn work-signal accumulators: we've just crossed\n // into a new turn window, so counts from previous turns must\n // not bleed into this one. The chat decorator aggregates across\n // turns server-side (D.S3); here we only emit per-PR counts.\n toolCallCount = 0;\n bashCommandCount = 0;\n editedFilePaths = new Set<string>();\n // Preserve the raw ISO string (only when it's actually a string)\n // for use as a dedup key. Unparseable timestamps are still kept\n // verbatim so upstream can match on exact-string equality.\n lastUserTimestampRaw =\n typeof parsed.timestamp === \"string\" && parsed.timestamp\n ? parsed.timestamp\n : undefined;\n } else if (parsed.type === \"assistant\" && parsed.message) {\n const text = extractTextContent(parsed.message.content);\n // Count every assistant entry (each represents one model response\n // turn, even if it contains only a tool_use or thinking block).\n numTurns += 1;\n if (text) lastAssistantText = text;\n if (typeof parsed.message.model === \"string\") {\n lastAssistantModel = parsed.message.model;\n }\n // Accumulate per-turn work signals. Each tool_use block\n // increments the total, Edit/Write/MultiEdit contribute their\n // file_path to a Set (distinct-path count is what we emit), and\n // Bash tool calls bump their own counter. Individual-block\n // parse failures are swallowed so one malformed entry cannot\n // zero out the whole turn's counts.\n const content = parsed.message.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n try {\n if (!block || block.type !== \"tool_use\") continue;\n toolCallCount += 1;\n const name = typeof block.name === \"string\" ? block.name : \"\";\n if (name === BASH_TOOL_NAME) {\n bashCommandCount += 1;\n continue;\n }\n if (FILE_EDITING_TOOL_NAMES.has(name)) {\n const input = block.input;\n if (\n input !== null &&\n typeof input === \"object\" &&\n \"file_path\" in input\n ) {\n const filePath = (input as { file_path?: unknown }).file_path;\n if (typeof filePath === \"string\" && filePath) {\n editedFilePaths.add(filePath);\n }\n }\n }\n } catch {\n // Malformed block — skip it without failing the whole\n // transcript walk. Partial counts are better than zero.\n }\n }\n }\n const usage = parsed.message.usage;\n if (usage) {\n // Overwrite with the most recent turn's usage. Include cache\n // tokens in the input total so total token count reflects real\n // billable input.\n const input =\n (usage.input_tokens ?? 0) +\n (usage.cache_creation_input_tokens ?? 0) +\n (usage.cache_read_input_tokens ?? 0);\n const output = usage.output_tokens ?? 0;\n lastAssistantInputTokens = input;\n lastAssistantOutputTokens = output;\n }\n const ts = parseTimestamp(parsed.timestamp);\n if (!Number.isNaN(ts)) {\n lastAssistantTimestamp = ts;\n }\n }\n }\n\n const result: ExtractedTranscriptData = {\n prompt: lastUserText,\n response: lastAssistantText,\n tokens: lastAssistantInputTokens + lastAssistantOutputTokens,\n inputTokens: lastAssistantInputTokens,\n outputTokens: lastAssistantOutputTokens,\n modelName: lastAssistantModel,\n numTurns,\n toolCallCount,\n filesEditedCount: editedFilePaths.size,\n bashCommandCount,\n };\n\n if (\n !Number.isNaN(currentTurnUserTimestamp) &&\n !Number.isNaN(lastAssistantTimestamp) &&\n lastAssistantTimestamp >= currentTurnUserTimestamp\n ) {\n result.latencyMs = Math.round(\n lastAssistantTimestamp - currentTurnUserTimestamp,\n );\n }\n\n const skill = detectSkill(lastUserText);\n if (skill) {\n result.skill = skill;\n }\n\n if (lastUserTimestampRaw) {\n result.userTurnTimestamp = lastUserTimestampRaw;\n }\n\n return result;\n}\n","/**\n * OpenAI Codex CLI plugin (Stage 3).\n *\n * Hooks the `Stop` event from `~/.codex/config.toml` into the canonical\n * Olakai monitoring pipeline. (Earlier versions also registered\n * `UserPromptSubmit`, but it fires pre-LLM and double-counts the\n * per-event cost baseline — `hook.ts` silent-exits any leftover legacy\n * registrations.) Mirrors the Claude\n * Code plugin's split (install / uninstall / status / hook /\n * transcript) so the dispatcher in `commands/monitor.ts` doesn't need\n * tool-specific branches beyond the existing `printCodexStatus` /\n * `printClaudeCodeStatus` formatter switch.\n *\n * Defensive parsing per D-003: every field access on the inbound\n * payload is guarded; unrecognized event types silent-exit; rollout\n * lookup failures fall back to inline payload fields where possible.\n *\n * The hook handler MUST run synchronously — Codex skips `async = true`\n * handlers with a warning. The dispatcher's fire-and-forget POST\n * (`postMonitoringPayload`) uses a 5s AbortController timeout to\n * guarantee the process exits within the hook's `timeout` window.\n */\nimport * as fs from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport {\n registerPlugin,\n type HookResult,\n type InstallOpts,\n type InstallResult,\n type StatusReport,\n type ToolMonitorPlugin,\n type UninstallOpts,\n} from \"../../plugin.js\";\nimport { findConfiguredWorkspace } from \"../../paths.js\";\nimport { installCodex } from \"./install.js\";\nimport { uninstallCodex } from \"./uninstall.js\";\nimport { getCodexStatus } from \"./status.js\";\nimport { handleCodexHook, type CodexHookEvent } from \"./hook.js\";\nimport {\n getCodexConfigPath as getCodexMonitorConfigPath,\n loadCodexConfig,\n} from \"./config.js\";\nimport { getCodexConfigPath as getCodexHomeConfigPath, getCodexHomeDir } from \"./paths.js\";\n\nconst TOOL_ID = \"codex\" as const;\n\n/**\n * Debug logger gated by `OLAKAI_MONITOR_DEBUG=1`. Mirrors the\n * Claude Code plugin's debug log so users only need to look in one\n * place when diagnosing hook failures.\n */\nfunction debugLog(label: string, data: unknown): void {\n if (process.env.OLAKAI_MONITOR_DEBUG !== \"1\") return;\n try {\n const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;\n const line = `[${new Date().toISOString()}] codex/${label}: ${\n typeof data === \"string\" ? data : JSON.stringify(data, null, 2)\n }\\n`;\n fs.appendFileSync(logPath, line, \"utf-8\");\n } catch {\n // Never break the hook path on a debug-log failure\n }\n}\n\n/**\n * Resolve the configured workspace root from a Codex hook payload.\n * Codex hooks fire from the user's terminal CWD, which the schema\n * exposes as `payload.cwd`. We walk up from there looking for any\n * `.olakai/monitor-*.json` so a developer running `codex` deep inside\n * a project's subfolder still hits their workspace's monitor config.\n *\n * Exported for unit tests.\n */\nexport function resolveCodexProjectRoot(\n eventData: CodexHookEvent,\n fallbackCwd: string,\n): string | null {\n const payloadCwd =\n typeof eventData.cwd === \"string\" && eventData.cwd.trim()\n ? eventData.cwd\n : fallbackCwd;\n return findConfiguredWorkspace(payloadCwd, [TOOL_ID]);\n}\n\nconst codexPlugin: ToolMonitorPlugin = {\n id: TOOL_ID,\n displayName: \"OpenAI Codex CLI\",\n\n install(opts: InstallOpts): Promise<InstallResult> {\n return installCodex(opts);\n },\n\n uninstall(opts: UninstallOpts): Promise<void> {\n return uninstallCodex(opts);\n },\n\n status(opts): Promise<StatusReport> {\n return getCodexStatus(opts);\n },\n\n async handleHook(\n eventName,\n payloadJson,\n ): Promise<HookResult | null> {\n const eventData = (payloadJson ?? {}) as CodexHookEvent;\n debugLog(\"hook-fired\", { eventName, hasPayload: payloadJson != null });\n\n const projectRoot = resolveCodexProjectRoot(eventData, process.cwd());\n if (!projectRoot) {\n debugLog(\"config-not-found\", {\n startDir:\n typeof eventData.cwd === \"string\" && eventData.cwd.trim()\n ? eventData.cwd\n : process.cwd(),\n });\n return null;\n }\n\n const config = loadCodexConfig(projectRoot);\n if (!config) {\n debugLog(\"monitor-config-missing\", { projectRoot });\n return null;\n }\n\n const payload = handleCodexHook(eventName, eventData, config, {\n debugLog,\n });\n if (!payload) return null;\n\n return {\n payload,\n transport: {\n endpoint: config.monitoringEndpoint,\n apiKey: config.apiKey,\n projectRoot,\n },\n };\n },\n\n async detectInstalled(opts): Promise<boolean> {\n // Two independent signals — either is enough to surface \"codex\"\n // in the interactive `init` selector:\n // 1. The user ran `olakai monitor init --tool codex` here\n // (workspace-local monitor config exists), OR\n // 2. The Codex CLI itself is installed on this host\n // (binary on PATH or `~/.codex/config.toml` present).\n //\n // The PATH probe uses `which` synchronously through\n // `child_process.spawnSync` to avoid shelling out — it's cheap\n // (<10ms) and any failure is treated as \"not installed\".\n const projectRoot = opts?.projectRoot ?? process.cwd();\n try {\n if (fs.existsSync(getCodexMonitorConfigPath(projectRoot))) {\n return true;\n }\n } catch {\n // Fall through to other checks\n }\n try {\n if (fs.existsSync(getCodexHomeConfigPath())) {\n return true;\n }\n if (fs.existsSync(getCodexHomeDir())) {\n // Even an empty ~/.codex tells us Codex has run before\n return true;\n }\n } catch {\n // Fall through\n }\n return detectCodexBinaryOnPath();\n },\n};\n\nfunction detectCodexBinaryOnPath(): boolean {\n try {\n const probe = spawnSync(\n process.platform === \"win32\" ? \"where\" : \"which\",\n [\"codex\"],\n {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n },\n );\n if (probe.status === 0 && probe.stdout && probe.stdout.toString().trim()) {\n return true;\n }\n } catch {\n // Treat any failure as \"not detectable\"\n }\n return false;\n}\n\nregisterPlugin(codexPlugin);\n\nexport default codexPlugin;\nexport { type CodexHookEvent } from \"./hook.js\";\nexport { type CodexMonitorConfig } from \"./config.js\";\n","/**\n * Codex install / uninstall flow.\n *\n * Installs Olakai hook entries into `~/.codex/config.toml` (creating\n * the file when missing) and writes a per-tool monitor config under\n * `.olakai/monitor-codex.json`.\n *\n * Uninstall is the reverse: strip Olakai-marked handlers from the\n * config TOML (preserving any user-added entries) and optionally\n * remove the local monitor config file.\n *\n * The TOML round-trip uses `@iarna/toml` so user-added comments will\n * be lost — there's no comment-preserving TOML library in the JS\n * ecosystem. We surface this in the install output (\"config.toml\n * comments are not preserved\") so users aren't surprised.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as TOML from \"@iarna/toml\";\nimport { type Agent } from \"../../../lib/api.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport { OLAKAI_DIR } from \"../../paths.js\";\nimport type {\n InstallOpts,\n InstallResult,\n UninstallOpts,\n} from \"../../plugin.js\";\nimport { provisionSelfMonitorAgent } from \"../../self-monitor-provision.js\";\nimport {\n CODEX_CONFIG_FILENAME,\n CODEX_HOME_DIRNAME,\n getCodexConfigPath as getCodexHomeConfigPath,\n getCodexHomeDir,\n} from \"./paths.js\";\nimport {\n CODEX_HOOK_TIMEOUT_SECONDS,\n hasOlakaiHooksInstalled,\n mergeCodexHooks,\n stripOlakaiHooks,\n SUPPORTED_HOOK_EVENT_NAMES,\n type CodexConfigToml,\n} from \"./hooks.js\";\nimport {\n deleteCodexConfig,\n getCodexConfigPath as getCodexMonitorConfigPath,\n writeCodexConfig,\n type CodexMonitorConfig,\n} from \"./config.js\";\n\nexport const CODEX_SOURCE = \"codex\";\nexport const CODEX_AGENT_SOURCE = \"CODEX\";\nexport const CODEX_AGENT_CATEGORY = \"CODING\";\n\nexport async function installCodex(opts: InstallOpts): Promise<InstallResult> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n\n const token = getValidToken();\n if (!token) {\n console.error(\"Not logged in. Run 'olakai login' first.\");\n process.exit(1);\n }\n\n console.log(\"Setting up Codex CLI monitoring for this workspace...\\n\");\n\n // Provision (or reuse) the self-monitor agent — same shared helper\n // as claude-code/cursor. See `self-monitor-provision.ts` for the\n // multi-workspace / multi-user collision handling.\n const agent: Agent = await provisionSelfMonitorAgent({\n projectRoot,\n source: CODEX_AGENT_SOURCE,\n displayName: \"Codex CLI\",\n category: CODEX_AGENT_CATEGORY,\n });\n\n const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;\n\n const apiKey = agent.apiKey?.key;\n if (!apiKey) {\n console.error(\n \"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'.\",\n );\n process.exit(1);\n }\n\n // 1) Install hooks into ~/.codex/config.toml\n const { configExisted: codexConfigExisted } = installCodexHooksConfig();\n\n // 2) Write per-tool monitor config in workspace\n const monitorConfig: CodexMonitorConfig = {\n agentId: agent.id,\n apiKey,\n agentName: agent.name,\n source: CODEX_SOURCE,\n createdAt: new Date().toISOString(),\n monitoringEndpoint,\n };\n writeCodexConfig(projectRoot, monitorConfig);\n\n const configPath = getCodexMonitorConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n\n console.log(\"\");\n console.log(`✓ Agent \"${agent.name}\" configured (ID: ${agent.id})`);\n if (agent.apiKey?.key) {\n console.log(\"✓ API key generated\");\n }\n console.log(\n `✓ Codex hooks configured in ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME}`,\n );\n console.log(`✓ Monitor config saved to ${configRel}`);\n console.log(\"\");\n console.log(\n \"Congrats! Monitoring is now active. Codex CLI will report activity to Olakai\",\n );\n console.log(`on each turn.`);\n console.log(\"\");\n console.log(\n `⚠ Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key).`,\n );\n console.log(\n `⚠ Codex hooks require codex >= 0.124.0. Earlier versions silently skip them.`,\n );\n if (codexConfigExisted) {\n // Only warn on rewrite — there's nothing to lose for a brand-new file.\n console.log(\n `⚠ Existing comments in ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME} were not preserved (TOML serializer limitation).`,\n );\n }\n console.log(\"\");\n console.log(\"To check status: olakai monitor status --tool codex\");\n console.log(\"To disable: olakai monitor disable --tool codex\");\n\n return {\n agentId: agent.id,\n agentName: agent.name,\n source: CODEX_SOURCE,\n monitoringEndpoint,\n };\n}\n\nexport interface InstallHooksConfigResult {\n /**\n * True when `~/.codex/config.toml` already existed before we wrote\n * to it (i.e. we performed a read-merge-write rewrite). False when\n * we created the file from scratch — callers can use this to avoid\n * surfacing a misleading \"comments not preserved\" warning on\n * brand-new files.\n */\n configExisted: boolean;\n}\n\n/**\n * Read-merge-write `~/.codex/config.toml` so our Stop handler is\n * installed alongside any user-defined config. Creates the file (and\n * parent directory) when missing.\n *\n * Only `Stop` is registered: it is the canonical capture point for a\n * turn (prompt + response + tokens via the rollout JSONL). The legacy\n * `UserPromptSubmit` registration is intentionally NOT written by new\n * installs — see `hook.ts` for the rationale (pre-LLM emit produced a\n * duplicate, response-less event that double-counted cost).\n *\n * Existing configs that still have a `UserPromptSubmit` Olakai entry\n * are not actively rewritten here, but uninstall (and a fresh init\n * round-trip) will strip them via `OLAKAI_HOOK_MARKER`.\n */\nexport function installCodexHooksConfig(): InstallHooksConfigResult {\n const homeDir = getCodexHomeDir();\n const configPath = getCodexHomeConfigPath();\n\n if (!fs.existsSync(homeDir)) {\n fs.mkdirSync(homeDir, { recursive: true });\n }\n\n const configExisted = fs.existsSync(configPath);\n\n const parsed = readCodexConfigToml(configPath);\n const merged: CodexConfigToml = {\n ...parsed,\n hooks: mergeCodexHooks(parsed.hooks, SUPPORTED_HOOK_EVENT_NAMES),\n };\n\n writeCodexConfigToml(configPath, merged);\n return { configExisted };\n}\n\nexport async function uninstallCodex(opts: UninstallOpts): Promise<void> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n\n const removedHooks = stripCodexHooksConfig();\n if (removedHooks) {\n console.log(\n `✓ Olakai hooks removed from ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME}`,\n );\n } else {\n console.log(\"No Olakai hooks found in Codex config.\");\n }\n\n if (!opts.keepConfig) {\n const configPath = getCodexMonitorConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n if (deleteCodexConfig(projectRoot)) {\n console.log(`✓ Monitor config removed (${configRel})`);\n }\n } else {\n const configPath = getCodexMonitorConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n console.log(`Monitor config retained at ${configRel}`);\n }\n\n console.log(\"\");\n console.log(\n \"Monitoring disabled. Run 'olakai monitor init --tool codex' to re-enable.\",\n );\n}\n\n/**\n * Apply the Olakai-strip transform to a parsed Codex config in\n * memory. Pure — no I/O — so it can be unit-tested directly.\n *\n * Returns a new config where:\n * - Olakai-marked handlers have been removed from `[hooks]`.\n * - If the resulting `[hooks]` block is empty, the `hooks` key is\n * OMITTED from the returned object (rather than left as an empty\n * `{}`), so the rewritten TOML doesn't carry an orphan `[hooks]`\n * header.\n * - Unrelated top-level keys and user-defined hook entries on\n * other events are preserved verbatim.\n */\nexport function applyOlakaiStripToConfig(\n parsed: CodexConfigToml,\n): CodexConfigToml {\n const cleaned = stripOlakaiHooks(parsed.hooks);\n const next: CodexConfigToml = { ...parsed };\n if (cleaned === undefined) {\n delete next.hooks;\n } else {\n next.hooks = cleaned;\n }\n return next;\n}\n\n/**\n * Strip our entries from the Codex config TOML on disk. Returns\n * true when at least one Olakai handler was present (and thus\n * removed); false when there was nothing to do.\n */\nexport function stripCodexHooksConfig(): boolean {\n const configPath = getCodexHomeConfigPath();\n if (!fs.existsSync(configPath)) return false;\n const parsed = readCodexConfigToml(configPath);\n if (!hasOlakaiHooksInstalled(parsed)) return false;\n\n const next = applyOlakaiStripToConfig(parsed);\n writeCodexConfigToml(configPath, next);\n return true;\n}\n\n/**\n * Read a Codex config.toml and parse it. Returns an empty object on\n * any I/O or parse failure — callers proceed as though the user had\n * an empty config so install can still succeed.\n */\nexport function readCodexConfigToml(configPath: string): CodexConfigToml {\n try {\n if (!fs.existsSync(configPath)) return {};\n const raw = fs.readFileSync(configPath, \"utf-8\");\n if (!raw.trim()) return {};\n return TOML.parse(raw) as CodexConfigToml;\n } catch {\n return {};\n }\n}\n\nexport function writeCodexConfigToml(\n configPath: string,\n data: CodexConfigToml,\n): void {\n const dir = path.dirname(configPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n // `@iarna/toml` requires plain JSON-compatible objects. We pass in\n // our typed shape via `as TOML.JsonMap` since the runtime check is\n // limited to value types the library supports (string/number/bool/\n // array/object). Nested arrays-of-tables (hooks) round-trip cleanly.\n const serialized = TOML.stringify(data as TOML.JsonMap);\n fs.writeFileSync(configPath, serialized, \"utf-8\");\n}\n\n// Re-export for tests.\nexport { CODEX_HOOK_TIMEOUT_SECONDS };\n","/**\n * Codex CLI filesystem layout helpers.\n *\n * Codex stores config and per-session rollouts under a single XDG-ish\n * home directory. The paths are documented at\n * https://developers.openai.com/codex/config-reference and have been\n * stable since v0.124.0 (the release that froze the hooks API).\n *\n * Centralizing these here keeps install/uninstall/status/hook in\n * lock-step on which file they're touching.\n */\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nexport const CODEX_HOME_DIRNAME = \".codex\";\nexport const CODEX_CONFIG_FILENAME = \"config.toml\";\nexport const CODEX_SESSIONS_DIRNAME = \"sessions\";\n\nexport function getCodexHomeDir(): string {\n return path.join(os.homedir(), CODEX_HOME_DIRNAME);\n}\n\nexport function getCodexConfigPath(): string {\n return path.join(getCodexHomeDir(), CODEX_CONFIG_FILENAME);\n}\n\nexport function getCodexSessionsDir(): string {\n return path.join(getCodexHomeDir(), CODEX_SESSIONS_DIRNAME);\n}\n","/**\n * Pure helpers for merging Olakai hook entries into a Codex\n * `~/.codex/config.toml`. Filesystem I/O lives in `install.ts` so these\n * can be unit-tested without touching disk.\n *\n * Codex's `[hooks]` block is a mapping of event name -> list of\n * \"matcher groups\", where each matcher group has its own list of\n * `HookHandlerConfig` records (tagged enum on `type`). The format is\n * documented in `codex-rs/config/src/hook_config.rs`:\n *\n * [[hooks.Stop]]\n * [[hooks.Stop.hooks]]\n * type = \"command\"\n * command = \"olakai monitor hook --tool codex Stop\"\n * timeout = 5\n *\n * `async = true` is intentionally NEVER set — Codex skips async hooks\n * with a warning, and our handler must run synchronously to catch\n * the per-turn data.\n */\n\n/**\n * Substring used to identify Olakai-installed hook commands. Any hook\n * entry whose `command` includes this marker is considered ours and\n * is eligible for stripping during uninstall — this matches BOTH the\n * Stop entries we still install and any legacy UserPromptSubmit\n * entries left over from earlier versions of the CLI.\n */\nexport const OLAKAI_HOOK_MARKER = \"olakai monitor hook\";\n\nexport const CODEX_HOOK_TIMEOUT_SECONDS = 5;\n\n/**\n * Events we register on a fresh install. Stop is the only canonical\n * source — see `hook.ts` for why UserPromptSubmit is no longer in\n * this list. Existing `~/.codex/config.toml` files that still have a\n * UserPromptSubmit entry will continue to fire it; the adapter\n * silent-exits, and the next `olakai monitor init --tool codex` run\n * (or any uninstall) will strip it via `OLAKAI_HOOK_MARKER`.\n */\nexport const SUPPORTED_HOOK_EVENT_NAMES = [\"Stop\"] as const;\n\n/**\n * Type-level superset that includes legacy event names we may still\n * encounter in user config files even though we don't install them\n * anymore. Used by the strip / status code paths so they can still\n * recognize and clean up legacy entries.\n */\nexport type SupportedHookEventName = \"Stop\" | \"UserPromptSubmit\";\n\nexport interface CodexHookHandler {\n type: \"command\" | \"prompt\" | \"agent\";\n command?: string;\n timeout?: number;\n /** Codex skips async handlers with a warning — we must never set this. */\n async?: boolean;\n statusMessage?: string;\n [key: string]: unknown;\n}\n\nexport interface CodexMatcherGroup {\n matcher?: string;\n hooks?: CodexHookHandler[];\n}\n\nexport type CodexHooksBlock = Partial<\n Record<SupportedHookEventName | string, CodexMatcherGroup[]>\n>;\n\nexport interface CodexConfigToml {\n hooks?: CodexHooksBlock;\n [key: string]: unknown;\n}\n\n/**\n * Build the canonical Olakai hook entry for a given event. Kept as a\n * function (not a constant) so tests can compare against the same\n * shape the installer writes.\n */\nexport function buildOlakaiHookGroup(\n event: SupportedHookEventName,\n): CodexMatcherGroup {\n return {\n hooks: [\n {\n type: \"command\",\n command: `olakai monitor hook --tool codex ${event}`,\n timeout: CODEX_HOOK_TIMEOUT_SECONDS,\n },\n ],\n };\n}\n\nfunction isOlakaiHandler(handler: CodexHookHandler): boolean {\n return (\n typeof handler.command === \"string\" &&\n handler.command.includes(OLAKAI_HOOK_MARKER)\n );\n}\n\nfunction groupContainsOlakaiHandler(group: CodexMatcherGroup): boolean {\n if (!Array.isArray(group.hooks)) return false;\n return group.hooks.some(isOlakaiHandler);\n}\n\n/**\n * Layer Olakai hook entries onto an existing `[hooks]` block.\n *\n * - Existing user entries on other events are preserved untouched.\n * - Existing user entries on Stop / UserPromptSubmit are preserved;\n * we append our matcher group only when no Olakai-marked handler\n * already exists (idempotent re-init).\n * - Existing Olakai-marked handlers are left exactly as written so\n * user customizations (e.g. a tweaked timeout) survive re-init.\n */\nexport function mergeCodexHooks(\n existing: CodexHooksBlock | undefined,\n events: readonly SupportedHookEventName[] = SUPPORTED_HOOK_EVENT_NAMES,\n): CodexHooksBlock {\n const merged: CodexHooksBlock = { ...(existing ?? {}) };\n\n for (const event of events) {\n const existingGroups = merged[event] ?? [];\n const hasOlakaiHook = existingGroups.some(groupContainsOlakaiHandler);\n if (hasOlakaiHook) {\n merged[event] = existingGroups;\n } else {\n merged[event] = [...existingGroups, buildOlakaiHookGroup(event)];\n }\n }\n\n return merged;\n}\n\n/**\n * Strip Olakai-installed entries from an existing `[hooks]` block.\n * Returns the cleaned block or `undefined` when no event keys remain\n * (so the caller can omit `[hooks]` entirely from the rewritten file).\n *\n * Within a matcher group we drop only the Olakai-marked handlers,\n * not the entire group — a user might have appended their own\n * `command` to the same group and we must preserve theirs.\n */\nexport function stripOlakaiHooks(\n existing: CodexHooksBlock | undefined,\n): CodexHooksBlock | undefined {\n if (!existing) return undefined;\n const cleaned: CodexHooksBlock = {};\n for (const [event, groups] of Object.entries(existing)) {\n if (!Array.isArray(groups)) continue;\n const filteredGroups: CodexMatcherGroup[] = [];\n for (const group of groups) {\n const handlers = Array.isArray(group.hooks) ? group.hooks : [];\n const remaining = handlers.filter((h) => !isOlakaiHandler(h));\n if (remaining.length === 0 && handlers.length > 0) {\n // Group existed only to host an Olakai hook -> drop the group.\n continue;\n }\n if (remaining.length === handlers.length) {\n // Nothing of ours in this group -> keep verbatim.\n filteredGroups.push(group);\n } else {\n filteredGroups.push({ ...group, hooks: remaining });\n }\n }\n if (filteredGroups.length > 0) {\n cleaned[event] = filteredGroups;\n }\n }\n return Object.keys(cleaned).length > 0 ? cleaned : undefined;\n}\n\n/**\n * Whether the parsed TOML contains at least one Olakai-marked handler\n * across any event. Used by `status` to surface \"hooks active\".\n */\nexport function hasOlakaiHooksInstalled(parsed: CodexConfigToml): boolean {\n const hooks = parsed.hooks;\n if (!hooks) return false;\n for (const groups of Object.values(hooks)) {\n if (!Array.isArray(groups)) continue;\n for (const group of groups) {\n if (groupContainsOlakaiHandler(group)) return true;\n }\n }\n return false;\n}\n","/**\n * Per-tool monitor config for Codex. Lives at\n * `.olakai/monitor-codex.json` next to other tools' configs.\n *\n * Mirrors the Claude Code shape so the dispatcher and any future\n * \"show all tools\" command can treat the file uniformly.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n getMonitorConfigPath as resolveMonitorConfigPath,\n getOlakaiDir,\n} from \"../../paths.js\";\n\nexport interface CodexMonitorConfig {\n agentId: string;\n apiKey: string;\n agentName: string;\n source: string;\n createdAt: string;\n monitoringEndpoint: string;\n}\n\nexport function getCodexConfigPath(projectRoot: string): string {\n return resolveMonitorConfigPath(projectRoot, \"codex\");\n}\n\nexport function loadCodexConfig(\n projectRoot: string,\n): CodexMonitorConfig | null {\n const filePath = getCodexConfigPath(projectRoot);\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw) as CodexMonitorConfig;\n } catch {\n return null;\n }\n}\n\nexport function writeCodexConfig(\n projectRoot: string,\n config: CodexMonitorConfig,\n): void {\n const filePath = getCodexConfigPath(projectRoot);\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // Non-fatal on Windows / unusual filesystems\n }\n}\n\nexport function deleteCodexConfig(projectRoot: string): boolean {\n const filePath = getCodexConfigPath(projectRoot);\n if (!fs.existsSync(filePath)) return false;\n try {\n fs.unlinkSync(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getCodexConfigDir(projectRoot: string): string {\n return getOlakaiDir(projectRoot);\n}\n","/**\n * Status reporter for the Codex plugin. Mirrors the Claude Code\n * status output: per-tool config metadata + a hint when the global\n * Codex config is missing the Olakai hook entries (e.g. when the user\n * edited config.toml manually since `init`).\n */\nimport path from \"node:path\";\nimport * as fs from \"node:fs\";\nimport type { StatusReport } from \"../../plugin.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport {\n CODEX_CONFIG_FILENAME,\n CODEX_HOME_DIRNAME,\n getCodexConfigPath as getCodexHomeConfigPath,\n} from \"./paths.js\";\nimport { hasOlakaiHooksInstalled } from \"./hooks.js\";\nimport { readCodexConfigToml } from \"./install.js\";\nimport {\n getCodexConfigPath as getCodexMonitorConfigPath,\n loadCodexConfig,\n} from \"./config.js\";\n\nexport async function getCodexStatus(opts?: {\n projectRoot?: string;\n}): Promise<StatusReport> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const configPath = getCodexMonitorConfigPath(projectRoot);\n const config = loadCodexConfig(projectRoot);\n\n const hooksConfigured = isHooksBlockInstalled();\n\n if (!config) {\n return {\n toolId: \"codex\",\n configured: false,\n hooksConfigured,\n configPath,\n notes: hooksConfigured\n ? [\n `Codex hooks present in ~/${CODEX_HOME_DIRNAME}/${CODEX_CONFIG_FILENAME} but no monitor config in this workspace — re-run init.`,\n ]\n : [],\n };\n }\n\n return {\n toolId: \"codex\",\n configured: true,\n hooksConfigured,\n agentId: config.agentId,\n agentName: config.agentName,\n source: config.source,\n apiKeyMasked: config.apiKey.slice(0, 12) + \"...\",\n monitoringEndpoint: config.monitoringEndpoint,\n configuredAt: config.createdAt,\n configPath,\n };\n}\n\nfunction isHooksBlockInstalled(): boolean {\n const homeConfig = getCodexHomeConfigPath();\n if (!fs.existsSync(homeConfig)) return false;\n const parsed = readCodexConfigToml(homeConfig);\n return hasOlakaiHooksInstalled(parsed);\n}\n\n/**\n * Pretty printer used by the dispatcher in non-JSON mode. Mirrors\n * `printClaudeCodeStatus` so the two tools render consistently.\n */\nexport async function printCodexStatus(opts?: {\n projectRoot?: string;\n}): Promise<void> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const status = await getCodexStatus({ projectRoot });\n\n if (!status.configured) {\n console.log(\"Codex monitoring is not configured for this workspace.\");\n console.log(\n \"Run 'olakai monitor init --tool codex' to set up monitoring.\",\n );\n if (status.notes && status.notes.length > 0) {\n for (const note of status.notes) {\n console.log(note);\n }\n }\n process.exit(1);\n }\n\n const configRel = status.configPath\n ? path.relative(projectRoot, status.configPath)\n : \"(unknown)\";\n\n console.log(\"Olakai Monitor Status (Codex CLI)\");\n console.log(\"=================================\");\n console.log(`Agent: ${status.agentName}`);\n console.log(`Agent ID: ${status.agentId}`);\n console.log(`API Key: ${status.apiKeyMasked}`);\n console.log(`Endpoint: ${status.monitoringEndpoint}`);\n console.log(`Source: ${status.source}`);\n console.log(`Configured: ${status.configuredAt}`);\n console.log(`Config file: ${configRel}`);\n console.log(\n `Hooks: ${\n status.hooksConfigured\n ? \"Active\"\n : `Missing (re-run 'olakai monitor init --tool codex')`\n }`,\n );\n\n try {\n const token = getValidToken();\n if (token && status.agentId) {\n const params = new URLSearchParams({\n agentId: status.agentId,\n limit: \"5\",\n });\n const response = await fetch(\n `${getBaseUrl()}/api/activity/prompts?${params}`,\n {\n headers: { Authorization: `Bearer ${token}` },\n },\n );\n if (response.ok) {\n const data = (await response.json()) as {\n prompts: Array<{ id: string; createdAt: string }>;\n };\n if (data.prompts && data.prompts.length > 0) {\n console.log(\"\");\n console.log(\"Recent Activity:\");\n for (const p of data.prompts) {\n console.log(` ${p.createdAt} ${p.id.slice(0, 12)}...`);\n }\n } else {\n console.log(\"\");\n console.log(\"No activity recorded yet.\");\n }\n }\n }\n } catch {\n // Activity check is optional — never fail status on it\n }\n}\n","/**\n * Codex rollout JSONL reader.\n *\n * A Codex session rollout lives at one of:\n * - `~/.codex/sessions/YYYY/MM/DD/rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl`\n * (production layout, since the date-bucketed reorg in v0.124.0)\n * - `~/.codex/sessions/rollout-...-<uuid>.jsonl`\n * (legacy flat layout — kept for back-compat with old Codex builds)\n *\n * Test fixtures mirror the production layout under\n * `tests/fixtures/codex/sessions/YYYY/MM/DD/rollout-...jsonl` so the\n * walker is exercised the same way it is at runtime.\n *\n * We resolve the file by suffix-matching on `<session_id>.jsonl` so we\n * don't have to know which layout the user's Codex emits. The walker\n * is bounded to a small recent slice — the Stop hook fires within\n * milliseconds of the session writing the rollout, so the relevant\n * file is virtually always the newest match.\n *\n * Each line is a `RolloutLine` (`{ timestamp, type, payload }`) that\n * unwraps into one of: `session_meta`, `response_item`, `event_msg`,\n * `compacted`, `turn_context`. We read the file lazily, defensively\n * tolerate malformed lines, and return a best-effort\n * `ExtractedRollout` even when fields are missing or unrecognized.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n CODEX_SESSIONS_DIRNAME,\n getCodexHomeDir,\n getCodexSessionsDir,\n} from \"./paths.js\";\n\nexport interface ExtractedRollout {\n prompt: string;\n response: string;\n modelName: string | null;\n inputTokens: number;\n outputTokens: number;\n cachedInputTokens: number;\n reasoningOutputTokens: number;\n tokens: number;\n numTurns: number;\n /**\n * Path of the rollout file we loaded the data from, if any. Useful\n * for surfacing in customData for debugging and for status checks.\n */\n rolloutPath?: string;\n}\n\nexport function emptyRollout(): ExtractedRollout {\n return {\n prompt: \"\",\n response: \"\",\n modelName: null,\n inputTokens: 0,\n outputTokens: 0,\n cachedInputTokens: 0,\n reasoningOutputTokens: 0,\n tokens: 0,\n numTurns: 0,\n };\n}\n\ntype DebugLogger = (label: string, data: unknown) => void;\nconst noopDebug: DebugLogger = () => {};\n\ninterface RolloutScanLimits {\n /** Cap on directories scanned during the search. Hard-bounds the walk. */\n maxDirs?: number;\n /** Cap on files visited during the search. */\n maxFiles?: number;\n}\n\nconst DEFAULT_LIMITS: Required<RolloutScanLimits> = {\n maxDirs: 200,\n maxFiles: 5000,\n};\n\n/**\n * Locate the rollout JSONL for a given Codex `session_id`. Returns\n * null when nothing matches (caller should treat as \"no transcript\n * available yet\"). Search is breadth-first over the (small) set of\n * date directories Codex writes; bounded by the limits above to\n * avoid pathological scans on a long-running install.\n */\nexport function findRolloutPathForSession(\n sessionId: string,\n sessionsDir: string = getCodexSessionsDir(),\n limits: RolloutScanLimits = {},\n): string | null {\n const merged = { ...DEFAULT_LIMITS, ...limits };\n if (!sessionId) return null;\n if (!fs.existsSync(sessionsDir)) return null;\n\n const suffix = `-${sessionId}.jsonl`;\n const matches: { path: string; mtimeMs: number }[] = [];\n let dirsScanned = 0;\n let filesScanned = 0;\n\n const queue: string[] = [sessionsDir];\n while (queue.length > 0) {\n const dir = queue.shift()!;\n if (dirsScanned++ > merged.maxDirs) break;\n\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (filesScanned++ > merged.maxFiles) break;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n queue.push(full);\n continue;\n }\n if (!entry.isFile()) continue;\n if (!entry.name.endsWith(suffix)) continue;\n if (!entry.name.startsWith(\"rollout-\")) continue;\n try {\n const stat = fs.statSync(full);\n matches.push({ path: full, mtimeMs: stat.mtimeMs });\n } catch {\n // ignore\n }\n }\n }\n\n if (matches.length === 0) return null;\n // Pick the freshest file — multiple matches can occur during\n // forks/resumes; the active session is always the most recent.\n matches.sort((a, b) => b.mtimeMs - a.mtimeMs);\n return matches[0].path;\n}\n\ninterface RolloutLine {\n timestamp?: string;\n type?: string;\n payload?: unknown;\n}\n\ninterface SessionMetaPayload {\n id?: unknown;\n cwd?: unknown;\n timestamp?: unknown;\n cli_version?: unknown;\n // SessionMeta is flattened into the line in older Codex builds — we\n // also accept inline fields when `payload` is missing.\n}\n\ninterface EventMsgPayload {\n type?: string;\n message?: string;\n info?: {\n last_token_usage?: TokenUsage;\n total_token_usage?: TokenUsage;\n };\n model?: string;\n}\n\ninterface TokenUsage {\n input_tokens?: number;\n cached_input_tokens?: number;\n output_tokens?: number;\n reasoning_output_tokens?: number;\n total_tokens?: number;\n}\n\ninterface ResponseItemPayload {\n type?: string;\n role?: string;\n content?: Array<{ type?: string; text?: string }>;\n}\n\n/**\n * Parse the raw bytes of a Codex rollout JSONL file into an\n * `ExtractedRollout`. Pure — no filesystem I/O — so it can be unit\n * tested directly. Malformed JSONL lines are skipped, not thrown.\n */\nexport function parseRolloutContent(\n raw: string,\n debugLog: DebugLogger = noopDebug,\n): ExtractedRollout {\n const result = emptyRollout();\n if (!raw) return result;\n\n const lines = raw.split(\"\\n\");\n let lastUserMessage = \"\";\n let lastAssistantMessage = \"\";\n let lastTokenUsage: TokenUsage | null = null;\n let totalTokenUsage: TokenUsage | null = null;\n let modelName: string | null = null;\n let userTurnCount = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n let parsed: RolloutLine;\n try {\n parsed = JSON.parse(trimmed) as RolloutLine;\n } catch (err) {\n debugLog(\"rollout-line-parse-failed\", {\n line: trimmed.slice(0, 120),\n error: (err as Error).message,\n });\n continue;\n }\n\n const type = parsed.type;\n const payload = parsed.payload as\n | EventMsgPayload\n | ResponseItemPayload\n | SessionMetaPayload\n | undefined;\n if (!type || !payload) continue;\n\n if (type === \"session_meta\") {\n // Older builds flattened SessionMeta directly into the line; if\n // we ever see a model name there, capture it as a fallback.\n continue;\n }\n\n if (type === \"event_msg\") {\n const ev = payload as EventMsgPayload;\n const evType = ev.type;\n if (evType === \"user_message\" && typeof ev.message === \"string\") {\n lastUserMessage = ev.message;\n userTurnCount += 1;\n } else if (evType === \"agent_message\" && typeof ev.message === \"string\") {\n lastAssistantMessage = ev.message;\n } else if (evType === \"token_count\" && ev.info) {\n if (ev.info.last_token_usage) {\n lastTokenUsage = ev.info.last_token_usage;\n }\n if (ev.info.total_token_usage) {\n totalTokenUsage = ev.info.total_token_usage;\n }\n } else if (\n evType === \"session_configured\" &&\n typeof ev.model === \"string\" &&\n ev.model\n ) {\n modelName = ev.model;\n }\n continue;\n }\n\n if (type === \"response_item\") {\n const ri = payload as ResponseItemPayload;\n if (ri.type === \"message\" && Array.isArray(ri.content)) {\n const text = extractTextFromContent(ri.content);\n if (!text) continue;\n if (ri.role === \"user\") {\n lastUserMessage = text;\n userTurnCount += 1;\n } else if (ri.role === \"assistant\") {\n lastAssistantMessage = text;\n }\n }\n continue;\n }\n // Unknown type — ignore silently. Codex may add new RolloutItem\n // variants; we don't want to throw on encounter.\n }\n\n result.prompt = lastUserMessage;\n result.response = lastAssistantMessage;\n result.modelName = modelName;\n result.numTurns = userTurnCount;\n\n // Prefer last_token_usage for per-turn metrics — it represents the\n // most recent model call. Total is a session running sum and is too\n // coarse for a single MonitoringPayload.\n const tokenSource = lastTokenUsage ?? totalTokenUsage;\n if (tokenSource) {\n result.inputTokens = numberOrZero(tokenSource.input_tokens);\n result.outputTokens = numberOrZero(tokenSource.output_tokens);\n result.cachedInputTokens = numberOrZero(tokenSource.cached_input_tokens);\n result.reasoningOutputTokens = numberOrZero(\n tokenSource.reasoning_output_tokens,\n );\n result.tokens =\n numberOrZero(tokenSource.total_tokens) ||\n result.inputTokens + result.outputTokens;\n }\n\n return result;\n}\n\nfunction extractTextFromContent(\n content: Array<{ type?: string; text?: string }>,\n): string {\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block.text !== \"string\") continue;\n if (\n block.type === \"output_text\" ||\n block.type === \"input_text\" ||\n block.type === \"text\"\n ) {\n parts.push(block.text);\n }\n }\n return parts.join(\"\\n\").trim();\n}\n\nfunction numberOrZero(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\n/**\n * Read the rollout JSONL for `sessionId` from disk and parse it. Any\n * I/O or parse failure returns an empty result; the caller (hook\n * adapter) treats that as \"no transcript yet\" and falls back to the\n * inline payload fields where possible.\n */\nexport function loadRolloutForSession(\n sessionId: string,\n options: { sessionsDir?: string; debugLog?: DebugLogger } = {},\n): ExtractedRollout {\n const debugLog = options.debugLog ?? noopDebug;\n const sessionsDir = options.sessionsDir ?? getCodexSessionsDir();\n const result = emptyRollout();\n\n const rolloutPath = findRolloutPathForSession(sessionId, sessionsDir);\n if (!rolloutPath) {\n debugLog(\"rollout-not-found\", {\n sessionId,\n sessionsDir,\n });\n return result;\n }\n\n let raw: string;\n try {\n raw = fs.readFileSync(rolloutPath, \"utf-8\");\n } catch (err) {\n debugLog(\"rollout-read-failed\", {\n rolloutPath,\n error: (err as Error).message,\n });\n return result;\n }\n\n const parsed = parseRolloutContent(raw, debugLog);\n parsed.rolloutPath = rolloutPath;\n return parsed;\n}\n\n/**\n * Test/debug entry point that mirrors `loadRolloutForSession` but\n * skips the codex-home lookup so callers can drop in a synthetic\n * rollout file. Exported so tests don't need to monkey-patch\n * `os.homedir()`.\n */\nexport function loadRolloutFromPath(\n rolloutPath: string,\n debugLog: DebugLogger = noopDebug,\n): ExtractedRollout {\n const result = emptyRollout();\n let raw: string;\n try {\n raw = fs.readFileSync(rolloutPath, \"utf-8\");\n } catch (err) {\n debugLog(\"rollout-read-failed\", {\n rolloutPath,\n error: (err as Error).message,\n });\n return result;\n }\n const parsed = parseRolloutContent(raw, debugLog);\n parsed.rolloutPath = rolloutPath;\n return parsed;\n}\n\n// Re-exports so consumers can grab the codex paths without a separate\n// import line in the tight modules that already import this one.\nexport { CODEX_SESSIONS_DIRNAME, getCodexHomeDir };\n","/**\n * Codex hook payload adapter. Two pure builders + one effectful\n * `handleCodexHook` that the plugin's `handleHook` method delegates to.\n *\n * The split mirrors the Claude Code adapter so tests can drive the\n * builders without touching the filesystem. Filesystem access (rollout\n * lookup, dedup state) lives in `handleCodexHook` and is exercised\n * via integration-style tests where a synthetic rollout file is laid\n * down ahead of time.\n *\n * Codex hook events the v1 plugin handles:\n * - `Stop` — final response per turn (canonical capture\n * via the rollout JSONL: prompt + response +\n * tokens for the turn)\n * - `UserPromptSubmit` — RECOGNIZED but always silent-exits. Codex\n * fires this pre-LLM with the prompt only;\n * emitting a response-less event creates a\n * duplicate that double-counts the per-event\n * cost baseline downstream. The paired Stop\n * event already includes the prompt from the\n * rollout, so UserPromptSubmit is pure noise.\n * Kept in the supported set so legacy\n * installs (which still register a hook\n * pointing at us) don't error out — we just\n * drop the event on the floor.\n *\n * Other event names (`PreToolUse`, `PostToolUse`, `SessionStart`,\n * `PermissionRequest`) silent-exit at the plugin layer (return null).\n *\n * The handler MUST run synchronously: Codex skips `async = true` hook\n * handlers with a warning, and the per-turn capture window is short.\n */\nimport type { MonitoringPayload } from \"../../plugin.js\";\nimport type { CodexMonitorConfig } from \"./config.js\";\nimport {\n emptyRollout,\n loadRolloutForSession,\n type ExtractedRollout,\n} from \"./transcript.js\";\n\n/**\n * Codex hook event payload — superset of fields seen across Stop /\n * UserPromptSubmit. Loose typing because the schema only stabilized\n * in v0.124.0 and we need to tolerate fields shifting between\n * patches. Per D-003: parse defensively.\n *\n * NOTE: `prompt` is still typed here for completeness even though\n * `UserPromptSubmit` is silent-exited at the handler layer. Tests and\n * legacy callers may still construct payloads with this field.\n */\nexport interface CodexHookEvent {\n hook_event_name?: string;\n session_id?: string;\n cwd?: string;\n model?: string;\n permission_mode?: string;\n transcript_path?: string | null;\n turn_id?: string;\n /** UserPromptSubmit only — currently dropped (see file header). */\n prompt?: string;\n /** Stop only. May be null when the assistant produced no text. */\n last_assistant_message?: string | null;\n /** Stop only. */\n stop_hook_active?: boolean;\n}\n\nexport type DebugLogger = (label: string, data: unknown) => void;\nconst noopDebug: DebugLogger = () => {};\n\n/**\n * Hook events Codex emits that we register handlers for. Compared\n * case-insensitively because Codex uses PascalCase and shell scripts\n * occasionally typo casing.\n */\nconst SUPPORTED_EVENTS = new Set([\"Stop\", \"UserPromptSubmit\"]);\n\nexport function isSupportedCodexEvent(eventName: string): boolean {\n return SUPPORTED_EVENTS.has(eventName);\n}\n\n/**\n * Build a canonical MonitoringPayload from a parsed Codex hook\n * payload + rollout extract + per-tool monitor config. Pure function;\n * no I/O. Returns null for events we don't handle.\n *\n * `UserPromptSubmit` always returns null here: the rollout-based\n * `Stop` capture is the canonical source of a turn's prompt + response,\n * and emitting a pre-LLM event with no response creates a duplicate\n * monitoring record that double-counts the per-event cost baseline.\n */\nexport function buildCodexPayload(\n eventName: string,\n eventData: CodexHookEvent,\n config: CodexMonitorConfig,\n rollout: ExtractedRollout = emptyRollout(),\n): MonitoringPayload | null {\n if (!isSupportedCodexEvent(eventName)) return null;\n // UserPromptSubmit is recognized (legacy installs still register it)\n // but produces no payload — see file header.\n if (eventName === \"UserPromptSubmit\") return null;\n\n const sessionId =\n typeof eventData.session_id === \"string\" && eventData.session_id\n ? eventData.session_id\n : `codex-${Date.now()}`;\n\n const cwd = typeof eventData.cwd === \"string\" ? eventData.cwd : \"\";\n const turnId =\n typeof eventData.turn_id === \"string\" ? eventData.turn_id : \"\";\n const permissionMode =\n typeof eventData.permission_mode === \"string\"\n ? eventData.permission_mode\n : \"\";\n const transcriptPath =\n typeof eventData.transcript_path === \"string\"\n ? eventData.transcript_path\n : \"\";\n\n // Model name preference: hook payload first (always present per\n // schema), then rollout fallback. The hook value is what Codex\n // *intends* to use for this turn; the rollout reflects what was\n // actually configured at session start.\n const modelName =\n (typeof eventData.model === \"string\" && eventData.model.trim()\n ? eventData.model\n : null) ?? rollout.modelName;\n\n const customData: Record<string, unknown> = {\n hookEvent: eventData.hook_event_name ?? eventName,\n sessionId,\n cwd,\n turnId,\n permissionMode,\n transcriptPath,\n inputTokens: rollout.inputTokens,\n outputTokens: rollout.outputTokens,\n cachedInputTokens: rollout.cachedInputTokens,\n reasoningOutputTokens: rollout.reasoningOutputTokens,\n numTurns: rollout.numTurns,\n };\n\n if (rollout.rolloutPath) {\n customData.rolloutPath = rollout.rolloutPath;\n }\n if (typeof eventData.stop_hook_active === \"boolean\") {\n customData.stopHookActive = eventData.stop_hook_active;\n }\n\n // Stop event\n const inlineAssistant =\n typeof eventData.last_assistant_message === \"string\" &&\n eventData.last_assistant_message.trim()\n ? eventData.last_assistant_message\n : \"\";\n const response = inlineAssistant || rollout.response;\n\n return {\n prompt: rollout.prompt,\n response,\n chatId: sessionId,\n source: config.source,\n modelName: modelName ?? undefined,\n tokens: rollout.tokens,\n customData,\n };\n}\n\n/**\n * Effectful handler: parse stdin payload, look up the rollout, and\n * build the canonical payload. Returns null when the event is not one\n * we handle, the payload doesn't carry a session_id, or the rollout\n * lookup yields nothing for a Stop event without an inline assistant\n * message (i.e. nothing to report).\n *\n * `UserPromptSubmit` is silent-exited here regardless of payload: the\n * Stop event captures the full turn (prompt + response + tokens) from\n * the rollout JSONL, and a pre-LLM emit creates a duplicate event\n * downstream that has no response and double-counts the per-event\n * cost baseline.\n */\nexport function handleCodexHook(\n eventName: string,\n payloadJson: unknown,\n config: CodexMonitorConfig,\n options: { debugLog?: DebugLogger; sessionsDir?: string } = {},\n): MonitoringPayload | null {\n const debugLog = options.debugLog ?? noopDebug;\n if (!isSupportedCodexEvent(eventName)) {\n debugLog(\"hook-unknown-event\", eventName);\n return null;\n }\n\n // Silent-exit UserPromptSubmit: rollout-driven Stop is the canonical\n // capture; emitting here would produce a duplicate, response-less\n // event and double-count cost downstream. Legacy installs may still\n // register this hook; we accept the call and drop it on the floor.\n if (eventName === \"UserPromptSubmit\") {\n debugLog(\"user-prompt-submit-dropped\", { eventName });\n return null;\n }\n\n const eventData = (payloadJson ?? {}) as CodexHookEvent;\n debugLog(\"event-parsed\", { eventName, eventData });\n\n const sessionId =\n typeof eventData.session_id === \"string\" ? eventData.session_id : \"\";\n\n let rollout = emptyRollout();\n if (eventName === \"Stop\" && sessionId) {\n rollout = loadRolloutForSession(sessionId, {\n debugLog,\n sessionsDir: options.sessionsDir,\n });\n }\n\n const payload = buildCodexPayload(eventName, eventData, config, rollout);\n if (!payload) return null;\n\n // For Stop: a payload with no prompt AND no response is noise — no\n // reason to send it.\n if (!payload.prompt && !payload.response) {\n debugLog(\"payload-empty\", { eventName, sessionId });\n return null;\n }\n\n debugLog(\"payload-built\", payload);\n return payload;\n}\n","/**\n * Cursor plugin (D-005 source = \"cursor\"). Per D-004, this stage\n * supports per-user install only (`~/.cursor/hooks.json`); system-wide\n * enterprise install is deferred to Stage 4.5.\n *\n * Contract: implements `ToolMonitorPlugin` against the canonical\n * MonitoringPayload shape. The hook adapter pairs `beforeSubmitPrompt`\n * + `afterAgentResponse` across separate CLI invocations via the\n * pairing-state sidecar in `pairing-state.ts`.\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport {\n registerPlugin,\n type HookResult,\n type InstallOpts,\n type InstallResult,\n type StatusReport,\n type ToolMonitorPlugin,\n type UninstallOpts,\n} from \"../../plugin.js\";\nimport { findConfiguredWorkspace } from \"../../paths.js\";\nimport { installCursor, uninstallCursor } from \"./install.js\";\nimport { getCursorStatus } from \"./status.js\";\nimport { loadCursorConfig } from \"./config.js\";\nimport { getCursorUserDir } from \"./paths.js\";\nimport {\n buildOrphanPayload,\n buildPairedPayload,\n collectUnknownFields,\n extractAttachments,\n extractCursorMeta,\n extractCursorTokens,\n extractPromptText,\n extractResponseText,\n normalizeEventName,\n type CursorHookPayload,\n} from \"./hook.js\";\nimport {\n clearPendingPrompt,\n listPendingPrompts,\n stashPendingPrompt,\n takePendingPrompt,\n} from \"./pairing-state.js\";\n\nconst TOOL_ID = \"cursor\" as const;\n\n/**\n * Debug logger. Mirrors the Claude Code plugin: writes only when\n * OLAKAI_MONITOR_DEBUG=1, never throws. Useful for diagnosing beta\n * payload-shape changes without polluting Cursor's UI.\n */\nfunction debugLog(label: string, data: unknown): void {\n if (process.env.OLAKAI_MONITOR_DEBUG !== \"1\") return;\n try {\n const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;\n const line = `[${new Date().toISOString()}] cursor:${label}: ${\n typeof data === \"string\" ? data : JSON.stringify(data, null, 2)\n }\\n`;\n fs.appendFileSync(logPath, line, \"utf-8\");\n } catch {\n // ignore\n }\n}\n\n/**\n * Resolve the configured project root for a Cursor hook event. Cursor's\n * payload exposes `workspace_roots` (an array) — we walk ancestors of\n * each candidate looking for an `.olakai/monitor-cursor.json` file.\n * Falls back to `process.cwd()` if the payload omits workspace_roots.\n *\n * Exported for unit tests.\n */\nexport function resolveCursorProjectRoot(\n payload: CursorHookPayload,\n fallbackCwd: string = process.cwd(),\n): string | null {\n const roots = Array.isArray(payload.workspace_roots)\n ? payload.workspace_roots.filter(\n (r): r is string => typeof r === \"string\" && r.trim().length > 0,\n )\n : [];\n const candidates = roots.length > 0 ? roots : [fallbackCwd];\n for (const candidate of candidates) {\n const found = findConfiguredWorkspace(candidate, [TOOL_ID]);\n if (found) return found;\n }\n return null;\n}\n\ninterface HandleHookOpts {\n projectRoot?: string;\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\n/**\n * Plugin's `handleHook` implementation. Public so tests can call it\n * directly with custom homeDir.\n */\nexport async function handleCursorHook(\n eventName: string,\n payloadJson: unknown,\n opts: HandleHookOpts = {},\n): Promise<HookResult | null> {\n const event = normalizeEventName(eventName);\n if (!event) {\n debugLog(\"hook-unknown-event\", eventName);\n return null;\n }\n\n const payload =\n payloadJson && typeof payloadJson === \"object\"\n ? (payloadJson as CursorHookPayload)\n : ({} as CursorHookPayload);\n debugLog(\"event-parsed\", { event, payload });\n\n const meta = extractCursorMeta(payload);\n const homeDir = opts.homeDir ?? os.homedir();\n\n switch (event) {\n case \"beforeSubmitPrompt\": {\n if (!meta.conversationId) {\n debugLog(\"missing-conversation-id\", { event });\n return null;\n }\n stashPendingPrompt(\n {\n prompt: extractPromptText(payload),\n userEmail: meta.userEmail,\n model: meta.model,\n cursorVersion: meta.cursorVersion,\n conversationId: meta.conversationId,\n generationId: meta.generationId,\n workspaceRoots: meta.workspaceRoots,\n transcriptPath: meta.transcriptPath,\n attachments: extractAttachments(payload),\n stashedAt: new Date().toISOString(),\n extra: collectUnknownFields(payload),\n },\n homeDir,\n );\n // Stash-only path emits no event yet — waits for afterAgentResponse.\n return null;\n }\n\n case \"afterAgentResponse\": {\n if (!meta.conversationId) {\n debugLog(\"missing-conversation-id\", { event });\n return null;\n }\n const projectRoot = resolveCursorProjectRoot(\n payload,\n opts.projectRoot ?? process.cwd(),\n );\n if (!projectRoot) {\n debugLog(\"config-not-found\", {\n workspaceRoots: meta.workspaceRoots,\n fallbackCwd: opts.projectRoot ?? process.cwd(),\n });\n // Still consume the stashed prompt so we don't leak files.\n takePendingPrompt(meta.conversationId, meta.generationId, homeDir);\n return null;\n }\n\n const config = loadCursorConfig(projectRoot);\n if (!config) {\n debugLog(\"config-load-failed\", { projectRoot });\n takePendingPrompt(meta.conversationId, meta.generationId, homeDir);\n return null;\n }\n\n const stashed = takePendingPrompt(\n meta.conversationId,\n meta.generationId,\n homeDir,\n );\n\n const responseText = extractResponseText(payload);\n const promptText = stashed?.prompt ?? extractPromptText(payload);\n const userEmail = meta.userEmail ?? stashed?.userEmail;\n const model = meta.model ?? stashed?.model;\n const cursorVersion = meta.cursorVersion ?? stashed?.cursorVersion;\n const workspaceRoots = meta.workspaceRoots ?? stashed?.workspaceRoots;\n const transcriptPath = meta.transcriptPath ?? stashed?.transcriptPath;\n const attachments =\n extractAttachments(payload) ?? stashed?.attachments;\n const tokens = extractCursorTokens(payload);\n const unknownFields = mergeUnknownFields(\n collectUnknownFields(payload),\n stashed?.extra,\n );\n\n const built = buildPairedPayload(\n {\n conversationId: meta.conversationId,\n generationId: meta.generationId ?? stashed?.generationId,\n prompt: promptText,\n response: responseText,\n userEmail,\n model,\n cursorVersion,\n workspaceRoots,\n transcriptPath,\n attachments,\n inputTokens: tokens.inputTokens,\n outputTokens: tokens.outputTokens,\n cacheReadTokens: tokens.cacheReadTokens,\n cacheWriteTokens: tokens.cacheWriteTokens,\n unknownFields,\n },\n config,\n );\n\n // First-class identity: surface user_email at the top level so\n // the backend can populate the event's email field directly.\n // The canonical MonitoringPayload doesn't include `email`, but\n // the existing /api/monitoring/prompt accepts extra top-level\n // keys — set it here as a non-typed extension.\n const finalPayload = userEmail\n ? ({ ...built, email: userEmail } as typeof built & { email: string })\n : built;\n\n debugLog(\"payload-built\", finalPayload);\n\n return {\n payload: finalPayload,\n transport: {\n endpoint: config.monitoringEndpoint,\n apiKey: config.apiKey,\n projectRoot,\n },\n };\n }\n\n case \"sessionEnd\":\n case \"stop\": {\n // Flush any orphan prompts for the ending conversation. We can\n // emit at most one orphan from a single hook invocation (the\n // dispatcher collects one HookResult per call); pick the orphan\n // matching this conversationId if any, otherwise the oldest.\n const orphan = pickOrphanForFlush(meta.conversationId, homeDir);\n if (!orphan) return null;\n\n const projectRoot = resolveCursorProjectRoot(\n // Synthesize a payload-like for resolution — orphan carries\n // the workspace_roots from the stashed beforeSubmitPrompt.\n {\n workspace_roots: orphan.workspaceRoots,\n } as CursorHookPayload,\n opts.projectRoot ?? process.cwd(),\n );\n if (!projectRoot) {\n debugLog(\"orphan-config-not-found\", {\n conversationId: orphan.conversationId,\n });\n clearPendingPrompt(\n orphan.conversationId,\n orphan.generationId,\n homeDir,\n );\n return null;\n }\n\n const config = loadCursorConfig(projectRoot);\n if (!config) {\n debugLog(\"orphan-config-load-failed\", { projectRoot });\n clearPendingPrompt(\n orphan.conversationId,\n orphan.generationId,\n homeDir,\n );\n return null;\n }\n\n // Remove the stash file now that we're committing to flush.\n clearPendingPrompt(\n orphan.conversationId,\n orphan.generationId,\n homeDir,\n );\n\n // Orphan tokens stay at 0: Cursor's `beforeSubmitPrompt` payload\n // (the only source for orphan flushes) does not carry the\n // input_tokens/output_tokens fields — those only appear on\n // `afterAgentResponse`. `buildPairedPayload` will coerce missing\n // token inputs to 0 via `asNumber`.\n const built = buildOrphanPayload(\n {\n conversationId: orphan.conversationId,\n generationId: orphan.generationId,\n prompt: orphan.prompt,\n response: \"\",\n userEmail: orphan.userEmail,\n model: orphan.model,\n cursorVersion: orphan.cursorVersion,\n workspaceRoots: orphan.workspaceRoots,\n transcriptPath: orphan.transcriptPath,\n attachments: orphan.attachments,\n unknownFields: orphan.extra,\n },\n config,\n event === \"sessionEnd\" ? \"session-end\" : \"orphan-prompt\",\n );\n\n const finalPayload = orphan.userEmail\n ? ({ ...built, email: orphan.userEmail } as typeof built & {\n email: string;\n })\n : built;\n\n return {\n payload: finalPayload,\n transport: {\n endpoint: config.monitoringEndpoint,\n apiKey: config.apiKey,\n projectRoot,\n },\n };\n }\n }\n}\n\nfunction mergeUnknownFields(\n primary: Record<string, unknown> | undefined,\n fallback: Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (!primary && !fallback) return undefined;\n return { ...(fallback ?? {}), ...(primary ?? {}) };\n}\n\nfunction pickOrphanForFlush(\n conversationId: string | undefined,\n homeDir: string,\n): ReturnType<typeof listPendingPrompts>[number] | null {\n const pending = listPendingPrompts(homeDir);\n if (pending.length === 0) return null;\n\n if (conversationId) {\n const match = pending.find((p) => p.conversationId === conversationId);\n if (match) return match;\n }\n // No conversationId in the sessionEnd payload, or no match — flush\n // the oldest orphan as a best-effort.\n return pending.sort((a, b) =>\n (a.stashedAt || \"\").localeCompare(b.stashedAt || \"\"),\n )[0];\n}\n\nconst cursorPlugin: ToolMonitorPlugin = {\n id: TOOL_ID,\n displayName: \"Cursor\",\n\n install(opts: InstallOpts): Promise<InstallResult> {\n return installCursor(opts);\n },\n\n uninstall(opts: UninstallOpts): Promise<void> {\n return uninstallCursor(opts);\n },\n\n status(opts): Promise<StatusReport> {\n return getCursorStatus(opts);\n },\n\n handleHook(eventName, payloadJson, opts): Promise<HookResult | null> {\n return handleCursorHook(eventName, payloadJson, opts);\n },\n\n async detectInstalled(): Promise<boolean> {\n try {\n // `~/.cursor/` exists for any installed Cursor user (it's where\n // Cursor stores per-user state). `which cursor` is a fallback\n // for portable installs that don't write to ~/.cursor yet.\n if (fs.existsSync(getCursorUserDir())) return true;\n return whichCursorOnPath();\n } catch {\n return false;\n }\n },\n};\n\nfunction whichCursorOnPath(): boolean {\n const pathEnv = process.env.PATH;\n if (!pathEnv) return false;\n const sep = process.platform === \"win32\" ? \";\" : \":\";\n const exts =\n process.platform === \"win32\"\n ? (process.env.PATHEXT ?? \".EXE;.CMD;.BAT\").split(\";\")\n : [\"\"];\n for (const dir of pathEnv.split(sep)) {\n for (const ext of exts) {\n const candidate = `${dir}/cursor${ext.toLowerCase()}`;\n try {\n if (fs.existsSync(candidate)) return true;\n } catch {\n // ignore\n }\n }\n }\n return false;\n}\n\nregisterPlugin(cursorPlugin);\n\nexport default cursorPlugin;\nexport {\n buildPairedPayload,\n buildOrphanPayload,\n extractCursorMeta,\n extractPromptText,\n extractResponseText,\n isCursorEventName,\n normalizeEventName,\n type CursorHookPayload,\n} from \"./hook.js\";\nexport {\n mergeCursorHooks,\n removeOlakaiCursorHooks,\n hasOlakaiCursorHooks,\n type CursorHookEntry,\n type CursorHooksConfig,\n} from \"./hooks-config.js\";\nexport type { CursorMonitorConfig } from \"./config.js\";\nexport {\n stashPendingPrompt,\n takePendingPrompt,\n listPendingPrompts,\n clearPendingPrompt,\n} from \"./pairing-state.js\";\n","/**\n * Cursor install/uninstall flows. Per D-004, this stage installs hooks\n * to the per-user path only (`~/.cursor/hooks.json`); system-wide\n * enterprise install is deferred to Stage 4.5.\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { type Agent } from \"../../../lib/api.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport type {\n InstallOpts,\n InstallResult,\n UninstallOpts,\n} from \"../../plugin.js\";\nimport { OLAKAI_DIR } from \"../../paths.js\";\nimport { provisionSelfMonitorAgent } from \"../../self-monitor-provision.js\";\nimport {\n getCursorConfigPath,\n writeCursorConfig,\n deleteCursorConfig,\n type CursorMonitorConfig,\n} from \"./config.js\";\nimport {\n CURSOR_DIR_NAME,\n CURSOR_HOOKS_FILE,\n getCursorHooksPath,\n getCursorUserDir,\n} from \"./paths.js\";\nimport {\n mergeCursorHooks,\n removeOlakaiCursorHooks,\n type CursorHooksConfig,\n} from \"./hooks-config.js\";\n\nconst CURSOR_SOURCE = \"cursor\";\nconst CURSOR_AGENT_SOURCE = \"CURSOR\";\nconst CURSOR_AGENT_CATEGORY = \"CODING\";\n\n/**\n * Read `~/.cursor/hooks.json` (or any JSON file) tolerantly. Returns\n * null on missing file, malformed JSON, or read error — install logic\n * treats null as \"start from scratch\" and writes a fresh config.\n */\nfunction readJsonFileTolerant<T>(filePath: string): T | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nfunction writeJsonFileWithDir(filePath: string, data: unknown): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n\nexport interface InstallCursorOpts extends InstallOpts {\n /** Override `~` for tests. Production callers leave this undefined. */\n homeDir?: string;\n}\n\nexport async function installCursor(\n opts: InstallCursorOpts,\n): Promise<InstallResult> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n const homeDir = opts.homeDir ?? os.homedir();\n\n const token = getValidToken();\n if (!token) {\n console.error(\"Not logged in. Run 'olakai login' first.\");\n process.exit(1);\n }\n\n console.log(\"Setting up Cursor monitoring for this workspace...\\n\");\n console.log(\n \"Note: Cursor hooks are installed globally per user (~/.cursor/hooks.json).\",\n );\n console.log(\n `Activity from this workspace (${projectRoot}) will be associated with`,\n );\n console.log(\"the agent you select below.\\n\");\n\n // Provision (or reuse) the self-monitor agent — same shared helper\n // as claude-code/codex.\n const agent: Agent = await provisionSelfMonitorAgent({\n projectRoot,\n source: CURSOR_AGENT_SOURCE,\n displayName: \"Cursor\",\n category: CURSOR_AGENT_CATEGORY,\n });\n\n const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;\n\n const apiKey = agent.apiKey?.key;\n if (!apiKey) {\n console.error(\n \"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'.\",\n );\n process.exit(1);\n }\n\n // Per-user hooks config\n const cursorDir = getCursorUserDir(homeDir);\n if (!fs.existsSync(cursorDir)) {\n fs.mkdirSync(cursorDir, { recursive: true });\n }\n\n const hooksPath = getCursorHooksPath(homeDir);\n const existingHooks = readJsonFileTolerant<CursorHooksConfig>(hooksPath);\n const mergedHooks = mergeCursorHooks(existingHooks);\n writeJsonFileWithDir(hooksPath, mergedHooks);\n\n // Per-project monitor config\n const monitorConfig: CursorMonitorConfig = {\n agentId: agent.id,\n apiKey,\n agentName: agent.name,\n source: CURSOR_SOURCE,\n createdAt: new Date().toISOString(),\n monitoringEndpoint,\n };\n writeCursorConfig(projectRoot, monitorConfig);\n\n const configPath = getCursorConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n const hooksDisplay = `~/${path.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`;\n\n console.log(\"\");\n console.log(`✓ Agent \"${agent.name}\" configured (ID: ${agent.id})`);\n if (agent.apiKey?.key) {\n console.log(\"✓ API key generated\");\n }\n console.log(`✓ Cursor hooks installed at ${hooksDisplay}`);\n console.log(`✓ Monitor config saved to ${configRel}`);\n console.log(\"\");\n console.log(\n \"Congrats! Monitoring is now active. Restart Cursor for the new hooks to take effect.\",\n );\n console.log(\"\");\n console.log(\n `⚠ Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key)`,\n );\n console.log(\"\");\n console.log(\"To check status: olakai monitor status --tool cursor\");\n console.log(\"To disable: olakai monitor disable --tool cursor\");\n\n return {\n agentId: agent.id,\n agentName: agent.name,\n source: CURSOR_SOURCE,\n monitoringEndpoint,\n };\n}\n\nexport interface UninstallCursorOpts extends UninstallOpts {\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\nexport async function uninstallCursor(\n opts: UninstallCursorOpts,\n): Promise<void> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n const homeDir = opts.homeDir ?? os.homedir();\n\n const hooksPath = getCursorHooksPath(homeDir);\n const existingHooks = readJsonFileTolerant<CursorHooksConfig>(hooksPath);\n if (existingHooks) {\n const cleaned = removeOlakaiCursorHooks(existingHooks);\n if (!cleaned.hooks || Object.keys(cleaned.hooks).length === 0) {\n // No hooks left at all. If the file only contained Olakai entries\n // and nothing else (no version override, no extra keys), remove\n // it entirely so we don't leave a dead `{}` behind.\n const otherKeys = Object.keys(cleaned).filter(\n (k) => k !== \"hooks\" && k !== \"version\",\n );\n const onlyDefaults =\n otherKeys.length === 0 &&\n (cleaned.version === 1 || cleaned.version === undefined);\n if (onlyDefaults) {\n try {\n fs.unlinkSync(hooksPath);\n } catch {\n // best-effort\n }\n } else {\n writeJsonFileWithDir(hooksPath, cleaned);\n }\n } else {\n writeJsonFileWithDir(hooksPath, cleaned);\n }\n console.log(\n `✓ Olakai hooks removed from ~/${path.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`,\n );\n } else {\n console.log(\"No Cursor hooks file found.\");\n }\n\n if (!opts.keepConfig) {\n const configPath = getCursorConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n if (deleteCursorConfig(projectRoot)) {\n console.log(`✓ Monitor config removed (${configRel})`);\n }\n } else {\n const configPath = getCursorConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n console.log(`Monitor config retained at ${configRel}`);\n }\n\n console.log(\"\");\n console.log(\n \"Monitoring disabled. Run 'olakai monitor init --tool cursor' to re-enable.\",\n );\n console.log(\"Restart Cursor for the change to take effect.\");\n}\n","/**\n * Per-tool monitor config storage for Cursor. Lives at\n * `.olakai/monitor-cursor.json` in the configured project root.\n *\n * Cursor's hooks config is global per user (`~/.cursor/hooks.json`),\n * but the per-project config (which agent + apiKey to associate with\n * events from this workspace) lives under the project root just like\n * Claude Code's. The hook adapter walks ancestors of `workspace_roots`\n * to find the configured workspace.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n getMonitorConfigPath as resolveMonitorConfigPath,\n getOlakaiDir,\n} from \"../../paths.js\";\n\nexport interface CursorMonitorConfig {\n agentId: string;\n apiKey: string;\n agentName: string;\n source: string;\n createdAt: string;\n monitoringEndpoint: string;\n}\n\nexport function getCursorConfigPath(projectRoot: string): string {\n return resolveMonitorConfigPath(projectRoot, \"cursor\");\n}\n\nexport function loadCursorConfig(\n projectRoot: string,\n): CursorMonitorConfig | null {\n const filePath = getCursorConfigPath(projectRoot);\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw) as CursorMonitorConfig;\n } catch {\n return null;\n }\n}\n\nexport function writeCursorConfig(\n projectRoot: string,\n config: CursorMonitorConfig,\n): void {\n const filePath = getCursorConfigPath(projectRoot);\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // chmod failures are non-fatal — Windows / unusual filesystems\n }\n}\n\nexport function deleteCursorConfig(projectRoot: string): boolean {\n const filePath = getCursorConfigPath(projectRoot);\n if (!fs.existsSync(filePath)) return false;\n try {\n fs.unlinkSync(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getOlakaiConfigDir(projectRoot: string): string {\n return getOlakaiDir(projectRoot);\n}\n","/**\n * Cursor-specific path helpers. Cursor hooks are configured globally\n * per user at `~/.cursor/hooks.json` (per D-004; system-wide enterprise\n * install is deferred to Stage 4.5).\n */\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nexport const CURSOR_DIR_NAME = \".cursor\";\nexport const CURSOR_HOOKS_FILE = \"hooks.json\";\n\n/**\n * Per-user Cursor directory (`~/.cursor`). `homeDir` override exists\n * for tests so we can point at a tmp dir without touching the real\n * user home.\n */\nexport function getCursorUserDir(homeDir: string = os.homedir()): string {\n return path.join(homeDir, CURSOR_DIR_NAME);\n}\n\n/**\n * Per-user Cursor hooks config (`~/.cursor/hooks.json`).\n */\nexport function getCursorHooksPath(homeDir: string = os.homedir()): string {\n return path.join(getCursorUserDir(homeDir), CURSOR_HOOKS_FILE);\n}\n","/**\n * Cursor `~/.cursor/hooks.json` merge helpers.\n *\n * Pure functions (no filesystem access) so they can be unit-tested\n * exhaustively against the merge semantics. The install/uninstall\n * dispatchers in `install.ts`/`uninstall.ts` own the actual file I/O.\n *\n * Cursor's hooks config is JSON, with each event name mapping to an\n * array of hook entries: `{ command: string, timeout?: number }`. The\n * shape mirrors Cursor's published examples at\n * https://cursor.com/docs/hooks (still beta as of v1.7+, expect drift).\n */\n\n/**\n * Substring used to identify Olakai-installed hook commands inside an\n * existing `~/.cursor/hooks.json`. Every Olakai hook command starts\n * with `olakai monitor hook --tool cursor`, so the marker is sufficient\n * for both install-side dedup and uninstall-side filtering.\n */\nexport const OLAKAI_HOOK_MARKER = \"olakai monitor hook --tool cursor\";\n\n/**\n * One hook entry as Cursor expects it. Other vendor-supported fields\n * (e.g. `script`, `cwd`) are tolerated on read but not produced by us;\n * preserved verbatim by the merge logic via the catch-all index signature.\n */\nexport interface CursorHookEntry {\n command: string;\n timeout?: number;\n [extra: string]: unknown;\n}\n\n/**\n * Top-level shape of `~/.cursor/hooks.json`. `version` is recommended\n * by Cursor docs; we set it on first write and preserve whatever value\n * the user already had on subsequent writes.\n */\nexport interface CursorHooksConfig {\n version?: number;\n hooks?: Record<string, CursorHookEntry[]>;\n [extra: string]: unknown;\n}\n\n/**\n * Hook events we register. `beforeSubmitPrompt` and `afterAgentResponse`\n * give us the prompt/response pair; `sessionEnd` and `stop` flush any\n * orphan prompts. Tab hooks and tool-use hooks deferred per the Stage 4\n * design decisions.\n */\nexport const CURSOR_HOOK_DEFINITIONS: Record<string, CursorHookEntry[]> = {\n beforeSubmitPrompt: [\n {\n command: \"olakai monitor hook --tool cursor beforeSubmitPrompt\",\n timeout: 5,\n },\n ],\n afterAgentResponse: [\n {\n command: \"olakai monitor hook --tool cursor afterAgentResponse\",\n timeout: 5,\n },\n ],\n sessionEnd: [\n {\n command: \"olakai monitor hook --tool cursor sessionEnd\",\n timeout: 5,\n },\n ],\n stop: [\n {\n command: \"olakai monitor hook --tool cursor stop\",\n timeout: 5,\n },\n ],\n};\n\n/**\n * Default version field written on first install. Matches the schema\n * version Cursor docs use in their examples.\n */\nexport const CURSOR_HOOKS_VERSION = 1;\n\n/**\n * Merge our hook entries into an existing config. For each event in\n * `definitions`:\n * - If the existing array already contains an Olakai hook (entry\n * whose `command` includes `OLAKAI_HOOK_MARKER`), leave the array\n * untouched.\n * - Otherwise, append our entries to whatever the user had.\n *\n * Other top-level keys and other event arrays are preserved verbatim.\n * Returns a new config object — never mutates the input.\n */\nexport function mergeCursorHooks(\n existing: CursorHooksConfig | null | undefined,\n definitions: Record<string, CursorHookEntry[]> = CURSOR_HOOK_DEFINITIONS,\n): CursorHooksConfig {\n const base: CursorHooksConfig = existing ? { ...existing } : {};\n if (typeof base.version !== \"number\") {\n base.version = CURSOR_HOOKS_VERSION;\n }\n\n const mergedHooks: Record<string, CursorHookEntry[]> = {\n ...(base.hooks ?? {}),\n };\n\n for (const [event, defaultEntries] of Object.entries(definitions)) {\n const existingEntries = mergedHooks[event] ?? [];\n const hasOlakaiEntry = existingEntries.some((e) =>\n typeof e?.command === \"string\" && e.command.includes(OLAKAI_HOOK_MARKER),\n );\n if (hasOlakaiEntry) {\n mergedHooks[event] = existingEntries;\n } else {\n mergedHooks[event] = [...existingEntries, ...defaultEntries];\n }\n }\n\n return {\n ...base,\n hooks: mergedHooks,\n };\n}\n\n/**\n * Inverse of merge — strip Olakai-installed entries from an existing\n * config. Events whose array becomes empty after filtering are removed\n * from `hooks`. If `hooks` itself is empty after filtering, it is\n * removed. Other top-level keys are preserved verbatim.\n */\nexport function removeOlakaiCursorHooks(\n existing: CursorHooksConfig | null | undefined,\n): CursorHooksConfig {\n const base: CursorHooksConfig = existing ? { ...existing } : {};\n const sourceHooks = base.hooks ?? {};\n const cleaned: Record<string, CursorHookEntry[]> = {};\n\n for (const [event, entries] of Object.entries(sourceHooks)) {\n const filtered = entries.filter(\n (e) =>\n typeof e?.command !== \"string\" ||\n !e.command.includes(OLAKAI_HOOK_MARKER),\n );\n if (filtered.length > 0) {\n cleaned[event] = filtered;\n }\n }\n\n if (Object.keys(cleaned).length > 0) {\n base.hooks = cleaned;\n } else {\n delete base.hooks;\n }\n return base;\n}\n\n/**\n * Convenience: does this config currently have any Olakai-installed\n * hook entries? Used by status reporting.\n */\nexport function hasOlakaiCursorHooks(\n config: CursorHooksConfig | null | undefined,\n): boolean {\n if (!config?.hooks) return false;\n return Object.values(config.hooks).some((entries) =>\n entries.some(\n (e) =>\n typeof e?.command === \"string\" && e.command.includes(OLAKAI_HOOK_MARKER),\n ),\n );\n}\n","import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { StatusReport } from \"../../plugin.js\";\nimport { CURSOR_DIR_NAME, CURSOR_HOOKS_FILE, getCursorHooksPath } from \"./paths.js\";\nimport {\n hasOlakaiCursorHooks,\n type CursorHooksConfig,\n} from \"./hooks-config.js\";\nimport { getCursorConfigPath, loadCursorConfig } from \"./config.js\";\n\nexport interface CursorStatusOpts {\n projectRoot?: string;\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\nexport async function getCursorStatus(\n opts?: CursorStatusOpts,\n): Promise<StatusReport> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const homeDir = opts?.homeDir ?? os.homedir();\n\n const configPath = getCursorConfigPath(projectRoot);\n const config = loadCursorConfig(projectRoot);\n\n let hooksConfigured = false;\n const hooksPath = getCursorHooksPath(homeDir);\n if (fs.existsSync(hooksPath)) {\n try {\n const raw = fs.readFileSync(hooksPath, \"utf-8\");\n const parsed = JSON.parse(raw) as CursorHooksConfig;\n hooksConfigured = hasOlakaiCursorHooks(parsed);\n } catch {\n // Treat unreadable hooks file as not-configured.\n }\n }\n\n if (!config) {\n return {\n toolId: \"cursor\",\n configured: false,\n hooksConfigured,\n configPath,\n notes: hooksConfigured\n ? [\n \"Cursor hooks installed but no monitor config in this workspace — run 'olakai monitor init --tool cursor' to associate it with an agent.\",\n ]\n : [],\n };\n }\n\n return {\n toolId: \"cursor\",\n configured: true,\n hooksConfigured,\n agentId: config.agentId,\n agentName: config.agentName,\n source: config.source,\n apiKeyMasked: config.apiKey.slice(0, 12) + \"...\",\n monitoringEndpoint: config.monitoringEndpoint,\n configuredAt: config.createdAt,\n configPath,\n };\n}\n\nexport async function printCursorStatus(opts?: CursorStatusOpts): Promise<void> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const status = await getCursorStatus(opts);\n\n if (!status.configured) {\n console.log(\"Monitoring is not configured for this workspace.\");\n if (status.hooksConfigured) {\n console.log(\n \"(Cursor hooks ARE installed globally — run init to associate this workspace with an agent.)\",\n );\n }\n console.log(\n \"Run 'olakai monitor init --tool cursor' to set up monitoring.\",\n );\n process.exit(1);\n }\n\n const configRel = status.configPath\n ? path.relative(projectRoot, status.configPath)\n : \"(unknown)\";\n const hooksDisplay = `~/${path.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`;\n\n console.log(\"Olakai Monitor Status (Cursor)\");\n console.log(\"==============================\");\n console.log(`Agent: ${status.agentName}`);\n console.log(`Agent ID: ${status.agentId}`);\n console.log(`API Key: ${status.apiKeyMasked}`);\n console.log(`Endpoint: ${status.monitoringEndpoint}`);\n console.log(`Source: ${status.source}`);\n console.log(`Configured: ${status.configuredAt}`);\n console.log(`Config file: ${configRel}`);\n console.log(\n `Hooks: ${\n status.hooksConfigured\n ? `Active (${hooksDisplay})`\n : `Missing (re-run 'olakai monitor init --tool cursor')`\n }`,\n );\n}\n","/**\n * Cursor hook adapter. Translates beta Cursor hook payloads into our\n * canonical MonitoringPayload, pairing `beforeSubmitPrompt` and\n * `afterAgentResponse` across separate CLI invocations via the\n * pairing-state sidecar.\n *\n * Defensive parsing: Cursor hooks shipped in v1.7 (Oct 2025) and the\n * schema is still beta. Every field access is guarded; unknown fields\n * are preserved in a debug bag rather than crashing.\n */\nimport type { MonitoringPayload } from \"../../plugin.js\";\nimport type { CursorMonitorConfig } from \"./config.js\";\n\nexport type CursorEventName =\n | \"beforeSubmitPrompt\"\n | \"afterAgentResponse\"\n | \"sessionEnd\"\n | \"stop\";\n\nexport const SUPPORTED_CURSOR_EVENTS = new Set<string>([\n \"beforeSubmitPrompt\",\n \"afterAgentResponse\",\n \"sessionEnd\",\n \"stop\",\n]);\n\n/**\n * Subset of the Cursor hook payload we care about. Documented fields\n * (per https://cursor.com/docs/hooks as of Apr 2026): `conversation_id`,\n * `generation_id`, `model`, `cursor_version`, `workspace_roots`,\n * `user_email`, `transcript_path`. Per-event fields like `prompt`,\n * `response`, `text`, and `attachments` vary across versions:\n * - `prompt` (beforeSubmitPrompt): top-level string, or nested\n * `{ text, attachments }` envelope (older betas).\n * - `response` (afterAgentResponse v1.7): top-level string or\n * `{ text }` envelope.\n * - `text` (afterAgentResponse v3.x): top-level string. Confirmed\n * against a real Cursor v3.2.11 staging payload — replaces the\n * v1.7 `response` field.\n * - `input_tokens` / `output_tokens` / `cache_read_tokens` /\n * `cache_write_tokens` (afterAgentResponse v3.x): per-turn token\n * counts. Not present on beforeSubmitPrompt.\n */\nexport interface CursorHookPayload {\n event?: string;\n conversation_id?: string;\n generation_id?: string;\n model?: string;\n cursor_version?: string;\n workspace_roots?: string[];\n user_email?: string;\n transcript_path?: string;\n prompt?: string | { text?: string; attachments?: unknown[] };\n response?: string | { text?: string };\n /** v3.x top-level response text (replaces `response`). */\n text?: string;\n attachments?: unknown[];\n /** v3.x per-turn token counts (afterAgentResponse only). */\n input_tokens?: number;\n output_tokens?: number;\n cache_read_tokens?: number;\n cache_write_tokens?: number;\n [extra: string]: unknown;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\n/**\n * Defensive numeric coercion. Treats `undefined`, `null`, non-numeric\n * types, and `NaN` as 0. Used for the v3.x token fields, which may be\n * missing on older Cursor builds and absent entirely on\n * `beforeSubmitPrompt`.\n */\nexport function asNumber(value: unknown): number {\n if (typeof value !== \"number\") return 0;\n if (!Number.isFinite(value)) return 0;\n return value;\n}\n\n/**\n * Extracted per-turn token breakdown from a Cursor v3.x\n * `afterAgentResponse` payload. Each field defaults to 0 when missing.\n */\nexport interface ExtractedCursorTokens {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n}\n\nexport function extractCursorTokens(\n payload: CursorHookPayload,\n): ExtractedCursorTokens {\n return {\n inputTokens: asNumber(payload.input_tokens),\n outputTokens: asNumber(payload.output_tokens),\n cacheReadTokens: asNumber(payload.cache_read_tokens),\n cacheWriteTokens: asNumber(payload.cache_write_tokens),\n };\n}\n\nfunction asStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const out: string[] = [];\n for (const item of value) {\n if (typeof item === \"string\" && item.trim()) out.push(item);\n }\n return out.length > 0 ? out : undefined;\n}\n\n/**\n * Pull the prompt text out of either a top-level string field or a\n * nested envelope. Returns \"\" when neither is present so callers can\n * stash an empty placeholder rather than a malformed entry.\n */\nexport function extractPromptText(payload: CursorHookPayload): string {\n if (typeof payload.prompt === \"string\") return payload.prompt;\n if (\n payload.prompt &&\n typeof payload.prompt === \"object\" &&\n typeof (payload.prompt as { text?: unknown }).text === \"string\"\n ) {\n return (payload.prompt as { text: string }).text;\n }\n return \"\";\n}\n\n/**\n * Pull the response text from one of three known shapes, in order of\n * preference:\n * 1. `payload.response` (string) — older beta builds.\n * 2. `payload.response.text` (object envelope) — older beta builds.\n * 3. `payload.text` (top-level string) — Cursor v3.x format,\n * confirmed against a real v3.2.11 payload. This is the current\n * production shape.\n * Returns \"\" when none match so callers can stash an empty placeholder\n * rather than a malformed entry.\n */\nexport function extractResponseText(payload: CursorHookPayload): string {\n if (typeof payload.response === \"string\") return payload.response;\n if (\n payload.response &&\n typeof payload.response === \"object\" &&\n typeof (payload.response as { text?: unknown }).text === \"string\"\n ) {\n return (payload.response as { text: string }).text;\n }\n if (typeof payload.text === \"string\") return payload.text;\n return \"\";\n}\n\nexport function extractAttachments(\n payload: CursorHookPayload,\n): unknown[] | undefined {\n if (Array.isArray(payload.attachments)) return payload.attachments;\n if (\n payload.prompt &&\n typeof payload.prompt === \"object\" &&\n Array.isArray((payload.prompt as { attachments?: unknown[] }).attachments)\n ) {\n return (payload.prompt as { attachments: unknown[] }).attachments;\n }\n return undefined;\n}\n\n/**\n * Inputs to `buildPairedPayload`. Pulled out as a struct to keep the\n * call-site at the plugin readable.\n */\nexport interface PairedPayloadInputs {\n conversationId: string;\n generationId?: string;\n prompt: string;\n response: string;\n userEmail?: string;\n model?: string;\n cursorVersion?: string;\n workspaceRoots?: string[];\n transcriptPath?: string;\n attachments?: unknown[];\n /**\n * Per-turn token counts from Cursor v3.x `afterAgentResponse`. All\n * default to 0 when absent (e.g. older builds, orphan flushes). The\n * canonical `tokens` field on the MonitoringPayload is computed as\n * `inputTokens + outputTokens` (mirrors the Codex plugin convention\n * — cache_read tokens are typically billed at a different rate so\n * we keep them in customData rather than rolling them into the\n * headline number).\n */\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n /** Free-form bag — surfaced into customData for forensic value. */\n unknownFields?: Record<string, unknown>;\n}\n\n/**\n * Build a canonical MonitoringPayload from a paired prompt/response.\n * Pure — exported for unit tests.\n */\nexport function buildPairedPayload(\n inputs: PairedPayloadInputs,\n config: CursorMonitorConfig,\n): MonitoringPayload {\n const customData: Record<string, unknown> = {\n hookEvent: \"afterAgentResponse\",\n conversationId: inputs.conversationId,\n };\n if (inputs.generationId) customData.generationId = inputs.generationId;\n if (inputs.cursorVersion) customData.cursorVersion = inputs.cursorVersion;\n if (inputs.workspaceRoots) customData.workspaceRoots = inputs.workspaceRoots;\n if (inputs.transcriptPath) customData.transcriptPath = inputs.transcriptPath;\n if (inputs.attachments && inputs.attachments.length > 0) {\n customData.attachmentCount = inputs.attachments.length;\n }\n if (inputs.userEmail) customData.userEmail = inputs.userEmail;\n\n // Token breakdown — surface even when zero so downstream consumers\n // see a stable shape and can distinguish \"no tokens reported\" from\n // \"field absent\". The aggregate `tokens` is what drives cost KPIs.\n const inputTokens = asNumber(inputs.inputTokens);\n const outputTokens = asNumber(inputs.outputTokens);\n const cacheReadTokens = asNumber(inputs.cacheReadTokens);\n const cacheWriteTokens = asNumber(inputs.cacheWriteTokens);\n customData.inputTokens = inputTokens;\n customData.outputTokens = outputTokens;\n customData.cacheReadTokens = cacheReadTokens;\n customData.cacheWriteTokens = cacheWriteTokens;\n\n if (\n inputs.unknownFields &&\n Object.keys(inputs.unknownFields).length > 0\n ) {\n customData.unknownPayloadFields = inputs.unknownFields;\n }\n\n return {\n prompt: inputs.prompt,\n response: inputs.response,\n chatId: inputs.conversationId,\n source: config.source,\n modelName: inputs.model,\n tokens: inputTokens + outputTokens,\n customData,\n };\n}\n\n/**\n * Build a partial-orphan MonitoringPayload — emitted by `sessionEnd`\n * or `stop` when we have a stashed prompt that never received a paired\n * response. Response is the empty string (the canonical payload\n * requires a string), and customData carries `partial: true` plus\n * `partialReason: \"orphan-prompt\"` so the backend can flag it.\n */\nexport function buildOrphanPayload(\n inputs: PairedPayloadInputs,\n config: CursorMonitorConfig,\n reason: \"orphan-prompt\" | \"session-end\" = \"orphan-prompt\",\n): MonitoringPayload {\n const base = buildPairedPayload({ ...inputs, response: \"\" }, config);\n return {\n ...base,\n customData: {\n ...base.customData,\n hookEvent: \"sessionEnd\",\n partial: true,\n partialReason: reason,\n },\n };\n}\n\n/**\n * Identify keys present in the payload that aren't part of the\n * documented schema. Used to populate `unknownPayloadFields` so we\n * can spot beta-schema drift in production.\n */\nconst KNOWN_PAYLOAD_KEYS = new Set([\n \"event\",\n \"conversation_id\",\n \"generation_id\",\n \"model\",\n \"cursor_version\",\n \"workspace_roots\",\n \"user_email\",\n \"transcript_path\",\n \"prompt\",\n \"response\",\n \"text\",\n \"attachments\",\n \"input_tokens\",\n \"output_tokens\",\n \"cache_read_tokens\",\n \"cache_write_tokens\",\n]);\n\nexport function collectUnknownFields(\n payload: CursorHookPayload,\n): Record<string, unknown> | undefined {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(payload)) {\n if (!KNOWN_PAYLOAD_KEYS.has(key)) {\n out[key] = payload[key];\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Normalize Cursor's documented event names to lowercase comparison.\n * Cursor docs use camelCase (`beforeSubmitPrompt`); we keep that as\n * the canonical string but accept any-case input from callers (the\n * dispatcher passes the literal CLI argument through).\n */\nexport function normalizeEventName(event: string): CursorEventName | null {\n const lower = event.trim();\n if (!lower) return null;\n const exact = lower as CursorEventName;\n if (SUPPORTED_CURSOR_EVENTS.has(exact)) return exact;\n // Accept lowercased variants (\"stop\" vs \"Stop\") — Cursor has been\n // inconsistent across beta builds. Falls back to null on no match.\n for (const known of SUPPORTED_CURSOR_EVENTS) {\n if (known.toLowerCase() === lower.toLowerCase()) return known as CursorEventName;\n }\n return null;\n}\n\nexport function isCursorEventName(value: string): value is CursorEventName {\n return SUPPORTED_CURSOR_EVENTS.has(value);\n}\n\n/**\n * Helpers for consumers to quickly extract the documented fields with\n * defensive guards. Returned as a struct so the plugin can pass it\n * around without re-walking the payload.\n */\nexport interface ExtractedCursorMeta {\n conversationId: string | undefined;\n generationId: string | undefined;\n model: string | undefined;\n cursorVersion: string | undefined;\n workspaceRoots: string[] | undefined;\n userEmail: string | undefined;\n transcriptPath: string | undefined;\n}\n\nexport function extractCursorMeta(\n payload: CursorHookPayload,\n): ExtractedCursorMeta {\n return {\n conversationId: asString(payload.conversation_id),\n generationId: asString(payload.generation_id),\n model: asString(payload.model),\n cursorVersion: asString(payload.cursor_version),\n workspaceRoots: asStringArray(payload.workspace_roots),\n userEmail: asString(payload.user_email),\n transcriptPath: asString(payload.transcript_path),\n };\n}\n","/**\n * Cross-invocation pairing state for Cursor's split prompt/response\n * hooks. `beforeSubmitPrompt` and `afterAgentResponse` fire as separate\n * CLI invocations — to build a single MonitoringPayload we stash the\n * prompt under the conversation+generation key on the first invocation\n * and pair it with the response on the second.\n *\n * State lives at `~/.olakai/cursor-pairings/<conversation>__<generation>.json`\n * (or `.../<conversation>.json` when generation is missing). Files are\n * removed on successful pair or session-end flush. The directory is\n * created on demand and best-effort cleaned up on flush.\n *\n * Mirrors the pattern in `commands/monitor-state.ts` (per-session dedup\n * for Claude Code) — pure w.r.t. process state, all errors swallowed,\n * `homeDir` overridable for tests.\n */\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nexport interface PendingCursorPrompt {\n prompt: string;\n userEmail?: string;\n model?: string;\n cursorVersion?: string;\n conversationId: string;\n generationId?: string;\n workspaceRoots?: string[];\n transcriptPath?: string;\n attachments?: unknown[];\n /** ISO timestamp captured at stash time. Powers orphan-flush ordering. */\n stashedAt: string;\n /** Free-form bag for additional payload-shape fields surfaced later. */\n extra?: Record<string, unknown>;\n}\n\nconst STATE_DIR_SEGMENTS = [\".olakai\", \"cursor-pairings\"];\n\nfunction getPairingsDir(homeDir: string): string {\n return path.join(homeDir, ...STATE_DIR_SEGMENTS);\n}\n\n/**\n * Filenames must round-trip safely on macOS/Linux/Windows. Cursor IDs\n * are UUIDs in practice; we still sanitize defensively in case the\n * beta schema ever loosens.\n */\nfunction sanitizeKeyFragment(value: string): string {\n return value.replace(/[^A-Za-z0-9._-]/g, \"_\");\n}\n\nfunction getPairingKey(\n conversationId: string,\n generationId: string | undefined,\n): string {\n const conv = sanitizeKeyFragment(conversationId);\n if (!generationId) return conv;\n return `${conv}__${sanitizeKeyFragment(generationId)}`;\n}\n\nfunction getPairingFile(\n conversationId: string,\n generationId: string | undefined,\n homeDir: string,\n): string {\n return path.join(\n getPairingsDir(homeDir),\n `${getPairingKey(conversationId, generationId)}.json`,\n );\n}\n\n/**\n * Stash a prompt for later pairing. Empty conversationId is a no-op:\n * without a key we can't pair, and emitting an unkeyed orphan would\n * race with concurrent prompts in other windows.\n */\nexport function stashPendingPrompt(\n pending: PendingCursorPrompt,\n homeDir: string = os.homedir(),\n): void {\n if (!pending.conversationId) return;\n const dir = getPairingsDir(homeDir);\n try {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n const filePath = getPairingFile(\n pending.conversationId,\n pending.generationId,\n homeDir,\n );\n fs.writeFileSync(\n filePath,\n JSON.stringify(pending, null, 2) + \"\\n\",\n \"utf-8\",\n );\n } catch {\n // Pairing-state failures must never break the host tool.\n }\n}\n\n/**\n * Load + delete a stashed prompt for the given conversation/generation\n * pair. Returns null when none is found. The delete is unconditional\n * even if the JSON parses to garbage, to avoid leaving poison files\n * behind that the orphan-flush would later trip over.\n *\n * Generation matching: when the response carries a generationId, we\n * try the (conversation+generation) key first, then fall back to the\n * conversation-only key. This handles the case where an older Cursor\n * version omits generationId on `beforeSubmitPrompt` but includes it\n * on `afterAgentResponse`.\n */\nexport function takePendingPrompt(\n conversationId: string,\n generationId: string | undefined,\n homeDir: string = os.homedir(),\n): PendingCursorPrompt | null {\n if (!conversationId) return null;\n\n const candidates: string[] = [];\n if (generationId) {\n candidates.push(getPairingFile(conversationId, generationId, homeDir));\n }\n candidates.push(getPairingFile(conversationId, undefined, homeDir));\n\n for (const filePath of candidates) {\n let raw: string;\n try {\n if (!fs.existsSync(filePath)) continue;\n raw = fs.readFileSync(filePath, \"utf-8\");\n } catch {\n continue;\n }\n try {\n fs.unlinkSync(filePath);\n } catch {\n // best-effort cleanup\n }\n try {\n const parsed = JSON.parse(raw) as PendingCursorPrompt;\n if (parsed && typeof parsed.conversationId === \"string\") {\n return parsed;\n }\n } catch {\n // poison file — file already deleted above; skip\n }\n }\n return null;\n}\n\n/**\n * List all currently-stashed prompts. Used by `sessionEnd`/`stop` to\n * flush orphans. The caller is responsible for deciding which orphans\n * belong to the ending session (via `conversationId` match) and for\n * removing the stash files via `clearPendingPrompt` after emitting\n * the partial event.\n */\nexport function listPendingPrompts(\n homeDir: string = os.homedir(),\n): PendingCursorPrompt[] {\n const dir = getPairingsDir(homeDir);\n if (!fs.existsSync(dir)) return [];\n\n let files: string[];\n try {\n files = fs.readdirSync(dir);\n } catch {\n return [];\n }\n\n const result: PendingCursorPrompt[] = [];\n for (const name of files) {\n if (!name.endsWith(\".json\")) continue;\n const filePath = path.join(dir, name);\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const parsed = JSON.parse(raw) as PendingCursorPrompt;\n if (parsed && typeof parsed.conversationId === \"string\") {\n result.push(parsed);\n }\n } catch {\n // skip malformed entries — orphan flush is best-effort\n }\n }\n return result;\n}\n\n/**\n * Delete a single stashed-prompt file. Used after flushing an orphan\n * partial event. Non-existent files are not an error.\n */\nexport function clearPendingPrompt(\n conversationId: string,\n generationId: string | undefined,\n homeDir: string = os.homedir(),\n): void {\n if (!conversationId) return;\n const candidates: string[] = [];\n if (generationId) {\n candidates.push(getPairingFile(conversationId, generationId, homeDir));\n }\n candidates.push(getPairingFile(conversationId, undefined, homeDir));\n for (const filePath of candidates) {\n try {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n } catch {\n // best-effort\n }\n }\n}\n","/**\n * Gemini CLI plugin (D-CA-3 source = \"gemini-cli\").\n *\n * Hooks the Gemini CLI hooks API into the canonical Olakai monitoring\n * pipeline. Mirrors the existing claude-code / codex / cursor plugins'\n * split (install / uninstall / status / hook / settings / config) so\n * the dispatcher in `commands/monitor.ts` doesn't need tool-specific\n * branches.\n *\n * Hook events registered (per `settings.ts`): SessionStart, SessionEnd,\n * BeforeModel, AfterModel, BeforeTool, AfterTool. Only `AfterModel`\n * emits a MonitoringPayload — the others accumulate sidecar state that\n * the AfterModel emit consumes (see `pairing-state.ts` + `hook.ts`).\n *\n * Defensive parsing throughout: every field access on the inbound\n * payload is guarded; unrecognized event types silent-exit.\n */\nimport * as fs from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport {\n registerPlugin,\n type HookResult,\n type InstallOpts,\n type InstallResult,\n type StatusReport,\n type ToolMonitorPlugin,\n type UninstallOpts,\n} from \"../../plugin.js\";\nimport { findConfiguredWorkspace } from \"../../paths.js\";\nimport { installGeminiCli, uninstallGeminiCli } from \"./install.js\";\nimport { getGeminiCliStatus } from \"./status.js\";\nimport { handleGeminiHook, type GeminiHookEvent } from \"./hook.js\";\nimport {\n getGeminiCliConfigPath,\n loadGeminiCliConfig,\n} from \"./config.js\";\nimport {\n getGeminiHomeDir,\n getGeminiSettingsPath,\n} from \"./paths.js\";\n\nconst TOOL_ID = \"gemini-cli\" as const;\n\n/**\n * Debug logger gated by `OLAKAI_MONITOR_DEBUG=1`. Mirrors the\n * claude-code/codex/cursor debug log so users only need to look in one\n * place when diagnosing hook failures.\n */\nfunction debugLog(label: string, data: unknown): void {\n if (process.env.OLAKAI_MONITOR_DEBUG !== \"1\") return;\n try {\n const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;\n const line = `[${new Date().toISOString()}] gemini-cli/${label}: ${\n typeof data === \"string\" ? data : JSON.stringify(data, null, 2)\n }\\n`;\n fs.appendFileSync(logPath, line, \"utf-8\");\n } catch {\n // Never break the hook path on a debug-log failure.\n }\n}\n\n/**\n * Resolve the configured workspace root from a Gemini hook payload.\n * Gemini's hook schema exposes `cwd` for every event; we walk up from\n * there looking for any `.olakai/monitor-*.json` so a developer running\n * `gemini` deep inside a project's subfolder still hits their\n * workspace's monitor config.\n *\n * Exported for unit tests.\n */\nexport function resolveGeminiCliProjectRoot(\n eventData: GeminiHookEvent,\n fallbackCwd: string,\n): string | null {\n const payloadCwd =\n typeof eventData.cwd === \"string\" && eventData.cwd.trim()\n ? eventData.cwd\n : fallbackCwd;\n return findConfiguredWorkspace(payloadCwd, [TOOL_ID]);\n}\n\nconst geminiCliPlugin: ToolMonitorPlugin = {\n id: TOOL_ID,\n displayName: \"Gemini CLI\",\n\n install(opts: InstallOpts): Promise<InstallResult> {\n return installGeminiCli(opts);\n },\n\n uninstall(opts: UninstallOpts): Promise<void> {\n return uninstallGeminiCli(opts);\n },\n\n status(opts): Promise<StatusReport> {\n return getGeminiCliStatus(opts);\n },\n\n async handleHook(eventName, payloadJson): Promise<HookResult | null> {\n const eventData = (payloadJson ?? {}) as GeminiHookEvent;\n debugLog(\"hook-fired\", { eventName, hasPayload: payloadJson != null });\n\n const projectRoot = resolveGeminiCliProjectRoot(\n eventData,\n process.cwd(),\n );\n if (!projectRoot) {\n debugLog(\"config-not-found\", {\n startDir:\n typeof eventData.cwd === \"string\" && eventData.cwd.trim()\n ? eventData.cwd\n : process.cwd(),\n });\n return null;\n }\n\n const config = loadGeminiCliConfig(projectRoot);\n if (!config) {\n debugLog(\"monitor-config-missing\", { projectRoot });\n return null;\n }\n\n const payload = handleGeminiHook(eventName, eventData, config, {\n debugLog,\n });\n if (!payload) return null;\n\n return {\n payload,\n transport: {\n endpoint: config.monitoringEndpoint,\n apiKey: config.apiKey,\n projectRoot,\n },\n };\n },\n\n async detectInstalled(opts): Promise<boolean> {\n // Two independent signals — either is enough to surface \"gemini-cli\"\n // in the interactive `init` selector:\n // 1. The user ran `olakai monitor init --tool gemini-cli` here\n // (workspace-local monitor config exists), OR\n // 2. The Gemini CLI itself is installed on this host (binary on\n // PATH or `~/.gemini/settings.json` present).\n //\n // The PATH probe uses `which` synchronously through\n // `child_process.spawnSync` to avoid shelling out — it's cheap\n // (<10ms) and any failure is treated as \"not installed\".\n const projectRoot = opts?.projectRoot ?? process.cwd();\n try {\n if (fs.existsSync(getGeminiCliConfigPath(projectRoot))) {\n return true;\n }\n } catch {\n // Fall through to other checks\n }\n try {\n if (fs.existsSync(getGeminiSettingsPath())) {\n return true;\n }\n if (fs.existsSync(getGeminiHomeDir())) {\n // Even an empty ~/.gemini tells us Gemini has run before\n return true;\n }\n } catch {\n // Fall through\n }\n return detectGeminiBinaryOnPath();\n },\n};\n\nfunction detectGeminiBinaryOnPath(): boolean {\n try {\n const probe = spawnSync(\n process.platform === \"win32\" ? \"where\" : \"which\",\n [\"gemini\"],\n {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n },\n );\n if (probe.status === 0 && probe.stdout && probe.stdout.toString().trim()) {\n return true;\n }\n } catch {\n // Treat any failure as \"not detectable\"\n }\n return false;\n}\n\nregisterPlugin(geminiCliPlugin);\n\nexport default geminiCliPlugin;\nexport { type GeminiHookEvent, buildAfterModelPayload } from \"./hook.js\";\nexport { type GeminiCliMonitorConfig } from \"./config.js\";\nexport {\n mergeHooksSettings,\n removeOlakaiHooks,\n hasOlakaiHooksInstalled,\n buildOlakaiHookEntry,\n SUPPORTED_GEMINI_HOOK_EVENTS,\n type GeminiHookMatcherEntry,\n type GeminiHookCommand,\n type GeminiSettings,\n} from \"./settings.js\";\n","/**\n * Gemini CLI install / uninstall flow.\n *\n * Installs Olakai hook entries into `~/.gemini/settings.json` (creating\n * the file when missing, preserving any other top-level keys when\n * present) and writes a per-tool monitor config under\n * `.olakai/monitor-gemini-cli.json`.\n *\n * Uninstall is the reverse: strip Olakai-marked handlers from the\n * settings (preserving any user-added entries) and optionally remove\n * the local monitor config file.\n *\n * Per D-CA-1, the settings file is written via a JSON round-trip — no\n * comments to preserve, and Gemini's docs treat the file as plain JSON.\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { type Agent } from \"../../../lib/api.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport { OLAKAI_DIR } from \"../../paths.js\";\nimport type {\n InstallOpts,\n InstallResult,\n UninstallOpts,\n} from \"../../plugin.js\";\nimport { provisionSelfMonitorAgent } from \"../../self-monitor-provision.js\";\nimport {\n deleteGeminiCliConfig,\n getGeminiCliConfigPath,\n writeGeminiCliConfig,\n type GeminiCliMonitorConfig,\n} from \"./config.js\";\nimport {\n GEMINI_HOME_DIRNAME,\n GEMINI_SETTINGS_FILENAME,\n getGeminiHomeDir,\n getGeminiSettingsPath,\n} from \"./paths.js\";\nimport {\n hasOlakaiHooksInstalled,\n mergeHooksSettings,\n readJsonFile,\n removeOlakaiHooks,\n writeJsonFile,\n type GeminiSettings,\n} from \"./settings.js\";\n\nexport const GEMINI_CLI_SOURCE = \"gemini-cli\";\nexport const GEMINI_CLI_AGENT_SOURCE = \"GEMINI_CLI\" as const;\nexport const GEMINI_CLI_AGENT_CATEGORY = \"CODING\";\n\nexport interface InstallGeminiCliOpts extends InstallOpts {\n /** Override `~` for tests. Production callers leave this undefined. */\n homeDir?: string;\n}\n\nexport async function installGeminiCli(\n opts: InstallGeminiCliOpts,\n): Promise<InstallResult> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n const homeDir = opts.homeDir ?? os.homedir();\n\n const token = getValidToken();\n if (!token) {\n console.error(\"Not logged in. Run 'olakai login' first.\");\n process.exit(1);\n }\n\n console.log(\"Setting up Gemini CLI monitoring for this workspace...\\n\");\n console.log(\n `Note: Gemini CLI hooks are installed globally per user (~/${GEMINI_HOME_DIRNAME}/${GEMINI_SETTINGS_FILENAME}).`,\n );\n console.log(\n `Activity from this workspace (${projectRoot}) will be associated with`,\n );\n console.log(\"the agent you select below.\\n\");\n\n // Provision (or reuse) the self-monitor agent — same shared helper\n // claude-code/codex/cursor use. See `self-monitor-provision.ts` for\n // the multi-workspace / multi-user collision handling. The string\n // backend AgentSource enum is `GEMINI_CLI` (new value added alongside\n // the existing CLAUDE_CODE / CODEX / CURSOR values).\n const agent: Agent = await provisionSelfMonitorAgent({\n projectRoot,\n source: GEMINI_CLI_AGENT_SOURCE,\n displayName: \"Gemini CLI\",\n category: GEMINI_CLI_AGENT_CATEGORY,\n });\n\n const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;\n\n const apiKey = agent.apiKey?.key;\n if (!apiKey) {\n console.error(\n \"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'.\",\n );\n process.exit(1);\n }\n\n // 1) Install hooks into ~/.gemini/settings.json (merging with any\n // existing user keys).\n const geminiDir = getGeminiHomeDir(homeDir);\n if (!fs.existsSync(geminiDir)) {\n fs.mkdirSync(geminiDir, { recursive: true });\n }\n\n const settingsPath = getGeminiSettingsPath(homeDir);\n const existingSettings = readJsonFile<GeminiSettings>(settingsPath) ?? {};\n const mergedHooks = mergeHooksSettings(existingSettings.hooks);\n const updatedSettings: GeminiSettings = {\n ...existingSettings,\n hooks: mergedHooks,\n };\n writeJsonFile(settingsPath, updatedSettings);\n\n // 2) Write per-tool monitor config in workspace\n const monitorConfig: GeminiCliMonitorConfig = {\n agentId: agent.id,\n apiKey,\n agentName: agent.name,\n source: GEMINI_CLI_SOURCE,\n createdAt: new Date().toISOString(),\n monitoringEndpoint,\n };\n writeGeminiCliConfig(projectRoot, monitorConfig);\n\n const configPath = getGeminiCliConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n const settingsDisplay = `~/${path.join(GEMINI_HOME_DIRNAME, GEMINI_SETTINGS_FILENAME)}`;\n\n console.log(\"\");\n console.log(`✓ Agent \"${agent.name}\" configured (ID: ${agent.id})`);\n if (agent.apiKey?.key) {\n console.log(\"✓ API key generated\");\n }\n console.log(`✓ Gemini CLI hooks configured in ${settingsDisplay}`);\n console.log(`✓ Monitor config saved to ${configRel}`);\n console.log(\"\");\n console.log(\n \"Congrats! Monitoring is now active. Gemini CLI will report activity to Olakai\",\n );\n console.log(`on each model turn.`);\n console.log(\"\");\n console.log(\n `⚠ Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key)`,\n );\n console.log(\n `⚠ Gemini CLI hooks require gemini >= 0.26.0. Earlier versions silently skip them.`,\n );\n console.log(\"\");\n console.log(\"To check status: olakai monitor status --tool gemini-cli\");\n console.log(\"To disable: olakai monitor disable --tool gemini-cli\");\n\n return {\n agentId: agent.id,\n agentName: agent.name,\n source: GEMINI_CLI_SOURCE,\n monitoringEndpoint,\n };\n}\n\nexport interface UninstallGeminiCliOpts extends UninstallOpts {\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\nexport async function uninstallGeminiCli(\n opts: UninstallGeminiCliOpts,\n): Promise<void> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n const homeDir = opts.homeDir ?? os.homedir();\n\n const settingsPath = getGeminiSettingsPath(homeDir);\n const settings = readJsonFile<GeminiSettings>(settingsPath);\n\n if (settings && hasOlakaiHooksInstalled(settings)) {\n const cleanedHooks = removeOlakaiHooks(settings.hooks);\n const next: GeminiSettings = { ...settings };\n if (cleanedHooks === undefined) {\n delete next.hooks;\n } else {\n next.hooks = cleanedHooks;\n }\n // When the only thing in the file was our hooks block (no other\n // top-level keys), unlink the file rather than leave a bare `{}`\n // behind. Matches the cursor-uninstall heuristic.\n const remainingKeys = Object.keys(next).filter((k) => k !== \"hooks\");\n const cleanedHasNoHooks = !next.hooks || Object.keys(next.hooks).length === 0;\n if (cleanedHasNoHooks && remainingKeys.length === 0) {\n try {\n fs.unlinkSync(settingsPath);\n } catch {\n // best-effort\n }\n } else {\n writeJsonFile(settingsPath, next);\n }\n console.log(\n `✓ Olakai hooks removed from ~/${path.join(GEMINI_HOME_DIRNAME, GEMINI_SETTINGS_FILENAME)}`,\n );\n } else {\n console.log(\"No Olakai hooks found in Gemini CLI settings.\");\n }\n\n if (!opts.keepConfig) {\n const configPath = getGeminiCliConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n if (deleteGeminiCliConfig(projectRoot)) {\n console.log(`✓ Monitor config removed (${configRel})`);\n }\n } else {\n const configPath = getGeminiCliConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n console.log(`Monitor config retained at ${configRel}`);\n }\n\n console.log(\"\");\n console.log(\n \"Monitoring disabled. Run 'olakai monitor init --tool gemini-cli' to re-enable.\",\n );\n}\n","/**\n * Per-tool monitor config storage for Gemini CLI. Lives at\n * `.olakai/monitor-gemini-cli.json` in the configured project root.\n *\n * Gemini CLI's hooks config is global per user (`~/.gemini/settings.json`),\n * but the per-project config (which agent + apiKey to associate with\n * events from this workspace) lives under the project root just like\n * Codex and Cursor. The hook adapter walks ancestors of the payload's\n * `cwd` to find the configured workspace.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n getMonitorConfigPath as resolveMonitorConfigPath,\n getOlakaiDir,\n} from \"../../paths.js\";\n\nexport interface GeminiCliMonitorConfig {\n agentId: string;\n apiKey: string;\n agentName: string;\n source: string;\n createdAt: string;\n monitoringEndpoint: string;\n}\n\nexport function getGeminiCliConfigPath(projectRoot: string): string {\n return resolveMonitorConfigPath(projectRoot, \"gemini-cli\");\n}\n\nexport function loadGeminiCliConfig(\n projectRoot: string,\n): GeminiCliMonitorConfig | null {\n const filePath = getGeminiCliConfigPath(projectRoot);\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw) as GeminiCliMonitorConfig;\n } catch {\n return null;\n }\n}\n\nexport function writeGeminiCliConfig(\n projectRoot: string,\n config: GeminiCliMonitorConfig,\n): void {\n const filePath = getGeminiCliConfigPath(projectRoot);\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // chmod failures are non-fatal — Windows / unusual filesystems.\n }\n}\n\nexport function deleteGeminiCliConfig(projectRoot: string): boolean {\n const filePath = getGeminiCliConfigPath(projectRoot);\n if (!fs.existsSync(filePath)) return false;\n try {\n fs.unlinkSync(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getOlakaiConfigDir(projectRoot: string): string {\n return getOlakaiDir(projectRoot);\n}\n","/**\n * Gemini CLI filesystem layout helpers.\n *\n * Gemini CLI v0.26.0+ stores per-user config at `~/.gemini/settings.json`.\n * The hooks system reads its top-level `hooks` object (a sibling of any\n * other user settings — model defaults, theme, etc.). The shape mirrors\n * Claude Code's `.claude/settings.json` (event name -> matcher entries\n * with command handlers).\n *\n * Centralizing the path helpers here keeps install/uninstall/status/hook\n * in lock-step on which file they're touching.\n */\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nexport const GEMINI_HOME_DIRNAME = \".gemini\";\nexport const GEMINI_SETTINGS_FILENAME = \"settings.json\";\n\n/**\n * Per-user Gemini directory (`~/.gemini`). `homeDir` override exists\n * for tests so we can point at a tmp dir without touching the real\n * user home.\n */\nexport function getGeminiHomeDir(homeDir: string = os.homedir()): string {\n return path.join(homeDir, GEMINI_HOME_DIRNAME);\n}\n\n/**\n * Per-user Gemini settings file (`~/.gemini/settings.json`).\n */\nexport function getGeminiSettingsPath(\n homeDir: string = os.homedir(),\n): string {\n return path.join(getGeminiHomeDir(homeDir), GEMINI_SETTINGS_FILENAME);\n}\n","/**\n * Gemini CLI `~/.gemini/settings.json` hook-block helpers.\n *\n * Pure functions (no filesystem I/O) so they can be unit-tested without\n * touching disk. The install/uninstall dispatchers in `install.ts` own\n * the actual file I/O.\n *\n * Gemini CLI's hooks system (v0.26.0+) reads a top-level `hooks` object\n * from `~/.gemini/settings.json`. The shape mirrors Claude Code's:\n *\n * \"hooks\": {\n * \"BeforeModel\": [\n * {\n * \"matcher\": \"\",\n * \"hooks\": [\n * { \"type\": \"command\", \"command\": \"olakai monitor hook --tool gemini-cli BeforeModel\", \"timeout\": 5000 }\n * ]\n * }\n * ],\n * ...\n * }\n *\n * The `matcher` field is reserved for future per-tool / per-event\n * filtering; an empty string matches all invocations of the event.\n *\n * Per-event `timeout` is in MILLISECONDS (Gemini CLI convention) — note\n * this differs from Codex's `timeout` field which is in seconds. The\n * 5000ms default matches the dispatcher's 5s AbortController so a hook\n * never overruns the host CLI.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\n/**\n * Substring used to identify Olakai-installed hook commands inside an\n * existing `~/.gemini/settings.json`. Every Olakai hook command starts\n * with `olakai monitor hook --tool gemini-cli`, so the marker is\n * sufficient for both install-side dedup and uninstall-side filtering.\n */\nexport const OLAKAI_HOOK_MARKER = \"olakai monitor hook --tool gemini-cli\";\n\n/** Per-event handler timeout (ms). */\nexport const GEMINI_HOOK_TIMEOUT_MS = 5000;\n\nexport interface GeminiHookCommand {\n type: string;\n command: string;\n timeout?: number;\n [extra: string]: unknown;\n}\n\nexport interface GeminiHookMatcherEntry {\n matcher: string;\n hooks: GeminiHookCommand[];\n [extra: string]: unknown;\n}\n\nexport interface GeminiSettings {\n hooks?: Record<string, GeminiHookMatcherEntry[]>;\n [extra: string]: unknown;\n}\n\n/**\n * The Gemini hook events we register. `BeforeModel` + `AfterModel` are\n * the canonical prompt/response capture pair; `BeforeTool` + `AfterTool`\n * enrich per-turn customData with tool-call counts; `SessionStart` and\n * `SessionEnd` bookend the session for future per-session aggregations.\n *\n * Listed in the order Gemini CLI's docs introduce them so the resulting\n * settings.json reads naturally to a human inspecting it.\n */\nexport const SUPPORTED_GEMINI_HOOK_EVENTS = [\n \"SessionStart\",\n \"SessionEnd\",\n \"BeforeModel\",\n \"AfterModel\",\n \"BeforeTool\",\n \"AfterTool\",\n] as const;\n\nexport type SupportedGeminiHookEvent =\n (typeof SUPPORTED_GEMINI_HOOK_EVENTS)[number];\n\n/**\n * Canonical Olakai hook entry for a given Gemini event. Kept as a\n * function (not a constant) so tests can compare against the same shape\n * the installer writes.\n */\nexport function buildOlakaiHookEntry(\n event: SupportedGeminiHookEvent,\n): GeminiHookMatcherEntry {\n return {\n matcher: \"\",\n hooks: [\n {\n type: \"command\",\n command: `olakai monitor hook --tool gemini-cli ${event}`,\n timeout: GEMINI_HOOK_TIMEOUT_MS,\n },\n ],\n };\n}\n\n/**\n * Default definitions for every supported event. Centralized here so\n * tests can drive `mergeHooksSettings` with a fixed set independent of\n * the install path.\n */\nexport const HOOK_DEFINITIONS: Record<string, GeminiHookMatcherEntry[]> =\n Object.fromEntries(\n SUPPORTED_GEMINI_HOOK_EVENTS.map((event) => [event, [buildOlakaiHookEntry(event)]]),\n );\n\nfunction entryContainsOlakaiHook(entry: GeminiHookMatcherEntry): boolean {\n if (!Array.isArray(entry.hooks)) return false;\n return entry.hooks.some(\n (h) => typeof h?.command === \"string\" && h.command.includes(OLAKAI_HOOK_MARKER),\n );\n}\n\n/**\n * Layer the Olakai default hook definitions onto an existing hooks\n * block. Merge rules mirror the Claude Code merge:\n *\n * - Existing user entries on other events are preserved untouched.\n * - For each supported event, we append our matcher entry only when\n * no Olakai-marked handler already exists (idempotent re-init).\n * - Existing Olakai-marked handlers are left exactly as written so\n * user customizations (e.g. a tweaked timeout) survive re-init.\n */\nexport function mergeHooksSettings(\n existing: Record<string, GeminiHookMatcherEntry[]> | undefined,\n definitions: Record<string, GeminiHookMatcherEntry[]> = HOOK_DEFINITIONS,\n): Record<string, GeminiHookMatcherEntry[]> {\n const merged: Record<string, GeminiHookMatcherEntry[]> = {\n ...(existing ?? {}),\n };\n\n for (const [event, defaultEntries] of Object.entries(definitions)) {\n const existingEntries = merged[event] ?? [];\n const hasOlakaiHook = existingEntries.some(entryContainsOlakaiHook);\n if (hasOlakaiHook) {\n merged[event] = existingEntries;\n } else {\n merged[event] = [...existingEntries, ...defaultEntries];\n }\n }\n\n return merged;\n}\n\n/**\n * Strip Olakai-installed entries from an existing hooks block. Returns\n * the cleaned block, or `undefined` when no event keys remain (caller\n * can then delete the top-level `hooks` key entirely).\n *\n * Within a matcher entry we drop only the Olakai-marked handlers — a\n * user might have appended their own command to the same entry and we\n * must preserve theirs.\n */\nexport function removeOlakaiHooks(\n existing: Record<string, GeminiHookMatcherEntry[]> | undefined,\n): Record<string, GeminiHookMatcherEntry[]> | undefined {\n if (!existing) return undefined;\n const cleaned: Record<string, GeminiHookMatcherEntry[]> = {};\n for (const [event, entries] of Object.entries(existing)) {\n if (!Array.isArray(entries)) continue;\n const filteredEntries: GeminiHookMatcherEntry[] = [];\n for (const entry of entries) {\n const handlers = Array.isArray(entry.hooks) ? entry.hooks : [];\n const remaining = handlers.filter(\n (h) =>\n typeof h?.command !== \"string\" ||\n !h.command.includes(OLAKAI_HOOK_MARKER),\n );\n if (remaining.length === 0 && handlers.length > 0) {\n // Entry existed only to host an Olakai hook -> drop entirely.\n continue;\n }\n if (remaining.length === handlers.length) {\n // Nothing of ours in this entry -> keep verbatim.\n filteredEntries.push(entry);\n } else {\n filteredEntries.push({ ...entry, hooks: remaining });\n }\n }\n if (filteredEntries.length > 0) {\n cleaned[event] = filteredEntries;\n }\n }\n return Object.keys(cleaned).length > 0 ? cleaned : undefined;\n}\n\n/**\n * Whether the parsed settings contain at least one Olakai-marked\n * handler across any event. Used by `status` to surface \"hooks active\".\n */\nexport function hasOlakaiHooksInstalled(settings: GeminiSettings): boolean {\n const hooks = settings.hooks;\n if (!hooks) return false;\n for (const entries of Object.values(hooks)) {\n if (!Array.isArray(entries)) continue;\n if (entries.some(entryContainsOlakaiHook)) return true;\n }\n return false;\n}\n\n/**\n * Tolerant JSON read — returns null when the file is missing, unreadable,\n * or malformed. Install logic treats null as \"start from scratch\" and\n * writes a fresh settings object.\n */\nexport function readJsonFile<T>(filePath: string): T | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const content = fs.readFileSync(filePath, \"utf-8\");\n if (!content.trim()) return null;\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\nexport function writeJsonFile(filePath: string, data: unknown): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n","/**\n * Status reporter for the Gemini CLI plugin. Mirrors the codex/cursor\n * status output: per-tool config metadata + a hint when the global\n * settings file is missing the Olakai hook entries (e.g. when the user\n * edited `~/.gemini/settings.json` manually since `init`).\n */\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { StatusReport } from \"../../plugin.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport {\n GEMINI_HOME_DIRNAME,\n GEMINI_SETTINGS_FILENAME,\n getGeminiSettingsPath,\n} from \"./paths.js\";\nimport {\n hasOlakaiHooksInstalled,\n readJsonFile,\n type GeminiSettings,\n} from \"./settings.js\";\nimport {\n getGeminiCliConfigPath,\n loadGeminiCliConfig,\n} from \"./config.js\";\n\nexport interface GeminiCliStatusOpts {\n projectRoot?: string;\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\nexport async function getGeminiCliStatus(\n opts?: GeminiCliStatusOpts,\n): Promise<StatusReport> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const homeDir = opts?.homeDir ?? os.homedir();\n\n const configPath = getGeminiCliConfigPath(projectRoot);\n const config = loadGeminiCliConfig(projectRoot);\n\n const settings = readJsonFile<GeminiSettings>(getGeminiSettingsPath(homeDir));\n const hooksConfigured = settings ? hasOlakaiHooksInstalled(settings) : false;\n\n if (!config) {\n return {\n toolId: \"gemini-cli\",\n configured: false,\n hooksConfigured,\n configPath,\n notes: hooksConfigured\n ? [\n `Gemini CLI hooks present in ~/${GEMINI_HOME_DIRNAME}/${GEMINI_SETTINGS_FILENAME} but no monitor config in this workspace — re-run init.`,\n ]\n : [],\n };\n }\n\n return {\n toolId: \"gemini-cli\",\n configured: true,\n hooksConfigured,\n agentId: config.agentId,\n agentName: config.agentName,\n source: config.source,\n apiKeyMasked: config.apiKey.slice(0, 12) + \"...\",\n monitoringEndpoint: config.monitoringEndpoint,\n configuredAt: config.createdAt,\n configPath,\n };\n}\n\n/**\n * Pretty printer used by the dispatcher in non-JSON mode. Mirrors\n * `printCodexStatus` / `printCursorStatus` so the four tools render\n * consistently.\n */\nexport async function printGeminiCliStatus(\n opts?: GeminiCliStatusOpts,\n): Promise<void> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const status = await getGeminiCliStatus(opts);\n\n if (!status.configured) {\n console.log(\"Gemini CLI monitoring is not configured for this workspace.\");\n if (status.hooksConfigured) {\n console.log(\n \"(Gemini CLI hooks ARE installed globally — run init to associate this workspace with an agent.)\",\n );\n }\n console.log(\n \"Run 'olakai monitor init --tool gemini-cli' to set up monitoring.\",\n );\n process.exit(1);\n }\n\n const configRel = status.configPath\n ? path.relative(projectRoot, status.configPath)\n : \"(unknown)\";\n const settingsDisplay = `~/${path.join(GEMINI_HOME_DIRNAME, GEMINI_SETTINGS_FILENAME)}`;\n\n console.log(\"Olakai Monitor Status (Gemini CLI)\");\n console.log(\"==================================\");\n console.log(`Agent: ${status.agentName}`);\n console.log(`Agent ID: ${status.agentId}`);\n console.log(`API Key: ${status.apiKeyMasked}`);\n console.log(`Endpoint: ${status.monitoringEndpoint}`);\n console.log(`Source: ${status.source}`);\n console.log(`Configured: ${status.configuredAt}`);\n console.log(`Config file: ${configRel}`);\n console.log(\n `Hooks: ${\n status.hooksConfigured\n ? `Active (${settingsDisplay})`\n : `Missing (re-run 'olakai monitor init --tool gemini-cli')`\n }`,\n );\n\n try {\n const token = getValidToken();\n if (token && status.agentId) {\n const params = new URLSearchParams({\n agentId: status.agentId,\n limit: \"5\",\n });\n const response = await fetch(\n `${getBaseUrl()}/api/activity/prompts?${params}`,\n {\n headers: { Authorization: `Bearer ${token}` },\n },\n );\n if (response.ok) {\n const data = (await response.json()) as {\n prompts: Array<{ id: string; createdAt: string }>;\n };\n if (data.prompts && data.prompts.length > 0) {\n console.log(\"\");\n console.log(\"Recent Activity:\");\n for (const p of data.prompts) {\n console.log(` ${p.createdAt} ${p.id.slice(0, 12)}...`);\n }\n } else {\n console.log(\"\");\n console.log(\"No activity recorded yet.\");\n }\n }\n }\n } catch {\n // Activity check is optional — never fail status on it.\n }\n}\n","/**\n * Cross-invocation pairing state for Gemini CLI's split prompt/response\n * hooks. `BeforeModel` and `AfterModel` fire as separate CLI invocations\n * — to build a single MonitoringPayload we stash the prompt + per-turn\n * metadata under the session key on the first invocation and pair it\n * with the response on the second.\n *\n * Tool-call counters (`BeforeTool`/`AfterTool`) accumulate into the same\n * sidecar file so the AfterModel emit can include them in `customData`.\n *\n * State lives at `~/.olakai/gemini-pairings/<sessionId>.json`. Files are\n * removed on successful pair or session-end flush. Mirrors the Cursor\n * pairing-state shape so we don't have to invent a new pattern.\n *\n * All filesystem errors are swallowed — the hook MUST NOT break Gemini.\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nexport interface PendingGeminiTurn {\n /** Last captured prompt (BeforeModel). */\n prompt: string;\n /**\n * Response text accumulated across streamed `AfterModel` chunks for the\n * in-flight model call. Gemini CLI streams the assistant response as a\n * sequence of `AfterModel` events carrying `llm_response.text` deltas;\n * we concatenate them here and emit once the final chunk arrives\n * (`finishReason` set). Reset to \"\" at the start of each model call.\n */\n accumulatedResponse?: string;\n /** Model id from the BeforeModel payload (if any). */\n modelName?: string;\n /** Working directory the session was started in. */\n cwd?: string;\n /** Session-level timestamps. */\n sessionStartedAt?: string;\n /** ISO timestamp captured on stash so orphans can be aged out. */\n stashedAt: string;\n /** Per-turn tool counters (BeforeTool increments). */\n toolCallCount: number;\n /** Names of tools that fired during the current turn (deduped). */\n toolNames: string[];\n /** Free-form bag — extra fields surfaced into customData. */\n extra?: Record<string, unknown>;\n}\n\nconst STATE_DIR_SEGMENTS = [\".olakai\", \"gemini-pairings\"];\n\nfunction getPairingsDir(homeDir: string): string {\n return path.join(homeDir, ...STATE_DIR_SEGMENTS);\n}\n\n/**\n * Filenames must round-trip safely on macOS/Linux/Windows. Gemini\n * session ids are UUID-shaped today; we still sanitize defensively so a\n * future schema loosening (or a manual override) can't escape the\n * pairings dir.\n */\nfunction sanitizeKeyFragment(value: string): string {\n return value.replace(/[^A-Za-z0-9._-]/g, \"_\");\n}\n\nfunction getPairingFile(sessionId: string, homeDir: string): string {\n return path.join(\n getPairingsDir(homeDir),\n `${sanitizeKeyFragment(sessionId)}.json`,\n );\n}\n\nfunction ensurePairingsDir(homeDir: string): void {\n const dir = getPairingsDir(homeDir);\n try {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n } catch {\n // Best effort — caller will fail on the next write and swallow.\n }\n}\n\n/**\n * Read the current pending-turn record for a session, or null when\n * absent / unparseable.\n */\nexport function readPendingTurn(\n sessionId: string,\n homeDir: string = os.homedir(),\n): PendingGeminiTurn | null {\n if (!sessionId) return null;\n const file = getPairingFile(sessionId, homeDir);\n try {\n if (!fs.existsSync(file)) return null;\n const raw = fs.readFileSync(file, \"utf-8\");\n return JSON.parse(raw) as PendingGeminiTurn;\n } catch {\n return null;\n }\n}\n\n/**\n * Overwrite the pending-turn record. Caller composes the full struct;\n * we just persist it. Best-effort — any failure is swallowed.\n */\nexport function writePendingTurn(\n sessionId: string,\n pending: PendingGeminiTurn,\n homeDir: string = os.homedir(),\n): void {\n if (!sessionId) return;\n ensurePairingsDir(homeDir);\n const file = getPairingFile(sessionId, homeDir);\n try {\n fs.writeFileSync(file, JSON.stringify(pending, null, 2) + \"\\n\", \"utf-8\");\n try {\n fs.chmodSync(file, 0o600);\n } catch {\n // chmod non-fatal — Windows / unusual filesystems.\n }\n } catch {\n // Best-effort\n }\n}\n\n/**\n * Read-modify-write helper. The mutator receives the current record (or\n * a fresh one if none exists yet) and returns the updated value. The\n * caller decides whether to keep the file around (return value) or let\n * it be cleared later via `clearPendingTurn`.\n */\nexport function updatePendingTurn(\n sessionId: string,\n mutator: (current: PendingGeminiTurn) => PendingGeminiTurn,\n homeDir: string = os.homedir(),\n): PendingGeminiTurn | null {\n if (!sessionId) return null;\n const current =\n readPendingTurn(sessionId, homeDir) ?? {\n prompt: \"\",\n stashedAt: new Date().toISOString(),\n toolCallCount: 0,\n toolNames: [],\n };\n const next = mutator(current);\n writePendingTurn(sessionId, next, homeDir);\n return next;\n}\n\n/**\n * Remove the pending-turn sidecar. Safe to call when the file is\n * missing — silently no-ops.\n */\nexport function clearPendingTurn(\n sessionId: string,\n homeDir: string = os.homedir(),\n): void {\n if (!sessionId) return;\n const file = getPairingFile(sessionId, homeDir);\n try {\n if (fs.existsSync(file)) fs.unlinkSync(file);\n } catch {\n // Best-effort\n }\n}\n","/**\n * Gemini CLI hook adapter.\n *\n * Translates Gemini hook payloads into the canonical MonitoringPayload.\n * Six events are wired through (see `settings.ts`); only `AfterModel`\n * actually emits an event — the others maintain sidecar state that the\n * `AfterModel` emit consumes:\n *\n * - `SessionStart` — stash cwd + start timestamp; return null\n * - `BeforeModel` — stash prompt + model name; reset the\n * streamed-response accumulator; return null\n * - `BeforeTool` — increment tool counter; return null\n * - `AfterTool` — no-op (counted by BeforeTool); return null\n * - `AfterModel` — accumulate the streamed response delta and,\n * once the model call finishes (`finishReason`\n * present), build the MonitoringPayload + emit\n * - `SessionEnd` — clear any stale pending sidecar; return null\n *\n * ## Payload schema (IMPORTANT — see OLA-297)\n *\n * Real Gemini CLI (validated against v0.44.1) sends a STRUCTURED payload,\n * not the flat `{prompt, response, usage}` shape this plugin originally\n * assumed. The response is also STREAMED across multiple `AfterModel`\n * events as `llm_response.text` deltas, with the final chunk carrying\n * `finishReason: \"STOP\"` and the cumulative `usageMetadata`:\n *\n * {\n * session_id, cwd, hook_event_name, transcript_path,\n * llm_request: { model, messages: [{role, content}], config },\n * llm_response: { text, candidates: [{content:{parts:[...]},\n * finishReason?}], usageMetadata: {\n * promptTokenCount, candidatesTokenCount,\n * totalTokenCount } }\n * }\n *\n * The prompt is the LAST `role:\"user\"` message in `llm_request.messages`\n * (the first is Gemini's `<session_context>` preamble). The earlier flat\n * schema (`event.prompt` / `event.response` / `event.usage`) is still\n * supported as a fallback so older Gemini builds — and our unit\n * fixtures — keep working.\n *\n * Defensive parsing: every field access on the inbound payload is\n * guarded so a schema drift across Gemini CLI patches can't break the\n * host tool. Unrecognized event names silent-exit at the dispatcher.\n *\n * `MonitoringPayload.source` is sourced from the per-tool monitor config\n * (D-CA-3 resolves it to `\"gemini-cli\"`).\n */\nimport type { MonitoringPayload } from \"../../plugin.js\";\nimport type { GeminiCliMonitorConfig } from \"./config.js\";\nimport {\n clearPendingTurn,\n readPendingTurn,\n updatePendingTurn,\n writePendingTurn,\n type PendingGeminiTurn,\n} from \"./pairing-state.js\";\n\n/**\n * A single message in `llm_request.messages`. `content` is a plain\n * string in the observed schema, but we tolerate the structured\n * `{parts:[{text}]}` shape too in case a future build switches to it.\n */\nexport interface GeminiRequestMessage {\n role?: string;\n content?: unknown;\n}\n\n/** `llm_request` block of a structured Gemini hook payload. */\nexport interface GeminiLlmRequest {\n model?: string;\n messages?: GeminiRequestMessage[];\n [extra: string]: unknown;\n}\n\n/** `llm_response` block of a structured Gemini hook payload. */\nexport interface GeminiLlmResponse {\n text?: string;\n candidates?: Array<{\n content?: { role?: string; parts?: unknown[] };\n finishReason?: string;\n [extra: string]: unknown;\n }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n totalTokenCount?: number;\n [key: string]: unknown;\n };\n [extra: string]: unknown;\n}\n\n/**\n * Gemini CLI hook event payload — superset of fields across events and\n * across schema versions. Loose typing because the schema is young and\n * we expect it to drift between Gemini CLI patches.\n *\n * Structured fields (Gemini CLI ≥ 0.26, validated on v0.44.1):\n * - `session_id` stable per-session UUID\n * - `hook_event_name` string, e.g. \"AfterModel\"\n * - `cwd` shell cwd at hook fire time\n * - `transcript_path` JSONL transcript for the session\n * - `llm_request` { model, messages, config }\n * - `llm_response` { text, candidates, usageMetadata }\n *\n * Legacy flat fields (kept as a fallback; used by unit fixtures):\n * - `prompt`, `response`, `model`, `usage`, `tool_name`, ...\n *\n * All keys are optional and defensively typed.\n */\nexport interface GeminiHookEvent {\n session_id?: string;\n hook_event_name?: string;\n cwd?: string;\n transcript_path?: string;\n // Structured schema\n llm_request?: GeminiLlmRequest;\n llm_response?: GeminiLlmResponse;\n // Legacy flat schema (fallback)\n model?: string;\n prompt?: string;\n response?: string;\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: unknown;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n [key: string]: unknown;\n };\n [extra: string]: unknown;\n}\n\nexport type DebugLogger = (label: string, data: unknown) => void;\nconst noopDebug: DebugLogger = () => {};\n\n/** Hook events we register handlers for (matches `settings.ts`). */\nexport const SUPPORTED_EVENTS = new Set<string>([\n \"SessionStart\",\n \"SessionEnd\",\n \"BeforeModel\",\n \"AfterModel\",\n \"BeforeTool\",\n \"AfterTool\",\n]);\n\nexport function isSupportedGeminiEvent(eventName: string): boolean {\n return SUPPORTED_EVENTS.has(eventName);\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return 0;\n return value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Coerce a message `content` into plain text. Handles the observed\n * string form and the structured `{parts:[{text}]}` / `[{text}]` forms.\n */\nfunction messageContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n return content\n .map((p) => (typeof p === \"string\" ? p : asString((p as { text?: unknown })?.text) ?? \"\"))\n .join(\"\");\n }\n if (isRecord(content) && Array.isArray((content as { parts?: unknown[] }).parts)) {\n return messageContentToText((content as { parts?: unknown[] }).parts);\n }\n return \"\";\n}\n\n/**\n * Extract the user prompt from a structured `llm_request`. The real\n * prompt is the LAST `role:\"user\"` message — the first user message is\n * Gemini's injected `<session_context>` preamble. Returns undefined when\n * there is no structured request to read.\n */\nexport function extractStructuredPrompt(\n llmRequest: GeminiLlmRequest | undefined,\n): string | undefined {\n if (!isRecord(llmRequest) || !Array.isArray(llmRequest.messages)) {\n return undefined;\n }\n for (let i = llmRequest.messages.length - 1; i >= 0; i--) {\n const msg = llmRequest.messages[i];\n if (msg && msg.role === \"user\") {\n const text = messageContentToText(msg.content).trim();\n if (text) return text;\n }\n }\n return undefined;\n}\n\n/** Model id from a structured `llm_request`. */\nexport function extractStructuredModel(\n llmRequest: GeminiLlmRequest | undefined,\n): string | undefined {\n return isRecord(llmRequest) ? asString(llmRequest.model) : undefined;\n}\n\n/**\n * Extract the response-text delta carried by one `AfterModel` chunk.\n * Prefers `llm_response.text`; falls back to concatenating the candidate\n * parts. Returns \"\" when there is nothing on this chunk (e.g. the final\n * `finishReason` chunk often carries empty text).\n */\nexport function extractStructuredResponseDelta(\n llmResponse: GeminiLlmResponse | undefined,\n): string {\n if (!isRecord(llmResponse)) return \"\";\n if (typeof llmResponse.text === \"string\" && llmResponse.text) {\n return llmResponse.text;\n }\n const parts = llmResponse.candidates?.[0]?.content?.parts;\n if (Array.isArray(parts)) {\n return parts\n .map((p) => (typeof p === \"string\" ? p : asString((p as { text?: unknown })?.text) ?? \"\"))\n .join(\"\");\n }\n return \"\";\n}\n\n/**\n * A structured `AfterModel` chunk is \"complete\" once any candidate\n * carries a `finishReason` (STOP / MAX_TOKENS / SAFETY / ...). That is\n * the signal to stop accumulating and emit.\n */\nexport function isStructuredStreamComplete(\n llmResponse: GeminiLlmResponse | undefined,\n): boolean {\n if (!isRecord(llmResponse) || !Array.isArray(llmResponse.candidates)) {\n return false;\n }\n return llmResponse.candidates.some((c) => asString(c?.finishReason));\n}\n\n/**\n * Coerce token counts into a `{input, output, total}` triple. Prefers\n * the structured `llm_response.usageMetadata` (the final chunk carries\n * cumulative totals); falls back to the legacy flat `usage` object.\n * `total` defaults to `input + output` when not provided.\n */\nexport function extractGeminiTokens(payload: GeminiHookEvent): {\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n} {\n const meta = payload.llm_response?.usageMetadata;\n if (isRecord(meta)) {\n const inputTokens = asNumber(meta.promptTokenCount);\n const outputTokens = asNumber(meta.candidatesTokenCount);\n const explicitTotal = asNumber(meta.totalTokenCount);\n const totalTokens =\n explicitTotal > 0 ? explicitTotal : inputTokens + outputTokens;\n return { inputTokens, outputTokens, totalTokens };\n }\n const usage = payload.usage ?? {};\n const inputTokens = asNumber(usage.input_tokens);\n const outputTokens = asNumber(usage.output_tokens);\n const explicitTotal = asNumber(usage.total_tokens);\n const totalTokens =\n explicitTotal > 0 ? explicitTotal : inputTokens + outputTokens;\n return { inputTokens, outputTokens, totalTokens };\n}\n\n/**\n * Whether this event carries the structured (`llm_request`/`llm_response`)\n * schema. When false we fall back to the legacy flat parsing.\n */\nfunction usesStructuredSchema(event: GeminiHookEvent): boolean {\n return isRecord(event.llm_request) || isRecord(event.llm_response);\n}\n\n/**\n * Assemble the canonical MonitoringPayload from the resolved per-turn\n * fields. Returns null when there is nothing meaningful to emit (no\n * prompt AND no response).\n */\nfunction buildPayload(args: {\n sessionId: string;\n prompt: string;\n response: string;\n modelName?: string;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n cwd?: string;\n toolCallCount: number;\n toolNames: string[];\n sessionStartedAt?: string;\n extra?: Record<string, unknown>;\n hookEvent?: string;\n config: GeminiCliMonitorConfig;\n}): MonitoringPayload | null {\n if (!args.prompt && !args.response) return null;\n\n const customData: Record<string, unknown> = {\n hookEvent: args.hookEvent ?? \"AfterModel\",\n sessionId: args.sessionId,\n cwd: args.cwd ?? \"\",\n inputTokens: args.inputTokens,\n outputTokens: args.outputTokens,\n toolCallCount: args.toolCallCount,\n toolNames: args.toolNames,\n };\n if (args.sessionStartedAt) {\n customData.sessionStartedAt = args.sessionStartedAt;\n }\n if (args.extra) {\n customData.stashedExtra = args.extra;\n }\n\n return {\n prompt: args.prompt,\n response: args.response,\n chatId: args.sessionId,\n source: args.config.source,\n modelName: args.modelName,\n tokens: args.totalTokens,\n customData,\n };\n}\n\n/**\n * Build a canonical MonitoringPayload for a LEGACY/flat `AfterModel`\n * event (no structured `llm_*` blocks). Pure; exported for unit tests.\n * Returns null when there is nothing to emit (no prompt AND no response).\n */\nexport function buildAfterModelPayload(\n event: GeminiHookEvent,\n stashed: PendingGeminiTurn | null,\n config: GeminiCliMonitorConfig,\n): MonitoringPayload | null {\n const sessionId = asString(event.session_id) ?? `gemini-cli-${Date.now()}`;\n const prompt = stashed?.prompt ?? asString(event.prompt) ?? \"\";\n const response = asString(event.response) ?? \"\";\n if (!prompt && !response) return null;\n\n const { inputTokens, outputTokens, totalTokens } = extractGeminiTokens(event);\n const modelName = asString(event.model) ?? stashed?.modelName ?? undefined;\n\n return buildPayload({\n sessionId,\n prompt,\n response,\n modelName,\n inputTokens,\n outputTokens,\n totalTokens,\n cwd: asString(event.cwd) ?? stashed?.cwd,\n toolCallCount: stashed?.toolCallCount ?? 0,\n toolNames: stashed?.toolNames ?? [],\n sessionStartedAt: stashed?.sessionStartedAt,\n extra: stashed?.extra,\n hookEvent: event.hook_event_name ?? \"AfterModel\",\n config,\n });\n}\n\nexport interface HandleHookOptions {\n debugLog?: DebugLogger;\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\n/**\n * Effectful handler: route the inbound Gemini event to the right\n * sidecar mutation (BeforeModel / BeforeTool / AfterTool / SessionStart\n * / SessionEnd) or build the `AfterModel` MonitoringPayload.\n *\n * Returns the canonical `MonitoringPayload` only when an `AfterModel`\n * turn completes. Every other supported event maintains state and\n * returns null; unsupported events also return null (and are logged in\n * debug mode).\n */\nexport function handleGeminiHook(\n eventName: string,\n payloadJson: unknown,\n config: GeminiCliMonitorConfig,\n options: HandleHookOptions = {},\n): MonitoringPayload | null {\n const debugLog = options.debugLog ?? noopDebug;\n if (!isSupportedGeminiEvent(eventName)) {\n debugLog(\"hook-unknown-event\", eventName);\n return null;\n }\n\n const event = (payloadJson ?? {}) as GeminiHookEvent;\n debugLog(\"event-parsed\", { eventName, event });\n\n const structured = usesStructuredSchema(event);\n const sessionId = asString(event.session_id) ?? \"\";\n\n if (!sessionId) {\n // Without a session id we can't pair across invocations. Emit only\n // when AfterModel carries enough inline data; otherwise drop.\n if (eventName !== \"AfterModel\") {\n debugLog(\"missing-session-id\", { eventName });\n return null;\n }\n if (structured) {\n const prompt = extractStructuredPrompt(event.llm_request) ?? \"\";\n const response = extractStructuredResponseDelta(event.llm_response);\n const { inputTokens, outputTokens, totalTokens } =\n extractGeminiTokens(event);\n const payload = buildPayload({\n sessionId: `gemini-cli-${Date.now()}`,\n prompt,\n response,\n modelName: extractStructuredModel(event.llm_request),\n inputTokens,\n outputTokens,\n totalTokens,\n cwd: asString(event.cwd),\n toolCallCount: 0,\n toolNames: [],\n config,\n });\n if (!payload) debugLog(\"after-model-empty-no-session\", { eventName });\n return payload;\n }\n const payload = buildAfterModelPayload(event, null, config);\n if (!payload) {\n debugLog(\"after-model-empty-no-session\", { eventName });\n return null;\n }\n return payload;\n }\n\n switch (eventName) {\n case \"SessionStart\": {\n writePendingTurn(\n sessionId,\n {\n prompt: \"\",\n accumulatedResponse: \"\",\n cwd: asString(event.cwd),\n sessionStartedAt: new Date().toISOString(),\n stashedAt: new Date().toISOString(),\n toolCallCount: 0,\n toolNames: [],\n },\n options.homeDir,\n );\n return null;\n }\n\n case \"BeforeModel\": {\n const structuredPrompt = extractStructuredPrompt(event.llm_request);\n const structuredModel = extractStructuredModel(event.llm_request);\n updatePendingTurn(\n sessionId,\n (current) => ({\n ...current,\n prompt: structuredPrompt ?? asString(event.prompt) ?? \"\",\n modelName:\n structuredModel ?? asString(event.model) ?? current.modelName,\n cwd: asString(event.cwd) ?? current.cwd,\n stashedAt: new Date().toISOString(),\n // New model call — reset the streamed-response accumulator and\n // the per-turn tool counters so they only reflect THIS call.\n accumulatedResponse: \"\",\n toolCallCount: 0,\n toolNames: [],\n }),\n options.homeDir,\n );\n return null;\n }\n\n case \"BeforeTool\": {\n const toolName = asString(event.tool_name);\n updatePendingTurn(\n sessionId,\n (current) => {\n const nextToolNames = toolName\n ? current.toolNames.includes(toolName)\n ? current.toolNames\n : [...current.toolNames, toolName]\n : current.toolNames;\n return {\n ...current,\n toolCallCount: current.toolCallCount + 1,\n toolNames: nextToolNames,\n };\n },\n options.homeDir,\n );\n return null;\n }\n\n case \"AfterTool\": {\n // BeforeTool already incremented the counter; we keep AfterTool\n // registered so users can see hook activity in `gemini --verbose`\n // and so future enrichment (e.g. tool-output summarization) has a\n // wire-in. No state mutation today.\n return null;\n }\n\n case \"AfterModel\": {\n // Legacy/flat schema: no streaming — emit immediately, as before.\n if (!structured) {\n const stashed = readPendingTurn(sessionId, options.homeDir);\n const payload = buildAfterModelPayload(event, stashed, config);\n clearPendingTurn(sessionId, options.homeDir);\n if (!payload) debugLog(\"after-model-empty\", { sessionId });\n return payload;\n }\n\n // Structured schema: the response is streamed across AfterModel\n // chunks as `llm_response.text` deltas. Accumulate until the final\n // chunk (`finishReason` present), then emit.\n const delta = extractStructuredResponseDelta(event.llm_response);\n const complete = isStructuredStreamComplete(event.llm_response);\n const structuredPrompt = extractStructuredPrompt(event.llm_request);\n const structuredModel = extractStructuredModel(event.llm_request);\n\n const updated = updatePendingTurn(\n sessionId,\n (current) => ({\n ...current,\n prompt: structuredPrompt ?? current.prompt,\n modelName: structuredModel ?? current.modelName,\n cwd: asString(event.cwd) ?? current.cwd,\n accumulatedResponse: (current.accumulatedResponse ?? \"\") + delta,\n }),\n options.homeDir,\n );\n\n if (!complete) {\n debugLog(\"after-model-streaming\", {\n sessionId,\n deltaLen: delta.length,\n });\n return null;\n }\n\n // Final chunk — the STOP event carries the cumulative usageMetadata.\n const { inputTokens, outputTokens, totalTokens } =\n extractGeminiTokens(event);\n const response = updated?.accumulatedResponse ?? \"\";\n const payload = buildPayload({\n sessionId,\n prompt: structuredPrompt ?? updated?.prompt ?? \"\",\n response,\n modelName: structuredModel ?? updated?.modelName,\n inputTokens,\n outputTokens,\n totalTokens,\n cwd: asString(event.cwd) ?? updated?.cwd,\n toolCallCount: updated?.toolCallCount ?? 0,\n toolNames: updated?.toolNames ?? [],\n sessionStartedAt: updated?.sessionStartedAt,\n extra: updated?.extra,\n hookEvent: event.hook_event_name ?? \"AfterModel\",\n config,\n });\n\n // Reset the per-call accumulator but keep the session sidecar so a\n // follow-up model call in the same session starts clean.\n updatePendingTurn(\n sessionId,\n (current) => ({\n ...current,\n accumulatedResponse: \"\",\n toolCallCount: 0,\n toolNames: [],\n }),\n options.homeDir,\n );\n\n if (!payload) {\n debugLog(\"after-model-empty\", { sessionId });\n } else {\n debugLog(\"payload-built\", {\n sessionId,\n responseLen: response.length,\n totalTokens,\n });\n }\n return payload;\n }\n\n case \"SessionEnd\": {\n // Best-effort cleanup of any leftover sidecar. We do NOT emit a\n // partial-orphan event here: Gemini's SessionEnd fires even on\n // clean shutdown and the AfterModel emit is the canonical capture\n // — surfacing an empty session-end event would just be noise.\n clearPendingTurn(sessionId, options.homeDir);\n return null;\n }\n }\n\n return null;\n}\n","/**\n * Antigravity (`agy`) plugin (source = \"antigravity\", AgentSource =\n * ANTIGRAVITY).\n *\n * Antigravity is a Windsurf/Codeium \"Cascade\" CLI agent. It supports a\n * `Stop` hook (fires when the agent finishes a turn). This plugin is a\n * near-port of the claude-code plugin (Stop-hook + transcript-read), and\n * mirrors the gemini-cli plugin's install/uninstall/status/hook split so\n * the dispatcher in `commands/monitor.ts` needs no tool-specific branch.\n *\n * Hook events registered (per `settings.ts`): only `Stop`, which emits a\n * MonitoringPayload built from the Stop payload + the JSONL transcript at\n * `transcriptPath`. Workspace resolution uses `workspacePaths[0]`.\n *\n * Defensive parsing throughout: every field access on the inbound\n * payload is guarded; unrecognized event types silent-exit.\n */\nimport * as fs from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport {\n registerPlugin,\n type HookResult,\n type InstallOpts,\n type InstallResult,\n type StatusReport,\n type ToolMonitorPlugin,\n type UninstallOpts,\n} from \"../../plugin.js\";\nimport { findConfiguredWorkspace } from \"../../paths.js\";\nimport { installAntigravity, uninstallAntigravity } from \"./install.js\";\nimport { getAntigravityStatus } from \"./status.js\";\nimport {\n getWorkspacePath,\n handleAntigravityHook,\n type AntigravityStopPayload,\n} from \"./hook.js\";\nimport {\n getAntigravityConfigPath,\n loadAntigravityConfig,\n} from \"./config.js\";\nimport { getAntigravityCliDir } from \"./paths.js\";\n\nconst TOOL_ID = \"antigravity\" as const;\n\n/**\n * Debug logger gated by `OLAKAI_MONITOR_DEBUG=1`. Mirrors the other\n * plugins' debug log so users only need to look in one place when\n * diagnosing hook failures.\n */\nfunction debugLog(label: string, data: unknown): void {\n if (process.env.OLAKAI_MONITOR_DEBUG !== \"1\") return;\n try {\n const logPath = `/tmp/olakai-monitor-debug-${process.pid}.log`;\n const line = `[${new Date().toISOString()}] antigravity/${label}: ${\n typeof data === \"string\" ? data : JSON.stringify(data, null, 2)\n }\\n`;\n fs.appendFileSync(logPath, line, \"utf-8\");\n } catch {\n // Never break the hook path on a debug-log failure.\n }\n}\n\n/**\n * Resolve the configured workspace root from an Antigravity Stop payload.\n * The Stop schema exposes `workspacePaths`; we seed the ancestor walk\n * with `workspacePaths[0]` (falling back to `process.cwd()`) and look for\n * any `.olakai/monitor-*.json` so a developer running `agy` deep inside a\n * subfolder still hits their workspace's monitor config.\n *\n * Exported for unit tests.\n */\nexport function resolveAntigravityProjectRoot(\n payload: AntigravityStopPayload,\n fallbackCwd: string,\n): string | null {\n const startDir = getWorkspacePath(payload) ?? fallbackCwd;\n return findConfiguredWorkspace(startDir, [TOOL_ID]);\n}\n\nconst antigravityPlugin: ToolMonitorPlugin = {\n id: TOOL_ID,\n displayName: \"Antigravity\",\n\n install(opts: InstallOpts): Promise<InstallResult> {\n return installAntigravity(opts);\n },\n\n uninstall(opts: UninstallOpts): Promise<void> {\n return uninstallAntigravity(opts);\n },\n\n status(opts): Promise<StatusReport> {\n return getAntigravityStatus(opts);\n },\n\n async handleHook(eventName, payloadJson): Promise<HookResult | null> {\n const payload = (payloadJson ?? {}) as AntigravityStopPayload;\n debugLog(\"hook-fired\", { eventName, hasPayload: payloadJson != null });\n\n const projectRoot = resolveAntigravityProjectRoot(payload, process.cwd());\n if (!projectRoot) {\n debugLog(\"config-not-found\", {\n startDir: getWorkspacePath(payload) ?? process.cwd(),\n });\n return null;\n }\n\n const config = loadAntigravityConfig(projectRoot);\n if (!config) {\n debugLog(\"monitor-config-missing\", { projectRoot });\n return null;\n }\n\n const result = handleAntigravityHook(eventName, payload, config, {\n debugLog,\n });\n if (!result) return null;\n\n return {\n payload: result,\n transport: {\n endpoint: config.monitoringEndpoint,\n apiKey: config.apiKey,\n projectRoot,\n },\n };\n },\n\n async detectInstalled(opts): Promise<boolean> {\n // Three independent signals — any is enough to surface \"antigravity\"\n // in the interactive `init` selector:\n // 1. The user ran `olakai monitor init --tool antigravity` here\n // (workspace-local monitor config exists), OR\n // 2. agy has run on this host (`~/.gemini/antigravity-cli/` dir\n // present), OR\n // 3. the `agy` binary is on PATH.\n const projectRoot = opts?.projectRoot ?? process.cwd();\n try {\n if (fs.existsSync(getAntigravityConfigPath(projectRoot))) {\n return true;\n }\n } catch {\n // Fall through to other checks\n }\n try {\n if (fs.existsSync(getAntigravityCliDir())) {\n return true;\n }\n } catch {\n // Fall through\n }\n return detectAgyBinaryOnPath();\n },\n};\n\nfunction detectAgyBinaryOnPath(): boolean {\n try {\n const probe = spawnSync(\n process.platform === \"win32\" ? \"where\" : \"which\",\n [\"agy\"],\n {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 1000,\n },\n );\n if (probe.status === 0 && probe.stdout && probe.stdout.toString().trim()) {\n return true;\n }\n } catch {\n // Treat any failure as \"not detectable\"\n }\n return false;\n}\n\nregisterPlugin(antigravityPlugin);\n\nexport default antigravityPlugin;\nexport {\n type AntigravityStopPayload,\n buildStopPayload,\n handleAntigravityHook,\n} from \"./hook.js\";\nexport { type AntigravityMonitorConfig } from \"./config.js\";\nexport {\n mergeHooksFile,\n removeOlakaiHooks,\n hasOlakaiHook,\n buildOlakaiHookEntry,\n buildOlakaiHookCommand,\n SUPPORTED_ANTIGRAVITY_HOOK_EVENTS,\n type AntigravityHooksFile,\n type AntigravityHookEntry,\n type AntigravityHookCommand,\n} from \"./settings.js\";\n","/**\n * Antigravity (`agy`) install / uninstall flow.\n *\n * Installs the Olakai `Stop` hook into `~/.gemini/config/hooks.json`\n * (creating the file when missing, preserving any other user-defined\n * hook names when present) and writes a per-tool monitor config under\n * `.olakai/monitor-antigravity.json`.\n *\n * IMPORTANT: the agy in-app hook editor writes a DIFFERENT path\n * (`~/.gemini/antigravity-cli/hooks.json`) that the RUNTIME ignores — an\n * agy bug. We write the file the runtime actually reads:\n * `~/.gemini/config/hooks.json`.\n *\n * Uninstall is the reverse: strip the Olakai-owned hook (preserving any\n * user-added entries) and optionally remove the local monitor config.\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { type Agent } from \"../../../lib/api.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport { OLAKAI_DIR } from \"../../paths.js\";\nimport type {\n InstallOpts,\n InstallResult,\n UninstallOpts,\n} from \"../../plugin.js\";\nimport { provisionSelfMonitorAgent } from \"../../self-monitor-provision.js\";\nimport {\n deleteAntigravityConfig,\n getAntigravityConfigPath,\n writeAntigravityConfig,\n type AntigravityMonitorConfig,\n} from \"./config.js\";\nimport {\n getGeminiConfigDir,\n getGeminiConfigHooksPath,\n} from \"./paths.js\";\nimport {\n hasOlakaiHook,\n mergeHooksFile,\n readJsonFile,\n removeOlakaiHooks,\n writeJsonFile,\n type AntigravityHooksFile,\n} from \"./settings.js\";\n\nexport const ANTIGRAVITY_SOURCE = \"antigravity\";\nexport const ANTIGRAVITY_AGENT_SOURCE = \"ANTIGRAVITY\" as const;\nexport const ANTIGRAVITY_AGENT_CATEGORY = \"CODING\";\n\nconst HOOKS_DISPLAY = \"~/.gemini/config/hooks.json\";\n\nexport interface InstallAntigravityOpts extends InstallOpts {\n /** Override `~` for tests. Production callers leave this undefined. */\n homeDir?: string;\n}\n\nexport async function installAntigravity(\n opts: InstallAntigravityOpts,\n): Promise<InstallResult> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n const homeDir = opts.homeDir ?? os.homedir();\n\n const token = getValidToken();\n if (!token) {\n console.error(\"Not logged in. Run 'olakai login' first.\");\n process.exit(1);\n }\n\n console.log(\"Setting up Antigravity monitoring for this workspace...\\n\");\n console.log(\n `Note: Antigravity hooks are installed globally per user (${HOOKS_DISPLAY}).`,\n );\n console.log(\n `Activity from this workspace (${projectRoot}) will be associated with`,\n );\n console.log(\"the agent you select below.\\n\");\n\n // Provision (or reuse) the self-monitor agent — same shared helper\n // claude-code/codex/cursor/gemini-cli use. The backend AgentSource\n // enum is `ANTIGRAVITY`.\n const agent: Agent = await provisionSelfMonitorAgent({\n projectRoot,\n source: ANTIGRAVITY_AGENT_SOURCE,\n displayName: \"Antigravity\",\n category: ANTIGRAVITY_AGENT_CATEGORY,\n });\n\n const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;\n\n const apiKey = agent.apiKey?.key;\n if (!apiKey) {\n console.error(\n \"Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'.\",\n );\n process.exit(1);\n }\n\n // 1) Install the Stop hook into ~/.gemini/config/hooks.json (merging\n // with any existing user-defined hook names).\n const configDir = getGeminiConfigDir(homeDir);\n if (!fs.existsSync(configDir)) {\n fs.mkdirSync(configDir, { recursive: true });\n }\n\n const hooksPath = getGeminiConfigHooksPath(homeDir);\n const existingHooks = readJsonFile<AntigravityHooksFile>(hooksPath) ?? {};\n const mergedHooks = mergeHooksFile(existingHooks);\n writeJsonFile(hooksPath, mergedHooks);\n\n // 2) Write per-tool monitor config in workspace\n const monitorConfig: AntigravityMonitorConfig = {\n agentId: agent.id,\n apiKey,\n agentName: agent.name,\n source: ANTIGRAVITY_SOURCE,\n createdAt: new Date().toISOString(),\n monitoringEndpoint,\n };\n writeAntigravityConfig(projectRoot, monitorConfig);\n\n const configPath = getAntigravityConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n\n console.log(\"\");\n console.log(`✓ Agent \"${agent.name}\" configured (ID: ${agent.id})`);\n if (agent.apiKey?.key) {\n console.log(\"✓ API key generated\");\n }\n console.log(`✓ Antigravity Stop hook configured in ${HOOKS_DISPLAY}`);\n console.log(`✓ Monitor config saved to ${configRel}`);\n console.log(\"\");\n console.log(\n \"Congrats! Monitoring is now active. Antigravity will report activity to Olakai\",\n );\n console.log(`when the agent finishes a turn (Stop hook).`);\n console.log(\"\");\n console.log(\n `⚠ Ensure ${OLAKAI_DIR}/ is in your .gitignore (it contains your API key)`,\n );\n console.log(\n `⚠ Antigravity hooks are global (${HOOKS_DISPLAY}) and require an agy build with hooks support.`,\n );\n console.log(\n `⚠ Turn capture is interactive-only — headless 'agy -p' runs skip hooks.`,\n );\n console.log(\"\");\n console.log(\"To check status: olakai monitor status --tool antigravity\");\n console.log(\"To disable: olakai monitor disable --tool antigravity\");\n\n return {\n agentId: agent.id,\n agentName: agent.name,\n source: ANTIGRAVITY_SOURCE,\n monitoringEndpoint,\n };\n}\n\nexport interface UninstallAntigravityOpts extends UninstallOpts {\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\nexport async function uninstallAntigravity(\n opts: UninstallAntigravityOpts,\n): Promise<void> {\n const projectRoot = opts.projectRoot ?? process.cwd();\n const homeDir = opts.homeDir ?? os.homedir();\n\n const hooksPath = getGeminiConfigHooksPath(homeDir);\n const hooksFile = readJsonFile<AntigravityHooksFile>(hooksPath);\n\n if (hooksFile && hasOlakaiHook(hooksFile)) {\n const cleaned = removeOlakaiHooks(hooksFile);\n if (cleaned === undefined) {\n // The file held only our hook — unlink it rather than leave a bare\n // `{}` behind (matches the gemini/cursor uninstall heuristic).\n try {\n fs.unlinkSync(hooksPath);\n } catch {\n // best-effort\n }\n } else {\n writeJsonFile(hooksPath, cleaned);\n }\n console.log(`✓ Olakai hooks removed from ${HOOKS_DISPLAY}`);\n } else {\n console.log(\"No Olakai hooks found in Antigravity hooks.json.\");\n }\n\n if (!opts.keepConfig) {\n const configPath = getAntigravityConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n if (deleteAntigravityConfig(projectRoot)) {\n console.log(`✓ Monitor config removed (${configRel})`);\n }\n } else {\n const configPath = getAntigravityConfigPath(projectRoot);\n const configRel = path.relative(projectRoot, configPath);\n console.log(`Monitor config retained at ${configRel}`);\n }\n\n console.log(\"\");\n console.log(\n \"Monitoring disabled. Run 'olakai monitor init --tool antigravity' to re-enable.\",\n );\n}\n","/**\n * Per-tool monitor config storage for Antigravity (`agy`). Lives at\n * `.olakai/monitor-antigravity.json` in the configured project root.\n *\n * Antigravity's hooks config is global per user\n * (`~/.gemini/config/hooks.json`), but the per-project config (which\n * agent + apiKey to associate with events from this workspace) lives\n * under the project root just like Gemini CLI, Codex, and Cursor. The\n * hook adapter resolves the workspace from the Stop payload's\n * `workspacePaths[0]`.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n getMonitorConfigPath as resolveMonitorConfigPath,\n getOlakaiDir,\n} from \"../../paths.js\";\n\nexport interface AntigravityMonitorConfig {\n agentId: string;\n apiKey: string;\n agentName: string;\n source: string;\n createdAt: string;\n monitoringEndpoint: string;\n}\n\nexport function getAntigravityConfigPath(projectRoot: string): string {\n return resolveMonitorConfigPath(projectRoot, \"antigravity\");\n}\n\nexport function loadAntigravityConfig(\n projectRoot: string,\n): AntigravityMonitorConfig | null {\n const filePath = getAntigravityConfigPath(projectRoot);\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw) as AntigravityMonitorConfig;\n } catch {\n return null;\n }\n}\n\nexport function writeAntigravityConfig(\n projectRoot: string,\n config: AntigravityMonitorConfig,\n): void {\n const filePath = getAntigravityConfigPath(projectRoot);\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // chmod failures are non-fatal — Windows / unusual filesystems.\n }\n}\n\nexport function deleteAntigravityConfig(projectRoot: string): boolean {\n const filePath = getAntigravityConfigPath(projectRoot);\n if (!fs.existsSync(filePath)) return false;\n try {\n fs.unlinkSync(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getOlakaiConfigDir(projectRoot: string): string {\n return getOlakaiDir(projectRoot);\n}\n","/**\n * Antigravity (`agy`) filesystem layout helpers.\n *\n * Antigravity is a Windsurf/Codeium \"Cascade\" CLI agent. Its hooks\n * RUNTIME reads `~/.gemini/config/hooks.json` (it shares the `~/.gemini`\n * home with Gemini-family tooling, but uses a SEPARATE `config/hooks.json`\n * file for its Stop/etc. hooks).\n *\n * IMPORTANT (verified live against agy v1.0.4): the in-app hook editor\n * writes a DIFFERENT path — `~/.gemini/antigravity-cli/hooks.json` — that\n * the runtime IGNORES (an agy bug). The plugin therefore writes the file\n * the runtime actually reads: `~/.gemini/config/hooks.json`.\n *\n * Centralizing the path helpers here keeps install/uninstall/status/hook\n * in lock-step on which file they're touching. `homeDir` overrides exist\n * for tests so we can point at a tmp dir without touching the real user\n * home.\n */\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nexport const GEMINI_HOME_DIRNAME = \".gemini\";\nexport const ANTIGRAVITY_CONFIG_DIRNAME = \"config\";\nexport const ANTIGRAVITY_HOOKS_FILENAME = \"hooks.json\";\n/** The agy install dir under ~/.gemini (presence signals agy has run). */\nexport const ANTIGRAVITY_CLI_DIRNAME = \"antigravity-cli\";\n\n/** Per-user Gemini-family home directory (`~/.gemini`). */\nexport function getGeminiHomeDir(homeDir: string = os.homedir()): string {\n return path.join(homeDir, GEMINI_HOME_DIRNAME);\n}\n\n/**\n * The `~/.gemini/config` directory that holds the runtime-read\n * `hooks.json`.\n */\nexport function getGeminiConfigDir(homeDir: string = os.homedir()): string {\n return path.join(getGeminiHomeDir(homeDir), ANTIGRAVITY_CONFIG_DIRNAME);\n}\n\n/**\n * The hooks file the agy RUNTIME reads: `~/.gemini/config/hooks.json`.\n */\nexport function getGeminiConfigHooksPath(\n homeDir: string = os.homedir(),\n): string {\n return path.join(getGeminiConfigDir(homeDir), ANTIGRAVITY_HOOKS_FILENAME);\n}\n\n/**\n * The agy install directory (`~/.gemini/antigravity-cli`). Used by\n * `detectInstalled` as a \"agy has run on this host\" signal.\n */\nexport function getAntigravityCliDir(homeDir: string = os.homedir()): string {\n return path.join(getGeminiHomeDir(homeDir), ANTIGRAVITY_CLI_DIRNAME);\n}\n","/**\n * Antigravity `~/.gemini/config/hooks.json` hook-block helpers.\n *\n * Pure functions (no filesystem I/O) so they can be unit-tested without\n * touching disk. The install/uninstall dispatchers in `install.ts` own\n * the actual file I/O.\n *\n * The agy hooks file is an object keyed by hook NAME; each value is an\n * object keyed by EVENT name whose value is an ARRAY of command specs:\n *\n * {\n * \"olakai-monitor\": {\n * \"Stop\": [\n * { \"type\": \"command\", \"command\": \"olakai monitor hook --tool antigravity Stop\", \"timeout\": 30 }\n * ]\n * }\n * }\n *\n * Merge rules mirror gemini-cli's settings merge: we own the\n * `\"olakai-monitor\"` named entry; any other user-defined hook names and\n * events are preserved verbatim. Re-install is idempotent.\n *\n * Per-event `timeout` is in SECONDS (agy convention) — note this differs\n * from Gemini CLI's `settings.json` hooks which use milliseconds.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\n/** The named hook key Olakai owns inside hooks.json. */\nexport const OLAKAI_HOOK_NAME = \"olakai-monitor\";\n\n/** Substring used to identify Olakai-installed hook commands. */\nexport const OLAKAI_HOOK_MARKER = \"olakai monitor hook --tool antigravity\";\n\n/** Per-event handler timeout (seconds). */\nexport const ANTIGRAVITY_HOOK_TIMEOUT_SECONDS = 30;\n\n/**\n * The agy hook events we register. Only `Stop` emits a MonitoringPayload\n * (fires when the agent finishes a turn).\n */\nexport const SUPPORTED_ANTIGRAVITY_HOOK_EVENTS = [\"Stop\"] as const;\n\nexport type SupportedAntigravityHookEvent =\n (typeof SUPPORTED_ANTIGRAVITY_HOOK_EVENTS)[number];\n\nexport interface AntigravityHookCommand {\n type: string;\n command: string;\n timeout?: number;\n [extra: string]: unknown;\n}\n\n/** A named hook entry: event name -> array of command specs (or null). */\nexport interface AntigravityHookEntry {\n [event: string]: AntigravityHookCommand[] | null | undefined;\n}\n\n/** Top-level hooks.json shape: hook name -> entry. */\nexport interface AntigravityHooksFile {\n [hookName: string]: AntigravityHookEntry | undefined;\n}\n\n/**\n * Canonical Olakai hook command spec for a given agy event. Kept as a\n * function (not a constant) so tests can compare against the same shape\n * the installer writes.\n */\nexport function buildOlakaiHookCommand(\n event: SupportedAntigravityHookEvent,\n): AntigravityHookCommand {\n return {\n type: \"command\",\n command: `${OLAKAI_HOOK_MARKER} ${event}`,\n timeout: ANTIGRAVITY_HOOK_TIMEOUT_SECONDS,\n };\n}\n\n/**\n * The canonical Olakai named-hook entry (all supported events).\n */\nexport function buildOlakaiHookEntry(): AntigravityHookEntry {\n const entry: AntigravityHookEntry = {};\n for (const event of SUPPORTED_ANTIGRAVITY_HOOK_EVENTS) {\n entry[event] = [buildOlakaiHookCommand(event)];\n }\n return entry;\n}\n\n/**\n * Merge the Olakai named-hook entry into an existing hooks.json object.\n *\n * - Every other hook NAME the user defined is preserved untouched.\n * - Our `\"olakai-monitor\"` entry is (re)written with the canonical\n * Stop command. We overwrite our own entry on re-init so a stale\n * command (e.g. from an older CLI) gets refreshed, but we never\n * touch other users' named hooks.\n */\nexport function mergeHooksFile(\n existing: AntigravityHooksFile | undefined,\n): AntigravityHooksFile {\n const merged: AntigravityHooksFile = { ...(existing ?? {}) };\n merged[OLAKAI_HOOK_NAME] = buildOlakaiHookEntry();\n return merged;\n}\n\nfunction entryContainsOlakaiHook(entry: AntigravityHookEntry): boolean {\n for (const commands of Object.values(entry)) {\n if (!Array.isArray(commands)) continue;\n if (\n commands.some(\n (c) =>\n typeof c?.command === \"string\" &&\n c.command.includes(OLAKAI_HOOK_MARKER),\n )\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Strip Olakai-installed hooks from an existing hooks.json object.\n * Returns the cleaned object, or `undefined` when nothing remains\n * (caller can then delete the file).\n *\n * We remove the whole `\"olakai-monitor\"` named entry. For robustness we\n * also scrub Olakai-marked commands out of any OTHER named entry a user\n * might have hand-edited to point at our command, dropping events (and\n * then names) that become empty.\n */\nexport function removeOlakaiHooks(\n existing: AntigravityHooksFile | undefined,\n): AntigravityHooksFile | undefined {\n if (!existing) return undefined;\n const cleaned: AntigravityHooksFile = {};\n for (const [name, entry] of Object.entries(existing)) {\n if (name === OLAKAI_HOOK_NAME) continue;\n if (!entry || typeof entry !== \"object\") continue;\n const cleanedEntry: AntigravityHookEntry = {};\n for (const [event, commands] of Object.entries(entry)) {\n if (!Array.isArray(commands)) {\n cleanedEntry[event] = commands;\n continue;\n }\n const remaining = commands.filter(\n (c) =>\n typeof c?.command !== \"string\" ||\n !c.command.includes(OLAKAI_HOOK_MARKER),\n );\n if (remaining.length > 0) {\n cleanedEntry[event] = remaining;\n }\n }\n if (Object.keys(cleanedEntry).length > 0) {\n cleaned[name] = cleanedEntry;\n }\n }\n return Object.keys(cleaned).length > 0 ? cleaned : undefined;\n}\n\n/**\n * Whether the parsed hooks file contains at least one Olakai-marked hook\n * (the `\"olakai-monitor\"` entry, or our command nested under any name).\n * Used by `status` / `detectInstalled` to surface \"hooks active\".\n */\nexport function hasOlakaiHook(hooksFile: AntigravityHooksFile | null): boolean {\n if (!hooksFile) return false;\n for (const entry of Object.values(hooksFile)) {\n if (entry && entryContainsOlakaiHook(entry)) return true;\n }\n return false;\n}\n\n/**\n * Tolerant JSON read — returns null when the file is missing, unreadable,\n * or malformed. Install logic treats null as \"start from scratch\".\n */\nexport function readJsonFile<T>(filePath: string): T | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const content = fs.readFileSync(filePath, \"utf-8\");\n if (!content.trim()) return null;\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\nexport function writeJsonFile(filePath: string, data: unknown): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n","/**\n * Status reporter for the Antigravity plugin. Mirrors the gemini-cli\n * status output: per-tool config metadata + a hint when the global\n * hooks file is missing the Olakai hook entry (e.g. when the user edited\n * `~/.gemini/config/hooks.json` manually since `init`).\n */\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { StatusReport } from \"../../plugin.js\";\nimport { getValidToken } from \"../../../lib/auth.js\";\nimport { getBaseUrl } from \"../../../lib/config.js\";\nimport { getGeminiConfigHooksPath } from \"./paths.js\";\nimport {\n hasOlakaiHook,\n readJsonFile,\n type AntigravityHooksFile,\n} from \"./settings.js\";\nimport {\n getAntigravityConfigPath,\n loadAntigravityConfig,\n} from \"./config.js\";\n\nexport interface AntigravityStatusOpts {\n projectRoot?: string;\n /** Override `~` for tests. */\n homeDir?: string;\n}\n\nconst HOOKS_DISPLAY = \"~/.gemini/config/hooks.json\";\n\nexport async function getAntigravityStatus(\n opts?: AntigravityStatusOpts,\n): Promise<StatusReport> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const homeDir = opts?.homeDir ?? os.homedir();\n\n const configPath = getAntigravityConfigPath(projectRoot);\n const config = loadAntigravityConfig(projectRoot);\n\n const hooksFile = readJsonFile<AntigravityHooksFile>(\n getGeminiConfigHooksPath(homeDir),\n );\n const hooksConfigured = hasOlakaiHook(hooksFile);\n\n if (!config) {\n return {\n toolId: \"antigravity\",\n configured: false,\n hooksConfigured,\n configPath,\n notes: hooksConfigured\n ? [\n `Antigravity hooks present in ${HOOKS_DISPLAY} but no monitor config in this workspace — re-run init.`,\n ]\n : [],\n };\n }\n\n return {\n toolId: \"antigravity\",\n configured: true,\n hooksConfigured,\n agentId: config.agentId,\n agentName: config.agentName,\n source: config.source,\n apiKeyMasked: config.apiKey.slice(0, 12) + \"...\",\n monitoringEndpoint: config.monitoringEndpoint,\n configuredAt: config.createdAt,\n configPath,\n };\n}\n\n/**\n * Pretty printer used by the dispatcher in non-JSON mode. Mirrors\n * `printGeminiCliStatus` so the tools render consistently.\n */\nexport async function printAntigravityStatus(\n opts?: AntigravityStatusOpts,\n): Promise<void> {\n const projectRoot = opts?.projectRoot ?? process.cwd();\n const status = await getAntigravityStatus(opts);\n\n if (!status.configured) {\n console.log(\n \"Antigravity monitoring is not configured for this workspace.\",\n );\n if (status.hooksConfigured) {\n console.log(\n \"(Antigravity hooks ARE installed globally — run init to associate this workspace with an agent.)\",\n );\n }\n console.log(\n \"Run 'olakai monitor init --tool antigravity' to set up monitoring.\",\n );\n process.exit(1);\n }\n\n const configRel = status.configPath\n ? path.relative(projectRoot, status.configPath)\n : \"(unknown)\";\n\n console.log(\"Olakai Monitor Status (Antigravity)\");\n console.log(\"===================================\");\n console.log(`Agent: ${status.agentName}`);\n console.log(`Agent ID: ${status.agentId}`);\n console.log(`API Key: ${status.apiKeyMasked}`);\n console.log(`Endpoint: ${status.monitoringEndpoint}`);\n console.log(`Source: ${status.source}`);\n console.log(`Configured: ${status.configuredAt}`);\n console.log(`Config file: ${configRel}`);\n console.log(\n `Hooks: ${\n status.hooksConfigured\n ? `Active (${HOOKS_DISPLAY})`\n : `Missing (re-run 'olakai monitor init --tool antigravity')`\n }`,\n );\n\n try {\n const token = getValidToken();\n if (token && status.agentId) {\n const params = new URLSearchParams({\n agentId: status.agentId,\n limit: \"5\",\n });\n const response = await fetch(\n `${getBaseUrl()}/api/activity/prompts?${params}`,\n {\n headers: { Authorization: `Bearer ${token}` },\n },\n );\n if (response.ok) {\n const data = (await response.json()) as {\n prompts: Array<{ id: string; createdAt: string }>;\n };\n if (data.prompts && data.prompts.length > 0) {\n console.log(\"\");\n console.log(\"Recent Activity:\");\n for (const p of data.prompts) {\n console.log(` ${p.createdAt} ${p.id.slice(0, 12)}...`);\n }\n } else {\n console.log(\"\");\n console.log(\"No activity recorded yet.\");\n }\n }\n }\n } catch {\n // Activity check is optional — never fail status on it.\n }\n}\n","/**\n * Antigravity (`agy`) Stop hook adapter.\n *\n * Splits cleanly into two pieces (mirroring the claude-code plugin):\n *\n * - `buildStopPayload` (pure) — canonical MonitoringPayload from\n * a parsed Stop event + already-extracted transcript prompt/response\n * + resolved monitor config. Unit-testable without filesystem.\n *\n * - `handleAntigravityHook` (effectful) — reads the transcript file\n * referenced by the Stop payload and builds the payload. Defensive\n * throughout (never throws). Returns null when there is nothing to\n * report.\n *\n * Only the `Stop` event emits a MonitoringPayload. The Stop payload +\n * transcript carry no usageMetadata, so `tokens` is emitted as 0 for v1.\n */\nimport * as fs from \"node:fs\";\nimport type { MonitoringPayload } from \"../../plugin.js\";\nimport type { AntigravityMonitorConfig } from \"./config.js\";\nimport {\n extractPrompt,\n extractResponse,\n parseTranscriptLines,\n} from \"./transcript.js\";\n\n/**\n * Antigravity Stop hook stdin payload (validated live against agy v1.0.4):\n *\n * {\n * \"conversationId\": \"bbd04e9f-...\",\n * \"terminationReason\": \"NO_TOOL_CALL\",\n * \"fullyIdle\": true,\n * \"error\": \"\",\n * \"transcriptPath\": \".../logs/transcript.jsonl\",\n * \"workspacePaths\": [\"/Users/esteban/dev/olakai\"]\n * }\n *\n * All keys are optional + defensively typed; schema drift across agy\n * patches must never break the host tool.\n */\nexport interface AntigravityStopPayload {\n conversationId?: string;\n terminationReason?: string;\n fullyIdle?: boolean;\n error?: string;\n transcriptPath?: string;\n workspacePaths?: unknown;\n [extra: string]: unknown;\n}\n\nexport type DebugLogger = (label: string, data: unknown) => void;\nconst noopDebug: DebugLogger = () => {};\n\n/** Hook events we register handlers for (matches `settings.ts`). */\nexport const SUPPORTED_EVENTS = new Set<string>([\"Stop\"]);\n\nexport function isSupportedAntigravityEvent(eventName: string): boolean {\n return SUPPORTED_EVENTS.has(eventName);\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\n/**\n * Resolve the workspace root (`workspacePaths[0]`) from a Stop payload.\n * Exported so the plugin's `handleHook` and tests agree on the rule.\n */\nexport function getWorkspacePath(\n payload: AntigravityStopPayload,\n): string | undefined {\n const paths = payload.workspacePaths;\n if (Array.isArray(paths)) {\n const first = paths[0];\n if (typeof first === \"string\" && first.trim()) return first;\n }\n return undefined;\n}\n\n/**\n * Read the transcript JSONL referenced by the Stop payload and return\n * the extracted prompt/response. Any failure (missing file, malformed\n * JSON) yields empty strings. Parsing lives in `transcript.ts`.\n */\nexport function extractFromTranscript(\n transcriptPath: string | undefined,\n debugLog: DebugLogger = noopDebug,\n): { prompt: string; response: string } {\n if (!transcriptPath) return { prompt: \"\", response: \"\" };\n let raw: string;\n try {\n raw = fs.readFileSync(transcriptPath, \"utf-8\");\n } catch (err) {\n debugLog(\"transcript-read-failed\", {\n transcriptPath,\n error: (err as Error).message,\n });\n return { prompt: \"\", response: \"\" };\n }\n const lines = parseTranscriptLines(raw);\n return {\n prompt: extractPrompt(lines),\n response: extractResponse(lines),\n };\n}\n\n/**\n * Build a canonical MonitoringPayload from a Stop event + already\n * extracted prompt/response. Pure; exported for unit tests. Returns null\n * when there is nothing to emit (no prompt AND no response).\n */\nexport function buildStopPayload(\n payload: AntigravityStopPayload,\n extracted: { prompt: string; response: string },\n config: AntigravityMonitorConfig,\n): MonitoringPayload | null {\n const prompt = extracted.prompt ?? \"\";\n const response = extracted.response ?? \"\";\n if (!prompt.trim() && !response.trim()) return null;\n\n const conversationId =\n asString(payload.conversationId) ?? `antigravity-${Date.now()}`;\n const cwd = getWorkspacePath(payload) ?? \"\";\n\n const customData: Record<string, unknown> = {\n hookEvent: \"Stop\",\n conversationId,\n cwd,\n terminationReason: asString(payload.terminationReason) ?? \"\",\n };\n\n return {\n prompt,\n response,\n chatId: conversationId,\n source: config.source,\n tokens: 0,\n customData,\n };\n}\n\nexport interface HandleHookOptions {\n debugLog?: DebugLogger;\n}\n\n/**\n * Effectful handler: for `Stop`, parse the payload, read the transcript,\n * and build the MonitoringPayload. Non-Stop events return null. Never\n * throws — any unexpected shape silent-exits with null.\n */\nexport function handleAntigravityHook(\n eventName: string,\n payloadJson: unknown,\n config: AntigravityMonitorConfig,\n options: HandleHookOptions = {},\n): MonitoringPayload | null {\n const debugLog = options.debugLog ?? noopDebug;\n if (!isSupportedAntigravityEvent(eventName)) {\n debugLog(\"hook-unknown-event\", eventName);\n return null;\n }\n\n const payload = (payloadJson ?? {}) as AntigravityStopPayload;\n debugLog(\"event-parsed\", { eventName, payload });\n\n const extracted = extractFromTranscript(payload.transcriptPath, debugLog);\n const result = buildStopPayload(payload, extracted, config);\n if (!result) {\n debugLog(\"stop-empty\", { conversationId: payload.conversationId });\n return null;\n }\n debugLog(\"payload-built\", result);\n return result;\n}\n","/**\n * Antigravity (`agy`) transcript parser.\n *\n * The Stop hook payload carries a `transcriptPath` pointing at a JSONL\n * file. Each line is one step:\n *\n * { \"step_index\", \"source\", \"type\", \"status\", \"created_at\", \"content\" }\n *\n * We care about two line types:\n *\n * - `USER_INPUT` — the user's prompt. The LAST such line is the\n * prompt for this turn. Its `content` is wrapped like:\n * \"<USER_REQUEST>\\n<the real prompt>\\n</USER_REQUEST>\\n\n * <ADDITIONAL_METADATA>\\n...\\n</ADDITIONAL_METADATA>\"\n * We extract ONLY the text inside `<USER_REQUEST>...</USER_REQUEST>`\n * (trimmed); when the wrapper is absent we fall back to the raw\n * content.\n *\n * - `PLANNER_RESPONSE` — the assistant's response. The LAST such line\n * is the response for this turn.\n *\n * All other types (`CONVERSATION_HISTORY`, etc.) are ignored.\n *\n * Pure functions only (no filesystem I/O) so they can be unit-tested\n * without touching disk. Every access is defensive — a malformed line\n * never throws, it's simply skipped.\n */\n\nexport interface TranscriptLine {\n step_index?: number;\n source?: string;\n type?: string;\n status?: string;\n created_at?: string;\n content?: unknown;\n [extra: string]: unknown;\n}\n\nconst USER_REQUEST_RE = /<USER_REQUEST>([\\s\\S]*?)<\\/USER_REQUEST>/;\n\n/**\n * Parse raw JSONL text into an array of transcript lines. Lines that\n * aren't valid JSON objects are skipped (defensive against partial\n * flushes / trailing blank lines).\n */\nexport function parseTranscriptLines(raw: string): TranscriptLine[] {\n const lines: TranscriptLine[] = [];\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const parsed = JSON.parse(trimmed) as unknown;\n if (parsed && typeof parsed === \"object\") {\n lines.push(parsed as TranscriptLine);\n }\n } catch {\n // Skip malformed lines.\n }\n }\n return lines;\n}\n\nfunction asContentString(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\n/**\n * Extract the user prompt: the `content` of the LAST `USER_INPUT` line,\n * with the `<USER_REQUEST>...</USER_REQUEST>` body extracted (metadata\n * stripped). Falls back to the raw content when the wrapper is absent.\n * Returns \"\" when there is no USER_INPUT line.\n */\nexport function extractPrompt(lines: TranscriptLine[]): string {\n let lastContent: string | null = null;\n for (const line of lines) {\n if (line?.type === \"USER_INPUT\") {\n lastContent = asContentString(line.content);\n }\n }\n if (lastContent === null) return \"\";\n\n const match = USER_REQUEST_RE.exec(lastContent);\n if (match) {\n return match[1].trim();\n }\n return lastContent.trim();\n}\n\n/**\n * Extract the assistant response: the `content` of the LAST\n * `PLANNER_RESPONSE` line, trimmed. Returns \"\" when there is none.\n */\nexport function extractResponse(lines: TranscriptLine[]): string {\n let lastContent: string | null = null;\n for (const line of lines) {\n if (line?.type === \"PLANNER_RESPONSE\") {\n lastContent = asContentString(line.content);\n }\n }\n if (lastContent === null) return \"\";\n return lastContent.trim();\n}\n","/**\n * Programmatic entry point for \"run `olakai monitor init --tool X` for\n * me, but from inside another command\" (OLA-214 — chained `olakai init`\n * → `olakai monitor init`).\n *\n * `monitor.ts` already wires the user-facing Commander surface; this\n * module exposes the same install path so `init.ts` can drive it\n * without going through argv parsing. We deliberately do NOT re-export\n * `monitor init`'s `--tool` resolution / prompt — the wizard caller has\n * already decided which tool to install (via auto-detection +\n * per-tool consent), so we skip straight to `plugin.install(...)`.\n *\n * Errors from `plugin.install` bubble up so the caller (the wizard)\n * can decide whether to abort the whole chain or just skip this tool\n * and continue with the next one. The wizard chooses the latter — one\n * tool's API failure shouldn't strand the user without monitoring on\n * the other detected tools.\n */\nimport \"./plugins/index.js\";\nimport {\n getPlugin,\n type InstallResult,\n type ToolId,\n} from \"./plugin.js\";\nimport { getMonitorConfigPath } from \"./paths.js\";\nimport {\n getToolHooksPath,\n getToolScope,\n upsertEntry,\n type RegistryEntry,\n} from \"./registry.js\";\n\nexport interface RunMonitorInstallOpts {\n /** Defaults to `process.cwd()`. Threaded through to the plugin. */\n projectRoot?: string;\n /**\n * `true` when the wizard knows the terminal is interactive AND the\n * user did not pass `--non-interactive`. Plugins decide what this\n * means — most still prompt for required inputs even when\n * `interactive` is false, because they have no other way to obtain\n * (e.g.) a pasted API key. That's fine: non-interactive callers must\n * supply the inputs the plugins need ahead of time, and a missing\n * input becomes a fast-fail rather than a hang.\n */\n interactive?: boolean;\n}\n\nexport async function runMonitorInstall(\n toolId: ToolId,\n opts: RunMonitorInstallOpts = {},\n): Promise<InstallResult> {\n const plugin = getPlugin(toolId);\n const projectRoot = opts.projectRoot ?? process.cwd();\n const result = await plugin.install({\n projectRoot,\n interactive: opts.interactive ?? true,\n });\n\n // Record this install in the machine registry (D-001) so `monitor\n // list`/`doctor`/`repair` can see it without re-walking every disk.\n // Best-effort: a registry write failure must never fail the install,\n // and reconcile on the next `monitor list` will backfill from disk.\n recordInstall(toolId, projectRoot, result);\n\n return result;\n}\n\n/**\n * Upsert a registry entry for a just-completed install. Derives `scope`\n * and `hooksPath` from the tool id via the registry helpers (the plugin\n * contract doesn't expose those today). Swallows all errors — the config\n * file on disk remains the durable record, and reconcile rebuilds the\n * registry entry on the next `monitor list`.\n */\nfunction recordInstall(\n toolId: ToolId,\n projectRoot: string,\n result: InstallResult,\n): void {\n try {\n const entry: RegistryEntry = {\n path: projectRoot,\n tool: toolId,\n scope: getToolScope(toolId),\n agentId: result.agentId,\n agentName: result.agentName,\n source: result.source,\n configPath: getMonitorConfigPath(projectRoot, toolId),\n hooksPath: getToolHooksPath(toolId, projectRoot),\n monitoringEndpoint: result.monitoringEndpoint,\n createdAt: new Date().toISOString(),\n };\n upsertEntry(entry);\n } catch {\n // Registry is a convenience index, not the source of truth — never\n // let its failure break a successful install.\n }\n}\n"],"mappings":"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","names":["registry","fs","spawnSync","fs","os","path","fs","path","path","path","fs","debugLog","spawnSync","fs","spawnSync","fs","path","os","path","OLAKAI_HOOK_MARKER","hasOlakaiHook","fs","path","getCodexConfigPath","getCodexConfigPath","path","fs","getCodexConfigPath","fs","path","noopDebug","debugLog","noopDebug","debugLog","TOOL_ID","debugLog","getCodexConfigPath","spawnSync","fs","os","fs","os","path","fs","path","os","path","OLAKAI_HOOK_MARKER","fs","os","path","fs","os","path","STATE_DIR_SEGMENTS","TOOL_ID","debugLog","fs","spawnSync","fs","os","path","fs","path","os","path","fs","path","OLAKAI_HOOK_MARKER","mergeHooksSettings","hasOlakaiHook","hasOlakaiHooksInstalled","readJsonFile","writeJsonFile","readJsonFile","mergeHooksSettings","writeJsonFile","hasOlakaiHooksInstalled","os","path","readJsonFile","hasOlakaiHooksInstalled","fs","os","path","STATE_DIR_SEGMENTS","getPairingsDir","sanitizeKeyFragment","getPairingFile","noopDebug","SUPPORTED_EVENTS","asString","asNumber","inputTokens","outputTokens","explicitTotal","totalTokens","debugLog","payload","TOOL_ID","debugLog","spawnSync","fs","spawnSync","fs","os","path","fs","path","os","path","GEMINI_HOME_DIRNAME","getGeminiHomeDir","fs","path","OLAKAI_HOOK_MARKER","buildOlakaiHookEntry","entryContainsOlakaiHook","removeOlakaiHooks","readJsonFile","writeJsonFile","readJsonFile","writeJsonFile","removeOlakaiHooks","os","path","HOOKS_DISPLAY","readJsonFile","fs","noopDebug","SUPPORTED_EVENTS","asString","extractFromTranscript","debugLog","TOOL_ID","debugLog","spawnSync"]}
|