@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.
- package/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- 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
|
+
}
|