@soederpop/luca 0.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 (358) hide show
  1. package/CLAUDE.md +71 -0
  2. package/README.md +78 -0
  3. package/bun.lock +2928 -0
  4. package/bunfig.toml +3 -0
  5. package/commands/audit-docs.ts +740 -0
  6. package/commands/build-scaffolds.ts +154 -0
  7. package/commands/generate-api-docs.ts +114 -0
  8. package/commands/update-introspection.ts +67 -0
  9. package/docs/CLI.md +335 -0
  10. package/docs/README.md +88 -0
  11. package/docs/TABLE-OF-CONTENTS.md +157 -0
  12. package/docs/apis/clients/elevenlabs.md +84 -0
  13. package/docs/apis/clients/graph.md +56 -0
  14. package/docs/apis/clients/openai.md +69 -0
  15. package/docs/apis/clients/rest.md +41 -0
  16. package/docs/apis/clients/websocket.md +107 -0
  17. package/docs/apis/features/agi/assistant.md +471 -0
  18. package/docs/apis/features/agi/assistants-manager.md +154 -0
  19. package/docs/apis/features/agi/claude-code.md +602 -0
  20. package/docs/apis/features/agi/conversation-history.md +352 -0
  21. package/docs/apis/features/agi/conversation.md +333 -0
  22. package/docs/apis/features/agi/docs-reader.md +121 -0
  23. package/docs/apis/features/agi/openai-codex.md +318 -0
  24. package/docs/apis/features/agi/openapi.md +138 -0
  25. package/docs/apis/features/agi/semantic-search.md +387 -0
  26. package/docs/apis/features/agi/skills-library.md +216 -0
  27. package/docs/apis/features/node/container-link.md +133 -0
  28. package/docs/apis/features/node/content-db.md +313 -0
  29. package/docs/apis/features/node/disk-cache.md +379 -0
  30. package/docs/apis/features/node/dns.md +651 -0
  31. package/docs/apis/features/node/docker.md +705 -0
  32. package/docs/apis/features/node/downloader.md +81 -0
  33. package/docs/apis/features/node/esbuild.md +59 -0
  34. package/docs/apis/features/node/file-manager.md +182 -0
  35. package/docs/apis/features/node/fs.md +581 -0
  36. package/docs/apis/features/node/git.md +330 -0
  37. package/docs/apis/features/node/google-auth.md +174 -0
  38. package/docs/apis/features/node/google-calendar.md +187 -0
  39. package/docs/apis/features/node/google-docs.md +151 -0
  40. package/docs/apis/features/node/google-drive.md +225 -0
  41. package/docs/apis/features/node/google-sheets.md +179 -0
  42. package/docs/apis/features/node/grep.md +290 -0
  43. package/docs/apis/features/node/helpers.md +135 -0
  44. package/docs/apis/features/node/ink.md +334 -0
  45. package/docs/apis/features/node/ipc-socket.md +260 -0
  46. package/docs/apis/features/node/json-tree.md +86 -0
  47. package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
  48. package/docs/apis/features/node/networking.md +281 -0
  49. package/docs/apis/features/node/nlp.md +133 -0
  50. package/docs/apis/features/node/opener.md +97 -0
  51. package/docs/apis/features/node/os.md +118 -0
  52. package/docs/apis/features/node/package-finder.md +402 -0
  53. package/docs/apis/features/node/postgres.md +212 -0
  54. package/docs/apis/features/node/proc.md +430 -0
  55. package/docs/apis/features/node/process-manager.md +210 -0
  56. package/docs/apis/features/node/python.md +278 -0
  57. package/docs/apis/features/node/repl.md +88 -0
  58. package/docs/apis/features/node/runpod.md +673 -0
  59. package/docs/apis/features/node/secure-shell.md +169 -0
  60. package/docs/apis/features/node/semantic-search.md +401 -0
  61. package/docs/apis/features/node/sqlite.md +211 -0
  62. package/docs/apis/features/node/telegram.md +254 -0
  63. package/docs/apis/features/node/tts.md +118 -0
  64. package/docs/apis/features/node/ui.md +703 -0
  65. package/docs/apis/features/node/vault.md +64 -0
  66. package/docs/apis/features/node/vm.md +84 -0
  67. package/docs/apis/features/node/window-manager.md +337 -0
  68. package/docs/apis/features/node/yaml-tree.md +85 -0
  69. package/docs/apis/features/node/yaml.md +176 -0
  70. package/docs/apis/features/web/asset-loader.md +47 -0
  71. package/docs/apis/features/web/container-link.md +133 -0
  72. package/docs/apis/features/web/esbuild.md +59 -0
  73. package/docs/apis/features/web/helpers.md +135 -0
  74. package/docs/apis/features/web/network.md +30 -0
  75. package/docs/apis/features/web/speech.md +55 -0
  76. package/docs/apis/features/web/vault.md +64 -0
  77. package/docs/apis/features/web/vm.md +84 -0
  78. package/docs/apis/features/web/voice.md +67 -0
  79. package/docs/apis/servers/express.md +127 -0
  80. package/docs/apis/servers/mcp.md +213 -0
  81. package/docs/apis/servers/websocket.md +99 -0
  82. package/docs/documentation-audit.md +134 -0
  83. package/docs/examples/content-db.md +77 -0
  84. package/docs/examples/disk-cache.md +83 -0
  85. package/docs/examples/docker.md +101 -0
  86. package/docs/examples/downloader.md +70 -0
  87. package/docs/examples/esbuild.md +80 -0
  88. package/docs/examples/file-manager.md +82 -0
  89. package/docs/examples/fs.md +83 -0
  90. package/docs/examples/git.md +85 -0
  91. package/docs/examples/google-auth.md +88 -0
  92. package/docs/examples/google-calendar.md +94 -0
  93. package/docs/examples/google-docs.md +82 -0
  94. package/docs/examples/google-drive.md +96 -0
  95. package/docs/examples/google-sheets.md +95 -0
  96. package/docs/examples/grep.md +85 -0
  97. package/docs/examples/ink-blocks.md +75 -0
  98. package/docs/examples/ink-renderer.md +41 -0
  99. package/docs/examples/ink.md +103 -0
  100. package/docs/examples/ipc-socket.md +103 -0
  101. package/docs/examples/json-tree.md +91 -0
  102. package/docs/examples/launcher-app-command-listener.md +120 -0
  103. package/docs/examples/networking.md +58 -0
  104. package/docs/examples/nlp.md +91 -0
  105. package/docs/examples/opener.md +78 -0
  106. package/docs/examples/os.md +72 -0
  107. package/docs/examples/package-finder.md +89 -0
  108. package/docs/examples/port-exposer.md +89 -0
  109. package/docs/examples/postgres.md +91 -0
  110. package/docs/examples/proc.md +81 -0
  111. package/docs/examples/process-manager.md +79 -0
  112. package/docs/examples/python.md +91 -0
  113. package/docs/examples/repl.md +93 -0
  114. package/docs/examples/runpod.md +119 -0
  115. package/docs/examples/secure-shell.md +92 -0
  116. package/docs/examples/sqlite.md +86 -0
  117. package/docs/examples/telegram.md +77 -0
  118. package/docs/examples/tts.md +86 -0
  119. package/docs/examples/ui.md +80 -0
  120. package/docs/examples/vault.md +70 -0
  121. package/docs/examples/vm.md +86 -0
  122. package/docs/examples/window-manager.md +125 -0
  123. package/docs/examples/yaml-tree.md +93 -0
  124. package/docs/examples/yaml.md +104 -0
  125. package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
  126. package/docs/ideas/container-use-api.md +9 -0
  127. package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
  128. package/docs/ideas/feature-stacks.md +22 -0
  129. package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
  130. package/docs/ideas/mcp-design.md +9 -0
  131. package/docs/ideas/web-container-debugging-feature.md +13 -0
  132. package/docs/introspection-audit.md +49 -0
  133. package/docs/introspection.md +154 -0
  134. package/docs/mcp/readme.md +162 -0
  135. package/docs/models.ts +38 -0
  136. package/docs/philosophy.md +85 -0
  137. package/docs/principles.md +7 -0
  138. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  139. package/docs/prompts/mcp-test-easy-command.md +27 -0
  140. package/docs/reports/assistant-bugs.md +38 -0
  141. package/docs/reports/attach-pattern-usage.md +18 -0
  142. package/docs/reports/code-audit-results.md +391 -0
  143. package/docs/reports/introspection-audit-tasks.md +378 -0
  144. package/docs/reports/luca-mcp-improvements.md +128 -0
  145. package/docs/scaffolds/client.md +140 -0
  146. package/docs/scaffolds/command.md +106 -0
  147. package/docs/scaffolds/endpoint.md +176 -0
  148. package/docs/scaffolds/feature.md +148 -0
  149. package/docs/scaffolds/server.md +187 -0
  150. package/docs/tasks/web-container-helper-discovery.md +71 -0
  151. package/docs/todos.md +1 -0
  152. package/docs/tutorials/01-getting-started.md +106 -0
  153. package/docs/tutorials/02-container.md +210 -0
  154. package/docs/tutorials/03-scripts.md +194 -0
  155. package/docs/tutorials/04-features-overview.md +196 -0
  156. package/docs/tutorials/05-state-and-events.md +171 -0
  157. package/docs/tutorials/06-servers.md +157 -0
  158. package/docs/tutorials/07-endpoints.md +198 -0
  159. package/docs/tutorials/08-commands.md +171 -0
  160. package/docs/tutorials/09-clients.md +162 -0
  161. package/docs/tutorials/10-creating-features.md +198 -0
  162. package/docs/tutorials/11-contentbase.md +191 -0
  163. package/docs/tutorials/12-assistants.md +215 -0
  164. package/docs/tutorials/13-introspection.md +147 -0
  165. package/docs/tutorials/14-type-system.md +174 -0
  166. package/docs/tutorials/15-project-patterns.md +222 -0
  167. package/docs/tutorials/16-google-features.md +534 -0
  168. package/docs/tutorials/17-tui-blocks.md +530 -0
  169. package/docs/tutorials/18-semantic-search.md +334 -0
  170. package/index.ts +1 -0
  171. package/luca.console.ts +9 -0
  172. package/main.py +6 -0
  173. package/package.json +154 -0
  174. package/pyproject.toml +7 -0
  175. package/scripts/animations/chrome-glitch.ts +55 -0
  176. package/scripts/animations/index.ts +16 -0
  177. package/scripts/animations/neon-pulse.ts +64 -0
  178. package/scripts/animations/types.ts +6 -0
  179. package/scripts/build-web.ts +28 -0
  180. package/scripts/examples/ask-luca-expert.ts +42 -0
  181. package/scripts/examples/assistant-questions.ts +12 -0
  182. package/scripts/examples/excalidraw-expert.ts +75 -0
  183. package/scripts/examples/expert-chat.ts +0 -0
  184. package/scripts/examples/file-manager.ts +14 -0
  185. package/scripts/examples/ideas.ts +12 -0
  186. package/scripts/examples/interactive-chat.ts +20 -0
  187. package/scripts/examples/openai-tool-calls.ts +113 -0
  188. package/scripts/examples/opening-a-web-browser.ts +5 -0
  189. package/scripts/examples/telegram-bot.ts +79 -0
  190. package/scripts/examples/telegram-ink-ui.ts +302 -0
  191. package/scripts/examples/using-assistant-with-mcp.ts +560 -0
  192. package/scripts/examples/using-claude-code.ts +10 -0
  193. package/scripts/examples/using-contentdb.ts +35 -0
  194. package/scripts/examples/using-conversations.ts +35 -0
  195. package/scripts/examples/using-disk-cache.ts +10 -0
  196. package/scripts/examples/using-docker-shell.ts +75 -0
  197. package/scripts/examples/using-elevenlabs.ts +25 -0
  198. package/scripts/examples/using-google-calendar.ts +57 -0
  199. package/scripts/examples/using-google-docs.ts +74 -0
  200. package/scripts/examples/using-google-drive.ts +74 -0
  201. package/scripts/examples/using-google-sheets.ts +89 -0
  202. package/scripts/examples/using-nlp.ts +55 -0
  203. package/scripts/examples/using-ollama.ts +10 -0
  204. package/scripts/examples/using-openai-codex.ts +23 -0
  205. package/scripts/examples/using-postgres.ts +55 -0
  206. package/scripts/examples/using-runpod.ts +32 -0
  207. package/scripts/examples/using-tts.ts +40 -0
  208. package/scripts/examples/vm-loading-esm-modules.ts +16 -0
  209. package/scripts/scaffold.ts +391 -0
  210. package/scripts/scratch.ts +15 -0
  211. package/scripts/test-command-listener.ts +123 -0
  212. package/scripts/test-window-manager-lifecycle.ts +86 -0
  213. package/scripts/test-window-manager.ts +43 -0
  214. package/scripts/update-introspection-data.ts +58 -0
  215. package/src/agi/README.md +14 -0
  216. package/src/agi/container.server.ts +114 -0
  217. package/src/agi/endpoints/ask.ts +60 -0
  218. package/src/agi/endpoints/conversations/[id].ts +45 -0
  219. package/src/agi/endpoints/conversations.ts +31 -0
  220. package/src/agi/endpoints/experts.ts +37 -0
  221. package/src/agi/features/assistant.ts +767 -0
  222. package/src/agi/features/assistants-manager.ts +260 -0
  223. package/src/agi/features/claude-code.ts +1111 -0
  224. package/src/agi/features/conversation-history.ts +497 -0
  225. package/src/agi/features/conversation.ts +799 -0
  226. package/src/agi/features/openai-codex.ts +631 -0
  227. package/src/agi/features/openapi.ts +438 -0
  228. package/src/agi/features/skills-library.ts +425 -0
  229. package/src/agi/index.ts +6 -0
  230. package/src/agi/lib/token-counter.ts +122 -0
  231. package/src/browser.ts +25 -0
  232. package/src/bus.ts +100 -0
  233. package/src/cli/cli.ts +70 -0
  234. package/src/client.ts +461 -0
  235. package/src/clients/civitai/index.ts +541 -0
  236. package/src/clients/client-template.ts +41 -0
  237. package/src/clients/comfyui/index.ts +597 -0
  238. package/src/clients/elevenlabs/index.ts +291 -0
  239. package/src/clients/openai/index.ts +451 -0
  240. package/src/clients/supabase/index.ts +366 -0
  241. package/src/command.ts +164 -0
  242. package/src/commands/chat.ts +182 -0
  243. package/src/commands/console.ts +192 -0
  244. package/src/commands/describe.ts +433 -0
  245. package/src/commands/eval.ts +116 -0
  246. package/src/commands/help.ts +214 -0
  247. package/src/commands/index.ts +14 -0
  248. package/src/commands/mcp.ts +64 -0
  249. package/src/commands/prompt.ts +807 -0
  250. package/src/commands/run.ts +257 -0
  251. package/src/commands/sandbox-mcp.ts +439 -0
  252. package/src/commands/scaffold.ts +79 -0
  253. package/src/commands/serve.ts +172 -0
  254. package/src/container.ts +781 -0
  255. package/src/endpoint.ts +340 -0
  256. package/src/feature.ts +75 -0
  257. package/src/hash-object.ts +97 -0
  258. package/src/helper.ts +543 -0
  259. package/src/introspection/generated.agi.ts +23388 -0
  260. package/src/introspection/generated.node.ts +18899 -0
  261. package/src/introspection/generated.web.ts +2021 -0
  262. package/src/introspection/index.ts +256 -0
  263. package/src/introspection/scan.ts +912 -0
  264. package/src/node/container.ts +354 -0
  265. package/src/node/feature.ts +13 -0
  266. package/src/node/features/container-link.ts +558 -0
  267. package/src/node/features/content-db.ts +475 -0
  268. package/src/node/features/disk-cache.ts +382 -0
  269. package/src/node/features/dns.ts +655 -0
  270. package/src/node/features/docker.ts +912 -0
  271. package/src/node/features/downloader.ts +92 -0
  272. package/src/node/features/esbuild.ts +68 -0
  273. package/src/node/features/file-manager.ts +357 -0
  274. package/src/node/features/fs.ts +534 -0
  275. package/src/node/features/git.ts +492 -0
  276. package/src/node/features/google-auth.ts +502 -0
  277. package/src/node/features/google-calendar.ts +300 -0
  278. package/src/node/features/google-docs.ts +404 -0
  279. package/src/node/features/google-drive.ts +339 -0
  280. package/src/node/features/google-sheets.ts +279 -0
  281. package/src/node/features/grep.ts +406 -0
  282. package/src/node/features/helpers.ts +374 -0
  283. package/src/node/features/ink.ts +490 -0
  284. package/src/node/features/ipc-socket.ts +459 -0
  285. package/src/node/features/json-tree.ts +188 -0
  286. package/src/node/features/launcher-app-command-listener.ts +388 -0
  287. package/src/node/features/networking.ts +925 -0
  288. package/src/node/features/nlp.ts +211 -0
  289. package/src/node/features/opener.ts +166 -0
  290. package/src/node/features/os.ts +157 -0
  291. package/src/node/features/package-finder.ts +539 -0
  292. package/src/node/features/port-exposer.ts +342 -0
  293. package/src/node/features/postgres.ts +273 -0
  294. package/src/node/features/proc.ts +502 -0
  295. package/src/node/features/process-manager.ts +542 -0
  296. package/src/node/features/python.ts +444 -0
  297. package/src/node/features/repl.ts +194 -0
  298. package/src/node/features/runpod.ts +802 -0
  299. package/src/node/features/secure-shell.ts +248 -0
  300. package/src/node/features/semantic-search.ts +924 -0
  301. package/src/node/features/sqlite.ts +289 -0
  302. package/src/node/features/telegram.ts +342 -0
  303. package/src/node/features/tts.ts +184 -0
  304. package/src/node/features/ui.ts +857 -0
  305. package/src/node/features/vault.ts +164 -0
  306. package/src/node/features/vm.ts +312 -0
  307. package/src/node/features/window-manager.ts +804 -0
  308. package/src/node/features/yaml-tree.ts +149 -0
  309. package/src/node/features/yaml.ts +132 -0
  310. package/src/node.ts +70 -0
  311. package/src/react/index.ts +175 -0
  312. package/src/registry.ts +199 -0
  313. package/src/scaffolds/generated.ts +1613 -0
  314. package/src/scaffolds/template.ts +37 -0
  315. package/src/schemas/base.ts +255 -0
  316. package/src/server.ts +135 -0
  317. package/src/servers/express.ts +209 -0
  318. package/src/servers/mcp.ts +805 -0
  319. package/src/servers/socket.ts +120 -0
  320. package/src/state.ts +101 -0
  321. package/src/web/clients/socket.ts +82 -0
  322. package/src/web/container.ts +74 -0
  323. package/src/web/extension.ts +30 -0
  324. package/src/web/feature.ts +12 -0
  325. package/src/web/features/asset-loader.ts +64 -0
  326. package/src/web/features/container-link.ts +385 -0
  327. package/src/web/features/esbuild.ts +79 -0
  328. package/src/web/features/helpers.ts +267 -0
  329. package/src/web/features/network.ts +61 -0
  330. package/src/web/features/speech.ts +87 -0
  331. package/src/web/features/vault.ts +189 -0
  332. package/src/web/features/vm.ts +78 -0
  333. package/src/web/features/voice-recognition.ts +129 -0
  334. package/src/web/shims/isomorphic-vm.ts +149 -0
  335. package/test/bus.test.ts +134 -0
  336. package/test/clients-servers.test.ts +216 -0
  337. package/test/container-link.test.ts +274 -0
  338. package/test/features.test.ts +160 -0
  339. package/test/integration.test.ts +787 -0
  340. package/test/node-container.test.ts +121 -0
  341. package/test/rate-limit.test.ts +272 -0
  342. package/test/semantic-search.test.ts +550 -0
  343. package/test/state.test.ts +121 -0
  344. package/test-integration/assistant.test.ts +138 -0
  345. package/test-integration/assistants-manager.test.ts +123 -0
  346. package/test-integration/claude-code.test.ts +98 -0
  347. package/test-integration/conversation-history.test.ts +205 -0
  348. package/test-integration/conversation.test.ts +137 -0
  349. package/test-integration/elevenlabs.test.ts +55 -0
  350. package/test-integration/google-services.test.ts +80 -0
  351. package/test-integration/helpers.ts +89 -0
  352. package/test-integration/openai-codex.test.ts +93 -0
  353. package/test-integration/runpod.test.ts +58 -0
  354. package/test-integration/server-endpoints.test.ts +97 -0
  355. package/test-integration/skills-library.test.ts +157 -0
  356. package/test-integration/telegram.test.ts +46 -0
  357. package/tsconfig.json +58 -0
  358. package/uv.lock +8 -0
@@ -0,0 +1,192 @@
1
+ import { z } from 'zod'
2
+ import { commands } from '../command.js'
3
+ import { CommandOptionsSchema } from '../schemas/base.js'
4
+ import type { ContainerContext } from '../container.js'
5
+
6
+ declare module '../command.js' {
7
+ interface AvailableCommands {
8
+ console: ReturnType<typeof commands.registerHandler>
9
+ }
10
+ }
11
+
12
+ export const argsSchema = CommandOptionsSchema.extend({
13
+ enable: z.string().optional().describe('Enable a feature before starting the REPL (e.g. --enable diskCache)'),
14
+ eval: z.string().optional().describe('Evaluate code, a script, or markdown file before dropping into the REPL'),
15
+ })
16
+
17
+ function resolveEvalTarget(ref: string, container: any): { type: 'code' | 'script' | 'markdown', value: string } {
18
+ const candidates = [ref, `${ref}.ts`, `${ref}.js`, `${ref}.md`]
19
+
20
+ for (const candidate of candidates) {
21
+ const resolved = container.paths.resolve(candidate)
22
+ if (container.fs.exists(resolved)) {
23
+ if (resolved.endsWith('.md')) return { type: 'markdown', value: resolved }
24
+ return { type: 'script', value: resolved }
25
+ }
26
+ }
27
+
28
+ // Not a file — treat as inline code
29
+ return { type: 'code', value: ref }
30
+ }
31
+
32
+ async function evalBeforeRepl(evalArg: string, container: any, featureContext: Record<string, any>): Promise<Record<string, any>> {
33
+ const target = resolveEvalTarget(evalArg, container)
34
+ const vm = container.feature('vm')
35
+ const ui = container.feature('ui')
36
+ const extraContext: Record<string, any> = {}
37
+
38
+ if (target.type === 'markdown') {
39
+ await container.docs.load()
40
+ const doc = await container.docs.parseMarkdownAtPath(target.value)
41
+ const esbuild = container.feature('esbuild')
42
+ const shared = vm.createContext({
43
+ console, fetch, URL, URLSearchParams,
44
+ setTimeout, clearTimeout, setInterval, clearInterval,
45
+ ...featureContext,
46
+ ...container.context,
47
+ })
48
+
49
+ const children = doc.ast.children
50
+ for (let i = 0; i < children.length; i++) {
51
+ const node = children[i]
52
+ if (node.type === 'code') {
53
+ const { value, lang, meta } = node
54
+ if (lang !== 'ts' && lang !== 'js' && lang !== 'tsx' && lang !== 'jsx') continue
55
+ if (meta && typeof meta === 'string' && meta.toLowerCase().includes('skip')) continue
56
+
57
+ console.log(ui.markdown(['```' + lang, value, '```'].join('\n')))
58
+
59
+ const needsTransform = lang === 'tsx' || lang === 'jsx'
60
+ let code = value
61
+ if (needsTransform) {
62
+ const { code: transformed } = esbuild.transformSync(value, { loader: lang as 'tsx' | 'jsx', format: 'cjs' })
63
+ code = transformed
64
+ }
65
+
66
+ const hasTopLevelAwait = /\bawait\b/.test(code)
67
+ code = hasTopLevelAwait ? `(async function() { ${code} })()` : code
68
+
69
+ await vm.run(code, shared)
70
+ Object.assign(shared, container.context)
71
+ } else {
72
+ const md = doc.stringify({ type: 'root', children: [node] })
73
+ console.log(ui.markdown(md))
74
+ }
75
+ }
76
+
77
+ Object.assign(extraContext, shared)
78
+ } else if (target.type === 'script') {
79
+ const code = container.fs.readFile(target.value, 'utf8')
80
+ const ctx = vm.createContext({
81
+ console, fetch, URL, URLSearchParams,
82
+ setTimeout, clearTimeout, setInterval, clearInterval,
83
+ ...featureContext,
84
+ ...container.context,
85
+ })
86
+ await vm.run(code, ctx)
87
+ Object.assign(extraContext, ctx)
88
+ } else {
89
+ const ctx = vm.createContext({
90
+ console, fetch, URL, URLSearchParams,
91
+ setTimeout, clearTimeout, setInterval, clearInterval,
92
+ ...featureContext,
93
+ ...container.context,
94
+ })
95
+ await vm.run(target.value, ctx)
96
+ Object.assign(extraContext, ctx)
97
+ }
98
+
99
+ return extraContext
100
+ }
101
+
102
+ export default async function lucaConsole(options: z.infer<typeof argsSchema>, context: ContainerContext) {
103
+ const container = context.container as any
104
+ const ui = container.feature('ui')
105
+
106
+ await container.helpers.discoverAll()
107
+
108
+ // make it easy to create features
109
+ container.addContext('feature', (...args: any) => container.feature(...args))
110
+
111
+ //this is a hack to make it so we can enable things before the console starts
112
+ if (container.argv.enable) {
113
+ for (const id of Array(container.argv.enable)) {
114
+ try {
115
+ container.feature(id, { ...container.argv, enable: true }).enable()
116
+ } catch(error: any) {
117
+ console.error(`Error enabling feature`, error.message)
118
+ }
119
+ }
120
+ }
121
+
122
+ const featureContext: Record<string, any> = {}
123
+ for (const name of container.features.available) {
124
+ try {
125
+ featureContext[name] = container.feature(name)
126
+ } catch {}
127
+ }
128
+
129
+ // Load user console module if present
130
+ const consoleModulePath = container.paths.resolve('luca.console.ts')
131
+ let consoleModuleLoaded = false
132
+ let consoleModuleError: Error | null = null
133
+
134
+ if (container.fs.exists(consoleModulePath)) {
135
+ try {
136
+ const vmFeature = container.feature('vm')
137
+ const userExports = vmFeature.loadModule(consoleModulePath, { container, console })
138
+ Object.assign(featureContext, userExports)
139
+ consoleModuleLoaded = true
140
+ } catch (err: any) {
141
+ consoleModuleError = err
142
+ }
143
+ }
144
+
145
+ // Run --eval target before starting the REPL
146
+ let evalContext: Record<string, any> = {}
147
+ if (options.eval) {
148
+ try {
149
+ evalContext = await evalBeforeRepl(options.eval, container, featureContext)
150
+ } catch (err: any) {
151
+ console.error(ui.colors.red(` Error evaluating: ${err.message}`))
152
+ }
153
+ }
154
+
155
+ const prompt = ui.colors.cyan('luca') + ui.colors.dim(' > ')
156
+
157
+ console.log()
158
+ console.log(ui.colors.dim(' Luca REPL — all container features in scope. Tab to autocomplete.'))
159
+ if (options.eval) {
160
+ console.log(ui.colors.dim(` Evaluated: ${options.eval}`))
161
+ }
162
+ if (consoleModuleLoaded) {
163
+ console.log(ui.colors.dim(' Loaded luca.console.ts exports into scope.'))
164
+ } else if (consoleModuleError) {
165
+ console.log(ui.colors.yellow(' ⚠ Failed to load luca.console.ts:'))
166
+ console.log(ui.colors.yellow(` ${consoleModuleError.message}`))
167
+ console.log(ui.colors.dim(' The REPL will start without your custom exports.'))
168
+ }
169
+ console.log(ui.colors.dim(' Type .exit to quit.'))
170
+ console.log()
171
+
172
+ const repl = container.feature('repl', { prompt })
173
+ await repl.start({
174
+ context: {
175
+ ...featureContext,
176
+ ...evalContext,
177
+ console,
178
+ setTimeout,
179
+ setInterval,
180
+ clearTimeout,
181
+ clearInterval,
182
+ fetch,
183
+ Bun,
184
+ },
185
+ })
186
+ }
187
+
188
+ commands.registerHandler('console', {
189
+ description: 'Start an interactive REPL with all container features in scope',
190
+ argsSchema,
191
+ handler: lucaConsole,
192
+ })
@@ -0,0 +1,433 @@
1
+ import { z } from 'zod'
2
+ import { commands } from '../command.js'
3
+ import { CommandOptionsSchema } from '../schemas/base.js'
4
+ import type { ContainerContext } from '../container.js'
5
+ import type { IntrospectionSection } from '../introspection/index.js'
6
+
7
+ declare module '../command.js' {
8
+ interface AvailableCommands {
9
+ describe: ReturnType<typeof commands.registerHandler>
10
+ }
11
+ }
12
+
13
+ const REGISTRY_NAMES = ['features', 'clients', 'servers', 'commands', 'endpoints'] as const
14
+ type RegistryName = (typeof REGISTRY_NAMES)[number]
15
+
16
+ /** Maps flag names to the section they represent. 'description' is handled specially. */
17
+ const SECTION_FLAGS: Record<string, IntrospectionSection | 'description'> = {
18
+ // Clean flag names (combinable)
19
+ 'description': 'description',
20
+ 'usage': 'usage',
21
+ 'methods': 'methods',
22
+ 'getters': 'getters',
23
+ 'events': 'events',
24
+ 'state': 'state',
25
+ 'options': 'options',
26
+ 'env-vars': 'envVars',
27
+ 'envvars': 'envVars',
28
+ 'examples': 'examples',
29
+ // Legacy --only-* flags (still work, map into same system)
30
+ 'only-methods': 'methods',
31
+ 'only-getters': 'getters',
32
+ 'only-events': 'events',
33
+ 'only-state': 'state',
34
+ 'only-options': 'options',
35
+ 'only-env-vars': 'envVars',
36
+ 'only-envvars': 'envVars',
37
+ 'only-examples': 'examples',
38
+ }
39
+
40
+ export const argsSchema = CommandOptionsSchema.extend({
41
+ json: z.boolean().default(false).describe('Output introspection data as JSON instead of markdown'),
42
+ pretty: z.boolean().default(false).describe('Render markdown with terminal styling via ui.markdown'),
43
+ title: z.boolean().default(true).describe('Include the title header in the output (use --no-title to omit)'),
44
+ // Clean section flags (can be combined: --description --usage)
45
+ description: z.boolean().default(false).describe('Show the description section'),
46
+ usage: z.boolean().default(false).describe('Show the usage section'),
47
+ methods: z.boolean().default(false).describe('Show the methods section'),
48
+ getters: z.boolean().default(false).describe('Show the getters section'),
49
+ events: z.boolean().default(false).describe('Show the events section'),
50
+ state: z.boolean().default(false).describe('Show the state section'),
51
+ options: z.boolean().default(false).describe('Show the options section'),
52
+ 'env-vars': z.boolean().default(false).describe('Show the envVars section'),
53
+ envvars: z.boolean().default(false).describe('Show the envVars section'),
54
+ examples: z.boolean().default(false).describe('Show the examples section'),
55
+ // Legacy --only-* flags
56
+ 'only-methods': z.boolean().default(false).describe('Show only the methods section'),
57
+ 'only-getters': z.boolean().default(false).describe('Show only the getters section'),
58
+ 'only-events': z.boolean().default(false).describe('Show only the events section'),
59
+ 'only-state': z.boolean().default(false).describe('Show only the state section'),
60
+ 'only-options': z.boolean().default(false).describe('Show only the options section'),
61
+ 'only-env-vars': z.boolean().default(false).describe('Show only the envVars section'),
62
+ 'only-envvars': z.boolean().default(false).describe('Show only the envVars section'),
63
+ 'only-examples': z.boolean().default(false).describe('Show only the examples section'),
64
+ })
65
+
66
+ type ResolvedTarget =
67
+ | { kind: 'container' }
68
+ | { kind: 'registry'; name: RegistryName }
69
+ | { kind: 'helper'; registry: RegistryName; id: string }
70
+
71
+ class DescribeError extends Error {
72
+ constructor(message: string) {
73
+ super(message)
74
+ this.name = 'DescribeError'
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Normalize an identifier to a comparable form by stripping file extensions,
80
+ * converting kebab-case and snake_case to lowercase-no-separators.
81
+ * e.g. "disk-cache.ts" | "diskCache" | "disk_cache" → "diskcache"
82
+ */
83
+ function normalize(name: string): string {
84
+ return name
85
+ .replace(/\.[tj]sx?$/, '') // strip .ts/.js/.tsx/.jsx
86
+ .replace(/[-_]/g, '') // remove dashes and underscores
87
+ .toLowerCase()
88
+ }
89
+
90
+ /**
91
+ * Find a registry entry by normalized name.
92
+ * Returns the canonical registered id, or undefined if no match.
93
+ */
94
+ function fuzzyFind(registry: any, input: string): string | undefined {
95
+ // Exact match first
96
+ if (registry.has(input)) return input
97
+
98
+ const norm = normalize(input)
99
+ return (registry.available as string[]).find((id: string) => normalize(id) === norm)
100
+ }
101
+
102
+ /**
103
+ * Parse a single target string into a resolved target.
104
+ * Accepts: "container", "features", "features.fs", "fs", etc.
105
+ */
106
+ function resolveTarget(target: string, container: any): ResolvedTarget {
107
+ const lower = target.toLowerCase()
108
+
109
+ // "container" or "self"
110
+ if (lower === 'container' || lower === 'self') {
111
+ return { kind: 'container' }
112
+ }
113
+
114
+ // Registry name: "features", "clients", "servers", "commands", "endpoints"
115
+ const registryMatch = REGISTRY_NAMES.find(
116
+ (r) => r === lower || r === lower + 's' || r.replace(/s$/, '') === lower
117
+ )
118
+ if (registryMatch && !target.includes('.')) {
119
+ return { kind: 'registry', name: registryMatch }
120
+ }
121
+
122
+ // Qualified name: "features.fs", "clients.rest", etc.
123
+ if (target.includes('.')) {
124
+ const [prefix, ...rest] = target.split('.')
125
+ const id = rest.join('.')
126
+ const registry = REGISTRY_NAMES.find(
127
+ (r) => r === prefix!.toLowerCase() || r === prefix!.toLowerCase() + 's' || r.replace(/s$/, '') === prefix!.toLowerCase()
128
+ )
129
+
130
+ if (registry) {
131
+ const reg = container[registry]
132
+ const resolved = fuzzyFind(reg, id)
133
+ if (!resolved) {
134
+ throw new DescribeError(`"${id}" is not registered in ${registry}. Available: ${reg.available.join(', ')}`)
135
+ }
136
+ return { kind: 'helper', registry, id: resolved }
137
+ }
138
+ }
139
+
140
+ // Unqualified name: search all registries (fuzzy)
141
+ const matches: { registry: RegistryName; id: string }[] = []
142
+ for (const registryName of REGISTRY_NAMES) {
143
+ const reg = container[registryName]
144
+ if (!reg) continue
145
+ const found = fuzzyFind(reg, target)
146
+ if (found) {
147
+ matches.push({ registry: registryName, id: found })
148
+ }
149
+ }
150
+
151
+ if (matches.length === 0) {
152
+ const lines = [`"${target}" was not found in any registry.`, '', 'Available:']
153
+ for (const registryName of REGISTRY_NAMES) {
154
+ const reg = container[registryName]
155
+ if (reg && reg.available.length > 0) {
156
+ lines.push(` ${registryName}: ${reg.available.join(', ')}`)
157
+ }
158
+ }
159
+ throw new DescribeError(lines.join('\n'))
160
+ }
161
+
162
+ if (matches.length > 1) {
163
+ const lines = [`"${target}" is ambiguous — found in multiple registries:`]
164
+ for (const m of matches) {
165
+ lines.push(` ${m.registry}.${m.id}`)
166
+ }
167
+ lines.push('', `Please qualify it, e.g.: ${matches[0]!.registry}.${target}`)
168
+ throw new DescribeError(lines.join('\n'))
169
+ }
170
+
171
+ return { kind: 'helper', registry: matches[0]!.registry, id: matches[0]!.id }
172
+ }
173
+
174
+ /** Collect all requested sections from flags. Empty array = show everything. */
175
+ function getSections(options: z.infer<typeof argsSchema>): (IntrospectionSection | 'description')[] {
176
+ const sections: (IntrospectionSection | 'description')[] = []
177
+ for (const [flag, section] of Object.entries(SECTION_FLAGS)) {
178
+ if ((options as any)[flag] && !sections.includes(section)) {
179
+ sections.push(section)
180
+ }
181
+ }
182
+ return sections
183
+ }
184
+
185
+ /**
186
+ * Build the title header for a helper. Includes className when available.
187
+ * headingDepth controls the markdown heading level (1 = #, 2 = ##, etc.)
188
+ */
189
+ function renderTitle(Ctor: any, headingDepth = 1): string {
190
+ const data = Ctor.introspect?.()
191
+ const id = data?.id || Ctor.shortcut || Ctor.name
192
+ const className = data?.className || Ctor.name
193
+ const h = '#'.repeat(headingDepth)
194
+ return className ? `${h} ${className} (${id})` : `${h} ${id}`
195
+ }
196
+
197
+ /**
198
+ * Render text output for a helper given requested sections.
199
+ * When sections is empty, renders everything. When sections are specified,
200
+ * renders only those sections (calling introspectAsText per section and concatenating).
201
+ * 'description' is handled specially as the description paragraph (title is always included).
202
+ * Pass noTitle to suppress the title header.
203
+ * headingDepth controls the starting heading level (1 = #, 2 = ##, etc.)
204
+ */
205
+ function renderHelperText(Ctor: any, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): string {
206
+ if (sections.length === 0) {
207
+ if (noTitle) {
208
+ // Render everything except the title
209
+ const data = Ctor.introspect?.()
210
+ if (!data) return 'No introspection data available.'
211
+ const parts: string[] = [data.description]
212
+ const text = Ctor.introspectAsText?.(headingDepth)
213
+ if (text) {
214
+ // Strip the first heading + description block that introspectAsText renders
215
+ const lines = text.split('\n')
216
+ const headingPrefix = '#'.repeat(headingDepth + 1) + ' '
217
+ let startIdx = 0
218
+ for (let i = 0; i < lines.length; i++) {
219
+ if (i > 0 && lines[i]!.startsWith(headingPrefix)) {
220
+ startIdx = i
221
+ break
222
+ }
223
+ }
224
+ if (startIdx > 0) {
225
+ parts.length = 0
226
+ parts.push(data.description)
227
+ parts.push(lines.slice(startIdx).join('\n'))
228
+ }
229
+ }
230
+ return parts.join('\n\n')
231
+ }
232
+ return Ctor.introspectAsText?.(headingDepth) ?? `${renderTitle(Ctor, headingDepth)}\n\nNo introspection data available.`
233
+ }
234
+
235
+ const introspectionSections = sections.filter((s): s is IntrospectionSection => s !== 'description')
236
+ const parts: string[] = []
237
+
238
+ // Always include the title and description unless noTitle
239
+ if (!noTitle) {
240
+ const data = Ctor.introspect?.()
241
+ parts.push(renderTitle(Ctor, headingDepth))
242
+ if (data?.description) {
243
+ parts.push(data.description)
244
+ }
245
+ }
246
+
247
+ for (const section of introspectionSections) {
248
+ const text = Ctor.introspectAsText?.(section, headingDepth)
249
+ if (text) parts.push(text)
250
+ }
251
+
252
+ return parts.join('\n\n') || `${noTitle ? '' : renderTitle(Ctor, headingDepth) + '\n\n'}No introspection data available.`
253
+ }
254
+
255
+ function renderHelperJson(Ctor: any, sections: (IntrospectionSection | 'description')[], noTitle = false): any {
256
+ if (sections.length === 0) {
257
+ return Ctor.introspect?.() ?? {}
258
+ }
259
+
260
+ const data = Ctor.introspect?.() ?? {}
261
+ const result: Record<string, any> = {}
262
+
263
+ // Always include id and className in JSON unless noTitle
264
+ if (!noTitle) {
265
+ result.id = data.id
266
+ if (data.className) result.className = data.className
267
+ }
268
+
269
+ for (const section of sections) {
270
+ if (section === 'description') {
271
+ result.id = data.id
272
+ if (data.className) result.className = data.className
273
+ result.description = data.description
274
+ } else if (section === 'usage') {
275
+ // Usage is a derived section — include shortcut and options as its JSON form
276
+ result.usage = { shortcut: data.shortcut, options: data.options }
277
+ } else {
278
+ const sectionData = Ctor.introspect?.(section)
279
+ if (sectionData) {
280
+ result[section] = sectionData[section]
281
+ }
282
+ }
283
+ }
284
+
285
+ return result
286
+ }
287
+
288
+ function getContainerData(container: any, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
289
+ if (sections.length === 0) {
290
+ const data = container.inspect()
291
+ return { json: data, text: container.inspectAsText(undefined, headingDepth) }
292
+ }
293
+
294
+ const data = container.inspect()
295
+ const introspectionSections = sections.filter((s): s is IntrospectionSection => s !== 'description')
296
+ const textParts: string[] = []
297
+ const jsonResult: Record<string, any> = {}
298
+ const h = '#'.repeat(headingDepth)
299
+
300
+ // Always include container title and description unless noTitle
301
+ if (!noTitle) {
302
+ const className = data.className || 'Container'
303
+ textParts.push(`${h} ${className} (Container)`)
304
+ jsonResult.className = className
305
+ if (data.description) {
306
+ textParts.push(data.description)
307
+ jsonResult.description = data.description
308
+ }
309
+ }
310
+
311
+ for (const section of introspectionSections) {
312
+ textParts.push(container.inspectAsText(section, headingDepth))
313
+ jsonResult[section] = data[section]
314
+ }
315
+
316
+ return {
317
+ json: jsonResult,
318
+ text: textParts.join('\n\n'),
319
+ }
320
+ }
321
+
322
+ function getRegistryData(container: any, registryName: RegistryName, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
323
+ const registry = container[registryName]
324
+ const available: string[] = registry.available
325
+
326
+ if (available.length === 0) {
327
+ return { json: {}, text: `No ${registryName} are registered.` }
328
+ }
329
+
330
+ const jsonResult: Record<string, any> = {}
331
+ const textParts: string[] = []
332
+ for (const id of available) {
333
+ const Ctor = registry.lookup(id)
334
+ jsonResult[id] = renderHelperJson(Ctor, sections, noTitle)
335
+ textParts.push(renderHelperText(Ctor, sections, noTitle, headingDepth))
336
+ }
337
+
338
+ return { json: jsonResult, text: textParts.join('\n\n---\n\n') }
339
+ }
340
+
341
+ function getHelperData(container: any, registryName: RegistryName, id: string, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
342
+ const registry = container[registryName]
343
+ const Ctor = registry.lookup(id)
344
+
345
+ return {
346
+ json: renderHelperJson(Ctor, sections, noTitle),
347
+ text: renderHelperText(Ctor, sections, noTitle, headingDepth),
348
+ }
349
+ }
350
+
351
+ export default async function describe(options: z.infer<typeof argsSchema>, context: ContainerContext) {
352
+ const container = context.container as any
353
+
354
+ await container.helpers.discoverAll()
355
+
356
+ const args = container.argv._ as string[]
357
+ // args[0] is "describe", the rest are targets
358
+ const targets = args.slice(1)
359
+ const json = options.json
360
+ const pretty = options.pretty
361
+ const noTitle = !options.title
362
+ const sections = getSections(options)
363
+
364
+ function output(text: string) {
365
+ if (pretty) {
366
+ const ui = container.feature('ui')
367
+ console.log(ui.markdown(text))
368
+ } else {
369
+ console.log(text)
370
+ }
371
+ }
372
+
373
+ // No targets: show help screen
374
+ if (targets.length === 0) {
375
+ const { formatCommandHelp } = await import('./help.js')
376
+ const ui = container.feature('ui') as any
377
+ const Cmd = container.commands.lookup('describe')
378
+ console.log(formatCommandHelp('describe', Cmd, ui.colors))
379
+ return
380
+ }
381
+
382
+ const resolved: ResolvedTarget[] = []
383
+
384
+ for (const target of targets) {
385
+ try {
386
+ resolved.push(resolveTarget(target, container))
387
+ } catch (err: any) {
388
+ if (err instanceof DescribeError) {
389
+ console.error(err.message)
390
+ return
391
+ }
392
+ throw err
393
+ }
394
+ }
395
+
396
+ // Multiple docs when there are multiple targets or any target is a registry
397
+ const isMulti = resolved.length > 1 || resolved.some((r) => r.kind === 'registry')
398
+ const headingDepth = isMulti ? 2 : 1
399
+
400
+ function getData(item: ResolvedTarget) {
401
+ switch (item.kind) {
402
+ case 'container':
403
+ return getContainerData(container, sections, noTitle, headingDepth)
404
+ case 'registry':
405
+ return getRegistryData(container, item.name, sections, noTitle, headingDepth)
406
+ case 'helper':
407
+ return getHelperData(container, item.registry, item.id, sections, noTitle, headingDepth)
408
+ }
409
+ }
410
+
411
+ if (json) {
412
+ if (resolved.length === 1) {
413
+ console.log(JSON.stringify(getData(resolved[0]!).json, null, 2))
414
+ } else {
415
+ const combined = resolved.map((item) => getData(item).json)
416
+ console.log(JSON.stringify(combined, null, 2))
417
+ }
418
+ } else {
419
+ const parts = resolved.map((item) => getData(item).text)
420
+ const body = parts.join('\n\n---\n\n')
421
+ if (isMulti) {
422
+ output(`# Luca Helper Descriptions\n\nBelow you'll find documentation.\n\n${body}`)
423
+ } else {
424
+ output(body)
425
+ }
426
+ }
427
+ }
428
+
429
+ commands.registerHandler('describe', {
430
+ description: 'Describe the container, registries, or individual helpers',
431
+ argsSchema,
432
+ handler: describe,
433
+ })