luca 1.1.2 → 3.0.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 (754) hide show
  1. package/.github/workflows/release.yaml +169 -0
  2. package/AGENTS.md +99 -0
  3. package/CLAUDE.md +115 -0
  4. package/CNAME +1 -0
  5. package/README.md +257 -8
  6. package/RUNME.md +56 -0
  7. package/assistants/codingAssistant/ABOUT.md +5 -0
  8. package/assistants/codingAssistant/CORE.md +28 -0
  9. package/assistants/codingAssistant/hooks.ts +21 -0
  10. package/assistants/codingAssistant/tools.ts +12 -0
  11. package/assistants/inkbot/ABOUT.md +16 -0
  12. package/assistants/inkbot/CORE.md +330 -0
  13. package/assistants/inkbot/hooks.ts +6 -0
  14. package/assistants/inkbot/tools.ts +53 -0
  15. package/assistants/researcher/ABOUT.md +5 -0
  16. package/assistants/researcher/CORE.md +46 -0
  17. package/assistants/researcher/hooks.ts +16 -0
  18. package/assistants/researcher/tools.ts +237 -0
  19. package/bun.lock +2769 -0
  20. package/bunfig.toml +3 -0
  21. package/commands/audit-docs.ts +740 -0
  22. package/commands/build-bootstrap.ts +118 -0
  23. package/commands/build-python-bridge.ts +43 -0
  24. package/commands/build-scaffolds.ts +176 -0
  25. package/commands/generate-api-docs.ts +114 -0
  26. package/commands/inkbot.ts +874 -0
  27. package/commands/release.ts +80 -0
  28. package/commands/try-all-challenges.ts +543 -0
  29. package/commands/try-challenge.ts +100 -0
  30. package/dist/agi/container.server.d.ts +63 -0
  31. package/dist/agi/container.server.d.ts.map +1 -0
  32. package/dist/agi/endpoints/ask.d.ts +20 -0
  33. package/dist/agi/endpoints/ask.d.ts.map +1 -0
  34. package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
  35. package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
  36. package/dist/agi/endpoints/conversations.d.ts +18 -0
  37. package/dist/agi/endpoints/conversations.d.ts.map +1 -0
  38. package/dist/agi/endpoints/experts.d.ts +8 -0
  39. package/dist/agi/endpoints/experts.d.ts.map +1 -0
  40. package/dist/agi/feature.d.ts +9 -0
  41. package/dist/agi/feature.d.ts.map +1 -0
  42. package/dist/agi/features/assistant.d.ts +509 -0
  43. package/dist/agi/features/assistant.d.ts.map +1 -0
  44. package/dist/agi/features/assistants-manager.d.ts +236 -0
  45. package/dist/agi/features/assistants-manager.d.ts.map +1 -0
  46. package/dist/agi/features/autonomous-assistant.d.ts +281 -0
  47. package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
  48. package/dist/agi/features/browser-use.d.ts +479 -0
  49. package/dist/agi/features/browser-use.d.ts.map +1 -0
  50. package/dist/agi/features/claude-code.d.ts +824 -0
  51. package/dist/agi/features/claude-code.d.ts.map +1 -0
  52. package/dist/agi/features/conversation-history.d.ts +245 -0
  53. package/dist/agi/features/conversation-history.d.ts.map +1 -0
  54. package/dist/agi/features/conversation.d.ts +464 -0
  55. package/dist/agi/features/conversation.d.ts.map +1 -0
  56. package/dist/agi/features/docs-reader.d.ts +72 -0
  57. package/dist/agi/features/docs-reader.d.ts.map +1 -0
  58. package/dist/agi/features/file-tools.d.ts +110 -0
  59. package/dist/agi/features/file-tools.d.ts.map +1 -0
  60. package/dist/agi/features/luca-coder.d.ts +323 -0
  61. package/dist/agi/features/luca-coder.d.ts.map +1 -0
  62. package/dist/agi/features/openai-codex.d.ts +381 -0
  63. package/dist/agi/features/openai-codex.d.ts.map +1 -0
  64. package/dist/agi/features/openapi.d.ts +200 -0
  65. package/dist/agi/features/openapi.d.ts.map +1 -0
  66. package/dist/agi/features/skills-library.d.ts +167 -0
  67. package/dist/agi/features/skills-library.d.ts.map +1 -0
  68. package/dist/agi/index.d.ts +5 -0
  69. package/dist/agi/index.d.ts.map +1 -0
  70. package/dist/agi/lib/interceptor-chain.d.ts +44 -0
  71. package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
  72. package/dist/agi/lib/token-counter.d.ts +13 -0
  73. package/dist/agi/lib/token-counter.d.ts.map +1 -0
  74. package/dist/bootstrap/generated.d.ts +5 -0
  75. package/dist/bootstrap/generated.d.ts.map +1 -0
  76. package/dist/browser.d.ts +12 -0
  77. package/dist/browser.d.ts.map +1 -0
  78. package/dist/bus.d.ts +29 -0
  79. package/dist/bus.d.ts.map +1 -0
  80. package/dist/cli/build-info.d.ts +4 -0
  81. package/dist/cli/build-info.d.ts.map +1 -0
  82. package/dist/cli/cli.d.ts +3 -0
  83. package/dist/cli/cli.d.ts.map +1 -0
  84. package/dist/client.d.ts +60 -0
  85. package/dist/client.d.ts.map +1 -0
  86. package/dist/clients/civitai/index.d.ts +472 -0
  87. package/dist/clients/civitai/index.d.ts.map +1 -0
  88. package/dist/clients/client-template.d.ts +30 -0
  89. package/dist/clients/client-template.d.ts.map +1 -0
  90. package/dist/clients/comfyui/index.d.ts +281 -0
  91. package/dist/clients/comfyui/index.d.ts.map +1 -0
  92. package/dist/clients/elevenlabs/index.d.ts +197 -0
  93. package/dist/clients/elevenlabs/index.d.ts.map +1 -0
  94. package/dist/clients/graph.d.ts +64 -0
  95. package/dist/clients/graph.d.ts.map +1 -0
  96. package/dist/clients/openai/index.d.ts +247 -0
  97. package/dist/clients/openai/index.d.ts.map +1 -0
  98. package/dist/clients/rest.d.ts +92 -0
  99. package/dist/clients/rest.d.ts.map +1 -0
  100. package/dist/clients/supabase/index.d.ts +176 -0
  101. package/dist/clients/supabase/index.d.ts.map +1 -0
  102. package/dist/clients/websocket.d.ts +127 -0
  103. package/dist/clients/websocket.d.ts.map +1 -0
  104. package/dist/command.d.ts +163 -0
  105. package/dist/command.d.ts.map +1 -0
  106. package/dist/commands/bootstrap.d.ts +20 -0
  107. package/dist/commands/bootstrap.d.ts.map +1 -0
  108. package/dist/commands/chat.d.ts +37 -0
  109. package/dist/commands/chat.d.ts.map +1 -0
  110. package/dist/commands/code.d.ts +28 -0
  111. package/dist/commands/code.d.ts.map +1 -0
  112. package/dist/commands/console.d.ts +22 -0
  113. package/dist/commands/console.d.ts.map +1 -0
  114. package/dist/commands/describe.d.ts +50 -0
  115. package/dist/commands/describe.d.ts.map +1 -0
  116. package/dist/commands/eval.d.ts +23 -0
  117. package/dist/commands/eval.d.ts.map +1 -0
  118. package/dist/commands/help.d.ts +25 -0
  119. package/dist/commands/help.d.ts.map +1 -0
  120. package/dist/commands/index.d.ts +18 -0
  121. package/dist/commands/index.d.ts.map +1 -0
  122. package/dist/commands/introspect.d.ts +24 -0
  123. package/dist/commands/introspect.d.ts.map +1 -0
  124. package/dist/commands/mcp.d.ts +35 -0
  125. package/dist/commands/mcp.d.ts.map +1 -0
  126. package/dist/commands/prompt.d.ts +38 -0
  127. package/dist/commands/prompt.d.ts.map +1 -0
  128. package/dist/commands/run.d.ts +24 -0
  129. package/dist/commands/run.d.ts.map +1 -0
  130. package/dist/commands/sandbox-mcp.d.ts +34 -0
  131. package/dist/commands/sandbox-mcp.d.ts.map +1 -0
  132. package/dist/commands/save-api-docs.d.ts +21 -0
  133. package/dist/commands/save-api-docs.d.ts.map +1 -0
  134. package/dist/commands/scaffold.d.ts +24 -0
  135. package/dist/commands/scaffold.d.ts.map +1 -0
  136. package/dist/commands/select.d.ts +22 -0
  137. package/dist/commands/select.d.ts.map +1 -0
  138. package/dist/commands/serve.d.ts +29 -0
  139. package/dist/commands/serve.d.ts.map +1 -0
  140. package/dist/container-describer.d.ts +144 -0
  141. package/dist/container-describer.d.ts.map +1 -0
  142. package/dist/container.d.ts +451 -0
  143. package/dist/container.d.ts.map +1 -0
  144. package/dist/endpoint.d.ts +113 -0
  145. package/dist/endpoint.d.ts.map +1 -0
  146. package/dist/feature.d.ts +47 -0
  147. package/dist/feature.d.ts.map +1 -0
  148. package/dist/graft.d.ts +29 -0
  149. package/dist/graft.d.ts.map +1 -0
  150. package/dist/hash-object.d.ts +8 -0
  151. package/dist/hash-object.d.ts.map +1 -0
  152. package/dist/helper.d.ts +209 -0
  153. package/dist/helper.d.ts.map +1 -0
  154. package/dist/introspection/generated.node.d.ts +44623 -0
  155. package/dist/introspection/generated.node.d.ts.map +1 -0
  156. package/dist/introspection/generated.web.d.ts +1412 -0
  157. package/dist/introspection/generated.web.d.ts.map +1 -0
  158. package/dist/introspection/index.d.ts +156 -0
  159. package/dist/introspection/index.d.ts.map +1 -0
  160. package/dist/introspection/scan.d.ts +147 -0
  161. package/dist/introspection/scan.d.ts.map +1 -0
  162. package/dist/node/container.d.ts +256 -0
  163. package/dist/node/container.d.ts.map +1 -0
  164. package/dist/node/feature.d.ts +9 -0
  165. package/dist/node/feature.d.ts.map +1 -0
  166. package/dist/node/features/container-link.d.ts +213 -0
  167. package/dist/node/features/container-link.d.ts.map +1 -0
  168. package/dist/node/features/content-db.d.ts +354 -0
  169. package/dist/node/features/content-db.d.ts.map +1 -0
  170. package/dist/node/features/disk-cache.d.ts +236 -0
  171. package/dist/node/features/disk-cache.d.ts.map +1 -0
  172. package/dist/node/features/dns.d.ts +511 -0
  173. package/dist/node/features/dns.d.ts.map +1 -0
  174. package/dist/node/features/docker.d.ts +485 -0
  175. package/dist/node/features/docker.d.ts.map +1 -0
  176. package/dist/node/features/downloader.d.ts +73 -0
  177. package/dist/node/features/downloader.d.ts.map +1 -0
  178. package/dist/node/features/figlet-fonts.d.ts +4 -0
  179. package/dist/node/features/figlet-fonts.d.ts.map +1 -0
  180. package/dist/node/features/file-manager.d.ts +177 -0
  181. package/dist/node/features/file-manager.d.ts.map +1 -0
  182. package/dist/node/features/fs.d.ts +635 -0
  183. package/dist/node/features/fs.d.ts.map +1 -0
  184. package/dist/node/features/git.d.ts +329 -0
  185. package/dist/node/features/git.d.ts.map +1 -0
  186. package/dist/node/features/google-auth.d.ts +200 -0
  187. package/dist/node/features/google-auth.d.ts.map +1 -0
  188. package/dist/node/features/google-calendar.d.ts +194 -0
  189. package/dist/node/features/google-calendar.d.ts.map +1 -0
  190. package/dist/node/features/google-docs.d.ts +138 -0
  191. package/dist/node/features/google-docs.d.ts.map +1 -0
  192. package/dist/node/features/google-drive.d.ts +202 -0
  193. package/dist/node/features/google-drive.d.ts.map +1 -0
  194. package/dist/node/features/google-mail.d.ts +221 -0
  195. package/dist/node/features/google-mail.d.ts.map +1 -0
  196. package/dist/node/features/google-sheets.d.ts +157 -0
  197. package/dist/node/features/google-sheets.d.ts.map +1 -0
  198. package/dist/node/features/grep.d.ts +207 -0
  199. package/dist/node/features/grep.d.ts.map +1 -0
  200. package/dist/node/features/helpers.d.ts +236 -0
  201. package/dist/node/features/helpers.d.ts.map +1 -0
  202. package/dist/node/features/ink.d.ts +332 -0
  203. package/dist/node/features/ink.d.ts.map +1 -0
  204. package/dist/node/features/ipc-socket.d.ts +298 -0
  205. package/dist/node/features/ipc-socket.d.ts.map +1 -0
  206. package/dist/node/features/json-tree.d.ts +140 -0
  207. package/dist/node/features/json-tree.d.ts.map +1 -0
  208. package/dist/node/features/networking.d.ts +373 -0
  209. package/dist/node/features/networking.d.ts.map +1 -0
  210. package/dist/node/features/nlp.d.ts +125 -0
  211. package/dist/node/features/nlp.d.ts.map +1 -0
  212. package/dist/node/features/opener.d.ts +93 -0
  213. package/dist/node/features/opener.d.ts.map +1 -0
  214. package/dist/node/features/os.d.ts +168 -0
  215. package/dist/node/features/os.d.ts.map +1 -0
  216. package/dist/node/features/package-finder.d.ts +419 -0
  217. package/dist/node/features/package-finder.d.ts.map +1 -0
  218. package/dist/node/features/postgres.d.ts +173 -0
  219. package/dist/node/features/postgres.d.ts.map +1 -0
  220. package/dist/node/features/proc.d.ts +285 -0
  221. package/dist/node/features/proc.d.ts.map +1 -0
  222. package/dist/node/features/process-manager.d.ts +427 -0
  223. package/dist/node/features/process-manager.d.ts.map +1 -0
  224. package/dist/node/features/python.d.ts +477 -0
  225. package/dist/node/features/python.d.ts.map +1 -0
  226. package/dist/node/features/redis.d.ts +247 -0
  227. package/dist/node/features/redis.d.ts.map +1 -0
  228. package/dist/node/features/repl.d.ts +84 -0
  229. package/dist/node/features/repl.d.ts.map +1 -0
  230. package/dist/node/features/runpod.d.ts +527 -0
  231. package/dist/node/features/runpod.d.ts.map +1 -0
  232. package/dist/node/features/secure-shell.d.ts +145 -0
  233. package/dist/node/features/secure-shell.d.ts.map +1 -0
  234. package/dist/node/features/semantic-search.d.ts +207 -0
  235. package/dist/node/features/semantic-search.d.ts.map +1 -0
  236. package/dist/node/features/sqlite.d.ts +180 -0
  237. package/dist/node/features/sqlite.d.ts.map +1 -0
  238. package/dist/node/features/telegram.d.ts +173 -0
  239. package/dist/node/features/telegram.d.ts.map +1 -0
  240. package/dist/node/features/transpiler.d.ts +51 -0
  241. package/dist/node/features/transpiler.d.ts.map +1 -0
  242. package/dist/node/features/tts.d.ts +108 -0
  243. package/dist/node/features/tts.d.ts.map +1 -0
  244. package/dist/node/features/ui.d.ts +562 -0
  245. package/dist/node/features/ui.d.ts.map +1 -0
  246. package/dist/node/features/vault.d.ts +90 -0
  247. package/dist/node/features/vault.d.ts.map +1 -0
  248. package/dist/node/features/vm.d.ts +285 -0
  249. package/dist/node/features/vm.d.ts.map +1 -0
  250. package/dist/node/features/yaml-tree.d.ts +118 -0
  251. package/dist/node/features/yaml-tree.d.ts.map +1 -0
  252. package/dist/node/features/yaml.d.ts +127 -0
  253. package/dist/node/features/yaml.d.ts.map +1 -0
  254. package/dist/node.d.ts +67 -0
  255. package/dist/node.d.ts.map +1 -0
  256. package/dist/python/generated.d.ts +2 -0
  257. package/dist/python/generated.d.ts.map +1 -0
  258. package/dist/react/index.d.ts +36 -0
  259. package/dist/react/index.d.ts.map +1 -0
  260. package/dist/registry.d.ts +97 -0
  261. package/dist/registry.d.ts.map +1 -0
  262. package/dist/scaffolds/generated.d.ts +13 -0
  263. package/dist/scaffolds/generated.d.ts.map +1 -0
  264. package/dist/scaffolds/template.d.ts +11 -0
  265. package/dist/scaffolds/template.d.ts.map +1 -0
  266. package/dist/schemas/base.d.ts +254 -0
  267. package/dist/schemas/base.d.ts.map +1 -0
  268. package/dist/selector.d.ts +130 -0
  269. package/dist/selector.d.ts.map +1 -0
  270. package/dist/server.d.ts +89 -0
  271. package/dist/server.d.ts.map +1 -0
  272. package/dist/servers/express.d.ts +104 -0
  273. package/dist/servers/express.d.ts.map +1 -0
  274. package/dist/servers/mcp.d.ts +201 -0
  275. package/dist/servers/mcp.d.ts.map +1 -0
  276. package/dist/servers/socket.d.ts +121 -0
  277. package/dist/servers/socket.d.ts.map +1 -0
  278. package/dist/state.d.ts +24 -0
  279. package/dist/state.d.ts.map +1 -0
  280. package/dist/web/clients/socket.d.ts +37 -0
  281. package/dist/web/clients/socket.d.ts.map +1 -0
  282. package/dist/web/container.d.ts +55 -0
  283. package/dist/web/container.d.ts.map +1 -0
  284. package/dist/web/extension.d.ts +4 -0
  285. package/dist/web/extension.d.ts.map +1 -0
  286. package/dist/web/feature.d.ts +8 -0
  287. package/dist/web/feature.d.ts.map +1 -0
  288. package/dist/web/features/asset-loader.d.ts +35 -0
  289. package/dist/web/features/asset-loader.d.ts.map +1 -0
  290. package/dist/web/features/container-link.d.ts +167 -0
  291. package/dist/web/features/container-link.d.ts.map +1 -0
  292. package/dist/web/features/esbuild.d.ts +51 -0
  293. package/dist/web/features/esbuild.d.ts.map +1 -0
  294. package/dist/web/features/helpers.d.ts +140 -0
  295. package/dist/web/features/helpers.d.ts.map +1 -0
  296. package/dist/web/features/network.d.ts +69 -0
  297. package/dist/web/features/network.d.ts.map +1 -0
  298. package/dist/web/features/speech.d.ts +71 -0
  299. package/dist/web/features/speech.d.ts.map +1 -0
  300. package/dist/web/features/vault.d.ts +62 -0
  301. package/dist/web/features/vault.d.ts.map +1 -0
  302. package/dist/web/features/vm.d.ts +48 -0
  303. package/dist/web/features/vm.d.ts.map +1 -0
  304. package/dist/web/features/voice-recognition.d.ts +96 -0
  305. package/dist/web/features/voice-recognition.d.ts.map +1 -0
  306. package/dist/web/shims/isomorphic-vm.d.ts +22 -0
  307. package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
  308. package/docs/CLI.md +335 -0
  309. package/docs/CNAME +1 -0
  310. package/docs/README.md +60 -0
  311. package/docs/TABLE-OF-CONTENTS.md +183 -0
  312. package/docs/apis/clients/elevenlabs.md +308 -0
  313. package/docs/apis/clients/graph.md +107 -0
  314. package/docs/apis/clients/openai.md +429 -0
  315. package/docs/apis/clients/rest.md +161 -0
  316. package/docs/apis/clients/websocket.md +174 -0
  317. package/docs/apis/features/agi/assistant.md +625 -0
  318. package/docs/apis/features/agi/assistants-manager.md +282 -0
  319. package/docs/apis/features/agi/auto-assistant.md +279 -0
  320. package/docs/apis/features/agi/browser-use.md +802 -0
  321. package/docs/apis/features/agi/claude-code.md +884 -0
  322. package/docs/apis/features/agi/conversation-history.md +364 -0
  323. package/docs/apis/features/agi/conversation.md +548 -0
  324. package/docs/apis/features/agi/docs-reader.md +99 -0
  325. package/docs/apis/features/agi/file-tools.md +163 -0
  326. package/docs/apis/features/agi/luca-coder.md +407 -0
  327. package/docs/apis/features/agi/openai-codex.md +396 -0
  328. package/docs/apis/features/agi/openapi.md +138 -0
  329. package/docs/apis/features/agi/semantic-search.md +387 -0
  330. package/docs/apis/features/agi/skills-library.md +239 -0
  331. package/docs/apis/features/node/container-link.md +192 -0
  332. package/docs/apis/features/node/content-db.md +450 -0
  333. package/docs/apis/features/node/disk-cache.md +379 -0
  334. package/docs/apis/features/node/dns.md +652 -0
  335. package/docs/apis/features/node/docker.md +706 -0
  336. package/docs/apis/features/node/downloader.md +81 -0
  337. package/docs/apis/features/node/esbuild.md +60 -0
  338. package/docs/apis/features/node/file-manager.md +191 -0
  339. package/docs/apis/features/node/fs.md +1217 -0
  340. package/docs/apis/features/node/git.md +371 -0
  341. package/docs/apis/features/node/google-auth.md +193 -0
  342. package/docs/apis/features/node/google-calendar.md +202 -0
  343. package/docs/apis/features/node/google-docs.md +173 -0
  344. package/docs/apis/features/node/google-drive.md +246 -0
  345. package/docs/apis/features/node/google-mail.md +214 -0
  346. package/docs/apis/features/node/google-sheets.md +194 -0
  347. package/docs/apis/features/node/grep.md +292 -0
  348. package/docs/apis/features/node/helpers.md +164 -0
  349. package/docs/apis/features/node/ink.md +334 -0
  350. package/docs/apis/features/node/ipc-socket.md +249 -0
  351. package/docs/apis/features/node/json-tree.md +86 -0
  352. package/docs/apis/features/node/networking.md +316 -0
  353. package/docs/apis/features/node/nlp.md +133 -0
  354. package/docs/apis/features/node/opener.md +97 -0
  355. package/docs/apis/features/node/os.md +146 -0
  356. package/docs/apis/features/node/package-finder.md +392 -0
  357. package/docs/apis/features/node/postgres.md +234 -0
  358. package/docs/apis/features/node/proc.md +399 -0
  359. package/docs/apis/features/node/process-manager.md +305 -0
  360. package/docs/apis/features/node/python.md +604 -0
  361. package/docs/apis/features/node/redis.md +380 -0
  362. package/docs/apis/features/node/repl.md +88 -0
  363. package/docs/apis/features/node/runpod.md +674 -0
  364. package/docs/apis/features/node/secure-shell.md +176 -0
  365. package/docs/apis/features/node/semantic-search.md +408 -0
  366. package/docs/apis/features/node/sqlite.md +233 -0
  367. package/docs/apis/features/node/telegram.md +279 -0
  368. package/docs/apis/features/node/transpiler.md +74 -0
  369. package/docs/apis/features/node/tts.md +133 -0
  370. package/docs/apis/features/node/ui.md +701 -0
  371. package/docs/apis/features/node/vault.md +59 -0
  372. package/docs/apis/features/node/vm.md +75 -0
  373. package/docs/apis/features/node/yaml-tree.md +85 -0
  374. package/docs/apis/features/node/yaml.md +176 -0
  375. package/docs/apis/features/web/asset-loader.md +59 -0
  376. package/docs/apis/features/web/container-link.md +192 -0
  377. package/docs/apis/features/web/esbuild.md +54 -0
  378. package/docs/apis/features/web/helpers.md +164 -0
  379. package/docs/apis/features/web/network.md +44 -0
  380. package/docs/apis/features/web/speech.md +69 -0
  381. package/docs/apis/features/web/vault.md +59 -0
  382. package/docs/apis/features/web/vm.md +75 -0
  383. package/docs/apis/features/web/voice.md +84 -0
  384. package/docs/apis/servers/express.md +171 -0
  385. package/docs/apis/servers/mcp.md +238 -0
  386. package/docs/apis/servers/websocket.md +170 -0
  387. package/docs/bootstrap/CLAUDE.md +101 -0
  388. package/docs/bootstrap/SKILL.md +341 -0
  389. package/docs/bootstrap/templates/about-command.ts +41 -0
  390. package/docs/bootstrap/templates/docs-models.ts +22 -0
  391. package/docs/bootstrap/templates/docs-readme.md +43 -0
  392. package/docs/bootstrap/templates/example-feature.ts +53 -0
  393. package/docs/bootstrap/templates/health-endpoint.ts +15 -0
  394. package/docs/bootstrap/templates/luca-cli.ts +30 -0
  395. package/docs/bootstrap/templates/runme.md +54 -0
  396. package/docs/challenges/caching-proxy.md +16 -0
  397. package/docs/challenges/content-db-round-trip.md +14 -0
  398. package/docs/challenges/custom-command.md +9 -0
  399. package/docs/challenges/file-watcher-pipeline.md +11 -0
  400. package/docs/challenges/grep-audit-report.md +15 -0
  401. package/docs/challenges/multi-feature-dashboard.md +14 -0
  402. package/docs/challenges/process-orchestrator.md +17 -0
  403. package/docs/challenges/rest-api-server-with-client.md +12 -0
  404. package/docs/challenges/script-runner-with-vm.md +11 -0
  405. package/docs/challenges/simple-rest-api.md +15 -0
  406. package/docs/challenges/websocket-serve-and-client.md +11 -0
  407. package/docs/challenges/yaml-config-system.md +14 -0
  408. package/docs/command-system-overhaul.md +94 -0
  409. package/docs/documentation-audit.md +134 -0
  410. package/docs/examples/assistant/CORE.md +18 -0
  411. package/docs/examples/assistant/hooks.ts +3 -0
  412. package/docs/examples/assistant/tools.ts +10 -0
  413. package/docs/examples/assistant-hooks-reference.ts +171 -0
  414. package/docs/examples/assistant-with-process-manager.md +84 -0
  415. package/docs/examples/content-db.md +77 -0
  416. package/docs/examples/disk-cache.md +83 -0
  417. package/docs/examples/docker.md +101 -0
  418. package/docs/examples/downloader.md +70 -0
  419. package/docs/examples/entity.md +124 -0
  420. package/docs/examples/esbuild.md +80 -0
  421. package/docs/examples/feature-as-tool-provider.md +143 -0
  422. package/docs/examples/file-manager.md +82 -0
  423. package/docs/examples/fs.md +83 -0
  424. package/docs/examples/git.md +85 -0
  425. package/docs/examples/google-auth.md +88 -0
  426. package/docs/examples/google-calendar.md +94 -0
  427. package/docs/examples/google-docs.md +82 -0
  428. package/docs/examples/google-drive.md +96 -0
  429. package/docs/examples/google-sheets.md +95 -0
  430. package/docs/examples/grep.md +85 -0
  431. package/docs/examples/ink-blocks.md +75 -0
  432. package/docs/examples/ink-renderer.md +41 -0
  433. package/docs/examples/ink.md +103 -0
  434. package/docs/examples/ipc-socket.md +103 -0
  435. package/docs/examples/json-tree.md +91 -0
  436. package/docs/examples/networking.md +58 -0
  437. package/docs/examples/nlp.md +91 -0
  438. package/docs/examples/opener.md +78 -0
  439. package/docs/examples/os.md +72 -0
  440. package/docs/examples/package-finder.md +89 -0
  441. package/docs/examples/postgres.md +91 -0
  442. package/docs/examples/proc.md +81 -0
  443. package/docs/examples/process-manager.md +79 -0
  444. package/docs/examples/python.md +132 -0
  445. package/docs/examples/repl.md +93 -0
  446. package/docs/examples/runpod.md +119 -0
  447. package/docs/examples/secure-shell.md +92 -0
  448. package/docs/examples/sqlite.md +86 -0
  449. package/docs/examples/structured-output-with-assistants.md +144 -0
  450. package/docs/examples/telegram.md +77 -0
  451. package/docs/examples/tts.md +86 -0
  452. package/docs/examples/ui.md +80 -0
  453. package/docs/examples/vault.md +70 -0
  454. package/docs/examples/vm.md +86 -0
  455. package/docs/examples/websocket-ask-and-reply-example.md +128 -0
  456. package/docs/examples/yaml-tree.md +93 -0
  457. package/docs/examples/yaml.md +104 -0
  458. package/docs/ideas/assistant-factory-pattern.md +142 -0
  459. package/docs/in-memory-fs.md +4 -0
  460. package/docs/introspection-audit.md +49 -0
  461. package/docs/introspection.md +164 -0
  462. package/docs/mcp/readme.md +162 -0
  463. package/docs/models.ts +41 -0
  464. package/docs/philosophy.md +86 -0
  465. package/docs/principles.md +7 -0
  466. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  467. package/docs/prompts/check-for-undocumented-features.md +27 -0
  468. package/docs/prompts/mcp-test-easy-command.md +27 -0
  469. package/docs/scaffolds/client.md +149 -0
  470. package/docs/scaffolds/command.md +120 -0
  471. package/docs/scaffolds/endpoint.md +171 -0
  472. package/docs/scaffolds/feature.md +158 -0
  473. package/docs/scaffolds/selector.md +91 -0
  474. package/docs/scaffolds/server.md +196 -0
  475. package/docs/selectors.md +115 -0
  476. package/docs/sessions/custom-command/attempt-log-2.md +195 -0
  477. package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
  478. package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
  479. package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
  480. package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
  481. package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
  482. package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
  483. package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
  484. package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
  485. package/docs/tutorials/00-bootstrap.md +166 -0
  486. package/docs/tutorials/01-getting-started.md +106 -0
  487. package/docs/tutorials/02-container.md +210 -0
  488. package/docs/tutorials/03-scripts.md +194 -0
  489. package/docs/tutorials/04-features-overview.md +196 -0
  490. package/docs/tutorials/05-state-and-events.md +171 -0
  491. package/docs/tutorials/06-servers.md +157 -0
  492. package/docs/tutorials/07-endpoints.md +198 -0
  493. package/docs/tutorials/08-commands.md +252 -0
  494. package/docs/tutorials/09-clients.md +162 -0
  495. package/docs/tutorials/10-creating-features.md +203 -0
  496. package/docs/tutorials/11-contentbase.md +191 -0
  497. package/docs/tutorials/12-assistants.md +215 -0
  498. package/docs/tutorials/13-introspection.md +157 -0
  499. package/docs/tutorials/14-type-system.md +174 -0
  500. package/docs/tutorials/15-project-patterns.md +222 -0
  501. package/docs/tutorials/16-google-features.md +534 -0
  502. package/docs/tutorials/17-tui-blocks.md +530 -0
  503. package/docs/tutorials/18-semantic-search.md +334 -0
  504. package/docs/tutorials/19-python-sessions.md +401 -0
  505. package/docs/tutorials/20-browser-esm.md +234 -0
  506. package/index.html +1430 -0
  507. package/index.ts +1 -0
  508. package/install.sh +84 -0
  509. package/luca.cli.ts +16 -0
  510. package/luca.console.ts +9 -0
  511. package/main.py +6 -0
  512. package/package.json +219 -66
  513. package/public/index.html +1430 -0
  514. package/public/slides-ai-native.html +902 -0
  515. package/public/slides-intro.html +974 -0
  516. package/pyproject.toml +7 -0
  517. package/scripts/build-web.ts +28 -0
  518. package/scripts/examples/ask-luca-expert.ts +42 -0
  519. package/scripts/examples/assistant-questions.ts +12 -0
  520. package/scripts/examples/excalidraw-expert.ts +75 -0
  521. package/scripts/examples/expert-chat.ts +0 -0
  522. package/scripts/examples/file-manager.ts +14 -0
  523. package/scripts/examples/ideas.ts +12 -0
  524. package/scripts/examples/interactive-chat.ts +20 -0
  525. package/scripts/examples/openai-tool-calls.ts +113 -0
  526. package/scripts/examples/opening-a-web-browser.ts +5 -0
  527. package/scripts/examples/telegram-bot.ts +79 -0
  528. package/scripts/examples/using-assistant-with-mcp.ts +555 -0
  529. package/scripts/examples/using-claude-code.ts +10 -0
  530. package/scripts/examples/using-contentdb.ts +35 -0
  531. package/scripts/examples/using-conversations.ts +35 -0
  532. package/scripts/examples/using-disk-cache.ts +10 -0
  533. package/scripts/examples/using-docker-shell.ts +75 -0
  534. package/scripts/examples/using-elevenlabs.ts +25 -0
  535. package/scripts/examples/using-google-calendar.ts +57 -0
  536. package/scripts/examples/using-google-docs.ts +74 -0
  537. package/scripts/examples/using-google-drive.ts +74 -0
  538. package/scripts/examples/using-google-sheets.ts +89 -0
  539. package/scripts/examples/using-nlp.ts +55 -0
  540. package/scripts/examples/using-ollama.ts +11 -0
  541. package/scripts/examples/using-postgres.ts +55 -0
  542. package/scripts/examples/using-runpod.ts +32 -0
  543. package/scripts/examples/using-tts.ts +40 -0
  544. package/scripts/scaffold.ts +391 -0
  545. package/scripts/scratch.ts +15 -0
  546. package/scripts/stamp-build.sh +12 -0
  547. package/scripts/test-assistant-hooks.ts +13 -0
  548. package/scripts/test-docs-reader.ts +10 -0
  549. package/scripts/test-linux-binary.sh +80 -0
  550. package/scripts/update-introspection-data.ts +58 -0
  551. package/src/agi/README.md +14 -0
  552. package/src/agi/container.server.ts +152 -0
  553. package/src/agi/endpoints/ask.ts +60 -0
  554. package/src/agi/endpoints/conversations/[id].ts +45 -0
  555. package/src/agi/endpoints/conversations.ts +31 -0
  556. package/src/agi/endpoints/experts.ts +37 -0
  557. package/src/agi/feature.ts +13 -0
  558. package/src/agi/features/agent-memory.ts +694 -0
  559. package/src/agi/features/assistant.ts +1624 -0
  560. package/src/agi/features/assistants-manager.ts +418 -0
  561. package/src/agi/features/autonomous-assistant.ts +431 -0
  562. package/src/agi/features/browser-use.ts +653 -0
  563. package/src/agi/features/claude-code.ts +1538 -0
  564. package/src/agi/features/coding-tools.ts +175 -0
  565. package/src/agi/features/conversation-history.ts +495 -0
  566. package/src/agi/features/conversation.ts +1323 -0
  567. package/src/agi/features/docs-reader.ts +167 -0
  568. package/src/agi/features/file-tools.ts +293 -0
  569. package/src/agi/features/luca-coder.ts +639 -0
  570. package/src/agi/features/openai-codex.ts +651 -0
  571. package/src/agi/features/openapi.ts +445 -0
  572. package/src/agi/features/skills-library.ts +478 -0
  573. package/src/agi/index.ts +6 -0
  574. package/src/agi/lib/interceptor-chain.ts +89 -0
  575. package/src/agi/lib/token-counter.ts +122 -0
  576. package/src/bootstrap/generated.ts +9792 -0
  577. package/src/browser.ts +25 -0
  578. package/src/bus.ts +122 -0
  579. package/src/cli/build-info.ts +4 -0
  580. package/src/cli/cli.ts +355 -0
  581. package/src/client.ts +170 -0
  582. package/src/clients/civitai/index.ts +537 -0
  583. package/src/clients/client-template.ts +41 -0
  584. package/src/clients/comfyui/index.ts +604 -0
  585. package/src/clients/elevenlabs/index.ts +317 -0
  586. package/src/clients/graph.ts +87 -0
  587. package/src/clients/openai/index.ts +456 -0
  588. package/src/clients/rest.ts +207 -0
  589. package/src/clients/supabase/index.ts +357 -0
  590. package/src/clients/voicebox/index.ts +300 -0
  591. package/src/clients/websocket.ts +251 -0
  592. package/src/command.ts +505 -0
  593. package/src/commands/bootstrap.ts +244 -0
  594. package/src/commands/chat.ts +308 -0
  595. package/src/commands/code.ts +371 -0
  596. package/src/commands/console.ts +189 -0
  597. package/src/commands/describe.ts +243 -0
  598. package/src/commands/eval.ts +121 -0
  599. package/src/commands/help.ts +240 -0
  600. package/src/commands/index.ts +19 -0
  601. package/src/commands/introspect.ts +218 -0
  602. package/src/commands/mcp.ts +64 -0
  603. package/src/commands/prompt.ts +982 -0
  604. package/src/commands/run.ts +278 -0
  605. package/src/commands/sandbox-mcp.ts +343 -0
  606. package/src/commands/save-api-docs.ts +51 -0
  607. package/src/commands/scaffold.ts +225 -0
  608. package/src/commands/select.ts +99 -0
  609. package/src/commands/serve.ts +208 -0
  610. package/src/container-describer.ts +1084 -0
  611. package/src/container.ts +1186 -0
  612. package/src/endpoint.ts +365 -0
  613. package/src/entity.ts +173 -0
  614. package/src/feature.ts +118 -0
  615. package/src/graft.ts +181 -0
  616. package/src/hash-object.ts +97 -0
  617. package/src/helper.ts +849 -0
  618. package/src/introspection/generated.agi.ts +40208 -0
  619. package/src/introspection/generated.node.ts +28686 -0
  620. package/src/introspection/generated.web.ts +2251 -0
  621. package/src/introspection/index.ts +296 -0
  622. package/src/introspection/scan.ts +1131 -0
  623. package/src/node/container.ts +409 -0
  624. package/src/node/feature.ts +13 -0
  625. package/src/node/features/container-link.ts +559 -0
  626. package/src/node/features/content-db.ts +812 -0
  627. package/src/node/features/disk-cache.ts +388 -0
  628. package/src/node/features/dns.ts +669 -0
  629. package/src/node/features/docker.ts +921 -0
  630. package/src/node/features/downloader.ts +79 -0
  631. package/src/node/features/figlet-fonts.ts +600 -0
  632. package/src/node/features/file-manager.ts +535 -0
  633. package/src/node/features/fs.ts +1050 -0
  634. package/src/node/features/git.ts +592 -0
  635. package/src/node/features/google-auth.ts +504 -0
  636. package/src/node/features/google-calendar.ts +306 -0
  637. package/src/node/features/google-docs.ts +412 -0
  638. package/src/node/features/google-drive.ts +346 -0
  639. package/src/node/features/google-mail.ts +540 -0
  640. package/src/node/features/google-sheets.ts +286 -0
  641. package/src/node/features/grep.ts +427 -0
  642. package/src/node/features/helpers.ts +735 -0
  643. package/src/node/features/ink.ts +490 -0
  644. package/src/node/features/ipc-socket.ts +649 -0
  645. package/src/node/features/json-tree.ts +170 -0
  646. package/src/node/features/networking.ts +961 -0
  647. package/src/node/features/nlp.ts +212 -0
  648. package/src/node/features/opener.ts +180 -0
  649. package/src/node/features/os.ts +403 -0
  650. package/src/node/features/package-finder.ts +540 -0
  651. package/src/node/features/postgres.ts +289 -0
  652. package/src/node/features/proc.ts +503 -0
  653. package/src/node/features/process-manager.ts +844 -0
  654. package/src/node/features/python.ts +906 -0
  655. package/src/node/features/redis.ts +446 -0
  656. package/src/node/features/repl.ts +212 -0
  657. package/src/node/features/runpod.ts +811 -0
  658. package/src/node/features/secure-shell.ts +267 -0
  659. package/src/node/features/semantic-search.ts +935 -0
  660. package/src/node/features/sqlite.ts +289 -0
  661. package/src/node/features/telegram.ts +343 -0
  662. package/src/node/features/transpiler.ts +161 -0
  663. package/src/node/features/tts.ts +185 -0
  664. package/src/node/features/ui.ts +786 -0
  665. package/src/node/features/vault.ts +153 -0
  666. package/src/node/features/vm.ts +462 -0
  667. package/src/node/features/yaml-tree.ts +148 -0
  668. package/src/node/features/yaml.ts +133 -0
  669. package/src/node.ts +76 -0
  670. package/src/python/bridge.py +220 -0
  671. package/src/python/generated.ts +227 -0
  672. package/src/react/index.ts +175 -0
  673. package/src/registry.ts +210 -0
  674. package/src/scaffolds/generated.ts +1815 -0
  675. package/src/scaffolds/template.ts +46 -0
  676. package/src/schemas/base.ts +296 -0
  677. package/src/selector.ts +352 -0
  678. package/src/server.ts +229 -0
  679. package/src/servers/express.ts +283 -0
  680. package/src/servers/mcp.ts +802 -0
  681. package/src/servers/socket.ts +258 -0
  682. package/src/state.ts +101 -0
  683. package/src/web/clients/socket.ts +99 -0
  684. package/src/web/container.ts +75 -0
  685. package/src/web/extension.ts +30 -0
  686. package/src/web/feature.ts +12 -0
  687. package/src/web/features/asset-loader.ts +72 -0
  688. package/src/web/features/container-link.ts +382 -0
  689. package/src/web/features/esbuild.ts +93 -0
  690. package/src/web/features/helpers.ts +269 -0
  691. package/src/web/features/network.ts +85 -0
  692. package/src/web/features/speech.ts +104 -0
  693. package/src/web/features/vault.ts +207 -0
  694. package/src/web/features/vm.ts +85 -0
  695. package/src/web/features/voice-recognition.ts +161 -0
  696. package/src/web/shims/isomorphic-vm.ts +149 -0
  697. package/test/assistant-hooks.test.ts +306 -0
  698. package/test/assistant.test.ts +81 -0
  699. package/test/bus.test.ts +134 -0
  700. package/test/clients-servers.test.ts +217 -0
  701. package/test/command.test.ts +267 -0
  702. package/test/container-link.test.ts +274 -0
  703. package/test/conversation.test.ts +220 -0
  704. package/test/features.test.ts +160 -0
  705. package/test/fork-and-research.test.ts +450 -0
  706. package/test/integration.test.ts +787 -0
  707. package/test/interceptor-chain.test.ts +61 -0
  708. package/test/node-container.test.ts +121 -0
  709. package/test/python-session.test.ts +105 -0
  710. package/test/rate-limit.test.ts +272 -0
  711. package/test/semantic-search.test.ts +550 -0
  712. package/test/state.test.ts +121 -0
  713. package/test/vm-context.test.ts +146 -0
  714. package/test/vm-loadmodule.test.ts +213 -0
  715. package/test/websocket-ask.test.ts +101 -0
  716. package/test-integration/assistant.test.ts +138 -0
  717. package/test-integration/assistants-manager.test.ts +113 -0
  718. package/test-integration/claude-code.test.ts +98 -0
  719. package/test-integration/conversation-history.test.ts +205 -0
  720. package/test-integration/conversation.test.ts +137 -0
  721. package/test-integration/elevenlabs.test.ts +55 -0
  722. package/test-integration/google-services.test.ts +80 -0
  723. package/test-integration/helpers.ts +89 -0
  724. package/test-integration/memory.test.ts +204 -0
  725. package/test-integration/openai-codex.test.ts +93 -0
  726. package/test-integration/runpod.test.ts +58 -0
  727. package/test-integration/server-endpoints.test.ts +97 -0
  728. package/test-integration/telegram.test.ts +46 -0
  729. package/tsconfig.build.json +12 -0
  730. package/tsconfig.json +58 -0
  731. package/uv.lock +8 -0
  732. package/LICENSE +0 -21
  733. package/dist/cli/index.d.ts +0 -2
  734. package/dist/cli/index.js +0 -5
  735. package/dist/cli/run.d.ts +0 -12
  736. package/dist/cli/run.js +0 -42
  737. package/dist/config/consts.d.ts +0 -2
  738. package/dist/config/consts.js +0 -29
  739. package/dist/config/default.d.ts +0 -8
  740. package/dist/config/default.js +0 -15
  741. package/dist/config/initConfig.d.ts +0 -1
  742. package/dist/config/initConfig.js +0 -52
  743. package/dist/config/openConfig.d.ts +0 -2
  744. package/dist/config/openConfig.js +0 -24
  745. package/dist/config/runConfig.d.ts +0 -3
  746. package/dist/config/runConfig.js +0 -117
  747. package/dist/config/types.d.ts +0 -13
  748. package/dist/config/types.js +0 -2
  749. package/dist/index.d.ts +0 -1
  750. package/dist/index.js +0 -5
  751. package/dist/utils/common.d.ts +0 -2
  752. package/dist/utils/common.js +0 -52
  753. package/dist/utils/index.d.ts +0 -1
  754. package/dist/utils/index.js +0 -17
@@ -0,0 +1,1538 @@
1
+ // @ts-nocheck
2
+ import { z } from 'zod'
3
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
4
+ import { type AvailableFeatures } from '@soederpop/luca/feature'
5
+ import { Feature } from '../feature.js'
6
+
7
+ declare module '@soederpop/luca/feature' {
8
+ interface AvailableFeatures {
9
+ claudeCode: typeof ClaudeCode
10
+ }
11
+ }
12
+
13
+ // --- Stream JSON types from the Claude CLI ---
14
+
15
+ export interface ClaudeInitEvent {
16
+ type: 'system'
17
+ subtype: 'init'
18
+ session_id: string
19
+ cwd: string
20
+ model: string
21
+ tools: string[]
22
+ mcp_servers: string[]
23
+ permissionMode: string
24
+ claude_code_version: string
25
+ }
26
+
27
+ export interface ClaudeAssistantMessage {
28
+ type: 'assistant'
29
+ message: {
30
+ id: string
31
+ model: string
32
+ role: 'assistant'
33
+ content: Array<{ type: 'text'; text: string } | { type: 'tool_use'; id: string; name: string; input: any }>
34
+ stop_reason: string | null
35
+ usage: {
36
+ input_tokens: number
37
+ output_tokens: number
38
+ cache_read_input_tokens?: number
39
+ cache_creation_input_tokens?: number
40
+ }
41
+ }
42
+ session_id: string
43
+ parent_tool_use_id: string | null
44
+ }
45
+
46
+ export interface ClaudeToolResult {
47
+ type: 'tool_result'
48
+ tool_use_id: string
49
+ content: string
50
+ session_id: string
51
+ }
52
+
53
+ export interface ClaudeStreamEvent {
54
+ type: 'stream_event'
55
+ event: {
56
+ type: 'message_start' | 'content_block_start' | 'content_block_delta' | 'content_block_stop' | 'message_delta' | 'message_stop'
57
+ index?: number
58
+ delta?: { type: string; text?: string }
59
+ content_block?: { type: string; text?: string }
60
+ message?: any
61
+ usage?: any
62
+ }
63
+ session_id: string
64
+ parent_tool_use_id: string | null
65
+ }
66
+
67
+ export interface ClaudeResultEvent {
68
+ type: 'result'
69
+ subtype: 'success' | 'error'
70
+ is_error: boolean
71
+ result: string
72
+ session_id: string
73
+ duration_ms: number
74
+ num_turns: number
75
+ total_cost_usd: number
76
+ usage: Record<string, any>
77
+ }
78
+
79
+ export type ClaudeEvent = ClaudeInitEvent | ClaudeAssistantMessage | ClaudeToolResult | ClaudeStreamEvent | ClaudeResultEvent | { type: string; [key: string]: any }
80
+
81
+ // --- Session types ---
82
+
83
+ export interface ClaudeSession {
84
+ id: string
85
+ sessionId?: string
86
+ status: 'idle' | 'running' | 'completed' | 'error'
87
+ prompt: string
88
+ result?: string
89
+ error?: string
90
+ costUsd: number
91
+ turns: number
92
+ messages: ClaudeAssistantMessage[]
93
+ process?: any
94
+ }
95
+
96
+ // --- MCP server config types ---
97
+
98
+ export interface McpStdioServer {
99
+ type: 'stdio'
100
+ command: string
101
+ args?: string[]
102
+ env?: Record<string, string>
103
+ }
104
+
105
+ export interface McpHttpServer {
106
+ type: 'http'
107
+ url: string
108
+ headers?: Record<string, string>
109
+ }
110
+
111
+ export interface McpSseServer {
112
+ type: 'sse'
113
+ url: string
114
+ headers?: Record<string, string>
115
+ }
116
+
117
+ export type McpServerConfig = McpStdioServer | McpHttpServer | McpSseServer
118
+
119
+ // --- Feature state and options ---
120
+
121
+ export const ClaudeCodeStateSchema = FeatureStateSchema.extend({
122
+ /** Map of session IDs to ClaudeSession objects */
123
+ sessions: z.record(z.string(), z.any()).describe('Map of session IDs to ClaudeSession objects'),
124
+ /** List of currently running session IDs */
125
+ activeSessions: z.array(z.string()).describe('List of currently running session IDs'),
126
+ /** Whether the Claude CLI binary is available */
127
+ claudeAvailable: z.boolean().describe('Whether the Claude CLI binary is available'),
128
+ /** Detected Claude CLI version string */
129
+ claudeVersion: z.string().optional().describe('Detected Claude CLI version string'),
130
+ })
131
+
132
+ export const FileLogLevelSchema = z.enum(['verbose', 'normal', 'minimal']).describe(
133
+ 'Log verbosity: verbose=all events including stream deltas, normal=messages+tool calls+results, minimal=init+result/error only'
134
+ )
135
+
136
+ export type FileLogLevel = z.infer<typeof FileLogLevelSchema>
137
+
138
+ export const ClaudeCodeOptionsSchema = FeatureOptionsSchema.extend({
139
+ /** Claude CLI session ID to resume by default. When set, subsequent run()/start() calls will resume this session unless overridden. */
140
+ session: z.string().optional().describe('Claude CLI session ID to resume by default'),
141
+ /** Path to the claude CLI binary. Defaults to 'claude'. */
142
+ claudePath: z.string().optional().describe('Path to the claude CLI binary'),
143
+ /** Default model to use for sessions. */
144
+ model: z.string().optional().describe('Default model to use for sessions'),
145
+ /** Default working directory for sessions. */
146
+ cwd: z.string().optional().describe('Default working directory for sessions'),
147
+ /** Default system prompt prepended to all sessions. */
148
+ systemPrompt: z.string().optional().describe('Default system prompt prepended to all sessions'),
149
+ /** Default append system prompt for all sessions. */
150
+ appendSystemPrompt: z.string().optional().describe('Default append system prompt for all sessions'),
151
+ /** Default permission mode. */
152
+ permissionMode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan', 'dontAsk']).optional().describe('Default permission mode for Claude CLI sessions'),
153
+ /** Default allowed tools. */
154
+ allowedTools: z.array(z.string()).optional().describe('Default allowed tools for sessions'),
155
+ /** Default disallowed tools. */
156
+ disallowedTools: z.array(z.string()).optional().describe('Default disallowed tools for sessions'),
157
+ /** Whether to stream partial messages (token-by-token). Defaults to false. */
158
+ streaming: z.boolean().optional().describe('Whether to stream partial messages token-by-token'),
159
+ /** MCP config file paths to pass to sessions. */
160
+ mcpConfig: z.array(z.string()).optional().describe('MCP config file paths to pass to sessions'),
161
+ /** MCP servers to inject into sessions, keyed by server name. Automatically written to a temp config file. */
162
+ mcpServers: z.record(z.string(), z.any()).optional().describe('MCP server configs keyed by name, injected into sessions via temp config file'),
163
+ /** Path to write a parseable NDJSON session log file. Each line is a JSON object with timestamp, sessionId, event type, and event data. */
164
+ fileLogPath: z.string().optional().describe('Path to write a parseable NDJSON session log file'),
165
+ /** Verbosity level for file logging. Defaults to "normal". */
166
+ fileLogLevel: FileLogLevelSchema.optional().describe('Verbosity level for file logging. Defaults to "normal"'),
167
+ /** Default effort level for Claude reasoning. */
168
+ effort: z.enum(['low', 'medium', 'high']).optional().describe('Default effort level for Claude reasoning'),
169
+ /** Maximum cost budget in USD per session. */
170
+ maxBudgetUsd: z.number().optional().describe('Maximum cost budget in USD per session'),
171
+ /** Fallback model when the primary model is unavailable. */
172
+ fallbackModel: z.string().optional().describe('Fallback model when the primary model is unavailable'),
173
+ /** Default agent to use. */
174
+ agent: z.string().optional().describe('Default agent to use'),
175
+ /** Disable session persistence across runs. */
176
+ noSessionPersistence: z.boolean().optional().describe('Disable session persistence across runs'),
177
+ /** Default tools to make available. */
178
+ tools: z.array(z.string()).optional().describe('Default tools to make available'),
179
+ /** Require strict MCP config validation. */
180
+ strictMcpConfig: z.boolean().optional().describe('Require strict MCP config validation'),
181
+ /** Path to a custom settings file. */
182
+ settingsFile: z.string().optional().describe('Path to a custom settings file'),
183
+ /** Directories containing Claude Code skills (SKILL.md files) to load into sessions. Passed as --add-dir. */
184
+ skillsFolders: z.array(z.string()).optional().describe('Directories containing Claude Code skills to load into sessions'),
185
+ /** Launch Claude Code with a Chrome browser tool. */
186
+ chrome: z.boolean().optional().describe('Launch Claude Code with a Chrome browser tool'),
187
+ })
188
+
189
+ export const ClaudeCodeEventsSchema = FeatureEventsSchema.extend({
190
+ 'session:start': z.tuple([z.object({ sessionId: z.string(), prompt: z.string() })]).describe('Fired when a new Claude Code session is spawned'),
191
+ 'session:init': z.tuple([z.object({ sessionId: z.string(), init: z.any() })]).describe('Fired when the CLI emits its init system event'),
192
+ 'session:event': z.tuple([z.object({ sessionId: z.string(), event: z.any() })]).describe('Fired for every parsed JSON event from the CLI stream'),
193
+ 'session:stream': z.tuple([z.object({ sessionId: z.string(), streamEvent: z.any() })]).describe('Fired for stream_event type events from the CLI'),
194
+ 'session:delta': z.tuple([z.object({ sessionId: z.string(), text: z.string(), role: z.string() })]).describe('Fired for each text delta from an assistant message'),
195
+ 'session:message': z.tuple([z.object({ sessionId: z.string(), message: z.any() })]).describe('Fired when a complete assistant message is received'),
196
+ 'session:result': z.tuple([z.object({ sessionId: z.string(), result: z.string() })]).describe('Fired when a session completes with a final result'),
197
+ 'session:error': z.tuple([z.object({ sessionId: z.string(), error: z.any(), exitCode: z.number().optional() })]).describe('Fired when a session encounters an error'),
198
+ 'session:abort': z.tuple([z.object({ sessionId: z.string() })]).describe('Fired when a session is aborted by the user'),
199
+ 'session:warning': z.tuple([z.object({ sessionId: z.string(), message: z.string() })]).describe('Fired when the log reader encounters a warning'),
200
+ 'session:log-error': z.tuple([z.object({ sessionId: z.string(), error: z.any() })]).describe('Fired when the log reader encounters an error'),
201
+ 'session:parse-error': z.tuple([z.object({ sessionId: z.string(), line: z.string() })]).describe('Fired when a JSON line from the CLI cannot be parsed'),
202
+ }).describe('ClaudeCode events')
203
+
204
+ export type ClaudeCodeState = z.infer<typeof ClaudeCodeStateSchema>
205
+ export type ClaudeCodeOptions = z.infer<typeof ClaudeCodeOptionsSchema>
206
+
207
+ export interface RunOptions {
208
+ /** Override model for this session. */
209
+ model?: string
210
+ /** Override working directory. */
211
+ cwd?: string
212
+ /** System prompt for this session. */
213
+ systemPrompt?: string
214
+ /** Append system prompt for this session. */
215
+ appendSystemPrompt?: string
216
+ /** Permission mode override. */
217
+ permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'dontAsk'
218
+ /** Allowed tools override. */
219
+ allowedTools?: string[]
220
+ /** Disallowed tools override. */
221
+ disallowedTools?: string[]
222
+ /** Whether to stream partial messages. */
223
+ streaming?: boolean
224
+ /** Resume a previous session by ID. */
225
+ resumeSessionId?: string
226
+ /** Continue the most recent conversation. */
227
+ continue?: boolean
228
+ /** Additional directories to allow tool access to. */
229
+ addDirs?: string[]
230
+ /** Directories containing Claude Code skills (SKILL.md files) to load into sessions. Merged with addDirs as --add-dir. */
231
+ skillsFolders?: string[]
232
+ /** MCP config file paths. */
233
+ mcpConfig?: string[]
234
+ /** MCP servers to inject, keyed by server name. */
235
+ mcpServers?: Record<string, McpServerConfig>
236
+ /** Skip all permission checks (only for sandboxed environments). */
237
+ dangerouslySkipPermissions?: boolean
238
+ /** Additional arbitrary CLI flags. */
239
+ extraArgs?: string[]
240
+ /** Path to write a parseable NDJSON session log file. Overrides feature-level fileLogPath. */
241
+ fileLogPath?: string
242
+ /** Verbosity level for file logging. Overrides feature-level fileLogLevel. */
243
+ fileLogLevel?: FileLogLevel
244
+ /** Effort level for Claude reasoning. */
245
+ effort?: 'low' | 'medium' | 'high'
246
+ /** Maximum cost budget in USD. */
247
+ maxBudgetUsd?: number
248
+ /** Fallback model when the primary is unavailable. */
249
+ fallbackModel?: string
250
+ /** JSON schema for structured output validation. */
251
+ jsonSchema?: string | object
252
+ /** Agent to use for this session. */
253
+ agent?: string
254
+ /** Resume or fork a specific Claude session by ID. */
255
+ sessionId?: string
256
+ /** Disable session persistence for this run. */
257
+ noSessionPersistence?: boolean
258
+ /** Fork from an existing session instead of resuming. */
259
+ forkSession?: boolean
260
+ /** Tools to make available. */
261
+ tools?: string[]
262
+ /** Require strict MCP config validation. */
263
+ strictMcpConfig?: boolean
264
+ /** Enable debug output. Pass a string for specific debug channels, or true for all. */
265
+ debug?: string | boolean
266
+ /** Path to write debug output to a file. */
267
+ debugFile?: string
268
+ /** Path to a custom settings file. */
269
+ settingsFile?: string
270
+ /** Launch Claude Code with a Chrome browser tool. */
271
+ chrome?: boolean
272
+ }
273
+
274
+ /**
275
+ * Claude Code CLI wrapper feature. Spawns and manages Claude Code sessions
276
+ * as subprocesses, streaming structured JSON events back through the
277
+ * container's event system.
278
+ *
279
+ * Sessions are long-lived: each call to `run()` spawns a `claude -p` process
280
+ * with `--output-format stream-json`, parses NDJSON from stdout line-by-line,
281
+ * and emits typed events on the feature's event bus.
282
+ *
283
+ * @extends Feature
284
+ *
285
+ * @example
286
+ * ```typescript
287
+ * const cc = container.feature('claudeCode')
288
+ *
289
+ * // Listen for events
290
+ * cc.on('session:delta', ({ sessionId, text }) => process.stdout.write(text))
291
+ * cc.on('session:result', ({ sessionId, result }) => console.log('Done:', result))
292
+ *
293
+ * // Run a prompt
294
+ * const session = await cc.run('Explain the architecture of this project')
295
+ * console.log(session.result)
296
+ * ```
297
+ */
298
+ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
299
+ static override stateSchema = ClaudeCodeStateSchema
300
+ static override optionsSchema = ClaudeCodeOptionsSchema
301
+ static override eventsSchema = ClaudeCodeEventsSchema
302
+ static override shortcut = 'features.claudeCode' as const
303
+ static override envVars = ['TMPDIR']
304
+
305
+ static { Feature.register(this, 'claudeCode') }
306
+
307
+ override get initialState(): ClaudeCodeState {
308
+ return {
309
+ ...super.initialState,
310
+ sessions: {},
311
+ activeSessions: [],
312
+ claudeAvailable: false
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Resolve the path to the claude CLI binary.
318
+ *
319
+ * @returns {string} The path to the claude binary
320
+ */
321
+ private _resolvedClaudePath: string | null = null
322
+
323
+ get claudePath(): string {
324
+ if (this.options.claudePath) return this.options.claudePath
325
+ if (this._resolvedClaudePath) return this._resolvedClaudePath
326
+ try {
327
+ this._resolvedClaudePath = this.container.feature('proc').resolveRealPath('claude')
328
+ } catch {
329
+ this._resolvedClaudePath = 'claude'
330
+ }
331
+ return this._resolvedClaudePath
332
+ }
333
+
334
+ /**
335
+ * Parsed semver components from the detected CLI version, or undefined if not yet checked.
336
+ *
337
+ * @returns {{ major: number; minor: number; patch: number } | undefined} Parsed version
338
+ */
339
+ get parsedVersion(): { major: number; minor: number; patch: number } | undefined {
340
+ const ver = this.state.current.claudeVersion
341
+ if (!ver) return undefined
342
+ const match = ver.match(/^(\d+)\.(\d+)\.(\d+)/)
343
+ if (!match) return undefined
344
+ return { major: parseInt(match[1], 10), minor: parseInt(match[2], 10), patch: parseInt(match[3], 10) }
345
+ }
346
+
347
+ /**
348
+ * Assert that the detected CLI version meets a minimum major.minor requirement.
349
+ * Throws if the CLI version is below the specified minimum.
350
+ *
351
+ * @param {number} major - Minimum major version
352
+ * @param {number} minor - Minimum minor version
353
+ */
354
+ assertMinVersion(major: number, minor: number): void {
355
+ const v = this.parsedVersion
356
+ if (!v) throw new Error('Claude CLI version not detected. Call checkAvailability() first.')
357
+ if (v.major < major || (v.major === major && v.minor < minor)) {
358
+ throw new Error(`Claude CLI ${this.state.current.claudeVersion} is below minimum ${major}.${minor}`)
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Check if the Claude CLI is available and capture its version.
364
+ *
365
+ * @returns {Promise<boolean>} Whether the CLI is available
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * const available = await cc.checkAvailability()
370
+ * if (!available) throw new Error('Claude CLI not found')
371
+ * ```
372
+ */
373
+ async checkAvailability(): Promise<boolean> {
374
+ try {
375
+ const proc = this.container.feature('proc')
376
+ const result = await proc.spawnAndCapture(this.claudePath, ['--version'])
377
+ const stdout = result.stdout
378
+ const exitCode = result.exitCode
379
+
380
+ if (exitCode === 0) {
381
+ const version = stdout.trim()
382
+ this.setState({ claudeAvailable: true, claudeVersion: version })
383
+
384
+ const v = this.parsedVersion
385
+ if (v && (v.major < 2 || (v.major === 2 && v.minor < 1))) {
386
+ this.emit('session:warning', {
387
+ message: `Claude CLI ${version} is below minimum 2.1. Some features may not work.`
388
+ })
389
+ }
390
+
391
+ return true
392
+ }
393
+
394
+ this.setState({ claudeAvailable: false })
395
+ return false
396
+ } catch {
397
+ this.setState({ claudeAvailable: false })
398
+ return false
399
+ }
400
+ }
401
+
402
+ /** Tracks temp MCP config files created for cleanup */
403
+ private mcpTempFiles: string[] = []
404
+
405
+ /** Tracks active file log paths per session */
406
+ private sessionLogPaths: Map<string, { path: string; level: FileLogLevel }> = new Map()
407
+
408
+ /**
409
+ * Resolve the file log path for a session, checking per-session options then feature-level defaults.
410
+ *
411
+ * @param {RunOptions} options - Per-session options
412
+ * @returns {{ path: string; level: FileLogLevel } | undefined} Log config if logging is enabled
413
+ */
414
+ private resolveFileLog(options: RunOptions = {}): { path: string; level: FileLogLevel } | undefined {
415
+ const path = options.fileLogPath ?? this.options.fileLogPath
416
+ if (!path) return undefined
417
+ const level = options.fileLogLevel ?? this.options.fileLogLevel ?? 'normal'
418
+ return { path, level }
419
+ }
420
+
421
+ /**
422
+ * Write a log entry to the session's NDJSON log file.
423
+ * Each line is a self-contained JSON object with timestamp, sessionId, event type, and data.
424
+ *
425
+ * @param {string} sessionId - The local session ID
426
+ * @param {string} type - Event type label (e.g. 'session:init', 'session:message')
427
+ * @param {any} data - Event payload
428
+ */
429
+ private async writeLogEntry(sessionId: string, type: string, data: any): Promise<void> {
430
+ const logConfig = this.sessionLogPaths.get(sessionId)
431
+ if (!logConfig) return
432
+
433
+ const entry = JSON.stringify({
434
+ ts: new Date().toISOString(),
435
+ session: sessionId,
436
+ type,
437
+ data
438
+ }) + '\n'
439
+
440
+ try {
441
+ await this.container.feature('fs').appendFileAsync(logConfig.path, entry)
442
+ } catch (err) {
443
+ this.emit('session:log-error', { sessionId, error: err })
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Determine if an event should be logged based on the configured log level.
449
+ *
450
+ * - verbose: all events (stream deltas, partial messages, everything)
451
+ * - normal: assistant messages, tool results, init, result, errors (no stream_event)
452
+ * - minimal: init and result/error only
453
+ *
454
+ * @param {string} eventType - The Claude event type
455
+ * @param {FileLogLevel} level - The configured log level
456
+ * @returns {boolean} Whether to log this event
457
+ */
458
+ private shouldLog(eventType: string, level: FileLogLevel): boolean {
459
+ switch (level) {
460
+ case 'verbose':
461
+ return true
462
+ case 'normal':
463
+ return eventType !== 'stream_event'
464
+ case 'minimal':
465
+ return eventType === 'system' || eventType === 'result'
466
+ default:
467
+ return true
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Write an MCP server config map to a temp file suitable for `--mcp-config`.
473
+ *
474
+ * @param {Record<string, McpServerConfig>} servers - Server configs keyed by name
475
+ * @returns {Promise<string>} Path to the generated temp config file
476
+ *
477
+ * @example
478
+ * ```typescript
479
+ * const configPath = await cc.writeMcpConfig({
480
+ * 'my-api': { type: 'http', url: 'https://api.example.com/mcp' },
481
+ * 'local-tool': { type: 'stdio', command: 'bun', args: ['run', 'server.ts'] }
482
+ * })
483
+ * ```
484
+ */
485
+ async writeMcpConfig(servers: Record<string, McpServerConfig>): Promise<string> {
486
+ const config = { mcpServers: servers }
487
+ const tmpDir = process.env.TMPDIR || '/tmp'
488
+ const tmpPath = `${tmpDir}/luca-mcp-${crypto.randomUUID()}.json`
489
+ await this.container.feature('fs').writeFileAsync(tmpPath, JSON.stringify(config, null, 2))
490
+ this.mcpTempFiles.push(tmpPath)
491
+ return tmpPath
492
+ }
493
+
494
+ /**
495
+ * Build the argument array for a claude CLI invocation.
496
+ *
497
+ * @param {string} prompt - The prompt text
498
+ * @param {RunOptions} options - Session options
499
+ * @returns {Promise<string[]>} CLI arguments
500
+ */
501
+ private async buildArgs(prompt: string, options: RunOptions = {}): Promise<string[]> {
502
+ const args: string[] = ['-p', '--output-format', 'stream-json', '--verbose']
503
+
504
+ const streaming = options.streaming ?? this.options.streaming ?? false
505
+ if (streaming) {
506
+ args.push('--include-partial-messages')
507
+ }
508
+
509
+ const model = options.model ?? this.options.model
510
+ if (model) args.push('--model', model)
511
+
512
+ const systemPrompt = options.systemPrompt ?? this.options.systemPrompt
513
+ if (systemPrompt) args.push('--system-prompt', systemPrompt)
514
+
515
+ const appendSystemPrompt = options.appendSystemPrompt ?? this.options.appendSystemPrompt
516
+ if (appendSystemPrompt) args.push('--append-system-prompt', appendSystemPrompt)
517
+
518
+ const permissionMode = options.permissionMode ?? this.options.permissionMode
519
+ if (permissionMode) args.push('--permission-mode', permissionMode)
520
+
521
+ const allowedTools = options.allowedTools ?? this.options.allowedTools
522
+ if (allowedTools?.length) args.push('--allowed-tools', ...allowedTools)
523
+
524
+ const disallowedTools = options.disallowedTools ?? this.options.disallowedTools
525
+ if (disallowedTools?.length) args.push('--disallowed-tools', ...disallowedTools)
526
+
527
+ // Collect all --mcp-config paths
528
+ const configPaths: string[] = []
529
+
530
+ const mcpConfig = options.mcpConfig ?? this.options.mcpConfig
531
+ if (mcpConfig?.length) configPaths.push(...mcpConfig)
532
+
533
+ // Merge mcpServers from feature-level defaults and per-session overrides
534
+ const defaultServers = this.options.mcpServers as Record<string, McpServerConfig> | undefined
535
+ const sessionServers = options.mcpServers
536
+ const mergedServers = { ...defaultServers, ...sessionServers }
537
+
538
+ if (Object.keys(mergedServers).length > 0) {
539
+ const tmpPath = await this.writeMcpConfig(mergedServers)
540
+ configPaths.push(tmpPath)
541
+ }
542
+
543
+ if (configPaths.length) args.push('--mcp-config', ...configPaths)
544
+
545
+ const resumeSessionId = options.resumeSessionId ?? (options.sessionId ? undefined : this.options.session)
546
+ if (resumeSessionId) args.push('--resume', resumeSessionId)
547
+ if (options.continue) args.push('--continue')
548
+ if (options.dangerouslySkipPermissions) args.push('--dangerously-skip-permissions')
549
+
550
+ // Merge addDirs and skillsFolders (both feature-level and per-session) into --add-dir
551
+ const addDirs: string[] = [
552
+ ...(options.addDirs ?? []),
553
+ ...(options.skillsFolders ?? []),
554
+ ...(this.options.skillsFolders ?? []),
555
+ ]
556
+ if (addDirs.length) {
557
+ args.push('--add-dir', ...addDirs)
558
+ }
559
+
560
+ // --- New v2.1 flags ---
561
+ const effort = options.effort ?? this.options.effort
562
+ if (effort) args.push('--effort', effort)
563
+
564
+ const maxBudgetUsd = options.maxBudgetUsd ?? this.options.maxBudgetUsd
565
+ if (maxBudgetUsd != null) args.push('--max-budget-usd', String(maxBudgetUsd))
566
+
567
+ const fallbackModel = options.fallbackModel ?? this.options.fallbackModel
568
+ if (fallbackModel) args.push('--fallback-model', fallbackModel)
569
+
570
+ const agent = options.agent ?? this.options.agent
571
+ if (agent) args.push('--agent', agent)
572
+
573
+ const noSessionPersistence = options.noSessionPersistence ?? this.options.noSessionPersistence
574
+ if (noSessionPersistence) args.push('--no-session-persistence')
575
+
576
+ const tools = options.tools ?? this.options.tools
577
+ if (tools?.length) args.push('--tools', ...tools)
578
+
579
+ const strictMcpConfig = options.strictMcpConfig ?? this.options.strictMcpConfig
580
+ if (strictMcpConfig) args.push('--strict-mcp-config')
581
+
582
+ const settingsFile = options.settingsFile ?? this.options.settingsFile
583
+ if (settingsFile) args.push('--settings', settingsFile)
584
+
585
+ // Per-session only flags
586
+ if (options.jsonSchema) {
587
+ const schemaStr = typeof options.jsonSchema === 'string' ? options.jsonSchema : JSON.stringify(options.jsonSchema)
588
+ args.push('--json-schema', schemaStr)
589
+ }
590
+
591
+ if (options.sessionId) args.push('--session-id', options.sessionId)
592
+ if (options.forkSession) args.push('--fork-session')
593
+
594
+ if (options.debug != null) {
595
+ if (typeof options.debug === 'string') {
596
+ args.push('--debug', options.debug)
597
+ } else if (options.debug) {
598
+ args.push('--debug')
599
+ }
600
+ }
601
+
602
+ if (options.debugFile) args.push('--debug-file', options.debugFile)
603
+
604
+ const chrome = options.chrome ?? this.options.chrome
605
+ if (chrome) args.push('--chrome')
606
+
607
+ if (options.extraArgs?.length) {
608
+ args.push(...options.extraArgs)
609
+ }
610
+
611
+ // Prompt is piped via stdin rather than passed as a positional arg,
612
+ // to avoid content like '---' being parsed as CLI flags.
613
+ return args
614
+ }
615
+
616
+ /**
617
+ * Create a unique session ID.
618
+ *
619
+ * @returns {string} A UUID-based session ID
620
+ */
621
+ private createSessionId(): string {
622
+ return crypto.randomUUID()
623
+ }
624
+
625
+ /**
626
+ * Update a session in state.
627
+ *
628
+ * @param {string} id - The local session ID
629
+ * @param {Partial<ClaudeSession>} update - Fields to merge
630
+ */
631
+ private updateSession(id: string, update: Partial<ClaudeSession>): void {
632
+ const sessions = { ...this.state.current.sessions }
633
+ const existing = sessions[id]
634
+ if (existing) {
635
+ sessions[id] = { ...existing, ...update }
636
+ this.setState({ sessions })
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Process a parsed JSON event from the Claude CLI stream.
642
+ *
643
+ * @param {string} sessionId - The local session ID
644
+ * @param {ClaudeEvent} event - The parsed event
645
+ */
646
+ private handleEvent(sessionId: string, event: ClaudeEvent): void {
647
+ this.emit('session:event', { sessionId, event })
648
+
649
+ // File logging
650
+ const logConfig = this.sessionLogPaths.get(sessionId)
651
+ if (logConfig && this.shouldLog(event.type, logConfig.level)) {
652
+ this.writeLogEntry(sessionId, event.type, event)
653
+ }
654
+
655
+ switch (event.type) {
656
+ case 'system': {
657
+ const init = event as ClaudeInitEvent
658
+ this.updateSession(sessionId, { sessionId: init.session_id })
659
+ this.emit('session:init', { sessionId, init })
660
+ break
661
+ }
662
+
663
+ case 'stream_event': {
664
+ const streamEvent = event as ClaudeStreamEvent
665
+ if (streamEvent.event.type === 'content_block_delta' && streamEvent.event.delta?.text) {
666
+ this.emit('session:delta', {
667
+ sessionId,
668
+ text: streamEvent.event.delta.text,
669
+ parentToolUseId: streamEvent.parent_tool_use_id
670
+ })
671
+ }
672
+ this.emit('session:stream', { sessionId, streamEvent })
673
+ break
674
+ }
675
+
676
+ case 'assistant': {
677
+ const msg = event as ClaudeAssistantMessage
678
+ const session = this.state.current.sessions[sessionId]
679
+ if (session) {
680
+ this.updateSession(sessionId, {
681
+ messages: [...session.messages, msg]
682
+ })
683
+ }
684
+ this.emit('session:message', { sessionId, message: msg })
685
+ break
686
+ }
687
+
688
+ case 'result': {
689
+ const result = event as ClaudeResultEvent
690
+ this.updateSession(sessionId, {
691
+ status: result.is_error ? 'error' : 'completed',
692
+ result: result.result,
693
+ error: result.is_error ? result.result : undefined,
694
+ costUsd: result.total_cost_usd,
695
+ turns: result.num_turns
696
+ })
697
+
698
+ const activeSessions = this.state.current.activeSessions.filter(id => id !== sessionId)
699
+ this.setState({ activeSessions })
700
+
701
+ this.emit('session:result', {
702
+ sessionId,
703
+ result: result.result,
704
+ isError: result.is_error,
705
+ costUsd: result.total_cost_usd,
706
+ turns: result.num_turns,
707
+ durationMs: result.duration_ms
708
+ })
709
+ break
710
+ }
711
+ }
712
+ }
713
+
714
+ /**
715
+ * Run a prompt in a new Claude Code session. Spawns a subprocess,
716
+ * streams NDJSON events, and resolves when the session completes.
717
+ *
718
+ * @param {string} prompt - The instruction/prompt to send
719
+ * @param {RunOptions} [options] - Session configuration overrides
720
+ * @returns {Promise<ClaudeSession>} The completed session with result
721
+ *
722
+ * @example
723
+ * ```typescript
724
+ * // Simple one-shot
725
+ * const session = await cc.run('What files are in this project?')
726
+ * console.log(session.result)
727
+ *
728
+ * // With options
729
+ * const session = await cc.run('Refactor the auth module', {
730
+ * model: 'opus',
731
+ * cwd: '/path/to/project',
732
+ * permissionMode: 'acceptEdits',
733
+ * streaming: true
734
+ * })
735
+ *
736
+ * // With injected MCP servers
737
+ * const session = await cc.run('Use the database tools to list tables', {
738
+ * mcpServers: {
739
+ * 'db-tools': { type: 'stdio', command: 'bun', args: ['run', 'db-mcp.ts'] },
740
+ * 'api': { type: 'http', url: 'https://api.example.com/mcp' }
741
+ * }
742
+ * })
743
+ *
744
+ * // Resume a previous session
745
+ * const session = await cc.run('Now add tests for that', {
746
+ * resumeSessionId: previousSession.sessionId
747
+ * })
748
+ * ```
749
+ */
750
+ async run(prompt: string, options: RunOptions = {}): Promise<ClaudeSession> {
751
+ const id = this.createSessionId()
752
+ const args = await this.buildArgs(prompt, options)
753
+ const cwd = options.cwd ?? this.options.cwd ?? (this.container as any).cwd
754
+
755
+ // Set up file logging for this session
756
+ const fileLog = this.resolveFileLog(options)
757
+ if (fileLog) {
758
+ this.sessionLogPaths.set(id, fileLog)
759
+ this.writeLogEntry(id, 'session:start', { prompt, cwd, args: [this.claudePath, ...args] })
760
+ }
761
+
762
+ const session: ClaudeSession = {
763
+ id,
764
+ status: 'running',
765
+ prompt,
766
+ costUsd: 0,
767
+ turns: 0,
768
+ messages: []
769
+ }
770
+
771
+ // Register session in state
772
+ const sessions = { ...this.state.current.sessions, [id]: session }
773
+ const activeSessions = [...this.state.current.activeSessions, id]
774
+ this.setState({ sessions, activeSessions })
775
+
776
+ this.emit('session:start', { sessionId: id, prompt })
777
+
778
+ const proc = this.container.feature('proc').spawn(this.claudePath, args, {
779
+ cwd,
780
+ stdout: 'pipe',
781
+ stderr: 'pipe',
782
+ stdin: Buffer.from(prompt),
783
+ environment: { ...process.env },
784
+ })
785
+
786
+ this.updateSession(id, { process: proc })
787
+ await this.consumeStream(id, proc)
788
+
789
+ return this.state.current.sessions[id]!
790
+ }
791
+
792
+ /**
793
+ * Run a prompt without waiting for completion. Returns the session ID
794
+ * immediately so you can subscribe to events.
795
+ *
796
+ * @param {string} prompt - The instruction/prompt to send
797
+ * @param {RunOptions} [options] - Session configuration overrides
798
+ * @returns {string} The session ID to track via events
799
+ *
800
+ * @example
801
+ * ```typescript
802
+ * const sessionId = cc.start('Build a REST API for users')
803
+ *
804
+ * cc.on('session:delta', ({ sessionId: sid, text }) => {
805
+ * if (sid === sessionId) process.stdout.write(text)
806
+ * })
807
+ *
808
+ * cc.on('session:result', ({ sessionId: sid, result }) => {
809
+ * if (sid === sessionId) console.log('\nDone:', result)
810
+ * })
811
+ * ```
812
+ */
813
+ async start(prompt: string, options: RunOptions = {}): Promise<string> {
814
+ const id = this.createSessionId()
815
+ const args = await this.buildArgs(prompt, options)
816
+ const cwd = options.cwd ?? this.options.cwd ?? (this.container as any).cwd
817
+
818
+ // Set up file logging for this session
819
+ const fileLog = this.resolveFileLog(options)
820
+ if (fileLog) {
821
+ this.sessionLogPaths.set(id, fileLog)
822
+ this.writeLogEntry(id, 'session:start', { prompt, cwd, args: [this.claudePath, ...args] })
823
+ }
824
+
825
+ const session: ClaudeSession = {
826
+ id,
827
+ status: 'running',
828
+ prompt,
829
+ costUsd: 0,
830
+ turns: 0,
831
+ messages: []
832
+ }
833
+
834
+ const sessions = { ...this.state.current.sessions, [id]: session }
835
+ const activeSessions = [...this.state.current.activeSessions, id]
836
+ this.setState({ sessions, activeSessions })
837
+
838
+ this.emit('session:start', { sessionId: id, prompt })
839
+
840
+ const proc = this.container.feature('proc').spawn(this.claudePath, args, {
841
+ cwd,
842
+ stdout: 'pipe',
843
+ stderr: 'pipe',
844
+ stdin: Buffer.from(prompt),
845
+ environment: { ...process.env },
846
+ })
847
+
848
+ this.updateSession(id, { process: proc })
849
+
850
+ // Process in background
851
+ this.consumeStream(id, proc)
852
+
853
+ return id
854
+ }
855
+
856
+ /**
857
+ * Consume the stdout stream of a running process in the background.
858
+ *
859
+ * @param {string} sessionId - The local session ID
860
+ * @param {any} proc - The process handle returned by features.proc.spawn()
861
+ */
862
+ private async consumeStream(sessionId: string, proc: any): Promise<void> {
863
+ if (!proc?.stdout || !proc?.stderr) {
864
+ const error = 'Process streams are not available'
865
+ this.updateSession(sessionId, { status: 'error', error })
866
+ this.emit('session:error', { sessionId, error })
867
+ return
868
+ }
869
+
870
+ let buffer = ''
871
+ let stderr = ''
872
+
873
+ proc.stderr.on('data', (chunk: Buffer | string) => {
874
+ stderr += Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk)
875
+ })
876
+
877
+ const stdoutDone = new Promise<void>((resolve, reject) => {
878
+ proc.stdout.on('data', (chunk: Buffer | string) => {
879
+ buffer += Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk)
880
+ const lines = buffer.split('\n')
881
+ buffer = lines.pop() || ''
882
+
883
+ for (const line of lines) {
884
+ const trimmed = line.trim()
885
+ if (!trimmed) continue
886
+
887
+ try {
888
+ const event = JSON.parse(trimmed) as ClaudeEvent
889
+ this.handleEvent(sessionId, event)
890
+ } catch {
891
+ this.emit('session:parse-error', { sessionId, line: trimmed })
892
+ }
893
+ }
894
+ })
895
+
896
+ proc.stdout.on('end', () => {
897
+ if (buffer.trim()) {
898
+ try {
899
+ const event = JSON.parse(buffer.trim()) as ClaudeEvent
900
+ this.handleEvent(sessionId, event)
901
+ } catch {
902
+ // ignore trailing partial data
903
+ }
904
+ }
905
+ resolve()
906
+ })
907
+
908
+ proc.stdout.on('error', reject)
909
+ })
910
+
911
+ const exitCodePromise = new Promise<number>((resolve, reject) => {
912
+ proc.once('error', reject)
913
+ proc.once('close', (code: number | null) => resolve(code ?? 0))
914
+ })
915
+
916
+ try {
917
+ await stdoutDone
918
+ } catch (err) {
919
+ this.updateSession(sessionId, {
920
+ status: 'error',
921
+ error: err instanceof Error ? err.message : String(err)
922
+ })
923
+ this.emit('session:error', { sessionId, error: err })
924
+ }
925
+
926
+ let exitCode = 1
927
+ try {
928
+ exitCode = await exitCodePromise
929
+ } catch (err) {
930
+ this.updateSession(sessionId, {
931
+ status: 'error',
932
+ error: err instanceof Error ? err.message : String(err),
933
+ })
934
+ this.emit('session:error', { sessionId, error: err })
935
+ }
936
+
937
+ if (exitCode !== 0 && this.state.current.sessions[sessionId]?.status !== 'completed') {
938
+ this.updateSession(sessionId, {
939
+ status: 'error',
940
+ error: stderr || `Process exited with code ${exitCode}`
941
+ })
942
+ this.emit('session:error', { sessionId, error: stderr, exitCode })
943
+ }
944
+
945
+ // Finalize file log
946
+ if (this.sessionLogPaths.has(sessionId)) {
947
+ const finalSession = this.state.current.sessions[sessionId]!
948
+ await this.writeLogEntry(sessionId, 'session:end', {
949
+ status: finalSession.status,
950
+ result: finalSession.result,
951
+ error: finalSession.error,
952
+ costUsd: finalSession.costUsd,
953
+ turns: finalSession.turns,
954
+ messageCount: finalSession.messages.length
955
+ })
956
+ this.sessionLogPaths.delete(sessionId)
957
+ }
958
+ }
959
+
960
+ /**
961
+ * Kill a running session's subprocess.
962
+ *
963
+ * @param {string} sessionId - The local session ID to abort
964
+ *
965
+ * @example
966
+ * ```typescript
967
+ * const sessionId = cc.start('Do something long')
968
+ * // ... later
969
+ * cc.abort(sessionId)
970
+ * ```
971
+ */
972
+ abort(sessionId: string): void {
973
+ const session = this.state.current.sessions[sessionId]
974
+ if (session?.process && session.status === 'running') {
975
+ session.process.kill()
976
+ this.updateSession(sessionId, { status: 'error', error: 'Aborted by user' })
977
+ const activeSessions = this.state.current.activeSessions.filter(id => id !== sessionId)
978
+ this.setState({ activeSessions })
979
+ this.emit('session:abort', { sessionId })
980
+
981
+ if (this.sessionLogPaths.has(sessionId)) {
982
+ this.writeLogEntry(sessionId, 'session:abort', { reason: 'Aborted by user' })
983
+ this.sessionLogPaths.delete(sessionId)
984
+ }
985
+ }
986
+ }
987
+
988
+ /**
989
+ * Get a session by its local ID.
990
+ *
991
+ * @param {string} sessionId - The local session ID
992
+ * @returns {ClaudeSession | undefined} The session if it exists
993
+ *
994
+ * @example
995
+ * ```typescript
996
+ * const session = cc.getSession(sessionId)
997
+ * if (session?.status === 'completed') {
998
+ * console.log(session.result)
999
+ * }
1000
+ * ```
1001
+ */
1002
+ getSession(sessionId: string): ClaudeSession | undefined {
1003
+ return this.state.current.sessions[sessionId]
1004
+ }
1005
+
1006
+ /**
1007
+ * Wait for a running session to complete.
1008
+ *
1009
+ * @param {string} sessionId - The local session ID
1010
+ * @returns {Promise<ClaudeSession>} The completed session
1011
+ *
1012
+ * @example
1013
+ * ```typescript
1014
+ * const id = cc.start('Build something cool')
1015
+ * const session = await cc.waitForSession(id)
1016
+ * console.log(session.result)
1017
+ * ```
1018
+ */
1019
+ async waitForSession(sessionId: string): Promise<ClaudeSession> {
1020
+ const session = this.state.current.sessions[sessionId]
1021
+ if (!session) throw new Error(`Session ${sessionId} not found`)
1022
+ if (session.status === 'completed' || session.status === 'error') return session
1023
+
1024
+ return new Promise((resolve) => {
1025
+ const handler = (data: { sessionId: string }) => {
1026
+ if (data.sessionId === sessionId) {
1027
+ this.off('session:result')
1028
+ this.off('session:error')
1029
+ resolve(this.state.current.sessions[sessionId]!)
1030
+ }
1031
+ }
1032
+ this.on('session:result', handler)
1033
+ this.on('session:error', handler)
1034
+ })
1035
+ }
1036
+
1037
+ /**
1038
+ * Get aggregated usage statistics across all sessions, or for a specific session.
1039
+ *
1040
+ * @param {string} [sessionId] - Optional session ID to get usage for a single session
1041
+ * @returns {{ totalCostUsd: number; totalInputTokens: number; totalOutputTokens: number; totalCacheReadTokens: number; totalCacheCreationTokens: number; totalTurns: number; sessionCount: number; sessions: Array<{ id: string; costUsd: number; turns: number; inputTokens: number; outputTokens: number; status: string }> }} Usage statistics
1042
+ *
1043
+ * @example
1044
+ * ```typescript
1045
+ * const stats = cc.usage()
1046
+ * console.log(`Total cost: $${stats.totalCostUsd.toFixed(4)}`)
1047
+ * console.log(`Tokens: ${stats.totalInputTokens} in / ${stats.totalOutputTokens} out`)
1048
+ *
1049
+ * // Single session
1050
+ * const sessionStats = cc.usage(sessionId)
1051
+ * ```
1052
+ */
1053
+ usage(sessionId?: string) {
1054
+ const allSessions = this.state.current.sessions
1055
+ const entries = sessionId
1056
+ ? (allSessions[sessionId] ? [allSessions[sessionId]] : [])
1057
+ : Object.values(allSessions)
1058
+
1059
+ let totalCostUsd = 0
1060
+ let totalInputTokens = 0
1061
+ let totalOutputTokens = 0
1062
+ let totalCacheReadTokens = 0
1063
+ let totalCacheCreationTokens = 0
1064
+ let totalTurns = 0
1065
+ const sessions: Array<{ id: string; costUsd: number; turns: number; inputTokens: number; outputTokens: number; status: string }> = []
1066
+
1067
+ for (const session of entries as ClaudeSession[]) {
1068
+ let inputTokens = 0
1069
+ let outputTokens = 0
1070
+ let cacheRead = 0
1071
+ let cacheCreation = 0
1072
+
1073
+ for (const msg of session.messages || []) {
1074
+ const u = msg.message?.usage
1075
+ if (u) {
1076
+ inputTokens += u.input_tokens || 0
1077
+ outputTokens += u.output_tokens || 0
1078
+ cacheRead += u.cache_read_input_tokens || 0
1079
+ cacheCreation += u.cache_creation_input_tokens || 0
1080
+ }
1081
+ }
1082
+
1083
+ totalCostUsd += session.costUsd || 0
1084
+ totalInputTokens += inputTokens
1085
+ totalOutputTokens += outputTokens
1086
+ totalCacheReadTokens += cacheRead
1087
+ totalCacheCreationTokens += cacheCreation
1088
+ totalTurns += session.turns || 0
1089
+
1090
+ sessions.push({
1091
+ id: session.id,
1092
+ costUsd: session.costUsd || 0,
1093
+ turns: session.turns || 0,
1094
+ inputTokens,
1095
+ outputTokens,
1096
+ status: session.status,
1097
+ })
1098
+ }
1099
+
1100
+ // Budget remaining: if maxBudgetUsd is configured, compute what's left
1101
+ const maxBudgetUsd = this.options.maxBudgetUsd
1102
+ const budgetRemainingUsd = maxBudgetUsd != null ? Math.max(0, maxBudgetUsd - totalCostUsd) : undefined
1103
+ const budgetUsedPercent = maxBudgetUsd != null && maxBudgetUsd > 0 ? Math.min(100, (totalCostUsd / maxBudgetUsd) * 100) : undefined
1104
+
1105
+ return {
1106
+ totalCostUsd,
1107
+ totalInputTokens,
1108
+ totalOutputTokens,
1109
+ totalCacheReadTokens,
1110
+ totalCacheCreationTokens,
1111
+ totalTurns,
1112
+ sessionCount: sessions.length,
1113
+ maxBudgetUsd: maxBudgetUsd ?? null,
1114
+ budgetRemainingUsd: budgetRemainingUsd ?? null,
1115
+ budgetUsedPercent: budgetUsedPercent ?? null,
1116
+ sessions,
1117
+ }
1118
+ }
1119
+
1120
+ /**
1121
+ * The Claude CLI session ID of the most recently initialized session,
1122
+ * or the session set via the `session` option. Useful for resuming later.
1123
+ *
1124
+ * @returns {string | undefined} The Claude CLI session ID
1125
+ *
1126
+ * @example
1127
+ * ```typescript
1128
+ * const cc = container.feature('claudeCode')
1129
+ * await cc.run('Do something')
1130
+ * console.log(cc.sessionId) // the Claude CLI session ID
1131
+ * ```
1132
+ */
1133
+ get sessionId(): string | undefined {
1134
+ // Check if a default session was set via options
1135
+ if (this.options.session) return this.options.session
1136
+
1137
+ // Find the most recently created session that has a Claude CLI sessionId
1138
+ const sessions = Object.values(this.state.current.sessions) as ClaudeSession[]
1139
+ if (sessions.length === 0) return undefined
1140
+
1141
+ // Return the last session's Claude CLI session ID
1142
+ const last = sessions[sessions.length - 1]
1143
+ return last?.sessionId
1144
+ }
1145
+
1146
+ /**
1147
+ * Export session history as a readable markdown document.
1148
+ * Reads from a raw JSONL file (Claude CLI session log or this feature's NDJSON log)
1149
+ * so it works independently of in-memory state.
1150
+ *
1151
+ * Can also accept a local session ID to export from in-memory state as a fallback.
1152
+ *
1153
+ * @param {string} [source] - Path to a JSONL file, a local session ID, or omit for the most recent session
1154
+ * @returns {Promise<string>} Markdown-formatted session history
1155
+ *
1156
+ * @example
1157
+ * ```typescript
1158
+ * // From a JSONL file (works without any prior state)
1159
+ * const md = await cc.sessionHistoryToMarkdown('/path/to/session.jsonl')
1160
+ *
1161
+ * // From the most recent in-memory session
1162
+ * const md = await cc.sessionHistoryToMarkdown()
1163
+ *
1164
+ * // From a specific local session ID
1165
+ * const md = await cc.sessionHistoryToMarkdown(localSessionId)
1166
+ * ```
1167
+ */
1168
+ async sessionHistoryToMarkdown(source?: string): Promise<string> {
1169
+ // If source looks like a file path, read JSONL from disk
1170
+ if (source && (source.includes('/') || source.endsWith('.jsonl'))) {
1171
+ return this.jsonlToMarkdown(source)
1172
+ }
1173
+
1174
+ // Otherwise try to resolve from in-memory state
1175
+ const sessionId = source || this.findLastSessionId()
1176
+ if (!sessionId) throw new Error('No session found. Pass a JSONL file path or run a session first.')
1177
+
1178
+ const session = this.state.current.sessions[sessionId] as ClaudeSession | undefined
1179
+ if (!session) throw new Error(`Session ${sessionId} not found in state. Pass a JSONL file path instead.`)
1180
+
1181
+ return this.sessionToMarkdown(session)
1182
+ }
1183
+
1184
+ /**
1185
+ * Find the local ID of the most recent session.
1186
+ */
1187
+ private findLastSessionId(): string | undefined {
1188
+ const ids = Object.keys(this.state.current.sessions)
1189
+ return ids.length > 0 ? ids[ids.length - 1] : undefined
1190
+ }
1191
+
1192
+ /**
1193
+ * Parse a JSONL file and convert its events to markdown.
1194
+ */
1195
+ private async jsonlToMarkdown(filePath: string): Promise<string> {
1196
+ const fs = this.container.feature('fs')
1197
+ const content = await fs.readFileAsync(filePath, 'utf-8')
1198
+ const lines = content.split('\n').filter((l: string) => l.trim())
1199
+
1200
+ const events: ClaudeEvent[] = []
1201
+ for (const line of lines) {
1202
+ try {
1203
+ const parsed = JSON.parse(line)
1204
+ // Support both raw Claude events and our wrapper format (which has a .data field)
1205
+ events.push(parsed.data ?? parsed)
1206
+ } catch {
1207
+ // skip malformed lines
1208
+ }
1209
+ }
1210
+
1211
+ return this.eventsToMarkdown(events, filePath)
1212
+ }
1213
+
1214
+ /**
1215
+ * Convert a ClaudeSession (from state) to markdown.
1216
+ */
1217
+ private sessionToMarkdown(session: ClaudeSession): string {
1218
+ const lines: string[] = []
1219
+
1220
+ lines.push(`# Session: ${session.id}`)
1221
+ if (session.sessionId) lines.push(`**Claude Session ID:** \`${session.sessionId}\``)
1222
+ lines.push(`**Status:** ${session.status}`)
1223
+ if (session.costUsd) lines.push(`**Cost:** $${session.costUsd.toFixed(4)}`)
1224
+ if (session.turns) lines.push(`**Turns:** ${session.turns}`)
1225
+ lines.push('')
1226
+
1227
+ lines.push(`## Prompt`)
1228
+ lines.push('')
1229
+ lines.push(session.prompt)
1230
+ lines.push('')
1231
+
1232
+ if (session.messages.length > 0) {
1233
+ lines.push(`## Conversation`)
1234
+ lines.push('')
1235
+
1236
+ for (const msg of session.messages) {
1237
+ this.renderAssistantMessage(lines, msg)
1238
+ }
1239
+ }
1240
+
1241
+ if (session.result) {
1242
+ lines.push(`## Result`)
1243
+ lines.push('')
1244
+ lines.push(session.result)
1245
+ lines.push('')
1246
+ }
1247
+
1248
+ if (session.error) {
1249
+ lines.push(`## Error`)
1250
+ lines.push('')
1251
+ lines.push(`\`\`\`\n${session.error}\n\`\`\``)
1252
+ lines.push('')
1253
+ }
1254
+
1255
+ return lines.join('\n')
1256
+ }
1257
+
1258
+ /**
1259
+ * Convert raw Claude events to markdown.
1260
+ */
1261
+ private eventsToMarkdown(events: ClaudeEvent[], source: string): string {
1262
+ const lines: string[] = []
1263
+ let sessionId: string | undefined
1264
+ let model: string | undefined
1265
+ let prompt: string | undefined
1266
+ let costUsd: number | undefined
1267
+ let turns: number | undefined
1268
+ let durationMs: number | undefined
1269
+
1270
+ // Extract metadata from init and result events
1271
+ for (const event of events) {
1272
+ if (event.type === 'system' && (event as any).subtype === 'init') {
1273
+ const init = event as ClaudeInitEvent
1274
+ sessionId = init.session_id
1275
+ model = init.model
1276
+ }
1277
+ if (event.type === 'result') {
1278
+ const result = event as ClaudeResultEvent
1279
+ costUsd = result.total_cost_usd
1280
+ turns = result.num_turns
1281
+ durationMs = result.duration_ms
1282
+ }
1283
+ }
1284
+
1285
+ lines.push(`# Session History`)
1286
+ lines.push(`**Source:** \`${source}\``)
1287
+ if (sessionId) lines.push(`**Session ID:** \`${sessionId}\``)
1288
+ if (model) lines.push(`**Model:** ${model}`)
1289
+ if (costUsd != null) lines.push(`**Cost:** $${costUsd.toFixed(4)}`)
1290
+ if (turns != null) lines.push(`**Turns:** ${turns}`)
1291
+ if (durationMs != null) lines.push(`**Duration:** ${(durationMs / 1000).toFixed(1)}s`)
1292
+ lines.push('')
1293
+
1294
+ lines.push(`## Conversation`)
1295
+ lines.push('')
1296
+
1297
+ for (const event of events) {
1298
+ if (event.type === 'assistant') {
1299
+ this.renderAssistantMessage(lines, event as ClaudeAssistantMessage)
1300
+ } else if (event.type === 'tool_result') {
1301
+ const tr = event as ClaudeToolResult
1302
+ lines.push(`<details>`)
1303
+ lines.push(`<summary>Tool Result (${tr.tool_use_id})</summary>`)
1304
+ lines.push('')
1305
+ lines.push('```')
1306
+ lines.push(tr.content.length > 2000 ? tr.content.slice(0, 2000) + '\n... (truncated)' : tr.content)
1307
+ lines.push('```')
1308
+ lines.push(`</details>`)
1309
+ lines.push('')
1310
+ } else if (event.type === 'result') {
1311
+ const result = event as ClaudeResultEvent
1312
+ lines.push(`## Result`)
1313
+ lines.push('')
1314
+ if (result.is_error) {
1315
+ lines.push(`**Error:**`)
1316
+ lines.push(`\`\`\`\n${result.result}\n\`\`\``)
1317
+ } else {
1318
+ lines.push(result.result)
1319
+ }
1320
+ lines.push('')
1321
+ }
1322
+ }
1323
+
1324
+ return lines.join('\n')
1325
+ }
1326
+
1327
+ /**
1328
+ * Render a single assistant message to markdown lines.
1329
+ */
1330
+ private renderAssistantMessage(lines: string[], msg: ClaudeAssistantMessage): void {
1331
+ lines.push(`### Assistant`)
1332
+ if (msg.message?.usage) {
1333
+ const u = msg.message.usage
1334
+ lines.push(`*${u.input_tokens} in / ${u.output_tokens} out tokens*`)
1335
+ }
1336
+ lines.push('')
1337
+
1338
+ for (const block of msg.message?.content || []) {
1339
+ if (block.type === 'text') {
1340
+ lines.push(block.text)
1341
+ lines.push('')
1342
+ } else if (block.type === 'tool_use') {
1343
+ lines.push(`**Tool Use:** \`${block.name}\``)
1344
+ lines.push('```json')
1345
+ lines.push(JSON.stringify(block.input, null, 2))
1346
+ lines.push('```')
1347
+ lines.push('')
1348
+ }
1349
+ }
1350
+ }
1351
+
1352
+ /**
1353
+ * List all Claude Code processes currently registered in ~/.claude/sessions/.
1354
+ * Returns each session's metadata along with whether the process is still alive.
1355
+ *
1356
+ * @returns {Promise<Array<{ pid: number; sessionId: string; cwd: string; startedAt: number; kind: string; entrypoint: string; alive: boolean }>>}
1357
+ *
1358
+ * @example
1359
+ * const sessions = await cc.listProcessSessions()
1360
+ * for (const s of sessions) {
1361
+ * console.log(`[${s.alive ? 'LIVE' : 'dead'}] PID ${s.pid} in ${s.cwd}`)
1362
+ * }
1363
+ */
1364
+ async listProcessSessions(): Promise<Array<{
1365
+ pid: number
1366
+ sessionId: string
1367
+ cwd: string
1368
+ startedAt: number
1369
+ kind: string
1370
+ entrypoint: string
1371
+ alive: boolean
1372
+ }>> {
1373
+ const fs = this.container.feature('fs')
1374
+ const proc = this.container.feature('proc')
1375
+ const home = this.container.feature('os').homedir
1376
+ const sessionsDir = `${home}/.claude/sessions`
1377
+
1378
+ let files: string[]
1379
+ try {
1380
+ files = await fs.readdir(sessionsDir)
1381
+ } catch {
1382
+ return []
1383
+ }
1384
+
1385
+ const jsonFiles = files.filter((f: string) => f.endsWith('.json'))
1386
+
1387
+ const results = await Promise.all(jsonFiles.map(async (file: string) => {
1388
+ try {
1389
+ const raw = await fs.readFile(`${sessionsDir}/${file}`, 'utf8')
1390
+ const data = JSON.parse(raw)
1391
+ let alive = false
1392
+ try {
1393
+ await proc.exec(`kill -0 ${data.pid}`)
1394
+ alive = true
1395
+ } catch {
1396
+ alive = false
1397
+ }
1398
+ return { ...data, alive }
1399
+ } catch {
1400
+ return null
1401
+ }
1402
+ }))
1403
+
1404
+ return results.filter(Boolean)
1405
+ }
1406
+
1407
+ /**
1408
+ * Read a single process session by PID from ~/.claude/sessions/<pid>.json.
1409
+ *
1410
+ * @param {number} pid - The process ID
1411
+ * @returns {Promise<{ pid: number; sessionId: string; cwd: string; startedAt: number; kind: string; entrypoint: string } | null>}
1412
+ *
1413
+ * @example
1414
+ * const session = await cc.getProcessSession(12345)
1415
+ * console.log(session?.cwd)
1416
+ */
1417
+ async getProcessSession(pid: number): Promise<{
1418
+ pid: number
1419
+ sessionId: string
1420
+ cwd: string
1421
+ startedAt: number
1422
+ kind: string
1423
+ entrypoint: string
1424
+ } | null> {
1425
+ const fs = this.container.feature('fs')
1426
+ const home = this.container.feature('os').homedir
1427
+ try {
1428
+ const raw = await fs.readFile(`${home}/.claude/sessions/${pid}.json`, 'utf8')
1429
+ return JSON.parse(raw)
1430
+ } catch {
1431
+ return null
1432
+ }
1433
+ }
1434
+
1435
+ /**
1436
+ * Read the conversation history for a Claude Code session from its JSONL file in
1437
+ * ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl.
1438
+ *
1439
+ * Returns an array of parsed message objects (user, assistant, tool_use, tool_result).
1440
+ *
1441
+ * @param {string} sessionId - The Claude CLI session ID (from listProcessSessions or getProcessSession)
1442
+ * @param {string} cwd - The working directory of the session (used to locate the project folder)
1443
+ * @returns {Promise<any[]>} Array of parsed JSONL records
1444
+ *
1445
+ * @example
1446
+ * const sessions = await cc.listProcessSessions()
1447
+ * const s = sessions[0]
1448
+ * const history = await cc.getConversationHistory(s.sessionId, s.cwd)
1449
+ * console.log(history.length, 'turns')
1450
+ */
1451
+ async getConversationHistory(sessionId: string, cwd?: string): Promise<any[]> {
1452
+ const fs = this.container.feature('fs')
1453
+ const home = this.container.feature('os').homedir
1454
+ const resolvedCwd = cwd ?? this.options.cwd ?? (this.container as any).cwd
1455
+ const encodedCwd = resolvedCwd.replace(/[\\/]/g, '-').replace(/@/g, '-')
1456
+ const filePath = `${home}/.claude/projects/${encodedCwd}/${sessionId}.jsonl`
1457
+ try {
1458
+ const raw = await fs.readFile(filePath, 'utf8')
1459
+ return raw
1460
+ .split('\n')
1461
+ .filter((line: string) => line.trim())
1462
+ .map((line: string) => JSON.parse(line))
1463
+ } catch {
1464
+ return []
1465
+ }
1466
+ }
1467
+
1468
+ /**
1469
+ * List all conversation sessions stored for a given working directory.
1470
+ * Reads ~/.claude/projects/<encoded-cwd>/ and returns metadata for each .jsonl file.
1471
+ *
1472
+ * @param {string} cwd - The working directory path to look up
1473
+ * @returns {Promise<Array<{ sessionId: string; filePath: string; messageCount: number }>>}
1474
+ *
1475
+ * @example
1476
+ * const sessions = await cc.listSessionsForCwd('/Users/me/my-project')
1477
+ * for (const s of sessions) {
1478
+ * console.log(s.sessionId, s.messageCount, 'messages')
1479
+ * }
1480
+ */
1481
+ async listSessionsForCwd(cwd?: string): Promise<Array<{
1482
+ sessionId: string
1483
+ filePath: string
1484
+ messageCount: number
1485
+ }>> {
1486
+ const fs = this.container.feature('fs')
1487
+ const home = this.container.feature('os').homedir
1488
+ const resolvedCwd = cwd ?? this.options.cwd ?? (this.container as any).cwd
1489
+ const encodedCwd = resolvedCwd.replace(/[\\/]/g, '-').replace(/@/g, '-')
1490
+ const projectDir = `${home}/.claude/projects/${encodedCwd}`
1491
+
1492
+ let files: string[]
1493
+ try {
1494
+ files = await fs.readdir(projectDir)
1495
+ } catch {
1496
+ return []
1497
+ }
1498
+
1499
+ const jsonlFiles = files.filter((f: string) => f.endsWith('.jsonl'))
1500
+
1501
+ const results = await Promise.all(jsonlFiles.map(async (file: string) => {
1502
+ const sessionId = file.replace(/\.jsonl$/, '')
1503
+ const filePath = `${projectDir}/${file}`
1504
+ try {
1505
+ const raw = await fs.readFile(filePath, 'utf8')
1506
+ const messageCount = raw.split('\n').filter((l: string) => l.trim()).length
1507
+ return { sessionId, filePath, messageCount }
1508
+ } catch {
1509
+ return { sessionId, filePath, messageCount: 0 }
1510
+ }
1511
+ }))
1512
+
1513
+ return results
1514
+ }
1515
+
1516
+ /**
1517
+ * Clean up any temp MCP config files created during sessions.
1518
+ */
1519
+ async cleanupMcpTempFiles(): Promise<void> {
1520
+ for (const path of this.mcpTempFiles) {
1521
+ try { await this.container.feature('fs').rm(path) } catch { /* already gone */ }
1522
+ }
1523
+ this.mcpTempFiles = []
1524
+ }
1525
+
1526
+ /**
1527
+ * Initialize the feature.
1528
+ *
1529
+ * @param {any} [options] - Enable options
1530
+ * @returns {Promise<this>} The enabled feature
1531
+ */
1532
+ override async enable(options: any = {}): Promise<this> {
1533
+ await super.enable(options)
1534
+ return this
1535
+ }
1536
+ }
1537
+
1538
+ export default ClaudeCode