@slicedearth/whoisleuth-cli 2.4.0 → 2.6.0

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 (340) hide show
  1. package/DISCLOSURE +1 -1
  2. package/NOTICE +2 -2
  3. package/cli/argument-values.mjs +55 -0
  4. package/cli/arguments.mjs +24 -470
  5. package/cli/artifact-validation/case-response.mjs +67 -9
  6. package/cli/artifact-validation/lookup-evidence.mjs +18 -4
  7. package/cli/artifact-validation/signed-review.mjs +61 -64
  8. package/cli/artifact-verify.mjs +11 -2
  9. package/cli/assurance-arguments.mjs +56 -0
  10. package/cli/assurance-command-definitions.mjs +179 -0
  11. package/cli/assurance-command-runner.mjs +1 -1
  12. package/cli/bounded-json.mjs +1 -1
  13. package/cli/bulk-checkpoint.mjs +1 -1
  14. package/cli/bulk-command-runner.mjs +6 -8
  15. package/cli/bulk.mjs +2 -3
  16. package/cli/case-command.mjs +99 -6
  17. package/cli/case-pack.mjs +19 -4
  18. package/cli/ci-report.mjs +6 -3
  19. package/cli/collection-arguments.mjs +123 -0
  20. package/cli/collection-command-definitions.mjs +330 -0
  21. package/cli/collection-preflight.mjs +8 -1
  22. package/cli/command-argument-grammar.mjs +8 -210
  23. package/cli/command-definition.mjs +330 -0
  24. package/cli/command-reference.mjs +55 -1388
  25. package/cli/command-seeds.mjs +18 -0
  26. package/cli/compare.mjs +1 -1
  27. package/cli/config-profile.mjs +1 -1
  28. package/cli/ct-event-intake.mjs +4 -4
  29. package/cli/discover.mjs +2 -2
  30. package/cli/discovery-observation-snapshot.mjs +2 -2
  31. package/cli/discovery-scan-command-runner.mjs +7 -9
  32. package/cli/discovery-scan.mjs +1 -1
  33. package/cli/discovery-snapshot.mjs +2 -2
  34. package/cli/discovery-workflow.mjs +4 -4
  35. package/cli/domain-control-monitor.mjs +6 -6
  36. package/cli/domain-control-observations.mjs +2 -1
  37. package/cli/domain-feed.mjs +61 -0
  38. package/cli/errors.mjs +5 -6
  39. package/cli/evidence-arguments.mjs +46 -0
  40. package/cli/evidence-command-definitions.mjs +69 -0
  41. package/cli/evidence-command-runner.mjs +2 -3
  42. package/cli/evidence-signing.mjs +3 -2
  43. package/cli/external-observation-mapping.mjs +2 -2
  44. package/cli/formatters/json.mjs +1 -13
  45. package/cli/formatters/terminal-command-formats.mjs +6 -0
  46. package/cli/formatters/terminal-lookup.mjs +6 -0
  47. package/cli/history-arguments.mjs +39 -0
  48. package/cli/history-command-definitions.mjs +88 -0
  49. package/cli/indicator-set-command.mjs +32 -0
  50. package/cli/inline-command-families.mjs +6 -47
  51. package/cli/inline-command-runner.mjs +4 -31
  52. package/cli/input.mjs +2 -2
  53. package/cli/intake-command.mjs +147 -0
  54. package/cli/interchange-report.mjs +4 -2
  55. package/cli/investigation-artifacts.mjs +3 -4
  56. package/cli/investigation-package-review.mjs +2 -0
  57. package/cli/investigation-plan.mjs +32 -154
  58. package/cli/investigation-recipes.mjs +177 -0
  59. package/cli/investigation-run.mjs +78 -12
  60. package/cli/local-mmdb-review.mjs +220 -17
  61. package/cli/lookalike-calibration.mjs +1 -1
  62. package/cli/lookup-brief.mjs +2 -2
  63. package/cli/lookup-command-runner.mjs +19 -35
  64. package/cli/lookup-diff.mjs +10 -9
  65. package/cli/lookup-plan.mjs +10 -14
  66. package/cli/lookup-reconcile.mjs +6 -5
  67. package/cli/lookup-timeline.mjs +7 -6
  68. package/cli/mail-header-review.mjs +24 -247
  69. package/cli/mail-review.mjs +9 -7
  70. package/cli/network-arguments.mjs +51 -0
  71. package/cli/network-command-definitions.mjs +130 -0
  72. package/cli/network-command-runner.mjs +43 -31
  73. package/cli/offline-evidence-review.mjs +85 -6
  74. package/cli/posture.mjs +1 -1
  75. package/cli/registry-fixture-scaffold.mjs +1 -1
  76. package/cli/retained-artifact-diff.mjs +8 -7
  77. package/cli/review-arguments.mjs +132 -0
  78. package/cli/review-command-definitions.mjs +279 -0
  79. package/cli/review-command-runner.mjs +72 -159
  80. package/cli/risk-calibration.mjs +14 -12
  81. package/cli/runner.mjs +20 -32
  82. package/cli/saved-lookup.mjs +2 -2
  83. package/cli/selected-input-worker.mjs +73 -0
  84. package/cli/sharing-review.mjs +5 -64
  85. package/cli/source-reliability.mjs +2 -2
  86. package/cli/support-arguments.mjs +49 -0
  87. package/cli/support-command-definitions.mjs +209 -0
  88. package/cli/support-command-runner.mjs +4 -4
  89. package/cli/watchlist-review.mjs +43 -0
  90. package/cli/workflow-arguments.mjs +100 -0
  91. package/cli/workflow-command-definitions.mjs +103 -0
  92. package/cli/workflow-command-runner.mjs +40 -1
  93. package/docs/cli-reference.md +27 -0
  94. package/docs/cli.md +322 -4
  95. package/docs/contextual-reviews.md +178 -0
  96. package/docs/infrastructure-observations.md +126 -0
  97. package/docs/offline-intake-context.md +213 -0
  98. package/lib/analyst-taxonomy.mjs +2 -40
  99. package/lib/application-version.mjs +1 -1
  100. package/lib/availability.mjs +13 -789
  101. package/lib/bounded-contract-normalizers.mjs +4 -0
  102. package/lib/bounded-json.mjs +2 -330
  103. package/lib/browser-library-profile.mjs +62 -84
  104. package/lib/candidate-provenance-bounds.mjs +2 -0
  105. package/lib/classify.mjs +1 -1
  106. package/lib/credential-form-attribution.mjs +136 -0
  107. package/lib/credential-surface-profile.mjs +6 -2
  108. package/lib/ct-query.mjs +2 -27
  109. package/lib/ct-response-bounds.mjs +2 -9
  110. package/lib/ct-search.mjs +83 -47
  111. package/lib/dmarc-discovery.mjs +62 -0
  112. package/lib/dns-convergence-review.mjs +21 -8
  113. package/lib/dns-delegation-health.mjs +35 -15
  114. package/lib/dns-inheritance-review.mjs +53 -63
  115. package/lib/dns-intelligence.mjs +27 -11
  116. package/lib/dns-query-coverage.mjs +40 -0
  117. package/lib/domain-assurance.mjs +1 -1
  118. package/lib/domain-change-packet.mjs +12 -13
  119. package/lib/domain-change-review.mjs +32 -14
  120. package/lib/domain-control-flight-recorder.mjs +103 -27
  121. package/lib/domain-control-manifest.mjs +25 -17
  122. package/lib/domain-posture-analysis.mjs +75 -7
  123. package/lib/domain-posture-parsers.mjs +9 -0
  124. package/lib/domain-posture.mjs +45 -25
  125. package/lib/domain-registration.mjs +392 -0
  126. package/lib/evidence-export-privacy.mjs +27 -17
  127. package/lib/evidence-export.mjs +3 -3
  128. package/lib/evidence-report.mjs +5 -1
  129. package/lib/external-intelligence-risk.mjs +2 -124
  130. package/lib/favicon.mjs +13 -4
  131. package/lib/generated/cisa-kev-catalog.mjs +41 -3
  132. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  133. package/lib/generated/retire-browser-catalog.mjs +332 -19
  134. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  135. package/lib/html-signals.mjs +13 -2
  136. package/lib/http-evidence-bounds.mjs +2 -8
  137. package/lib/http-intelligence.mjs +13 -1
  138. package/lib/idn-confusable-policy.mjs +6 -4
  139. package/lib/idn-confusables.mjs +1 -1
  140. package/lib/interchange-fidelity-registry.mjs +10 -0
  141. package/lib/lookup-child-profile-contract.mjs +28 -11
  142. package/lib/lookup-contract-primitives.mjs +1 -1
  143. package/lib/lookup-diagnostics.mjs +1 -23
  144. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  145. package/lib/lookup-progress.mjs +50 -1
  146. package/lib/lookup-response-contract.mjs +70 -40
  147. package/lib/lookup-response.mjs +5 -15
  148. package/lib/lookup-source-progress.mjs +8 -22
  149. package/lib/lookup.mjs +1 -1
  150. package/lib/operation-budget.mjs +4 -0
  151. package/lib/opportunity-scoring.mjs +2 -107
  152. package/lib/outbound-identity.mjs +1 -1
  153. package/lib/page-fingerprints.mjs +18 -1
  154. package/lib/perceptual-hash-comparison.mjs +2 -34
  155. package/lib/perceptual-hash.mjs +36 -10
  156. package/lib/portable-generator.mjs +2 -29
  157. package/lib/project-metadata.mjs +2 -8
  158. package/lib/rdap-client.mjs +11 -7
  159. package/lib/registrable-domain.mjs +2 -56
  160. package/lib/registrar-standing-contract.mjs +1 -1
  161. package/lib/registrar-standing.mjs +9 -13
  162. package/lib/registry-capabilities.mjs +2 -2
  163. package/lib/registry-capability-catalogue.mjs +27 -19
  164. package/lib/risk-calibration-summary.mjs +2 -352
  165. package/lib/risk-scoring.mjs +2 -345
  166. package/lib/scoring-evidence-quality.mjs +2 -107
  167. package/lib/semantic-version.mjs +2 -54
  168. package/lib/service-binding-dns.mjs +46 -2
  169. package/lib/smtp-transport-review.mjs +48 -19
  170. package/lib/sslbl-certificates.generated.mjs +142 -139
  171. package/lib/static-html-analysis.mjs +32 -4
  172. package/lib/structured-data-identity.mjs +1 -1
  173. package/lib/technology-evidence-role.mjs +1 -51
  174. package/lib/threat-intelligence-manifests.mjs +1 -1
  175. package/lib/threat-intelligence-runtime.mjs +1 -1
  176. package/lib/threat-intelligence-targets.mjs +1 -1
  177. package/lib/threat-intelligence-types.mjs +2 -75
  178. package/lib/tls-intelligence.mjs +30 -5
  179. package/lib/typosquat-generator.mjs +11 -2
  180. package/lib/website-enrichment.mjs +404 -0
  181. package/lib/whois-authority.mjs +43 -5
  182. package/lib/whois-chain.mjs +12 -3
  183. package/lib/whois-common-formats.mjs +3 -1
  184. package/lib/whois-contacts.mjs +13 -2
  185. package/lib/whois-normalization.mjs +3 -1
  186. package/lib/whois-parser.mjs +5 -3
  187. package/package.json +15 -14
  188. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  189. package/packages/analysis/bounded-json.mjs +330 -0
  190. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  191. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  192. package/packages/analysis/ct-query.mjs +27 -0
  193. package/packages/analysis/ct-response-bounds.mjs +9 -0
  194. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  195. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  196. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  197. package/packages/analysis/opportunity-scoring.mjs +107 -0
  198. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  199. package/packages/analysis/portable-generator.mjs +29 -0
  200. package/packages/analysis/project-metadata.mjs +8 -0
  201. package/packages/analysis/registrable-domain.mjs +56 -0
  202. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  203. package/packages/analysis/risk-scoring.mjs +348 -0
  204. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  205. package/packages/analysis/semantic-version.mjs +54 -0
  206. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  207. package/packages/cases/case-containment-handoff.mjs +195 -0
  208. package/packages/cases/case-evidence-links.mjs +151 -0
  209. package/packages/cases/case-evidence-model.mjs +89 -12
  210. package/packages/cases/case-evidence-quality.mjs +5 -0
  211. package/packages/cases/case-export-input.mjs +7 -5
  212. package/packages/cases/case-incident-context.mjs +52 -0
  213. package/packages/cases/case-markdown.mjs +11 -0
  214. package/packages/cases/case-migration-model.mjs +40 -24
  215. package/packages/cases/case-object-coverage.mjs +33 -0
  216. package/packages/cases/case-packet-correction.mjs +106 -0
  217. package/packages/cases/case-recheck-model.mjs +124 -36
  218. package/packages/cases/case-record-contracts.mjs +1 -1
  219. package/packages/cases/case-record-core.mjs +1 -1
  220. package/packages/cases/case-record-operations.mjs +111 -118
  221. package/packages/cases/case-record-projection.mjs +23 -4
  222. package/packages/cases/case-record-version-input.mjs +41 -0
  223. package/packages/cases/case-report.mjs +93 -27
  224. package/packages/cases/case-requested-evidence.mjs +146 -0
  225. package/packages/cases/case-response-actions.mjs +159 -1
  226. package/packages/cases/case-response-model.mjs +16 -1
  227. package/packages/cases/case-response-object.mjs +119 -0
  228. package/packages/cases/case-response-outcomes.mjs +108 -20
  229. package/packages/cases/case-response-packet-render.mjs +35 -12
  230. package/packages/cases/case-response-packet.mjs +55 -13
  231. package/packages/cases/case-response-records.mjs +0 -1
  232. package/packages/cases/case-response-review-inputs.mjs +60 -8
  233. package/packages/cases/case-storage-model.mjs +1 -1
  234. package/packages/cases/case-workflow-metadata.mjs +217 -0
  235. package/packages/cases/case-workflow-migration.mjs +43 -0
  236. package/packages/cases/http-summary.mjs +6 -1
  237. package/packages/comparison/favicon-similarity.mjs +1 -1
  238. package/packages/comparison/page-similarity.mjs +1 -1
  239. package/packages/comparison/relationship-evidence.mjs +21 -9
  240. package/packages/comparison/relationship-provenance.mjs +7 -1
  241. package/packages/contracts/analyst-interchange.mjs +35 -15
  242. package/packages/contracts/candidate-watch-review.mjs +54 -0
  243. package/packages/contracts/case-portability.mjs +360 -49
  244. package/packages/contracts/cli-command-semantics.mjs +5 -0
  245. package/packages/contracts/cli-grammar.mjs +6 -0
  246. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  247. package/packages/contracts/context-review.mjs +25 -0
  248. package/packages/contracts/document-intake.mjs +11 -0
  249. package/packages/contracts/domain-control-flight-recorder.mjs +30 -14
  250. package/packages/contracts/domain-control-manifest.mjs +1 -1
  251. package/packages/contracts/domain-control-monitor.mjs +13 -2
  252. package/packages/contracts/domain-control-review.mjs +36 -15
  253. package/packages/contracts/external-observation-interchange.mjs +96 -28
  254. package/packages/contracts/har-review.mjs +2 -0
  255. package/packages/contracts/identity-events.mjs +4 -0
  256. package/packages/contracts/investigation-run.mjs +2 -0
  257. package/packages/contracts/ip-address.mjs +10 -0
  258. package/packages/contracts/lookup-evidence.mjs +3 -2
  259. package/packages/contracts/mail-authentication.mjs +5 -0
  260. package/packages/contracts/message-intake.mjs +37 -0
  261. package/packages/contracts/offline-comparison.mjs +4 -1
  262. package/packages/contracts/passive-mail.mjs +10 -0
  263. package/packages/contracts/risk-calibration.mjs +44 -29
  264. package/packages/contracts/schema-lifecycle.mjs +3 -1
  265. package/packages/contracts/web-capture.mjs +2 -2
  266. package/packages/contracts/workspace-portability.mjs +144 -28
  267. package/packages/evidence/collection-quality.mjs +67 -0
  268. package/packages/evidence/credential-form-attribution.mjs +60 -0
  269. package/packages/evidence/dns-query-outcome.mjs +72 -0
  270. package/packages/evidence/email-recipient.mjs +18 -0
  271. package/packages/evidence/image-regions.mjs +15 -0
  272. package/packages/evidence/lookup-target.mjs +5 -1
  273. package/packages/evidence/observation.mjs +2 -0
  274. package/packages/evidence/png-pixels.mjs +117 -0
  275. package/packages/evidence/public-address-policy.mjs +83 -0
  276. package/packages/evidence/record-identity.mjs +60 -16
  277. package/packages/evidence/technology-evidence-role.mjs +51 -0
  278. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  279. package/packages/interchange/external-findings-import.mjs +36 -29
  280. package/packages/interchange/json-output.mjs +14 -0
  281. package/packages/interchange/managed-indicator-export.mjs +36 -0
  282. package/packages/interchange/managed-indicator-set.mjs +223 -0
  283. package/packages/interchange/misp-indicator-export.mjs +93 -0
  284. package/packages/interchange/sharing-policy.mjs +56 -0
  285. package/packages/interchange/stix-indicator-export.mjs +129 -0
  286. package/packages/interchange/web-capture-import.mjs +22 -15
  287. package/packages/investigation/acquisition-decision-packet.mjs +79 -1
  288. package/packages/investigation/bulk-mail-exposure.mjs +7 -13
  289. package/packages/investigation/capture-coverage.mjs +57 -0
  290. package/packages/investigation/connector-provenance-review.mjs +100 -0
  291. package/packages/investigation/context-review.mjs +35 -0
  292. package/packages/investigation/document-intake.mjs +87 -0
  293. package/packages/investigation/docx-intake.mjs +133 -0
  294. package/packages/investigation/domain-history-review.mjs +132 -0
  295. package/packages/investigation/domain-transition-review.mjs +49 -0
  296. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  297. package/packages/investigation/har-intake.mjs +68 -0
  298. package/packages/investigation/identity-event-intake.mjs +131 -0
  299. package/packages/investigation/identity-incident-review.mjs +39 -0
  300. package/packages/investigation/incident-sequence-review.mjs +58 -0
  301. package/packages/investigation/infrastructure-observation.mjs +234 -0
  302. package/packages/investigation/intake-context.mjs +284 -0
  303. package/packages/investigation/intake-indicators.mjs +116 -0
  304. package/packages/investigation/intake-phones.mjs +155 -0
  305. package/packages/investigation/intake-report.mjs +20 -0
  306. package/packages/investigation/investigation-manifest.mjs +35 -9
  307. package/packages/investigation/link-intake.mjs +209 -0
  308. package/packages/investigation/lookup-asset-graph.mjs +1 -1
  309. package/packages/investigation/mail-authentication-review.mjs +222 -0
  310. package/packages/investigation/mail-header-identity.mjs +209 -0
  311. package/packages/investigation/message-intake.mjs +235 -0
  312. package/packages/investigation/page-behaviour.mjs +113 -0
  313. package/packages/investigation/pdf-intake.mjs +155 -0
  314. package/packages/investigation/platform-continuity-review.mjs +60 -0
  315. package/packages/investigation/qr-intake.mjs +56 -0
  316. package/packages/investigation/requested-action-hints.mjs +11 -0
  317. package/packages/investigation/selected-input-review.mjs +17 -0
  318. package/packages/investigation/storefront-review.mjs +70 -0
  319. package/packages/monitoring/domain-feed.mjs +321 -0
  320. package/packages/workspace/brand-candidate-workflow.mjs +351 -0
  321. package/packages/workspace/brand-keyword-campaign.mjs +231 -0
  322. package/packages/workspace/brand-profile-model.mjs +48 -4
  323. package/packages/workspace/bulk-review-model.mjs +28 -26
  324. package/packages/workspace/bulk-session-model.mjs +27 -13
  325. package/packages/workspace/campaign-model.mjs +1 -1
  326. package/packages/workspace/candidate-watch-handoff.mjs +195 -0
  327. package/packages/workspace/detection-rule-model.mjs +11 -3
  328. package/packages/workspace/hostile-input.mjs +1 -1
  329. package/packages/workspace/investigation-guide.mjs +2 -2
  330. package/packages/workspace/investigation-template-model.mjs +26 -2
  331. package/packages/workspace/page-baseline.mjs +2 -2
  332. package/packages/workspace/relationship-observation-model.mjs +17 -13
  333. package/packages/workspace/shortlist-model.mjs +24 -7
  334. package/packages/workspace/watchlist-history.mjs +59 -16
  335. package/packages/workspace/watchlist-store.mjs +152 -11
  336. package/packages/workspace/website-snapshot-model.mjs +28 -3
  337. package/packages/workspace/workspace-archive-crypto.mjs +1 -1
  338. package/packages/workspace/workspace-archive.mjs +5 -4
  339. package/third-party-notices.txt +549 -16
  340. package/lib/hostname.mjs +0 -2
@@ -0,0 +1,330 @@
1
+ import { positional, OPTIONAL_FILE_POSITIONAL, OPTIONAL_TEXT_POSITIONAL, constraint, commandSeed, } from "./command-definition.mjs";
2
+ import { bulkQueryLimit, bulkConcurrencyLimit } from "../lib/bulk-limits.mjs";
3
+ const COLLECTION_CEILINGS = `${bulkQueryLimit('fast')} fast or ${bulkQueryLimit('deep')} deep`;
4
+ const CONCURRENCY_CEILINGS = `${bulkConcurrencyLimit('fast')} fast or ${bulkConcurrencyLimit('deep')} deep`;
5
+ export const COLLECTION_COMMAND_DEFINITIONS = Object.freeze({
6
+ lookup: commandSeed({
7
+ reference: {
8
+ description: 'Collect registration evidence for one domain, IP, or ASN.',
9
+ example: 'whoisleuth lookup example.test --deep --browse',
10
+ boundary: 'Fast is the default. An ICANN-recognised public domain, reserved documentation domain, IP, or ASN may occupy command position as shorthand; it delegates to this same parser and URL-like input requires the explicit lookup command. Deep mode adds bounded WHOIS, DNS, HTTP, TLS, technology, posture, and network context where applicable. --deep --exact-url explicitly sends the input URL path and query to the website, without its fragment; ordinary URL input sends only the hostname. --plan remains offline and omits the selected URL. Retained paths and page-derived text require review before sharing. A full Deep homepage observation can derive fixed publication and delivery/cache summaries from the same response without retaining raw metadata values or making another request. --browse opens before collection, shows aggregate Fast progress or independently settled planned Deep sources, and then navigates allowlisted retained fields in the completed document. Press ? for help and / to search rendered panel text only. Closing during collection cancels without a partial document. --save-lookup writes the exact completed private JSON only after a normal browser close; it can contain normalised evidence omitted from panels and refuses an existing path.',
11
+ },
12
+ collection: {
13
+ scope: 'Accepts one target. Fast is the default; deep collection must be selected explicitly.',
14
+ },
15
+ summary: 'Collect one domain, IP, or ASN',
16
+ options: [
17
+ '--json',
18
+ '--junit',
19
+ '--markdown',
20
+ '--html',
21
+ '--no-attribution',
22
+ '--fast',
23
+ '--deep',
24
+ '--exact-url',
25
+ '--observer',
26
+ '--vantage',
27
+ '--plan',
28
+ '--summary',
29
+ '--verbose',
30
+ '--browse',
31
+ '--save-lookup',
32
+ '--strict-exit',
33
+ '--fail-on',
34
+ '--events',
35
+ '--quiet',
36
+ '--no-color',
37
+ ],
38
+ positionals: Object.freeze([
39
+ positional('target', 'text', 0, 1, [], 'argv_or_stdin', ['--browse']),
40
+ ]),
41
+ constraints: Object.freeze([
42
+ constraint({
43
+ kind: 'mutually_exclusive',
44
+ options: ['--json', '--junit', '--markdown', '--html'],
45
+ }),
46
+ constraint({ kind: 'mutually_exclusive', options: ['--fast', '--deep'] }),
47
+ constraint({ kind: 'mutually_exclusive', options: ['--summary', '--verbose'] }),
48
+ constraint({ kind: 'requires_all', option: '--save-lookup', requiredOptions: ['--browse'] }),
49
+ constraint({ kind: 'requires_all', option: '--exact-url', requiredOptions: ['--deep'] }),
50
+ constraint({
51
+ kind: 'excludes_all',
52
+ option: '--summary',
53
+ excludedOptions: ['--json', '--junit', '--markdown', '--html'],
54
+ }),
55
+ constraint({
56
+ kind: 'excludes_all',
57
+ option: '--verbose',
58
+ excludedOptions: ['--json', '--junit', '--markdown', '--html'],
59
+ }),
60
+ constraint({
61
+ kind: 'excludes_all',
62
+ option: '--browse',
63
+ excludedOptions: [
64
+ '--json',
65
+ '--junit',
66
+ '--markdown',
67
+ '--html',
68
+ '--summary',
69
+ '--verbose',
70
+ '--events',
71
+ '--plan',
72
+ '--quiet',
73
+ ],
74
+ }),
75
+ constraint({
76
+ kind: 'requires_any',
77
+ option: '--no-attribution',
78
+ requiredOptions: ['--markdown', '--html'],
79
+ }),
80
+ constraint({
81
+ kind: 'excludes_all',
82
+ option: '--plan',
83
+ excludedOptions: [
84
+ '--junit',
85
+ '--markdown',
86
+ '--html',
87
+ '--summary',
88
+ '--verbose',
89
+ '--strict-exit',
90
+ '--events',
91
+ '--quiet',
92
+ '--fail-on',
93
+ ],
94
+ }),
95
+ ]),
96
+ handlerOwner: 'lookup',
97
+ networkEffect: 'conditional_network',
98
+ common: true,
99
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.lookup', 'whoisleuth.cli.lookup-plan']),
100
+ primaryArtefacts: Object.freeze(['Source-qualified Lookup', 'Lookup request plan']),
101
+ planSupport: true,
102
+ additionalOutputFormats: Object.freeze([]),
103
+ bootstrapProfile: 'allowed',
104
+ }),
105
+ bulk: commandSeed({
106
+ reference: {
107
+ description: 'Triage newline-delimited domains, IPs, or ASNs with bounded concurrency.',
108
+ example: 'cat domains.txt | whoisleuth bulk --jsonl',
109
+ boundary: 'Fast and deep jobs use separate concurrency ceilings. Filters affect output only; collection failures and inconclusive authority states remain explicit in JSON, JSONL, and CSV. --csv-with-metadata adds source versions, separate observation and report times, collection origin and diagnostic states; --csv retains the compact columns.',
110
+ },
111
+ collection: {
112
+ scope: `Accepts at most ${COLLECTION_CEILINGS} targets, with concurrency capped at ${CONCURRENCY_CEILINGS}.`,
113
+ },
114
+ summary: 'Run bounded multi-target collection',
115
+ options: [
116
+ '--json',
117
+ '--jsonl',
118
+ '--junit',
119
+ '--csv',
120
+ '--csv-with-metadata',
121
+ '--domains',
122
+ '--queries',
123
+ '--registered-only',
124
+ '--inconclusive-only',
125
+ '--errors-only',
126
+ '--fast',
127
+ '--deep',
128
+ '--concurrency',
129
+ '--checkpoint',
130
+ '--resume',
131
+ '--events',
132
+ '--plan',
133
+ '--fail-on',
134
+ '--quiet',
135
+ '--no-color',
136
+ ],
137
+ positionals: OPTIONAL_FILE_POSITIONAL,
138
+ constraints: Object.freeze([
139
+ constraint({
140
+ kind: 'mutually_exclusive',
141
+ options: [
142
+ '--json',
143
+ '--jsonl',
144
+ '--junit',
145
+ '--csv',
146
+ '--csv-with-metadata',
147
+ '--domains',
148
+ '--queries',
149
+ ],
150
+ }),
151
+ constraint({
152
+ kind: 'mutually_exclusive',
153
+ options: ['--registered-only', '--inconclusive-only', '--errors-only'],
154
+ }),
155
+ constraint({ kind: 'mutually_exclusive', options: ['--fast', '--deep'] }),
156
+ constraint({ kind: 'requires_all', option: '--resume', requiredOptions: ['--checkpoint'] }),
157
+ constraint({
158
+ kind: 'excludes_all',
159
+ option: '--plan',
160
+ excludedOptions: [
161
+ '--jsonl',
162
+ '--junit',
163
+ '--csv',
164
+ '--csv-with-metadata',
165
+ '--domains',
166
+ '--queries',
167
+ '--events',
168
+ '--checkpoint',
169
+ '--resume',
170
+ '--quiet',
171
+ '--fail-on',
172
+ ],
173
+ }),
174
+ ]),
175
+ handlerOwner: 'bulk',
176
+ networkEffect: 'conditional_network',
177
+ common: true,
178
+ schemaIdentifiers: Object.freeze([
179
+ 'whoisleuth.cli.bulk',
180
+ 'whoisleuth.cli.bulk.item',
181
+ 'whoisleuth.cli.bulk-checkpoint',
182
+ ]),
183
+ primaryArtefacts: Object.freeze(['Bulk result', 'Bulk checkpoint']),
184
+ planSupport: true,
185
+ additionalOutputFormats: Object.freeze([]),
186
+ bootstrapProfile: 'allowed',
187
+ }),
188
+ discover: commandSeed({
189
+ reference: {
190
+ description: 'Generate bounded lookalike-domain candidates from a brand or registrable domain, including multi-part public suffixes.',
191
+ example: 'whoisleuth discover example.test --preset common --jsonl',
192
+ boundary: 'Generation and optional local snapshot comparison are offline. Candidates are leads only and are not resolved, registered, or classified as malicious.',
193
+ },
194
+ collection: {
195
+ scope: 'Generates a bounded candidate set from local rules, dictionaries, and optional saved snapshots.',
196
+ },
197
+ summary: 'Generate lookalike candidates offline',
198
+ options: [
199
+ '--tlds',
200
+ '--preset',
201
+ '--families',
202
+ '--keyboard',
203
+ '--dictionary',
204
+ '--snapshot',
205
+ '--json',
206
+ '--jsonl',
207
+ '--domains',
208
+ '--quiet',
209
+ '--no-color',
210
+ ],
211
+ positionals: OPTIONAL_TEXT_POSITIONAL,
212
+ constraints: Object.freeze([
213
+ constraint({ kind: 'mutually_exclusive', options: ['--json', '--jsonl', '--domains'] }),
214
+ constraint({ kind: 'mutually_exclusive', options: ['--preset', '--families'] }),
215
+ constraint({
216
+ kind: 'value_excludes',
217
+ option: '--preset',
218
+ value: 'common',
219
+ excludedOptions: ['--dictionary'],
220
+ }),
221
+ ]),
222
+ handlerOwner: 'discovery',
223
+ networkEffect: 'offline',
224
+ common: true,
225
+ schemaIdentifiers: Object.freeze([
226
+ 'whoisleuth.cli.discover',
227
+ 'whoisleuth.cli.discover.item',
228
+ 'whoisleuth.cli.discovery-snapshot',
229
+ ]),
230
+ primaryArtefacts: Object.freeze(['Candidate set', 'Discovery snapshot']),
231
+ planSupport: false,
232
+ additionalOutputFormats: Object.freeze([]),
233
+ bootstrapProfile: 'allowed',
234
+ }),
235
+ 'discover-scan': commandSeed({
236
+ reference: {
237
+ description: 'Generate a bounded candidate set, collect a selected subset, and produce a supervised review queue.',
238
+ example: 'whoisleuth discover-scan example.test --scan-limit 50 --checkpoint scan.json --json',
239
+ boundary: `This command performs network collection. Fast compact lookup is the default; deep mode is capped at ${bulkQueryLimit('deep')} candidates. Allowlisting changes review priority only and shared infrastructure remains a lead, not attribution. --csv-with-metadata adds source versions, separate observation and report times, collection origin and diagnostic states; --csv retains the compact columns.`,
240
+ },
241
+ collection: {
242
+ scope: `Scans at most ${COLLECTION_CEILINGS} candidates, with concurrency capped at ${CONCURRENCY_CEILINGS}.`,
243
+ },
244
+ summary: 'Collect a supervised candidate review queue',
245
+ options: [
246
+ '--tlds',
247
+ '--preset',
248
+ '--families',
249
+ '--keyboard',
250
+ '--dictionary',
251
+ '--fast',
252
+ '--deep',
253
+ '--scan-limit',
254
+ '--chunk-size',
255
+ '--concurrency',
256
+ '--resolver',
257
+ '--allowlist',
258
+ '--checkpoint',
259
+ '--resume',
260
+ '--observation-snapshot',
261
+ '--registered-only',
262
+ '--inconclusive-only',
263
+ '--acquisition-only',
264
+ '--suppressed-only',
265
+ '--events',
266
+ '--plan',
267
+ '--fail-on',
268
+ '--json',
269
+ '--jsonl',
270
+ '--csv',
271
+ '--csv-with-metadata',
272
+ '--domains',
273
+ '--quiet',
274
+ '--no-color',
275
+ ],
276
+ positionals: OPTIONAL_TEXT_POSITIONAL,
277
+ constraints: Object.freeze([
278
+ constraint({
279
+ kind: 'mutually_exclusive',
280
+ options: ['--json', '--jsonl', '--csv', '--csv-with-metadata', '--domains'],
281
+ }),
282
+ constraint({ kind: 'mutually_exclusive', options: ['--preset', '--families'] }),
283
+ constraint({ kind: 'mutually_exclusive', options: ['--fast', '--deep'] }),
284
+ constraint({
285
+ kind: 'mutually_exclusive',
286
+ options: [
287
+ '--registered-only',
288
+ '--inconclusive-only',
289
+ '--acquisition-only',
290
+ '--suppressed-only',
291
+ ],
292
+ }),
293
+ constraint({ kind: 'requires_all', option: '--resume', requiredOptions: ['--checkpoint'] }),
294
+ constraint({
295
+ kind: 'value_excludes',
296
+ option: '--preset',
297
+ value: 'common',
298
+ excludedOptions: ['--dictionary'],
299
+ }),
300
+ constraint({
301
+ kind: 'excludes_all',
302
+ option: '--plan',
303
+ excludedOptions: [
304
+ '--jsonl',
305
+ '--csv',
306
+ '--csv-with-metadata',
307
+ '--domains',
308
+ '--events',
309
+ '--checkpoint',
310
+ '--resume',
311
+ '--observation-snapshot',
312
+ '--quiet',
313
+ '--fail-on',
314
+ ],
315
+ }),
316
+ ]),
317
+ handlerOwner: 'discovery_scan',
318
+ networkEffect: 'conditional_network',
319
+ common: true,
320
+ schemaIdentifiers: Object.freeze([
321
+ 'whoisleuth.cli.discovery-scan',
322
+ 'whoisleuth.cli.discovery-scan.item',
323
+ 'whoisleuth.cli.discovery-observation-snapshot',
324
+ ]),
325
+ primaryArtefacts: Object.freeze(['Reviewed candidate queue', 'Observation snapshot']),
326
+ planSupport: true,
327
+ additionalOutputFormats: Object.freeze([]),
328
+ bootstrapProfile: 'allowed',
329
+ }),
330
+ });
@@ -1,3 +1,4 @@
1
+ import { bulkQueryLimit } from "../lib/bulk-limits.mjs";
1
2
  export const CLI_COLLECTION_PREFLIGHT_SCHEMA = 'whoisleuth.cli.collection-preflight';
2
3
  export const CLI_COLLECTION_PREFLIGHT_VERSION = 1;
3
4
  export function buildCollectionPreflight(options) {
@@ -12,7 +13,7 @@ export function buildCollectionPreflight(options) {
12
13
  networkRequestsMade: false,
13
14
  scope: Object.freeze({
14
15
  selectedTargets: options.targetCount,
15
- commandTargetLimit: options.targetLimit,
16
+ commandTargetLimit: bulkQueryLimit(options.deep ? 'deep' : 'fast'),
16
17
  concurrency: options.concurrency,
17
18
  sourceFamilies: Object.freeze(sourceFamilies),
18
19
  exactRequestCountKnown: false,
@@ -53,6 +54,12 @@ export function formatCollectionPreflight(document) {
53
54
  ...document.scope.sourceFamilies.map((source) => ` ${source}`),
54
55
  '',
55
56
  ...document.disclosure.map((item) => `Disclosure: ${item}`),
57
+ '',
58
+ `Output format: ${document.persistence.output}`,
59
+ `Checkpoint: ${document.persistence.checkpoint ? 'enabled' : 'not written'}. ${document.persistence.note}`,
60
+ ...(document.review.allowlistAffectsPriorityOnly ? ['Allowlist: affects review priority only; it does not exclude targets from collection.'] : []),
61
+ 'Enforcement: none. This plan does not report, block or remove a target.',
62
+ '',
56
63
  ...document.limitations.map((item) => `Limitation: ${item}`),
57
64
  '',
58
65
  ].join('\n');
@@ -1,215 +1,13 @@
1
1
  import { CliUsageError } from "./errors.mjs";
2
- import { commandDefinition, } from "./command-reference.mjs";
3
- function humanList(values, conjunction = 'or') {
4
- if (values.length < 2)
5
- return values[0] ?? '';
6
- if (values.length === 2)
7
- return `${values[0]} ${conjunction} ${values[1]}`;
8
- return `${values.slice(0, -1).join(', ')}, ${conjunction} ${values.at(-1)}`;
9
- }
10
- function valueError(specification) {
11
- if (specification.valueKind === 'file') {
12
- return new CliUsageError(`${specification.option} requires one bounded file path.`);
13
- }
14
- if (specification.valueKind === 'integer') {
15
- const baseRange = specification.integerRanges[0];
16
- return new CliUsageError(baseRange
17
- ? `${specification.option} requires an integer from ${baseRange.minimum} to ${baseRange.maximum}.`
18
- : `${specification.option} requires one bounded integer.`);
19
- }
20
- if (specification.values.length > 0) {
21
- return new CliUsageError(`${specification.option} requires ${humanList(specification.values)}.`);
22
- }
23
- return new CliUsageError(`${specification.option} requires one bounded value.`);
24
- }
25
- function parseTokens(command, argv) {
26
- const specificationByOption = new Map(commandDefinition(command).grammar.options.map((specification) => [specification.option, specification]));
27
- const options = new Map();
28
- const positionals = [];
29
- for (let index = 0; index < argv.length; index += 1) {
30
- const argument = argv[index];
31
- if (argument === '--') {
32
- positionals.push(...argv.slice(index + 1));
33
- break;
34
- }
35
- if (!argument.startsWith('-')) {
36
- positionals.push(argument);
37
- continue;
38
- }
39
- const specification = specificationByOption.get(argument);
40
- if (!specification || specification.metaAction !== null) {
41
- throw new CliUsageError(`Unknown option "${argument}".`);
42
- }
43
- const existing = options.get(argument);
44
- if (existing && specification.occurrence === 'once') {
45
- throw new CliUsageError(`${argument} may be supplied only once.`);
46
- }
47
- if (existing && specification.occurrence === 'idempotent')
48
- continue;
49
- let value = '';
50
- if (specification.arity === 1) {
51
- const candidate = argv[++index];
52
- if (candidate === undefined || candidate === ''
53
- || (!specification.acceptsOptionLikeValue && candidate.startsWith('-'))) {
54
- throw valueError(specification);
55
- }
56
- value = candidate;
57
- }
58
- if (existing)
59
- existing.push(value);
60
- else
61
- options.set(argument, [value]);
62
- }
63
- return { options, positionals };
64
- }
65
- function selectedIntegerRange(specification, options) {
66
- return specification.integerRanges
67
- .filter((range) => range.whenOptionPresent === null || options.has(range.whenOptionPresent))
68
- .at(-1) ?? null;
69
- }
70
- function validateOptionValues(command, options) {
71
- for (const specification of commandDefinition(command).grammar.options) {
72
- const values = options.get(specification.option);
73
- if (!values || specification.arity === 0)
74
- continue;
75
- if (specification.valueKind === 'enum') {
76
- if (values.some((value) => !specification.values.includes(value)))
77
- throw valueError(specification);
78
- continue;
79
- }
80
- if (specification.valueKind === 'integer') {
81
- const range = selectedIntegerRange(specification, options);
82
- if (!range)
83
- throw new Error(`Integer option ${command} ${specification.option} has no applicable range.`);
84
- for (const value of values) {
85
- if (!/^\d+$/u.test(value))
86
- throw valueError(specification);
87
- const parsed = Number(value);
88
- if (!Number.isSafeInteger(parsed) || parsed < range.minimum || parsed > range.maximum) {
89
- throw new CliUsageError(`${specification.option} must be from ${range.minimum} to ${range.maximum}.`);
90
- }
91
- }
92
- continue;
93
- }
94
- if (specification.valueKind === 'policy_list') {
95
- for (const value of values) {
96
- const policies = [...new Set(value.split(',').map((item) => item.trim().toLowerCase()).filter(Boolean))];
97
- if (!policies.length || policies.some((policy) => !specification.values.includes(policy))) {
98
- throw new CliUsageError(`${specification.option} for ${command} supports: ${specification.values.join(', ')}.`);
99
- }
100
- }
101
- }
102
- }
103
- }
104
- function allocatePositionals(command, rawPositionals) {
105
- const specifications = commandDefinition(command).grammar.positionals;
106
- const maximum = specifications.reduce((sum, specification) => sum + specification.maximum, 0);
107
- if (rawPositionals.length > maximum) {
108
- throw new CliUsageError(`${command} accepts at most ${maximum} positional value${maximum === 1 ? '' : 's'}.`);
109
- }
110
- const allocated = {};
111
- let offset = 0;
112
- for (const [specificationIndex, specification] of specifications.entries()) {
113
- const remainingMinimum = specifications.slice(specificationIndex + 1)
114
- .reduce((sum, later) => sum + later.minimum, 0);
115
- const available = Math.max(0, rawPositionals.length - offset - remainingMinimum);
116
- const count = Math.min(specification.maximum, available);
117
- const values = Object.freeze(rawPositionals.slice(offset, offset + count));
118
- offset += count;
119
- if (values.length < specification.minimum) {
120
- throw new CliUsageError(`${command} requires ${specification.minimum} ${specification.name} value${specification.minimum === 1 ? '' : 's'}.`);
121
- }
122
- if (specification.valueKind === 'enum'
123
- && values.some((value) => !specification.values.includes(value))) {
124
- throw new CliUsageError(`${command} ${specification.name} must be ${humanList(specification.values)}.`);
125
- }
126
- allocated[specification.name] = values;
127
- }
128
- return Object.freeze(allocated);
129
- }
130
- function present(options, option) {
131
- return options.has(option);
132
- }
133
- function validateConstraint(command, constraint, options) {
134
- if (constraint.kind === 'required') {
135
- const missing = constraint.options.filter((option) => !present(options, option));
136
- if (missing.length > 0)
137
- throw new CliUsageError(`${command} requires ${missing.join(', ')}.`);
138
- return;
139
- }
140
- if (constraint.kind === 'mutually_exclusive') {
141
- const selected = constraint.options.filter((option) => present(options, option));
142
- if (selected.length > 1) {
143
- throw new CliUsageError(`${selected.join(' and ')} are mutually exclusive and may be supplied only once.`);
144
- }
145
- return;
146
- }
147
- if (constraint.kind === 'excludes_all') {
148
- if (!present(options, constraint.option))
149
- return;
150
- const selected = constraint.excludedOptions.filter((option) => present(options, option));
151
- if (selected.length > 0) {
152
- throw new CliUsageError(`${constraint.option} cannot be combined with ${selected.join(', ')}.`);
153
- }
154
- return;
155
- }
156
- if (constraint.kind === 'value_excludes') {
157
- if (!options.get(constraint.option)?.includes(constraint.value))
158
- return;
159
- const selected = constraint.excludedOptions.filter((option) => present(options, option));
160
- if (selected.length > 0) {
161
- throw new CliUsageError(`${constraint.option} ${constraint.value} cannot be combined with ${selected.join(', ')}.`);
162
- }
163
- return;
164
- }
165
- if (!present(options, constraint.option))
166
- return;
167
- const selected = constraint.requiredOptions.filter((option) => present(options, option));
168
- if (constraint.kind === 'requires_all' && selected.length !== constraint.requiredOptions.length) {
169
- throw new CliUsageError(`${constraint.option} requires ${constraint.requiredOptions.join(' and ')}.`);
170
- }
171
- if (constraint.kind === 'requires_any' && selected.length === 0) {
172
- throw new CliUsageError(`${constraint.option} requires ${constraint.requiredOptions.join(' or ')}.`);
173
- }
174
- }
175
- function validatePositionalRequirements(command, allocated, options) {
176
- for (const specification of commandDefinition(command).grammar.positionals) {
177
- if (specification.requiredWhenOptions.some((option) => options.has(option))
178
- && (allocated[specification.name]?.length ?? 0) === 0) {
179
- throw new CliUsageError(`${specification.requiredWhenOptions.join(' or ')} requires a positional ${specification.name}.`);
180
- }
181
- }
182
- }
183
- function freezeOptions(options) {
184
- return new Map([...options].map(([option, values]) => [option, Object.freeze([...values])]));
185
- }
2
+ import { commandDefinition } from "./command-reference.mjs";
3
+ import { CliGrammarError, parseCliGrammar } from "../packages/analysis/cli-argument-grammar.mjs";
186
4
  export function parseCommandArguments(command, argv) {
187
- const parsed = parseTokens(command, argv);
188
- const options = freezeOptions(parsed.options);
189
- validateOptionValues(command, options);
190
- const allocated = allocatePositionals(command, parsed.positionals);
191
- validatePositionalRequirements(command, allocated, options);
192
- for (const constraint of commandDefinition(command).grammar.constraints) {
193
- validateConstraint(command, constraint, options);
5
+ try {
6
+ return parseCliGrammar(command, commandDefinition(command).grammar, argv);
194
7
  }
195
- for (const option of ['--config', '--profile']) {
196
- const specification = commandDefinition(command).grammar.options.find((candidate) => candidate.option === option);
197
- if (specification?.scope === 'common' && options.has(option)) {
198
- throw new CliUsageError(`${option} must be resolved before command parsing.`);
199
- }
8
+ catch (cause) {
9
+ if (cause instanceof CliGrammarError)
10
+ throw new CliUsageError(cause.message);
11
+ throw cause;
200
12
  }
201
- const allPositionals = Object.freeze([...parsed.positionals]);
202
- return Object.freeze({
203
- command,
204
- allPositionals,
205
- hasOption: (option) => options.has(option),
206
- optionValue: (option) => options.get(option)?.at(-1) ?? null,
207
- optionValues: (option) => options.get(option) ?? Object.freeze([]),
208
- integerOption: (option) => {
209
- const value = options.get(option)?.at(-1);
210
- return value === undefined ? null : Number(value);
211
- },
212
- positionalValue: (name) => allocated[name]?.[0] ?? null,
213
- positionalValues: (name) => allocated[name] ?? Object.freeze([]),
214
- });
215
13
  }