@soederpop/luca 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/CLAUDE.md +71 -0
  2. package/README.md +78 -0
  3. package/bun.lock +2928 -0
  4. package/bunfig.toml +3 -0
  5. package/commands/audit-docs.ts +740 -0
  6. package/commands/build-scaffolds.ts +154 -0
  7. package/commands/generate-api-docs.ts +114 -0
  8. package/commands/update-introspection.ts +67 -0
  9. package/docs/CLI.md +335 -0
  10. package/docs/README.md +88 -0
  11. package/docs/TABLE-OF-CONTENTS.md +157 -0
  12. package/docs/apis/clients/elevenlabs.md +84 -0
  13. package/docs/apis/clients/graph.md +56 -0
  14. package/docs/apis/clients/openai.md +69 -0
  15. package/docs/apis/clients/rest.md +41 -0
  16. package/docs/apis/clients/websocket.md +107 -0
  17. package/docs/apis/features/agi/assistant.md +471 -0
  18. package/docs/apis/features/agi/assistants-manager.md +154 -0
  19. package/docs/apis/features/agi/claude-code.md +602 -0
  20. package/docs/apis/features/agi/conversation-history.md +352 -0
  21. package/docs/apis/features/agi/conversation.md +333 -0
  22. package/docs/apis/features/agi/docs-reader.md +121 -0
  23. package/docs/apis/features/agi/openai-codex.md +318 -0
  24. package/docs/apis/features/agi/openapi.md +138 -0
  25. package/docs/apis/features/agi/semantic-search.md +387 -0
  26. package/docs/apis/features/agi/skills-library.md +216 -0
  27. package/docs/apis/features/node/container-link.md +133 -0
  28. package/docs/apis/features/node/content-db.md +313 -0
  29. package/docs/apis/features/node/disk-cache.md +379 -0
  30. package/docs/apis/features/node/dns.md +651 -0
  31. package/docs/apis/features/node/docker.md +705 -0
  32. package/docs/apis/features/node/downloader.md +81 -0
  33. package/docs/apis/features/node/esbuild.md +59 -0
  34. package/docs/apis/features/node/file-manager.md +182 -0
  35. package/docs/apis/features/node/fs.md +581 -0
  36. package/docs/apis/features/node/git.md +330 -0
  37. package/docs/apis/features/node/google-auth.md +174 -0
  38. package/docs/apis/features/node/google-calendar.md +187 -0
  39. package/docs/apis/features/node/google-docs.md +151 -0
  40. package/docs/apis/features/node/google-drive.md +225 -0
  41. package/docs/apis/features/node/google-sheets.md +179 -0
  42. package/docs/apis/features/node/grep.md +290 -0
  43. package/docs/apis/features/node/helpers.md +135 -0
  44. package/docs/apis/features/node/ink.md +334 -0
  45. package/docs/apis/features/node/ipc-socket.md +260 -0
  46. package/docs/apis/features/node/json-tree.md +86 -0
  47. package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
  48. package/docs/apis/features/node/networking.md +281 -0
  49. package/docs/apis/features/node/nlp.md +133 -0
  50. package/docs/apis/features/node/opener.md +97 -0
  51. package/docs/apis/features/node/os.md +118 -0
  52. package/docs/apis/features/node/package-finder.md +402 -0
  53. package/docs/apis/features/node/postgres.md +212 -0
  54. package/docs/apis/features/node/proc.md +430 -0
  55. package/docs/apis/features/node/process-manager.md +210 -0
  56. package/docs/apis/features/node/python.md +278 -0
  57. package/docs/apis/features/node/repl.md +88 -0
  58. package/docs/apis/features/node/runpod.md +673 -0
  59. package/docs/apis/features/node/secure-shell.md +169 -0
  60. package/docs/apis/features/node/semantic-search.md +401 -0
  61. package/docs/apis/features/node/sqlite.md +211 -0
  62. package/docs/apis/features/node/telegram.md +254 -0
  63. package/docs/apis/features/node/tts.md +118 -0
  64. package/docs/apis/features/node/ui.md +703 -0
  65. package/docs/apis/features/node/vault.md +64 -0
  66. package/docs/apis/features/node/vm.md +84 -0
  67. package/docs/apis/features/node/window-manager.md +337 -0
  68. package/docs/apis/features/node/yaml-tree.md +85 -0
  69. package/docs/apis/features/node/yaml.md +176 -0
  70. package/docs/apis/features/web/asset-loader.md +47 -0
  71. package/docs/apis/features/web/container-link.md +133 -0
  72. package/docs/apis/features/web/esbuild.md +59 -0
  73. package/docs/apis/features/web/helpers.md +135 -0
  74. package/docs/apis/features/web/network.md +30 -0
  75. package/docs/apis/features/web/speech.md +55 -0
  76. package/docs/apis/features/web/vault.md +64 -0
  77. package/docs/apis/features/web/vm.md +84 -0
  78. package/docs/apis/features/web/voice.md +67 -0
  79. package/docs/apis/servers/express.md +127 -0
  80. package/docs/apis/servers/mcp.md +213 -0
  81. package/docs/apis/servers/websocket.md +99 -0
  82. package/docs/documentation-audit.md +134 -0
  83. package/docs/examples/content-db.md +77 -0
  84. package/docs/examples/disk-cache.md +83 -0
  85. package/docs/examples/docker.md +101 -0
  86. package/docs/examples/downloader.md +70 -0
  87. package/docs/examples/esbuild.md +80 -0
  88. package/docs/examples/file-manager.md +82 -0
  89. package/docs/examples/fs.md +83 -0
  90. package/docs/examples/git.md +85 -0
  91. package/docs/examples/google-auth.md +88 -0
  92. package/docs/examples/google-calendar.md +94 -0
  93. package/docs/examples/google-docs.md +82 -0
  94. package/docs/examples/google-drive.md +96 -0
  95. package/docs/examples/google-sheets.md +95 -0
  96. package/docs/examples/grep.md +85 -0
  97. package/docs/examples/ink-blocks.md +75 -0
  98. package/docs/examples/ink-renderer.md +41 -0
  99. package/docs/examples/ink.md +103 -0
  100. package/docs/examples/ipc-socket.md +103 -0
  101. package/docs/examples/json-tree.md +91 -0
  102. package/docs/examples/launcher-app-command-listener.md +120 -0
  103. package/docs/examples/networking.md +58 -0
  104. package/docs/examples/nlp.md +91 -0
  105. package/docs/examples/opener.md +78 -0
  106. package/docs/examples/os.md +72 -0
  107. package/docs/examples/package-finder.md +89 -0
  108. package/docs/examples/port-exposer.md +89 -0
  109. package/docs/examples/postgres.md +91 -0
  110. package/docs/examples/proc.md +81 -0
  111. package/docs/examples/process-manager.md +79 -0
  112. package/docs/examples/python.md +91 -0
  113. package/docs/examples/repl.md +93 -0
  114. package/docs/examples/runpod.md +119 -0
  115. package/docs/examples/secure-shell.md +92 -0
  116. package/docs/examples/sqlite.md +86 -0
  117. package/docs/examples/telegram.md +77 -0
  118. package/docs/examples/tts.md +86 -0
  119. package/docs/examples/ui.md +80 -0
  120. package/docs/examples/vault.md +70 -0
  121. package/docs/examples/vm.md +86 -0
  122. package/docs/examples/window-manager.md +125 -0
  123. package/docs/examples/yaml-tree.md +93 -0
  124. package/docs/examples/yaml.md +104 -0
  125. package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
  126. package/docs/ideas/container-use-api.md +9 -0
  127. package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
  128. package/docs/ideas/feature-stacks.md +22 -0
  129. package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
  130. package/docs/ideas/mcp-design.md +9 -0
  131. package/docs/ideas/web-container-debugging-feature.md +13 -0
  132. package/docs/introspection-audit.md +49 -0
  133. package/docs/introspection.md +154 -0
  134. package/docs/mcp/readme.md +162 -0
  135. package/docs/models.ts +38 -0
  136. package/docs/philosophy.md +85 -0
  137. package/docs/principles.md +7 -0
  138. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  139. package/docs/prompts/mcp-test-easy-command.md +27 -0
  140. package/docs/reports/assistant-bugs.md +38 -0
  141. package/docs/reports/attach-pattern-usage.md +18 -0
  142. package/docs/reports/code-audit-results.md +391 -0
  143. package/docs/reports/introspection-audit-tasks.md +378 -0
  144. package/docs/reports/luca-mcp-improvements.md +128 -0
  145. package/docs/scaffolds/client.md +140 -0
  146. package/docs/scaffolds/command.md +106 -0
  147. package/docs/scaffolds/endpoint.md +176 -0
  148. package/docs/scaffolds/feature.md +148 -0
  149. package/docs/scaffolds/server.md +187 -0
  150. package/docs/tasks/web-container-helper-discovery.md +71 -0
  151. package/docs/todos.md +1 -0
  152. package/docs/tutorials/01-getting-started.md +106 -0
  153. package/docs/tutorials/02-container.md +210 -0
  154. package/docs/tutorials/03-scripts.md +194 -0
  155. package/docs/tutorials/04-features-overview.md +196 -0
  156. package/docs/tutorials/05-state-and-events.md +171 -0
  157. package/docs/tutorials/06-servers.md +157 -0
  158. package/docs/tutorials/07-endpoints.md +198 -0
  159. package/docs/tutorials/08-commands.md +171 -0
  160. package/docs/tutorials/09-clients.md +162 -0
  161. package/docs/tutorials/10-creating-features.md +198 -0
  162. package/docs/tutorials/11-contentbase.md +191 -0
  163. package/docs/tutorials/12-assistants.md +215 -0
  164. package/docs/tutorials/13-introspection.md +147 -0
  165. package/docs/tutorials/14-type-system.md +174 -0
  166. package/docs/tutorials/15-project-patterns.md +222 -0
  167. package/docs/tutorials/16-google-features.md +534 -0
  168. package/docs/tutorials/17-tui-blocks.md +530 -0
  169. package/docs/tutorials/18-semantic-search.md +334 -0
  170. package/index.ts +1 -0
  171. package/luca.console.ts +9 -0
  172. package/main.py +6 -0
  173. package/package.json +154 -0
  174. package/pyproject.toml +7 -0
  175. package/scripts/animations/chrome-glitch.ts +55 -0
  176. package/scripts/animations/index.ts +16 -0
  177. package/scripts/animations/neon-pulse.ts +64 -0
  178. package/scripts/animations/types.ts +6 -0
  179. package/scripts/build-web.ts +28 -0
  180. package/scripts/examples/ask-luca-expert.ts +42 -0
  181. package/scripts/examples/assistant-questions.ts +12 -0
  182. package/scripts/examples/excalidraw-expert.ts +75 -0
  183. package/scripts/examples/expert-chat.ts +0 -0
  184. package/scripts/examples/file-manager.ts +14 -0
  185. package/scripts/examples/ideas.ts +12 -0
  186. package/scripts/examples/interactive-chat.ts +20 -0
  187. package/scripts/examples/openai-tool-calls.ts +113 -0
  188. package/scripts/examples/opening-a-web-browser.ts +5 -0
  189. package/scripts/examples/telegram-bot.ts +79 -0
  190. package/scripts/examples/telegram-ink-ui.ts +302 -0
  191. package/scripts/examples/using-assistant-with-mcp.ts +560 -0
  192. package/scripts/examples/using-claude-code.ts +10 -0
  193. package/scripts/examples/using-contentdb.ts +35 -0
  194. package/scripts/examples/using-conversations.ts +35 -0
  195. package/scripts/examples/using-disk-cache.ts +10 -0
  196. package/scripts/examples/using-docker-shell.ts +75 -0
  197. package/scripts/examples/using-elevenlabs.ts +25 -0
  198. package/scripts/examples/using-google-calendar.ts +57 -0
  199. package/scripts/examples/using-google-docs.ts +74 -0
  200. package/scripts/examples/using-google-drive.ts +74 -0
  201. package/scripts/examples/using-google-sheets.ts +89 -0
  202. package/scripts/examples/using-nlp.ts +55 -0
  203. package/scripts/examples/using-ollama.ts +10 -0
  204. package/scripts/examples/using-openai-codex.ts +23 -0
  205. package/scripts/examples/using-postgres.ts +55 -0
  206. package/scripts/examples/using-runpod.ts +32 -0
  207. package/scripts/examples/using-tts.ts +40 -0
  208. package/scripts/examples/vm-loading-esm-modules.ts +16 -0
  209. package/scripts/scaffold.ts +391 -0
  210. package/scripts/scratch.ts +15 -0
  211. package/scripts/test-command-listener.ts +123 -0
  212. package/scripts/test-window-manager-lifecycle.ts +86 -0
  213. package/scripts/test-window-manager.ts +43 -0
  214. package/scripts/update-introspection-data.ts +58 -0
  215. package/src/agi/README.md +14 -0
  216. package/src/agi/container.server.ts +114 -0
  217. package/src/agi/endpoints/ask.ts +60 -0
  218. package/src/agi/endpoints/conversations/[id].ts +45 -0
  219. package/src/agi/endpoints/conversations.ts +31 -0
  220. package/src/agi/endpoints/experts.ts +37 -0
  221. package/src/agi/features/assistant.ts +767 -0
  222. package/src/agi/features/assistants-manager.ts +260 -0
  223. package/src/agi/features/claude-code.ts +1111 -0
  224. package/src/agi/features/conversation-history.ts +497 -0
  225. package/src/agi/features/conversation.ts +799 -0
  226. package/src/agi/features/openai-codex.ts +631 -0
  227. package/src/agi/features/openapi.ts +438 -0
  228. package/src/agi/features/skills-library.ts +425 -0
  229. package/src/agi/index.ts +6 -0
  230. package/src/agi/lib/token-counter.ts +122 -0
  231. package/src/browser.ts +25 -0
  232. package/src/bus.ts +100 -0
  233. package/src/cli/cli.ts +70 -0
  234. package/src/client.ts +461 -0
  235. package/src/clients/civitai/index.ts +541 -0
  236. package/src/clients/client-template.ts +41 -0
  237. package/src/clients/comfyui/index.ts +597 -0
  238. package/src/clients/elevenlabs/index.ts +291 -0
  239. package/src/clients/openai/index.ts +451 -0
  240. package/src/clients/supabase/index.ts +366 -0
  241. package/src/command.ts +164 -0
  242. package/src/commands/chat.ts +182 -0
  243. package/src/commands/console.ts +192 -0
  244. package/src/commands/describe.ts +433 -0
  245. package/src/commands/eval.ts +116 -0
  246. package/src/commands/help.ts +214 -0
  247. package/src/commands/index.ts +14 -0
  248. package/src/commands/mcp.ts +64 -0
  249. package/src/commands/prompt.ts +807 -0
  250. package/src/commands/run.ts +257 -0
  251. package/src/commands/sandbox-mcp.ts +439 -0
  252. package/src/commands/scaffold.ts +79 -0
  253. package/src/commands/serve.ts +172 -0
  254. package/src/container.ts +781 -0
  255. package/src/endpoint.ts +340 -0
  256. package/src/feature.ts +75 -0
  257. package/src/hash-object.ts +97 -0
  258. package/src/helper.ts +543 -0
  259. package/src/introspection/generated.agi.ts +23388 -0
  260. package/src/introspection/generated.node.ts +18899 -0
  261. package/src/introspection/generated.web.ts +2021 -0
  262. package/src/introspection/index.ts +256 -0
  263. package/src/introspection/scan.ts +912 -0
  264. package/src/node/container.ts +354 -0
  265. package/src/node/feature.ts +13 -0
  266. package/src/node/features/container-link.ts +558 -0
  267. package/src/node/features/content-db.ts +475 -0
  268. package/src/node/features/disk-cache.ts +382 -0
  269. package/src/node/features/dns.ts +655 -0
  270. package/src/node/features/docker.ts +912 -0
  271. package/src/node/features/downloader.ts +92 -0
  272. package/src/node/features/esbuild.ts +68 -0
  273. package/src/node/features/file-manager.ts +357 -0
  274. package/src/node/features/fs.ts +534 -0
  275. package/src/node/features/git.ts +492 -0
  276. package/src/node/features/google-auth.ts +502 -0
  277. package/src/node/features/google-calendar.ts +300 -0
  278. package/src/node/features/google-docs.ts +404 -0
  279. package/src/node/features/google-drive.ts +339 -0
  280. package/src/node/features/google-sheets.ts +279 -0
  281. package/src/node/features/grep.ts +406 -0
  282. package/src/node/features/helpers.ts +374 -0
  283. package/src/node/features/ink.ts +490 -0
  284. package/src/node/features/ipc-socket.ts +459 -0
  285. package/src/node/features/json-tree.ts +188 -0
  286. package/src/node/features/launcher-app-command-listener.ts +388 -0
  287. package/src/node/features/networking.ts +925 -0
  288. package/src/node/features/nlp.ts +211 -0
  289. package/src/node/features/opener.ts +166 -0
  290. package/src/node/features/os.ts +157 -0
  291. package/src/node/features/package-finder.ts +539 -0
  292. package/src/node/features/port-exposer.ts +342 -0
  293. package/src/node/features/postgres.ts +273 -0
  294. package/src/node/features/proc.ts +502 -0
  295. package/src/node/features/process-manager.ts +542 -0
  296. package/src/node/features/python.ts +444 -0
  297. package/src/node/features/repl.ts +194 -0
  298. package/src/node/features/runpod.ts +802 -0
  299. package/src/node/features/secure-shell.ts +248 -0
  300. package/src/node/features/semantic-search.ts +924 -0
  301. package/src/node/features/sqlite.ts +289 -0
  302. package/src/node/features/telegram.ts +342 -0
  303. package/src/node/features/tts.ts +184 -0
  304. package/src/node/features/ui.ts +857 -0
  305. package/src/node/features/vault.ts +164 -0
  306. package/src/node/features/vm.ts +312 -0
  307. package/src/node/features/window-manager.ts +804 -0
  308. package/src/node/features/yaml-tree.ts +149 -0
  309. package/src/node/features/yaml.ts +132 -0
  310. package/src/node.ts +70 -0
  311. package/src/react/index.ts +175 -0
  312. package/src/registry.ts +199 -0
  313. package/src/scaffolds/generated.ts +1613 -0
  314. package/src/scaffolds/template.ts +37 -0
  315. package/src/schemas/base.ts +255 -0
  316. package/src/server.ts +135 -0
  317. package/src/servers/express.ts +209 -0
  318. package/src/servers/mcp.ts +805 -0
  319. package/src/servers/socket.ts +120 -0
  320. package/src/state.ts +101 -0
  321. package/src/web/clients/socket.ts +82 -0
  322. package/src/web/container.ts +74 -0
  323. package/src/web/extension.ts +30 -0
  324. package/src/web/feature.ts +12 -0
  325. package/src/web/features/asset-loader.ts +64 -0
  326. package/src/web/features/container-link.ts +385 -0
  327. package/src/web/features/esbuild.ts +79 -0
  328. package/src/web/features/helpers.ts +267 -0
  329. package/src/web/features/network.ts +61 -0
  330. package/src/web/features/speech.ts +87 -0
  331. package/src/web/features/vault.ts +189 -0
  332. package/src/web/features/vm.ts +78 -0
  333. package/src/web/features/voice-recognition.ts +129 -0
  334. package/src/web/shims/isomorphic-vm.ts +149 -0
  335. package/test/bus.test.ts +134 -0
  336. package/test/clients-servers.test.ts +216 -0
  337. package/test/container-link.test.ts +274 -0
  338. package/test/features.test.ts +160 -0
  339. package/test/integration.test.ts +787 -0
  340. package/test/node-container.test.ts +121 -0
  341. package/test/rate-limit.test.ts +272 -0
  342. package/test/semantic-search.test.ts +550 -0
  343. package/test/state.test.ts +121 -0
  344. package/test-integration/assistant.test.ts +138 -0
  345. package/test-integration/assistants-manager.test.ts +123 -0
  346. package/test-integration/claude-code.test.ts +98 -0
  347. package/test-integration/conversation-history.test.ts +205 -0
  348. package/test-integration/conversation.test.ts +137 -0
  349. package/test-integration/elevenlabs.test.ts +55 -0
  350. package/test-integration/google-services.test.ts +80 -0
  351. package/test-integration/helpers.ts +89 -0
  352. package/test-integration/openai-codex.test.ts +93 -0
  353. package/test-integration/runpod.test.ts +58 -0
  354. package/test-integration/server-endpoints.test.ts +97 -0
  355. package/test-integration/skills-library.test.ts +157 -0
  356. package/test-integration/telegram.test.ts +46 -0
  357. package/tsconfig.json +58 -0
  358. package/uv.lock +8 -0
@@ -0,0 +1,374 @@
1
+ import { z } from 'zod'
2
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
+ import { Feature, features } from '../feature.js'
4
+ import { Feature as UniversalFeature } from '../../feature.js'
5
+ import { Client, clients } from '../../client.js'
6
+ import { Server, servers } from '../../server.js'
7
+ import { commands } from '../../command.js'
8
+ import { endpoints } from '../../endpoint.js'
9
+ import type { Registry } from '../../registry.js'
10
+ import type { FileManager } from './file-manager.js'
11
+ import { resolve, parse } from 'path'
12
+
13
+ export const HelpersStateSchema = FeatureStateSchema.extend({
14
+ discovered: z.record(z.string(), z.boolean()).default({}).describe('Which registry types have been discovered'),
15
+ registered: z.array(z.string()).default([]).describe('Names of project-level helpers that were discovered (type.name)'),
16
+ })
17
+
18
+ export type HelpersState = z.infer<typeof HelpersStateSchema>
19
+
20
+ export const HelpersOptionsSchema = FeatureOptionsSchema.extend({
21
+ rootDir: z.string().optional().describe('Root directory to scan for helper folders. Defaults to container.cwd'),
22
+ })
23
+
24
+ export type HelpersOptions = z.infer<typeof HelpersOptionsSchema>
25
+
26
+ export const HelpersEventsSchema = FeatureEventsSchema.extend({
27
+ discovered: z.tuple([
28
+ z.string().describe('Registry type that was discovered'),
29
+ z.array(z.string()).describe('Names of newly registered helpers'),
30
+ ]).describe('Emitted after a registry type has been discovered'),
31
+ registered: z.tuple([
32
+ z.string().describe('Registry type'),
33
+ z.string().describe('Helper name'),
34
+ z.any().describe('The helper class or module'),
35
+ ]).describe('Emitted when a single helper is registered'),
36
+ })
37
+
38
+ type RegistryType = 'features' | 'clients' | 'servers' | 'commands' | 'endpoints'
39
+
40
+ const CLASS_BASED: RegistryType[] = ['features', 'clients', 'servers']
41
+
42
+ /**
43
+ * The Helpers feature is a unified gateway for discovering and registering
44
+ * project-level helpers from conventional folder locations.
45
+ *
46
+ * It scans known folder names (features/, clients/, servers/, commands/, endpoints/)
47
+ * and handles registration differently based on the helper type:
48
+ *
49
+ * - Class-based (features, clients, servers): Dynamic import, validate subclass, register
50
+ * - Config-based (commands, endpoints): Delegate to existing discovery mechanisms
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const helpers = container.feature('helpers', { enable: true })
55
+ *
56
+ * // Discover all helper types
57
+ * await helpers.discoverAll()
58
+ *
59
+ * // Discover a specific type
60
+ * await helpers.discover('features')
61
+ *
62
+ * // Unified view of all available helpers
63
+ * console.log(helpers.available)
64
+ * ```
65
+ */
66
+ export class Helpers extends Feature<HelpersState, HelpersOptions> {
67
+ static override shortcut = 'features.helpers' as const
68
+ static override description = 'Unified gateway for discovering and registering project-level helpers'
69
+ static override stateSchema = HelpersStateSchema
70
+ static override optionsSchema = HelpersOptionsSchema
71
+ static override eventsSchema = HelpersEventsSchema
72
+
73
+ /**
74
+ * Returns a mapping from registry type name to its registry singleton, base class, and conventional folder candidates.
75
+ */
76
+ private get registryMap(): Record<RegistryType, { registry: Registry<any>, baseClass: any, folders: string[] }> {
77
+ return {
78
+ features: { registry: this.container.features as any, baseClass: UniversalFeature, folders: ['features'] },
79
+ clients: { registry: clients, baseClass: Client, folders: ['clients'] },
80
+ servers: { registry: servers, baseClass: Server, folders: ['servers'] },
81
+ commands: { registry: commands, baseClass: null, folders: ['commands'] },
82
+ endpoints: { registry: endpoints, baseClass: null, folders: ['endpoints'] },
83
+ }
84
+ }
85
+
86
+ /** The root directory to scan for helper folders. */
87
+ get rootDir(): string {
88
+ return this.options.rootDir || this.container.cwd
89
+ }
90
+
91
+ /**
92
+ * Returns a unified view of all available helpers across all registries.
93
+ * Each key is a registry type, each value is the list of helper names in that registry.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * container.helpers.available
98
+ * // { features: ['fs', 'git', ...], clients: ['rest', 'websocket'], ... }
99
+ * ```
100
+ */
101
+ get available(): Record<string, string[]> {
102
+ const result: Record<string, string[]> = {}
103
+ for (const [type, { registry }] of Object.entries(this.registryMap)) {
104
+ result[type] = registry.available
105
+ }
106
+ return result
107
+ }
108
+
109
+ /**
110
+ * Ensures the fileManager feature is started before using it for discovery.
111
+ *
112
+ * @returns The started fileManager instance
113
+ */
114
+ private async ensureFileManager(): Promise<FileManager> {
115
+ const fm = this.container.feature('fileManager', { enable: true }) as unknown as FileManager
116
+ if (!fm.isStarted) {
117
+ await fm.start()
118
+ }
119
+ return fm
120
+ }
121
+
122
+ /**
123
+ * Resolves which conventional folder path exists for a given registry type.
124
+ * Tries each candidate folder in order and returns the first one that exists.
125
+ *
126
+ * @param type - The registry type to resolve the folder for
127
+ * @returns Absolute path to the folder, or null if none exist
128
+ */
129
+ private resolveFolderPath(type: RegistryType): string | null {
130
+ const { folders } = this.registryMap[type]
131
+ const { fs } = this.container
132
+
133
+ for (const candidate of folders) {
134
+ const dir = resolve(this.rootDir, candidate)
135
+ if (fs.exists(dir)) {
136
+ return dir
137
+ }
138
+ }
139
+
140
+ return null
141
+ }
142
+
143
+ /**
144
+ * Discover and register project-level helpers of the given type.
145
+ *
146
+ * For class-based types (features, clients, servers), scans the matching
147
+ * directory for .ts files, dynamically imports each, validates the default
148
+ * export is a subclass of the registry's base class, and registers it.
149
+ *
150
+ * For config-based types (commands, endpoints), delegates to existing discovery mechanisms.
151
+ *
152
+ * @param type - Which type of helpers to discover
153
+ * @param options - Optional overrides
154
+ * @param options.directory - Override the directory to scan
155
+ * @returns Names of helpers that were discovered and registered
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const names = await container.helpers.discover('features')
160
+ * console.log(names) // ['myCustomFeature']
161
+ * ```
162
+ */
163
+ async discover(type: RegistryType, options: { directory?: string } = {}): Promise<string[]> {
164
+ const discovered = this.state.get('discovered') || {}
165
+
166
+ if (discovered[type]) {
167
+ return []
168
+ }
169
+
170
+ const dir = options.directory || this.resolveFolderPath(type)
171
+
172
+ if (!dir) {
173
+ this.state.set('discovered', { ...discovered, [type]: true })
174
+ return []
175
+ }
176
+
177
+ let names: string[]
178
+
179
+ if (CLASS_BASED.includes(type)) {
180
+ names = await this.discoverClassBased(type, dir)
181
+ } else {
182
+ names = await this.discoverConfigBased(type, dir)
183
+ }
184
+
185
+ this.state.set('discovered', { ...this.state.get('discovered'), [type]: true })
186
+
187
+ const existing = this.state.get('registered') || []
188
+ this.state.set('registered', [...existing, ...names.map(n => `${type}.${n}`)])
189
+
190
+ this.emit('discovered' as any, type, names)
191
+
192
+ return names
193
+ }
194
+
195
+ /**
196
+ * Discover all helper types from their conventional folder locations.
197
+ *
198
+ * @returns Map of registry type to discovered helper names
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * const results = await container.helpers.discoverAll()
203
+ * // { features: ['myFeature'], clients: [], servers: [], commands: ['deploy'], endpoints: [] }
204
+ * ```
205
+ */
206
+ async discoverAll(): Promise<Record<string, string[]>> {
207
+ const results: Record<string, string[]> = {}
208
+
209
+ for (const type of ['features', 'clients', 'servers', 'commands', 'endpoints'] as RegistryType[]) {
210
+ results[type] = await this.discover(type)
211
+ }
212
+
213
+ return results
214
+ }
215
+
216
+ /**
217
+ * Look up a helper class by type and name.
218
+ *
219
+ * @param type - The registry type (features, clients, servers, commands, endpoints)
220
+ * @param name - The helper name within that registry
221
+ * @returns The helper constructor
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * const FsClass = container.helpers.lookup('features', 'fs')
226
+ * ```
227
+ */
228
+ lookup(type: RegistryType, name: string): any {
229
+ const { registry } = this.registryMap[type]
230
+ return registry.lookup(name)
231
+ }
232
+
233
+ /**
234
+ * Get the introspection description for a specific helper.
235
+ *
236
+ * @param type - The registry type
237
+ * @param name - The helper name
238
+ * @returns Markdown description of the helper's interface
239
+ */
240
+ describe(type: RegistryType, name: string): string {
241
+ const { registry } = this.registryMap[type]
242
+ return registry.describe(name)
243
+ }
244
+
245
+ /**
246
+ * Discovers class-based helpers (features, clients, servers) from a directory.
247
+ * Uses fileManager for fast file matching.
248
+ */
249
+ private async discoverClassBased(type: RegistryType, dir: string): Promise<string[]> {
250
+ const { registry, baseClass } = this.registryMap[type]
251
+ const fm = await this.ensureFileManager()
252
+ const discovered: string[] = []
253
+
254
+ const tests = [`${type}/*/*.ts`, `${type}/*.ts`]
255
+ const files = fm.match(tests)
256
+
257
+ for (const file of files) {
258
+ const absPath = resolve(this.rootDir, file)
259
+ const { name: fileName } = parse(absPath)
260
+
261
+ if (fileName.includes('.test.') || fileName.includes('.spec.')) {
262
+ continue
263
+ }
264
+
265
+ try {
266
+ const mod = await import(absPath)
267
+ const ExportedClass = mod.default || mod
268
+
269
+ if (typeof ExportedClass !== 'function') {
270
+ continue
271
+ }
272
+
273
+ if (!this.isSubclassOf(ExportedClass, baseClass)) {
274
+ continue
275
+ }
276
+
277
+ const shortcut = ExportedClass.shortcut as string | undefined
278
+ const registryName = shortcut
279
+ ? shortcut.replace(`${type}.`, '')
280
+ : this.fileNameToRegistryName(fileName)
281
+
282
+ discovered.push(registryName)
283
+
284
+ if (!registry.has(registryName)) {
285
+ registry.register(registryName, ExportedClass)
286
+ // this is only if they didn't export it by default
287
+ this.emit('registered' as any, type, registryName, ExportedClass)
288
+ }
289
+
290
+ } catch (err: any) {
291
+ if (err.message?.includes('name collision')) {
292
+ throw err
293
+ }
294
+ console.warn(`Helpers gateway: failed to load ${type} from ${absPath}: ${err.message}`)
295
+ }
296
+ }
297
+
298
+ return discovered
299
+ }
300
+
301
+ /**
302
+ * Discovers config-based helpers (commands, endpoints) by delegating
303
+ * to existing discovery mechanisms.
304
+ */
305
+ private async discoverConfigBased(type: RegistryType, dir: string): Promise<string[]> {
306
+ const { registry } = this.registryMap[type]
307
+ const beforeNames = new Set(registry.available)
308
+
309
+ if (type === 'commands') {
310
+ await commands.discover({ directory: dir })
311
+ } else if (type === 'endpoints') {
312
+ await this.discoverEndpoints(dir)
313
+ }
314
+
315
+ const afterNames = new Set(registry.available)
316
+ return [...afterNames].filter(n => !beforeNames.has(n))
317
+ }
318
+
319
+ /**
320
+ * Discovers endpoints from a directory, registering them for discoverability.
321
+ * Actual mounting to an express server is handled separately by ExpressServer.useEndpoints().
322
+ */
323
+ private async discoverEndpoints(dir: string): Promise<void> {
324
+ const { Glob } = globalThis.Bun || (await import('bun'))
325
+ const glob = new Glob('**/*.ts')
326
+
327
+ for await (const file of glob.scan({ cwd: dir, absolute: true })) {
328
+ try {
329
+ const mod = await import(file)
330
+ const endpointModule = mod.default || mod
331
+
332
+ if (endpointModule.path && typeof endpointModule.path === 'string') {
333
+ const name = endpointModule.path.replace(/^\//, '').replace(/\//g, '_') || parse(file).name
334
+
335
+ if (!endpoints.has(name)) {
336
+ // Import the module so it's available, but don't mount it to a server
337
+ // The express server's useEndpoints() handles the actual mounting
338
+ }
339
+ }
340
+ } catch (err: any) {
341
+ console.warn(`Helpers gateway: failed to load endpoint from ${file}: ${err.message}`)
342
+ }
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Check if a class is a subclass of a given base class by walking the prototype chain.
348
+ * Uses identity comparison first, then falls back to name comparison to handle
349
+ * cross-module boundaries (e.g. compiled binary vs dynamically imported modules
350
+ * that resolve to separate module instances of the same class).
351
+ */
352
+ private isSubclassOf(candidate: any, base: any): boolean {
353
+ if (!candidate || !base) return false
354
+ if (candidate === base) return true
355
+
356
+ let proto = Object.getPrototypeOf(candidate)
357
+ while (proto) {
358
+ if (proto === base || (base.name && proto.name === base.name)) return true
359
+ proto = Object.getPrototypeOf(proto)
360
+ }
361
+ return false
362
+ }
363
+
364
+ /**
365
+ * Convert a kebab-case or snake_case filename to a camelCase registry name.
366
+ */
367
+ private fileNameToRegistryName(fileName: string): string {
368
+ return fileName
369
+ .replace(/[-_](.)/g, (_, c: string) => c.toUpperCase())
370
+ .replace(/^(.)/, (_, c: string) => c.toLowerCase())
371
+ }
372
+ }
373
+
374
+ export default features.register('helpers', Helpers)