luca 1.1.2 → 3.0.0
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 +169 -0
- package/AGENTS.md +99 -0
- package/CLAUDE.md +115 -0
- package/CNAME +1 -0
- package/README.md +257 -8
- package/RUNME.md +56 -0
- package/assistants/codingAssistant/ABOUT.md +5 -0
- package/assistants/codingAssistant/CORE.md +28 -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 +2769 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-bootstrap.ts +118 -0
- package/commands/build-python-bridge.ts +43 -0
- package/commands/build-scaffolds.ts +176 -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 -0
- 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/docs/CLI.md +335 -0
- package/docs/CNAME +1 -0
- package/docs/README.md +60 -0
- package/docs/TABLE-OF-CONTENTS.md +183 -0
- package/docs/apis/clients/elevenlabs.md +308 -0
- package/docs/apis/clients/graph.md +107 -0
- package/docs/apis/clients/openai.md +429 -0
- package/docs/apis/clients/rest.md +161 -0
- package/docs/apis/clients/websocket.md +174 -0
- package/docs/apis/features/agi/assistant.md +625 -0
- package/docs/apis/features/agi/assistants-manager.md +282 -0
- package/docs/apis/features/agi/auto-assistant.md +279 -0
- package/docs/apis/features/agi/browser-use.md +802 -0
- package/docs/apis/features/agi/claude-code.md +884 -0
- package/docs/apis/features/agi/conversation-history.md +364 -0
- package/docs/apis/features/agi/conversation.md +548 -0
- package/docs/apis/features/agi/docs-reader.md +99 -0
- package/docs/apis/features/agi/file-tools.md +163 -0
- package/docs/apis/features/agi/luca-coder.md +407 -0
- package/docs/apis/features/agi/openai-codex.md +396 -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 +239 -0
- package/docs/apis/features/node/container-link.md +192 -0
- package/docs/apis/features/node/content-db.md +450 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +652 -0
- package/docs/apis/features/node/docker.md +706 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +60 -0
- package/docs/apis/features/node/file-manager.md +191 -0
- package/docs/apis/features/node/fs.md +1217 -0
- package/docs/apis/features/node/git.md +371 -0
- package/docs/apis/features/node/google-auth.md +193 -0
- package/docs/apis/features/node/google-calendar.md +202 -0
- package/docs/apis/features/node/google-docs.md +173 -0
- package/docs/apis/features/node/google-drive.md +246 -0
- package/docs/apis/features/node/google-mail.md +214 -0
- package/docs/apis/features/node/google-sheets.md +194 -0
- package/docs/apis/features/node/grep.md +292 -0
- package/docs/apis/features/node/helpers.md +164 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +249 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/networking.md +316 -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 +146 -0
- package/docs/apis/features/node/package-finder.md +392 -0
- package/docs/apis/features/node/postgres.md +234 -0
- package/docs/apis/features/node/proc.md +399 -0
- package/docs/apis/features/node/process-manager.md +305 -0
- package/docs/apis/features/node/python.md +604 -0
- package/docs/apis/features/node/redis.md +380 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +674 -0
- package/docs/apis/features/node/secure-shell.md +176 -0
- package/docs/apis/features/node/semantic-search.md +408 -0
- package/docs/apis/features/node/sqlite.md +233 -0
- package/docs/apis/features/node/telegram.md +279 -0
- package/docs/apis/features/node/transpiler.md +74 -0
- package/docs/apis/features/node/tts.md +133 -0
- package/docs/apis/features/node/ui.md +701 -0
- package/docs/apis/features/node/vault.md +59 -0
- package/docs/apis/features/node/vm.md +75 -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 +59 -0
- package/docs/apis/features/web/container-link.md +192 -0
- package/docs/apis/features/web/esbuild.md +54 -0
- package/docs/apis/features/web/helpers.md +164 -0
- package/docs/apis/features/web/network.md +44 -0
- package/docs/apis/features/web/speech.md +69 -0
- package/docs/apis/features/web/vault.md +59 -0
- package/docs/apis/features/web/vm.md +75 -0
- package/docs/apis/features/web/voice.md +84 -0
- package/docs/apis/servers/express.md +171 -0
- package/docs/apis/servers/mcp.md +238 -0
- package/docs/apis/servers/websocket.md +170 -0
- package/docs/bootstrap/CLAUDE.md +101 -0
- package/docs/bootstrap/SKILL.md +341 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +30 -0
- package/docs/bootstrap/templates/runme.md +54 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/assistant-hooks-reference.ts +171 -0
- package/docs/examples/assistant-with-process-manager.md +84 -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/entity.md +124 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/feature-as-tool-provider.md +143 -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/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/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +132 -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/structured-output-with-assistants.md +144 -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/websocket-ask-and-reply-example.md +128 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/assistant-factory-pattern.md +142 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +164 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +41 -0
- package/docs/philosophy.md +86 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/check-for-undocumented-features.md +27 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/scaffolds/client.md +149 -0
- package/docs/scaffolds/command.md +120 -0
- package/docs/scaffolds/endpoint.md +171 -0
- package/docs/scaffolds/feature.md +158 -0
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +196 -0
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +166 -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 +252 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +203 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +157 -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/docs/tutorials/19-python-sessions.md +401 -0
- package/docs/tutorials/20-browser-esm.md +234 -0
- package/index.html +1430 -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 -66
- package/public/index.html +1430 -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 +152 -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/feature.ts +13 -0
- package/src/agi/features/agent-memory.ts +694 -0
- package/src/agi/features/assistant.ts +1624 -0
- package/src/agi/features/assistants-manager.ts +418 -0
- package/src/agi/features/autonomous-assistant.ts +431 -0
- package/src/agi/features/browser-use.ts +653 -0
- package/src/agi/features/claude-code.ts +1538 -0
- package/src/agi/features/coding-tools.ts +175 -0
- package/src/agi/features/conversation-history.ts +495 -0
- package/src/agi/features/conversation.ts +1323 -0
- package/src/agi/features/docs-reader.ts +167 -0
- package/src/agi/features/file-tools.ts +293 -0
- package/src/agi/features/luca-coder.ts +639 -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 +478 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/interceptor-chain.ts +89 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/bootstrap/generated.ts +9792 -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 +505 -0
- package/src/commands/bootstrap.ts +244 -0
- package/src/commands/chat.ts +308 -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 +121 -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 +982 -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 +1084 -0
- package/src/container.ts +1186 -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 +40208 -0
- package/src/introspection/generated.node.ts +28686 -0
- package/src/introspection/generated.web.ts +2251 -0
- package/src/introspection/index.ts +296 -0
- package/src/introspection/scan.ts +1131 -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 +812 -0
- package/src/node/features/disk-cache.ts +388 -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 +735 -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 +906 -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 +267 -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 +161 -0
- package/src/node/features/tts.ts +185 -0
- package/src/node/features/ui.ts +786 -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 +227 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +210 -0
- package/src/scaffolds/generated.ts +1815 -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 +269 -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/test/assistant-hooks.test.ts +306 -0
- package/test/assistant.test.ts +81 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +217 -0
- package/test/command.test.ts +267 -0
- package/test/container-link.test.ts +274 -0
- package/test/conversation.test.ts +220 -0
- package/test/features.test.ts +160 -0
- package/test/fork-and-research.test.ts +450 -0
- package/test/integration.test.ts +787 -0
- package/test/interceptor-chain.test.ts +61 -0
- package/test/node-container.test.ts +121 -0
- package/test/python-session.test.ts +105 -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/vm-context.test.ts +146 -0
- package/test/vm-loadmodule.test.ts +213 -0
- package/test/websocket-ask.test.ts +101 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +113 -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/memory.test.ts +204 -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/telegram.test.ts +46 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
- package/LICENSE +0 -21
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -5
- package/dist/cli/run.d.ts +0 -12
- package/dist/cli/run.js +0 -42
- package/dist/config/consts.d.ts +0 -2
- package/dist/config/consts.js +0 -29
- package/dist/config/default.d.ts +0 -8
- package/dist/config/default.js +0 -15
- package/dist/config/initConfig.d.ts +0 -1
- package/dist/config/initConfig.js +0 -52
- package/dist/config/openConfig.d.ts +0 -2
- package/dist/config/openConfig.js +0 -24
- package/dist/config/runConfig.d.ts +0 -3
- package/dist/config/runConfig.js +0 -117
- package/dist/config/types.d.ts +0 -13
- package/dist/config/types.js +0 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -5
- package/dist/utils/common.d.ts +0 -2
- package/dist/utils/common.js +0 -52
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -17
|
@@ -0,0 +1,906 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { Feature } from "../feature.js";
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
import { tmpdir } from 'os';
|
|
7
|
+
import { bridgeScript } from '../../python/generated.js';
|
|
8
|
+
import type { ChildProcess } from 'child_process';
|
|
9
|
+
|
|
10
|
+
export const PythonStateSchema = FeatureStateSchema.extend({
|
|
11
|
+
/** Path to the detected Python executable */
|
|
12
|
+
pythonPath: z.string().nullable().default(null).describe('Path to the detected Python executable'),
|
|
13
|
+
/** Root directory of the Python project */
|
|
14
|
+
projectDir: z.string().nullable().default(null).describe('Root directory of the Python project'),
|
|
15
|
+
/** Detected Python environment type */
|
|
16
|
+
environmentType: z.enum(['uv', 'conda', 'venv', 'system']).nullable().default(null).describe('Detected Python environment type (uv, conda, venv, or system)'),
|
|
17
|
+
/** Whether the Python environment is ready for execution */
|
|
18
|
+
isReady: z.boolean().default(false).describe('Whether the Python environment is ready for execution'),
|
|
19
|
+
/** Path to the last executed Python script */
|
|
20
|
+
lastExecutedScript: z.string().nullable().default(null).describe('Path to the last executed Python script'),
|
|
21
|
+
/** Whether a persistent Python session is currently active */
|
|
22
|
+
sessionActive: z.boolean().default(false).describe('Whether a persistent Python session is currently active'),
|
|
23
|
+
/** Unique ID of the current persistent session */
|
|
24
|
+
sessionId: z.string().nullable().default(null).describe('Unique ID of the current persistent session'),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const PythonOptionsSchema = FeatureOptionsSchema.extend({
|
|
28
|
+
/** Directory containing the Python project */
|
|
29
|
+
dir: z.string().optional().describe('Directory containing the Python project'),
|
|
30
|
+
/** Custom install command to override auto-detection */
|
|
31
|
+
installCommand: z.string().optional().describe('Custom install command to override auto-detection'),
|
|
32
|
+
/** Path to Python script that will populate locals/context */
|
|
33
|
+
contextScript: z.string().optional().describe('Path to Python script that will populate locals/context'),
|
|
34
|
+
/** Specific Python executable to use */
|
|
35
|
+
pythonPath: z.string().optional().describe('Specific Python executable path to use'),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
export type PythonState = z.infer<typeof PythonStateSchema>
|
|
39
|
+
export type PythonOptions = z.infer<typeof PythonOptionsSchema>
|
|
40
|
+
|
|
41
|
+
export const PythonEventsSchema = FeatureEventsSchema.extend({
|
|
42
|
+
ready: z.tuple([]).describe('When the Python environment is ready for execution'),
|
|
43
|
+
environmentDetected: z.tuple([z.object({
|
|
44
|
+
pythonPath: z.string().nullable().describe('Path to the detected Python executable'),
|
|
45
|
+
environmentType: z.enum(['uv', 'conda', 'venv', 'system']).nullable().describe('Detected environment type'),
|
|
46
|
+
}).describe('Environment detection result')]).describe('When the Python environment type is detected'),
|
|
47
|
+
installingDependencies: z.tuple([z.object({
|
|
48
|
+
command: z.string().describe('The install command being run'),
|
|
49
|
+
}).describe('Install details')]).describe('When dependency installation begins'),
|
|
50
|
+
dependenciesInstalled: z.tuple([z.object({
|
|
51
|
+
stdout: z.string().describe('Standard output from install'),
|
|
52
|
+
stderr: z.string().describe('Standard error from install'),
|
|
53
|
+
exitCode: z.number().describe('Process exit code'),
|
|
54
|
+
}).describe('Install result')]).describe('When dependencies are successfully installed'),
|
|
55
|
+
dependencyInstallFailed: z.tuple([z.object({
|
|
56
|
+
stdout: z.string().describe('Standard output from install'),
|
|
57
|
+
stderr: z.string().describe('Standard error from install'),
|
|
58
|
+
exitCode: z.number().describe('Process exit code'),
|
|
59
|
+
}).describe('Install result')]).describe('When dependency installation fails'),
|
|
60
|
+
codeExecuted: z.tuple([z.object({
|
|
61
|
+
code: z.string().describe('The Python code that was executed'),
|
|
62
|
+
variables: z.record(z.string(), z.any()).describe('Variables passed to the execution'),
|
|
63
|
+
result: z.object({
|
|
64
|
+
stdout: z.string().describe('Standard output'),
|
|
65
|
+
stderr: z.string().describe('Standard error'),
|
|
66
|
+
exitCode: z.number().describe('Process exit code'),
|
|
67
|
+
}).describe('Execution result'),
|
|
68
|
+
}).describe('Code execution details')]).describe('When Python code finishes executing'),
|
|
69
|
+
fileExecuted: z.tuple([z.object({
|
|
70
|
+
filePath: z.string().describe('Path to the executed Python file'),
|
|
71
|
+
variables: z.record(z.string(), z.any()).describe('Variables passed as arguments'),
|
|
72
|
+
result: z.object({
|
|
73
|
+
stdout: z.string().describe('Standard output'),
|
|
74
|
+
stderr: z.string().describe('Standard error'),
|
|
75
|
+
exitCode: z.number().describe('Process exit code'),
|
|
76
|
+
}).describe('Execution result'),
|
|
77
|
+
}).describe('File execution details')]).describe('When a Python file finishes executing'),
|
|
78
|
+
localsParseError: z.tuple([z.any().describe('The parse error')]).describe('When captured locals fail to parse as JSON'),
|
|
79
|
+
sessionStarted: z.tuple([z.object({
|
|
80
|
+
sessionId: z.string().describe('Unique session identifier'),
|
|
81
|
+
}).describe('Session start details')]).describe('When a persistent Python session starts'),
|
|
82
|
+
sessionStopped: z.tuple([z.object({
|
|
83
|
+
sessionId: z.string().describe('Session identifier that stopped'),
|
|
84
|
+
}).describe('Session stop details')]).describe('When a persistent Python session stops'),
|
|
85
|
+
sessionError: z.tuple([z.object({
|
|
86
|
+
error: z.string().describe('Error message'),
|
|
87
|
+
sessionId: z.string().nullable().describe('Session identifier, if available'),
|
|
88
|
+
}).describe('Session error details')]).describe('When a session-level error occurs'),
|
|
89
|
+
}).describe('Python events')
|
|
90
|
+
|
|
91
|
+
/** Result from a persistent session run() call. */
|
|
92
|
+
export interface RunResult {
|
|
93
|
+
ok: boolean
|
|
94
|
+
result: any
|
|
95
|
+
stdout: string
|
|
96
|
+
error?: string
|
|
97
|
+
traceback?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The Python VM feature provides Python virtual machine capabilities for executing Python code.
|
|
102
|
+
*
|
|
103
|
+
* This feature automatically detects Python environments (uv, conda, venv, system) and provides
|
|
104
|
+
* methods to install dependencies and execute Python scripts. It can manage project-specific
|
|
105
|
+
* Python environments and maintain context between executions.
|
|
106
|
+
*
|
|
107
|
+
* Supports two modes:
|
|
108
|
+
* - **Stateless** (default): `execute()` and `executeFile()` spawn a fresh process per call
|
|
109
|
+
* - **Persistent session**: `startSession()` spawns a long-lived bridge process that maintains
|
|
110
|
+
* state across `run()` calls, enabling real codebase interaction with imports and session variables
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* const python = container.feature('python', {
|
|
115
|
+
* dir: "/path/to/python/project",
|
|
116
|
+
* })
|
|
117
|
+
*
|
|
118
|
+
* // Stateless execution
|
|
119
|
+
* const result = await python.execute('print("Hello from Python!")')
|
|
120
|
+
*
|
|
121
|
+
* // Persistent session
|
|
122
|
+
* await python.startSession()
|
|
123
|
+
* await python.run('import myapp.models')
|
|
124
|
+
* await python.run('users = myapp.models.User.objects.all()')
|
|
125
|
+
* const result = await python.run('print(len(users))')
|
|
126
|
+
* await python.stopSession()
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @extends Feature
|
|
130
|
+
*/
|
|
131
|
+
export class Python<
|
|
132
|
+
T extends PythonState = PythonState,
|
|
133
|
+
K extends PythonOptions = PythonOptions
|
|
134
|
+
> extends Feature<T, K> {
|
|
135
|
+
static override shortcut = "features.python" as const
|
|
136
|
+
static override stateSchema = PythonStateSchema
|
|
137
|
+
static override optionsSchema = PythonOptionsSchema
|
|
138
|
+
static override eventsSchema = PythonEventsSchema
|
|
139
|
+
static { Feature.register(this, 'python') }
|
|
140
|
+
|
|
141
|
+
private _bridgeProcess: ChildProcess | null = null
|
|
142
|
+
private _bridgeScriptPath: string | null = null
|
|
143
|
+
private _pendingRequests = new Map<string, { resolve: (v: any) => void, reject: (e: any) => void }>()
|
|
144
|
+
private _stdoutBuffer = ''
|
|
145
|
+
|
|
146
|
+
override get initialState(): T {
|
|
147
|
+
return {
|
|
148
|
+
...super.initialState,
|
|
149
|
+
pythonPath: null,
|
|
150
|
+
projectDir: null,
|
|
151
|
+
environmentType: null,
|
|
152
|
+
isReady: false,
|
|
153
|
+
lastExecutedScript: null,
|
|
154
|
+
sessionActive: false,
|
|
155
|
+
sessionId: null,
|
|
156
|
+
} as T
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
override async enable(options: any = {}): Promise<this> {
|
|
160
|
+
await super.enable(options)
|
|
161
|
+
|
|
162
|
+
// Setup project directory
|
|
163
|
+
if (this.options.dir) {
|
|
164
|
+
this.state.set('projectDir', resolve(this.options.dir))
|
|
165
|
+
} else {
|
|
166
|
+
this.state.set('projectDir', this.container.cwd)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Detect Python environment
|
|
170
|
+
await this.detectEnvironment()
|
|
171
|
+
|
|
172
|
+
// Execute context script if provided
|
|
173
|
+
if (this.options.contextScript && existsSync(this.options.contextScript)) {
|
|
174
|
+
await this.execute(`exec(open('${this.options.contextScript}').read())`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.state.set('isReady', true)
|
|
178
|
+
this.emit('ready')
|
|
179
|
+
|
|
180
|
+
return this
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Returns the root directory of the Python project. */
|
|
184
|
+
get projectDir() {
|
|
185
|
+
return this.state.get('projectDir') || this.container.cwd
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Returns the path to the Python executable for this environment. */
|
|
189
|
+
get pythonPath() {
|
|
190
|
+
return this.state.get('pythonPath') || 'python'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Returns the detected environment type: 'uv', 'conda', 'venv', or 'system'. */
|
|
194
|
+
get environmentType() {
|
|
195
|
+
return this.state.get('environmentType') || 'system'
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Detects the Python environment type and sets the appropriate Python path.
|
|
200
|
+
*
|
|
201
|
+
* This method checks for various Python environment managers in order of preference:
|
|
202
|
+
* uv, conda, venv, then falls back to system Python. It sets the pythonPath and
|
|
203
|
+
* environmentType in the state.
|
|
204
|
+
*
|
|
205
|
+
* @returns {Promise<void>}
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* await python.detectEnvironment()
|
|
210
|
+
* console.log(python.state.get('environmentType')) // 'uv' | 'conda' | 'venv' | 'system'
|
|
211
|
+
* console.log(python.state.get('pythonPath')) // '/path/to/python/executable'
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
async detectEnvironment(): Promise<void> {
|
|
215
|
+
const projectDir = this.state.get('projectDir')!
|
|
216
|
+
let pythonPath: string | null = null
|
|
217
|
+
let environmentType: PythonState['environmentType'] = null
|
|
218
|
+
|
|
219
|
+
const proc = this.container.feature('proc')
|
|
220
|
+
|
|
221
|
+
/** Resolve a binary to its full path via `which`, falling back to the bare name. */
|
|
222
|
+
const resolveBin = (name: string): string => {
|
|
223
|
+
try { return proc.exec(`which ${name}`).trim() } catch { return name }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Use explicitly provided Python path
|
|
227
|
+
if (this.options.pythonPath) {
|
|
228
|
+
pythonPath = this.options.pythonPath
|
|
229
|
+
environmentType = 'system'
|
|
230
|
+
}
|
|
231
|
+
// Check for uv
|
|
232
|
+
else if (existsSync(join(projectDir, 'uv.lock')) || existsSync(join(projectDir, 'pyproject.toml'))) {
|
|
233
|
+
try {
|
|
234
|
+
const uvBin = resolveBin('uv')
|
|
235
|
+
const result = await proc.execAndCapture(`${uvBin} run python --version`)
|
|
236
|
+
if (result.exitCode === 0) {
|
|
237
|
+
pythonPath = `${uvBin} run python`
|
|
238
|
+
environmentType = 'uv'
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// Fall through to next detection method
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Check for conda
|
|
245
|
+
else if (existsSync(join(projectDir, 'environment.yml')) || existsSync(join(projectDir, 'conda.yml'))) {
|
|
246
|
+
try {
|
|
247
|
+
const condaBin = resolveBin('conda')
|
|
248
|
+
const result = await proc.execAndCapture(`${condaBin} run python --version`)
|
|
249
|
+
if (result.exitCode === 0) {
|
|
250
|
+
pythonPath = `${condaBin} run python`
|
|
251
|
+
environmentType = 'conda'
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
// Fall through to next detection method
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Check for venv
|
|
258
|
+
else if (existsSync(join(projectDir, 'venv')) || existsSync(join(projectDir, '.venv'))) {
|
|
259
|
+
const venvPath = existsSync(join(projectDir, 'venv')) ? 'venv' : '.venv'
|
|
260
|
+
const venvPython = process.platform === 'win32'
|
|
261
|
+
? join(projectDir, venvPath, 'Scripts', 'python.exe')
|
|
262
|
+
: join(projectDir, venvPath, 'bin', 'python')
|
|
263
|
+
|
|
264
|
+
if (existsSync(venvPython)) {
|
|
265
|
+
pythonPath = venvPython
|
|
266
|
+
environmentType = 'venv'
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Fall back to system Python
|
|
271
|
+
if (!pythonPath) {
|
|
272
|
+
try {
|
|
273
|
+
const python3Bin = resolveBin('python3')
|
|
274
|
+
const result = await proc.execAndCapture(`${python3Bin} --version`)
|
|
275
|
+
if (result.exitCode === 0) {
|
|
276
|
+
pythonPath = python3Bin
|
|
277
|
+
environmentType = 'system'
|
|
278
|
+
} else {
|
|
279
|
+
const pythonBin = resolveBin('python')
|
|
280
|
+
const result2 = await proc.execAndCapture(`${pythonBin} --version`)
|
|
281
|
+
if (result2.exitCode === 0) {
|
|
282
|
+
pythonPath = pythonBin
|
|
283
|
+
environmentType = 'system'
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
throw new Error('Could not find Python installation')
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.state.set('pythonPath', pythonPath)
|
|
292
|
+
this.state.set('environmentType', environmentType)
|
|
293
|
+
|
|
294
|
+
this.emit('environmentDetected', { pythonPath, environmentType })
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Installs dependencies for the Python project.
|
|
299
|
+
*
|
|
300
|
+
* This method automatically detects the appropriate package manager and install command
|
|
301
|
+
* based on the environment type. If a custom installCommand is provided in options,
|
|
302
|
+
* it will use that instead.
|
|
303
|
+
*
|
|
304
|
+
* @returns {Promise<{ stdout: string; stderr: string; exitCode: number }>}
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* // Auto-detect and install
|
|
309
|
+
* const result = await python.installDependencies()
|
|
310
|
+
*
|
|
311
|
+
* // With custom install command
|
|
312
|
+
* const python = container.feature('python', {
|
|
313
|
+
* installCommand: 'pip install -r requirements.txt'
|
|
314
|
+
* })
|
|
315
|
+
* const result = await python.installDependencies()
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
async installDependencies(): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
319
|
+
const proc = this.container.feature('proc')
|
|
320
|
+
const projectDir = this.state.get('projectDir')!
|
|
321
|
+
const environmentType = this.state.get('environmentType')
|
|
322
|
+
|
|
323
|
+
let installCommand: string
|
|
324
|
+
|
|
325
|
+
if (this.options.installCommand) {
|
|
326
|
+
installCommand = this.options.installCommand
|
|
327
|
+
} else {
|
|
328
|
+
switch (environmentType) {
|
|
329
|
+
case 'uv':
|
|
330
|
+
installCommand = 'uv sync'
|
|
331
|
+
break
|
|
332
|
+
case 'conda':
|
|
333
|
+
if (existsSync(join(projectDir, 'environment.yml'))) {
|
|
334
|
+
installCommand = 'conda env update -f environment.yml'
|
|
335
|
+
} else if (existsSync(join(projectDir, 'conda.yml'))) {
|
|
336
|
+
installCommand = 'conda env update -f conda.yml'
|
|
337
|
+
} else {
|
|
338
|
+
installCommand = 'conda install --file requirements.txt'
|
|
339
|
+
}
|
|
340
|
+
break
|
|
341
|
+
case 'venv':
|
|
342
|
+
case 'system':
|
|
343
|
+
default:
|
|
344
|
+
if (existsSync(join(projectDir, 'requirements.txt'))) {
|
|
345
|
+
const pythonPath = this.state.get('pythonPath')!
|
|
346
|
+
installCommand = `${pythonPath} -m pip install -r requirements.txt`
|
|
347
|
+
} else if (existsSync(join(projectDir, 'pyproject.toml'))) {
|
|
348
|
+
const pythonPath = this.state.get('pythonPath')!
|
|
349
|
+
installCommand = `${pythonPath} -m pip install -e .`
|
|
350
|
+
} else {
|
|
351
|
+
throw new Error('No requirements.txt or pyproject.toml found for dependency installation')
|
|
352
|
+
}
|
|
353
|
+
break
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this.emit('installingDependencies', { command: installCommand })
|
|
358
|
+
|
|
359
|
+
const result = await proc.execAndCapture(installCommand, { cwd: projectDir })
|
|
360
|
+
|
|
361
|
+
if (result.exitCode === 0) {
|
|
362
|
+
this.emit('dependenciesInstalled', result)
|
|
363
|
+
} else {
|
|
364
|
+
this.emit('dependencyInstallFailed', result)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return result
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Executes Python code and returns the result.
|
|
372
|
+
*
|
|
373
|
+
* This method creates a temporary Python script with the provided code and variables,
|
|
374
|
+
* executes it using the detected Python environment, and captures the output.
|
|
375
|
+
*
|
|
376
|
+
* @param {string} code - The Python code to execute
|
|
377
|
+
* @param {Record<string, any>} [variables={}] - Variables to make available to the Python code
|
|
378
|
+
* @param {object} [options] - Execution options
|
|
379
|
+
* @param {boolean} [options.captureLocals=false] - Whether to capture and return local variables after execution
|
|
380
|
+
* @returns {Promise<{ stdout: string; stderr: string; exitCode: number; locals?: any }>}
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```typescript
|
|
384
|
+
* // Simple execution
|
|
385
|
+
* const result = await python.execute('print("Hello World")')
|
|
386
|
+
* console.log(result.stdout) // 'Hello World'
|
|
387
|
+
*
|
|
388
|
+
* // With variables
|
|
389
|
+
* const result = await python.execute('print(f"Hello {name}!")', { name: 'Alice' })
|
|
390
|
+
*
|
|
391
|
+
* // Capture locals
|
|
392
|
+
* const result = await python.execute('x = 42\ny = x * 2', {}, { captureLocals: true })
|
|
393
|
+
* console.log(result.locals) // { x: 42, y: 84 }
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
async execute(
|
|
397
|
+
code: string,
|
|
398
|
+
variables: Record<string, any> = {},
|
|
399
|
+
options: { captureLocals?: boolean } = {}
|
|
400
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number; locals?: any }> {
|
|
401
|
+
const proc = this.container.feature('proc')
|
|
402
|
+
const fs = this.container.feature('fs')
|
|
403
|
+
|
|
404
|
+
const { projectDir, pythonPath } = this
|
|
405
|
+
|
|
406
|
+
// Create temporary script in system temp dir (not inside the project)
|
|
407
|
+
const tempDir = `${tmpdir()}/luca-python-temp`
|
|
408
|
+
await fs.ensureFolder(tempDir)
|
|
409
|
+
const scriptPath = join(tempDir, `script-${Date.now()}.py`)
|
|
410
|
+
|
|
411
|
+
// Build the Python script
|
|
412
|
+
let script = ''
|
|
413
|
+
|
|
414
|
+
// Import json for serialization
|
|
415
|
+
script += 'import json\nimport sys\n\n'
|
|
416
|
+
|
|
417
|
+
// Set up variables
|
|
418
|
+
if (Object.keys(variables).length > 0) {
|
|
419
|
+
script += '# Variables\n'
|
|
420
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
421
|
+
script += `${key} = ${JSON.stringify(value)}\n`
|
|
422
|
+
}
|
|
423
|
+
script += '\n'
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Add the user code
|
|
427
|
+
script += '# User code\n'
|
|
428
|
+
script += code
|
|
429
|
+
|
|
430
|
+
// Capture locals if requested
|
|
431
|
+
if (options.captureLocals) {
|
|
432
|
+
script += '\n\n# Capture locals\n'
|
|
433
|
+
script += 'locals_dict = {k: v for k, v in locals().items() if not k.startswith("__")}\n'
|
|
434
|
+
script += 'print("__LOCALS__:" + json.dumps(locals_dict, default=str))\n'
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
await fs.writeFileAsync(scriptPath, script)
|
|
438
|
+
|
|
439
|
+
// Execute the script
|
|
440
|
+
const command = pythonPath.includes(' ') ? pythonPath : `${pythonPath}`
|
|
441
|
+
const result = await proc.execAndCapture(`${command} ${scriptPath}`, { cwd: projectDir })
|
|
442
|
+
|
|
443
|
+
// Parse locals if captured
|
|
444
|
+
let locals: any = undefined
|
|
445
|
+
if (options.captureLocals && result.stdout.includes('__LOCALS__:')) {
|
|
446
|
+
try {
|
|
447
|
+
const localsMatch = result.stdout.match(/__LOCALS__:(.+)$/m)
|
|
448
|
+
if (localsMatch && localsMatch[1]) {
|
|
449
|
+
locals = JSON.parse(localsMatch[1])
|
|
450
|
+
// Remove the locals output from stdout
|
|
451
|
+
result.stdout = result.stdout.replace(/__LOCALS__:.+$/m, '').trim()
|
|
452
|
+
}
|
|
453
|
+
} catch (error) {
|
|
454
|
+
this.emit('localsParseError', error)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Clean up temporary file
|
|
459
|
+
await fs.rm(scriptPath)
|
|
460
|
+
|
|
461
|
+
this.state.set('lastExecutedScript', scriptPath)
|
|
462
|
+
this.emit('codeExecuted', { code, variables, result })
|
|
463
|
+
|
|
464
|
+
return { ...result, locals }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Executes a Python file and returns the result.
|
|
469
|
+
*
|
|
470
|
+
* @param {string} filePath - Path to the Python file to execute
|
|
471
|
+
* @param {Record<string, any>} [variables={}] - Variables to make available via command line arguments
|
|
472
|
+
* @returns {Promise<{ stdout: string; stderr: string; exitCode: number }>}
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* const result = await python.executeFile('/path/to/script.py')
|
|
477
|
+
* console.log(result.stdout)
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
async executeFile(
|
|
481
|
+
filePath: string,
|
|
482
|
+
variables: Record<string, any> = {}
|
|
483
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
484
|
+
const proc = this.container.feature('proc')
|
|
485
|
+
const projectDir = this.state.get('projectDir')!
|
|
486
|
+
const pythonPath = this.state.get('pythonPath')!
|
|
487
|
+
|
|
488
|
+
// Convert variables to command line arguments
|
|
489
|
+
const args = Object.entries(variables).map(([key, value]) => `--${key}=${value}`).join(' ')
|
|
490
|
+
const command = pythonPath.includes(' ') ? pythonPath : `${pythonPath}`
|
|
491
|
+
|
|
492
|
+
const result = await proc.execAndCapture(`${command} ${filePath} ${args}`, { cwd: projectDir })
|
|
493
|
+
|
|
494
|
+
this.emit('fileExecuted', { filePath, variables, result })
|
|
495
|
+
|
|
496
|
+
return result
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Gets information about the current Python environment.
|
|
501
|
+
*
|
|
502
|
+
* @returns {Promise<{ version: string; path: string; packages: string[] }>}
|
|
503
|
+
*/
|
|
504
|
+
async getEnvironmentInfo(): Promise<{ version: string; path: string; packages: string[] }> {
|
|
505
|
+
const proc = this.container.feature('proc')
|
|
506
|
+
const pythonPath = this.state.get('pythonPath')!
|
|
507
|
+
const projectDir = this.state.get('projectDir')!
|
|
508
|
+
|
|
509
|
+
// Get Python version
|
|
510
|
+
const versionResult = await proc.execAndCapture(`${pythonPath} --version`, { cwd: projectDir })
|
|
511
|
+
const version = versionResult.stdout.trim()
|
|
512
|
+
|
|
513
|
+
// Get Python path
|
|
514
|
+
const pathResult = await proc.execAndCapture(`${pythonPath} -c "import sys; print(sys.executable)"`, { cwd: projectDir })
|
|
515
|
+
const path = pathResult.stdout.trim()
|
|
516
|
+
|
|
517
|
+
// Get installed packages
|
|
518
|
+
const packagesResult = await proc.execAndCapture(`${pythonPath} -m pip list --format=freeze`, { cwd: projectDir })
|
|
519
|
+
const packages = packagesResult.stdout.trim().split('\n').filter(line => line.length > 0)
|
|
520
|
+
|
|
521
|
+
return { version, path, packages }
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
// Persistent session methods
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Splits the (possibly multi-word) pythonPath into a command and args array
|
|
530
|
+
* suitable for proc.spawn(). For example, `uv run python` becomes
|
|
531
|
+
* `{ command: 'uv', args: ['run', 'python', ...extraArgs] }`.
|
|
532
|
+
*/
|
|
533
|
+
private _parsePythonCommand(extraArgs: string[]): { command: string, args: string[] } {
|
|
534
|
+
const parts = this.pythonPath.split(/\s+/)
|
|
535
|
+
return { command: parts[0] ?? 'python', args: [...parts.slice(1), ...extraArgs] }
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Writes the bundled bridge.py to a temp directory and returns its path.
|
|
540
|
+
* Reuses the same path across calls within a process.
|
|
541
|
+
*/
|
|
542
|
+
private async _ensureBridgeScript(): Promise<string> {
|
|
543
|
+
if (this._bridgeScriptPath) return this._bridgeScriptPath
|
|
544
|
+
|
|
545
|
+
const fs = this.container.feature('fs')
|
|
546
|
+
const bridgeDir = `${tmpdir()}/luca-python-bridge`
|
|
547
|
+
await fs.ensureFolder(bridgeDir)
|
|
548
|
+
const scriptPath = `${bridgeDir}/bridge.py`
|
|
549
|
+
await fs.writeFileAsync(scriptPath, bridgeScript)
|
|
550
|
+
this._bridgeScriptPath = scriptPath
|
|
551
|
+
return scriptPath
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Sends a JSON-line request to the bridge process and returns a promise
|
|
556
|
+
* that resolves when the matching response (by id) arrives.
|
|
557
|
+
*
|
|
558
|
+
* @param type - The request type (exec, eval, import, call, get_locals, reset)
|
|
559
|
+
* @param payload - Additional fields to include in the request
|
|
560
|
+
* @param timeout - Timeout in ms (default 30000)
|
|
561
|
+
*/
|
|
562
|
+
private _sendRequest(type: string, payload: Record<string, any> = {}, timeout = 30000): Promise<any> {
|
|
563
|
+
if (!this._bridgeProcess || !this._bridgeProcess.stdin) {
|
|
564
|
+
return Promise.reject(new Error('No active Python session. Call startSession() first.'))
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const id = this.container.utils.uuid()
|
|
568
|
+
const request = JSON.stringify({ id, type, ...payload }) + '\n'
|
|
569
|
+
|
|
570
|
+
return new Promise((resolve, reject) => {
|
|
571
|
+
const timer = setTimeout(() => {
|
|
572
|
+
this._pendingRequests.delete(id)
|
|
573
|
+
reject(new Error(`Python bridge request timed out after ${timeout}ms (type: ${type})`))
|
|
574
|
+
}, timeout)
|
|
575
|
+
|
|
576
|
+
this._pendingRequests.set(id, {
|
|
577
|
+
resolve: (value: any) => {
|
|
578
|
+
clearTimeout(timer)
|
|
579
|
+
resolve(value)
|
|
580
|
+
},
|
|
581
|
+
reject: (err: any) => {
|
|
582
|
+
clearTimeout(timer)
|
|
583
|
+
reject(err)
|
|
584
|
+
},
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
this._bridgeProcess!.stdin!.write(request)
|
|
588
|
+
})
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Handles incoming stdout data from the bridge process. Buffers partial
|
|
593
|
+
* lines and parses complete JSON-line responses, resolving their matching
|
|
594
|
+
* pending requests.
|
|
595
|
+
*/
|
|
596
|
+
private _onBridgeData(chunk: Buffer | string): void {
|
|
597
|
+
this._stdoutBuffer += chunk.toString()
|
|
598
|
+
|
|
599
|
+
const lines = this._stdoutBuffer.split('\n')
|
|
600
|
+
// Keep the last (possibly incomplete) segment in the buffer
|
|
601
|
+
this._stdoutBuffer = lines.pop() || ''
|
|
602
|
+
|
|
603
|
+
for (const line of lines) {
|
|
604
|
+
if (!line.trim()) continue
|
|
605
|
+
try {
|
|
606
|
+
const response = JSON.parse(line)
|
|
607
|
+
const id = response.id
|
|
608
|
+
if (id && this._pendingRequests.has(id)) {
|
|
609
|
+
const pending = this._pendingRequests.get(id)!
|
|
610
|
+
this._pendingRequests.delete(id)
|
|
611
|
+
pending.resolve(response)
|
|
612
|
+
}
|
|
613
|
+
// Non-id responses (like the initial "ready") are handled by startSession
|
|
614
|
+
} catch {
|
|
615
|
+
// Not JSON — could be stray output, ignore
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Starts a persistent Python session by spawning the bridge process.
|
|
622
|
+
*
|
|
623
|
+
* The bridge sets up sys.path for the project directory, then enters a
|
|
624
|
+
* JSON-line REPL loop. State (variables, imports) persists across run() calls
|
|
625
|
+
* until stopSession() or resetSession() is called.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* ```typescript
|
|
629
|
+
* const python = container.feature('python', { dir: '/path/to/project' })
|
|
630
|
+
* await python.enable()
|
|
631
|
+
* await python.startSession()
|
|
632
|
+
* await python.run('x = 42')
|
|
633
|
+
* const result = await python.run('print(x)')
|
|
634
|
+
* console.log(result.stdout) // '42\n'
|
|
635
|
+
* await python.stopSession()
|
|
636
|
+
* ```
|
|
637
|
+
*/
|
|
638
|
+
async startSession(): Promise<void> {
|
|
639
|
+
if (this.state.get('sessionActive')) {
|
|
640
|
+
throw new Error('A Python session is already active. Call stopSession() first.')
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const proc = this.container.feature('proc')
|
|
644
|
+
const bridgePath = await this._ensureBridgeScript()
|
|
645
|
+
const { command, args } = this._parsePythonCommand(['-u', bridgePath])
|
|
646
|
+
|
|
647
|
+
const child = proc.spawn(command, args, {
|
|
648
|
+
cwd: this.projectDir,
|
|
649
|
+
stdout: 'pipe',
|
|
650
|
+
stderr: 'pipe',
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
this._bridgeProcess = child
|
|
654
|
+
this._stdoutBuffer = ''
|
|
655
|
+
|
|
656
|
+
// Wait for the ready signal from the bridge
|
|
657
|
+
const readyPromise = new Promise<void>((resolve, reject) => {
|
|
658
|
+
const timer = setTimeout(() => {
|
|
659
|
+
reject(new Error('Python bridge failed to start within 15 seconds'))
|
|
660
|
+
}, 15000)
|
|
661
|
+
|
|
662
|
+
const onData = (chunk: Buffer | string) => {
|
|
663
|
+
this._stdoutBuffer += chunk.toString()
|
|
664
|
+
const lines = this._stdoutBuffer.split('\n')
|
|
665
|
+
this._stdoutBuffer = lines.pop() || ''
|
|
666
|
+
|
|
667
|
+
for (const line of lines) {
|
|
668
|
+
if (!line.trim()) continue
|
|
669
|
+
try {
|
|
670
|
+
const msg = JSON.parse(line)
|
|
671
|
+
if (msg.type === 'ready' && msg.ok) {
|
|
672
|
+
clearTimeout(timer)
|
|
673
|
+
// Switch to the normal data handler
|
|
674
|
+
child.stdout!.removeListener('data', onData)
|
|
675
|
+
child.stdout!.on('data', this._onBridgeData.bind(this))
|
|
676
|
+
resolve()
|
|
677
|
+
return
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
// ignore non-JSON during init
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
child.stdout!.on('data', onData)
|
|
686
|
+
|
|
687
|
+
child.on('error', (err: Error) => {
|
|
688
|
+
clearTimeout(timer)
|
|
689
|
+
reject(new Error(`Python bridge process error: ${err.message}`))
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
child.on('exit', (code: number | null) => {
|
|
693
|
+
clearTimeout(timer)
|
|
694
|
+
reject(new Error(`Python bridge exited during startup with code ${code}`))
|
|
695
|
+
})
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
// Send init handshake with project directory
|
|
699
|
+
child.stdin!.write(JSON.stringify({ project_dir: this.projectDir }) + '\n')
|
|
700
|
+
|
|
701
|
+
await readyPromise
|
|
702
|
+
|
|
703
|
+
// Register crash handler (after successful startup)
|
|
704
|
+
child.removeAllListeners('exit')
|
|
705
|
+
child.on('exit', (code: number | null) => {
|
|
706
|
+
const sessionId = this.state.get('sessionId')
|
|
707
|
+
this.state.set('sessionActive', false)
|
|
708
|
+
this._bridgeProcess = null
|
|
709
|
+
|
|
710
|
+
// Reject all pending requests
|
|
711
|
+
for (const [id, pending] of this._pendingRequests) {
|
|
712
|
+
pending.reject(new Error(`Python bridge exited unexpectedly with code ${code}`))
|
|
713
|
+
}
|
|
714
|
+
this._pendingRequests.clear()
|
|
715
|
+
|
|
716
|
+
this.emit('sessionError', { error: `Bridge exited with code ${code}`, sessionId })
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
// Capture stderr for diagnostics (don't interfere with protocol)
|
|
720
|
+
child.stderr!.on('data', (chunk: Buffer | string) => {
|
|
721
|
+
const text = chunk.toString().trim()
|
|
722
|
+
if (text) {
|
|
723
|
+
this.emit('sessionError', { error: text, sessionId: this.state.get('sessionId') })
|
|
724
|
+
}
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
const sessionId = this.container.utils.uuid()
|
|
728
|
+
this.state.set('sessionActive', true)
|
|
729
|
+
this.state.set('sessionId', sessionId)
|
|
730
|
+
this.emit('sessionStarted', { sessionId })
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Stops the persistent Python session and cleans up the bridge process.
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```typescript
|
|
738
|
+
* await python.stopSession()
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
async stopSession(): Promise<void> {
|
|
742
|
+
const sessionId = this.state.get('sessionId')
|
|
743
|
+
|
|
744
|
+
if (this._bridgeProcess) {
|
|
745
|
+
this._bridgeProcess.removeAllListeners('exit')
|
|
746
|
+
this._bridgeProcess.kill('SIGTERM')
|
|
747
|
+
this._bridgeProcess = null
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Reject any pending requests
|
|
751
|
+
for (const [id, pending] of this._pendingRequests) {
|
|
752
|
+
pending.reject(new Error('Python session stopped'))
|
|
753
|
+
}
|
|
754
|
+
this._pendingRequests.clear()
|
|
755
|
+
this._stdoutBuffer = ''
|
|
756
|
+
|
|
757
|
+
this.state.set('sessionActive', false)
|
|
758
|
+
this.state.set('sessionId', null)
|
|
759
|
+
|
|
760
|
+
if (sessionId) {
|
|
761
|
+
this.emit('sessionStopped', { sessionId })
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Executes Python code in the persistent session. Variables and imports
|
|
767
|
+
* survive across calls. This is the session equivalent of execute().
|
|
768
|
+
*
|
|
769
|
+
* @param code - Python code to execute
|
|
770
|
+
* @param variables - Variables to inject into the namespace before execution
|
|
771
|
+
* @returns The execution result including captured stdout and any error info
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```typescript
|
|
775
|
+
* await python.startSession()
|
|
776
|
+
*
|
|
777
|
+
* // State persists across calls
|
|
778
|
+
* await python.run('x = 42')
|
|
779
|
+
* const result = await python.run('print(x * 2)')
|
|
780
|
+
* console.log(result.stdout) // '84\n'
|
|
781
|
+
*
|
|
782
|
+
* // Inject variables from JS
|
|
783
|
+
* const result2 = await python.run('print(f"Hello {name}!")', { name: 'World' })
|
|
784
|
+
* console.log(result2.stdout) // 'Hello World!\n'
|
|
785
|
+
* ```
|
|
786
|
+
*/
|
|
787
|
+
async run(code: string, variables: Record<string, any> = {}): Promise<RunResult> {
|
|
788
|
+
const response = await this._sendRequest('exec', { code, variables })
|
|
789
|
+
return {
|
|
790
|
+
ok: response.ok,
|
|
791
|
+
result: response.result ?? null,
|
|
792
|
+
stdout: response.stdout ?? '',
|
|
793
|
+
error: response.error,
|
|
794
|
+
traceback: response.traceback,
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Evaluates a Python expression in the persistent session and returns its value.
|
|
800
|
+
*
|
|
801
|
+
* @param expression - Python expression to evaluate
|
|
802
|
+
* @returns The evaluated result (JSON-serializable, or repr() string for complex types)
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```typescript
|
|
806
|
+
* await python.run('x = 42')
|
|
807
|
+
* const result = await python.eval('x * 2')
|
|
808
|
+
* console.log(result) // 84
|
|
809
|
+
* ```
|
|
810
|
+
*/
|
|
811
|
+
async eval(expression: string): Promise<any> {
|
|
812
|
+
const response = await this._sendRequest('eval', { expression })
|
|
813
|
+
if (!response.ok) {
|
|
814
|
+
throw new Error(response.error || 'eval failed')
|
|
815
|
+
}
|
|
816
|
+
return response.result
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Imports a Python module into the persistent session namespace.
|
|
821
|
+
*
|
|
822
|
+
* @param moduleName - Dotted module path (e.g. 'myapp.models')
|
|
823
|
+
* @param alias - Optional alias for the import (defaults to the last segment)
|
|
824
|
+
*
|
|
825
|
+
* @example
|
|
826
|
+
* ```typescript
|
|
827
|
+
* await python.importModule('json')
|
|
828
|
+
* await python.importModule('myapp.models', 'models')
|
|
829
|
+
* const result = await python.eval('models.User')
|
|
830
|
+
* ```
|
|
831
|
+
*/
|
|
832
|
+
async importModule(moduleName: string, alias?: string): Promise<void> {
|
|
833
|
+
const response = await this._sendRequest('import', { module: moduleName, alias })
|
|
834
|
+
if (!response.ok) {
|
|
835
|
+
throw new Error(response.error || `Failed to import ${moduleName}`)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Calls a function by dotted path in the persistent session namespace.
|
|
841
|
+
*
|
|
842
|
+
* @param funcPath - Dotted path to the function (e.g. 'json.dumps' or 'my_func')
|
|
843
|
+
* @param args - Positional arguments
|
|
844
|
+
* @param kwargs - Keyword arguments
|
|
845
|
+
* @returns The function's return value
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* ```typescript
|
|
849
|
+
* await python.importModule('json')
|
|
850
|
+
* const result = await python.call('json.dumps', [{ a: 1 }], { indent: 2 })
|
|
851
|
+
* ```
|
|
852
|
+
*/
|
|
853
|
+
async call(funcPath: string, args: any[] = [], kwargs: Record<string, any> = {}): Promise<any> {
|
|
854
|
+
const response = await this._sendRequest('call', { function: funcPath, args, kwargs })
|
|
855
|
+
if (!response.ok) {
|
|
856
|
+
throw new Error(response.error || `Failed to call ${funcPath}`)
|
|
857
|
+
}
|
|
858
|
+
return response.result
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Returns all non-dunder variables from the persistent session namespace.
|
|
863
|
+
*
|
|
864
|
+
* @returns A record of variable names to their JSON-serializable values
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* ```typescript
|
|
868
|
+
* await python.run('x = 42\ny = "hello"')
|
|
869
|
+
* const locals = await python.getLocals()
|
|
870
|
+
* console.log(locals) // { x: 42, y: 'hello' }
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
async getLocals(): Promise<Record<string, any>> {
|
|
874
|
+
const response = await this._sendRequest('get_locals')
|
|
875
|
+
if (!response.ok) {
|
|
876
|
+
throw new Error(response.error || 'Failed to get locals')
|
|
877
|
+
}
|
|
878
|
+
return response.result
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Clears all variables and imports from the persistent session namespace.
|
|
883
|
+
* The session remains active — you can continue calling run() after reset.
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* ```typescript
|
|
887
|
+
* await python.run('x = 42')
|
|
888
|
+
* await python.resetSession()
|
|
889
|
+
* // x is now undefined
|
|
890
|
+
* ```
|
|
891
|
+
*/
|
|
892
|
+
async resetSession(): Promise<void> {
|
|
893
|
+
const response = await this._sendRequest('reset')
|
|
894
|
+
if (!response.ok) {
|
|
895
|
+
throw new Error(response.error || 'Failed to reset session')
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
export default Python
|
|
901
|
+
// Module augmentation for type safety
|
|
902
|
+
declare module '../feature.js' {
|
|
903
|
+
interface AvailableFeatures {
|
|
904
|
+
python: typeof Python;
|
|
905
|
+
}
|
|
906
|
+
}
|