@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,92 @@
1
+ import { Feature, features } from '../feature.js'
2
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
+ import fetch from 'cross-fetch'
4
+
5
+ /**
6
+ * A feature that provides file downloading capabilities from URLs.
7
+ *
8
+ * The Downloader feature allows you to fetch files from remote URLs and save them
9
+ * to the local filesystem. It handles the network request, buffering, and file writing
10
+ * operations automatically.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Enable the downloader feature
15
+ * const downloader = container.feature('downloader')
16
+ *
17
+ * // Download a file
18
+ * const localPath = await downloader.download(
19
+ * 'https://example.com/image.jpg',
20
+ * 'downloads/image.jpg'
21
+ * )
22
+ * console.log(`File saved to: ${localPath}`)
23
+ * ```
24
+ *
25
+ * @extends Feature
26
+ */
27
+ export class Downloader extends Feature {
28
+ /**
29
+ * The shortcut path for accessing this feature through the container.
30
+ *
31
+ * @static
32
+ * @readonly
33
+ * @type {string}
34
+ */
35
+ static override shortcut = 'features.downloader' as const
36
+ static override stateSchema = FeatureStateSchema
37
+ static override optionsSchema = FeatureOptionsSchema
38
+
39
+ /**
40
+ * Downloads a file from a URL and saves it to the specified local path.
41
+ *
42
+ * This method fetches the file from the provided URL, converts it to a buffer,
43
+ * and writes it to the filesystem at the target path. The target path is resolved
44
+ * relative to the container's configured paths.
45
+ *
46
+ * @param {string} url - The URL to download the file from. Must be a valid HTTP/HTTPS URL.
47
+ * @param {string} targetPath - The local file path where the downloaded file should be saved.
48
+ * This path will be resolved relative to the container's base path.
49
+ *
50
+ * @returns {Promise<string>} A promise that resolves to the absolute path of the saved file.
51
+ *
52
+ * @throws {Error} Throws an error if the URL is invalid or unreachable.
53
+ * @throws {Error} Throws an error if the target directory doesn't exist or is not writable.
54
+ * @throws {Error} Throws an error if the network request fails or times out.
55
+ * @throws {Error} Throws an error if there's insufficient disk space to save the file.
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Download an image file
60
+ * const imagePath = await downloader.download(
61
+ * 'https://example.com/photo.jpg',
62
+ * 'images/downloaded-photo.jpg'
63
+ * )
64
+ *
65
+ * // Download a document
66
+ * const docPath = await downloader.download(
67
+ * 'https://api.example.com/files/document.pdf',
68
+ * 'documents/report.pdf'
69
+ * )
70
+ * ```
71
+ *
72
+ * @since 1.0.0
73
+ */
74
+ async download(url: string, targetPath: string) {
75
+ const buffer = await fetch(url).then(res => res.arrayBuffer())
76
+ await this.container.fs.writeFileAsync(
77
+ this.container.paths.resolve(targetPath),
78
+ Buffer.from(buffer)
79
+ )
80
+
81
+ return this.container.paths.resolve(targetPath)
82
+ }
83
+
84
+ }
85
+
86
+ /**
87
+ * Registers the Downloader feature with the features registry.
88
+ * This makes the feature available for use in containers via `container.use('downloader')`.
89
+ *
90
+ * @type {typeof Downloader}
91
+ */
92
+ export default features.register('downloader', Downloader)
@@ -0,0 +1,68 @@
1
+ import * as esbuild from 'esbuild-wasm'
2
+ import { Feature, features } from '../feature.js'
3
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
4
+ import { NodeContainer } from '../container.js'
5
+
6
+ /**
7
+ * A Feature for compiling typescript / esm modules, etc to JavaScript
8
+ * that the container can run at runtime. Uses esbuild for fast, reliable
9
+ * TypeScript/ESM transformation with full format support (esm, cjs, iife).
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const esbuild = container.feature('esbuild')
14
+ * const result = esbuild.transformSync('const x: number = 1')
15
+ * console.log(result.code) // 'const x = 1;\n'
16
+ * ```
17
+ */
18
+ export class ESBuild extends Feature {
19
+ static override shortcut = 'features.esbuild' as const
20
+ static override stateSchema = FeatureStateSchema
21
+ static override optionsSchema = FeatureOptionsSchema
22
+
23
+ /**
24
+ * Attaches the ESBuild feature to a NodeContainer instance.
25
+ *
26
+ * @param c - The NodeContainer to attach to
27
+ * @returns The container for method chaining
28
+ */
29
+ static attach(c: NodeContainer) {
30
+ return c
31
+ }
32
+
33
+ /**
34
+ * Transform code synchronously
35
+ * @param code - The code to transform
36
+ * @param options - The options to pass to esbuild
37
+ * @returns The transformed code
38
+ */
39
+ transformSync(code: string, options?: esbuild.TransformOptions) {
40
+ return esbuild.transformSync(code, {
41
+ loader: 'ts',
42
+ format: 'esm',
43
+ target: 'es2020',
44
+ sourcemap: false,
45
+ minify: false,
46
+ ...options
47
+ })
48
+ }
49
+
50
+ /**
51
+ * Transform code asynchronously
52
+ * @param code - The code to transform
53
+ * @param options - The options to pass to esbuild
54
+ * @returns The transformed code
55
+ */
56
+ async transform(code: string, options?: esbuild.TransformOptions) {
57
+ return esbuild.transform(code, {
58
+ loader: 'ts',
59
+ format: 'esm',
60
+ target: 'es2020',
61
+ sourcemap: false,
62
+ minify: false,
63
+ ...options
64
+ })
65
+ }
66
+ }
67
+
68
+ export default features.register('esbuild', ESBuild)
@@ -0,0 +1,357 @@
1
+ import { z } from 'zod'
2
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
+ import { State } from "../../state.js";
4
+ import { Feature, features } from "../feature.js";
5
+ import { parse, relative } from "path";
6
+ import { statSync } from "fs";
7
+ import micromatch from "micromatch";
8
+ import { castArray } from "lodash-es";
9
+ import chokidar from "chokidar";
10
+ import type { FSWatcher } from "chokidar";
11
+
12
+ type File = {
13
+ absolutePath: string;
14
+ relativePath: string;
15
+ relativeDirname: string;
16
+ dirname: string;
17
+ name: string;
18
+ extension: string;
19
+ size: number;
20
+ modifiedAt: Date;
21
+ createdAt: Date;
22
+ };
23
+
24
+ export const FileManagerStateSchema = FeatureStateSchema.extend({
25
+ /** Whether the file manager has completed its initial scan */
26
+ started: z.boolean().optional().describe('Whether the file manager has completed its initial scan'),
27
+ /** Whether the file manager is currently scanning files */
28
+ starting: z.boolean().optional().describe('Whether the file manager is currently scanning files'),
29
+ /** Whether the file watcher is actively monitoring for changes */
30
+ watching: z.boolean().optional().describe('Whether the file watcher is actively monitoring for changes'),
31
+ /** Whether the initial scan failed */
32
+ failed: z.boolean().optional().describe('Whether the initial file scan failed'),
33
+ })
34
+
35
+ export const FileManagerOptionsSchema = FeatureOptionsSchema.extend({
36
+ /** Glob patterns to exclude from file scanning */
37
+ exclude: z.union([z.string(), z.array(z.string())]).optional().describe('Glob patterns to exclude from file scanning'),
38
+ })
39
+
40
+ export type FileManagerState = z.infer<typeof FileManagerStateSchema>
41
+ export type FileManagerOptions = z.infer<typeof FileManagerOptionsSchema>
42
+
43
+ /**
44
+ * The FileManager feature creates a database like index of all of the files in the project,
45
+ * and provides metadata about these files, and also provides a way to watch for changes to the files.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const fileManager = container.feature('fileManager')
50
+ * await fileManager.start()
51
+ *
52
+ * const fileIds = fileManager.fileIds
53
+ * const typescriptFiles = fileManager.matchFiles("**ts")
54
+ * ```
55
+ */
56
+ export class FileManager<
57
+ T extends FileManagerState = FileManagerState,
58
+ K extends FileManagerOptions = FileManagerOptions
59
+ > extends Feature<T, K> {
60
+
61
+ static override shortcut = 'features.fileManager' as const
62
+ static override stateSchema = FileManagerStateSchema
63
+ static override optionsSchema = FileManagerOptionsSchema
64
+
65
+ files: State<Record<string, File>> = new State<Record<string, File>>({
66
+ initialState: {},
67
+ });
68
+
69
+ /** Returns an array of all relative file paths indexed by the file manager. */
70
+ get fileIds() {
71
+ return Array.from(this.files.keys());
72
+ }
73
+
74
+ /** Returns an array of all file metadata objects indexed by the file manager. */
75
+ get fileObjects() {
76
+ return Array.from(this.files.values());
77
+ }
78
+
79
+ /**
80
+ * Matches the file IDs against the pattern(s) provided
81
+ * @param {string | string[]} patterns - The patterns to match against the file IDs
82
+ * @returns {string[]} The file IDs that match the patterns
83
+ */
84
+ match(patterns: string | string[]) {
85
+ return micromatch(this.files.keys(), patterns);
86
+ }
87
+
88
+ /**
89
+ * Matches the file IDs against the pattern(s) provided and returns the file objects for each.
90
+ *
91
+ * @param {string | string[]} patterns - The patterns to match against the file IDs
92
+ * @returns {File[]} The file objects that match the patterns
93
+ */
94
+ matchFiles(patterns: string | string[]) {
95
+ const fileIds = this.match(Array.isArray(patterns) ? patterns : [patterns]);
96
+ return fileIds.map((fileId) => this.files.get(fileId));
97
+ }
98
+
99
+ /**
100
+ * Returns the directory IDs for all of the files in the project.
101
+ */
102
+ get directoryIds() {
103
+ return Array.from(
104
+ new Set(
105
+ this.files
106
+ .values()
107
+ .map((file) => this.container.paths.relative(file.dirname))
108
+ .filter(v => v.length)
109
+ )
110
+ );
111
+ }
112
+
113
+ /** Returns an array of unique file extensions found across all indexed files. */
114
+ get uniqueExtensions() {
115
+ return Array.from(
116
+ new Set(
117
+ this.files.values().map((file) => file.extension)
118
+ )
119
+ );
120
+ }
121
+
122
+ /** Whether the file manager has completed its initial scan. */
123
+ get isStarted() {
124
+ return !!this.state.get("started");
125
+ }
126
+
127
+ /** Whether the file manager is currently performing its initial scan. */
128
+ get isStarting() {
129
+ return !!this.state.get("starting");
130
+ }
131
+
132
+ /** Whether the file watcher is actively monitoring for changes. */
133
+ get isWatching() {
134
+ return !!this.state.get("watching");
135
+ }
136
+
137
+ /**
138
+ * Starts the file manager and scans the files in the project.
139
+ * @param {object} [options={}] - Options for the file manager
140
+ * @param {string | string[]} [options.exclude] - The patterns to exclude from the scan
141
+ * @returns {Promise<FileManager>} The file manager instance
142
+ */
143
+ async start(options: { exclude?: string | string[] } = {}) {
144
+ if (this.isStarted) {
145
+ return this;
146
+ }
147
+
148
+ if (this.isStarting) {
149
+ await this.waitFor("started");
150
+ return this;
151
+ } else {
152
+ this.state.set("starting", true);
153
+ }
154
+
155
+ try {
156
+ await this.scanFiles(options);
157
+ } catch (error) {
158
+ console.error(error);
159
+ this.state.set("failed", true);
160
+ } finally {
161
+ this.state.set("started", true);
162
+ this.state.set("starting", false);
163
+ }
164
+
165
+ return this;
166
+ }
167
+
168
+ /**
169
+ * Scans the files in the project and updates the file manager state.
170
+ * @param {object} [options={}] - Options for the file manager
171
+ * @param {string | string[]} [options.exclude] - The patterns to exclude from the scan
172
+ * @returns {Promise<FileManager>} The file manager instance
173
+ */
174
+ async scanFiles(options: { exclude?: string | string[] } = {}) {
175
+ const { cwd, git, fs } = this.container;
176
+
177
+ const fileIds: string[] = [];
178
+
179
+ if (!Array.isArray(options.exclude)) {
180
+ options.exclude = [options.exclude!].filter((v) => v?.length);
181
+ }
182
+
183
+ const { exclude = ["dist", "node_modules", "out"] } = options;
184
+
185
+ exclude.push(...castArray(this.options.exclude!).filter((v) => v?.length));
186
+
187
+ exclude.push("node_modules");
188
+ exclude.push("out");
189
+ exclude.push("dist");
190
+
191
+ if (git.isRepo) {
192
+ const repoRoot = git.repoRoot;
193
+ const cwdRelative = repoRoot ? relative(repoRoot, cwd) : '';
194
+ const baseDir = cwdRelative || '';
195
+
196
+ const deleted = await git.lsFiles({ deleted: true, baseDir })
197
+ await git.lsFiles({ baseDir }).then((results) => fileIds.push(...results.filter((id:string) => !deleted.includes(id))));
198
+ await git
199
+ .lsFiles({ others: true, includeIgnored: true, exclude, baseDir })
200
+ .then((results) => fileIds.push(...results.filter((id:string) => !deleted.includes(id))));
201
+
202
+ // git ls-files returns paths relative to repo root; make them relative to cwd
203
+ if (cwdRelative) {
204
+ const prefix = cwdRelative + '/';
205
+ for (let i = 0; i < fileIds.length; i++) {
206
+ if (fileIds[i].startsWith(prefix)) {
207
+ fileIds[i] = fileIds[i].slice(prefix.length);
208
+ }
209
+ }
210
+ }
211
+ } else {
212
+ await fs.walkAsync(cwd).then(({ files } : { files: string[] }) => fileIds.push(...files));
213
+ }
214
+
215
+ fileIds.forEach((relativePath) => {
216
+ const absolutePath = this.container.paths.resolve(relativePath);
217
+ const { name, ext, dir } = parse(absolutePath);
218
+
219
+ let size = 0
220
+ let modifiedAt = new Date(0)
221
+ let createdAt = new Date(0)
222
+
223
+ try {
224
+ const stats = statSync(absolutePath);
225
+ size = stats.size;
226
+ modifiedAt = stats.mtime;
227
+ createdAt = stats.birthtime;
228
+ } catch (error) {
229
+ }
230
+
231
+ this.files.set(relativePath, {
232
+ dirname: dir,
233
+ absolutePath,
234
+ relativePath,
235
+ relativeDirname: this.container.paths.relative(dir),
236
+ name,
237
+ extension: ext,
238
+ size,
239
+ modifiedAt,
240
+ createdAt,
241
+ });
242
+ });
243
+
244
+ return this;
245
+ }
246
+
247
+ watcher: FSWatcher | null = null;
248
+
249
+ /** Returns the directories and files currently being watched by chokidar. */
250
+ get watchedFiles(): Record<string, string[]> {
251
+ return this.watcher?.getWatched() || {};
252
+ }
253
+
254
+ /**
255
+ * Watches the files in the project and updates the file manager state.
256
+ * @param {object} [options={}] - Options for the file manager
257
+ * @param {string | string[]} [options.exclude] - The patterns to exclude from the watch
258
+ * @returns {Promise<void>} The file manager instance
259
+ */
260
+ async watch(options: { exclude?: string | string[] } = {}) {
261
+ if (this.isWatching) {
262
+ return;
263
+ }
264
+
265
+ if (!Array.isArray(options.exclude)) {
266
+ options.exclude = [options.exclude!].filter((v) => v?.length);
267
+ }
268
+
269
+ const {
270
+ exclude = [".git/**", "dist/**", "node_modules/**", "out/**", "build/**"],
271
+ } = options;
272
+
273
+ exclude.push(...castArray(this.options.exclude!).filter((v) => v?.length));
274
+
275
+ const { cwd } = this.container;
276
+
277
+ const watcher = chokidar.watch(
278
+ this.directoryIds.map(id => this.container.paths.resolve(id))
279
+ , {
280
+ ignoreInitial: true,
281
+ persistent: true,
282
+ ignored: [
283
+ '.git/**',
284
+ ...[".git", "dist/**", "node_modules/**", "out/**", "build/**"],
285
+ ...exclude,
286
+ ].map((pattern) => micromatch.makeRe(pattern)).concat([
287
+ /\.git/,
288
+ /node_modules/
289
+ ]),
290
+ });
291
+
292
+ watcher
293
+ .on("add", (path) => {
294
+ this.emit("file:change", {
295
+ type: "add",
296
+ path,
297
+ });
298
+ this.updateFile(path);
299
+ })
300
+ .on("change", (path) => {
301
+ this.updateFile(path);
302
+ this.emit("file:change", {
303
+ path,
304
+ type: "change",
305
+ });
306
+ })
307
+ .on("unlink", (path) => {
308
+ this.removeFile(path);
309
+ this.emit("file:change", {
310
+ type: "delete",
311
+ path,
312
+ });
313
+ });
314
+
315
+ watcher.on("ready", () => {
316
+ this.state.set("watching", true);
317
+ });
318
+
319
+ this.watcher = watcher;
320
+ }
321
+
322
+ async stopWatching() {
323
+ if (!this.isWatching) {
324
+ return;
325
+ }
326
+
327
+ if (this.watcher) {
328
+ this.watcher.close();
329
+ this.state.set("watching", false);
330
+ this.watcher = null;
331
+ }
332
+ }
333
+
334
+ async updateFile(path: string) {
335
+ // Reuse the logic from the scanFiles method to update a single file
336
+ const absolutePath = this.container.paths.resolve(path);
337
+ const { name, ext, dir } = parse(absolutePath);
338
+ const stats = statSync(absolutePath);
339
+
340
+ this.files.set(path, {
341
+ dirname: dir,
342
+ absolutePath,
343
+ relativePath: path,
344
+ name,
345
+ extension: ext,
346
+ size: stats.size,
347
+ modifiedAt: stats.mtime,
348
+ createdAt: stats.birthtime,
349
+ });
350
+ }
351
+
352
+ async removeFile(path: string) {
353
+ this.files.delete(path);
354
+ }
355
+ }
356
+
357
+ export default features.register("fileManager", FileManager);