@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,1613 @@
1
+ // Auto-generated scaffold and MCP readme content
2
+ // Generated at: 2026-03-07T09:00:47.116Z
3
+ // Source: docs/scaffolds/*.md and docs/mcp/readme.md
4
+ //
5
+ // Do not edit manually. Run: luca build-scaffolds
6
+
7
+ export interface ScaffoldSection {
8
+ heading: string
9
+ code: string
10
+ }
11
+
12
+ export interface ScaffoldData {
13
+ sections: ScaffoldSection[]
14
+ full: string
15
+ tutorial: string
16
+ }
17
+
18
+ export const scaffolds: Record<string, ScaffoldData> = {
19
+ feature: {
20
+ sections: [
21
+ { heading: "Imports", code: `import { z } from 'zod'
22
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '@soederpop/luca'
23
+ import { Feature, features } from '@soederpop/luca'
24
+ import type { ContainerContext } from '@soederpop/luca'` },
25
+ { heading: "Schemas", code: `export const {{PascalName}}StateSchema = FeatureStateSchema.extend({
26
+ // Add your state fields here. These are observable — changes emit events.
27
+ // Example: itemCount: z.number().default(0).describe('Number of items stored'),
28
+ })
29
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
30
+
31
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({
32
+ // Add constructor options here. Validated when the feature is created.
33
+ // Example: directory: z.string().optional().describe('Storage directory path'),
34
+ })
35
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
36
+
37
+ export const {{PascalName}}EventsSchema = FeatureEventsSchema.extend({
38
+ // Each key is an event name. Value is z.tuple() of listener arguments.
39
+ // Example: itemAdded: z.tuple([z.string().describe('Item key')]).describe('Emitted when an item is added'),
40
+ })` },
41
+ { heading: "Class", code: `/**
42
+ * {{description}}
43
+ *
44
+ * @example
45
+ * \`\`\`typescript
46
+ * const {{camelName}} = container.feature('{{camelName}}')
47
+ * \`\`\`
48
+ *
49
+ * @extends Feature
50
+ */
51
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
52
+ static override shortcut = 'features.{{camelName}}' as const
53
+ static override stateSchema = {{PascalName}}StateSchema
54
+ static override optionsSchema = {{PascalName}}OptionsSchema
55
+ static override eventsSchema = {{PascalName}}EventsSchema
56
+ static override description = '{{description}}'
57
+
58
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
59
+ super(options, context)
60
+ // Initialize state, set up resources
61
+ }
62
+
63
+ // Add your methods here. Every public method needs JSDoc.
64
+ }` },
65
+ { heading: "Module Augmentation", code: `declare module '@soederpop/luca' {
66
+ interface AvailableFeatures {
67
+ {{camelName}}: typeof {{PascalName}}
68
+ }
69
+ }` },
70
+ { heading: "Registration", code: `export default features.register('{{camelName}}', {{PascalName}})` },
71
+ { heading: "Complete Example", code: `import { z } from 'zod'
72
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
73
+ import { Feature, features } from '@soederpop/luca'
74
+ import type { ContainerContext } from '@soederpop/luca'
75
+
76
+ declare module '@soederpop/luca' {
77
+ interface AvailableFeatures {
78
+ {{camelName}}: typeof {{PascalName}}
79
+ }
80
+ }
81
+
82
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({})
83
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
84
+
85
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({})
86
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
87
+
88
+ /**
89
+ * {{description}}
90
+ *
91
+ * @example
92
+ * \`\`\`typescript
93
+ * const {{camelName}} = container.feature('{{camelName}}')
94
+ * \`\`\`
95
+ *
96
+ * @extends Feature
97
+ */
98
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
99
+ static override shortcut = 'features.{{camelName}}' as const
100
+ static override stateSchema = {{PascalName}}StateSchema
101
+ static override optionsSchema = {{PascalName}}OptionsSchema
102
+ static override description = '{{description}}'
103
+
104
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
105
+ super(options, context)
106
+ }
107
+ }
108
+
109
+ export default features.register('{{camelName}}', {{PascalName}})` }
110
+ ],
111
+ full: `import { z } from 'zod'
112
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
113
+ import { Feature, features } from '@soederpop/luca'
114
+ import type { ContainerContext } from '@soederpop/luca'
115
+
116
+ declare module '@soederpop/luca' {
117
+ interface AvailableFeatures {
118
+ {{camelName}}: typeof {{PascalName}}
119
+ }
120
+ }
121
+
122
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({})
123
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
124
+
125
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({})
126
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
127
+
128
+ /**
129
+ * {{description}}
130
+ *
131
+ * @example
132
+ * \`\`\`typescript
133
+ * const {{camelName}} = container.feature('{{camelName}}')
134
+ * \`\`\`
135
+ *
136
+ * @extends Feature
137
+ */
138
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
139
+ static override shortcut = 'features.{{camelName}}' as const
140
+ static override stateSchema = {{PascalName}}StateSchema
141
+ static override optionsSchema = {{PascalName}}OptionsSchema
142
+ static override description = '{{description}}'
143
+
144
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
145
+ super(options, context)
146
+ }
147
+ }
148
+
149
+ export default features.register('{{camelName}}', {{PascalName}})`,
150
+ tutorial: `# Building a Feature
151
+
152
+ 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.
153
+
154
+ When to build a feature:
155
+ - You need a reusable local capability (not a network call — that's a client)
156
+ - You want state management, events, and introspection for free
157
+ - You're wrapping a library so the rest of the codebase uses a uniform interface
158
+
159
+ ## Imports
160
+
161
+ \`\`\`ts
162
+ import { z } from 'zod'
163
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '@soederpop/luca'
164
+ import { Feature, features } from '@soederpop/luca'
165
+ import type { ContainerContext } from '@soederpop/luca'
166
+ \`\`\`
167
+
168
+ 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.
169
+
170
+ ## Schemas
171
+
172
+ Define the shape of your feature's state, options, and events using Zod. Every field must have a \`.describe()\` — this becomes the documentation.
173
+
174
+ \`\`\`ts
175
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({
176
+ // Add your state fields here. These are observable — changes emit events.
177
+ // Example: itemCount: z.number().default(0).describe('Number of items stored'),
178
+ })
179
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
180
+
181
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({
182
+ // Add constructor options here. Validated when the feature is created.
183
+ // Example: directory: z.string().optional().describe('Storage directory path'),
184
+ })
185
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
186
+
187
+ export const {{PascalName}}EventsSchema = FeatureEventsSchema.extend({
188
+ // Each key is an event name. Value is z.tuple() of listener arguments.
189
+ // Example: itemAdded: z.tuple([z.string().describe('Item key')]).describe('Emitted when an item is added'),
190
+ })
191
+ \`\`\`
192
+
193
+ ## Class
194
+
195
+ 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\`.
196
+
197
+ \`\`\`ts
198
+ /**
199
+ * {{description}}
200
+ *
201
+ * @example
202
+ * \`\`\`typescript
203
+ * const {{camelName}} = container.feature('{{camelName}}')
204
+ * \`\`\`
205
+ *
206
+ * @extends Feature
207
+ */
208
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
209
+ static override shortcut = 'features.{{camelName}}' as const
210
+ static override stateSchema = {{PascalName}}StateSchema
211
+ static override optionsSchema = {{PascalName}}OptionsSchema
212
+ static override eventsSchema = {{PascalName}}EventsSchema
213
+ static override description = '{{description}}'
214
+
215
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
216
+ super(options, context)
217
+ // Initialize state, set up resources
218
+ }
219
+
220
+ // Add your methods here. Every public method needs JSDoc.
221
+ }
222
+ \`\`\`
223
+
224
+ ## Module Augmentation
225
+
226
+ This is what gives \`container.feature('yourName')\` TypeScript autocomplete. Without it, the feature works but TypeScript won't know about it.
227
+
228
+ \`\`\`ts
229
+ declare module '@soederpop/luca' {
230
+ interface AvailableFeatures {
231
+ {{camelName}}: typeof {{PascalName}}
232
+ }
233
+ }
234
+ \`\`\`
235
+
236
+ ## Registration
237
+
238
+ The last line of the file registers the feature in the global registry. This must happen at module level (not inside a function).
239
+
240
+ \`\`\`ts
241
+ export default features.register('{{camelName}}', {{PascalName}})
242
+ \`\`\`
243
+
244
+ ## Complete Example
245
+
246
+ Here's a minimal but complete feature. This is what a real feature file looks like:
247
+
248
+ \`\`\`ts
249
+ import { z } from 'zod'
250
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
251
+ import { Feature, features } from '@soederpop/luca'
252
+ import type { ContainerContext } from '@soederpop/luca'
253
+
254
+ declare module '@soederpop/luca' {
255
+ interface AvailableFeatures {
256
+ {{camelName}}: typeof {{PascalName}}
257
+ }
258
+ }
259
+
260
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({})
261
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
262
+
263
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({})
264
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
265
+
266
+ /**
267
+ * {{description}}
268
+ *
269
+ * @example
270
+ * \`\`\`typescript
271
+ * const {{camelName}} = container.feature('{{camelName}}')
272
+ * \`\`\`
273
+ *
274
+ * @extends Feature
275
+ */
276
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
277
+ static override shortcut = 'features.{{camelName}}' as const
278
+ static override stateSchema = {{PascalName}}StateSchema
279
+ static override optionsSchema = {{PascalName}}OptionsSchema
280
+ static override description = '{{description}}'
281
+
282
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
283
+ super(options, context)
284
+ }
285
+ }
286
+
287
+ export default features.register('{{camelName}}', {{PascalName}})
288
+ \`\`\`
289
+
290
+ ## Conventions
291
+
292
+ - **Naming**: PascalCase for class, camelCase for registration ID. The file name should be kebab-case (e.g. \`disk-cache.ts\`).
293
+ - **JSDoc**: Every public method, getter, and the class itself needs a JSDoc block. Include \`@example\` with working code.
294
+ - **Describe everything**: Every Zod field needs \`.describe()\`. Every event tuple argument needs \`.describe()\`. This IS the documentation.
295
+ - **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.
296
+ - **State is observable**: Use \`this.state.set()\` and \`this.state.get()\`. Don't use plain instance properties for data that should be reactive.
297
+ - **Events for lifecycle**: Emit events for significant state changes so consumers can react.
298
+ `,
299
+ },
300
+ client: {
301
+ sections: [
302
+ { heading: "Imports", code: `import { z } from 'zod'
303
+ import { Client, clients, RestClient } from '@soederpop/luca/client'
304
+ import { ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca'
305
+ import type { ContainerContext } from '@soederpop/luca'` },
306
+ { heading: "Schemas", code: `export const {{PascalName}}StateSchema = ClientStateSchema.extend({
307
+ // Add your state fields here.
308
+ // Example: authenticated: z.boolean().default(false).describe('Whether API auth is configured'),
309
+ })
310
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
311
+
312
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
313
+ // Add constructor options here.
314
+ // Example: apiKey: z.string().optional().describe('API key for authentication'),
315
+ })
316
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>` },
317
+ { heading: "Class", code: `/**
318
+ * {{description}}
319
+ *
320
+ * @example
321
+ * \`\`\`typescript
322
+ * const {{camelName}} = container.client('{{camelName}}')
323
+ * \`\`\`
324
+ *
325
+ * @extends RestClient
326
+ */
327
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
328
+ static override shortcut = 'clients.{{camelName}}' as const
329
+ static override stateSchema = {{PascalName}}StateSchema
330
+ static override optionsSchema = {{PascalName}}OptionsSchema
331
+ static override description = '{{description}}'
332
+
333
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
334
+ options = {
335
+ ...options,
336
+ baseURL: options.baseURL || 'https://api.example.com',
337
+ }
338
+ super(options, context)
339
+ }
340
+
341
+ // Add API methods here. Each wraps an endpoint.
342
+ // Example:
343
+ // async listItems(): Promise<Item[]> {
344
+ // return this.get('/items')
345
+ // }
346
+ }` },
347
+ { heading: "Module Augmentation", code: `declare module '@soederpop/luca/client' {
348
+ interface AvailableClients {
349
+ {{camelName}}: typeof {{PascalName}}
350
+ }
351
+ }` },
352
+ { heading: "Registration", code: `export default clients.register('{{camelName}}', {{PascalName}})` },
353
+ { heading: "Complete Example", code: `import { z } from 'zod'
354
+ import { clients, RestClient } from '@soederpop/luca/client'
355
+ import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca'
356
+ import type { ContainerContext } from '@soederpop/luca'
357
+
358
+ declare module '@soederpop/luca/client' {
359
+ interface AvailableClients {
360
+ {{camelName}}: typeof {{PascalName}}
361
+ }
362
+ }
363
+
364
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({})
365
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
366
+
367
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
368
+ baseURL: z.string().default('https://api.example.com').describe('API base URL'),
369
+ })
370
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
371
+
372
+ /**
373
+ * {{description}}
374
+ *
375
+ * @example
376
+ * \`\`\`typescript
377
+ * const {{camelName}} = container.client('{{camelName}}')
378
+ * \`\`\`
379
+ *
380
+ * @extends RestClient
381
+ */
382
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
383
+ static override shortcut = 'clients.{{camelName}}' as const
384
+ static override stateSchema = {{PascalName}}StateSchema
385
+ static override optionsSchema = {{PascalName}}OptionsSchema
386
+ static override description = '{{description}}'
387
+
388
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
389
+ super({ ...options, baseURL: options.baseURL }, context)
390
+ }
391
+ }
392
+
393
+ export default clients.register('{{camelName}}', {{PascalName}})` }
394
+ ],
395
+ full: `import { z } from 'zod'
396
+ import { clients, RestClient } from '@soederpop/luca/client'
397
+ import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca'
398
+ import type { ContainerContext } from '@soederpop/luca'
399
+
400
+ declare module '@soederpop/luca/client' {
401
+ interface AvailableClients {
402
+ {{camelName}}: typeof {{PascalName}}
403
+ }
404
+ }
405
+
406
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({})
407
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
408
+
409
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
410
+ baseURL: z.string().default('https://api.example.com').describe('API base URL'),
411
+ })
412
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
413
+
414
+ /**
415
+ * {{description}}
416
+ *
417
+ * @example
418
+ * \`\`\`typescript
419
+ * const {{camelName}} = container.client('{{camelName}}')
420
+ * \`\`\`
421
+ *
422
+ * @extends RestClient
423
+ */
424
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
425
+ static override shortcut = 'clients.{{camelName}}' as const
426
+ static override stateSchema = {{PascalName}}StateSchema
427
+ static override optionsSchema = {{PascalName}}OptionsSchema
428
+ static override description = '{{description}}'
429
+
430
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
431
+ super({ ...options, baseURL: options.baseURL }, context)
432
+ }
433
+ }
434
+
435
+ export default clients.register('{{camelName}}', {{PascalName}})`,
436
+ tutorial: `# Building a Client
437
+
438
+ A client is a container-managed connection to an external service. Clients handle network communication — HTTP APIs, WebSocket connections, GraphQL endpoints. They extend \`RestClient\` (for HTTP), \`WebSocketClient\` (for WS), or the base \`Client\` class.
439
+
440
+ When to build a client:
441
+ - You need to talk to an external API or service
442
+ - You want connection management, error handling, and observability for free
443
+ - You're wrapping an API so the rest of the codebase uses \`container.client('name')\`
444
+
445
+ ## Imports
446
+
447
+ \`\`\`ts
448
+ import { z } from 'zod'
449
+ import { Client, clients, RestClient } from '@soederpop/luca/client'
450
+ import { ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca'
451
+ import type { ContainerContext } from '@soederpop/luca'
452
+ \`\`\`
453
+
454
+ Use \`RestClient\` for HTTP APIs (most common). It gives you \`get\`, \`post\`, \`put\`, \`patch\`, \`delete\` methods that handle JSON, headers, and error wrapping.
455
+
456
+ ## Schemas
457
+
458
+ \`\`\`ts
459
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({
460
+ // Add your state fields here.
461
+ // Example: authenticated: z.boolean().default(false).describe('Whether API auth is configured'),
462
+ })
463
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
464
+
465
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
466
+ // Add constructor options here.
467
+ // Example: apiKey: z.string().optional().describe('API key for authentication'),
468
+ })
469
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
470
+ \`\`\`
471
+
472
+ ## Class
473
+
474
+ \`\`\`ts
475
+ /**
476
+ * {{description}}
477
+ *
478
+ * @example
479
+ * \`\`\`typescript
480
+ * const {{camelName}} = container.client('{{camelName}}')
481
+ * \`\`\`
482
+ *
483
+ * @extends RestClient
484
+ */
485
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
486
+ static override shortcut = 'clients.{{camelName}}' as const
487
+ static override stateSchema = {{PascalName}}StateSchema
488
+ static override optionsSchema = {{PascalName}}OptionsSchema
489
+ static override description = '{{description}}'
490
+
491
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
492
+ options = {
493
+ ...options,
494
+ baseURL: options.baseURL || 'https://api.example.com',
495
+ }
496
+ super(options, context)
497
+ }
498
+
499
+ // Add API methods here. Each wraps an endpoint.
500
+ // Example:
501
+ // async listItems(): Promise<Item[]> {
502
+ // return this.get('/items')
503
+ // }
504
+ }
505
+ \`\`\`
506
+
507
+ ## Module Augmentation
508
+
509
+ \`\`\`ts
510
+ declare module '@soederpop/luca/client' {
511
+ interface AvailableClients {
512
+ {{camelName}}: typeof {{PascalName}}
513
+ }
514
+ }
515
+ \`\`\`
516
+
517
+ ## Registration
518
+
519
+ \`\`\`ts
520
+ export default clients.register('{{camelName}}', {{PascalName}})
521
+ \`\`\`
522
+
523
+ ## Complete Example
524
+
525
+ \`\`\`ts
526
+ import { z } from 'zod'
527
+ import { clients, RestClient } from '@soederpop/luca/client'
528
+ import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca'
529
+ import type { ContainerContext } from '@soederpop/luca'
530
+
531
+ declare module '@soederpop/luca/client' {
532
+ interface AvailableClients {
533
+ {{camelName}}: typeof {{PascalName}}
534
+ }
535
+ }
536
+
537
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({})
538
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
539
+
540
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
541
+ baseURL: z.string().default('https://api.example.com').describe('API base URL'),
542
+ })
543
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
544
+
545
+ /**
546
+ * {{description}}
547
+ *
548
+ * @example
549
+ * \`\`\`typescript
550
+ * const {{camelName}} = container.client('{{camelName}}')
551
+ * \`\`\`
552
+ *
553
+ * @extends RestClient
554
+ */
555
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
556
+ static override shortcut = 'clients.{{camelName}}' as const
557
+ static override stateSchema = {{PascalName}}StateSchema
558
+ static override optionsSchema = {{PascalName}}OptionsSchema
559
+ static override description = '{{description}}'
560
+
561
+ constructor(options: {{PascalName}}Options, context: ContainerContext) {
562
+ super({ ...options, baseURL: options.baseURL }, context)
563
+ }
564
+ }
565
+
566
+ export default clients.register('{{camelName}}', {{PascalName}})
567
+ \`\`\`
568
+
569
+ ## Conventions
570
+
571
+ - **Extend RestClient for HTTP**: It gives you typed HTTP methods. Only use base \`Client\` if you need a non-HTTP protocol.
572
+ - **Set baseURL in constructor**: Override options to hardcode or default the API base URL.
573
+ - **Wrap endpoints as methods**: Each API endpoint gets a method. Keep them thin — just map to HTTP calls.
574
+ - **JSDoc everything**: Every public method needs \`@param\`, \`@returns\`, \`@example\`.
575
+ - **Auth in options**: Pass API keys, tokens via options schema. Check them in the constructor or a setup method.
576
+ `,
577
+ },
578
+ server: {
579
+ sections: [
580
+ { heading: "Imports", code: `import { z } from 'zod'
581
+ import { Server, servers } from '@soederpop/luca'
582
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
583
+ import type { ContainerContext, NodeContainer } from '@soederpop/luca'
584
+ import type { ServersInterface } from '@soederpop/luca'` },
585
+ { heading: "Schemas", code: `export const {{PascalName}}StateSchema = ServerStateSchema.extend({
586
+ // Add your state fields here.
587
+ // Example: connectionCount: z.number().default(0).describe('Active connections'),
588
+ })
589
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
590
+
591
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({
592
+ // Add constructor options here. port and host come from ServerOptionsSchema.
593
+ // Example: cors: z.boolean().default(true).describe('Enable CORS'),
594
+ })
595
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
596
+
597
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({
598
+ // Add your events here.
599
+ // Example: connection: z.tuple([z.string().describe('Client ID')]).describe('New client connected'),
600
+ })` },
601
+ { heading: "Class", code: `/**
602
+ * {{description}}
603
+ *
604
+ * @example
605
+ * \`\`\`typescript
606
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
607
+ * await {{camelName}}.start()
608
+ * \`\`\`
609
+ *
610
+ * @extends Server
611
+ */
612
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
613
+ static override shortcut = 'servers.{{camelName}}' as const
614
+ static override stateSchema = {{PascalName}}StateSchema
615
+ static override optionsSchema = {{PascalName}}OptionsSchema
616
+ static override eventsSchema = {{PascalName}}EventsSchema
617
+ static override description = '{{description}}'
618
+
619
+ static override attach(container: NodeContainer & ServersInterface) {
620
+ return container
621
+ }
622
+
623
+ override async configure() {
624
+ if (this.isConfigured) return this
625
+ // Set up the underlying server here
626
+ this.state.set('configured', true)
627
+ return this
628
+ }
629
+
630
+ override async start(options?: { port?: number }) {
631
+ if (this.isListening) return this
632
+ if (!this.isConfigured) await this.configure()
633
+
634
+ const port = options?.port || this.options.port || 3000
635
+ // Start listening here
636
+ this.state.set('port', port)
637
+ this.state.set('listening', true)
638
+ return this
639
+ }
640
+
641
+ override async stop() {
642
+ if (this.isStopped) return this
643
+ // Clean up connections here
644
+ this.state.set('listening', false)
645
+ this.state.set('stopped', true)
646
+ return this
647
+ }
648
+ }` },
649
+ { heading: "Module Augmentation", code: `declare module '@soederpop/luca' {
650
+ interface AvailableServers {
651
+ {{camelName}}: typeof {{PascalName}}
652
+ }
653
+ }` },
654
+ { heading: "Registration", code: `export default servers.register('{{camelName}}', {{PascalName}})` },
655
+ { heading: "Complete Example", code: `import { z } from 'zod'
656
+ import { Server, servers } from '@soederpop/luca'
657
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
658
+ import type { ContainerContext, NodeContainer } from '@soederpop/luca'
659
+ import type { ServersInterface } from '@soederpop/luca'
660
+
661
+ declare module '@soederpop/luca' {
662
+ interface AvailableServers {
663
+ {{camelName}}: typeof {{PascalName}}
664
+ }
665
+ }
666
+
667
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({})
668
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
669
+
670
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({})
671
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
672
+
673
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({})
674
+
675
+ /**
676
+ * {{description}}
677
+ *
678
+ * @example
679
+ * \`\`\`typescript
680
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
681
+ * await {{camelName}}.start()
682
+ * \`\`\`
683
+ *
684
+ * @extends Server
685
+ */
686
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
687
+ static override shortcut = 'servers.{{camelName}}' as const
688
+ static override stateSchema = {{PascalName}}StateSchema
689
+ static override optionsSchema = {{PascalName}}OptionsSchema
690
+ static override eventsSchema = {{PascalName}}EventsSchema
691
+ static override description = '{{description}}'
692
+
693
+ static override attach(container: NodeContainer & ServersInterface) {
694
+ return container
695
+ }
696
+
697
+ override async configure() {
698
+ if (this.isConfigured) return this
699
+ this.state.set('configured', true)
700
+ return this
701
+ }
702
+
703
+ override async start(options?: { port?: number }) {
704
+ if (this.isListening) return this
705
+ if (!this.isConfigured) await this.configure()
706
+ const port = options?.port || this.options.port || 3000
707
+ this.state.set('port', port)
708
+ this.state.set('listening', true)
709
+ return this
710
+ }
711
+
712
+ override async stop() {
713
+ if (this.isStopped) return this
714
+ this.state.set('listening', false)
715
+ this.state.set('stopped', true)
716
+ return this
717
+ }
718
+ }
719
+
720
+ export default servers.register('{{camelName}}', {{PascalName}})` }
721
+ ],
722
+ full: `import { z } from 'zod'
723
+ import { Server, servers } from '@soederpop/luca'
724
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
725
+ import type { ContainerContext, NodeContainer } from '@soederpop/luca'
726
+ import type { ServersInterface } from '@soederpop/luca'
727
+
728
+ declare module '@soederpop/luca' {
729
+ interface AvailableServers {
730
+ {{camelName}}: typeof {{PascalName}}
731
+ }
732
+ }
733
+
734
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({})
735
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
736
+
737
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({})
738
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
739
+
740
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({})
741
+
742
+ /**
743
+ * {{description}}
744
+ *
745
+ * @example
746
+ * \`\`\`typescript
747
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
748
+ * await {{camelName}}.start()
749
+ * \`\`\`
750
+ *
751
+ * @extends Server
752
+ */
753
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
754
+ static override shortcut = 'servers.{{camelName}}' as const
755
+ static override stateSchema = {{PascalName}}StateSchema
756
+ static override optionsSchema = {{PascalName}}OptionsSchema
757
+ static override eventsSchema = {{PascalName}}EventsSchema
758
+ static override description = '{{description}}'
759
+
760
+ static override attach(container: NodeContainer & ServersInterface) {
761
+ return container
762
+ }
763
+
764
+ override async configure() {
765
+ if (this.isConfigured) return this
766
+ this.state.set('configured', true)
767
+ return this
768
+ }
769
+
770
+ override async start(options?: { port?: number }) {
771
+ if (this.isListening) return this
772
+ if (!this.isConfigured) await this.configure()
773
+ const port = options?.port || this.options.port || 3000
774
+ this.state.set('port', port)
775
+ this.state.set('listening', true)
776
+ return this
777
+ }
778
+
779
+ override async stop() {
780
+ if (this.isStopped) return this
781
+ this.state.set('listening', false)
782
+ this.state.set('stopped', true)
783
+ return this
784
+ }
785
+ }
786
+
787
+ export default servers.register('{{camelName}}', {{PascalName}})`,
788
+ tutorial: `# Building a Server
789
+
790
+ 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.
791
+
792
+ When to build a server:
793
+ - You need to accept incoming connections (HTTP, WebSocket, custom protocol)
794
+ - You want lifecycle management, port handling, and observability for free
795
+ - You're wrapping a server library so the codebase uses \`container.server('name')\`
796
+
797
+ ## Imports
798
+
799
+ \`\`\`ts
800
+ import { z } from 'zod'
801
+ import { Server, servers } from '@soederpop/luca'
802
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
803
+ import type { ContainerContext, NodeContainer } from '@soederpop/luca'
804
+ import type { ServersInterface } from '@soederpop/luca'
805
+ \`\`\`
806
+
807
+ ## Schemas
808
+
809
+ \`\`\`ts
810
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({
811
+ // Add your state fields here.
812
+ // Example: connectionCount: z.number().default(0).describe('Active connections'),
813
+ })
814
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
815
+
816
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({
817
+ // Add constructor options here. port and host come from ServerOptionsSchema.
818
+ // Example: cors: z.boolean().default(true).describe('Enable CORS'),
819
+ })
820
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
821
+
822
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({
823
+ // Add your events here.
824
+ // Example: connection: z.tuple([z.string().describe('Client ID')]).describe('New client connected'),
825
+ })
826
+ \`\`\`
827
+
828
+ ## Class
829
+
830
+ \`\`\`ts
831
+ /**
832
+ * {{description}}
833
+ *
834
+ * @example
835
+ * \`\`\`typescript
836
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
837
+ * await {{camelName}}.start()
838
+ * \`\`\`
839
+ *
840
+ * @extends Server
841
+ */
842
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
843
+ static override shortcut = 'servers.{{camelName}}' as const
844
+ static override stateSchema = {{PascalName}}StateSchema
845
+ static override optionsSchema = {{PascalName}}OptionsSchema
846
+ static override eventsSchema = {{PascalName}}EventsSchema
847
+ static override description = '{{description}}'
848
+
849
+ static override attach(container: NodeContainer & ServersInterface) {
850
+ return container
851
+ }
852
+
853
+ override async configure() {
854
+ if (this.isConfigured) return this
855
+ // Set up the underlying server here
856
+ this.state.set('configured', true)
857
+ return this
858
+ }
859
+
860
+ override async start(options?: { port?: number }) {
861
+ if (this.isListening) return this
862
+ if (!this.isConfigured) await this.configure()
863
+
864
+ const port = options?.port || this.options.port || 3000
865
+ // Start listening here
866
+ this.state.set('port', port)
867
+ this.state.set('listening', true)
868
+ return this
869
+ }
870
+
871
+ override async stop() {
872
+ if (this.isStopped) return this
873
+ // Clean up connections here
874
+ this.state.set('listening', false)
875
+ this.state.set('stopped', true)
876
+ return this
877
+ }
878
+ }
879
+ \`\`\`
880
+
881
+ ## Module Augmentation
882
+
883
+ \`\`\`ts
884
+ declare module '@soederpop/luca' {
885
+ interface AvailableServers {
886
+ {{camelName}}: typeof {{PascalName}}
887
+ }
888
+ }
889
+ \`\`\`
890
+
891
+ ## Registration
892
+
893
+ \`\`\`ts
894
+ export default servers.register('{{camelName}}', {{PascalName}})
895
+ \`\`\`
896
+
897
+ ## Complete Example
898
+
899
+ \`\`\`ts
900
+ import { z } from 'zod'
901
+ import { Server, servers } from '@soederpop/luca'
902
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
903
+ import type { ContainerContext, NodeContainer } from '@soederpop/luca'
904
+ import type { ServersInterface } from '@soederpop/luca'
905
+
906
+ declare module '@soederpop/luca' {
907
+ interface AvailableServers {
908
+ {{camelName}}: typeof {{PascalName}}
909
+ }
910
+ }
911
+
912
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({})
913
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
914
+
915
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({})
916
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
917
+
918
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({})
919
+
920
+ /**
921
+ * {{description}}
922
+ *
923
+ * @example
924
+ * \`\`\`typescript
925
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
926
+ * await {{camelName}}.start()
927
+ * \`\`\`
928
+ *
929
+ * @extends Server
930
+ */
931
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
932
+ static override shortcut = 'servers.{{camelName}}' as const
933
+ static override stateSchema = {{PascalName}}StateSchema
934
+ static override optionsSchema = {{PascalName}}OptionsSchema
935
+ static override eventsSchema = {{PascalName}}EventsSchema
936
+ static override description = '{{description}}'
937
+
938
+ static override attach(container: NodeContainer & ServersInterface) {
939
+ return container
940
+ }
941
+
942
+ override async configure() {
943
+ if (this.isConfigured) return this
944
+ this.state.set('configured', true)
945
+ return this
946
+ }
947
+
948
+ override async start(options?: { port?: number }) {
949
+ if (this.isListening) return this
950
+ if (!this.isConfigured) await this.configure()
951
+ const port = options?.port || this.options.port || 3000
952
+ this.state.set('port', port)
953
+ this.state.set('listening', true)
954
+ return this
955
+ }
956
+
957
+ override async stop() {
958
+ if (this.isStopped) return this
959
+ this.state.set('listening', false)
960
+ this.state.set('stopped', true)
961
+ return this
962
+ }
963
+ }
964
+
965
+ export default servers.register('{{camelName}}', {{PascalName}})
966
+ \`\`\`
967
+
968
+ ## Conventions
969
+
970
+ - **Lifecycle**: Implement \`configure()\`, \`start()\`, and \`stop()\`. Check guards (\`isConfigured\`, \`isListening\`, \`isStopped\`) at the top of each.
971
+ - **State tracking**: Set \`configured\`, \`listening\`, \`stopped\`, and \`port\` on the state. This powers the introspection system.
972
+ - **attach() is static**: It runs when the container first loads the server class. Use it for container-level setup if needed.
973
+ - **Port from options**: Accept port via options schema and respect it in \`start()\`. Allow override via start options.
974
+ - **JSDoc everything**: Every public method needs \`@param\`, \`@returns\`, \`@example\`.
975
+ `,
976
+ },
977
+ command: {
978
+ sections: [
979
+ { heading: "Imports", code: `import { z } from 'zod'
980
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
981
+ import type { ContainerContext } from '@soederpop/luca'` },
982
+ { heading: "Args Schema", code: `export const argsSchema = CommandOptionsSchema.extend({
983
+ // Add your flags here. Each becomes a --flag on the CLI.
984
+ // Example: verbose: z.boolean().default(false).describe('Enable verbose output'),
985
+ // Example: output: z.string().optional().describe('Output file path'),
986
+ })` },
987
+ { heading: "Handler", code: `export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
988
+ const container = context.container as any
989
+ const fs = container.feature('fs')
990
+ const args = container.argv._ as string[]
991
+
992
+ // args[0] is your command name, args[1+] are positional arguments
993
+ // options contains parsed --flags
994
+
995
+ // Your implementation here
996
+ }` },
997
+ { heading: "Registration", code: `commands.registerHandler('{{camelName}}', {
998
+ description: '{{description}}',
999
+ argsSchema,
1000
+ handler: {{camelName}},
1001
+ })` },
1002
+ { heading: "Module Augmentation", code: `declare module '@soederpop/luca' {
1003
+ interface AvailableCommands {
1004
+ {{camelName}}: ReturnType<typeof commands.registerHandler>
1005
+ }
1006
+ }` },
1007
+ { heading: "Complete Example", code: `import { z } from 'zod'
1008
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
1009
+ import type { ContainerContext } from '@soederpop/luca'
1010
+
1011
+ declare module '@soederpop/luca' {
1012
+ interface AvailableCommands {
1013
+ {{camelName}}: ReturnType<typeof commands.registerHandler>
1014
+ }
1015
+ }
1016
+
1017
+ export const argsSchema = CommandOptionsSchema.extend({})
1018
+
1019
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1020
+ const container = context.container as any
1021
+ const fs = container.feature('fs')
1022
+
1023
+ console.log('{{camelName}} running...')
1024
+ }
1025
+
1026
+ commands.registerHandler('{{camelName}}', {
1027
+ description: '{{description}}',
1028
+ argsSchema,
1029
+ handler: {{camelName}},
1030
+ })` }
1031
+ ],
1032
+ full: `import { z } from 'zod'
1033
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
1034
+ import type { ContainerContext } from '@soederpop/luca'
1035
+
1036
+ declare module '@soederpop/luca' {
1037
+ interface AvailableCommands {
1038
+ {{camelName}}: ReturnType<typeof commands.registerHandler>
1039
+ }
1040
+ }
1041
+
1042
+ export const argsSchema = CommandOptionsSchema.extend({})
1043
+
1044
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1045
+ const container = context.container as any
1046
+ const fs = container.feature('fs')
1047
+
1048
+ console.log('{{camelName}} running...')
1049
+ }
1050
+
1051
+ commands.registerHandler('{{camelName}}', {
1052
+ description: '{{description}}',
1053
+ argsSchema,
1054
+ handler: {{camelName}},
1055
+ })`,
1056
+ tutorial: `# Building a Command
1057
+
1058
+ 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.
1059
+
1060
+ When to build a command:
1061
+ - You need a CLI task for a project (build scripts, generators, automation)
1062
+ - You want argument parsing, help text, and container access for free
1063
+ - The task should be runnable via \`luca yourCommand\`
1064
+
1065
+ ## Imports
1066
+
1067
+ \`\`\`ts
1068
+ import { z } from 'zod'
1069
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
1070
+ import type { ContainerContext } from '@soederpop/luca'
1071
+ \`\`\`
1072
+
1073
+ ## Args Schema
1074
+
1075
+ Define your command's arguments and flags. Extend \`CommandOptionsSchema\` which gives you \`_\` (positional args) and \`name\` for free.
1076
+
1077
+ \`\`\`ts
1078
+ export const argsSchema = CommandOptionsSchema.extend({
1079
+ // Add your flags here. Each becomes a --flag on the CLI.
1080
+ // Example: verbose: z.boolean().default(false).describe('Enable verbose output'),
1081
+ // Example: output: z.string().optional().describe('Output file path'),
1082
+ })
1083
+ \`\`\`
1084
+
1085
+ ## Handler
1086
+
1087
+ The handler function receives parsed options and the container context. Use the container for all I/O.
1088
+
1089
+ \`\`\`ts
1090
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1091
+ const container = context.container as any
1092
+ const fs = container.feature('fs')
1093
+ const args = container.argv._ as string[]
1094
+
1095
+ // args[0] is your command name, args[1+] are positional arguments
1096
+ // options contains parsed --flags
1097
+
1098
+ // Your implementation here
1099
+ }
1100
+ \`\`\`
1101
+
1102
+ ## Registration
1103
+
1104
+ Register the command at the bottom of the file. The \`description\` shows up in \`luca --help\`.
1105
+
1106
+ \`\`\`ts
1107
+ commands.registerHandler('{{camelName}}', {
1108
+ description: '{{description}}',
1109
+ argsSchema,
1110
+ handler: {{camelName}},
1111
+ })
1112
+ \`\`\`
1113
+
1114
+ ## Module Augmentation
1115
+
1116
+ Optional but gives TypeScript autocomplete for \`commands.lookup('yourCommand')\`.
1117
+
1118
+ \`\`\`ts
1119
+ declare module '@soederpop/luca' {
1120
+ interface AvailableCommands {
1121
+ {{camelName}}: ReturnType<typeof commands.registerHandler>
1122
+ }
1123
+ }
1124
+ \`\`\`
1125
+
1126
+ ## Complete Example
1127
+
1128
+ \`\`\`ts
1129
+ import { z } from 'zod'
1130
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
1131
+ import type { ContainerContext } from '@soederpop/luca'
1132
+
1133
+ declare module '@soederpop/luca' {
1134
+ interface AvailableCommands {
1135
+ {{camelName}}: ReturnType<typeof commands.registerHandler>
1136
+ }
1137
+ }
1138
+
1139
+ export const argsSchema = CommandOptionsSchema.extend({})
1140
+
1141
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1142
+ const container = context.container as any
1143
+ const fs = container.feature('fs')
1144
+
1145
+ console.log('{{camelName}} running...')
1146
+ }
1147
+
1148
+ commands.registerHandler('{{camelName}}', {
1149
+ description: '{{description}}',
1150
+ argsSchema,
1151
+ handler: {{camelName}},
1152
+ })
1153
+ \`\`\`
1154
+
1155
+ ## Conventions
1156
+
1157
+ - **File location**: \`commands/{{camelName}}.ts\` in the project root. The \`luca\` CLI discovers these automatically.
1158
+ - **Naming**: camelCase for both file and registration ID. \`luca my-command\` maps to \`commands/my-command.ts\`.
1159
+ - **Use the container**: Never import \`fs\`, \`path\`, \`child_process\` directly. Use \`container.feature('fs')\`, \`container.paths\`, \`container.feature('proc')\`.
1160
+ - **Positional args**: Access via \`container.argv._\` — it's an array where \`_[0]\` is the command name.
1161
+ - **Exit codes**: Return nothing for success. Throw for errors — the CLI catches and reports them.
1162
+ `,
1163
+ },
1164
+ endpoint: {
1165
+ sections: [
1166
+ { heading: "Imports", code: `import { z } from 'zod'
1167
+ import type { EndpointContext } from '@soederpop/luca'` },
1168
+ { heading: "Required Exports", code: `export const path = '/api/{{camelName}}'
1169
+ export const description = '{{description}}'
1170
+ export const tags = ['{{camelName}}']` },
1171
+ { heading: "Handler Functions", code: `export async function get(params: any, ctx: EndpointContext) {
1172
+ const fs = ctx.container.feature('fs')
1173
+ // Your logic here
1174
+ return { message: 'ok' }
1175
+ }
1176
+
1177
+ export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
1178
+ // Create something
1179
+ return { created: true }
1180
+ }` },
1181
+ { heading: "Validation Schemas", code: `export const getSchema = z.object({
1182
+ q: z.string().optional().describe('Search query'),
1183
+ limit: z.number().default(20).describe('Max results'),
1184
+ })
1185
+
1186
+ export const postSchema = z.object({
1187
+ title: z.string().min(1).describe('Item title'),
1188
+ body: z.string().min(1).describe('Item content'),
1189
+ })` },
1190
+ { heading: "Rate Limiting", code: `// Global rate limit for all methods
1191
+ export const rateLimit = { maxRequests: 100, windowSeconds: 60 }
1192
+
1193
+ // Per-method rate limit
1194
+ export const postRateLimit = { maxRequests: 10, windowSeconds: 1 }` },
1195
+ { heading: "Delete Handler", code: `const del = async (params: any, ctx: EndpointContext) => {
1196
+ return { deleted: true }
1197
+ }
1198
+ export { del as delete }` },
1199
+ { heading: "Complete Example", code: `import { z } from 'zod'
1200
+ import type { EndpointContext } from '@soederpop/luca'
1201
+
1202
+ export const path = '/api/{{camelName}}'
1203
+ export const description = '{{description}}'
1204
+ export const tags = ['{{camelName}}']
1205
+
1206
+ export const getSchema = z.object({
1207
+ q: z.string().optional().describe('Search query'),
1208
+ })
1209
+
1210
+ export async function get(params: z.infer<typeof getSchema>, ctx: EndpointContext) {
1211
+ return { items: [], total: 0 }
1212
+ }
1213
+
1214
+ export const postSchema = z.object({
1215
+ name: z.string().min(1).describe('Item name'),
1216
+ })
1217
+
1218
+ export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
1219
+ return { item: { id: '1', ...params }, message: 'Created' }
1220
+ }` },
1221
+ { heading: "Dynamic Route Example", code: `// endpoints/{{camelName}}/[id].ts
1222
+ import { z } from 'zod'
1223
+ import type { EndpointContext } from '@soederpop/luca'
1224
+
1225
+ export const path = '/api/{{camelName}}/:id'
1226
+ export const description = 'Get, update, or delete a specific item'
1227
+ export const tags = ['{{camelName}}']
1228
+
1229
+ export async function get(params: any, ctx: EndpointContext) {
1230
+ const { id } = ctx.params
1231
+ return { item: { id } }
1232
+ }
1233
+
1234
+ export const putSchema = z.object({
1235
+ name: z.string().min(1).optional().describe('Updated name'),
1236
+ })
1237
+
1238
+ export async function put(params: z.infer<typeof putSchema>, ctx: EndpointContext) {
1239
+ const { id } = ctx.params
1240
+ return { item: { id, ...params }, message: 'Updated' }
1241
+ }
1242
+
1243
+ const del = async (params: any, ctx: EndpointContext) => {
1244
+ const { id } = ctx.params
1245
+ return { message: \`Deleted \${id}\` }
1246
+ }
1247
+ export { del as delete }` }
1248
+ ],
1249
+ full: `import { z } from 'zod'
1250
+ import type { EndpointContext } from '@soederpop/luca'
1251
+
1252
+ export const path = '/api/{{camelName}}'
1253
+ export const description = '{{description}}'
1254
+ export const tags = ['{{camelName}}']
1255
+
1256
+ export const getSchema = z.object({
1257
+ q: z.string().optional().describe('Search query'),
1258
+ })
1259
+
1260
+ export async function get(params: z.infer<typeof getSchema>, ctx: EndpointContext) {
1261
+ return { items: [], total: 0 }
1262
+ }
1263
+
1264
+ export const postSchema = z.object({
1265
+ name: z.string().min(1).describe('Item name'),
1266
+ })
1267
+
1268
+ export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
1269
+ return { item: { id: '1', ...params }, message: 'Created' }
1270
+ }`,
1271
+ tutorial: `# Building an Endpoint
1272
+
1273
+ 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.
1274
+
1275
+ When to build an endpoint:
1276
+ - You need a REST API route (GET, POST, PUT, PATCH, DELETE)
1277
+ - You want Zod validation, OpenAPI docs, and rate limiting for free
1278
+ - You're building an API that \`luca serve\` manages
1279
+
1280
+ ## File Location
1281
+
1282
+ Endpoints go in \`endpoints/\` at your project root:
1283
+ - \`endpoints/health.ts\` → \`/health\`
1284
+ - \`endpoints/posts.ts\` → \`/api/posts\`
1285
+ - \`endpoints/posts/[id].ts\` → \`/api/posts/:id\` (requires \`path\` export)
1286
+
1287
+ Run \`luca serve\` and they're automatically discovered and mounted.
1288
+
1289
+ ## Imports
1290
+
1291
+ \`\`\`ts
1292
+ import { z } from 'zod'
1293
+ import type { EndpointContext } from '@soederpop/luca'
1294
+ \`\`\`
1295
+
1296
+ That's it. Endpoints are lightweight — just exports and functions.
1297
+
1298
+ ## Required Exports
1299
+
1300
+ Every endpoint MUST export a \`path\` string:
1301
+
1302
+ \`\`\`ts
1303
+ export const path = '/api/{{camelName}}'
1304
+ export const description = '{{description}}'
1305
+ export const tags = ['{{camelName}}']
1306
+ \`\`\`
1307
+
1308
+ ## Handler Functions
1309
+
1310
+ Export named functions for each HTTP method you support. Each receives validated parameters and an \`EndpointContext\`:
1311
+
1312
+ \`\`\`ts
1313
+ export async function get(params: any, ctx: EndpointContext) {
1314
+ const fs = ctx.container.feature('fs')
1315
+ // Your logic here
1316
+ return { message: 'ok' }
1317
+ }
1318
+
1319
+ export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
1320
+ // Create something
1321
+ return { created: true }
1322
+ }
1323
+ \`\`\`
1324
+
1325
+ The \`EndpointContext\` gives you:
1326
+ - \`ctx.container\` — the luca container (access any feature, client, etc.)
1327
+ - \`ctx.request\` — Express request object
1328
+ - \`ctx.response\` — Express response object
1329
+ - \`ctx.query\` — parsed query string
1330
+ - \`ctx.body\` — parsed request body
1331
+ - \`ctx.params\` — URL parameters (\`:id\`, etc.)
1332
+
1333
+ Return any object — it's automatically JSON-serialized as the response.
1334
+
1335
+ ## Validation Schemas
1336
+
1337
+ Export Zod schemas to validate parameters for each method. Name them \`{method}Schema\`:
1338
+
1339
+ \`\`\`ts
1340
+ export const getSchema = z.object({
1341
+ q: z.string().optional().describe('Search query'),
1342
+ limit: z.number().default(20).describe('Max results'),
1343
+ })
1344
+
1345
+ export const postSchema = z.object({
1346
+ title: z.string().min(1).describe('Item title'),
1347
+ body: z.string().min(1).describe('Item content'),
1348
+ })
1349
+ \`\`\`
1350
+
1351
+ Invalid requests automatically return 400 with Zod error details. Schemas also feed the auto-generated OpenAPI spec.
1352
+
1353
+ ## Rate Limiting
1354
+
1355
+ Export rate limit config to protect endpoints:
1356
+
1357
+ \`\`\`ts
1358
+ // Global rate limit for all methods
1359
+ export const rateLimit = { maxRequests: 100, windowSeconds: 60 }
1360
+
1361
+ // Per-method rate limit
1362
+ export const postRateLimit = { maxRequests: 10, windowSeconds: 1 }
1363
+ \`\`\`
1364
+
1365
+ ## Delete Handler
1366
+
1367
+ \`delete\` is a reserved word in JS. Use an alias:
1368
+
1369
+ \`\`\`ts
1370
+ const del = async (params: any, ctx: EndpointContext) => {
1371
+ return { deleted: true }
1372
+ }
1373
+ export { del as delete }
1374
+ \`\`\`
1375
+
1376
+ ## Complete Example
1377
+
1378
+ A CRUD endpoint for a resource:
1379
+
1380
+ \`\`\`ts
1381
+ import { z } from 'zod'
1382
+ import type { EndpointContext } from '@soederpop/luca'
1383
+
1384
+ export const path = '/api/{{camelName}}'
1385
+ export const description = '{{description}}'
1386
+ export const tags = ['{{camelName}}']
1387
+
1388
+ export const getSchema = z.object({
1389
+ q: z.string().optional().describe('Search query'),
1390
+ })
1391
+
1392
+ export async function get(params: z.infer<typeof getSchema>, ctx: EndpointContext) {
1393
+ return { items: [], total: 0 }
1394
+ }
1395
+
1396
+ export const postSchema = z.object({
1397
+ name: z.string().min(1).describe('Item name'),
1398
+ })
1399
+
1400
+ export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
1401
+ return { item: { id: '1', ...params }, message: 'Created' }
1402
+ }
1403
+ \`\`\`
1404
+
1405
+ ## Dynamic Route Example
1406
+
1407
+ For routes with URL parameters, create a nested file:
1408
+
1409
+ \`\`\`ts
1410
+ // endpoints/{{camelName}}/[id].ts
1411
+ import { z } from 'zod'
1412
+ import type { EndpointContext } from '@soederpop/luca'
1413
+
1414
+ export const path = '/api/{{camelName}}/:id'
1415
+ export const description = 'Get, update, or delete a specific item'
1416
+ export const tags = ['{{camelName}}']
1417
+
1418
+ export async function get(params: any, ctx: EndpointContext) {
1419
+ const { id } = ctx.params
1420
+ return { item: { id } }
1421
+ }
1422
+
1423
+ export const putSchema = z.object({
1424
+ name: z.string().min(1).optional().describe('Updated name'),
1425
+ })
1426
+
1427
+ export async function put(params: z.infer<typeof putSchema>, ctx: EndpointContext) {
1428
+ const { id } = ctx.params
1429
+ return { item: { id, ...params }, message: 'Updated' }
1430
+ }
1431
+
1432
+ const del = async (params: any, ctx: EndpointContext) => {
1433
+ const { id } = ctx.params
1434
+ return { message: \`Deleted \${id}\` }
1435
+ }
1436
+ export { del as delete }
1437
+ \`\`\`
1438
+
1439
+ ## Conventions
1440
+
1441
+ - **File = route**: The file path maps to the URL path. \`endpoints/users.ts\` serves \`/api/users\`.
1442
+ - **Export \`path\`**: Every endpoint must export a \`path\` string. This is the mounted route.
1443
+ - **Use Zod schemas**: Name them \`getSchema\`, \`postSchema\`, etc. They validate AND document.
1444
+ - **Use the container**: Access features via \`ctx.container.feature('fs')\`, not Node.js imports.
1445
+ - **Return objects**: Handler return values are JSON-serialized. Use \`ctx.response\` only for streaming or custom status codes.
1446
+ - **OpenAPI for free**: Your \`path\`, \`description\`, \`tags\`, and schemas automatically generate an OpenAPI spec at \`/openapi.json\`.
1447
+ `,
1448
+ }
1449
+ }
1450
+
1451
+ export const mcpReadme = `# Luca Development Guide
1452
+
1453
+ You are working in a **luca project**. The luca container provides all capabilities your code needs. Do not install npm packages or import Node.js builtins directly.
1454
+
1455
+ ## The Contract
1456
+
1457
+ Every capability goes through the container. If you need something that doesn't exist, build it as a feature, client, or server. If it wraps a third-party library, the helper IS the interface — consumer code never imports the library directly.
1458
+
1459
+ ## Import Rule
1460
+
1461
+ All consumer code imports from \`@soederpop/luca\` only:
1462
+
1463
+ \`\`\`ts
1464
+ import { Feature, features, z, FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
1465
+ import { Client, clients, RestClient, ClientStateSchema } from '@soederpop/luca/client'
1466
+ import { Server, servers, ServerStateSchema } from '@soederpop/luca'
1467
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
1468
+ \`\`\`
1469
+
1470
+ Never import from \`fs\`, \`path\`, \`crypto\`, or other Node builtins. Never import third-party packages in consumer code. The only exception is inside helper implementations themselves — a feature that wraps a library may import it.
1471
+
1472
+ ## Zod v4
1473
+
1474
+ This project uses **Zod v4** — import \`z\` from \`@soederpop/luca\`, never from \`'zod'\` directly. All option, state, and event schemas use Zod v4 syntax. Key patterns:
1475
+
1476
+ \`\`\`ts
1477
+ // Extending base schemas (options, state, events)
1478
+ export const MyStateSchema = FeatureStateSchema.extend({
1479
+ count: z.number().default(0).describe('Number of items'),
1480
+ label: z.string().optional().describe('Display label'),
1481
+ })
1482
+
1483
+ // Events use z.tuple() for listener arguments
1484
+ export const MyEventsSchema = FeatureEventsSchema.extend({
1485
+ itemAdded: z.tuple([z.string().describe('key'), z.number().describe('index')]),
1486
+ })
1487
+
1488
+ // Type inference
1489
+ export type MyState = z.infer<typeof MyStateSchema>
1490
+ \`\`\`
1491
+
1492
+ Zod v4 differences from v3 that matter:
1493
+ - \`z.string().check(...)\` replaces some v3 refinement patterns
1494
+ - \`.toJSONSchema()\` is built-in on any schema — no external library needed
1495
+ - Error customization uses \`z.string({ error: "message" })\` not \`.refine()\` for simple cases
1496
+ - \`z.interface()\` exists for recursive/lazy object types
1497
+ - Do NOT use \`z.nativeEnum()\` — use \`z.enum()\` instead
1498
+
1499
+ ## Dependencies
1500
+
1501
+ If the project has \`node_modules\` and a package manager, helper implementations can import third-party libraries internally. If not (e.g. running via the \`luca\` binary's VM), all code must import only from \`@soederpop/luca\`.
1502
+
1503
+ ## Discovering Capabilities
1504
+
1505
+ The container has registries for features, clients, servers, commands, and endpoints. **Do not guess** what is available — use your MCP tools to discover it:
1506
+
1507
+ 1. **\`find_capability\`** — Overview of all features, clients, and servers with descriptions. Start here.
1508
+ 2. **\`list_registry\`** — List all names in a specific registry (features, clients, servers, commands, endpoints).
1509
+ 3. **\`describe_helper\`** — Full API docs for a specific helper (methods, options, state, events). Call this before writing code that uses a helper.
1510
+ 4. **\`eval\`** — Once you know what you need, prototype calls in the live sandbox before writing them into files.
1511
+
1512
+ ## Mini Examples
1513
+
1514
+ ### Feature with composition
1515
+
1516
+ Features access other features via \`this.container.feature(...)\`:
1517
+
1518
+ \`\`\`ts
1519
+ import { z } from 'zod'
1520
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '@soederpop/luca'
1521
+ import { Feature, features } from '@soederpop/luca'
1522
+ import type { ContainerContext } from '@soederpop/luca'
1523
+
1524
+ export const ConfigStateSchema = FeatureStateSchema.extend({
1525
+ loaded: z.boolean().default(false).describe('Whether config has been loaded'),
1526
+ })
1527
+
1528
+ export const ConfigOptionsSchema = FeatureOptionsSchema.extend({
1529
+ filePath: z.string().default('config.json').describe('Path to config file'),
1530
+ })
1531
+
1532
+ export const ConfigEventsSchema = FeatureEventsSchema.extend({
1533
+ configLoaded: z.tuple([z.record(z.unknown()).describe('parsed config')]),
1534
+ })
1535
+
1536
+ export class Config extends Feature<z.infer<typeof ConfigStateSchema>, z.infer<typeof ConfigOptionsSchema>> {
1537
+ static override shortcut = 'features.config' as const
1538
+ static override stateSchema = ConfigStateSchema
1539
+ static override optionsSchema = ConfigOptionsSchema
1540
+ static override eventsSchema = ConfigEventsSchema
1541
+ static override description = 'Loads and manages project configuration'
1542
+
1543
+ /** Load and parse the config file */
1544
+ async load() {
1545
+ const fs = this.container.feature('fs')
1546
+ const raw = await fs.readFile(this.options.filePath)
1547
+ const data = JSON.parse(raw)
1548
+ this.state.set('loaded', true)
1549
+ this.emit('configLoaded', data)
1550
+ return data
1551
+ }
1552
+ }
1553
+
1554
+ declare module '@soederpop/luca' {
1555
+ interface AvailableFeatures { config: typeof Config }
1556
+ }
1557
+ export default features.register('config', Config)
1558
+ \`\`\`
1559
+
1560
+ ### Client with composition
1561
+
1562
+ Clients access features and other clients via \`this.container\`:
1563
+
1564
+ \`\`\`ts
1565
+ import { z } from 'zod'
1566
+ import { Client, clients, ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca'
1567
+ import type { ContainerContext } from '@soederpop/luca'
1568
+
1569
+ export const GithubOptionsSchema = ClientOptionsSchema.extend({
1570
+ token: z.string().describe('GitHub personal access token'),
1571
+ owner: z.string().describe('Repository owner'),
1572
+ repo: z.string().describe('Repository name'),
1573
+ })
1574
+
1575
+ export const GithubEventsSchema = ClientEventsSchema.extend({
1576
+ issuesFetched: z.tuple([z.number().describe('count')]),
1577
+ })
1578
+
1579
+ export class GithubClient extends Client<z.infer<typeof ClientStateSchema>, z.infer<typeof GithubOptionsSchema>> {
1580
+ static override shortcut = 'clients.github' as const
1581
+ static override optionsSchema = GithubOptionsSchema
1582
+ static override eventsSchema = GithubEventsSchema
1583
+ static override description = 'GitHub API client using container REST client'
1584
+
1585
+ /** Fetch open issues */
1586
+ async issues() {
1587
+ const rest = this.container.client('rest')
1588
+ const res = await rest.get(\`https://api.github.com/repos/\${this.options.owner}/\${this.options.repo}/issues\`, {
1589
+ headers: { Authorization: \`token \${this.options.token}\` },
1590
+ })
1591
+ this.emit('issuesFetched', res.data.length)
1592
+ return res.data
1593
+ }
1594
+ }
1595
+
1596
+ declare module '@soederpop/luca' {
1597
+ interface AvailableClients { github: typeof GithubClient }
1598
+ }
1599
+ export default clients.register('github', GithubClient)
1600
+ \`\`\`
1601
+
1602
+ ## Workflow
1603
+
1604
+ 1. **\`find_capability\`** — Search what already exists before writing anything
1605
+ 2. **\`describe_helper\`** — Read the full API docs for the helper you need
1606
+ 3. **\`eval\`** — Prototype and test container API calls in the sandbox
1607
+ 4. **\`scaffold\`** — Generate correct boilerplate when building something new
1608
+ 5. **Write the file** — Using the patterns from the scaffold
1609
+
1610
+ ## Portability
1611
+
1612
+ Code that only imports from \`@soederpop/luca\` can be copied between any luca project. That's the goal. Features, clients, servers, and commands written this way are portable building blocks.
1613
+ `