luca 2.0.0 → 3.0.2

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 (532) hide show
  1. package/.github/workflows/release.yaml +170 -0
  2. package/AGENTS.md +99 -0
  3. package/CLAUDE.md +123 -0
  4. package/CNAME +1 -0
  5. package/README.md +275 -9
  6. package/RUNME.md +56 -0
  7. package/assistants/codingAssistant/ABOUT.md +5 -0
  8. package/assistants/codingAssistant/CORE.md +33 -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 +2667 -0
  20. package/bunfig.toml +3 -0
  21. package/commands/audit-docs.ts +740 -0
  22. package/commands/build-bootstrap.ts +117 -0
  23. package/commands/build-python-bridge.ts +42 -0
  24. package/commands/build-scaffolds.ts +175 -0
  25. package/commands/bundle-consumer-project.ts +521 -0
  26. package/commands/generate-api-docs.ts +114 -0
  27. package/commands/inkbot.ts +874 -0
  28. package/commands/release.ts +80 -0
  29. package/commands/try-all-challenges.ts +543 -0
  30. package/commands/try-challenge.ts +100 -0
  31. package/dist/agi/container.server.d.ts +63 -0
  32. package/dist/agi/container.server.d.ts.map +1 -0
  33. package/dist/agi/endpoints/ask.d.ts +20 -0
  34. package/dist/agi/endpoints/ask.d.ts.map +1 -0
  35. package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
  36. package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
  37. package/dist/agi/endpoints/conversations.d.ts +18 -0
  38. package/dist/agi/endpoints/conversations.d.ts.map +1 -0
  39. package/dist/agi/endpoints/experts.d.ts +8 -0
  40. package/dist/agi/endpoints/experts.d.ts.map +1 -0
  41. package/dist/agi/feature.d.ts +9 -0
  42. package/dist/agi/feature.d.ts.map +1 -0
  43. package/dist/agi/features/assistant.d.ts +509 -0
  44. package/dist/agi/features/assistant.d.ts.map +1 -0
  45. package/dist/agi/features/assistants-manager.d.ts +236 -0
  46. package/dist/agi/features/assistants-manager.d.ts.map +1 -0
  47. package/dist/agi/features/autonomous-assistant.d.ts +281 -0
  48. package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
  49. package/dist/agi/features/browser-use.d.ts +479 -0
  50. package/dist/agi/features/browser-use.d.ts.map +1 -0
  51. package/dist/agi/features/claude-code.d.ts +824 -0
  52. package/dist/agi/features/claude-code.d.ts.map +1 -0
  53. package/dist/agi/features/conversation-history.d.ts +245 -0
  54. package/dist/agi/features/conversation-history.d.ts.map +1 -0
  55. package/dist/agi/features/conversation.d.ts +464 -0
  56. package/dist/agi/features/conversation.d.ts.map +1 -0
  57. package/dist/agi/features/docs-reader.d.ts +72 -0
  58. package/dist/agi/features/docs-reader.d.ts.map +1 -0
  59. package/dist/agi/features/file-tools.d.ts +110 -0
  60. package/dist/agi/features/file-tools.d.ts.map +1 -0
  61. package/dist/agi/features/luca-coder.d.ts +323 -0
  62. package/dist/agi/features/luca-coder.d.ts.map +1 -0
  63. package/dist/agi/features/openai-codex.d.ts +381 -0
  64. package/dist/agi/features/openai-codex.d.ts.map +1 -0
  65. package/dist/agi/features/openapi.d.ts +200 -0
  66. package/dist/agi/features/openapi.d.ts.map +1 -0
  67. package/dist/agi/features/skills-library.d.ts +167 -0
  68. package/dist/agi/features/skills-library.d.ts.map +1 -0
  69. package/dist/agi/index.d.ts +5 -0
  70. package/dist/agi/index.d.ts.map +1 -0
  71. package/dist/agi/lib/interceptor-chain.d.ts +44 -0
  72. package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
  73. package/dist/agi/lib/token-counter.d.ts +13 -0
  74. package/dist/agi/lib/token-counter.d.ts.map +1 -0
  75. package/dist/bootstrap/generated.d.ts +5 -0
  76. package/dist/bootstrap/generated.d.ts.map +1 -0
  77. package/dist/browser.d.ts +12 -0
  78. package/dist/browser.d.ts.map +1 -0
  79. package/dist/bus.d.ts +29 -0
  80. package/dist/bus.d.ts.map +1 -0
  81. package/dist/cli/build-info.d.ts +4 -0
  82. package/dist/cli/build-info.d.ts.map +1 -0
  83. package/dist/cli/cli.d.ts +3 -12
  84. package/dist/cli/cli.d.ts.map +1 -0
  85. package/dist/client.d.ts +60 -0
  86. package/dist/client.d.ts.map +1 -0
  87. package/dist/clients/civitai/index.d.ts +472 -0
  88. package/dist/clients/civitai/index.d.ts.map +1 -0
  89. package/dist/clients/client-template.d.ts +30 -0
  90. package/dist/clients/client-template.d.ts.map +1 -0
  91. package/dist/clients/comfyui/index.d.ts +281 -0
  92. package/dist/clients/comfyui/index.d.ts.map +1 -0
  93. package/dist/clients/elevenlabs/index.d.ts +197 -0
  94. package/dist/clients/elevenlabs/index.d.ts.map +1 -0
  95. package/dist/clients/graph.d.ts +64 -0
  96. package/dist/clients/graph.d.ts.map +1 -0
  97. package/dist/clients/openai/index.d.ts +247 -0
  98. package/dist/clients/openai/index.d.ts.map +1 -0
  99. package/dist/clients/rest.d.ts +92 -0
  100. package/dist/clients/rest.d.ts.map +1 -0
  101. package/dist/clients/supabase/index.d.ts +176 -0
  102. package/dist/clients/supabase/index.d.ts.map +1 -0
  103. package/dist/clients/websocket.d.ts +127 -0
  104. package/dist/clients/websocket.d.ts.map +1 -0
  105. package/dist/command.d.ts +163 -0
  106. package/dist/command.d.ts.map +1 -0
  107. package/dist/commands/bootstrap.d.ts +20 -0
  108. package/dist/commands/bootstrap.d.ts.map +1 -0
  109. package/dist/commands/chat.d.ts +37 -0
  110. package/dist/commands/chat.d.ts.map +1 -0
  111. package/dist/commands/code.d.ts +28 -0
  112. package/dist/commands/code.d.ts.map +1 -0
  113. package/dist/commands/console.d.ts +22 -0
  114. package/dist/commands/console.d.ts.map +1 -0
  115. package/dist/commands/describe.d.ts +50 -0
  116. package/dist/commands/describe.d.ts.map +1 -0
  117. package/dist/commands/eval.d.ts +23 -0
  118. package/dist/commands/eval.d.ts.map +1 -0
  119. package/dist/commands/help.d.ts +25 -0
  120. package/dist/commands/help.d.ts.map +1 -0
  121. package/dist/commands/index.d.ts +18 -0
  122. package/dist/commands/index.d.ts.map +1 -0
  123. package/dist/commands/introspect.d.ts +24 -0
  124. package/dist/commands/introspect.d.ts.map +1 -0
  125. package/dist/commands/mcp.d.ts +35 -0
  126. package/dist/commands/mcp.d.ts.map +1 -0
  127. package/dist/commands/prompt.d.ts +38 -0
  128. package/dist/commands/prompt.d.ts.map +1 -0
  129. package/dist/commands/run.d.ts +24 -0
  130. package/dist/commands/run.d.ts.map +1 -0
  131. package/dist/commands/sandbox-mcp.d.ts +34 -0
  132. package/dist/commands/sandbox-mcp.d.ts.map +1 -0
  133. package/dist/commands/save-api-docs.d.ts +21 -0
  134. package/dist/commands/save-api-docs.d.ts.map +1 -0
  135. package/dist/commands/scaffold.d.ts +24 -0
  136. package/dist/commands/scaffold.d.ts.map +1 -0
  137. package/dist/commands/select.d.ts +22 -0
  138. package/dist/commands/select.d.ts.map +1 -0
  139. package/dist/commands/serve.d.ts +29 -0
  140. package/dist/commands/serve.d.ts.map +1 -0
  141. package/dist/container-describer.d.ts +144 -0
  142. package/dist/container-describer.d.ts.map +1 -0
  143. package/dist/container.d.ts +451 -0
  144. package/dist/container.d.ts.map +1 -0
  145. package/dist/endpoint.d.ts +113 -0
  146. package/dist/endpoint.d.ts.map +1 -0
  147. package/dist/feature.d.ts +47 -0
  148. package/dist/feature.d.ts.map +1 -0
  149. package/dist/graft.d.ts +29 -0
  150. package/dist/graft.d.ts.map +1 -0
  151. package/dist/hash-object.d.ts +8 -0
  152. package/dist/hash-object.d.ts.map +1 -0
  153. package/dist/helper.d.ts +209 -0
  154. package/dist/helper.d.ts.map +1 -0
  155. package/dist/introspection/generated.node.d.ts +44623 -0
  156. package/dist/introspection/generated.node.d.ts.map +1 -0
  157. package/dist/introspection/generated.web.d.ts +1412 -0
  158. package/dist/introspection/generated.web.d.ts.map +1 -0
  159. package/dist/introspection/index.d.ts +156 -0
  160. package/dist/introspection/index.d.ts.map +1 -0
  161. package/dist/introspection/scan.d.ts +147 -0
  162. package/dist/introspection/scan.d.ts.map +1 -0
  163. package/dist/node/container.d.ts +256 -0
  164. package/dist/node/container.d.ts.map +1 -0
  165. package/dist/node/feature.d.ts +9 -0
  166. package/dist/node/feature.d.ts.map +1 -0
  167. package/dist/node/features/container-link.d.ts +213 -0
  168. package/dist/node/features/container-link.d.ts.map +1 -0
  169. package/dist/node/features/content-db.d.ts +354 -0
  170. package/dist/node/features/content-db.d.ts.map +1 -0
  171. package/dist/node/features/disk-cache.d.ts +236 -0
  172. package/dist/node/features/disk-cache.d.ts.map +1 -0
  173. package/dist/node/features/dns.d.ts +511 -0
  174. package/dist/node/features/dns.d.ts.map +1 -0
  175. package/dist/node/features/docker.d.ts +485 -0
  176. package/dist/node/features/docker.d.ts.map +1 -0
  177. package/dist/node/features/downloader.d.ts +73 -0
  178. package/dist/node/features/downloader.d.ts.map +1 -0
  179. package/dist/node/features/figlet-fonts.d.ts +4 -0
  180. package/dist/node/features/figlet-fonts.d.ts.map +1 -0
  181. package/dist/node/features/file-manager.d.ts +177 -0
  182. package/dist/node/features/file-manager.d.ts.map +1 -0
  183. package/dist/node/features/fs.d.ts +635 -0
  184. package/dist/node/features/fs.d.ts.map +1 -0
  185. package/dist/node/features/git.d.ts +329 -0
  186. package/dist/node/features/git.d.ts.map +1 -0
  187. package/dist/node/features/google-auth.d.ts +200 -0
  188. package/dist/node/features/google-auth.d.ts.map +1 -0
  189. package/dist/node/features/google-calendar.d.ts +194 -0
  190. package/dist/node/features/google-calendar.d.ts.map +1 -0
  191. package/dist/node/features/google-docs.d.ts +138 -0
  192. package/dist/node/features/google-docs.d.ts.map +1 -0
  193. package/dist/node/features/google-drive.d.ts +202 -0
  194. package/dist/node/features/google-drive.d.ts.map +1 -0
  195. package/dist/node/features/google-mail.d.ts +221 -0
  196. package/dist/node/features/google-mail.d.ts.map +1 -0
  197. package/dist/node/features/google-sheets.d.ts +157 -0
  198. package/dist/node/features/google-sheets.d.ts.map +1 -0
  199. package/dist/node/features/grep.d.ts +207 -0
  200. package/dist/node/features/grep.d.ts.map +1 -0
  201. package/dist/node/features/helpers.d.ts +236 -0
  202. package/dist/node/features/helpers.d.ts.map +1 -0
  203. package/dist/node/features/ink.d.ts +332 -0
  204. package/dist/node/features/ink.d.ts.map +1 -0
  205. package/dist/node/features/ipc-socket.d.ts +298 -0
  206. package/dist/node/features/ipc-socket.d.ts.map +1 -0
  207. package/dist/node/features/json-tree.d.ts +140 -0
  208. package/dist/node/features/json-tree.d.ts.map +1 -0
  209. package/dist/node/features/networking.d.ts +373 -0
  210. package/dist/node/features/networking.d.ts.map +1 -0
  211. package/dist/node/features/nlp.d.ts +125 -0
  212. package/dist/node/features/nlp.d.ts.map +1 -0
  213. package/dist/node/features/opener.d.ts +93 -0
  214. package/dist/node/features/opener.d.ts.map +1 -0
  215. package/dist/node/features/os.d.ts +168 -0
  216. package/dist/node/features/os.d.ts.map +1 -0
  217. package/dist/node/features/package-finder.d.ts +419 -0
  218. package/dist/node/features/package-finder.d.ts.map +1 -0
  219. package/dist/node/features/postgres.d.ts +173 -0
  220. package/dist/node/features/postgres.d.ts.map +1 -0
  221. package/dist/node/features/proc.d.ts +285 -0
  222. package/dist/node/features/proc.d.ts.map +1 -0
  223. package/dist/node/features/process-manager.d.ts +427 -0
  224. package/dist/node/features/process-manager.d.ts.map +1 -0
  225. package/dist/node/features/python.d.ts +477 -0
  226. package/dist/node/features/python.d.ts.map +1 -0
  227. package/dist/node/features/redis.d.ts +247 -0
  228. package/dist/node/features/redis.d.ts.map +1 -0
  229. package/dist/node/features/repl.d.ts +84 -0
  230. package/dist/node/features/repl.d.ts.map +1 -0
  231. package/dist/node/features/runpod.d.ts +527 -0
  232. package/dist/node/features/runpod.d.ts.map +1 -0
  233. package/dist/node/features/secure-shell.d.ts +145 -0
  234. package/dist/node/features/secure-shell.d.ts.map +1 -0
  235. package/dist/node/features/semantic-search.d.ts +207 -0
  236. package/dist/node/features/semantic-search.d.ts.map +1 -0
  237. package/dist/node/features/sqlite.d.ts +180 -0
  238. package/dist/node/features/sqlite.d.ts.map +1 -0
  239. package/dist/node/features/telegram.d.ts +173 -0
  240. package/dist/node/features/telegram.d.ts.map +1 -0
  241. package/dist/node/features/transpiler.d.ts +51 -0
  242. package/dist/node/features/transpiler.d.ts.map +1 -0
  243. package/dist/node/features/tts.d.ts +108 -0
  244. package/dist/node/features/tts.d.ts.map +1 -0
  245. package/dist/node/features/ui.d.ts +562 -0
  246. package/dist/node/features/ui.d.ts.map +1 -0
  247. package/dist/node/features/vault.d.ts +90 -0
  248. package/dist/node/features/vault.d.ts.map +1 -0
  249. package/dist/node/features/vm.d.ts +285 -0
  250. package/dist/node/features/vm.d.ts.map +1 -0
  251. package/dist/node/features/yaml-tree.d.ts +118 -0
  252. package/dist/node/features/yaml-tree.d.ts.map +1 -0
  253. package/dist/node/features/yaml.d.ts +127 -0
  254. package/dist/node/features/yaml.d.ts.map +1 -0
  255. package/dist/node.d.ts +67 -0
  256. package/dist/node.d.ts.map +1 -0
  257. package/dist/python/generated.d.ts +2 -0
  258. package/dist/python/generated.d.ts.map +1 -0
  259. package/dist/react/index.d.ts +36 -0
  260. package/dist/react/index.d.ts.map +1 -0
  261. package/dist/registry.d.ts +97 -0
  262. package/dist/registry.d.ts.map +1 -0
  263. package/dist/scaffolds/generated.d.ts +13 -0
  264. package/dist/scaffolds/generated.d.ts.map +1 -0
  265. package/dist/scaffolds/template.d.ts +11 -0
  266. package/dist/scaffolds/template.d.ts.map +1 -0
  267. package/dist/schemas/base.d.ts +254 -0
  268. package/dist/schemas/base.d.ts.map +1 -0
  269. package/dist/selector.d.ts +130 -0
  270. package/dist/selector.d.ts.map +1 -0
  271. package/dist/server.d.ts +89 -0
  272. package/dist/server.d.ts.map +1 -0
  273. package/dist/servers/express.d.ts +104 -0
  274. package/dist/servers/express.d.ts.map +1 -0
  275. package/dist/servers/mcp.d.ts +201 -0
  276. package/dist/servers/mcp.d.ts.map +1 -0
  277. package/dist/servers/socket.d.ts +121 -0
  278. package/dist/servers/socket.d.ts.map +1 -0
  279. package/dist/state.d.ts +24 -0
  280. package/dist/state.d.ts.map +1 -0
  281. package/dist/web/clients/socket.d.ts +37 -0
  282. package/dist/web/clients/socket.d.ts.map +1 -0
  283. package/dist/web/container.d.ts +55 -0
  284. package/dist/web/container.d.ts.map +1 -0
  285. package/dist/web/extension.d.ts +4 -0
  286. package/dist/web/extension.d.ts.map +1 -0
  287. package/dist/web/feature.d.ts +8 -0
  288. package/dist/web/feature.d.ts.map +1 -0
  289. package/dist/web/features/asset-loader.d.ts +35 -0
  290. package/dist/web/features/asset-loader.d.ts.map +1 -0
  291. package/dist/web/features/container-link.d.ts +167 -0
  292. package/dist/web/features/container-link.d.ts.map +1 -0
  293. package/dist/web/features/esbuild.d.ts +51 -0
  294. package/dist/web/features/esbuild.d.ts.map +1 -0
  295. package/dist/web/features/helpers.d.ts +140 -0
  296. package/dist/web/features/helpers.d.ts.map +1 -0
  297. package/dist/web/features/network.d.ts +69 -0
  298. package/dist/web/features/network.d.ts.map +1 -0
  299. package/dist/web/features/speech.d.ts +71 -0
  300. package/dist/web/features/speech.d.ts.map +1 -0
  301. package/dist/web/features/vault.d.ts +62 -0
  302. package/dist/web/features/vault.d.ts.map +1 -0
  303. package/dist/web/features/vm.d.ts +48 -0
  304. package/dist/web/features/vm.d.ts.map +1 -0
  305. package/dist/web/features/voice-recognition.d.ts +96 -0
  306. package/dist/web/features/voice-recognition.d.ts.map +1 -0
  307. package/dist/web/shims/isomorphic-vm.d.ts +22 -0
  308. package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
  309. package/index.html +1457 -0
  310. package/index.ts +1 -0
  311. package/install.sh +84 -0
  312. package/luca.cli.ts +16 -0
  313. package/luca.console.ts +9 -0
  314. package/main.py +6 -0
  315. package/package.json +219 -58
  316. package/public/index.html +1457 -0
  317. package/public/slides-ai-native.html +902 -0
  318. package/public/slides-intro.html +974 -0
  319. package/pyproject.toml +7 -0
  320. package/scripts/build-web.ts +28 -0
  321. package/scripts/examples/ask-luca-expert.ts +42 -0
  322. package/scripts/examples/assistant-questions.ts +12 -0
  323. package/scripts/examples/excalidraw-expert.ts +75 -0
  324. package/scripts/examples/expert-chat.ts +0 -0
  325. package/scripts/examples/file-manager.ts +14 -0
  326. package/scripts/examples/ideas.ts +12 -0
  327. package/scripts/examples/interactive-chat.ts +20 -0
  328. package/scripts/examples/openai-tool-calls.ts +113 -0
  329. package/scripts/examples/opening-a-web-browser.ts +5 -0
  330. package/scripts/examples/telegram-bot.ts +79 -0
  331. package/scripts/examples/using-assistant-with-mcp.ts +555 -0
  332. package/scripts/examples/using-claude-code.ts +10 -0
  333. package/scripts/examples/using-contentdb.ts +35 -0
  334. package/scripts/examples/using-conversations.ts +35 -0
  335. package/scripts/examples/using-disk-cache.ts +10 -0
  336. package/scripts/examples/using-docker-shell.ts +75 -0
  337. package/scripts/examples/using-elevenlabs.ts +25 -0
  338. package/scripts/examples/using-google-calendar.ts +57 -0
  339. package/scripts/examples/using-google-docs.ts +74 -0
  340. package/scripts/examples/using-google-drive.ts +74 -0
  341. package/scripts/examples/using-google-sheets.ts +89 -0
  342. package/scripts/examples/using-nlp.ts +55 -0
  343. package/scripts/examples/using-ollama.ts +11 -0
  344. package/scripts/examples/using-postgres.ts +55 -0
  345. package/scripts/examples/using-runpod.ts +32 -0
  346. package/scripts/examples/using-tts.ts +40 -0
  347. package/scripts/scaffold.ts +391 -0
  348. package/scripts/scratch.ts +15 -0
  349. package/scripts/stamp-build.sh +12 -0
  350. package/scripts/test-assistant-hooks.ts +13 -0
  351. package/scripts/test-docs-reader.ts +10 -0
  352. package/scripts/test-linux-binary.sh +80 -0
  353. package/scripts/update-introspection-data.ts +58 -0
  354. package/src/agi/README.md +14 -0
  355. package/src/agi/container.server.ts +156 -0
  356. package/src/agi/feature.ts +13 -0
  357. package/src/agi/features/agent-memory.ts +694 -0
  358. package/src/agi/features/assistant.ts +1653 -0
  359. package/src/agi/features/assistants-manager.ts +534 -0
  360. package/src/agi/features/autonomous-assistant.ts +431 -0
  361. package/src/agi/features/browser-use.ts +672 -0
  362. package/src/agi/features/claude-code.ts +1584 -0
  363. package/src/agi/features/coding-tools.ts +175 -0
  364. package/src/agi/features/conversation-history.ts +672 -0
  365. package/src/agi/features/conversation.ts +1494 -0
  366. package/src/agi/features/docs-reader.ts +167 -0
  367. package/src/agi/features/file-tools.ts +340 -0
  368. package/src/agi/features/luca-coder.ts +641 -0
  369. package/src/agi/features/mcp-bridge.ts +532 -0
  370. package/src/agi/features/openai-codex.ts +651 -0
  371. package/src/agi/features/openapi.ts +445 -0
  372. package/src/agi/features/skills-library.ts +557 -0
  373. package/src/agi/index.ts +6 -0
  374. package/src/agi/lib/interceptor-chain.ts +89 -0
  375. package/src/agi/lib/token-counter.ts +202 -0
  376. package/src/bootstrap/generated.ts +9791 -0
  377. package/src/browser.ts +25 -0
  378. package/src/bus.ts +122 -0
  379. package/src/cli/build-info.ts +4 -0
  380. package/src/cli/cli.ts +355 -0
  381. package/src/client.ts +170 -0
  382. package/src/clients/civitai/index.ts +537 -0
  383. package/src/clients/client-template.ts +41 -0
  384. package/src/clients/comfyui/index.ts +604 -0
  385. package/src/clients/elevenlabs/index.ts +317 -0
  386. package/src/clients/graph.ts +87 -0
  387. package/src/clients/openai/index.ts +456 -0
  388. package/src/clients/rest.ts +207 -0
  389. package/src/clients/supabase/index.ts +357 -0
  390. package/src/clients/voicebox/index.ts +300 -0
  391. package/src/clients/websocket.ts +251 -0
  392. package/src/command.ts +506 -0
  393. package/src/commands/bootstrap.ts +244 -0
  394. package/src/commands/chat.ts +309 -0
  395. package/src/commands/code.ts +371 -0
  396. package/src/commands/console.ts +189 -0
  397. package/src/commands/describe.ts +243 -0
  398. package/src/commands/eval.ts +67 -0
  399. package/src/commands/help.ts +240 -0
  400. package/src/commands/index.ts +19 -0
  401. package/src/commands/introspect.ts +218 -0
  402. package/src/commands/mcp.ts +64 -0
  403. package/src/commands/prompt.ts +1014 -0
  404. package/src/commands/run.ts +278 -0
  405. package/src/commands/sandbox-mcp.ts +343 -0
  406. package/src/commands/save-api-docs.ts +51 -0
  407. package/src/commands/scaffold.ts +225 -0
  408. package/src/commands/select.ts +99 -0
  409. package/src/commands/serve.ts +208 -0
  410. package/src/container-describer.ts +1091 -0
  411. package/src/container.ts +1199 -0
  412. package/src/endpoint.ts +365 -0
  413. package/src/entity.ts +173 -0
  414. package/src/feature.ts +118 -0
  415. package/src/graft.ts +181 -0
  416. package/src/hash-object.ts +97 -0
  417. package/src/helper.ts +849 -0
  418. package/src/introspection/generated.agi.ts +41200 -0
  419. package/src/introspection/generated.node.ts +28773 -0
  420. package/src/introspection/generated.web.ts +2272 -0
  421. package/src/introspection/index.ts +296 -0
  422. package/src/introspection/scan.ts +1136 -0
  423. package/src/node/container.ts +409 -0
  424. package/src/node/feature.ts +13 -0
  425. package/src/node/features/container-link.ts +559 -0
  426. package/src/node/features/content-db.ts +849 -0
  427. package/src/node/features/disk-cache.ts +388 -0
  428. package/src/node/features/display-result.ts +57 -0
  429. package/src/node/features/dns.ts +669 -0
  430. package/src/node/features/docker.ts +921 -0
  431. package/src/node/features/downloader.ts +79 -0
  432. package/src/node/features/figlet-fonts.ts +600 -0
  433. package/src/node/features/file-manager.ts +535 -0
  434. package/src/node/features/fs.ts +1050 -0
  435. package/src/node/features/git.ts +592 -0
  436. package/src/node/features/google-auth.ts +504 -0
  437. package/src/node/features/google-calendar.ts +306 -0
  438. package/src/node/features/google-docs.ts +412 -0
  439. package/src/node/features/google-drive.ts +346 -0
  440. package/src/node/features/google-mail.ts +540 -0
  441. package/src/node/features/google-sheets.ts +286 -0
  442. package/src/node/features/grep.ts +427 -0
  443. package/src/node/features/helpers.ts +762 -0
  444. package/src/node/features/ink.ts +490 -0
  445. package/src/node/features/ipc-socket.ts +649 -0
  446. package/src/node/features/json-tree.ts +170 -0
  447. package/src/node/features/networking.ts +961 -0
  448. package/src/node/features/nlp.ts +212 -0
  449. package/src/node/features/opener.ts +180 -0
  450. package/src/node/features/os.ts +403 -0
  451. package/src/node/features/package-finder.ts +540 -0
  452. package/src/node/features/postgres.ts +289 -0
  453. package/src/node/features/proc.ts +503 -0
  454. package/src/node/features/process-manager.ts +844 -0
  455. package/src/node/features/python.ts +912 -0
  456. package/src/node/features/redis.ts +446 -0
  457. package/src/node/features/repl.ts +212 -0
  458. package/src/node/features/runpod.ts +811 -0
  459. package/src/node/features/secure-shell.ts +261 -0
  460. package/src/node/features/semantic-search.ts +935 -0
  461. package/src/node/features/sqlite.ts +289 -0
  462. package/src/node/features/telegram.ts +343 -0
  463. package/src/node/features/transpiler.ts +160 -0
  464. package/src/node/features/tts.ts +185 -0
  465. package/src/node/features/ui.ts +791 -0
  466. package/src/node/features/vault.ts +153 -0
  467. package/src/node/features/vm.ts +462 -0
  468. package/src/node/features/yaml-tree.ts +148 -0
  469. package/src/node/features/yaml.ts +133 -0
  470. package/src/node.ts +76 -0
  471. package/src/python/bridge.py +220 -0
  472. package/src/python/generated.ts +226 -0
  473. package/src/react/index.ts +175 -0
  474. package/src/registry.ts +210 -0
  475. package/src/scaffolds/generated.ts +1814 -0
  476. package/src/scaffolds/template.ts +46 -0
  477. package/src/schemas/base.ts +296 -0
  478. package/src/selector.ts +352 -0
  479. package/src/server.ts +229 -0
  480. package/src/servers/express.ts +283 -0
  481. package/src/servers/mcp.ts +802 -0
  482. package/src/servers/socket.ts +258 -0
  483. package/src/state.ts +101 -0
  484. package/src/web/clients/socket.ts +99 -0
  485. package/src/web/container.ts +75 -0
  486. package/src/web/extension.ts +30 -0
  487. package/src/web/feature.ts +12 -0
  488. package/src/web/features/asset-loader.ts +72 -0
  489. package/src/web/features/container-link.ts +382 -0
  490. package/src/web/features/esbuild.ts +93 -0
  491. package/src/web/features/helpers.ts +291 -0
  492. package/src/web/features/network.ts +85 -0
  493. package/src/web/features/speech.ts +104 -0
  494. package/src/web/features/vault.ts +207 -0
  495. package/src/web/features/vm.ts +85 -0
  496. package/src/web/features/voice-recognition.ts +161 -0
  497. package/src/web/shims/isomorphic-vm.ts +149 -0
  498. package/tsconfig.build.json +12 -0
  499. package/tsconfig.json +58 -0
  500. package/uv.lock +8 -0
  501. package/LICENSE +0 -21
  502. package/dist/cli/cli.js +0 -48
  503. package/dist/cli/common.d.ts +0 -2
  504. package/dist/cli/common.js +0 -6
  505. package/dist/cli/index.d.ts +0 -2
  506. package/dist/cli/index.js +0 -5
  507. package/dist/cli/run.d.ts +0 -1
  508. package/dist/cli/run.js +0 -38
  509. package/dist/core/index.d.ts +0 -4
  510. package/dist/core/index.js +0 -32
  511. package/dist/core/read.d.ts +0 -2
  512. package/dist/core/read.js +0 -29
  513. package/dist/core/request.d.ts +0 -1
  514. package/dist/core/request.js +0 -2
  515. package/dist/core/write.d.ts +0 -2
  516. package/dist/core/write.js +0 -21
  517. package/dist/index.d.ts +0 -1
  518. package/dist/index.js +0 -5
  519. package/dist/utils/common.d.ts +0 -9
  520. package/dist/utils/common.js +0 -57
  521. package/dist/utils/consts.d.ts +0 -3
  522. package/dist/utils/consts.js +0 -11
  523. package/dist/utils/dict.d.ts +0 -1
  524. package/dist/utils/dict.js +0 -7
  525. package/dist/utils/index.d.ts +0 -5
  526. package/dist/utils/index.js +0 -21
  527. package/dist/utils/log.d.ts +0 -1
  528. package/dist/utils/log.js +0 -5
  529. package/dist/utils/types.d.ts +0 -1
  530. package/dist/utils/types.js +0 -2
  531. package/dist/utils/utils.test.d.ts +0 -1
  532. package/dist/utils/utils.test.js +0 -7
@@ -0,0 +1,80 @@
1
+ import { z } from "zod";
2
+ import type { ContainerContext } from "luca";
3
+ import { CommandOptionsSchema } from "luca/schemas";
4
+
5
+ export const argsSchema = CommandOptionsSchema.extend({
6
+ skipTests: z
7
+ .boolean()
8
+ .optional()
9
+ .describe("Skip running tests before release"),
10
+ });
11
+
12
+ async function release(
13
+ options: z.infer<typeof argsSchema>,
14
+ context: ContainerContext,
15
+ ) {
16
+ const container = context.container as any;
17
+ const proc = container.feature("proc");
18
+ const fileSystem = container.feature("fs");
19
+ const ui = container.feature("ui");
20
+
21
+ const pkg = JSON.parse(await fileSystem.readFileAsync("package.json"));
22
+ const version = pkg.version;
23
+ const tag = `v${version}`;
24
+
25
+ ui.banner(`Luca Release ${tag}`);
26
+
27
+ // Check if tag already exists
28
+ const tagCheck = await proc.execAndCapture(`git tag -l "${tag}"`, {
29
+ silent: true,
30
+ });
31
+ if (tagCheck.stdout.trim() === tag) {
32
+ console.error(
33
+ `\nTag ${tag} already exists. Bump the version in package.json first.`,
34
+ );
35
+ return;
36
+ }
37
+
38
+ // Run tests
39
+ if (!options.skipTests) {
40
+ console.log("\n→ Running tests...");
41
+ const testResult = await proc.execAndCapture("bun test test/*.test.ts", {
42
+ silent: false,
43
+ });
44
+ if (testResult.exitCode !== 0) {
45
+ console.error("Tests failed. Fix them before releasing.");
46
+ return;
47
+ }
48
+ }
49
+
50
+ // Create and push git tag — triggers the GitHub Actions release workflow
51
+ console.log(`\n→ Creating tag ${tag}...`);
52
+ const tagResult = await proc.execAndCapture(
53
+ `git tag -a "${tag}" -m "Release ${tag}"`,
54
+ { silent: true },
55
+ );
56
+ if (tagResult.exitCode !== 0) {
57
+ console.error(`Failed to create tag:\n${tagResult.stderr}`);
58
+ return;
59
+ }
60
+
61
+ console.log(`→ Pushing tag ${tag}...`);
62
+ const pushResult = await proc.execAndCapture(`git push origin "${tag}"`, {
63
+ silent: true,
64
+ });
65
+ if (pushResult.exitCode !== 0) {
66
+ console.error(`Failed to push tag:\n${pushResult.stderr}`);
67
+ return;
68
+ }
69
+
70
+ console.log(
71
+ `\n✓ Tag ${tag} pushed. GitHub Actions will build, sign, and create the draft release.`,
72
+ );
73
+ console.log(` https://github.com/soederpop/luca/actions`);
74
+ }
75
+
76
+ export default {
77
+ description: "Run tests and trigger a GitHub Actions release via git tag",
78
+ argsSchema,
79
+ handler: release,
80
+ };
@@ -0,0 +1,543 @@
1
+ import { z } from 'zod'
2
+ import { commands, CommandOptionsSchema } from 'luca'
3
+ import type { ContainerContext } from 'luca'
4
+
5
+ declare module 'luca' {
6
+ interface AvailableCommands {
7
+ tryAllChallenges: ReturnType<typeof commands.registerHandler>
8
+ }
9
+ }
10
+
11
+ export const argsSchema = CommandOptionsSchema.extend({
12
+ 'batch-size': z.number().default(4).describe('Number of challenges to run in parallel per batch'),
13
+ 'time-limit': z.number().optional().describe('Override time limit in minutes for all challenges'),
14
+ 'dry-run': z.boolean().default(false).describe('List the batch schedule without running anything'),
15
+ })
16
+
17
+ // ─── Types ──────────────────────────────────────────────────────────────────
18
+
19
+ type ChallengeStatus = 'queued' | 'bootstrapping' | 'running' | 'done' | 'failed' | 'timeout'
20
+
21
+ interface ChallengeState {
22
+ id: string
23
+ slug: string
24
+ title: string
25
+ status: ChallengeStatus
26
+ startTime: number
27
+ durationMs: number
28
+ timeLimitMinutes: number
29
+ lastActivity: string
30
+ activityLines: string[]
31
+ lessonsWritten: boolean
32
+ attemptFolder: string
33
+ error: string | undefined
34
+ batchIndex: number
35
+ }
36
+
37
+ // ─── Constants ──────────────────────────────────────────────────────────────
38
+
39
+ const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
40
+ const MAX_ACTIVITY_LINES = 50
41
+ const STRIP_ANSI = /\x1b\[[0-9;]*m/g
42
+
43
+ // ─── Helpers ────────────────────────────────────────────────────────────────
44
+
45
+ function formatElapsed(ms: number): string {
46
+ const s = Math.floor(ms / 1000)
47
+ const m = Math.floor(s / 60)
48
+ const sec = s % 60
49
+ return `${m}:${String(sec).padStart(2, '0')}`
50
+ }
51
+
52
+ function pushActivity(cs: ChallengeState, line: string) {
53
+ const clean = line.replace(STRIP_ANSI, '').trim()
54
+ if (!clean) return
55
+ cs.activityLines.push(clean)
56
+ if (cs.activityLines.length > MAX_ACTIVITY_LINES) {
57
+ cs.activityLines = cs.activityLines.slice(-MAX_ACTIVITY_LINES)
58
+ }
59
+ cs.lastActivity = clean.slice(0, 80)
60
+ if (/lessons\.md/i.test(clean)) {
61
+ cs.lessonsWritten = true
62
+ }
63
+ }
64
+
65
+ // ─── Orchestration ──────────────────────────────────────────────────────────
66
+
67
+ async function bootstrapFolder(container: any, folder: string) {
68
+ const result = await container.proc.spawnAndCapture('luca', ['bootstrap'], {
69
+ cwd: container.paths.resolve(folder),
70
+ onOutput: () => {},
71
+ onError: () => {},
72
+ })
73
+ if (result.exitCode !== 0) {
74
+ throw new Error(`bootstrap failed (exit ${result.exitCode}): ${result.stderr}`)
75
+ }
76
+ }
77
+
78
+ // Track active child processes for cleanup on abort
79
+ const activeChildProcesses = new Set<any>()
80
+
81
+ async function runChallenge(
82
+ cs: ChallengeState,
83
+ container: any,
84
+ sessionFolder: string,
85
+ abortSignal: { aborted: boolean },
86
+ ): Promise<void> {
87
+ if (abortSignal.aborted) return
88
+
89
+ const fs = container.feature('fs')
90
+ const attemptFolder = `${sessionFolder}/${cs.slug}`
91
+ cs.attemptFolder = attemptFolder
92
+ fs.ensureFolder(attemptFolder)
93
+
94
+ // Bootstrap
95
+ cs.status = 'bootstrapping'
96
+ cs.startTime = Date.now()
97
+ cs.lastActivity = 'bootstrapping...'
98
+ await bootstrapFolder(container, attemptFolder)
99
+
100
+ if (abortSignal.aborted) return
101
+
102
+ // Run claude via luca prompt
103
+ cs.status = 'running'
104
+ cs.lastActivity = 'claude starting...'
105
+
106
+ const promptArgs = [
107
+ 'prompt', 'claude', `docs/${cs.id}`,
108
+ '--exclude-sections', 'Internal Notes',
109
+ '--out-file', `${sessionFolder}/logs/${cs.slug}-session.md`,
110
+ '--in-folder', attemptFolder,
111
+ '--dont-touch-file',
112
+ ]
113
+
114
+ const timeLimitMs = cs.timeLimitMinutes * 60 * 1000
115
+
116
+ const promptProcess = container.proc.spawnAndCapture('luca', promptArgs, {
117
+ onStart: (childProcess: any) => {
118
+ activeChildProcesses.add(childProcess)
119
+ },
120
+ onOutput: (str: string) => {
121
+ for (const line of str.split('\n')) {
122
+ pushActivity(cs, line)
123
+ }
124
+ },
125
+ onError: (str: string) => {
126
+ for (const line of str.split('\n')) {
127
+ pushActivity(cs, line)
128
+ }
129
+ },
130
+ })
131
+
132
+ const timeout = new Promise<'timeout'>((resolve) => {
133
+ setTimeout(() => resolve('timeout'), timeLimitMs + 30_000)
134
+ })
135
+
136
+ const result = await Promise.race([
137
+ promptProcess.then(() => 'done' as const),
138
+ timeout,
139
+ ]).catch((err: any) => {
140
+ cs.error = err?.message || String(err)
141
+ return 'failed' as const
142
+ })
143
+
144
+ cs.durationMs = Date.now() - cs.startTime
145
+
146
+ if (result === 'timeout') {
147
+ cs.status = 'timeout'
148
+ cs.error = `Exceeded ${cs.timeLimitMinutes}min + 30s safety margin`
149
+ pushActivity(cs, `[TIMEOUT: ${cs.timeLimitMinutes} min limit reached]`)
150
+ } else if (result === 'failed') {
151
+ cs.status = 'failed'
152
+ pushActivity(cs, `[FAILED: ${cs.error}]`)
153
+ } else {
154
+ // Check if LESSONS.md was actually written
155
+ if (fs.existsSync(container.paths.resolve(attemptFolder, 'LESSONS.md'))) {
156
+ cs.lessonsWritten = true
157
+ }
158
+ cs.status = 'done'
159
+ }
160
+ }
161
+
162
+ async function runBatch(
163
+ batch: ChallengeState[],
164
+ container: any,
165
+ sessionFolder: string,
166
+ abortSignal: { aborted: boolean },
167
+ ): Promise<void> {
168
+ const results = await Promise.allSettled(
169
+ batch.map((cs) => runChallenge(cs, container, sessionFolder, abortSignal))
170
+ )
171
+
172
+ for (let i = 0; i < results.length; i++) {
173
+ const r = results[i]
174
+ if (r.status === 'rejected' && batch[i].status === 'running') {
175
+ batch[i].status = 'failed'
176
+ batch[i].error = String(r.reason)
177
+ batch[i].durationMs = Date.now() - batch[i].startTime
178
+ }
179
+ }
180
+ }
181
+
182
+ async function runSynthesis(
183
+ challenges: ChallengeState[],
184
+ container: any,
185
+ sessionFolder: string,
186
+ ): Promise<void> {
187
+ const fs = container.feature('fs')
188
+ const paths = container.paths
189
+
190
+ // Gather all LESSONS.md content
191
+ const lessonParts: string[] = []
192
+ const summaryParts: string[] = []
193
+
194
+ for (const cs of challenges) {
195
+ const lessonsPath = paths.resolve(cs.attemptFolder, 'LESSONS.md')
196
+ const statusLabel = cs.status === 'done' ? 'completed' : cs.status
197
+ const duration = formatElapsed(cs.durationMs)
198
+
199
+ summaryParts.push(`- **${cs.title}** (${cs.slug}): ${statusLabel} in ${duration}${cs.lessonsWritten ? '' : ' — no LESSONS.md'}`)
200
+
201
+ if (cs.lessonsWritten && fs.existsSync(lessonsPath)) {
202
+ const content = fs.readFile(lessonsPath) as string
203
+ lessonParts.push(`## ${cs.title} (${cs.slug})\n\n${content}`)
204
+ }
205
+ }
206
+
207
+ const done = challenges.filter(c => c.status === 'done').length
208
+ const failed = challenges.filter(c => c.status === 'failed' || c.status === 'timeout').length
209
+
210
+ const synthesisPrompt = `You are reviewing the results of a batch challenge evaluation session for the Luca framework.
211
+
212
+ ${done} challenges completed successfully. ${failed} challenges failed or timed out.
213
+
214
+ ## Challenge Results
215
+
216
+ ${summaryParts.join('\n')}
217
+
218
+ ## Individual LESSONS.md Files
219
+
220
+ ${lessonParts.length > 0 ? lessonParts.join('\n\n---\n\n') : '(No LESSONS.md files were produced)'}
221
+
222
+ ## Your Task
223
+
224
+ Write a RETRO.md file in the current directory that contains:
225
+
226
+ 1. **What Went Well** — patterns and capabilities that worked reliably across challenges
227
+ 2. **What Didn't Go Well** — common struggles, failures, and pain points
228
+ 3. **Actionable Improvements** — specific, concrete steps to improve the CLAUDE.md, SKILL.md, framework docs, or luca internals that would help future challenge runs succeed faster and more reliably
229
+ 4. **Challenge-by-Challenge Notes** — brief per-challenge observations worth preserving
230
+
231
+ Be specific and actionable. Reference concrete file paths, APIs, and patterns. This retro should directly inform what we work on next.`
232
+
233
+ // Write synthesis prompt to a temp file and run it through luca prompt
234
+ const synthPromptPath = paths.resolve(sessionFolder, '_synthesis-prompt.md')
235
+ fs.ensureFile(synthPromptPath, `---\nrepeatable: true\n---\n\n${synthesisPrompt}`, true)
236
+
237
+ fs.ensureFolder(paths.resolve(sessionFolder, 'logs'))
238
+
239
+ await container.proc.spawnAndCapture('luca', [
240
+ 'prompt', 'claude', synthPromptPath,
241
+ '--in-folder', sessionFolder,
242
+ '--out-file', `${sessionFolder}/logs/synthesis-session.md`,
243
+ '--dont-touch-file',
244
+ '--include-frontmatter',
245
+ ], {
246
+ onOutput: (str: string) => { process.stdout.write(str) },
247
+ onError: (str: string) => { process.stderr.write(str) },
248
+ })
249
+ }
250
+
251
+ // ─── Ink Dashboard ──────────────────────────────────────────────────────────
252
+
253
+ async function renderDashboard(
254
+ challenges: ChallengeState[],
255
+ container: any,
256
+ sessionFolder: string,
257
+ batchSize: number,
258
+ ): Promise<boolean> {
259
+ const ink = container.feature('ink', { enable: true })
260
+ await ink.loadModules()
261
+
262
+ const React = ink.React
263
+ const h = React.createElement
264
+ const { Box, Text } = ink.components
265
+ const { useApp, useInput, useStdout } = ink.hooks
266
+ const { useState, useEffect } = React
267
+
268
+ const numBatches = Math.ceil(challenges.length / batchSize)
269
+ let currentBatchIndex = 0
270
+ let allBatchesDone = false
271
+ let userAborted = false
272
+ const abortSignal = { aborted: false }
273
+
274
+ // Run batches in sequence outside React
275
+ const orchestrate = async () => {
276
+ for (let b = 0; b < numBatches; b++) {
277
+ if (abortSignal.aborted) break
278
+ currentBatchIndex = b
279
+ const start = b * batchSize
280
+ const batch = challenges.slice(start, start + batchSize)
281
+ await runBatch(batch, container, sessionFolder, abortSignal)
282
+ }
283
+ allBatchesDone = true
284
+ }
285
+
286
+ const orchestrationPromise = orchestrate().catch(() => { allBatchesDone = true })
287
+
288
+ function App() {
289
+ const { exit } = useApp()
290
+ const { stdout } = useStdout()
291
+ const [tick, setTick] = useState(0)
292
+ const [focusIdx, setFocusIdx] = useState(0)
293
+
294
+ const cols = stdout?.columns || 120
295
+ const rows = stdout?.rows || 40
296
+
297
+ useEffect(() => {
298
+ const timer = setInterval(() => setTick((t: number) => t + 1), 250)
299
+ return () => clearInterval(timer)
300
+ }, [])
301
+
302
+ useEffect(() => {
303
+ if (allBatchesDone) {
304
+ setTimeout(() => exit(), 600)
305
+ }
306
+ }, [tick])
307
+
308
+ useInput((input: string, key: any) => {
309
+ if (input === 'q' || (key.ctrl && input === 'c')) {
310
+ userAborted = true
311
+ abortSignal.aborted = true
312
+ // Kill all active child processes
313
+ for (const cp of activeChildProcesses) {
314
+ try { cp.kill?.('SIGTERM') } catch {}
315
+ }
316
+ activeChildProcesses.clear()
317
+ exit()
318
+ }
319
+ if (key.upArrow) setFocusIdx((i: number) => Math.max(0, i - 1))
320
+ if (key.downArrow) setFocusIdx((i: number) => Math.min(challenges.length - 1, i + 1))
321
+ })
322
+
323
+ const done = challenges.filter(c => c.status === 'done').length
324
+ const failed = challenges.filter(c => c.status === 'failed' || c.status === 'timeout').length
325
+ const running = challenges.filter(c => c.status === 'running' || c.status === 'bootstrapping').length
326
+ const queued = challenges.filter(c => c.status === 'queued').length
327
+
328
+ // Progress bar
329
+ const progress = challenges.length > 0 ? (done + failed) / challenges.length : 0
330
+ const barWidth = 20
331
+ const filled = Math.round(progress * barWidth)
332
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled)
333
+
334
+ // Spinner frame
335
+ const spinFrame = SPINNER[tick % SPINNER.length]
336
+
337
+ // Detail panel lines
338
+ const focused = challenges[focusIdx]
339
+ const detailHeight = Math.min(8, Math.max(rows - challenges.length - 8, 4))
340
+ const detailLines = focused ? focused.activityLines.slice(-detailHeight) : []
341
+
342
+ return h(Box, { flexDirection: 'column', width: cols },
343
+ // ── Header ──
344
+ h(Box, { paddingX: 1, marginBottom: 1, justifyContent: 'space-between' },
345
+ h(Text, { bold: true, color: '#61dafb' }, 'LUCA CHALLENGES'),
346
+ h(Text, null,
347
+ h(Text, { dimColor: true }, `Batch ${currentBatchIndex + 1}/${numBatches} `),
348
+ h(Text, { color: 'cyan' }, bar),
349
+ h(Text, { dimColor: true }, ` ${done + failed}/${challenges.length}`),
350
+ ),
351
+ ),
352
+ // ── Stats row ──
353
+ h(Box, { paddingX: 1, marginBottom: 1, gap: 2 },
354
+ h(Text, { color: 'green' }, `${done} done`),
355
+ h(Text, { color: 'red' }, `${failed} failed`),
356
+ running > 0
357
+ ? h(Text, { color: 'cyan' }, `${running} running`)
358
+ : null,
359
+ queued > 0
360
+ ? h(Text, { dimColor: true }, `${queued} queued`)
361
+ : null,
362
+ ),
363
+ // ── Challenge rows ──
364
+ ...challenges.map((cs, i) => {
365
+ const isFocused = i === focusIdx
366
+ const elapsed = cs.status === 'queued'
367
+ ? '--:--'
368
+ : cs.status === 'done' || cs.status === 'failed' || cs.status === 'timeout'
369
+ ? formatElapsed(cs.durationMs)
370
+ : formatElapsed(Date.now() - cs.startTime)
371
+
372
+ let icon = ' · '
373
+ let iconColor = 'gray'
374
+ if (cs.status === 'bootstrapping') { icon = ' ⚙ '; iconColor = 'yellow' }
375
+ else if (cs.status === 'running') { icon = ` ${spinFrame} `; iconColor = 'cyan' }
376
+ else if (cs.status === 'done') { icon = ' ✓ '; iconColor = 'green' }
377
+ else if (cs.status === 'failed') { icon = ' ✗ '; iconColor = 'red' }
378
+ else if (cs.status === 'timeout') { icon = ' ⏱ '; iconColor = 'yellow' }
379
+
380
+ const slugDisplay = cs.slug.slice(0, 36).padEnd(36)
381
+ const elapsedDisplay = elapsed.padStart(6)
382
+ const activityWidth = Math.max(0, cols - 52)
383
+ const activity = cs.lastActivity ? cs.lastActivity.slice(0, activityWidth) : ''
384
+ const lessonsTag = cs.lessonsWritten ? ' [L]' : ''
385
+
386
+ return h(Box, { key: cs.id, paddingX: 1 },
387
+ h(Text, { color: isFocused ? 'white' : undefined, bold: isFocused, inverse: isFocused },
388
+ h(Text, { color: iconColor }, icon),
389
+ h(Text, null, slugDisplay),
390
+ h(Text, { dimColor: !isFocused }, ` ${elapsedDisplay} `),
391
+ h(Text, { dimColor: true }, activity),
392
+ cs.lessonsWritten
393
+ ? h(Text, { color: 'green', bold: true }, lessonsTag)
394
+ : null,
395
+ ),
396
+ )
397
+ }),
398
+ // ── Detail panel ──
399
+ h(Box, {
400
+ flexDirection: 'column',
401
+ borderStyle: 'round',
402
+ borderColor: focused?.status === 'running' ? 'cyan'
403
+ : focused?.status === 'done' ? 'green'
404
+ : focused?.status === 'failed' || focused?.status === 'timeout' ? 'red'
405
+ : 'gray',
406
+ paddingX: 1,
407
+ marginTop: 1,
408
+ marginX: 1,
409
+ height: detailHeight + 2,
410
+ },
411
+ h(Box, { justifyContent: 'space-between' },
412
+ h(Text, { bold: true }, focused ? focused.title : ''),
413
+ focused && focused.status !== 'queued'
414
+ ? h(Text, { dimColor: true },
415
+ focused.status === 'running' || focused.status === 'bootstrapping'
416
+ ? formatElapsed(Date.now() - focused.startTime)
417
+ : formatElapsed(focused.durationMs),
418
+ )
419
+ : null,
420
+ ),
421
+ h(Text, { wrap: 'truncate', dimColor: true },
422
+ detailLines.length > 0 ? detailLines.join('\n') : '(waiting...)',
423
+ ),
424
+ ),
425
+ // ── Footer ──
426
+ h(Box, { paddingX: 1, marginTop: 1, gap: 3 },
427
+ h(Text, { dimColor: true }, '↑↓ navigate'),
428
+ h(Text, { dimColor: true }, 'q quit'),
429
+ ),
430
+ )
431
+ }
432
+
433
+ await ink.render(h(App))
434
+ await ink.waitUntilExit()
435
+
436
+ if (userAborted) return false
437
+
438
+ await orchestrationPromise
439
+ return true
440
+ }
441
+
442
+ // ─── Main Handler ───────────────────────────────────────────────────────────
443
+
444
+ export async function tryAllChallenges(options: z.infer<typeof argsSchema>, context: ContainerContext) {
445
+ const container = context.container as any
446
+ const fs = container.feature('fs')
447
+ const paths = container.paths
448
+ const batchSize = options['batch-size']
449
+
450
+ await container.docs.load()
451
+ const allChallenges = await container.docs.queries.challenges.fetchAll()
452
+
453
+ if (allChallenges.length === 0) {
454
+ container.ui.print('No challenges found in docs/challenges/')
455
+ return
456
+ }
457
+
458
+ // Build challenge states
459
+ const numBatches = Math.ceil(allChallenges.length / batchSize)
460
+ const challengeStates: ChallengeState[] = allChallenges.map((c: any, i: number) => ({
461
+ id: c.id,
462
+ slug: c.id.split('/').pop()!,
463
+ title: c.title || c.id.split('/').pop()!,
464
+ status: 'queued' as ChallengeStatus,
465
+ startTime: 0,
466
+ durationMs: 0,
467
+ timeLimitMinutes: options['time-limit'] ?? c.meta?.maxTime ?? 5,
468
+ lastActivity: '',
469
+ activityLines: [],
470
+ lessonsWritten: false,
471
+ attemptFolder: '',
472
+ error: undefined,
473
+ batchIndex: Math.floor(i / batchSize),
474
+ }))
475
+
476
+ // Dry run — just print the schedule
477
+ if (options['dry-run']) {
478
+ container.ui.print(`\n${allChallenges.length} challenges in ${numBatches} batches of ${batchSize}:\n`)
479
+ for (let b = 0; b < numBatches; b++) {
480
+ const batch = challengeStates.filter(c => c.batchIndex === b)
481
+ container.ui.print(` Batch ${b + 1}:`)
482
+ for (const cs of batch) {
483
+ container.ui.print(` - ${cs.slug} (${cs.timeLimitMinutes}min)`)
484
+ }
485
+ }
486
+ return
487
+ }
488
+
489
+ // Create session folder
490
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
491
+ const sessionFolder = paths.resolve(`attempts/session-${timestamp}`)
492
+ fs.ensureFolder(sessionFolder)
493
+ fs.ensureFolder(`${sessionFolder}/logs`)
494
+
495
+ container.ui.print(`Session: ${sessionFolder}`)
496
+ container.ui.print(`${allChallenges.length} challenges, ${numBatches} batches of ${batchSize}\n`)
497
+
498
+ // Run dashboard
499
+ const completed = await renderDashboard(challengeStates, container, sessionFolder, batchSize)
500
+
501
+ if (!completed) {
502
+ container.ui.print('\nAborted by user.')
503
+ process.exit(1)
504
+ }
505
+
506
+ // Print summary
507
+ const done = challengeStates.filter(c => c.status === 'done').length
508
+ const failed = challengeStates.filter(c => c.status === 'failed' || c.status === 'timeout').length
509
+ const withLessons = challengeStates.filter(c => c.lessonsWritten).length
510
+
511
+ container.ui.print(`\n${'─'.repeat(60)}`)
512
+ container.ui.print(`Results: ${done} done, ${failed} failed, ${withLessons} with LESSONS.md`)
513
+ container.ui.print(`Session folder: ${sessionFolder}`)
514
+
515
+ // Write a session manifest
516
+ const manifest = challengeStates.map(cs => ({
517
+ slug: cs.slug,
518
+ status: cs.status,
519
+ durationMs: cs.durationMs,
520
+ lessonsWritten: cs.lessonsWritten,
521
+ error: cs.error,
522
+ }))
523
+ fs.ensureFile(
524
+ paths.resolve(sessionFolder, 'manifest.json'),
525
+ JSON.stringify(manifest, null, 2),
526
+ true,
527
+ )
528
+
529
+ // Synthesis
530
+ if (withLessons > 0) {
531
+ container.ui.print(`\nRunning synthesis across ${withLessons} LESSONS.md files...\n`)
532
+ await runSynthesis(challengeStates, container, sessionFolder)
533
+ container.ui.print(`\nRetro written to ${sessionFolder}/RETRO.md`)
534
+ } else {
535
+ container.ui.print('\nNo LESSONS.md files produced — skipping synthesis.')
536
+ }
537
+ }
538
+
539
+ export default {
540
+ description: 'Run all challenges in parallel batches with a live dashboard, then synthesize lessons into a retro.',
541
+ argsSchema,
542
+ handler: tryAllChallenges,
543
+ }