@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,490 @@
1
+ import { z } from 'zod'
2
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
3
+ import { features, Feature } from '../feature.js'
4
+
5
+ // ─── Schemas ────────────────────────────────────────────────────────────────
6
+
7
+ export const InkStateSchema = FeatureStateSchema.extend({
8
+ /** Whether an ink app is currently rendered / mounted */
9
+ mounted: z.boolean().describe('Whether an ink app is currently rendered / mounted'),
10
+ })
11
+ type InkState = z.infer<typeof InkStateSchema>
12
+
13
+ export const InkOptionsSchema = FeatureOptionsSchema.extend({
14
+ /** Maximum frames per second for render updates (default 30) */
15
+ maxFps: z.number().optional().describe('Maximum frames per second for render updates'),
16
+ /** Patch console methods so console.log doesnt break the TUI (default true) */
17
+ patchConsole: z.boolean().optional().describe('Patch console methods to avoid mixing with Ink output'),
18
+ /** Enable incremental rendering to reduce flicker (default false) */
19
+ incrementalRendering: z.boolean().optional().describe('Enable incremental rendering mode'),
20
+ /** Enable React concurrent mode (default false) */
21
+ concurrent: z.boolean().optional().describe('Enable React concurrent rendering mode'),
22
+ })
23
+ type InkOptions = z.infer<typeof InkOptionsSchema>
24
+
25
+ export const InkEventsSchema = FeatureEventsSchema.extend({
26
+ mounted: z.tuple([]).describe('Emitted when a React element is mounted to the terminal via render()'),
27
+ unmounted: z.tuple([]).describe('Emitted when the mounted Ink app exits or is unmounted'),
28
+ })
29
+
30
+ /**
31
+ * Ink Feature — React-powered Terminal UI via Ink
32
+ *
33
+ * Exposes the Ink library (React for CLIs) through the container so any
34
+ * feature, script, or application can build rich terminal user interfaces
35
+ * using React components rendered directly in the terminal.
36
+ *
37
+ * This feature is intentionally a thin pass-through. It re-exports all of
38
+ * Ink's components, hooks, and the render function, plus a few convenience
39
+ * methods for mounting / unmounting apps. The actual UI composition is left
40
+ * entirely to the consumer — the feature just makes Ink available.
41
+ *
42
+ * **What you get:**
43
+ * - `ink.render(element)` — mount a React element to the terminal
44
+ * - `ink.components` — { Box, Text, Static, Transform, Newline, Spacer }
45
+ * - `ink.hooks` — { useInput, useApp, useStdin, useStdout, useStderr, useFocus, useFocusManager }
46
+ * - `ink.React` — the React module itself (createElement, useState, etc.)
47
+ * - `ink.unmount()` — tear down the currently mounted app
48
+ * - `ink.waitUntilExit()` — await the mounted app's exit
49
+ *
50
+ * **Quick start:**
51
+ * ```tsx
52
+ * const ink = container.feature('ink', { enable: true })
53
+ * const { Box, Text } = ink.components
54
+ * const { React } = ink
55
+ *
56
+ * ink.render(
57
+ * React.createElement(Box, { flexDirection: 'column' },
58
+ * React.createElement(Text, { color: 'green' }, 'hello from ink'),
59
+ * React.createElement(Text, { dimColor: true }, 'powered by luca'),
60
+ * )
61
+ * )
62
+ *
63
+ * await ink.waitUntilExit()
64
+ * ```
65
+ *
66
+ * Or if you're in a .tsx file:
67
+ * ```tsx
68
+ * import React from 'react'
69
+ * const ink = container.feature('ink', { enable: true })
70
+ * const { Box, Text } = ink.components
71
+ *
72
+ * ink.render(
73
+ * <Box flexDirection="column">
74
+ * <Text color="green">hello from ink</Text>
75
+ * <Text dimColor>powered by luca</Text>
76
+ * </Box>
77
+ * )
78
+ * ```
79
+ *
80
+ * @extends Feature
81
+ */
82
+ export class Ink extends Feature<InkState, InkOptions> {
83
+ static override shortcut = 'features.ink' as const
84
+ static override stateSchema = InkStateSchema
85
+ static override optionsSchema = InkOptionsSchema
86
+ static override eventsSchema = InkEventsSchema
87
+
88
+ private _instance: any | null = null
89
+ private _inkModule: typeof import('ink') | null = null
90
+ private _reactModule: typeof import('react') | null = null
91
+ private _blocks = new Map<string, Function>()
92
+
93
+ override get initialState(): InkState {
94
+ return {
95
+ enabled: true,
96
+ mounted: false,
97
+ } as InkState
98
+ }
99
+
100
+ // ─── Lazy module loading ──────────────────────────────────────────────
101
+
102
+ /**
103
+ * The raw ink module. Lazy-loaded on first access.
104
+ */
105
+ private async _getInk() {
106
+ if (!this._inkModule) {
107
+ this._inkModule = await import('ink')
108
+ }
109
+ return this._inkModule
110
+ }
111
+
112
+ /**
113
+ * The raw react module. Lazy-loaded on first access.
114
+ */
115
+ private async _getReact() {
116
+ if (!this._reactModule) {
117
+ this._reactModule = await import('react')
118
+ }
119
+ return this._reactModule
120
+ }
121
+
122
+ // ─── Public API ───────────────────────────────────────────────────────
123
+
124
+ /**
125
+ * The React module (createElement, useState, useEffect, etc.)
126
+ *
127
+ * Exposed so consumers don't need a separate react import.
128
+ * Lazy-loaded — first access triggers the import.
129
+ */
130
+ get React() {
131
+ // return a promise the first time, but for ergonomics we also
132
+ // expose a sync getter that throws if react hasn't loaded yet.
133
+ if (this._reactModule) return this._reactModule
134
+ throw new Error(
135
+ 'React not loaded yet. Either await ink.loadModules() first, or use ink.render() which loads automatically.'
136
+ )
137
+ }
138
+
139
+ /**
140
+ * Pre-load ink + react modules so the sync getters work.
141
+ * Called automatically by render(), but you can call it early.
142
+ *
143
+ * @returns This Ink feature instance for method chaining
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const ink = container.feature('ink', { enable: true })
148
+ * await ink.loadModules()
149
+ * // Now sync getters like ink.React, ink.components, ink.hooks work
150
+ * const { Box, Text } = ink.components
151
+ * ```
152
+ */
153
+ async loadModules() {
154
+ await Promise.all([this._getInk(), this._getReact()])
155
+ return this
156
+ }
157
+
158
+ /**
159
+ * All Ink components as a single object for destructuring.
160
+ *
161
+ * ```ts
162
+ * const { Box, Text, Static, Spacer } = ink.components
163
+ * ```
164
+ */
165
+ get components() {
166
+ const ink = this._inkModule
167
+ if (!ink) {
168
+ throw new Error(
169
+ 'Ink not loaded yet. Call await ink.loadModules() or ink.render() first.'
170
+ )
171
+ }
172
+
173
+ return {
174
+ Box: ink.Box,
175
+ Text: ink.Text,
176
+ Static: ink.Static,
177
+ Transform: ink.Transform,
178
+ Newline: ink.Newline,
179
+ Spacer: ink.Spacer,
180
+ }
181
+ }
182
+
183
+ /**
184
+ * All Ink hooks as a single object for destructuring.
185
+ *
186
+ * ```ts
187
+ * const { useInput, useApp, useFocus } = ink.hooks
188
+ * ```
189
+ */
190
+ get hooks() {
191
+ const ink = this._inkModule
192
+ if (!ink) {
193
+ throw new Error(
194
+ 'Ink not loaded yet. Call await ink.loadModules() or ink.render() first.'
195
+ )
196
+ }
197
+
198
+ return {
199
+ useInput: ink.useInput,
200
+ useApp: ink.useApp,
201
+ useStdin: ink.useStdin,
202
+ useStdout: ink.useStdout,
203
+ useStderr: ink.useStderr,
204
+ useFocus: ink.useFocus,
205
+ useFocusManager: ink.useFocusManager,
206
+ useCursor: ink.useCursor,
207
+ }
208
+ }
209
+
210
+ /**
211
+ * The Ink measureElement utility.
212
+ */
213
+ get measureElement() {
214
+ const ink = this._inkModule
215
+ if (!ink) {
216
+ throw new Error('Ink not loaded yet.')
217
+ }
218
+ return ink.measureElement
219
+ }
220
+
221
+ /**
222
+ * Mount a React element to the terminal.
223
+ *
224
+ * Wraps `ink.render()` — automatically loads modules if needed,
225
+ * tracks the instance for unmount / waitUntilExit, and updates state.
226
+ *
227
+ * @param node - A React element (JSX or React.createElement)
228
+ * @param options - Ink render options (stdout, stdin, debug, etc.)
229
+ * @returns The Ink instance with rerender, unmount, waitUntilExit, clear
230
+ */
231
+ async render(node: any, options: Record<string, any> = {}) {
232
+ const ink = await this._getInk()
233
+ await this._getReact()
234
+
235
+ // merge feature-level defaults with per-render overrides
236
+ const mergedOptions = {
237
+ patchConsole: this.options.patchConsole ?? true,
238
+ maxFps: this.options.maxFps,
239
+ incrementalRendering: this.options.incrementalRendering,
240
+ concurrent: this.options.concurrent,
241
+ ...options,
242
+ }
243
+
244
+ // clean out undefined values so ink uses its own defaults
245
+ for (const key of Object.keys(mergedOptions)) {
246
+ if ((mergedOptions as any)[key] === undefined) {
247
+ delete (mergedOptions as any)[key]
248
+ }
249
+ }
250
+
251
+ this._instance = ink.render(node, mergedOptions)
252
+ this.setState({ mounted: true })
253
+ this.emit('mounted')
254
+
255
+ // when the app exits, update state
256
+ this._instance.waitUntilExit().then(() => {
257
+ this.setState({ mounted: false })
258
+ this._instance = null
259
+ this.emit('unmounted')
260
+ }).catch(() => {
261
+ // noop — app might be force-unmounted
262
+ })
263
+
264
+ return this._instance
265
+ }
266
+
267
+ /**
268
+ * Re-render the currently mounted app with a new root element.
269
+ *
270
+ * @returns void
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * const ink = container.feature('ink', { enable: true })
275
+ * const { React } = await ink.loadModules()
276
+ * const { Text } = ink.components
277
+ *
278
+ * await ink.render(React.createElement(Text, null, 'Hello'))
279
+ * ink.rerender(React.createElement(Text, null, 'Updated!'))
280
+ * ```
281
+ */
282
+ rerender(node: any) {
283
+ if (!this._instance) {
284
+ throw new Error('No mounted ink app. Call render() first.')
285
+ }
286
+ this._instance.rerender(node)
287
+ }
288
+
289
+ /**
290
+ * Unmount the currently mounted Ink app.
291
+ *
292
+ * Tears down the React tree rendered in the terminal and resets state.
293
+ * Safe to call when no app is mounted (no-op).
294
+ *
295
+ * @returns void
296
+ *
297
+ * @example
298
+ * ```typescript
299
+ * const ink = container.feature('ink', { enable: true })
300
+ * await ink.render(myElement)
301
+ * // ... later
302
+ * ink.unmount()
303
+ * console.log(ink.isMounted) // false
304
+ * ```
305
+ */
306
+ unmount() {
307
+ if (this._instance) {
308
+ this._instance.unmount()
309
+ this.setState({ mounted: false })
310
+ this._instance = null
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Returns a promise that resolves when the mounted app exits.
316
+ *
317
+ * Useful for keeping a script alive while the terminal UI is active.
318
+ *
319
+ * @returns Promise that resolves when the Ink app exits
320
+ * @throws {Error} When no app is currently mounted
321
+ *
322
+ * @example
323
+ * ```typescript
324
+ * const ink = container.feature('ink', { enable: true })
325
+ * await ink.render(myElement)
326
+ * await ink.waitUntilExit()
327
+ * console.log('App exited')
328
+ * ```
329
+ */
330
+ async waitUntilExit(): Promise<void> {
331
+ if (!this._instance) {
332
+ throw new Error('No mounted ink app. Call render() first.')
333
+ }
334
+ return this._instance.waitUntilExit()
335
+ }
336
+
337
+ /**
338
+ * Clear the terminal output of the mounted app.
339
+ *
340
+ * Erases all Ink-rendered content from the terminal. Safe to call
341
+ * when no app is mounted (no-op).
342
+ *
343
+ * @returns void
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * const ink = container.feature('ink', { enable: true })
348
+ * await ink.render(myElement)
349
+ * // ... later, wipe the screen
350
+ * ink.clear()
351
+ * ```
352
+ */
353
+ clear() {
354
+ if (this._instance) {
355
+ this._instance.clear()
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Whether an ink app is currently mounted.
361
+ */
362
+ get isMounted(): boolean {
363
+ return this.state.get('mounted') ?? false
364
+ }
365
+
366
+ /**
367
+ * The raw ink render instance if you need low-level access.
368
+ */
369
+ get instance() {
370
+ return this._instance
371
+ }
372
+
373
+ // ─── Block Registry ─────────────────────────────────────────────────
374
+
375
+ /**
376
+ * Register a named React function component as a renderable block.
377
+ *
378
+ * @param name - Unique block name
379
+ * @param component - A React function component
380
+ * @returns This Ink feature instance for method chaining
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * ink.registerBlock('Greeting', ({ name }) =>
385
+ * React.createElement(Text, { color: 'green' }, `Hello ${name}!`)
386
+ * )
387
+ * ```
388
+ */
389
+ registerBlock(name: string, component: Function) {
390
+ this._blocks.set(name, component)
391
+ return this
392
+ }
393
+
394
+ /**
395
+ * Render a registered block by name with optional props.
396
+ *
397
+ * Looks up the component, creates a React element, renders it via ink,
398
+ * then immediately unmounts so the static output stays on screen while
399
+ * freeing the React tree.
400
+ *
401
+ * @param name - The registered block name
402
+ * @param data - Props to pass to the component
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * await ink.renderBlock('Greeting', { name: 'Jon' })
407
+ * ```
408
+ */
409
+ async renderBlock(name: string, data?: Record<string, any>) {
410
+ const component = this._blocks.get(name)
411
+ if (!component) {
412
+ throw new Error(`No block registered with name "${name}". Available: ${[...this._blocks.keys()].join(', ') || '(none)'}`)
413
+ }
414
+
415
+ await this.loadModules()
416
+ const React = this.React
417
+ const element = React.createElement(component as any, data || {})
418
+ const instance = await this.render(element, { patchConsole: false })
419
+ instance.unmount()
420
+ }
421
+
422
+ /**
423
+ * Render a registered block that needs to stay mounted for async work.
424
+ *
425
+ * The component receives a `done` prop — a callback it must invoke when
426
+ * it has finished rendering its final output. The React tree stays alive
427
+ * until `done()` is called or the timeout expires.
428
+ *
429
+ * @param name - The registered block name
430
+ * @param data - Props to pass to the component (a `done` prop is added automatically)
431
+ * @param options - `timeout` in ms before force-unmounting (default 30 000)
432
+ *
433
+ * @example
434
+ * ```tsx
435
+ * // In a ## Blocks section:
436
+ * function AsyncChart({ url, done }) {
437
+ * const [rows, setRows] = React.useState(null)
438
+ * React.useEffect(() => {
439
+ * fetch(url).then(r => r.json()).then(data => {
440
+ * setRows(data)
441
+ * done()
442
+ * })
443
+ * }, [])
444
+ * if (!rows) return <Text dimColor>Loading...</Text>
445
+ * return <Box><Text>{JSON.stringify(rows)}</Text></Box>
446
+ * }
447
+ *
448
+ * // In a code block:
449
+ * await renderAsync('AsyncChart', { url: 'https://api.example.com/data' })
450
+ * ```
451
+ */
452
+ async renderBlockAsync(name: string, data?: Record<string, any>, options?: { timeout?: number }) {
453
+ const component = this._blocks.get(name)
454
+ if (!component) {
455
+ throw new Error(`No block registered with name "${name}". Available: ${[...this._blocks.keys()].join(', ') || '(none)'}`)
456
+ }
457
+
458
+ const timeout = options?.timeout ?? 30_000
459
+
460
+ await this.loadModules()
461
+ const React = this.React
462
+
463
+ const { promise, resolve } = Promise.withResolvers<void>()
464
+
465
+ const done = () => resolve()
466
+ const element = React.createElement(component as any, { ...data, done })
467
+ const instance = await this.render(element, { patchConsole: false })
468
+
469
+ const timer = setTimeout(() => resolve(), timeout)
470
+
471
+ await promise
472
+ clearTimeout(timer)
473
+ instance.unmount()
474
+ }
475
+
476
+ /**
477
+ * List all registered block names.
478
+ */
479
+ get blocks(): string[] {
480
+ return [...this._blocks.keys()]
481
+ }
482
+ }
483
+
484
+ export default features.register('ink', Ink)
485
+
486
+ declare module '../../feature' {
487
+ interface AvailableFeatures {
488
+ ink: typeof Ink
489
+ }
490
+ }