@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,106 @@
1
+ # Building a Command
2
+
3
+ A command extends the `luca` CLI. Commands live in a project's `commands/` folder and are automatically discovered. They receive parsed options and a container context.
4
+
5
+ When to build a command:
6
+ - You need a CLI task for a project (build scripts, generators, automation)
7
+ - You want argument parsing, help text, and container access for free
8
+ - The task should be runnable via `luca yourCommand`
9
+
10
+ ## Imports
11
+
12
+ ```ts
13
+ import { z } from 'zod'
14
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
15
+ import type { ContainerContext } from '@soederpop/luca'
16
+ ```
17
+
18
+ ## Args Schema
19
+
20
+ Define your command's arguments and flags. Extend `CommandOptionsSchema` which gives you `_` (positional args) and `name` for free.
21
+
22
+ ```ts
23
+ export const argsSchema = CommandOptionsSchema.extend({
24
+ // Add your flags here. Each becomes a --flag on the CLI.
25
+ // Example: verbose: z.boolean().default(false).describe('Enable verbose output'),
26
+ // Example: output: z.string().optional().describe('Output file path'),
27
+ })
28
+ ```
29
+
30
+ ## Handler
31
+
32
+ The handler function receives parsed options and the container context. Use the container for all I/O.
33
+
34
+ ```ts
35
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
36
+ const container = context.container as any
37
+ const fs = container.feature('fs')
38
+ const args = container.argv._ as string[]
39
+
40
+ // args[0] is your command name, args[1+] are positional arguments
41
+ // options contains parsed --flags
42
+
43
+ // Your implementation here
44
+ }
45
+ ```
46
+
47
+ ## Registration
48
+
49
+ Register the command at the bottom of the file. The `description` shows up in `luca --help`.
50
+
51
+ ```ts
52
+ commands.registerHandler('{{camelName}}', {
53
+ description: '{{description}}',
54
+ argsSchema,
55
+ handler: {{camelName}},
56
+ })
57
+ ```
58
+
59
+ ## Module Augmentation
60
+
61
+ Optional but gives TypeScript autocomplete for `commands.lookup('yourCommand')`.
62
+
63
+ ```ts
64
+ declare module '@soederpop/luca' {
65
+ interface AvailableCommands {
66
+ {{camelName}}: ReturnType<typeof commands.registerHandler>
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Complete Example
72
+
73
+ ```ts
74
+ import { z } from 'zod'
75
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
76
+ import type { ContainerContext } from '@soederpop/luca'
77
+
78
+ declare module '@soederpop/luca' {
79
+ interface AvailableCommands {
80
+ {{camelName}}: ReturnType<typeof commands.registerHandler>
81
+ }
82
+ }
83
+
84
+ export const argsSchema = CommandOptionsSchema.extend({})
85
+
86
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
87
+ const container = context.container as any
88
+ const fs = container.feature('fs')
89
+
90
+ console.log('{{camelName}} running...')
91
+ }
92
+
93
+ commands.registerHandler('{{camelName}}', {
94
+ description: '{{description}}',
95
+ argsSchema,
96
+ handler: {{camelName}},
97
+ })
98
+ ```
99
+
100
+ ## Conventions
101
+
102
+ - **File location**: `commands/{{camelName}}.ts` in the project root. The `luca` CLI discovers these automatically.
103
+ - **Naming**: camelCase for both file and registration ID. `luca my-command` maps to `commands/my-command.ts`.
104
+ - **Use the container**: Never import `fs`, `path`, `child_process` directly. Use `container.feature('fs')`, `container.paths`, `container.feature('proc')`.
105
+ - **Positional args**: Access via `container.argv._` — it's an array where `_[0]` is the command name.
106
+ - **Exit codes**: Return nothing for success. Throw for errors — the CLI catches and reports them.
@@ -0,0 +1,176 @@
1
+ # Building an Endpoint
2
+
3
+ An endpoint is a route handler that `luca serve` auto-discovers and mounts on an Express server. Endpoints live in `endpoints/` and follow a file-based routing convention — each file becomes an API route with automatic validation, OpenAPI spec generation, and rate limiting.
4
+
5
+ When to build an endpoint:
6
+ - You need a REST API route (GET, POST, PUT, PATCH, DELETE)
7
+ - You want Zod validation, OpenAPI docs, and rate limiting for free
8
+ - You're building an API that `luca serve` manages
9
+
10
+ ## File Location
11
+
12
+ Endpoints go in `endpoints/` at your project root:
13
+ - `endpoints/health.ts` → `/health`
14
+ - `endpoints/posts.ts` → `/api/posts`
15
+ - `endpoints/posts/[id].ts` → `/api/posts/:id` (requires `path` export)
16
+
17
+ Run `luca serve` and they're automatically discovered and mounted.
18
+
19
+ ## Imports
20
+
21
+ ```ts
22
+ import { z } from 'zod'
23
+ import type { EndpointContext } from '@soederpop/luca'
24
+ ```
25
+
26
+ That's it. Endpoints are lightweight — just exports and functions.
27
+
28
+ ## Required Exports
29
+
30
+ Every endpoint MUST export a `path` string:
31
+
32
+ ```ts
33
+ export const path = '/api/{{camelName}}'
34
+ export const description = '{{description}}'
35
+ export const tags = ['{{camelName}}']
36
+ ```
37
+
38
+ ## Handler Functions
39
+
40
+ Export named functions for each HTTP method you support. Each receives validated parameters and an `EndpointContext`:
41
+
42
+ ```ts
43
+ export async function get(params: any, ctx: EndpointContext) {
44
+ const fs = ctx.container.feature('fs')
45
+ // Your logic here
46
+ return { message: 'ok' }
47
+ }
48
+
49
+ export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
50
+ // Create something
51
+ return { created: true }
52
+ }
53
+ ```
54
+
55
+ The `EndpointContext` gives you:
56
+ - `ctx.container` — the luca container (access any feature, client, etc.)
57
+ - `ctx.request` — Express request object
58
+ - `ctx.response` — Express response object
59
+ - `ctx.query` — parsed query string
60
+ - `ctx.body` — parsed request body
61
+ - `ctx.params` — URL parameters (`:id`, etc.)
62
+
63
+ Return any object — it's automatically JSON-serialized as the response.
64
+
65
+ ## Validation Schemas
66
+
67
+ Export Zod schemas to validate parameters for each method. Name them `{method}Schema`:
68
+
69
+ ```ts
70
+ export const getSchema = z.object({
71
+ q: z.string().optional().describe('Search query'),
72
+ limit: z.number().default(20).describe('Max results'),
73
+ })
74
+
75
+ export const postSchema = z.object({
76
+ title: z.string().min(1).describe('Item title'),
77
+ body: z.string().min(1).describe('Item content'),
78
+ })
79
+ ```
80
+
81
+ Invalid requests automatically return 400 with Zod error details. Schemas also feed the auto-generated OpenAPI spec.
82
+
83
+ ## Rate Limiting
84
+
85
+ Export rate limit config to protect endpoints:
86
+
87
+ ```ts
88
+ // Global rate limit for all methods
89
+ export const rateLimit = { maxRequests: 100, windowSeconds: 60 }
90
+
91
+ // Per-method rate limit
92
+ export const postRateLimit = { maxRequests: 10, windowSeconds: 1 }
93
+ ```
94
+
95
+ ## Delete Handler
96
+
97
+ `delete` is a reserved word in JS. Use an alias:
98
+
99
+ ```ts
100
+ const del = async (params: any, ctx: EndpointContext) => {
101
+ return { deleted: true }
102
+ }
103
+ export { del as delete }
104
+ ```
105
+
106
+ ## Complete Example
107
+
108
+ A CRUD endpoint for a resource:
109
+
110
+ ```ts
111
+ import { z } from 'zod'
112
+ import type { EndpointContext } from '@soederpop/luca'
113
+
114
+ export const path = '/api/{{camelName}}'
115
+ export const description = '{{description}}'
116
+ export const tags = ['{{camelName}}']
117
+
118
+ export const getSchema = z.object({
119
+ q: z.string().optional().describe('Search query'),
120
+ })
121
+
122
+ export async function get(params: z.infer<typeof getSchema>, ctx: EndpointContext) {
123
+ return { items: [], total: 0 }
124
+ }
125
+
126
+ export const postSchema = z.object({
127
+ name: z.string().min(1).describe('Item name'),
128
+ })
129
+
130
+ export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
131
+ return { item: { id: '1', ...params }, message: 'Created' }
132
+ }
133
+ ```
134
+
135
+ ## Dynamic Route Example
136
+
137
+ For routes with URL parameters, create a nested file:
138
+
139
+ ```ts
140
+ // endpoints/{{camelName}}/[id].ts
141
+ import { z } from 'zod'
142
+ import type { EndpointContext } from '@soederpop/luca'
143
+
144
+ export const path = '/api/{{camelName}}/:id'
145
+ export const description = 'Get, update, or delete a specific item'
146
+ export const tags = ['{{camelName}}']
147
+
148
+ export async function get(params: any, ctx: EndpointContext) {
149
+ const { id } = ctx.params
150
+ return { item: { id } }
151
+ }
152
+
153
+ export const putSchema = z.object({
154
+ name: z.string().min(1).optional().describe('Updated name'),
155
+ })
156
+
157
+ export async function put(params: z.infer<typeof putSchema>, ctx: EndpointContext) {
158
+ const { id } = ctx.params
159
+ return { item: { id, ...params }, message: 'Updated' }
160
+ }
161
+
162
+ const del = async (params: any, ctx: EndpointContext) => {
163
+ const { id } = ctx.params
164
+ return { message: `Deleted ${id}` }
165
+ }
166
+ export { del as delete }
167
+ ```
168
+
169
+ ## Conventions
170
+
171
+ - **File = route**: The file path maps to the URL path. `endpoints/users.ts` serves `/api/users`.
172
+ - **Export `path`**: Every endpoint must export a `path` string. This is the mounted route.
173
+ - **Use Zod schemas**: Name them `getSchema`, `postSchema`, etc. They validate AND document.
174
+ - **Use the container**: Access features via `ctx.container.feature('fs')`, not Node.js imports.
175
+ - **Return objects**: Handler return values are JSON-serialized. Use `ctx.response` only for streaming or custom status codes.
176
+ - **OpenAPI for free**: Your `path`, `description`, `tags`, and schemas automatically generate an OpenAPI spec at `/openapi.json`.
@@ -0,0 +1,148 @@
1
+ # Building a Feature
2
+
3
+ A feature is a container-managed capability — something your application needs that lives on the machine (file I/O, caching, encryption, etc). Features are lazy-loaded, observable, and self-documenting.
4
+
5
+ When to build a feature:
6
+ - You need a reusable local capability (not a network call — that's a client)
7
+ - You want state management, events, and introspection for free
8
+ - You're wrapping a library so the rest of the codebase uses a uniform interface
9
+
10
+ ## Imports
11
+
12
+ ```ts
13
+ import { z } from 'zod'
14
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '@soederpop/luca'
15
+ import { Feature, features } from '@soederpop/luca'
16
+ import type { ContainerContext } from '@soederpop/luca'
17
+ ```
18
+
19
+ These are the only imports your feature file needs from luca. If your feature wraps a third-party library, import it here too — feature implementations are the ONE place where direct library imports are allowed.
20
+
21
+ ## Schemas
22
+
23
+ Define the shape of your feature's state, options, and events using Zod. Every field must have a `.describe()` — this becomes the documentation.
24
+
25
+ ```ts
26
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({
27
+ // Add your state fields here. These are observable — changes emit events.
28
+ // Example: itemCount: z.number().default(0).describe('Number of items stored'),
29
+ })
30
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
31
+
32
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({
33
+ // Add constructor options here. Validated when the feature is created.
34
+ // Example: directory: z.string().optional().describe('Storage directory path'),
35
+ })
36
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
37
+
38
+ export const {{PascalName}}EventsSchema = FeatureEventsSchema.extend({
39
+ // Each key is an event name. Value is z.tuple() of listener arguments.
40
+ // Example: itemAdded: z.tuple([z.string().describe('Item key')]).describe('Emitted when an item is added'),
41
+ })
42
+ ```
43
+
44
+ ## Class
45
+
46
+ The class extends `Feature` with your state and options types. Static properties drive registration and introspection. Every public method needs a JSDoc block with `@param`, `@returns`, and `@example`.
47
+
48
+ ```ts
49
+ /**
50
+ * {{description}}
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const {{camelName}} = container.feature('{{camelName}}')
55
+ * ```
56
+ *
57
+ * @extends Feature
58
+ */
59
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
60
+ static override shortcut = 'features.{{camelName}}' as const
61
+ static override stateSchema = {{PascalName}}StateSchema
62
+ static override optionsSchema = {{PascalName}}OptionsSchema
63
+ static override eventsSchema = {{PascalName}}EventsSchema
64
+ static override description = '{{description}}'
65
+
66
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
67
+ super(options, context)
68
+ // Initialize state, set up resources
69
+ }
70
+
71
+ // Add your methods here. Every public method needs JSDoc.
72
+ }
73
+ ```
74
+
75
+ ## Module Augmentation
76
+
77
+ This is what gives `container.feature('yourName')` TypeScript autocomplete. Without it, the feature works but TypeScript won't know about it.
78
+
79
+ ```ts
80
+ declare module '@soederpop/luca' {
81
+ interface AvailableFeatures {
82
+ {{camelName}}: typeof {{PascalName}}
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Registration
88
+
89
+ The last line of the file registers the feature in the global registry. This must happen at module level (not inside a function).
90
+
91
+ ```ts
92
+ export default features.register('{{camelName}}', {{PascalName}})
93
+ ```
94
+
95
+ ## Complete Example
96
+
97
+ Here's a minimal but complete feature. This is what a real feature file looks like:
98
+
99
+ ```ts
100
+ import { z } from 'zod'
101
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
102
+ import { Feature, features } from '@soederpop/luca'
103
+ import type { ContainerContext } from '@soederpop/luca'
104
+
105
+ declare module '@soederpop/luca' {
106
+ interface AvailableFeatures {
107
+ {{camelName}}: typeof {{PascalName}}
108
+ }
109
+ }
110
+
111
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({})
112
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
113
+
114
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({})
115
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
116
+
117
+ /**
118
+ * {{description}}
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const {{camelName}} = container.feature('{{camelName}}')
123
+ * ```
124
+ *
125
+ * @extends Feature
126
+ */
127
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
128
+ static override shortcut = 'features.{{camelName}}' as const
129
+ static override stateSchema = {{PascalName}}StateSchema
130
+ static override optionsSchema = {{PascalName}}OptionsSchema
131
+ static override description = '{{description}}'
132
+
133
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
134
+ super(options, context)
135
+ }
136
+ }
137
+
138
+ export default features.register('{{camelName}}', {{PascalName}})
139
+ ```
140
+
141
+ ## Conventions
142
+
143
+ - **Naming**: PascalCase for class, camelCase for registration ID. The file name should be kebab-case (e.g. `disk-cache.ts`).
144
+ - **JSDoc**: Every public method, getter, and the class itself needs a JSDoc block. Include `@example` with working code.
145
+ - **Describe everything**: Every Zod field needs `.describe()`. Every event tuple argument needs `.describe()`. This IS the documentation.
146
+ - **No Node builtins in consumer code**: If your feature wraps `fs` or `crypto`, that's fine inside the feature. But code that USES your feature should never import those directly.
147
+ - **State is observable**: Use `this.state.set()` and `this.state.get()`. Don't use plain instance properties for data that should be reactive.
148
+ - **Events for lifecycle**: Emit events for significant state changes so consumers can react.
@@ -0,0 +1,187 @@
1
+ # Building a Server
2
+
3
+ A server is a container-managed listener — something that accepts connections and handles requests. Servers manage their own lifecycle (configure, start, stop) and expose observable state.
4
+
5
+ When to build a server:
6
+ - You need to accept incoming connections (HTTP, WebSocket, custom protocol)
7
+ - You want lifecycle management, port handling, and observability for free
8
+ - You're wrapping a server library so the codebase uses `container.server('name')`
9
+
10
+ ## Imports
11
+
12
+ ```ts
13
+ import { z } from 'zod'
14
+ import { Server, servers } from '@soederpop/luca'
15
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
16
+ import type { ContainerContext, NodeContainer } from '@soederpop/luca'
17
+ import type { ServersInterface } from '@soederpop/luca'
18
+ ```
19
+
20
+ ## Schemas
21
+
22
+ ```ts
23
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({
24
+ // Add your state fields here.
25
+ // Example: connectionCount: z.number().default(0).describe('Active connections'),
26
+ })
27
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
28
+
29
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({
30
+ // Add constructor options here. port and host come from ServerOptionsSchema.
31
+ // Example: cors: z.boolean().default(true).describe('Enable CORS'),
32
+ })
33
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
34
+
35
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({
36
+ // Add your events here.
37
+ // Example: connection: z.tuple([z.string().describe('Client ID')]).describe('New client connected'),
38
+ })
39
+ ```
40
+
41
+ ## Class
42
+
43
+ ```ts
44
+ /**
45
+ * {{description}}
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
50
+ * await {{camelName}}.start()
51
+ * ```
52
+ *
53
+ * @extends Server
54
+ */
55
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
56
+ static override shortcut = 'servers.{{camelName}}' as const
57
+ static override stateSchema = {{PascalName}}StateSchema
58
+ static override optionsSchema = {{PascalName}}OptionsSchema
59
+ static override eventsSchema = {{PascalName}}EventsSchema
60
+ static override description = '{{description}}'
61
+
62
+ static override attach(container: NodeContainer & ServersInterface) {
63
+ return container
64
+ }
65
+
66
+ override async configure() {
67
+ if (this.isConfigured) return this
68
+ // Set up the underlying server here
69
+ this.state.set('configured', true)
70
+ return this
71
+ }
72
+
73
+ override async start(options?: { port?: number }) {
74
+ if (this.isListening) return this
75
+ if (!this.isConfigured) await this.configure()
76
+
77
+ const port = options?.port || this.options.port || 3000
78
+ // Start listening here
79
+ this.state.set('port', port)
80
+ this.state.set('listening', true)
81
+ return this
82
+ }
83
+
84
+ override async stop() {
85
+ if (this.isStopped) return this
86
+ // Clean up connections here
87
+ this.state.set('listening', false)
88
+ this.state.set('stopped', true)
89
+ return this
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Module Augmentation
95
+
96
+ ```ts
97
+ declare module '@soederpop/luca' {
98
+ interface AvailableServers {
99
+ {{camelName}}: typeof {{PascalName}}
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Registration
105
+
106
+ ```ts
107
+ export default servers.register('{{camelName}}', {{PascalName}})
108
+ ```
109
+
110
+ ## Complete Example
111
+
112
+ ```ts
113
+ import { z } from 'zod'
114
+ import { Server, servers } from '@soederpop/luca'
115
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
116
+ import type { ContainerContext, NodeContainer } from '@soederpop/luca'
117
+ import type { ServersInterface } from '@soederpop/luca'
118
+
119
+ declare module '@soederpop/luca' {
120
+ interface AvailableServers {
121
+ {{camelName}}: typeof {{PascalName}}
122
+ }
123
+ }
124
+
125
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({})
126
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
127
+
128
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({})
129
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
130
+
131
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({})
132
+
133
+ /**
134
+ * {{description}}
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
139
+ * await {{camelName}}.start()
140
+ * ```
141
+ *
142
+ * @extends Server
143
+ */
144
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
145
+ static override shortcut = 'servers.{{camelName}}' as const
146
+ static override stateSchema = {{PascalName}}StateSchema
147
+ static override optionsSchema = {{PascalName}}OptionsSchema
148
+ static override eventsSchema = {{PascalName}}EventsSchema
149
+ static override description = '{{description}}'
150
+
151
+ static override attach(container: NodeContainer & ServersInterface) {
152
+ return container
153
+ }
154
+
155
+ override async configure() {
156
+ if (this.isConfigured) return this
157
+ this.state.set('configured', true)
158
+ return this
159
+ }
160
+
161
+ override async start(options?: { port?: number }) {
162
+ if (this.isListening) return this
163
+ if (!this.isConfigured) await this.configure()
164
+ const port = options?.port || this.options.port || 3000
165
+ this.state.set('port', port)
166
+ this.state.set('listening', true)
167
+ return this
168
+ }
169
+
170
+ override async stop() {
171
+ if (this.isStopped) return this
172
+ this.state.set('listening', false)
173
+ this.state.set('stopped', true)
174
+ return this
175
+ }
176
+ }
177
+
178
+ export default servers.register('{{camelName}}', {{PascalName}})
179
+ ```
180
+
181
+ ## Conventions
182
+
183
+ - **Lifecycle**: Implement `configure()`, `start()`, and `stop()`. Check guards (`isConfigured`, `isListening`, `isStopped`) at the top of each.
184
+ - **State tracking**: Set `configured`, `listening`, `stopped`, and `port` on the state. This powers the introspection system.
185
+ - **attach() is static**: It runs when the container first loads the server class. Use it for container-level setup if needed.
186
+ - **Port from options**: Accept port via options schema and respect it in `start()`. Allow override via start options.
187
+ - **JSDoc everything**: Every public method needs `@param`, `@returns`, `@example`.
@@ -0,0 +1,71 @@
1
+ ---
2
+ repeatable: false
3
+ ---
4
+
5
+ # Implement Helper Discovery for WebContainer
6
+
7
+ The NodeContainer has a `helpers` feature (`src/node/features/helpers.ts`) that provides unified discovery across all registries. The WebContainer has no equivalent. Implement helper discovery for the web container.
8
+
9
+ ## Context
10
+
11
+ The node `Helpers` feature provides:
12
+ - `container.helpers.discover('features')` — scan conventional folders and register what it finds
13
+ - `container.helpers.discoverAll()` — discover across all registry types
14
+ - `container.helpers.available` — unified view of all registries
15
+ - `container.helpers.lookup(type, name)` and `container.helpers.describe(type, name)`
16
+
17
+ The web container has `features` and `clients` registries (Client is attached via `extension.ts`, which calls `container.use(Client)` and triggers `registerHelperType('clients', 'client')`). It has `RestClient` and `SocketClient` available. No servers, commands, or endpoints registries.
18
+
19
+ ## What to Build
20
+
21
+ ### 1. Create `src/web/features/helpers.ts`
22
+
23
+ Port the node `Helpers` feature to work in the browser environment. Key differences from the node version:
24
+
25
+ - **No filesystem scanning** — the browser can't scan directories. Instead, discovery should work via explicit registration or a manifest/config object that lists available helpers and their import paths.
26
+ - **Registry scope** — cover `features` and `clients` (both already attached). The `registryMap` should reflect what the web container actually has.
27
+ - **No dynamic `import()` from disk** — helper modules need to be bundled or loaded via URL. Consider accepting a map of `{ name: () => import('./my-feature.js') }` lazy loaders.
28
+ - **Keep the same public API surface** — `discover()`, `discoverAll()`, `available`, `lookup()`, `describe()` should all work identically from the consumer's perspective.
29
+
30
+ ### 2. Register it in `src/web/extension.ts`
31
+
32
+ Add the helpers feature to the web extension so it's available as `container.feature('helpers')` / `container.helpers`.
33
+
34
+ ### 3. Approach for Browser Discovery
35
+
36
+ Since there's no filesystem to scan, discovery needs a different mechanism. Recommended approach:
37
+
38
+ ```typescript
39
+ // Option A: Manifest-based discovery
40
+ const helpers = container.feature('helpers', {
41
+ enable: true,
42
+ manifest: {
43
+ features: {
44
+ myFeature: () => import('./features/my-feature.js'),
45
+ }
46
+ }
47
+ })
48
+ await helpers.discoverAll()
49
+ ```
50
+
51
+ This keeps the same `discover()` / `discoverAll()` API but replaces folder scanning with a lazy-import manifest. The manifest can be generated at build time by a bundler plugin or written by hand.
52
+
53
+ ### 4. Shared Base
54
+
55
+ Look at whether a base `Helpers` class can be extracted to `src/features/helpers.ts` (universal, not node or web specific) with the shared API surface (`available`, `lookup`, `describe`, state/events schemas). Then `src/node/features/helpers.ts` and `src/web/features/helpers.ts` extend it with their environment-specific discovery strategies (filesystem vs manifest).
56
+
57
+ ## Files to Touch
58
+
59
+ - `src/features/helpers.ts` — new, shared base class with common API
60
+ - `src/web/features/helpers.ts` — new, web-specific discovery via manifest
61
+ - `src/node/features/helpers.ts` — refactor to extend shared base
62
+ - `src/web/extension.ts` — register the web helpers feature
63
+ - `src/schemas/base.ts` — only if new shared schemas are needed
64
+
65
+ ## Acceptance Criteria
66
+
67
+ - `container.helpers.available` works in both node and web containers
68
+ - `container.helpers.discover('features')` works in web via manifest config
69
+ - `container.helpers.lookup()` and `container.helpers.describe()` work in web
70
+ - Node behavior is unchanged (existing tests still pass)
71
+ - The shared base class eliminates duplicated logic between node and web
package/docs/todos.md ADDED
@@ -0,0 +1 @@
1
+ - make container.start() meaningful. i like the idea of container.use() accepting in addition to the current shape, an async function and container.start() basically running all of those functions.