@soederpop/luca 0.0.5 → 0.0.7

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 (211) hide show
  1. package/CLAUDE.md +10 -1
  2. package/bun.lock +1 -1
  3. package/commands/build-bootstrap.ts +78 -0
  4. package/commands/build-scaffolds.ts +24 -2
  5. package/commands/try-all-challenges.ts +543 -0
  6. package/commands/try-challenge.ts +100 -0
  7. package/docs/README.md +52 -80
  8. package/docs/TABLE-OF-CONTENTS.md +82 -51
  9. package/docs/apis/clients/elevenlabs.md +232 -8
  10. package/docs/apis/clients/graph.md +59 -8
  11. package/docs/apis/clients/openai.md +362 -2
  12. package/docs/apis/clients/rest.md +122 -2
  13. package/docs/apis/clients/websocket.md +71 -17
  14. package/docs/apis/features/agi/assistant.md +9 -3
  15. package/docs/apis/features/agi/assistants-manager.md +2 -2
  16. package/docs/apis/features/agi/claude-code.md +153 -14
  17. package/docs/apis/features/agi/conversation-history.md +15 -3
  18. package/docs/apis/features/agi/conversation.md +133 -20
  19. package/docs/apis/features/agi/openai-codex.md +90 -12
  20. package/docs/apis/features/agi/skills-library.md +23 -5
  21. package/docs/apis/features/node/container-link.md +59 -0
  22. package/docs/apis/features/node/content-db.md +1 -1
  23. package/docs/apis/features/node/disk-cache.md +1 -1
  24. package/docs/apis/features/node/dns.md +1 -0
  25. package/docs/apis/features/node/docker.md +2 -1
  26. package/docs/apis/features/node/esbuild.md +4 -3
  27. package/docs/apis/features/node/file-manager.md +13 -4
  28. package/docs/apis/features/node/fs.md +726 -171
  29. package/docs/apis/features/node/git.md +1 -0
  30. package/docs/apis/features/node/google-auth.md +23 -4
  31. package/docs/apis/features/node/google-calendar.md +14 -2
  32. package/docs/apis/features/node/google-docs.md +15 -2
  33. package/docs/apis/features/node/google-drive.md +21 -3
  34. package/docs/apis/features/node/google-sheets.md +14 -2
  35. package/docs/apis/features/node/grep.md +2 -0
  36. package/docs/apis/features/node/helpers.md +29 -0
  37. package/docs/apis/features/node/ink.md +2 -2
  38. package/docs/apis/features/node/networking.md +39 -4
  39. package/docs/apis/features/node/os.md +28 -0
  40. package/docs/apis/features/node/postgres.md +26 -4
  41. package/docs/apis/features/node/proc.md +37 -28
  42. package/docs/apis/features/node/process-manager.md +33 -5
  43. package/docs/apis/features/node/repl.md +1 -1
  44. package/docs/apis/features/node/runpod.md +1 -0
  45. package/docs/apis/features/node/secure-shell.md +7 -0
  46. package/docs/apis/features/node/semantic-search.md +12 -5
  47. package/docs/apis/features/node/sqlite.md +26 -4
  48. package/docs/apis/features/node/telegram.md +30 -5
  49. package/docs/apis/features/node/tts.md +17 -2
  50. package/docs/apis/features/node/ui.md +1 -1
  51. package/docs/apis/features/node/vault.md +4 -9
  52. package/docs/apis/features/node/vm.md +3 -12
  53. package/docs/apis/features/node/window-manager.md +128 -20
  54. package/docs/apis/features/web/asset-loader.md +13 -1
  55. package/docs/apis/features/web/container-link.md +59 -0
  56. package/docs/apis/features/web/esbuild.md +4 -3
  57. package/docs/apis/features/web/helpers.md +29 -0
  58. package/docs/apis/features/web/network.md +16 -2
  59. package/docs/apis/features/web/speech.md +16 -2
  60. package/docs/apis/features/web/vault.md +4 -9
  61. package/docs/apis/features/web/vm.md +3 -12
  62. package/docs/apis/features/web/voice.md +18 -1
  63. package/docs/apis/servers/express.md +18 -2
  64. package/docs/apis/servers/mcp.md +29 -4
  65. package/docs/apis/servers/websocket.md +34 -6
  66. package/docs/bootstrap/CLAUDE.md +100 -0
  67. package/docs/bootstrap/SKILL.md +222 -0
  68. package/docs/bootstrap/templates/about-command.ts +41 -0
  69. package/docs/bootstrap/templates/docs-models.ts +22 -0
  70. package/docs/bootstrap/templates/docs-readme.md +43 -0
  71. package/docs/bootstrap/templates/example-feature.ts +53 -0
  72. package/docs/bootstrap/templates/health-endpoint.ts +15 -0
  73. package/docs/bootstrap/templates/luca-cli.ts +25 -0
  74. package/docs/challenges/caching-proxy.md +16 -0
  75. package/docs/challenges/content-db-round-trip.md +14 -0
  76. package/docs/challenges/custom-command.md +9 -0
  77. package/docs/challenges/file-watcher-pipeline.md +11 -0
  78. package/docs/challenges/grep-audit-report.md +15 -0
  79. package/docs/challenges/multi-feature-dashboard.md +14 -0
  80. package/docs/challenges/process-orchestrator.md +17 -0
  81. package/docs/challenges/rest-api-server-with-client.md +12 -0
  82. package/docs/challenges/script-runner-with-vm.md +11 -0
  83. package/docs/challenges/simple-rest-api.md +15 -0
  84. package/docs/challenges/websocket-serve-and-client.md +11 -0
  85. package/docs/challenges/yaml-config-system.md +14 -0
  86. package/docs/command-system-overhaul.md +94 -0
  87. package/docs/examples/assistant/CORE.md +18 -0
  88. package/docs/examples/assistant/hooks.ts +3 -0
  89. package/docs/examples/assistant/tools.ts +10 -0
  90. package/docs/examples/window-manager-layouts.md +180 -0
  91. package/docs/in-memory-fs.md +4 -0
  92. package/docs/models.ts +13 -10
  93. package/docs/philosophy.md +4 -3
  94. package/docs/reports/console-hmr-design.md +170 -0
  95. package/docs/reports/helper-semantic-search.md +72 -0
  96. package/docs/scaffolds/client.md +29 -20
  97. package/docs/scaffolds/command.md +64 -50
  98. package/docs/scaffolds/endpoint.md +31 -36
  99. package/docs/scaffolds/feature.md +28 -18
  100. package/docs/scaffolds/selector.md +91 -0
  101. package/docs/scaffolds/server.md +18 -9
  102. package/docs/selectors.md +115 -0
  103. package/docs/sessions/custom-command/attempt-log-2.md +195 -0
  104. package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
  105. package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
  106. package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
  107. package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
  108. package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
  109. package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
  110. package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
  111. package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
  112. package/docs/tutorials/00-bootstrap.md +148 -0
  113. package/docs/tutorials/07-endpoints.md +7 -7
  114. package/docs/tutorials/08-commands.md +153 -72
  115. package/luca.cli.ts +3 -0
  116. package/package.json +6 -5
  117. package/public/index.html +1430 -0
  118. package/scripts/examples/using-ollama.ts +2 -1
  119. package/scripts/update-introspection-data.ts +2 -2
  120. package/src/agi/endpoints/experts.ts +1 -1
  121. package/src/agi/features/assistant.ts +7 -0
  122. package/src/agi/features/assistants-manager.ts +5 -5
  123. package/src/agi/features/claude-code.ts +263 -3
  124. package/src/agi/features/conversation-history.ts +7 -1
  125. package/src/agi/features/conversation.ts +26 -3
  126. package/src/agi/features/openai-codex.ts +26 -2
  127. package/src/agi/features/openapi.ts +6 -1
  128. package/src/agi/features/skills-library.ts +9 -1
  129. package/src/bootstrap/generated.ts +540 -0
  130. package/src/cli/cli.ts +64 -21
  131. package/src/client.ts +23 -357
  132. package/src/clients/civitai/index.ts +1 -1
  133. package/src/clients/client-template.ts +1 -1
  134. package/src/clients/comfyui/index.ts +13 -2
  135. package/src/clients/elevenlabs/index.ts +2 -1
  136. package/src/clients/graph.ts +87 -0
  137. package/src/clients/openai/index.ts +10 -1
  138. package/src/clients/rest.ts +207 -0
  139. package/src/clients/websocket.ts +176 -0
  140. package/src/command.ts +281 -34
  141. package/src/commands/bootstrap.ts +181 -0
  142. package/src/commands/chat.ts +5 -4
  143. package/src/commands/describe.ts +225 -2
  144. package/src/commands/help.ts +35 -9
  145. package/src/commands/index.ts +3 -0
  146. package/src/commands/introspect.ts +92 -2
  147. package/src/commands/prompt.ts +5 -6
  148. package/src/commands/run.ts +33 -10
  149. package/src/commands/save-api-docs.ts +49 -0
  150. package/src/commands/scaffold.ts +169 -23
  151. package/src/commands/select.ts +94 -0
  152. package/src/commands/serve.ts +10 -1
  153. package/src/container.ts +15 -0
  154. package/src/endpoint.ts +19 -0
  155. package/src/graft.ts +181 -0
  156. package/src/introspection/generated.agi.ts +12458 -8968
  157. package/src/introspection/generated.node.ts +10573 -7145
  158. package/src/introspection/generated.web.ts +1 -1
  159. package/src/introspection/index.ts +26 -0
  160. package/src/node/container.ts +6 -7
  161. package/src/node/features/content-db.ts +49 -2
  162. package/src/node/features/disk-cache.ts +16 -9
  163. package/src/node/features/dns.ts +16 -3
  164. package/src/node/features/docker.ts +16 -4
  165. package/src/node/features/esbuild.ts +20 -0
  166. package/src/node/features/file-manager.ts +184 -29
  167. package/src/node/features/fs.ts +704 -248
  168. package/src/node/features/git.ts +21 -8
  169. package/src/node/features/grep.ts +23 -3
  170. package/src/node/features/helpers.ts +372 -43
  171. package/src/node/features/networking.ts +39 -4
  172. package/src/node/features/opener.ts +28 -15
  173. package/src/node/features/os.ts +76 -0
  174. package/src/node/features/port-exposer.ts +11 -1
  175. package/src/node/features/postgres.ts +17 -1
  176. package/src/node/features/proc.ts +4 -1
  177. package/src/node/features/python.ts +63 -14
  178. package/src/node/features/repl.ts +11 -7
  179. package/src/node/features/runpod.ts +16 -3
  180. package/src/node/features/secure-shell.ts +27 -2
  181. package/src/node/features/semantic-search.ts +12 -1
  182. package/src/node/features/ui.ts +5 -69
  183. package/src/node/features/vm.ts +17 -0
  184. package/src/node/features/window-manager.ts +68 -20
  185. package/src/node.ts +5 -0
  186. package/src/scaffolds/generated.ts +492 -290
  187. package/src/scaffolds/template.ts +9 -0
  188. package/src/schemas/base.ts +46 -5
  189. package/src/selector.ts +282 -0
  190. package/src/server.ts +11 -0
  191. package/src/servers/express.ts +27 -12
  192. package/src/servers/socket.ts +45 -11
  193. package/src/web/clients/socket.ts +4 -1
  194. package/src/web/container.ts +2 -1
  195. package/src/web/features/network.ts +7 -1
  196. package/src/web/features/voice-recognition.ts +16 -1
  197. package/test/clients-servers.test.ts +2 -1
  198. package/test/command.test.ts +267 -0
  199. package/test-integration/assistants-manager.test.ts +10 -20
  200. package/tmp/.cache/luca-disk-cache/content-v2/sha512/1b/b5/c75b28794f00f94c4d609a98978e9420e9b7146d204a7fbf5b0b30477292581705d207c0100dabaac27eef540aaaece3374af75104a93219d4ec8bfb44e7 +1 -0
  201. package/tmp/.cache/luca-disk-cache/content-v2/sha512/da/df/1d90ce4e042abeb035a197832c6d6893420a747a056be773eb00e4f745a037d505c8db13dde7d36b36b6b893addbb7df0f5fe9f0c13e665f20056447318b +1 -0
  202. package/tmp/.cache/luca-disk-cache/content-v2/sha512/ed/04/e1d0c2a58c2db29b3921ca2affb3ea4febe831c53b38ebc21019fb799823aba6ed5b4611873d2cd25d422d49955b852a9c326da0d678899bc1c2c2960901 +1 -0
  203. package/tmp/.cache/luca-disk-cache/index-v5/00/13/572aa4c9a94f99eda999695d050cdd0ca7fe2d23a50af03234d4c8ce0791 +2 -0
  204. package/tmp/.cache/luca-disk-cache/index-v5/75/a9/cb61dc0f0589e8ec10a9aca27b834bc73884c479941042d22a2b22324cd3 +2 -0
  205. package/tmp/.cache/luca-disk-cache/index-v5/9f/0f/8b1f915ee64cfff7667dd96acd7a5ac0a96aa91a346e19cefd45909a9c9c +2 -0
  206. package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
  207. package/docs/examples/launcher-app-command-listener.md +0 -120
  208. package/docs/tasks/web-container-helper-discovery.md +0 -71
  209. package/docs/todos.md +0 -1
  210. package/scripts/test-command-listener.ts +0 -123
  211. package/src/node/features/launcher-app-command-listener.ts +0 -389
@@ -0,0 +1,540 @@
1
+ // Auto-generated bootstrap content
2
+ // Generated at: 2026-03-19T00:28:07.695Z
3
+ // Source: docs/bootstrap/*.md, docs/bootstrap/templates/*
4
+ //
5
+ // Do not edit manually. Run: luca build-bootstrap
6
+
7
+ export const bootstrapFiles: Record<string, string> = {
8
+ "SKILL": `---
9
+ name: Using the luca framework
10
+ description: Learn the luca container — discover what's available with luca describe, build new helpers with luca scaffold, and prototype with luca eval
11
+ ---
12
+ # Luca: Learning the Container
13
+
14
+ The Luca framework \`@soederpop/luca\` ships a \`luca\` binary — a bun-based CLI for a dependency injection container. This project is based on it if this skill is present. The container auto-discovers modules in \`commands/\`, \`clients/\`, \`servers/\`, \`features/\`, and \`endpoints/\` folders.
15
+
16
+ There are three things to learn, in this order:
17
+
18
+ 1. **Discover** what the container can do — \`luca describe\`
19
+ 2. **Build** new helpers when your project needs them — \`luca scaffold\`
20
+ 3. **Prototype** and debug with live code — \`luca eval\`
21
+
22
+ ---
23
+
24
+ ## Phase 1: Discover with \`luca describe\`
25
+
26
+ This is your primary tool. Before reading source files, searching for APIs, or writing any code — ask describe. It outputs full documentation for any part of the container: methods, options, events, state, examples.
27
+
28
+ ### See what's available
29
+
30
+ \`\`\`shell
31
+ luca describe features # index of all available features
32
+ luca describe clients # index of all available clients
33
+ luca describe servers # index of all available servers
34
+ \`\`\`
35
+
36
+ ### Learn about specific helpers
37
+
38
+ \`\`\`shell
39
+ luca describe fs # full docs for the fs feature
40
+ luca describe git # full docs for git
41
+ luca describe rest # full docs for the rest client
42
+ luca describe express # full docs for the express server
43
+ luca describe git fs proc # multiple helpers in one shot
44
+ \`\`\`
45
+
46
+ ### Get targeted documentation
47
+
48
+ You can filter to only the sections you need:
49
+
50
+ \`\`\`shell
51
+ luca describe fs --methods # just the methods
52
+ luca describe git --events # just the events it emits
53
+ luca describe express --options # just the constructor options
54
+ luca describe fs git --examples # just examples for both
55
+ luca describe fs --usage --methods # combine sections
56
+ \`\`\`
57
+
58
+ ### Describe the container itself
59
+
60
+ \`\`\`shell
61
+ luca describe # overview of the container
62
+ luca describe self # same thing
63
+ \`\`\`
64
+
65
+ ### Get help on any command
66
+
67
+ \`\`\`shell
68
+ luca # list all available commands
69
+ luca describe --help # full flag reference for describe
70
+ luca help scaffold # help for any command
71
+ \`\`\`
72
+
73
+ **Use \`luca describe\` liberally.** It is the fastest, safest way to understand what the container provides. Every feature, client, and server is self-describing — if you know a name, describe will tell you everything about it.
74
+
75
+ ---
76
+
77
+ ## Phase 2: Build with \`luca scaffold\`
78
+
79
+ When your project needs a new helper, scaffold it. The \`scaffold\` command generates correct boilerplate — you fill in the logic.
80
+
81
+ ### Learn how to build each type
82
+
83
+ Before creating anything, read the tutorial for that helper type:
84
+
85
+ \`\`\`shell
86
+ luca scaffold feature --tutorial # how features work, full guide
87
+ luca scaffold command --tutorial # how commands work
88
+ luca scaffold endpoint --tutorial # how endpoints work
89
+ luca scaffold client --tutorial # how clients work
90
+ luca scaffold server --tutorial # how servers work
91
+ \`\`\`
92
+
93
+ These tutorials are the authoritative reference for each helper type. They cover imports, schemas, class structure, registration, conventions, and complete examples.
94
+
95
+ ### Generate a helper
96
+
97
+ \`\`\`shell
98
+ luca scaffold <type> <name> --description "What it does"
99
+ \`\`\`
100
+
101
+ The workflow after scaffolding:
102
+
103
+ \`\`\`shell
104
+ luca scaffold command sync-data --description "Pull data from staging"
105
+ # edit commands/sync-data.ts — add your logic
106
+ luca describe sync-data # verify it shows up and reads correctly
107
+ \`\`\`
108
+
109
+ Every scaffolded helper is auto-discovered by the container at runtime.
110
+
111
+ ### When to use each type
112
+
113
+ | You need to... | Scaffold a... | Example |
114
+ |----------------------------------------------------|----------------|----------------------------------------------------------------|
115
+ | Add a reusable local capability (caching, crypto) | **feature** | \`luca scaffold feature disk-cache --description "File-backed key-value cache"\` |
116
+ | Add a CLI task (build, deploy, generate) | **command** | \`luca scaffold command deploy --description "Deploy to production"\` |
117
+ | Talk to an external API or service | **client** | \`luca scaffold client github --description "GitHub API wrapper"\` |
118
+ | Accept incoming connections (HTTP, WS) | **server** | \`luca scaffold server mqtt --description "MQTT broker"\` |
119
+ | Add a REST route to \`luca serve\` | **endpoint** | \`luca scaffold endpoint users --description "User management API"\` |
120
+
121
+ ### Scaffold options
122
+
123
+ \`\`\`shell
124
+ luca scaffold command deploy --description "..." # writes to commands/deploy.ts
125
+ luca scaffold endpoint users --print # print to stdout instead of writing
126
+ luca scaffold feature cache --output lib/cache.ts # override output path
127
+ \`\`\`
128
+
129
+ ---
130
+
131
+ ## Phase 3: Prototype with \`luca eval\`
132
+
133
+ Once you know what's available (describe) and how to build things (scaffold), use \`luca eval\` to test ideas, verify behavior, and debug.
134
+
135
+ \`\`\`shell
136
+ luca eval "container.features.available"
137
+ luca eval "container.feature('proc').exec('ls')"
138
+ luca eval "container.feature('fs').readFile('package.json')"
139
+ \`\`\`
140
+
141
+ The eval command boots a full container with all helpers discovered and registered. Core features are available as top-level shortcuts:
142
+
143
+ \`\`\`shell
144
+ luca eval "fs.readFile('package.json')"
145
+ luca eval "git.branch"
146
+ luca eval "proc.exec('ls')"
147
+ \`\`\`
148
+
149
+ **Reach for eval when you're stuck.** It gives you full control of the container at runtime — you can test method calls, inspect state, verify event behavior, and debug issues that are hard to reason about from docs alone.
150
+
151
+ **Use eval as a testing tool.** Before wiring up a full command handler or feature, test your logic in eval first. Want to verify how \`fs.moveAsync\` behaves, or whether a watcher event fires the way you expect? Run it in eval. This is the fastest way to validate container code without the overhead of building the full command around it.
152
+
153
+ \`\`\`shell
154
+ # Test file operations before building a command around them
155
+ luca eval "await fs.moveAsync('inbox/test.json', 'inbox/valid/test.json')"
156
+
157
+ # First: luca describe fileManager --events (to learn what events exist)
158
+ # Then test the behavior:
159
+ luca eval "const fm = container.feature('fileManager'); fm.on('file:change', (e) => console.log(e)); await fm.watch({ paths: ['inbox'] })"
160
+ \`\`\`
161
+
162
+ ### The REPL
163
+
164
+ For interactive exploration, \`luca console\` opens a persistent REPL with the container in scope. Useful when you need to try multiple things in sequence.
165
+
166
+ ---
167
+
168
+ ## Key Concepts
169
+
170
+ ### The Container
171
+
172
+ The container is a singleton that holds everything your application needs. It organizes components into **registries**: features, clients, servers, commands, and endpoints. Use the factory functions to get instances:
173
+
174
+ \`\`\`js
175
+ const fs = container.feature('fs')
176
+ const rest = container.client('rest')
177
+ const server = container.server('express')
178
+ \`\`\`
179
+
180
+ ### State
181
+
182
+ Every helper and the container itself have observable state:
183
+
184
+ \`\`\`js
185
+ const feature = container.feature('fs')
186
+
187
+ feature.state.current // snapshot of all state
188
+ feature.state.get('someKey') // single value
189
+ feature.state.set('key', 'value') // update
190
+
191
+ // Watch for changes
192
+ feature.state.observe((changeType, key, value) => {
193
+ // changeType: 'add' | 'update' | 'delete'
194
+ })
195
+ \`\`\`
196
+
197
+ The container has state too: \`container.state.current\`, \`container.state.observe()\`.
198
+
199
+ ### Events
200
+
201
+ Every helper and the container are event emitters — \`on\`, \`once\`, \`emit\`, \`waitFor\` all work as expected. Use \`luca describe <name> --events\` to see what a helper emits.
202
+
203
+ ### Utilities
204
+
205
+ The container provides common utilities at \`container.utils\` — no external imports needed:
206
+
207
+ - \`container.utils.uuid()\` — v4 UUID
208
+ - \`container.utils.hashObject(obj)\` — deterministic hash
209
+ - \`container.utils.stringUtils\` — camelCase, kebabCase, pluralize, etc.
210
+ - \`container.utils.lodash\` — groupBy, keyBy, pick, omit, debounce, etc.
211
+ - \`container.paths.resolve()\` / \`container.paths.join()\` — path operations
212
+
213
+ ### Programmatic introspection
214
+
215
+ Everything \`luca describe\` outputs is also available at runtime in code:
216
+
217
+ \`\`\`js
218
+ container.features.describe('fs') // markdown docs (same as the CLI)
219
+ feature.introspect() // structured object: { methods, events, state, options }
220
+ container.inspectAsText() // full container overview as markdown
221
+ \`\`\`
222
+
223
+ This is useful inside commands and scripts where you need introspection data programmatically.
224
+
225
+ ---
226
+
227
+ ## Reference
228
+
229
+ See \`references/api-docs/\` for the full pre-generated API reference for every built-in feature, client, and server.
230
+ `,
231
+ "CLAUDE": `# Luca Project
232
+
233
+ This project uses the [Luca framework](https://github.com/soederpop/luca) — Lightweight Universal Conversational Architecture.
234
+
235
+ For a deep dive into the framework internals, see the [Luca GitHub repository](https://github.com/soederpop/luca).
236
+
237
+ ## Runtime
238
+
239
+ The runtime is **bun**. Use \`bun run\` for scripts, \`bun test\` for tests.
240
+
241
+ ## The \`luca\` CLI
242
+
243
+ The \`luca\` binary is available in the path. Key commands:
244
+
245
+ - \`luca\` — list available commands (built-in + project commands)
246
+ - \`luca eval "expression"\` — evaluate JS with the container in scope
247
+ - \`luca describe <name>\` — full docs for any feature, client, or server (e.g. \`luca describe fs\`)
248
+ - \`luca describe features\` — index of all available features (also: \`clients\`, \`servers\`)
249
+ - \`luca serve\` — start a local server using \`endpoints/\` folder
250
+ - \`luca run script.ts\` — run a script with the container
251
+ - \`luca scaffold <type> <name>\` — generate boilerplate for a new helper (run \`luca scaffold\` for full help)
252
+
253
+ ## Container Rules
254
+
255
+ - **NEVER import from \`fs\`, \`path\`, or other Node builtins.** Use \`container.feature('fs')\` for file operations, \`container.paths\` for path operations.
256
+ - The container should provide everything you need. If something is missing, raise the concern rather than pulling in external dependencies.
257
+ - Use \`container.utils\` for common utilities (uuid, lodash helpers, string utils).
258
+
259
+ ## Learning the Framework
260
+
261
+ 1. **Discover** — Run \`luca describe features\`, \`luca describe clients\`, \`luca describe servers\` to see what's available. Then \`luca describe <name>\` for full docs on any helper. This is your first move, always. (See \`.claude/skills/luca-framework/SKILL.md\` for the full mental model.)
262
+ 2. **Build** — Run \`luca scaffold <type> --tutorial\` before creating a new helper. It covers the full guide for that type.
263
+ 3. **Prototype** — Use \`luca eval "expression"\` to test container code before wiring up full handlers. Reach for eval when you're stuck — it gives you full runtime access.
264
+ 4. **Reference** — Browse \`.claude/skills/luca-framework/references/api-docs/\` for pre-generated API docs
265
+
266
+ ## Project Structure
267
+
268
+ - \`commands/\` — custom CLI commands, run via \`luca <commandName>\` (auto-discovered)
269
+ - \`endpoints/\` — file-based HTTP routes, served via \`luca serve\` (auto-discovered)
270
+ - \`features/\` — custom container features, discovered via \`container.helpers.discoverAll()\` (auto-discovered)
271
+ - \`docs/\` — content documents managed by the \`contentDb\` feature (\`container.docs\`). See [contentbase](https://github.com/soederpop/contentbase) for the document model system.
272
+ - \`luca.cli.ts\` — optional project-level CLI customization (runs before any command)
273
+
274
+ ## Command Arguments
275
+
276
+ Command handlers receive \`(options, context)\`. The \`options\` object contains:
277
+ - **Named flags** from \`argsSchema\`: \`--verbose\` → \`options.verbose\`
278
+ - **Positional args** mapped via \`positionals\` export: \`luca cmd ./src\` → \`options.target\`
279
+ - **Raw positionals** in \`options._\`: array where \`_[0]\` is the command name, \`_[1+]\` are positional args
280
+
281
+ To accept positional arguments, export a \`positionals\` array that maps them to named fields in \`argsSchema\`:
282
+
283
+ \`\`\`ts
284
+ export const positionals = ['target'] // luca myCmd ./src => options.target === './src'
285
+ export const argsSchema = z.object({
286
+ target: z.string().optional().describe('The target to operate on'),
287
+ verbose: z.boolean().default(false).describe('Enable verbose output'),
288
+ })
289
+ \`\`\`
290
+
291
+ ## What's Available
292
+
293
+ The container provides more than you might expect. Before importing anything external, check here:
294
+
295
+ - **YAML** — \`container.feature('yaml')\` wraps \`js-yaml\`. Use \`.parse(str)\` and \`.stringify(obj)\`.
296
+ - **SQLite** — \`container.feature('sqlite')\` for databases. Parameterized queries, tagged templates.
297
+ - **REST client** — \`container.client('rest', { baseURL })\`. Methods (\`get\`, \`post\`, etc.) return **parsed JSON directly**, not \`{ data, status, headers }\`. On HTTP errors, the error is returned (not thrown).
298
+ - **Content DB** — \`container.docs\` (alias for \`container.feature('contentDb')\`) manages markdown documents with frontmatter. Query with \`docs.query(docs.models.MyModel).fetchAll()\`.
299
+ - **Grep** — \`container.feature('grep')\` has \`search()\` and \`codeAnnotations()\` for finding TODOs/FIXMEs/etc.
300
+ - **chalk** — available as \`container.feature('ui').colors\`, not via \`import('chalk')\`.
301
+ - **figlet** — available as \`container.feature('ui').asciiArt(text)\`.
302
+ - **uuid** — \`container.utils.uuid()\`
303
+ - **lodash** — \`container.utils.lodash\` (groupBy, keyBy, pick, omit, debounce, etc.)
304
+ - **string utils** — \`container.utils.stringUtils\` (camelCase, kebabCase, pluralize, etc.)
305
+
306
+ ## Known Gotchas
307
+
308
+ - **For DELETE endpoint handlers, use \`export { del as delete }\`** — \`delete\` is a JS reserved word. Define your function with any name, then re-export it as \`delete\`.
309
+ - **Bun globals (\`Bun.spawn\`, \`Bun.serve\`) are unavailable** in command/endpoint handlers. Use Node's \`child_process\` for spawning processes, or use \`container.feature('proc').exec()\`.
310
+ - **\`ui.print.*\` writes to stdout** — if your command supports \`--json\`, gate UI output behind \`if (!options.json)\`.
311
+ - **VM contexts start empty** — when using \`container.feature('vm')\`, inject globals explicitly (\`console\`, \`Date\`, \`Promise\`, \`crypto\`, \`TextEncoder\`, \`setTimeout\`).
312
+ - **Long-running commands** (servers, watchers) need \`await new Promise(() => {})\` at the end with a \`process.on('SIGINT', ...)\` handler for cleanup.
313
+ - **Shared state between endpoints**: use \`ctx.request.app.locals\` to share data across endpoint files.
314
+ - **Database init**: use \`luca.cli.ts\` \`main()\` hook for table creation and seeding — it runs before any command or server starts.
315
+
316
+ ## Extending the Container
317
+
318
+ Use \`luca scaffold\` to generate new helpers:
319
+
320
+ \`\`\`sh
321
+ luca scaffold command myTask --description "Automate something"
322
+ luca scaffold feature myCache --description "Custom caching layer"
323
+ luca scaffold endpoint users --description "User management API"
324
+ \`\`\`
325
+
326
+ Run \`luca scaffold\` with no arguments for full usage and examples.
327
+
328
+ ## Git Strategy
329
+
330
+ Roll on main. Commit with good messages that explain why, not just what.
331
+ `
332
+ }
333
+
334
+ export const bootstrapTemplates: Record<string, string> = {
335
+ "docs-models": `import { defineModel, z } from 'contentbase'
336
+
337
+ /**
338
+ * Define your content models here. Each model maps to a folder prefix
339
+ * inside the docs/ directory. Documents in those folders follow the
340
+ * model's metadata schema.
341
+ *
342
+ * Access documents at runtime:
343
+ * const docs = container.docs // contentDb feature
344
+ * if (!docs.isLoaded) await docs.load()
345
+ * const notes = await docs.query(docs.models.Note).fetchAll()
346
+ *
347
+ * See https://github.com/soederpop/contentbase for full documentation.
348
+ */
349
+
350
+ export const Note = defineModel('Note', {
351
+ prefix: 'notes',
352
+ meta: z.object({
353
+ tags: z.array(z.string()).default([]),
354
+ status: z.enum(['draft', 'published']).default('draft'),
355
+ }),
356
+ })
357
+ `,
358
+ "luca-cli": `/**
359
+ * luca.cli.ts — Project-level CLI customization
360
+ *
361
+ * This file is automatically loaded by the \`luca\` CLI before any command runs.
362
+ * Use it to:
363
+ *
364
+ * - Discover project-level helpers (features, commands, endpoints)
365
+ * - Register custom context variables accessible in \`luca eval\`
366
+ * - Set up project-specific container configuration
367
+ *
368
+ * Exports:
369
+ * main(container) — called at CLI startup, before command execution
370
+ * onStart(container) — called when the container's 'started' event fires
371
+ *
372
+ * Example:
373
+ * export async function main(container: any) {
374
+ * await container.helpers.discoverAll()
375
+ * container.addContext('myFeature', container.feature('myFeature'))
376
+ * }
377
+ */
378
+
379
+ export async function main(container: any) {
380
+ // Discover project-level helpers (commands/, features/, endpoints/)
381
+ await container.helpers.discoverAll()
382
+ }
383
+ `,
384
+ "docs-readme": `# Docs
385
+
386
+ This folder contains structured content documents managed by the [contentbase](https://github.com/soederpop/contentbase) system.
387
+
388
+ ## How it works
389
+
390
+ Documents are markdown files with YAML frontmatter. Each document belongs to a **model** defined in \`docs/models.ts\`. Models specify:
391
+ - A **prefix** (subfolder name, e.g. \`notes/\`)
392
+ - A **metadata schema** (validated frontmatter fields)
393
+
394
+ ## Accessing documents at runtime
395
+
396
+ The \`contentDb\` feature (aliased to \`container.docs\`) loads and queries documents:
397
+
398
+ \`\`\`typescript
399
+ const docs = container.docs
400
+ if (!docs.isLoaded) await docs.load()
401
+
402
+ // Query all notes
403
+ const notes = await docs.query(docs.models.Note).fetchAll()
404
+
405
+ // Get a specific document
406
+ const doc = docs.collection('notes').document('my-note')
407
+ \`\`\`
408
+
409
+ ## Creating a new document
410
+
411
+ Add a markdown file in the appropriate subfolder:
412
+
413
+ \`\`\`markdown
414
+ ---
415
+ title: My First Note
416
+ tags: [example]
417
+ status: draft
418
+ ---
419
+
420
+ Content goes here.
421
+ \`\`\`
422
+
423
+ ## Learn more
424
+
425
+ - [Contentbase GitHub](https://github.com/soederpop/contentbase) — full documentation and API reference
426
+ - \`luca describe contentDb\` — runtime docs for the contentDb feature
427
+ `,
428
+ "example-feature": `import { z } from 'zod'
429
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
430
+ import { Feature } from '@soederpop/luca'
431
+ import type { ContainerContext } from '@soederpop/luca'
432
+
433
+ declare module '@soederpop/luca' {
434
+ interface AvailableFeatures {
435
+ example: typeof Example
436
+ }
437
+ }
438
+
439
+ const ExampleStateSchema = FeatureStateSchema.extend({
440
+ greetCount: z.number().default(0).describe('Number of times greet() has been called'),
441
+ })
442
+ type ExampleState = z.infer<typeof ExampleStateSchema>
443
+
444
+ const ExampleOptionsSchema = FeatureOptionsSchema.extend({})
445
+ type ExampleOptions = z.infer<typeof ExampleOptionsSchema>
446
+
447
+ /**
448
+ * An example feature demonstrating the luca feature pattern.
449
+ *
450
+ * Discovered automatically by \`container.helpers.discoverAll()\`
451
+ * and available as \`container.feature('example')\`.
452
+ *
453
+ * To learn more: \`luca scaffold feature --tutorial\`
454
+ *
455
+ * @example
456
+ * \`\`\`typescript
457
+ * const example = container.feature('example')
458
+ * example.greet('Luca') // => "Hello, Luca! (greeting #1)"
459
+ * \`\`\`
460
+ */
461
+ export class Example extends Feature<ExampleState, ExampleOptions> {
462
+ static override shortcut = 'features.example' as const
463
+ static override stateSchema = ExampleStateSchema
464
+ static override optionsSchema = ExampleOptionsSchema
465
+ static override description = 'An example feature demonstrating the luca feature pattern'
466
+ static { Feature.register(this, 'example') }
467
+
468
+ /**
469
+ * A simple method to show the feature works.
470
+ * @param name - Name to greet
471
+ * @returns Greeting string
472
+ */
473
+ greet(name = 'World') {
474
+ const count = (this.state.get('greetCount') || 0) + 1
475
+ this.state.set('greetCount', count)
476
+ return \`Hello, \${name}! (greeting #\${count})\`
477
+ }
478
+ }
479
+
480
+ export default Example
481
+ `,
482
+ "about-command": `/**
483
+ * about — Display project information and discovered helpers.
484
+ * Run with: luca about
485
+ *
486
+ * Positional words after the command name are available as options._
487
+ * For example: \`luca about commands\` → options._[1] === 'commands'
488
+ */
489
+ import { z } from 'zod'
490
+ import type { ContainerContext } from '@soederpop/luca'
491
+
492
+ export const description = 'Display project information and discovered helpers'
493
+
494
+ export const argsSchema = z.object({})
495
+
496
+ export default async function about(options: z.infer<typeof argsSchema>, context: ContainerContext) {
497
+ const { container } = context
498
+ const ui = container.feature('ui')
499
+
500
+ // Discover all project-level helpers (commands, features, endpoints, etc.)
501
+ const discovered = await container.helpers.discoverAll()
502
+
503
+ const projectName = container.paths.resolve('.').split('/').pop() || 'project'
504
+
505
+ ui.print.cyan(\`\\n \${projectName}\\n\`)
506
+ ui.print(' Powered by luca — Lightweight Universal Conversational Architecture\\n')
507
+
508
+ const types = ['features', 'clients', 'servers', 'commands', 'endpoints']
509
+
510
+ for (const type of types) {
511
+ const names = discovered[type] || []
512
+ if (names.length > 0) {
513
+ ui.print.green(\` \${type} (\${names.length})\`)
514
+ for (const name of names) {
515
+ ui.print(\` • \${name}\`)
516
+ }
517
+ }
518
+ }
519
+
520
+ const totalBuiltIn = types.reduce((sum: number, t: string) => sum + (container[t]?.available?.length || 0), 0)
521
+ ui.print.dim(\`\\n \${totalBuiltIn} built-in helpers available. Run \\\`luca describe\\\` for details.\\n\`)
522
+ }
523
+ `,
524
+ "health-endpoint": `/**
525
+ * Health check endpoint.
526
+ * Accessible at GET /api/health when you run \`luca serve\`.
527
+ */
528
+ export const path = '/api/health'
529
+ export const description = 'Health check endpoint'
530
+ export const tags = ['health']
531
+
532
+ export async function get(_params: any, ctx: any) {
533
+ return {
534
+ status: 'ok',
535
+ timestamp: Date.now(),
536
+ uptime: process.uptime(),
537
+ }
538
+ }
539
+ `
540
+ }
package/src/cli/cli.ts CHANGED
@@ -5,30 +5,74 @@ import { homedir } from 'os'
5
5
  import { join } from 'path'
6
6
 
7
7
  async function main() {
8
+ const profile = process.env.LUCA_PROFILE === '1'
9
+ const t = (label?: string) => {
10
+ if (!profile) return () => {}
11
+ const start = performance.now()
12
+ return (suffix?: string) => {
13
+ const ms = (performance.now() - start).toFixed(1)
14
+ console.error(`[profile] ${label}${suffix ? ` ${suffix}` : ''}: ${ms}ms`)
15
+ }
16
+ }
17
+
18
+ const tTotal = t('total boot')
19
+
20
+ // LUCA_COMMAND_DISCOVERY: "disable" skips all, "no-local" skips project, "no-home" skips user
21
+ const discovery = process.env.LUCA_COMMAND_DISCOVERY || ''
22
+
23
+ // Snapshot built-in commands BEFORE loadCliModule — luca.cli.ts may call
24
+ // helpers.discoverAll() which registers project commands early
25
+ const builtinCommands = new Set(container.commands.available as string[])
26
+
8
27
  // Load project-level CLI module (luca.cli.ts) for container customization
28
+ let done = t('loadCliModule')
9
29
  await loadCliModule()
30
+ done()
31
+
10
32
  // Discover project-local commands (commands/ or src/commands/)
11
- await discoverProjectCommands()
33
+ done = t('discoverProjectCommands')
34
+ if (discovery !== 'disable' && discovery !== 'no-local') {
35
+ await discoverProjectCommands()
36
+ }
37
+ done()
38
+ const afterProject = new Set(container.commands.available as string[])
39
+ const projectCommands = new Set([...afterProject].filter((n) => !builtinCommands.has(n)))
40
+
12
41
  // Discover user-level commands (~/.luca/commands/)
13
- await discoverUserCommands()
42
+ done = t('discoverUserCommands')
43
+ if (discovery !== 'disable' && discovery !== 'no-home') {
44
+ await discoverUserCommands()
45
+ }
46
+ done()
47
+ const afterUser = new Set(container.commands.available as string[])
48
+ const userCommands = new Set([...afterUser].filter((n) => !builtinCommands.has(n) && !projectCommands.has(n)))
49
+
50
+ // Store command sources for help display
51
+ ;(container as any)._commandSources = { builtinCommands, projectCommands, userCommands }
52
+
14
53
  // Load generated introspection data if present
54
+ done = t('loadProjectIntrospection')
15
55
  await loadProjectIntrospection()
56
+ done()
16
57
 
17
58
  const commandName = container.argv._[0] as string
18
59
 
60
+ done = t('dispatch')
19
61
  if (commandName && container.commands.has(commandName)) {
20
62
  const cmd = container.command(commandName as any)
21
- await cmd.run()
63
+ await cmd.dispatch()
22
64
  } else if (commandName) {
23
65
  // not a known command — treat as implicit `run`
24
66
  container.argv._.splice(0, 0, 'run')
25
67
  const cmd = container.command('run' as any)
26
- await cmd.run()
68
+ await cmd.dispatch()
27
69
  } else {
28
70
  container.argv._.splice(0, 0, 'help')
29
71
  const cmd = container.command('help' as any)
30
- await cmd.run()
72
+ await cmd.dispatch()
31
73
  }
74
+ done()
75
+ tTotal()
32
76
  }
33
77
 
34
78
 
@@ -36,28 +80,26 @@ async function loadCliModule() {
36
80
  const modulePath = container.paths.resolve('luca.cli.ts')
37
81
  if (!container.fs.exists(modulePath)) return
38
82
 
39
- const mod = await import(modulePath)
40
- const exports = mod.default || mod
83
+ // Use the helpers feature to load the module — it handles the native import
84
+ // vs VM decision using the same useNativeImport check as discovery
85
+ const helpers = container.feature('helpers') as any
86
+ const exports = await helpers.loadModuleExports(modulePath)
41
87
 
42
- if (typeof exports.main === 'function') {
88
+ if (typeof exports?.main === 'function') {
43
89
  await exports.main(container)
44
90
  }
45
91
 
46
- if (typeof exports.onStart === 'function') {
92
+ if (typeof exports?.onStart === 'function') {
47
93
  container.once('started', () => exports.onStart(container))
48
94
  }
49
95
  }
50
96
 
51
97
  async function discoverProjectCommands() {
52
- const { fs, paths } = container
53
-
54
- for (const candidate of ['commands', 'src/commands']) {
55
- const dir = paths.resolve(candidate)
56
- if (fs.exists(dir)) {
57
- await container.commands.discover({ directory: dir })
58
- return
59
- }
60
- }
98
+ // Always route through the helpers feature — it handles native import vs VM
99
+ // internally, and deduplicates concurrent/repeated discovery via promise caching.
100
+ // If luca.cli.ts already called helpers.discoverAll(), this resolves instantly.
101
+ const helpers = container.feature('helpers') as any
102
+ await helpers.discover('commands')
61
103
  }
62
104
 
63
105
  async function loadProjectIntrospection() {
@@ -81,11 +123,12 @@ async function loadProjectIntrospection() {
81
123
  }
82
124
 
83
125
  async function discoverUserCommands() {
84
- const { fs } = container
85
126
  const dir = join(homedir(), '.luca', 'commands')
86
127
 
87
- if (fs.exists(dir)) {
88
- await container.commands.discover({ directory: dir })
128
+ if (container.fs.exists(dir)) {
129
+ // Route through helpers for consistent dedup and VM/native handling
130
+ const helpers = container.feature('helpers') as any
131
+ await helpers.discover('commands', { directory: dir })
89
132
  }
90
133
  }
91
134