@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,257 @@
|
|
|
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
|
+
run: ReturnType<typeof commands.registerHandler>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
13
|
+
safe: z.boolean().default(false).describe('Require approval before each code block (markdown mode)'),
|
|
14
|
+
console: z.boolean().default(false).describe('Start an interactive REPL after executing a markdown file, with all accumulated context'),
|
|
15
|
+
onlySections: z.string().optional().describe('Comma-separated list of section headings to run (case-insensitive, markdown only)'),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
function resolveScript(ref: string, context: ContainerContext): string | null {
|
|
19
|
+
const container = context.container as any
|
|
20
|
+
const candidates = [
|
|
21
|
+
ref,
|
|
22
|
+
`${ref}.ts`,
|
|
23
|
+
`${ref}.js`,
|
|
24
|
+
`${ref}.md`,
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
for (const candidate of candidates) {
|
|
28
|
+
const resolved = container.paths.resolve(candidate)
|
|
29
|
+
if (container.fs.exists(resolved)) return resolved
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Find the ## Blocks section in the AST and return the indices to skip
|
|
37
|
+
* plus the tsx/jsx code block values to register.
|
|
38
|
+
*/
|
|
39
|
+
function extractBlocksSection(children: any[]): { skipIndices: Set<number>, blockSources: string[] } {
|
|
40
|
+
const skipIndices = new Set<number>()
|
|
41
|
+
const blockSources: string[] = []
|
|
42
|
+
|
|
43
|
+
let inBlocks = false
|
|
44
|
+
for (let i = 0; i < children.length; i++) {
|
|
45
|
+
const node = children[i]
|
|
46
|
+
|
|
47
|
+
if (node.type === 'heading' && node.depth === 2) {
|
|
48
|
+
const text = (node.children || []).map((c: any) => c.value || '').join('').trim()
|
|
49
|
+
if (text === 'Blocks') {
|
|
50
|
+
inBlocks = true
|
|
51
|
+
skipIndices.add(i)
|
|
52
|
+
continue
|
|
53
|
+
} else if (inBlocks) {
|
|
54
|
+
// hit the next ## heading — stop collecting
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (inBlocks) {
|
|
60
|
+
skipIndices.add(i)
|
|
61
|
+
if (node.type === 'code' && (node.lang === 'tsx' || node.lang === 'jsx')) {
|
|
62
|
+
blockSources.push(node.value)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { skipIndices, blockSources }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function runMarkdown(scriptPath: string, options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
71
|
+
console.clear()
|
|
72
|
+
const container = context.container as any
|
|
73
|
+
const requireApproval = options.safe
|
|
74
|
+
await container.docs.load()
|
|
75
|
+
|
|
76
|
+
const doc = await container.docs.parseMarkdownAtPath(scriptPath)
|
|
77
|
+
|
|
78
|
+
const esbuild = container.feature('esbuild')
|
|
79
|
+
const ink = container.feature('ink', { enable: true })
|
|
80
|
+
await ink.loadModules()
|
|
81
|
+
|
|
82
|
+
const vm = container.feature('vm')
|
|
83
|
+
const render = async (name: string, data?: any) => ink.renderBlock(name, data)
|
|
84
|
+
const renderAsync = async (name: string, data?: any, options?: { timeout?: number }) => ink.renderBlockAsync(name, data, options)
|
|
85
|
+
const shared = vm.createContext({
|
|
86
|
+
console, ink, render, renderAsync,
|
|
87
|
+
setTimeout, clearTimeout, setInterval, clearInterval,
|
|
88
|
+
fetch, URL, URLSearchParams,
|
|
89
|
+
...container.context,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// ─── Parse and register ## Blocks section ──────────────────────────
|
|
93
|
+
const { skipIndices, blockSources } = extractBlocksSection(doc.ast.children)
|
|
94
|
+
|
|
95
|
+
for (const source of blockSources) {
|
|
96
|
+
const keysBefore = new Set(Object.keys(shared))
|
|
97
|
+
const { code: transformed } = esbuild.transformSync(source, { loader: 'tsx', format: 'cjs' })
|
|
98
|
+
|
|
99
|
+
const hasTopLevelAwait = /\bawait\b/.test(transformed)
|
|
100
|
+
const wrapped = hasTopLevelAwait
|
|
101
|
+
? `(async function() { ${transformed} })()`
|
|
102
|
+
: transformed
|
|
103
|
+
|
|
104
|
+
await vm.run(wrapped, shared)
|
|
105
|
+
|
|
106
|
+
// auto-register any new functions as blocks
|
|
107
|
+
for (const key of Object.keys(shared)) {
|
|
108
|
+
if (!keysBefore.has(key) && typeof shared[key] === 'function') {
|
|
109
|
+
ink.registerBlock(key, shared[key])
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Build section filter from --only-sections ───────────────────
|
|
115
|
+
let allowedIndices: Set<number> | null = null
|
|
116
|
+
if (options.onlySections) {
|
|
117
|
+
const requestedSections = options.onlySections.split(',').map(s => s.trim())
|
|
118
|
+
allowedIndices = new Set<number>()
|
|
119
|
+
for (const sectionName of requestedSections) {
|
|
120
|
+
try {
|
|
121
|
+
const sectionNodes = doc.extractSection(sectionName)
|
|
122
|
+
for (const node of sectionNodes) {
|
|
123
|
+
const idx = doc.ast.children.indexOf(node as any)
|
|
124
|
+
if (idx !== -1) allowedIndices.add(idx)
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// Section not found — skip silently
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── Execute document ──────────────────────────────────────────────
|
|
133
|
+
const children = doc.ast.children
|
|
134
|
+
for (let i = 0; i < children.length; i++) {
|
|
135
|
+
if (skipIndices.has(i)) continue
|
|
136
|
+
if (allowedIndices && !allowedIndices.has(i)) continue
|
|
137
|
+
|
|
138
|
+
const node = children[i]
|
|
139
|
+
if (node.type === 'code') {
|
|
140
|
+
const { value, lang, meta } = node
|
|
141
|
+
|
|
142
|
+
if (lang !== 'ts' && lang !== 'js' && lang !== 'tsx' && lang !== 'jsx') {
|
|
143
|
+
console.log(container.ui.markdown(['```' + (lang || ''), value, '```'].join('\n')))
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (meta && typeof meta === 'string' && meta.toLowerCase().includes('skip')) continue
|
|
148
|
+
|
|
149
|
+
console.log(container.ui.markdown(['```' + lang, value, '```'].join('\n')))
|
|
150
|
+
|
|
151
|
+
if (requireApproval) {
|
|
152
|
+
const answer = await container.ui.askQuestion('Run this block? (y/n)')
|
|
153
|
+
if (answer.question.toLowerCase() !== 'y') continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Transform tsx/jsx through esbuild, and also ts for consistency
|
|
157
|
+
const needsTransform = lang === 'tsx' || lang === 'jsx'
|
|
158
|
+
let code = value
|
|
159
|
+
|
|
160
|
+
if (needsTransform) {
|
|
161
|
+
const { code: transformed } = esbuild.transformSync(value, {
|
|
162
|
+
loader: lang as 'tsx' | 'jsx',
|
|
163
|
+
format: 'cjs',
|
|
164
|
+
})
|
|
165
|
+
code = transformed
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const hasTopLevelAwait = /\bawait\b/.test(code)
|
|
169
|
+
code = hasTopLevelAwait
|
|
170
|
+
? `(async function() { ${code} })()`
|
|
171
|
+
: code
|
|
172
|
+
|
|
173
|
+
await vm.run(code, shared)
|
|
174
|
+
|
|
175
|
+
// if we enabled any features, they will be in the context object
|
|
176
|
+
Object.assign(shared, container.context)
|
|
177
|
+
} else {
|
|
178
|
+
const md = doc.stringify({ type: 'root', children: [node] })
|
|
179
|
+
console.log(container.ui.markdown(md))
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return shared
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function runScript(scriptPath: string, context: ContainerContext) {
|
|
187
|
+
const container = context.container as any
|
|
188
|
+
|
|
189
|
+
const { exitCode, stderr } = await container.proc.runScript(scriptPath)
|
|
190
|
+
|
|
191
|
+
if (exitCode === 0) return
|
|
192
|
+
|
|
193
|
+
console.error(`\nScript failed with exit code ${exitCode}.\n`)
|
|
194
|
+
if (stderr.length) {
|
|
195
|
+
console.error(stderr.join('\n'))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function diagnoseError(_scriptPath: string, error: Error, _context: ContainerContext) {
|
|
200
|
+
console.error(`\n${error.stack || error.message}\n`)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export default async function run(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
204
|
+
const container = context.container as any
|
|
205
|
+
const fileRef = container.argv._[1] as string
|
|
206
|
+
|
|
207
|
+
if (!fileRef) {
|
|
208
|
+
console.error('Usage: luca run <file>')
|
|
209
|
+
process.exit(1)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const scriptPath = resolveScript(fileRef, context)
|
|
213
|
+
|
|
214
|
+
if (!scriptPath) {
|
|
215
|
+
console.error(`Could not find script: ${fileRef}`)
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
if (scriptPath.endsWith('.md')) {
|
|
221
|
+
const shared = await runMarkdown(scriptPath, options, context)
|
|
222
|
+
|
|
223
|
+
if (options.console) {
|
|
224
|
+
const ui = container.feature('ui')
|
|
225
|
+
const prompt = ui.colors.cyan('luca') + ui.colors.dim(' > ')
|
|
226
|
+
|
|
227
|
+
console.log()
|
|
228
|
+
console.log(ui.colors.dim(' Entering REPL with markdown context. Type .exit to quit.'))
|
|
229
|
+
console.log()
|
|
230
|
+
|
|
231
|
+
const repl = container.feature('repl', { prompt })
|
|
232
|
+
await repl.start({
|
|
233
|
+
context: {
|
|
234
|
+
...shared,
|
|
235
|
+
console,
|
|
236
|
+
setTimeout,
|
|
237
|
+
setInterval,
|
|
238
|
+
clearTimeout,
|
|
239
|
+
clearInterval,
|
|
240
|
+
fetch,
|
|
241
|
+
Bun,
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
await runScript(scriptPath, context)
|
|
247
|
+
}
|
|
248
|
+
} catch (err: any) {
|
|
249
|
+
await diagnoseError(scriptPath, err instanceof Error ? err : new Error(String(err)), context)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
commands.registerHandler('run', {
|
|
254
|
+
description: 'Run a script or markdown file (.ts, .js, .md)',
|
|
255
|
+
argsSchema,
|
|
256
|
+
handler: run,
|
|
257
|
+
})
|
|
@@ -0,0 +1,439 @@
|
|
|
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 { MCPServer } from '../servers/mcp.js'
|
|
6
|
+
import { scaffolds, mcpReadme } from '../scaffolds/generated.js'
|
|
7
|
+
import { generateScaffold } from '../scaffolds/template.js'
|
|
8
|
+
|
|
9
|
+
declare module '../command.js' {
|
|
10
|
+
interface AvailableCommands {
|
|
11
|
+
'sandbox-mcp': ReturnType<typeof commands.registerHandler>
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
16
|
+
transport: z.enum(['stdio', 'http']).default('stdio').describe('Transport type (stdio or http)'),
|
|
17
|
+
port: z.number().default(3002).describe('Port for HTTP transport'),
|
|
18
|
+
mcpCompat: z.enum(['standard', 'codex']).optional()
|
|
19
|
+
.describe('HTTP compatibility profile. Defaults to standard. Can also be set via MCP_HTTP_COMPAT.'),
|
|
20
|
+
stdioCompat: z.enum(['standard', 'codex', 'auto']).optional()
|
|
21
|
+
.describe('Stdio framing compatibility profile. Defaults to standard. Can also be set via MCP_STDIO_COMPAT.'),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export default async function mcpSandbox(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
25
|
+
const container = context.container as any
|
|
26
|
+
const envCompat = process.env.MCP_HTTP_COMPAT?.toLowerCase()
|
|
27
|
+
const resolvedCompat = options.mcpCompat || (envCompat === 'codex' ? 'codex' : 'standard')
|
|
28
|
+
const envStdioCompat = process.env.MCP_STDIO_COMPAT?.toLowerCase()
|
|
29
|
+
const resolvedStdioCompat = options.stdioCompat
|
|
30
|
+
|| (envStdioCompat === 'codex' || envStdioCompat === 'auto' ? envStdioCompat : 'standard')
|
|
31
|
+
|
|
32
|
+
const mcpServer = container.server('mcp', {
|
|
33
|
+
transport: options.transport,
|
|
34
|
+
port: options.port,
|
|
35
|
+
serverName: 'luca-sandbox',
|
|
36
|
+
serverVersion: container.manifest?.version || '1.0.0',
|
|
37
|
+
mcpCompat: options.mcpCompat,
|
|
38
|
+
stdioCompat: options.stdioCompat,
|
|
39
|
+
}) as MCPServer
|
|
40
|
+
|
|
41
|
+
// Persistent VM context shared across eval calls so variables survive between invocations
|
|
42
|
+
const vmFeature = container.feature('vm')
|
|
43
|
+
const sandboxContext = vmFeature.createContext({
|
|
44
|
+
container,
|
|
45
|
+
console: {
|
|
46
|
+
log: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
|
|
47
|
+
error: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
|
|
48
|
+
warn: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
|
|
49
|
+
},
|
|
50
|
+
setTimeout,
|
|
51
|
+
setInterval,
|
|
52
|
+
clearTimeout,
|
|
53
|
+
clearInterval,
|
|
54
|
+
fetch,
|
|
55
|
+
Bun,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Pre-populate sandbox with all enabled features
|
|
59
|
+
for (const name of container.features.available) {
|
|
60
|
+
try {
|
|
61
|
+
vmFeature.runSync(`var ${name} = container.feature('${name}')`, sandboxContext)
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- Tool: read_me ---
|
|
66
|
+
mcpServer.tool('read_me', {
|
|
67
|
+
description: [
|
|
68
|
+
'Returns the Luca framework development guide. Call this BEFORE writing any code in a luca project.',
|
|
69
|
+
'Contains the import conventions, capability map, and workflow for discovering and using container features.',
|
|
70
|
+
'You should call this tool at the start of every session.',
|
|
71
|
+
].join('\n'),
|
|
72
|
+
schema: z.object({}),
|
|
73
|
+
handler: () => mcpReadme,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// --- Tool: find_capability ---
|
|
77
|
+
mcpServer.tool('find_capability', {
|
|
78
|
+
description: [
|
|
79
|
+
'Search for container capabilities by intent. Returns the full catalog of available features, clients,',
|
|
80
|
+
'and servers with their descriptions so you can find what you need before writing code.',
|
|
81
|
+
'Call this when you need to do something and aren\'t sure which helper provides it.',
|
|
82
|
+
'Prefer this over installing npm packages — the container likely already has what you need.',
|
|
83
|
+
].join('\n'),
|
|
84
|
+
schema: z.object({}),
|
|
85
|
+
handler: () => {
|
|
86
|
+
const sections: string[] = []
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
sections.push(container.features.describeAll())
|
|
90
|
+
} catch {}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
sections.push(container.clients.describeAll())
|
|
94
|
+
} catch {}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
sections.push(container.servers.describeAll())
|
|
98
|
+
} catch {}
|
|
99
|
+
|
|
100
|
+
return sections.join('\n\n---\n\n')
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// --- Tool: scaffold ---
|
|
105
|
+
mcpServer.tool('scaffold', {
|
|
106
|
+
description: [
|
|
107
|
+
'Generate correct boilerplate for a new luca helper (feature, client, server, command, or endpoint).',
|
|
108
|
+
'Returns the complete file content with your name and description filled in.',
|
|
109
|
+
'Write this to a file, then fill in your implementation.',
|
|
110
|
+
'The scaffold follows all luca conventions including schemas, jsdoc, module augmentation, and registration.',
|
|
111
|
+
].join('\n'),
|
|
112
|
+
schema: z.object({
|
|
113
|
+
type: z.enum(['feature', 'client', 'server', 'command', 'endpoint'])
|
|
114
|
+
.describe('What kind of helper to scaffold'),
|
|
115
|
+
name: z.string()
|
|
116
|
+
.describe('Name for the helper (e.g. "diskCache", "myApi", "healthCheck")'),
|
|
117
|
+
description: z.string().optional()
|
|
118
|
+
.describe('Brief description of what this helper does'),
|
|
119
|
+
}),
|
|
120
|
+
handler: (args) => {
|
|
121
|
+
const result = generateScaffold(args.type, args.name, args.description)
|
|
122
|
+
return result || `No scaffold template available for type: ${args.type}`
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// --- Tool: eval ---
|
|
127
|
+
mcpServer.tool('eval', {
|
|
128
|
+
description: [
|
|
129
|
+
'Evaluate JavaScript/TypeScript code in a Luca container sandbox.',
|
|
130
|
+
'Use this to prototype and test container API calls before writing them into files.',
|
|
131
|
+
'The sandbox has all features available as top-level variables.',
|
|
132
|
+
'',
|
|
133
|
+
'The sandbox has a live `container` object and all enabled features as top-level variables',
|
|
134
|
+
'(e.g. `fs`, `git`, `ui`, `vm`, `proc`, `networking`, etc).',
|
|
135
|
+
'',
|
|
136
|
+
'Variables you define persist across calls, so you can build up state incrementally.',
|
|
137
|
+
'',
|
|
138
|
+
'The result of the last expression is returned. For async code, use `await`.',
|
|
139
|
+
'',
|
|
140
|
+
'Quick reference:',
|
|
141
|
+
' container.features.available — list available feature names',
|
|
142
|
+
' container.clients.available — list available client names',
|
|
143
|
+
' container.servers.available — list available server names',
|
|
144
|
+
' container.commands.available — list available command names',
|
|
145
|
+
' container.features.describe(n) — get docs for a feature by name',
|
|
146
|
+
' container.clients.describe(n) — get docs for a client by name',
|
|
147
|
+
' container.inspectAsText() — full container introspection',
|
|
148
|
+
' fs.readFile(path) — read a file',
|
|
149
|
+
' fs.readdir(dir) — list directory contents',
|
|
150
|
+
' proc.exec(cmd) — run a shell command',
|
|
151
|
+
].join('\n'),
|
|
152
|
+
schema: z.object({
|
|
153
|
+
code: z.string().describe('JavaScript code to evaluate in the Luca container sandbox'),
|
|
154
|
+
}),
|
|
155
|
+
handler: async (args) => {
|
|
156
|
+
try {
|
|
157
|
+
// Wrap code containing top-level await in an async IIFE so the VM can handle it.
|
|
158
|
+
// Try to return the last expression's value by prepending `return` to the last statement.
|
|
159
|
+
let code = args.code
|
|
160
|
+
if (/\bawait\b/.test(code) && !/^\s*\(?\s*async\b/.test(code)) {
|
|
161
|
+
const lines = code.split('\n')
|
|
162
|
+
const lastLine = lines[lines.length - 1]
|
|
163
|
+
// If the last line doesn't start with a keyword that can't be returned, add return
|
|
164
|
+
if (!/^\s*(var|let|const|if|for|while|switch|try|throw|class|function)\b/.test(lastLine)) {
|
|
165
|
+
lines[lines.length - 1] = `return ${lastLine}`
|
|
166
|
+
}
|
|
167
|
+
code = `(async () => { ${lines.join('\n')} })()`
|
|
168
|
+
}
|
|
169
|
+
const result = await vmFeature.run(code, sandboxContext)
|
|
170
|
+
|
|
171
|
+
let text: string
|
|
172
|
+
if (result === undefined) {
|
|
173
|
+
text = 'undefined'
|
|
174
|
+
} else if (result === null) {
|
|
175
|
+
text = 'null'
|
|
176
|
+
} else if (typeof result === 'string') {
|
|
177
|
+
text = result
|
|
178
|
+
} else if (typeof result === 'object') {
|
|
179
|
+
try {
|
|
180
|
+
text = JSON.stringify(result, null, 2)
|
|
181
|
+
} catch {
|
|
182
|
+
text = String(result)
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
text = String(result)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { content: [{ type: 'text' as const, text }] }
|
|
189
|
+
} catch (error: any) {
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: 'text' as const, text: `Error: ${error.message}\n\n${error.stack || ''}` }],
|
|
192
|
+
isError: true,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// --- Tool: inspect_container ---
|
|
199
|
+
mcpServer.tool('inspect_container', {
|
|
200
|
+
description: [
|
|
201
|
+
'Inspect the Luca container — registries, state, methods, events, environment.',
|
|
202
|
+
'',
|
|
203
|
+
'Returns a markdown overview of the container. Optionally filter to a specific section.',
|
|
204
|
+
'',
|
|
205
|
+
'Sections: "methods", "getters", "events", "state", "options", "envVars"',
|
|
206
|
+
'Leave section empty for the full overview.',
|
|
207
|
+
].join('\n'),
|
|
208
|
+
schema: z.object({
|
|
209
|
+
section: z.enum(['methods', 'getters', 'events', 'state', 'options', 'envVars']).optional()
|
|
210
|
+
.describe('Optional section to filter to. Omit for full overview.'),
|
|
211
|
+
}),
|
|
212
|
+
handler: (args) => {
|
|
213
|
+
return container.inspectAsText(args.section)
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// --- Tool: list_registry ---
|
|
218
|
+
mcpServer.tool('list_registry', {
|
|
219
|
+
description: [
|
|
220
|
+
'List available items in a container registry.',
|
|
221
|
+
'',
|
|
222
|
+
'Returns names and brief descriptions for all registered helpers in the chosen registry.',
|
|
223
|
+
'Use describe_helper to get full documentation for a specific item.',
|
|
224
|
+
].join('\n'),
|
|
225
|
+
schema: z.object({
|
|
226
|
+
registry: z.enum(['features', 'clients', 'servers', 'commands', 'endpoints'])
|
|
227
|
+
.describe('Which registry to list'),
|
|
228
|
+
}),
|
|
229
|
+
handler: (args) => {
|
|
230
|
+
const registry = (container as any)[args.registry]
|
|
231
|
+
if (!registry) return `Unknown registry: ${args.registry}`
|
|
232
|
+
const names: string[] = registry.available
|
|
233
|
+
if (names.length === 0) return `No ${args.registry} registered.`
|
|
234
|
+
const lines = [`# Available ${args.registry}\n`]
|
|
235
|
+
for (const name of names) {
|
|
236
|
+
try {
|
|
237
|
+
const Ctor = registry.lookup(name) as any
|
|
238
|
+
const desc = Ctor.description || ''
|
|
239
|
+
lines.push(`- **${name}**${desc ? `: ${desc}` : ''}`)
|
|
240
|
+
} catch {
|
|
241
|
+
lines.push(`- **${name}**`)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return lines.join('\n')
|
|
245
|
+
},
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// --- Tool: describe_helper ---
|
|
249
|
+
mcpServer.tool('describe_helper', {
|
|
250
|
+
description: [
|
|
251
|
+
'Get full documentation for a specific helper (feature, client, server, command, endpoint).',
|
|
252
|
+
'This is the API documentation for any luca helper. There is no other documentation available —',
|
|
253
|
+
'call this before writing code that uses a feature, client, or server.',
|
|
254
|
+
'',
|
|
255
|
+
'Returns markdown with options, state schema, methods, getters, events, env vars, and descriptions.',
|
|
256
|
+
'Use list_registry or find_capability first to see what is available.',
|
|
257
|
+
].join('\n'),
|
|
258
|
+
schema: z.object({
|
|
259
|
+
registry: z.enum(['features', 'clients', 'servers', 'commands', 'endpoints'])
|
|
260
|
+
.describe('Which registry the helper belongs to'),
|
|
261
|
+
name: z.string().describe('Name of the helper (e.g. "fs", "rest", "express")'),
|
|
262
|
+
section: z.enum(['methods', 'getters', 'events', 'state', 'options', 'envVars']).optional()
|
|
263
|
+
.describe('Optional section to filter to. Omit for full documentation.'),
|
|
264
|
+
}),
|
|
265
|
+
handler: (args) => {
|
|
266
|
+
const registry = (container as any)[args.registry]
|
|
267
|
+
if (!registry) return `Unknown registry: ${args.registry}`
|
|
268
|
+
if (!registry.has(args.name)) {
|
|
269
|
+
return `"${args.name}" not found in ${args.registry}. Available: ${registry.available.join(', ')}`
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const Ctor = registry.lookup(args.name) as any
|
|
273
|
+
return Ctor.introspectAsText(args.section)
|
|
274
|
+
} catch (e: any) {
|
|
275
|
+
return `Error describing ${args.name}: ${e.message}`
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// --- Tool: inspect_helper_instance ---
|
|
281
|
+
mcpServer.tool('inspect_helper_instance', {
|
|
282
|
+
description: [
|
|
283
|
+
'Inspect a live helper instance (enabled feature, active client/server).',
|
|
284
|
+
'Use this to inspect a live, running instance — see its current state,',
|
|
285
|
+
'check method signatures, and understand runtime behavior.',
|
|
286
|
+
'',
|
|
287
|
+
'Creates or retrieves the helper and returns introspectAsText() — the same',
|
|
288
|
+
'rich markdown documentation available on any Helper instance at runtime.',
|
|
289
|
+
'Optionally filter to a specific section.',
|
|
290
|
+
].join('\n'),
|
|
291
|
+
schema: z.object({
|
|
292
|
+
type: z.enum(['feature', 'client', 'server'])
|
|
293
|
+
.describe('What kind of helper to inspect'),
|
|
294
|
+
name: z.string().describe('Name of the helper (e.g. "fs", "rest", "express")'),
|
|
295
|
+
section: z.enum(['methods', 'getters', 'events', 'state', 'options', 'envVars']).optional()
|
|
296
|
+
.describe('Optional section to filter to. Omit for full introspection.'),
|
|
297
|
+
}),
|
|
298
|
+
handler: (args) => {
|
|
299
|
+
try {
|
|
300
|
+
let instance: any
|
|
301
|
+
if (args.type === 'feature') {
|
|
302
|
+
instance = container.feature(args.name as any)
|
|
303
|
+
} else if (args.type === 'client') {
|
|
304
|
+
instance = container.client(args.name as any)
|
|
305
|
+
} else if (args.type === 'server') {
|
|
306
|
+
instance = container.server(args.name as any)
|
|
307
|
+
}
|
|
308
|
+
return instance.introspectAsText(args.section)
|
|
309
|
+
} catch (e: any) {
|
|
310
|
+
return `Error inspecting ${args.type} "${args.name}": ${e.message}`
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// --- Prompt: discover ---
|
|
316
|
+
mcpServer.prompt('discover', {
|
|
317
|
+
description: 'Learn how to explore the Luca container and discover available features, clients, servers, and commands.',
|
|
318
|
+
handler: () => [{
|
|
319
|
+
role: 'user' as const,
|
|
320
|
+
content: [
|
|
321
|
+
'# Luca Container Sandbox',
|
|
322
|
+
'',
|
|
323
|
+
'You have access to a live Luca container through the `eval` tool. The sandbox is a persistent JavaScript environment with a `container` global and all enabled features available as top-level variables.',
|
|
324
|
+
'',
|
|
325
|
+
'## Discovering what is available',
|
|
326
|
+
'',
|
|
327
|
+
'The container has registries for each helper type. Each registry has:',
|
|
328
|
+
'- `.available` — array of registered names',
|
|
329
|
+
'- `.describe(name)` — returns markdown docs for one helper',
|
|
330
|
+
'- `.describeAll()` — returns a condensed overview of all helpers (name + description)',
|
|
331
|
+
'',
|
|
332
|
+
'### Registries',
|
|
333
|
+
'```',
|
|
334
|
+
'container.features.available // Features (fs, git, ui, vm, proc, ...)',
|
|
335
|
+
'container.clients.available // Clients (rest, websocket, graphql, ...)',
|
|
336
|
+
'container.servers.available // Servers (express, mcp, socket, ...)',
|
|
337
|
+
'container.commands.available // Commands (run, console, serve, ...)',
|
|
338
|
+
'```',
|
|
339
|
+
'',
|
|
340
|
+
'### Getting documentation',
|
|
341
|
+
'```',
|
|
342
|
+
'container.features.describe("fs") // Docs for the fs feature',
|
|
343
|
+
'container.features.describe("vm") // Docs for the vm feature',
|
|
344
|
+
'container.clients.describe("rest") // Docs for the rest client',
|
|
345
|
+
'container.inspectAsText() // Full container introspection',
|
|
346
|
+
'container.inspectAsText("methods") // Just the methods section',
|
|
347
|
+
'container.inspectAsText("state") // Just the state section',
|
|
348
|
+
'```',
|
|
349
|
+
'',
|
|
350
|
+
'### Using features directly',
|
|
351
|
+
'All enabled features are available as top-level variables:',
|
|
352
|
+
'```',
|
|
353
|
+
'fs.readFile("package.json") // Read a file',
|
|
354
|
+
'fs.readdir("src") // List directory',
|
|
355
|
+
'git.log({ max: 5 }) // Recent git commits',
|
|
356
|
+
'proc.exec("ls -la") // Run a shell command',
|
|
357
|
+
'```',
|
|
358
|
+
'',
|
|
359
|
+
'### Persistent state',
|
|
360
|
+
'Variables you define in one eval call persist to the next:',
|
|
361
|
+
'```',
|
|
362
|
+
'// Call 1',
|
|
363
|
+
'const data = fs.readFile("package.json")',
|
|
364
|
+
'// Call 2',
|
|
365
|
+
'JSON.parse(data).name // still has `data` from previous call',
|
|
366
|
+
'```',
|
|
367
|
+
'',
|
|
368
|
+
'## Recommended first steps',
|
|
369
|
+
'1. `container.features.available` — see what features exist',
|
|
370
|
+
'2. Pick an interesting feature and `container.features.describe("name")` it',
|
|
371
|
+
'3. Try using the feature directly',
|
|
372
|
+
].join('\n'),
|
|
373
|
+
}],
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
// --- Prompt: introspect ---
|
|
377
|
+
mcpServer.prompt('introspect', {
|
|
378
|
+
description: 'Get full introspection of the Luca container — all registries, state, methods, events, and environment info.',
|
|
379
|
+
handler: async () => {
|
|
380
|
+
const text = container.inspectAsText()
|
|
381
|
+
return [{
|
|
382
|
+
role: 'user' as const,
|
|
383
|
+
content: `Here is the full container introspection:\n\n${text}`,
|
|
384
|
+
}]
|
|
385
|
+
},
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// --- Resource: container-info ---
|
|
389
|
+
mcpServer.resource('luca://container/info', {
|
|
390
|
+
name: 'Container Info',
|
|
391
|
+
description: 'Full introspection of the running Luca container',
|
|
392
|
+
mimeType: 'text/markdown',
|
|
393
|
+
handler: () => container.inspectAsText(),
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
// --- Resource: feature list ---
|
|
397
|
+
mcpServer.resource('luca://features', {
|
|
398
|
+
name: 'Available Features',
|
|
399
|
+
description: 'List of all registered features with descriptions',
|
|
400
|
+
mimeType: 'text/markdown',
|
|
401
|
+
handler: () => {
|
|
402
|
+
const lines = ['# Available Features\n']
|
|
403
|
+
for (const name of container.features.available) {
|
|
404
|
+
try {
|
|
405
|
+
const Ctor = container.features.lookup(name) as any
|
|
406
|
+
const desc = Ctor.description || ''
|
|
407
|
+
lines.push(`- **${name}**: ${desc}`)
|
|
408
|
+
} catch {
|
|
409
|
+
lines.push(`- **${name}**`)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return lines.join('\n')
|
|
413
|
+
},
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
// Start the server
|
|
417
|
+
await mcpServer.start({
|
|
418
|
+
transport: options.transport,
|
|
419
|
+
port: options.port,
|
|
420
|
+
mcpCompat: options.mcpCompat,
|
|
421
|
+
stdioCompat: options.stdioCompat,
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
if (options.transport === 'http') {
|
|
425
|
+
console.log(`\nLuca Sandbox MCP listening on http://localhost:${options.port}/mcp`)
|
|
426
|
+
console.log(`Compatibility: ${resolvedCompat}`)
|
|
427
|
+
} else {
|
|
428
|
+
console.error(`Luca Sandbox MCP started (stdio transport)`)
|
|
429
|
+
console.error(`Stdio Compatibility: ${resolvedStdioCompat}`)
|
|
430
|
+
console.error(`Tools: read_me, find_capability, scaffold, eval, inspect_container, list_registry, describe_helper, inspect_helper_instance`)
|
|
431
|
+
console.error(`Prompts: discover, introspect | Resources: luca://container/info, luca://features`)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
commands.registerHandler('sandbox-mcp', {
|
|
436
|
+
description: 'Start an MCP server with a Luca container sandbox for AI agents to explore and test code',
|
|
437
|
+
argsSchema,
|
|
438
|
+
handler: mcpSandbox,
|
|
439
|
+
})
|