@soederpop/luca 0.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/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -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/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -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 +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -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/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { Feature, features } from '../feature.js'
|
|
3
|
+
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
4
|
+
|
|
5
|
+
/** Supported DNS record types. */
|
|
6
|
+
export type DnsRecordType = 'A' | 'AAAA' | 'CNAME' | 'MX' | 'NS' | 'TXT' | 'SOA' | 'PTR' | 'SRV' | 'CAA'
|
|
7
|
+
|
|
8
|
+
export const DnsRecordSchema = z.object({
|
|
9
|
+
name: z.string().describe('The queried domain name'),
|
|
10
|
+
ttl: z.number().describe('Time to live in seconds'),
|
|
11
|
+
class: z.string().describe('Record class (usually IN)'),
|
|
12
|
+
type: z.string().describe('DNS record type'),
|
|
13
|
+
value: z.string().describe('Record value/data'),
|
|
14
|
+
})
|
|
15
|
+
export type DnsRecord = z.infer<typeof DnsRecordSchema>
|
|
16
|
+
|
|
17
|
+
export const MxRecordSchema = DnsRecordSchema.extend({
|
|
18
|
+
priority: z.number().describe('MX priority value'),
|
|
19
|
+
exchange: z.string().describe('Mail exchange hostname'),
|
|
20
|
+
})
|
|
21
|
+
export type MxRecord = z.infer<typeof MxRecordSchema>
|
|
22
|
+
|
|
23
|
+
export const SoaRecordSchema = DnsRecordSchema.extend({
|
|
24
|
+
mname: z.string().describe('Primary nameserver'),
|
|
25
|
+
rname: z.string().describe('Responsible party email (dot notation)'),
|
|
26
|
+
serial: z.number().describe('Zone serial number'),
|
|
27
|
+
refresh: z.number().describe('Refresh interval in seconds'),
|
|
28
|
+
retry: z.number().describe('Retry interval in seconds'),
|
|
29
|
+
expire: z.number().describe('Expire time in seconds'),
|
|
30
|
+
minimum: z.number().describe('Minimum TTL in seconds'),
|
|
31
|
+
})
|
|
32
|
+
export type SoaRecord = z.infer<typeof SoaRecordSchema>
|
|
33
|
+
|
|
34
|
+
export const SrvRecordSchema = DnsRecordSchema.extend({
|
|
35
|
+
priority: z.number().describe('SRV priority'),
|
|
36
|
+
weight: z.number().describe('SRV weight'),
|
|
37
|
+
port: z.number().describe('SRV port'),
|
|
38
|
+
target: z.string().describe('SRV target hostname'),
|
|
39
|
+
})
|
|
40
|
+
export type SrvRecord = z.infer<typeof SrvRecordSchema>
|
|
41
|
+
|
|
42
|
+
export const CaaRecordSchema = DnsRecordSchema.extend({
|
|
43
|
+
flags: z.number().describe('CAA flags'),
|
|
44
|
+
tag: z.string().describe('CAA tag (issue, issuewild, iodef)'),
|
|
45
|
+
issuer: z.string().describe('CAA value/issuer'),
|
|
46
|
+
})
|
|
47
|
+
export type CaaRecord = z.infer<typeof CaaRecordSchema>
|
|
48
|
+
|
|
49
|
+
export const DnsQueryResultSchema = z.object({
|
|
50
|
+
domain: z.string().describe('The queried domain'),
|
|
51
|
+
type: z.string().describe('The record type queried'),
|
|
52
|
+
server: z.string().optional().describe('DNS server used for the query'),
|
|
53
|
+
records: z.array(DnsRecordSchema).describe('Returned DNS records'),
|
|
54
|
+
queryTime: z.number().optional().describe('Query time in milliseconds'),
|
|
55
|
+
})
|
|
56
|
+
export type DnsQueryResult = z.infer<typeof DnsQueryResultSchema>
|
|
57
|
+
|
|
58
|
+
export const DnsOverviewSchema = z.object({
|
|
59
|
+
domain: z.string().describe('The queried domain'),
|
|
60
|
+
a: z.array(DnsRecordSchema).describe('A records'),
|
|
61
|
+
aaaa: z.array(DnsRecordSchema).describe('AAAA records'),
|
|
62
|
+
cname: z.array(DnsRecordSchema).describe('CNAME records'),
|
|
63
|
+
mx: z.array(MxRecordSchema).describe('MX records'),
|
|
64
|
+
ns: z.array(DnsRecordSchema).describe('NS records'),
|
|
65
|
+
txt: z.array(DnsRecordSchema).describe('TXT records'),
|
|
66
|
+
soa: z.array(SoaRecordSchema).describe('SOA records'),
|
|
67
|
+
caa: z.array(CaaRecordSchema).describe('CAA records'),
|
|
68
|
+
})
|
|
69
|
+
export type DnsOverview = z.infer<typeof DnsOverviewSchema>
|
|
70
|
+
|
|
71
|
+
export const DnsStateSchema = FeatureStateSchema.extend({
|
|
72
|
+
lastQuery: z.object({
|
|
73
|
+
domain: z.string(),
|
|
74
|
+
type: z.string(),
|
|
75
|
+
timestamp: z.number(),
|
|
76
|
+
}).optional().describe('The most recent DNS query'),
|
|
77
|
+
})
|
|
78
|
+
export type DnsState = z.infer<typeof DnsStateSchema>
|
|
79
|
+
|
|
80
|
+
export const DnsOptionsSchema = FeatureOptionsSchema.extend({
|
|
81
|
+
server: z.string().optional().describe('Default DNS server to use for queries'),
|
|
82
|
+
timeout: z.number().optional().describe('Default timeout in seconds for dig queries'),
|
|
83
|
+
})
|
|
84
|
+
export type DnsOptions = z.infer<typeof DnsOptionsSchema>
|
|
85
|
+
|
|
86
|
+
type QueryOptions = {
|
|
87
|
+
server?: string
|
|
88
|
+
timeout?: number
|
|
89
|
+
short?: boolean
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The Dns feature provides structured DNS lookups by wrapping the `dig` CLI.
|
|
94
|
+
*
|
|
95
|
+
* All query methods parse dig output into typed JSON objects, making it easy
|
|
96
|
+
* to explore and audit a domain's DNS configuration programmatically.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const dns = container.feature('dns')
|
|
101
|
+
*
|
|
102
|
+
* // Look up A records
|
|
103
|
+
* const result = await dns.resolve('example.com', 'A')
|
|
104
|
+
* console.log(result.records)
|
|
105
|
+
*
|
|
106
|
+
* // Get a full overview of all record types
|
|
107
|
+
* const overview = await dns.overview('example.com')
|
|
108
|
+
* console.log(overview.mx) // mail servers
|
|
109
|
+
* console.log(overview.ns) // nameservers
|
|
110
|
+
* console.log(overview.txt) // TXT records (SPF, DKIM, etc.)
|
|
111
|
+
*
|
|
112
|
+
* // Reverse lookup
|
|
113
|
+
* const ptr = await dns.reverse('8.8.8.8')
|
|
114
|
+
* console.log(ptr) // ['dns.google.']
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @extends Feature
|
|
118
|
+
*/
|
|
119
|
+
export class Dns extends Feature<DnsState, DnsOptions> {
|
|
120
|
+
static override shortcut = 'features.dns' as const
|
|
121
|
+
static override description = 'DNS lookup utilities wrapping the dig CLI'
|
|
122
|
+
static override stateSchema = DnsStateSchema
|
|
123
|
+
static override optionsSchema = DnsOptionsSchema
|
|
124
|
+
|
|
125
|
+
override get initialState(): DnsState {
|
|
126
|
+
return {
|
|
127
|
+
...super.initialState,
|
|
128
|
+
enabled: false,
|
|
129
|
+
lastQuery: undefined,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get proc() {
|
|
134
|
+
return this.container.feature('proc')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Checks whether the `dig` binary is available on the system.
|
|
139
|
+
*
|
|
140
|
+
* @returns True if dig is installed and accessible
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* if (await dns.isAvailable()) {
|
|
145
|
+
* const records = await dns.a('example.com')
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
async isAvailable(): Promise<boolean> {
|
|
150
|
+
const result = await this.proc.spawnAndCapture('dig', ['-v'])
|
|
151
|
+
// dig -v prints version to stderr and exits 0
|
|
152
|
+
return result.exitCode === 0
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Resolves DNS records of a given type for a domain.
|
|
157
|
+
*
|
|
158
|
+
* This is the core query method. All convenience methods (a, aaaa, mx, etc.)
|
|
159
|
+
* delegate to this method.
|
|
160
|
+
*
|
|
161
|
+
* @param domain - The domain name to query
|
|
162
|
+
* @param type - The DNS record type to look up
|
|
163
|
+
* @param options - Optional query parameters
|
|
164
|
+
* @param options.server - DNS server to use (e.g. '8.8.8.8')
|
|
165
|
+
* @param options.timeout - Query timeout in seconds
|
|
166
|
+
* @param options.short - If true, returns only values (no TTL, class, etc.)
|
|
167
|
+
* @returns Parsed query result with typed records
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const result = await dns.resolve('example.com', 'A')
|
|
172
|
+
* for (const record of result.records) {
|
|
173
|
+
* console.log(`${record.name} -> ${record.value} (TTL: ${record.ttl}s)`)
|
|
174
|
+
* }
|
|
175
|
+
*
|
|
176
|
+
* // Query a specific DNS server
|
|
177
|
+
* const result = await dns.resolve('example.com', 'A', { server: '1.1.1.1' })
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
async resolve(domain: string, type: DnsRecordType, options: QueryOptions = {}): Promise<DnsQueryResult> {
|
|
181
|
+
const args = this.buildDigArgs(domain, type, options)
|
|
182
|
+
const result = await this.proc.spawnAndCapture('dig', args)
|
|
183
|
+
|
|
184
|
+
if (result.exitCode !== 0) {
|
|
185
|
+
throw new Error(`dig query failed: ${result.stderr || 'unknown error'}`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const records = this.parseDigAnswer(result.stdout)
|
|
189
|
+
const queryTime = this.parseQueryTime(result.stdout)
|
|
190
|
+
const server = options.server || this.options.server
|
|
191
|
+
|
|
192
|
+
this.setState({
|
|
193
|
+
lastQuery: {
|
|
194
|
+
domain,
|
|
195
|
+
type,
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
domain,
|
|
202
|
+
type,
|
|
203
|
+
server,
|
|
204
|
+
records,
|
|
205
|
+
queryTime,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Looks up A (IPv4 address) records for a domain.
|
|
211
|
+
*
|
|
212
|
+
* @param domain - The domain name to query
|
|
213
|
+
* @param options - Optional query parameters
|
|
214
|
+
* @returns Array of A records
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const records = await dns.a('google.com')
|
|
219
|
+
* // [{ name: 'google.com.', ttl: 300, class: 'IN', type: 'A', value: '142.250.x.x' }]
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
async a(domain: string, options: QueryOptions = {}): Promise<DnsRecord[]> {
|
|
223
|
+
const result = await this.resolve(domain, 'A', options)
|
|
224
|
+
return result.records
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Looks up AAAA (IPv6 address) records for a domain.
|
|
229
|
+
*
|
|
230
|
+
* @param domain - The domain name to query
|
|
231
|
+
* @param options - Optional query parameters
|
|
232
|
+
* @returns Array of AAAA records
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* const records = await dns.aaaa('google.com')
|
|
237
|
+
* // [{ name: 'google.com.', ttl: 300, class: 'IN', type: 'AAAA', value: '2607:f8b0:...' }]
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
async aaaa(domain: string, options: QueryOptions = {}): Promise<DnsRecord[]> {
|
|
241
|
+
const result = await this.resolve(domain, 'AAAA', options)
|
|
242
|
+
return result.records
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Looks up CNAME (canonical name) records for a domain.
|
|
247
|
+
*
|
|
248
|
+
* @param domain - The domain name to query
|
|
249
|
+
* @param options - Optional query parameters
|
|
250
|
+
* @returns Array of CNAME records
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```typescript
|
|
254
|
+
* const records = await dns.cname('www.github.com')
|
|
255
|
+
* // [{ name: 'www.github.com.', ttl: 3600, class: 'IN', type: 'CNAME', value: 'github.com.' }]
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
async cname(domain: string, options: QueryOptions = {}): Promise<DnsRecord[]> {
|
|
259
|
+
const result = await this.resolve(domain, 'CNAME', options)
|
|
260
|
+
return result.records
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Looks up MX (mail exchange) records for a domain.
|
|
265
|
+
*
|
|
266
|
+
* Returns enriched records with parsed priority and exchange fields.
|
|
267
|
+
*
|
|
268
|
+
* @param domain - The domain name to query
|
|
269
|
+
* @param options - Optional query parameters
|
|
270
|
+
* @returns Array of MX records with priority and exchange
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* const records = await dns.mx('google.com')
|
|
275
|
+
* // [{ name: 'google.com.', ttl: 300, type: 'MX', priority: 10, exchange: 'smtp.google.com.' }]
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
async mx(domain: string, options: QueryOptions = {}): Promise<MxRecord[]> {
|
|
279
|
+
const result = await this.resolve(domain, 'MX', options)
|
|
280
|
+
return result.records.map((record) => {
|
|
281
|
+
const parts = record.value.split(/\s+/)
|
|
282
|
+
const priority = parseInt(parts[0] || '0', 10)
|
|
283
|
+
const exchange = parts[1] || record.value
|
|
284
|
+
return { ...record, priority, exchange }
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Looks up NS (nameserver) records for a domain.
|
|
290
|
+
*
|
|
291
|
+
* @param domain - The domain name to query
|
|
292
|
+
* @param options - Optional query parameters
|
|
293
|
+
* @returns Array of NS records
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* const records = await dns.ns('google.com')
|
|
298
|
+
* // [{ name: 'google.com.', ttl: 86400, type: 'NS', value: 'ns1.google.com.' }, ...]
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
async ns(domain: string, options: QueryOptions = {}): Promise<DnsRecord[]> {
|
|
302
|
+
const result = await this.resolve(domain, 'NS', options)
|
|
303
|
+
return result.records
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Looks up TXT records for a domain.
|
|
308
|
+
*
|
|
309
|
+
* TXT records often contain SPF policies, DKIM keys, domain verification tokens,
|
|
310
|
+
* and other metadata.
|
|
311
|
+
*
|
|
312
|
+
* @param domain - The domain name to query
|
|
313
|
+
* @param options - Optional query parameters
|
|
314
|
+
* @returns Array of TXT records
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```typescript
|
|
318
|
+
* const records = await dns.txt('google.com')
|
|
319
|
+
* const spf = records.find(r => r.value.includes('v=spf1'))
|
|
320
|
+
* console.log(spf?.value) // 'v=spf1 include:_spf.google.com ~all'
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
async txt(domain: string, options: QueryOptions = {}): Promise<DnsRecord[]> {
|
|
324
|
+
const result = await this.resolve(domain, 'TXT', options)
|
|
325
|
+
return result.records.map((record) => ({
|
|
326
|
+
...record,
|
|
327
|
+
value: record.value.replace(/^"(.*)"$/, '$1'),
|
|
328
|
+
}))
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Looks up the SOA (Start of Authority) record for a domain.
|
|
333
|
+
*
|
|
334
|
+
* Returns enriched records with parsed SOA fields including primary nameserver,
|
|
335
|
+
* responsible party, serial number, and timing parameters.
|
|
336
|
+
*
|
|
337
|
+
* @param domain - The domain name to query
|
|
338
|
+
* @param options - Optional query parameters
|
|
339
|
+
* @returns Array of SOA records (typically one)
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* const records = await dns.soa('google.com')
|
|
344
|
+
* console.log(records[0].mname) // 'ns1.google.com.'
|
|
345
|
+
* console.log(records[0].serial) // 879543655
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
async soa(domain: string, options: QueryOptions = {}): Promise<SoaRecord[]> {
|
|
349
|
+
const result = await this.resolve(domain, 'SOA', options)
|
|
350
|
+
return result.records.map((record) => {
|
|
351
|
+
const parts = record.value.split(/\s+/)
|
|
352
|
+
return {
|
|
353
|
+
...record,
|
|
354
|
+
mname: parts[0] || '',
|
|
355
|
+
rname: parts[1] || '',
|
|
356
|
+
serial: parseInt(parts[2] || '0', 10),
|
|
357
|
+
refresh: parseInt(parts[3] || '0', 10),
|
|
358
|
+
retry: parseInt(parts[4] || '0', 10),
|
|
359
|
+
expire: parseInt(parts[5] || '0', 10),
|
|
360
|
+
minimum: parseInt(parts[6] || '0', 10),
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Looks up SRV (service) records for a domain.
|
|
367
|
+
*
|
|
368
|
+
* SRV records specify the location of services. The domain should include
|
|
369
|
+
* the service and protocol prefix (e.g. `_sip._tcp.example.com`).
|
|
370
|
+
*
|
|
371
|
+
* @param domain - The full SRV domain (e.g. `_sip._tcp.example.com`)
|
|
372
|
+
* @param options - Optional query parameters
|
|
373
|
+
* @returns Array of SRV records with priority, weight, port, and target
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* const records = await dns.srv('_sip._tcp.example.com')
|
|
378
|
+
* // [{ priority: 10, weight: 60, port: 5060, target: 'sip.example.com.' }]
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
async srv(domain: string, options: QueryOptions = {}): Promise<SrvRecord[]> {
|
|
382
|
+
const result = await this.resolve(domain, 'SRV', options)
|
|
383
|
+
return result.records.map((record) => {
|
|
384
|
+
const parts = record.value.split(/\s+/)
|
|
385
|
+
return {
|
|
386
|
+
...record,
|
|
387
|
+
priority: parseInt(parts[0] || '0', 10),
|
|
388
|
+
weight: parseInt(parts[1] || '0', 10),
|
|
389
|
+
port: parseInt(parts[2] || '0', 10),
|
|
390
|
+
target: parts[3] || '',
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Looks up CAA (Certificate Authority Authorization) records for a domain.
|
|
397
|
+
*
|
|
398
|
+
* CAA records specify which certificate authorities are allowed to issue
|
|
399
|
+
* certificates for a domain.
|
|
400
|
+
*
|
|
401
|
+
* @param domain - The domain name to query
|
|
402
|
+
* @param options - Optional query parameters
|
|
403
|
+
* @returns Array of CAA records with flags, tag, and issuer
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* const records = await dns.caa('google.com')
|
|
408
|
+
* // [{ flags: 0, tag: 'issue', issuer: 'pki.goog' }]
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
async caa(domain: string, options: QueryOptions = {}): Promise<CaaRecord[]> {
|
|
412
|
+
const result = await this.resolve(domain, 'CAA', options)
|
|
413
|
+
return result.records.map((record) => {
|
|
414
|
+
const parts = record.value.split(/\s+/)
|
|
415
|
+
return {
|
|
416
|
+
...record,
|
|
417
|
+
flags: parseInt(parts[0] || '0', 10),
|
|
418
|
+
tag: (parts[1] || '').replace(/"/g, ''),
|
|
419
|
+
issuer: (parts.slice(2).join(' ') || '').replace(/"/g, ''),
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Performs a reverse DNS lookup for an IP address.
|
|
426
|
+
*
|
|
427
|
+
* Converts the IP to its in-addr.arpa form and queries for PTR records.
|
|
428
|
+
*
|
|
429
|
+
* @param ip - The IPv4 address to look up
|
|
430
|
+
* @param options - Optional query parameters
|
|
431
|
+
* @returns Array of hostnames (PTR record values)
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```typescript
|
|
435
|
+
* const hostnames = await dns.reverse('8.8.8.8')
|
|
436
|
+
* // ['dns.google.']
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
async reverse(ip: string, options: QueryOptions = {}): Promise<string[]> {
|
|
440
|
+
const args = this.buildDigArgs('', 'PTR', options)
|
|
441
|
+
// Replace domain arg with -x flag
|
|
442
|
+
const idx = args.indexOf('')
|
|
443
|
+
if (idx !== -1) {
|
|
444
|
+
args.splice(idx, 1)
|
|
445
|
+
}
|
|
446
|
+
args.unshift('-x', ip)
|
|
447
|
+
|
|
448
|
+
const result = await this.proc.spawnAndCapture('dig', args)
|
|
449
|
+
|
|
450
|
+
if (result.exitCode !== 0) {
|
|
451
|
+
throw new Error(`dig reverse lookup failed: ${result.stderr || 'unknown error'}`)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const records = this.parseDigAnswer(result.stdout)
|
|
455
|
+
return records.map((r) => r.value)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Retrieves a comprehensive DNS overview for a domain.
|
|
460
|
+
*
|
|
461
|
+
* Queries all common record types (A, AAAA, CNAME, MX, NS, TXT, SOA, CAA)
|
|
462
|
+
* in parallel and returns a consolidated view. This is the go-to method for
|
|
463
|
+
* exploring a domain's full DNS configuration.
|
|
464
|
+
*
|
|
465
|
+
* @param domain - The domain name to query
|
|
466
|
+
* @param options - Optional query parameters applied to all queries
|
|
467
|
+
* @returns Complete DNS overview with all record types
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* const overview = await dns.overview('example.com')
|
|
472
|
+
* console.log('IPs:', overview.a.map(r => r.value))
|
|
473
|
+
* console.log('Mail:', overview.mx.map(r => r.exchange))
|
|
474
|
+
* console.log('Nameservers:', overview.ns.map(r => r.value))
|
|
475
|
+
* console.log('TXT:', overview.txt.map(r => r.value))
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
async overview(domain: string, options: QueryOptions = {}): Promise<DnsOverview> {
|
|
479
|
+
const [a, aaaa, cname, mx, ns, txt, soa, caa] = await Promise.all([
|
|
480
|
+
this.a(domain, options),
|
|
481
|
+
this.aaaa(domain, options),
|
|
482
|
+
this.cname(domain, options),
|
|
483
|
+
this.mx(domain, options),
|
|
484
|
+
this.ns(domain, options),
|
|
485
|
+
this.txt(domain, options),
|
|
486
|
+
this.soa(domain, options),
|
|
487
|
+
this.caa(domain, options),
|
|
488
|
+
])
|
|
489
|
+
|
|
490
|
+
return { domain, a, aaaa, cname, mx, ns, txt, soa, caa }
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Compares DNS resolution between two nameservers for a given record type.
|
|
495
|
+
*
|
|
496
|
+
* Useful for verifying DNS propagation or checking for inconsistencies
|
|
497
|
+
* between authoritative and recursive resolvers.
|
|
498
|
+
*
|
|
499
|
+
* @param domain - The domain name to query
|
|
500
|
+
* @param type - The DNS record type to compare
|
|
501
|
+
* @param serverA - First DNS server (e.g. '8.8.8.8')
|
|
502
|
+
* @param serverB - Second DNS server (e.g. '1.1.1.1')
|
|
503
|
+
* @returns Object with results from both servers and whether they match
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```typescript
|
|
507
|
+
* const diff = await dns.compare('example.com', 'A', '8.8.8.8', '1.1.1.1')
|
|
508
|
+
* console.log(diff.match) // true if both return the same values
|
|
509
|
+
* console.log(diff.serverA) // records from 8.8.8.8
|
|
510
|
+
* console.log(diff.serverB) // records from 1.1.1.1
|
|
511
|
+
* ```
|
|
512
|
+
*/
|
|
513
|
+
async compare(
|
|
514
|
+
domain: string,
|
|
515
|
+
type: DnsRecordType,
|
|
516
|
+
serverA: string,
|
|
517
|
+
serverB: string,
|
|
518
|
+
): Promise<{ serverA: DnsQueryResult; serverB: DnsQueryResult; match: boolean }> {
|
|
519
|
+
const [resultA, resultB] = await Promise.all([
|
|
520
|
+
this.resolve(domain, type, { server: serverA }),
|
|
521
|
+
this.resolve(domain, type, { server: serverB }),
|
|
522
|
+
])
|
|
523
|
+
|
|
524
|
+
const valuesA = resultA.records.map((r) => r.value).sort()
|
|
525
|
+
const valuesB = resultB.records.map((r) => r.value).sort()
|
|
526
|
+
const match = JSON.stringify(valuesA) === JSON.stringify(valuesB)
|
|
527
|
+
|
|
528
|
+
return { serverA: resultA, serverB: resultB, match }
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Queries a domain's authoritative nameservers directly.
|
|
533
|
+
*
|
|
534
|
+
* First resolves the NS records, then queries each nameserver for the
|
|
535
|
+
* specified record type. Useful for bypassing caches and checking what
|
|
536
|
+
* the authoritative servers actually report.
|
|
537
|
+
*
|
|
538
|
+
* @param domain - The domain name to query
|
|
539
|
+
* @param type - The DNS record type to look up
|
|
540
|
+
* @returns Array of results, one per authoritative nameserver
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```typescript
|
|
544
|
+
* const results = await dns.queryAuthoritative('example.com', 'A')
|
|
545
|
+
* for (const r of results) {
|
|
546
|
+
* console.log(`${r.server}: ${r.records.map(rec => rec.value).join(', ')}`)
|
|
547
|
+
* }
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
async queryAuthoritative(domain: string, type: DnsRecordType): Promise<DnsQueryResult[]> {
|
|
551
|
+
const nsRecords = await this.ns(domain)
|
|
552
|
+
|
|
553
|
+
if (nsRecords.length === 0) {
|
|
554
|
+
return []
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const results = await Promise.all(
|
|
558
|
+
nsRecords.map((ns) => this.resolve(domain, type, { server: ns.value.replace(/\.$/, '') })),
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
return results
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Checks whether a domain has a specific TXT record containing the given text.
|
|
566
|
+
*
|
|
567
|
+
* Useful for verifying domain ownership tokens, SPF records, DKIM entries, etc.
|
|
568
|
+
*
|
|
569
|
+
* @param domain - The domain name to query
|
|
570
|
+
* @param search - The text to search for in TXT record values
|
|
571
|
+
* @returns True if any TXT record contains the search string
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```typescript
|
|
575
|
+
* // Check for SPF record
|
|
576
|
+
* const hasSPF = await dns.hasTxtRecord('google.com', 'v=spf1')
|
|
577
|
+
*
|
|
578
|
+
* // Check for domain verification
|
|
579
|
+
* const verified = await dns.hasTxtRecord('example.com', 'google-site-verification=')
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
async hasTxtRecord(domain: string, search: string): Promise<boolean> {
|
|
583
|
+
const records = await this.txt(domain)
|
|
584
|
+
return records.some((r) => r.value.includes(search))
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Builds the dig CLI arguments for a query. */
|
|
588
|
+
private buildDigArgs(domain: string, type: DnsRecordType | 'PTR', options: QueryOptions = {}): string[] {
|
|
589
|
+
const args: string[] = []
|
|
590
|
+
const server = options.server || this.options.server
|
|
591
|
+
|
|
592
|
+
if (server) {
|
|
593
|
+
args.push(`@${server}`)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (domain) {
|
|
597
|
+
args.push(domain)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
args.push(type)
|
|
601
|
+
|
|
602
|
+
if (options.short) {
|
|
603
|
+
args.push('+short')
|
|
604
|
+
} else {
|
|
605
|
+
args.push('+noall', '+answer', '+stats')
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const timeout = options.timeout ?? this.options.timeout
|
|
609
|
+
if (timeout) {
|
|
610
|
+
args.push(`+time=${timeout}`)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return args
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** Parses dig's answer section into structured records. */
|
|
617
|
+
private parseDigAnswer(output: string): DnsRecord[] {
|
|
618
|
+
const records: DnsRecord[] = []
|
|
619
|
+
const lines = output.split('\n')
|
|
620
|
+
|
|
621
|
+
for (const line of lines) {
|
|
622
|
+
const trimmed = line.trim()
|
|
623
|
+
// Skip comments, empty lines, and non-record lines
|
|
624
|
+
if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith(';;')) {
|
|
625
|
+
continue
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Record format: name ttl class type value...
|
|
629
|
+
const match = trimmed.match(/^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.+)$/)
|
|
630
|
+
if (!match) {
|
|
631
|
+
continue
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const [, name, ttlStr, cls, recordType, value] = match
|
|
635
|
+
|
|
636
|
+
records.push({
|
|
637
|
+
name: name!,
|
|
638
|
+
ttl: parseInt(ttlStr!, 10),
|
|
639
|
+
class: cls!,
|
|
640
|
+
type: recordType!,
|
|
641
|
+
value: value!.trim(),
|
|
642
|
+
})
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return records
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** Extracts query time from dig's stats section. */
|
|
649
|
+
private parseQueryTime(output: string): number | undefined {
|
|
650
|
+
const match = output.match(/;; Query time:\s+(\d+)\s+msec/)
|
|
651
|
+
return match ? parseInt(match[1]!, 10) : undefined
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export default features.register('dns', Dns)
|