@ken-jo/agent-connector 0.1.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.
Files changed (203) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +348 -0
  4. package/dist/amp-QSFH2V7Y.js +217 -0
  5. package/dist/amp-QSFH2V7Y.js.map +1 -0
  6. package/dist/amp-TULUPXXL.js +237 -0
  7. package/dist/amp-TULUPXXL.js.map +1 -0
  8. package/dist/antigravity-CX2YLSY7.js +17 -0
  9. package/dist/antigravity-CX2YLSY7.js.map +1 -0
  10. package/dist/antigravity-STHZWMP3.js +171 -0
  11. package/dist/antigravity-STHZWMP3.js.map +1 -0
  12. package/dist/antigravity-cli-P6HADOAO.js +121 -0
  13. package/dist/antigravity-cli-P6HADOAO.js.map +1 -0
  14. package/dist/antigravity-cli-YQDFVV4U.js +86 -0
  15. package/dist/antigravity-cli-YQDFVV4U.js.map +1 -0
  16. package/dist/chunk-54EMKTTX.js +191 -0
  17. package/dist/chunk-54EMKTTX.js.map +1 -0
  18. package/dist/chunk-7L6ITC4T.js +54 -0
  19. package/dist/chunk-7L6ITC4T.js.map +1 -0
  20. package/dist/chunk-7MSDGHJW.js +316 -0
  21. package/dist/chunk-7MSDGHJW.js.map +1 -0
  22. package/dist/chunk-BCHWFTV4.js +196 -0
  23. package/dist/chunk-BCHWFTV4.js.map +1 -0
  24. package/dist/chunk-DBQMX75U.js +36 -0
  25. package/dist/chunk-DBQMX75U.js.map +1 -0
  26. package/dist/chunk-EXLS7BSI.js +29 -0
  27. package/dist/chunk-EXLS7BSI.js.map +1 -0
  28. package/dist/chunk-JNM3TPLX.js +593 -0
  29. package/dist/chunk-JNM3TPLX.js.map +1 -0
  30. package/dist/chunk-JWNT5KW7.js +197 -0
  31. package/dist/chunk-JWNT5KW7.js.map +1 -0
  32. package/dist/chunk-KHO5WNTP.js +30 -0
  33. package/dist/chunk-KHO5WNTP.js.map +1 -0
  34. package/dist/chunk-LTT56GI3.js +155 -0
  35. package/dist/chunk-LTT56GI3.js.map +1 -0
  36. package/dist/chunk-MEH7GC7R.js +111 -0
  37. package/dist/chunk-MEH7GC7R.js.map +1 -0
  38. package/dist/chunk-MQPNU2CY.js +64 -0
  39. package/dist/chunk-MQPNU2CY.js.map +1 -0
  40. package/dist/chunk-OPXAP3AC.js +12 -0
  41. package/dist/chunk-OPXAP3AC.js.map +1 -0
  42. package/dist/chunk-RK6VBLXP.js +50 -0
  43. package/dist/chunk-RK6VBLXP.js.map +1 -0
  44. package/dist/chunk-TIIP3RG2.js +420 -0
  45. package/dist/chunk-TIIP3RG2.js.map +1 -0
  46. package/dist/chunk-TRKWUNO4.js +702 -0
  47. package/dist/chunk-TRKWUNO4.js.map +1 -0
  48. package/dist/chunk-TRQKHP7D.js +93 -0
  49. package/dist/chunk-TRQKHP7D.js.map +1 -0
  50. package/dist/chunk-WCETQWWK.js +59 -0
  51. package/dist/chunk-WCETQWWK.js.map +1 -0
  52. package/dist/chunk-WV63X7IK.js +357 -0
  53. package/dist/chunk-WV63X7IK.js.map +1 -0
  54. package/dist/chunk-XD7MY6HB.js +343 -0
  55. package/dist/chunk-XD7MY6HB.js.map +1 -0
  56. package/dist/chunk-XNAM7F35.js +276 -0
  57. package/dist/chunk-XNAM7F35.js.map +1 -0
  58. package/dist/chunk-XUCWLJY5.js +238 -0
  59. package/dist/chunk-XUCWLJY5.js.map +1 -0
  60. package/dist/chunk-YNZU6PAV.js +800 -0
  61. package/dist/chunk-YNZU6PAV.js.map +1 -0
  62. package/dist/chunk-YT43MUSJ.js +146 -0
  63. package/dist/chunk-YT43MUSJ.js.map +1 -0
  64. package/dist/claude-code-HKDV7T32.js +637 -0
  65. package/dist/claude-code-HKDV7T32.js.map +1 -0
  66. package/dist/claude-code-W74MNTAC.js +652 -0
  67. package/dist/claude-code-W74MNTAC.js.map +1 -0
  68. package/dist/cli/sdk.d.ts +54 -0
  69. package/dist/cli/sdk.js +82 -0
  70. package/dist/cli/sdk.js.map +1 -0
  71. package/dist/cli.d.ts +2 -0
  72. package/dist/cli.js +15 -0
  73. package/dist/cli.js.map +1 -0
  74. package/dist/codebuff-3PMZJJHP.js +307 -0
  75. package/dist/codebuff-3PMZJJHP.js.map +1 -0
  76. package/dist/codebuff-QX57FJDN.js +220 -0
  77. package/dist/codebuff-QX57FJDN.js.map +1 -0
  78. package/dist/codex-JFQTS6LU.js +593 -0
  79. package/dist/codex-JFQTS6LU.js.map +1 -0
  80. package/dist/codex-Q3ATRRX3.js +364 -0
  81. package/dist/codex-Q3ATRRX3.js.map +1 -0
  82. package/dist/copilot-cli-MTCGHZ2T.js +622 -0
  83. package/dist/copilot-cli-MTCGHZ2T.js.map +1 -0
  84. package/dist/copilot-cli-SJGIXVSD.js +369 -0
  85. package/dist/copilot-cli-SJGIXVSD.js.map +1 -0
  86. package/dist/crush-I7PK3FGI.js +360 -0
  87. package/dist/crush-I7PK3FGI.js.map +1 -0
  88. package/dist/crush-LN5QFFR2.js +183 -0
  89. package/dist/crush-LN5QFFR2.js.map +1 -0
  90. package/dist/cursor-4TMXKJOZ.js +603 -0
  91. package/dist/cursor-4TMXKJOZ.js.map +1 -0
  92. package/dist/cursor-QZ6MTO74.js +194 -0
  93. package/dist/cursor-QZ6MTO74.js.map +1 -0
  94. package/dist/detect-WIAGVYS4.js +68 -0
  95. package/dist/detect-WIAGVYS4.js.map +1 -0
  96. package/dist/doctor-EZW2K4NI.js +372 -0
  97. package/dist/doctor-EZW2K4NI.js.map +1 -0
  98. package/dist/droid-KN6VRMMT.js +424 -0
  99. package/dist/droid-KN6VRMMT.js.map +1 -0
  100. package/dist/droid-W5WF5XEX.js +224 -0
  101. package/dist/droid-W5WF5XEX.js.map +1 -0
  102. package/dist/gemini-cli-CIRW37TU.js +369 -0
  103. package/dist/gemini-cli-CIRW37TU.js.map +1 -0
  104. package/dist/gemini-cli-Y35FFK4M.js +705 -0
  105. package/dist/gemini-cli-Y35FFK4M.js.map +1 -0
  106. package/dist/goose-A3M5GODH.js +212 -0
  107. package/dist/goose-A3M5GODH.js.map +1 -0
  108. package/dist/goose-ZTGTSUFJ.js +480 -0
  109. package/dist/goose-ZTGTSUFJ.js.map +1 -0
  110. package/dist/hermes-H2KV44GK.js +470 -0
  111. package/dist/hermes-H2KV44GK.js.map +1 -0
  112. package/dist/hermes-ZYLMOFLZ.js +196 -0
  113. package/dist/hermes-ZYLMOFLZ.js.map +1 -0
  114. package/dist/hook-YVDILAMZ.js +75 -0
  115. package/dist/hook-YVDILAMZ.js.map +1 -0
  116. package/dist/index.d.ts +22 -0
  117. package/dist/index.js +10 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/install-HQRYZOQ2.js +60 -0
  120. package/dist/install-HQRYZOQ2.js.map +1 -0
  121. package/dist/jetbrains-copilot-HWA3DMH7.js +633 -0
  122. package/dist/jetbrains-copilot-HWA3DMH7.js.map +1 -0
  123. package/dist/kilo-NSWAVP6P.js +235 -0
  124. package/dist/kilo-NSWAVP6P.js.map +1 -0
  125. package/dist/kilo-QXJRCSLT.js +384 -0
  126. package/dist/kilo-QXJRCSLT.js.map +1 -0
  127. package/dist/kilo-cli-EAQTHXUW.js +587 -0
  128. package/dist/kilo-cli-EAQTHXUW.js.map +1 -0
  129. package/dist/kilo-cli-EVBKUODA.js +160 -0
  130. package/dist/kilo-cli-EVBKUODA.js.map +1 -0
  131. package/dist/kimi-EI3UM3ZV.js +155 -0
  132. package/dist/kimi-EI3UM3ZV.js.map +1 -0
  133. package/dist/kimi-GQ7VXNYT.js +423 -0
  134. package/dist/kimi-GQ7VXNYT.js.map +1 -0
  135. package/dist/kiro-UCLSLBW2.js +217 -0
  136. package/dist/kiro-UCLSLBW2.js.map +1 -0
  137. package/dist/kiro-VQTT6LO2.js +451 -0
  138. package/dist/kiro-VQTT6LO2.js.map +1 -0
  139. package/dist/leaderboard-K6ON3SWW.js +137 -0
  140. package/dist/leaderboard-K6ON3SWW.js.map +1 -0
  141. package/dist/mux-T2TV4CIQ.js +211 -0
  142. package/dist/mux-T2TV4CIQ.js.map +1 -0
  143. package/dist/mux-ZMLVAXOO.js +96 -0
  144. package/dist/mux-ZMLVAXOO.js.map +1 -0
  145. package/dist/omp-PB4VDKS6.js +586 -0
  146. package/dist/omp-PB4VDKS6.js.map +1 -0
  147. package/dist/openclaw-ETN2M63O.js +751 -0
  148. package/dist/openclaw-ETN2M63O.js.map +1 -0
  149. package/dist/openclaw-IYA7ASL6.js +150 -0
  150. package/dist/openclaw-IYA7ASL6.js.map +1 -0
  151. package/dist/opencode-FZ33JKUZ.js +734 -0
  152. package/dist/opencode-FZ33JKUZ.js.map +1 -0
  153. package/dist/opencode-HWT3RSMN.js +216 -0
  154. package/dist/opencode-HWT3RSMN.js.map +1 -0
  155. package/dist/package-DLOVE2E4.js +1132 -0
  156. package/dist/package-DLOVE2E4.js.map +1 -0
  157. package/dist/pi-CIU2QHT4.js +208 -0
  158. package/dist/pi-CIU2QHT4.js.map +1 -0
  159. package/dist/pi-UFX2OYFV.js +139 -0
  160. package/dist/pi-UFX2OYFV.js.map +1 -0
  161. package/dist/qwen-code-5POCNU6P.js +573 -0
  162. package/dist/qwen-code-5POCNU6P.js.map +1 -0
  163. package/dist/qwen-code-W7NUCDEN.js +134 -0
  164. package/dist/qwen-code-W7NUCDEN.js.map +1 -0
  165. package/dist/roo-code-IOIUXRDE.js +242 -0
  166. package/dist/roo-code-IOIUXRDE.js.map +1 -0
  167. package/dist/roo-code-ZLKH4A7N.js +229 -0
  168. package/dist/roo-code-ZLKH4A7N.js.map +1 -0
  169. package/dist/runtime/index.d.ts +157 -0
  170. package/dist/runtime/index.js +20 -0
  171. package/dist/runtime/index.js.map +1 -0
  172. package/dist/serve-HVWWY4B6.js +65 -0
  173. package/dist/serve-HVWWY4B6.js.map +1 -0
  174. package/dist/status-FE7TECZD.js +128 -0
  175. package/dist/status-FE7TECZD.js.map +1 -0
  176. package/dist/synthetic-UKR6SMGS.js +178 -0
  177. package/dist/synthetic-UKR6SMGS.js.map +1 -0
  178. package/dist/telemetry-36GESKQQ.js +349 -0
  179. package/dist/telemetry-36GESKQQ.js.map +1 -0
  180. package/dist/trae-JSNDZFKL.js +204 -0
  181. package/dist/trae-JSNDZFKL.js.map +1 -0
  182. package/dist/trae-PA26DPBW.js +157 -0
  183. package/dist/trae-PA26DPBW.js.map +1 -0
  184. package/dist/types-CYUB1OTr.d.ts +427 -0
  185. package/dist/uninstall-IGCKNOSF.js +72 -0
  186. package/dist/uninstall-IGCKNOSF.js.map +1 -0
  187. package/dist/upgrade-JV2VK242.js +104 -0
  188. package/dist/upgrade-JV2VK242.js.map +1 -0
  189. package/dist/usage-6ER6EMEY.js +324 -0
  190. package/dist/usage-6ER6EMEY.js.map +1 -0
  191. package/dist/usage-event-JKBI6XCF.js +48 -0
  192. package/dist/usage-event-JKBI6XCF.js.map +1 -0
  193. package/dist/vscode-copilot-CK3NO54M.js +715 -0
  194. package/dist/vscode-copilot-CK3NO54M.js.map +1 -0
  195. package/dist/warp-JNQA7PBU.js +126 -0
  196. package/dist/warp-JNQA7PBU.js.map +1 -0
  197. package/dist/warp-QNVDXOG4.js +199 -0
  198. package/dist/warp-QNVDXOG4.js.map +1 -0
  199. package/dist/zed-NNNX5MNT.js +272 -0
  200. package/dist/zed-NNNX5MNT.js.map +1 -0
  201. package/dist/zed-VRJKWYUR.js +209 -0
  202. package/dist/zed-VRJKWYUR.js.map +1 -0
  203. package/package.json +79 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/gemini-cli/index.ts"],"sourcesContent":["/**\n * adapters/gemini-cli — Gemini CLI platform adapter for agent-connector.\n *\n * Gemini CLI (Google) is a json-stdio host: the host pipes a JSON payload to a\n * hook command on stdin and reads a JSON control object / exit code back — the\n * same paradigm as Claude Code. Two things make Gemini distinct, and both are\n * handled below:\n *\n * 1. TRANSPORT IS SELECTED BY KEY (not a `type` field). Under `mcpServers`, a\n * stdio server is `{ command, args, env }`; an SSE server is `{ url }`; a\n * streamable-HTTP server is `{ httpUrl }`. We render stdio with\n * command/args/env and remote transports with the matching key.\n * 2. Gemini's hook EVENT VOCABULARY differs from the Claude-compatible hosts,\n * so our normalized event names are mapped to Gemini's:\n * PreToolUse → BeforeTool\n * PostToolUse → AfterTool\n * PreCompact → PreCompress\n * SessionStart→ SessionStart\n * UserPromptSubmit → BeforeAgent (BeforeAgent fires on prompt submit;\n * additionalContext is appended)\n * SessionEnd → SessionEnd\n * Notification→ Notification\n * `Stop` has no Gemini equivalent and is reported as a warn/skip at install.\n *\n * Native config (JSON):\n * - MCP servers: user → ~/.gemini/settings.json; project → .gemini/settings.json\n * (system scope is /etc/gemini-cli/settings.json — not a target we write).\n * Root key \"mcpServers\".\n * - Hooks: the SAME settings.json, top-level \"hooks\" key, keyed by the Gemini\n * event name, each value an array of `{ matcher, hooks: [{ type:\"command\",\n * command }] }` (the nested shape; Gemini accepts ONLY type \"command\").\n * - Reply (stdout, exit 0):\n * deny → { decision:\"deny\", reason }\n * modify → { hookSpecificOutput:{ tool_input } } (merged with original)\n * context → { hookSpecificOutput:{ additionalContext } }\n * PostToolUse output rewrite → { decision:\"deny\", reason:<newOutput> }\n * (Gemini replaces tool output via a deny+reason on AfterTool).\n * ask → no native \"ask\"; degrade to deny to stay fail-safe.\n *\n * NOTE: Gemini's AfterModel hook payload carries `usageMetadata.totalTokenCount`\n * (per-LLM-call real token usage — the one host-native usage signal in the\n * matrix). This is the OPT-IN host-native usage enricher (4a): when host-native\n * capture is enabled (telemetry.hostNativeUsage, or AGENT_CONNECTOR_HOST_NATIVE=1\n * at install), installHooks ALSO writes an AfterModel hook whose command is the\n * hidden `<homeBin> usage-event gemini-cli --connector <id>` entrypoint. That\n * records a DISTINCT `model_turn` telemetry row (confidence host-native) covering\n * the whole conversation turn — never summed with the per-MCP `serve`-proxy rows.\n * AfterModel is NOT a normalized connector event (no handler dispatch); it is a\n * host-native-only sink. When the opt-in is OFF, the hook is NOT installed and\n * telemetry continues to flow only through the `serve` proxy.\n *\n * Env handling: Gemini's native settings interpolation token is `${VAR}`/`$VAR`,\n * not the framework's `${env:VAR}` syntax. Rather than depend on that, env /\n * header / url refs are resolved to literals at install time via\n * resolveEnvRefsDeep — the safe default for a host without `${env:VAR}` support\n * (matching the Codex / Copilot CLI adapters).\n *\n * Grounded in docs/research/understand-report.md §2 (Platform Integration\n * Matrix, \"Gemini CLI\" row) and context-mode's proven gemini-cli adapter.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { BaseAdapter } from \"../base.js\";\nimport type { Adapter, HookReply, InstallContext, NormalizedEvent } from \"../spi.js\";\nimport type {\n ChangeRecord,\n CommandDef,\n DetectedPlatform,\n HealthCheck,\n HookEventName,\n HookParadigm,\n HookResponse,\n NotificationEvent,\n PlatformCapabilities,\n PlatformId,\n PostToolUseEvent,\n PreCompactEvent,\n PreToolUseEvent,\n ServerDef,\n SessionEndEvent,\n SessionStartEvent,\n SkillDef,\n SubagentDef,\n Transport,\n UserPromptSubmitEvent,\n} from \"../../core/types.js\";\nimport { resolveEnvRefsDeep } from \"../../core/interpolate.js\";\nimport { writeTomlString } from \"../../core/toml.js\";\nimport {\n buildHomeBinHookCommand,\n buildServeWrapperCommand,\n buildUsageEventCommand,\n isHomeBinHookCommand,\n isHostNativeUsageEnabled,\n isUsageEventCommand,\n shouldWrapForTelemetry,\n} from \"../../core/spawn.js\";\n\nconst HOST: PlatformId = \"gemini-cli\";\nconst MCP_ROOT_KEY = \"mcpServers\";\n\n/**\n * Gemini-native hook event names (settings.json top-level `hooks` keys).\n * Only the events Gemini fires for the JSON-stdio command paradigm we use.\n */\nconst GEMINI_EVENT = {\n BeforeTool: \"BeforeTool\",\n AfterTool: \"AfterTool\",\n PreCompress: \"PreCompress\",\n SessionStart: \"SessionStart\",\n SessionEnd: \"SessionEnd\",\n BeforeAgent: \"BeforeAgent\",\n Notification: \"Notification\",\n /** Fires AFTER each model turn; payload carries `usageMetadata` (opt-in 4a). */\n AfterModel: \"AfterModel\",\n} as const;\n\n/**\n * Map our normalized event names to Gemini's native hook event names. Only the\n * events Gemini actually supports are present; `Stop` has no Gemini equivalent\n * and is reported as a warn/skip at install time.\n */\nconst EVENT_MAP: Partial<Record<HookEventName, string>> = {\n PreToolUse: GEMINI_EVENT.BeforeTool,\n PostToolUse: GEMINI_EVENT.AfterTool,\n PreCompact: GEMINI_EVENT.PreCompress,\n SessionStart: GEMINI_EVENT.SessionStart,\n SessionEnd: GEMINI_EVENT.SessionEnd,\n UserPromptSubmit: GEMINI_EVENT.BeforeAgent,\n Notification: GEMINI_EVENT.Notification,\n};\n\n/** A single Gemini native hook registration entry (nested, Claude-shaped). */\ninterface GeminiHookEntry {\n matcher: string;\n hooks: Array<{ type: \"command\"; command: string }>;\n}\n\n/** The shape of Gemini's settings.json (only the parts we touch). */\ninterface GeminiSettingsFile {\n mcpServers?: Record<string, unknown>;\n hooks?: Record<string, GeminiHookEntry[]>;\n [key: string]: unknown;\n}\n\n/**\n * Native MCP server entry shapes Gemini accepts under `mcpServers`. Gemini\n * selects the transport by which key is present (NOT a `type` field):\n * stdio → command / args / env\n * SSE → url\n * streamable-HTTP→ httpUrl\n */\ninterface GeminiStdioServer {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n}\ninterface GeminiSseServer {\n url: string;\n headers?: Record<string, string>;\n}\ninterface GeminiHttpServer {\n httpUrl: string;\n headers?: Record<string, string>;\n}\n\n/** Raw Gemini CLI hook stdin payload (snake_case wire fields). */\ninterface GeminiWireInput {\n connector?: unknown;\n tool_name?: string;\n tool_input?: Record<string, unknown>;\n tool_output?: string;\n is_error?: boolean;\n session_id?: string;\n cwd?: string;\n source?: string;\n trigger?: string;\n reason?: string;\n prompt?: string;\n message?: string;\n}\n\nexport class GeminiCliAdapter extends BaseAdapter implements Adapter {\n readonly id: PlatformId = HOST;\n readonly name = \"Gemini CLI\";\n readonly paradigm: HookParadigm = \"json-stdio\";\n\n readonly capabilities: PlatformCapabilities = {\n // Gemini fires Before/AfterTool, PreCompress, SessionStart/End, BeforeAgent\n // (≈ UserPromptSubmit), and Notification. It has no `Stop` equivalent.\n preToolUse: true,\n postToolUse: true,\n preCompact: true,\n sessionStart: true,\n sessionEnd: true,\n userPromptSubmit: true,\n stop: false,\n notification: true,\n // BeforeTool can rewrite tool input (hookSpecificOutput.tool_input); unlike\n // the Claude-family hosts, AfterTool CAN replace already-emitted tool output\n // (via a deny + reason), so output modification is supported here.\n canModifyArgs: true,\n canModifyOutput: true,\n canInjectSessionContext: true,\n // Transport is selected by key: command/args (stdio), url (sse), httpUrl (http).\n transports: [\"stdio\", \"sse\", \"http\"],\n // Content surfaces: Gemini CLI supports all three. Commands are authored as\n // TOML (description + prompt with {{args}} placeholders); skills follow the\n // uniform <geminiDir>/skills/<name>/SKILL.md layout; subagents are md+fm.\n supportsCommands: true,\n supportsSkills: true,\n supportsSubagents: true,\n };\n\n // ── Detection ────────────────────────────────────────────────────────────\n\n detectInstalled(projectDir: string): DetectedPlatform {\n const userDir = join(homedir(), \".gemini\");\n const userSettings = join(userDir, \"settings.json\");\n const projectDirGemini = join(projectDir, \".gemini\");\n const projectSettings = join(projectDirGemini, \"settings.json\");\n const userInstalled = existsSync(userDir) || existsSync(userSettings);\n const projInstalled = existsSync(projectDirGemini) || existsSync(projectSettings);\n const installed = userInstalled || projInstalled;\n // Report the scope/path that actually matched, so a project-only install\n // isn't misreported as a (non-existent) user install.\n const scope = projInstalled && !userInstalled ? \"project\" : \"user\";\n const configPath = scope === \"project\" ? projectSettings : userSettings;\n return {\n id: this.id,\n name: this.name,\n installed,\n paradigm: this.paradigm,\n capabilities: this.capabilities,\n configPath,\n scope,\n reason: installed\n ? scope === \"project\"\n ? `found project Gemini CLI config at ${projectSettings}`\n : `found Gemini CLI config under ${userDir}`\n : `no Gemini CLI config at ${userDir}`,\n confidence: installed ? \"high\" : \"low\",\n };\n }\n\n // ── Native paths ─────────────────────────────────────────────────────────\n\n getConfigDir(ctx: InstallContext): string {\n return ctx.scope === \"project\"\n ? join(ctx.projectDir, \".gemini\")\n : join(homedir(), \".gemini\");\n }\n\n /** MCP servers live in settings.json under `mcpServers`. */\n getServerConfigPath(ctx: InstallContext): string {\n return join(this.getConfigDir(ctx), \"settings.json\");\n }\n\n /** Hooks live in the SAME settings.json under the top-level `hooks` key. */\n getHookConfigPath(ctx: InstallContext): string {\n return join(this.getConfigDir(ctx), \"settings.json\");\n }\n\n // ── MCP server install / uninstall ───────────────────────────────────────\n\n installServer(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n const override = connector.platforms[HOST]?.server;\n if (!connector.server || override === false) {\n return [\n {\n platform: this.id,\n action: \"skip\",\n detail: connector.server\n ? \"server registration disabled for gemini-cli\"\n : \"connector declares no MCP server\",\n },\n ];\n }\n\n // Shallow-merge any per-platform server override into the base ServerDef.\n const server: ServerDef =\n override && typeof override === \"object\"\n ? { ...connector.server, ...override }\n : connector.server;\n\n const serverPath = this.getServerConfigPath(ctx);\n const entry = this.renderServerEntry(ctx, server);\n\n return [\n this.upsertServerInJson(serverPath, MCP_ROOT_KEY, connector.id, entry, ctx.dryRun),\n ];\n }\n\n uninstallServer(ctx: InstallContext): ChangeRecord[] {\n const serverPath = this.getServerConfigPath(ctx);\n return [\n this.removeServerFromJson(serverPath, MCP_ROOT_KEY, ctx.connector.id, ctx.dryRun),\n ];\n }\n\n /**\n * Render a normalized ServerDef into Gemini's native mcpServers entry. The\n * transport is encoded by WHICH KEY is present, not a `type` field.\n */\n private renderServerEntry(\n ctx: InstallContext,\n server: ServerDef,\n ): GeminiStdioServer | GeminiSseServer | GeminiHttpServer {\n const transport: Transport = server.transport;\n\n if (transport === \"stdio\") {\n let command = server.command ?? \"\";\n let args = [...(server.args ?? [])];\n\n // Transparent telemetry wrapping: route the real command through\n // `<homeBin> serve --connector <id> -- <command> <args...>`.\n if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {\n const wrapped = buildServeWrapperCommand(\n ctx.homeBinPath,\n ctx.connector.id,\n command,\n args,\n ctx.scope,\n this.id,\n );\n command = wrapped.command;\n args = wrapped.args;\n }\n\n const entry: GeminiStdioServer = { command: resolveEnvRefsDeep(command) };\n if (args.length > 0) entry.args = args.map((a) => resolveEnvRefsDeep(a));\n const env = this.renderEnv(server.env);\n if (env) entry.env = env;\n if (server.cwd) entry.cwd = resolveEnvRefsDeep(server.cwd);\n return entry;\n }\n\n // SSE transport → `url` key.\n if (transport === \"sse\") {\n const entry: GeminiSseServer = { url: resolveEnvRefsDeep(server.url ?? \"\") };\n const headers = this.renderEnv(server.headers);\n if (headers) entry.headers = headers;\n return entry;\n }\n\n // http (streamable-HTTP) and any other remote transport → `httpUrl` key.\n const entry: GeminiHttpServer = { httpUrl: resolveEnvRefsDeep(server.url ?? \"\") };\n const headers = this.renderEnv(server.headers);\n if (headers) entry.headers = headers;\n return entry;\n }\n\n /**\n * Render env/header values. Gemini's native settings token is `${VAR}` (not\n * the framework's `${env:VAR}`), so refs resolve to literals at install time —\n * the safe default for a host without `${env:VAR}` interpolation.\n */\n private renderEnv(\n env: Record<string, string> | undefined,\n ): Record<string, string> | undefined {\n if (!env || Object.keys(env).length === 0) return undefined;\n return resolveEnvRefsDeep({ ...env });\n }\n\n // ── Hook install / uninstall ─────────────────────────────────────────────\n\n installHooks(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (connector.platforms[HOST]?.hooks === false) {\n return [{ platform: this.id, action: \"skip\", detail: \"hooks disabled for gemini-cli\" }];\n }\n // The opt-in host-native usage hook (4a) is a host-native-only sink that does\n // NOT need a connector handler — so it may be installed even for a connector\n // that declares no normalized hook events.\n const hostNative = isHostNativeUsageEnabled(connector.telemetry);\n if (connector.hookEvents.length === 0 && !hostNative) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no hooks\" }];\n }\n\n const settingsPath = this.getHookConfigPath(ctx);\n const settings = this.readJson<GeminiSettingsFile>(settingsPath) ?? {};\n const hooks = (settings.hooks ??= {});\n\n const changes: ChangeRecord[] = [];\n let mutated = false;\n\n for (const event of connector.hookEvents) {\n const geminiEvent = EVENT_MAP[event];\n if (!geminiEvent) {\n // No Gemini equivalent for this normalized event (e.g. Stop) — report.\n changes.push({\n platform: this.id,\n action: \"warn\",\n path: settingsPath,\n detail: `${event} has no Gemini CLI hook equivalent — skipped`,\n });\n continue;\n }\n\n const command = buildHomeBinHookCommand(ctx.homeBinPath, HOST, event, connector.id);\n const matcher = connector.hooks[event]?.matcher ?? \"\";\n const entry: GeminiHookEntry = {\n matcher,\n hooks: [{ type: \"command\", command }],\n };\n\n const bucket = (hooks[geminiEvent] ??= []);\n const existingIdx = bucket.findIndex((e) => this.entryHasOurCommand(e, ctx));\n\n if (existingIdx >= 0) {\n if (JSON.stringify(bucket[existingIdx]) === JSON.stringify(entry)) {\n changes.push({\n platform: this.id,\n action: \"skip\",\n path: settingsPath,\n detail: `hooks.${geminiEvent} already registered`,\n });\n continue;\n }\n bucket[existingIdx] = entry;\n changes.push({\n platform: this.id,\n action: \"update\",\n path: settingsPath,\n detail: `hooks.${geminiEvent}`,\n });\n } else {\n bucket.push(entry);\n changes.push({\n platform: this.id,\n action: \"create\",\n path: settingsPath,\n detail: `hooks.${geminiEvent}`,\n });\n }\n mutated = true;\n }\n\n // OPT-IN host-native usage hook (4a): when enabled, register an AfterModel\n // hook whose command is the hidden `usage-event` entrypoint. It carries no\n // matcher (it is not a tool event) and records a DISTINCT `model_turn` row.\n if (hostNative) {\n if (this.installUsageHook(hooks, ctx, settingsPath, changes)) mutated = true;\n }\n\n if (mutated) this.writeJson(settingsPath, settings, ctx.dryRun);\n return changes;\n }\n\n /**\n * Register the opt-in AfterModel host-native usage hook (4a) into `hooks`.\n * Idempotent: a byte-identical entry skips, a drifted one updates. Returns true\n * when the file was mutated. The command is the hidden `usage-event` entrypoint\n * (NOT a connector hook), so it carries an empty matcher.\n */\n private installUsageHook(\n hooks: Record<string, GeminiHookEntry[]>,\n ctx: InstallContext,\n settingsPath: string,\n changes: ChangeRecord[],\n ): boolean {\n const command = buildUsageEventCommand(ctx.homeBinPath, HOST, ctx.connector.id);\n const entry: GeminiHookEntry = {\n matcher: \"\",\n hooks: [{ type: \"command\", command }],\n };\n const bucket = (hooks[GEMINI_EVENT.AfterModel] ??= []);\n const existingIdx = bucket.findIndex((e) =>\n (e.hooks ?? []).some((h) => isUsageEventCommand(h.command, ctx.homeBinPath, ctx.connector.id)),\n );\n\n if (existingIdx >= 0) {\n if (JSON.stringify(bucket[existingIdx]) === JSON.stringify(entry)) {\n changes.push({\n platform: this.id,\n action: \"skip\",\n path: settingsPath,\n detail: `hooks.${GEMINI_EVENT.AfterModel} (host-native usage) already registered`,\n });\n return false;\n }\n bucket[existingIdx] = entry;\n changes.push({\n platform: this.id,\n action: \"update\",\n path: settingsPath,\n detail: `hooks.${GEMINI_EVENT.AfterModel} (host-native usage)`,\n });\n } else {\n bucket.push(entry);\n changes.push({\n platform: this.id,\n action: \"create\",\n path: settingsPath,\n detail: `hooks.${GEMINI_EVENT.AfterModel} (host-native usage)`,\n });\n }\n return true;\n }\n\n uninstallHooks(ctx: InstallContext): ChangeRecord[] {\n const settingsPath = this.getHookConfigPath(ctx);\n const settings = this.readJson<GeminiSettingsFile>(settingsPath);\n const hooks = settings?.hooks;\n if (!settings || !hooks) {\n return [\n {\n platform: this.id,\n action: \"skip\",\n path: settingsPath,\n detail: \"no hooks section present\",\n },\n ];\n }\n\n const changes: ChangeRecord[] = [];\n let mutated = false;\n\n for (const geminiEvent of Object.keys(hooks)) {\n const bucket = hooks[geminiEvent];\n if (!Array.isArray(bucket)) continue;\n\n // Strip our hook command from each entry; drop entries left empty so we\n // never remove another connector's (or the user's own) hook commands.\n const next: GeminiHookEntry[] = [];\n let removed = 0;\n for (const e of bucket) {\n const innerBefore = e.hooks?.length ?? 0;\n const inner = (e.hooks ?? []).filter((h) => !this.isOurCommand(h.command, ctx));\n removed += innerBefore - inner.length;\n if (inner.length > 0) next.push({ matcher: e.matcher ?? \"\", hooks: inner });\n }\n\n if (removed > 0) {\n if (next.length > 0) hooks[geminiEvent] = next;\n else delete hooks[geminiEvent];\n changes.push({\n platform: this.id,\n action: \"remove\",\n path: settingsPath,\n detail: `hooks.${geminiEvent} (${removed})`,\n });\n mutated = true;\n }\n }\n\n if (mutated) this.writeJson(settingsPath, settings, ctx.dryRun);\n if (changes.length === 0) {\n changes.push({\n platform: this.id,\n action: \"skip\",\n path: settingsPath,\n detail: \"no matching hook entries\",\n });\n }\n return changes;\n }\n\n private entryHasOurCommand(entry: GeminiHookEntry, ctx: InstallContext): boolean {\n return (entry.hooks ?? []).some((h) => this.isOurCommand(h.command, ctx));\n }\n\n /**\n * True when a hook command is ONE OF OURS for this connector — either the\n * universal `hook` dispatcher OR the opt-in `usage-event` sink. Both are\n * anchored on the connector id (see isHomeBinHookCommand) so a shared-prefix id\n * can't collide, and so uninstall reverses the AfterModel usage hook too.\n */\n private isOurCommand(command: string | undefined, ctx: InstallContext): boolean {\n return (\n isHomeBinHookCommand(command, ctx.homeBinPath, ctx.connector.id) ||\n isUsageEventCommand(command, ctx.homeBinPath, ctx.connector.id)\n );\n }\n\n // ── Content surfaces: commands / skills / subagents ──────────────────────\n // CONTENT-ONLY: pure native-file writers under <geminiDir>/{commands,skills,\n // agents}. No runtime dispatch, no home-bin pointer, no telemetry wrap. Each\n // method is idempotent (byte-identical → skip) via BaseAdapter.writeContentFile\n // and reversible via removeContentFile. Honors platforms[\"gemini-cli\"]\n // per-surface false to skip. Commands are TOML (Gemini's native format, with\n // {{args}} placeholders preserved verbatim); skills/subagents mirror the\n // Claude reference (uniform SKILL.md + md+frontmatter subagents).\n\n private commandsDir(ctx: InstallContext): string {\n return join(this.getConfigDir(ctx), \"commands\");\n }\n private skillsDir(ctx: InstallContext): string {\n return join(this.getConfigDir(ctx), \"skills\");\n }\n private agentsDir(ctx: InstallContext): string {\n return join(this.getConfigDir(ctx), \"agents\");\n }\n\n /** Native command file path: <geminiDir>/commands/<name>.toml. */\n private commandPath(ctx: InstallContext, name: string): string {\n return join(this.commandsDir(ctx), `${name}.toml`);\n }\n /** Native skill dir: <geminiDir>/skills/<name>. */\n private skillDir(ctx: InstallContext, name: string): string {\n return join(this.skillsDir(ctx), name);\n }\n /** Native subagent file path: <geminiDir>/agents/<name>.md. */\n private subagentPath(ctx: InstallContext, name: string): string {\n return join(this.agentsDir(ctx), `${name}.md`);\n }\n\n // ── Commands ──────────────────────────────────────────────────────────────\n\n override installCommands(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (connector.platforms[HOST]?.commands === false) {\n return [{ platform: this.id, action: \"skip\", detail: \"commands disabled for gemini-cli\" }];\n }\n if (connector.commands.length === 0) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no commands\" }];\n }\n return connector.commands.map((cmd) =>\n this.writeContentFile(this.commandPath(ctx, cmd.name), this.renderCommand(cmd), ctx.dryRun),\n );\n }\n\n override uninstallCommands(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (connector.commands.length === 0) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no commands\" }];\n }\n return connector.commands.map((cmd) =>\n this.removeContentFile(this.commandPath(ctx, cmd.name), ctx.dryRun),\n );\n }\n\n /**\n * Render a command to Gemini's native TOML: { description, prompt }. Gemini\n * uses `{{args}}` placeholders in the prompt; the prompt body is passed\n * through verbatim so any placeholders are preserved.\n */\n private renderCommand(cmd: CommandDef): string {\n const obj: Record<string, unknown> = {};\n if (cmd.description !== undefined) obj.description = cmd.description;\n obj.prompt = cmd.prompt;\n return writeTomlString(obj);\n }\n\n // ── Skills ────────────────────────────────────────────────────────────────\n\n override installSkills(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (connector.platforms[HOST]?.skills === false) {\n return [{ platform: this.id, action: \"skip\", detail: \"skills disabled for gemini-cli\" }];\n }\n if (connector.skills.length === 0) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no skills\" }];\n }\n const changes: ChangeRecord[] = [];\n for (const skill of connector.skills) {\n const dir = this.skillDir(ctx, skill.name);\n changes.push(\n this.writeContentFile(join(dir, \"SKILL.md\"), this.renderSkill(skill), ctx.dryRun),\n );\n // Bundle any resource files beside SKILL.md (relative path → contents).\n // Defense-in-depth: skip+warn on any key that escapes the skill dir\n // (config-time validation already rejects these, but never trust input).\n for (const [rel, contents] of Object.entries(skill.resources ?? {})) {\n const target = this.resolveWithin(dir, rel);\n if (target === null) {\n changes.push({\n platform: this.id,\n action: \"warn\",\n detail: `skill resource \"${rel}\" escapes the skill dir; skipped`,\n });\n continue;\n }\n changes.push(this.writeContentFile(target, contents, ctx.dryRun));\n }\n }\n return changes;\n }\n\n override uninstallSkills(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (connector.skills.length === 0) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no skills\" }];\n }\n const changes: ChangeRecord[] = [];\n for (const skill of connector.skills) {\n const dir = this.skillDir(ctx, skill.name);\n // Remove only the files we wrote (SKILL.md + declared resources), then the\n // skill dir itself when we own its full contents.\n changes.push(this.removeContentFile(join(dir, \"SKILL.md\"), ctx.dryRun));\n for (const rel of Object.keys(skill.resources ?? {})) {\n const target = this.resolveWithin(dir, rel);\n if (target === null) continue; // never delete outside the skill dir\n changes.push(this.removeContentFile(target, ctx.dryRun));\n }\n // Only remove the skill dir when WE own its full contents — never rm -rf a\n // dir that still holds user-added / sibling-tool / shared files.\n changes.push(this.removeDirIfEmpty(dir, ctx.dryRun));\n }\n return changes;\n }\n\n /**\n * Render a skill's SKILL.md: frontmatter (name, description + optional model,\n * allowed-tools, disable-model-invocation) + body. Uniform across platforms.\n */\n private renderSkill(skill: SkillDef): string {\n const frontmatter: Record<string, unknown> = {\n name: skill.name,\n description: skill.description,\n };\n if (skill.model !== undefined) frontmatter.model = skill.model;\n const allow = skill.tools?.allow;\n if (allow && allow.length > 0) frontmatter[\"allowed-tools\"] = allow.join(\", \");\n if (skill.disableModelInvocation !== undefined) {\n frontmatter[\"disable-model-invocation\"] = skill.disableModelInvocation;\n }\n if (skill.extra) Object.assign(frontmatter, skill.extra);\n return this.renderFrontmatterMd(frontmatter, skill.body);\n }\n\n // ── Subagents ───────────────────────────────────────────────────────────────\n\n override installSubagents(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (connector.platforms[HOST]?.subagents === false) {\n return [{ platform: this.id, action: \"skip\", detail: \"subagents disabled for gemini-cli\" }];\n }\n if (connector.subagents.length === 0) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no subagents\" }];\n }\n return connector.subagents.map((agent) =>\n this.writeContentFile(this.subagentPath(ctx, agent.name), this.renderSubagent(agent), ctx.dryRun),\n );\n }\n\n override uninstallSubagents(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (connector.subagents.length === 0) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no subagents\" }];\n }\n return connector.subagents.map((agent) =>\n this.removeContentFile(this.subagentPath(ctx, agent.name), ctx.dryRun),\n );\n }\n\n /** Render a subagent to md+frontmatter (name, description, tools, model) + prompt body. */\n private renderSubagent(agent: SubagentDef): string {\n const frontmatter: Record<string, unknown> = {\n name: agent.name,\n description: agent.description,\n };\n const allow = agent.tools?.allow;\n if (allow && allow.length > 0) frontmatter.tools = allow.join(\", \");\n if (agent.model !== undefined) frontmatter.model = agent.model;\n if (agent.extra) Object.assign(frontmatter, agent.extra);\n return this.renderFrontmatterMd(frontmatter, agent.prompt);\n }\n\n // ── Diagnostics ──────────────────────────────────────────────────────────\n\n override getHealthChecks(ctx: InstallContext): readonly HealthCheck[] {\n const settingsPath = this.getServerConfigPath(ctx);\n const connectorId = ctx.connector.id;\n const homeBin = ctx.homeBinPath;\n const hookEvents = ctx.connector.hookEvents;\n const checks: HealthCheck[] = [\n {\n name: `${this.name}: settings.json present`,\n check: () =>\n existsSync(settingsPath)\n ? { status: \"OK\", detail: settingsPath }\n : { status: \"FAIL\", detail: `not found: ${settingsPath}` },\n },\n {\n name: `${this.name}: hook command registered`,\n check: () => {\n if (hookEvents.length === 0) {\n return { status: \"OK\", detail: \"no hooks declared\" };\n }\n const settings = this.readJson<GeminiSettingsFile>(settingsPath);\n if (!settings) return { status: \"FAIL\", detail: `cannot read ${settingsPath}` };\n const hooks = settings.hooks ?? {};\n const registered = Object.values(hooks).some((entries) =>\n (entries ?? []).some((e) =>\n (e.hooks ?? []).some((h) =>\n isHomeBinHookCommand(h.command, homeBin, connectorId),\n ),\n ),\n );\n return registered\n ? { status: \"OK\", detail: \"hook command present\" }\n : { status: \"FAIL\", detail: `no hook for ${connectorId} in ${settingsPath}` };\n },\n },\n ];\n\n // Content-surface checks: only assert presence of the files this connector\n // declares (skip silently for surfaces it never asked for).\n for (const cmd of ctx.connector.commands) {\n const p = this.commandPath(ctx, cmd.name);\n checks.push({\n name: `${this.name}: command ${cmd.name} present`,\n check: () =>\n existsSync(p) ? { status: \"OK\", detail: p } : { status: \"FAIL\", detail: `not found: ${p}` },\n });\n }\n for (const skill of ctx.connector.skills) {\n const p = join(this.skillDir(ctx, skill.name), \"SKILL.md\");\n checks.push({\n name: `${this.name}: skill ${skill.name} present`,\n check: () =>\n existsSync(p) ? { status: \"OK\", detail: p } : { status: \"FAIL\", detail: `not found: ${p}` },\n });\n }\n for (const agent of ctx.connector.subagents) {\n const p = this.subagentPath(ctx, agent.name);\n checks.push({\n name: `${this.name}: subagent ${agent.name} present`,\n check: () =>\n existsSync(p) ? { status: \"OK\", detail: p } : { status: \"FAIL\", detail: `not found: ${p}` },\n });\n }\n return checks;\n }\n\n // ── Runtime: parse Gemini stdin JSON → normalized event ──────────────────\n\n parseEvent(event: HookEventName, raw: unknown): NormalizedEvent {\n const input = (raw ?? {}) as GeminiWireInput;\n const connectorId = typeof input.connector === \"string\" ? input.connector : \"\";\n const sessionId = typeof input.session_id === \"string\" ? input.session_id : \"\";\n const projectDir = typeof input.cwd === \"string\" ? input.cwd : undefined;\n\n const base = {\n hostPlatform: HOST,\n connectorId,\n sessionId,\n raw,\n ...(projectDir !== undefined ? { projectDir } : {}),\n } as const;\n\n switch (event) {\n case \"PreToolUse\": {\n const ev: PreToolUseEvent = {\n ...base,\n toolName: input.tool_name ?? \"\",\n toolInput: input.tool_input ?? {},\n };\n return ev;\n }\n case \"PostToolUse\": {\n const ev: PostToolUseEvent = {\n ...base,\n toolName: input.tool_name ?? \"\",\n toolInput: input.tool_input ?? {},\n ...(input.tool_output !== undefined ? { toolOutput: input.tool_output } : {}),\n ...(input.is_error === true ? { isError: true } : {}),\n };\n return ev;\n }\n case \"SessionStart\": {\n const ev: SessionStartEvent = {\n ...base,\n source: normalizeSessionSource(input.source),\n };\n return ev;\n }\n case \"SessionEnd\": {\n const ev: SessionEndEvent = {\n ...base,\n ...(typeof input.reason === \"string\" ? { reason: input.reason } : {}),\n };\n return ev;\n }\n case \"UserPromptSubmit\": {\n const ev: UserPromptSubmitEvent = {\n ...base,\n prompt: typeof input.prompt === \"string\" ? input.prompt : \"\",\n };\n return ev;\n }\n case \"PreCompact\": {\n const ev: PreCompactEvent = {\n ...base,\n ...(input.trigger === \"auto\" || input.trigger === \"manual\"\n ? { trigger: input.trigger }\n : {}),\n };\n return ev;\n }\n case \"Notification\": {\n const ev: NotificationEvent = {\n ...base,\n message: typeof input.message === \"string\" ? input.message : \"\",\n };\n return ev;\n }\n default: {\n // Gemini never delivers `Stop` (no native equivalent). If the runtime\n // dispatches one anyway, surface it as an explicit unsupported error so\n // the mismatch is loud rather than silently mis-parsed.\n throw new Error(`unsupported gemini-cli hook event: ${String(event)}`);\n }\n }\n }\n\n // ── Runtime: normalized response → Gemini native hook reply ──────────────\n\n formatReply(event: HookEventName, response: HookResponse): HookReply {\n const decision = response.decision ?? \"allow\";\n\n // deny → Gemini blocks via a top-level `decision:\"deny\"` + reason (NOT the\n // Claude-style permissionDecision wrapper).\n if (decision === \"deny\") {\n return this.stdout({\n decision: \"deny\",\n reason: response.reason ?? \"Blocked by hook\",\n });\n }\n\n // ask → Gemini has no native \"ask\"; degrade to deny to stay fail-safe.\n if (decision === \"ask\") {\n return this.stdout({\n decision: \"deny\",\n reason: response.reason ?? \"Action requires user confirmation (security policy)\",\n });\n }\n\n // modify → rewrite BeforeTool input. Gemini merges tool_input with the\n // original; output rewrite on AfterTool is expressed as deny + reason.\n if (decision === \"modify\") {\n if (event === \"PreToolUse\" && response.updatedInput) {\n return this.stdout({\n hookSpecificOutput: { tool_input: response.updatedInput },\n });\n }\n if (event === \"PostToolUse\" && response.updatedOutput !== undefined) {\n return this.stdout({ decision: \"deny\", reason: response.updatedOutput });\n }\n // Nothing to apply; fall through to allow.\n }\n\n // context → inject soft guidance via hookSpecificOutput.additionalContext\n // (also the SessionStart / BeforeAgent context-injection path).\n if (decision === \"context\" && response.additionalContext) {\n return this.stdout({\n hookSpecificOutput: { additionalContext: response.additionalContext },\n });\n }\n\n // allow / void / unsupported-degradation → pass through with exit 0.\n return { exitCode: 0 };\n }\n\n private stdout(payload: unknown): HookReply {\n return { exitCode: 0, stdout: JSON.stringify(payload) };\n }\n}\n\nfunction normalizeSessionSource(source: string | undefined): SessionStartEvent[\"source\"] {\n switch (source) {\n case \"compact\":\n return \"compact\";\n case \"resume\":\n return \"resume\";\n case \"clear\":\n return \"clear\";\n default:\n return \"startup\";\n }\n}\n\nexport const adapter = new GeminiCliAdapter();\nexport default adapter;\n"],"mappings":"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","names":["entry","headers"]}
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ openSqlite
4
+ } from "./chunk-YT43MUSJ.js";
5
+ import {
6
+ firstExistingRoot
7
+ } from "./chunk-JWNT5KW7.js";
8
+ import {
9
+ emptyTokens,
10
+ inferProvider
11
+ } from "./chunk-XD7MY6HB.js";
12
+
13
+ // src/usage/readers/goose.ts
14
+ var PLATFORM_ID = "goose";
15
+ var DEFAULT_PROVIDER = "goose";
16
+ var QUERY = `
17
+ SELECT
18
+ id,
19
+ model_config_json,
20
+ provider_name,
21
+ created_at,
22
+ total_tokens,
23
+ input_tokens,
24
+ output_tokens,
25
+ accumulated_total_tokens,
26
+ accumulated_input_tokens,
27
+ accumulated_output_tokens
28
+ FROM sessions
29
+ WHERE model_config_json IS NOT NULL
30
+ AND TRIM(model_config_json) != ''
31
+ `;
32
+ function parseModelConfig(json) {
33
+ let parsed;
34
+ try {
35
+ parsed = JSON.parse(json);
36
+ } catch {
37
+ return null;
38
+ }
39
+ if (typeof parsed !== "object" || parsed === null) return null;
40
+ const name = parsed.model_name;
41
+ if (typeof name !== "string") return null;
42
+ const trimmed = name.trim();
43
+ return trimmed === "" ? null : trimmed;
44
+ }
45
+ function canonicalizeProviderSegment(segment) {
46
+ const normalized = segment.trim().replace(/\/+$/, "").toLowerCase().replace(/-/g, "_");
47
+ if (normalized.startsWith("<") && normalized.endsWith(">")) return void 0;
48
+ switch (normalized) {
49
+ case "":
50
+ case "unknown":
51
+ return void 0;
52
+ case "x_ai":
53
+ case "xai":
54
+ return "xai";
55
+ case "z_ai":
56
+ case "zai":
57
+ return "zai";
58
+ case "moonshot":
59
+ case "moonshotai":
60
+ return "moonshotai";
61
+ case "meta":
62
+ case "meta_llama":
63
+ return "meta_llama";
64
+ case "azure":
65
+ case "azure_ai":
66
+ return "azure_ai";
67
+ case "anthropic":
68
+ case "vertex":
69
+ case "vertex_ai":
70
+ return "anthropic";
71
+ case "together":
72
+ case "together_ai":
73
+ return "together_ai";
74
+ case "fireworks":
75
+ case "fireworks_ai":
76
+ return "fireworks_ai";
77
+ case "google":
78
+ case "gemini":
79
+ return "google";
80
+ case "openai":
81
+ case "openai_codex":
82
+ return "openai";
83
+ case "minimax":
84
+ case "minimaxai":
85
+ case "minimax_ai":
86
+ return "minimax";
87
+ case "mistral":
88
+ case "mistralai":
89
+ return "mistralai";
90
+ case "ai21":
91
+ return "ai21";
92
+ default:
93
+ if (/[0-9]/.test(normalized)) return void 0;
94
+ return normalized;
95
+ }
96
+ }
97
+ function canonicalProvider(raw) {
98
+ for (const segment of raw.trim().replace(/\/+$/, "").split("/")) {
99
+ const tag = canonicalizeProviderSegment(segment);
100
+ if (tag !== void 0) return tag;
101
+ if (segment.includes(".")) {
102
+ for (const dotted of segment.split(".")) {
103
+ const dottedTag = canonicalizeProviderSegment(dotted);
104
+ if (dottedTag !== void 0) return dottedTag;
105
+ }
106
+ }
107
+ }
108
+ return void 0;
109
+ }
110
+ function resolvedProvider(providerName, modelId) {
111
+ if (typeof providerName === "string") {
112
+ const trimmed = providerName.trim();
113
+ if (trimmed !== "") {
114
+ const canonical = canonicalProvider(trimmed);
115
+ if (canonical !== void 0) return canonical;
116
+ }
117
+ }
118
+ return inferProvider(modelId) ?? DEFAULT_PROVIDER;
119
+ }
120
+ function parseCreatedAt(s) {
121
+ const dt = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(s);
122
+ if (dt) {
123
+ const ms = Date.UTC(
124
+ Number(dt[1]),
125
+ Number(dt[2]) - 1,
126
+ Number(dt[3]),
127
+ Number(dt[4]),
128
+ Number(dt[5]),
129
+ Number(dt[6])
130
+ );
131
+ return Number.isNaN(ms) ? 0 : ms;
132
+ }
133
+ const d = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
134
+ if (d) {
135
+ const ms = Date.UTC(Number(d[1]), Number(d[2]) - 1, Number(d[3]));
136
+ return Number.isNaN(ms) ? 0 : ms;
137
+ }
138
+ const iso = Date.parse(s);
139
+ if (!Number.isNaN(iso)) return iso;
140
+ return 0;
141
+ }
142
+ function timestampSecsToMs(ts) {
143
+ return ts > 1e12 ? Math.trunc(ts) : Math.trunc(ts * 1e3);
144
+ }
145
+ function toIntOrNull(v) {
146
+ if (v === null || v === void 0) return null;
147
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
148
+ if (!Number.isFinite(n)) return null;
149
+ return Math.trunc(n);
150
+ }
151
+ function preferAccumulated(accumulated, plain) {
152
+ const acc = toIntOrNull(accumulated);
153
+ const value = acc ?? toIntOrNull(plain) ?? 0;
154
+ return Math.max(0, value);
155
+ }
156
+ var gooseReader = {
157
+ platformId: PLATFORM_ID,
158
+ kind: "local",
159
+ async read({ sinceMs }) {
160
+ const dbPath = firstExistingRoot(PLATFORM_ID);
161
+ if (dbPath === void 0) return [];
162
+ const db = await openSqlite(dbPath);
163
+ if (db === null) return [];
164
+ try {
165
+ const rows = db.all(QUERY);
166
+ const records = [];
167
+ for (const raw of rows) {
168
+ const row = raw;
169
+ const sessionId = row.id;
170
+ if (typeof sessionId !== "string" || sessionId === "") continue;
171
+ const modelConfig = row.model_config_json;
172
+ if (typeof modelConfig !== "string") continue;
173
+ const modelId = parseModelConfig(modelConfig);
174
+ if (modelId === null) continue;
175
+ const input = preferAccumulated(row.accumulated_input_tokens, row.input_tokens);
176
+ const output = preferAccumulated(row.accumulated_output_tokens, row.output_tokens);
177
+ const total = preferAccumulated(row.accumulated_total_tokens, row.total_tokens);
178
+ if (input === 0 && output === 0 && total === 0) continue;
179
+ const reasoning = total > input + output ? total - input - output : 0;
180
+ const createdAtRaw = typeof row.created_at === "string" ? row.created_at : "";
181
+ const ts = timestampSecsToMs(parseCreatedAt(createdAtRaw));
182
+ if (sinceMs !== void 0 && ts < sinceMs) continue;
183
+ const providerId = resolvedProvider(row.provider_name, modelId);
184
+ const tokens = emptyTokens();
185
+ tokens.input = input;
186
+ tokens.output = output;
187
+ tokens.reasoning = reasoning;
188
+ const record = {
189
+ platformId: PLATFORM_ID,
190
+ modelId,
191
+ providerId,
192
+ sessionId,
193
+ tokens,
194
+ ts,
195
+ messageCount: 1,
196
+ dedupKey: sessionId,
197
+ // one row per session → session id is the dedup key
198
+ confidence: "host-reported"
199
+ };
200
+ records.push(record);
201
+ }
202
+ return records;
203
+ } finally {
204
+ db.close();
205
+ }
206
+ }
207
+ };
208
+ var goose_default = gooseReader;
209
+ export {
210
+ goose_default as default
211
+ };
212
+ //# sourceMappingURL=goose-A3M5GODH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/usage/readers/goose.ts"],"sourcesContent":["/**\n * usage/readers/goose — Goose CLI usage reader (SQLite, sessions table).\n *\n * Faithful port of tokscale sessions/goose.rs. Goose stores one row per session\n * in a `sessions` table inside a single SQLite database:\n * • Linux: ~/.local/share/goose/sessions/sessions.db\n * • macOS: ~/Library/Application Support/goose/sessions/sessions.db\n * • Legacy: ~/.local/share/Block/goose/sessions/sessions.db\n * (host roots, incl. the AGENT_CONNECTOR_GOOSE_DIR override, are resolved by\n * paths.ts hostRoots(\"goose\"); the macOS / Block variants are listed there.)\n *\n * One SELECT pulls every session that carries a model config:\n * SELECT id, model_config_json, provider_name, created_at,\n * total_tokens, input_tokens, output_tokens,\n * accumulated_total_tokens, accumulated_input_tokens,\n * accumulated_output_tokens\n * FROM sessions\n * WHERE model_config_json IS NOT NULL AND TRIM(model_config_json) != ''\n *\n * Token extraction (flat columns, no JSON token nesting):\n * input = accumulated_input_tokens ?? input_tokens ?? 0 (clamped ≥ 0)\n * output = accumulated_output_tokens ?? output_tokens ?? 0 (clamped ≥ 0)\n * total = accumulated_total_tokens ?? total_tokens ?? 0 (clamped ≥ 0)\n * reasoning = total > input + output ? total - input - output : 0 (INFERRED)\n * cacheRead / cacheWrite are always 0 (Goose does not report them).\n * Rows with input == 0 && output == 0 && total == 0 are skipped.\n *\n * The reasoning value is *inferred*, not host-reported (Goose has no reasoning\n * column); we surface it but flag the inference in `detail`.\n *\n * Model: `model_config_json` is parsed for its `model_name` field (trimmed; the\n * row is dropped when it is missing/empty). Provider: canonical_provider() of the\n * `provider_name` column when present, else inferProvider(model), else \"goose\".\n * Timestamp: `created_at` (RFC3339, \"YYYY-MM-DD HH:MM:SS\", or \"YYYY-MM-DD\"),\n * parsed to epoch ms then disambiguated s↔ms. Session id = the `id` column, which\n * is also the dedup key (one row per session). No project/workspace in the schema.\n * Confidence is \"host-reported\".\n *\n * Fail-open: db missing/locked/unreadable → openSqlite returns null → []; a bad\n * row is skipped, never thrown.\n */\n\nimport type { UsageReader, UsageRecord } from \"../types.js\";\nimport { emptyTokens } from \"../aggregate.js\";\nimport { inferProvider } from \"../normalize.js\";\nimport { firstExistingRoot } from \"../paths.js\";\nimport { openSqlite } from \"../sqlite.js\";\n\nconst PLATFORM_ID = \"goose\" as const;\nconst DEFAULT_PROVIDER = \"goose\";\n\n/** The session columns we read (everything optional / unknown off the row). */\ninterface GooseRow {\n id?: unknown;\n model_config_json?: unknown;\n provider_name?: unknown;\n created_at?: unknown;\n total_tokens?: unknown;\n input_tokens?: unknown;\n output_tokens?: unknown;\n accumulated_total_tokens?: unknown;\n accumulated_input_tokens?: unknown;\n accumulated_output_tokens?: unknown;\n}\n\nconst QUERY = `\n SELECT\n id,\n model_config_json,\n provider_name,\n created_at,\n total_tokens,\n input_tokens,\n output_tokens,\n accumulated_total_tokens,\n accumulated_input_tokens,\n accumulated_output_tokens\n FROM sessions\n WHERE model_config_json IS NOT NULL\n AND TRIM(model_config_json) != ''\n`;\n\n/**\n * Parse `model_config_json` for its `model_name` (port of parse_model_config):\n * JSON-parse the column, take the trimmed `model_name` string, or null when the\n * json is invalid / the name is absent or empty.\n */\nfunction parseModelConfig(json: string): string | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const name = (parsed as { model_name?: unknown }).model_name;\n if (typeof name !== \"string\") return null;\n const trimmed = name.trim();\n return trimmed === \"\" ? null : trimmed;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Provider resolution (port of provider_identity.rs canonical_provider)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Canonicalize a single provider segment (port of canonicalize_provider_segment):\n * trim, drop a trailing slash, lowercase, `-`→`_`; reject `<…>` placeholders, the\n * empty/`unknown` segment, and unknown segments that contain a digit (those are\n * model-name fragments). Returns the canonical id or undefined.\n */\nfunction canonicalizeProviderSegment(segment: string): string | undefined {\n const normalized = segment.trim().replace(/\\/+$/, \"\").toLowerCase().replace(/-/g, \"_\");\n if (normalized.startsWith(\"<\") && normalized.endsWith(\">\")) return undefined;\n switch (normalized) {\n case \"\":\n case \"unknown\":\n return undefined;\n case \"x_ai\":\n case \"xai\":\n return \"xai\";\n case \"z_ai\":\n case \"zai\":\n return \"zai\";\n case \"moonshot\":\n case \"moonshotai\":\n return \"moonshotai\";\n case \"meta\":\n case \"meta_llama\":\n return \"meta_llama\";\n case \"azure\":\n case \"azure_ai\":\n return \"azure_ai\";\n case \"anthropic\":\n case \"vertex\":\n case \"vertex_ai\":\n return \"anthropic\";\n case \"together\":\n case \"together_ai\":\n return \"together_ai\";\n case \"fireworks\":\n case \"fireworks_ai\":\n return \"fireworks_ai\";\n case \"google\":\n case \"gemini\":\n return \"google\";\n case \"openai\":\n case \"openai_codex\":\n return \"openai\";\n case \"minimax\":\n case \"minimaxai\":\n case \"minimax_ai\":\n return \"minimax\";\n case \"mistral\":\n case \"mistralai\":\n return \"mistralai\";\n case \"ai21\":\n return \"ai21\";\n default:\n if (/[0-9]/.test(normalized)) return undefined;\n return normalized;\n }\n}\n\n/**\n * Port of canonical_provider: the first canonical tag from `provider_tags(raw)`\n * (split on '/', and for any dotted segment also try its dot-parts), or undefined\n * when no segment canonicalizes. Unlike the droid port, this returns undefined\n * (not the raw string) so goose's resolver can fall through to model inference,\n * matching the Rust `Option<String>` semantics.\n */\nfunction canonicalProvider(raw: string): string | undefined {\n for (const segment of raw.trim().replace(/\\/+$/, \"\").split(\"/\")) {\n const tag = canonicalizeProviderSegment(segment);\n if (tag !== undefined) return tag;\n if (segment.includes(\".\")) {\n for (const dotted of segment.split(\".\")) {\n const dottedTag = canonicalizeProviderSegment(dotted);\n if (dottedTag !== undefined) return dottedTag;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Resolve the provider (port of resolved_provider): canonical_provider of a\n * non-blank `provider_name`, else inferProvider(model), else \"goose\".\n */\nfunction resolvedProvider(providerName: unknown, modelId: string): string {\n if (typeof providerName === \"string\") {\n const trimmed = providerName.trim();\n if (trimmed !== \"\") {\n const canonical = canonicalProvider(trimmed);\n if (canonical !== undefined) return canonical;\n }\n }\n return inferProvider(modelId) ?? DEFAULT_PROVIDER;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Timestamps & numbers\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Parse `created_at` to epoch ms (port of parse_created_at). Accepts RFC3339,\n * \"YYYY-MM-DD HH:MM:SS\" (interpreted as UTC), and \"YYYY-MM-DD\" (UTC midnight).\n * Returns 0 when unparseable (mirroring the Rust 0.0 sentinel).\n */\nfunction parseCreatedAt(s: string): number {\n // ORDER MATTERS: the bare \"YYYY-MM-DD HH:MM:SS\" and \"YYYY-MM-DD\" forms must be\n // matched BEFORE the Date.parse fallback. V8's Date.parse accepts both shapes\n // but interprets them as LOCAL time, whereas goose.rs treats them as UTC\n // (chrono NaiveDateTime/NaiveDate.and_utc()). Detecting them first via regex\n // and building with Date.UTC keeps the timestamp tz-stable (mirrors cursor.ts).\n\n // \"YYYY-MM-DD HH:MM:SS\" → treat the space-separated form as UTC.\n const dt = /^(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})$/.exec(s);\n if (dt) {\n const ms = Date.UTC(\n Number(dt[1]),\n Number(dt[2]) - 1,\n Number(dt[3]),\n Number(dt[4]),\n Number(dt[5]),\n Number(dt[6]),\n );\n return Number.isNaN(ms) ? 0 : ms;\n }\n\n // \"YYYY-MM-DD\" → UTC midnight.\n const d = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(s);\n if (d) {\n const ms = Date.UTC(Number(d[1]), Number(d[2]) - 1, Number(d[3]));\n return Number.isNaN(ms) ? 0 : ms;\n }\n\n // RFC3339 (e.g. \"2026-04-14T16:18:53Z\" / with offset). Date.parse handles these\n // with explicit tz info, so the local-vs-UTC ambiguity above does not apply.\n const iso = Date.parse(s);\n if (!Number.isNaN(iso)) return iso;\n\n return 0;\n}\n\n/**\n * Disambiguate a seconds-or-ms timestamp (port of timestamp_secs_to_ms): values\n * already in ms (> 1e12) pass through; smaller values are treated as seconds.\n * `parseCreatedAt` already yields ms, but we replicate the Rust pipeline exactly\n * (parse_created_at → timestamp_secs_to_ms) so a 0 sentinel stays 0.\n */\nfunction timestampSecsToMs(ts: number): number {\n return ts > 1e12 ? Math.trunc(ts) : Math.trunc(ts * 1000);\n}\n\n/** Coerce an unknown SQLite cell to a non-negative integer, or null when absent. */\nfunction toIntOrNull(v: unknown): number | null {\n if (v === null || v === undefined) return null;\n const n = typeof v === \"number\" ? v : typeof v === \"string\" ? Number(v) : NaN;\n if (!Number.isFinite(n)) return null;\n return Math.trunc(n);\n}\n\n/** First non-null of (accumulated, plain), defaulting to 0, clamped ≥ 0. */\nfunction preferAccumulated(accumulated: unknown, plain: unknown): number {\n const acc = toIntOrNull(accumulated);\n const value = acc ?? toIntOrNull(plain) ?? 0;\n return Math.max(0, value);\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Reader\n// ─────────────────────────────────────────────────────────────────────────\n\n/** The Goose CLI usage reader singleton. */\nconst gooseReader: UsageReader = {\n platformId: PLATFORM_ID,\n kind: \"local\",\n async read({ sinceMs }: { sinceMs?: number }): Promise<UsageRecord[]> {\n const dbPath = firstExistingRoot(PLATFORM_ID);\n if (dbPath === undefined) return []; // no sessions.db → fail-open\n\n const db = await openSqlite(dbPath);\n if (db === null) return []; // missing / locked / unreadable / non-sqlite → fail-open\n\n try {\n const rows = db.all(QUERY); // bad SQL / schema mismatch → [] (fail-open)\n const records: UsageRecord[] = [];\n\n for (const raw of rows) {\n const row = raw as GooseRow;\n\n const sessionId = row.id;\n if (typeof sessionId !== \"string\" || sessionId === \"\") continue;\n\n const modelConfig = row.model_config_json;\n if (typeof modelConfig !== \"string\") continue;\n const modelId = parseModelConfig(modelConfig);\n if (modelId === null) continue;\n\n const input = preferAccumulated(row.accumulated_input_tokens, row.input_tokens);\n const output = preferAccumulated(row.accumulated_output_tokens, row.output_tokens);\n const total = preferAccumulated(row.accumulated_total_tokens, row.total_tokens);\n\n // Skip all-zero sessions (matches the Rust input==0 && output==0 && total==0 check).\n if (input === 0 && output === 0 && total === 0) continue;\n\n // Reasoning is INFERRED, not reported: total minus accounted input+output.\n const reasoning = total > input + output ? total - input - output : 0;\n\n const createdAtRaw = typeof row.created_at === \"string\" ? row.created_at : \"\";\n const ts = timestampSecsToMs(parseCreatedAt(createdAtRaw));\n if (sinceMs !== undefined && ts < sinceMs) continue;\n\n const providerId = resolvedProvider(row.provider_name, modelId);\n\n const tokens = emptyTokens();\n tokens.input = input;\n tokens.output = output;\n tokens.reasoning = reasoning;\n // cacheRead / cacheWrite stay 0 (Goose does not report them).\n\n const record: UsageRecord = {\n platformId: PLATFORM_ID,\n modelId,\n providerId,\n sessionId,\n tokens,\n ts,\n messageCount: 1,\n dedupKey: sessionId, // one row per session → session id is the dedup key\n confidence: \"host-reported\",\n };\n records.push(record);\n }\n\n return records;\n } finally {\n db.close();\n }\n },\n};\n\nexport default gooseReader;\n"],"mappings":"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","names":[]}