@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,391 @@
1
+ import container from '@soederpop/luca/node'
2
+ import { join } from 'path'
3
+
4
+ const { ui, fs, argv } = container
5
+
6
+ type HelperType = 'feature' | 'client' | 'server' | 'endpoint'
7
+
8
+ interface ScaffoldAnswers {
9
+ type: HelperType
10
+ name: string
11
+ description: string
12
+ }
13
+
14
+ const toPascalCase = (str: string) =>
15
+ str
16
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
17
+ .replace(/^(.)/, (_, c) => c.toUpperCase())
18
+
19
+ const toCamelCase = (str: string) => {
20
+ const pascal = toPascalCase(str)
21
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1)
22
+ }
23
+
24
+ const toKebabCase = (str: string) =>
25
+ str
26
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
27
+ .replace(/[\s_]+/g, '-')
28
+ .toLowerCase()
29
+
30
+ function generateFeature(name: string, description: string): string {
31
+ const className = toPascalCase(name)
32
+ const shortcut = toCamelCase(name)
33
+
34
+ return ui.endent`
35
+ import { z } from 'zod'
36
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
37
+ import { Feature, features } from '../feature.js'
38
+
39
+ export const ${className}StateSchema = FeatureStateSchema.extend({
40
+ // TODO: add state properties
41
+ })
42
+
43
+ export const ${className}OptionsSchema = FeatureOptionsSchema.extend({
44
+ // TODO: add option properties
45
+ })
46
+
47
+ export type ${className}State = z.infer<typeof ${className}StateSchema>
48
+ export type ${className}Options = z.infer<typeof ${className}OptionsSchema>
49
+
50
+ /**
51
+ * ${description}
52
+ *
53
+ * @extends Feature
54
+ */
55
+ export class ${className} extends Feature<${className}State, ${className}Options> {
56
+ static override shortcut = 'features.${shortcut}' as const
57
+ static override stateSchema = ${className}StateSchema
58
+ static override optionsSchema = ${className}OptionsSchema
59
+
60
+ override get initialState(): ${className}State {
61
+ return {
62
+ ...super.initialState,
63
+ // TODO: add default state values
64
+ }
65
+ }
66
+
67
+ /**
68
+ * TODO: describe this method.
69
+ *
70
+ * @returns {Promise<void>}
71
+ */
72
+ async doSomething(): Promise<void> {
73
+ // TODO: implement
74
+ }
75
+ }
76
+
77
+ export default features.register('${shortcut}', ${className})
78
+ `
79
+ }
80
+
81
+ function generateClient(name: string, description: string): string {
82
+ const className = toPascalCase(name) + 'Client'
83
+ const registryKey = toCamelCase(name)
84
+
85
+ return ui.endent`
86
+ import { z } from 'zod'
87
+ import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca/schemas/base.js'
88
+ import {
89
+ type ClientsInterface,
90
+ clients,
91
+ RestClient,
92
+ } from '@soederpop/luca/client'
93
+ import { type ContainerContext } from '@soederpop/luca/container'
94
+
95
+ declare module '@soederpop/luca/client' {
96
+ interface AvailableClients {
97
+ ${registryKey}: typeof ${className}
98
+ }
99
+ }
100
+
101
+ export const ${className}StateSchema = ClientStateSchema.extend({
102
+ // TODO: add state properties
103
+ })
104
+
105
+ export const ${className}OptionsSchema = ClientOptionsSchema.extend({
106
+ // TODO: add option properties
107
+ })
108
+
109
+ export type ${className}State = z.infer<typeof ${className}StateSchema>
110
+ export type ${className}Options = z.infer<typeof ${className}OptionsSchema>
111
+
112
+ /**
113
+ * ${description}
114
+ *
115
+ * @extends RestClient
116
+ */
117
+ export class ${className}<T extends ${className}State = ${className}State> extends RestClient<T> {
118
+ static override shortcut = 'clients.${registryKey}' as const
119
+ static override stateSchema = ${className}StateSchema
120
+ static override optionsSchema = ${className}OptionsSchema
121
+
122
+ constructor(options: ${className}Options, context: ContainerContext) {
123
+ options = {
124
+ ...options,
125
+ baseURL: options.baseURL || 'https://api.example.com',
126
+ }
127
+
128
+ super(options as any, context)
129
+ }
130
+
131
+ /**
132
+ * TODO: describe this method.
133
+ *
134
+ * @returns {Promise<any>}
135
+ */
136
+ async fetchSomething(): Promise<any> {
137
+ const response = await this.get('/endpoint')
138
+ return response.data
139
+ }
140
+ }
141
+
142
+ export default clients.register('${registryKey}', ${className})
143
+ `
144
+ }
145
+
146
+ function generateServer(name: string, description: string): string {
147
+ const className = toPascalCase(name) + 'Server'
148
+ const registryKey = toCamelCase(name)
149
+
150
+ return ui.endent`
151
+ import { z } from 'zod'
152
+ import { ServerStateSchema, ServerOptionsSchema } from '../schemas/base.js'
153
+ import type { NodeContainer } from '../node/container.js'
154
+ import { servers, Server, type ServersInterface, type ServerState, type StartOptions } from '../server.js'
155
+
156
+ declare module '../server' {
157
+ interface AvailableServers {
158
+ ${registryKey}: typeof ${className}
159
+ }
160
+ }
161
+
162
+ export const ${className}OptionsSchema = ServerOptionsSchema.extend({
163
+ // TODO: add option properties
164
+ })
165
+
166
+ export type ${className}Options = z.infer<typeof ${className}OptionsSchema>
167
+
168
+ /**
169
+ * ${description}
170
+ *
171
+ * @extends Server
172
+ */
173
+ export class ${className}<T extends ServerState = ServerState, K extends ${className}Options = ${className}Options> extends Server<T, K> {
174
+ static override shortcut = 'servers.${registryKey}' as const
175
+ static override optionsSchema = ${className}OptionsSchema
176
+
177
+ override async start(options?: StartOptions) {
178
+ if (this.isListening) {
179
+ return this
180
+ }
181
+
182
+ // TODO: implement server start logic
183
+
184
+ this.state.set('listening', true)
185
+ return this
186
+ }
187
+
188
+ override async stop() {
189
+ if (this.isStopped) {
190
+ return this
191
+ }
192
+
193
+ // TODO: implement server stop logic
194
+
195
+ this.state.set('stopped', true)
196
+ return this
197
+ }
198
+
199
+ override async configure() {
200
+ // TODO: implement server configuration
201
+
202
+ this.state.set('configured', true)
203
+ return this
204
+ }
205
+ }
206
+
207
+ export default servers.register('${registryKey}', ${className})
208
+ `
209
+ }
210
+
211
+ function generateEndpoint(name: string, description: string): string {
212
+ const kebab = toKebabCase(name)
213
+ const path = `/${kebab}`
214
+
215
+ return ui.endent`
216
+ import { z } from 'zod'
217
+ import type { EndpointContext } from '../../endpoint.js'
218
+
219
+ export const path = '${path}'
220
+ export const description = '${description}'
221
+ export const tags = ['${kebab}']
222
+
223
+ export const postSchema = z.object({
224
+ // TODO: define your parameters
225
+ })
226
+
227
+ export async function post(parameters: z.infer<typeof postSchema>, ctx: EndpointContext) {
228
+ const { container } = ctx
229
+ // TODO: implement
230
+ return { ok: true }
231
+ }
232
+ `
233
+ }
234
+
235
+ const generators: Record<HelperType, (name: string, desc: string) => string> = {
236
+ feature: generateFeature,
237
+ client: generateClient,
238
+ server: generateServer,
239
+ endpoint: generateEndpoint,
240
+ }
241
+
242
+ const outputPaths: Record<HelperType, (name: string) => string> = {
243
+ feature: (name) => join('src', 'node', 'features', `${toKebabCase(name)}.ts`),
244
+ client: (name) => join('src', 'clients', toKebabCase(name), 'index.ts'),
245
+ server: (name) => join('src', 'servers', `${toKebabCase(name)}.ts`),
246
+ endpoint: (name) => join('src', 'agi', 'endpoints', `${toKebabCase(name)}.ts`),
247
+ }
248
+
249
+ async function main() {
250
+ console.log(ui.banner('Scaffold', { font: 'Small', colors: ['cyan', 'blue', 'magenta'] }))
251
+ console.log()
252
+
253
+ // Support passing type and name as positional args: bun scripts/scaffold.ts feature myThing
254
+ const positionalType = argv._?.[0] as HelperType | undefined
255
+ const positionalName = argv._?.[1] as string | undefined
256
+
257
+ const answers = await ui.wizard([
258
+ {
259
+ type: 'list',
260
+ name: 'type',
261
+ message: 'What do you want to scaffold?',
262
+ choices: [
263
+ { name: 'Feature - registered in FeaturesRegistry, enable/disable lifecycle', value: 'feature' },
264
+ { name: 'Client - registered in ClientsRegistry, connect/configure lifecycle', value: 'client' },
265
+ { name: 'Server - registered in ServersRegistry, start/stop/configure lifecycle', value: 'server' },
266
+ { name: 'Endpoint - file-based HTTP endpoint, Remix-like DX', value: 'endpoint' },
267
+ ],
268
+ when: () => !positionalType,
269
+ },
270
+ {
271
+ type: 'input',
272
+ name: 'name',
273
+ message: 'Name (camelCase or kebab-case):',
274
+ validate: (input: string) => (input.trim().length > 0 ? true : 'Name is required'),
275
+ when: () => !positionalName,
276
+ },
277
+ {
278
+ type: 'input',
279
+ name: 'description',
280
+ message: 'Short description:',
281
+ default: 'TODO: describe this helper.',
282
+ },
283
+ ]) as Partial<ScaffoldAnswers>
284
+
285
+ const type = positionalType || answers.type!
286
+ const name = positionalName || answers.name!
287
+ const description = answers.description || 'TODO: describe this helper.'
288
+
289
+ if (!['feature', 'client', 'server', 'endpoint'].includes(type)) {
290
+ ui.print.red(`Invalid type "${type}". Must be feature, client, server, or endpoint.`)
291
+ process.exit(1)
292
+ }
293
+
294
+ const code = generators[type](name, description)
295
+ const relativePath = outputPaths[type](name)
296
+ const fullPath = container.paths.resolve(relativePath)
297
+
298
+ if (await fs.existsAsync(fullPath)) {
299
+ ui.print.red(`File already exists: ${relativePath}`)
300
+ process.exit(1)
301
+ }
302
+
303
+ const dir = container.paths.dirname(fullPath)
304
+ await fs.ensureFolder(dir)
305
+ await fs.writeFileAsync(fullPath, code + '\n')
306
+
307
+ console.log()
308
+ ui.print.green(`Created ${relativePath}`)
309
+ console.log()
310
+
311
+ if (type === 'feature') {
312
+ const className = toPascalCase(name)
313
+ const shortcut = toCamelCase(name)
314
+ const kebab = toKebabCase(name)
315
+
316
+ ui.print.yellow('Next steps for your new Feature:')
317
+ console.log()
318
+ console.log(ui.endent`
319
+ 1. Add the side-effect import in src/node/container.ts:
320
+ ${ui.colors.cyan(`import './features/${kebab}'`)}
321
+
322
+ 2. Add the type import:
323
+ ${ui.colors.cyan(`import type { ${className} } from './features/${kebab}'`)}
324
+
325
+ 3. Add to NodeFeatures interface:
326
+ ${ui.colors.cyan(`${shortcut}: typeof ${className}`)}
327
+
328
+ 4. Add to the export block:
329
+ ${ui.colors.cyan(`type ${className}`)}
330
+
331
+ 5. Optionally add as a property on NodeContainer:
332
+ ${ui.colors.cyan(`${shortcut}?: ${className}`)}
333
+ `)
334
+ } else if (type === 'client') {
335
+ const className = toPascalCase(name) + 'Client'
336
+ const kebab = toKebabCase(name)
337
+
338
+ ui.print.yellow('Next steps for your new Client:')
339
+ console.log()
340
+ console.log(ui.endent`
341
+ 1. Update the baseURL in src/clients/${kebab}/index.ts
342
+
343
+ 2. Import and register in your script or container setup:
344
+ ${ui.colors.cyan(`import '@/clients/${kebab}'`)}
345
+
346
+ 3. Use it:
347
+ ${ui.colors.cyan(`const client = container.client('${toCamelCase(name)}', { ... })`)}
348
+ `)
349
+ } else if (type === 'server') {
350
+ const className = toPascalCase(name) + 'Server'
351
+ const kebab = toKebabCase(name)
352
+
353
+ ui.print.yellow('Next steps for your new Server:')
354
+ console.log()
355
+ console.log(ui.endent`
356
+ 1. Import and register the server in src/server.ts:
357
+ ${ui.colors.cyan(`import { ${className} } from './servers/${kebab}.js'`)}
358
+ ${ui.colors.cyan(`servers.register('${toCamelCase(name)}', ${className})`)}
359
+
360
+ 2. Use it:
361
+ ${ui.colors.cyan(`const server = container.server('${toCamelCase(name)}', { port: 3000 })`)}
362
+ ${ui.colors.cyan(`await server.configure()`)}
363
+ ${ui.colors.cyan(`await server.start()`)}
364
+ `)
365
+ } else if (type === 'endpoint') {
366
+ const kebab = toKebabCase(name)
367
+
368
+ ui.print.yellow('Next steps for your new Endpoint:')
369
+ console.log()
370
+ console.log(ui.endent`
371
+ 1. Edit the endpoint file at src/agi/endpoints/${kebab}.ts
372
+
373
+ 2. Define your Zod schemas and handler functions (get, post, put, patch, delete)
374
+
375
+ 3. It will be auto-loaded when useEndpoints() scans the endpoints directory:
376
+ ${ui.colors.cyan(`await expressServer.useEndpoints('src/agi/endpoints')`)}
377
+
378
+ 4. Or load it manually:
379
+ ${ui.colors.cyan(`const ep = new Endpoint({ path: '/${kebab}', filePath: '...' }, container.context)`)}
380
+ ${ui.colors.cyan(`await ep.load()`)}
381
+ ${ui.colors.cyan(`expressServer.useEndpoint(ep)`)}
382
+ `)
383
+ }
384
+
385
+ console.log()
386
+ }
387
+
388
+ main().catch((err) => {
389
+ ui.print.red(err.message)
390
+ process.exit(1)
391
+ })
@@ -0,0 +1,15 @@
1
+ import container from '@soederpop/luca/agi'
2
+
3
+ const fs = container.feature('fs')
4
+
5
+ const data = await fs.readJson('package.json') as { name: string, version: string }
6
+
7
+ console.log(data.name)
8
+
9
+ const info = container.feature('git').branch
10
+
11
+ console.log(info)
12
+
13
+ const opener = container.feature('opener')
14
+
15
+ await opener.open('https://www.tiktok.com')
@@ -0,0 +1,123 @@
1
+ import { NodeContainer } from '../src/node/container'
2
+
3
+ const container = new NodeContainer({ cwd: process.cwd() })
4
+ const listener = container.feature('launcherAppCommandListener', {
5
+ autoListen: true,
6
+ })
7
+
8
+ const windowManager = container.feature('windowManager')
9
+
10
+ console.log('Listening on:', listener.state.get('socketPath'))
11
+ console.log('Waiting for native app to connect...\n')
12
+
13
+ listener.enable()
14
+
15
+ listener.on('clientConnected', () => {
16
+ console.log('[connected] Native app connected')
17
+ })
18
+
19
+ listener.on('clientDisconnected', () => {
20
+ console.log('[disconnected] Native app disconnected')
21
+ })
22
+
23
+ listener.on('command', async (cmd) => {
24
+ console.log(`[command] "${cmd.text}" (source: ${cmd.source}, id: ${cmd.id})`)
25
+
26
+ const normalizedText = String(cmd.text).toLowerCase()
27
+
28
+ if (normalizedText.includes('terminal')) {
29
+ await container.sleep(1000)
30
+ cmd.ack('Sheeeeeeeit. I got you fam!')
31
+ await container.sleep(1000)
32
+ console.log('Spawning terminal')
33
+ const result = await windowManager.spawnTTY({
34
+ command: '/Users/jon/.bun/bin/bun',
35
+ args: ['run', '/Users/jon/@luca/src/cli/cli.ts', 'console'],
36
+ cwd: '/Users/jon/@soederpop',
37
+ title: 'The Console',
38
+ cols: 120,
39
+ rows: 40,
40
+ width: 1000,
41
+ height: 700,
42
+ })
43
+
44
+ await container.sleep(4000)
45
+
46
+ cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Check that shit out playboy. Fuckin terminal output.' })
47
+
48
+ return
49
+ } else if (normalizedText.includes('code')) {
50
+ await container.sleep(1000)
51
+ cmd.ack('Real talk, I feel for the homies we told to learn to code. Now that claude is on this shit?? I mean.')
52
+ await container.sleep(1000)
53
+ console.log('Spawning terminal')
54
+ const result = await windowManager.spawnTTY({
55
+ command: '/Users/jon/.bun/bin/claude',
56
+ cwd: '/Users/jon/@soederpop',
57
+ title: 'Claude',
58
+ cols: 120,
59
+ rows: 80,
60
+ width: 1000,
61
+ height: 700,
62
+ })
63
+
64
+ await container.sleep(4000)
65
+
66
+ cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Good luck with claude bro.' })
67
+
68
+ return
69
+ } else if (normalizedText.includes('web') || normalizedText.includes('browser')) {
70
+ cmd.ack('Yo.... Fuckin check this out, twin.')
71
+ await container.sleep(1000)
72
+ const result = await windowManager.spawn({
73
+ url: 'https://google.com',
74
+ width: 1000,
75
+ height: 700,
76
+ })
77
+
78
+ console.log('Web browser spawned', result)
79
+
80
+ await container.sleep(3000)
81
+ cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Motherfucker I can even launch web browsers' })
82
+ return
83
+ } else if (normalizedText.includes('write')) {
84
+ await container.sleep(1000)
85
+ cmd.ack('Aight. Sheeeit. We got a real fuckin earnest hemmingway up in here.')
86
+ const result = await windowManager.spawn({
87
+ url: 'http://localhost:3080',
88
+ width: 1200,
89
+ height: 900,
90
+ })
91
+
92
+ await container.sleep(4000)
93
+ cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Let the boy COOK' })
94
+ return
95
+ } else if (normalizedText.includes('track')) {
96
+ await container.sleep(1000)
97
+ cmd.ack('Better believe it. Aint nobody hiding from your boy.')
98
+
99
+ container.proc.spawnAndCapture('luca', ['serve', '--force', '--port', '3969', '--no-open'], {
100
+ cwd: '/Users/jon/@soederpop/playground/enemy-tracker'
101
+ })
102
+
103
+ await container.sleep(4000)
104
+
105
+ const result = await windowManager.spawn({
106
+ url: 'http://localhost:3969',
107
+ width: 1400,
108
+ height: 1000,
109
+ })
110
+
111
+ cmd.finish({ result: { action: 'completed', text: cmd.text }, speech: 'Get em dawg. Me and the homies are ready.' })
112
+
113
+ return
114
+ }
115
+
116
+ await container.sleep(4000)
117
+ cmd.ack('Look unc. I dont know the fuck you talmbout.')
118
+ cmd.finish({ result: { action: 'unknown' }})
119
+ })
120
+
121
+ listener.on('message', (msg) => {
122
+ console.log('[message]', JSON.stringify(msg))
123
+ })
@@ -0,0 +1,86 @@
1
+ import { NodeContainer } from '../src/node/container'
2
+
3
+ const args = new Set(process.argv.slice(2))
4
+ const socketArg = process.argv.slice(2).find((arg) => arg.startsWith('--socket='))
5
+ const socketPath = socketArg?.slice('--socket='.length)
6
+ const shouldSpawnTTY = args.has('--spawn-tty')
7
+ const shouldSpawnWindow = args.has('--spawn-window')
8
+
9
+ const container = new NodeContainer({ cwd: process.cwd() })
10
+ const wm = socketPath
11
+ ? container.feature('windowManager', { socketPath })
12
+ : container.feature('windowManager')
13
+
14
+ wm.listen()
15
+
16
+ const logCount = () => {
17
+ console.log(`[state] windowCount=${wm.state.get('windowCount')}`)
18
+ }
19
+
20
+ wm.on('clientConnected', () => {
21
+ console.log('[clientConnected] native launcher connected')
22
+ })
23
+
24
+ wm.on('clientDisconnected', () => {
25
+ console.log('[clientDisconnected] native launcher disconnected')
26
+ })
27
+
28
+ wm.on('windowAck', (msg) => {
29
+ console.log('[windowAck]', JSON.stringify(msg))
30
+ logCount()
31
+ })
32
+
33
+ wm.on('windowClosed', (msg) => {
34
+ console.log('[windowClosed]', JSON.stringify(msg))
35
+ logCount()
36
+ })
37
+
38
+ wm.on('terminalExited', (msg) => {
39
+ console.log('[terminalExited]', JSON.stringify(msg))
40
+ })
41
+
42
+ wm.on('message', (msg) => {
43
+ if (msg?.type === 'windowAck' || msg?.type === 'windowClosed' || msg?.type === 'terminalExited') {
44
+ return
45
+ }
46
+ console.log('[message]', JSON.stringify(msg))
47
+ })
48
+
49
+ let spawned = false
50
+ wm.on('clientConnected', async () => {
51
+ if (spawned) return
52
+ spawned = true
53
+
54
+ if (shouldSpawnWindow) {
55
+ const opened = await wm.spawn({
56
+ url: 'https://example.com',
57
+ width: 900,
58
+ height: 620,
59
+ })
60
+ console.log('[spawn-window]', opened)
61
+ }
62
+
63
+ if (shouldSpawnTTY) {
64
+ const tty = await wm.spawnTTY({
65
+ command: 'zsh',
66
+ args: ['-lc', 'echo "window-manager lifecycle demo"; sleep 2; exit 7'],
67
+ title: 'WM Lifecycle Demo',
68
+ cols: 110,
69
+ rows: 32,
70
+ width: 960,
71
+ height: 620,
72
+ cwd: process.cwd(),
73
+ })
74
+ console.log('[spawn-tty]', tty)
75
+ }
76
+ })
77
+
78
+ await container.sleep(150)
79
+
80
+ console.log(`[ready] listening=${wm.isListening} socket=${wm.state.get('socketPath') ?? '<unset>'}`)
81
+ if (!wm.isListening) {
82
+ console.log(`[error] ${wm.state.get('lastError') ?? 'windowManager failed to bind socket'}`)
83
+ console.log('[hint] try: bun run scripts/test-window-manager-lifecycle.ts --socket=/tmp/ipc-window.sock')
84
+ }
85
+ console.log('[usage] bun run scripts/test-window-manager-lifecycle.ts [--spawn-tty] [--spawn-window] [--socket=/path/to/ipc-window.sock]')
86
+ console.log('[ready] waiting for native app connection...')
@@ -0,0 +1,43 @@
1
+ import { NodeContainer } from '../src/node/container'
2
+
3
+ const container = new NodeContainer({ cwd: process.cwd() })
4
+ const wm = container.feature('windowManager')
5
+
6
+ await wm.listen()
7
+ console.log('Listening:', wm.isListening)
8
+ console.log('Socket path:', wm.state.get('socketPath'))
9
+
10
+ wm.on('clientConnected', () => {
11
+ console.log('App connected')
12
+ })
13
+
14
+ wm.on('message', (msg) => {
15
+ console.log('Message from app:', msg)
16
+ })
17
+
18
+ wm.on('clientDisconnected', () => {
19
+ console.log('App disconnected')
20
+ })
21
+
22
+ // Wait for the app, then spawn a TTY running luca serve in the writing assistant playground
23
+ wm.on('clientConnected', async () => {
24
+ const result = await wm.spawnTTY({
25
+ command: 'luca',
26
+ args: ['serve', '--any-port'],
27
+ cwd: '/Users/jon/@soederpop/playground/writing-assistant',
28
+ title: 'Writing Assistant Server',
29
+ cols: 120,
30
+ rows: 40,
31
+ width: 1000,
32
+ height: 700,
33
+ })
34
+
35
+ console.log('Spawned TTY:', result)
36
+
37
+ if (result.windowId) {
38
+ console.log(`Window ID: ${result.windowId}`)
39
+ console.log(`PID: ${result.pid}`)
40
+ }
41
+ })
42
+
43
+ console.log('Waiting for native app to connect...')