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,1136 @@
|
|
|
1
|
+
import { Feature } from '../feature.js';
|
|
2
|
+
import type { HelperIntrospection, MethodIntrospection, GetterIntrospection, EventIntrospection, ContainerIntrospection, ExampleIntrospection } from './index.js';
|
|
3
|
+
import * as ts from 'typescript';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
import { FeatureStateSchema, FeatureOptionsSchema } from '../schemas/base.js'
|
|
9
|
+
|
|
10
|
+
export const IntrospectionScannerStateSchema = FeatureStateSchema.extend({
|
|
11
|
+
scanResults: z.array(z.any()).default([]),
|
|
12
|
+
containerResults: z.array(z.any()).default([]),
|
|
13
|
+
lastScanTime: z.date().nullable().default(null),
|
|
14
|
+
scannedFiles: z.array(z.string()).default([]),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export const IntrospectionScannerOptionsSchema = FeatureOptionsSchema.extend({
|
|
18
|
+
src: z.array(z.string()).optional(),
|
|
19
|
+
outputPath: z.string().optional(),
|
|
20
|
+
includePrivate: z.boolean().optional(),
|
|
21
|
+
importSource: z.string().optional(),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export type IntrospectionScannerState = z.infer<typeof IntrospectionScannerStateSchema>
|
|
25
|
+
export type IntrospectionScannerOptions = z.infer<typeof IntrospectionScannerOptionsSchema>
|
|
26
|
+
|
|
27
|
+
export class IntrospectionScannerFeature extends Feature<IntrospectionScannerState, IntrospectionScannerOptions> {
|
|
28
|
+
static override shortcut = 'introspectionScanner';
|
|
29
|
+
static override description = 'Scans TypeScript files for Helper classes and generates introspection data using AST analysis';
|
|
30
|
+
static override stateSchema = IntrospectionScannerStateSchema;
|
|
31
|
+
static override optionsSchema = IntrospectionScannerOptionsSchema;
|
|
32
|
+
|
|
33
|
+
override get initialState(): IntrospectionScannerState {
|
|
34
|
+
return {
|
|
35
|
+
...super.initialState,
|
|
36
|
+
scanResults: [],
|
|
37
|
+
containerResults: [],
|
|
38
|
+
lastScanTime: null,
|
|
39
|
+
scannedFiles: []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override async enable(options: Partial<IntrospectionScannerOptions> = {}): Promise<this> {
|
|
44
|
+
const fullOptions: IntrospectionScannerOptions = {
|
|
45
|
+
src: [],
|
|
46
|
+
outputPath: 'src/introspection/generated.ts',
|
|
47
|
+
includePrivate: false,
|
|
48
|
+
...options
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
await super.enable(fullOptions);
|
|
52
|
+
|
|
53
|
+
if (!this.options.src || this.options.src.length === 0) {
|
|
54
|
+
throw new Error('IntrospectionScanner requires src directories to be specified');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Scan the specified source directories for Helper classes and extract introspection data
|
|
62
|
+
*/
|
|
63
|
+
async scan(): Promise<HelperIntrospection[]> {
|
|
64
|
+
const startTime = Date.now();
|
|
65
|
+
this.emit('scanStarted', { directories: this.options.src });
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const results: HelperIntrospection[] = [];
|
|
69
|
+
const containerResults: Partial<ContainerIntrospection>[] = [];
|
|
70
|
+
const scannedFiles: string[] = [];
|
|
71
|
+
|
|
72
|
+
for (const srcDir of this.options.src || []) {
|
|
73
|
+
const files = await this.findTypeScriptFiles(srcDir);
|
|
74
|
+
scannedFiles.push(...files);
|
|
75
|
+
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
const { helpers, containers } = await this.analyzeFile(file);
|
|
78
|
+
results.push(...helpers);
|
|
79
|
+
containerResults.push(...containers);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.setState({
|
|
84
|
+
scanResults: results,
|
|
85
|
+
containerResults,
|
|
86
|
+
lastScanTime: new Date(),
|
|
87
|
+
scannedFiles
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const duration = Date.now() - startTime;
|
|
91
|
+
this.emit('scanCompleted', {
|
|
92
|
+
results: results.length,
|
|
93
|
+
containers: containerResults.length,
|
|
94
|
+
files: scannedFiles.length,
|
|
95
|
+
duration
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return results;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.emit('scanFailed', error);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Generate a TypeScript file that populates the introspection registry
|
|
107
|
+
*/
|
|
108
|
+
async generateRegistryScript(): Promise<string> {
|
|
109
|
+
let results = this.state.get('scanResults');
|
|
110
|
+
if (!results || results.length === 0) {
|
|
111
|
+
await this.scan();
|
|
112
|
+
results = this.state.get('scanResults');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const containerResults = this.state.get('containerResults') || [];
|
|
116
|
+
const script = this.createRegistryScript(results!, containerResults);
|
|
117
|
+
|
|
118
|
+
if (this.options.outputPath) {
|
|
119
|
+
await fs.promises.writeFile(this.options.outputPath, script);
|
|
120
|
+
this.emit('scriptGenerated', { path: this.options.outputPath });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return script;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async findTypeScriptFiles(srcPath: string): Promise<string[]> {
|
|
127
|
+
// If it's a direct .ts file path, return it directly (if it exists)
|
|
128
|
+
if (srcPath.endsWith('.ts')) {
|
|
129
|
+
try {
|
|
130
|
+
await fs.promises.access(srcPath);
|
|
131
|
+
return [srcPath];
|
|
132
|
+
} catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const pattern = path.join(srcPath, '**/*.ts');
|
|
138
|
+
const files = await glob(pattern, {
|
|
139
|
+
ignore: ['**/*.d.ts', '**/node_modules/**']
|
|
140
|
+
});
|
|
141
|
+
return files.sort();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async analyzeFile(filePath: string): Promise<{ helpers: HelperIntrospection[], containers: Partial<ContainerIntrospection>[] }> {
|
|
145
|
+
const sourceCode = await fs.promises.readFile(filePath, 'utf-8');
|
|
146
|
+
const sourceFile = ts.createSourceFile(
|
|
147
|
+
filePath,
|
|
148
|
+
sourceCode,
|
|
149
|
+
ts.ScriptTarget.Latest,
|
|
150
|
+
true
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const helpers: HelperIntrospection[] = [];
|
|
154
|
+
const containers: Partial<ContainerIntrospection>[] = [];
|
|
155
|
+
|
|
156
|
+
const visit = (node: ts.Node) => {
|
|
157
|
+
if (ts.isClassDeclaration(node)) {
|
|
158
|
+
if (this.extendsContainer(node)) {
|
|
159
|
+
const containerData = this.analyzeContainerClass(node, sourceFile);
|
|
160
|
+
if (containerData) {
|
|
161
|
+
containers.push(containerData);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
const introspection = this.analyzeClass(node, sourceFile);
|
|
165
|
+
if (introspection) {
|
|
166
|
+
helpers.push(introspection);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
ts.forEachChild(node, visit);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
visit(sourceFile);
|
|
174
|
+
return { helpers, containers };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private analyzeClass(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): HelperIntrospection | null {
|
|
178
|
+
const className = classNode.name?.text;
|
|
179
|
+
if (!className) return null;
|
|
180
|
+
|
|
181
|
+
// Check if this extends Helper, Feature, Client, or Server
|
|
182
|
+
const isHelperClass = this.extendsHelper(classNode);
|
|
183
|
+
if (!isHelperClass) return null;
|
|
184
|
+
|
|
185
|
+
const shortcut = this.extractShortcut(classNode);
|
|
186
|
+
if (!shortcut) return null;
|
|
187
|
+
|
|
188
|
+
const description = this.extractJSDocDescription(classNode);
|
|
189
|
+
const methods = this.extractMethods(classNode, sourceFile);
|
|
190
|
+
const getters = this.extractGetters(classNode, sourceFile);
|
|
191
|
+
const events = this.extractEvents(classNode, sourceFile);
|
|
192
|
+
const examples = this.extractJSDocExamples(classNode);
|
|
193
|
+
const types = this.collectReferencedTypes(methods, getters, sourceFile);
|
|
194
|
+
|
|
195
|
+
// state and options are derived at runtime from Zod schemas
|
|
196
|
+
// via interceptRegistration — no need to extract them at build time
|
|
197
|
+
return {
|
|
198
|
+
id: shortcut,
|
|
199
|
+
description: description || `${className} helper`,
|
|
200
|
+
shortcut,
|
|
201
|
+
className,
|
|
202
|
+
methods,
|
|
203
|
+
getters,
|
|
204
|
+
events,
|
|
205
|
+
state: {},
|
|
206
|
+
options: {},
|
|
207
|
+
envVars: [],
|
|
208
|
+
...(examples.length > 0 ? { examples } : {}),
|
|
209
|
+
...(Object.keys(types).length > 0 ? { types } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private extendsHelper(classNode: ts.ClassDeclaration): boolean {
|
|
214
|
+
if (!classNode.heritageClauses) return false;
|
|
215
|
+
|
|
216
|
+
for (const clause of classNode.heritageClauses) {
|
|
217
|
+
if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
|
|
218
|
+
for (const type of clause.types) {
|
|
219
|
+
if (type.expression) {
|
|
220
|
+
const typeName = type.expression.getText();
|
|
221
|
+
if (['Helper', 'Feature', 'Client', 'Server'].some(base => typeName.includes(base))) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Container-specific getters to exclude from introspection.
|
|
233
|
+
* These are framework internals (class references, utility objects) not useful in inspection.
|
|
234
|
+
*/
|
|
235
|
+
private static CONTAINER_SKIP_GETTERS = new Set([
|
|
236
|
+
'Feature', 'Helper', 'State', 'z',
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
private extendsContainer(classNode: ts.ClassDeclaration): boolean {
|
|
240
|
+
const className = classNode.name?.text;
|
|
241
|
+
const containerNames = ['Container', 'NodeContainer', 'WebContainer', 'AGIContainer'];
|
|
242
|
+
|
|
243
|
+
// The root Container class itself is a container
|
|
244
|
+
if (className && containerNames.includes(className)) return true;
|
|
245
|
+
|
|
246
|
+
if (!classNode.heritageClauses) return false;
|
|
247
|
+
|
|
248
|
+
for (const clause of classNode.heritageClauses) {
|
|
249
|
+
if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
|
|
250
|
+
for (const type of clause.types) {
|
|
251
|
+
if (type.expression) {
|
|
252
|
+
const typeName = type.expression.getText();
|
|
253
|
+
if (containerNames.some(base => typeName.includes(base))) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Analyze a Container class and extract introspection data.
|
|
265
|
+
* Container classes don't have a shortcut; they use className as the key.
|
|
266
|
+
*/
|
|
267
|
+
private analyzeContainerClass(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): Partial<ContainerIntrospection> | null {
|
|
268
|
+
const className = classNode.name?.text;
|
|
269
|
+
if (!className) return null;
|
|
270
|
+
|
|
271
|
+
const description = this.extractJSDocDescription(classNode);
|
|
272
|
+
const methods = this.extractContainerMethods(classNode, sourceFile);
|
|
273
|
+
const getters = this.extractContainerGetters(classNode, sourceFile);
|
|
274
|
+
const events = this.extractEvents(classNode, sourceFile);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
className,
|
|
278
|
+
description: description || `${className} container`,
|
|
279
|
+
methods,
|
|
280
|
+
getters,
|
|
281
|
+
events,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
private extractContainerMethods(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): Record<string, MethodIntrospection> {
|
|
287
|
+
const methods: Record<string, MethodIntrospection> = {};
|
|
288
|
+
|
|
289
|
+
for (const member of classNode.members) {
|
|
290
|
+
if (ts.isMethodDeclaration(member) && member.name) {
|
|
291
|
+
const methodName = member.name.getText();
|
|
292
|
+
|
|
293
|
+
// Skip private methods unless includePrivate is true
|
|
294
|
+
const isPrivate = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
|
295
|
+
if (isPrivate && !this.options.includePrivate) continue;
|
|
296
|
+
|
|
297
|
+
// Skip static methods
|
|
298
|
+
const isStatic = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
|
299
|
+
if (isStatic) continue;
|
|
300
|
+
|
|
301
|
+
// Skip methods starting with _ (internal methods like _hide)
|
|
302
|
+
if (methodName.startsWith('_')) continue;
|
|
303
|
+
|
|
304
|
+
// Skip methods tagged with @internal
|
|
305
|
+
if (this.hasJSDocTag(member, 'internal')) continue;
|
|
306
|
+
|
|
307
|
+
const description = this.extractJSDocDescription(member) || '';
|
|
308
|
+
const parameters = this.extractParameters(member);
|
|
309
|
+
const required = this.extractRequiredParameters(member);
|
|
310
|
+
const returns = this.extractReturnType(member);
|
|
311
|
+
const examples = this.extractJSDocExamples(member);
|
|
312
|
+
|
|
313
|
+
methods[methodName] = {
|
|
314
|
+
description,
|
|
315
|
+
parameters,
|
|
316
|
+
required,
|
|
317
|
+
returns,
|
|
318
|
+
...(examples.length > 0 ? { examples } : {}),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return methods;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private extractContainerGetters(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): Record<string, GetterIntrospection> {
|
|
327
|
+
const getters: Record<string, GetterIntrospection> = {};
|
|
328
|
+
|
|
329
|
+
for (const member of classNode.members) {
|
|
330
|
+
if (ts.isGetAccessorDeclaration(member) && member.name) {
|
|
331
|
+
const getterName = member.name.getText();
|
|
332
|
+
|
|
333
|
+
// Skip container framework getters
|
|
334
|
+
if (IntrospectionScannerFeature.CONTAINER_SKIP_GETTERS.has(getterName)) continue;
|
|
335
|
+
|
|
336
|
+
// Skip getters tagged with @internal
|
|
337
|
+
if (this.hasJSDocTag(member, 'internal')) continue;
|
|
338
|
+
|
|
339
|
+
// Skip private getters unless includePrivate is true
|
|
340
|
+
const isPrivate = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
|
341
|
+
if (isPrivate && !this.options.includePrivate) continue;
|
|
342
|
+
|
|
343
|
+
// Skip static getters
|
|
344
|
+
const isStatic = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
|
345
|
+
if (isStatic) continue;
|
|
346
|
+
|
|
347
|
+
const description = this.extractJSDocDescriptionFromAccessor(member) || '';
|
|
348
|
+
const returns = this.resolveReturnType(member, member.type, 'any');
|
|
349
|
+
const examples = this.extractJSDocExamples(member);
|
|
350
|
+
|
|
351
|
+
getters[getterName] = {
|
|
352
|
+
description,
|
|
353
|
+
returns,
|
|
354
|
+
...(examples.length > 0 ? { examples } : {}),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return getters;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private extractShortcut(classNode: ts.ClassDeclaration): string | null {
|
|
363
|
+
// Look for static shortcut property
|
|
364
|
+
for (const member of classNode.members) {
|
|
365
|
+
if (ts.isPropertyDeclaration(member) &&
|
|
366
|
+
member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword) &&
|
|
367
|
+
member.name?.getText() === 'shortcut') {
|
|
368
|
+
|
|
369
|
+
if (member.initializer) {
|
|
370
|
+
if (ts.isStringLiteral(member.initializer)) {
|
|
371
|
+
return member.initializer.text;
|
|
372
|
+
} else if (ts.isAsExpression(member.initializer) && ts.isStringLiteral(member.initializer.expression)) {
|
|
373
|
+
return member.initializer.expression.text;
|
|
374
|
+
} else {
|
|
375
|
+
// For cases like "features.fs" as const, extract the string part
|
|
376
|
+
const text = member.initializer.getText();
|
|
377
|
+
const match = text.match(/^"([^"]+)"/);
|
|
378
|
+
if (match && match[1]) {
|
|
379
|
+
return match[1];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private extractJSDocDescription(node: ts.ClassDeclaration | ts.MethodDeclaration): string | null {
|
|
389
|
+
// Use TypeScript's built-in function to get leading comments
|
|
390
|
+
const sourceFile = node.getSourceFile();
|
|
391
|
+
const fullText = sourceFile.getFullText();
|
|
392
|
+
|
|
393
|
+
// Get the text range for the node including leading trivia (comments)
|
|
394
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
395
|
+
|
|
396
|
+
if (ranges && ranges.length > 0) {
|
|
397
|
+
// Find the last JSDoc comment (the one immediately before the node)
|
|
398
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
399
|
+
const range = ranges[i];
|
|
400
|
+
if (!range) continue;
|
|
401
|
+
const commentText = fullText.substring(range.pos, range.end);
|
|
402
|
+
|
|
403
|
+
// Check if it's a JSDoc comment (starts with /**)
|
|
404
|
+
if (commentText.startsWith('/**')) {
|
|
405
|
+
// Extract the content between /** and */
|
|
406
|
+
const content = commentText.slice(3, -2);
|
|
407
|
+
|
|
408
|
+
// Clean up by removing * prefixes and extracting main description
|
|
409
|
+
const lines = content.split('\n');
|
|
410
|
+
const cleanLines: string[] = [];
|
|
411
|
+
|
|
412
|
+
for (const line of lines) {
|
|
413
|
+
const cleaned = line.replace(/^\s*\*\s?/, '').trim();
|
|
414
|
+
if (cleaned && !cleaned.startsWith('@')) {
|
|
415
|
+
cleanLines.push(cleaned);
|
|
416
|
+
} else if (cleaned.startsWith('@')) {
|
|
417
|
+
// Stop at first @tag
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const description = cleanLines.join(' ').trim();
|
|
423
|
+
return description || null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private extractJSDocParamDescriptions(method: ts.MethodDeclaration): {
|
|
432
|
+
params: Record<string, string>,
|
|
433
|
+
subProps: Record<string, Record<string, string>>
|
|
434
|
+
} {
|
|
435
|
+
const params: Record<string, string> = {};
|
|
436
|
+
const subProps: Record<string, Record<string, string>> = {};
|
|
437
|
+
|
|
438
|
+
const sourceFile = method.getSourceFile();
|
|
439
|
+
const fullText = sourceFile.getFullText();
|
|
440
|
+
|
|
441
|
+
const ranges = ts.getLeadingCommentRanges(fullText, method.getFullStart());
|
|
442
|
+
|
|
443
|
+
if (ranges && ranges.length > 0) {
|
|
444
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
445
|
+
const range = ranges[i];
|
|
446
|
+
if (range) {
|
|
447
|
+
const commentText = fullText.substring(range.pos, range.end);
|
|
448
|
+
|
|
449
|
+
if (commentText.startsWith('/**')) {
|
|
450
|
+
const content = commentText.slice(3, -2);
|
|
451
|
+
|
|
452
|
+
const lines = content.split('\n');
|
|
453
|
+
for (const line of lines) {
|
|
454
|
+
const cleaned = line.replace(/^\s*\*\s?/, '').trim();
|
|
455
|
+
|
|
456
|
+
// Match @param {type} name.subProp or @param {type} [name.subProp=default] - description
|
|
457
|
+
const paramMatch = cleaned.match(/^@param\s+(?:\{[^}]*\}\s+)?\[?([\w.]+)(?:=[^\]]*)?\]?\s*-?\s*(.+)$/);
|
|
458
|
+
if (paramMatch && paramMatch[1] && paramMatch[2]) {
|
|
459
|
+
const fullName = paramMatch[1];
|
|
460
|
+
const description = paramMatch[2].trim();
|
|
461
|
+
|
|
462
|
+
if (fullName.includes('.')) {
|
|
463
|
+
// Sub-property: e.g. options.cached -> { options: { cached: "..." } }
|
|
464
|
+
const parts = fullName.split('.');
|
|
465
|
+
const parentName = parts[0]!;
|
|
466
|
+
const propName = parts.slice(1).join('.');
|
|
467
|
+
if (!subProps[parentName]) subProps[parentName] = {};
|
|
468
|
+
subProps[parentName]![propName] = description;
|
|
469
|
+
} else {
|
|
470
|
+
params[fullName] = description;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return { params, subProps };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private extractMethods(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): Record<string, MethodIntrospection> {
|
|
484
|
+
const methods: Record<string, MethodIntrospection> = {};
|
|
485
|
+
|
|
486
|
+
for (const member of classNode.members) {
|
|
487
|
+
if (ts.isMethodDeclaration(member) && member.name) {
|
|
488
|
+
const methodName = member.name.getText();
|
|
489
|
+
|
|
490
|
+
// Skip private methods unless includePrivate is true
|
|
491
|
+
const isPrivate = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
|
492
|
+
if (isPrivate && !this.options.includePrivate) continue;
|
|
493
|
+
|
|
494
|
+
// Skip static methods (like attach)
|
|
495
|
+
const isStatic = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
|
496
|
+
if (isStatic) continue;
|
|
497
|
+
|
|
498
|
+
// Skip methods tagged with @internal
|
|
499
|
+
if (this.hasJSDocTag(member, 'internal')) continue;
|
|
500
|
+
|
|
501
|
+
const description = this.extractJSDocDescription(member) || '';
|
|
502
|
+
const parameters = this.extractParameters(member);
|
|
503
|
+
const required = this.extractRequiredParameters(member);
|
|
504
|
+
const returns = this.extractReturnType(member);
|
|
505
|
+
const examples = this.extractJSDocExamples(member);
|
|
506
|
+
|
|
507
|
+
methods[methodName] = {
|
|
508
|
+
description,
|
|
509
|
+
parameters,
|
|
510
|
+
required,
|
|
511
|
+
returns,
|
|
512
|
+
...(examples.length > 0 ? { examples } : {}),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return methods;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Base class getters that should be excluded from introspection output.
|
|
522
|
+
* These are inherited from Helper/Feature and not specific to individual features.
|
|
523
|
+
*/
|
|
524
|
+
private static BASE_GETTERS = new Set([
|
|
525
|
+
'initialState', 'container', 'options', 'context', 'cacheKey', 'isEnabled', 'shortcut'
|
|
526
|
+
]);
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Base class events that should be excluded from introspection output.
|
|
530
|
+
* These are inherited from Helper lifecycle and not specific to individual helpers.
|
|
531
|
+
*/
|
|
532
|
+
private static BASE_EVENTS = new Set([
|
|
533
|
+
'stateChange', 'enabled'
|
|
534
|
+
]);
|
|
535
|
+
|
|
536
|
+
private extractGetters(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): Record<string, GetterIntrospection> {
|
|
537
|
+
const getters: Record<string, GetterIntrospection> = {};
|
|
538
|
+
|
|
539
|
+
for (const member of classNode.members) {
|
|
540
|
+
if (ts.isGetAccessorDeclaration(member) && member.name) {
|
|
541
|
+
const getterName = member.name.getText();
|
|
542
|
+
|
|
543
|
+
// Skip base class getters
|
|
544
|
+
if (IntrospectionScannerFeature.BASE_GETTERS.has(getterName)) continue;
|
|
545
|
+
|
|
546
|
+
// Skip private getters unless includePrivate is true
|
|
547
|
+
const isPrivate = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
|
548
|
+
if (isPrivate && !this.options.includePrivate) continue;
|
|
549
|
+
|
|
550
|
+
// Skip static getters
|
|
551
|
+
const isStatic = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
|
552
|
+
if (isStatic) continue;
|
|
553
|
+
|
|
554
|
+
// Skip getters tagged with @internal
|
|
555
|
+
if (this.hasJSDocTag(member, 'internal')) continue;
|
|
556
|
+
|
|
557
|
+
const description = this.extractJSDocDescriptionFromAccessor(member) || '';
|
|
558
|
+
const returns = this.resolveReturnType(member, member.type, 'any');
|
|
559
|
+
const examples = this.extractJSDocExamples(member);
|
|
560
|
+
|
|
561
|
+
getters[getterName] = {
|
|
562
|
+
description,
|
|
563
|
+
returns,
|
|
564
|
+
...(examples.length > 0 ? { examples } : {}),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return getters;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Extract a return type from a node, preferring explicit TS annotation,
|
|
574
|
+
* falling back to JSDoc @returns {Type}, then to the given default.
|
|
575
|
+
*/
|
|
576
|
+
private resolveReturnType(node: ts.Node, typeNode: ts.TypeNode | undefined, fallback: string): string {
|
|
577
|
+
if (typeNode) return typeNode.getText();
|
|
578
|
+
|
|
579
|
+
const sourceFile = node.getSourceFile();
|
|
580
|
+
const fullText = sourceFile.getFullText();
|
|
581
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
582
|
+
|
|
583
|
+
if (ranges && ranges.length > 0) {
|
|
584
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
585
|
+
const range = ranges[i];
|
|
586
|
+
if (!range) continue;
|
|
587
|
+
const commentText = fullText.substring(range.pos, range.end);
|
|
588
|
+
if (!commentText.startsWith('/**')) continue;
|
|
589
|
+
|
|
590
|
+
const match = commentText.match(/@returns?\s*\{([^}]+)\}/);
|
|
591
|
+
if (match && match[1]) return match[1].trim();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return fallback;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Check if a node's JSDoc comment contains a specific @tag (e.g. @internal).
|
|
600
|
+
*/
|
|
601
|
+
private hasJSDocTag(node: ts.Node, tagName: string): boolean {
|
|
602
|
+
const sourceFile = node.getSourceFile();
|
|
603
|
+
const fullText = sourceFile.getFullText();
|
|
604
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
605
|
+
|
|
606
|
+
if (!ranges || ranges.length === 0) return false;
|
|
607
|
+
|
|
608
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
609
|
+
const range = ranges[i];
|
|
610
|
+
if (!range) continue;
|
|
611
|
+
const commentText = fullText.substring(range.pos, range.end);
|
|
612
|
+
if (!commentText.startsWith('/**')) continue;
|
|
613
|
+
|
|
614
|
+
// Check for @tagName anywhere in the JSDoc
|
|
615
|
+
if (new RegExp(`@${tagName}\\b`).test(commentText)) {
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private extractJSDocDescriptionFromAccessor(node: ts.GetAccessorDeclaration): string | null {
|
|
624
|
+
const sourceFile = node.getSourceFile();
|
|
625
|
+
const fullText = sourceFile.getFullText();
|
|
626
|
+
|
|
627
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
628
|
+
|
|
629
|
+
if (ranges && ranges.length > 0) {
|
|
630
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
631
|
+
const range = ranges[i];
|
|
632
|
+
if (!range) continue;
|
|
633
|
+
const commentText = fullText.substring(range.pos, range.end);
|
|
634
|
+
|
|
635
|
+
if (commentText.startsWith('/**')) {
|
|
636
|
+
const content = commentText.slice(3, -2);
|
|
637
|
+
const lines = content.split('\n');
|
|
638
|
+
const cleanLines: string[] = [];
|
|
639
|
+
|
|
640
|
+
for (const line of lines) {
|
|
641
|
+
const cleaned = line.replace(/^\s*\*\s?/, '').trim();
|
|
642
|
+
if (cleaned && !cleaned.startsWith('@')) {
|
|
643
|
+
cleanLines.push(cleaned);
|
|
644
|
+
} else if (cleaned.startsWith('@')) {
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const description = cleanLines.join(' ').trim();
|
|
650
|
+
return description || null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Extract @example blocks from JSDoc comments on any node type.
|
|
660
|
+
* Supports fenced code blocks (```lang ... ```) within @example tags.
|
|
661
|
+
* Returns an empty array if no examples are found.
|
|
662
|
+
*/
|
|
663
|
+
private extractJSDocExamples(node: ts.Node): ExampleIntrospection[] {
|
|
664
|
+
const sourceFile = node.getSourceFile();
|
|
665
|
+
const fullText = sourceFile.getFullText();
|
|
666
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
667
|
+
|
|
668
|
+
if (!ranges || ranges.length === 0) return [];
|
|
669
|
+
|
|
670
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
671
|
+
const range = ranges[i];
|
|
672
|
+
if (!range) continue;
|
|
673
|
+
const commentText = fullText.substring(range.pos, range.end);
|
|
674
|
+
if (!commentText.startsWith('/**')) continue;
|
|
675
|
+
|
|
676
|
+
const content = commentText.slice(3, -2);
|
|
677
|
+
const lines = content.split('\n');
|
|
678
|
+
const examples: ExampleIntrospection[] = [];
|
|
679
|
+
|
|
680
|
+
let inExample = false;
|
|
681
|
+
let inFence = false;
|
|
682
|
+
let language = 'ts';
|
|
683
|
+
let codeLines: string[] = [];
|
|
684
|
+
|
|
685
|
+
for (const line of lines) {
|
|
686
|
+
const cleaned = line.replace(/^\s*\*\s?/, '');
|
|
687
|
+
|
|
688
|
+
if (cleaned.trim().startsWith('@example')) {
|
|
689
|
+
// If we were already collecting an example, save it
|
|
690
|
+
if (inExample && codeLines.length > 0) {
|
|
691
|
+
examples.push({ language, code: codeLines.join('\n').trim() });
|
|
692
|
+
}
|
|
693
|
+
inExample = true;
|
|
694
|
+
inFence = false;
|
|
695
|
+
language = 'ts';
|
|
696
|
+
codeLines = [];
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (!inExample) continue;
|
|
701
|
+
|
|
702
|
+
// Another @tag ends the current example
|
|
703
|
+
if (cleaned.trim().startsWith('@') && !cleaned.trim().startsWith('@example')) {
|
|
704
|
+
if (codeLines.length > 0) {
|
|
705
|
+
examples.push({ language, code: codeLines.join('\n').trim() });
|
|
706
|
+
}
|
|
707
|
+
inExample = false;
|
|
708
|
+
inFence = false;
|
|
709
|
+
codeLines = [];
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const trimmed = cleaned.trim();
|
|
714
|
+
|
|
715
|
+
if (!inFence && trimmed.startsWith('```')) {
|
|
716
|
+
inFence = true;
|
|
717
|
+
const lang = trimmed.slice(3).trim();
|
|
718
|
+
if (lang) language = lang === 'typescript' ? 'ts' : lang === 'javascript' ? 'js' : lang;
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (inFence && trimmed === '```') {
|
|
723
|
+
inFence = false;
|
|
724
|
+
// Don't end the example yet — there could be more fences or text
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (inFence) {
|
|
729
|
+
codeLines.push(cleaned.replace(/^\s?/, ''));
|
|
730
|
+
} else if (inExample && trimmed) {
|
|
731
|
+
// Unfenced example code line
|
|
732
|
+
codeLines.push(cleaned.replace(/^\s?/, ''));
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Flush final example
|
|
737
|
+
if (inExample && codeLines.length > 0) {
|
|
738
|
+
examples.push({ language, code: codeLines.join('\n').trim() });
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return examples;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return [];
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private static PRIMITIVE_TYPES = new Set([
|
|
748
|
+
'string', 'number', 'boolean', 'any', 'void', 'undefined', 'null',
|
|
749
|
+
'never', 'unknown', 'object', 'bigint', 'symbol',
|
|
750
|
+
]);
|
|
751
|
+
|
|
752
|
+
private extractParameters(method: ts.MethodDeclaration): Record<string, { type: string, description: string, properties?: Record<string, { type: string, description: string }> }> {
|
|
753
|
+
const parameters: Record<string, { type: string, description: string, properties?: Record<string, { type: string, description: string }> }> = {};
|
|
754
|
+
|
|
755
|
+
// Extract JSDoc parameter descriptions (including sub-property descriptions)
|
|
756
|
+
const { params: paramDescriptions, subProps } = this.extractJSDocParamDescriptions(method);
|
|
757
|
+
const sourceFile = method.getSourceFile();
|
|
758
|
+
|
|
759
|
+
for (const param of method.parameters) {
|
|
760
|
+
const paramName = param.name.getText();
|
|
761
|
+
const type = param.type ? param.type.getText() : 'any';
|
|
762
|
+
|
|
763
|
+
// Use JSDoc description if available, otherwise fallback to generic description
|
|
764
|
+
const description = paramDescriptions[paramName] || `Parameter ${paramName}`;
|
|
765
|
+
|
|
766
|
+
const entry: { type: string, description: string, properties?: Record<string, { type: string, description: string }> } = { type, description };
|
|
767
|
+
|
|
768
|
+
// Resolve non-primitive types to their member properties
|
|
769
|
+
const baseType = type.replace(/\[\]$/, '').replace(/\s*\|.*$/, '').trim();
|
|
770
|
+
if (!IntrospectionScannerFeature.PRIMITIVE_TYPES.has(baseType)) {
|
|
771
|
+
const properties = this.resolveTypeProperties(baseType, sourceFile);
|
|
772
|
+
if (properties) {
|
|
773
|
+
entry.properties = properties;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Merge JSDoc sub-property descriptions (e.g. @param {boolean} [options.cached] - ...)
|
|
778
|
+
// into the resolved type properties
|
|
779
|
+
const jsdocSubProps = subProps[paramName];
|
|
780
|
+
if (jsdocSubProps) {
|
|
781
|
+
if (!entry.properties) entry.properties = {};
|
|
782
|
+
for (const [propName, propDesc] of Object.entries(jsdocSubProps)) {
|
|
783
|
+
if (entry.properties[propName]) {
|
|
784
|
+
// Enrich existing property with JSDoc description if it was empty
|
|
785
|
+
if (!entry.properties[propName].description) {
|
|
786
|
+
entry.properties[propName].description = propDesc;
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
entry.properties[propName] = { type: 'any', description: propDesc };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
parameters[paramName] = entry;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return parameters;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Resolves a type name to its member properties by searching the source file
|
|
802
|
+
* for type alias or interface declarations.
|
|
803
|
+
*/
|
|
804
|
+
private resolveTypeProperties(typeName: string, sourceFile: ts.SourceFile): Record<string, { type: string, description: string }> | null {
|
|
805
|
+
for (const statement of sourceFile.statements) {
|
|
806
|
+
// Handle: type Foo = { ... }
|
|
807
|
+
if (ts.isTypeAliasDeclaration(statement) && statement.name.text === typeName) {
|
|
808
|
+
if (ts.isTypeLiteralNode(statement.type)) {
|
|
809
|
+
return this.extractTypeLiteralMembers(statement.type, sourceFile);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// Handle: interface Foo { ... }
|
|
813
|
+
if (ts.isInterfaceDeclaration(statement) && statement.name.text === typeName) {
|
|
814
|
+
return this.extractInterfaceMembers(statement, sourceFile);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
private extractTypeLiteralMembers(node: ts.TypeLiteralNode, sourceFile: ts.SourceFile): Record<string, { type: string, description: string }> {
|
|
821
|
+
const members: Record<string, { type: string, description: string }> = {};
|
|
822
|
+
|
|
823
|
+
for (const member of node.members) {
|
|
824
|
+
if (ts.isPropertySignature(member) && member.name) {
|
|
825
|
+
const name = member.name.getText();
|
|
826
|
+
const type = member.type ? member.type.getText() : 'any';
|
|
827
|
+
const description = this.extractJSDocFromNode(member, sourceFile) || '';
|
|
828
|
+
members[name] = { type, description };
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return Object.keys(members).length > 0 ? members : null as any;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
private extractInterfaceMembers(node: ts.InterfaceDeclaration, sourceFile: ts.SourceFile): Record<string, { type: string, description: string }> {
|
|
836
|
+
const members: Record<string, { type: string, description: string }> = {};
|
|
837
|
+
|
|
838
|
+
for (const member of node.members) {
|
|
839
|
+
if (ts.isPropertySignature(member) && member.name) {
|
|
840
|
+
const name = member.name.getText();
|
|
841
|
+
const type = member.type ? member.type.getText() : 'any';
|
|
842
|
+
const description = this.extractJSDocFromNode(member, sourceFile) || '';
|
|
843
|
+
members[name] = { type, description };
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return Object.keys(members).length > 0 ? members : null as any;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Collect all non-primitive type names referenced in method parameters and return types,
|
|
852
|
+
* then resolve each to its properties from the source file. Returns a map of type name
|
|
853
|
+
* to its property definitions, suitable for emitting as standalone interface declarations.
|
|
854
|
+
*/
|
|
855
|
+
private collectReferencedTypes(
|
|
856
|
+
methods: Record<string, MethodIntrospection>,
|
|
857
|
+
getters: Record<string, { returns: string; description: string }>,
|
|
858
|
+
sourceFile: ts.SourceFile
|
|
859
|
+
): Record<string, { description: string; properties: Record<string, { type: string; description: string; optional?: boolean }> }> {
|
|
860
|
+
const types: Record<string, { description: string; properties: Record<string, { type: string; description: string; optional?: boolean }> }> = {};
|
|
861
|
+
const seen = new Set<string>();
|
|
862
|
+
|
|
863
|
+
const tryResolve = (rawType: string) => {
|
|
864
|
+
// Extract type names from complex type strings
|
|
865
|
+
// e.g. "Promise<GrepMatch[]>" -> "GrepMatch"
|
|
866
|
+
// e.g. "GrepOptions" -> "GrepOptions"
|
|
867
|
+
// e.g. "string | Foo" -> "Foo"
|
|
868
|
+
// e.g. "Omit<GrepOptions, 'pattern'>" -> "GrepOptions"
|
|
869
|
+
const typeNames = this.extractTypeNames(rawType);
|
|
870
|
+
|
|
871
|
+
for (const typeName of typeNames) {
|
|
872
|
+
if (seen.has(typeName)) continue;
|
|
873
|
+
seen.add(typeName);
|
|
874
|
+
|
|
875
|
+
if (IntrospectionScannerFeature.PRIMITIVE_TYPES.has(typeName)) continue;
|
|
876
|
+
|
|
877
|
+
const properties = this.resolveTypePropertiesWithOptional(typeName, sourceFile);
|
|
878
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
879
|
+
// Extract JSDoc description from the type declaration itself
|
|
880
|
+
const description = this.extractTypeDescription(typeName, sourceFile) || '';
|
|
881
|
+
types[typeName] = { description, properties };
|
|
882
|
+
|
|
883
|
+
// Recursively resolve types referenced in properties
|
|
884
|
+
for (const prop of Object.values(properties)) {
|
|
885
|
+
tryResolve(prop.type);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// Collect from method parameters and return types
|
|
892
|
+
for (const method of Object.values(methods)) {
|
|
893
|
+
for (const param of Object.values(method.parameters || {})) {
|
|
894
|
+
tryResolve(param.type);
|
|
895
|
+
}
|
|
896
|
+
if (method.returns) {
|
|
897
|
+
tryResolve(method.returns);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Collect from getter return types
|
|
902
|
+
for (const getter of Object.values(getters)) {
|
|
903
|
+
if (getter.returns) {
|
|
904
|
+
tryResolve(getter.returns);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return types;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Extract concrete type names from a potentially complex type string.
|
|
913
|
+
* Handles generics, unions, intersections, arrays, and utility types.
|
|
914
|
+
*/
|
|
915
|
+
private extractTypeNames(typeStr: string): string[] {
|
|
916
|
+
if (!typeStr) return [];
|
|
917
|
+
|
|
918
|
+
// Remove Promise<...> wrapper but keep the inner type
|
|
919
|
+
// Remove array suffix [], Partial<>, Omit<>, Pick<>, etc.
|
|
920
|
+
// Split on | and & for unions/intersections
|
|
921
|
+
const names: string[] = [];
|
|
922
|
+
|
|
923
|
+
// Match identifiers that look like type names (PascalCase or known patterns)
|
|
924
|
+
// This regex finds all capitalized identifiers that aren't JS built-ins
|
|
925
|
+
const matches = typeStr.match(/\b([A-Z][A-Za-z0-9_]*)\b/g) || [];
|
|
926
|
+
const BUILTIN_TYPES = new Set([
|
|
927
|
+
'Promise', 'Array', 'Record', 'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
928
|
+
'Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'Exclude', 'Extract',
|
|
929
|
+
'Buffer', 'Date', 'RegExp', 'Error', 'Function', 'Symbol',
|
|
930
|
+
'AsyncIterable', 'Iterable', 'Iterator', 'AsyncIterator',
|
|
931
|
+
'InstanceType', 'ReturnType', 'Parameters',
|
|
932
|
+
'HTMLElement', 'Event', 'EventTarget',
|
|
933
|
+
'Stats', // Node.js fs.Stats — commonly used but not resolvable in-file
|
|
934
|
+
]);
|
|
935
|
+
|
|
936
|
+
for (const match of matches) {
|
|
937
|
+
if (!BUILTIN_TYPES.has(match) && !IntrospectionScannerFeature.PRIMITIVE_TYPES.has(match.toLowerCase())) {
|
|
938
|
+
names.push(match);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return [...new Set(names)];
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Like resolveTypeProperties but also captures whether each property is optional (has ?).
|
|
947
|
+
*/
|
|
948
|
+
private resolveTypePropertiesWithOptional(typeName: string, sourceFile: ts.SourceFile): Record<string, { type: string; description: string; optional?: boolean }> | null {
|
|
949
|
+
for (const statement of sourceFile.statements) {
|
|
950
|
+
if (ts.isTypeAliasDeclaration(statement) && statement.name.text === typeName) {
|
|
951
|
+
if (ts.isTypeLiteralNode(statement.type)) {
|
|
952
|
+
return this.extractTypeLiteralMembersWithOptional(statement.type, sourceFile);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (ts.isInterfaceDeclaration(statement) && statement.name.text === typeName) {
|
|
956
|
+
return this.extractInterfaceMembersWithOptional(statement, sourceFile);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
private extractTypeLiteralMembersWithOptional(node: ts.TypeLiteralNode, sourceFile: ts.SourceFile): Record<string, { type: string; description: string; optional?: boolean }> | null {
|
|
963
|
+
const members: Record<string, { type: string; description: string; optional?: boolean }> = {};
|
|
964
|
+
for (const member of node.members) {
|
|
965
|
+
if (ts.isPropertySignature(member) && member.name) {
|
|
966
|
+
const name = member.name.getText();
|
|
967
|
+
const type = member.type ? member.type.getText() : 'any';
|
|
968
|
+
const description = this.extractJSDocFromNode(member, sourceFile) || '';
|
|
969
|
+
const optional = !!member.questionToken;
|
|
970
|
+
members[name] = { type, description, ...(optional ? { optional } : {}) };
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return Object.keys(members).length > 0 ? members : null;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
private extractInterfaceMembersWithOptional(node: ts.InterfaceDeclaration, sourceFile: ts.SourceFile): Record<string, { type: string; description: string; optional?: boolean }> | null {
|
|
977
|
+
const members: Record<string, { type: string; description: string; optional?: boolean }> = {};
|
|
978
|
+
for (const member of node.members) {
|
|
979
|
+
if (ts.isPropertySignature(member) && member.name) {
|
|
980
|
+
const name = member.name.getText();
|
|
981
|
+
const type = member.type ? member.type.getText() : 'any';
|
|
982
|
+
const description = this.extractJSDocFromNode(member, sourceFile) || '';
|
|
983
|
+
const optional = !!member.questionToken;
|
|
984
|
+
members[name] = { type, description, ...(optional ? { optional } : {}) };
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return Object.keys(members).length > 0 ? members : null;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Extract the JSDoc description from a type alias or interface declaration.
|
|
992
|
+
*/
|
|
993
|
+
private extractTypeDescription(typeName: string, sourceFile: ts.SourceFile): string | null {
|
|
994
|
+
for (const statement of sourceFile.statements) {
|
|
995
|
+
if (
|
|
996
|
+
(ts.isTypeAliasDeclaration(statement) || ts.isInterfaceDeclaration(statement)) &&
|
|
997
|
+
statement.name.text === typeName
|
|
998
|
+
) {
|
|
999
|
+
return this.extractJSDocFromNode(statement, sourceFile);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Extracts a JSDoc description from any node that may have leading comments.
|
|
1007
|
+
*/
|
|
1008
|
+
private extractJSDocFromNode(node: ts.Node, sourceFile: ts.SourceFile): string | null {
|
|
1009
|
+
const fullText = sourceFile.getFullText();
|
|
1010
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
1011
|
+
|
|
1012
|
+
if (ranges && ranges.length > 0) {
|
|
1013
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
1014
|
+
const range = ranges[i];
|
|
1015
|
+
if (!range) continue;
|
|
1016
|
+
const commentText = fullText.substring(range.pos, range.end);
|
|
1017
|
+
|
|
1018
|
+
if (commentText.startsWith('/**')) {
|
|
1019
|
+
const content = commentText.slice(3, -2);
|
|
1020
|
+
const lines = content.split('\n');
|
|
1021
|
+
const cleanLines: string[] = [];
|
|
1022
|
+
|
|
1023
|
+
for (const line of lines) {
|
|
1024
|
+
const cleaned = line.replace(/^\s*\*\s?/, '').trim();
|
|
1025
|
+
if (cleaned && !cleaned.startsWith('@')) {
|
|
1026
|
+
cleanLines.push(cleaned);
|
|
1027
|
+
} else if (cleaned.startsWith('@')) {
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
return cleanLines.join(' ').trim() || null;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Handle single-line // comments
|
|
1036
|
+
if (commentText.startsWith('//')) {
|
|
1037
|
+
return commentText.replace(/^\/\/\s*/, '').trim() || null;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private extractRequiredParameters(method: ts.MethodDeclaration): string[] {
|
|
1046
|
+
return method.parameters
|
|
1047
|
+
.filter(param => !param.questionToken && !param.initializer)
|
|
1048
|
+
.map(param => param.name.getText());
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
private extractReturnType(method: ts.MethodDeclaration): string {
|
|
1052
|
+
return this.resolveReturnType(method, method.type, 'void');
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
private extractEvents(classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): Record<string, EventIntrospection> {
|
|
1056
|
+
const events: Record<string, EventIntrospection> = {};
|
|
1057
|
+
|
|
1058
|
+
// Look for this.emit() calls in methods
|
|
1059
|
+
const visit = (node: ts.Node) => {
|
|
1060
|
+
if (ts.isCallExpression(node) &&
|
|
1061
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
1062
|
+
node.expression.expression &&
|
|
1063
|
+
node.expression.expression.kind === ts.SyntaxKind.ThisKeyword &&
|
|
1064
|
+
node.expression.name.text === 'emit') {
|
|
1065
|
+
|
|
1066
|
+
if (node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0]!)) {
|
|
1067
|
+
const eventName = node.arguments[0]!.text;
|
|
1068
|
+
|
|
1069
|
+
// Skip base class lifecycle events
|
|
1070
|
+
if (IntrospectionScannerFeature.BASE_EVENTS.has(eventName)) return;
|
|
1071
|
+
|
|
1072
|
+
if (!events[eventName]) {
|
|
1073
|
+
events[eventName] = {
|
|
1074
|
+
name: eventName,
|
|
1075
|
+
description: `Event emitted by ${classNode.name?.text || 'class'}`,
|
|
1076
|
+
arguments: {}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
ts.forEachChild(node, visit);
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
for (const member of classNode.members) {
|
|
1085
|
+
if (ts.isMethodDeclaration(member)) {
|
|
1086
|
+
visit(member);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
return events;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
private createRegistryScript(results: HelperIntrospection[], containerResults: Partial<ContainerIntrospection>[] = []): string {
|
|
1094
|
+
const hasContainers = containerResults.length > 0;
|
|
1095
|
+
|
|
1096
|
+
const importSource = this.options.importSource || './index.js';
|
|
1097
|
+
let imports = `import { setBuildTimeData`;
|
|
1098
|
+
if (hasContainers) {
|
|
1099
|
+
imports += `, setContainerBuildTimeData`;
|
|
1100
|
+
}
|
|
1101
|
+
imports += ` } from '${importSource}';\n\n`;
|
|
1102
|
+
|
|
1103
|
+
// Sort by id/className for deterministic output across runs
|
|
1104
|
+
const sortedResults = [...results].sort((a, b) => a.id.localeCompare(b.id));
|
|
1105
|
+
const sortedContainers = [...containerResults].sort((a, b) => (a.className || '').localeCompare(b.className || ''));
|
|
1106
|
+
|
|
1107
|
+
const registrations = sortedResults.map(result => {
|
|
1108
|
+
const data = JSON.stringify(result, null, 2);
|
|
1109
|
+
return `setBuildTimeData('${result.id}', ${data});`;
|
|
1110
|
+
}).join('\n\n');
|
|
1111
|
+
|
|
1112
|
+
let containerRegistrations = '';
|
|
1113
|
+
if (hasContainers) {
|
|
1114
|
+
containerRegistrations = '\n\n// Container introspection data\n' + sortedContainers.map(result => {
|
|
1115
|
+
const data = JSON.stringify(result, null, 2);
|
|
1116
|
+
return `setContainerBuildTimeData('${result.className}', ${data});`;
|
|
1117
|
+
}).join('\n\n');
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const exportStatement = `\nexport const introspectionData = ${JSON.stringify(sortedResults, null, 2)};\n`;
|
|
1121
|
+
const containerExport = hasContainers ? `\nexport const containerIntrospectionData = ${JSON.stringify(sortedContainers, null, 2)};\n` : '';
|
|
1122
|
+
|
|
1123
|
+
return `${imports}// Auto-generated introspection registry data\n\n${registrations}${containerRegistrations}${exportStatement}${containerExport}`;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Register the feature
|
|
1128
|
+
import { features } from '../feature.js';
|
|
1129
|
+
|
|
1130
|
+
declare module '../feature.js' {
|
|
1131
|
+
interface AvailableFeatures {
|
|
1132
|
+
introspectionScanner: typeof IntrospectionScannerFeature;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
export default features.register('introspectionScanner', IntrospectionScannerFeature);
|