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,557 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { type AvailableFeatures } from 'luca/feature'
|
|
4
|
+
import { Feature } from '../feature.js'
|
|
5
|
+
import { parse } from 'contentbase'
|
|
6
|
+
import type { DocsReader } from './docs-reader.js'
|
|
7
|
+
import Assistant from './assistant.js'
|
|
8
|
+
|
|
9
|
+
declare module 'luca/feature' {
|
|
10
|
+
interface AvailableFeatures {
|
|
11
|
+
skillsLibrary: typeof SkillsLibrary
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SkillInfo {
|
|
16
|
+
/** Skill name derived from folder name or frontmatter */
|
|
17
|
+
name: string
|
|
18
|
+
/** Description from frontmatter */
|
|
19
|
+
description: string
|
|
20
|
+
/** Absolute path to the skill folder (dirname of SKILL.md) */
|
|
21
|
+
path: string
|
|
22
|
+
/** Absolute path to SKILL.md */
|
|
23
|
+
skillFilePath: string
|
|
24
|
+
/** Which location this skill was found in */
|
|
25
|
+
locationPath: string
|
|
26
|
+
/** All frontmatter metadata */
|
|
27
|
+
meta: Record<string, unknown>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const SkillsLibraryStateSchema = FeatureStateSchema.extend({
|
|
31
|
+
loaded: z.boolean().describe('Whether skill locations have been scanned'),
|
|
32
|
+
locations: z.array(z.string()).describe('Tracked skill location folder paths'),
|
|
33
|
+
skillCount: z.number().describe('Total number of discovered skills'),
|
|
34
|
+
skills: z.record(z.string(), z.any()).describe('Discovered skills keyed by name'),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const SkillsLibraryOptionsSchema = FeatureOptionsSchema.extend({
|
|
38
|
+
configPath: z.string().optional().describe('Override path for skills.json (defaults to ~/.luca/skills.json)'),
|
|
39
|
+
only: z.array(z.string()).optional().describe('Glob patterns to filter which skills are exposed. When set, only matching skills are available. Supports * wildcards (e.g. "luca-*", "react-ink").'),
|
|
40
|
+
locations: z.array(z.string()).optional().describe('Additional skill location directories to scan for this instance only. Not persisted to skills.json — other consumers will not see these.'),
|
|
41
|
+
useAgentsFolders: z.boolean().optional().default(false).describe('When true, automatically scan conventional agent skill folders: .claude/skills and .agents/skills in both the home directory and project cwd.'),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const SkillsLibraryEventsSchema = FeatureEventsSchema.extend({
|
|
45
|
+
started: z.tuple([]).describe('Fired after all skill locations have been scanned'),
|
|
46
|
+
locationAdded: z.tuple([z.string().describe('The absolute path of the added location')]).describe('Fired when a new skill location is registered'),
|
|
47
|
+
skillDiscovered: z.tuple([z.any().describe('The SkillInfo object')]).describe('Fired when a skill is discovered during scanning'),
|
|
48
|
+
}).describe('SkillsLibrary events')
|
|
49
|
+
|
|
50
|
+
export type SkillsLibraryState = z.infer<typeof SkillsLibraryStateSchema>
|
|
51
|
+
export type SkillsLibraryOptions = z.infer<typeof SkillsLibraryOptionsSchema>
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Manages a registry of skill locations — folders containing SKILL.md files.
|
|
55
|
+
*
|
|
56
|
+
* Persists known locations to ~/.luca/skills.json and scans them on start.
|
|
57
|
+
* Each skill folder can be opened as a DocsReader for AI-assisted Q&A.
|
|
58
|
+
* Exposes tools for assistant integration via assistant.use(skillsLibrary).
|
|
59
|
+
*
|
|
60
|
+
* No paths are scanned by default — callers must explicitly provide locations
|
|
61
|
+
* via the `locations` option or `addLocation()`. Set `useAgentsFolders: true`
|
|
62
|
+
* to automatically scan conventional agent skill folders (.claude/skills and
|
|
63
|
+
* .agents/skills in both $HOME and cwd).
|
|
64
|
+
*
|
|
65
|
+
* @extends Feature
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const lib = container.feature('skillsLibrary', { locations: ['./my-skills'] })
|
|
69
|
+
* await lib.start()
|
|
70
|
+
* lib.list() // => SkillInfo[]
|
|
71
|
+
*
|
|
72
|
+
* // Or opt in to conventional agent folders:
|
|
73
|
+
* const lib2 = container.feature('skillsLibrary', { useAgentsFolders: true })
|
|
74
|
+
* await lib2.start()
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export class SkillsLibrary extends Feature<SkillsLibraryState, SkillsLibraryOptions> {
|
|
78
|
+
static override stateSchema = SkillsLibraryStateSchema
|
|
79
|
+
static override optionsSchema = SkillsLibraryOptionsSchema
|
|
80
|
+
static override eventsSchema = SkillsLibraryEventsSchema
|
|
81
|
+
static override shortcut = 'features.skillsLibrary' as const
|
|
82
|
+
|
|
83
|
+
static { Feature.register(this, 'skillsLibrary') }
|
|
84
|
+
|
|
85
|
+
/** Tools for assistant integration via assistant.use(skillsLibrary). */
|
|
86
|
+
static override tools: Record<string, { schema: z.ZodType; handler?: Function }> = {
|
|
87
|
+
searchAvailableSkills: {
|
|
88
|
+
schema: z.object({
|
|
89
|
+
query: z.string().optional().describe('A keyword or phrase to filter skills by name or description. Omit to list all available skills.'),
|
|
90
|
+
}).describe('Discover what skills are available. Call this first when you need specialized knowledge — skills are curated guides and reference material for specific domains (frameworks, tools, patterns). Returns skill names and descriptions so you can decide which to load.'),
|
|
91
|
+
},
|
|
92
|
+
loadSkill: {
|
|
93
|
+
schema: z.object({
|
|
94
|
+
skillName: z.string().describe('The exact skill name as returned by searchAvailableSkills'),
|
|
95
|
+
}).describe('Load a skill\'s full reference content (SKILL.md). This gives you detailed guidance, examples, and best practices for that domain. Load a skill before attempting work in an unfamiliar area — the content is curated to prevent common mistakes.'),
|
|
96
|
+
},
|
|
97
|
+
askSkillBasedQuestion: {
|
|
98
|
+
schema: z.object({
|
|
99
|
+
skillName: z.string().describe('The exact skill name to query'),
|
|
100
|
+
question: z.string().describe('A specific question about the skill\'s domain. Be precise — "how do I add a new feature to the container?" is better than "tell me about features".'),
|
|
101
|
+
}).describe('Ask a focused question about a skill\'s domain using AI-assisted document reading. Use this when you need a specific answer from a skill rather than reading the whole thing. More efficient than loadSkill for targeted lookups.'),
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @returns Default state. */
|
|
106
|
+
override get initialState(): SkillsLibraryState {
|
|
107
|
+
return {
|
|
108
|
+
...super.initialState,
|
|
109
|
+
started: false,
|
|
110
|
+
locations: [],
|
|
111
|
+
skillCount: 0,
|
|
112
|
+
skills: {},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override setupToolsConsumer(assistant: Feature) {
|
|
117
|
+
if (!(assistant instanceof Assistant)) {
|
|
118
|
+
throw new Error('Skills library tools require an Assistant instance (including subclasses).')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const a : Assistant = assistant as Assistant
|
|
122
|
+
const { container } = a
|
|
123
|
+
const skillsLibrary = this
|
|
124
|
+
const skillCount = Object.keys(this.filteredSkills).length
|
|
125
|
+
const isSmallSet = skillCount <= 10
|
|
126
|
+
|
|
127
|
+
if (!isSmallSet && !this.options.only) {
|
|
128
|
+
if (!process.env.LUCA_SKILLS_NO_WARN) {
|
|
129
|
+
container.feature('ui').print.yellow(`SkillsLibrary: ${skillCount} skills loaded with no \`only\` filter. Use container.feature('skillsLibrary', { only: ['pattern*'] }) to limit, or set LUCA_SKILLS_NO_WARN=1 to silence.`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (isSmallSet) {
|
|
134
|
+
const table = Object.entries(this.skillsTable)
|
|
135
|
+
.map(([name, desc]) => `- **${name}**: ${desc}`)
|
|
136
|
+
.join('\n')
|
|
137
|
+
|
|
138
|
+
a.addSystemPromptExtension('skillsLibrary', [
|
|
139
|
+
'## Skills Library',
|
|
140
|
+
'',
|
|
141
|
+
'You have access to the following curated skills — domain-specific reference guides with examples, patterns, and best practices.',
|
|
142
|
+
'',
|
|
143
|
+
'**Available skills:**',
|
|
144
|
+
table,
|
|
145
|
+
'',
|
|
146
|
+
'**When to use skills:**',
|
|
147
|
+
'- When working in an unfamiliar domain or framework — load the skill before writing code',
|
|
148
|
+
'- When you see "Required Skills" in a message — load those skills immediately with `loadSkill` before answering',
|
|
149
|
+
'',
|
|
150
|
+
'**Workflow:** Choose a skill from the list above → `loadSkill` to get the full guide → follow its patterns. Use `askSkillBasedQuestion` for targeted lookups when you don\'t need the whole guide.',
|
|
151
|
+
'',
|
|
152
|
+
'**Skills are authoritative.** When a loaded skill contradicts your general knowledge, follow the skill — it reflects project-specific conventions and decisions.',
|
|
153
|
+
].join('\n'))
|
|
154
|
+
} else {
|
|
155
|
+
a.addSystemPromptExtension('skillsLibrary', [
|
|
156
|
+
'## Skills Library',
|
|
157
|
+
'',
|
|
158
|
+
`You have access to a large library of ${skillCount} curated skills — domain-specific reference guides with examples, patterns, and best practices.`,
|
|
159
|
+
'',
|
|
160
|
+
'**When to use skills:**',
|
|
161
|
+
'- When working in an unfamiliar domain or framework — search for a skill before writing code',
|
|
162
|
+
'- When the user asks about a topic that might have a matching skill — search first',
|
|
163
|
+
'- When you see "Required Skills" in a message — load those skills immediately with `loadSkill` before answering',
|
|
164
|
+
'',
|
|
165
|
+
'**Workflow:** `searchAvailableSkills` → find relevant skill → `loadSkill` to get the full guide → follow its patterns. Use `askSkillBasedQuestion` for targeted lookups when you don\'t need the whole guide.',
|
|
166
|
+
'',
|
|
167
|
+
'**Skills are authoritative.** When a loaded skill contradicts your general knowledge, follow the skill — it reflects project-specific conventions and decisions.',
|
|
168
|
+
].join('\n'))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const preloadSkills : string[] = []
|
|
172
|
+
if (a.meta.skills) {
|
|
173
|
+
if (Array.isArray(a.meta.skills)) {
|
|
174
|
+
preloadSkills.push(...a.meta.skills)
|
|
175
|
+
} else {
|
|
176
|
+
preloadSkills.push(a.meta.skills)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Only use the fork-based auto-detection for small skill sets
|
|
181
|
+
if (isSmallSet) {
|
|
182
|
+
async function beforeAskCheckIfWeNeedSkills(ctx: any, next: any) {
|
|
183
|
+
const { question } = ctx
|
|
184
|
+
const skills = await skillsLibrary.findRelevantSkillsForAssistant(a, question as string)
|
|
185
|
+
|
|
186
|
+
const allSkillsToLoad : string[] = container.utils.lodash.uniq([
|
|
187
|
+
...skills,
|
|
188
|
+
...preloadSkills,
|
|
189
|
+
])
|
|
190
|
+
|
|
191
|
+
if (allSkillsToLoad.length) {
|
|
192
|
+
ctx.question = `${ctx.question} \n\n## Required Skills\nYou will need to load the following skills to answer this question: ${allSkillsToLoad.join(', ')}`
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
a.interceptors.beforeAsk.remove(beforeAskCheckIfWeNeedSkills)
|
|
196
|
+
|
|
197
|
+
await next()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
assistant.intercept('beforeAsk', beforeAskCheckIfWeNeedSkills as any)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return assistant
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Discovered skills keyed by name (unfiltered). */
|
|
207
|
+
get skills(): Record<string, SkillInfo> {
|
|
208
|
+
return (this.state.get('skills') || {}) as Record<string, SkillInfo>
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Skills filtered by the `only` option when set. */
|
|
212
|
+
get filteredSkills(): Record<string, SkillInfo> {
|
|
213
|
+
const all = this.skills
|
|
214
|
+
const only = this.options.only
|
|
215
|
+
if (!only || only.length === 0) return all
|
|
216
|
+
|
|
217
|
+
const result: Record<string, SkillInfo> = {}
|
|
218
|
+
for (const [name, info] of Object.entries(all)) {
|
|
219
|
+
if (only.some(pattern => this.matchPattern(pattern, name))) {
|
|
220
|
+
result[name] = info
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return result
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
get availableSkills() {
|
|
227
|
+
return Object.keys(this.filteredSkills)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
get skillsTable() : Record<string, string> {
|
|
231
|
+
const skills = this.filteredSkills
|
|
232
|
+
|
|
233
|
+
return Object.fromEntries(
|
|
234
|
+
Object.keys(skills).map((name) => [name, skills[name]!.description])
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Match a name against a glob pattern (* wildcards). */
|
|
239
|
+
private matchPattern(pattern: string, name: string): boolean {
|
|
240
|
+
if (pattern === '*') return true
|
|
241
|
+
if (!pattern.includes('*')) return pattern === name
|
|
242
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')
|
|
243
|
+
return new RegExp(`^${escaped}$`).test(name)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Resolved path to the skills.json config file. */
|
|
247
|
+
get configPath(): string {
|
|
248
|
+
if (this.options.configPath) return this.options.configPath
|
|
249
|
+
const { os, paths } = this.container
|
|
250
|
+
return paths.resolve(os.homedir, '.luca', 'skills.json')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Whether the library has been loaded. */
|
|
254
|
+
get isStarted(): boolean {
|
|
255
|
+
return !!this.state.get('started')
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Expand ~ to home directory in a path. */
|
|
259
|
+
private expandHome(p: string): string {
|
|
260
|
+
return p.replace(/^\~/, this.container.os.homedir)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Read the persisted config, creating it if it doesn't exist. */
|
|
264
|
+
private readConfig(): { locations: string[] } {
|
|
265
|
+
const { fs } = this.container
|
|
266
|
+
|
|
267
|
+
if (!fs.exists(this.configPath)) {
|
|
268
|
+
const defaultConfig = { locations: [] as string[] }
|
|
269
|
+
this.writeConfig(defaultConfig)
|
|
270
|
+
return defaultConfig
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return fs.readJson(this.configPath)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Write the config back to disk. */
|
|
277
|
+
private writeConfig(config: { locations: string[] }): void {
|
|
278
|
+
const { fs, os, paths } = this.container
|
|
279
|
+
fs.mkdirp(paths.resolve(os.homedir, '.luca'))
|
|
280
|
+
fs.writeJson(this.configPath, config, 2)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Start the skills library: read config, scan all locations.
|
|
285
|
+
*
|
|
286
|
+
* @returns This instance for chaining
|
|
287
|
+
*/
|
|
288
|
+
async start(): Promise<SkillsLibrary> {
|
|
289
|
+
if (this.isStarted) return this
|
|
290
|
+
|
|
291
|
+
const { uniq } = this.container.utils.lodash
|
|
292
|
+
const config = this.readConfig()
|
|
293
|
+
const configLocations = config.locations.map(l => this.expandHome(l))
|
|
294
|
+
const instanceLocations = (this.options.locations || []).map(l => this.expandHome(l))
|
|
295
|
+
const agentsFolders: string[] = []
|
|
296
|
+
if (this.options.useAgentsFolders) {
|
|
297
|
+
const { paths, os, cwd } = this.container as any
|
|
298
|
+
// Check conventional agent skill locations (home + project cwd)
|
|
299
|
+
const candidates = [
|
|
300
|
+
paths.resolve(os.homedir, '.claude', 'skills'),
|
|
301
|
+
paths.resolve(os.homedir, '.agents', 'skills'),
|
|
302
|
+
paths.resolve(cwd, '.claude', 'skills'),
|
|
303
|
+
paths.resolve(cwd, '.agents', 'skills'),
|
|
304
|
+
]
|
|
305
|
+
agentsFolders.push(...candidates)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const allLocations = uniq([
|
|
309
|
+
...configLocations,
|
|
310
|
+
...instanceLocations,
|
|
311
|
+
...agentsFolders,
|
|
312
|
+
]).filter(Boolean).filter(l => (this.container as any).fs.exists(l))
|
|
313
|
+
this.state.set('locations', allLocations)
|
|
314
|
+
|
|
315
|
+
for (const loc of allLocations) {
|
|
316
|
+
await this.scanLocation(loc)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this.state.set('started', true)
|
|
320
|
+
this.state.set('skillCount', Object.keys(this.skills).length)
|
|
321
|
+
this.emit('started')
|
|
322
|
+
|
|
323
|
+
return this
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Add a new skill location folder and scan it for skills.
|
|
328
|
+
*
|
|
329
|
+
* @param locationPath - Path to a directory containing skill subfolders with SKILL.md
|
|
330
|
+
*/
|
|
331
|
+
async addLocation(locationPath: string): Promise<void> {
|
|
332
|
+
const resolved = this.expandHome(locationPath)
|
|
333
|
+
const current = this.state.get('locations') as string[]
|
|
334
|
+
|
|
335
|
+
if (current.includes(resolved)) return
|
|
336
|
+
|
|
337
|
+
const updated = [...current, resolved]
|
|
338
|
+
this.state.set('locations', updated)
|
|
339
|
+
|
|
340
|
+
// Persist — store the original (unexpanded) path for portability
|
|
341
|
+
const config = this.readConfig()
|
|
342
|
+
if (!config.locations.includes(locationPath)) {
|
|
343
|
+
config.locations.push(locationPath)
|
|
344
|
+
this.writeConfig(config)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await this.scanLocation(resolved)
|
|
348
|
+
this.state.set('skillCount', Object.keys(this.skills).length)
|
|
349
|
+
this.emit('locationAdded', resolved)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Remove a skill location and its skills from the library.
|
|
354
|
+
*
|
|
355
|
+
* @param locationPath - The location path to remove
|
|
356
|
+
*/
|
|
357
|
+
async removeLocation(locationPath: string): Promise<void> {
|
|
358
|
+
const resolved = this.expandHome(locationPath)
|
|
359
|
+
const current = this.state.get('locations') as string[]
|
|
360
|
+
this.state.set('locations', current.filter(l => l !== resolved))
|
|
361
|
+
|
|
362
|
+
// Remove skills from this location
|
|
363
|
+
const remaining: Record<string, SkillInfo> = {}
|
|
364
|
+
for (const [name, info] of Object.entries(this.skills)) {
|
|
365
|
+
if (info.locationPath !== resolved) {
|
|
366
|
+
remaining[name] = info
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
this.state.set('skills', remaining)
|
|
370
|
+
this.state.set('skillCount', Object.keys(remaining).length)
|
|
371
|
+
|
|
372
|
+
// Persist
|
|
373
|
+
const config = this.readConfig()
|
|
374
|
+
config.locations = config.locations.filter(l => this.expandHome(l) !== resolved)
|
|
375
|
+
this.writeConfig(config)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Scan a location folder for skill subfolders containing SKILL.md.
|
|
380
|
+
*
|
|
381
|
+
* @param locationPath - Absolute path to scan
|
|
382
|
+
*/
|
|
383
|
+
async scanLocation(locationPath: string): Promise<void> {
|
|
384
|
+
const { fs, paths } = this.container
|
|
385
|
+
if (!fs.exists(locationPath)) return
|
|
386
|
+
|
|
387
|
+
const entries = fs.readdirSync(locationPath)
|
|
388
|
+
|
|
389
|
+
for (const entry of entries) {
|
|
390
|
+
const skillDir = paths.resolve(locationPath, entry)
|
|
391
|
+
const skillFile = paths.resolve(skillDir, 'SKILL.md')
|
|
392
|
+
|
|
393
|
+
if (!fs.exists(skillFile)) continue
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
const parsed = await parse(skillFile)
|
|
397
|
+
const meta = (parsed.meta || {}) as Record<string, unknown>
|
|
398
|
+
const name = entry
|
|
399
|
+
|
|
400
|
+
const info: SkillInfo = {
|
|
401
|
+
name,
|
|
402
|
+
description: (meta.description as string) || '',
|
|
403
|
+
path: skillDir,
|
|
404
|
+
skillFilePath: skillFile,
|
|
405
|
+
locationPath,
|
|
406
|
+
meta,
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
this.state.set('skills', { ...this.skills, [name]: info })
|
|
410
|
+
this.emit('skillDiscovered', info)
|
|
411
|
+
} catch {
|
|
412
|
+
// Skip unparseable skill files
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Return all discovered skills (respects the `only` filter). */
|
|
418
|
+
list(): SkillInfo[] {
|
|
419
|
+
return Object.values(this.filteredSkills)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Find a skill by name. */
|
|
423
|
+
find(skillName: string): SkillInfo | undefined {
|
|
424
|
+
return this.skills[skillName]
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create a DocsReader for a skill's folder, enabling AI-assisted Q&A.
|
|
429
|
+
*
|
|
430
|
+
* @param skillName - Name of the skill to create a reader for
|
|
431
|
+
* @returns A DocsReader instance rooted at the skill's folder
|
|
432
|
+
*/
|
|
433
|
+
createSkillReader(skillName: string): DocsReader {
|
|
434
|
+
const skill = this.find(skillName)
|
|
435
|
+
if (!skill) throw new Error(`Skill "${skillName}" not found in the library`)
|
|
436
|
+
|
|
437
|
+
return this.container.feature('docsReader', { contentDb: skill.path })
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Create a tmp directory containing symlinked/copied skill folders by name,
|
|
442
|
+
* suitable for passing to claude --add-dir.
|
|
443
|
+
*
|
|
444
|
+
* @param skillNames - Array of skill names to include
|
|
445
|
+
* @returns Absolute path to the created directory
|
|
446
|
+
*/
|
|
447
|
+
ensureFolderCreatedWithSkillsByName(skillNames: string[]): string {
|
|
448
|
+
const { fs, paths, os } = this.container
|
|
449
|
+
const hash = this.container.utils.hashObject(skillNames.sort())
|
|
450
|
+
const dir = paths.resolve(os.tmpdir, 'luca-skills', hash)
|
|
451
|
+
|
|
452
|
+
if (fs.exists(dir)) return dir
|
|
453
|
+
|
|
454
|
+
fs.mkdirp(dir)
|
|
455
|
+
|
|
456
|
+
for (const name of skillNames) {
|
|
457
|
+
const skill = this.find(name)
|
|
458
|
+
if (!skill) throw new Error(`Skill "${name}" not found in the library`)
|
|
459
|
+
|
|
460
|
+
const dest = paths.resolve(dir, name)
|
|
461
|
+
if (!fs.exists(dest)) {
|
|
462
|
+
fs.copy(skill.path, dest)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return dir
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// --- Tool handlers for assistant.use(skillsLibrary) ---
|
|
470
|
+
|
|
471
|
+
/** Search available skills, optionally filtered by a query string. Respects the `only` filter. */
|
|
472
|
+
async searchAvailableSkills({ query }: { query?: string } = {}): Promise<string> {
|
|
473
|
+
if (!this.isStarted) await this.start()
|
|
474
|
+
|
|
475
|
+
let skills = Object.values(this.filteredSkills)
|
|
476
|
+
|
|
477
|
+
if (query) {
|
|
478
|
+
const q = query.toLowerCase()
|
|
479
|
+
skills = skills.filter(s =>
|
|
480
|
+
s.name.toLowerCase().includes(q) ||
|
|
481
|
+
s.description.toLowerCase().includes(q)
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (skills.length === 0) return 'No skills found.'
|
|
486
|
+
|
|
487
|
+
return skills.map(s => `- **${s.name}**: ${s.description || '(no description)'}\n Path: ${s.path}`).join('\n')
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** Load a skill's full SKILL.md content and metadata. */
|
|
491
|
+
async loadSkill({ skillName }: { skillName: string }): Promise<string> {
|
|
492
|
+
if (!this.isStarted) await this.start()
|
|
493
|
+
|
|
494
|
+
const skill = this.find(skillName)
|
|
495
|
+
if (!skill) return `Skill "${skillName}" not found.`
|
|
496
|
+
|
|
497
|
+
const content = this.container.fs.readFile(skill.skillFilePath)
|
|
498
|
+
|
|
499
|
+
return `# Skill: ${skill.name}\n\n**Description:** ${skill.description}\n**Path:** ${skill.path}\n\n---\n\n${content}`
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Ask a question about a specific skill using a DocsReader. */
|
|
503
|
+
async askSkillBasedQuestion({ skillName, question }: { skillName: string; question: string }): Promise<string> {
|
|
504
|
+
if (!this.isStarted) await this.start()
|
|
505
|
+
|
|
506
|
+
const reader = this.createSkillReader(skillName)
|
|
507
|
+
const answer = await reader.ask(question)
|
|
508
|
+
return answer
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Fork the given assistant and ask it which skills (if any) are relevant
|
|
513
|
+
* to the user's query. Returns an array of skill names that should be loaded
|
|
514
|
+
* before the real question is answered.
|
|
515
|
+
*
|
|
516
|
+
* The fork is ephemeral (historyMode: 'none') and uses structured output so
|
|
517
|
+
* the result is always a clean string array — never free text.
|
|
518
|
+
*
|
|
519
|
+
* @param assistant - The assistant instance to fork
|
|
520
|
+
* @param userQuery - The user's original question
|
|
521
|
+
* @returns Array of skill names relevant to the query (may be empty)
|
|
522
|
+
*/
|
|
523
|
+
async findRelevantSkillsForAssistant(assistant: Assistant, userQuery: string): Promise<string[]> {
|
|
524
|
+
if (!this.isStarted) await this.start()
|
|
525
|
+
|
|
526
|
+
const skills = this.list()
|
|
527
|
+
if (skills.length === 0) return []
|
|
528
|
+
|
|
529
|
+
const responseSchema = z.object({
|
|
530
|
+
skills: z.array(z.string()).describe('Names of skills relevant to the query. Empty array if none apply.'),
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
const skillsDescription = Object.entries(this.skillsTable)
|
|
534
|
+
.map(([title,description]) => `- **${title}**: ${description}`)
|
|
535
|
+
.join("\n")
|
|
536
|
+
|
|
537
|
+
const prompt = this.container.ui.endent(`You are a routing assistant. Given a user query and a list of available skills, determine which skills (if any) should be loaded to help answer the query.
|
|
538
|
+
Available skills:
|
|
539
|
+
-------
|
|
540
|
+
${skillsDescription}
|
|
541
|
+
|
|
542
|
+
User query: ${userQuery}
|
|
543
|
+
|
|
544
|
+
Return only the skill names that are directly relevant. Return an empty array if none apply. Do not load skills speculatively — only include ones that would materially help answer this specific query.`)
|
|
545
|
+
|
|
546
|
+
const fork = assistant.conversation.fork()
|
|
547
|
+
const result = await fork.ask(prompt, { schema: responseSchema }) as unknown as { skills: string[] }
|
|
548
|
+
|
|
549
|
+
const found = result.skills.filter(name => this.find(name) !== undefined)
|
|
550
|
+
|
|
551
|
+
this.emit('foundSkills', found, assistant, userQuery)
|
|
552
|
+
|
|
553
|
+
return found
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export default SkillsLibrary
|
package/src/agi/index.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A composable middleware chain. Each interceptor receives a mutable
|
|
3
|
+
* context and a `next` function. Calling `next()` continues the chain;
|
|
4
|
+
* skipping it short-circuits.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type InterceptorFn<T> = (ctx: T, next: () => Promise<void>) => Promise<void>
|
|
8
|
+
|
|
9
|
+
export class InterceptorChain<T> {
|
|
10
|
+
private fns: InterceptorFn<T>[] = []
|
|
11
|
+
|
|
12
|
+
add(fn: InterceptorFn<T>): void {
|
|
13
|
+
this.fns.push(fn)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
remove(fn: InterceptorFn<T>): void {
|
|
17
|
+
const idx = this.fns.indexOf(fn)
|
|
18
|
+
if (idx !== -1) this.fns.splice(idx, 1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get hasInterceptors(): boolean {
|
|
22
|
+
return this.fns.length > 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get size(): number {
|
|
26
|
+
return this.fns.length
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
clear(): void {
|
|
30
|
+
this.fns = []
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
clone(): InterceptorChain<T> {
|
|
34
|
+
const copy = new InterceptorChain<T>()
|
|
35
|
+
for (const fn of this.fns) copy.add(fn)
|
|
36
|
+
return copy
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async run(ctx: T, final: () => Promise<void>): Promise<void> {
|
|
40
|
+
let index = 0
|
|
41
|
+
const fns = this.fns
|
|
42
|
+
|
|
43
|
+
const next = async (): Promise<void> => {
|
|
44
|
+
if (index < fns.length) {
|
|
45
|
+
const fn = fns[index++]!
|
|
46
|
+
await fn(ctx, next)
|
|
47
|
+
} else {
|
|
48
|
+
await final()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await next()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BeforeAskCtx {
|
|
57
|
+
question: string | any[]
|
|
58
|
+
options?: any
|
|
59
|
+
result?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ToolCallCtx {
|
|
63
|
+
name: string
|
|
64
|
+
args: Record<string, any>
|
|
65
|
+
result?: string
|
|
66
|
+
error?: any
|
|
67
|
+
skip?: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface BeforeResponseCtx {
|
|
71
|
+
text: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface BeforeTurnCtx {
|
|
75
|
+
turn: number
|
|
76
|
+
isFollowUp: boolean
|
|
77
|
+
messages: any[]
|
|
78
|
+
skip?: boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface InterceptorPoints {
|
|
82
|
+
beforeAsk: BeforeAskCtx
|
|
83
|
+
beforeTurn: BeforeTurnCtx
|
|
84
|
+
beforeToolCall: ToolCallCtx
|
|
85
|
+
afterToolCall: ToolCallCtx
|
|
86
|
+
beforeResponse: BeforeResponseCtx
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type InterceptorPoint = keyof InterceptorPoints
|