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,961 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import net from 'net'
|
|
3
|
+
import detectPort from 'detect-port'
|
|
4
|
+
import { Feature } from '../feature.js'
|
|
5
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
6
|
+
|
|
7
|
+
const MAX_CIDR_HOSTS = 65536
|
|
8
|
+
|
|
9
|
+
const PORT_SERVICE_MAP: Record<number, string> = {
|
|
10
|
+
20: 'ftp-data',
|
|
11
|
+
21: 'ftp',
|
|
12
|
+
22: 'ssh',
|
|
13
|
+
23: 'telnet',
|
|
14
|
+
25: 'smtp',
|
|
15
|
+
53: 'dns',
|
|
16
|
+
67: 'dhcp',
|
|
17
|
+
68: 'dhcp',
|
|
18
|
+
69: 'tftp',
|
|
19
|
+
80: 'http',
|
|
20
|
+
110: 'pop3',
|
|
21
|
+
111: 'rpcbind',
|
|
22
|
+
119: 'nntp',
|
|
23
|
+
123: 'ntp',
|
|
24
|
+
135: 'msrpc',
|
|
25
|
+
137: 'netbios-ns',
|
|
26
|
+
138: 'netbios-dgm',
|
|
27
|
+
139: 'netbios-ssn',
|
|
28
|
+
143: 'imap',
|
|
29
|
+
161: 'snmp',
|
|
30
|
+
389: 'ldap',
|
|
31
|
+
443: 'https',
|
|
32
|
+
445: 'microsoft-ds',
|
|
33
|
+
465: 'smtps',
|
|
34
|
+
514: 'syslog',
|
|
35
|
+
515: 'printer',
|
|
36
|
+
543: 'kerberos',
|
|
37
|
+
587: 'submission',
|
|
38
|
+
631: 'ipp',
|
|
39
|
+
636: 'ldaps',
|
|
40
|
+
873: 'rsync',
|
|
41
|
+
993: 'imaps',
|
|
42
|
+
995: 'pop3s',
|
|
43
|
+
1080: 'socks',
|
|
44
|
+
1194: 'openvpn',
|
|
45
|
+
1433: 'ms-sql',
|
|
46
|
+
1521: 'oracle',
|
|
47
|
+
1723: 'pptp',
|
|
48
|
+
1883: 'mqtt',
|
|
49
|
+
2049: 'nfs',
|
|
50
|
+
2375: 'docker',
|
|
51
|
+
2376: 'docker-tls',
|
|
52
|
+
3000: 'http-alt',
|
|
53
|
+
3306: 'mysql',
|
|
54
|
+
3389: 'rdp',
|
|
55
|
+
4000: 'http-alt',
|
|
56
|
+
5000: 'http-alt',
|
|
57
|
+
5432: 'postgresql',
|
|
58
|
+
5672: 'amqp',
|
|
59
|
+
5900: 'vnc',
|
|
60
|
+
5985: 'winrm',
|
|
61
|
+
5986: 'winrm-https',
|
|
62
|
+
6379: 'redis',
|
|
63
|
+
7001: 'http-alt',
|
|
64
|
+
8080: 'http-proxy',
|
|
65
|
+
8081: 'http-alt',
|
|
66
|
+
8443: 'https-alt',
|
|
67
|
+
9000: 'http-alt',
|
|
68
|
+
9092: 'kafka',
|
|
69
|
+
9200: 'elasticsearch',
|
|
70
|
+
11211: 'memcached',
|
|
71
|
+
27017: 'mongodb',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const DEFAULT_PORTS = [22, 80, 443, 3000, 3306, 5432, 6379, 8080, 8443]
|
|
75
|
+
|
|
76
|
+
export const LocalNetworkSchema = z.object({
|
|
77
|
+
interface: z.string().describe('Network interface name'),
|
|
78
|
+
address: z.string().describe('IPv4 address for this interface'),
|
|
79
|
+
netmask: z.string().describe('IPv4 netmask'),
|
|
80
|
+
cidr: z.string().describe('Derived CIDR range for this interface'),
|
|
81
|
+
mac: z.string().optional().describe('MAC address for this interface'),
|
|
82
|
+
})
|
|
83
|
+
export type LocalNetwork = z.infer<typeof LocalNetworkSchema>
|
|
84
|
+
|
|
85
|
+
export const ArpEntrySchema = z.object({
|
|
86
|
+
ip: z.string().describe('IP address from ARP table'),
|
|
87
|
+
mac: z.string().optional().describe('MAC address from ARP table'),
|
|
88
|
+
interface: z.string().optional().describe('Interface name if available'),
|
|
89
|
+
})
|
|
90
|
+
export type ArpEntry = z.infer<typeof ArpEntrySchema>
|
|
91
|
+
|
|
92
|
+
export const DiscoverHostSchema = z.object({
|
|
93
|
+
ip: z.string().describe('Host IP address'),
|
|
94
|
+
mac: z.string().optional().describe('MAC address when available'),
|
|
95
|
+
reachable: z.boolean().describe('Whether host appears reachable'),
|
|
96
|
+
method: z.enum(['arp', 'tcp']).describe('Discovery method used for this host'),
|
|
97
|
+
})
|
|
98
|
+
export type DiscoverHost = z.infer<typeof DiscoverHostSchema>
|
|
99
|
+
|
|
100
|
+
export const PortScanResultSchema = z.object({
|
|
101
|
+
port: z.number().int().min(1).max(65535).describe('Port number'),
|
|
102
|
+
status: z.enum(['open', 'closed', 'filtered']).describe('TCP connect scan status'),
|
|
103
|
+
service: z.string().optional().describe('Best-effort service guess by port'),
|
|
104
|
+
banner: z.string().optional().describe('Banner text when captured'),
|
|
105
|
+
})
|
|
106
|
+
export type PortScanResult = z.infer<typeof PortScanResultSchema>
|
|
107
|
+
|
|
108
|
+
export const LocalNetworkScanHostSchema = z.object({
|
|
109
|
+
ip: z.string().describe('Host IP address'),
|
|
110
|
+
mac: z.string().optional().describe('MAC address when available'),
|
|
111
|
+
hostname: z.string().optional().describe('Hostname when available'),
|
|
112
|
+
openPorts: z.array(PortScanResultSchema).describe('Open ports discovered for this host'),
|
|
113
|
+
})
|
|
114
|
+
export type LocalNetworkScanHost = z.infer<typeof LocalNetworkScanHostSchema>
|
|
115
|
+
|
|
116
|
+
export const NetworkSnapshotSchema = z.object({
|
|
117
|
+
cidr: z.string().describe('Scanned CIDR range'),
|
|
118
|
+
hosts: z.array(LocalNetworkScanHostSchema).describe('Hosts discovered in this CIDR'),
|
|
119
|
+
})
|
|
120
|
+
export type NetworkSnapshot = z.infer<typeof NetworkSnapshotSchema>
|
|
121
|
+
|
|
122
|
+
export const NetworkingEventsSchema = FeatureEventsSchema.extend({
|
|
123
|
+
'host:discovered': z.tuple([DiscoverHostSchema.describe('The discovered host')]).describe('When a host is found during network scanning'),
|
|
124
|
+
'port:open': z.tuple([z.object({
|
|
125
|
+
host: z.string().describe('Host IP address'),
|
|
126
|
+
port: z.number().describe('Open port number'),
|
|
127
|
+
service: z.string().optional().describe('Best-effort service name'),
|
|
128
|
+
banner: z.string().optional().describe('Banner text when captured'),
|
|
129
|
+
}).describe('Open port details')]).describe('When an open port is detected on a host'),
|
|
130
|
+
'scan:start': z.tuple([z.object({
|
|
131
|
+
target: z.string().describe('Scan target identifier'),
|
|
132
|
+
type: z.string().describe('Scan type identifier'),
|
|
133
|
+
}).describe('Scan start details')]).describe('When a network scan begins'),
|
|
134
|
+
'scan:complete': z.tuple([z.object({
|
|
135
|
+
target: z.string().describe('Scan target identifier'),
|
|
136
|
+
type: z.string().describe('Scan type identifier'),
|
|
137
|
+
duration: z.number().describe('Scan duration in milliseconds'),
|
|
138
|
+
hostsFound: z.number().describe('Number of hosts discovered'),
|
|
139
|
+
portsFound: z.number().describe('Number of open ports discovered'),
|
|
140
|
+
}).describe('Scan completion details')]).describe('When a network scan finishes'),
|
|
141
|
+
}).describe('Networking events')
|
|
142
|
+
|
|
143
|
+
export const NetworkingStateSchema = FeatureStateSchema.extend({
|
|
144
|
+
lastScan: z.object({
|
|
145
|
+
timestamp: z.number().describe('Unix epoch timestamp in ms'),
|
|
146
|
+
target: z.string().describe('Primary scan target identifier'),
|
|
147
|
+
type: z.string().describe('Scan type identifier'),
|
|
148
|
+
networks: z.array(NetworkSnapshotSchema).describe('Last known scan results'),
|
|
149
|
+
}).optional().describe('The most recent network scan result'),
|
|
150
|
+
})
|
|
151
|
+
export type NetworkingState = z.infer<typeof NetworkingStateSchema>
|
|
152
|
+
|
|
153
|
+
export const NetworkingOptionsSchema = FeatureOptionsSchema.extend({
|
|
154
|
+
timeout: z.number().optional().describe('Default timeout in milliseconds for probing'),
|
|
155
|
+
concurrency: z.number().optional().describe('Default concurrency for scanning operations'),
|
|
156
|
+
})
|
|
157
|
+
export type NetworkingOptions = z.infer<typeof NetworkingOptionsSchema>
|
|
158
|
+
|
|
159
|
+
type ScanPortsOptions = {
|
|
160
|
+
ports?: string | number[]
|
|
161
|
+
timeout?: number
|
|
162
|
+
concurrency?: number
|
|
163
|
+
banner?: boolean
|
|
164
|
+
includeClosed?: boolean
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
type DiscoverHostsOptions = {
|
|
168
|
+
timeout?: number
|
|
169
|
+
concurrency?: number
|
|
170
|
+
ports?: number[]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type ReachableHostOptions = {
|
|
174
|
+
timeout?: number
|
|
175
|
+
ports?: number[]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
type ScanLocalNetworksOptions = {
|
|
179
|
+
ports?: string | number[]
|
|
180
|
+
timeout?: number
|
|
181
|
+
concurrency?: number
|
|
182
|
+
hostConcurrency?: number
|
|
183
|
+
banner?: boolean
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type NmapPort = {
|
|
187
|
+
port: number
|
|
188
|
+
state: string
|
|
189
|
+
protocol: string
|
|
190
|
+
service?: string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type NmapHost = {
|
|
194
|
+
ip: string
|
|
195
|
+
hostname?: string
|
|
196
|
+
status?: string
|
|
197
|
+
mac?: string
|
|
198
|
+
ports: NmapPort[]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The Networking feature provides utilities for network-related operations.
|
|
203
|
+
*
|
|
204
|
+
* This feature includes utilities for port detection and availability checking,
|
|
205
|
+
* which are commonly needed when setting up servers or network services.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* const networking = container.feature('networking')
|
|
210
|
+
*
|
|
211
|
+
* // Find an available port starting from 3000
|
|
212
|
+
* const port = await networking.findOpenPort(3000)
|
|
213
|
+
* console.log(`Available port: ${port}`)
|
|
214
|
+
*
|
|
215
|
+
* // Check if a specific port is available
|
|
216
|
+
* const isAvailable = await networking.isPortOpen(8080)
|
|
217
|
+
* if (isAvailable) {
|
|
218
|
+
* console.log('Port 8080 is available')
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @extends Feature
|
|
223
|
+
*/
|
|
224
|
+
export class Networking extends Feature<NetworkingState, NetworkingOptions> {
|
|
225
|
+
static override shortcut = 'features.networking' as const
|
|
226
|
+
static override stateSchema = NetworkingStateSchema
|
|
227
|
+
static override optionsSchema = NetworkingOptionsSchema
|
|
228
|
+
static override eventsSchema = NetworkingEventsSchema
|
|
229
|
+
static { Feature.register(this, 'networking') }
|
|
230
|
+
|
|
231
|
+
override get initialState(): NetworkingState {
|
|
232
|
+
return {
|
|
233
|
+
...super.initialState,
|
|
234
|
+
enabled: false,
|
|
235
|
+
lastScan: undefined,
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private _binCache: Record<string, string> = {}
|
|
240
|
+
|
|
241
|
+
/** Resolve a binary path via `which`, caching the result. */
|
|
242
|
+
private resolveBin(name: string): string {
|
|
243
|
+
if (this._binCache[name]) return this._binCache[name]
|
|
244
|
+
try {
|
|
245
|
+
this._binCache[name] = this.proc.exec(`which ${name}`).trim()
|
|
246
|
+
} catch {
|
|
247
|
+
this._binCache[name] = name
|
|
248
|
+
}
|
|
249
|
+
return this._binCache[name]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
get proc(): ReturnType<typeof this.container.feature<'proc'>> {
|
|
253
|
+
return this.container.feature('proc')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
get os(): ReturnType<typeof this.container.feature<'os'>> {
|
|
257
|
+
return this.container.feature('os')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Finds the next available port starting from the specified port number.
|
|
262
|
+
*
|
|
263
|
+
* This method will search for the first available port starting from the given
|
|
264
|
+
* port number. If the specified port is available, it returns that port.
|
|
265
|
+
* Otherwise, it returns the next available port.
|
|
266
|
+
*
|
|
267
|
+
* @param {number} [startAt=0] - The port number to start searching from (0 means system will choose)
|
|
268
|
+
* @returns {Promise<number>} Promise that resolves to an available port number
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```typescript
|
|
272
|
+
* // Find any available port
|
|
273
|
+
* const anyPort = await networking.findOpenPort()
|
|
274
|
+
*
|
|
275
|
+
* // Find an available port starting from 3000
|
|
276
|
+
* const port = await networking.findOpenPort(3000)
|
|
277
|
+
* console.log(`Server can use port: ${port}`)
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
async findOpenPort(startAt = 0) {
|
|
281
|
+
const nextPort = await detectPort(Number(startAt))
|
|
282
|
+
return nextPort
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Checks if a specific port is available for use.
|
|
287
|
+
*
|
|
288
|
+
* This method attempts to detect if the specified port is available.
|
|
289
|
+
* It returns true if the port is available, false if it's already in use.
|
|
290
|
+
*
|
|
291
|
+
* @param {number} [checkPort=0] - The port number to check for availability
|
|
292
|
+
* @returns {Promise<boolean>} Promise that resolves to true if the port is available, false otherwise
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* // Check if port 8080 is available
|
|
297
|
+
* const isAvailable = await networking.isPortOpen(8080)
|
|
298
|
+
* if (isAvailable) {
|
|
299
|
+
* console.log('Port 8080 is free to use')
|
|
300
|
+
* } else {
|
|
301
|
+
* console.log('Port 8080 is already in use')
|
|
302
|
+
* }
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
async isPortOpen(checkPort = 0) {
|
|
306
|
+
const nextPort = await detectPort(Number(checkPort))
|
|
307
|
+
return nextPort && nextPort === Number(checkPort)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Returns local external IPv4 interfaces and their CIDR ranges.
|
|
312
|
+
*/
|
|
313
|
+
getLocalNetworks(): LocalNetwork[] {
|
|
314
|
+
const interfaces = this.os.networkInterfaces as Record<string, Array<{
|
|
315
|
+
address: string
|
|
316
|
+
netmask: string
|
|
317
|
+
family: string | number
|
|
318
|
+
mac?: string
|
|
319
|
+
internal: boolean
|
|
320
|
+
}> | undefined>
|
|
321
|
+
const networks: LocalNetwork[] = []
|
|
322
|
+
|
|
323
|
+
for (const [interfaceName, details] of Object.entries(interfaces)) {
|
|
324
|
+
if (!details || details.length === 0) {
|
|
325
|
+
continue
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
for (const detail of details) {
|
|
329
|
+
if (!detail) {
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const family = typeof detail.family === 'string' ? detail.family : detail.family === 4 ? 'IPv4' : 'IPv6'
|
|
334
|
+
if (detail.internal || family !== 'IPv4') {
|
|
335
|
+
continue
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const cidr = this.computeCidr(detail.address, detail.netmask)
|
|
339
|
+
networks.push({
|
|
340
|
+
interface: interfaceName,
|
|
341
|
+
address: detail.address,
|
|
342
|
+
netmask: detail.netmask,
|
|
343
|
+
cidr,
|
|
344
|
+
mac: detail.mac,
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return networks
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Expands a CIDR block to host IP addresses.
|
|
354
|
+
* For /31 and /32, all addresses are returned. For all others, network/broadcast are excluded.
|
|
355
|
+
*/
|
|
356
|
+
expandCidr(cidr: string): string[] {
|
|
357
|
+
const [ipPart, maskPart] = cidr.split('/')
|
|
358
|
+
if (!ipPart || !maskPart) {
|
|
359
|
+
throw new Error(`Invalid CIDR: ${cidr}`)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const maskBits = Number(maskPart)
|
|
363
|
+
if (!Number.isInteger(maskBits) || maskBits < 0 || maskBits > 32) {
|
|
364
|
+
throw new Error(`Invalid CIDR mask: ${cidr}`)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const baseIpInt = this.ipToInt(ipPart)
|
|
368
|
+
const networkMask = maskBits === 0 ? 0 : (0xffffffff << (32 - maskBits)) >>> 0
|
|
369
|
+
const network = baseIpInt & networkMask
|
|
370
|
+
const broadcast = (network | (~networkMask >>> 0)) >>> 0
|
|
371
|
+
const totalAddresses = broadcast - network + 1
|
|
372
|
+
|
|
373
|
+
if (totalAddresses > MAX_CIDR_HOSTS) {
|
|
374
|
+
throw new Error(`CIDR ${cidr} is too large to expand safely (${totalAddresses} addresses)`)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const includeAll = maskBits >= 31
|
|
378
|
+
const start = includeAll ? network : network + 1
|
|
379
|
+
const end = includeAll ? broadcast : broadcast - 1
|
|
380
|
+
const hosts: string[] = []
|
|
381
|
+
|
|
382
|
+
if (end < start) {
|
|
383
|
+
return []
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (let current = start; current <= end; current += 1) {
|
|
387
|
+
hosts.push(this.intToIp(current >>> 0))
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return hosts
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Reads and parses the system ARP cache.
|
|
395
|
+
*/
|
|
396
|
+
async getArpTable(): Promise<ArpEntry[]> {
|
|
397
|
+
const output = await this.proc.execAndCapture(`${this.resolveBin('arp')} -a`)
|
|
398
|
+
if (output.exitCode !== 0) {
|
|
399
|
+
return []
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return this.parseArpOutput(output.stdout)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Performs a lightweight TCP reachability probe.
|
|
407
|
+
*/
|
|
408
|
+
async isHostReachable(host: string, options: ReachableHostOptions = {}): Promise<boolean> {
|
|
409
|
+
const timeout = options.timeout ?? this.options.timeout ?? 1000
|
|
410
|
+
const ports = options.ports?.length ? options.ports : [80, 443]
|
|
411
|
+
|
|
412
|
+
for (const port of ports) {
|
|
413
|
+
const probe = await this.probeTcpPort(host, port, timeout, false)
|
|
414
|
+
if (probe.status === 'open') {
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return false
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Discovers hosts in a CIDR range by combining ARP cache and TCP probes.
|
|
424
|
+
*/
|
|
425
|
+
async discoverHosts(cidr: string, options: DiscoverHostsOptions = {}): Promise<DiscoverHost[]> {
|
|
426
|
+
const timeout = options.timeout ?? this.options.timeout ?? 1000
|
|
427
|
+
const concurrency = Math.max(1, options.concurrency ?? this.options.concurrency ?? 200)
|
|
428
|
+
const probePorts = options.ports?.length ? options.ports : [80, 443]
|
|
429
|
+
const allIps = this.expandCidr(cidr)
|
|
430
|
+
const arp = await this.getArpTable()
|
|
431
|
+
const arpByIp = new Map<string, ArpEntry>(arp.map(entry => [entry.ip, entry]))
|
|
432
|
+
|
|
433
|
+
this.emit('scan:start', { target: cidr, type: 'discoverHosts' })
|
|
434
|
+
const startTime = Date.now()
|
|
435
|
+
|
|
436
|
+
const discovered = await this.mapWithConcurrency(allIps, concurrency, async (ip): Promise<DiscoverHost | null> => {
|
|
437
|
+
const arpHit = arpByIp.get(ip)
|
|
438
|
+
if (arpHit) {
|
|
439
|
+
const host: DiscoverHost = {
|
|
440
|
+
ip,
|
|
441
|
+
mac: arpHit.mac,
|
|
442
|
+
reachable: true,
|
|
443
|
+
method: 'arp',
|
|
444
|
+
}
|
|
445
|
+
this.emit('host:discovered', host)
|
|
446
|
+
return host
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const reachable = await this.isHostReachable(ip, { timeout, ports: probePorts })
|
|
450
|
+
if (!reachable) {
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const host: DiscoverHost = {
|
|
455
|
+
ip,
|
|
456
|
+
reachable: true,
|
|
457
|
+
method: 'tcp',
|
|
458
|
+
}
|
|
459
|
+
this.emit('host:discovered', host)
|
|
460
|
+
return host
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
const hosts = discovered.filter((value): value is DiscoverHost => value !== null)
|
|
464
|
+
|
|
465
|
+
this.setState({
|
|
466
|
+
lastScan: {
|
|
467
|
+
timestamp: Date.now(),
|
|
468
|
+
target: cidr,
|
|
469
|
+
type: 'discoverHosts',
|
|
470
|
+
networks: [
|
|
471
|
+
{
|
|
472
|
+
cidr,
|
|
473
|
+
hosts: hosts.map(host => ({
|
|
474
|
+
ip: host.ip,
|
|
475
|
+
mac: host.mac,
|
|
476
|
+
openPorts: [],
|
|
477
|
+
})),
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
this.emit('scan:complete', {
|
|
484
|
+
target: cidr,
|
|
485
|
+
type: 'discoverHosts',
|
|
486
|
+
duration: Date.now() - startTime,
|
|
487
|
+
hostsFound: hosts.length,
|
|
488
|
+
portsFound: 0,
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
return hosts
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* TCP connect scan for a host. By default only returns open ports.
|
|
496
|
+
*/
|
|
497
|
+
async scanPorts(host: string, options: ScanPortsOptions = {}): Promise<PortScanResult[]> {
|
|
498
|
+
const timeout = options.timeout ?? this.options.timeout ?? 2000
|
|
499
|
+
const concurrency = Math.max(1, options.concurrency ?? this.options.concurrency ?? 200)
|
|
500
|
+
const includeBanner = !!options.banner
|
|
501
|
+
const includeClosed = !!options.includeClosed
|
|
502
|
+
const ports = this.parsePortsOption(options.ports)
|
|
503
|
+
|
|
504
|
+
this.emit('scan:start', { target: host, type: 'scanPorts' })
|
|
505
|
+
const startTime = Date.now()
|
|
506
|
+
|
|
507
|
+
const scanned = await this.mapWithConcurrency(ports, concurrency, async (port): Promise<PortScanResult> => {
|
|
508
|
+
const probe = await this.probeTcpPort(host, port, timeout, includeBanner)
|
|
509
|
+
const result: PortScanResult = {
|
|
510
|
+
port,
|
|
511
|
+
status: probe.status,
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (probe.status === 'open') {
|
|
515
|
+
result.service = PORT_SERVICE_MAP[port] || 'unknown'
|
|
516
|
+
if (probe.banner) {
|
|
517
|
+
result.banner = probe.banner
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
this.emit('port:open', {
|
|
521
|
+
host,
|
|
522
|
+
port,
|
|
523
|
+
service: result.service,
|
|
524
|
+
banner: result.banner,
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return result
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
const results = includeClosed ? scanned : scanned.filter(result => result.status === 'open')
|
|
532
|
+
|
|
533
|
+
this.emit('scan:complete', {
|
|
534
|
+
target: host,
|
|
535
|
+
type: 'scanPorts',
|
|
536
|
+
duration: Date.now() - startTime,
|
|
537
|
+
hostsFound: 1,
|
|
538
|
+
portsFound: results.filter(result => result.status === 'open').length,
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
return results
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Convenience method: discover and port-scan hosts across all local networks.
|
|
546
|
+
*/
|
|
547
|
+
async scanLocalNetworks(options: ScanLocalNetworksOptions = {}): Promise<LocalNetworkScanHost[]> {
|
|
548
|
+
const networks = this.getLocalNetworks()
|
|
549
|
+
const timeout = options.timeout ?? this.options.timeout ?? 1500
|
|
550
|
+
const concurrency = Math.max(1, options.concurrency ?? this.options.concurrency ?? 200)
|
|
551
|
+
const hostConcurrency = Math.max(1, options.hostConcurrency ?? 20)
|
|
552
|
+
const targetLabel = networks.map(network => network.cidr).join(', ')
|
|
553
|
+
const startTime = Date.now()
|
|
554
|
+
|
|
555
|
+
this.emit('scan:start', { target: targetLabel, type: 'scanLocalNetworks' })
|
|
556
|
+
|
|
557
|
+
const snapshots: NetworkSnapshot[] = []
|
|
558
|
+
const mergedHosts = new Map<string, LocalNetworkScanHost>()
|
|
559
|
+
|
|
560
|
+
for (const network of networks) {
|
|
561
|
+
const discovered = await this.discoverHosts(network.cidr, { timeout, concurrency })
|
|
562
|
+
const hosts = await this.mapWithConcurrency(discovered, hostConcurrency, async (host): Promise<LocalNetworkScanHost> => {
|
|
563
|
+
const scan = await this.scanPorts(host.ip, {
|
|
564
|
+
ports: options.ports,
|
|
565
|
+
timeout,
|
|
566
|
+
concurrency,
|
|
567
|
+
banner: options.banner,
|
|
568
|
+
includeClosed: false,
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
ip: host.ip,
|
|
573
|
+
mac: host.mac,
|
|
574
|
+
openPorts: scan.filter(port => port.status === 'open'),
|
|
575
|
+
}
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
snapshots.push({
|
|
579
|
+
cidr: network.cidr,
|
|
580
|
+
hosts,
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
for (const host of hosts) {
|
|
584
|
+
const existing = mergedHosts.get(host.ip)
|
|
585
|
+
if (!existing) {
|
|
586
|
+
mergedHosts.set(host.ip, host)
|
|
587
|
+
continue
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const portsByNumber = new Map<number, PortScanResult>()
|
|
591
|
+
for (const port of existing.openPorts) {
|
|
592
|
+
portsByNumber.set(port.port, port)
|
|
593
|
+
}
|
|
594
|
+
for (const port of host.openPorts) {
|
|
595
|
+
portsByNumber.set(port.port, port)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
mergedHosts.set(host.ip, {
|
|
599
|
+
ip: host.ip,
|
|
600
|
+
mac: host.mac || existing.mac,
|
|
601
|
+
hostname: host.hostname || existing.hostname,
|
|
602
|
+
openPorts: Array.from(portsByNumber.values()).sort((a, b) => a.port - b.port),
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const results = Array.from(mergedHosts.values()).sort((a, b) => a.ip.localeCompare(b.ip))
|
|
608
|
+
const portsFound = results.reduce((sum, host) => sum + host.openPorts.length, 0)
|
|
609
|
+
|
|
610
|
+
this.setState({
|
|
611
|
+
lastScan: {
|
|
612
|
+
timestamp: Date.now(),
|
|
613
|
+
target: targetLabel,
|
|
614
|
+
type: 'scanLocalNetworks',
|
|
615
|
+
networks: snapshots,
|
|
616
|
+
},
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
this.emit('scan:complete', {
|
|
620
|
+
target: targetLabel,
|
|
621
|
+
type: 'scanLocalNetworks',
|
|
622
|
+
duration: Date.now() - startTime,
|
|
623
|
+
hostsFound: results.length,
|
|
624
|
+
portsFound,
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
return results
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Optional nmap wrapper for users that already have nmap installed.
|
|
632
|
+
*/
|
|
633
|
+
get nmap(): { isAvailable: () => Promise<boolean>; scan: (target: string, args?: string[]) => Promise<{ hosts: NmapHost[]; raw: string }>; quickScan: (cidr: string) => Promise<{ hosts: NmapHost[]; raw: string }>; fullScan: (target: string) => Promise<{ hosts: NmapHost[]; raw: string }> } {
|
|
634
|
+
return {
|
|
635
|
+
isAvailable: async () => this.isNmapAvailable(),
|
|
636
|
+
scan: async (target: string, args: string[] = []) => this.runNmapScan(target, args),
|
|
637
|
+
quickScan: async (cidr: string) => this.runNmapScan(cidr, ['-sn']),
|
|
638
|
+
fullScan: async (target: string) => this.runNmapScan(target, ['-sV', '-O']),
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private async isNmapAvailable(): Promise<boolean> {
|
|
643
|
+
const result = await this.proc.spawnAndCapture(this.resolveBin('nmap'), ['--version'])
|
|
644
|
+
return result.exitCode === 0
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
private async runNmapScan(target: string, args: string[] = []) {
|
|
648
|
+
const available = await this.isNmapAvailable()
|
|
649
|
+
if (!available) {
|
|
650
|
+
throw new Error('nmap binary not found in PATH')
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
this.emit('scan:start', { target, type: 'nmap' })
|
|
654
|
+
const startTime = Date.now()
|
|
655
|
+
|
|
656
|
+
const cmdArgs = [...args, '-oG', '-', target]
|
|
657
|
+
const result = await this.proc.spawnAndCapture(this.resolveBin('nmap'), cmdArgs)
|
|
658
|
+
|
|
659
|
+
if (result.exitCode !== 0) {
|
|
660
|
+
throw new Error(result.stderr || 'nmap scan failed')
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const hosts = this.parseNmapGrepable(result.stdout)
|
|
664
|
+
const portsFound = hosts.reduce((sum, host) => sum + host.ports.filter(port => port.state === 'open').length, 0)
|
|
665
|
+
|
|
666
|
+
this.emit('scan:complete', {
|
|
667
|
+
target,
|
|
668
|
+
type: 'nmap',
|
|
669
|
+
duration: Date.now() - startTime,
|
|
670
|
+
hostsFound: hosts.length,
|
|
671
|
+
portsFound,
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
hosts,
|
|
676
|
+
raw: result.stdout,
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private parseNmapGrepable(output: string): NmapHost[] {
|
|
681
|
+
const hostMap = new Map<string, NmapHost>()
|
|
682
|
+
const lines = output.split(/\r?\n/).map(line => line.trim()).filter(Boolean)
|
|
683
|
+
|
|
684
|
+
for (const line of lines) {
|
|
685
|
+
if (!line.startsWith('Host: ')) {
|
|
686
|
+
continue
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const hostMatch = line.match(/^Host:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+\(([^)]*)\)/)
|
|
690
|
+
if (!hostMatch) {
|
|
691
|
+
continue
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const ip = hostMatch[1]
|
|
695
|
+
if (!ip) {
|
|
696
|
+
continue
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const hostname = hostMatch[2] || undefined
|
|
700
|
+
const current: NmapHost = hostMap.get(ip) || { ip, hostname, ports: [] }
|
|
701
|
+
|
|
702
|
+
const statusMatch = line.match(/Status:\s+([A-Za-z]+)/)
|
|
703
|
+
if (statusMatch?.[1]) {
|
|
704
|
+
current.status = statusMatch[1]
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const macMatch = line.match(/MAC Address:\s+([0-9A-Fa-f:]+)/)
|
|
708
|
+
if (macMatch?.[1]) {
|
|
709
|
+
current.mac = macMatch[1].toLowerCase()
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const portsMatch = line.match(/Ports:\s+(.+)$/)
|
|
713
|
+
if (portsMatch?.[1]) {
|
|
714
|
+
const parts = portsMatch[1].split(',').map(portSpec => portSpec.trim()).filter(Boolean)
|
|
715
|
+
const ports: NmapPort[] = []
|
|
716
|
+
|
|
717
|
+
for (const part of parts) {
|
|
718
|
+
const portBits = part.split('/')
|
|
719
|
+
const parsedPort = Number(portBits[0])
|
|
720
|
+
if (!Number.isInteger(parsedPort) || parsedPort <= 0) {
|
|
721
|
+
continue
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
ports.push({
|
|
725
|
+
port: parsedPort,
|
|
726
|
+
state: portBits[1] ?? 'unknown',
|
|
727
|
+
protocol: portBits[2] ?? 'tcp',
|
|
728
|
+
service: portBits[4] || undefined,
|
|
729
|
+
})
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
current.ports = ports
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
hostMap.set(ip, current)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return Array.from(hostMap.values())
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
private parseArpOutput(output: string): ArpEntry[] {
|
|
742
|
+
const entries = new Map<string, ArpEntry>()
|
|
743
|
+
const lines = output.split(/\r?\n/).map(line => line.trim()).filter(Boolean)
|
|
744
|
+
let currentInterface: string | undefined
|
|
745
|
+
|
|
746
|
+
for (const line of lines) {
|
|
747
|
+
const ifaceMatch = line.match(/^Interface:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/i)
|
|
748
|
+
if (ifaceMatch?.[1]) {
|
|
749
|
+
currentInterface = ifaceMatch[1]
|
|
750
|
+
continue
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const unixMatch = line.match(/^\?\s+\(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)\s+at\s+([0-9a-fA-F:-]+|\(incomplete\))\s+on\s+([a-zA-Z0-9_.:-]+)/)
|
|
754
|
+
if (unixMatch?.[1] && unixMatch?.[2] && unixMatch?.[3]) {
|
|
755
|
+
const ip = unixMatch[1]
|
|
756
|
+
const rawMac = unixMatch[2]
|
|
757
|
+
const iface = unixMatch[3]
|
|
758
|
+
const mac = rawMac === '(incomplete)' ? undefined : this.normalizeMac(rawMac)
|
|
759
|
+
|
|
760
|
+
entries.set(ip, { ip, mac, interface: iface })
|
|
761
|
+
continue
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const winMatch = line.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+([0-9a-fA-F-]{17})\s+\w+/)
|
|
765
|
+
if (winMatch?.[1] && winMatch?.[2]) {
|
|
766
|
+
const ip = winMatch[1]
|
|
767
|
+
entries.set(ip, {
|
|
768
|
+
ip,
|
|
769
|
+
mac: this.normalizeMac(winMatch[2]),
|
|
770
|
+
interface: currentInterface,
|
|
771
|
+
})
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return Array.from(entries.values())
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
private normalizeMac(mac: string): string {
|
|
779
|
+
return mac.replaceAll('-', ':').toLowerCase()
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
private parsePortsOption(ports?: string | number[]): number[] {
|
|
783
|
+
if (!ports) {
|
|
784
|
+
return [...DEFAULT_PORTS]
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (Array.isArray(ports)) {
|
|
788
|
+
const unique = Array.from(new Set(ports.map(port => Number(port)).filter(port => Number.isInteger(port) && port >= 1 && port <= 65535)))
|
|
789
|
+
return unique.sort((a, b) => a - b)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const parsed = new Set<number>()
|
|
793
|
+
for (const segment of ports.split(',').map(part => part.trim()).filter(Boolean)) {
|
|
794
|
+
if (segment.includes('-')) {
|
|
795
|
+
const [startRaw, endRaw] = segment.split('-')
|
|
796
|
+
const start = Number(startRaw)
|
|
797
|
+
const end = Number(endRaw)
|
|
798
|
+
|
|
799
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end > 65535 || start > end) {
|
|
800
|
+
continue
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
for (let current = start; current <= end; current += 1) {
|
|
804
|
+
parsed.add(current)
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
const value = Number(segment)
|
|
808
|
+
if (Number.isInteger(value) && value >= 1 && value <= 65535) {
|
|
809
|
+
parsed.add(value)
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const values = Array.from(parsed.values()).sort((a, b) => a - b)
|
|
815
|
+
return values.length ? values : [...DEFAULT_PORTS]
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private async probeTcpPort(host: string, port: number, timeout: number, captureBanner: boolean): Promise<{ status: 'open' | 'closed' | 'filtered'; banner?: string }> {
|
|
819
|
+
return new Promise((resolve) => {
|
|
820
|
+
const socket = new net.Socket()
|
|
821
|
+
let settled = false
|
|
822
|
+
let connected = false
|
|
823
|
+
let banner = ''
|
|
824
|
+
|
|
825
|
+
const done = (status: 'open' | 'closed' | 'filtered') => {
|
|
826
|
+
if (settled) {
|
|
827
|
+
return
|
|
828
|
+
}
|
|
829
|
+
settled = true
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
socket.destroy()
|
|
833
|
+
} catch {
|
|
834
|
+
// no-op
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
resolve({
|
|
838
|
+
status,
|
|
839
|
+
banner: banner.length > 0 ? banner : undefined,
|
|
840
|
+
})
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
socket.setTimeout(timeout)
|
|
844
|
+
|
|
845
|
+
socket.on('connect', () => {
|
|
846
|
+
connected = true
|
|
847
|
+
|
|
848
|
+
if (!captureBanner) {
|
|
849
|
+
done('open')
|
|
850
|
+
return
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if ([80, 3000, 4000, 5000, 8080, 8443, 9000].includes(port)) {
|
|
854
|
+
socket.write('HEAD / HTTP/1.0\r\n\r\n')
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
setTimeout(() => done('open'), 300)
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
socket.on('data', (chunk: Buffer) => {
|
|
861
|
+
if (!captureBanner) {
|
|
862
|
+
return
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
banner = `${banner}${chunk.toString('utf8')}`.slice(0, 256).trim()
|
|
866
|
+
done('open')
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
socket.on('timeout', () => {
|
|
870
|
+
done(connected ? 'open' : 'filtered')
|
|
871
|
+
})
|
|
872
|
+
|
|
873
|
+
socket.on('error', (error: any) => {
|
|
874
|
+
const code = String(error?.code || '')
|
|
875
|
+
if (code === 'ECONNREFUSED' || code === 'ECONNRESET' || code === 'EHOSTUNREACH' || code === 'ENETUNREACH') {
|
|
876
|
+
done('closed')
|
|
877
|
+
return
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
done('filtered')
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
socket.on('close', () => {
|
|
884
|
+
if (!settled) {
|
|
885
|
+
done(connected ? 'open' : 'filtered')
|
|
886
|
+
}
|
|
887
|
+
})
|
|
888
|
+
|
|
889
|
+
socket.connect(port, host)
|
|
890
|
+
})
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private computeCidr(address: string, netmask: string): string {
|
|
894
|
+
const addressInt = this.ipToInt(address)
|
|
895
|
+
const netmaskInt = this.ipToInt(netmask)
|
|
896
|
+
const prefixLength = this.countMaskBits(netmaskInt)
|
|
897
|
+
const network = addressInt & netmaskInt
|
|
898
|
+
|
|
899
|
+
return `${this.intToIp(network >>> 0)}/${prefixLength}`
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private ipToInt(ip: string): number {
|
|
903
|
+
const parts = ip.split('.').map(part => Number(part))
|
|
904
|
+
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
905
|
+
throw new Error(`Invalid IPv4 address: ${ip}`)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const a = parts[0]!
|
|
909
|
+
const b = parts[1]!
|
|
910
|
+
const c = parts[2]!
|
|
911
|
+
const d = parts[3]!
|
|
912
|
+
return (((a << 24) >>> 0) + (b << 16) + (c << 8) + d) >>> 0
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
private intToIp(value: number): string {
|
|
916
|
+
const normalized = value >>> 0
|
|
917
|
+
return [
|
|
918
|
+
(normalized >>> 24) & 255,
|
|
919
|
+
(normalized >>> 16) & 255,
|
|
920
|
+
(normalized >>> 8) & 255,
|
|
921
|
+
normalized & 255,
|
|
922
|
+
].join('.')
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private countMaskBits(mask: number): number {
|
|
926
|
+
let bits = 0
|
|
927
|
+
let current = mask >>> 0
|
|
928
|
+
while (current > 0) {
|
|
929
|
+
bits += current & 1
|
|
930
|
+
current >>>= 1
|
|
931
|
+
}
|
|
932
|
+
return bits
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
private async mapWithConcurrency<T, R>(items: T[], concurrency: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]> {
|
|
936
|
+
if (items.length === 0) {
|
|
937
|
+
return []
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const safeConcurrency = Math.min(Math.max(1, concurrency), items.length)
|
|
941
|
+
const results = new Array<R>(items.length)
|
|
942
|
+
let nextIndex = 0
|
|
943
|
+
|
|
944
|
+
const runWorker = async () => {
|
|
945
|
+
while (true) {
|
|
946
|
+
const index = nextIndex
|
|
947
|
+
nextIndex += 1
|
|
948
|
+
if (index >= items.length) {
|
|
949
|
+
return
|
|
950
|
+
}
|
|
951
|
+
const item = items[index] as T
|
|
952
|
+
results[index] = await worker(item, index)
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
await Promise.all(Array.from({ length: safeConcurrency }, () => runWorker()))
|
|
957
|
+
return results
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
export default Networking
|