@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,192 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { commands } from '../command.js'
|
|
3
|
+
import { CommandOptionsSchema } from '../schemas/base.js'
|
|
4
|
+
import type { ContainerContext } from '../container.js'
|
|
5
|
+
|
|
6
|
+
declare module '../command.js' {
|
|
7
|
+
interface AvailableCommands {
|
|
8
|
+
console: ReturnType<typeof commands.registerHandler>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
13
|
+
enable: z.string().optional().describe('Enable a feature before starting the REPL (e.g. --enable diskCache)'),
|
|
14
|
+
eval: z.string().optional().describe('Evaluate code, a script, or markdown file before dropping into the REPL'),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function resolveEvalTarget(ref: string, container: any): { type: 'code' | 'script' | 'markdown', value: string } {
|
|
18
|
+
const candidates = [ref, `${ref}.ts`, `${ref}.js`, `${ref}.md`]
|
|
19
|
+
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
const resolved = container.paths.resolve(candidate)
|
|
22
|
+
if (container.fs.exists(resolved)) {
|
|
23
|
+
if (resolved.endsWith('.md')) return { type: 'markdown', value: resolved }
|
|
24
|
+
return { type: 'script', value: resolved }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Not a file — treat as inline code
|
|
29
|
+
return { type: 'code', value: ref }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function evalBeforeRepl(evalArg: string, container: any, featureContext: Record<string, any>): Promise<Record<string, any>> {
|
|
33
|
+
const target = resolveEvalTarget(evalArg, container)
|
|
34
|
+
const vm = container.feature('vm')
|
|
35
|
+
const ui = container.feature('ui')
|
|
36
|
+
const extraContext: Record<string, any> = {}
|
|
37
|
+
|
|
38
|
+
if (target.type === 'markdown') {
|
|
39
|
+
await container.docs.load()
|
|
40
|
+
const doc = await container.docs.parseMarkdownAtPath(target.value)
|
|
41
|
+
const esbuild = container.feature('esbuild')
|
|
42
|
+
const shared = vm.createContext({
|
|
43
|
+
console, fetch, URL, URLSearchParams,
|
|
44
|
+
setTimeout, clearTimeout, setInterval, clearInterval,
|
|
45
|
+
...featureContext,
|
|
46
|
+
...container.context,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const children = doc.ast.children
|
|
50
|
+
for (let i = 0; i < children.length; i++) {
|
|
51
|
+
const node = children[i]
|
|
52
|
+
if (node.type === 'code') {
|
|
53
|
+
const { value, lang, meta } = node
|
|
54
|
+
if (lang !== 'ts' && lang !== 'js' && lang !== 'tsx' && lang !== 'jsx') continue
|
|
55
|
+
if (meta && typeof meta === 'string' && meta.toLowerCase().includes('skip')) continue
|
|
56
|
+
|
|
57
|
+
console.log(ui.markdown(['```' + lang, value, '```'].join('\n')))
|
|
58
|
+
|
|
59
|
+
const needsTransform = lang === 'tsx' || lang === 'jsx'
|
|
60
|
+
let code = value
|
|
61
|
+
if (needsTransform) {
|
|
62
|
+
const { code: transformed } = esbuild.transformSync(value, { loader: lang as 'tsx' | 'jsx', format: 'cjs' })
|
|
63
|
+
code = transformed
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const hasTopLevelAwait = /\bawait\b/.test(code)
|
|
67
|
+
code = hasTopLevelAwait ? `(async function() { ${code} })()` : code
|
|
68
|
+
|
|
69
|
+
await vm.run(code, shared)
|
|
70
|
+
Object.assign(shared, container.context)
|
|
71
|
+
} else {
|
|
72
|
+
const md = doc.stringify({ type: 'root', children: [node] })
|
|
73
|
+
console.log(ui.markdown(md))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Object.assign(extraContext, shared)
|
|
78
|
+
} else if (target.type === 'script') {
|
|
79
|
+
const code = container.fs.readFile(target.value, 'utf8')
|
|
80
|
+
const ctx = vm.createContext({
|
|
81
|
+
console, fetch, URL, URLSearchParams,
|
|
82
|
+
setTimeout, clearTimeout, setInterval, clearInterval,
|
|
83
|
+
...featureContext,
|
|
84
|
+
...container.context,
|
|
85
|
+
})
|
|
86
|
+
await vm.run(code, ctx)
|
|
87
|
+
Object.assign(extraContext, ctx)
|
|
88
|
+
} else {
|
|
89
|
+
const ctx = vm.createContext({
|
|
90
|
+
console, fetch, URL, URLSearchParams,
|
|
91
|
+
setTimeout, clearTimeout, setInterval, clearInterval,
|
|
92
|
+
...featureContext,
|
|
93
|
+
...container.context,
|
|
94
|
+
})
|
|
95
|
+
await vm.run(target.value, ctx)
|
|
96
|
+
Object.assign(extraContext, ctx)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return extraContext
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default async function lucaConsole(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
103
|
+
const container = context.container as any
|
|
104
|
+
const ui = container.feature('ui')
|
|
105
|
+
|
|
106
|
+
await container.helpers.discoverAll()
|
|
107
|
+
|
|
108
|
+
// make it easy to create features
|
|
109
|
+
container.addContext('feature', (...args: any) => container.feature(...args))
|
|
110
|
+
|
|
111
|
+
//this is a hack to make it so we can enable things before the console starts
|
|
112
|
+
if (container.argv.enable) {
|
|
113
|
+
for (const id of Array(container.argv.enable)) {
|
|
114
|
+
try {
|
|
115
|
+
container.feature(id, { ...container.argv, enable: true }).enable()
|
|
116
|
+
} catch(error: any) {
|
|
117
|
+
console.error(`Error enabling feature`, error.message)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const featureContext: Record<string, any> = {}
|
|
123
|
+
for (const name of container.features.available) {
|
|
124
|
+
try {
|
|
125
|
+
featureContext[name] = container.feature(name)
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Load user console module if present
|
|
130
|
+
const consoleModulePath = container.paths.resolve('luca.console.ts')
|
|
131
|
+
let consoleModuleLoaded = false
|
|
132
|
+
let consoleModuleError: Error | null = null
|
|
133
|
+
|
|
134
|
+
if (container.fs.exists(consoleModulePath)) {
|
|
135
|
+
try {
|
|
136
|
+
const vmFeature = container.feature('vm')
|
|
137
|
+
const userExports = vmFeature.loadModule(consoleModulePath, { container, console })
|
|
138
|
+
Object.assign(featureContext, userExports)
|
|
139
|
+
consoleModuleLoaded = true
|
|
140
|
+
} catch (err: any) {
|
|
141
|
+
consoleModuleError = err
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Run --eval target before starting the REPL
|
|
146
|
+
let evalContext: Record<string, any> = {}
|
|
147
|
+
if (options.eval) {
|
|
148
|
+
try {
|
|
149
|
+
evalContext = await evalBeforeRepl(options.eval, container, featureContext)
|
|
150
|
+
} catch (err: any) {
|
|
151
|
+
console.error(ui.colors.red(` Error evaluating: ${err.message}`))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const prompt = ui.colors.cyan('luca') + ui.colors.dim(' > ')
|
|
156
|
+
|
|
157
|
+
console.log()
|
|
158
|
+
console.log(ui.colors.dim(' Luca REPL — all container features in scope. Tab to autocomplete.'))
|
|
159
|
+
if (options.eval) {
|
|
160
|
+
console.log(ui.colors.dim(` Evaluated: ${options.eval}`))
|
|
161
|
+
}
|
|
162
|
+
if (consoleModuleLoaded) {
|
|
163
|
+
console.log(ui.colors.dim(' Loaded luca.console.ts exports into scope.'))
|
|
164
|
+
} else if (consoleModuleError) {
|
|
165
|
+
console.log(ui.colors.yellow(' ⚠ Failed to load luca.console.ts:'))
|
|
166
|
+
console.log(ui.colors.yellow(` ${consoleModuleError.message}`))
|
|
167
|
+
console.log(ui.colors.dim(' The REPL will start without your custom exports.'))
|
|
168
|
+
}
|
|
169
|
+
console.log(ui.colors.dim(' Type .exit to quit.'))
|
|
170
|
+
console.log()
|
|
171
|
+
|
|
172
|
+
const repl = container.feature('repl', { prompt })
|
|
173
|
+
await repl.start({
|
|
174
|
+
context: {
|
|
175
|
+
...featureContext,
|
|
176
|
+
...evalContext,
|
|
177
|
+
console,
|
|
178
|
+
setTimeout,
|
|
179
|
+
setInterval,
|
|
180
|
+
clearTimeout,
|
|
181
|
+
clearInterval,
|
|
182
|
+
fetch,
|
|
183
|
+
Bun,
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
commands.registerHandler('console', {
|
|
189
|
+
description: 'Start an interactive REPL with all container features in scope',
|
|
190
|
+
argsSchema,
|
|
191
|
+
handler: lucaConsole,
|
|
192
|
+
})
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { commands } from '../command.js'
|
|
3
|
+
import { CommandOptionsSchema } from '../schemas/base.js'
|
|
4
|
+
import type { ContainerContext } from '../container.js'
|
|
5
|
+
import type { IntrospectionSection } from '../introspection/index.js'
|
|
6
|
+
|
|
7
|
+
declare module '../command.js' {
|
|
8
|
+
interface AvailableCommands {
|
|
9
|
+
describe: ReturnType<typeof commands.registerHandler>
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const REGISTRY_NAMES = ['features', 'clients', 'servers', 'commands', 'endpoints'] as const
|
|
14
|
+
type RegistryName = (typeof REGISTRY_NAMES)[number]
|
|
15
|
+
|
|
16
|
+
/** Maps flag names to the section they represent. 'description' is handled specially. */
|
|
17
|
+
const SECTION_FLAGS: Record<string, IntrospectionSection | 'description'> = {
|
|
18
|
+
// Clean flag names (combinable)
|
|
19
|
+
'description': 'description',
|
|
20
|
+
'usage': 'usage',
|
|
21
|
+
'methods': 'methods',
|
|
22
|
+
'getters': 'getters',
|
|
23
|
+
'events': 'events',
|
|
24
|
+
'state': 'state',
|
|
25
|
+
'options': 'options',
|
|
26
|
+
'env-vars': 'envVars',
|
|
27
|
+
'envvars': 'envVars',
|
|
28
|
+
'examples': 'examples',
|
|
29
|
+
// Legacy --only-* flags (still work, map into same system)
|
|
30
|
+
'only-methods': 'methods',
|
|
31
|
+
'only-getters': 'getters',
|
|
32
|
+
'only-events': 'events',
|
|
33
|
+
'only-state': 'state',
|
|
34
|
+
'only-options': 'options',
|
|
35
|
+
'only-env-vars': 'envVars',
|
|
36
|
+
'only-envvars': 'envVars',
|
|
37
|
+
'only-examples': 'examples',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
41
|
+
json: z.boolean().default(false).describe('Output introspection data as JSON instead of markdown'),
|
|
42
|
+
pretty: z.boolean().default(false).describe('Render markdown with terminal styling via ui.markdown'),
|
|
43
|
+
title: z.boolean().default(true).describe('Include the title header in the output (use --no-title to omit)'),
|
|
44
|
+
// Clean section flags (can be combined: --description --usage)
|
|
45
|
+
description: z.boolean().default(false).describe('Show the description section'),
|
|
46
|
+
usage: z.boolean().default(false).describe('Show the usage section'),
|
|
47
|
+
methods: z.boolean().default(false).describe('Show the methods section'),
|
|
48
|
+
getters: z.boolean().default(false).describe('Show the getters section'),
|
|
49
|
+
events: z.boolean().default(false).describe('Show the events section'),
|
|
50
|
+
state: z.boolean().default(false).describe('Show the state section'),
|
|
51
|
+
options: z.boolean().default(false).describe('Show the options section'),
|
|
52
|
+
'env-vars': z.boolean().default(false).describe('Show the envVars section'),
|
|
53
|
+
envvars: z.boolean().default(false).describe('Show the envVars section'),
|
|
54
|
+
examples: z.boolean().default(false).describe('Show the examples section'),
|
|
55
|
+
// Legacy --only-* flags
|
|
56
|
+
'only-methods': z.boolean().default(false).describe('Show only the methods section'),
|
|
57
|
+
'only-getters': z.boolean().default(false).describe('Show only the getters section'),
|
|
58
|
+
'only-events': z.boolean().default(false).describe('Show only the events section'),
|
|
59
|
+
'only-state': z.boolean().default(false).describe('Show only the state section'),
|
|
60
|
+
'only-options': z.boolean().default(false).describe('Show only the options section'),
|
|
61
|
+
'only-env-vars': z.boolean().default(false).describe('Show only the envVars section'),
|
|
62
|
+
'only-envvars': z.boolean().default(false).describe('Show only the envVars section'),
|
|
63
|
+
'only-examples': z.boolean().default(false).describe('Show only the examples section'),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
type ResolvedTarget =
|
|
67
|
+
| { kind: 'container' }
|
|
68
|
+
| { kind: 'registry'; name: RegistryName }
|
|
69
|
+
| { kind: 'helper'; registry: RegistryName; id: string }
|
|
70
|
+
|
|
71
|
+
class DescribeError extends Error {
|
|
72
|
+
constructor(message: string) {
|
|
73
|
+
super(message)
|
|
74
|
+
this.name = 'DescribeError'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Normalize an identifier to a comparable form by stripping file extensions,
|
|
80
|
+
* converting kebab-case and snake_case to lowercase-no-separators.
|
|
81
|
+
* e.g. "disk-cache.ts" | "diskCache" | "disk_cache" → "diskcache"
|
|
82
|
+
*/
|
|
83
|
+
function normalize(name: string): string {
|
|
84
|
+
return name
|
|
85
|
+
.replace(/\.[tj]sx?$/, '') // strip .ts/.js/.tsx/.jsx
|
|
86
|
+
.replace(/[-_]/g, '') // remove dashes and underscores
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find a registry entry by normalized name.
|
|
92
|
+
* Returns the canonical registered id, or undefined if no match.
|
|
93
|
+
*/
|
|
94
|
+
function fuzzyFind(registry: any, input: string): string | undefined {
|
|
95
|
+
// Exact match first
|
|
96
|
+
if (registry.has(input)) return input
|
|
97
|
+
|
|
98
|
+
const norm = normalize(input)
|
|
99
|
+
return (registry.available as string[]).find((id: string) => normalize(id) === norm)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse a single target string into a resolved target.
|
|
104
|
+
* Accepts: "container", "features", "features.fs", "fs", etc.
|
|
105
|
+
*/
|
|
106
|
+
function resolveTarget(target: string, container: any): ResolvedTarget {
|
|
107
|
+
const lower = target.toLowerCase()
|
|
108
|
+
|
|
109
|
+
// "container" or "self"
|
|
110
|
+
if (lower === 'container' || lower === 'self') {
|
|
111
|
+
return { kind: 'container' }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Registry name: "features", "clients", "servers", "commands", "endpoints"
|
|
115
|
+
const registryMatch = REGISTRY_NAMES.find(
|
|
116
|
+
(r) => r === lower || r === lower + 's' || r.replace(/s$/, '') === lower
|
|
117
|
+
)
|
|
118
|
+
if (registryMatch && !target.includes('.')) {
|
|
119
|
+
return { kind: 'registry', name: registryMatch }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Qualified name: "features.fs", "clients.rest", etc.
|
|
123
|
+
if (target.includes('.')) {
|
|
124
|
+
const [prefix, ...rest] = target.split('.')
|
|
125
|
+
const id = rest.join('.')
|
|
126
|
+
const registry = REGISTRY_NAMES.find(
|
|
127
|
+
(r) => r === prefix!.toLowerCase() || r === prefix!.toLowerCase() + 's' || r.replace(/s$/, '') === prefix!.toLowerCase()
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if (registry) {
|
|
131
|
+
const reg = container[registry]
|
|
132
|
+
const resolved = fuzzyFind(reg, id)
|
|
133
|
+
if (!resolved) {
|
|
134
|
+
throw new DescribeError(`"${id}" is not registered in ${registry}. Available: ${reg.available.join(', ')}`)
|
|
135
|
+
}
|
|
136
|
+
return { kind: 'helper', registry, id: resolved }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Unqualified name: search all registries (fuzzy)
|
|
141
|
+
const matches: { registry: RegistryName; id: string }[] = []
|
|
142
|
+
for (const registryName of REGISTRY_NAMES) {
|
|
143
|
+
const reg = container[registryName]
|
|
144
|
+
if (!reg) continue
|
|
145
|
+
const found = fuzzyFind(reg, target)
|
|
146
|
+
if (found) {
|
|
147
|
+
matches.push({ registry: registryName, id: found })
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (matches.length === 0) {
|
|
152
|
+
const lines = [`"${target}" was not found in any registry.`, '', 'Available:']
|
|
153
|
+
for (const registryName of REGISTRY_NAMES) {
|
|
154
|
+
const reg = container[registryName]
|
|
155
|
+
if (reg && reg.available.length > 0) {
|
|
156
|
+
lines.push(` ${registryName}: ${reg.available.join(', ')}`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
throw new DescribeError(lines.join('\n'))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (matches.length > 1) {
|
|
163
|
+
const lines = [`"${target}" is ambiguous — found in multiple registries:`]
|
|
164
|
+
for (const m of matches) {
|
|
165
|
+
lines.push(` ${m.registry}.${m.id}`)
|
|
166
|
+
}
|
|
167
|
+
lines.push('', `Please qualify it, e.g.: ${matches[0]!.registry}.${target}`)
|
|
168
|
+
throw new DescribeError(lines.join('\n'))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { kind: 'helper', registry: matches[0]!.registry, id: matches[0]!.id }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Collect all requested sections from flags. Empty array = show everything. */
|
|
175
|
+
function getSections(options: z.infer<typeof argsSchema>): (IntrospectionSection | 'description')[] {
|
|
176
|
+
const sections: (IntrospectionSection | 'description')[] = []
|
|
177
|
+
for (const [flag, section] of Object.entries(SECTION_FLAGS)) {
|
|
178
|
+
if ((options as any)[flag] && !sections.includes(section)) {
|
|
179
|
+
sections.push(section)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return sections
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Build the title header for a helper. Includes className when available.
|
|
187
|
+
* headingDepth controls the markdown heading level (1 = #, 2 = ##, etc.)
|
|
188
|
+
*/
|
|
189
|
+
function renderTitle(Ctor: any, headingDepth = 1): string {
|
|
190
|
+
const data = Ctor.introspect?.()
|
|
191
|
+
const id = data?.id || Ctor.shortcut || Ctor.name
|
|
192
|
+
const className = data?.className || Ctor.name
|
|
193
|
+
const h = '#'.repeat(headingDepth)
|
|
194
|
+
return className ? `${h} ${className} (${id})` : `${h} ${id}`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Render text output for a helper given requested sections.
|
|
199
|
+
* When sections is empty, renders everything. When sections are specified,
|
|
200
|
+
* renders only those sections (calling introspectAsText per section and concatenating).
|
|
201
|
+
* 'description' is handled specially as the description paragraph (title is always included).
|
|
202
|
+
* Pass noTitle to suppress the title header.
|
|
203
|
+
* headingDepth controls the starting heading level (1 = #, 2 = ##, etc.)
|
|
204
|
+
*/
|
|
205
|
+
function renderHelperText(Ctor: any, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): string {
|
|
206
|
+
if (sections.length === 0) {
|
|
207
|
+
if (noTitle) {
|
|
208
|
+
// Render everything except the title
|
|
209
|
+
const data = Ctor.introspect?.()
|
|
210
|
+
if (!data) return 'No introspection data available.'
|
|
211
|
+
const parts: string[] = [data.description]
|
|
212
|
+
const text = Ctor.introspectAsText?.(headingDepth)
|
|
213
|
+
if (text) {
|
|
214
|
+
// Strip the first heading + description block that introspectAsText renders
|
|
215
|
+
const lines = text.split('\n')
|
|
216
|
+
const headingPrefix = '#'.repeat(headingDepth + 1) + ' '
|
|
217
|
+
let startIdx = 0
|
|
218
|
+
for (let i = 0; i < lines.length; i++) {
|
|
219
|
+
if (i > 0 && lines[i]!.startsWith(headingPrefix)) {
|
|
220
|
+
startIdx = i
|
|
221
|
+
break
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (startIdx > 0) {
|
|
225
|
+
parts.length = 0
|
|
226
|
+
parts.push(data.description)
|
|
227
|
+
parts.push(lines.slice(startIdx).join('\n'))
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return parts.join('\n\n')
|
|
231
|
+
}
|
|
232
|
+
return Ctor.introspectAsText?.(headingDepth) ?? `${renderTitle(Ctor, headingDepth)}\n\nNo introspection data available.`
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const introspectionSections = sections.filter((s): s is IntrospectionSection => s !== 'description')
|
|
236
|
+
const parts: string[] = []
|
|
237
|
+
|
|
238
|
+
// Always include the title and description unless noTitle
|
|
239
|
+
if (!noTitle) {
|
|
240
|
+
const data = Ctor.introspect?.()
|
|
241
|
+
parts.push(renderTitle(Ctor, headingDepth))
|
|
242
|
+
if (data?.description) {
|
|
243
|
+
parts.push(data.description)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const section of introspectionSections) {
|
|
248
|
+
const text = Ctor.introspectAsText?.(section, headingDepth)
|
|
249
|
+
if (text) parts.push(text)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return parts.join('\n\n') || `${noTitle ? '' : renderTitle(Ctor, headingDepth) + '\n\n'}No introspection data available.`
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderHelperJson(Ctor: any, sections: (IntrospectionSection | 'description')[], noTitle = false): any {
|
|
256
|
+
if (sections.length === 0) {
|
|
257
|
+
return Ctor.introspect?.() ?? {}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const data = Ctor.introspect?.() ?? {}
|
|
261
|
+
const result: Record<string, any> = {}
|
|
262
|
+
|
|
263
|
+
// Always include id and className in JSON unless noTitle
|
|
264
|
+
if (!noTitle) {
|
|
265
|
+
result.id = data.id
|
|
266
|
+
if (data.className) result.className = data.className
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const section of sections) {
|
|
270
|
+
if (section === 'description') {
|
|
271
|
+
result.id = data.id
|
|
272
|
+
if (data.className) result.className = data.className
|
|
273
|
+
result.description = data.description
|
|
274
|
+
} else if (section === 'usage') {
|
|
275
|
+
// Usage is a derived section — include shortcut and options as its JSON form
|
|
276
|
+
result.usage = { shortcut: data.shortcut, options: data.options }
|
|
277
|
+
} else {
|
|
278
|
+
const sectionData = Ctor.introspect?.(section)
|
|
279
|
+
if (sectionData) {
|
|
280
|
+
result[section] = sectionData[section]
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return result
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getContainerData(container: any, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
|
|
289
|
+
if (sections.length === 0) {
|
|
290
|
+
const data = container.inspect()
|
|
291
|
+
return { json: data, text: container.inspectAsText(undefined, headingDepth) }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const data = container.inspect()
|
|
295
|
+
const introspectionSections = sections.filter((s): s is IntrospectionSection => s !== 'description')
|
|
296
|
+
const textParts: string[] = []
|
|
297
|
+
const jsonResult: Record<string, any> = {}
|
|
298
|
+
const h = '#'.repeat(headingDepth)
|
|
299
|
+
|
|
300
|
+
// Always include container title and description unless noTitle
|
|
301
|
+
if (!noTitle) {
|
|
302
|
+
const className = data.className || 'Container'
|
|
303
|
+
textParts.push(`${h} ${className} (Container)`)
|
|
304
|
+
jsonResult.className = className
|
|
305
|
+
if (data.description) {
|
|
306
|
+
textParts.push(data.description)
|
|
307
|
+
jsonResult.description = data.description
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for (const section of introspectionSections) {
|
|
312
|
+
textParts.push(container.inspectAsText(section, headingDepth))
|
|
313
|
+
jsonResult[section] = data[section]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
json: jsonResult,
|
|
318
|
+
text: textParts.join('\n\n'),
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function getRegistryData(container: any, registryName: RegistryName, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
|
|
323
|
+
const registry = container[registryName]
|
|
324
|
+
const available: string[] = registry.available
|
|
325
|
+
|
|
326
|
+
if (available.length === 0) {
|
|
327
|
+
return { json: {}, text: `No ${registryName} are registered.` }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const jsonResult: Record<string, any> = {}
|
|
331
|
+
const textParts: string[] = []
|
|
332
|
+
for (const id of available) {
|
|
333
|
+
const Ctor = registry.lookup(id)
|
|
334
|
+
jsonResult[id] = renderHelperJson(Ctor, sections, noTitle)
|
|
335
|
+
textParts.push(renderHelperText(Ctor, sections, noTitle, headingDepth))
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return { json: jsonResult, text: textParts.join('\n\n---\n\n') }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function getHelperData(container: any, registryName: RegistryName, id: string, sections: (IntrospectionSection | 'description')[], noTitle = false, headingDepth = 1): { json: any; text: string } {
|
|
342
|
+
const registry = container[registryName]
|
|
343
|
+
const Ctor = registry.lookup(id)
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
json: renderHelperJson(Ctor, sections, noTitle),
|
|
347
|
+
text: renderHelperText(Ctor, sections, noTitle, headingDepth),
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export default async function describe(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
352
|
+
const container = context.container as any
|
|
353
|
+
|
|
354
|
+
await container.helpers.discoverAll()
|
|
355
|
+
|
|
356
|
+
const args = container.argv._ as string[]
|
|
357
|
+
// args[0] is "describe", the rest are targets
|
|
358
|
+
const targets = args.slice(1)
|
|
359
|
+
const json = options.json
|
|
360
|
+
const pretty = options.pretty
|
|
361
|
+
const noTitle = !options.title
|
|
362
|
+
const sections = getSections(options)
|
|
363
|
+
|
|
364
|
+
function output(text: string) {
|
|
365
|
+
if (pretty) {
|
|
366
|
+
const ui = container.feature('ui')
|
|
367
|
+
console.log(ui.markdown(text))
|
|
368
|
+
} else {
|
|
369
|
+
console.log(text)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// No targets: show help screen
|
|
374
|
+
if (targets.length === 0) {
|
|
375
|
+
const { formatCommandHelp } = await import('./help.js')
|
|
376
|
+
const ui = container.feature('ui') as any
|
|
377
|
+
const Cmd = container.commands.lookup('describe')
|
|
378
|
+
console.log(formatCommandHelp('describe', Cmd, ui.colors))
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const resolved: ResolvedTarget[] = []
|
|
383
|
+
|
|
384
|
+
for (const target of targets) {
|
|
385
|
+
try {
|
|
386
|
+
resolved.push(resolveTarget(target, container))
|
|
387
|
+
} catch (err: any) {
|
|
388
|
+
if (err instanceof DescribeError) {
|
|
389
|
+
console.error(err.message)
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
throw err
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Multiple docs when there are multiple targets or any target is a registry
|
|
397
|
+
const isMulti = resolved.length > 1 || resolved.some((r) => r.kind === 'registry')
|
|
398
|
+
const headingDepth = isMulti ? 2 : 1
|
|
399
|
+
|
|
400
|
+
function getData(item: ResolvedTarget) {
|
|
401
|
+
switch (item.kind) {
|
|
402
|
+
case 'container':
|
|
403
|
+
return getContainerData(container, sections, noTitle, headingDepth)
|
|
404
|
+
case 'registry':
|
|
405
|
+
return getRegistryData(container, item.name, sections, noTitle, headingDepth)
|
|
406
|
+
case 'helper':
|
|
407
|
+
return getHelperData(container, item.registry, item.id, sections, noTitle, headingDepth)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (json) {
|
|
412
|
+
if (resolved.length === 1) {
|
|
413
|
+
console.log(JSON.stringify(getData(resolved[0]!).json, null, 2))
|
|
414
|
+
} else {
|
|
415
|
+
const combined = resolved.map((item) => getData(item).json)
|
|
416
|
+
console.log(JSON.stringify(combined, null, 2))
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
const parts = resolved.map((item) => getData(item).text)
|
|
420
|
+
const body = parts.join('\n\n---\n\n')
|
|
421
|
+
if (isMulti) {
|
|
422
|
+
output(`# Luca Helper Descriptions\n\nBelow you'll find documentation.\n\n${body}`)
|
|
423
|
+
} else {
|
|
424
|
+
output(body)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
commands.registerHandler('describe', {
|
|
430
|
+
description: 'Describe the container, registries, or individual helpers',
|
|
431
|
+
argsSchema,
|
|
432
|
+
handler: describe,
|
|
433
|
+
})
|