@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,267 @@
1
+ import { z } from 'zod'
2
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
+ import { Feature, features } from '../feature.js'
4
+ import { Client } from '../../client.js'
5
+ import type { Registry } from '../../registry.js'
6
+ import type { AssetLoader } from './asset-loader.js'
7
+
8
+ export const HelpersStateSchema = FeatureStateSchema.extend({
9
+ discovered: z.record(z.string(), z.boolean()).default({}).describe('Which registry types have been discovered'),
10
+ registered: z.array(z.string()).default([]).describe('Names of project-level helpers that were discovered (type.name)'),
11
+ manifestLoaded: z.boolean().default(false).describe('Whether the manifest has been fetched'),
12
+ })
13
+
14
+ export type HelpersState = z.infer<typeof HelpersStateSchema>
15
+
16
+ export const HelpersOptionsSchema = FeatureOptionsSchema.extend({
17
+ manifestURL: z.string().optional().describe('URL to fetch the helpers manifest from. Defaults to /.well-known/luca.manifest.json'),
18
+ })
19
+
20
+ export type HelpersOptions = z.infer<typeof HelpersOptionsSchema>
21
+
22
+ export const HelpersEventsSchema = FeatureEventsSchema.extend({
23
+ discovered: z.tuple([
24
+ z.string().describe('Registry type that was discovered'),
25
+ z.array(z.string()).describe('Names of newly registered helpers'),
26
+ ]).describe('Emitted after a registry type has been discovered'),
27
+ registered: z.tuple([
28
+ z.string().describe('Registry type'),
29
+ z.string().describe('Helper name'),
30
+ ]).describe('Emitted when a single helper is registered'),
31
+ manifestLoaded: z.tuple([
32
+ z.any().describe('The parsed manifest object'),
33
+ ]).describe('Emitted when the manifest is successfully fetched'),
34
+ manifestError: z.tuple([
35
+ z.any().describe('The error that occurred'),
36
+ ]).describe('Emitted when the manifest fetch fails'),
37
+ })
38
+
39
+ type RegistryType = 'features' | 'clients'
40
+
41
+ interface ManifestEntry {
42
+ id: string
43
+ description?: string
44
+ url: string
45
+ }
46
+
47
+ interface Manifest {
48
+ features?: Record<string, ManifestEntry>
49
+ clients?: Record<string, ManifestEntry>
50
+ }
51
+
52
+ /**
53
+ * The Helpers feature discovers and loads project-level helpers from a JSON manifest
54
+ * served over HTTP. Scripts are injected via AssetLoader and self-register into
55
+ * the container's registries.
56
+ *
57
+ * This is the web equivalent of the node Helpers feature, which scans the filesystem.
58
+ * Instead of filesystem scanning, this feature fetches a manifest from a well-known URL
59
+ * and uses AssetLoader.loadScript() to inject each helper's script tag.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const helpers = container.feature('helpers', { enable: true })
64
+ *
65
+ * // Discover all helper types from the manifest
66
+ * await helpers.discoverAll()
67
+ *
68
+ * // Discover a specific type
69
+ * await helpers.discover('features')
70
+ *
71
+ * // Unified view of all available helpers
72
+ * console.log(helpers.available)
73
+ * ```
74
+ */
75
+ export class Helpers extends Feature<HelpersState, HelpersOptions> {
76
+ static override shortcut = 'features.helpers' as const
77
+ static override description = 'Unified gateway for discovering and registering project-level helpers via HTTP manifest'
78
+ static override stateSchema = HelpersStateSchema
79
+ static override optionsSchema = HelpersOptionsSchema
80
+ static override eventsSchema = HelpersEventsSchema
81
+
82
+ private _manifest: Manifest | null = null
83
+
84
+ private get registryMap(): Record<RegistryType, { registry: Registry<any> }> {
85
+ return {
86
+ features: { registry: this.container.features as any },
87
+ clients: { registry: (this.container as any).clients as Registry<any> },
88
+ }
89
+ }
90
+
91
+ /** The URL to fetch the helpers manifest from. */
92
+ get manifestURL(): string {
93
+ return this.options.manifestURL || '/.well-known/luca.manifest.json'
94
+ }
95
+
96
+ /**
97
+ * Set a new manifest URL. Invalidates any cached manifest.
98
+ *
99
+ * @param url - The new URL to fetch the manifest from
100
+ */
101
+ setManifestURL(url: string) {
102
+ this.options.manifestURL = url
103
+ this._manifest = null
104
+ this.state.set('manifestLoaded', false)
105
+ }
106
+
107
+ /**
108
+ * Returns a unified view of all available helpers across all registries.
109
+ * Each key is a registry type, each value is the list of helper names in that registry.
110
+ */
111
+ get available(): Record<string, string[]> {
112
+ const result: Record<string, string[]> = {}
113
+ for (const [type, { registry }] of Object.entries(this.registryMap)) {
114
+ result[type] = registry.available
115
+ }
116
+ return result
117
+ }
118
+
119
+ /**
120
+ * Fetch and cache the manifest JSON. Returns cached version on subsequent calls
121
+ * unless invalidated by setManifestURL().
122
+ */
123
+ private async fetchManifest(): Promise<Manifest> {
124
+ if (this._manifest) {
125
+ return this._manifest
126
+ }
127
+
128
+ try {
129
+ const response = await fetch(this.manifestURL)
130
+
131
+ if (!response.ok) {
132
+ const err = new Error(`Manifest fetch failed: ${response.status} ${response.statusText}`)
133
+ this.emit('manifestError' as any, err)
134
+ return {}
135
+ }
136
+
137
+ const manifest: Manifest = await response.json()
138
+ this._manifest = manifest
139
+ this.state.set('manifestLoaded', true)
140
+ this.emit('manifestLoaded' as any, manifest)
141
+ return manifest
142
+ } catch (err: any) {
143
+ this.emit('manifestError' as any, err)
144
+ return {}
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Get the AssetLoader instance from the container.
150
+ */
151
+ private get assetLoader(): AssetLoader {
152
+ return this.container.feature('assetLoader') as unknown as AssetLoader
153
+ }
154
+
155
+ /**
156
+ * Discover and register helpers of the given type from the manifest.
157
+ *
158
+ * Fetches the manifest, then for each entry of the requested type,
159
+ * loads the script via AssetLoader and checks what got newly registered.
160
+ *
161
+ * @param type - Which type of helpers to discover ('features' or 'clients')
162
+ * @returns Names of helpers that were discovered and registered
163
+ */
164
+ async discover(type: RegistryType): Promise<string[]> {
165
+ const discovered = this.state.get('discovered') || {}
166
+
167
+ if (discovered[type]) {
168
+ return []
169
+ }
170
+
171
+ const manifest = await this.fetchManifest()
172
+ const entries = manifest[type] || {}
173
+ const { registry } = this.registryMap[type]
174
+ const newNames: string[] = []
175
+
176
+ for (const [name, entry] of Object.entries(entries)) {
177
+ const beforeNames = new Set(registry.available)
178
+
179
+ try {
180
+ await this.assetLoader.loadScript(entry.url)
181
+ } catch (err: any) {
182
+ console.warn(`Helpers: failed to load ${type}/${name} from ${entry.url}: ${err.message}`)
183
+ continue
184
+ }
185
+
186
+ const afterNames = registry.available
187
+ const added = afterNames.filter((n: string) => !beforeNames.has(n))
188
+
189
+ if (added.length > 0) {
190
+ for (const addedName of added) {
191
+ newNames.push(addedName)
192
+ this.emit('registered' as any, type, addedName)
193
+ }
194
+ } else if (registry.has(name)) {
195
+ // Script may have already been registered under the expected name
196
+ if (!beforeNames.has(name)) {
197
+ newNames.push(name)
198
+ this.emit('registered' as any, type, name)
199
+ }
200
+ }
201
+ }
202
+
203
+ this.state.set('discovered', { ...this.state.get('discovered'), [type]: true })
204
+
205
+ const existing = this.state.get('registered') || []
206
+ this.state.set('registered', [...existing, ...newNames.map(n => `${type}.${n}`)])
207
+
208
+ this.emit('discovered' as any, type, newNames)
209
+
210
+ return newNames
211
+ }
212
+
213
+ /**
214
+ * Discover all helper types from the manifest.
215
+ *
216
+ * @returns Map of registry type to discovered helper names
217
+ */
218
+ async discoverAll(): Promise<Record<string, string[]>> {
219
+ const results: Record<string, string[]> = {}
220
+
221
+ for (const type of ['features', 'clients'] as RegistryType[]) {
222
+ results[type] = await this.discover(type)
223
+ }
224
+
225
+ return results
226
+ }
227
+
228
+ /**
229
+ * Convenience method to discover only features.
230
+ */
231
+ async discoverFeatures(): Promise<string[]> {
232
+ return this.discover('features')
233
+ }
234
+
235
+ /**
236
+ * Convenience method to discover only clients.
237
+ */
238
+ async discoverClients(): Promise<string[]> {
239
+ return this.discover('clients')
240
+ }
241
+
242
+ /**
243
+ * Look up a helper class by type and name.
244
+ *
245
+ * @param type - The registry type
246
+ * @param name - The helper name within that registry
247
+ * @returns The helper constructor
248
+ */
249
+ lookup(type: RegistryType, name: string): any {
250
+ const { registry } = this.registryMap[type]
251
+ return registry.lookup(name)
252
+ }
253
+
254
+ /**
255
+ * Get the introspection description for a specific helper.
256
+ *
257
+ * @param type - The registry type
258
+ * @param name - The helper name
259
+ * @returns Markdown description of the helper's interface
260
+ */
261
+ describe(type: RegistryType, name: string): string {
262
+ const { registry } = this.registryMap[type]
263
+ return registry.describe(name)
264
+ }
265
+ }
266
+
267
+ export default features.register('helpers', Helpers)
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod'
2
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
+ import { features, Feature } from "../feature.js";
4
+ import type { Container, ContainerContext } from "../container.js";
5
+
6
+ export const NetworkStateSchema = FeatureStateSchema.extend({
7
+ offline: z.boolean().describe('Whether the browser is currently offline'),
8
+ })
9
+
10
+ export const NetworkOptionsSchema = FeatureOptionsSchema.extend({})
11
+
12
+ export type NetworkState = z.infer<typeof NetworkStateSchema>
13
+ export type NetworkOptions = z.infer<typeof NetworkOptionsSchema>
14
+
15
+ export class Network<
16
+ T extends NetworkState = NetworkState,
17
+ K extends NetworkOptions = NetworkOptions
18
+ > extends Feature<T, K> {
19
+ static override stateSchema = NetworkStateSchema
20
+ static override optionsSchema = NetworkOptionsSchema
21
+ static override shortcut = "features.network" as const
22
+
23
+ static attach(container: Container & { network?: Network }) {
24
+ container.features.register("network", Network);
25
+ }
26
+
27
+ constructor(options: K, context: ContainerContext) {
28
+ super(options, context);
29
+ this.state.set("offline", !navigator.onLine);
30
+ }
31
+
32
+ /** Whether the browser is currently offline. */
33
+ get isOffline() {
34
+ return this.state.get("offline") === true;
35
+ }
36
+
37
+ /** Whether the browser is currently online. */
38
+ get isOnline() {
39
+ return this.state.get("offline") === false;
40
+ }
41
+
42
+ private handleConnectionChange = () => {
43
+ const isOffline = !navigator.onLine;
44
+ this.state.set('offline', isOffline)
45
+ this.emit(isOffline ? "offline" : "online");
46
+ };
47
+
48
+ start() {
49
+ window.addEventListener("online", this.handleConnectionChange);
50
+ window.addEventListener("offline", this.handleConnectionChange);
51
+ return this
52
+ }
53
+
54
+ disable() {
55
+ window.removeEventListener("online", this.handleConnectionChange);
56
+ window.removeEventListener("offline", this.handleConnectionChange);
57
+ return this
58
+ }
59
+ }
60
+
61
+ export default features.register('network', Network)
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod'
2
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
+ import { Feature, features } from "../feature.js";
4
+ import { Container, type ContainerContext } from "../container.js";
5
+
6
+ export const SpeechOptionsSchema = FeatureOptionsSchema.extend({
7
+ voice: z.string().optional().describe('The voice to use for the speech'),
8
+ })
9
+
10
+ export const SpeechStateSchema = FeatureStateSchema.extend({
11
+ defaultVoice: z.string().describe('Name of the currently selected default voice'),
12
+ voices: z.array(z.any().describe('Voice object')).optional().describe('Available speech synthesis voices'),
13
+ })
14
+
15
+ export type SpeechOptions = z.infer<typeof SpeechOptionsSchema>
16
+ export type SpeechState = z.infer<typeof SpeechStateSchema>
17
+
18
+ type Voice = {
19
+ voiceURI: string;
20
+ name: string;
21
+ lang: string;
22
+ localService: boolean;
23
+ default: boolean;
24
+ };
25
+
26
+ export class Speech<
27
+ T extends SpeechState = SpeechState,
28
+ K extends SpeechOptions = SpeechOptions
29
+ > extends Feature<T, K> {
30
+
31
+ static attach(container: Container & { speech?: Speech }) {
32
+ container.features.register("speech", Speech);
33
+ }
34
+
35
+ static override stateSchema = SpeechStateSchema
36
+ static override optionsSchema = SpeechOptionsSchema
37
+ static override shortcut = "features.speech" as const
38
+
39
+ constructor(options: K, context: ContainerContext) {
40
+ super(options,context)
41
+
42
+ if(options.voice) {
43
+ this.state.set("defaultVoice", options.voice)
44
+ }
45
+
46
+ this.loadVoices()
47
+ }
48
+
49
+ /** Returns the array of available speech synthesis voices. */
50
+ get voices() {
51
+ return this.state.get('voices') || []
52
+ }
53
+
54
+ /** Returns the Voice object matching the currently selected default voice name. */
55
+ get defaultVoice() {
56
+ return this.voices.find(v => v.name === this.state.get("defaultVoice"))
57
+ }
58
+
59
+ loadVoices() {
60
+ const voices = speechSynthesis.getVoices();
61
+ this.state.set("voices", voices);
62
+
63
+ if (!this.state.get("defaultVoice") && voices.length > 0) {
64
+ const defaultVoice = voices.find(v => v.default)!
65
+ this.state.set("defaultVoice", defaultVoice.name);
66
+ }
67
+ }
68
+
69
+ setDefaultVoice(name: string) {
70
+ const voice = this.voices.find(v => v.name === name)!
71
+ this.state.set("defaultVoice", voice.name);
72
+ }
73
+
74
+ cancel() {
75
+ speechSynthesis.cancel()
76
+ return this
77
+ }
78
+
79
+ say(text: string, options: { voice?: Voice } = {}) {
80
+ const utterance = new SpeechSynthesisUtterance(text);
81
+ const voice = options.voice || this.defaultVoice
82
+ utterance.voice = voice || this.voices[0]!
83
+ speechSynthesis.speak(utterance);
84
+ }
85
+ }
86
+
87
+ export default features.register("speech", Speech);
@@ -0,0 +1,189 @@
1
+ // @ts-nocheck
2
+ import { z } from 'zod'
3
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
4
+ import { Feature, features } from '../feature.js'
5
+ import { WebContainer} from '../container.js'
6
+
7
+ export const WebVaultStateSchema = FeatureStateSchema.extend({
8
+ secret: z.string().optional().describe('Base64-encoded AES-GCM encryption secret key'),
9
+ })
10
+
11
+ export const WebVaultOptionsSchema = FeatureOptionsSchema.extend({
12
+ secret: z.string().optional().describe('Pre-existing base64-encoded secret key to use'),
13
+ })
14
+
15
+ export type WebVaultState = z.infer<typeof WebVaultStateSchema>
16
+ export type WebVaultOptions = z.infer<typeof WebVaultOptionsSchema>
17
+
18
+ export class WebVault extends Feature<WebVaultState, WebVaultOptions> {
19
+ static override stateSchema = WebVaultStateSchema
20
+ static override optionsSchema = WebVaultOptionsSchema
21
+ static override shortcut = "features.vault" as const
22
+
23
+ async secret({ refresh = false, set = true } = {}) : Promise<ArrayBuffer> {
24
+ if (!this.state.get('secret') && this.options.secret) {
25
+ this.state.set('secret', this.options.secret)
26
+ }
27
+
28
+ if (!refresh && this.state.get('secret')) {
29
+ return base64ToArrayBuffer(this.state.get('secret')!)
30
+ }
31
+
32
+ const val = await generateSecretKey()
33
+ const asString = arrayBufferToBase64(val)
34
+
35
+ if(set && !this.state.get('secret')) {
36
+ this.state.set('secret', asString)
37
+ }
38
+
39
+ return val
40
+ }
41
+
42
+ async decrypt(payload: string) {
43
+ const parts = payload.split("\n------\n")
44
+ const iv = base64ToUint8Array(parts[1]!)
45
+ const ciphertext = base64ToArrayBuffer(parts[0]!)
46
+ const secret = await this.secret()
47
+
48
+ console.log(ciphertext, secret, iv)
49
+ return await decrypt(ciphertext, secret, iv)
50
+ }
51
+
52
+ async encrypt(payload: string) {
53
+ const secret = await this.secret()
54
+ console.log("encrypting", payload, secret)
55
+ const { iv, ciphertext, } = await encrypt(payload, secret)
56
+
57
+ return [
58
+ arrayBufferToBase64(ciphertext),
59
+ uint8ArrayToBase64(iv)
60
+ ].join("\n------\n")
61
+ }
62
+
63
+ utils = {
64
+ arrayToString: arrayBufferToBase64,
65
+ stringToArray: base64ToArrayBuffer,
66
+ uintToString: uint8ArrayToBase64,
67
+ }
68
+ }
69
+
70
+ export default features.register('vault', WebVault)
71
+
72
+ async function generateSecretKey(): Promise<ArrayBuffer> {
73
+ const key = await crypto.subtle.generateKey(
74
+ { name: "AES-GCM", length: 256 },
75
+ true,
76
+ ["encrypt", "decrypt"]
77
+ );
78
+ const secretKey = await crypto.subtle.exportKey("raw", key);
79
+ return secretKey;
80
+ }
81
+
82
+ async function encrypt(plaintext: string, secretKey: ArrayBuffer): Promise<{ iv: Uint8Array; ciphertext: ArrayBuffer }> {
83
+ const encoder = new TextEncoder();
84
+ const encodedText = encoder.encode(plaintext);
85
+ const key = await crypto.subtle.importKey(
86
+ "raw",
87
+ secretKey,
88
+ { name: "AES-GCM" },
89
+ false,
90
+ ["encrypt", "decrypt"]
91
+ );
92
+ const iv = crypto.getRandomValues(new Uint8Array(12));
93
+ const ciphertext = await crypto.subtle.encrypt(
94
+ { name: "AES-GCM", iv },
95
+ key,
96
+ encodedText
97
+ );
98
+ return { iv, ciphertext };
99
+ }
100
+
101
+ async function decrypt(ciphertext: ArrayBuffer, secretKey: ArrayBuffer, iv: Uint8Array): Promise<string> {
102
+ const key = await crypto.subtle.importKey(
103
+ "raw",
104
+ secretKey,
105
+ { name: "AES-GCM" },
106
+ false,
107
+ ["encrypt", "decrypt"]
108
+ );
109
+ const plaintextArrayBuffer = await crypto.subtle.decrypt(
110
+ { name: "AES-GCM", iv },
111
+ key,
112
+ ciphertext
113
+ );
114
+ const decoder = new TextDecoder();
115
+ const plaintext = decoder.decode(plaintextArrayBuffer);
116
+ return plaintext;
117
+ }
118
+
119
+ function arrayBufferToBase64(buffer: ArrayBuffer): string {
120
+ const bytes = new Uint8Array(buffer);
121
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
122
+ let base64 = "";
123
+
124
+ for (let i = 0; i < bytes.byteLength; i += 3) {
125
+ const a = bytes[i];
126
+ const b = bytes[i + 1];
127
+ const c = bytes[i + 2];
128
+
129
+ const index1 = a >> 2;
130
+ const index2 = ((a & 0x03) << 4) | (b >> 4);
131
+ const index3 = isNaN(b!) ? 64 : ((b & 0x0f) << 2) | (c >> 6);
132
+ const index4 = isNaN(b!) || isNaN(c!) ? 64 : c & 0x3f;
133
+
134
+ base64 += chars[index1] + chars[index2] + chars[index3] + chars[index4];
135
+ }
136
+
137
+ return base64.replace('undefined', '==');
138
+ }
139
+
140
+ function uint8ArrayToBase64(u: Uint8Array): string {
141
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
142
+ let base64 = "";
143
+
144
+ for (let i = 0; i < u.byteLength; i += 3) {
145
+ const a = u[i];
146
+ const b = u[i + 1];
147
+ const c = u[i + 2];
148
+
149
+ const index1 = a >> 2;
150
+ const index2 = ((a & 0x03) << 4) | (b >> 4);
151
+ const index3 = isNaN(b!) ? 64 : ((b & 0x0f) << 2) | (c >> 6);
152
+ const index4 = isNaN(b!) || isNaN(c!) ? 64 : c & 0x3f;
153
+
154
+ base64 += chars[index1]! + chars[index2]! + chars[index3]! + chars[index4]!;
155
+ }
156
+
157
+ return base64;
158
+ }
159
+
160
+ function base64ToArrayBuffer(base64: string): ArrayBuffer {
161
+ const uint8Array = base64ToUint8Array(base64)!;
162
+ return uint8Array.buffer as ArrayBuffer;
163
+ }
164
+
165
+ function base64ToUint8Array(base64: string): Uint8Array {
166
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
167
+ const lookup = new Uint8Array(256);
168
+
169
+ for (let i = 0; i < chars.length; i++) {
170
+ lookup[chars.charCodeAt(i)] = i;
171
+ }
172
+
173
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
174
+ const length = (base64.length * 3 / 4) - padding;
175
+ const bytes = new Uint8Array(length);
176
+
177
+ for (let i = 0, j = 0; i < base64.length; i += 4, j += 3) {
178
+ const index1 = lookup[base64.charCodeAt(i)];
179
+ const index2 = lookup[base64.charCodeAt(i + 1)];
180
+ const index3 = lookup[base64.charCodeAt(i + 2)];
181
+ const index4 = lookup[base64.charCodeAt(i + 3)];
182
+
183
+ bytes[j] = (index1 << 2) | (index2 >> 4);
184
+ if (j + 1 < length) bytes[j + 1] = ((index2 & 0x0f) << 4) | (index3 >> 2);
185
+ if (j + 2 < length) bytes[j + 2] = ((index3 & 0x03) << 6) | index4;
186
+ }
187
+
188
+ return bytes;
189
+ }
@@ -0,0 +1,78 @@
1
+ // @ts-nocheck
2
+ //
3
+ import { z } from 'zod'
4
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
5
+ import vm from '../shims/isomorphic-vm'
6
+ import { Feature, features } from "../feature.js";
7
+ import { Container } from '../../container.js';
8
+
9
+ export const VMStateSchema = FeatureStateSchema.extend({})
10
+
11
+ export const VMOptionsSchema = FeatureOptionsSchema.extend({
12
+ context: z.any().describe('VM context object').optional(),
13
+ })
14
+
15
+ export type VMState = z.infer<typeof VMStateSchema>
16
+ export type VMOptions = z.infer<typeof VMOptionsSchema>
17
+
18
+ /**
19
+ * The VM features providers a virtual machine for executing JavaScript code in a sandboxed environment.
20
+ *
21
+ * The Vm feature automatically injects the container.context object into the global scope, so these things
22
+ * can be referenced in the code and the code can use anything provided by the container.
23
+ */
24
+ export class VM<
25
+ T extends VMState = VMState,
26
+ K extends VMOptions = VMOptions
27
+ > extends Feature<T, K> {
28
+
29
+ static attach(container: Container) {
30
+ container.features.register('vm', VM)
31
+ }
32
+
33
+ static override stateSchema = VMStateSchema
34
+ static override optionsSchema = VMOptionsSchema
35
+ static override shortcut = "features.vm" as const
36
+
37
+ createScript(code: string) {
38
+ return new vm.Script(code)
39
+ }
40
+
41
+ createContext(ctx: any = {}) {
42
+ return vm.createContext({
43
+ ...this.container.context,
44
+ ...ctx
45
+ })
46
+ }
47
+
48
+ async run(code: string, ctx: any = {}, options : any = {}) {
49
+ let script = this.createScript(code)
50
+
51
+ if (options.transform) {
52
+ const esbuild = this.container.feature('esbuild')
53
+ await esbuild.start()
54
+ const result = await esbuild.compile(code, options.transform)
55
+ script = this.createScript(result.code)
56
+ }
57
+
58
+ const context = this.createContext(ctx)
59
+
60
+ try {
61
+ const result = script.runInContext({
62
+ ...context,
63
+ ...(options.exports && { exports: options.exports })
64
+ })
65
+
66
+ if (options.exports) {
67
+ return { result, exports: options.exports, context }
68
+ }
69
+
70
+ return result
71
+ } catch(error) {
72
+ console.error(`Error running code`, error)
73
+ return error
74
+ }
75
+ }
76
+ }
77
+
78
+ export default features.register("vm", VM);