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,874 @@
1
+ import { z } from 'zod'
2
+ import { commands, CommandOptionsSchema } from 'luca'
3
+ import type { ContainerContext } from 'luca'
4
+ import { AGIContainer } from '../src/agi/container.server.js'
5
+
6
+ export const argsSchema = CommandOptionsSchema.extend({
7
+ model: z.string().optional().describe('OpenAI model to use'),
8
+ })
9
+
10
+ // ─── Scene Types ──────────────────────────────────────────────────────
11
+
12
+ interface SceneState {
13
+ code: string
14
+ component: ((...args: any[]) => any) | null
15
+ error: string
16
+ status: 'idle' | 'rendered' | 'interactive' | 'complete' | 'failed'
17
+ interactive: boolean
18
+ }
19
+
20
+ type Layout = 'split' | 'canvas' | 'chat'
21
+ const LAYOUT_CYCLE: Layout[] = ['split', 'canvas', 'chat']
22
+
23
+ function seedMentalState(ms: any) {
24
+ ms.set('activeSceneId', null)
25
+ ms.set('scenes', {} as Record<string, SceneState>)
26
+ ms.set('focus', 'chat' as 'chat' | 'canvas')
27
+ ms.set('layout', 'split' as Layout)
28
+ ms.set('thoughts', [] as Array<{ at: string; text: string }>)
29
+ ms.set('observations', {} as Record<string, string>)
30
+ ms.set('plan', '')
31
+ ms.set('mood', 'ready')
32
+ }
33
+
34
+ // ─── Safe Eval ────────────────────────────────────────────────────────
35
+
36
+ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
37
+
38
+ const EVAL_TIMEOUT = 15_000
39
+
40
+ async function evalWithTimeout<T>(fn: () => Promise<T>, ms: number): Promise<T> {
41
+ let timer: ReturnType<typeof setTimeout>
42
+ const timeout = new Promise<never>((_, reject) => {
43
+ timer = setTimeout(() => reject(new Error(`Scene evaluation timed out after ${ms}ms`)), ms)
44
+ })
45
+ try {
46
+ return await Promise.race([fn(), timeout])
47
+ } finally {
48
+ clearTimeout(timer!)
49
+ }
50
+ }
51
+
52
+ // ─── Main Handler ─────────────────────────────────────────────────────
53
+
54
+ export async function inkbot(options: z.infer<typeof argsSchema>, context: ContainerContext) {
55
+ const container = new AGIContainer()
56
+
57
+ // ─── Load Ink ──────────────────────────────────────────────────────
58
+ const ink = container.feature('ink', { enable: true, patchConsole: true })
59
+ await ink.loadModules()
60
+ const React = ink.React
61
+ const h = React.createElement
62
+ const { Box, Text, Spacer, Newline } = ink.components
63
+ const { useInput, useApp, useStdout } = ink.hooks
64
+ const { useState, useEffect, useRef, useCallback, useMemo } = React
65
+
66
+ // ─── Assistant ─────────────────────────────────────────────────────
67
+ const mgr = container.feature('assistantsManager')
68
+ await mgr.discover()
69
+ const assistant = mgr.create('inkbot', { model: options.model })
70
+
71
+ seedMentalState(assistant.mentalState)
72
+
73
+ // ─── Container features for scene scope ────────────────────────────
74
+ const fs = container.feature('fs')
75
+ const proc = container.feature('proc')
76
+ const ui = container.feature('ui')
77
+ const yaml = container.feature('yaml')
78
+ const grep = container.feature('grep')
79
+ const git = container.feature('git')
80
+
81
+ // ─── Scene Focus Context ───────────────────────────────────────────
82
+ // Scenes use useSceneInput() which is only active when canvas is focused.
83
+ const SceneFocusContext = React.createContext(false)
84
+
85
+ function makeUseSceneInput(onError: (err: Error) => void) {
86
+ return function useSceneInput(handler: (ch: string, key: any) => void) {
87
+ const focused = React.useContext(SceneFocusContext)
88
+ useInput(
89
+ (ch: string, key: any) => {
90
+ // Reserve Tab, Escape, and Ctrl+L (layout toggle) for the host app
91
+ if (key.tab || key.escape) return
92
+ if (key.ctrl && ch === 'l') return
93
+ try {
94
+ handler(ch, key)
95
+ } catch (err: any) {
96
+ onError(err)
97
+ }
98
+ },
99
+ { isActive: focused },
100
+ )
101
+ }
102
+ }
103
+
104
+ // ─── Error Boundary ────────────────────────────────────────────────
105
+
106
+ class SceneErrorBoundary extends React.Component<
107
+ { children: any; onError?: (err: Error) => void; fallback?: any },
108
+ { error: Error | null }
109
+ > {
110
+ constructor(props: any) {
111
+ super(props)
112
+ this.state = { error: null }
113
+ }
114
+ static getDerivedStateFromError(error: Error) {
115
+ return { error }
116
+ }
117
+ componentDidCatch(error: Error) {
118
+ this.props.onError?.(error)
119
+ }
120
+ render() {
121
+ if (this.state.error) {
122
+ return h(
123
+ Box,
124
+ { flexDirection: 'column', paddingX: 1 },
125
+ h(Text, { color: 'red', bold: true }, 'Render Error'),
126
+ h(Text, { color: 'red', wrap: 'wrap' }, this.state.error.message),
127
+ h(
128
+ Text,
129
+ { dimColor: true, wrap: 'wrap' },
130
+ this.state.error.stack?.split('\n').slice(1, 4).join('\n') || '',
131
+ ),
132
+ )
133
+ }
134
+ return this.props.children
135
+ }
136
+ }
137
+
138
+ // ─── Scene State Management ────────────────────────────────────────
139
+
140
+ let busy = false
141
+ assistant.on('turnStart', () => { busy = true })
142
+ assistant.on('response', () => { busy = false })
143
+
144
+ function getScenes(): Record<string, SceneState> {
145
+ return { ...(assistant.mentalState.get('scenes') || {}) }
146
+ }
147
+
148
+ function setScene(id: string, patch: Partial<SceneState>) {
149
+ const scenes = getScenes()
150
+ scenes[id] = {
151
+ ...(scenes[id] || { code: '', component: null, error: '', status: 'idle', interactive: false }),
152
+ ...patch,
153
+ }
154
+ assistant.mentalState.set('scenes', scenes)
155
+ }
156
+
157
+ // ─── Pending Responders ────────────────────────────────────────────
158
+ // For interactive scenes: draw() blocks until the component calls respond().
159
+ const pendingResponders = new Map<string, (result: any) => void>()
160
+
161
+ function createRespondForScene(sceneId: string) {
162
+ return (data: any) => {
163
+ const resolver = pendingResponders.get(sceneId)
164
+ if (resolver) {
165
+ pendingResponders.delete(sceneId)
166
+ setScene(sceneId, { status: 'complete' })
167
+ assistant.mentalState.set('focus', 'chat')
168
+ resolver({ status: 'completed', sceneId, response: data })
169
+ }
170
+ }
171
+ }
172
+
173
+ // ─── Scene Error Reporter ──────────────────────────────────────────
174
+ // When a scene errors and the assistant is idle, feed the error back
175
+ // so it can self-correct.
176
+ function reportSceneError(sceneId: string, error: string) {
177
+ setScene(sceneId, { error, status: 'failed' })
178
+ if (!busy) {
179
+ const errMsg = `[Scene "${sceneId}" render error]\n${error}\n\nFix the component code and redraw.`
180
+ assistant.conversation?.pushMessage({ role: 'developer', content: errMsg })
181
+ assistant.ask(errMsg).catch(() => {})
182
+ }
183
+ }
184
+
185
+ // ─── Scene Evaluator ──────────────────────────────────────────────
186
+ // Evaluates scene code in an async function scope with all APIs injected.
187
+ // The code must `return` a React component function.
188
+
189
+ async function evaluateScene(
190
+ code: string,
191
+ sceneId: string,
192
+ interactive: boolean,
193
+ ): Promise<{ component: ((...args: any[]) => any) | null; error: string | null }> {
194
+ const respondFn = createRespondForScene(sceneId)
195
+ const sceneErrorHandler = (err: Error) => reportSceneError(sceneId, err.message)
196
+ const useSceneInput = makeUseSceneInput(sceneErrorHandler)
197
+
198
+ const paramNames = [
199
+ // React core
200
+ 'h', 'React', 'Box', 'Text', 'Spacer', 'Newline',
201
+ // React hooks
202
+ 'useState', 'useEffect', 'useRef', 'useCallback', 'useMemo',
203
+ // Scene input (focus-aware, error-safe)
204
+ 'useSceneInput',
205
+ // Canvas API
206
+ 'setMental', 'getMental', 'respond',
207
+ // Container
208
+ 'container', 'fs', 'proc', 'ui', 'yaml', 'grep', 'git',
209
+ // Utilities
210
+ 'fetch', 'URL', 'Buffer', 'JSON', 'Date', 'Math', 'console',
211
+ ]
212
+
213
+ const paramValues = [
214
+ h, React, Box, Text, Spacer, Newline,
215
+ useState, useEffect, useRef, useCallback, useMemo,
216
+ useSceneInput,
217
+ (k: string, v: any) => assistant.mentalState.set(k, v),
218
+ (k: string) => assistant.mentalState.get(k),
219
+ respondFn,
220
+ container, fs, proc, ui, yaml, grep, git,
221
+ fetch, URL, Buffer, JSON, Date, Math, console,
222
+ ]
223
+
224
+ try {
225
+ const factory = new AsyncFunction(...paramNames, code)
226
+ const result = await evalWithTimeout(() => factory(...paramValues), EVAL_TIMEOUT)
227
+
228
+ if (typeof result !== 'function') {
229
+ return {
230
+ component: null,
231
+ error: `Scene code must return a React component function, got ${typeof result}. End your code with: return function Scene() { return h(Box, {}, h(Text, {}, "hello")) }`,
232
+ }
233
+ }
234
+
235
+ return { component: result, error: null }
236
+ } catch (err: any) {
237
+ return { component: null, error: err.message }
238
+ }
239
+ }
240
+
241
+ // ─── Canvas Tools ─────────────────────────────────────────────────
242
+
243
+ assistant.addTool(
244
+ 'draw',
245
+ async (args: { code: string; sceneId?: string; interactive?: boolean }) => {
246
+ const id = args.sceneId || 'default'
247
+ const interactive = !!args.interactive
248
+
249
+ const { component, error } = await evaluateScene(args.code, id, interactive)
250
+
251
+ if (error) {
252
+ setScene(id, { code: args.code, component: null, error, status: 'failed', interactive })
253
+ assistant.mentalState.set('activeSceneId', id)
254
+ return { status: 'failed', sceneId: id, error }
255
+ }
256
+
257
+ setScene(id, { code: args.code, component, error: '', status: interactive ? 'interactive' : 'rendered', interactive })
258
+ assistant.mentalState.set('activeSceneId', id)
259
+
260
+ if (interactive) {
261
+ // Block until the component calls respond()
262
+ return new Promise<any>((resolve) => {
263
+ pendingResponders.set(id, resolve)
264
+ })
265
+ }
266
+
267
+ return { status: 'rendered', sceneId: id }
268
+ },
269
+ z.object({
270
+ code: z
271
+ .string()
272
+ .describe(
273
+ 'Async function body that returns a React component function. Use h() for elements. Has access to: h, React, Box, Text, Spacer, Newline, useState, useEffect, useRef, useCallback, useMemo, useSceneInput, setMental, getMental, respond, container, fs, proc, ui, yaml, grep, git, fetch.',
274
+ ),
275
+ sceneId: z.string().optional().describe('Scene id (defaults to "default").'),
276
+ interactive: z
277
+ .boolean()
278
+ .optional()
279
+ .describe('When true, the tool call blocks until the component calls respond(data). The component should use useSceneInput() for keyboard input.'),
280
+ }).describe(
281
+ 'Render a React Ink component in the canvas pane. The code runs as an async function body and must return a React component function. Interactive scenes block until respond(data) is called.',
282
+ ),
283
+ )
284
+
285
+ assistant.addTool(
286
+ 'create_scene',
287
+ async (args: { id: string; code: string; interactive?: boolean }) => {
288
+ const { component, error } = await evaluateScene(args.code, args.id, !!args.interactive)
289
+ if (error) {
290
+ setScene(args.id, { code: args.code, component: null, error, status: 'failed', interactive: !!args.interactive })
291
+ return { created: args.id, error }
292
+ }
293
+ setScene(args.id, { code: args.code, component, error: '', status: 'idle', interactive: !!args.interactive })
294
+ if (!assistant.mentalState.get('activeSceneId')) {
295
+ assistant.mentalState.set('activeSceneId', args.id)
296
+ }
297
+ return { created: args.id, allScenes: Object.keys(getScenes()) }
298
+ },
299
+ z.object({
300
+ id: z.string().describe('Unique scene identifier'),
301
+ code: z.string().describe('Async function body returning a React component function'),
302
+ interactive: z.boolean().optional().describe('Whether this scene uses interactive APIs'),
303
+ }).describe('Create a named scene without activating it. Validates the code immediately.'),
304
+ )
305
+
306
+ assistant.addTool(
307
+ 'activate_scene',
308
+ async (args: { id: string }) => {
309
+ const scenes = getScenes()
310
+ if (!scenes[args.id]) return { error: `Scene "${args.id}" not found` }
311
+ assistant.mentalState.set('activeSceneId', args.id)
312
+ return { activeSceneId: args.id }
313
+ },
314
+ z.object({
315
+ id: z.string().describe('Scene id to display in the canvas'),
316
+ }).describe('Switch the canvas to display a different scene.'),
317
+ )
318
+
319
+ assistant.addTool(
320
+ 'get_canvas',
321
+ async () => {
322
+ const activeId = assistant.mentalState.get('activeSceneId') as string | null
323
+ const scenes = getScenes()
324
+ const currentLayout = assistant.mentalState.get('layout') || 'split'
325
+ if (!activeId || !scenes[activeId]) return { status: 'empty', layout: currentLayout, allScenes: [] }
326
+ const s = scenes[activeId]
327
+ return { sceneId: activeId, status: s.status, error: s.error, interactive: s.interactive, layout: currentLayout, allScenes: Object.keys(scenes) }
328
+ },
329
+ z.object({}).describe('Inspect the current canvas state: active scene, status, errors, scene list.'),
330
+ )
331
+
332
+ // ─── Mental State Tools ───────────────────────────────────────────
333
+
334
+ assistant.addTool(
335
+ 'think',
336
+ async (args: { text: string }) => {
337
+ const thoughts = [...(assistant.mentalState.get('thoughts') || [])]
338
+ thoughts.push({ at: new Date().toISOString(), text: args.text })
339
+ if (thoughts.length > 50) thoughts.splice(0, thoughts.length - 50)
340
+ assistant.mentalState.set('thoughts', thoughts)
341
+ return { recorded: true, totalThoughts: thoughts.length }
342
+ },
343
+ z.object({
344
+ text: z.string().describe('Your thought, observation, or internal note.'),
345
+ }).describe('Record a thought in your mental state.'),
346
+ )
347
+
348
+ assistant.addTool(
349
+ 'observe',
350
+ async (args: { key: string; value: string }) => {
351
+ const observations = { ...(assistant.mentalState.get('observations') || {}) }
352
+ observations[args.key] = args.value
353
+ assistant.mentalState.set('observations', observations)
354
+ return { recorded: true, key: args.key }
355
+ },
356
+ z.object({
357
+ key: z.string().describe('A short label for what you observed.'),
358
+ value: z.string().describe('Your observation.'),
359
+ }).describe('Record a named observation.'),
360
+ )
361
+
362
+ assistant.addTool(
363
+ 'set_plan',
364
+ async (args: { plan: string }) => {
365
+ assistant.mentalState.set('plan', args.plan)
366
+ return { updated: true }
367
+ },
368
+ z.object({
369
+ plan: z.string().describe('Your current plan of action.'),
370
+ }).describe('Set or update your current plan.'),
371
+ )
372
+
373
+ assistant.addTool(
374
+ 'set_mood',
375
+ async (args: { mood: string }) => {
376
+ assistant.mentalState.set('mood', args.mood)
377
+ return { updated: true }
378
+ },
379
+ z.object({
380
+ mood: z.string().describe('A word or short phrase describing your current state.'),
381
+ }).describe('Update your mood/status displayed in the UI header.'),
382
+ )
383
+
384
+ assistant.addTool(
385
+ 'set_layout',
386
+ async (args: { layout: string }) => {
387
+ const valid: Layout[] = ['split', 'canvas', 'chat']
388
+ const l = args.layout as Layout
389
+ if (!valid.includes(l)) return { error: `Invalid layout "${args.layout}". Must be: ${valid.join(', ')}` }
390
+ assistant.mentalState.set('layout', l)
391
+ return { updated: true, layout: l }
392
+ },
393
+ z.object({
394
+ layout: z.enum(['split', 'canvas', 'chat']).describe(
395
+ 'split = 50/50 side by side. canvas = full-width canvas with minimized chat strip. chat = full-width chat with minimized canvas strip.',
396
+ ),
397
+ }).describe('Change the UI layout. Use "canvas" when you need more screen real estate for your component. Use "chat" for conversation-heavy work. Use "split" for balanced interaction. The user can also cycle layouts with Ctrl+L.'),
398
+ )
399
+
400
+ assistant.addTool(
401
+ 'reflect',
402
+ async () => {
403
+ return {
404
+ mood: assistant.mentalState.get('mood'),
405
+ plan: assistant.mentalState.get('plan'),
406
+ layout: assistant.mentalState.get('layout'),
407
+ thoughts: assistant.mentalState.get('thoughts'),
408
+ observations: assistant.mentalState.get('observations'),
409
+ scenes: Object.keys(getScenes()),
410
+ activeScene: assistant.mentalState.get('activeSceneId'),
411
+ }
412
+ },
413
+ z.object({}).describe('Review your full mental state.'),
414
+ )
415
+
416
+ // ─── Coder Subagent ───────────────────────────────────────────────
417
+
418
+ let coderAssistant: any = null
419
+
420
+ const CODER_PREFIX = `You are answering a question from Inkbot, a canvas-rendering assistant that renders React Ink components directly in a split-pane terminal UI.
421
+
422
+ IMPORTANT CONTEXT about Inkbot's execution environment:
423
+ - Scene code is an async function body that must RETURN a React component function
424
+ - Uses h() (React.createElement) instead of JSX — no JSX compilation available
425
+ - Available in scope: h, React, Box, Text, Spacer, Newline, useState, useEffect, useRef, useCallback, useMemo
426
+ - useSceneInput(handler) for keyboard input (focus-aware, error-safe — NOT raw useInput)
427
+ - setMental(key, value) and getMental(key) for assistant mental state
428
+ - respond(data) to complete interactive scenes
429
+ - container, fs, proc, ui, yaml, grep, git — all Luca container features
430
+ - fetch, URL, Buffer, JSON, Date, Math, console — standard globals
431
+ - Top-level await works (the wrapper is async)
432
+ - No imports possible — everything via scope injection
433
+
434
+ When answering, return a SINGLE code snippet that works as a scene code body. Must end with \`return function SceneName() { ... }\`.
435
+
436
+ Inkbot's question: `
437
+
438
+ assistant.addTool(
439
+ 'ask_coder',
440
+ async (args: { question: string }) => {
441
+ if (!coderAssistant) {
442
+ coderAssistant = mgr.create('codingAssistant', { model: options.model || 'gpt-5.4' })
443
+ await coderAssistant.start()
444
+ }
445
+ const answer = await coderAssistant.ask(CODER_PREFIX + args.question)
446
+ return { answer }
447
+ },
448
+ z.object({
449
+ question: z.string().describe('Your question about the Luca framework, container APIs, or how to write scene components.'),
450
+ }).describe('Ask the coding assistant a question about the Luca framework or how to build scene components.'),
451
+ )
452
+
453
+ // ─── Ink App ──────────────────────────────────────────────────────
454
+
455
+ type Msg = { role: 'user' | 'assistant' | 'system'; content: string }
456
+
457
+ function App() {
458
+ const [messages, setMessages] = useState<Msg[]>([])
459
+ const [input, setInput] = useState('')
460
+ const [streaming, setStreaming] = useState('')
461
+ const [thinking, setThinking] = useState(false)
462
+ const [activity, setActivity] = useState('')
463
+ const [canvasError, setCanvasError] = useState('')
464
+ const [canvasStatus, setCanvasStatus] = useState('empty')
465
+ const [mood, setMood] = useState('ready')
466
+ const [focus, setFocus] = useState<'chat' | 'canvas'>('chat')
467
+ const [layout, setLayout] = useState<Layout>('split')
468
+ const [activeComponent, setActiveComponent] = useState<((...args: any[]) => any) | null>(null)
469
+ const [errorBoundaryKey, setErrorBoundaryKey] = useState(0)
470
+ const { exit } = useApp()
471
+ const { stdout } = useStdout()
472
+ const rows = stdout?.rows ?? 24
473
+ const cols = stdout?.columns ?? 80
474
+
475
+ // --- assistant events ---
476
+ useEffect(() => {
477
+ const onPreview = (text: string) => setStreaming(text)
478
+ const onToolCall = (name: string) => setActivity(`${name}`)
479
+ const onToolResult = () => setActivity('')
480
+
481
+ assistant.on('preview', onPreview)
482
+ assistant.on('toolCall', onToolCall)
483
+ assistant.on('toolResult', onToolResult)
484
+ return () => {
485
+ assistant.off('preview', onPreview)
486
+ assistant.off('toolCall', onToolCall)
487
+ assistant.off('toolResult', onToolResult)
488
+ }
489
+ }, [])
490
+
491
+ // --- mentalState → UI state ---
492
+ useEffect(() => {
493
+ const unsub = assistant.mentalState.observe((_changeType: any, key: any) => {
494
+ if (key === 'scenes' || key === 'activeSceneId') {
495
+ const activeId = assistant.mentalState.get('activeSceneId') as string | null
496
+ const scenes = (assistant.mentalState.get('scenes') as Record<string, SceneState>) || {}
497
+ if (!activeId || !scenes[activeId]) {
498
+ setActiveComponent(null)
499
+ setCanvasError('')
500
+ setCanvasStatus('empty')
501
+ return
502
+ }
503
+ const s = scenes[activeId]
504
+ setActiveComponent(() => s.component)
505
+ setCanvasError(s.error)
506
+ setCanvasStatus(s.status)
507
+ // Reset error boundary when component changes
508
+ setErrorBoundaryKey((k) => k + 1)
509
+ }
510
+ if (key === 'mood') {
511
+ setMood((assistant.mentalState.get('mood') || 'ready') as string)
512
+ }
513
+ if (key === 'focus') {
514
+ setFocus((assistant.mentalState.get('focus') || 'chat') as 'chat' | 'canvas')
515
+ }
516
+ if (key === 'layout') {
517
+ setLayout((assistant.mentalState.get('layout') || 'split') as Layout)
518
+ }
519
+ })
520
+ return unsub
521
+ }, [])
522
+
523
+ // --- keyboard (host-level) ---
524
+ useInput((ch, key) => {
525
+ if (key.escape) {
526
+ exit()
527
+ return
528
+ }
529
+
530
+ // Ctrl+L cycles layout: split → canvas → chat → split
531
+ if (key.ctrl && ch === 'l') {
532
+ const idx = LAYOUT_CYCLE.indexOf(layout)
533
+ const next = LAYOUT_CYCLE[(idx + 1) % LAYOUT_CYCLE.length]
534
+ setLayout(next)
535
+ assistant.mentalState.set('layout', next)
536
+ return
537
+ }
538
+
539
+ // Tab toggles focus
540
+ if (key.tab) {
541
+ const next = focus === 'chat' ? 'canvas' : 'chat'
542
+ setFocus(next)
543
+ assistant.mentalState.set('focus', next)
544
+ return
545
+ }
546
+
547
+ // When canvas is focused, only the scene component handles input
548
+ // (via useSceneInput which filters Tab/Escape)
549
+ if (focus === 'canvas') return
550
+
551
+ // Chat-focused input
552
+ if (key.return) {
553
+ if (thinking) return
554
+ const msg = input.trim()
555
+ if (!msg) return
556
+ setInput('')
557
+ setMessages((prev) => [...prev, { role: 'user', content: msg }])
558
+ setThinking(true)
559
+ assistant
560
+ .ask(msg)
561
+ .then((text: string) => {
562
+ setStreaming('')
563
+ setThinking(false)
564
+ setActivity('')
565
+ setMessages((prev) => [...prev, { role: 'assistant', content: text }])
566
+ })
567
+ .catch((err: any) => {
568
+ setStreaming('')
569
+ setThinking(false)
570
+ setActivity('')
571
+ setMessages((prev) => [...prev, { role: 'system', content: `error: ${err.message}` }])
572
+ })
573
+ return
574
+ }
575
+
576
+ if (key.backspace || key.delete) {
577
+ setInput((prev) => prev.slice(0, -1))
578
+ return
579
+ }
580
+
581
+ if (ch && !key.ctrl && !key.meta) {
582
+ setInput((prev) => prev + ch)
583
+ }
584
+ })
585
+
586
+ // --- render building blocks ---
587
+ const scenes = (assistant.mentalState.get('scenes') as Record<string, SceneState>) || {}
588
+ const sceneIds = Object.keys(scenes)
589
+ const activeId = (assistant.mentalState.get('activeSceneId') || '') as string
590
+ const chatFocused = focus === 'chat'
591
+ const canvasFocused = focus === 'canvas'
592
+ const layoutHint = `Ctrl+L: layout`
593
+
594
+ // Estimate available chat lines: total rows minus header, input bar, borders, padding
595
+ const chatOverhead = 6 // header + input + borders
596
+ const availLines = Math.max(4, rows - chatOverhead)
597
+ // In split layout chat is half-width; estimate wrap width accordingly
598
+ const chatCols = layout === 'split' ? Math.floor(cols / 2) - 4 : cols - 4
599
+
600
+ // Walk messages newest-first, estimating wrapped line count, until we fill the viewport
601
+ const visible: Msg[] = []
602
+ let usedLines = 0
603
+ // Cap individual message display height so one huge response doesn't eat the whole pane
604
+ const maxMsgLines = Math.max(6, Math.floor(availLines * 0.6))
605
+ for (let i = messages.length - 1; i >= 0; i--) {
606
+ const m = messages[i]
607
+ const textLen = m.content.length + 2 // prefix chars
608
+ const estimatedLines = Math.min(Math.max(1, Math.ceil(textLen / Math.max(1, chatCols))), maxMsgLines)
609
+ if (usedLines + estimatedLines > availLines && visible.length > 0) break
610
+ usedLines += estimatedLines
611
+ visible.unshift(m)
612
+ }
613
+
614
+ // Chat message list — truncate long messages to fit
615
+ const chatChildren: any[] = []
616
+ visible.forEach((m, i) => {
617
+ const color = m.role === 'user' ? 'green' : m.role === 'system' ? 'red' : 'white'
618
+ const prefix = m.role === 'user' ? '> ' : ' '
619
+ // Truncate display text if it would exceed maxMsgLines worth of characters
620
+ const maxChars = maxMsgLines * Math.max(1, chatCols)
621
+ const display = m.content.length > maxChars ? m.content.slice(0, maxChars - 1) + '…' : m.content
622
+ chatChildren.push(h(Text, { key: `msg-${i}`, wrap: 'wrap', color }, `${prefix}${display}`))
623
+ })
624
+ if (streaming) chatChildren.push(h(Text, { key: 'chat-stream', wrap: 'wrap', dimColor: true }, ` ${streaming}`))
625
+ if (thinking && !streaming) chatChildren.push(h(Text, { key: 'chat-think', color: 'yellow' }, ' thinking...'))
626
+ if (activity) chatChildren.push(h(Text, { key: 'chat-act', color: 'blue' }, ` [${activity}]`))
627
+
628
+ // Canvas rendered content
629
+ let canvasContent: any
630
+ if (activeComponent) {
631
+ canvasContent = h(
632
+ SceneFocusContext.Provider,
633
+ { value: canvasFocused },
634
+ h(
635
+ SceneErrorBoundary,
636
+ {
637
+ key: errorBoundaryKey,
638
+ onError: (err: Error) => reportSceneError(activeId, err.message),
639
+ },
640
+ h(activeComponent),
641
+ ),
642
+ )
643
+ } else if (canvasError) {
644
+ canvasContent = h(
645
+ Box,
646
+ { flexDirection: 'column', paddingX: 1 },
647
+ h(Text, { color: 'red', bold: true }, 'Error'),
648
+ h(Text, { color: 'red', wrap: 'wrap' }, canvasError),
649
+ )
650
+ } else {
651
+ canvasContent = h(Text, { dimColor: true, key: 'cvs-empty' }, ' ask inkbot to draw something')
652
+ }
653
+
654
+ // Reusable input bar
655
+ const inputBar = (active: boolean, borderColor?: string) =>
656
+ h(
657
+ Box,
658
+ {
659
+ borderStyle: 'single' as const,
660
+ borderColor: borderColor || (active ? 'gray' : 'blackBright'),
661
+ paddingX: 1,
662
+ },
663
+ h(Text, { color: active ? 'green' : 'blackBright' }, '> '),
664
+ h(Text, { dimColor: !active }, input),
665
+ active ? h(Text, { dimColor: true }, '\u2588') : null,
666
+ )
667
+
668
+ // Canvas status bar
669
+ const canvasStatusBar = h(
670
+ Box,
671
+ null,
672
+ h(Text, { dimColor: true }, ` ${canvasStatus}`),
673
+ sceneIds.length > 1
674
+ ? h(Text, { dimColor: true }, ` scenes: ${sceneIds.join(', ')} active: ${activeId}`)
675
+ : null,
676
+ )
677
+
678
+ // ─── Layout: split (50/50 side by side) ─────────────────────────
679
+ if (layout === 'split') {
680
+ return h(
681
+ Box,
682
+ { flexDirection: 'row', width: '100%', height: rows },
683
+ // Chat Pane
684
+ h(
685
+ Box,
686
+ {
687
+ key: 'chat',
688
+ flexDirection: 'column',
689
+ width: '50%',
690
+ height: rows,
691
+ borderStyle: 'round',
692
+ borderColor: chatFocused ? 'cyan' : 'gray',
693
+ paddingX: 1,
694
+ },
695
+ h(
696
+ Text,
697
+ { bold: true, color: chatFocused ? 'cyan' : 'gray' },
698
+ ' inkbot ',
699
+ h(Text, { dimColor: true }, `[${mood}]`),
700
+ !chatFocused ? h(Text, { dimColor: true }, ' (tab)') : null,
701
+ ),
702
+ h(Box, { flexDirection: 'column', flexGrow: 1, overflow: 'hidden' }, ...chatChildren),
703
+ inputBar(chatFocused),
704
+ ),
705
+ // Canvas Pane
706
+ h(
707
+ Box,
708
+ {
709
+ key: 'canvas',
710
+ flexDirection: 'column',
711
+ width: '50%',
712
+ height: rows,
713
+ borderStyle: 'round',
714
+ borderColor: canvasFocused ? 'magenta' : 'gray',
715
+ paddingX: 1,
716
+ },
717
+ h(
718
+ Text,
719
+ { bold: true, color: canvasFocused ? 'magenta' : 'gray' },
720
+ ' canvas ',
721
+ canvasStatus === 'interactive' ? h(Text, { color: 'yellow' }, '[interactive]') : null,
722
+ !canvasFocused && canvasStatus === 'interactive'
723
+ ? h(Text, { dimColor: true }, ' (tab)')
724
+ : null,
725
+ ),
726
+ h(Box, { flexDirection: 'column', flexGrow: 1, overflow: 'hidden' }, canvasContent),
727
+ canvasStatusBar,
728
+ ),
729
+ )
730
+ }
731
+
732
+ // ─── Layout: canvas (full-width canvas, chat minimized) ─────────
733
+ if (layout === 'canvas') {
734
+ // Last message for the minimized chat strip
735
+ const lastMsg = visible.length > 0 ? visible[visible.length - 1] : null
736
+ const lastMsgColor = lastMsg ? (lastMsg.role === 'user' ? 'green' : lastMsg.role === 'system' ? 'red' : 'white') : 'gray'
737
+ const statusText = thinking
738
+ ? (streaming ? streaming.slice(0, 60) + (streaming.length > 60 ? '...' : '') : 'thinking...')
739
+ : activity
740
+ ? `[${activity}]`
741
+ : lastMsg
742
+ ? `${lastMsg.role === 'user' ? '> ' : ' '}${lastMsg.content}`
743
+ : ''
744
+
745
+ return h(
746
+ Box,
747
+ { flexDirection: 'column', width: '100%', height: rows },
748
+ // Canvas — full width, takes most of the space
749
+ h(
750
+ Box,
751
+ {
752
+ key: 'canvas',
753
+ flexDirection: 'column',
754
+ width: '100%',
755
+ flexGrow: 1,
756
+ borderStyle: 'round',
757
+ borderColor: canvasFocused ? 'magenta' : 'gray',
758
+ paddingX: 1,
759
+ },
760
+ h(
761
+ Box,
762
+ null,
763
+ h(
764
+ Text,
765
+ { bold: true, color: canvasFocused ? 'magenta' : 'gray' },
766
+ ' canvas ',
767
+ canvasStatus === 'interactive' ? h(Text, { color: 'yellow' }, '[interactive] ') : null,
768
+ ),
769
+ h(Spacer),
770
+ h(Text, { dimColor: true }, layoutHint),
771
+ ),
772
+ h(Box, { flexDirection: 'column', flexGrow: 1, overflow: 'hidden' }, canvasContent),
773
+ canvasStatusBar,
774
+ ),
775
+ // Chat strip — minimized at the bottom
776
+ h(
777
+ Box,
778
+ {
779
+ key: 'chat',
780
+ flexDirection: 'column',
781
+ width: '100%',
782
+ borderStyle: 'round',
783
+ borderColor: chatFocused ? 'cyan' : 'gray',
784
+ paddingX: 1,
785
+ },
786
+ h(
787
+ Box,
788
+ null,
789
+ h(
790
+ Text,
791
+ { bold: true, color: chatFocused ? 'cyan' : 'gray' },
792
+ ' inkbot ',
793
+ h(Text, { dimColor: true }, `[${mood}] `),
794
+ ),
795
+ h(Text, {
796
+ wrap: 'truncate-end',
797
+ dimColor: !thinking,
798
+ color: thinking ? 'yellow' : lastMsgColor,
799
+ }, statusText),
800
+ ),
801
+ inputBar(chatFocused),
802
+ ),
803
+ )
804
+ }
805
+
806
+ // ─── Layout: chat (full-width chat, canvas minimized) ───────────
807
+ return h(
808
+ Box,
809
+ { flexDirection: 'column', width: '100%', height: rows },
810
+ // Chat — full width, takes most of the space
811
+ h(
812
+ Box,
813
+ {
814
+ key: 'chat',
815
+ flexDirection: 'column',
816
+ width: '100%',
817
+ flexGrow: 1,
818
+ borderStyle: 'round',
819
+ borderColor: chatFocused ? 'cyan' : 'gray',
820
+ paddingX: 1,
821
+ },
822
+ h(
823
+ Box,
824
+ null,
825
+ h(
826
+ Text,
827
+ { bold: true, color: chatFocused ? 'cyan' : 'gray' },
828
+ ' inkbot ',
829
+ h(Text, { dimColor: true }, `[${mood}]`),
830
+ ),
831
+ h(Spacer),
832
+ h(Text, { dimColor: true }, layoutHint),
833
+ ),
834
+ h(Box, { flexDirection: 'column', flexGrow: 1, overflow: 'hidden' }, ...chatChildren),
835
+ inputBar(chatFocused),
836
+ ),
837
+ // Canvas strip — minimized at the bottom
838
+ h(
839
+ Box,
840
+ {
841
+ key: 'canvas',
842
+ flexDirection: 'row',
843
+ width: '100%',
844
+ borderStyle: 'round',
845
+ borderColor: canvasFocused ? 'magenta' : 'gray',
846
+ paddingX: 1,
847
+ },
848
+ h(
849
+ Text,
850
+ { bold: true, color: canvasFocused ? 'magenta' : 'gray' },
851
+ ' canvas ',
852
+ ),
853
+ h(Text, { dimColor: true }, `${canvasStatus}`),
854
+ sceneIds.length > 0
855
+ ? h(Text, { dimColor: true }, ` [${activeId || 'none'}]`)
856
+ : null,
857
+ canvasStatus === 'interactive'
858
+ ? h(Text, { color: 'yellow' }, ' (tab to interact)')
859
+ : null,
860
+ h(Spacer),
861
+ h(Text, { dimColor: true }, layoutHint),
862
+ ),
863
+ )
864
+ }
865
+
866
+ await ink.render(h(App))
867
+ await ink.waitUntilExit()
868
+ }
869
+
870
+ export default {
871
+ description: 'Launch the Inkbot split-pane assistant with a live canvas.',
872
+ argsSchema,
873
+ handler: inkbot,
874
+ }