@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/kilo-cli/index.ts"],"sourcesContent":["/**\n * adapters/kilo-cli — Kilo CLI platform adapter for agent-connector.\n *\n * The Kilo CLI (the `kilo` binary) is a **live-confirmed OpenCode fork**: a\n * SQLite-backed command-line agent (storage `~/.local/share/kilo/kilo.db`) that\n * loads `@kilocode/plugin` modules. It is DISTINCT from the Kilo Code VS Code\n * extension (adapter id \"kilo\", a Roo/Cline fork) — the two products carry\n * different platformIds so their config and usage never merge.\n *\n * Paradigm — **ts-plugin** (mirrors the OpenCode adapter):\n * The fork has no JSON hook table. Instead it loads JS/TS plugin modules whose\n * default export is a `@kilocode/plugin` PluginModule:\n * { id?, server: async (input) => Hooks }\n * where the returned `Hooks` object is keyed by the fork's event names\n * (\"tool.execute.before\", \"tool.execute.after\", a session/system event). The\n * `server` factory runs in-process inside Kilo and is handed mutable args/output\n * objects; it throws to block a tool call.\n *\n * Why we BRIDGE instead of importing handlers (identical rationale to OpenCode):\n * the connector's hook handlers are arbitrary developer code we must not import\n * into Kilo's runtime (wrong cwd, wrong deps, version skew). So instead of\n * importing handlers we synthesize a tiny, fully self-contained ESM plugin\n * module that imports NOTHING from agent-connector and, on each hook firing,\n * shells out to the ONE stable home binary's universal entrypoint\n * <homeBin> hook kilo-cli <event> --connector <id>\n * feeding the event payload on stdin and JSON.parsing the normalized\n * HookResponse back from stdout. Fail-open: any bridge error → no-op.\n *\n * Registration (the key difference from OpenCode):\n * OpenCode auto-discovers every file in its plugin dir, so writing the file is\n * enough. This fork does NOT auto-discover by directory — it reads an explicit\n * `plugin` ARRAY in kilo.jsonc (module file paths). So installHooks must BOTH\n * synthesize the module AND register its path in that `plugin` array; uninstall\n * reverses both.\n *\n * Plugin module location (project | user):\n * user → ~/.config/kilo/plugin/<id>.js\n * project → <projectDir>/.kilo/plugin/<id>.js\n *\n * MCP config (the CLI's new-gen dialect — already correct, KEPT verbatim):\n * - user scope → ~/.config/kilo/kilo.jsonc\n * - project scope → <projectDir>/.kilo/kilo.jsonc (project overrides global)\n * Both are JSON/JSONC; we write plain JSON, which is valid JSONC. The root key\n * is \"mcp\" (NOT the extension's \"mcpServers\").\n *\n * Server entry shape (mirrors OpenCode's new-gen dialect):\n * - stdio → { type: \"local\", command: [exe, ...args], environment: {...} }\n * NOTE the command is a single ARRAY (exe + args together), unlike\n * Claude/Cursor's scalar `command` + `args[]` split.\n * - remote → { type: \"remote\", url }\n *\n * The Kilo CLI documents no native `${env:VAR}` interpolation token, so env/url\n * refs are resolved to literals at install time (the no-native-token path).\n *\n * Content surfaces: the Kilo CLI exposes no confirmed writable command/skill/\n * subagent dir (the `.kilocode/` tree belongs to the VS Code extension, served\n * by the \"kilo\" adapter), so those inherit the BaseAdapter warn/skip.\n *\n * Capability degradations (documented, never thrown):\n * - The fork has no \"ask\" gate. A decision of \"ask\" degrades to a block (throw\n * with the reason) in tool.execute.before — the safe direction.\n */\n\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport { BaseAdapter } from \"../base.js\";\nimport type {\n Adapter,\n GeneratedPluginFile,\n HookReply,\n InstallContext,\n NormalizedEvent,\n} from \"../spi.js\";\nimport type {\n ChangeRecord,\n DetectedPlatform,\n HealthCheck,\n HookEventName,\n HookParadigm,\n HookResponse,\n PlatformCapabilities,\n PlatformId,\n PostToolUseEvent,\n PreToolUseEvent,\n ServerDef,\n SessionStartEvent,\n Transport,\n} from \"../../core/types.js\";\nimport { resolveEnvRefsDeep } from \"../../core/interpolate.js\";\nimport {\n buildServeWrapperCommand,\n shouldWrapForTelemetry,\n} from \"../../core/spawn.js\";\n\nconst HOST: PlatformId = \"kilo-cli\";\n/** New-generation root key for MCP servers — \"mcp\", not the extension's \"mcpServers\". */\nconst MCP_ROOT_KEY = \"mcp\";\n/** kilo.jsonc array of plugin module paths the fork loads (NOT auto-discovery). */\nconst PLUGIN_ARRAY_KEY = \"plugin\";\n/** New-generation config filename (JSON/JSONC). */\nconst CONFIG_FILE = \"kilo.jsonc\";\n/** The Kilo CLI's SQLite session store — a CLI-exclusive detection marker. */\nconst CLI_DB_RELPATH = [\".local\", \"share\", \"kilo\", \"kilo.db\"] as const;\n\n/**\n * Canonical → Kilo (OpenCode-fork) event name map. A connector hook event is\n * only emitted by the generated plugin when it appears here AND is declared by\n * the connector.\n *\n * The fork inherits OpenCode's tool-execution events; like OpenCode it has no\n * real SessionStart hook, so SessionStart maps to the same system-prompt\n * transform surrogate (the verified context-injection point) — we inject\n * additionalContext into the system prompt there.\n */\nconst EVENT_TO_KILO: Partial<Record<HookEventName, string>> = {\n PreToolUse: \"tool.execute.before\",\n PostToolUse: \"tool.execute.after\",\n SessionStart: \"experimental.chat.system.transform\",\n};\n\n/** Raw payload the generated plugin posts to the universal hook entrypoint. */\ninterface KiloBridgePayload {\n toolName?: string;\n toolInput?: Record<string, unknown>;\n toolOutput?: string;\n isError?: boolean;\n sessionId?: string;\n projectDir?: string;\n}\n\n/**\n * Native MCP server entry shapes the Kilo CLI's new-gen config accepts under\n * `mcp`. QUIRK: a local (stdio) server keys its whole invocation as\n * `command: [...]` — a single array of [executable, ...args] — and env as\n * `environment`.\n */\ninterface KiloLocalServer {\n type: \"local\";\n command: string[];\n environment?: Record<string, string>;\n}\ninterface KiloRemoteServer {\n type: \"remote\";\n url: string;\n}\n\nexport class KiloCliAdapter extends BaseAdapter implements Adapter {\n readonly id: PlatformId = HOST;\n readonly name = \"Kilo CLI\";\n readonly paradigm: HookParadigm = \"ts-plugin\";\n\n readonly capabilities: PlatformCapabilities = {\n // Matches the OpenCode capability surface — the fork inherits its plugin\n // event model.\n preToolUse: true,\n postToolUse: true,\n // The fork's compaction hook is experimental and not wired here.\n preCompact: false,\n sessionStart: true,\n sessionEnd: false,\n userPromptSubmit: false,\n stop: false,\n notification: false,\n // tool.execute.before mutates output.args → input rewrite supported.\n canModifyArgs: true,\n // tool.execute.after mutates output.output → output rewrite supported.\n canModifyOutput: true,\n // injected via the system-prompt transform at session start.\n canInjectSessionContext: true,\n // The Kilo CLI registers stdio (local), SSE, and Streamable HTTP MCP servers.\n transports: [\"stdio\", \"sse\", \"http\"],\n // No confirmed CLI content surface — these inherit the BaseAdapter warn/skip.\n supportsCommands: false,\n supportsSkills: false,\n supportsSubagents: false,\n };\n\n // ── Detection ────────────────────────────────────────────────────────────\n\n detectInstalled(projectDir: string): DetectedPlatform {\n const userDir = join(homedir(), \".config\", \"kilo\");\n const userConfig = join(userDir, CONFIG_FILE);\n const projectConfig = join(projectDir, \".kilo\", CONFIG_FILE);\n // The SQLite session DB is a CLI-exclusive marker (the VS Code extension\n // never writes it), so it disambiguates the CLI from the \"kilo\" extension\n // even when neither writes a kilo.jsonc yet.\n const cliDb = join(homedir(), ...CLI_DB_RELPATH);\n const userInstalled = existsSync(userDir) || existsSync(cliDb);\n const projInstalled = existsSync(projectConfig);\n const installed = userInstalled || projInstalled;\n // Report the scope/path/reason for the marker that actually matched, so a\n // project-only install isn't misreported as a (non-existent) user install.\n const scope = projInstalled && !userInstalled ? \"project\" : \"user\";\n const configPath = scope === \"project\" ? projectConfig : userConfig;\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 Kilo CLI config at ${projectConfig}`\n : existsSync(cliDb)\n ? `found Kilo CLI session store at ${cliDb}`\n : `found Kilo CLI config under ${userDir}`\n : `no Kilo CLI config at ${userDir} or session store at ${cliDb}`,\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, \".kilo\")\n : join(homedir(), \".config\", \"kilo\");\n }\n\n getServerConfigPath(ctx: InstallContext): string {\n return join(this.getConfigDir(ctx), CONFIG_FILE);\n }\n\n /**\n * For ts-plugin hosts the \"hook config path\" is the generated plugin FILE.\n * Unlike OpenCode, writing the file is NOT sufficient: the fork loads only the\n * module paths listed in kilo.jsonc's `plugin` array, so installHooks also\n * registers this path there.\n */\n getHookConfigPath(ctx: InstallContext): string {\n return join(this.pluginDir(ctx), this.pluginFileName(ctx));\n }\n\n /** Plugin directory the synthesized module is written into, per scope. */\n private pluginDir(ctx: InstallContext): string {\n return ctx.scope === \"project\"\n ? join(ctx.projectDir, \".kilo\", \"plugin\")\n : join(homedir(), \".config\", \"kilo\", \"plugin\");\n }\n\n /** Plugin module file name (one per connector, kebab-case id). */\n private pluginFileName(ctx: InstallContext): string {\n return `${ctx.connector.id}.js`;\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 kilo-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 /** Render a normalized ServerDef into the Kilo CLI's new-gen `mcp` entry. */\n private renderServerEntry(\n ctx: InstallContext,\n server: ServerDef,\n ): KiloLocalServer | KiloRemoteServer {\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 // QUIRK: the Kilo CLI's new-gen local server keys its whole invocation as\n // a single `command` ARRAY (executable + args together), like OpenCode.\n // The CLI has no documented native interpolation token, so resolve every\n // ${env:VAR} to a literal at install time.\n const entry: KiloLocalServer = {\n type: \"local\",\n command: resolveEnvRefsDeep([command, ...args]),\n };\n const env = this.renderEnv(server.env);\n if (env) entry.environment = env;\n return entry;\n }\n\n // sse / http (and any other remote transport) — the Kilo CLI registers a URL.\n const entry: KiloRemoteServer = {\n type: \"remote\",\n url: resolveEnvRefsDeep(server.url ?? \"\"),\n };\n return entry;\n }\n\n /**\n * Render env values. The Kilo CLI documents no native interpolation token, so\n * resolve `${env:VAR}` references to literals at install time.\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 (ts-plugin + explicit `plugin` array) ────────\n\n installHooks(ctx: InstallContext): ChangeRecord[] {\n const pluginPath = this.getHookConfigPath(ctx);\n const configPath = this.getServerConfigPath(ctx);\n\n if (ctx.connector.platforms[HOST]?.hooks === false) {\n return [\n { platform: this.id, action: \"skip\", path: pluginPath, detail: \"hooks disabled for kilo-cli\" },\n ];\n }\n if (ctx.connector.hookEvents.length === 0) {\n return [\n { platform: this.id, action: \"skip\", path: pluginPath, detail: \"connector declares no hooks\" },\n ];\n }\n\n const changes: ChangeRecord[] = [];\n\n // 1. Write the synthesized plugin module.\n for (const file of this.synthesizePlugin(ctx)) {\n const before = existsSync(file.path) ? this.safeRead(file.path) : undefined;\n let action: ChangeRecord[\"action\"];\n if (before === undefined) action = \"create\";\n else if (before === file.contents) action = \"skip\";\n else action = \"update\";\n\n if (action !== \"skip\" && !ctx.dryRun) {\n ensureDir(dirname(file.path));\n writeFileSync(file.path, file.contents, \"utf8\");\n chmodSync(file.path, file.executable ? 0o755 : 0o644);\n }\n changes.push({\n platform: this.id,\n action,\n path: file.path,\n detail: `kilo plugin module (${this.hookDetail(ctx)})`,\n });\n }\n\n // 2. Register the module path in kilo.jsonc's `plugin` array (the fork does\n // NOT auto-discover by directory — it reads this array).\n changes.push(this.upsertPluginInArray(configPath, pluginPath, ctx.dryRun));\n\n return changes;\n }\n\n /**\n * Human-facing summary of which declared events the synthesized module ACTUALLY\n * wires. Only events present in EVENT_TO_KILO are mapped/wired; any declared\n * event with no Kilo mapping is reported separately as \"unsupported here\" so\n * the detail never overstates coverage.\n */\n private hookDetail(ctx: InstallContext): string {\n const declared = ctx.connector.hookEvents;\n const mapped = declared.filter((e) => EVENT_TO_KILO[e] !== undefined);\n const unsupported = declared.filter((e) => EVENT_TO_KILO[e] === undefined);\n const base = mapped.join(\",\");\n return unsupported.length > 0\n ? `${base}; unsupported here: ${unsupported.join(\",\")}`\n : base;\n }\n\n uninstallHooks(ctx: InstallContext): ChangeRecord[] {\n const pluginPath = this.getHookConfigPath(ctx);\n const configPath = this.getServerConfigPath(ctx);\n const changes: ChangeRecord[] = [];\n\n // 1. Deregister the module path from kilo.jsonc's `plugin` array.\n changes.push(this.removePluginFromArray(configPath, pluginPath, ctx.dryRun));\n\n // 2. Remove the plugin module on disk.\n if (existsSync(pluginPath)) {\n if (!ctx.dryRun) rmSync(pluginPath, { force: true });\n changes.push({\n platform: this.id,\n action: \"remove\",\n path: pluginPath,\n detail: \"kilo plugin module\",\n });\n } else {\n changes.push({\n platform: this.id,\n action: \"skip\",\n path: pluginPath,\n detail: \"no kilo plugin module present\",\n });\n }\n\n // 3. Drop the now-empty plugin dir (only if WE own its full contents).\n changes.push(this.removeDirIfEmpty(this.pluginDir(ctx), ctx.dryRun));\n\n return changes;\n }\n\n /** Upsert the module path into kilo.jsonc's top-level `plugin` array (idempotent). */\n private upsertPluginInArray(\n configPath: string,\n modulePath: string,\n dryRun: boolean,\n ): ChangeRecord {\n // OVERWRITE GUARD: never blank a present-but-unparseable kilo.jsonc.\n if (this.isPresentButUnparseable(configPath)) {\n return {\n platform: this.id,\n action: \"warn\",\n path: configPath,\n detail: `existing ${configPath} is not parseable; left untouched (back it up / fix it, then re-run)`,\n };\n }\n const cfg = this.readJson<Record<string, unknown>>(configPath) ?? {};\n const plugins = this.pluginArrayBucket(cfg);\n if (plugins.includes(modulePath)) {\n return {\n platform: this.id,\n action: \"skip\",\n path: configPath,\n detail: `${PLUGIN_ARRAY_KEY}[] already includes module`,\n };\n }\n plugins.push(modulePath);\n this.writeJson(configPath, cfg, dryRun);\n return {\n platform: this.id,\n action: \"create\",\n path: configPath,\n detail: `${PLUGIN_ARRAY_KEY}[] += module`,\n };\n }\n\n /** Remove the module path from kilo.jsonc's `plugin` array. */\n private removePluginFromArray(\n configPath: string,\n modulePath: string,\n dryRun: boolean,\n ): ChangeRecord {\n // OVERWRITE GUARD: a present-but-unparseable file would round-trip to `{}`.\n if (this.isPresentButUnparseable(configPath)) {\n return {\n platform: this.id,\n action: \"warn\",\n path: configPath,\n detail: `existing ${configPath} is not parseable; left untouched (back it up / fix it, then re-run)`,\n };\n }\n const cfg = this.readJson<Record<string, unknown>>(configPath);\n const raw = cfg?.[PLUGIN_ARRAY_KEY];\n const plugins = Array.isArray(raw) ? (raw as unknown[]) : undefined;\n const idx = plugins ? plugins.indexOf(modulePath) : -1;\n if (!cfg || !plugins || idx < 0) {\n return {\n platform: this.id,\n action: \"skip\",\n path: configPath,\n detail: `${PLUGIN_ARRAY_KEY}[] does not include module`,\n };\n }\n plugins.splice(idx, 1);\n this.writeJson(configPath, cfg, dryRun);\n return {\n platform: this.id,\n action: \"remove\",\n path: configPath,\n detail: `${PLUGIN_ARRAY_KEY}[] -= module`,\n };\n }\n\n /** Get-or-create the top-level `plugin` array as a mutable string array. */\n private pluginArrayBucket(cfg: Record<string, unknown>): string[] {\n const existing = cfg[PLUGIN_ARRAY_KEY];\n // Normalize to a fresh string[] (drop any non-string a user may have placed\n // here) and write it back so the caller mutates the live array.\n const plugins = Array.isArray(existing)\n ? existing.filter((p): p is string => typeof p === \"string\")\n : [];\n cfg[PLUGIN_ARRAY_KEY] = plugins;\n return plugins;\n }\n\n // ── ts-plugin synthesis ────────────────────────────────────────────────\n\n /**\n * Build ONE self-contained ESM plugin module for the Kilo CLI fork.\n *\n * The module imports nothing from agent-connector. It embeds two constants\n * (the absolute home-bin path and the connector id) and a `bridge()` helper\n * that shells out to the universal hook entrypoint via execFileSync, feeding\n * the Kilo payload on stdin and JSON.parsing the normalized HookResponse back\n * from stdout (fail-open: any error → null). Its default export is the\n * `@kilocode/plugin` PluginModule shape — { id, server: async (input) =>\n * ({ \"tool.execute.before\": …, \"tool.execute.after\": …, … }) } — keyed only by\n * the fork event names for the events this connector actually declares.\n */\n synthesizePlugin(ctx: InstallContext): GeneratedPluginFile[] {\n const path = this.getHookConfigPath(ctx);\n const contents = this.buildPluginSource(ctx);\n return [{ path, contents, executable: false }];\n }\n\n /** Compose the generated plugin source with plain string concatenation. */\n private buildPluginSource(ctx: InstallContext): string {\n const homeBin = JSON.stringify(ctx.homeBinPath);\n const connectorId = JSON.stringify(ctx.connector.id);\n\n // The Kilo event keys this connector declares (and that we can map).\n const events = ctx.connector.hookEvents.filter(\n (e): e is HookEventName => EVENT_TO_KILO[e] !== undefined,\n );\n const has = (e: HookEventName) => events.includes(e);\n\n const header = `/**\n * AUTO-GENERATED by agent-connector — DO NOT EDIT.\n *\n * Self-contained Kilo CLI plugin bridge for connector ${ctx.connector.id}.\n * It imports nothing from agent-connector: every hook invocation shells out to\n * the stable home binary's universal entrypoint and JSON-parses the normalized\n * response. Fail-open: any bridge error degrades to \"allow\".\n *\n * Default export is the @kilocode/plugin PluginModule shape\n * ({ id, server: async (input) => Hooks }); the fork loads it because its path\n * is registered in kilo.jsonc's \"plugin\" array.\n */\nimport { execFileSync, execSync } from \"node:child_process\";\n\nconst HOME_BIN = ${homeBin};\nconst CONNECTOR_ID = ${connectorId};\n\n/**\n * Invoke the universal hook entrypoint for one event.\n * @param {string} event canonical event name (PreToolUse|PostToolUse|SessionStart)\n * @param {object} payload Kilo-shaped payload posted on stdin\n * @returns {object|null} normalized HookResponse, or null on any failure\n */\nfunction bridge(event, payload) {\n try {\n // On Windows HOME_BIN is the agent-connector.cmd launcher: Node cannot\n // execFile a batch file, and shell+args is deprecated (DEP0190), so run one\n // quoted command line via a shell. POSIX keeps the direct execFile (no shell).\n const args = [\"hook\", \"kilo-cli\", event, \"--connector\", CONNECTOR_ID];\n const opts = { input: JSON.stringify(payload), encoding: \"utf8\" };\n const stdout =\n process.platform === \"win32\"\n ? execSync([HOME_BIN, ...args].map((a) => '\"' + a + '\"').join(\" \"), opts)\n : execFileSync(HOME_BIN, args, opts);\n const text = (stdout || \"\").trim();\n if (text === \"\") return { decision: \"allow\" };\n return JSON.parse(text);\n } catch {\n // Fail-open — never wedge a tool call on a bridge error.\n return null;\n }\n}\n`;\n\n const handlers: string[] = [];\n\n if (has(\"PreToolUse\")) {\n handlers.push(` // PreToolUse → block (throw) / rewrite args (mutate output.args).\n \"tool.execute.before\": async (input, output) => {\n const payload = {\n toolName: input.tool ?? \"\",\n toolInput: (output && output.args) ?? {},\n sessionId: input.sessionID ?? \"\",\n projectDir: PROJECT_DIR,\n };\n const res = bridge(\"PreToolUse\", payload);\n if (!res) return;\n // Kilo has no \"ask\" gate — degrade \"ask\" to a block (safe direction).\n if (res.decision === \"deny\" || res.decision === \"ask\") {\n throw new Error(res.reason || \"Blocked by ${ctx.connector.id}\");\n }\n if (res.updatedInput && output && output.args) {\n Object.assign(output.args, res.updatedInput);\n }\n },`);\n }\n\n if (has(\"PostToolUse\")) {\n handlers.push(` // PostToolUse → observe / rewrite tool output (mutate output.output).\n \"tool.execute.after\": async (input, output) => {\n const payload = {\n toolName: input.tool ?? \"\",\n toolInput: input.args ?? {},\n toolOutput: output ? output.output : undefined,\n sessionId: input.sessionID ?? \"\",\n projectDir: PROJECT_DIR,\n };\n const res = bridge(\"PostToolUse\", payload);\n if (!res) return;\n if (typeof res.updatedOutput === \"string\" && output) {\n output.output = res.updatedOutput;\n }\n },`);\n }\n\n if (has(\"SessionStart\")) {\n handlers.push(` // SessionStart surrogate → inject context into the system prompt.\n // The fork has no real SessionStart hook; the system-prompt transform is the\n // verified injection point (mirrors OpenCode).\n \"experimental.chat.system.transform\": async (input, output) => {\n const payload = {\n sessionId: (input && input.sessionID) ?? \"\",\n projectDir: PROJECT_DIR,\n };\n const res = bridge(\"SessionStart\", payload);\n if (!res) return;\n if (res.additionalContext && output && Array.isArray(output.system)) {\n // Insert at index 1 (after the header) to preserve the prompt-cache fold\n // (header must remain system[0]).\n output.system.splice(1, 0, res.additionalContext);\n }\n },`);\n }\n\n const definition = `\nconst plugin = {\n id: CONNECTOR_ID,\n // @kilocode/plugin PluginModule: server(input) returns the Hooks object keyed\n // by the fork's event names. ctx.directory is the project root; fall back to cwd.\n server: async (input) => {\n const PROJECT_DIR =\n (input && (input.directory || (input.worktree && input.worktree.path))) ||\n process.cwd();\n\n return {\n${handlers.join(\"\\n\")}\n };\n },\n};\n\nexport default plugin;\n`;\n\n return header + definition;\n }\n\n // ── Runtime: parse OUR bridge payload → normalized event ───────────────\n\n /**\n * `raw` is the payload OUR generated plugin posts (NOT a host-native shape):\n * { toolName, toolInput, toolOutput?, isError?, sessionId, projectDir }\n * so this maps straight through.\n */\n parseEvent(event: HookEventName, raw: unknown): NormalizedEvent {\n const input = (raw ?? {}) as KiloBridgePayload;\n const base = {\n hostPlatform: HOST,\n connectorId: \"\",\n sessionId: typeof input.sessionId === \"string\" ? input.sessionId : \"\",\n raw,\n ...(typeof input.projectDir === \"string\" ? { projectDir: input.projectDir } : {}),\n } as const;\n\n switch (event) {\n case \"PreToolUse\": {\n const ev: PreToolUseEvent = {\n ...base,\n toolName: input.toolName ?? \"\",\n toolInput: input.toolInput ?? {},\n };\n return ev;\n }\n case \"PostToolUse\": {\n const ev: PostToolUseEvent = {\n ...base,\n toolName: input.toolName ?? \"\",\n toolInput: input.toolInput ?? {},\n ...(typeof input.toolOutput === \"string\" ? { toolOutput: input.toolOutput } : {}),\n ...(typeof input.isError === \"boolean\" ? { isError: input.isError } : {}),\n };\n return ev;\n }\n case \"SessionStart\": {\n const ev: SessionStartEvent = { ...base, source: \"startup\" };\n return ev;\n }\n default:\n // Other canonical events are not surfaced by Kilo; treat as a\n // session-start-shaped no-op so the dispatcher fails open gracefully.\n return { ...base, source: \"startup\" } satisfies SessionStartEvent;\n }\n }\n\n // ── Runtime: normalized response → reply the generated bridge parses ───\n\n /**\n * Unlike json-stdio hosts (whose reply is the host's NATIVE control payload),\n * OUR generated bridge consumes this stdout directly. So the reply body is the\n * NORMALIZED HookResponse itself — the bridge JSON.parses it and reads\n * decision / updatedInput / updatedOutput / additionalContext.\n */\n formatReply(_event: HookEventName, response: HookResponse): HookReply {\n return {\n exitCode: 0,\n stdout: JSON.stringify(response ?? { decision: \"allow\" }),\n };\n }\n\n // ── Diagnostics ──────────────────────────────────────────────────────────\n\n override getHealthChecks(ctx: InstallContext): readonly HealthCheck[] {\n const configPath = this.getServerConfigPath(ctx);\n const pluginPath = this.getHookConfigPath(ctx);\n const connectorId = ctx.connector.id;\n const hasHooks = ctx.connector.hookEvents.length > 0;\n\n return [\n {\n name: `${this.name}: ${CONFIG_FILE} present`,\n check: () =>\n existsSync(configPath)\n ? { status: \"OK\", detail: configPath }\n : { status: \"FAIL\", detail: `not found: ${configPath}` },\n },\n {\n name: `${this.name}: server entry registered`,\n check: () => {\n if (!ctx.connector.server) {\n return { status: \"OK\", detail: \"no MCP server declared\" };\n }\n const cfg = this.readJson<{ [k: string]: Record<string, unknown> }>(configPath);\n const bucket = cfg?.[MCP_ROOT_KEY];\n if (!cfg || !bucket) {\n return { status: \"FAIL\", detail: `no ${MCP_ROOT_KEY} in ${configPath}` };\n }\n return connectorId in bucket\n ? { status: \"OK\", detail: `${MCP_ROOT_KEY}.${connectorId} present` }\n : {\n status: \"FAIL\",\n detail: `no ${MCP_ROOT_KEY}.${connectorId} in ${configPath}`,\n };\n },\n },\n {\n name: `${this.name}: plugin module present`,\n check: () => {\n if (!hasHooks) return { status: \"OK\", detail: \"no hooks declared\" };\n return existsSync(pluginPath)\n ? { status: \"OK\", detail: pluginPath }\n : { status: \"FAIL\", detail: `not found: ${pluginPath}` };\n },\n },\n {\n name: `${this.name}: plugin registered in ${PLUGIN_ARRAY_KEY}[]`,\n check: () => {\n if (!hasHooks) return { status: \"OK\", detail: \"no hooks declared\" };\n const cfg = this.readJson<Record<string, unknown>>(configPath);\n const raw = cfg?.[PLUGIN_ARRAY_KEY];\n const plugins = Array.isArray(raw) ? (raw as unknown[]) : undefined;\n return plugins && plugins.includes(pluginPath)\n ? { status: \"OK\", detail: `${PLUGIN_ARRAY_KEY}[] includes module` }\n : {\n status: \"FAIL\",\n detail: `module not in ${PLUGIN_ARRAY_KEY}[] of ${configPath} (fork will not load it)`,\n };\n },\n },\n ];\n }\n\n /** Read a file, returning undefined on any error (idempotency compare). */\n private safeRead(path: string): string | undefined {\n try {\n return readFileSync(path, \"utf8\");\n } catch {\n return undefined;\n }\n }\n}\n\n/** Create a directory (recursive) if it does not already exist. */\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\nexport const adapter = new KiloCliAdapter();\nexport default adapter;\n"],"mappings":"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","names":["entry"]}
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ openSqlite
4
+ } from "./chunk-YT43MUSJ.js";
5
+ import {
6
+ fileMtimeMs
7
+ } from "./chunk-RK6VBLXP.js";
8
+ import {
9
+ firstExistingRoot
10
+ } from "./chunk-JWNT5KW7.js";
11
+ import {
12
+ emptyTokens,
13
+ inferProvider
14
+ } from "./chunk-XD7MY6HB.js";
15
+
16
+ // src/usage/readers/kilo-cli.ts
17
+ var PLATFORM_ID = "kilo-cli";
18
+ var DEFAULT_PROVIDER = "kilo";
19
+ var QUERY = `
20
+ SELECT m.id, m.session_id, m.data
21
+ FROM message m
22
+ WHERE json_valid(m.data)
23
+ AND json_extract(m.data, '$.role') = 'assistant'
24
+ AND json_extract(m.data, '$.tokens') IS NOT NULL
25
+ `;
26
+ function tokenField(v) {
27
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
28
+ if (!Number.isFinite(n)) return 0;
29
+ return Math.max(0, Math.trunc(n));
30
+ }
31
+ function asFiniteNumber(v) {
32
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
33
+ return Number.isFinite(n) ? n : void 0;
34
+ }
35
+ function asNonEmptyString(v) {
36
+ return typeof v === "string" && v !== "" ? v : void 0;
37
+ }
38
+ function asString(v) {
39
+ if (typeof v === "string") return v;
40
+ if (typeof v === "number" && Number.isFinite(v)) return String(v);
41
+ return void 0;
42
+ }
43
+ function parseData(dataJson) {
44
+ let value;
45
+ try {
46
+ value = JSON.parse(dataJson);
47
+ } catch {
48
+ return void 0;
49
+ }
50
+ if (typeof value !== "object" || value === null) return void 0;
51
+ const obj = value;
52
+ if (obj["role"] !== "assistant") return void 0;
53
+ const tokensRaw = obj["tokens"];
54
+ if (typeof tokensRaw !== "object" || tokensRaw === null) return void 0;
55
+ const t = tokensRaw;
56
+ const input = asFiniteNumber(t["input"]);
57
+ const output = asFiniteNumber(t["output"]);
58
+ if (input === void 0 || output === void 0) return void 0;
59
+ const cacheRaw = t["cache"];
60
+ if (typeof cacheRaw !== "object" || cacheRaw === null) return void 0;
61
+ const cache = cacheRaw;
62
+ const cacheRead = asFiniteNumber(cache["read"]);
63
+ const cacheWrite = asFiniteNumber(cache["write"]);
64
+ if (cacheRead === void 0 || cacheWrite === void 0) return void 0;
65
+ const modelId = asNonEmptyString(obj["modelID"]);
66
+ if (modelId === void 0) return void 0;
67
+ const timeRaw = obj["time"];
68
+ let ts;
69
+ if (timeRaw !== void 0 && timeRaw !== null) {
70
+ if (typeof timeRaw !== "object") return void 0;
71
+ const created = asFiniteNumber(timeRaw["created"]);
72
+ if (created === void 0) return void 0;
73
+ ts = Math.trunc(created);
74
+ }
75
+ const providerId = asNonEmptyString(obj["providerID"]) ?? inferProvider(modelId) ?? DEFAULT_PROVIDER;
76
+ const agent = asNonEmptyString(obj["agent"]) ?? asNonEmptyString(obj["mode"]);
77
+ return {
78
+ id: asNonEmptyString(obj["id"]),
79
+ sessionId: asNonEmptyString(obj["session_id"]),
80
+ modelId,
81
+ providerId,
82
+ agent,
83
+ tokens: {
84
+ input: tokenField(input),
85
+ output: tokenField(output),
86
+ reasoning: tokenField(t["reasoning"]),
87
+ cacheRead: tokenField(cacheRead),
88
+ cacheWrite: tokenField(cacheWrite)
89
+ },
90
+ cost: Math.max(0, asFiniteNumber(obj["cost"]) ?? 0),
91
+ ts
92
+ };
93
+ }
94
+ function selectRows(db) {
95
+ const rows = [];
96
+ for (const r of db.all(QUERY)) {
97
+ const id = asString(r["id"]);
98
+ const sessionId = asString(r["session_id"]);
99
+ const data = asString(r["data"]);
100
+ if (id === void 0 || sessionId === void 0 || data === void 0) continue;
101
+ rows.push({ id, sessionId, data });
102
+ }
103
+ return rows;
104
+ }
105
+ function parseDb(db, fallbackTs) {
106
+ const records = [];
107
+ for (const row of selectRows(db)) {
108
+ const msg = parseData(row.data);
109
+ if (msg === void 0) continue;
110
+ const dedupKey = msg.id ?? row.id;
111
+ const sessionId = msg.sessionId ?? row.sessionId;
112
+ const ts = msg.ts ?? fallbackTs;
113
+ const tokens = emptyTokens();
114
+ tokens.input = msg.tokens.input;
115
+ tokens.output = msg.tokens.output;
116
+ tokens.cacheRead = msg.tokens.cacheRead;
117
+ tokens.cacheWrite = msg.tokens.cacheWrite;
118
+ tokens.reasoning = msg.tokens.reasoning;
119
+ const record = {
120
+ platformId: PLATFORM_ID,
121
+ modelId: msg.modelId,
122
+ providerId: msg.providerId,
123
+ sessionId,
124
+ tokens,
125
+ ts,
126
+ messageCount: 1,
127
+ dedupKey,
128
+ confidence: "host-reported"
129
+ };
130
+ if (msg.cost > 0) record.cost = msg.cost;
131
+ if (msg.agent !== void 0) record.agent = msg.agent;
132
+ records.push(record);
133
+ }
134
+ return records;
135
+ }
136
+ var kiloCliReader = {
137
+ platformId: PLATFORM_ID,
138
+ kind: "local",
139
+ async read({ sinceMs }) {
140
+ const dbPath = firstExistingRoot(PLATFORM_ID);
141
+ if (dbPath === void 0) return [];
142
+ const db = await openSqlite(dbPath);
143
+ if (db === null) return [];
144
+ try {
145
+ const fallbackTs = fileMtimeMs(dbPath);
146
+ const rows = parseDb(db, fallbackTs);
147
+ if (sinceMs === void 0) return rows;
148
+ return rows.filter((r) => r.ts >= sinceMs);
149
+ } catch {
150
+ return [];
151
+ } finally {
152
+ db.close();
153
+ }
154
+ }
155
+ };
156
+ var kilo_cli_default = kiloCliReader;
157
+ export {
158
+ kilo_cli_default as default
159
+ };
160
+ //# sourceMappingURL=kilo-cli-EVBKUODA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/usage/readers/kilo-cli.ts"],"sourcesContent":["/**\n * usage/readers/kilo-cli — Kilo CLI SQLite session reader.\n *\n * Faithful port of tokscale sessions/kilo.rs (parse_kilo_sqlite /\n * parse_kilo_sqlite_with_fallback, lines 53-155). The Kilo CLI stores every\n * message as one row in a `message` table whose `data` column is the raw message\n * JSON (TEXT) — the same shape OpenCode uses. This reader opens that DB read-only\n * and reports the assistant rows' token usage.\n *\n * This is the SQLite Kilo CLI reader and is DISTINCT from the already-shipped\n * `kilo` reader (sessions/kilocode.rs), which parses the VS Code KiloCode task\n * logs (`ui_messages.json`, the Roo/Kilo `api_req_started` JSON format). They are\n * different products with different storage and carry different platformIds\n * (`kilo-cli` here vs `kilo` there) so their rows never merge.\n *\n * DB path: ~/.local/share/kilo/kilo.db (XDG_DATA_HOME and the\n * AGENT_CONNECTOR_KILO_CLI_DIR override are honored via paths.ts hostRoots).\n *\n * SQL (port of the Rust `query`):\n * SELECT m.id, m.session_id, m.data\n * FROM message m\n * WHERE json_valid(m.data)\n * AND json_extract(m.data, '$.role') = 'assistant'\n * AND json_extract(m.data, '$.tokens') IS NOT NULL\n *\n * Tokens (off the parsed `data` JSON, each clamped ≥ 0 — the Rust `.max(0)` on\n * the i64 fields; KiloTokens makes input/output/cache.read/cache.write required\n * and reasoning optional, so a row missing one of the required fields fails the\n * serde deserialize and is skipped — replicated here):\n * tokens.input → input\n * tokens.output → output\n * tokens.cache.read → cacheRead\n * tokens.cache.write → cacheWrite\n * tokens.reasoning → reasoning (optional; 0 when absent)\n * cost is `data.$.cost` (clamped ≥ 0), emitted only when > 0.\n *\n * model / provider: data.$.modelID is REQUIRED — a row without it is skipped\n * (the Rust `match msg.model_id { None => continue }`). provider prefers\n * data.$.providerID, then inferProvider(modelId), then the \"kilo\" default.\n *\n * agent: data.$.agent then data.$.mode (Rust `msg.agent.or(msg.mode)` — note this\n * is the OPPOSITE precedence to the OpenCode reader's mode-then-agent).\n *\n * session: data.$.session_id (preferred) else the m.session_id column.\n *\n * project: NONE — the Kilo CLI log carries no cwd/workspace, so no projectKey\n * is emitted (matches the spec's \"projectAttribution: None\").\n *\n * timestamp: data.$.time.created (Unix MILLISECONDS, float, truncated to int).\n * When the `time` block is absent the fallback is the DB file's mtime (port of\n * file_modified_timestamp_ms). A `time` block present but with a non-numeric\n * `created` fails the serde deserialize, so the row is skipped (not fallen back).\n *\n * DEDUP — dedupKey = data.$.id (the embedded message id) else the m.id row id\n * (Rust `msg.id.or(Some(row_id))`). No fingerprint merge: the Kilo CLI is a\n * single source of record, so a row-id backstop is sufficient.\n *\n * confidence: \"host-reported\" (Kilo logs real token counts).\n *\n * Fail-open: DB missing/locked/unreadable/corrupt → openSqlite() returns null →\n * []; a malformed row → skipped. Never throws. Read-only.\n */\n\nimport type { SqliteDb } from \"../sqlite.js\";\nimport type { UsageReader, UsageRecord } from \"../types.js\";\nimport { emptyTokens } from \"../aggregate.js\";\nimport { fileMtimeMs } from \"../jsonl.js\";\nimport { inferProvider } from \"../normalize.js\";\nimport { firstExistingRoot } from \"../paths.js\";\nimport { openSqlite } from \"../sqlite.js\";\n\nconst PLATFORM_ID = \"kilo-cli\" as const;\nconst DEFAULT_PROVIDER = \"kilo\";\n\nconst QUERY = `\n SELECT m.id, m.session_id, m.data\n FROM message m\n WHERE json_valid(m.data)\n AND json_extract(m.data, '$.role') = 'assistant'\n AND json_extract(m.data, '$.tokens') IS NOT NULL\n`;\n\n// ─────────────────────────────────────────────────────────────────────────\n// Value coercion\n// ─────────────────────────────────────────────────────────────────────────\n\n/** A non-negative integer token field (port of `.max(0)` on an i64). */\nfunction tokenField(v: unknown): number {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" ? Number(v) : NaN;\n if (!Number.isFinite(n)) return 0;\n return Math.max(0, Math.trunc(n));\n}\n\n/** A finite number (the serde i64/f64 required-field semantics), or undefined. */\nfunction asFiniteNumber(v: unknown): number | undefined {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" ? Number(v) : NaN;\n return Number.isFinite(n) ? n : undefined;\n}\n\n/** A non-empty string, or undefined. */\nfunction asNonEmptyString(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** A SELECT column that may come back as Uint8Array (BLOB) / number — to string. */\nfunction asString(v: unknown): string | undefined {\n if (typeof v === \"string\") return v;\n if (typeof v === \"number\" && Number.isFinite(v)) return String(v);\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// The parsed `data` column (the KiloMessage subset we read)\n// ─────────────────────────────────────────────────────────────────────────\n\ninterface KiloTokens {\n input: number;\n output: number;\n reasoning: number;\n cacheRead: number;\n cacheWrite: number;\n}\n\ninterface ParsedMessage {\n /** Embedded message id (preferred dedup key); undefined when absent. */\n id: string | undefined;\n /** Embedded session id (preferred over the row column); undefined when absent. */\n sessionId: string | undefined;\n modelId: string;\n providerId: string;\n agent: string | undefined;\n tokens: KiloTokens;\n cost: number;\n /** Truncated time.created (ms), or undefined → caller uses the mtime fallback. */\n ts: number | undefined;\n}\n\n/**\n * Parse the `data` JSON of a row (port of the KiloMessage deserialize + the\n * post-deserialize checks). Returns undefined for any row the Rust would skip:\n * • not a JSON object / not parseable;\n * • role != \"assistant\" (redundant with the SQL filter, but the Rust re-checks);\n * • no tokens block, or a tokens block missing a REQUIRED field\n * (input / output / cache.read / cache.write — serde would fail the struct);\n * • a `time` block present but with a non-numeric `created` (serde fails);\n * • no modelID (the Rust `match msg.model_id { None => continue }`).\n */\nfunction parseData(dataJson: string): ParsedMessage | undefined {\n let value: unknown;\n try {\n value = JSON.parse(dataJson);\n } catch {\n return undefined;\n }\n if (typeof value !== \"object\" || value === null) return undefined;\n const obj = value as Record<string, unknown>;\n\n if (obj[\"role\"] !== \"assistant\") return undefined;\n\n // tokens — required block; input/output/cache.{read,write} are required fields\n // (KiloTokens / KiloCache), reasoning is optional. A missing required field\n // would fail serde, so we reject the row rather than coerce it to 0.\n const tokensRaw = obj[\"tokens\"];\n if (typeof tokensRaw !== \"object\" || tokensRaw === null) return undefined;\n const t = tokensRaw as Record<string, unknown>;\n const input = asFiniteNumber(t[\"input\"]);\n const output = asFiniteNumber(t[\"output\"]);\n if (input === undefined || output === undefined) return undefined;\n\n const cacheRaw = t[\"cache\"];\n if (typeof cacheRaw !== \"object\" || cacheRaw === null) return undefined;\n const cache = cacheRaw as Record<string, unknown>;\n const cacheRead = asFiniteNumber(cache[\"read\"]);\n const cacheWrite = asFiniteNumber(cache[\"write\"]);\n if (cacheRead === undefined || cacheWrite === undefined) return undefined;\n\n // modelID — required (the Rust skips a row that lacks it).\n const modelId = asNonEmptyString(obj[\"modelID\"]);\n if (modelId === undefined) return undefined;\n\n // time — optional block; when present, `created` is a required f64 (serde\n // would fail an absent/non-numeric created), so a malformed time skips the row.\n const timeRaw = obj[\"time\"];\n let ts: number | undefined;\n if (timeRaw !== undefined && timeRaw !== null) {\n if (typeof timeRaw !== \"object\") return undefined;\n const created = asFiniteNumber((timeRaw as Record<string, unknown>)[\"created\"]);\n if (created === undefined) return undefined;\n ts = Math.trunc(created);\n }\n\n // provider: data.$.providerID, then inferred from model, then the \"kilo\" default.\n const providerId =\n asNonEmptyString(obj[\"providerID\"]) ?? inferProvider(modelId) ?? DEFAULT_PROVIDER;\n\n // agent prefers agent then mode (Rust: msg.agent.or(msg.mode)).\n const agent = asNonEmptyString(obj[\"agent\"]) ?? asNonEmptyString(obj[\"mode\"]);\n\n return {\n id: asNonEmptyString(obj[\"id\"]),\n sessionId: asNonEmptyString(obj[\"session_id\"]),\n modelId,\n providerId,\n agent,\n tokens: {\n input: tokenField(input),\n output: tokenField(output),\n reasoning: tokenField(t[\"reasoning\"]),\n cacheRead: tokenField(cacheRead),\n cacheWrite: tokenField(cacheWrite),\n },\n cost: Math.max(0, asFiniteNumber(obj[\"cost\"]) ?? 0),\n ts,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// DB read\n// ─────────────────────────────────────────────────────────────────────────\n\n/** The three selected columns of one row. */\ninterface Row {\n id: string;\n sessionId: string;\n data: string;\n}\n\n/** Select the assistant rows that carry a tokens block. */\nfunction selectRows(db: SqliteDb): Row[] {\n const rows: Row[] = [];\n for (const r of db.all(QUERY)) {\n const id = asString(r[\"id\"]);\n const sessionId = asString(r[\"session_id\"]);\n const data = asString(r[\"data\"]);\n if (id === undefined || sessionId === undefined || data === undefined) continue;\n rows.push({ id, sessionId, data });\n }\n return rows;\n}\n\n/** Parse the Kilo CLI SQLite DB into usage records (fallbackTs = DB file mtime). */\nfunction parseDb(db: SqliteDb, fallbackTs: number): UsageRecord[] {\n const records: UsageRecord[] = [];\n\n for (const row of selectRows(db)) {\n const msg = parseData(row.data);\n if (msg === undefined) continue;\n\n // dedupKey prefers the embedded message id; falls back to the row id.\n const dedupKey = msg.id ?? row.id;\n // session prefers the embedded id; falls back to the m.session_id column.\n const sessionId = msg.sessionId ?? row.sessionId;\n // timestamp prefers time.created; falls back to the DB file mtime.\n const ts = msg.ts ?? fallbackTs;\n\n const tokens = emptyTokens();\n tokens.input = msg.tokens.input;\n tokens.output = msg.tokens.output;\n tokens.cacheRead = msg.tokens.cacheRead;\n tokens.cacheWrite = msg.tokens.cacheWrite;\n tokens.reasoning = msg.tokens.reasoning;\n\n const record: UsageRecord = {\n platformId: PLATFORM_ID,\n modelId: msg.modelId,\n providerId: msg.providerId,\n sessionId,\n tokens,\n ts,\n messageCount: 1,\n dedupKey,\n confidence: \"host-reported\",\n };\n if (msg.cost > 0) record.cost = msg.cost;\n if (msg.agent !== undefined) record.agent = msg.agent;\n\n records.push(record);\n }\n\n return records;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Reader singleton\n// ─────────────────────────────────────────────────────────────────────────\n\n/** The Kilo CLI usage reader singleton. */\nconst kiloCliReader: 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 kilo.db → fail-open\n\n const db = await openSqlite(dbPath);\n if (db === null) return []; // missing/locked/unreadable/corrupt → fail-open\n\n try {\n const fallbackTs = fileMtimeMs(dbPath);\n const rows = parseDb(db, fallbackTs);\n if (sinceMs === undefined) return rows;\n return rows.filter((r) => r.ts >= sinceMs);\n } catch {\n return []; // any unexpected error → fail-open\n } finally {\n db.close();\n }\n },\n};\n\nexport default kiloCliReader;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ fileMtimeMs,
4
+ readJsonFile,
5
+ readJsonlLines
6
+ } from "./chunk-RK6VBLXP.js";
7
+ import {
8
+ expandHome,
9
+ firstExistingRoot,
10
+ isDir,
11
+ walkFiles
12
+ } from "./chunk-JWNT5KW7.js";
13
+ import {
14
+ emptyTokens
15
+ } from "./chunk-XD7MY6HB.js";
16
+
17
+ // src/usage/readers/kimi.ts
18
+ import { basename, dirname, join } from "path";
19
+ var PLATFORM_ID = "kimi";
20
+ var DEFAULT_MODEL = "kimi-for-coding";
21
+ var DEFAULT_PROVIDER = "moonshot";
22
+ function toNonNegInt(v) {
23
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
24
+ if (!Number.isFinite(n)) return 0;
25
+ return Math.max(0, Math.trunc(n));
26
+ }
27
+ function parseWireTs(v) {
28
+ if (typeof v === "number" && Number.isFinite(v) && v > 0) {
29
+ return Math.trunc(v * 1e3);
30
+ }
31
+ return null;
32
+ }
33
+ function readModelFromConfig(wirePath) {
34
+ const sessionDir = dirname(wirePath);
35
+ const groupDir = dirname(sessionDir);
36
+ const sessionsDir = dirname(groupDir);
37
+ const homeDir = dirname(sessionsDir);
38
+ const configPath = join(homeDir, "config.json");
39
+ const parsed = readJsonFile(configPath);
40
+ if (typeof parsed === "object" && parsed !== null) {
41
+ const model = parsed.model;
42
+ if (typeof model === "string" && model !== "") return model;
43
+ }
44
+ return DEFAULT_MODEL;
45
+ }
46
+ function extractSessionId(wirePath) {
47
+ const dir = basename(dirname(wirePath));
48
+ return dir === "" ? "unknown" : dir;
49
+ }
50
+ function tokenTotal(t) {
51
+ return t.input + t.output + t.cacheRead + t.cacheWrite + t.reasoning;
52
+ }
53
+ function shouldReplace(existing, candidate) {
54
+ const existingTotal = tokenTotal(existing.tokens);
55
+ const candidateTotal = tokenTotal(candidate.tokens);
56
+ return candidateTotal > existingTotal || candidateTotal === existingTotal && candidate.ts >= existing.ts;
57
+ }
58
+ function parseKimiFile(path) {
59
+ const lines = readJsonlLines(path);
60
+ if (lines.length === 0) return [];
61
+ const model = readModelFromConfig(path);
62
+ const sessionId = extractSessionId(path);
63
+ const mtime = fileMtimeMs(path);
64
+ const out = [];
65
+ const keyedIndices = /* @__PURE__ */ new Map();
66
+ for (const raw of lines) {
67
+ if (typeof raw !== "object" || raw === null) continue;
68
+ const wire = raw;
69
+ if (wire.type === "metadata") continue;
70
+ const message = wire.message;
71
+ if (typeof message !== "object" || message === null) continue;
72
+ if (message.type !== "StatusUpdate") continue;
73
+ const payload = message.payload;
74
+ if (typeof payload !== "object" || payload === null) continue;
75
+ const usage = payload.token_usage;
76
+ if (typeof usage !== "object" || usage === null) continue;
77
+ const input = toNonNegInt(usage.input_other);
78
+ const output = toNonNegInt(usage.output);
79
+ const cacheRead = toNonNegInt(usage.input_cache_read);
80
+ const cacheWrite = toNonNegInt(usage.input_cache_creation);
81
+ if (input + output + cacheRead + cacheWrite === 0) continue;
82
+ const ts = parseWireTs(wire.timestamp) ?? mtime;
83
+ const tokens = emptyTokens();
84
+ tokens.input = input;
85
+ tokens.output = output;
86
+ tokens.cacheRead = cacheRead;
87
+ tokens.cacheWrite = cacheWrite;
88
+ const messageId = payload.message_id;
89
+ const dedupKey = typeof messageId === "string" && messageId !== "" ? messageId : void 0;
90
+ const record = {
91
+ platformId: PLATFORM_ID,
92
+ modelId: model,
93
+ providerId: DEFAULT_PROVIDER,
94
+ sessionId,
95
+ tokens,
96
+ ts,
97
+ messageCount: 1,
98
+ confidence: "host-reported"
99
+ };
100
+ if (dedupKey !== void 0) record.dedupKey = dedupKey;
101
+ if (dedupKey === void 0) {
102
+ out.push(record);
103
+ continue;
104
+ }
105
+ const existingIndex = keyedIndices.get(dedupKey);
106
+ if (existingIndex !== void 0) {
107
+ const existing = out[existingIndex];
108
+ if (existing !== void 0 && shouldReplace(existing, record)) {
109
+ out[existingIndex] = record;
110
+ }
111
+ continue;
112
+ }
113
+ keyedIndices.set(dedupKey, out.length);
114
+ out.push(record);
115
+ }
116
+ return out;
117
+ }
118
+ function kimiSessionRoots() {
119
+ const roots = [];
120
+ const codeHome = process.env.KIMI_CODE_HOME;
121
+ if (codeHome != null && codeHome.trim() !== "") {
122
+ roots.push(join(expandHome(codeHome.trim()), "sessions"));
123
+ }
124
+ const standard = firstExistingRoot(PLATFORM_ID);
125
+ if (standard !== void 0) roots.push(standard);
126
+ return roots;
127
+ }
128
+ var kimiReader = {
129
+ platformId: PLATFORM_ID,
130
+ kind: "local",
131
+ async read({ sinceMs }) {
132
+ const roots = kimiSessionRoots().filter((r) => isDir(r));
133
+ if (roots.length === 0) return [];
134
+ const seen = /* @__PURE__ */ new Set();
135
+ const records = [];
136
+ for (const root of roots) {
137
+ const files = walkFiles(root, (name) => name === "wire.jsonl");
138
+ for (const file of files) {
139
+ if (seen.has(file)) continue;
140
+ seen.add(file);
141
+ const rows = parseKimiFile(file);
142
+ for (const row of rows) {
143
+ if (sinceMs !== void 0 && row.ts < sinceMs) continue;
144
+ records.push(row);
145
+ }
146
+ }
147
+ }
148
+ return records;
149
+ }
150
+ };
151
+ var kimi_default = kimiReader;
152
+ export {
153
+ kimi_default as default
154
+ };
155
+ //# sourceMappingURL=kimi-EI3UM3ZV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/usage/readers/kimi.ts"],"sourcesContent":["/**\n * usage/readers/kimi — Kimi CLI (Moonshot) native session-log reader.\n *\n * Faithful port of tokscale sessions/kimi.rs. Reads the wire protocol log at\n * ~/.kimi/sessions/<GROUP_ID>/<SESSION_UUID>/wire.jsonl\n * (the model name is read from the sibling ~/.kimi/config.json). Each line is a\n * timestamped wire frame; only frames whose `message.type === \"StatusUpdate\"`\n * carry a `payload.token_usage` block:\n * input_other → input\n * output → output\n * input_cache_read → cacheRead\n * input_cache_creation → cacheWrite (cache CREATION cost, not a read)\n * reasoning is always 0 (the wire protocol folds reasoning into output).\n *\n * The first line (`{\"type\":\"metadata\", …}`) and every non-StatusUpdate frame\n * (TurnBegin / ContentPart / ToolCall / StepBegin …) are skipped. Zero-token\n * StatusUpdates are dropped.\n *\n * DEDUP (the double-counting hazard): Kimi emits PROGRESSIVE StatusUpdates for a\n * single assistant message as generation streams (e.g. message_id \"msg-x\" at\n * 100→10 tokens, then 120→30). We dedup by `payload.message_id`, keeping the row\n * with the MAX total tokens (tie-break: the later timestamp). Records WITHOUT a\n * (non-empty) message_id are never merged — each passes through as its own row,\n * exactly as the Rust push_or_replace_status_update does. The kept row's\n * `dedupKey` is its message_id (absent for un-keyed rows).\n *\n * Storage root: ~/.kimi/sessions, with two overrides honored on top of the fixed\n * paths.ts resolution — the AGENT_CONNECTOR_KIMI_DIR override (via\n * firstExistingRoot) and $KIMI_CODE_HOME, which relocates the Kimi home (the\n * config.json lookup is path-relative, so it transparently handles a `.kimi-code`\n * home as well as `.kimi`).\n *\n * Model: `.model` from <home>/config.json (fallback \"kimi-for-coding\"); provider\n * is hard-coded \"moonshot\". Session id is the SESSION_UUID directory name. Kimi's\n * wire log carries no cwd, so there is no project attribution. Confidence is\n * \"host-reported\" (real host token counts).\n *\n * Fail-open: no root → []; an unreadable/malformed file or line → skipped.\n */\n\nimport { basename, dirname, join } from \"node:path\";\n\nimport type { TokenBreakdown, UsageReader, UsageRecord } from \"../types.js\";\nimport { emptyTokens } from \"../aggregate.js\";\nimport { fileMtimeMs, readJsonFile, readJsonlLines } from \"../jsonl.js\";\nimport { expandHome, firstExistingRoot, isDir, walkFiles } from \"../paths.js\";\n\nconst PLATFORM_ID = \"kimi\" as const;\nconst DEFAULT_MODEL = \"kimi-for-coding\";\nconst DEFAULT_PROVIDER = \"moonshot\";\n\n/** A wire.jsonl line: metadata header OR a timestamped message frame. */\ninterface WireLine {\n type?: unknown;\n timestamp?: unknown;\n message?: {\n type?: unknown;\n payload?: {\n token_usage?: {\n input_other?: unknown;\n output?: unknown;\n input_cache_read?: unknown;\n input_cache_creation?: unknown;\n };\n message_id?: unknown;\n };\n };\n}\n\n/** Coerce an unknown to a non-negative integer (0 on absence/garbage). */\nfunction toNonNegInt(v: unknown): number {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" ? Number(v) : NaN;\n if (!Number.isFinite(n)) return 0;\n return Math.max(0, Math.trunc(n));\n}\n\n/**\n * Convert the wire `timestamp` (Unix seconds, often fractional like\n * 1770983426.420942) to epoch ms. Returns null when unusable so the caller can\n * fall back to the file mtime (port of `timestamp * 1000.0 as i64`).\n */\nfunction parseWireTs(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v) && v > 0) {\n return Math.trunc(v * 1000);\n }\n return null;\n}\n\n/**\n * Read the model name from the Kimi home's config.json (port of\n * read_model_from_config). The wire path is\n * <home>/sessions/<GROUP_ID>/<SESSION_UUID>/wire.jsonl\n * so the home dir is four `dirname` hops up, and config.json sits beside the\n * sessions dir. Fail-open to DEFAULT_MODEL on any missing/garbage value.\n */\nfunction readModelFromConfig(wirePath: string): string {\n // wire.jsonl → SESSION_UUID → GROUP_ID → sessions → <home>\n const sessionDir = dirname(wirePath);\n const groupDir = dirname(sessionDir);\n const sessionsDir = dirname(groupDir);\n const homeDir = dirname(sessionsDir);\n const configPath = join(homeDir, \"config.json\");\n\n const parsed = readJsonFile(configPath);\n if (typeof parsed === \"object\" && parsed !== null) {\n const model = (parsed as { model?: unknown }).model;\n if (typeof model === \"string\" && model !== \"\") return model;\n }\n return DEFAULT_MODEL;\n}\n\n/**\n * Session id from the wire path: the SESSION_UUID directory name (the immediate\n * parent of wire.jsonl). Port of extract_session_id.\n */\nfunction extractSessionId(wirePath: string): string {\n const dir = basename(dirname(wirePath));\n return dir === \"\" ? \"unknown\" : dir;\n}\n\n/** Sum of the five token dimensions (port of TokenBreakdown::total). */\nfunction tokenTotal(t: TokenBreakdown): number {\n return t.input + t.output + t.cacheRead + t.cacheWrite + t.reasoning;\n}\n\n/**\n * Whether `candidate` should replace `existing` for the same message_id. Port of\n * should_replace_status_update: a strictly larger total wins; on an equal total\n * the later-or-equal timestamp wins (so the freshest progressive update is kept).\n */\nfunction shouldReplace(existing: UsageRecord, candidate: UsageRecord): boolean {\n const existingTotal = tokenTotal(existing.tokens);\n const candidateTotal = tokenTotal(candidate.tokens);\n return (\n candidateTotal > existingTotal ||\n (candidateTotal === existingTotal && candidate.ts >= existing.ts)\n );\n}\n\n/** Parse one Kimi wire.jsonl file into usage records (port of parse_kimi_file). */\nfunction parseKimiFile(path: string): UsageRecord[] {\n const lines = readJsonlLines(path);\n if (lines.length === 0) return [];\n\n const model = readModelFromConfig(path);\n const sessionId = extractSessionId(path);\n const mtime = fileMtimeMs(path);\n\n const out: UsageRecord[] = [];\n // message_id → index into `out`, for progressive-StatusUpdate dedup.\n const keyedIndices = new Map<string, number>();\n\n for (const raw of lines) {\n if (typeof raw !== \"object\" || raw === null) continue;\n const wire = raw as WireLine;\n\n // Skip the metadata header line.\n if (wire.type === \"metadata\") continue;\n\n const message = wire.message;\n if (typeof message !== \"object\" || message === null) continue;\n\n // Only StatusUpdate frames carry token usage.\n if (message.type !== \"StatusUpdate\") continue;\n\n const payload = message.payload;\n if (typeof payload !== \"object\" || payload === null) continue;\n\n const usage = payload.token_usage;\n if (typeof usage !== \"object\" || usage === null) continue;\n\n const input = toNonNegInt(usage.input_other);\n const output = toNonNegInt(usage.output);\n const cacheRead = toNonNegInt(usage.input_cache_read);\n const cacheWrite = toNonNegInt(usage.input_cache_creation);\n\n // Skip zero-token entries.\n if (input + output + cacheRead + cacheWrite === 0) continue;\n\n const ts = parseWireTs(wire.timestamp) ?? mtime;\n\n const tokens = emptyTokens();\n tokens.input = input;\n tokens.output = output;\n tokens.cacheRead = cacheRead;\n tokens.cacheWrite = cacheWrite;\n // reasoning stays 0 — the Kimi wire protocol folds reasoning into output.\n\n const messageId = payload.message_id;\n const dedupKey =\n typeof messageId === \"string\" && messageId !== \"\" ? messageId : undefined;\n\n const record: UsageRecord = {\n platformId: PLATFORM_ID,\n modelId: model,\n providerId: DEFAULT_PROVIDER,\n sessionId,\n tokens,\n ts,\n messageCount: 1,\n confidence: \"host-reported\",\n };\n if (dedupKey !== undefined) record.dedupKey = dedupKey;\n\n if (dedupKey === undefined) {\n // Un-keyed StatusUpdates are never merged — each is its own row.\n out.push(record);\n continue;\n }\n\n const existingIndex = keyedIndices.get(dedupKey);\n if (existingIndex !== undefined) {\n const existing = out[existingIndex];\n if (existing !== undefined && shouldReplace(existing, record)) {\n out[existingIndex] = record;\n }\n continue;\n }\n\n keyedIndices.set(dedupKey, out.length);\n out.push(record);\n }\n\n return out;\n}\n\n/**\n * Candidate Kimi session roots, most-preferred first. Honors the fixed paths.ts\n * resolution (AGENT_CONNECTOR_KIMI_DIR override → ~/.kimi/sessions) plus\n * $KIMI_CODE_HOME, which relocates the Kimi home directory.\n */\nfunction kimiSessionRoots(): string[] {\n const roots: string[] = [];\n\n const codeHome = process.env.KIMI_CODE_HOME;\n if (codeHome != null && codeHome.trim() !== \"\") {\n roots.push(join(expandHome(codeHome.trim()), \"sessions\"));\n }\n\n const standard = firstExistingRoot(PLATFORM_ID);\n if (standard !== undefined) roots.push(standard);\n\n return roots;\n}\n\n/** The Kimi CLI usage reader singleton. */\nconst kimiReader: UsageReader = {\n platformId: PLATFORM_ID,\n kind: \"local\",\n async read({ sinceMs }: { sinceMs?: number }): Promise<UsageRecord[]> {\n const roots = kimiSessionRoots().filter((r) => isDir(r));\n if (roots.length === 0) return []; // no ~/.kimi/sessions → fail-open\n\n // <root>/<GROUP_ID>/<SESSION_UUID>/wire.jsonl\n const seen = new Set<string>();\n const records: UsageRecord[] = [];\n for (const root of roots) {\n const files = walkFiles(root, (name) => name === \"wire.jsonl\");\n for (const file of files) {\n if (seen.has(file)) continue; // de-overlap if KIMI_CODE_HOME == ~/.kimi\n seen.add(file);\n const rows = parseKimiFile(file);\n for (const row of rows) {\n if (sinceMs !== undefined && row.ts < sinceMs) continue;\n records.push(row);\n }\n }\n }\n return records;\n },\n};\n\nexport default kimiReader;\n"],"mappings":"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","names":[]}