@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
package/src/cli/cli.ts ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bun
2
+ import container from '@soederpop/luca/agi'
3
+ import '@/commands/index.js'
4
+ import { homedir } from 'os'
5
+ import { join } from 'path'
6
+
7
+ async function main() {
8
+ // Load project-level CLI module (luca.cli.ts) for container customization
9
+ await loadCliModule()
10
+ // Discover project-local commands (commands/ or src/commands/)
11
+ await discoverProjectCommands()
12
+ // Discover user-level commands (~/.luca/commands/)
13
+ await discoverUserCommands()
14
+
15
+ const commandName = container.argv._[0] as string
16
+
17
+ if (commandName && container.commands.has(commandName)) {
18
+ const cmd = container.command(commandName as any)
19
+ await cmd.run()
20
+ } else if (commandName) {
21
+ // not a known command — treat as implicit `run`
22
+ container.argv._.splice(0, 0, 'run')
23
+ const cmd = container.command('run' as any)
24
+ await cmd.run()
25
+ } else {
26
+ container.argv._.splice(0, 0, 'help')
27
+ const cmd = container.command('help' as any)
28
+ await cmd.run()
29
+ }
30
+ }
31
+
32
+
33
+ async function loadCliModule() {
34
+ const modulePath = container.paths.resolve('luca.cli.ts')
35
+ if (!container.fs.exists(modulePath)) return
36
+
37
+ const mod = await import(modulePath)
38
+ const exports = mod.default || mod
39
+
40
+ if (typeof exports.main === 'function') {
41
+ await exports.main(container)
42
+ }
43
+
44
+ if (typeof exports.onStart === 'function') {
45
+ container.once('started', () => exports.onStart(container))
46
+ }
47
+ }
48
+
49
+ async function discoverProjectCommands() {
50
+ const { fs, paths } = container
51
+
52
+ for (const candidate of ['commands', 'src/commands']) {
53
+ const dir = paths.resolve(candidate)
54
+ if (fs.exists(dir)) {
55
+ await container.commands.discover({ directory: dir })
56
+ return
57
+ }
58
+ }
59
+ }
60
+
61
+ async function discoverUserCommands() {
62
+ const { fs } = container
63
+ const dir = join(homedir(), '.luca', 'commands')
64
+
65
+ if (fs.exists(dir)) {
66
+ await container.commands.discover({ directory: dir })
67
+ }
68
+ }
69
+
70
+ main()
package/src/client.ts ADDED
@@ -0,0 +1,461 @@
1
+ import { Helper } from "./helper.js";
2
+ import type { Container, ContainerContext } from "./container.js";
3
+ import axios, { type AxiosError, type AxiosInstance, type AxiosRequestConfig } from "axios";
4
+ import { Registry } from "./registry.js";
5
+ import { z } from 'zod'
6
+ import {
7
+ ClientStateSchema, ClientOptionsSchema, ClientEventsSchema,
8
+ WebSocketClientStateSchema, WebSocketClientOptionsSchema, WebSocketClientEventsSchema,
9
+ GraphClientOptionsSchema, GraphClientEventsSchema,
10
+ } from './schemas/base.js'
11
+
12
+ export type ClientOptions = z.infer<typeof ClientOptionsSchema>
13
+ export type ClientState = z.infer<typeof ClientStateSchema>
14
+ export type WebSocketClientState = z.infer<typeof WebSocketClientStateSchema>
15
+ export type WebSocketClientOptions = z.infer<typeof WebSocketClientOptionsSchema>
16
+ export type GraphClientOptions = z.infer<typeof GraphClientOptionsSchema>
17
+
18
+ export interface AvailableClients {
19
+ rest: typeof RestClient;
20
+ graph: typeof GraphClient;
21
+ websocket: typeof WebSocketClient;
22
+ }
23
+
24
+ export interface ClientsInterface {
25
+ clients: ClientsRegistry;
26
+ client<T extends keyof AvailableClients>(
27
+ key: T,
28
+ options?: ConstructorParameters<AvailableClients[T]>[0]
29
+ ): InstanceType<AvailableClients[T]>;
30
+ }
31
+
32
+ export class Client<
33
+ T extends ClientState = ClientState,
34
+ K extends ClientOptions = ClientOptions
35
+ > extends Helper<T, K> {
36
+ static override shortcut = "clients.base"
37
+ static override stateSchema = ClientStateSchema
38
+ static override optionsSchema = ClientOptionsSchema
39
+ static override eventsSchema = ClientEventsSchema
40
+
41
+ static attach(container: Container & ClientsInterface): any {
42
+ Object.assign(container, {
43
+ get clients() {
44
+ return clients;
45
+ },
46
+
47
+ client<T extends keyof AvailableClients>(
48
+ id: T,
49
+ options?: ConstructorParameters<AvailableClients[T]>[0]
50
+ ): InstanceType<AvailableClients[T]> {
51
+ const BaseClass = clients.lookup(
52
+ id as keyof AvailableClients
53
+ ) as AvailableClients[T];
54
+
55
+ return container.createHelperInstance({
56
+ cache: helperCache,
57
+ type: 'client',
58
+ id: String(id),
59
+ BaseClass,
60
+ options,
61
+ fallbackName: String(id),
62
+ }) as InstanceType<AvailableClients[T]>;
63
+ },
64
+ });
65
+
66
+ container.registerHelperType('clients', 'client');
67
+
68
+ return container;
69
+ }
70
+
71
+ constructor(options?: K, context?: ContainerContext) {
72
+ if (typeof context !== "object") {
73
+ throw new Error("Client must be instantiated with a context object");
74
+ }
75
+
76
+ super((options as K) || {}, context);
77
+
78
+ this.state.set("connected", false);
79
+ }
80
+
81
+ get baseURL() {
82
+ return this.options.baseURL || ''
83
+ }
84
+
85
+ override get options() {
86
+ return this._options as K;
87
+ }
88
+
89
+ configure(options?: any): this {
90
+ return this;
91
+ }
92
+
93
+ get isConnected() {
94
+ return !!this.state.get("connected");
95
+ }
96
+
97
+ async connect(): Promise<this> {
98
+ this.state.set("connected", true);
99
+ return this;
100
+ }
101
+ }
102
+
103
+ export class RestClient<
104
+ T extends ClientState = ClientState,
105
+ K extends ClientOptions = ClientOptions
106
+ > extends Client<T, K> {
107
+ axios!: AxiosInstance;
108
+
109
+ static override shortcut: string = "clients.rest"
110
+
111
+ static override attach(container: Container & ClientsInterface): any {
112
+ return container
113
+ }
114
+
115
+ constructor(options: K, context: ContainerContext) {
116
+ super(options, context);
117
+
118
+ this.axios = axios.create({
119
+ baseURL: this.baseURL,
120
+ });
121
+
122
+ if (this.useJSON) {
123
+ this.axios.defaults.headers.common = {
124
+ ...this.axios.defaults.headers.common,
125
+ "Content-Type": "application/json",
126
+ Accept: "application/json",
127
+ }
128
+ }
129
+ }
130
+
131
+ async beforeRequest() {
132
+ }
133
+
134
+ get useJSON() {
135
+ return !!this.options.json
136
+ }
137
+
138
+ override get baseURL() {
139
+ return this.options.baseURL || '/'
140
+ }
141
+
142
+ async patch(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
143
+ await this.beforeRequest();
144
+ return this.axios({
145
+ ...options,
146
+ method: "PATCH",
147
+ url,
148
+ data,
149
+ })
150
+ .then((r) => r.data)
151
+ .catch((e: any) => {
152
+ if (e.isAxiosError) {
153
+ return this.handleError(e);
154
+ } else {
155
+ throw e;
156
+ }
157
+ });
158
+ }
159
+
160
+ async put(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
161
+ await this.beforeRequest();
162
+ return this.axios({
163
+ ...options,
164
+ method: "PUT",
165
+ url,
166
+ data,
167
+ })
168
+ .then((r) => r.data)
169
+ .catch((e: any) => {
170
+ if (e.isAxiosError) {
171
+ return this.handleError(e);
172
+ } else {
173
+ throw e;
174
+ }
175
+ });
176
+ }
177
+
178
+ async post(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
179
+ await this.beforeRequest();
180
+ return this.axios({
181
+ ...options,
182
+ method: "POST",
183
+ url,
184
+ data,
185
+ })
186
+ .then((r) => r.data)
187
+ .catch((e: any) => {
188
+ if (e.isAxiosError) {
189
+ return this.handleError(e);
190
+ } else {
191
+ throw e;
192
+ }
193
+ });
194
+ }
195
+
196
+ async delete(url: string, params: any = {}, options: AxiosRequestConfig = {}) {
197
+ await this.beforeRequest();
198
+ return this.axios({
199
+ ...options,
200
+ method: "DELETE",
201
+ url,
202
+ params,
203
+ })
204
+ .then((r) => r.data)
205
+ .catch((e: any) => {
206
+ if (e.isAxiosError) {
207
+ return this.handleError(e);
208
+ } else {
209
+ throw e;
210
+ }
211
+ });
212
+ }
213
+
214
+
215
+ async get(url: string, params: any = {}, options: AxiosRequestConfig = {}) {
216
+ await this.beforeRequest()
217
+ return this.axios({
218
+ ...options,
219
+ method: "GET",
220
+ url,
221
+ params,
222
+ })
223
+ .then((r) => r.data)
224
+ .catch((e: any) => {
225
+ if (e.isAxiosError) {
226
+ return this.handleError(e);
227
+ } else {
228
+ throw e;
229
+ }
230
+ });
231
+ }
232
+
233
+ async handleError(error: AxiosError) {
234
+ this.emit('failure', error)
235
+ return error.toJSON();
236
+ }
237
+ }
238
+
239
+ /**
240
+ * GraphQL client that wraps RestClient with convenience methods for executing
241
+ * queries and mutations. Automatically handles the GraphQL request envelope
242
+ * (query/variables/operationName) and unwraps responses, extracting the `data`
243
+ * field and emitting events for GraphQL-level errors.
244
+ */
245
+ export class GraphClient<
246
+ T extends ClientState = ClientState,
247
+ K extends GraphClientOptions = GraphClientOptions
248
+ > extends RestClient<T, K> {
249
+ static override shortcut = "clients.graph" as const
250
+ static override optionsSchema = GraphClientOptionsSchema
251
+ static override eventsSchema = GraphClientEventsSchema
252
+
253
+ /** The GraphQL endpoint path. Defaults to '/graphql'. */
254
+ get endpoint() {
255
+ return (this.options as GraphClientOptions).endpoint || '/graphql'
256
+ }
257
+
258
+ /**
259
+ * Execute a GraphQL query and return the unwrapped data.
260
+ * @param query - The GraphQL query string
261
+ * @param variables - Optional variables for the query
262
+ * @param operationName - Optional operation name when the query contains multiple operations
263
+ */
264
+ async query<R = any>(query: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
265
+ return this.execute<R>(query, variables, operationName)
266
+ }
267
+
268
+ /**
269
+ * Execute a GraphQL mutation and return the unwrapped data.
270
+ * Semantically identical to query() but named for clarity when performing mutations.
271
+ * @param mutation - The GraphQL mutation string
272
+ * @param variables - Optional variables for the mutation
273
+ * @param operationName - Optional operation name when the mutation contains multiple operations
274
+ */
275
+ async mutate<R = any>(mutation: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
276
+ return this.execute<R>(mutation, variables, operationName)
277
+ }
278
+
279
+ /**
280
+ * Execute a GraphQL operation, unwrap the response, and handle errors.
281
+ * Posts to the configured endpoint with the standard GraphQL envelope.
282
+ * If the response contains GraphQL-level errors, emits both 'graphqlError'
283
+ * and 'failure' events before returning the data.
284
+ */
285
+ private async execute<R = any>(query: string, variables?: Record<string, any>, operationName?: string): Promise<R> {
286
+ const body: Record<string, any> = { query }
287
+ if (variables) body.variables = variables
288
+ if (operationName) body.operationName = operationName
289
+
290
+ const response = await this.post(this.endpoint, body)
291
+
292
+ if (response?.errors?.length) {
293
+ this.emit('graphqlError', response.errors)
294
+ this.emit('failure', response.errors)
295
+ }
296
+
297
+ return response?.data as R
298
+ }
299
+ }
300
+
301
+ /**
302
+ * WebSocket client that bridges raw WebSocket events to Luca's Helper event bus,
303
+ * providing a clean interface for sending/receiving messages, tracking connection
304
+ * state, and optional auto-reconnection with exponential backoff.
305
+ *
306
+ * Events emitted:
307
+ * - `open` — connection established
308
+ * - `message` — message received (JSON-parsed when possible)
309
+ * - `close` — connection closed (with code and reason)
310
+ * - `error` — connection error
311
+ * - `reconnecting` — attempting reconnection (with attempt number)
312
+ */
313
+ export class WebSocketClient<
314
+ T extends WebSocketClientState = WebSocketClientState,
315
+ K extends WebSocketClientOptions = WebSocketClientOptions
316
+ > extends Client<T, K> {
317
+ ws!: WebSocket
318
+ _intentionalClose: boolean
319
+
320
+ static override shortcut = "clients.websocket" as const
321
+ static override stateSchema = WebSocketClientStateSchema
322
+ static override optionsSchema = WebSocketClientOptionsSchema
323
+ static override eventsSchema = WebSocketClientEventsSchema
324
+
325
+ constructor(options?: K, context?: ContainerContext) {
326
+ super(options, context)
327
+ this._intentionalClose = false
328
+ }
329
+
330
+ override get initialState(): T {
331
+ return {
332
+ connected: false,
333
+ reconnectAttempts: 0,
334
+ } as T
335
+ }
336
+
337
+ /**
338
+ * Establish a WebSocket connection to the configured baseURL.
339
+ * Wires all raw WebSocket events (open, message, close, error) to the
340
+ * Helper event bus and updates connection state accordingly.
341
+ * Resolves once the connection is open; rejects on error.
342
+ */
343
+ override async connect(): Promise<this> {
344
+ if (this.isConnected) {
345
+ return this
346
+ }
347
+
348
+ const ws = this.ws = new WebSocket(this.baseURL)
349
+ const state = this.state as any
350
+
351
+ await new Promise<void>((resolve, reject) => {
352
+ ws.onopen = () => {
353
+ state.set('connected', true)
354
+ state.set('connectionError', undefined)
355
+ state.set('reconnectAttempts', 0)
356
+ this.emit('open')
357
+ resolve()
358
+ }
359
+
360
+ ws.onerror = (event: any) => {
361
+ state.set('connectionError', event)
362
+ this.emit('error', event)
363
+ reject(event)
364
+ }
365
+
366
+ ws.onmessage = (event: any) => {
367
+ let data = event?.data ?? event
368
+ try {
369
+ data = JSON.parse(data)
370
+ } catch {}
371
+ this.emit('message', data)
372
+ }
373
+
374
+ ws.onclose = (event: any) => {
375
+ state.set('connected', false)
376
+ this.emit('close', event?.code, event?.reason)
377
+ if (!this._intentionalClose) {
378
+ this.maybeReconnect()
379
+ }
380
+ this._intentionalClose = false
381
+ }
382
+ })
383
+
384
+ return this
385
+ }
386
+
387
+ /**
388
+ * Send data over the WebSocket connection. Automatically JSON-serializes
389
+ * the payload. If not currently connected, attempts to connect first.
390
+ * @param data - The data to send (will be JSON.stringify'd)
391
+ */
392
+ async send(data: any): Promise<void> {
393
+ if (!this.isConnected) {
394
+ await this.connect()
395
+ }
396
+
397
+ if (!this.ws) {
398
+ throw new Error('WebSocket instance not available')
399
+ }
400
+
401
+ this.ws.send(JSON.stringify(data))
402
+ }
403
+
404
+ /**
405
+ * Gracefully close the WebSocket connection. Suppresses auto-reconnect
406
+ * and updates connection state to disconnected.
407
+ */
408
+ async disconnect(): Promise<this> {
409
+ this._intentionalClose = true
410
+ if (this.ws) {
411
+ this.ws.close()
412
+ }
413
+ ;(this.state as any).set('connected', false)
414
+ return this
415
+ }
416
+
417
+ /** Whether the client is in an error state. */
418
+ get hasError() {
419
+ return !!(this.state as any).get('connectionError')
420
+ }
421
+
422
+ /**
423
+ * Attempt to reconnect if the reconnect option is enabled and we haven't
424
+ * exceeded maxReconnectAttempts. Uses exponential backoff capped at 30s.
425
+ */
426
+ private maybeReconnect() {
427
+ const opts = this.options as WebSocketClientOptions
428
+ if (!opts.reconnect) return
429
+
430
+ const state = this.state as any
431
+ const maxAttempts = opts.maxReconnectAttempts ?? Infinity
432
+ const baseInterval = opts.reconnectInterval ?? 1000
433
+ const attempts = ((state.get('reconnectAttempts') as number) ?? 0) + 1
434
+
435
+ if (attempts > maxAttempts) return
436
+
437
+ state.set('reconnectAttempts', attempts)
438
+ this.emit('reconnecting', attempts)
439
+
440
+ const delay = Math.min(baseInterval * Math.pow(2, attempts - 1), 30000)
441
+ setTimeout(() => {
442
+ this.connect().catch(() => {})
443
+ }, delay)
444
+ }
445
+ }
446
+
447
+
448
+ export class ClientsRegistry extends Registry<Client<any>> {
449
+ override scope = "clients"
450
+ override baseClass = Client
451
+ }
452
+
453
+ export const clients = new ClientsRegistry();
454
+
455
+ clients.register("rest", RestClient);
456
+ clients.register("graph", GraphClient);
457
+ clients.register("websocket", WebSocketClient);
458
+
459
+ export const helperCache = new Map();
460
+
461
+ export default Client;