@slicedearth/whoisleuth-cli 2.3.0 → 2.5.1

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 (418) hide show
  1. package/NOTICE +8 -2
  2. package/README.md +19 -15
  3. package/bin/whoisleuth.mjs +8 -1
  4. package/cli/archive-content-digest.mjs +14 -0
  5. package/cli/archive-inspect.mjs +20 -53
  6. package/cli/argument-values.mjs +55 -0
  7. package/cli/arguments.mjs +24 -408
  8. package/cli/artifact-validation/case-response.mjs +65 -44
  9. package/cli/artifact-validation/investigation-capsule.mjs +52 -31
  10. package/cli/artifact-validation/lookup-evidence.mjs +15 -9
  11. package/cli/artifact-validation/signed-review.mjs +136 -68
  12. package/cli/artifact-validation/structure-primitives.mjs +1 -105
  13. package/cli/artifact-verify.mjs +92 -24
  14. package/cli/assurance-arguments.mjs +56 -0
  15. package/cli/assurance-command-definitions.mjs +179 -0
  16. package/cli/assurance-command-runner.mjs +40 -5
  17. package/cli/bagit-folder.mjs +57 -0
  18. package/cli/bagit-review.mjs +23 -0
  19. package/cli/bounded-json.mjs +1 -1
  20. package/cli/bulk-checkpoint.mjs +1 -1
  21. package/cli/bulk-command-runner.mjs +4 -2
  22. package/cli/bulk-output.mjs +29 -3
  23. package/cli/bulk.mjs +2 -3
  24. package/cli/case-command.mjs +212 -0
  25. package/cli/case-pack.mjs +50 -32
  26. package/cli/ci-report.mjs +6 -3
  27. package/cli/collection-arguments.mjs +123 -0
  28. package/cli/collection-command-definitions.mjs +330 -0
  29. package/cli/collection-preflight.mjs +8 -1
  30. package/cli/command-argument-grammar.mjs +8 -206
  31. package/cli/command-definition.mjs +329 -0
  32. package/cli/command-reference.mjs +94 -1357
  33. package/cli/command-seeds.mjs +18 -0
  34. package/cli/compare.mjs +1 -1
  35. package/cli/completion.mjs +298 -79
  36. package/cli/config-profile.mjs +8 -6
  37. package/cli/ct-event-intake.mjs +16 -16
  38. package/cli/discover.mjs +5 -4
  39. package/cli/discovery-observation-snapshot.mjs +46 -18
  40. package/cli/discovery-scan-command-runner.mjs +5 -3
  41. package/cli/discovery-scan.mjs +7 -4
  42. package/cli/discovery-snapshot.mjs +2 -2
  43. package/cli/discovery-workflow.mjs +5 -5
  44. package/cli/domain-control-monitor.mjs +50 -20
  45. package/cli/domain-control-observations.mjs +151 -103
  46. package/cli/errors.mjs +37 -6
  47. package/cli/evidence-arguments.mjs +46 -0
  48. package/cli/evidence-command-definitions.mjs +69 -0
  49. package/cli/evidence-command-runner.mjs +18 -8
  50. package/cli/evidence-signing.mjs +5 -4
  51. package/cli/external-observation-mapping.mjs +9 -10
  52. package/cli/formatters/terminal-command-formats.mjs +6 -0
  53. package/cli/formatters/terminal-lookup.mjs +24 -46
  54. package/cli/formatters/terminal-shared.mjs +5 -3
  55. package/cli/formatters/terminal-technology.mjs +114 -0
  56. package/cli/history-arguments.mjs +39 -0
  57. package/cli/history-command-definitions.mjs +88 -0
  58. package/cli/indicator-set-command.mjs +33 -0
  59. package/cli/inline-command-families.mjs +6 -46
  60. package/cli/inline-command-runner.mjs +4 -31
  61. package/cli/input.mjs +2 -2
  62. package/cli/intake-command.mjs +107 -0
  63. package/cli/interactive-launcher.mjs +2 -160
  64. package/cli/interchange-report.mjs +8 -10
  65. package/cli/investigation-artifacts.mjs +176 -0
  66. package/cli/investigation-checkpoint.mjs +10 -0
  67. package/cli/investigation-folder.mjs +114 -0
  68. package/cli/investigation-manifest.mjs +1 -112
  69. package/cli/investigation-package-review.mjs +103 -0
  70. package/cli/investigation-plan.mjs +38 -147
  71. package/cli/investigation-recipes.mjs +177 -0
  72. package/cli/investigation-run.mjs +256 -35
  73. package/cli/local-document-checkpoint.mjs +170 -0
  74. package/cli/local-mmdb-review.mjs +197 -17
  75. package/cli/lookalike-calibration.mjs +1 -1
  76. package/cli/lookup-brief.mjs +2 -2
  77. package/cli/lookup-browser.mjs +5 -12
  78. package/cli/lookup-command-runner.mjs +33 -39
  79. package/cli/lookup-diff.mjs +47 -12
  80. package/cli/lookup-plan.mjs +16 -14
  81. package/cli/lookup-reconcile.mjs +15 -12
  82. package/cli/lookup-timeline.mjs +7 -6
  83. package/cli/mail-header-review.mjs +24 -247
  84. package/cli/mail-review.mjs +75 -21
  85. package/cli/manual.mjs +1 -0
  86. package/cli/network-arguments.mjs +51 -0
  87. package/cli/network-command-definitions.mjs +130 -0
  88. package/cli/network-command-runner.mjs +45 -31
  89. package/cli/offline-evidence-review.mjs +39 -6
  90. package/cli/output-file.mjs +31 -5
  91. package/cli/posture.mjs +1 -1
  92. package/cli/registry-doctor.mjs +3 -1
  93. package/cli/registry-fixture-scaffold.mjs +1 -1
  94. package/cli/registry-support.mjs +1 -1
  95. package/cli/retained-artifact-diff.mjs +10 -9
  96. package/cli/review-arguments.mjs +131 -0
  97. package/cli/review-command-definitions.mjs +273 -0
  98. package/cli/review-command-runner.mjs +104 -149
  99. package/cli/risk-calibration.mjs +8 -8
  100. package/cli/runner.mjs +74 -37
  101. package/cli/saved-lookup.mjs +2 -2
  102. package/cli/selected-input-worker.mjs +63 -0
  103. package/cli/sharing-review.mjs +8 -80
  104. package/cli/signer-trust.mjs +104 -0
  105. package/cli/source-reliability.mjs +75 -84
  106. package/cli/support-arguments.mjs +49 -0
  107. package/cli/support-command-definitions.mjs +209 -0
  108. package/cli/support-command-runner.mjs +2 -2
  109. package/cli/terminal-input.mjs +168 -0
  110. package/cli/workflow-arguments.mjs +86 -0
  111. package/cli/workflow-command-definitions.mjs +78 -0
  112. package/cli/workflow-command-runner.mjs +75 -13
  113. package/docs/cli-reference.md +103 -30
  114. package/docs/cli.md +510 -25
  115. package/docs/contextual-reviews.md +155 -0
  116. package/lib/abort.mjs +4 -1
  117. package/lib/analyst-taxonomy.mjs +2 -40
  118. package/lib/application-version.mjs +1 -1
  119. package/lib/availability.mjs +27 -754
  120. package/lib/bounded-contract-normalizers.mjs +4 -0
  121. package/lib/bounded-file.mjs +3 -1
  122. package/lib/bounded-html-document.mjs +125 -0
  123. package/lib/bounded-json.mjs +2 -290
  124. package/lib/browser-library-profile.mjs +99 -130
  125. package/lib/browser-library-worker.mjs +112 -0
  126. package/lib/candidate-provenance-bounds.mjs +2 -0
  127. package/lib/classify.mjs +1 -1
  128. package/lib/client-behavior-profile.mjs +3 -3
  129. package/lib/ct-query.mjs +2 -27
  130. package/lib/ct-response-bounds.mjs +2 -9
  131. package/lib/ct-search.mjs +89 -46
  132. package/lib/dmarc-discovery.mjs +62 -0
  133. package/lib/dns-convergence-review.mjs +21 -8
  134. package/lib/dns-delegation-health.mjs +36 -22
  135. package/lib/dns-inheritance-review.mjs +207 -0
  136. package/lib/dns-intelligence.mjs +44 -29
  137. package/lib/dns-mx.mjs +1 -1
  138. package/lib/dns-query-coverage.mjs +40 -0
  139. package/lib/dnssec-evidence-validation.mjs +11 -3
  140. package/lib/domain-assurance.mjs +1 -1
  141. package/lib/domain-change-packet.mjs +18 -14
  142. package/lib/domain-change-review.mjs +40 -16
  143. package/lib/domain-control-flight-recorder.mjs +301 -124
  144. package/lib/domain-control-manifest.mjs +126 -58
  145. package/lib/domain-posture-analysis.mjs +41 -11
  146. package/lib/domain-posture-parsers.mjs +88 -6
  147. package/lib/domain-posture.mjs +120 -32
  148. package/lib/domain-registration.mjs +392 -0
  149. package/lib/evidence-export-privacy.mjs +568 -0
  150. package/lib/evidence-export.mjs +12 -593
  151. package/lib/evidence-report.mjs +7 -2
  152. package/lib/external-intelligence-risk.mjs +2 -124
  153. package/lib/favicon.mjs +117 -41
  154. package/lib/generated/cisa-kev-catalog.mjs +50 -4
  155. package/lib/generated/registrar-standing-catalogue.mjs +18 -10
  156. package/lib/generated/retire-browser-catalog.mjs +2742 -1034
  157. package/lib/generated/{unicode-confusables-17.mjs → unicode-confusables.mjs} +33 -33
  158. package/lib/html-signals.mjs +87 -85
  159. package/lib/http-evidence-bounds.mjs +2 -7
  160. package/lib/http-intelligence.mjs +52 -27
  161. package/lib/idn-confusable-policy.mjs +6 -4
  162. package/lib/idn-confusables.mjs +1 -1
  163. package/lib/interchange-fidelity-registry.mjs +10 -0
  164. package/lib/lookup-cache.mjs +34 -11
  165. package/lib/lookup-child-profile-contract.mjs +24 -15
  166. package/lib/lookup-contract-primitives.mjs +1 -1
  167. package/lib/lookup-diagnostics.mjs +2 -1
  168. package/lib/lookup-network-evidence-bounds.mjs +1 -10
  169. package/lib/lookup-progress.mjs +79 -13
  170. package/lib/lookup-response-contract.mjs +1077 -0
  171. package/lib/lookup-response.mjs +11 -24
  172. package/lib/lookup-source-progress.mjs +24 -35
  173. package/lib/lookup.mjs +49 -28
  174. package/lib/observed-network-context.mjs +43 -27
  175. package/lib/opportunity-scoring.mjs +2 -107
  176. package/lib/outbound-identity.mjs +1 -1
  177. package/lib/outbound-request-bounds.mjs +4 -1
  178. package/lib/page-fingerprints.mjs +87 -134
  179. package/lib/page-role-profile.mjs +12 -8
  180. package/lib/perceptual-hash-comparison.mjs +2 -34
  181. package/lib/perceptual-hash.mjs +79 -24
  182. package/lib/portable-generator.mjs +2 -29
  183. package/lib/project-metadata.mjs +2 -8
  184. package/lib/rdap-bootstrap.mjs +72 -22
  185. package/lib/rdap-client.mjs +26 -14
  186. package/lib/rdap-normalization.mjs +33 -18
  187. package/lib/rdap-search-workbench.mjs +1 -1
  188. package/lib/rdap-transport.mjs +47 -20
  189. package/lib/rdap-types.mjs +2 -1
  190. package/lib/rdap.mjs +18 -3
  191. package/lib/registrable-domain.mjs +2 -36
  192. package/lib/registrar-standing-contract.mjs +2 -1
  193. package/lib/registrar-standing.mjs +9 -13
  194. package/lib/registry-capabilities.mjs +7 -3
  195. package/lib/registry-capability-catalogue.mjs +29 -20
  196. package/lib/registry-dates.mjs +2 -190
  197. package/lib/registry-insights.mjs +56 -18
  198. package/lib/response-policy.mjs +86 -48
  199. package/lib/risk-calibration-summary.mjs +2 -338
  200. package/lib/risk-scoring.mjs +2 -345
  201. package/lib/rpki-evidence.mjs +8 -5
  202. package/lib/safe-fetch.mjs +54 -47
  203. package/lib/scoring-evidence-quality.mjs +2 -107
  204. package/lib/security-txt.mjs +10 -7
  205. package/lib/semantic-version.mjs +2 -54
  206. package/lib/service-binding-dns.mjs +35 -0
  207. package/lib/smtp-transport-review.mjs +75 -34
  208. package/lib/sslbl-certificates.generated.mjs +141 -138
  209. package/lib/static-html-analysis.mjs +117 -146
  210. package/lib/structured-data-identity.mjs +2 -1
  211. package/lib/technology-markup.mjs +100 -0
  212. package/lib/threat-intelligence-manifests.mjs +11 -139
  213. package/lib/threat-intelligence-runtime.mjs +24 -52
  214. package/lib/threat-intelligence-targets.mjs +6 -34
  215. package/lib/threat-intelligence-types.mjs +2 -36
  216. package/lib/typosquat-generator.mjs +29 -16
  217. package/lib/urlhaus-intelligence.mjs +8 -3
  218. package/lib/urlscan-intelligence.mjs +1 -1
  219. package/lib/website-enrichment.mjs +388 -0
  220. package/lib/website-security-posture.mjs +30 -6
  221. package/lib/website-technology.mjs +111 -71
  222. package/lib/whois-authority.mjs +55 -5
  223. package/lib/whois-chain.mjs +9 -2
  224. package/lib/whois-common-formats.mjs +5 -5
  225. package/lib/whois-contracts.mjs +2 -1
  226. package/lib/whois-dialects.mjs +2 -2
  227. package/lib/whois-normalization.mjs +3 -1
  228. package/lib/whois-parser-support.mjs +8 -1
  229. package/lib/whois-parser.mjs +5 -3
  230. package/lib/whois-transport.mjs +23 -7
  231. package/package.json +14 -14
  232. package/packages/analysis/analyst-taxonomy.mjs +40 -0
  233. package/packages/analysis/bounded-json.mjs +330 -0
  234. package/packages/analysis/candidate-provenance-bounds.mjs +4 -0
  235. package/packages/analysis/cli-argument-grammar.mjs +216 -0
  236. package/packages/analysis/ct-query.mjs +27 -0
  237. package/packages/analysis/ct-response-bounds.mjs +9 -0
  238. package/packages/analysis/external-intelligence-risk.mjs +124 -0
  239. package/packages/analysis/http-evidence-bounds.mjs +8 -0
  240. package/packages/analysis/lookup-network-evidence-bounds.mjs +10 -0
  241. package/packages/analysis/opportunity-scoring.mjs +107 -0
  242. package/packages/analysis/perceptual-hash-comparison.mjs +34 -0
  243. package/packages/analysis/portable-generator.mjs +29 -0
  244. package/packages/analysis/project-metadata.mjs +8 -0
  245. package/packages/analysis/registrable-domain.mjs +56 -0
  246. package/packages/analysis/risk-calibration-summary.mjs +352 -0
  247. package/packages/analysis/risk-scoring.mjs +345 -0
  248. package/packages/analysis/scoring-evidence-quality.mjs +107 -0
  249. package/packages/analysis/semantic-version.mjs +54 -0
  250. package/packages/analysis/threat-intelligence-types.mjs +75 -0
  251. package/packages/cases/case-attachment-model.mjs +83 -0
  252. package/packages/cases/case-evidence-links.mjs +135 -0
  253. package/packages/cases/case-evidence-model.mjs +229 -69
  254. package/packages/cases/case-evidence-quality.mjs +5 -0
  255. package/packages/cases/case-export-input.mjs +55 -0
  256. package/packages/cases/case-incident-context.mjs +52 -0
  257. package/packages/cases/case-markdown.mjs +11 -0
  258. package/packages/cases/case-migration-model.mjs +98 -48
  259. package/packages/cases/case-model.mjs +1 -0
  260. package/packages/cases/case-recheck-model.mjs +149 -0
  261. package/packages/cases/case-record-contracts.mjs +1 -1
  262. package/packages/cases/case-record-core.mjs +4 -3
  263. package/packages/cases/case-record-operations.mjs +123 -133
  264. package/packages/cases/case-record-projection.mjs +30 -20
  265. package/packages/cases/case-record-version-input.mjs +19 -0
  266. package/packages/cases/case-report.mjs +114 -67
  267. package/packages/cases/case-requested-evidence.mjs +146 -0
  268. package/packages/cases/case-response-actions.mjs +756 -0
  269. package/packages/cases/case-response-model.mjs +49 -1129
  270. package/packages/cases/case-response-outcomes.mjs +338 -0
  271. package/packages/cases/case-response-packet-render.mjs +157 -0
  272. package/packages/cases/case-response-packet-types.mjs +1 -0
  273. package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
  274. package/packages/cases/case-response-packet.mjs +58 -593
  275. package/packages/cases/case-response-records.mjs +93 -0
  276. package/packages/cases/case-response-review-inputs.mjs +382 -0
  277. package/packages/cases/case-response-values.mjs +85 -0
  278. package/packages/cases/case-review-package.mjs +45 -0
  279. package/packages/cases/case-selection.mjs +46 -0
  280. package/packages/cases/case-storage-model.mjs +26 -1
  281. package/packages/cases/case-workflow-metadata.mjs +225 -0
  282. package/packages/cases/case-workflow-migration.mjs +43 -0
  283. package/packages/cases/http-summary.mjs +6 -1
  284. package/packages/cases/response-route-freshness.mjs +16 -0
  285. package/packages/comparison/capture-context.mjs +61 -0
  286. package/packages/comparison/comparison-ledger-bulk.mjs +4 -1
  287. package/packages/comparison/favicon-similarity.mjs +1 -1
  288. package/packages/comparison/page-similarity.mjs +3 -2
  289. package/packages/comparison/relationship-evidence.mjs +103 -14
  290. package/packages/comparison/relationship-provenance.mjs +99 -0
  291. package/packages/contracts/analyst-interchange.mjs +234 -0
  292. package/packages/contracts/analyst-review-state-contract.mjs +11 -0
  293. package/packages/contracts/analyst-review-state.mjs +5 -4
  294. package/packages/contracts/case-portability.mjs +370 -114
  295. package/packages/contracts/case-views-contract.mjs +12 -0
  296. package/packages/contracts/case-views.mjs +26 -0
  297. package/packages/contracts/cli-command-semantics.mjs +3 -0
  298. package/packages/contracts/cli-grammar.mjs +6 -0
  299. package/packages/contracts/cli-lookup-brief.mjs +2 -0
  300. package/packages/contracts/cli-lookup.mjs +0 -1
  301. package/packages/contracts/context-review.mjs +24 -0
  302. package/packages/contracts/document-intake.mjs +11 -0
  303. package/packages/contracts/domain-control-flight-recorder.mjs +98 -44
  304. package/packages/contracts/domain-control-manifest.mjs +128 -74
  305. package/packages/contracts/domain-control-monitor.mjs +59 -45
  306. package/packages/contracts/domain-control-review.mjs +200 -121
  307. package/packages/contracts/domain-name.mjs +21 -0
  308. package/packages/contracts/external-observation-interchange.mjs +2 -1
  309. package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
  310. package/packages/contracts/har-review.mjs +2 -0
  311. package/packages/contracts/http-url.mjs +2 -0
  312. package/packages/contracts/identity-events.mjs +4 -0
  313. package/packages/contracts/investigation-package-limits.mjs +24 -0
  314. package/packages/contracts/investigation-portability.mjs +56 -27
  315. package/packages/contracts/investigation-run.mjs +18 -0
  316. package/packages/contracts/ip-address.mjs +44 -0
  317. package/packages/contracts/lookup-evidence.mjs +35 -0
  318. package/packages/contracts/mail-authentication.mjs +5 -0
  319. package/packages/contracts/message-intake.mjs +31 -0
  320. package/packages/contracts/offline-comparison.mjs +4 -2
  321. package/packages/contracts/page-fingerprints.mjs +15 -0
  322. package/packages/contracts/passive-mail.mjs +10 -0
  323. package/packages/contracts/risk-calibration.mjs +0 -1
  324. package/packages/contracts/schema-compatibility.mjs +1 -1
  325. package/packages/contracts/schema-lifecycle.mjs +83 -153
  326. package/packages/contracts/selected-file-limits.mjs +6 -0
  327. package/packages/contracts/vulnerability-identifiers.mjs +5 -0
  328. package/packages/contracts/web-capture.mjs +10 -0
  329. package/packages/contracts/workspace-portability.mjs +219 -238
  330. package/packages/evidence/artifact-integrity.mjs +8 -2
  331. package/packages/evidence/artifact-structure.mjs +105 -0
  332. package/packages/evidence/collection-quality.mjs +67 -0
  333. package/packages/evidence/decision-fact.mjs +4 -1
  334. package/packages/evidence/dns-query-outcome.mjs +72 -0
  335. package/packages/evidence/domain-control-runtime.mjs +155 -27
  336. package/packages/evidence/domain-posture-context.mjs +79 -0
  337. package/packages/evidence/email-recipient.mjs +18 -0
  338. package/packages/evidence/image-regions.mjs +56 -0
  339. package/packages/evidence/latest-observations.mjs +28 -0
  340. package/packages/evidence/lookup-target.mjs +108 -0
  341. package/packages/evidence/observation.mjs +18 -1
  342. package/packages/evidence/passphrase-encryption.mjs +44 -0
  343. package/packages/evidence/png-pixels.mjs +117 -0
  344. package/packages/evidence/public-address-policy.mjs +83 -0
  345. package/packages/evidence/record-identity.mjs +72 -0
  346. package/packages/evidence/registry-dates.mjs +190 -0
  347. package/packages/evidence/retained-file.mjs +69 -0
  348. package/packages/evidence/review-age.mjs +14 -0
  349. package/packages/interchange/bagit.mjs +342 -0
  350. package/packages/interchange/bounded-zip-extraction.mjs +278 -0
  351. package/packages/interchange/crc32.mjs +17 -0
  352. package/packages/interchange/defensive-indicator-export.mjs +264 -0
  353. package/packages/interchange/external-findings-import.mjs +93 -61
  354. package/packages/interchange/managed-indicator-export.mjs +35 -0
  355. package/packages/interchange/managed-indicator-set.mjs +210 -0
  356. package/packages/interchange/misp-indicator-export.mjs +93 -0
  357. package/packages/interchange/sharing-policy.mjs +56 -0
  358. package/packages/interchange/stix-indicator-export.mjs +129 -0
  359. package/packages/interchange/web-capture-import.mjs +389 -0
  360. package/packages/investigation/acquisition-decision-packet.mjs +1 -1
  361. package/packages/investigation/bulk-mail-exposure.mjs +9 -14
  362. package/packages/investigation/capture-coverage.mjs +57 -0
  363. package/packages/investigation/connector-provenance-review.mjs +100 -0
  364. package/packages/investigation/context-review.mjs +35 -0
  365. package/packages/investigation/document-intake.mjs +81 -0
  366. package/packages/investigation/docx-intake.mjs +133 -0
  367. package/packages/investigation/domain-history-review.mjs +100 -0
  368. package/packages/investigation/domain-transition-review.mjs +47 -0
  369. package/packages/investigation/evidence-coverage-ledger.mjs +9 -1
  370. package/packages/investigation/har-intake.mjs +68 -0
  371. package/packages/investigation/identity-event-intake.mjs +131 -0
  372. package/packages/investigation/identity-incident-review.mjs +39 -0
  373. package/packages/investigation/incident-sequence-review.mjs +58 -0
  374. package/packages/investigation/intake-report.mjs +19 -0
  375. package/packages/investigation/investigation-bagit.mjs +14 -0
  376. package/packages/investigation/investigation-capsule.mjs +33 -5
  377. package/packages/investigation/investigation-manifest.mjs +200 -0
  378. package/packages/investigation/investigation-package-crypto.mjs +98 -0
  379. package/packages/investigation/investigation-package.mjs +200 -0
  380. package/packages/investigation/link-intake.mjs +179 -0
  381. package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
  382. package/packages/investigation/lookup-asset-graph.mjs +161 -141
  383. package/packages/investigation/lookup-claim-readiness.mjs +18 -0
  384. package/packages/investigation/lookup-investigation-brief.mjs +3 -3
  385. package/packages/investigation/lookup-task-guidance.mjs +58 -0
  386. package/packages/investigation/mail-authentication-review.mjs +222 -0
  387. package/packages/investigation/mail-header-identity.mjs +209 -0
  388. package/packages/investigation/message-intake.mjs +195 -0
  389. package/packages/investigation/page-behaviour.mjs +113 -0
  390. package/packages/investigation/pdf-intake.mjs +155 -0
  391. package/packages/investigation/platform-continuity-review.mjs +60 -0
  392. package/packages/investigation/qr-intake.mjs +56 -0
  393. package/packages/investigation/requested-action-hints.mjs +11 -0
  394. package/packages/investigation/selected-input-review.mjs +17 -0
  395. package/packages/investigation/storefront-review.mjs +70 -0
  396. package/packages/monitoring/analyst-review-state.mjs +38 -90
  397. package/packages/workspace/brand-profile-model.mjs +160 -22
  398. package/packages/workspace/bulk-columns.mjs +17 -0
  399. package/packages/workspace/bulk-review-model.mjs +39 -31
  400. package/packages/workspace/bulk-session-model.mjs +112 -27
  401. package/packages/workspace/bulk-sort.mjs +1 -0
  402. package/packages/workspace/campaign-model.mjs +1 -1
  403. package/packages/workspace/case-views.mjs +102 -0
  404. package/packages/workspace/detection-rule-model.mjs +1 -1
  405. package/packages/workspace/hostile-input.mjs +29 -8
  406. package/packages/workspace/investigation-guide.mjs +2 -2
  407. package/packages/workspace/investigation-template-model.mjs +45 -6
  408. package/packages/workspace/page-baseline.mjs +20 -17
  409. package/packages/workspace/relationship-observation-model.mjs +54 -35
  410. package/packages/workspace/shortlist-model.mjs +14 -5
  411. package/packages/workspace/watchlist-history.mjs +30 -14
  412. package/packages/workspace/watchlist-store.mjs +24 -6
  413. package/packages/workspace/website-snapshot-model.mjs +98 -33
  414. package/packages/workspace/workspace-archive-crypto.mjs +41 -61
  415. package/packages/workspace/workspace-archive.mjs +90 -25
  416. package/third-party-notices.txt +577 -17
  417. package/lib/hostname.mjs +0 -38
  418. package/packages/investigation/bulk-review-export.mjs +0 -63
@@ -0,0 +1,209 @@
1
+ import { RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_REPORT_SCHEMA } from "../packages/contracts/risk-calibration.mjs";
2
+ import { positional, NO_POSITIONALS, OPTIONAL_FILE_POSITIONAL, constraint, EMPTY_CONSTRAINTS, commandSeed, text } from "./command-definition.mjs";
3
+ export const SUPPORT_COMMAND_DEFINITIONS = Object.freeze({
4
+ completion: commandSeed({
5
+ reference: {
6
+ description: 'Print a static shell-completion script for the installed CLI.',
7
+ example: 'whoisleuth completion zsh > ~/.zfunc/_whoisleuth',
8
+ boundary: 'Generation is offline and writes only the script to stdout. The command never modifies shell configuration.',
9
+ },
10
+ collection: { scope: 'Prints one static script and changes no shell configuration.' },
11
+ summary: 'Print shell completion',
12
+ options: [],
13
+ positionals: Object.freeze([positional('shell', 'enum', 1, 1, ['bash', 'zsh', 'fish', 'powershell'])]),
14
+ constraints: EMPTY_CONSTRAINTS,
15
+ handlerOwner: 'support',
16
+ networkEffect: 'offline',
17
+ common: false,
18
+ schemaIdentifiers: Object.freeze([]),
19
+ primaryArtefacts: Object.freeze([]),
20
+ planSupport: false,
21
+ additionalOutputFormats: Object.freeze([]),
22
+ bootstrapProfile: 'allowed',
23
+ }),
24
+ doctor: commandSeed({
25
+ reference: {
26
+ description: 'Check the supported runtime and local terminal capabilities.',
27
+ example: 'whoisleuth doctor --json',
28
+ boundary: 'The default check is offline. Public DNS and port 43 checks run only when --network is explicitly supplied.',
29
+ },
30
+ collection: { scope: 'Network access is opt-in with --network and is limited to fixed public DNS, HTTPS, and WHOIS diagnostics.' },
31
+ summary: 'Check the local CLI runtime',
32
+ options: ['--network', '--json', '--quiet', '--no-color'],
33
+ positionals: NO_POSITIONALS,
34
+ constraints: EMPTY_CONSTRAINTS,
35
+ handlerOwner: 'support',
36
+ networkEffect: 'conditional_network',
37
+ common: true,
38
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.doctor']),
39
+ primaryArtefacts: Object.freeze([]),
40
+ planSupport: false,
41
+ additionalOutputFormats: Object.freeze([]),
42
+ bootstrapProfile: 'allowed',
43
+ }),
44
+ commands: commandSeed({
45
+ reference: {
46
+ description: 'List the installed command contracts in terminal or versioned JSON form.',
47
+ example: 'whoisleuth commands --json',
48
+ boundary: 'Catalogue generation is offline. It reports declared command modes and limits without executing collection or inspecting local evidence.',
49
+ },
50
+ collection: { scope: 'Reads the embedded command catalogue and performs no collection.' },
51
+ summary: 'List installed command contracts',
52
+ options: ['--common', '--group', '--mode', '--json', '--quiet', '--no-color'],
53
+ positionals: NO_POSITIONALS,
54
+ constraints: Object.freeze([]),
55
+ handlerOwner: 'support',
56
+ networkEffect: 'offline',
57
+ common: true,
58
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.command-catalogue']),
59
+ primaryArtefacts: Object.freeze([]),
60
+ planSupport: false,
61
+ additionalOutputFormats: Object.freeze([]),
62
+ bootstrapProfile: 'allowed',
63
+ }),
64
+ manual: commandSeed({
65
+ reference: {
66
+ description: 'Print a generated roff manual page for local installation.',
67
+ example: 'whoisleuth manual | man -l -',
68
+ boundary: 'Generation is offline and derives from the same command catalogue as focused help.',
69
+ },
70
+ collection: { scope: 'Builds documentation from the embedded command catalogue.' },
71
+ summary: 'Print the generated manual page',
72
+ options: [],
73
+ positionals: NO_POSITIONALS,
74
+ constraints: EMPTY_CONSTRAINTS,
75
+ handlerOwner: 'support',
76
+ networkEffect: 'offline',
77
+ common: false,
78
+ schemaIdentifiers: Object.freeze([]),
79
+ primaryArtefacts: Object.freeze([]),
80
+ planSupport: false,
81
+ additionalOutputFormats: Object.freeze([]),
82
+ bootstrapProfile: 'allowed',
83
+ }),
84
+ "registry-support": commandSeed({
85
+ reference: {
86
+ description: 'Explain the local registry capability profile for one domain or suffix.',
87
+ example: 'whoisleuth registry-support example.test --json',
88
+ boundary: 'This command is offline. Catalogue coverage does not test live reachability or decide registration or availability.',
89
+ },
90
+ collection: { scope: 'Reads the embedded registry capability catalogue for one domain or suffix.' },
91
+ summary: 'Explain local registry coverage',
92
+ options: ['--json', '--quiet', '--no-color'],
93
+ positionals: Object.freeze([positional('domain-or-suffix', 'text', 0, 1, [], 'argv_or_stdin')]),
94
+ constraints: EMPTY_CONSTRAINTS,
95
+ handlerOwner: 'support',
96
+ networkEffect: 'offline',
97
+ common: false,
98
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.registry-support', 'whoisleuth.registry-standards-coverage']),
99
+ primaryArtefacts: Object.freeze([]),
100
+ planSupport: false,
101
+ additionalOutputFormats: Object.freeze([]),
102
+ bootstrapProfile: 'allowed',
103
+ }),
104
+ "registry-doctor": commandSeed({
105
+ reference: {
106
+ description: 'Compare a saved Lookup registry result with the reviewed local capability profile.',
107
+ example: 'whoisleuth registry-doctor lookup.json --json',
108
+ boundary: 'The command is offline. It distinguishes expected access constraints from collection results and does not contact a live registry.',
109
+ },
110
+ collection: { scope: 'Reads one saved Lookup and the embedded registry capability catalogue.' },
111
+ summary: 'Diagnose saved registry collection',
112
+ options: ['--json', '--quiet', '--no-color'],
113
+ positionals: OPTIONAL_FILE_POSITIONAL,
114
+ constraints: EMPTY_CONSTRAINTS,
115
+ handlerOwner: 'support',
116
+ networkEffect: 'offline',
117
+ common: false,
118
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.registry-doctor']),
119
+ primaryArtefacts: Object.freeze([]),
120
+ planSupport: false,
121
+ additionalOutputFormats: Object.freeze([]),
122
+ bootstrapProfile: 'allowed',
123
+ }),
124
+ "registry-cohort": commandSeed({
125
+ reference: {
126
+ description: 'Build privacy-safe suffix and capability-profile timelines from saved observations or retained cohort reports.',
127
+ example: 'whoisleuth registry-cohort saved-lookups.jsonl --json',
128
+ boundary: 'This command is offline and omits domains, queries, and raw evidence. Input families cannot be mixed, and retained samples are never assumed independent.',
129
+ },
130
+ collection: { scope: 'Reads at most 500 saved Lookups or retained cohort reports from one unmixed family and emits bounded target-free timelines.' },
131
+ summary: 'Build target-free registry quality timelines',
132
+ options: ['--json', '--quiet', '--no-color'],
133
+ positionals: OPTIONAL_FILE_POSITIONAL,
134
+ constraints: EMPTY_CONSTRAINTS,
135
+ handlerOwner: 'support',
136
+ networkEffect: 'offline',
137
+ common: false,
138
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.registry-cohort']),
139
+ primaryArtefacts: Object.freeze([]),
140
+ planSupport: false,
141
+ additionalOutputFormats: Object.freeze([]),
142
+ bootstrapProfile: 'allowed',
143
+ }),
144
+ "registry-scaffold": commandSeed({
145
+ reference: {
146
+ description: 'Create a bounded synthetic WHOIS fixture scaffold for one existing capability profile.',
147
+ example: 'whoisleuth registry-scaffold --profile nic-io-colon --suffix ac --scenario registered',
148
+ boundary: 'The output is a sanitised template only. Its command-owned --profile selects fixture capability, --config is rejected, and contributors must not paste live responses or personal registration data into fixtures.',
149
+ },
150
+ collection: { scope: 'Reads the embedded registry capability catalogue and prints one synthetic fixture template.' },
151
+ summary: 'Create a sanitised registry fixture scaffold',
152
+ options: ['--profile', '--suffix', '--scenario'],
153
+ optionOverrides: { '--profile': text('Select a catalogue profile; find its ID with registry-support <suffix> --json.', true) },
154
+ positionals: NO_POSITIONALS,
155
+ constraints: Object.freeze([
156
+ constraint({ kind: 'required', options: ['--profile', '--suffix', '--scenario'] }),
157
+ ]),
158
+ handlerOwner: 'support',
159
+ networkEffect: 'offline',
160
+ common: false,
161
+ schemaIdentifiers: Object.freeze([]),
162
+ primaryArtefacts: Object.freeze([]),
163
+ planSupport: false,
164
+ additionalOutputFormats: Object.freeze([]),
165
+ bootstrapProfile: 'command_owned',
166
+ }),
167
+ "risk-calibrate": commandSeed({
168
+ reference: {
169
+ description: 'Replay reviewed labels against the current explainable Risk model.',
170
+ example: 'whoisleuth risk-calibrate calibration.json --summary-json',
171
+ boundary: 'Calibration is offline and diagnostic. The summary form omits record identifiers, domains, and evidence; neither form trains, tunes, or changes the scoring model automatically.',
172
+ },
173
+ collection: { scope: 'Reads one bounded reviewed-label dataset and changes no model or evidence.' },
174
+ summary: 'Replay reviewed Risk labels offline',
175
+ options: ['--json', '--summary-json', '--quiet', '--no-color'],
176
+ positionals: OPTIONAL_FILE_POSITIONAL,
177
+ constraints: Object.freeze([
178
+ constraint({ kind: 'mutually_exclusive', options: ['--json', '--summary-json'] }),
179
+ ]),
180
+ handlerOwner: 'support',
181
+ networkEffect: 'offline',
182
+ common: false,
183
+ schemaIdentifiers: Object.freeze([RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_REPORT_SCHEMA]),
184
+ primaryArtefacts: Object.freeze([]),
185
+ planSupport: false,
186
+ additionalOutputFormats: Object.freeze([]),
187
+ bootstrapProfile: 'allowed',
188
+ }),
189
+ "lookalike-calibrate": commandSeed({
190
+ reference: {
191
+ description: 'Summarise reviewed candidate dispositions by mutation family without retaining domains.',
192
+ example: 'whoisleuth lookalike-calibrate reviewed-candidates.json --json',
193
+ boundary: 'Calibration is offline and diagnostic. It omits candidate identifiers, domains, notes, and evidence and never tunes generation or filtering automatically.',
194
+ },
195
+ collection: { scope: 'Reads at most 5,000 reviewed candidate labels from one dataset capped at 2 MiB.' },
196
+ summary: 'Summarise reviewed lookalike yield offline',
197
+ options: ['--json', '--quiet', '--no-color'],
198
+ positionals: OPTIONAL_FILE_POSITIONAL,
199
+ constraints: EMPTY_CONSTRAINTS,
200
+ handlerOwner: 'support',
201
+ networkEffect: 'offline',
202
+ common: false,
203
+ schemaIdentifiers: Object.freeze(['whoisleuth.lookalike-calibration-input', 'whoisleuth.lookalike-calibration']),
204
+ primaryArtefacts: Object.freeze([]),
205
+ planSupport: false,
206
+ additionalOutputFormats: Object.freeze([]),
207
+ bootstrapProfile: 'allowed',
208
+ }),
209
+ });
@@ -1,6 +1,6 @@
1
1
  import { REGISTRY_CAPABILITIES_VERSION, registryCapabilityFor } from "../lib/registry-capabilities.mjs";
2
- import { buildRiskCalibrationSummaryReport } from "../lib/risk-calibration-summary.mjs";
3
- import { explainRiskScore, explainRiskScoreV7, RISK_MODEL_VERSION, RISK_REVIEW_THRESHOLD } from "../lib/risk-scoring.mjs";
2
+ import { buildRiskCalibrationSummaryReport } from "../packages/analysis/risk-calibration-summary.mjs";
3
+ import { explainRiskScore, explainRiskScoreV7, RISK_MODEL_VERSION, RISK_REVIEW_THRESHOLD } from "../packages/analysis/risk-scoring.mjs";
4
4
  import { buildCliCommandCatalogue, formatCliCommandCatalogue, selectCliCommands } from "./command-catalogue.mjs";
5
5
  import { CLI_COMMAND_REGISTRY, CLI_COMMANDS, COMMAND_COLLECTION, COMMAND_DETAILS, COMMAND_USAGE, } from "./command-reference.mjs";
6
6
  import { buildShellCompletion } from "./completion.mjs";
@@ -0,0 +1,168 @@
1
+ import { StringDecoder } from 'node:string_decoder';
2
+ import { CliUsageError, hasUnsafeCliText } from "./errors.mjs";
3
+ export function canReadInteractiveLine(input, output, environment = process.env) {
4
+ return input?.isTTY === true && output?.isTTY === true
5
+ && typeof input.setRawMode === 'function' && typeof input.resume === 'function'
6
+ && typeof input.pause === 'function' && typeof input.on === 'function'
7
+ && typeof input.off === 'function' && typeof output.write === 'function'
8
+ && !environment.CI;
9
+ }
10
+ const MAX_INTERACTIVE_ANSWER_SCALARS = 1_024;
11
+ const MAX_INTERACTIVE_ANSWER_BYTES = 4_096;
12
+ function boundedInteractiveAnswer(value) {
13
+ const supplied = typeof value === 'string' ? value : '';
14
+ if (Buffer.byteLength(supplied, 'utf8') > MAX_INTERACTIVE_ANSWER_BYTES
15
+ || Array.from(supplied).length > MAX_INTERACTIVE_ANSWER_SCALARS
16
+ || hasUnsafeCliText(supplied)) {
17
+ throw new CliUsageError('Interactive input must be bounded text without control characters.');
18
+ }
19
+ return supplied.trim();
20
+ }
21
+ function abortError() {
22
+ return new DOMException('Aborted', 'AbortError');
23
+ }
24
+ function readBoundedInteractiveLine(prompt, options) {
25
+ const { input, output, signal } = options;
26
+ if (signal?.aborted)
27
+ return Promise.reject(signal.reason || abortError());
28
+ const previousRaw = input.isRaw === true;
29
+ const wasPaused = input.isPaused?.() === true;
30
+ let receivedBytes = 0;
31
+ let receivedScalars = 0;
32
+ let settled = false;
33
+ let characters = [];
34
+ const decoder = new StringDecoder('utf8');
35
+ return new Promise((resolve, reject) => {
36
+ const cleanup = () => {
37
+ let failure = null;
38
+ const attempt = (operation) => {
39
+ try {
40
+ operation();
41
+ }
42
+ catch (error) {
43
+ failure ||= error instanceof Error ? error : new Error('Interactive terminal cleanup failed.');
44
+ }
45
+ };
46
+ attempt(() => input.off?.('data', onData));
47
+ attempt(() => input.off?.('end', onEnd));
48
+ attempt(() => signal?.removeEventListener('abort', onAbort));
49
+ attempt(() => input.setRawMode?.(previousRaw));
50
+ if (wasPaused)
51
+ attempt(() => input.pause?.());
52
+ return failure;
53
+ };
54
+ const finish = (value, error) => {
55
+ if (settled)
56
+ return;
57
+ settled = true;
58
+ const cleanupFailure = cleanup();
59
+ characters = [];
60
+ if (error && cleanupFailure) {
61
+ reject(new AggregateError([error, cleanupFailure], `${error.message}; terminal cleanup also failed: ${cleanupFailure.message}`));
62
+ }
63
+ else if (error || cleanupFailure)
64
+ reject(error || cleanupFailure);
65
+ else
66
+ resolve(value || '');
67
+ };
68
+ const rejectUsage = () => finish(undefined, new CliUsageError('Interactive input must be bounded text without control characters.'));
69
+ const onData = (chunk) => {
70
+ if (settled)
71
+ return;
72
+ let rawChunk;
73
+ if (Buffer.isBuffer(chunk)) {
74
+ if (chunk.length > MAX_INTERACTIVE_ANSWER_BYTES - receivedBytes) {
75
+ rejectUsage();
76
+ return;
77
+ }
78
+ rawChunk = chunk;
79
+ }
80
+ else {
81
+ const supplied = String(chunk ?? '');
82
+ if (supplied.length > MAX_INTERACTIVE_ANSWER_BYTES - receivedBytes
83
+ || Buffer.byteLength(supplied, 'utf8') > MAX_INTERACTIVE_ANSWER_BYTES - receivedBytes) {
84
+ rejectUsage();
85
+ return;
86
+ }
87
+ rawChunk = Buffer.from(supplied, 'utf8');
88
+ }
89
+ receivedBytes += rawChunk.length;
90
+ const value = decoder.write(rawChunk);
91
+ receivedScalars += Array.from(value).length;
92
+ if (receivedBytes > MAX_INTERACTIVE_ANSWER_BYTES
93
+ || receivedScalars > MAX_INTERACTIVE_ANSWER_SCALARS
94
+ || value.includes('\ufffd')) {
95
+ rejectUsage();
96
+ return;
97
+ }
98
+ for (const character of value) {
99
+ if (character === '\r' || character === '\n') {
100
+ try {
101
+ output.write('\n');
102
+ }
103
+ catch (error) {
104
+ finish(undefined, error instanceof Error ? error : new Error('Interactive terminal output failed.'));
105
+ return;
106
+ }
107
+ finish(characters.join(''));
108
+ return;
109
+ }
110
+ if (character === '\u0003' || character === '\u0004' || character === '\u001b') {
111
+ finish(undefined, abortError());
112
+ return;
113
+ }
114
+ if (character === '\u0008' || character === '\u007f') {
115
+ if (characters.length) {
116
+ characters.pop();
117
+ try {
118
+ output.write('\b \b');
119
+ }
120
+ catch (error) {
121
+ finish(undefined, error instanceof Error ? error : new Error('Interactive terminal output failed.'));
122
+ return;
123
+ }
124
+ }
125
+ continue;
126
+ }
127
+ if (hasUnsafeCliText(character)) {
128
+ rejectUsage();
129
+ return;
130
+ }
131
+ characters.push(character);
132
+ try {
133
+ output.write(character);
134
+ }
135
+ catch (error) {
136
+ finish(undefined, error instanceof Error ? error : new Error('Interactive terminal output failed.'));
137
+ return;
138
+ }
139
+ }
140
+ };
141
+ const onEnd = () => {
142
+ const remainder = decoder.end();
143
+ if (remainder)
144
+ rejectUsage();
145
+ else
146
+ finish(undefined, abortError());
147
+ };
148
+ const onAbort = () => finish(undefined, signal?.reason instanceof Error ? signal.reason : abortError());
149
+ try {
150
+ output.write(prompt);
151
+ input.on?.('data', onData);
152
+ input.on?.('end', onEnd);
153
+ signal?.addEventListener('abort', onAbort, { once: true });
154
+ if (signal?.aborted) {
155
+ onAbort();
156
+ return;
157
+ }
158
+ input.setRawMode?.(true);
159
+ if (settled)
160
+ return;
161
+ input.resume?.();
162
+ }
163
+ catch (error) {
164
+ finish(undefined, error instanceof Error ? error : new Error('Interactive terminal input failed.'));
165
+ }
166
+ });
167
+ }
168
+ export { MAX_INTERACTIVE_ANSWER_BYTES, MAX_INTERACTIVE_ANSWER_SCALARS, boundedInteractiveAnswer, readBoundedInteractiveLine };
@@ -0,0 +1,86 @@
1
+ import {} from "./command-argument-grammar.mjs";
2
+ import { CliUsageError } from "./errors.mjs";
3
+ import {} from "./fail-policy.mjs";
4
+ import { commandDefaultNumber } from "./command-reference.mjs";
5
+ import { WORKFLOW_COMMAND_DEFINITIONS } from "./workflow-command-definitions.mjs";
6
+ import { terminalOptions, jsonOutput, failPolicies, parseOutput, } from "./argument-values.mjs";
7
+ function parseMonitorOnceArguments(parsed) {
8
+ const selectedFailPolicies = failPolicies(parsed, 'monitor-once');
9
+ return {
10
+ action: 'monitor-once',
11
+ source: parsed.positionalValue('source'),
12
+ previousSource: parsed.optionValue('--previous'),
13
+ output: parseOutput(parsed, WORKFLOW_COMMAND_DEFINITIONS['monitor-once']),
14
+ limit: parsed.integerOption('--limit') ?? commandDefaultNumber('monitor-once', '--limit'),
15
+ concurrency: parsed.integerOption('--concurrency') ??
16
+ commandDefaultNumber('monitor-once', '--concurrency'),
17
+ ...terminalOptions(parsed),
18
+ ...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
19
+ };
20
+ }
21
+ function parseWorkflowPlanArguments(parsed) {
22
+ const options = { output: jsonOutput(parsed), ...terminalOptions(parsed) };
23
+ if (parsed.hasOption('--list')) {
24
+ if (parsed.allPositionals.length > 0)
25
+ throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
26
+ return { action: 'workflow-plan', discovery: 'list', ...options };
27
+ }
28
+ if (parsed.hasOption('--explain')) {
29
+ if (parsed.allPositionals.length > 0)
30
+ throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
31
+ return {
32
+ action: 'workflow-plan',
33
+ discovery: 'explain',
34
+ recipe: parsed.optionValue('--explain'),
35
+ ...options,
36
+ };
37
+ }
38
+ if (parsed.allPositionals.length !== 2) {
39
+ throw new CliUsageError('workflow-plan requires one fixed recipe and one subject, --list, or --explain <recipe>.');
40
+ }
41
+ return {
42
+ action: 'workflow-plan',
43
+ recipe: parsed.positionalValue('recipe'),
44
+ subject: parsed.positionalValue('subject'),
45
+ ...options,
46
+ };
47
+ }
48
+ function parseWorkflowRunArguments(parsed) {
49
+ const artifactBindings = parsed.optionValues('--use-artifact').map((value) => {
50
+ const match = /^([a-z0-9]+(?:-[a-z0-9]+)*):([1-9][0-9]?)=([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(value);
51
+ if (!match)
52
+ throw new CliUsageError('--use-artifact requires <step-id>:<input-number>=<earlier-step-id>.');
53
+ return Object.freeze({ stepId: match[1], input: Number(match[2]), sourceStepId: match[3] });
54
+ });
55
+ const selections = parsed.optionValues('--select').map((value) => {
56
+ const separator = value.indexOf('=');
57
+ const stepId = separator === -1 ? '' : value.slice(0, separator);
58
+ const selection = separator === -1 ? '' : value.slice(separator + 1);
59
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(stepId) || !selection.trim()) {
60
+ throw new CliUsageError('--select requires <step-id>=<path-or-value>.');
61
+ }
62
+ if (selection.startsWith('-')) {
63
+ throw new CliUsageError('--select values cannot start with a hyphen; prefix a file path with ./ when needed.');
64
+ }
65
+ return Object.freeze({ stepId, value: selection });
66
+ });
67
+ return {
68
+ action: 'workflow-run',
69
+ recipe: parsed.positionalValue('recipe'),
70
+ subject: parsed.positionalValue('subject'),
71
+ resumeSource: parsed.optionValue('--resume'),
72
+ selections: Object.freeze(selections),
73
+ artifactBindings: Object.freeze(artifactBindings),
74
+ confirmedReviews: Object.freeze(parsed.optionValues('--confirm-review')),
75
+ approveNetwork: parsed.hasOption('--approve-network'),
76
+ interactive: parsed.hasOption('--interactive'),
77
+ ...(parsed.hasOption('--preview') ? { preview: true } : {}),
78
+ output: jsonOutput(parsed),
79
+ ...terminalOptions(parsed),
80
+ };
81
+ }
82
+ export const WORKFLOW_ARGUMENT_PARSERS = Object.freeze({
83
+ 'monitor-once': parseMonitorOnceArguments,
84
+ 'workflow-plan': parseWorkflowPlanArguments,
85
+ 'workflow-run': parseWorkflowRunArguments,
86
+ });
@@ -0,0 +1,78 @@
1
+ import { INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES } from "./investigation-recipes.mjs";
2
+ import { positional, OPTIONAL_FILE_POSITIONAL, constraint, commandSeed, integer, BASE_INTEGER_RANGE, file } from "./command-definition.mjs";
3
+ export const WORKFLOW_COMMAND_DEFINITIONS = Object.freeze({
4
+ "monitor-once": commandSeed({
5
+ reference: {
6
+ description: 'Collect one bounded owned-domain review and compare it with an optional prior checkpoint.',
7
+ example: 'whoisleuth monitor-once manifest.json --previous previous.json --json --output next.json',
8
+ boundary: 'This is an operator-scheduled one-shot collection, not a daemon. It caps targets and concurrency, retains normalised observations, and never changes domain configuration.',
9
+ },
10
+ collection: { scope: 'Runs deep collection for at most 20 manifest domains with concurrency capped at 3.' },
11
+ summary: 'Run one bounded domain control review',
12
+ options: ['--previous', '--limit', '--concurrency', '--fail-on', '--json', '--junit', '--quiet', '--no-color'],
13
+ optionOverrides: {
14
+ '--concurrency': integer('Set the maximum number of concurrent collection tasks.', () => Object.freeze([BASE_INTEGER_RANGE(1, 3)]), () => 2),
15
+ },
16
+ positionals: OPTIONAL_FILE_POSITIONAL,
17
+ constraints: Object.freeze([
18
+ constraint({ kind: 'mutually_exclusive', options: ['--json', '--junit'] }),
19
+ ]),
20
+ handlerOwner: 'workflow',
21
+ networkEffect: 'always_network',
22
+ common: false,
23
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.domain-control-monitor', 'whoisleuth.domain-control-flight-recorder.input']),
24
+ primaryArtefacts: Object.freeze([]),
25
+ planSupport: false,
26
+ additionalOutputFormats: Object.freeze([]),
27
+ bootstrapProfile: 'allowed',
28
+ }),
29
+ "workflow-plan": commandSeed({
30
+ reference: {
31
+ description: 'Build a fixed domain-investigation plan from existing bounded CLI commands.',
32
+ example: 'whoisleuth workflow-plan domain-triage example.test --json',
33
+ boundary: 'Planning is offline and plan-only. It does not execute commands, expand placeholders, read files, make requests, or submit evidence.',
34
+ },
35
+ collection: { scope: 'Builds a fixed typed recipe and executes none of its network or file steps.' },
36
+ summary: 'Plan a fixed investigation recipe',
37
+ options: ['--list', '--explain', '--json', '--quiet', '--no-color'],
38
+ positionals: Object.freeze([
39
+ positional('recipe', 'enum', 0, 1, INVESTIGATION_PLAN_RECIPES),
40
+ positional('subject', 'text', 0, 1),
41
+ ]),
42
+ constraints: Object.freeze([
43
+ constraint({ kind: 'mutually_exclusive', options: ['--list', '--explain'] }),
44
+ ]),
45
+ handlerOwner: 'workflow',
46
+ networkEffect: 'offline',
47
+ common: true,
48
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-plan', 'whoisleuth.cli.workflow-recipe-catalogue']),
49
+ primaryArtefacts: Object.freeze(['Plan-only workflow document']),
50
+ planSupport: true,
51
+ additionalOutputFormats: Object.freeze([]),
52
+ bootstrapProfile: 'allowed',
53
+ }),
54
+ "workflow-run": commandSeed({
55
+ reference: {
56
+ description: 'Execute approved steps from a fixed investigation recipe and emit a resumable checkpoint.',
57
+ example: 'whoisleuth workflow-run domain-triage example.test --approve-network --json --output run.json',
58
+ boundary: '--preview validates the checkpoint and shows retained outputs, unresolved inputs and remaining approvals without executing or writing. It cannot be combined with approval, interactive, quiet or output-file flags. Only installed recipe commands can run. Network steps require explicit approval for each invocation. New runs connect compatible earlier outputs using the recipe defaults. Use --use-artifact <step-id>:<input-number>=<earlier-step-id> to override a connection; input numbers start at 1. Repeat --select for remaining placeholders in order, or supply every input for a step to replace its connections with files. Values stay literal and cannot start with a hyphen or invoke a shell. Optional --interactive prompts on terminal stderr for missing inputs; a blank answer pauses. It grants neither network approval nor human-review confirmation. A step declaring human review still requires --confirm-review <step-id> for that invocation. Checkpoints do not grant later approvals. Resumes preserve recorded connections. Content digests identify retained output, not authenticity or freshness. Partial collections pause for review and are not recollected on resume; failed validation or export steps remain retryable. Diagnostics go to stderr. File output holds exclusive adjacent locks and refuses concurrently changed state files.',
59
+ },
60
+ collection: { scope: 'Runs only fixed-recipe steps; network collection requires --approve-network and unresolved analyst selections pause.' },
61
+ summary: 'Execute approved fixed-recipe steps',
62
+ options: ['--select', '--use-artifact', '--confirm-review', '--approve-network', '--resume', '--interactive', '--preview', '--json', '--quiet', '--no-color'],
63
+ optionOverrides: { '--resume': file('Resume the selected workflow checkpoint; approvals must be supplied again.') },
64
+ positionals: Object.freeze([
65
+ positional('recipe', 'enum', 1, 1, RUNNABLE_INVESTIGATION_PLAN_RECIPES),
66
+ positional('subject', 'text', 1, 1),
67
+ ]),
68
+ constraints: Object.freeze([constraint({ kind: 'excludes_all', option: '--preview', excludedOptions: ['--approve-network', '--confirm-review', '--interactive', '--output', '--force', '--quiet'] })]),
69
+ handlerOwner: 'workflow',
70
+ networkEffect: 'conditional_network',
71
+ common: false,
72
+ schemaIdentifiers: Object.freeze(['whoisleuth.cli.investigation-run', 'whoisleuth.cli.investigation-preview']),
73
+ primaryArtefacts: Object.freeze(['Resumable workflow state', 'Offline resume preview']),
74
+ planSupport: false,
75
+ additionalOutputFormats: Object.freeze([]),
76
+ bootstrapProfile: 'allowed',
77
+ }),
78
+ });