@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.
Files changed (358) hide show
  1. package/CLAUDE.md +71 -0
  2. package/README.md +78 -0
  3. package/bun.lock +2928 -0
  4. package/bunfig.toml +3 -0
  5. package/commands/audit-docs.ts +740 -0
  6. package/commands/build-scaffolds.ts +154 -0
  7. package/commands/generate-api-docs.ts +114 -0
  8. package/commands/update-introspection.ts +67 -0
  9. package/docs/CLI.md +335 -0
  10. package/docs/README.md +88 -0
  11. package/docs/TABLE-OF-CONTENTS.md +157 -0
  12. package/docs/apis/clients/elevenlabs.md +84 -0
  13. package/docs/apis/clients/graph.md +56 -0
  14. package/docs/apis/clients/openai.md +69 -0
  15. package/docs/apis/clients/rest.md +41 -0
  16. package/docs/apis/clients/websocket.md +107 -0
  17. package/docs/apis/features/agi/assistant.md +471 -0
  18. package/docs/apis/features/agi/assistants-manager.md +154 -0
  19. package/docs/apis/features/agi/claude-code.md +602 -0
  20. package/docs/apis/features/agi/conversation-history.md +352 -0
  21. package/docs/apis/features/agi/conversation.md +333 -0
  22. package/docs/apis/features/agi/docs-reader.md +121 -0
  23. package/docs/apis/features/agi/openai-codex.md +318 -0
  24. package/docs/apis/features/agi/openapi.md +138 -0
  25. package/docs/apis/features/agi/semantic-search.md +387 -0
  26. package/docs/apis/features/agi/skills-library.md +216 -0
  27. package/docs/apis/features/node/container-link.md +133 -0
  28. package/docs/apis/features/node/content-db.md +313 -0
  29. package/docs/apis/features/node/disk-cache.md +379 -0
  30. package/docs/apis/features/node/dns.md +651 -0
  31. package/docs/apis/features/node/docker.md +705 -0
  32. package/docs/apis/features/node/downloader.md +81 -0
  33. package/docs/apis/features/node/esbuild.md +59 -0
  34. package/docs/apis/features/node/file-manager.md +182 -0
  35. package/docs/apis/features/node/fs.md +581 -0
  36. package/docs/apis/features/node/git.md +330 -0
  37. package/docs/apis/features/node/google-auth.md +174 -0
  38. package/docs/apis/features/node/google-calendar.md +187 -0
  39. package/docs/apis/features/node/google-docs.md +151 -0
  40. package/docs/apis/features/node/google-drive.md +225 -0
  41. package/docs/apis/features/node/google-sheets.md +179 -0
  42. package/docs/apis/features/node/grep.md +290 -0
  43. package/docs/apis/features/node/helpers.md +135 -0
  44. package/docs/apis/features/node/ink.md +334 -0
  45. package/docs/apis/features/node/ipc-socket.md +260 -0
  46. package/docs/apis/features/node/json-tree.md +86 -0
  47. package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
  48. package/docs/apis/features/node/networking.md +281 -0
  49. package/docs/apis/features/node/nlp.md +133 -0
  50. package/docs/apis/features/node/opener.md +97 -0
  51. package/docs/apis/features/node/os.md +118 -0
  52. package/docs/apis/features/node/package-finder.md +402 -0
  53. package/docs/apis/features/node/postgres.md +212 -0
  54. package/docs/apis/features/node/proc.md +430 -0
  55. package/docs/apis/features/node/process-manager.md +210 -0
  56. package/docs/apis/features/node/python.md +278 -0
  57. package/docs/apis/features/node/repl.md +88 -0
  58. package/docs/apis/features/node/runpod.md +673 -0
  59. package/docs/apis/features/node/secure-shell.md +169 -0
  60. package/docs/apis/features/node/semantic-search.md +401 -0
  61. package/docs/apis/features/node/sqlite.md +211 -0
  62. package/docs/apis/features/node/telegram.md +254 -0
  63. package/docs/apis/features/node/tts.md +118 -0
  64. package/docs/apis/features/node/ui.md +703 -0
  65. package/docs/apis/features/node/vault.md +64 -0
  66. package/docs/apis/features/node/vm.md +84 -0
  67. package/docs/apis/features/node/window-manager.md +337 -0
  68. package/docs/apis/features/node/yaml-tree.md +85 -0
  69. package/docs/apis/features/node/yaml.md +176 -0
  70. package/docs/apis/features/web/asset-loader.md +47 -0
  71. package/docs/apis/features/web/container-link.md +133 -0
  72. package/docs/apis/features/web/esbuild.md +59 -0
  73. package/docs/apis/features/web/helpers.md +135 -0
  74. package/docs/apis/features/web/network.md +30 -0
  75. package/docs/apis/features/web/speech.md +55 -0
  76. package/docs/apis/features/web/vault.md +64 -0
  77. package/docs/apis/features/web/vm.md +84 -0
  78. package/docs/apis/features/web/voice.md +67 -0
  79. package/docs/apis/servers/express.md +127 -0
  80. package/docs/apis/servers/mcp.md +213 -0
  81. package/docs/apis/servers/websocket.md +99 -0
  82. package/docs/documentation-audit.md +134 -0
  83. package/docs/examples/content-db.md +77 -0
  84. package/docs/examples/disk-cache.md +83 -0
  85. package/docs/examples/docker.md +101 -0
  86. package/docs/examples/downloader.md +70 -0
  87. package/docs/examples/esbuild.md +80 -0
  88. package/docs/examples/file-manager.md +82 -0
  89. package/docs/examples/fs.md +83 -0
  90. package/docs/examples/git.md +85 -0
  91. package/docs/examples/google-auth.md +88 -0
  92. package/docs/examples/google-calendar.md +94 -0
  93. package/docs/examples/google-docs.md +82 -0
  94. package/docs/examples/google-drive.md +96 -0
  95. package/docs/examples/google-sheets.md +95 -0
  96. package/docs/examples/grep.md +85 -0
  97. package/docs/examples/ink-blocks.md +75 -0
  98. package/docs/examples/ink-renderer.md +41 -0
  99. package/docs/examples/ink.md +103 -0
  100. package/docs/examples/ipc-socket.md +103 -0
  101. package/docs/examples/json-tree.md +91 -0
  102. package/docs/examples/launcher-app-command-listener.md +120 -0
  103. package/docs/examples/networking.md +58 -0
  104. package/docs/examples/nlp.md +91 -0
  105. package/docs/examples/opener.md +78 -0
  106. package/docs/examples/os.md +72 -0
  107. package/docs/examples/package-finder.md +89 -0
  108. package/docs/examples/port-exposer.md +89 -0
  109. package/docs/examples/postgres.md +91 -0
  110. package/docs/examples/proc.md +81 -0
  111. package/docs/examples/process-manager.md +79 -0
  112. package/docs/examples/python.md +91 -0
  113. package/docs/examples/repl.md +93 -0
  114. package/docs/examples/runpod.md +119 -0
  115. package/docs/examples/secure-shell.md +92 -0
  116. package/docs/examples/sqlite.md +86 -0
  117. package/docs/examples/telegram.md +77 -0
  118. package/docs/examples/tts.md +86 -0
  119. package/docs/examples/ui.md +80 -0
  120. package/docs/examples/vault.md +70 -0
  121. package/docs/examples/vm.md +86 -0
  122. package/docs/examples/window-manager.md +125 -0
  123. package/docs/examples/yaml-tree.md +93 -0
  124. package/docs/examples/yaml.md +104 -0
  125. package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
  126. package/docs/ideas/container-use-api.md +9 -0
  127. package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
  128. package/docs/ideas/feature-stacks.md +22 -0
  129. package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
  130. package/docs/ideas/mcp-design.md +9 -0
  131. package/docs/ideas/web-container-debugging-feature.md +13 -0
  132. package/docs/introspection-audit.md +49 -0
  133. package/docs/introspection.md +154 -0
  134. package/docs/mcp/readme.md +162 -0
  135. package/docs/models.ts +38 -0
  136. package/docs/philosophy.md +85 -0
  137. package/docs/principles.md +7 -0
  138. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  139. package/docs/prompts/mcp-test-easy-command.md +27 -0
  140. package/docs/reports/assistant-bugs.md +38 -0
  141. package/docs/reports/attach-pattern-usage.md +18 -0
  142. package/docs/reports/code-audit-results.md +391 -0
  143. package/docs/reports/introspection-audit-tasks.md +378 -0
  144. package/docs/reports/luca-mcp-improvements.md +128 -0
  145. package/docs/scaffolds/client.md +140 -0
  146. package/docs/scaffolds/command.md +106 -0
  147. package/docs/scaffolds/endpoint.md +176 -0
  148. package/docs/scaffolds/feature.md +148 -0
  149. package/docs/scaffolds/server.md +187 -0
  150. package/docs/tasks/web-container-helper-discovery.md +71 -0
  151. package/docs/todos.md +1 -0
  152. package/docs/tutorials/01-getting-started.md +106 -0
  153. package/docs/tutorials/02-container.md +210 -0
  154. package/docs/tutorials/03-scripts.md +194 -0
  155. package/docs/tutorials/04-features-overview.md +196 -0
  156. package/docs/tutorials/05-state-and-events.md +171 -0
  157. package/docs/tutorials/06-servers.md +157 -0
  158. package/docs/tutorials/07-endpoints.md +198 -0
  159. package/docs/tutorials/08-commands.md +171 -0
  160. package/docs/tutorials/09-clients.md +162 -0
  161. package/docs/tutorials/10-creating-features.md +198 -0
  162. package/docs/tutorials/11-contentbase.md +191 -0
  163. package/docs/tutorials/12-assistants.md +215 -0
  164. package/docs/tutorials/13-introspection.md +147 -0
  165. package/docs/tutorials/14-type-system.md +174 -0
  166. package/docs/tutorials/15-project-patterns.md +222 -0
  167. package/docs/tutorials/16-google-features.md +534 -0
  168. package/docs/tutorials/17-tui-blocks.md +530 -0
  169. package/docs/tutorials/18-semantic-search.md +334 -0
  170. package/index.ts +1 -0
  171. package/luca.console.ts +9 -0
  172. package/main.py +6 -0
  173. package/package.json +154 -0
  174. package/pyproject.toml +7 -0
  175. package/scripts/animations/chrome-glitch.ts +55 -0
  176. package/scripts/animations/index.ts +16 -0
  177. package/scripts/animations/neon-pulse.ts +64 -0
  178. package/scripts/animations/types.ts +6 -0
  179. package/scripts/build-web.ts +28 -0
  180. package/scripts/examples/ask-luca-expert.ts +42 -0
  181. package/scripts/examples/assistant-questions.ts +12 -0
  182. package/scripts/examples/excalidraw-expert.ts +75 -0
  183. package/scripts/examples/expert-chat.ts +0 -0
  184. package/scripts/examples/file-manager.ts +14 -0
  185. package/scripts/examples/ideas.ts +12 -0
  186. package/scripts/examples/interactive-chat.ts +20 -0
  187. package/scripts/examples/openai-tool-calls.ts +113 -0
  188. package/scripts/examples/opening-a-web-browser.ts +5 -0
  189. package/scripts/examples/telegram-bot.ts +79 -0
  190. package/scripts/examples/telegram-ink-ui.ts +302 -0
  191. package/scripts/examples/using-assistant-with-mcp.ts +560 -0
  192. package/scripts/examples/using-claude-code.ts +10 -0
  193. package/scripts/examples/using-contentdb.ts +35 -0
  194. package/scripts/examples/using-conversations.ts +35 -0
  195. package/scripts/examples/using-disk-cache.ts +10 -0
  196. package/scripts/examples/using-docker-shell.ts +75 -0
  197. package/scripts/examples/using-elevenlabs.ts +25 -0
  198. package/scripts/examples/using-google-calendar.ts +57 -0
  199. package/scripts/examples/using-google-docs.ts +74 -0
  200. package/scripts/examples/using-google-drive.ts +74 -0
  201. package/scripts/examples/using-google-sheets.ts +89 -0
  202. package/scripts/examples/using-nlp.ts +55 -0
  203. package/scripts/examples/using-ollama.ts +10 -0
  204. package/scripts/examples/using-openai-codex.ts +23 -0
  205. package/scripts/examples/using-postgres.ts +55 -0
  206. package/scripts/examples/using-runpod.ts +32 -0
  207. package/scripts/examples/using-tts.ts +40 -0
  208. package/scripts/examples/vm-loading-esm-modules.ts +16 -0
  209. package/scripts/scaffold.ts +391 -0
  210. package/scripts/scratch.ts +15 -0
  211. package/scripts/test-command-listener.ts +123 -0
  212. package/scripts/test-window-manager-lifecycle.ts +86 -0
  213. package/scripts/test-window-manager.ts +43 -0
  214. package/scripts/update-introspection-data.ts +58 -0
  215. package/src/agi/README.md +14 -0
  216. package/src/agi/container.server.ts +114 -0
  217. package/src/agi/endpoints/ask.ts +60 -0
  218. package/src/agi/endpoints/conversations/[id].ts +45 -0
  219. package/src/agi/endpoints/conversations.ts +31 -0
  220. package/src/agi/endpoints/experts.ts +37 -0
  221. package/src/agi/features/assistant.ts +767 -0
  222. package/src/agi/features/assistants-manager.ts +260 -0
  223. package/src/agi/features/claude-code.ts +1111 -0
  224. package/src/agi/features/conversation-history.ts +497 -0
  225. package/src/agi/features/conversation.ts +799 -0
  226. package/src/agi/features/openai-codex.ts +631 -0
  227. package/src/agi/features/openapi.ts +438 -0
  228. package/src/agi/features/skills-library.ts +425 -0
  229. package/src/agi/index.ts +6 -0
  230. package/src/agi/lib/token-counter.ts +122 -0
  231. package/src/browser.ts +25 -0
  232. package/src/bus.ts +100 -0
  233. package/src/cli/cli.ts +70 -0
  234. package/src/client.ts +461 -0
  235. package/src/clients/civitai/index.ts +541 -0
  236. package/src/clients/client-template.ts +41 -0
  237. package/src/clients/comfyui/index.ts +597 -0
  238. package/src/clients/elevenlabs/index.ts +291 -0
  239. package/src/clients/openai/index.ts +451 -0
  240. package/src/clients/supabase/index.ts +366 -0
  241. package/src/command.ts +164 -0
  242. package/src/commands/chat.ts +182 -0
  243. package/src/commands/console.ts +192 -0
  244. package/src/commands/describe.ts +433 -0
  245. package/src/commands/eval.ts +116 -0
  246. package/src/commands/help.ts +214 -0
  247. package/src/commands/index.ts +14 -0
  248. package/src/commands/mcp.ts +64 -0
  249. package/src/commands/prompt.ts +807 -0
  250. package/src/commands/run.ts +257 -0
  251. package/src/commands/sandbox-mcp.ts +439 -0
  252. package/src/commands/scaffold.ts +79 -0
  253. package/src/commands/serve.ts +172 -0
  254. package/src/container.ts +781 -0
  255. package/src/endpoint.ts +340 -0
  256. package/src/feature.ts +75 -0
  257. package/src/hash-object.ts +97 -0
  258. package/src/helper.ts +543 -0
  259. package/src/introspection/generated.agi.ts +23388 -0
  260. package/src/introspection/generated.node.ts +18899 -0
  261. package/src/introspection/generated.web.ts +2021 -0
  262. package/src/introspection/index.ts +256 -0
  263. package/src/introspection/scan.ts +912 -0
  264. package/src/node/container.ts +354 -0
  265. package/src/node/feature.ts +13 -0
  266. package/src/node/features/container-link.ts +558 -0
  267. package/src/node/features/content-db.ts +475 -0
  268. package/src/node/features/disk-cache.ts +382 -0
  269. package/src/node/features/dns.ts +655 -0
  270. package/src/node/features/docker.ts +912 -0
  271. package/src/node/features/downloader.ts +92 -0
  272. package/src/node/features/esbuild.ts +68 -0
  273. package/src/node/features/file-manager.ts +357 -0
  274. package/src/node/features/fs.ts +534 -0
  275. package/src/node/features/git.ts +492 -0
  276. package/src/node/features/google-auth.ts +502 -0
  277. package/src/node/features/google-calendar.ts +300 -0
  278. package/src/node/features/google-docs.ts +404 -0
  279. package/src/node/features/google-drive.ts +339 -0
  280. package/src/node/features/google-sheets.ts +279 -0
  281. package/src/node/features/grep.ts +406 -0
  282. package/src/node/features/helpers.ts +374 -0
  283. package/src/node/features/ink.ts +490 -0
  284. package/src/node/features/ipc-socket.ts +459 -0
  285. package/src/node/features/json-tree.ts +188 -0
  286. package/src/node/features/launcher-app-command-listener.ts +388 -0
  287. package/src/node/features/networking.ts +925 -0
  288. package/src/node/features/nlp.ts +211 -0
  289. package/src/node/features/opener.ts +166 -0
  290. package/src/node/features/os.ts +157 -0
  291. package/src/node/features/package-finder.ts +539 -0
  292. package/src/node/features/port-exposer.ts +342 -0
  293. package/src/node/features/postgres.ts +273 -0
  294. package/src/node/features/proc.ts +502 -0
  295. package/src/node/features/process-manager.ts +542 -0
  296. package/src/node/features/python.ts +444 -0
  297. package/src/node/features/repl.ts +194 -0
  298. package/src/node/features/runpod.ts +802 -0
  299. package/src/node/features/secure-shell.ts +248 -0
  300. package/src/node/features/semantic-search.ts +924 -0
  301. package/src/node/features/sqlite.ts +289 -0
  302. package/src/node/features/telegram.ts +342 -0
  303. package/src/node/features/tts.ts +184 -0
  304. package/src/node/features/ui.ts +857 -0
  305. package/src/node/features/vault.ts +164 -0
  306. package/src/node/features/vm.ts +312 -0
  307. package/src/node/features/window-manager.ts +804 -0
  308. package/src/node/features/yaml-tree.ts +149 -0
  309. package/src/node/features/yaml.ts +132 -0
  310. package/src/node.ts +70 -0
  311. package/src/react/index.ts +175 -0
  312. package/src/registry.ts +199 -0
  313. package/src/scaffolds/generated.ts +1613 -0
  314. package/src/scaffolds/template.ts +37 -0
  315. package/src/schemas/base.ts +255 -0
  316. package/src/server.ts +135 -0
  317. package/src/servers/express.ts +209 -0
  318. package/src/servers/mcp.ts +805 -0
  319. package/src/servers/socket.ts +120 -0
  320. package/src/state.ts +101 -0
  321. package/src/web/clients/socket.ts +82 -0
  322. package/src/web/container.ts +74 -0
  323. package/src/web/extension.ts +30 -0
  324. package/src/web/feature.ts +12 -0
  325. package/src/web/features/asset-loader.ts +64 -0
  326. package/src/web/features/container-link.ts +385 -0
  327. package/src/web/features/esbuild.ts +79 -0
  328. package/src/web/features/helpers.ts +267 -0
  329. package/src/web/features/network.ts +61 -0
  330. package/src/web/features/speech.ts +87 -0
  331. package/src/web/features/vault.ts +189 -0
  332. package/src/web/features/vm.ts +78 -0
  333. package/src/web/features/voice-recognition.ts +129 -0
  334. package/src/web/shims/isomorphic-vm.ts +149 -0
  335. package/test/bus.test.ts +134 -0
  336. package/test/clients-servers.test.ts +216 -0
  337. package/test/container-link.test.ts +274 -0
  338. package/test/features.test.ts +160 -0
  339. package/test/integration.test.ts +787 -0
  340. package/test/node-container.test.ts +121 -0
  341. package/test/rate-limit.test.ts +272 -0
  342. package/test/semantic-search.test.ts +550 -0
  343. package/test/state.test.ts +121 -0
  344. package/test-integration/assistant.test.ts +138 -0
  345. package/test-integration/assistants-manager.test.ts +123 -0
  346. package/test-integration/claude-code.test.ts +98 -0
  347. package/test-integration/conversation-history.test.ts +205 -0
  348. package/test-integration/conversation.test.ts +137 -0
  349. package/test-integration/elevenlabs.test.ts +55 -0
  350. package/test-integration/google-services.test.ts +80 -0
  351. package/test-integration/helpers.ts +89 -0
  352. package/test-integration/openai-codex.test.ts +93 -0
  353. package/test-integration/runpod.test.ts +58 -0
  354. package/test-integration/server-endpoints.test.ts +97 -0
  355. package/test-integration/skills-library.test.ts +157 -0
  356. package/test-integration/telegram.test.ts +46 -0
  357. package/tsconfig.json +58 -0
  358. 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)