luca 2.0.0 → 3.0.0

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 (763) hide show
  1. package/.github/workflows/release.yaml +169 -0
  2. package/AGENTS.md +99 -0
  3. package/CLAUDE.md +115 -0
  4. package/CNAME +1 -0
  5. package/README.md +257 -9
  6. package/RUNME.md +56 -0
  7. package/assistants/codingAssistant/ABOUT.md +5 -0
  8. package/assistants/codingAssistant/CORE.md +28 -0
  9. package/assistants/codingAssistant/hooks.ts +21 -0
  10. package/assistants/codingAssistant/tools.ts +12 -0
  11. package/assistants/inkbot/ABOUT.md +16 -0
  12. package/assistants/inkbot/CORE.md +330 -0
  13. package/assistants/inkbot/hooks.ts +6 -0
  14. package/assistants/inkbot/tools.ts +53 -0
  15. package/assistants/researcher/ABOUT.md +5 -0
  16. package/assistants/researcher/CORE.md +46 -0
  17. package/assistants/researcher/hooks.ts +16 -0
  18. package/assistants/researcher/tools.ts +237 -0
  19. package/bun.lock +2769 -0
  20. package/bunfig.toml +3 -0
  21. package/commands/audit-docs.ts +740 -0
  22. package/commands/build-bootstrap.ts +118 -0
  23. package/commands/build-python-bridge.ts +43 -0
  24. package/commands/build-scaffolds.ts +176 -0
  25. package/commands/generate-api-docs.ts +114 -0
  26. package/commands/inkbot.ts +874 -0
  27. package/commands/release.ts +80 -0
  28. package/commands/try-all-challenges.ts +543 -0
  29. package/commands/try-challenge.ts +100 -0
  30. package/dist/agi/container.server.d.ts +63 -0
  31. package/dist/agi/container.server.d.ts.map +1 -0
  32. package/dist/agi/endpoints/ask.d.ts +20 -0
  33. package/dist/agi/endpoints/ask.d.ts.map +1 -0
  34. package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
  35. package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
  36. package/dist/agi/endpoints/conversations.d.ts +18 -0
  37. package/dist/agi/endpoints/conversations.d.ts.map +1 -0
  38. package/dist/agi/endpoints/experts.d.ts +8 -0
  39. package/dist/agi/endpoints/experts.d.ts.map +1 -0
  40. package/dist/agi/feature.d.ts +9 -0
  41. package/dist/agi/feature.d.ts.map +1 -0
  42. package/dist/agi/features/assistant.d.ts +509 -0
  43. package/dist/agi/features/assistant.d.ts.map +1 -0
  44. package/dist/agi/features/assistants-manager.d.ts +236 -0
  45. package/dist/agi/features/assistants-manager.d.ts.map +1 -0
  46. package/dist/agi/features/autonomous-assistant.d.ts +281 -0
  47. package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
  48. package/dist/agi/features/browser-use.d.ts +479 -0
  49. package/dist/agi/features/browser-use.d.ts.map +1 -0
  50. package/dist/agi/features/claude-code.d.ts +824 -0
  51. package/dist/agi/features/claude-code.d.ts.map +1 -0
  52. package/dist/agi/features/conversation-history.d.ts +245 -0
  53. package/dist/agi/features/conversation-history.d.ts.map +1 -0
  54. package/dist/agi/features/conversation.d.ts +464 -0
  55. package/dist/agi/features/conversation.d.ts.map +1 -0
  56. package/dist/agi/features/docs-reader.d.ts +72 -0
  57. package/dist/agi/features/docs-reader.d.ts.map +1 -0
  58. package/dist/agi/features/file-tools.d.ts +110 -0
  59. package/dist/agi/features/file-tools.d.ts.map +1 -0
  60. package/dist/agi/features/luca-coder.d.ts +323 -0
  61. package/dist/agi/features/luca-coder.d.ts.map +1 -0
  62. package/dist/agi/features/openai-codex.d.ts +381 -0
  63. package/dist/agi/features/openai-codex.d.ts.map +1 -0
  64. package/dist/agi/features/openapi.d.ts +200 -0
  65. package/dist/agi/features/openapi.d.ts.map +1 -0
  66. package/dist/agi/features/skills-library.d.ts +167 -0
  67. package/dist/agi/features/skills-library.d.ts.map +1 -0
  68. package/dist/agi/index.d.ts +5 -0
  69. package/dist/agi/index.d.ts.map +1 -0
  70. package/dist/agi/lib/interceptor-chain.d.ts +44 -0
  71. package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
  72. package/dist/agi/lib/token-counter.d.ts +13 -0
  73. package/dist/agi/lib/token-counter.d.ts.map +1 -0
  74. package/dist/bootstrap/generated.d.ts +5 -0
  75. package/dist/bootstrap/generated.d.ts.map +1 -0
  76. package/dist/browser.d.ts +12 -0
  77. package/dist/browser.d.ts.map +1 -0
  78. package/dist/bus.d.ts +29 -0
  79. package/dist/bus.d.ts.map +1 -0
  80. package/dist/cli/build-info.d.ts +4 -0
  81. package/dist/cli/build-info.d.ts.map +1 -0
  82. package/dist/cli/cli.d.ts +3 -12
  83. package/dist/cli/cli.d.ts.map +1 -0
  84. package/dist/client.d.ts +60 -0
  85. package/dist/client.d.ts.map +1 -0
  86. package/dist/clients/civitai/index.d.ts +472 -0
  87. package/dist/clients/civitai/index.d.ts.map +1 -0
  88. package/dist/clients/client-template.d.ts +30 -0
  89. package/dist/clients/client-template.d.ts.map +1 -0
  90. package/dist/clients/comfyui/index.d.ts +281 -0
  91. package/dist/clients/comfyui/index.d.ts.map +1 -0
  92. package/dist/clients/elevenlabs/index.d.ts +197 -0
  93. package/dist/clients/elevenlabs/index.d.ts.map +1 -0
  94. package/dist/clients/graph.d.ts +64 -0
  95. package/dist/clients/graph.d.ts.map +1 -0
  96. package/dist/clients/openai/index.d.ts +247 -0
  97. package/dist/clients/openai/index.d.ts.map +1 -0
  98. package/dist/clients/rest.d.ts +92 -0
  99. package/dist/clients/rest.d.ts.map +1 -0
  100. package/dist/clients/supabase/index.d.ts +176 -0
  101. package/dist/clients/supabase/index.d.ts.map +1 -0
  102. package/dist/clients/websocket.d.ts +127 -0
  103. package/dist/clients/websocket.d.ts.map +1 -0
  104. package/dist/command.d.ts +163 -0
  105. package/dist/command.d.ts.map +1 -0
  106. package/dist/commands/bootstrap.d.ts +20 -0
  107. package/dist/commands/bootstrap.d.ts.map +1 -0
  108. package/dist/commands/chat.d.ts +37 -0
  109. package/dist/commands/chat.d.ts.map +1 -0
  110. package/dist/commands/code.d.ts +28 -0
  111. package/dist/commands/code.d.ts.map +1 -0
  112. package/dist/commands/console.d.ts +22 -0
  113. package/dist/commands/console.d.ts.map +1 -0
  114. package/dist/commands/describe.d.ts +50 -0
  115. package/dist/commands/describe.d.ts.map +1 -0
  116. package/dist/commands/eval.d.ts +23 -0
  117. package/dist/commands/eval.d.ts.map +1 -0
  118. package/dist/commands/help.d.ts +25 -0
  119. package/dist/commands/help.d.ts.map +1 -0
  120. package/dist/commands/index.d.ts +18 -0
  121. package/dist/commands/index.d.ts.map +1 -0
  122. package/dist/commands/introspect.d.ts +24 -0
  123. package/dist/commands/introspect.d.ts.map +1 -0
  124. package/dist/commands/mcp.d.ts +35 -0
  125. package/dist/commands/mcp.d.ts.map +1 -0
  126. package/dist/commands/prompt.d.ts +38 -0
  127. package/dist/commands/prompt.d.ts.map +1 -0
  128. package/dist/commands/run.d.ts +24 -0
  129. package/dist/commands/run.d.ts.map +1 -0
  130. package/dist/commands/sandbox-mcp.d.ts +34 -0
  131. package/dist/commands/sandbox-mcp.d.ts.map +1 -0
  132. package/dist/commands/save-api-docs.d.ts +21 -0
  133. package/dist/commands/save-api-docs.d.ts.map +1 -0
  134. package/dist/commands/scaffold.d.ts +24 -0
  135. package/dist/commands/scaffold.d.ts.map +1 -0
  136. package/dist/commands/select.d.ts +22 -0
  137. package/dist/commands/select.d.ts.map +1 -0
  138. package/dist/commands/serve.d.ts +29 -0
  139. package/dist/commands/serve.d.ts.map +1 -0
  140. package/dist/container-describer.d.ts +144 -0
  141. package/dist/container-describer.d.ts.map +1 -0
  142. package/dist/container.d.ts +451 -0
  143. package/dist/container.d.ts.map +1 -0
  144. package/dist/endpoint.d.ts +113 -0
  145. package/dist/endpoint.d.ts.map +1 -0
  146. package/dist/feature.d.ts +47 -0
  147. package/dist/feature.d.ts.map +1 -0
  148. package/dist/graft.d.ts +29 -0
  149. package/dist/graft.d.ts.map +1 -0
  150. package/dist/hash-object.d.ts +8 -0
  151. package/dist/hash-object.d.ts.map +1 -0
  152. package/dist/helper.d.ts +209 -0
  153. package/dist/helper.d.ts.map +1 -0
  154. package/dist/introspection/generated.node.d.ts +44623 -0
  155. package/dist/introspection/generated.node.d.ts.map +1 -0
  156. package/dist/introspection/generated.web.d.ts +1412 -0
  157. package/dist/introspection/generated.web.d.ts.map +1 -0
  158. package/dist/introspection/index.d.ts +156 -0
  159. package/dist/introspection/index.d.ts.map +1 -0
  160. package/dist/introspection/scan.d.ts +147 -0
  161. package/dist/introspection/scan.d.ts.map +1 -0
  162. package/dist/node/container.d.ts +256 -0
  163. package/dist/node/container.d.ts.map +1 -0
  164. package/dist/node/feature.d.ts +9 -0
  165. package/dist/node/feature.d.ts.map +1 -0
  166. package/dist/node/features/container-link.d.ts +213 -0
  167. package/dist/node/features/container-link.d.ts.map +1 -0
  168. package/dist/node/features/content-db.d.ts +354 -0
  169. package/dist/node/features/content-db.d.ts.map +1 -0
  170. package/dist/node/features/disk-cache.d.ts +236 -0
  171. package/dist/node/features/disk-cache.d.ts.map +1 -0
  172. package/dist/node/features/dns.d.ts +511 -0
  173. package/dist/node/features/dns.d.ts.map +1 -0
  174. package/dist/node/features/docker.d.ts +485 -0
  175. package/dist/node/features/docker.d.ts.map +1 -0
  176. package/dist/node/features/downloader.d.ts +73 -0
  177. package/dist/node/features/downloader.d.ts.map +1 -0
  178. package/dist/node/features/figlet-fonts.d.ts +4 -0
  179. package/dist/node/features/figlet-fonts.d.ts.map +1 -0
  180. package/dist/node/features/file-manager.d.ts +177 -0
  181. package/dist/node/features/file-manager.d.ts.map +1 -0
  182. package/dist/node/features/fs.d.ts +635 -0
  183. package/dist/node/features/fs.d.ts.map +1 -0
  184. package/dist/node/features/git.d.ts +329 -0
  185. package/dist/node/features/git.d.ts.map +1 -0
  186. package/dist/node/features/google-auth.d.ts +200 -0
  187. package/dist/node/features/google-auth.d.ts.map +1 -0
  188. package/dist/node/features/google-calendar.d.ts +194 -0
  189. package/dist/node/features/google-calendar.d.ts.map +1 -0
  190. package/dist/node/features/google-docs.d.ts +138 -0
  191. package/dist/node/features/google-docs.d.ts.map +1 -0
  192. package/dist/node/features/google-drive.d.ts +202 -0
  193. package/dist/node/features/google-drive.d.ts.map +1 -0
  194. package/dist/node/features/google-mail.d.ts +221 -0
  195. package/dist/node/features/google-mail.d.ts.map +1 -0
  196. package/dist/node/features/google-sheets.d.ts +157 -0
  197. package/dist/node/features/google-sheets.d.ts.map +1 -0
  198. package/dist/node/features/grep.d.ts +207 -0
  199. package/dist/node/features/grep.d.ts.map +1 -0
  200. package/dist/node/features/helpers.d.ts +236 -0
  201. package/dist/node/features/helpers.d.ts.map +1 -0
  202. package/dist/node/features/ink.d.ts +332 -0
  203. package/dist/node/features/ink.d.ts.map +1 -0
  204. package/dist/node/features/ipc-socket.d.ts +298 -0
  205. package/dist/node/features/ipc-socket.d.ts.map +1 -0
  206. package/dist/node/features/json-tree.d.ts +140 -0
  207. package/dist/node/features/json-tree.d.ts.map +1 -0
  208. package/dist/node/features/networking.d.ts +373 -0
  209. package/dist/node/features/networking.d.ts.map +1 -0
  210. package/dist/node/features/nlp.d.ts +125 -0
  211. package/dist/node/features/nlp.d.ts.map +1 -0
  212. package/dist/node/features/opener.d.ts +93 -0
  213. package/dist/node/features/opener.d.ts.map +1 -0
  214. package/dist/node/features/os.d.ts +168 -0
  215. package/dist/node/features/os.d.ts.map +1 -0
  216. package/dist/node/features/package-finder.d.ts +419 -0
  217. package/dist/node/features/package-finder.d.ts.map +1 -0
  218. package/dist/node/features/postgres.d.ts +173 -0
  219. package/dist/node/features/postgres.d.ts.map +1 -0
  220. package/dist/node/features/proc.d.ts +285 -0
  221. package/dist/node/features/proc.d.ts.map +1 -0
  222. package/dist/node/features/process-manager.d.ts +427 -0
  223. package/dist/node/features/process-manager.d.ts.map +1 -0
  224. package/dist/node/features/python.d.ts +477 -0
  225. package/dist/node/features/python.d.ts.map +1 -0
  226. package/dist/node/features/redis.d.ts +247 -0
  227. package/dist/node/features/redis.d.ts.map +1 -0
  228. package/dist/node/features/repl.d.ts +84 -0
  229. package/dist/node/features/repl.d.ts.map +1 -0
  230. package/dist/node/features/runpod.d.ts +527 -0
  231. package/dist/node/features/runpod.d.ts.map +1 -0
  232. package/dist/node/features/secure-shell.d.ts +145 -0
  233. package/dist/node/features/secure-shell.d.ts.map +1 -0
  234. package/dist/node/features/semantic-search.d.ts +207 -0
  235. package/dist/node/features/semantic-search.d.ts.map +1 -0
  236. package/dist/node/features/sqlite.d.ts +180 -0
  237. package/dist/node/features/sqlite.d.ts.map +1 -0
  238. package/dist/node/features/telegram.d.ts +173 -0
  239. package/dist/node/features/telegram.d.ts.map +1 -0
  240. package/dist/node/features/transpiler.d.ts +51 -0
  241. package/dist/node/features/transpiler.d.ts.map +1 -0
  242. package/dist/node/features/tts.d.ts +108 -0
  243. package/dist/node/features/tts.d.ts.map +1 -0
  244. package/dist/node/features/ui.d.ts +562 -0
  245. package/dist/node/features/ui.d.ts.map +1 -0
  246. package/dist/node/features/vault.d.ts +90 -0
  247. package/dist/node/features/vault.d.ts.map +1 -0
  248. package/dist/node/features/vm.d.ts +285 -0
  249. package/dist/node/features/vm.d.ts.map +1 -0
  250. package/dist/node/features/yaml-tree.d.ts +118 -0
  251. package/dist/node/features/yaml-tree.d.ts.map +1 -0
  252. package/dist/node/features/yaml.d.ts +127 -0
  253. package/dist/node/features/yaml.d.ts.map +1 -0
  254. package/dist/node.d.ts +67 -0
  255. package/dist/node.d.ts.map +1 -0
  256. package/dist/python/generated.d.ts +2 -0
  257. package/dist/python/generated.d.ts.map +1 -0
  258. package/dist/react/index.d.ts +36 -0
  259. package/dist/react/index.d.ts.map +1 -0
  260. package/dist/registry.d.ts +97 -0
  261. package/dist/registry.d.ts.map +1 -0
  262. package/dist/scaffolds/generated.d.ts +13 -0
  263. package/dist/scaffolds/generated.d.ts.map +1 -0
  264. package/dist/scaffolds/template.d.ts +11 -0
  265. package/dist/scaffolds/template.d.ts.map +1 -0
  266. package/dist/schemas/base.d.ts +254 -0
  267. package/dist/schemas/base.d.ts.map +1 -0
  268. package/dist/selector.d.ts +130 -0
  269. package/dist/selector.d.ts.map +1 -0
  270. package/dist/server.d.ts +89 -0
  271. package/dist/server.d.ts.map +1 -0
  272. package/dist/servers/express.d.ts +104 -0
  273. package/dist/servers/express.d.ts.map +1 -0
  274. package/dist/servers/mcp.d.ts +201 -0
  275. package/dist/servers/mcp.d.ts.map +1 -0
  276. package/dist/servers/socket.d.ts +121 -0
  277. package/dist/servers/socket.d.ts.map +1 -0
  278. package/dist/state.d.ts +24 -0
  279. package/dist/state.d.ts.map +1 -0
  280. package/dist/web/clients/socket.d.ts +37 -0
  281. package/dist/web/clients/socket.d.ts.map +1 -0
  282. package/dist/web/container.d.ts +55 -0
  283. package/dist/web/container.d.ts.map +1 -0
  284. package/dist/web/extension.d.ts +4 -0
  285. package/dist/web/extension.d.ts.map +1 -0
  286. package/dist/web/feature.d.ts +8 -0
  287. package/dist/web/feature.d.ts.map +1 -0
  288. package/dist/web/features/asset-loader.d.ts +35 -0
  289. package/dist/web/features/asset-loader.d.ts.map +1 -0
  290. package/dist/web/features/container-link.d.ts +167 -0
  291. package/dist/web/features/container-link.d.ts.map +1 -0
  292. package/dist/web/features/esbuild.d.ts +51 -0
  293. package/dist/web/features/esbuild.d.ts.map +1 -0
  294. package/dist/web/features/helpers.d.ts +140 -0
  295. package/dist/web/features/helpers.d.ts.map +1 -0
  296. package/dist/web/features/network.d.ts +69 -0
  297. package/dist/web/features/network.d.ts.map +1 -0
  298. package/dist/web/features/speech.d.ts +71 -0
  299. package/dist/web/features/speech.d.ts.map +1 -0
  300. package/dist/web/features/vault.d.ts +62 -0
  301. package/dist/web/features/vault.d.ts.map +1 -0
  302. package/dist/web/features/vm.d.ts +48 -0
  303. package/dist/web/features/vm.d.ts.map +1 -0
  304. package/dist/web/features/voice-recognition.d.ts +96 -0
  305. package/dist/web/features/voice-recognition.d.ts.map +1 -0
  306. package/dist/web/shims/isomorphic-vm.d.ts +22 -0
  307. package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
  308. package/docs/CLI.md +335 -0
  309. package/docs/CNAME +1 -0
  310. package/docs/README.md +60 -0
  311. package/docs/TABLE-OF-CONTENTS.md +183 -0
  312. package/docs/apis/clients/elevenlabs.md +308 -0
  313. package/docs/apis/clients/graph.md +107 -0
  314. package/docs/apis/clients/openai.md +429 -0
  315. package/docs/apis/clients/rest.md +161 -0
  316. package/docs/apis/clients/websocket.md +174 -0
  317. package/docs/apis/features/agi/assistant.md +625 -0
  318. package/docs/apis/features/agi/assistants-manager.md +282 -0
  319. package/docs/apis/features/agi/auto-assistant.md +279 -0
  320. package/docs/apis/features/agi/browser-use.md +802 -0
  321. package/docs/apis/features/agi/claude-code.md +884 -0
  322. package/docs/apis/features/agi/conversation-history.md +364 -0
  323. package/docs/apis/features/agi/conversation.md +548 -0
  324. package/docs/apis/features/agi/docs-reader.md +99 -0
  325. package/docs/apis/features/agi/file-tools.md +163 -0
  326. package/docs/apis/features/agi/luca-coder.md +407 -0
  327. package/docs/apis/features/agi/openai-codex.md +396 -0
  328. package/docs/apis/features/agi/openapi.md +138 -0
  329. package/docs/apis/features/agi/semantic-search.md +387 -0
  330. package/docs/apis/features/agi/skills-library.md +239 -0
  331. package/docs/apis/features/node/container-link.md +192 -0
  332. package/docs/apis/features/node/content-db.md +450 -0
  333. package/docs/apis/features/node/disk-cache.md +379 -0
  334. package/docs/apis/features/node/dns.md +652 -0
  335. package/docs/apis/features/node/docker.md +706 -0
  336. package/docs/apis/features/node/downloader.md +81 -0
  337. package/docs/apis/features/node/esbuild.md +60 -0
  338. package/docs/apis/features/node/file-manager.md +191 -0
  339. package/docs/apis/features/node/fs.md +1217 -0
  340. package/docs/apis/features/node/git.md +371 -0
  341. package/docs/apis/features/node/google-auth.md +193 -0
  342. package/docs/apis/features/node/google-calendar.md +202 -0
  343. package/docs/apis/features/node/google-docs.md +173 -0
  344. package/docs/apis/features/node/google-drive.md +246 -0
  345. package/docs/apis/features/node/google-mail.md +214 -0
  346. package/docs/apis/features/node/google-sheets.md +194 -0
  347. package/docs/apis/features/node/grep.md +292 -0
  348. package/docs/apis/features/node/helpers.md +164 -0
  349. package/docs/apis/features/node/ink.md +334 -0
  350. package/docs/apis/features/node/ipc-socket.md +249 -0
  351. package/docs/apis/features/node/json-tree.md +86 -0
  352. package/docs/apis/features/node/networking.md +316 -0
  353. package/docs/apis/features/node/nlp.md +133 -0
  354. package/docs/apis/features/node/opener.md +97 -0
  355. package/docs/apis/features/node/os.md +146 -0
  356. package/docs/apis/features/node/package-finder.md +392 -0
  357. package/docs/apis/features/node/postgres.md +234 -0
  358. package/docs/apis/features/node/proc.md +399 -0
  359. package/docs/apis/features/node/process-manager.md +305 -0
  360. package/docs/apis/features/node/python.md +604 -0
  361. package/docs/apis/features/node/redis.md +380 -0
  362. package/docs/apis/features/node/repl.md +88 -0
  363. package/docs/apis/features/node/runpod.md +674 -0
  364. package/docs/apis/features/node/secure-shell.md +176 -0
  365. package/docs/apis/features/node/semantic-search.md +408 -0
  366. package/docs/apis/features/node/sqlite.md +233 -0
  367. package/docs/apis/features/node/telegram.md +279 -0
  368. package/docs/apis/features/node/transpiler.md +74 -0
  369. package/docs/apis/features/node/tts.md +133 -0
  370. package/docs/apis/features/node/ui.md +701 -0
  371. package/docs/apis/features/node/vault.md +59 -0
  372. package/docs/apis/features/node/vm.md +75 -0
  373. package/docs/apis/features/node/yaml-tree.md +85 -0
  374. package/docs/apis/features/node/yaml.md +176 -0
  375. package/docs/apis/features/web/asset-loader.md +59 -0
  376. package/docs/apis/features/web/container-link.md +192 -0
  377. package/docs/apis/features/web/esbuild.md +54 -0
  378. package/docs/apis/features/web/helpers.md +164 -0
  379. package/docs/apis/features/web/network.md +44 -0
  380. package/docs/apis/features/web/speech.md +69 -0
  381. package/docs/apis/features/web/vault.md +59 -0
  382. package/docs/apis/features/web/vm.md +75 -0
  383. package/docs/apis/features/web/voice.md +84 -0
  384. package/docs/apis/servers/express.md +171 -0
  385. package/docs/apis/servers/mcp.md +238 -0
  386. package/docs/apis/servers/websocket.md +170 -0
  387. package/docs/bootstrap/CLAUDE.md +101 -0
  388. package/docs/bootstrap/SKILL.md +341 -0
  389. package/docs/bootstrap/templates/about-command.ts +41 -0
  390. package/docs/bootstrap/templates/docs-models.ts +22 -0
  391. package/docs/bootstrap/templates/docs-readme.md +43 -0
  392. package/docs/bootstrap/templates/example-feature.ts +53 -0
  393. package/docs/bootstrap/templates/health-endpoint.ts +15 -0
  394. package/docs/bootstrap/templates/luca-cli.ts +30 -0
  395. package/docs/bootstrap/templates/runme.md +54 -0
  396. package/docs/challenges/caching-proxy.md +16 -0
  397. package/docs/challenges/content-db-round-trip.md +14 -0
  398. package/docs/challenges/custom-command.md +9 -0
  399. package/docs/challenges/file-watcher-pipeline.md +11 -0
  400. package/docs/challenges/grep-audit-report.md +15 -0
  401. package/docs/challenges/multi-feature-dashboard.md +14 -0
  402. package/docs/challenges/process-orchestrator.md +17 -0
  403. package/docs/challenges/rest-api-server-with-client.md +12 -0
  404. package/docs/challenges/script-runner-with-vm.md +11 -0
  405. package/docs/challenges/simple-rest-api.md +15 -0
  406. package/docs/challenges/websocket-serve-and-client.md +11 -0
  407. package/docs/challenges/yaml-config-system.md +14 -0
  408. package/docs/command-system-overhaul.md +94 -0
  409. package/docs/documentation-audit.md +134 -0
  410. package/docs/examples/assistant/CORE.md +18 -0
  411. package/docs/examples/assistant/hooks.ts +3 -0
  412. package/docs/examples/assistant/tools.ts +10 -0
  413. package/docs/examples/assistant-hooks-reference.ts +171 -0
  414. package/docs/examples/assistant-with-process-manager.md +84 -0
  415. package/docs/examples/content-db.md +77 -0
  416. package/docs/examples/disk-cache.md +83 -0
  417. package/docs/examples/docker.md +101 -0
  418. package/docs/examples/downloader.md +70 -0
  419. package/docs/examples/entity.md +124 -0
  420. package/docs/examples/esbuild.md +80 -0
  421. package/docs/examples/feature-as-tool-provider.md +143 -0
  422. package/docs/examples/file-manager.md +82 -0
  423. package/docs/examples/fs.md +83 -0
  424. package/docs/examples/git.md +85 -0
  425. package/docs/examples/google-auth.md +88 -0
  426. package/docs/examples/google-calendar.md +94 -0
  427. package/docs/examples/google-docs.md +82 -0
  428. package/docs/examples/google-drive.md +96 -0
  429. package/docs/examples/google-sheets.md +95 -0
  430. package/docs/examples/grep.md +85 -0
  431. package/docs/examples/ink-blocks.md +75 -0
  432. package/docs/examples/ink-renderer.md +41 -0
  433. package/docs/examples/ink.md +103 -0
  434. package/docs/examples/ipc-socket.md +103 -0
  435. package/docs/examples/json-tree.md +91 -0
  436. package/docs/examples/networking.md +58 -0
  437. package/docs/examples/nlp.md +91 -0
  438. package/docs/examples/opener.md +78 -0
  439. package/docs/examples/os.md +72 -0
  440. package/docs/examples/package-finder.md +89 -0
  441. package/docs/examples/postgres.md +91 -0
  442. package/docs/examples/proc.md +81 -0
  443. package/docs/examples/process-manager.md +79 -0
  444. package/docs/examples/python.md +132 -0
  445. package/docs/examples/repl.md +93 -0
  446. package/docs/examples/runpod.md +119 -0
  447. package/docs/examples/secure-shell.md +92 -0
  448. package/docs/examples/sqlite.md +86 -0
  449. package/docs/examples/structured-output-with-assistants.md +144 -0
  450. package/docs/examples/telegram.md +77 -0
  451. package/docs/examples/tts.md +86 -0
  452. package/docs/examples/ui.md +80 -0
  453. package/docs/examples/vault.md +70 -0
  454. package/docs/examples/vm.md +86 -0
  455. package/docs/examples/websocket-ask-and-reply-example.md +128 -0
  456. package/docs/examples/yaml-tree.md +93 -0
  457. package/docs/examples/yaml.md +104 -0
  458. package/docs/ideas/assistant-factory-pattern.md +142 -0
  459. package/docs/in-memory-fs.md +4 -0
  460. package/docs/introspection-audit.md +49 -0
  461. package/docs/introspection.md +164 -0
  462. package/docs/mcp/readme.md +162 -0
  463. package/docs/models.ts +41 -0
  464. package/docs/philosophy.md +86 -0
  465. package/docs/principles.md +7 -0
  466. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  467. package/docs/prompts/check-for-undocumented-features.md +27 -0
  468. package/docs/prompts/mcp-test-easy-command.md +27 -0
  469. package/docs/scaffolds/client.md +149 -0
  470. package/docs/scaffolds/command.md +120 -0
  471. package/docs/scaffolds/endpoint.md +171 -0
  472. package/docs/scaffolds/feature.md +158 -0
  473. package/docs/scaffolds/selector.md +91 -0
  474. package/docs/scaffolds/server.md +196 -0
  475. package/docs/selectors.md +115 -0
  476. package/docs/sessions/custom-command/attempt-log-2.md +195 -0
  477. package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
  478. package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
  479. package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
  480. package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
  481. package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
  482. package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
  483. package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
  484. package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
  485. package/docs/tutorials/00-bootstrap.md +166 -0
  486. package/docs/tutorials/01-getting-started.md +106 -0
  487. package/docs/tutorials/02-container.md +210 -0
  488. package/docs/tutorials/03-scripts.md +194 -0
  489. package/docs/tutorials/04-features-overview.md +196 -0
  490. package/docs/tutorials/05-state-and-events.md +171 -0
  491. package/docs/tutorials/06-servers.md +157 -0
  492. package/docs/tutorials/07-endpoints.md +198 -0
  493. package/docs/tutorials/08-commands.md +252 -0
  494. package/docs/tutorials/09-clients.md +162 -0
  495. package/docs/tutorials/10-creating-features.md +203 -0
  496. package/docs/tutorials/11-contentbase.md +191 -0
  497. package/docs/tutorials/12-assistants.md +215 -0
  498. package/docs/tutorials/13-introspection.md +157 -0
  499. package/docs/tutorials/14-type-system.md +174 -0
  500. package/docs/tutorials/15-project-patterns.md +222 -0
  501. package/docs/tutorials/16-google-features.md +534 -0
  502. package/docs/tutorials/17-tui-blocks.md +530 -0
  503. package/docs/tutorials/18-semantic-search.md +334 -0
  504. package/docs/tutorials/19-python-sessions.md +401 -0
  505. package/docs/tutorials/20-browser-esm.md +234 -0
  506. package/index.html +1430 -0
  507. package/index.ts +1 -0
  508. package/install.sh +84 -0
  509. package/luca.cli.ts +16 -0
  510. package/luca.console.ts +9 -0
  511. package/main.py +6 -0
  512. package/package.json +219 -58
  513. package/public/index.html +1430 -0
  514. package/public/slides-ai-native.html +902 -0
  515. package/public/slides-intro.html +974 -0
  516. package/pyproject.toml +7 -0
  517. package/scripts/build-web.ts +28 -0
  518. package/scripts/examples/ask-luca-expert.ts +42 -0
  519. package/scripts/examples/assistant-questions.ts +12 -0
  520. package/scripts/examples/excalidraw-expert.ts +75 -0
  521. package/scripts/examples/expert-chat.ts +0 -0
  522. package/scripts/examples/file-manager.ts +14 -0
  523. package/scripts/examples/ideas.ts +12 -0
  524. package/scripts/examples/interactive-chat.ts +20 -0
  525. package/scripts/examples/openai-tool-calls.ts +113 -0
  526. package/scripts/examples/opening-a-web-browser.ts +5 -0
  527. package/scripts/examples/telegram-bot.ts +79 -0
  528. package/scripts/examples/using-assistant-with-mcp.ts +555 -0
  529. package/scripts/examples/using-claude-code.ts +10 -0
  530. package/scripts/examples/using-contentdb.ts +35 -0
  531. package/scripts/examples/using-conversations.ts +35 -0
  532. package/scripts/examples/using-disk-cache.ts +10 -0
  533. package/scripts/examples/using-docker-shell.ts +75 -0
  534. package/scripts/examples/using-elevenlabs.ts +25 -0
  535. package/scripts/examples/using-google-calendar.ts +57 -0
  536. package/scripts/examples/using-google-docs.ts +74 -0
  537. package/scripts/examples/using-google-drive.ts +74 -0
  538. package/scripts/examples/using-google-sheets.ts +89 -0
  539. package/scripts/examples/using-nlp.ts +55 -0
  540. package/scripts/examples/using-ollama.ts +11 -0
  541. package/scripts/examples/using-postgres.ts +55 -0
  542. package/scripts/examples/using-runpod.ts +32 -0
  543. package/scripts/examples/using-tts.ts +40 -0
  544. package/scripts/scaffold.ts +391 -0
  545. package/scripts/scratch.ts +15 -0
  546. package/scripts/stamp-build.sh +12 -0
  547. package/scripts/test-assistant-hooks.ts +13 -0
  548. package/scripts/test-docs-reader.ts +10 -0
  549. package/scripts/test-linux-binary.sh +80 -0
  550. package/scripts/update-introspection-data.ts +58 -0
  551. package/src/agi/README.md +14 -0
  552. package/src/agi/container.server.ts +152 -0
  553. package/src/agi/endpoints/ask.ts +60 -0
  554. package/src/agi/endpoints/conversations/[id].ts +45 -0
  555. package/src/agi/endpoints/conversations.ts +31 -0
  556. package/src/agi/endpoints/experts.ts +37 -0
  557. package/src/agi/feature.ts +13 -0
  558. package/src/agi/features/agent-memory.ts +694 -0
  559. package/src/agi/features/assistant.ts +1624 -0
  560. package/src/agi/features/assistants-manager.ts +418 -0
  561. package/src/agi/features/autonomous-assistant.ts +431 -0
  562. package/src/agi/features/browser-use.ts +653 -0
  563. package/src/agi/features/claude-code.ts +1538 -0
  564. package/src/agi/features/coding-tools.ts +175 -0
  565. package/src/agi/features/conversation-history.ts +495 -0
  566. package/src/agi/features/conversation.ts +1323 -0
  567. package/src/agi/features/docs-reader.ts +167 -0
  568. package/src/agi/features/file-tools.ts +293 -0
  569. package/src/agi/features/luca-coder.ts +639 -0
  570. package/src/agi/features/openai-codex.ts +651 -0
  571. package/src/agi/features/openapi.ts +445 -0
  572. package/src/agi/features/skills-library.ts +478 -0
  573. package/src/agi/index.ts +6 -0
  574. package/src/agi/lib/interceptor-chain.ts +89 -0
  575. package/src/agi/lib/token-counter.ts +122 -0
  576. package/src/bootstrap/generated.ts +9792 -0
  577. package/src/browser.ts +25 -0
  578. package/src/bus.ts +122 -0
  579. package/src/cli/build-info.ts +4 -0
  580. package/src/cli/cli.ts +355 -0
  581. package/src/client.ts +170 -0
  582. package/src/clients/civitai/index.ts +537 -0
  583. package/src/clients/client-template.ts +41 -0
  584. package/src/clients/comfyui/index.ts +604 -0
  585. package/src/clients/elevenlabs/index.ts +317 -0
  586. package/src/clients/graph.ts +87 -0
  587. package/src/clients/openai/index.ts +456 -0
  588. package/src/clients/rest.ts +207 -0
  589. package/src/clients/supabase/index.ts +357 -0
  590. package/src/clients/voicebox/index.ts +300 -0
  591. package/src/clients/websocket.ts +251 -0
  592. package/src/command.ts +505 -0
  593. package/src/commands/bootstrap.ts +244 -0
  594. package/src/commands/chat.ts +308 -0
  595. package/src/commands/code.ts +371 -0
  596. package/src/commands/console.ts +189 -0
  597. package/src/commands/describe.ts +243 -0
  598. package/src/commands/eval.ts +121 -0
  599. package/src/commands/help.ts +240 -0
  600. package/src/commands/index.ts +19 -0
  601. package/src/commands/introspect.ts +218 -0
  602. package/src/commands/mcp.ts +64 -0
  603. package/src/commands/prompt.ts +982 -0
  604. package/src/commands/run.ts +278 -0
  605. package/src/commands/sandbox-mcp.ts +343 -0
  606. package/src/commands/save-api-docs.ts +51 -0
  607. package/src/commands/scaffold.ts +225 -0
  608. package/src/commands/select.ts +99 -0
  609. package/src/commands/serve.ts +208 -0
  610. package/src/container-describer.ts +1084 -0
  611. package/src/container.ts +1186 -0
  612. package/src/endpoint.ts +365 -0
  613. package/src/entity.ts +173 -0
  614. package/src/feature.ts +118 -0
  615. package/src/graft.ts +181 -0
  616. package/src/hash-object.ts +97 -0
  617. package/src/helper.ts +849 -0
  618. package/src/introspection/generated.agi.ts +40208 -0
  619. package/src/introspection/generated.node.ts +28686 -0
  620. package/src/introspection/generated.web.ts +2251 -0
  621. package/src/introspection/index.ts +296 -0
  622. package/src/introspection/scan.ts +1131 -0
  623. package/src/node/container.ts +409 -0
  624. package/src/node/feature.ts +13 -0
  625. package/src/node/features/container-link.ts +559 -0
  626. package/src/node/features/content-db.ts +812 -0
  627. package/src/node/features/disk-cache.ts +388 -0
  628. package/src/node/features/dns.ts +669 -0
  629. package/src/node/features/docker.ts +921 -0
  630. package/src/node/features/downloader.ts +79 -0
  631. package/src/node/features/figlet-fonts.ts +600 -0
  632. package/src/node/features/file-manager.ts +535 -0
  633. package/src/node/features/fs.ts +1050 -0
  634. package/src/node/features/git.ts +592 -0
  635. package/src/node/features/google-auth.ts +504 -0
  636. package/src/node/features/google-calendar.ts +306 -0
  637. package/src/node/features/google-docs.ts +412 -0
  638. package/src/node/features/google-drive.ts +346 -0
  639. package/src/node/features/google-mail.ts +540 -0
  640. package/src/node/features/google-sheets.ts +286 -0
  641. package/src/node/features/grep.ts +427 -0
  642. package/src/node/features/helpers.ts +735 -0
  643. package/src/node/features/ink.ts +490 -0
  644. package/src/node/features/ipc-socket.ts +649 -0
  645. package/src/node/features/json-tree.ts +170 -0
  646. package/src/node/features/networking.ts +961 -0
  647. package/src/node/features/nlp.ts +212 -0
  648. package/src/node/features/opener.ts +180 -0
  649. package/src/node/features/os.ts +403 -0
  650. package/src/node/features/package-finder.ts +540 -0
  651. package/src/node/features/postgres.ts +289 -0
  652. package/src/node/features/proc.ts +503 -0
  653. package/src/node/features/process-manager.ts +844 -0
  654. package/src/node/features/python.ts +906 -0
  655. package/src/node/features/redis.ts +446 -0
  656. package/src/node/features/repl.ts +212 -0
  657. package/src/node/features/runpod.ts +811 -0
  658. package/src/node/features/secure-shell.ts +267 -0
  659. package/src/node/features/semantic-search.ts +935 -0
  660. package/src/node/features/sqlite.ts +289 -0
  661. package/src/node/features/telegram.ts +343 -0
  662. package/src/node/features/transpiler.ts +161 -0
  663. package/src/node/features/tts.ts +185 -0
  664. package/src/node/features/ui.ts +786 -0
  665. package/src/node/features/vault.ts +153 -0
  666. package/src/node/features/vm.ts +462 -0
  667. package/src/node/features/yaml-tree.ts +148 -0
  668. package/src/node/features/yaml.ts +133 -0
  669. package/src/node.ts +76 -0
  670. package/src/python/bridge.py +220 -0
  671. package/src/python/generated.ts +227 -0
  672. package/src/react/index.ts +175 -0
  673. package/src/registry.ts +210 -0
  674. package/src/scaffolds/generated.ts +1815 -0
  675. package/src/scaffolds/template.ts +46 -0
  676. package/src/schemas/base.ts +296 -0
  677. package/src/selector.ts +352 -0
  678. package/src/server.ts +229 -0
  679. package/src/servers/express.ts +283 -0
  680. package/src/servers/mcp.ts +802 -0
  681. package/src/servers/socket.ts +258 -0
  682. package/src/state.ts +101 -0
  683. package/src/web/clients/socket.ts +99 -0
  684. package/src/web/container.ts +75 -0
  685. package/src/web/extension.ts +30 -0
  686. package/src/web/feature.ts +12 -0
  687. package/src/web/features/asset-loader.ts +72 -0
  688. package/src/web/features/container-link.ts +382 -0
  689. package/src/web/features/esbuild.ts +93 -0
  690. package/src/web/features/helpers.ts +269 -0
  691. package/src/web/features/network.ts +85 -0
  692. package/src/web/features/speech.ts +104 -0
  693. package/src/web/features/vault.ts +207 -0
  694. package/src/web/features/vm.ts +85 -0
  695. package/src/web/features/voice-recognition.ts +161 -0
  696. package/src/web/shims/isomorphic-vm.ts +149 -0
  697. package/test/assistant-hooks.test.ts +306 -0
  698. package/test/assistant.test.ts +81 -0
  699. package/test/bus.test.ts +134 -0
  700. package/test/clients-servers.test.ts +217 -0
  701. package/test/command.test.ts +267 -0
  702. package/test/container-link.test.ts +274 -0
  703. package/test/conversation.test.ts +220 -0
  704. package/test/features.test.ts +160 -0
  705. package/test/fork-and-research.test.ts +450 -0
  706. package/test/integration.test.ts +787 -0
  707. package/test/interceptor-chain.test.ts +61 -0
  708. package/test/node-container.test.ts +121 -0
  709. package/test/python-session.test.ts +105 -0
  710. package/test/rate-limit.test.ts +272 -0
  711. package/test/semantic-search.test.ts +550 -0
  712. package/test/state.test.ts +121 -0
  713. package/test/vm-context.test.ts +146 -0
  714. package/test/vm-loadmodule.test.ts +213 -0
  715. package/test/websocket-ask.test.ts +101 -0
  716. package/test-integration/assistant.test.ts +138 -0
  717. package/test-integration/assistants-manager.test.ts +113 -0
  718. package/test-integration/claude-code.test.ts +98 -0
  719. package/test-integration/conversation-history.test.ts +205 -0
  720. package/test-integration/conversation.test.ts +137 -0
  721. package/test-integration/elevenlabs.test.ts +55 -0
  722. package/test-integration/google-services.test.ts +80 -0
  723. package/test-integration/helpers.ts +89 -0
  724. package/test-integration/memory.test.ts +204 -0
  725. package/test-integration/openai-codex.test.ts +93 -0
  726. package/test-integration/runpod.test.ts +58 -0
  727. package/test-integration/server-endpoints.test.ts +97 -0
  728. package/test-integration/telegram.test.ts +46 -0
  729. package/tsconfig.build.json +12 -0
  730. package/tsconfig.json +58 -0
  731. package/uv.lock +8 -0
  732. package/LICENSE +0 -21
  733. package/dist/cli/cli.js +0 -48
  734. package/dist/cli/common.d.ts +0 -2
  735. package/dist/cli/common.js +0 -6
  736. package/dist/cli/index.d.ts +0 -2
  737. package/dist/cli/index.js +0 -5
  738. package/dist/cli/run.d.ts +0 -1
  739. package/dist/cli/run.js +0 -38
  740. package/dist/core/index.d.ts +0 -4
  741. package/dist/core/index.js +0 -32
  742. package/dist/core/read.d.ts +0 -2
  743. package/dist/core/read.js +0 -29
  744. package/dist/core/request.d.ts +0 -1
  745. package/dist/core/request.js +0 -2
  746. package/dist/core/write.d.ts +0 -2
  747. package/dist/core/write.js +0 -21
  748. package/dist/index.d.ts +0 -1
  749. package/dist/index.js +0 -5
  750. package/dist/utils/common.d.ts +0 -9
  751. package/dist/utils/common.js +0 -57
  752. package/dist/utils/consts.d.ts +0 -3
  753. package/dist/utils/consts.js +0 -11
  754. package/dist/utils/dict.d.ts +0 -1
  755. package/dist/utils/dict.js +0 -7
  756. package/dist/utils/index.d.ts +0 -5
  757. package/dist/utils/index.js +0 -21
  758. package/dist/utils/log.d.ts +0 -1
  759. package/dist/utils/log.js +0 -5
  760. package/dist/utils/types.d.ts +0 -1
  761. package/dist/utils/types.js +0 -2
  762. package/dist/utils/utils.test.d.ts +0 -1
  763. package/dist/utils/utils.test.js +0 -7
@@ -0,0 +1,1815 @@
1
+ // Auto-generated scaffold and MCP readme content
2
+ // Generated at: 2026-04-10T05:00:53.697Z
3
+ // Source: docs/scaffolds/*.md, docs/examples/assistant/, 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 } from '@soederpop/luca'` },
24
+ { heading: "Schemas", code: `export const {{PascalName}}StateSchema = FeatureStateSchema.extend({
25
+ // Add your state fields here. These are observable — changes emit events.
26
+ // Example: itemCount: z.number().default(0).describe('Number of items stored'),
27
+ })
28
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
29
+
30
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({
31
+ // Add constructor options here. Validated when the feature is created.
32
+ // Example: directory: z.string().optional().describe('Storage directory path'),
33
+ })
34
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
35
+
36
+ export const {{PascalName}}EventsSchema = FeatureEventsSchema.extend({
37
+ // Each key is an event name. Value is z.tuple() of listener arguments.
38
+ // Example: itemAdded: z.tuple([z.string().describe('Item key')]).describe('Emitted when an item is added'),
39
+ })` },
40
+ { heading: "Class", code: `/**
41
+ * {{description}}
42
+ * \`\`\`typescript
43
+ * const {{camelName}} = container.feature('{{camelName}}')
44
+ * \`\`\`
45
+ *
46
+ * @extends Feature
47
+ */
48
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
49
+ static override shortcut = 'features.{{camelName}}' as const
50
+ static override stateSchema = {{PascalName}}StateSchema
51
+ static override optionsSchema = {{PascalName}}OptionsSchema
52
+ static override eventsSchema = {{PascalName}}EventsSchema
53
+
54
+ static { Feature.register(this, '{{camelName}}') }
55
+
56
+ /**
57
+ * Called after the feature is initialized. Use this for any setup logic
58
+ * instead of overriding the constructor.
59
+ */
60
+ async afterInitialize() {
61
+ // Set up initial state, start background tasks, etc.
62
+ }
63
+ }` },
64
+ { heading: "Module Augmentation", code: `declare module '@soederpop/luca' {
65
+ interface AvailableFeatures {
66
+ {{camelName}}: typeof {{PascalName}}
67
+ }
68
+ }` },
69
+ { heading: "Registration", code: `// Inside the class:
70
+ static { Feature.register(this, '{{camelName}}') }
71
+
72
+ // At module level:
73
+ export default {{PascalName}}` },
74
+ { heading: "Complete Example", code: `import { z } from 'zod'
75
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
76
+ import { Feature } from '@soederpop/luca'
77
+
78
+ declare module '@soederpop/luca' {
79
+ interface AvailableFeatures {
80
+ {{camelName}}: typeof {{PascalName}}
81
+ }
82
+ }
83
+
84
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({})
85
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
86
+
87
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({})
88
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
89
+
90
+ /**
91
+ * {{description}}
92
+ *
93
+ * @example
94
+ * \`\`\`typescript
95
+ * const {{camelName}} = container.feature('{{camelName}}')
96
+ * \`\`\`
97
+ *
98
+ * @extends Feature
99
+ */
100
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
101
+ static override shortcut = 'features.{{camelName}}' as const
102
+ static override stateSchema = {{PascalName}}StateSchema
103
+ static override optionsSchema = {{PascalName}}OptionsSchema
104
+ static { Feature.register(this, '{{camelName}}') }
105
+
106
+ async afterInitialize() {
107
+ // Setup logic goes here — not in the constructor
108
+ }
109
+ }
110
+
111
+ export default {{PascalName}}` }
112
+ ],
113
+ full: `import { z } from 'zod'
114
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
115
+ import { Feature } from '@soederpop/luca'
116
+
117
+ declare module '@soederpop/luca' {
118
+ interface AvailableFeatures {
119
+ {{camelName}}: typeof {{PascalName}}
120
+ }
121
+ }
122
+
123
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({})
124
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
125
+
126
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({})
127
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
128
+
129
+ /**
130
+ * {{description}}
131
+ *
132
+ * @example
133
+ * \`\`\`typescript
134
+ * const {{camelName}} = container.feature('{{camelName}}')
135
+ * \`\`\`
136
+ *
137
+ * @extends Feature
138
+ */
139
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
140
+ static override shortcut = 'features.{{camelName}}' as const
141
+ static override stateSchema = {{PascalName}}StateSchema
142
+ static override optionsSchema = {{PascalName}}OptionsSchema
143
+ static { Feature.register(this, '{{camelName}}') }
144
+
145
+ async afterInitialize() {
146
+ // Setup logic goes here — not in the constructor
147
+ }
148
+ }
149
+
150
+ export default {{PascalName}}`,
151
+ tutorial: `# Building a Feature
152
+
153
+ 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.
154
+
155
+ When to build a feature:
156
+ - You need a reusable local capability (not a network call — that's a client)
157
+ - You want state management, events, and introspection for free
158
+ - You're wrapping a library so the rest of the codebase uses a uniform interface
159
+
160
+ ## Imports
161
+
162
+ \`\`\`ts
163
+ import { z } from 'zod'
164
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '@soederpop/luca'
165
+ import { Feature } 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
+ The use of dynamic imports is encouraged here, only import libraries you need when the feature is used, and only when necessary in the lifecycle of the feature if it can be done.
171
+
172
+ feature's have built in ways to check if their requirements are supported and can be enabled cautiously.
173
+
174
+ ## Schemas
175
+
176
+ Define the shape of your feature's state, options, and events using Zod. Every field must have a \`.describe()\` — this becomes the documentation.
177
+
178
+ \`\`\`ts
179
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({
180
+ // Add your state fields here. These are observable — changes emit events.
181
+ // Example: itemCount: z.number().default(0).describe('Number of items stored'),
182
+ })
183
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
184
+
185
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({
186
+ // Add constructor options here. Validated when the feature is created.
187
+ // Example: directory: z.string().optional().describe('Storage directory path'),
188
+ })
189
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
190
+
191
+ export const {{PascalName}}EventsSchema = FeatureEventsSchema.extend({
192
+ // Each key is an event name. Value is z.tuple() of listener arguments.
193
+ // Example: itemAdded: z.tuple([z.string().describe('Item key')]).describe('Emitted when an item is added'),
194
+ })
195
+ \`\`\`
196
+
197
+ ## Class
198
+
199
+ 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\`.
200
+
201
+ Running \`luca introspect\` captures JSDoc blocks and Zod schemas and includes them in the description whenever somebody calls \`container.features.describe('{{camelName}}')\` or \`luca describe {{camelName}}\`.
202
+
203
+ \`\`\`ts
204
+ /**
205
+ * {{description}}
206
+ * \`\`\`typescript
207
+ * const {{camelName}} = container.feature('{{camelName}}')
208
+ * \`\`\`
209
+ *
210
+ * @extends Feature
211
+ */
212
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
213
+ static override shortcut = 'features.{{camelName}}' as const
214
+ static override stateSchema = {{PascalName}}StateSchema
215
+ static override optionsSchema = {{PascalName}}OptionsSchema
216
+ static override eventsSchema = {{PascalName}}EventsSchema
217
+
218
+ static { Feature.register(this, '{{camelName}}') }
219
+
220
+ /**
221
+ * Called after the feature is initialized. Use this for any setup logic
222
+ * instead of overriding the constructor.
223
+ */
224
+ async afterInitialize() {
225
+ // Set up initial state, start background tasks, etc.
226
+ }
227
+ }
228
+ \`\`\`
229
+
230
+ **Important**: You almost never need to override the constructor. Use \`afterInitialize()\` for any setup logic — it runs after the feature is fully wired into the container and has access to \`this.container\`, \`this.options\`, \`this.state\`, etc.
231
+
232
+ ## Module Augmentation
233
+
234
+ This is what gives \`container.feature('yourName')\` TypeScript autocomplete. Without it, the feature works but TypeScript won't know about it.
235
+
236
+ \`\`\`ts
237
+ declare module '@soederpop/luca' {
238
+ interface AvailableFeatures {
239
+ {{camelName}}: typeof {{PascalName}}
240
+ }
241
+ }
242
+ \`\`\`
243
+
244
+ ## Registration
245
+
246
+ Registration happens inside the class body using a static block. The default export is just the class itself.
247
+
248
+ \`\`\`ts
249
+ // Inside the class:
250
+ static { Feature.register(this, '{{camelName}}') }
251
+
252
+ // At module level:
253
+ export default {{PascalName}}
254
+ \`\`\`
255
+
256
+ ## Complete Example
257
+
258
+ Here's a minimal but complete feature. This is what a real feature file looks like:
259
+
260
+ \`\`\`ts
261
+ import { z } from 'zod'
262
+ import { FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
263
+ import { Feature } from '@soederpop/luca'
264
+
265
+ declare module '@soederpop/luca' {
266
+ interface AvailableFeatures {
267
+ {{camelName}}: typeof {{PascalName}}
268
+ }
269
+ }
270
+
271
+ export const {{PascalName}}StateSchema = FeatureStateSchema.extend({})
272
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
273
+
274
+ export const {{PascalName}}OptionsSchema = FeatureOptionsSchema.extend({})
275
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
276
+
277
+ /**
278
+ * {{description}}
279
+ *
280
+ * @example
281
+ * \`\`\`typescript
282
+ * const {{camelName}} = container.feature('{{camelName}}')
283
+ * \`\`\`
284
+ *
285
+ * @extends Feature
286
+ */
287
+ export class {{PascalName}} extends Feature<{{PascalName}}State, {{PascalName}}Options> {
288
+ static override shortcut = 'features.{{camelName}}' as const
289
+ static override stateSchema = {{PascalName}}StateSchema
290
+ static override optionsSchema = {{PascalName}}OptionsSchema
291
+ static { Feature.register(this, '{{camelName}}') }
292
+
293
+ async afterInitialize() {
294
+ // Setup logic goes here — not in the constructor
295
+ }
296
+ }
297
+
298
+ export default {{PascalName}}
299
+ \`\`\`
300
+
301
+ ## Conventions
302
+
303
+ - **Naming**: PascalCase for class, camelCase for registration ID. The file name should be kebab-case (e.g. \`disk-cache.ts\`).
304
+ - **JSDoc**: Every public method, getter, and the class itself needs a JSDoc block. Include \`@example\` with working code.
305
+ - **Describe everything**: Every Zod field needs \`.describe()\`. Every event tuple argument needs \`.describe()\`. This IS the documentation.
306
+ - **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.
307
+ - **State is observable**: Use \`this.state.set()\` and \`this.state.get()\`. Don't use plain instance properties for data that should be reactive.
308
+ - **Events for lifecycle**: Emit events for significant state changes so consumers can react.
309
+ `,
310
+ },
311
+ client: {
312
+ sections: [
313
+ { heading: "Imports", code: `import { z } from 'zod'
314
+ import { Client, RestClient } from '@soederpop/luca/client'
315
+ import { ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca'` },
316
+ { heading: "Schemas", code: `export const {{PascalName}}StateSchema = ClientStateSchema.extend({
317
+ // Add your state fields here.
318
+ // Example: authenticated: z.boolean().default(false).describe('Whether API auth is configured'),
319
+ })
320
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
321
+
322
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
323
+ // Add constructor options here.
324
+ // Example: apiKey: z.string().optional().describe('API key for authentication'),
325
+ })
326
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>` },
327
+ { heading: "Class", code: `/**
328
+ * {{description}}
329
+ *
330
+ * @example
331
+ * \`\`\`typescript
332
+ * const {{camelName}} = container.client('{{camelName}}')
333
+ * \`\`\`
334
+ *
335
+ * @extends RestClient
336
+ */
337
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
338
+ static override shortcut = 'clients.{{camelName}}' as const
339
+ static override stateSchema = {{PascalName}}StateSchema
340
+ static override optionsSchema = {{PascalName}}OptionsSchema
341
+ static { Client.register(this, '{{camelName}}') }
342
+
343
+ /**
344
+ * Called after the client is initialized. Use this for any setup logic
345
+ * instead of overriding the constructor.
346
+ */
347
+ async afterInitialize() {
348
+ // Set up default headers, configure auth, etc.
349
+ }
350
+
351
+ // Add API methods here. Each wraps an endpoint.
352
+ // Example:
353
+ // async listItems(): Promise<Item[]> {
354
+ // return this.get('/items')
355
+ // }
356
+ }` },
357
+ { heading: "Module Augmentation", code: `declare module '@soederpop/luca/client' {
358
+ interface AvailableClients {
359
+ {{camelName}}: typeof {{PascalName}}
360
+ }
361
+ }` },
362
+ { heading: "Registration", code: `// Inside the class:
363
+ static { Client.register(this, '{{camelName}}') }
364
+
365
+ // At module level:
366
+ export default {{PascalName}}` },
367
+ { heading: "Complete Example", code: `import { z } from 'zod'
368
+ import { Client, RestClient } from '@soederpop/luca/client'
369
+ import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca'
370
+
371
+ declare module '@soederpop/luca/client' {
372
+ interface AvailableClients {
373
+ {{camelName}}: typeof {{PascalName}}
374
+ }
375
+ }
376
+
377
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({})
378
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
379
+
380
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
381
+ baseURL: z.string().default('https://api.example.com').describe('API base URL'),
382
+ })
383
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
384
+
385
+ /**
386
+ * {{description}}
387
+ *
388
+ * @example
389
+ * \`\`\`typescript
390
+ * const {{camelName}} = container.client('{{camelName}}')
391
+ * \`\`\`
392
+ *
393
+ * @extends RestClient
394
+ */
395
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
396
+ static override shortcut = 'clients.{{camelName}}' as const
397
+ static override stateSchema = {{PascalName}}StateSchema
398
+ static override optionsSchema = {{PascalName}}OptionsSchema
399
+ static { Client.register(this, '{{camelName}}') }
400
+
401
+ async afterInitialize() {
402
+ // Setup logic goes here — not in the constructor
403
+ }
404
+ }
405
+
406
+ export default {{PascalName}}` }
407
+ ],
408
+ full: `import { z } from 'zod'
409
+ import { Client, RestClient } from '@soederpop/luca/client'
410
+ import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca'
411
+
412
+ declare module '@soederpop/luca/client' {
413
+ interface AvailableClients {
414
+ {{camelName}}: typeof {{PascalName}}
415
+ }
416
+ }
417
+
418
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({})
419
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
420
+
421
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
422
+ baseURL: z.string().default('https://api.example.com').describe('API base URL'),
423
+ })
424
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
425
+
426
+ /**
427
+ * {{description}}
428
+ *
429
+ * @example
430
+ * \`\`\`typescript
431
+ * const {{camelName}} = container.client('{{camelName}}')
432
+ * \`\`\`
433
+ *
434
+ * @extends RestClient
435
+ */
436
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
437
+ static override shortcut = 'clients.{{camelName}}' as const
438
+ static override stateSchema = {{PascalName}}StateSchema
439
+ static override optionsSchema = {{PascalName}}OptionsSchema
440
+ static { Client.register(this, '{{camelName}}') }
441
+
442
+ async afterInitialize() {
443
+ // Setup logic goes here — not in the constructor
444
+ }
445
+ }
446
+
447
+ export default {{PascalName}}`,
448
+ tutorial: `# Building a Client
449
+
450
+ 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.
451
+
452
+ When to build a client:
453
+ - You need to talk to an external API or service
454
+ - You want connection management, error handling, and observability for free
455
+ - You're wrapping an API so the rest of the codebase uses \`container.client('name')\`
456
+
457
+ ## Imports
458
+
459
+ \`\`\`ts
460
+ import { z } from 'zod'
461
+ import { Client, RestClient } from '@soederpop/luca/client'
462
+ import { ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca'
463
+ \`\`\`
464
+
465
+ Use \`RestClient\` for HTTP APIs (most common). It gives you \`get\`, \`post\`, \`put\`, \`patch\`, \`delete\` methods that handle JSON, headers, and error wrapping.
466
+
467
+ ## Schemas
468
+
469
+ \`\`\`ts
470
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({
471
+ // Add your state fields here.
472
+ // Example: authenticated: z.boolean().default(false).describe('Whether API auth is configured'),
473
+ })
474
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
475
+
476
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
477
+ // Add constructor options here.
478
+ // Example: apiKey: z.string().optional().describe('API key for authentication'),
479
+ })
480
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
481
+ \`\`\`
482
+
483
+ ## Class
484
+
485
+ Running \`luca introspect\` captures JSDoc blocks and Zod schemas and includes them in the description whenever somebody calls \`container.clients.describe('{{camelName}}')\` or \`luca describe {{camelName}}\`.
486
+
487
+ \`\`\`ts
488
+ /**
489
+ * {{description}}
490
+ *
491
+ * @example
492
+ * \`\`\`typescript
493
+ * const {{camelName}} = container.client('{{camelName}}')
494
+ * \`\`\`
495
+ *
496
+ * @extends RestClient
497
+ */
498
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
499
+ static override shortcut = 'clients.{{camelName}}' as const
500
+ static override stateSchema = {{PascalName}}StateSchema
501
+ static override optionsSchema = {{PascalName}}OptionsSchema
502
+ static { Client.register(this, '{{camelName}}') }
503
+
504
+ /**
505
+ * Called after the client is initialized. Use this for any setup logic
506
+ * instead of overriding the constructor.
507
+ */
508
+ async afterInitialize() {
509
+ // Set up default headers, configure auth, etc.
510
+ }
511
+
512
+ // Add API methods here. Each wraps an endpoint.
513
+ // Example:
514
+ // async listItems(): Promise<Item[]> {
515
+ // return this.get('/items')
516
+ // }
517
+ }
518
+ \`\`\`
519
+
520
+ **Important**: You almost never need to override the constructor. Use \`afterInitialize()\` for setup logic — it runs after the client is fully wired into the container. Set \`baseURL\` via the options schema default instead of constructor manipulation.
521
+
522
+ ## Module Augmentation
523
+
524
+ \`\`\`ts
525
+ declare module '@soederpop/luca/client' {
526
+ interface AvailableClients {
527
+ {{camelName}}: typeof {{PascalName}}
528
+ }
529
+ }
530
+ \`\`\`
531
+
532
+ ## Registration
533
+
534
+ Registration happens inside the class body using a static block. The default export is just the class itself.
535
+
536
+ \`\`\`ts
537
+ // Inside the class:
538
+ static { Client.register(this, '{{camelName}}') }
539
+
540
+ // At module level:
541
+ export default {{PascalName}}
542
+ \`\`\`
543
+
544
+ ## Complete Example
545
+
546
+ \`\`\`ts
547
+ import { z } from 'zod'
548
+ import { Client, RestClient } from '@soederpop/luca/client'
549
+ import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca'
550
+
551
+ declare module '@soederpop/luca/client' {
552
+ interface AvailableClients {
553
+ {{camelName}}: typeof {{PascalName}}
554
+ }
555
+ }
556
+
557
+ export const {{PascalName}}StateSchema = ClientStateSchema.extend({})
558
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
559
+
560
+ export const {{PascalName}}OptionsSchema = ClientOptionsSchema.extend({
561
+ baseURL: z.string().default('https://api.example.com').describe('API base URL'),
562
+ })
563
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
564
+
565
+ /**
566
+ * {{description}}
567
+ *
568
+ * @example
569
+ * \`\`\`typescript
570
+ * const {{camelName}} = container.client('{{camelName}}')
571
+ * \`\`\`
572
+ *
573
+ * @extends RestClient
574
+ */
575
+ export class {{PascalName}} extends RestClient<{{PascalName}}State, {{PascalName}}Options> {
576
+ static override shortcut = 'clients.{{camelName}}' as const
577
+ static override stateSchema = {{PascalName}}StateSchema
578
+ static override optionsSchema = {{PascalName}}OptionsSchema
579
+ static { Client.register(this, '{{camelName}}') }
580
+
581
+ async afterInitialize() {
582
+ // Setup logic goes here — not in the constructor
583
+ }
584
+ }
585
+
586
+ export default {{PascalName}}
587
+ \`\`\`
588
+
589
+ ## Conventions
590
+
591
+ - **Extend RestClient for HTTP**: It gives you typed HTTP methods. Only use base \`Client\` if you need a non-HTTP protocol.
592
+ - **Set baseURL via options schema**: Use a Zod \`.default()\` on the \`baseURL\` field rather than overriding the constructor.
593
+ - **Use \`afterInitialize()\`**: For any setup logic (auth, default headers, etc.) instead of overriding the constructor.
594
+ - **Wrap endpoints as methods**: Each API endpoint gets a method. Keep them thin — just map to HTTP calls.
595
+ - **JSDoc everything**: Every public method needs \`@param\`, \`@returns\`, \`@example\`. Run \`luca introspect\` after changes to update generated docs.
596
+ - **Auth in options**: Pass API keys, tokens via options schema. Check them in \`afterInitialize()\` or a setup method.
597
+ `,
598
+ },
599
+ server: {
600
+ sections: [
601
+ { heading: "Imports", code: `import { z } from 'zod'
602
+ import { Server } from '@soederpop/luca'
603
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
604
+ import type { NodeContainer } from '@soederpop/luca'
605
+ import type { ServersInterface } from '@soederpop/luca'` },
606
+ { heading: "Schemas", code: `export const {{PascalName}}StateSchema = ServerStateSchema.extend({
607
+ // Add your state fields here.
608
+ // Example: connectionCount: z.number().default(0).describe('Active connections'),
609
+ })
610
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
611
+
612
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({
613
+ // Add constructor options here. port and host come from ServerOptionsSchema.
614
+ // Example: cors: z.boolean().default(true).describe('Enable CORS'),
615
+ })
616
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
617
+
618
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({
619
+ // Add your events here.
620
+ // Example: connection: z.tuple([z.string().describe('Client ID')]).describe('New client connected'),
621
+ })` },
622
+ { heading: "Class", code: `/**
623
+ * {{description}}
624
+ *
625
+ * @example
626
+ * \`\`\`typescript
627
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
628
+ * await {{camelName}}.start()
629
+ * \`\`\`
630
+ *
631
+ * @extends Server
632
+ */
633
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
634
+ static override shortcut = 'servers.{{camelName}}' as const
635
+ static override stateSchema = {{PascalName}}StateSchema
636
+ static override optionsSchema = {{PascalName}}OptionsSchema
637
+ static override eventsSchema = {{PascalName}}EventsSchema
638
+ static { Server.register(this, '{{camelName}}') }
639
+
640
+ static override attach(container: NodeContainer & ServersInterface) {
641
+ return container
642
+ }
643
+
644
+ override async configure() {
645
+ if (this.isConfigured) return this
646
+ // Set up the underlying server here
647
+ this.state.set('configured', true)
648
+ return this
649
+ }
650
+
651
+ override async start(options?: { port?: number }) {
652
+ if (this.isListening) return this
653
+ if (!this.isConfigured) await this.configure()
654
+
655
+ const port = options?.port || this.options.port || 3000
656
+ // Start listening here
657
+ this.state.set('port', port)
658
+ this.state.set('listening', true)
659
+ return this
660
+ }
661
+
662
+ override async stop() {
663
+ if (this.isStopped) return this
664
+ // Clean up connections here
665
+ this.state.set('listening', false)
666
+ this.state.set('stopped', true)
667
+ return this
668
+ }
669
+ }` },
670
+ { heading: "Module Augmentation", code: `declare module '@soederpop/luca' {
671
+ interface AvailableServers {
672
+ {{camelName}}: typeof {{PascalName}}
673
+ }
674
+ }` },
675
+ { heading: "Registration", code: `// Inside the class:
676
+ static { Server.register(this, '{{camelName}}') }
677
+
678
+ // At module level:
679
+ export default {{PascalName}}` },
680
+ { heading: "Complete Example", code: `import { z } from 'zod'
681
+ import { Server } from '@soederpop/luca'
682
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
683
+ import type { NodeContainer } from '@soederpop/luca'
684
+ import type { ServersInterface } from '@soederpop/luca'
685
+
686
+ declare module '@soederpop/luca' {
687
+ interface AvailableServers {
688
+ {{camelName}}: typeof {{PascalName}}
689
+ }
690
+ }
691
+
692
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({})
693
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
694
+
695
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({})
696
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
697
+
698
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({})
699
+
700
+ /**
701
+ * {{description}}
702
+ *
703
+ * @example
704
+ * \`\`\`typescript
705
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
706
+ * await {{camelName}}.start()
707
+ * \`\`\`
708
+ *
709
+ * @extends Server
710
+ */
711
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
712
+ static override shortcut = 'servers.{{camelName}}' as const
713
+ static override stateSchema = {{PascalName}}StateSchema
714
+ static override optionsSchema = {{PascalName}}OptionsSchema
715
+ static override eventsSchema = {{PascalName}}EventsSchema
716
+ static { Server.register(this, '{{camelName}}') }
717
+
718
+ static override attach(container: NodeContainer & ServersInterface) {
719
+ return container
720
+ }
721
+
722
+ override async configure() {
723
+ if (this.isConfigured) return this
724
+ this.state.set('configured', true)
725
+ return this
726
+ }
727
+
728
+ override async start(options?: { port?: number }) {
729
+ if (this.isListening) return this
730
+ if (!this.isConfigured) await this.configure()
731
+ const port = options?.port || this.options.port || 3000
732
+ this.state.set('port', port)
733
+ this.state.set('listening', true)
734
+ return this
735
+ }
736
+
737
+ override async stop() {
738
+ if (this.isStopped) return this
739
+ this.state.set('listening', false)
740
+ this.state.set('stopped', true)
741
+ return this
742
+ }
743
+ }
744
+
745
+ export default {{PascalName}}` }
746
+ ],
747
+ full: `import { z } from 'zod'
748
+ import { Server } from '@soederpop/luca'
749
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
750
+ import type { NodeContainer } from '@soederpop/luca'
751
+ import type { ServersInterface } from '@soederpop/luca'
752
+
753
+ declare module '@soederpop/luca' {
754
+ interface AvailableServers {
755
+ {{camelName}}: typeof {{PascalName}}
756
+ }
757
+ }
758
+
759
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({})
760
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
761
+
762
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({})
763
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
764
+
765
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({})
766
+
767
+ /**
768
+ * {{description}}
769
+ *
770
+ * @example
771
+ * \`\`\`typescript
772
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
773
+ * await {{camelName}}.start()
774
+ * \`\`\`
775
+ *
776
+ * @extends Server
777
+ */
778
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
779
+ static override shortcut = 'servers.{{camelName}}' as const
780
+ static override stateSchema = {{PascalName}}StateSchema
781
+ static override optionsSchema = {{PascalName}}OptionsSchema
782
+ static override eventsSchema = {{PascalName}}EventsSchema
783
+ static { Server.register(this, '{{camelName}}') }
784
+
785
+ static override attach(container: NodeContainer & ServersInterface) {
786
+ return container
787
+ }
788
+
789
+ override async configure() {
790
+ if (this.isConfigured) return this
791
+ this.state.set('configured', true)
792
+ return this
793
+ }
794
+
795
+ override async start(options?: { port?: number }) {
796
+ if (this.isListening) return this
797
+ if (!this.isConfigured) await this.configure()
798
+ const port = options?.port || this.options.port || 3000
799
+ this.state.set('port', port)
800
+ this.state.set('listening', true)
801
+ return this
802
+ }
803
+
804
+ override async stop() {
805
+ if (this.isStopped) return this
806
+ this.state.set('listening', false)
807
+ this.state.set('stopped', true)
808
+ return this
809
+ }
810
+ }
811
+
812
+ export default {{PascalName}}`,
813
+ tutorial: `# Building a Server
814
+
815
+ 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.
816
+
817
+ When to build a server:
818
+ - You need to accept incoming connections (HTTP, WebSocket, custom protocol)
819
+ - You want lifecycle management, port handling, and observability for free
820
+ - You're wrapping a server library so the codebase uses \`container.server('name')\`
821
+
822
+ ## Imports
823
+
824
+ \`\`\`ts
825
+ import { z } from 'zod'
826
+ import { Server } from '@soederpop/luca'
827
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
828
+ import type { NodeContainer } from '@soederpop/luca'
829
+ import type { ServersInterface } from '@soederpop/luca'
830
+ \`\`\`
831
+
832
+ ## Schemas
833
+
834
+ \`\`\`ts
835
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({
836
+ // Add your state fields here.
837
+ // Example: connectionCount: z.number().default(0).describe('Active connections'),
838
+ })
839
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
840
+
841
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({
842
+ // Add constructor options here. port and host come from ServerOptionsSchema.
843
+ // Example: cors: z.boolean().default(true).describe('Enable CORS'),
844
+ })
845
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
846
+
847
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({
848
+ // Add your events here.
849
+ // Example: connection: z.tuple([z.string().describe('Client ID')]).describe('New client connected'),
850
+ })
851
+ \`\`\`
852
+
853
+ ## Class
854
+
855
+ Running \`luca introspect\` captures JSDoc blocks and Zod schemas and includes them in the description whenever somebody calls \`container.servers.describe('{{camelName}}')\` or \`luca describe {{camelName}}\`.
856
+
857
+ \`\`\`ts
858
+ /**
859
+ * {{description}}
860
+ *
861
+ * @example
862
+ * \`\`\`typescript
863
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
864
+ * await {{camelName}}.start()
865
+ * \`\`\`
866
+ *
867
+ * @extends Server
868
+ */
869
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
870
+ static override shortcut = 'servers.{{camelName}}' as const
871
+ static override stateSchema = {{PascalName}}StateSchema
872
+ static override optionsSchema = {{PascalName}}OptionsSchema
873
+ static override eventsSchema = {{PascalName}}EventsSchema
874
+ static { Server.register(this, '{{camelName}}') }
875
+
876
+ static override attach(container: NodeContainer & ServersInterface) {
877
+ return container
878
+ }
879
+
880
+ override async configure() {
881
+ if (this.isConfigured) return this
882
+ // Set up the underlying server here
883
+ this.state.set('configured', true)
884
+ return this
885
+ }
886
+
887
+ override async start(options?: { port?: number }) {
888
+ if (this.isListening) return this
889
+ if (!this.isConfigured) await this.configure()
890
+
891
+ const port = options?.port || this.options.port || 3000
892
+ // Start listening here
893
+ this.state.set('port', port)
894
+ this.state.set('listening', true)
895
+ return this
896
+ }
897
+
898
+ override async stop() {
899
+ if (this.isStopped) return this
900
+ // Clean up connections here
901
+ this.state.set('listening', false)
902
+ this.state.set('stopped', true)
903
+ return this
904
+ }
905
+ }
906
+ \`\`\`
907
+
908
+ ## Module Augmentation
909
+
910
+ \`\`\`ts
911
+ declare module '@soederpop/luca' {
912
+ interface AvailableServers {
913
+ {{camelName}}: typeof {{PascalName}}
914
+ }
915
+ }
916
+ \`\`\`
917
+
918
+ ## Registration
919
+
920
+ Registration happens inside the class body using a static block. The default export is just the class itself.
921
+
922
+ \`\`\`ts
923
+ // Inside the class:
924
+ static { Server.register(this, '{{camelName}}') }
925
+
926
+ // At module level:
927
+ export default {{PascalName}}
928
+ \`\`\`
929
+
930
+ ## Complete Example
931
+
932
+ \`\`\`ts
933
+ import { z } from 'zod'
934
+ import { Server } from '@soederpop/luca'
935
+ import { ServerStateSchema, ServerOptionsSchema, ServerEventsSchema } from '@soederpop/luca'
936
+ import type { NodeContainer } from '@soederpop/luca'
937
+ import type { ServersInterface } from '@soederpop/luca'
938
+
939
+ declare module '@soederpop/luca' {
940
+ interface AvailableServers {
941
+ {{camelName}}: typeof {{PascalName}}
942
+ }
943
+ }
944
+
945
+ export const {{PascalName}}StateSchema = ServerStateSchema.extend({})
946
+ export type {{PascalName}}State = z.infer<typeof {{PascalName}}StateSchema>
947
+
948
+ export const {{PascalName}}OptionsSchema = ServerOptionsSchema.extend({})
949
+ export type {{PascalName}}Options = z.infer<typeof {{PascalName}}OptionsSchema>
950
+
951
+ export const {{PascalName}}EventsSchema = ServerEventsSchema.extend({})
952
+
953
+ /**
954
+ * {{description}}
955
+ *
956
+ * @example
957
+ * \`\`\`typescript
958
+ * const {{camelName}} = container.server('{{camelName}}', { port: 3000 })
959
+ * await {{camelName}}.start()
960
+ * \`\`\`
961
+ *
962
+ * @extends Server
963
+ */
964
+ export class {{PascalName}} extends Server<{{PascalName}}State, {{PascalName}}Options> {
965
+ static override shortcut = 'servers.{{camelName}}' as const
966
+ static override stateSchema = {{PascalName}}StateSchema
967
+ static override optionsSchema = {{PascalName}}OptionsSchema
968
+ static override eventsSchema = {{PascalName}}EventsSchema
969
+ static { Server.register(this, '{{camelName}}') }
970
+
971
+ static override attach(container: NodeContainer & ServersInterface) {
972
+ return container
973
+ }
974
+
975
+ override async configure() {
976
+ if (this.isConfigured) return this
977
+ this.state.set('configured', true)
978
+ return this
979
+ }
980
+
981
+ override async start(options?: { port?: number }) {
982
+ if (this.isListening) return this
983
+ if (!this.isConfigured) await this.configure()
984
+ const port = options?.port || this.options.port || 3000
985
+ this.state.set('port', port)
986
+ this.state.set('listening', true)
987
+ return this
988
+ }
989
+
990
+ override async stop() {
991
+ if (this.isStopped) return this
992
+ this.state.set('listening', false)
993
+ this.state.set('stopped', true)
994
+ return this
995
+ }
996
+ }
997
+
998
+ export default {{PascalName}}
999
+ \`\`\`
1000
+
1001
+ ## Conventions
1002
+
1003
+ - **Lifecycle**: Implement \`configure()\`, \`start()\`, and \`stop()\`. Check guards (\`isConfigured\`, \`isListening\`, \`isStopped\`) at the top of each.
1004
+ - **Use \`afterInitialize()\`**: For any setup logic instead of overriding the constructor. Lifecycle methods (\`configure\`, \`start\`, \`stop\`) handle the server's runtime phases.
1005
+ - **State tracking**: Set \`configured\`, \`listening\`, \`stopped\`, and \`port\` on the state. This powers the introspection system.
1006
+ - **attach() is static**: It runs when the container first loads the server class. Use it for container-level setup if needed.
1007
+ - **Port from options**: Accept port via options schema and respect it in \`start()\`. Allow override via start options.
1008
+ - **JSDoc everything**: Every public method needs \`@param\`, \`@returns\`, \`@example\`. Run \`luca introspect\` after changes to update generated docs.
1009
+ `,
1010
+ },
1011
+ command: {
1012
+ sections: [
1013
+ { heading: "Imports", code: `import { z } from 'zod'
1014
+ import type { ContainerContext } from '@soederpop/luca'` },
1015
+ { heading: "Positional Arguments", code: `// luca {{kebabName}} ./src => options.target === './src'
1016
+ export const positionals = ['target']` },
1017
+ { heading: "Args Schema", code: `export const argsSchema = z.object({
1018
+ // Positional: first arg after command name (via positionals array above)
1019
+ // target: z.string().optional().describe('The target to operate on'),
1020
+
1021
+ // Flags: passed as --flag on the CLI
1022
+ // verbose: z.boolean().default(false).describe('Enable verbose output'),
1023
+ // output: z.string().optional().describe('Output file path'),
1024
+ })` },
1025
+ { heading: "Description", code: `export const description = '{{description}}'` },
1026
+ { heading: "Handler", code: `export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1027
+ const { container } = context
1028
+ const fs = container.feature('fs')
1029
+
1030
+ // options.target is set from the first positional arg (via positionals export)
1031
+ // options.verbose, options.output, etc. come from --flags
1032
+
1033
+ // Your implementation here
1034
+ }` },
1035
+ { heading: "Complete Example", code: `import { z } from 'zod'
1036
+ import type { ContainerContext } from '@soederpop/luca'
1037
+
1038
+ export const description = '{{description}}'
1039
+
1040
+ // Map positional args to named options: luca {{kebabName}} myTarget => options.target === 'myTarget'
1041
+ export const positionals = ['target']
1042
+
1043
+ export const argsSchema = z.object({
1044
+ target: z.string().optional().describe('The target to operate on'),
1045
+ })
1046
+
1047
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1048
+ const { container } = context
1049
+ const fs = container.feature('fs')
1050
+
1051
+ console.log('{{kebabName}} running...', options.target)
1052
+ }` },
1053
+ { heading: "Container Properties", code: `export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1054
+ const { container } = context
1055
+
1056
+ // Current working directory
1057
+ container.cwd // '/path/to/project'
1058
+
1059
+ // Path utilities (scoped to cwd)
1060
+ container.paths.resolve('src') // '/path/to/project/src'
1061
+ container.paths.join('a', 'b') // '/path/to/project/a/b'
1062
+ container.paths.relative('src') // 'src'
1063
+
1064
+ // Package manifest (parsed package.json)
1065
+ container.manifest.name // 'my-project'
1066
+ container.manifest.version // '1.0.0'
1067
+
1068
+ // Raw CLI arguments (from minimist) — prefer positionals export for positional args
1069
+ container.argv // { _: ['{{kebabName}}', ...], verbose: true, ... }
1070
+ }` }
1071
+ ],
1072
+ full: `import { z } from 'zod'
1073
+ import type { ContainerContext } from '@soederpop/luca'
1074
+
1075
+ export const description = '{{description}}'
1076
+
1077
+ // Map positional args to named options: luca {{kebabName}} myTarget => options.target === 'myTarget'
1078
+ export const positionals = ['target']
1079
+
1080
+ export const argsSchema = z.object({
1081
+ target: z.string().optional().describe('The target to operate on'),
1082
+ })
1083
+
1084
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1085
+ const { container } = context
1086
+ const fs = container.feature('fs')
1087
+
1088
+ console.log('{{kebabName}} running...', options.target)
1089
+ }`,
1090
+ tutorial: `# Building a Command
1091
+
1092
+ A command extends the \`luca\` CLI. Commands live in a project's \`commands/\` folder and are automatically discovered. They are Helper subclasses under the hood — the framework grafts your module exports into a Command class at runtime.
1093
+
1094
+ When to build a command:
1095
+ - You need a CLI task for a project (build scripts, generators, automation)
1096
+ - You want argument parsing, help text, and container access for free
1097
+ - The task should be runnable via \`luca yourCommand\`
1098
+
1099
+ ## Imports
1100
+
1101
+ \`\`\`ts
1102
+ import { z } from 'zod'
1103
+ import type { ContainerContext } from '@soederpop/luca'
1104
+ \`\`\`
1105
+
1106
+ ## Positional Arguments
1107
+
1108
+ Export a \`positionals\` array to map CLI positional args into named options fields. The first positional (\`_[0]\`) is always the command name — \`positionals\` maps \`_[1]\`, \`_[2]\`, etc.
1109
+
1110
+ \`\`\`ts
1111
+ // luca {{kebabName}} ./src => options.target === './src'
1112
+ export const positionals = ['target']
1113
+ \`\`\`
1114
+
1115
+ ## Args Schema
1116
+
1117
+ Define your command's arguments and flags with Zod. Each field becomes a \`--flag\` on the CLI. Fields named in \`positionals\` also accept positional args.
1118
+
1119
+ \`\`\`ts
1120
+ export const argsSchema = z.object({
1121
+ // Positional: first arg after command name (via positionals array above)
1122
+ // target: z.string().optional().describe('The target to operate on'),
1123
+
1124
+ // Flags: passed as --flag on the CLI
1125
+ // verbose: z.boolean().default(false).describe('Enable verbose output'),
1126
+ // output: z.string().optional().describe('Output file path'),
1127
+ })
1128
+ \`\`\`
1129
+
1130
+ ## Description
1131
+
1132
+ Export a description string for \`luca --help\` display:
1133
+
1134
+ \`\`\`ts
1135
+ export const description = '{{description}}'
1136
+ \`\`\`
1137
+
1138
+ ## Handler
1139
+
1140
+ Export a default async function. It receives parsed options and the container context. Use the container for all I/O. Positional args declared in the \`positionals\` export are available as named fields on \`options\`.
1141
+
1142
+ \`\`\`ts
1143
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1144
+ const { container } = context
1145
+ const fs = container.feature('fs')
1146
+
1147
+ // options.target is set from the first positional arg (via positionals export)
1148
+ // options.verbose, options.output, etc. come from --flags
1149
+
1150
+ // Your implementation here
1151
+ }
1152
+ \`\`\`
1153
+
1154
+ ## Complete Example
1155
+
1156
+ \`\`\`ts
1157
+ import { z } from 'zod'
1158
+ import type { ContainerContext } from '@soederpop/luca'
1159
+
1160
+ export const description = '{{description}}'
1161
+
1162
+ // Map positional args to named options: luca {{kebabName}} myTarget => options.target === 'myTarget'
1163
+ export const positionals = ['target']
1164
+
1165
+ export const argsSchema = z.object({
1166
+ target: z.string().optional().describe('The target to operate on'),
1167
+ })
1168
+
1169
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1170
+ const { container } = context
1171
+ const fs = container.feature('fs')
1172
+
1173
+ console.log('{{kebabName}} running...', options.target)
1174
+ }
1175
+ \`\`\`
1176
+
1177
+ ## Container Properties
1178
+
1179
+ The \`context.container\` object provides useful properties beyond features:
1180
+
1181
+ \`\`\`ts
1182
+ export default async function {{camelName}}(options: z.infer<typeof argsSchema>, context: ContainerContext) {
1183
+ const { container } = context
1184
+
1185
+ // Current working directory
1186
+ container.cwd // '/path/to/project'
1187
+
1188
+ // Path utilities (scoped to cwd)
1189
+ container.paths.resolve('src') // '/path/to/project/src'
1190
+ container.paths.join('a', 'b') // '/path/to/project/a/b'
1191
+ container.paths.relative('src') // 'src'
1192
+
1193
+ // Package manifest (parsed package.json)
1194
+ container.manifest.name // 'my-project'
1195
+ container.manifest.version // '1.0.0'
1196
+
1197
+ // Raw CLI arguments (from minimist) — prefer positionals export for positional args
1198
+ container.argv // { _: ['{{kebabName}}', ...], verbose: true, ... }
1199
+ }
1200
+ \`\`\`
1201
+
1202
+ ## Conventions
1203
+
1204
+ - **File location**: \`commands/{{kebabName}}.ts\` in the project root. The \`luca\` CLI discovers these automatically.
1205
+ - **Naming**: kebab-case for filename. \`luca {{kebabName}}\` maps to \`commands/{{kebabName}}.ts\`.
1206
+ - **Use the container**: Never import \`fs\`, \`path\`, \`child_process\` directly. Use \`container.feature('fs')\`, \`container.paths\`, \`container.feature('proc')\`.
1207
+ - **Positional args**: Export \`positionals = ['name1', 'name2']\` to map CLI positional args into named options fields. For raw access, use \`container.argv._\` where \`_[0]\` is the command name.
1208
+ - **Exit codes**: Return nothing for success. Throw for errors — the CLI catches and reports them.
1209
+ - **Help text**: Use \`.describe()\` on every schema field — it powers \`luca {{kebabName}} --help\`.
1210
+ `,
1211
+ },
1212
+ endpoint: {
1213
+ sections: [
1214
+ { heading: "Required Exports", code: `export const path = '/api/{{camelName}}'
1215
+ export const description = '{{description}}'
1216
+ export const tags = ['{{camelName}}']` },
1217
+ { heading: "Handler Functions", code: `export async function get(params: any, ctx: any) {
1218
+ const fs = ctx.container.feature('fs')
1219
+ // Your logic here
1220
+ return { message: 'ok' }
1221
+ }
1222
+
1223
+ export async function post(params: any, ctx: any) {
1224
+ // Create something
1225
+ return { created: true }
1226
+ }` },
1227
+ { heading: "Validation Schemas", code: `import { z } from 'zod'
1228
+
1229
+ export const getSchema = z.object({
1230
+ q: z.string().optional().describe('Search query'),
1231
+ limit: z.number().default(20).describe('Max results'),
1232
+ })
1233
+
1234
+ export const postSchema = z.object({
1235
+ title: z.string().min(1).describe('Item title'),
1236
+ body: z.string().min(1).describe('Item content'),
1237
+ })` },
1238
+ { heading: "Rate Limiting", code: `// Global rate limit for all methods
1239
+ export const rateLimit = { maxRequests: 100, windowSeconds: 60 }
1240
+
1241
+ // Per-method rate limit
1242
+ export const postRateLimit = { maxRequests: 10, windowSeconds: 1 }` },
1243
+ { heading: "Delete Handler", code: `// Use a local name, then re-export as \`delete\`
1244
+ const del = async (params: any, ctx: any) => {
1245
+ return { deleted: true }
1246
+ }
1247
+ export { del as delete }` },
1248
+ { heading: "Complete Example", code: `export const path = '/api/{{camelName}}'
1249
+ export const description = '{{description}}'
1250
+ export const tags = ['{{camelName}}']
1251
+
1252
+ export async function get(params: any, ctx: any) {
1253
+ return { items: [], total: 0 }
1254
+ }
1255
+
1256
+ export async function post(params: any, ctx: any) {
1257
+ return { item: { id: '1', ...params }, message: 'Created' }
1258
+ }
1259
+
1260
+ const del = async (params: any, ctx: any) => {
1261
+ const { id } = ctx.params
1262
+ return { message: \`Deleted \${id}\` }
1263
+ }
1264
+ export { del as delete }` },
1265
+ { heading: "Dynamic Route Example", code: `// endpoints/{{camelName}}/[id].ts
1266
+ export const path = '/api/{{camelName}}/:id'
1267
+ export const description = 'Get, update, or delete a specific item'
1268
+ export const tags = ['{{camelName}}']
1269
+
1270
+ export async function get(params: any, ctx: any) {
1271
+ const { id } = ctx.params
1272
+ return { item: { id } }
1273
+ }
1274
+
1275
+ export async function put(params: any, ctx: any) {
1276
+ const { id } = ctx.params
1277
+ return { item: { id, ...params }, message: 'Updated' }
1278
+ }
1279
+
1280
+ const del = async (params: any, ctx: any) => {
1281
+ const { id } = ctx.params
1282
+ return { message: \`Deleted \${id}\` }
1283
+ }
1284
+ export { del as delete }` }
1285
+ ],
1286
+ full: `export const path = '/api/{{camelName}}'
1287
+ export const description = '{{description}}'
1288
+ export const tags = ['{{camelName}}']
1289
+
1290
+ export async function get(params: any, ctx: any) {
1291
+ return { items: [], total: 0 }
1292
+ }
1293
+
1294
+ export async function post(params: any, ctx: any) {
1295
+ return { item: { id: '1', ...params }, message: 'Created' }
1296
+ }
1297
+
1298
+ const del = async (params: any, ctx: any) => {
1299
+ const { id } = ctx.params
1300
+ return { message: \`Deleted \${id}\` }
1301
+ }
1302
+ export { del as delete }`,
1303
+ tutorial: `# Building an Endpoint
1304
+
1305
+ 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.
1306
+
1307
+ When to build an endpoint:
1308
+ - You need a REST API route (GET, POST, PUT, PATCH, DELETE)
1309
+ - You want Zod validation, OpenAPI docs, and rate limiting for free
1310
+ - You're building an API that \`luca serve\` manages
1311
+
1312
+ ## File Location
1313
+
1314
+ Endpoints go in \`endpoints/\` at your project root:
1315
+ - \`endpoints/health.ts\` → \`/health\`
1316
+ - \`endpoints/posts.ts\` → \`/api/posts\`
1317
+ - \`endpoints/posts/[id].ts\` → \`/api/posts/:id\` (requires \`path\` export)
1318
+
1319
+ Run \`luca serve\` and they're automatically discovered and mounted.
1320
+
1321
+ ## Imports
1322
+
1323
+ Endpoints are lightweight — just exports and handler functions. No imports are required.
1324
+
1325
+ If your project has \`@soederpop/luca\` as an npm dependency, you can import \`z\` from \`zod\` and \`EndpointContext\` from \`@soederpop/luca\` for type safety. Otherwise, use \`any\` types — the framework handles validation and context injection for you.
1326
+
1327
+ Access framework capabilities through the \`ctx\` parameter:
1328
+ - \`ctx.container.feature('fs')\` for file operations
1329
+ - \`ctx.container.feature('yaml')\` for YAML parsing
1330
+ - \`ctx.container.feature('sqlite')\` for database access
1331
+
1332
+ ## Required Exports
1333
+
1334
+ Every endpoint MUST export a \`path\` string:
1335
+
1336
+ \`\`\`ts
1337
+ export const path = '/api/{{camelName}}'
1338
+ export const description = '{{description}}'
1339
+ export const tags = ['{{camelName}}']
1340
+ \`\`\`
1341
+
1342
+ ## Handler Functions
1343
+
1344
+ Export named functions for each HTTP method you support. Each receives validated parameters and a context object:
1345
+
1346
+ \`\`\`ts
1347
+ export async function get(params: any, ctx: any) {
1348
+ const fs = ctx.container.feature('fs')
1349
+ // Your logic here
1350
+ return { message: 'ok' }
1351
+ }
1352
+
1353
+ export async function post(params: any, ctx: any) {
1354
+ // Create something
1355
+ return { created: true }
1356
+ }
1357
+ \`\`\`
1358
+
1359
+ The \`EndpointContext\` gives you:
1360
+ - \`ctx.container\` — the luca container (access any feature, client, etc.)
1361
+ - \`ctx.request\` — Express request object
1362
+ - \`ctx.response\` — Express response object
1363
+ - \`ctx.query\` — parsed query string
1364
+ - \`ctx.body\` — parsed request body
1365
+ - \`ctx.params\` — URL parameters (\`:id\`, etc.)
1366
+
1367
+ Return any object — it's automatically JSON-serialized as the response.
1368
+
1369
+ ## Validation Schemas
1370
+
1371
+ If \`zod\` is available (via \`@soederpop/luca\` dependency or \`node_modules\`), export Zod schemas to validate parameters for each method. Name them \`{method}Schema\`:
1372
+
1373
+ \`\`\`ts
1374
+ import { z } from 'zod'
1375
+
1376
+ export const getSchema = z.object({
1377
+ q: z.string().optional().describe('Search query'),
1378
+ limit: z.number().default(20).describe('Max results'),
1379
+ })
1380
+
1381
+ export const postSchema = z.object({
1382
+ title: z.string().min(1).describe('Item title'),
1383
+ body: z.string().min(1).describe('Item content'),
1384
+ })
1385
+ \`\`\`
1386
+
1387
+ Invalid requests automatically return 400 with Zod error details. Schemas also feed the auto-generated OpenAPI spec. If zod is not available, skip schema exports — the endpoint still works, you just lose automatic validation.
1388
+
1389
+ ## Rate Limiting
1390
+
1391
+ Export rate limit config to protect endpoints:
1392
+
1393
+ \`\`\`ts
1394
+ // Global rate limit for all methods
1395
+ export const rateLimit = { maxRequests: 100, windowSeconds: 60 }
1396
+
1397
+ // Per-method rate limit
1398
+ export const postRateLimit = { maxRequests: 10, windowSeconds: 1 }
1399
+ \`\`\`
1400
+
1401
+ ## Delete Handler
1402
+
1403
+ \`delete\` is a reserved word in JS, so you can't use it as a function name directly. Use a named export alias:
1404
+
1405
+ \`\`\`ts
1406
+ // Use a local name, then re-export as \`delete\`
1407
+ const del = async (params: any, ctx: any) => {
1408
+ return { deleted: true }
1409
+ }
1410
+ export { del as delete }
1411
+ \`\`\`
1412
+
1413
+ You can also export \`deleteSchema\` and \`deleteRateLimit\` for validation and rate limiting on DELETE.
1414
+
1415
+ ## Complete Example
1416
+
1417
+ A CRUD endpoint for a resource (no external imports needed):
1418
+
1419
+ \`\`\`ts
1420
+ export const path = '/api/{{camelName}}'
1421
+ export const description = '{{description}}'
1422
+ export const tags = ['{{camelName}}']
1423
+
1424
+ export async function get(params: any, ctx: any) {
1425
+ return { items: [], total: 0 }
1426
+ }
1427
+
1428
+ export async function post(params: any, ctx: any) {
1429
+ return { item: { id: '1', ...params }, message: 'Created' }
1430
+ }
1431
+
1432
+ const del = async (params: any, ctx: any) => {
1433
+ const { id } = ctx.params
1434
+ return { message: \`Deleted \${id}\` }
1435
+ }
1436
+ export { del as delete }
1437
+ \`\`\`
1438
+
1439
+ ## Dynamic Route Example
1440
+
1441
+ For routes with URL parameters, create a nested file:
1442
+
1443
+ \`\`\`ts
1444
+ // endpoints/{{camelName}}/[id].ts
1445
+ export const path = '/api/{{camelName}}/:id'
1446
+ export const description = 'Get, update, or delete a specific item'
1447
+ export const tags = ['{{camelName}}']
1448
+
1449
+ export async function get(params: any, ctx: any) {
1450
+ const { id } = ctx.params
1451
+ return { item: { id } }
1452
+ }
1453
+
1454
+ export async function put(params: any, ctx: any) {
1455
+ const { id } = ctx.params
1456
+ return { item: { id, ...params }, message: 'Updated' }
1457
+ }
1458
+
1459
+ const del = async (params: any, ctx: any) => {
1460
+ const { id } = ctx.params
1461
+ return { message: \`Deleted \${id}\` }
1462
+ }
1463
+ export { del as delete }
1464
+ \`\`\`
1465
+
1466
+ ## Conventions
1467
+
1468
+ - **File = route**: The file path maps to the URL path. \`endpoints/users.ts\` serves \`/api/users\`.
1469
+ - **Export \`path\`**: Every endpoint must export a \`path\` string. This is the mounted route.
1470
+ - **Use Zod schemas**: Name them \`getSchema\`, \`postSchema\`, etc. They validate AND document.
1471
+ - **Use the container**: Access features via \`ctx.container.feature('fs')\`, not Node.js imports.
1472
+ - **Return objects**: Handler return values are JSON-serialized. Use \`ctx.response\` only for streaming or custom status codes.
1473
+ - **OpenAPI for free**: Your \`path\`, \`description\`, \`tags\`, and schemas automatically generate an OpenAPI spec at \`/openapi.json\`.
1474
+ `,
1475
+ },
1476
+ selector: {
1477
+ sections: [
1478
+ { heading: "Imports", code: `import { z } from 'zod'
1479
+ import type { ContainerContext } from '@soederpop/luca'` },
1480
+ { heading: "Args Schema", code: `export const argsSchema = z.object({
1481
+ // Add your input arguments here.
1482
+ // Example: field: z.string().optional().describe('Specific field to return'),
1483
+ })` },
1484
+ { heading: "Description", code: `export const description = '{{description}}'` },
1485
+ { heading: "Caching", code: `export function cacheKey(args: z.infer<typeof argsSchema>, context: ContainerContext) {
1486
+ return context.container.git.currentCommitSha
1487
+ }` },
1488
+ { heading: "Caching", code: `export const cacheable = false` },
1489
+ { heading: "Handler", code: `export async function run(args: z.infer<typeof argsSchema>, context: ContainerContext) {
1490
+ const { container } = context
1491
+ // Query and return your data
1492
+ return { /* your data */ }
1493
+ }` },
1494
+ { heading: "Complete Example", code: `import { z } from 'zod'
1495
+ import type { ContainerContext } from '@soederpop/luca'
1496
+
1497
+ export const description = '{{description}}'
1498
+
1499
+ export const argsSchema = z.object({})
1500
+
1501
+ export async function run(args: z.infer<typeof argsSchema>, context: ContainerContext) {
1502
+ const { container } = context
1503
+
1504
+ // Return your data here
1505
+ return {}
1506
+ }` }
1507
+ ],
1508
+ full: `import { z } from 'zod'
1509
+ import type { ContainerContext } from '@soederpop/luca'
1510
+
1511
+ export const description = '{{description}}'
1512
+
1513
+ export const argsSchema = z.object({})
1514
+
1515
+ export async function run(args: z.infer<typeof argsSchema>, context: ContainerContext) {
1516
+ const { container } = context
1517
+
1518
+ // Return your data here
1519
+ return {}
1520
+ }`,
1521
+ tutorial: `# Building a Selector
1522
+
1523
+ A selector returns data. Where commands perform actions, selectors query and return structured results with built-in caching. Selectors live in a project's \`selectors/\` folder and are automatically discovered.
1524
+
1525
+ When to build a selector:
1526
+ - You need to query project data (package info, file listings, config values)
1527
+ - The result benefits from caching (keyed by git SHA or custom key)
1528
+ - You want the data available via \`container.select('name')\` or \`luca select name\`
1529
+
1530
+ ## Imports
1531
+
1532
+ \`\`\`ts
1533
+ import { z } from 'zod'
1534
+ import type { ContainerContext } from '@soederpop/luca'
1535
+ \`\`\`
1536
+
1537
+ ## Args Schema
1538
+
1539
+ Define the selector's input arguments with Zod.
1540
+
1541
+ \`\`\`ts
1542
+ export const argsSchema = z.object({
1543
+ // Add your input arguments here.
1544
+ // Example: field: z.string().optional().describe('Specific field to return'),
1545
+ })
1546
+ \`\`\`
1547
+
1548
+ ## Description
1549
+
1550
+ Export a description string for discoverability:
1551
+
1552
+ \`\`\`ts
1553
+ export const description = '{{description}}'
1554
+ \`\`\`
1555
+
1556
+ ## Caching
1557
+
1558
+ Selectors cache by default. The default cache key is \`hashObject({ selectorName, args, gitSha })\` — same args + same commit = cache hit.
1559
+
1560
+ To customize the cache key:
1561
+
1562
+ \`\`\`ts
1563
+ export function cacheKey(args: z.infer<typeof argsSchema>, context: ContainerContext) {
1564
+ return context.container.git.currentCommitSha
1565
+ }
1566
+ \`\`\`
1567
+
1568
+ To disable caching:
1569
+
1570
+ \`\`\`ts
1571
+ export const cacheable = false
1572
+ \`\`\`
1573
+
1574
+ ## Handler
1575
+
1576
+ Export a \`run\` function that returns data. It receives parsed args and the container context.
1577
+
1578
+ \`\`\`ts
1579
+ export async function run(args: z.infer<typeof argsSchema>, context: ContainerContext) {
1580
+ const { container } = context
1581
+ // Query and return your data
1582
+ return { /* your data */ }
1583
+ }
1584
+ \`\`\`
1585
+
1586
+ ## Complete Example
1587
+
1588
+ \`\`\`ts
1589
+ import { z } from 'zod'
1590
+ import type { ContainerContext } from '@soederpop/luca'
1591
+
1592
+ export const description = '{{description}}'
1593
+
1594
+ export const argsSchema = z.object({})
1595
+
1596
+ export async function run(args: z.infer<typeof argsSchema>, context: ContainerContext) {
1597
+ const { container } = context
1598
+
1599
+ // Return your data here
1600
+ return {}
1601
+ }
1602
+ \`\`\`
1603
+
1604
+ ## Conventions
1605
+
1606
+ - **File location**: \`selectors/{{kebabName}}.ts\` in the project root. Discovered automatically.
1607
+ - **Naming**: kebab-case for filename. \`luca select {{kebabName}}\` maps to \`selectors/{{kebabName}}.ts\`.
1608
+ - **Use the container**: Never import \`fs\`, \`path\` directly. Use \`container.feature('fs')\`, \`container.paths\`.
1609
+ - **Return data**: The \`run\` function must return the data. It gets wrapped in \`{ data, cached, cacheKey }\` by the framework.
1610
+ - **Caching**: On by default. Override \`cacheKey()\` for custom invalidation, or set \`cacheable = false\` to skip.
1611
+ - **CLI**: \`luca select {{kebabName}}\` runs the selector and prints JSON. Use \`--json\` for data only, \`--no-cache\` to force fresh.
1612
+ `,
1613
+ }
1614
+ }
1615
+
1616
+ export const assistantFiles: Record<string, string> = {
1617
+ "CORE.md": `# Luca Assistant Example
1618
+
1619
+ You are currently an example / template "Assistant" provided by the Luca framework. ( You'll probably have no idea what that is, don't worry, it doesn't matter ).
1620
+
1621
+ You are what gets scaffolded when a user writes the \`luca scaffold assistant\` command.
1622
+
1623
+ In luca, an Assistant is backed by a folder which has a few components:
1624
+
1625
+ - CORE.md -- this is a markdown file that will get injected into the system prompt of a chat completion call
1626
+ - tools.ts -- this file is expected to export functions, and a schemas object whose keys are the names of the functions that get exported, and whose values are zod v4 schemas that describe the parameters
1627
+ - hooks.ts -- this file is expexted to export functions, whose names match the events emitted by the luca assistant helper
1628
+
1629
+ Currently, the user is chatting with you from the \`luca chat\` CLI.
1630
+
1631
+ You should tell them what each of these files is and how to edit them.
1632
+
1633
+ It is also important for them to know that the luca \`container\` is globally available for them in the context of the \`tools.ts\` and \`hooks.ts\` files.
1634
+
1635
+ `,
1636
+ "tools.ts": `import { z } from 'zod'
1637
+
1638
+ export const schemas = {
1639
+ README: z.object({}).describe('CALL THIS README FUNCTION AS EARLY AS POSSIBLE')
1640
+ }
1641
+
1642
+ export function README(options: z.infer<typeof schemas.README>) {
1643
+ return 'YO YO'
1644
+ }
1645
+
1646
+ `,
1647
+ "hooks.ts": `export function started() {
1648
+ console.log('Assistant started!')
1649
+ }
1650
+ `
1651
+ }
1652
+
1653
+ export const mcpReadme = `# Luca Development Guide
1654
+
1655
+ 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.
1656
+
1657
+ ## The Contract
1658
+
1659
+ 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.
1660
+
1661
+ ## Import Rule
1662
+
1663
+ All consumer code imports from \`@soederpop/luca\` only:
1664
+
1665
+ \`\`\`ts
1666
+ import { Feature, features, z, FeatureStateSchema, FeatureOptionsSchema } from '@soederpop/luca'
1667
+ import { Client, clients, RestClient, ClientStateSchema } from '@soederpop/luca/client'
1668
+ import { Server, servers, ServerStateSchema } from '@soederpop/luca'
1669
+ import { commands, CommandOptionsSchema } from '@soederpop/luca'
1670
+ \`\`\`
1671
+
1672
+ Never import from \`fs\`, \`path\`, \`crypto\`, or other Node builtins. Never import third-party packages in consumer code. If a container feature wraps the functionality, use it.
1673
+
1674
+ ## Zod v4
1675
+
1676
+ 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:
1677
+
1678
+ \`\`\`ts
1679
+ // Extending base schemas (options, state, events)
1680
+ export const MyStateSchema = FeatureStateSchema.extend({
1681
+ count: z.number().default(0).describe('Number of items'),
1682
+ label: z.string().optional().describe('Display label'),
1683
+ })
1684
+
1685
+ // Events use z.tuple() for listener arguments
1686
+ export const MyEventsSchema = FeatureEventsSchema.extend({
1687
+ itemAdded: z.tuple([z.string().describe('key'), z.number().describe('index')]),
1688
+ })
1689
+
1690
+ // Type inference
1691
+ export type MyState = z.infer<typeof MyStateSchema>
1692
+ \`\`\`
1693
+
1694
+ Zod v4 differences from v3 that matter:
1695
+ - \`z.string().check(...)\` replaces some v3 refinement patterns
1696
+ - \`.toJSONSchema()\` is built-in on any schema — no external library needed
1697
+ - Error customization uses \`z.string({ error: "message" })\` not \`.refine()\` for simple cases
1698
+ - \`z.interface()\` exists for recursive/lazy object types
1699
+ - Do NOT use \`z.nativeEnum()\` — use \`z.enum()\` instead
1700
+
1701
+ ## Dependencies
1702
+
1703
+ 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\`.
1704
+
1705
+ ## Discovering Capabilities
1706
+
1707
+ The container has registries for features, clients, servers, commands, and endpoints. **Do not guess** what is available — use your MCP tools to discover it:
1708
+
1709
+ 1. **\`find_capability\`** — Overview of all features, clients, and servers with descriptions. Start here.
1710
+ 2. **\`list_registry\`** — List all names in a specific registry (features, clients, servers, commands, endpoints).
1711
+ 3. **\`describe_helper\`** — Full API docs for a specific helper (methods, options, state, events). Call this before writing code that uses a helper.
1712
+ 4. **\`eval\`** — Once you know what you need, prototype calls in the live sandbox before writing them into files.
1713
+
1714
+ ## Mini Examples
1715
+
1716
+ ### Feature with composition
1717
+
1718
+ Features access other features via \`this.container.feature(...)\`:
1719
+
1720
+ \`\`\`ts
1721
+ import { z } from 'zod'
1722
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '@soederpop/luca'
1723
+ import { Feature, features } from '@soederpop/luca'
1724
+ import type { ContainerContext } from '@soederpop/luca'
1725
+
1726
+ export const ConfigStateSchema = FeatureStateSchema.extend({
1727
+ loaded: z.boolean().default(false).describe('Whether config has been loaded'),
1728
+ })
1729
+
1730
+ export const ConfigOptionsSchema = FeatureOptionsSchema.extend({
1731
+ filePath: z.string().default('config.json').describe('Path to config file'),
1732
+ })
1733
+
1734
+ export const ConfigEventsSchema = FeatureEventsSchema.extend({
1735
+ configLoaded: z.tuple([z.record(z.unknown()).describe('parsed config')]),
1736
+ })
1737
+
1738
+ export class Config extends Feature<z.infer<typeof ConfigStateSchema>, z.infer<typeof ConfigOptionsSchema>> {
1739
+ static override shortcut = 'features.config' as const
1740
+ static override stateSchema = ConfigStateSchema
1741
+ static override optionsSchema = ConfigOptionsSchema
1742
+ static override eventsSchema = ConfigEventsSchema
1743
+ static override description = 'Loads and manages project configuration'
1744
+
1745
+ /** Load and parse the config file */
1746
+ async load() {
1747
+ const fs = this.container.feature('fs')
1748
+ const raw = await fs.readFile(this.options.filePath)
1749
+ const data = JSON.parse(raw)
1750
+ this.state.set('loaded', true)
1751
+ this.emit('configLoaded', data)
1752
+ return data
1753
+ }
1754
+ }
1755
+
1756
+ declare module '@soederpop/luca' {
1757
+ interface AvailableFeatures { config: typeof Config }
1758
+ }
1759
+ export default features.register('config', Config)
1760
+ \`\`\`
1761
+
1762
+ ### Client with composition
1763
+
1764
+ Clients access features and other clients via \`this.container\`:
1765
+
1766
+ \`\`\`ts
1767
+ import { z } from 'zod'
1768
+ import { Client, clients, ClientStateSchema, ClientOptionsSchema, ClientEventsSchema } from '@soederpop/luca'
1769
+ import type { ContainerContext } from '@soederpop/luca'
1770
+
1771
+ export const GithubOptionsSchema = ClientOptionsSchema.extend({
1772
+ token: z.string().describe('GitHub personal access token'),
1773
+ owner: z.string().describe('Repository owner'),
1774
+ repo: z.string().describe('Repository name'),
1775
+ })
1776
+
1777
+ export const GithubEventsSchema = ClientEventsSchema.extend({
1778
+ issuesFetched: z.tuple([z.number().describe('count')]),
1779
+ })
1780
+
1781
+ export class GithubClient extends Client<z.infer<typeof ClientStateSchema>, z.infer<typeof GithubOptionsSchema>> {
1782
+ static override shortcut = 'clients.github' as const
1783
+ static override optionsSchema = GithubOptionsSchema
1784
+ static override eventsSchema = GithubEventsSchema
1785
+ static override description = 'GitHub API client using container REST client'
1786
+
1787
+ /** Fetch open issues */
1788
+ async issues() {
1789
+ const rest = this.container.client('rest')
1790
+ const res = await rest.get(\`https://api.github.com/repos/\${this.options.owner}/\${this.options.repo}/issues\`, {
1791
+ headers: { Authorization: \`token \${this.options.token}\` },
1792
+ })
1793
+ this.emit('issuesFetched', res.data.length)
1794
+ return res.data
1795
+ }
1796
+ }
1797
+
1798
+ declare module '@soederpop/luca' {
1799
+ interface AvailableClients { github: typeof GithubClient }
1800
+ }
1801
+ export default clients.register('github', GithubClient)
1802
+ \`\`\`
1803
+
1804
+ ## Workflow
1805
+
1806
+ 1. **\`find_capability\`** — Search what already exists before writing anything
1807
+ 2. **\`describe_helper\`** — Read the full API docs for the helper you need
1808
+ 3. **\`eval\`** — Prototype and test container API calls in the sandbox
1809
+ 4. **\`scaffold\`** — Generate correct boilerplate when building something new
1810
+ 5. **Write the file** — Using the patterns from the scaffold
1811
+
1812
+ ## Portability
1813
+
1814
+ 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.
1815
+ `