luca 2.0.0 → 3.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/.github/workflows/release.yaml +170 -0
- package/AGENTS.md +99 -0
- package/CLAUDE.md +123 -0
- package/CNAME +1 -0
- package/README.md +275 -9
- package/RUNME.md +56 -0
- package/assistants/codingAssistant/ABOUT.md +5 -0
- package/assistants/codingAssistant/CORE.md +33 -0
- package/assistants/codingAssistant/hooks.ts +21 -0
- package/assistants/codingAssistant/tools.ts +12 -0
- package/assistants/inkbot/ABOUT.md +16 -0
- package/assistants/inkbot/CORE.md +330 -0
- package/assistants/inkbot/hooks.ts +6 -0
- package/assistants/inkbot/tools.ts +53 -0
- package/assistants/researcher/ABOUT.md +5 -0
- package/assistants/researcher/CORE.md +46 -0
- package/assistants/researcher/hooks.ts +16 -0
- package/assistants/researcher/tools.ts +237 -0
- package/bun.lock +2667 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-bootstrap.ts +117 -0
- package/commands/build-python-bridge.ts +42 -0
- package/commands/build-scaffolds.ts +175 -0
- package/commands/bundle-consumer-project.ts +521 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/inkbot.ts +874 -0
- package/commands/release.ts +80 -0
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/dist/agi/container.server.d.ts +63 -0
- package/dist/agi/container.server.d.ts.map +1 -0
- package/dist/agi/endpoints/ask.d.ts +20 -0
- package/dist/agi/endpoints/ask.d.ts.map +1 -0
- package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
- package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
- package/dist/agi/endpoints/conversations.d.ts +18 -0
- package/dist/agi/endpoints/conversations.d.ts.map +1 -0
- package/dist/agi/endpoints/experts.d.ts +8 -0
- package/dist/agi/endpoints/experts.d.ts.map +1 -0
- package/dist/agi/feature.d.ts +9 -0
- package/dist/agi/feature.d.ts.map +1 -0
- package/dist/agi/features/assistant.d.ts +509 -0
- package/dist/agi/features/assistant.d.ts.map +1 -0
- package/dist/agi/features/assistants-manager.d.ts +236 -0
- package/dist/agi/features/assistants-manager.d.ts.map +1 -0
- package/dist/agi/features/autonomous-assistant.d.ts +281 -0
- package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
- package/dist/agi/features/browser-use.d.ts +479 -0
- package/dist/agi/features/browser-use.d.ts.map +1 -0
- package/dist/agi/features/claude-code.d.ts +824 -0
- package/dist/agi/features/claude-code.d.ts.map +1 -0
- package/dist/agi/features/conversation-history.d.ts +245 -0
- package/dist/agi/features/conversation-history.d.ts.map +1 -0
- package/dist/agi/features/conversation.d.ts +464 -0
- package/dist/agi/features/conversation.d.ts.map +1 -0
- package/dist/agi/features/docs-reader.d.ts +72 -0
- package/dist/agi/features/docs-reader.d.ts.map +1 -0
- package/dist/agi/features/file-tools.d.ts +110 -0
- package/dist/agi/features/file-tools.d.ts.map +1 -0
- package/dist/agi/features/luca-coder.d.ts +323 -0
- package/dist/agi/features/luca-coder.d.ts.map +1 -0
- package/dist/agi/features/openai-codex.d.ts +381 -0
- package/dist/agi/features/openai-codex.d.ts.map +1 -0
- package/dist/agi/features/openapi.d.ts +200 -0
- package/dist/agi/features/openapi.d.ts.map +1 -0
- package/dist/agi/features/skills-library.d.ts +167 -0
- package/dist/agi/features/skills-library.d.ts.map +1 -0
- package/dist/agi/index.d.ts +5 -0
- package/dist/agi/index.d.ts.map +1 -0
- package/dist/agi/lib/interceptor-chain.d.ts +44 -0
- package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
- package/dist/agi/lib/token-counter.d.ts +13 -0
- package/dist/agi/lib/token-counter.d.ts.map +1 -0
- package/dist/bootstrap/generated.d.ts +5 -0
- package/dist/bootstrap/generated.d.ts.map +1 -0
- package/dist/browser.d.ts +12 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/bus.d.ts +29 -0
- package/dist/bus.d.ts.map +1 -0
- package/dist/cli/build-info.d.ts +4 -0
- package/dist/cli/build-info.d.ts.map +1 -0
- package/dist/cli/cli.d.ts +3 -12
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/client.d.ts +60 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/clients/civitai/index.d.ts +472 -0
- package/dist/clients/civitai/index.d.ts.map +1 -0
- package/dist/clients/client-template.d.ts +30 -0
- package/dist/clients/client-template.d.ts.map +1 -0
- package/dist/clients/comfyui/index.d.ts +281 -0
- package/dist/clients/comfyui/index.d.ts.map +1 -0
- package/dist/clients/elevenlabs/index.d.ts +197 -0
- package/dist/clients/elevenlabs/index.d.ts.map +1 -0
- package/dist/clients/graph.d.ts +64 -0
- package/dist/clients/graph.d.ts.map +1 -0
- package/dist/clients/openai/index.d.ts +247 -0
- package/dist/clients/openai/index.d.ts.map +1 -0
- package/dist/clients/rest.d.ts +92 -0
- package/dist/clients/rest.d.ts.map +1 -0
- package/dist/clients/supabase/index.d.ts +176 -0
- package/dist/clients/supabase/index.d.ts.map +1 -0
- package/dist/clients/websocket.d.ts +127 -0
- package/dist/clients/websocket.d.ts.map +1 -0
- package/dist/command.d.ts +163 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/commands/bootstrap.d.ts +20 -0
- package/dist/commands/bootstrap.d.ts.map +1 -0
- package/dist/commands/chat.d.ts +37 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/code.d.ts +28 -0
- package/dist/commands/code.d.ts.map +1 -0
- package/dist/commands/console.d.ts +22 -0
- package/dist/commands/console.d.ts.map +1 -0
- package/dist/commands/describe.d.ts +50 -0
- package/dist/commands/describe.d.ts.map +1 -0
- package/dist/commands/eval.d.ts +23 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/help.d.ts +25 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/introspect.d.ts +24 -0
- package/dist/commands/introspect.d.ts.map +1 -0
- package/dist/commands/mcp.d.ts +35 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/prompt.d.ts +38 -0
- package/dist/commands/prompt.d.ts.map +1 -0
- package/dist/commands/run.d.ts +24 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/sandbox-mcp.d.ts +34 -0
- package/dist/commands/sandbox-mcp.d.ts.map +1 -0
- package/dist/commands/save-api-docs.d.ts +21 -0
- package/dist/commands/save-api-docs.d.ts.map +1 -0
- package/dist/commands/scaffold.d.ts +24 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/select.d.ts +22 -0
- package/dist/commands/select.d.ts.map +1 -0
- package/dist/commands/serve.d.ts +29 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/container-describer.d.ts +144 -0
- package/dist/container-describer.d.ts.map +1 -0
- package/dist/container.d.ts +451 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/endpoint.d.ts +113 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/feature.d.ts +47 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/graft.d.ts +29 -0
- package/dist/graft.d.ts.map +1 -0
- package/dist/hash-object.d.ts +8 -0
- package/dist/hash-object.d.ts.map +1 -0
- package/dist/helper.d.ts +209 -0
- package/dist/helper.d.ts.map +1 -0
- package/dist/introspection/generated.node.d.ts +44623 -0
- package/dist/introspection/generated.node.d.ts.map +1 -0
- package/dist/introspection/generated.web.d.ts +1412 -0
- package/dist/introspection/generated.web.d.ts.map +1 -0
- package/dist/introspection/index.d.ts +156 -0
- package/dist/introspection/index.d.ts.map +1 -0
- package/dist/introspection/scan.d.ts +147 -0
- package/dist/introspection/scan.d.ts.map +1 -0
- package/dist/node/container.d.ts +256 -0
- package/dist/node/container.d.ts.map +1 -0
- package/dist/node/feature.d.ts +9 -0
- package/dist/node/feature.d.ts.map +1 -0
- package/dist/node/features/container-link.d.ts +213 -0
- package/dist/node/features/container-link.d.ts.map +1 -0
- package/dist/node/features/content-db.d.ts +354 -0
- package/dist/node/features/content-db.d.ts.map +1 -0
- package/dist/node/features/disk-cache.d.ts +236 -0
- package/dist/node/features/disk-cache.d.ts.map +1 -0
- package/dist/node/features/dns.d.ts +511 -0
- package/dist/node/features/dns.d.ts.map +1 -0
- package/dist/node/features/docker.d.ts +485 -0
- package/dist/node/features/docker.d.ts.map +1 -0
- package/dist/node/features/downloader.d.ts +73 -0
- package/dist/node/features/downloader.d.ts.map +1 -0
- package/dist/node/features/figlet-fonts.d.ts +4 -0
- package/dist/node/features/figlet-fonts.d.ts.map +1 -0
- package/dist/node/features/file-manager.d.ts +177 -0
- package/dist/node/features/file-manager.d.ts.map +1 -0
- package/dist/node/features/fs.d.ts +635 -0
- package/dist/node/features/fs.d.ts.map +1 -0
- package/dist/node/features/git.d.ts +329 -0
- package/dist/node/features/git.d.ts.map +1 -0
- package/dist/node/features/google-auth.d.ts +200 -0
- package/dist/node/features/google-auth.d.ts.map +1 -0
- package/dist/node/features/google-calendar.d.ts +194 -0
- package/dist/node/features/google-calendar.d.ts.map +1 -0
- package/dist/node/features/google-docs.d.ts +138 -0
- package/dist/node/features/google-docs.d.ts.map +1 -0
- package/dist/node/features/google-drive.d.ts +202 -0
- package/dist/node/features/google-drive.d.ts.map +1 -0
- package/dist/node/features/google-mail.d.ts +221 -0
- package/dist/node/features/google-mail.d.ts.map +1 -0
- package/dist/node/features/google-sheets.d.ts +157 -0
- package/dist/node/features/google-sheets.d.ts.map +1 -0
- package/dist/node/features/grep.d.ts +207 -0
- package/dist/node/features/grep.d.ts.map +1 -0
- package/dist/node/features/helpers.d.ts +236 -0
- package/dist/node/features/helpers.d.ts.map +1 -0
- package/dist/node/features/ink.d.ts +332 -0
- package/dist/node/features/ink.d.ts.map +1 -0
- package/dist/node/features/ipc-socket.d.ts +298 -0
- package/dist/node/features/ipc-socket.d.ts.map +1 -0
- package/dist/node/features/json-tree.d.ts +140 -0
- package/dist/node/features/json-tree.d.ts.map +1 -0
- package/dist/node/features/networking.d.ts +373 -0
- package/dist/node/features/networking.d.ts.map +1 -0
- package/dist/node/features/nlp.d.ts +125 -0
- package/dist/node/features/nlp.d.ts.map +1 -0
- package/dist/node/features/opener.d.ts +93 -0
- package/dist/node/features/opener.d.ts.map +1 -0
- package/dist/node/features/os.d.ts +168 -0
- package/dist/node/features/os.d.ts.map +1 -0
- package/dist/node/features/package-finder.d.ts +419 -0
- package/dist/node/features/package-finder.d.ts.map +1 -0
- package/dist/node/features/postgres.d.ts +173 -0
- package/dist/node/features/postgres.d.ts.map +1 -0
- package/dist/node/features/proc.d.ts +285 -0
- package/dist/node/features/proc.d.ts.map +1 -0
- package/dist/node/features/process-manager.d.ts +427 -0
- package/dist/node/features/process-manager.d.ts.map +1 -0
- package/dist/node/features/python.d.ts +477 -0
- package/dist/node/features/python.d.ts.map +1 -0
- package/dist/node/features/redis.d.ts +247 -0
- package/dist/node/features/redis.d.ts.map +1 -0
- package/dist/node/features/repl.d.ts +84 -0
- package/dist/node/features/repl.d.ts.map +1 -0
- package/dist/node/features/runpod.d.ts +527 -0
- package/dist/node/features/runpod.d.ts.map +1 -0
- package/dist/node/features/secure-shell.d.ts +145 -0
- package/dist/node/features/secure-shell.d.ts.map +1 -0
- package/dist/node/features/semantic-search.d.ts +207 -0
- package/dist/node/features/semantic-search.d.ts.map +1 -0
- package/dist/node/features/sqlite.d.ts +180 -0
- package/dist/node/features/sqlite.d.ts.map +1 -0
- package/dist/node/features/telegram.d.ts +173 -0
- package/dist/node/features/telegram.d.ts.map +1 -0
- package/dist/node/features/transpiler.d.ts +51 -0
- package/dist/node/features/transpiler.d.ts.map +1 -0
- package/dist/node/features/tts.d.ts +108 -0
- package/dist/node/features/tts.d.ts.map +1 -0
- package/dist/node/features/ui.d.ts +562 -0
- package/dist/node/features/ui.d.ts.map +1 -0
- package/dist/node/features/vault.d.ts +90 -0
- package/dist/node/features/vault.d.ts.map +1 -0
- package/dist/node/features/vm.d.ts +285 -0
- package/dist/node/features/vm.d.ts.map +1 -0
- package/dist/node/features/yaml-tree.d.ts +118 -0
- package/dist/node/features/yaml-tree.d.ts.map +1 -0
- package/dist/node/features/yaml.d.ts +127 -0
- package/dist/node/features/yaml.d.ts.map +1 -0
- package/dist/node.d.ts +67 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/python/generated.d.ts +2 -0
- package/dist/python/generated.d.ts.map +1 -0
- package/dist/react/index.d.ts +36 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/registry.d.ts +97 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/scaffolds/generated.d.ts +13 -0
- package/dist/scaffolds/generated.d.ts.map +1 -0
- package/dist/scaffolds/template.d.ts +11 -0
- package/dist/scaffolds/template.d.ts.map +1 -0
- package/dist/schemas/base.d.ts +254 -0
- package/dist/schemas/base.d.ts.map +1 -0
- package/dist/selector.d.ts +130 -0
- package/dist/selector.d.ts.map +1 -0
- package/dist/server.d.ts +89 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/servers/express.d.ts +104 -0
- package/dist/servers/express.d.ts.map +1 -0
- package/dist/servers/mcp.d.ts +201 -0
- package/dist/servers/mcp.d.ts.map +1 -0
- package/dist/servers/socket.d.ts +121 -0
- package/dist/servers/socket.d.ts.map +1 -0
- package/dist/state.d.ts +24 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/web/clients/socket.d.ts +37 -0
- package/dist/web/clients/socket.d.ts.map +1 -0
- package/dist/web/container.d.ts +55 -0
- package/dist/web/container.d.ts.map +1 -0
- package/dist/web/extension.d.ts +4 -0
- package/dist/web/extension.d.ts.map +1 -0
- package/dist/web/feature.d.ts +8 -0
- package/dist/web/feature.d.ts.map +1 -0
- package/dist/web/features/asset-loader.d.ts +35 -0
- package/dist/web/features/asset-loader.d.ts.map +1 -0
- package/dist/web/features/container-link.d.ts +167 -0
- package/dist/web/features/container-link.d.ts.map +1 -0
- package/dist/web/features/esbuild.d.ts +51 -0
- package/dist/web/features/esbuild.d.ts.map +1 -0
- package/dist/web/features/helpers.d.ts +140 -0
- package/dist/web/features/helpers.d.ts.map +1 -0
- package/dist/web/features/network.d.ts +69 -0
- package/dist/web/features/network.d.ts.map +1 -0
- package/dist/web/features/speech.d.ts +71 -0
- package/dist/web/features/speech.d.ts.map +1 -0
- package/dist/web/features/vault.d.ts +62 -0
- package/dist/web/features/vault.d.ts.map +1 -0
- package/dist/web/features/vm.d.ts +48 -0
- package/dist/web/features/vm.d.ts.map +1 -0
- package/dist/web/features/voice-recognition.d.ts +96 -0
- package/dist/web/features/voice-recognition.d.ts.map +1 -0
- package/dist/web/shims/isomorphic-vm.d.ts +22 -0
- package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
- package/index.html +1457 -0
- package/index.ts +1 -0
- package/install.sh +84 -0
- package/luca.cli.ts +16 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +219 -58
- package/public/index.html +1457 -0
- package/public/slides-ai-native.html +902 -0
- package/public/slides-intro.html +974 -0
- package/pyproject.toml +7 -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/using-assistant-with-mcp.ts +555 -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 +11 -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/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/stamp-build.sh +12 -0
- package/scripts/test-assistant-hooks.ts +13 -0
- package/scripts/test-docs-reader.ts +10 -0
- package/scripts/test-linux-binary.sh +80 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +156 -0
- package/src/agi/feature.ts +13 -0
- package/src/agi/features/agent-memory.ts +694 -0
- package/src/agi/features/assistant.ts +1653 -0
- package/src/agi/features/assistants-manager.ts +534 -0
- package/src/agi/features/autonomous-assistant.ts +431 -0
- package/src/agi/features/browser-use.ts +672 -0
- package/src/agi/features/claude-code.ts +1584 -0
- package/src/agi/features/coding-tools.ts +175 -0
- package/src/agi/features/conversation-history.ts +672 -0
- package/src/agi/features/conversation.ts +1494 -0
- package/src/agi/features/docs-reader.ts +167 -0
- package/src/agi/features/file-tools.ts +340 -0
- package/src/agi/features/luca-coder.ts +641 -0
- package/src/agi/features/mcp-bridge.ts +532 -0
- package/src/agi/features/openai-codex.ts +651 -0
- package/src/agi/features/openapi.ts +445 -0
- package/src/agi/features/skills-library.ts +557 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/interceptor-chain.ts +89 -0
- package/src/agi/lib/token-counter.ts +202 -0
- package/src/bootstrap/generated.ts +9791 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +122 -0
- package/src/cli/build-info.ts +4 -0
- package/src/cli/cli.ts +355 -0
- package/src/client.ts +170 -0
- package/src/clients/civitai/index.ts +537 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +604 -0
- package/src/clients/elevenlabs/index.ts +317 -0
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +456 -0
- package/src/clients/rest.ts +207 -0
- package/src/clients/supabase/index.ts +357 -0
- package/src/clients/voicebox/index.ts +300 -0
- package/src/clients/websocket.ts +251 -0
- package/src/command.ts +506 -0
- package/src/commands/bootstrap.ts +244 -0
- package/src/commands/chat.ts +309 -0
- package/src/commands/code.ts +371 -0
- package/src/commands/console.ts +189 -0
- package/src/commands/describe.ts +243 -0
- package/src/commands/eval.ts +67 -0
- package/src/commands/help.ts +240 -0
- package/src/commands/index.ts +19 -0
- package/src/commands/introspect.ts +218 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +1014 -0
- package/src/commands/run.ts +278 -0
- package/src/commands/sandbox-mcp.ts +343 -0
- package/src/commands/save-api-docs.ts +51 -0
- package/src/commands/scaffold.ts +225 -0
- package/src/commands/select.ts +99 -0
- package/src/commands/serve.ts +208 -0
- package/src/container-describer.ts +1091 -0
- package/src/container.ts +1199 -0
- package/src/endpoint.ts +365 -0
- package/src/entity.ts +173 -0
- package/src/feature.ts +118 -0
- package/src/graft.ts +181 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +849 -0
- package/src/introspection/generated.agi.ts +41200 -0
- package/src/introspection/generated.node.ts +28773 -0
- package/src/introspection/generated.web.ts +2272 -0
- package/src/introspection/index.ts +296 -0
- package/src/introspection/scan.ts +1136 -0
- package/src/node/container.ts +409 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +559 -0
- package/src/node/features/content-db.ts +849 -0
- package/src/node/features/disk-cache.ts +388 -0
- package/src/node/features/display-result.ts +57 -0
- package/src/node/features/dns.ts +669 -0
- package/src/node/features/docker.ts +921 -0
- package/src/node/features/downloader.ts +79 -0
- package/src/node/features/figlet-fonts.ts +600 -0
- package/src/node/features/file-manager.ts +535 -0
- package/src/node/features/fs.ts +1050 -0
- package/src/node/features/git.ts +592 -0
- package/src/node/features/google-auth.ts +504 -0
- package/src/node/features/google-calendar.ts +306 -0
- package/src/node/features/google-docs.ts +412 -0
- package/src/node/features/google-drive.ts +346 -0
- package/src/node/features/google-mail.ts +540 -0
- package/src/node/features/google-sheets.ts +286 -0
- package/src/node/features/grep.ts +427 -0
- package/src/node/features/helpers.ts +762 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +649 -0
- package/src/node/features/json-tree.ts +170 -0
- package/src/node/features/networking.ts +961 -0
- package/src/node/features/nlp.ts +212 -0
- package/src/node/features/opener.ts +180 -0
- package/src/node/features/os.ts +403 -0
- package/src/node/features/package-finder.ts +540 -0
- package/src/node/features/postgres.ts +289 -0
- package/src/node/features/proc.ts +503 -0
- package/src/node/features/process-manager.ts +844 -0
- package/src/node/features/python.ts +912 -0
- package/src/node/features/redis.ts +446 -0
- package/src/node/features/repl.ts +212 -0
- package/src/node/features/runpod.ts +811 -0
- package/src/node/features/secure-shell.ts +261 -0
- package/src/node/features/semantic-search.ts +935 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +343 -0
- package/src/node/features/transpiler.ts +160 -0
- package/src/node/features/tts.ts +185 -0
- package/src/node/features/ui.ts +791 -0
- package/src/node/features/vault.ts +153 -0
- package/src/node/features/vm.ts +462 -0
- package/src/node/features/yaml-tree.ts +148 -0
- package/src/node/features/yaml.ts +133 -0
- package/src/node.ts +76 -0
- package/src/python/bridge.py +220 -0
- package/src/python/generated.ts +226 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +210 -0
- package/src/scaffolds/generated.ts +1814 -0
- package/src/scaffolds/template.ts +46 -0
- package/src/schemas/base.ts +296 -0
- package/src/selector.ts +352 -0
- package/src/server.ts +229 -0
- package/src/servers/express.ts +283 -0
- package/src/servers/mcp.ts +802 -0
- package/src/servers/socket.ts +258 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +99 -0
- package/src/web/container.ts +75 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +72 -0
- package/src/web/features/container-link.ts +382 -0
- package/src/web/features/esbuild.ts +93 -0
- package/src/web/features/helpers.ts +291 -0
- package/src/web/features/network.ts +85 -0
- package/src/web/features/speech.ts +104 -0
- package/src/web/features/vault.ts +207 -0
- package/src/web/features/vm.ts +85 -0
- package/src/web/features/voice-recognition.ts +161 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
- package/LICENSE +0 -21
- package/dist/cli/cli.js +0 -48
- package/dist/cli/common.d.ts +0 -2
- package/dist/cli/common.js +0 -6
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -5
- package/dist/cli/run.d.ts +0 -1
- package/dist/cli/run.js +0 -38
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.js +0 -32
- package/dist/core/read.d.ts +0 -2
- package/dist/core/read.js +0 -29
- package/dist/core/request.d.ts +0 -1
- package/dist/core/request.js +0 -2
- package/dist/core/write.d.ts +0 -2
- package/dist/core/write.js +0 -21
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -5
- package/dist/utils/common.d.ts +0 -9
- package/dist/utils/common.js +0 -57
- package/dist/utils/consts.d.ts +0 -3
- package/dist/utils/consts.js +0 -11
- package/dist/utils/dict.d.ts +0 -1
- package/dist/utils/dict.js +0 -7
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.js +0 -21
- package/dist/utils/log.d.ts +0 -1
- package/dist/utils/log.js +0 -5
- package/dist/utils/types.d.ts +0 -1
- package/dist/utils/types.js +0 -2
- package/dist/utils/utils.test.d.ts +0 -1
- package/dist/utils/utils.test.js +0 -7
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { Document } from 'contentbase'
|
|
3
|
+
import { commands } from '../command.js'
|
|
4
|
+
import { CommandOptionsSchema } from '../schemas/base.js'
|
|
5
|
+
import type { ContainerContext } from '../container.js'
|
|
6
|
+
|
|
7
|
+
declare module '../command.js' {
|
|
8
|
+
interface AvailableCommands {
|
|
9
|
+
prompt: ReturnType<typeof commands.registerHandler>
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const argsSchema = CommandOptionsSchema.extend({
|
|
14
|
+
model: z.string().optional().describe('Override the LLM model (assistant mode only)'),
|
|
15
|
+
'include-frontmatter': z.boolean().default(false).describe('Keep YAML frontmatter in the prompt instead of stripping it before sending to the agent.'),
|
|
16
|
+
'skip-eval': z.boolean().default(false).describe('Skip execution of fenced code blocks in the prompt file'),
|
|
17
|
+
'permission-mode': z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).default('acceptEdits').describe('Permission mode for CLI agents (default: acceptEdits)'),
|
|
18
|
+
'in-folder': z.string().optional().describe('Run the CLI agent in this directory (resolved via container.paths)'),
|
|
19
|
+
'out-file': z.string().optional().describe('Save session output as a markdown file'),
|
|
20
|
+
'include-output': z.boolean().default(false).describe('Include tool call outputs in the markdown (requires --out-file)'),
|
|
21
|
+
'inputs-file': z.string().optional().describe('Path to a JSON or YAML file supplying input values'),
|
|
22
|
+
'parallel': z.boolean().default(false).describe('Run multiple prompt files in parallel with side-by-side terminal UI'),
|
|
23
|
+
'exclude-sections': z.string().optional().describe('Comma-separated list of section headings to exclude from the prompt'),
|
|
24
|
+
'chrome': z.boolean().default(false).describe('Launch Claude Code with a Chrome browser tool'),
|
|
25
|
+
'dry-run': z.boolean().default(false).describe('Display the resolved prompt and options without running the assistant'),
|
|
26
|
+
'local': z.boolean().default(false).describe('Use local models (sets ANTHROPIC_BASE_URL and model for claudeCode, or local flag for assistants)'),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
function normalizeTarget(raw: string): string {
|
|
30
|
+
const lower = raw.toLowerCase().replace(/[-_]/g, '')
|
|
31
|
+
if (/claude/.test(lower)) return 'claude'
|
|
32
|
+
if (/codex/.test(lower) || /openai/.test(lower)) return 'codex'
|
|
33
|
+
return raw
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const CLI_TARGETS = new Set(['claude', 'codex'])
|
|
37
|
+
|
|
38
|
+
function formatSessionMarkdown(events: any[], includeOutput: boolean): string {
|
|
39
|
+
const lines: string[] = []
|
|
40
|
+
|
|
41
|
+
for (const event of events) {
|
|
42
|
+
if (event.type === 'assistant' || event.type === 'message') {
|
|
43
|
+
const role = event.message?.role ?? event.role
|
|
44
|
+
if (role && role !== 'assistant') continue
|
|
45
|
+
|
|
46
|
+
const content = event.message?.content ?? event.content
|
|
47
|
+
if (!Array.isArray(content)) continue
|
|
48
|
+
|
|
49
|
+
for (const block of content) {
|
|
50
|
+
if (block.type === 'text' && block.text) {
|
|
51
|
+
lines.push(block.text)
|
|
52
|
+
lines.push('')
|
|
53
|
+
} else if (block.type === 'tool_use') {
|
|
54
|
+
lines.push(`**${block.name}**`)
|
|
55
|
+
lines.push('```json')
|
|
56
|
+
lines.push(JSON.stringify(block.input, null, 2))
|
|
57
|
+
lines.push('```')
|
|
58
|
+
lines.push('')
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else if ((event.type === 'tool_result' || event.type === 'function_call_output') && includeOutput) {
|
|
62
|
+
const rawContent = event.type === 'function_call_output' ? event.output : event.content
|
|
63
|
+
const content = typeof rawContent === 'string' ? rawContent : JSON.stringify(rawContent, null, 2)
|
|
64
|
+
lines.push('```')
|
|
65
|
+
lines.push(content)
|
|
66
|
+
lines.push('```')
|
|
67
|
+
lines.push('')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return lines.join('\n')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface RunStats {
|
|
75
|
+
collectedEvents: any[]
|
|
76
|
+
durationMs: number
|
|
77
|
+
outputTokens: number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface PreparedPrompt {
|
|
81
|
+
resolvedPath: string
|
|
82
|
+
promptContent: string
|
|
83
|
+
filename: string
|
|
84
|
+
agentOptions: Record<string, any>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function runClaudeOrCodex(target: 'claude' | 'codex', promptContent: string, container: any, options: z.infer<typeof argsSchema>, agentOptions: Record<string, any> = {}): Promise<RunStats> {
|
|
88
|
+
const ui = container.feature('ui')
|
|
89
|
+
const featureName = target === 'claude' ? 'claudeCode' : 'openaiCodex'
|
|
90
|
+
const feature = container.feature(featureName)
|
|
91
|
+
|
|
92
|
+
const available = await feature.checkAvailability()
|
|
93
|
+
if (!available) {
|
|
94
|
+
console.error(`${target} CLI is not available. Make sure it is installed and in your PATH.`)
|
|
95
|
+
process.exit(1)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let outputTokens = 0
|
|
99
|
+
|
|
100
|
+
// Stream text via deltas for real-time output
|
|
101
|
+
feature.on('session:delta', ({ text }: { text: string }) => {
|
|
102
|
+
process.stdout.write(text)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Render complete messages — only tool_use blocks and final text (markdown formatted)
|
|
106
|
+
feature.on('session:message', ({ message }: { message: any }) => {
|
|
107
|
+
const role = message?.message?.role ?? message?.role
|
|
108
|
+
if (role && role !== 'assistant') return
|
|
109
|
+
|
|
110
|
+
const content = message?.message?.content ?? message?.content
|
|
111
|
+
if (!Array.isArray(content)) return
|
|
112
|
+
|
|
113
|
+
const usage = message?.message?.usage ?? message?.usage
|
|
114
|
+
if (usage?.output_tokens) outputTokens += usage.output_tokens
|
|
115
|
+
|
|
116
|
+
// Skip partial messages — text is already rendered via session:delta
|
|
117
|
+
const stopReason = message?.message?.stop_reason ?? message?.stop_reason
|
|
118
|
+
if (!stopReason) return
|
|
119
|
+
|
|
120
|
+
for (const block of content) {
|
|
121
|
+
if (block.type === 'tool_use') {
|
|
122
|
+
const argsStr = JSON.stringify(block.input).slice(0, 120)
|
|
123
|
+
process.stdout.write(ui.colors.dim(`\n ⟳ ${block.name}`) + ui.colors.dim(`(${argsStr})\n`))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Collect structured events for --out-file
|
|
129
|
+
const collectedEvents: any[] = []
|
|
130
|
+
if (options['out-file']) {
|
|
131
|
+
feature.on('session:event', ({ event }: { event: any }) => {
|
|
132
|
+
// Skip partial assistant messages (streaming tokens) — only collect complete messages with stop_reason
|
|
133
|
+
if (event.type === 'assistant') {
|
|
134
|
+
if (event.message?.stop_reason) collectedEvents.push(event)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
if (event.type === 'tool_result' || event.type === 'message' || event.type === 'function_call_output' || event.type === 'item.completed' || event.type === 'turn.completed') {
|
|
138
|
+
collectedEvents.push(event)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const runOptions: Record<string, any> = { streaming: true, ...agentOptions }
|
|
144
|
+
|
|
145
|
+
if (options['in-folder']) {
|
|
146
|
+
runOptions.cwd = container.paths.resolve(options['in-folder'])
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (target === 'claude') {
|
|
150
|
+
runOptions.permissionMode = options['permission-mode']
|
|
151
|
+
if (options.chrome) runOptions.chrome = true
|
|
152
|
+
if (options.local) runOptions.local = true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// CLI flags override agentOptions from frontmatter
|
|
156
|
+
if (options.model) runOptions.model = options.model
|
|
157
|
+
|
|
158
|
+
const startTime = Date.now()
|
|
159
|
+
const sessionId = await feature.start(promptContent, runOptions)
|
|
160
|
+
const session = await feature.waitForSession(sessionId)
|
|
161
|
+
|
|
162
|
+
if (session.status === 'error') {
|
|
163
|
+
console.error(session.error || 'Session failed')
|
|
164
|
+
process.exit(1)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
process.stdout.write('\n')
|
|
168
|
+
|
|
169
|
+
return { collectedEvents, durationMs: Date.now() - startTime, outputTokens }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function runAssistant(name: string, promptContent: string, options: z.infer<typeof argsSchema>, container: any, agentOptions: Record<string, any> = {}): Promise<RunStats> {
|
|
173
|
+
const ui = container.feature('ui')
|
|
174
|
+
const manager = container.feature('assistantsManager')
|
|
175
|
+
await manager.discover()
|
|
176
|
+
|
|
177
|
+
const entry = manager.get(name)
|
|
178
|
+
if (!entry) {
|
|
179
|
+
const entries = manager.list()
|
|
180
|
+
const available = entries.length ? entries.map((e: any) => e.name).join(', ') : '(none)'
|
|
181
|
+
console.error(`Assistant "${name}" not found. Available: ${available}`)
|
|
182
|
+
process.exit(1)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const createOptions: Record<string, any> = { ...agentOptions }
|
|
186
|
+
// CLI flags override agentOptions from frontmatter
|
|
187
|
+
if (options.model) createOptions.model = options.model
|
|
188
|
+
if (options.local) createOptions.local = true
|
|
189
|
+
|
|
190
|
+
const assistant = manager.create(name, createOptions)
|
|
191
|
+
let isFirstChunk = true
|
|
192
|
+
|
|
193
|
+
// Collect structured events for --out-file
|
|
194
|
+
const collectedEvents: any[] = []
|
|
195
|
+
let textBuffer = ''
|
|
196
|
+
|
|
197
|
+
assistant.on('chunk', (text: string) => {
|
|
198
|
+
if (isFirstChunk) {
|
|
199
|
+
process.stdout.write('\n')
|
|
200
|
+
isFirstChunk = false
|
|
201
|
+
}
|
|
202
|
+
process.stdout.write(text)
|
|
203
|
+
if (options['out-file']) {
|
|
204
|
+
textBuffer += text
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
assistant.on('toolCall', (toolName: string, args: any) => {
|
|
209
|
+
const argsStr = JSON.stringify(args).slice(0, 120)
|
|
210
|
+
process.stdout.write(ui.colors.dim(`\n ⟳ ${toolName}`) + ui.colors.dim(`(${argsStr})\n`))
|
|
211
|
+
if (options['out-file']) {
|
|
212
|
+
collectedEvents.push({ type: 'assistant', message: { content: [{ type: 'tool_use', name: toolName, input: args }] } })
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
assistant.on('toolResult', (toolName: string, result: any) => {
|
|
217
|
+
const preview = typeof result === 'string' ? result.slice(0, 100) : JSON.stringify(result).slice(0, 100)
|
|
218
|
+
process.stdout.write(ui.colors.green(` ✓ ${toolName}`) + ui.colors.dim(` → ${preview}${preview.length >= 100 ? '…' : ''}\n`))
|
|
219
|
+
if (options['out-file']) {
|
|
220
|
+
collectedEvents.push({ type: 'tool_result', content: typeof result === 'string' ? result : JSON.stringify(result, null, 2) })
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
assistant.on('toolError', (toolName: string, error: any) => {
|
|
225
|
+
const msg = error?.message || String(error)
|
|
226
|
+
process.stdout.write(ui.colors.red(` ✗ ${toolName}: ${msg}\n`))
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const startTime = Date.now()
|
|
230
|
+
await assistant.ask(promptContent)
|
|
231
|
+
process.stdout.write('\n')
|
|
232
|
+
|
|
233
|
+
// Flush accumulated text buffer as a single event
|
|
234
|
+
if (textBuffer) {
|
|
235
|
+
collectedEvents.push({ type: 'assistant', message: { content: [{ type: 'text', text: textBuffer }] } })
|
|
236
|
+
textBuffer = ''
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { collectedEvents, durationMs: Date.now() - startTime, outputTokens: 0 }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function runParallel(
|
|
243
|
+
target: string,
|
|
244
|
+
prepared: PreparedPrompt[],
|
|
245
|
+
options: z.infer<typeof argsSchema>,
|
|
246
|
+
container: any,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const { fs, paths } = container
|
|
249
|
+
const ink = container.feature('ink', { enable: true })
|
|
250
|
+
await ink.loadModules()
|
|
251
|
+
|
|
252
|
+
const React = ink.React
|
|
253
|
+
const h = React.createElement
|
|
254
|
+
const { Box, Text } = ink.components
|
|
255
|
+
const { useApp, useInput, useStdout } = ink.hooks
|
|
256
|
+
const { useState, useEffect } = React
|
|
257
|
+
|
|
258
|
+
const MAX_LINES = 500
|
|
259
|
+
|
|
260
|
+
// Mutable state that event handlers write to directly.
|
|
261
|
+
// The Ink component reads this on a timer to trigger re-renders.
|
|
262
|
+
const promptStates = prepared.map((p) => ({
|
|
263
|
+
filename: p.filename,
|
|
264
|
+
resolvedPath: p.resolvedPath,
|
|
265
|
+
status: 'running' as 'running' | 'done' | 'error',
|
|
266
|
+
lines: [] as string[],
|
|
267
|
+
outputTokens: 0,
|
|
268
|
+
startTime: Date.now(),
|
|
269
|
+
durationMs: 0,
|
|
270
|
+
collectedEvents: [] as any[],
|
|
271
|
+
error: undefined as string | undefined,
|
|
272
|
+
}))
|
|
273
|
+
|
|
274
|
+
const sessionMap = new Map<string, number>()
|
|
275
|
+
let allDone = false
|
|
276
|
+
let userAborted = false
|
|
277
|
+
|
|
278
|
+
function pushLines(idx: number, text: string) {
|
|
279
|
+
const newLines = text.split('\n')
|
|
280
|
+
promptStates[idx]!.lines.push(...newLines)
|
|
281
|
+
if (promptStates[idx]!.lines.length > MAX_LINES) {
|
|
282
|
+
promptStates[idx]!.lines = promptStates[idx]!.lines.slice(-MAX_LINES)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function pushToolLine(idx: number, text: string) {
|
|
287
|
+
promptStates[idx]!.lines.push(text)
|
|
288
|
+
if (promptStates[idx]!.lines.length > MAX_LINES) {
|
|
289
|
+
promptStates[idx]!.lines.splice(0, 1)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const runOptions: Record<string, any> = { streaming: true }
|
|
294
|
+
if (options['in-folder']) {
|
|
295
|
+
runOptions.cwd = container.paths.resolve(options['in-folder'])
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const isCli = CLI_TARGETS.has(target)
|
|
299
|
+
let sessionPromise: Promise<any>
|
|
300
|
+
|
|
301
|
+
if (isCli) {
|
|
302
|
+
const featureName = target === 'claude' ? 'claudeCode' : 'openaiCodex'
|
|
303
|
+
const feature = container.feature(featureName)
|
|
304
|
+
|
|
305
|
+
const available = await feature.checkAvailability()
|
|
306
|
+
if (!available) {
|
|
307
|
+
console.error(`${target} CLI is not available. Make sure it is installed and in your PATH.`)
|
|
308
|
+
process.exit(1)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (target === 'claude') {
|
|
312
|
+
runOptions.permissionMode = options['permission-mode']
|
|
313
|
+
if (options.chrome) runOptions.chrome = true
|
|
314
|
+
if (options.local) runOptions.local = true
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
feature.on('session:message', ({ sessionId, message }: { sessionId: string; message: any }) => {
|
|
318
|
+
const idx = sessionMap.get(sessionId)
|
|
319
|
+
if (idx === undefined) return
|
|
320
|
+
|
|
321
|
+
const role = message?.message?.role ?? message?.role
|
|
322
|
+
if (role && role !== 'assistant') return
|
|
323
|
+
|
|
324
|
+
const content = message?.message?.content ?? message?.content
|
|
325
|
+
if (!Array.isArray(content)) return
|
|
326
|
+
|
|
327
|
+
const usage = message?.message?.usage ?? message?.usage
|
|
328
|
+
if (usage?.output_tokens) promptStates[idx]!.outputTokens += usage.output_tokens
|
|
329
|
+
|
|
330
|
+
for (const block of content) {
|
|
331
|
+
if (block.type === 'text' && block.text) {
|
|
332
|
+
pushLines(idx, block.text)
|
|
333
|
+
} else if (block.type === 'tool_use') {
|
|
334
|
+
const argsStr = JSON.stringify(block.input).slice(0, 80)
|
|
335
|
+
pushToolLine(idx, ` > ${block.name}(${argsStr})`)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
if (options['out-file']) {
|
|
341
|
+
feature.on('session:event', ({ sessionId, event }: { sessionId: string; event: any }) => {
|
|
342
|
+
const idx = sessionMap.get(sessionId)
|
|
343
|
+
if (idx === undefined) return
|
|
344
|
+
// Skip partial assistant messages (streaming tokens) — only collect complete messages
|
|
345
|
+
if (event.type === 'assistant') {
|
|
346
|
+
if (event.message?.stop_reason) promptStates[idx]!.collectedEvents.push(event)
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
if (event.type === 'tool_result' || event.type === 'message' || event.type === 'function_call_output') {
|
|
350
|
+
promptStates[idx]!.collectedEvents.push(event)
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Start all sessions — merge per-prompt agentOptions with shared runOptions
|
|
356
|
+
for (let i = 0; i < prepared.length; i++) {
|
|
357
|
+
const perPromptOptions = { ...prepared[i]!.agentOptions, ...runOptions }
|
|
358
|
+
if (options.model) perPromptOptions.model = options.model
|
|
359
|
+
const id = await feature.start(prepared[i]!.promptContent, perPromptOptions)
|
|
360
|
+
sessionMap.set(id, i)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const ids = [...sessionMap.keys()]
|
|
364
|
+
sessionPromise = Promise.allSettled(ids.map((id) => feature.waitForSession(id))).then((results) => {
|
|
365
|
+
results.forEach((r, ri) => {
|
|
366
|
+
const id = ids[ri]!
|
|
367
|
+
const idx = sessionMap.get(id)!
|
|
368
|
+
promptStates[idx]!.durationMs = Date.now() - promptStates[idx]!.startTime
|
|
369
|
+
if (r.status === 'fulfilled' && r.value?.status === 'error') {
|
|
370
|
+
promptStates[idx]!.status = 'error'
|
|
371
|
+
promptStates[idx]!.error = r.value?.error || 'Session failed'
|
|
372
|
+
} else if (r.status === 'rejected') {
|
|
373
|
+
promptStates[idx]!.status = 'error'
|
|
374
|
+
promptStates[idx]!.error = String(r.reason)
|
|
375
|
+
} else {
|
|
376
|
+
promptStates[idx]!.status = 'done'
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
allDone = true
|
|
380
|
+
})
|
|
381
|
+
} else {
|
|
382
|
+
// Assistant targets
|
|
383
|
+
const manager = container.feature('assistantsManager')
|
|
384
|
+
await manager.discover()
|
|
385
|
+
|
|
386
|
+
const entry = manager.get(target)
|
|
387
|
+
if (!entry) {
|
|
388
|
+
const entries = manager.list()
|
|
389
|
+
const available = entries.length ? entries.map((e: any) => e.name).join(', ') : '(none)'
|
|
390
|
+
console.error(`Assistant "${target}" not found. Available: ${available}`)
|
|
391
|
+
process.exit(1)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const lineBuffers: string[] = prepared.map(() => '')
|
|
395
|
+
const textBuffers: string[] = prepared.map(() => '')
|
|
396
|
+
|
|
397
|
+
const assistants = prepared.map((p, i) => {
|
|
398
|
+
const createOptions: Record<string, any> = { ...p.agentOptions }
|
|
399
|
+
if (options.model) createOptions.model = options.model
|
|
400
|
+
if (options.local) createOptions.local = true
|
|
401
|
+
const assistant = manager.create(target, createOptions)
|
|
402
|
+
|
|
403
|
+
assistant.on('chunk', (text: string) => {
|
|
404
|
+
lineBuffers[i] += text
|
|
405
|
+
const parts = lineBuffers[i]!.split('\n')
|
|
406
|
+
lineBuffers[i] = parts.pop() || ''
|
|
407
|
+
if (parts.length) {
|
|
408
|
+
promptStates[i]!.lines.push(...parts)
|
|
409
|
+
if (promptStates[i]!.lines.length > MAX_LINES) {
|
|
410
|
+
promptStates[i]!.lines = promptStates[i]!.lines.slice(-MAX_LINES)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (options['out-file']) {
|
|
414
|
+
textBuffers[i] += text
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
assistant.on('toolCall', (toolName: string, args: any) => {
|
|
419
|
+
const argsStr = JSON.stringify(args).slice(0, 80)
|
|
420
|
+
pushToolLine(i, ` > ${toolName}(${argsStr})`)
|
|
421
|
+
if (options['out-file']) {
|
|
422
|
+
promptStates[i]!.collectedEvents.push({
|
|
423
|
+
type: 'assistant',
|
|
424
|
+
message: { content: [{ type: 'tool_use', name: toolName, input: args }] },
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
assistant.on('toolResult', (toolName: string, result: any) => {
|
|
430
|
+
const preview = typeof result === 'string' ? result.slice(0, 60) : JSON.stringify(result).slice(0, 60)
|
|
431
|
+
pushToolLine(i, ` ✓ ${toolName} → ${preview}`)
|
|
432
|
+
if (options['out-file']) {
|
|
433
|
+
promptStates[i]!.collectedEvents.push({
|
|
434
|
+
type: 'tool_result',
|
|
435
|
+
content: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
assistant.on('toolError', (toolName: string, error: any) => {
|
|
441
|
+
const msg = error?.message || String(error)
|
|
442
|
+
pushToolLine(i, ` ✗ ${toolName}: ${msg}`)
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
return assistant
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
sessionPromise = Promise.allSettled(assistants.map((a, i) => a.ask(prepared[i]!.promptContent))).then(
|
|
449
|
+
(results) => {
|
|
450
|
+
results.forEach((r, i) => {
|
|
451
|
+
promptStates[i]!.durationMs = Date.now() - promptStates[i]!.startTime
|
|
452
|
+
// Flush remaining line buffer
|
|
453
|
+
if (lineBuffers[i]) {
|
|
454
|
+
promptStates[i]!.lines.push(lineBuffers[i]!)
|
|
455
|
+
lineBuffers[i] = ''
|
|
456
|
+
}
|
|
457
|
+
// Flush accumulated text as a single event for out-file
|
|
458
|
+
if (textBuffers[i]) {
|
|
459
|
+
promptStates[i]!.collectedEvents.push({ type: 'assistant', message: { content: [{ type: 'text', text: textBuffers[i] }] } })
|
|
460
|
+
textBuffers[i] = ''
|
|
461
|
+
}
|
|
462
|
+
if (r.status === 'rejected') {
|
|
463
|
+
promptStates[i]!.status = 'error'
|
|
464
|
+
promptStates[i]!.error = String(r.reason)
|
|
465
|
+
} else {
|
|
466
|
+
promptStates[i]!.status = 'done'
|
|
467
|
+
}
|
|
468
|
+
})
|
|
469
|
+
allDone = true
|
|
470
|
+
},
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// --- Ink React Component ---
|
|
475
|
+
function App() {
|
|
476
|
+
const { exit } = useApp()
|
|
477
|
+
const { stdout } = useStdout()
|
|
478
|
+
const [tick, setTick] = useState(0)
|
|
479
|
+
|
|
480
|
+
const cols = stdout?.columns || 120
|
|
481
|
+
const rows = stdout?.rows || 30
|
|
482
|
+
const numPrompts = prepared.length
|
|
483
|
+
const colWidth = Math.max(30, Math.floor(cols / numPrompts))
|
|
484
|
+
const visibleLines = Math.max(5, rows - 7)
|
|
485
|
+
|
|
486
|
+
useEffect(() => {
|
|
487
|
+
const timer = setInterval(() => setTick((t: number) => t + 1), 200)
|
|
488
|
+
return () => clearInterval(timer)
|
|
489
|
+
}, [])
|
|
490
|
+
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (allDone) {
|
|
493
|
+
setTimeout(() => exit(), 400)
|
|
494
|
+
}
|
|
495
|
+
}, [tick])
|
|
496
|
+
|
|
497
|
+
useInput((input: string, key: any) => {
|
|
498
|
+
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
499
|
+
userAborted = true
|
|
500
|
+
if (isCli) {
|
|
501
|
+
const featureName = target === 'claude' ? 'claudeCode' : 'openaiCodex'
|
|
502
|
+
const feature = container.feature(featureName)
|
|
503
|
+
for (const [sid] of sessionMap) {
|
|
504
|
+
try {
|
|
505
|
+
feature.abort(sid)
|
|
506
|
+
} catch {}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
exit()
|
|
510
|
+
}
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
const formatElapsed = (ms: number) => {
|
|
514
|
+
const s = Math.floor(ms / 1000)
|
|
515
|
+
const m = Math.floor(s / 60)
|
|
516
|
+
const sec = s % 60
|
|
517
|
+
return `${m}:${String(sec).padStart(2, '0')}`
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const runningCount = promptStates.filter((p) => p.status === 'running').length
|
|
521
|
+
|
|
522
|
+
return h(
|
|
523
|
+
Box,
|
|
524
|
+
{ flexDirection: 'column', width: cols },
|
|
525
|
+
// Header
|
|
526
|
+
h(
|
|
527
|
+
Box,
|
|
528
|
+
{ justifyContent: 'space-between', paddingX: 1, marginBottom: 1 },
|
|
529
|
+
h(Text, { bold: true, color: '#61dafb' }, 'LUCA PROMPT // PARALLEL'),
|
|
530
|
+
h(Text, { dimColor: true }, `${runningCount} running / ${numPrompts} total`),
|
|
531
|
+
),
|
|
532
|
+
// Columns
|
|
533
|
+
h(
|
|
534
|
+
Box,
|
|
535
|
+
{ flexDirection: 'row' },
|
|
536
|
+
...promptStates.map((ps, i) => {
|
|
537
|
+
const elapsed = ps.status === 'running' ? Date.now() - ps.startTime : ps.durationMs
|
|
538
|
+
const borderColor = ps.status === 'running' ? 'cyan' : ps.status === 'done' ? 'green' : 'red'
|
|
539
|
+
const statusLabel =
|
|
540
|
+
ps.status === 'running'
|
|
541
|
+
? `RUNNING ${formatElapsed(elapsed)}`
|
|
542
|
+
: ps.status === 'done'
|
|
543
|
+
? `DONE ${formatElapsed(ps.durationMs)}`
|
|
544
|
+
: `ERROR`
|
|
545
|
+
const tail = ps.lines.slice(-visibleLines)
|
|
546
|
+
|
|
547
|
+
return h(
|
|
548
|
+
Box,
|
|
549
|
+
{
|
|
550
|
+
key: String(i),
|
|
551
|
+
flexDirection: 'column',
|
|
552
|
+
width: colWidth,
|
|
553
|
+
borderStyle: 'round',
|
|
554
|
+
borderColor,
|
|
555
|
+
paddingX: 1,
|
|
556
|
+
height: visibleLines + 4,
|
|
557
|
+
},
|
|
558
|
+
h(Text, { bold: true }, ps.filename),
|
|
559
|
+
h(Text, { color: borderColor, dimColor: ps.status === 'done' }, statusLabel),
|
|
560
|
+
h(Text, { dimColor: true }, '\u2500'.repeat(Math.max(1, colWidth - 4))),
|
|
561
|
+
h(Text, { wrap: 'truncate' }, tail.join('\n')),
|
|
562
|
+
)
|
|
563
|
+
}),
|
|
564
|
+
),
|
|
565
|
+
// Footer
|
|
566
|
+
h(Box, { paddingX: 1 }, h(Text, { dimColor: true }, 'q: quit all')),
|
|
567
|
+
)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
await ink.render(h(App))
|
|
571
|
+
await ink.waitUntilExit()
|
|
572
|
+
|
|
573
|
+
if (userAborted) return
|
|
574
|
+
|
|
575
|
+
// Wait for sessions to fully settle
|
|
576
|
+
await sessionPromise
|
|
577
|
+
|
|
578
|
+
// Post-completion: out-files
|
|
579
|
+
if (options['out-file']) {
|
|
580
|
+
const base = options['out-file']
|
|
581
|
+
const dotIdx = base.lastIndexOf('.')
|
|
582
|
+
const ext = dotIdx > 0 ? base.slice(dotIdx) : '.md'
|
|
583
|
+
const stem = dotIdx > 0 ? base.slice(0, dotIdx) : base
|
|
584
|
+
|
|
585
|
+
for (let i = 0; i < promptStates.length; i++) {
|
|
586
|
+
const ps = promptStates[i]!
|
|
587
|
+
if (!ps.collectedEvents.length) continue
|
|
588
|
+
const promptBasename = paths.basename(prepared[i]!.resolvedPath)
|
|
589
|
+
const promptStem = promptBasename.lastIndexOf('.') > 0 ? promptBasename.slice(0, promptBasename.lastIndexOf('.')) : promptBasename
|
|
590
|
+
const outPath = paths.resolve(`${stem}-${promptStem}${ext}`)
|
|
591
|
+
const markdown = formatSessionMarkdown(ps.collectedEvents, options['include-output'])
|
|
592
|
+
await Bun.write(outPath, markdown)
|
|
593
|
+
console.log(`Session saved to ${outPath}`)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Print summary
|
|
598
|
+
const errors = promptStates.filter((p) => p.status === 'error')
|
|
599
|
+
if (errors.length) {
|
|
600
|
+
console.error(`\n${errors.length} prompt(s) failed:`)
|
|
601
|
+
for (const ps of errors) {
|
|
602
|
+
console.error(` ${ps.filename}: ${ps.error}`)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
interface InputDef {
|
|
608
|
+
description?: string
|
|
609
|
+
required?: boolean
|
|
610
|
+
default?: any
|
|
611
|
+
type?: string
|
|
612
|
+
choices?: string[]
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function parseInputDefs(meta: Record<string, any>): Record<string, InputDef> | null {
|
|
616
|
+
if (!meta?.inputs || typeof meta.inputs !== 'object') return null
|
|
617
|
+
const defs: Record<string, InputDef> = {}
|
|
618
|
+
for (const [key, val] of Object.entries(meta.inputs)) {
|
|
619
|
+
if (typeof val === 'object' && val !== null) {
|
|
620
|
+
defs[key] = val as InputDef
|
|
621
|
+
} else {
|
|
622
|
+
// Shorthand: `topic: "What to write about"` means description-only, required
|
|
623
|
+
defs[key] = { description: typeof val === 'string' ? val : String(val) }
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return Object.keys(defs).length ? defs : null
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
async function resolveInputs(
|
|
630
|
+
inputDefs: Record<string, InputDef>,
|
|
631
|
+
options: z.infer<typeof argsSchema>,
|
|
632
|
+
container: any,
|
|
633
|
+
): Promise<Record<string, any>> {
|
|
634
|
+
const { fs, paths } = container
|
|
635
|
+
const yaml = container.feature('yaml')
|
|
636
|
+
const ui = container.feature('ui')
|
|
637
|
+
|
|
638
|
+
// Layer 1: inputs-file (lowest priority of supplied values)
|
|
639
|
+
let fileInputs: Record<string, any> = {}
|
|
640
|
+
if (options['inputs-file']) {
|
|
641
|
+
const filePath = paths.resolve(options['inputs-file'])
|
|
642
|
+
const raw = fs.readFile(filePath) as string
|
|
643
|
+
if (filePath.endsWith('.json')) {
|
|
644
|
+
fileInputs = JSON.parse(raw)
|
|
645
|
+
} else {
|
|
646
|
+
fileInputs = yaml.parse(raw) || {}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Layer 2: CLI flags (highest priority) — any unknown option that matches an input name
|
|
651
|
+
const cliInputs: Record<string, any> = {}
|
|
652
|
+
const argv = container.argv as Record<string, any>
|
|
653
|
+
for (const key of Object.keys(inputDefs)) {
|
|
654
|
+
if (argv[key] !== undefined) {
|
|
655
|
+
cliInputs[key] = argv[key]
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Merge: CLI > file > defaults
|
|
660
|
+
const supplied: Record<string, any> = {}
|
|
661
|
+
for (const [key, def] of Object.entries(inputDefs)) {
|
|
662
|
+
if (cliInputs[key] !== undefined) {
|
|
663
|
+
supplied[key] = cliInputs[key]
|
|
664
|
+
} else if (fileInputs[key] !== undefined) {
|
|
665
|
+
supplied[key] = fileInputs[key]
|
|
666
|
+
} else if (def.default !== undefined) {
|
|
667
|
+
supplied[key] = def.default
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Find missing required inputs
|
|
672
|
+
const missing: string[] = []
|
|
673
|
+
for (const [key, def] of Object.entries(inputDefs)) {
|
|
674
|
+
const isRequired = def.required !== false // default to required
|
|
675
|
+
if (isRequired && supplied[key] === undefined) {
|
|
676
|
+
missing.push(key)
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (missing.length === 0) return supplied
|
|
681
|
+
|
|
682
|
+
// In parallel mode, we can't run an interactive wizard
|
|
683
|
+
if ((options as any).parallel) {
|
|
684
|
+
console.error(`Missing required inputs for parallel mode (use --inputs-file or CLI flags): ${missing.join(', ')}`)
|
|
685
|
+
process.exit(1)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Build wizard questions for missing inputs
|
|
689
|
+
const questions = missing.map((key) => {
|
|
690
|
+
const def = inputDefs[key]!
|
|
691
|
+
const q: Record<string, any> = {
|
|
692
|
+
name: key,
|
|
693
|
+
message: def.description || key,
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Auto-infer type
|
|
697
|
+
if (def.choices?.length) {
|
|
698
|
+
q.type = 'list'
|
|
699
|
+
q.choices = def.choices
|
|
700
|
+
} else if (def.type) {
|
|
701
|
+
q.type = def.type
|
|
702
|
+
} else {
|
|
703
|
+
q.type = 'input'
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (def.default !== undefined) {
|
|
707
|
+
q.default = def.default
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return q
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
const answers = await ui.wizard(questions, supplied)
|
|
714
|
+
return { ...supplied, ...answers }
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function substituteInputs(content: string, inputs: Record<string, any>): string {
|
|
718
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
719
|
+
return inputs[key] !== undefined ? String(inputs[key]) : match
|
|
720
|
+
})
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
async function executePromptFile(resolvedPath: string, container: any, inputs?: Record<string, any>): Promise<string> {
|
|
724
|
+
if (!container.docs.isLoaded) await container.docs.load()
|
|
725
|
+
const doc = await container.docs.parseMarkdownAtPath(resolvedPath)
|
|
726
|
+
const vm = container.feature('vm')
|
|
727
|
+
const parts: string[] = []
|
|
728
|
+
|
|
729
|
+
const capturedLines: string[] = []
|
|
730
|
+
const captureConsole = {
|
|
731
|
+
log: (...args: any[]) => capturedLines.push(args.map(String).join(' ')),
|
|
732
|
+
error: (...args: any[]) => capturedLines.push(args.map(String).join(' ')),
|
|
733
|
+
warn: (...args: any[]) => capturedLines.push(args.map(String).join(' ')),
|
|
734
|
+
info: (...args: any[]) => capturedLines.push(args.map(String).join(' ')),
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const shared = vm.createContext({
|
|
738
|
+
...container.context,
|
|
739
|
+
INPUTS: inputs || {},
|
|
740
|
+
console: captureConsole,
|
|
741
|
+
setTimeout, clearTimeout, setInterval, clearInterval,
|
|
742
|
+
fetch, URL, URLSearchParams,
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
for (const node of doc.ast.children) {
|
|
746
|
+
if (node.type === 'code') {
|
|
747
|
+
const { value, lang, meta } = node
|
|
748
|
+
if (!lang || !['ts', 'js', 'tsx', 'jsx'].includes(lang)) {
|
|
749
|
+
parts.push(doc.stringify({ type: 'root', children: [node] }))
|
|
750
|
+
continue
|
|
751
|
+
}
|
|
752
|
+
if (meta && typeof meta === 'string' && meta.toLowerCase().includes('skip')) continue
|
|
753
|
+
|
|
754
|
+
capturedLines.length = 0
|
|
755
|
+
let code = value
|
|
756
|
+
if (lang === 'tsx' || lang === 'jsx') {
|
|
757
|
+
const transpiler = container.feature('transpiler')
|
|
758
|
+
const { code: transformed } = transpiler.transformSync(value, { loader: lang as 'tsx' | 'jsx', format: 'cjs' })
|
|
759
|
+
code = transformed
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
await vm.run(code, shared)
|
|
763
|
+
Object.assign(shared, container.context)
|
|
764
|
+
|
|
765
|
+
if (capturedLines.length) {
|
|
766
|
+
parts.push(capturedLines.join('\n'))
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
parts.push(doc.stringify({ type: 'root', children: [node] }))
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return parts.join('\n\n')
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
async function preparePrompt(
|
|
777
|
+
filePath: string,
|
|
778
|
+
options: z.infer<typeof argsSchema>,
|
|
779
|
+
container: any,
|
|
780
|
+
): Promise<PreparedPrompt | null> {
|
|
781
|
+
const { fs, paths } = container
|
|
782
|
+
|
|
783
|
+
let resolvedPath = paths.resolve(filePath)
|
|
784
|
+
if (!fs.exists(resolvedPath)) {
|
|
785
|
+
// Try common fallbacks: add .md extension, docs/ prefix, or both
|
|
786
|
+
const candidates = [
|
|
787
|
+
`${resolvedPath}.md`,
|
|
788
|
+
paths.resolve('docs', filePath),
|
|
789
|
+
paths.resolve('docs', `${filePath}.md`),
|
|
790
|
+
]
|
|
791
|
+
const found = candidates.find((c) => fs.exists(c))
|
|
792
|
+
if (!found) {
|
|
793
|
+
console.error(`Prompt file not found: ${resolvedPath}`)
|
|
794
|
+
return null
|
|
795
|
+
}
|
|
796
|
+
resolvedPath = found
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
let content = fs.readFile(resolvedPath) as string
|
|
800
|
+
|
|
801
|
+
// Parse frontmatter for input definitions and agentOptions
|
|
802
|
+
let resolvedInputs: Record<string, any> = {}
|
|
803
|
+
let agentOptions: Record<string, any> = {}
|
|
804
|
+
let hasInputDefs = false
|
|
805
|
+
if (content.startsWith('---')) {
|
|
806
|
+
const fmEnd = content.indexOf('\n---', 3)
|
|
807
|
+
if (fmEnd !== -1) {
|
|
808
|
+
const yaml = container.feature('yaml')
|
|
809
|
+
const meta = yaml.parse(content.slice(4, fmEnd)) || {}
|
|
810
|
+
const inputDefs = parseInputDefs(meta)
|
|
811
|
+
if (inputDefs) {
|
|
812
|
+
hasInputDefs = true
|
|
813
|
+
resolvedInputs = await resolveInputs(inputDefs, options, container)
|
|
814
|
+
}
|
|
815
|
+
if (meta.agentOptions && typeof meta.agentOptions === 'object') {
|
|
816
|
+
agentOptions = { ...meta.agentOptions }
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (options['inputs-file'] && !hasInputDefs) {
|
|
822
|
+
console.warn(`Warning: --inputs-file was provided but ${filePath} does not define any inputs in its frontmatter`)
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
let promptContent: string
|
|
826
|
+
if (options['skip-eval']) {
|
|
827
|
+
// Strip frontmatter but don't execute code blocks
|
|
828
|
+
if (content.startsWith('---')) {
|
|
829
|
+
const fmEnd = content.indexOf('\n---', 3)
|
|
830
|
+
if (fmEnd !== -1) {
|
|
831
|
+
promptContent = content.slice(fmEnd + 4).trimStart()
|
|
832
|
+
} else {
|
|
833
|
+
promptContent = content
|
|
834
|
+
}
|
|
835
|
+
} else {
|
|
836
|
+
promptContent = content
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
promptContent = await executePromptFile(resolvedPath, container, resolvedInputs)
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Re-attach frontmatter if requested
|
|
843
|
+
if (options['include-frontmatter'] && content.startsWith('---')) {
|
|
844
|
+
const fmEnd = content.indexOf('\n---', 3)
|
|
845
|
+
if (fmEnd !== -1) {
|
|
846
|
+
const frontmatter = content.slice(0, fmEnd + 4)
|
|
847
|
+
promptContent = frontmatter + '\n' + promptContent
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Substitute {{key}} placeholders with resolved input values
|
|
852
|
+
if (Object.keys(resolvedInputs).length) {
|
|
853
|
+
promptContent = substituteInputs(promptContent, resolvedInputs)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Exclude sections by heading name
|
|
857
|
+
if (options['exclude-sections']) {
|
|
858
|
+
const headings = options['exclude-sections'].split(',').map((s) => s.trim()).filter(Boolean)
|
|
859
|
+
let doc = new Document({ id: filePath, content: promptContent, collection: null as any })
|
|
860
|
+
|
|
861
|
+
for (const heading of headings) {
|
|
862
|
+
try {
|
|
863
|
+
doc = doc.removeSection(heading)
|
|
864
|
+
} catch {
|
|
865
|
+
// Section not found — skip silently
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
promptContent = doc.content
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
resolvedPath,
|
|
874
|
+
promptContent,
|
|
875
|
+
filename: paths.basename(resolvedPath),
|
|
876
|
+
agentOptions,
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
export default async function prompt(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
881
|
+
const container = context.container as any
|
|
882
|
+
const { fs, paths } = container
|
|
883
|
+
|
|
884
|
+
let target = container.argv._[1] as string | undefined
|
|
885
|
+
const allPaths = (container.argv._.slice(2) as string[]).filter(Boolean)
|
|
886
|
+
|
|
887
|
+
// If only one arg given and it looks like a file path, default target to claude
|
|
888
|
+
if (target && allPaths.length === 0) {
|
|
889
|
+
const candidate = paths.resolve(target)
|
|
890
|
+
if (fs.exists(candidate)) {
|
|
891
|
+
allPaths.push(target)
|
|
892
|
+
// this gives a way for you to say on a per project basis what you want the default coding assistant to be for the prompt command
|
|
893
|
+
// TODO need to document this somewhere
|
|
894
|
+
const { codingAssistant } = (container.manifest.luca || {})
|
|
895
|
+
target = codingAssistant || 'claude'
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Normalize target aliases (e.g. claude-code → claude, openai-codex → codex)
|
|
900
|
+
if (target) target = normalizeTarget(target)
|
|
901
|
+
|
|
902
|
+
if (!target || allPaths.length === 0) {
|
|
903
|
+
console.error('Usage: luca prompt [claude|codex|assistant-name] <path/to/prompt.md> [more paths...]')
|
|
904
|
+
process.exit(1)
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// --- Parallel mode ---
|
|
908
|
+
if (options.parallel && allPaths.length > 1) {
|
|
909
|
+
if (allPaths.length > 4) {
|
|
910
|
+
console.error('--parallel supports a maximum of 4 concurrent prompts')
|
|
911
|
+
process.exit(1)
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const prepared: PreparedPrompt[] = []
|
|
915
|
+
for (const pp of allPaths) {
|
|
916
|
+
const p = await preparePrompt(pp, options, container)
|
|
917
|
+
if (p) prepared.push(p)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (prepared.length === 0) {
|
|
921
|
+
console.error('No prompt files to run (all skipped).')
|
|
922
|
+
process.exit(1)
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (options['dry-run']) {
|
|
926
|
+
const ui = container.feature('ui')
|
|
927
|
+
console.log(ui.colors.bold('\n── Dry Run (Parallel) ──\n'))
|
|
928
|
+
console.log(ui.colors.bold('Target:'), target)
|
|
929
|
+
console.log(ui.colors.bold('Prompts:'), prepared.length)
|
|
930
|
+
for (const p of prepared) {
|
|
931
|
+
console.log(ui.colors.bold(`\n── ${p.filename} ──`))
|
|
932
|
+
console.log(ui.colors.dim(` Path: ${p.resolvedPath}`))
|
|
933
|
+
console.log(ui.colors.dim(` Length: ${p.promptContent.length} chars`))
|
|
934
|
+
if (Object.keys(p.agentOptions).length) {
|
|
935
|
+
console.log(ui.colors.dim(' Agent options:'))
|
|
936
|
+
for (const [key, val] of Object.entries(p.agentOptions)) {
|
|
937
|
+
const display = typeof val === 'object' ? JSON.stringify(val) : val
|
|
938
|
+
console.log(ui.colors.dim(` ${key}: ${display}`))
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
console.log('')
|
|
942
|
+
process.stdout.write(ui.markdown(p.promptContent))
|
|
943
|
+
}
|
|
944
|
+
return
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (prepared.length > 1) {
|
|
948
|
+
await runParallel(target, prepared, options, container)
|
|
949
|
+
return
|
|
950
|
+
}
|
|
951
|
+
// Only 1 left after filtering — fall through to single mode
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// --- Single prompt mode ---
|
|
955
|
+
const promptPath = allPaths[0]!
|
|
956
|
+
const p = await preparePrompt(promptPath, options, container)
|
|
957
|
+
|
|
958
|
+
if (!p) {
|
|
959
|
+
process.exit(1)
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const ui = container.feature('ui')
|
|
963
|
+
|
|
964
|
+
if (options['dry-run']) {
|
|
965
|
+
const runOptions: Record<string, any> = { ...p.agentOptions }
|
|
966
|
+
if (options.model) runOptions.model = options.model
|
|
967
|
+
if (options['in-folder']) runOptions.cwd = container.paths.resolve(options['in-folder'])
|
|
968
|
+
if (options['out-file']) runOptions.outFile = options['out-file']
|
|
969
|
+
if (options['include-output']) runOptions.includeOutput = true
|
|
970
|
+
if (options['exclude-sections']) runOptions.excludeSections = options['exclude-sections']
|
|
971
|
+
if (CLI_TARGETS.has(target)) {
|
|
972
|
+
runOptions.permissionMode = options['permission-mode']
|
|
973
|
+
if (options.chrome) runOptions.chrome = true
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
console.log(ui.colors.bold('\n── Dry Run ──\n'))
|
|
977
|
+
console.log(ui.colors.bold('Target:'), target)
|
|
978
|
+
console.log(ui.colors.bold('Prompt file:'), p.resolvedPath)
|
|
979
|
+
console.log(ui.colors.bold('Prompt length:'), `${p.promptContent.length} chars`)
|
|
980
|
+
if (Object.keys(runOptions).length) {
|
|
981
|
+
console.log(ui.colors.bold('Options:'))
|
|
982
|
+
for (const [key, val] of Object.entries(runOptions)) {
|
|
983
|
+
const display = typeof val === 'object' ? JSON.stringify(val) : val
|
|
984
|
+
console.log(` ${key}: ${display}`)
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
console.log(ui.colors.bold('\n── Prompt Content ──\n'))
|
|
988
|
+
process.stdout.write(ui.markdown(p.promptContent))
|
|
989
|
+
return
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
process.stdout.write(ui.markdown(p.promptContent))
|
|
993
|
+
|
|
994
|
+
let stats: RunStats
|
|
995
|
+
|
|
996
|
+
if (CLI_TARGETS.has(target)) {
|
|
997
|
+
stats = await runClaudeOrCodex(target as 'claude' | 'codex', p.promptContent, container, options, p.agentOptions)
|
|
998
|
+
} else {
|
|
999
|
+
stats = await runAssistant(target, p.promptContent, options, container, p.agentOptions)
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (options['out-file'] && stats.collectedEvents.length) {
|
|
1003
|
+
const markdown = formatSessionMarkdown(stats.collectedEvents, options['include-output'])
|
|
1004
|
+
const outPath = paths.resolve(options['out-file'])
|
|
1005
|
+
await Bun.write(outPath, markdown)
|
|
1006
|
+
console.log(`Session saved to ${outPath}`)
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
commands.registerHandler('prompt', {
|
|
1011
|
+
description: 'Send a prompt file to an assistant, Claude Code, or OpenAI Codex',
|
|
1012
|
+
argsSchema,
|
|
1013
|
+
handler: prompt,
|
|
1014
|
+
})
|