@slicedearth/whoisleuth-cli 2.0.1 → 2.2.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 (97) hide show
  1. package/cli/arguments.mjs +21 -5
  2. package/cli/artifact-validation/case-response.mjs +771 -0
  3. package/cli/artifact-validation/investigation-capsule.mjs +426 -0
  4. package/cli/artifact-validation/lookup-evidence.mjs +718 -0
  5. package/cli/artifact-validation/signed-review.mjs +598 -0
  6. package/cli/artifact-validation/structure-primitives.mjs +105 -0
  7. package/cli/artifact-verify.mjs +1 -1
  8. package/cli/assurance-command-runner.mjs +324 -0
  9. package/cli/command-reference.mjs +1175 -843
  10. package/cli/completion.mjs +56 -9
  11. package/cli/discriminated-command-handlers.mjs +11 -0
  12. package/cli/doctor.mjs +2 -2
  13. package/cli/evidence-command-runner.mjs +41 -31
  14. package/cli/export-evidence.mjs +1 -1
  15. package/cli/fail-policy.mjs +33 -8
  16. package/cli/formatters/terminal-command-formats.mjs +437 -0
  17. package/cli/formatters/terminal-lookup.mjs +758 -0
  18. package/cli/formatters/terminal-metadata.mjs +58 -0
  19. package/cli/formatters/terminal-shared.mjs +83 -0
  20. package/cli/formatters/terminal.mjs +5 -1232
  21. package/cli/history-command-runner.mjs +132 -0
  22. package/cli/inline-command-families.mjs +45 -0
  23. package/cli/inline-command-runner.mjs +55 -0
  24. package/cli/investigation-plan.mjs +9 -2
  25. package/cli/investigation-run.mjs +114 -9
  26. package/cli/lookup-brief.mjs +141 -23
  27. package/cli/lookup-browser-view.mjs +289 -0
  28. package/cli/lookup-browser.mjs +3 -288
  29. package/cli/mail-review.mjs +2 -6
  30. package/cli/offline-artifact-validation.mjs +19 -0
  31. package/cli/review-command-runner.mjs +269 -0
  32. package/cli/runner.mjs +15 -972
  33. package/cli/saved-lookup.mjs +19 -2
  34. package/cli/support-command-runner.mjs +229 -0
  35. package/cli/workflow-command-runner.mjs +129 -0
  36. package/docs/cli-reference.md +16 -9
  37. package/docs/cli.md +16 -2
  38. package/lib/availability.mjs +12 -9
  39. package/lib/bounded-contract-normalizers.mjs +2 -9
  40. package/lib/browser-library-profile.mjs +1 -2
  41. package/lib/client-behavior-profile.mjs +1 -2
  42. package/lib/credential-surface-profile.mjs +1 -1
  43. package/lib/domain-posture-analysis.mjs +10 -1
  44. package/lib/domain-posture.mjs +51 -23
  45. package/lib/evidence-export.mjs +47 -5
  46. package/lib/evidence-report-markdown.mjs +4 -0
  47. package/lib/evidence-report.mjs +50 -0
  48. package/lib/generated/cisa-kev-catalog.mjs +34 -4
  49. package/lib/generated/registrar-standing-catalogue.mjs +367 -0
  50. package/lib/json-record.mjs +12 -0
  51. package/lib/lookup-child-profile-contract.mjs +858 -0
  52. package/lib/lookup-contract-primitives.mjs +98 -0
  53. package/lib/lookup-diagnostics.mjs +96 -0
  54. package/lib/lookup-network-evidence-bounds.mjs +1 -0
  55. package/lib/lookup-response.mjs +367 -0
  56. package/lib/lookup.mjs +28 -443
  57. package/lib/page-role-profile.mjs +1 -3
  58. package/lib/registrar-standing-catalogue-contract.mjs +70 -0
  59. package/lib/registrar-standing-contract.mjs +300 -0
  60. package/lib/registrar-standing.mjs +269 -0
  61. package/lib/registry-insights.mjs +2 -8
  62. package/lib/risk-scoring.mjs +26 -10
  63. package/lib/sslbl-certificates.generated.mjs +138 -136
  64. package/lib/structured-data-identity.mjs +1 -3
  65. package/lib/technology-evidence-role.mjs +51 -0
  66. package/lib/threat-intelligence-runtime.mjs +3 -3
  67. package/lib/threat-intelligence-types.mjs +13 -0
  68. package/lib/threatfox-intelligence.mjs +1 -3
  69. package/lib/tls-intelligence.mjs +1 -2
  70. package/lib/urlhaus-intelligence.mjs +1 -3
  71. package/lib/urlscan-intelligence.mjs +1 -3
  72. package/lib/website-security-posture.mjs +1 -2
  73. package/lib/website-technology.mjs +72 -23
  74. package/lib/whois-common-formats.mjs +276 -0
  75. package/lib/whois-dialects.mjs +534 -0
  76. package/lib/whois-parser-support.mjs +305 -0
  77. package/lib/whois-parser.mjs +135 -0
  78. package/lib/whois.mjs +6 -1187
  79. package/package.json +2 -2
  80. package/packages/cases/case-evidence-model.mjs +9 -0
  81. package/packages/cases/case-migration-model.mjs +17 -11
  82. package/packages/cases/case-record-operations.mjs +235 -0
  83. package/packages/cases/case-response-model.mjs +13 -9
  84. package/packages/cases/case-response-packet.mjs +297 -112
  85. package/packages/comparison/page-similarity.mjs +23 -0
  86. package/packages/contracts/case-portability.mjs +235 -61
  87. package/packages/contracts/cli-lookup.mjs +4 -3
  88. package/packages/contracts/domain-control-manifest.mjs +1 -1
  89. package/packages/contracts/investigation-portability.mjs +1 -1
  90. package/packages/contracts/risk-calibration.mjs +2 -2
  91. package/packages/contracts/workspace-portability.mjs +34 -6
  92. package/packages/interchange/external-findings-import.mjs +113 -45
  93. package/packages/investigation/evidence-coverage-ledger.mjs +21 -0
  94. package/packages/investigation/lookup-claim-readiness.mjs +2 -2
  95. package/packages/workspace/website-snapshot-model.mjs +79 -6
  96. package/third-party-notices.txt +1 -1
  97. package/cli/artifact-structure.mjs +0 -2507
package/cli/runner.mjs CHANGED
@@ -1,64 +1,16 @@
1
- import { Buffer } from 'node:buffer';
2
1
  import { createRequire } from 'node:module';
3
2
  import { abortable } from "../lib/abort.mjs";
4
- import { scanBoundedJson } from "../lib/bounded-json.mjs";
5
- import { REGISTRY_CAPABILITIES_VERSION, registryCapabilityFor } from "../lib/registry-capabilities.mjs";
6
- import { explainRiskScore, explainRiskScoreV6, RISK_MODEL_VERSION, RISK_REVIEW_THRESHOLD } from "../lib/risk-scoring.mjs";
7
- import { buildRiskCalibrationSummaryReport } from "../lib/risk-calibration-summary.mjs";
8
3
  import { parseCliArguments } from "./arguments.mjs";
9
- import { buildCliCommandCatalogue, formatCliCommandCatalogue, selectCliCommands } from "./command-catalogue.mjs";
10
- import { COMMAND_COLLECTION, COMMAND_DETAILS, COMMAND_USAGE, HELP, CLI_COMMANDS, CLI_COMMAND_REGISTRY, commandDefinition, commandHelp, } from "./command-reference.mjs";
11
- import { buildShellCompletion } from "./completion.mjs";
12
- import { buildDoctorReport, formatDoctorReport } from "./doctor.mjs";
13
- import { MAX_COMPARE_INPUT_BYTES, compareLookupDocument, parseCliLookupDocument, } from "./compare.mjs";
4
+ import { HELP, commandDefinition, commandHelp, } from "./command-reference.mjs";
14
5
  import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
15
6
  import { evidenceCommandFailureLabel, isEvidenceCommand, runEvidenceCommand, } from "./evidence-command-runner.mjs";
16
- import { buildCliEvidenceExport, formatCliEvidenceExport } from "./export-evidence.mjs";
17
7
  import EXIT_CODES from "./exit-codes.mjs";
18
- import { formatLookupEvidenceHtml } from "./formatters/html.mjs";
19
- import { buildCliCompareDocument, formatJsonDocument, } from "./formatters/json.mjs";
20
- import { formatLookupEvidenceMarkdown } from "./formatters/markdown.mjs";
21
- import { formatTerminalCompare, formatTerminalRegistrySupport, formatTerminalRiskCalibration, } from "./formatters/terminal.mjs";
22
- import { MAX_RETAINED_ARTIFACT_DIFF_BYTES, buildCliRetainedArtifactDiff, formatCliRetainedArtifactDiff, } from "./retained-artifact-diff.mjs";
23
- import { MAX_LOOKUP_RECONCILIATION_INPUT_BYTES, buildCliLookupReconciliation, formatCliLookupReconciliation, } from "./lookup-reconcile.mjs";
24
- import { MAX_SHARING_REVIEW_BYTES, buildSharingReview, formatSharingReview, } from "./sharing-review.mjs";
25
- import { MAX_LOOKUP_TIMELINE_INPUT_BYTES, buildCliLookupTimeline, formatCliLookupTimeline, } from "./lookup-timeline.mjs";
26
- import { buildCliPageComparison, formatCliPageComparison } from "./page-compare.mjs";
27
- import { MAX_MAIL_REVIEW_INPUT_BYTES, buildCliMailReview, formatCliMailReview, } from "./mail-review.mjs";
28
- import { MAX_OFFLINE_EVIDENCE_INPUT_BYTES, buildOfflineEvidenceReview, buildOfflineEvidenceReviewWithLocalResources, formatOfflineEvidenceReview, } from "./offline-evidence-review.mjs";
29
- import { DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA, DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA, buildDomainControlManifest, formatDomainControlResult, reviewDomainControlManifest, } from "../lib/domain-control-manifest.mjs";
30
- import { serializeDomainControlManifest } from "../packages/evidence/domain-control-runtime.mjs";
31
- import { MAX_DOMAIN_CONTROL_REVIEW_COMMAND_BYTES } from "../packages/contracts/domain-control-review.mjs";
32
- import { DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA, buildDomainControlFlightRecorder, formatDomainControlFlightRecorder, serializeDomainControlFlightRecorder, } from "../lib/domain-control-flight-recorder.mjs";
33
- import { CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA, MAX_DOMAIN_CONTROL_REVIEW_JSON_DEPTH, MAX_DOMAIN_CONTROL_REVIEW_JSON_KEYS, MAX_DOMAIN_CONTROL_REVIEW_JSON_VALUES, buildCliDomainControlReview, formatCliDomainControlReview, } from "./domain-control-observations.mjs";
34
- import { buildCliLookupBrief, formatCliLookupBrief } from "./lookup-brief.mjs";
35
- import { buildCliCasePack, formatCliCasePack } from "./case-pack.mjs";
36
- import { formatDomainControlMonitor, runDomainControlMonitor } from "./domain-control-monitor.mjs";
37
- import { MAX_ASSURANCE_INPUT_BYTES, buildDomainAssurance, formatDomainAssurance, } from "../lib/domain-assurance.mjs";
38
- import { MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES, buildDomainChangePacket, formatDomainChangePacket, } from "../lib/domain-change-packet.mjs";
39
- import { buildCliManual } from "./manual.mjs";
40
- import { MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES, MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES, buildInvestigationManifest, formatInvestigationManifest, } from "./investigation-manifest.mjs";
41
- import { MAX_EXTERNAL_OBSERVATION_MAPPING_BYTES, mapExternalObservations, formatExternalObservationMapping, } from "./external-observation-mapping.mjs";
42
- import { buildOpenAssetModelBridge, formatOpenAssetModelBridge, } from "./open-asset-model-bridge.mjs";
43
- import { MAX_CT_EVENT_INPUT_BYTES, buildCtEventFindings, formatCtEventFindings, } from "./ct-event-intake.mjs";
44
- import { buildInvestigationPlan, buildWorkflowRecipeCatalogue, formatInvestigationPlan, formatWorkflowRecipeCatalogue, } from "./investigation-plan.mjs";
45
- import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, runInvestigationRecipe } from "./investigation-run.mjs";
46
8
  import { readCliTextInput } from "./input.mjs";
47
- import { evaluateCliFailPolicies, formatFailPolicyNotice } from "./fail-policy.mjs";
48
- import { formatCliJunit } from "./ci-report.mjs";
49
- import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile } from "./output-file.mjs";
9
+ import { INLINE_CLI_COMMANDS, runInlineCommand } from "./inline-command-runner.mjs";
50
10
  import { canLaunchInteractiveCli, launchInteractiveCli, } from "./interactive-launcher.mjs";
11
+ import { MAX_OFFLINE_PASSPHRASE_FILE_BYTES, } from "./artifact-verify.mjs";
12
+ import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile } from "./output-file.mjs";
51
13
  import { createTerminalProgress } from "./progress.mjs";
52
- import { buildRegistrySupportDocument } from "./registry-support.mjs";
53
- import { buildRegistryDoctorReport, formatRegistryDoctorReport } from "./registry-doctor.mjs";
54
- import { MAX_REGISTRY_COHORT_INPUT_BYTES, buildRegistryCohortReport, formatRegistryCohortReport, } from "./registry-cohort.mjs";
55
- import { buildRegistryFixtureScaffold } from "./registry-fixture-scaffold.mjs";
56
- import { MAX_OFFLINE_ARTIFACT_BYTES, MAX_OFFLINE_PASSPHRASE_FILE_BYTES, formatOfflineArtifactVerification, isCompleteOfflineArtifactVerification, verifyOfflineArtifact, } from "./artifact-verify.mjs";
57
- import { buildInterchangeFidelityReport, formatInterchangeFidelityReport, } from "./interchange-report.mjs";
58
- import { MAX_SOURCE_RELIABILITY_INPUT_BYTES, buildSourceReliabilityReport, formatSourceReliabilityReport, } from "./source-reliability.mjs";
59
- import { MAX_RISK_CALIBRATION_INPUT_BYTES, buildRiskCalibrationReport, parseRiskCalibrationDataset, serializeRiskCalibrationReport, } from "./risk-calibration.mjs";
60
- import { MAX_LOOKALIKE_CALIBRATION_BYTES, buildLookalikeCalibration, formatLookalikeCalibration, } from "./lookalike-calibration.mjs";
61
- import { MAX_SAVED_LOOKUP_INPUT_BYTES } from "./saved-lookup.mjs";
62
14
  import { presentTerminalOutput, terminalPresentation, } from "./terminal-presentation.mjs";
63
15
  const require = createRequire(import.meta.url);
64
16
  const { version: VERSION } = require('../package.json');
@@ -98,16 +50,6 @@ function usageEventReason(error) {
98
50
  return 'missing_input';
99
51
  return 'invalid_input';
100
52
  }
101
- const INLINE_CLI_COMMANDS = Object.freeze([
102
- 'completion', 'doctor', 'commands', 'manual', 'manifest', 'map-observations',
103
- 'oam-export', 'ct-intake', 'registry-support', 'registry-doctor',
104
- 'registry-cohort', 'registry-scaffold', 'risk-calibrate', 'lookalike-calibrate',
105
- 'verify-artifact', 'interchange-report', 'source-report', 'compare',
106
- 'page-compare', 'mail-review', 'review-evidence', 'brief', 'case-pack',
107
- 'domain-control', 'monitor-once', 'assurance', 'change-packet',
108
- 'sharing-review', 'workflow-plan', 'workflow-run', 'diff', 'reconcile',
109
- 'timeline', 'export',
110
- ]);
111
53
  async function runParsedCli(args, dependencies = {}) {
112
54
  const stdout = dependencies.stdout || process.stdout;
113
55
  const stderr = dependencies.stderr || process.stderr;
@@ -175,13 +117,16 @@ async function runParsedCli(args, dependencies = {}) {
175
117
  return passphrase;
176
118
  };
177
119
  const commandContext = Object.freeze({
120
+ packageVersion: VERSION,
178
121
  stdout,
179
122
  stderr,
180
123
  terminal,
124
+ presentation: (color) => terminalPresentation(stdout, color, environment, palette),
181
125
  writeStdout: (value) => write(stdout, value),
182
126
  writeStderr: (value) => write(stderr, value),
183
127
  readSingleInput,
184
128
  readInput,
129
+ readPassphraseSource,
185
130
  now: () => dependencies.now ? dependencies.now() : new Date().toISOString(),
186
131
  beginProgress,
187
132
  endProgress,
@@ -189,6 +134,13 @@ async function runParsedCli(args, dependencies = {}) {
189
134
  setEventProgress: (next) => {
190
135
  eventProgress.current = next;
191
136
  },
137
+ setFailureLabel: (label) => {
138
+ failureLabel = label;
139
+ },
140
+ executeCli: (argv, overrides = {}) => runCli(argv, {
141
+ ...dependencies,
142
+ ...overrides,
143
+ }),
192
144
  });
193
145
  if (args.action === 'help') {
194
146
  write(stdout, terminal(args.command ? commandHelp(args.command) : HELP));
@@ -270,916 +222,7 @@ async function runParsedCli(args, dependencies = {}) {
270
222
  if (handlerOwner !== 'inline' || !INLINE_CLI_COMMANDS.includes(args.action)) {
271
223
  throw new Error('No CLI execution route is registered for the parsed command.');
272
224
  }
273
- if (args.action === 'completion') {
274
- write(stdout, buildShellCompletion(args.shell));
275
- return EXIT_CODES.SUCCESS;
276
- }
277
- if (args.action === 'commands') {
278
- const selectedCommands = selectCliCommands(CLI_COMMAND_REGISTRY, {
279
- common: args.common,
280
- group: args.group,
281
- mode: args.mode,
282
- });
283
- const catalogue = buildCliCommandCatalogue({
284
- commands: selectedCommands,
285
- collections: COMMAND_COLLECTION,
286
- details: COMMAND_DETAILS,
287
- usage: COMMAND_USAGE,
288
- packageVersion: VERSION,
289
- });
290
- if (!args.quiet)
291
- write(stdout, args.output === 'json'
292
- ? formatJsonDocument(catalogue)
293
- : terminal(formatCliCommandCatalogue(catalogue), args.color));
294
- return EXIT_CODES.SUCCESS;
295
- }
296
- if (args.action === 'manual') {
297
- write(stdout, buildCliManual({
298
- commands: CLI_COMMANDS,
299
- collections: COMMAND_COLLECTION,
300
- details: COMMAND_DETAILS,
301
- usage: COMMAND_USAGE,
302
- version: VERSION,
303
- }));
304
- return EXIT_CODES.SUCCESS;
305
- }
306
- if (args.action === 'manifest') {
307
- failureLabel = 'Investigation manifest';
308
- const artifacts = [];
309
- let totalBytes = 0;
310
- try {
311
- for (const source of args.sources) {
312
- const content = dependencies.readDiffInput
313
- ? await dependencies.readDiffInput(source)
314
- : await readInput(source, MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES, 'Manifest artefact input');
315
- totalBytes += Buffer.byteLength(content, 'utf8');
316
- if (totalBytes > MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES) {
317
- throw new CliUsageError(`Manifest artefacts exceed the ${MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES}-byte combined limit.`);
318
- }
319
- artifacts.push({ content });
320
- }
321
- }
322
- catch (error) {
323
- if (error instanceof CliUsageError)
324
- throw error;
325
- throw new CliUsageError(`Could not read manifest artefact input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
326
- }
327
- let document;
328
- try {
329
- document = await buildInvestigationManifest({
330
- workflow: args.workflow,
331
- configurationDigestSha256: args.configurationDigestSha256,
332
- artifacts,
333
- }, commandContext.now(), VERSION);
334
- }
335
- catch (error) {
336
- throw new CliUsageError(boundedCliErrorMessage(error, 'Investigation manifest input is invalid'));
337
- }
338
- if (!args.quiet)
339
- write(stdout, args.output === 'json'
340
- ? formatJsonDocument(document)
341
- : terminal(formatInvestigationManifest(document), args.color));
342
- return EXIT_CODES.SUCCESS;
343
- }
344
- if (args.action === 'map-observations' || args.action === 'oam-export') {
345
- const mapping = args.action === 'map-observations';
346
- failureLabel = mapping ? 'External observation mapping' : 'Open Asset Model export';
347
- let input;
348
- try {
349
- input = dependencies.readArtifactInput
350
- ? await dependencies.readArtifactInput(args.source)
351
- : await readInput(args.source, MAX_EXTERNAL_OBSERVATION_MAPPING_BYTES, mapping ? 'External observation mapping input' : 'Open Asset Model bridge input');
352
- }
353
- catch (error) {
354
- if (error instanceof CliUsageError)
355
- throw error;
356
- throw new CliUsageError(`Could not read ${mapping ? 'observation mapping' : 'asset bridge'} input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
357
- }
358
- if (!input.trim())
359
- throw new CliUsageError(`${args.action} requires one versioned JSON file or a document on stdin.`);
360
- let parsed;
361
- try {
362
- scanBoundedJson(input);
363
- parsed = JSON.parse(input);
364
- }
365
- catch {
366
- throw new CliUsageError(`${mapping ? 'External observation mapping' : 'Open Asset Model bridge'} input is not valid bounded JSON without duplicate keys.`);
367
- }
368
- let document;
369
- try {
370
- document = mapping
371
- ? mapExternalObservations(parsed)
372
- : buildOpenAssetModelBridge(parsed, commandContext.now());
373
- }
374
- catch (error) {
375
- throw new CliUsageError(boundedCliErrorMessage(error, `${mapping ? 'External observation mapping' : 'Open Asset Model bridge'} input is invalid`));
376
- }
377
- if (!args.quiet)
378
- write(stdout, args.output === 'json'
379
- ? formatJsonDocument(document)
380
- : terminal(mapping
381
- ? formatExternalObservationMapping(document)
382
- : formatOpenAssetModelBridge(document), args.color));
383
- return EXIT_CODES.SUCCESS;
384
- }
385
- if (args.action === 'doctor') {
386
- failureLabel = 'CLI diagnostics';
387
- const buildReport = () => buildDoctorReport({
388
- version: VERSION,
389
- generatedAt: dependencies.now ? dependencies.now() : new Date().toISOString(),
390
- network: args.network,
391
- presentation: terminalPresentation(stdout, args.color, environment, palette),
392
- ...(dependencies.resolvePublicAddresses ? { resolveAddresses: dependencies.resolvePublicAddresses } : {}),
393
- ...(dependencies.safeFetch ? { fetchHttps: dependencies.safeFetch } : {}),
394
- ...(dependencies.whoisQuery ? { queryWhois: dependencies.whoisQuery } : {}),
395
- });
396
- const report = args.network
397
- ? await withProgress('Checking public DNS, HTTPS, and WHOIS connectivity', buildReport)
398
- : await buildReport();
399
- if (!args.quiet) {
400
- write(stdout, args.output === 'json'
401
- ? formatJsonDocument(report)
402
- : terminal(formatDoctorReport(report), args.color));
403
- }
404
- return report.state === 'partial' ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
405
- }
406
- if (args.action === 'registry-support') {
407
- failureLabel = 'Registry support';
408
- const readInput = dependencies.readStdin || (() => readStdinBounded(dependencies.stdin || process.stdin, MAX_STDIN_BYTES, dependencies.signal));
409
- const requestedInput = args.target || await readInput();
410
- if (!requestedInput)
411
- throw new CliUsageError('registry-support requires one domain or suffix as an argument or on stdin.');
412
- const lookupCapability = dependencies.registryCapabilityFor || registryCapabilityFor;
413
- const capability = lookupCapability(requestedInput);
414
- if (!capability)
415
- throw new CliUsageError('registry-support requires a valid domain or suffix.');
416
- const now = dependencies.now ? dependencies.now() : new Date().toISOString();
417
- const catalogueVersion = dependencies.registryCapabilitiesVersion || REGISTRY_CAPABILITIES_VERSION;
418
- const document = buildRegistrySupportDocument(requestedInput, capability, catalogueVersion, now);
419
- if (!args.quiet)
420
- write(stdout, args.output === 'json' ? formatJsonDocument(document) : terminal(formatTerminalRegistrySupport(document), args.color));
421
- return EXIT_CODES.SUCCESS;
422
- }
423
- if (args.action === 'registry-doctor') {
424
- failureLabel = 'Registry compatibility diagnostic';
425
- let input;
426
- try {
427
- input = dependencies.readCompareInput
428
- ? await dependencies.readCompareInput(args.source)
429
- : await readInput(args.source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Registry doctor input');
430
- }
431
- catch (error) {
432
- if (error instanceof CliUsageError)
433
- throw error;
434
- throw new CliUsageError(`Could not read registry doctor input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
435
- }
436
- if (!input.trim())
437
- throw new CliUsageError('registry-doctor requires one saved Lookup JSON file or a document on stdin.');
438
- const report = buildRegistryDoctorReport(input, commandContext.now());
439
- if (!args.quiet)
440
- write(stdout, args.output === 'json'
441
- ? formatJsonDocument(report)
442
- : terminal(formatRegistryDoctorReport(report), args.color));
443
- return report.summary.investigate ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
444
- }
445
- if (args.action === 'registry-cohort') {
446
- failureLabel = 'Registry quality cohort';
447
- let input;
448
- try {
449
- input = dependencies.readCompareInput
450
- ? await dependencies.readCompareInput(args.source)
451
- : await readInput(args.source, MAX_REGISTRY_COHORT_INPUT_BYTES, 'Registry cohort input');
452
- }
453
- catch (error) {
454
- if (error instanceof CliUsageError)
455
- throw error;
456
- throw new CliUsageError(`Could not read registry cohort input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
457
- }
458
- const report = buildRegistryCohortReport(input, commandContext.now());
459
- if (!args.quiet)
460
- write(stdout, args.output === 'json'
461
- ? formatJsonDocument(report)
462
- : terminal(formatRegistryCohortReport(report), args.color));
463
- return report.cohorts.some((cohort) => cohort.state === 'review')
464
- ? EXIT_CODES.PARTIAL_FAILURE
465
- : EXIT_CODES.SUCCESS;
466
- }
467
- if (args.action === 'registry-scaffold') {
468
- failureLabel = 'Registry fixture scaffold';
469
- try {
470
- write(stdout, buildRegistryFixtureScaffold(args.profile, args.suffix, args.scenario));
471
- }
472
- catch (error) {
473
- throw new CliUsageError(boundedCliErrorMessage(error, 'Registry fixture scaffold failed'));
474
- }
475
- return EXIT_CODES.SUCCESS;
476
- }
477
- if (args.action === 'risk-calibrate') {
478
- failureLabel = 'Risk calibration';
479
- let input;
480
- try {
481
- input = dependencies.readRiskCalibrationInput
482
- ? await dependencies.readRiskCalibrationInput(args.source)
483
- : await readInput(args.source, MAX_RISK_CALIBRATION_INPUT_BYTES, 'Risk calibration input');
484
- }
485
- catch (error) {
486
- if (error instanceof CliUsageError)
487
- throw error;
488
- throw new CliUsageError(`Could not read Risk calibration input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
489
- }
490
- if (!input.trim())
491
- throw new CliUsageError('risk-calibrate requires one dataset JSON file or a dataset on stdin.');
492
- const dataset = parseRiskCalibrationDataset(input);
493
- const report = buildRiskCalibrationReport(dataset, dependencies.explainRiskScore || explainRiskScore, {
494
- generatedAt: dependencies.now ? dependencies.now() : new Date().toISOString(),
495
- modelVersion: dependencies.riskModelVersion || RISK_MODEL_VERSION,
496
- reviewThreshold: dependencies.riskReviewThreshold || RISK_REVIEW_THRESHOLD,
497
- ...(!dependencies.explainRiskScore ? {
498
- previousModelVersion: 6,
499
- explainPreviousRiskScore: explainRiskScoreV6,
500
- } : {}),
501
- });
502
- if (!args.quiet)
503
- write(stdout, args.output === 'summary_json'
504
- ? serializeRiskCalibrationReport(buildRiskCalibrationSummaryReport(report))
505
- : args.output === 'json'
506
- ? serializeRiskCalibrationReport(report)
507
- : terminal(formatTerminalRiskCalibration(report), args.color));
508
- return EXIT_CODES.SUCCESS;
509
- }
510
- if (args.action === 'lookalike-calibrate') {
511
- failureLabel = 'Lookalike review-yield calibration';
512
- let input;
513
- try {
514
- input = dependencies.readRiskCalibrationInput
515
- ? await dependencies.readRiskCalibrationInput(args.source)
516
- : await readInput(args.source, MAX_LOOKALIKE_CALIBRATION_BYTES, 'Lookalike calibration input');
517
- }
518
- catch (error) {
519
- if (error instanceof CliUsageError)
520
- throw error;
521
- throw new CliUsageError(`Could not read lookalike calibration input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
522
- }
523
- if (!input.trim())
524
- throw new CliUsageError('lookalike-calibrate requires one dataset JSON file or a document on stdin.');
525
- let report;
526
- try {
527
- report = buildLookalikeCalibration(input, commandContext.now());
528
- }
529
- catch (error) {
530
- throw new CliUsageError(boundedCliErrorMessage(error, 'Lookalike calibration input is invalid'));
531
- }
532
- if (!args.quiet)
533
- write(stdout, args.output === 'json'
534
- ? formatJsonDocument(report)
535
- : terminal(formatLookalikeCalibration(report), args.color));
536
- return EXIT_CODES.SUCCESS;
537
- }
538
- if (args.action === 'verify-artifact') {
539
- failureLabel = 'Artefact verification';
540
- let input;
541
- try {
542
- input = dependencies.readArtifactInput
543
- ? await dependencies.readArtifactInput(args.source)
544
- : await readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Artefact input');
545
- }
546
- catch (error) {
547
- if (error instanceof CliUsageError)
548
- throw error;
549
- throw new CliUsageError(`Could not read artifact input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
550
- }
551
- if (!input.trim())
552
- throw new CliUsageError('verify-artifact requires one JSON file or an artefact on stdin.');
553
- const passphrase = args.passphraseSource ? await readPassphraseSource(args.passphraseSource) : null;
554
- let manifest = null;
555
- if (args.manifestSource && args.manifestEntryId) {
556
- let raw;
557
- try {
558
- raw = dependencies.readArtifactInput
559
- ? await dependencies.readArtifactInput(args.manifestSource)
560
- : await readInput(args.manifestSource, MAX_OFFLINE_ARTIFACT_BYTES, 'Investigation manifest input');
561
- }
562
- catch (error) {
563
- if (error instanceof CliUsageError)
564
- throw error;
565
- throw new CliUsageError(`Could not read investigation manifest input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
566
- }
567
- if (!raw.trim())
568
- throw new CliUsageError('The investigation manifest input is empty.');
569
- manifest = Object.freeze({ raw, entryId: args.manifestEntryId });
570
- }
571
- const report = await verifyOfflineArtifact(input, { passphrase, manifest });
572
- if (!args.quiet) {
573
- write(stdout, args.output === 'json'
574
- ? formatJsonDocument(report)
575
- : terminal(formatOfflineArtifactVerification(report), args.color));
576
- }
577
- return args.strictExit && !isCompleteOfflineArtifactVerification(report)
578
- ? EXIT_CODES.PARTIAL_FAILURE
579
- : EXIT_CODES.SUCCESS;
580
- }
581
- if (args.action === 'interchange-report') {
582
- failureLabel = 'Interchange fidelity report';
583
- let input;
584
- try {
585
- input = dependencies.readArtifactInput
586
- ? await dependencies.readArtifactInput(args.source)
587
- : await readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Interchange input');
588
- }
589
- catch (error) {
590
- if (error instanceof CliUsageError)
591
- throw error;
592
- throw new CliUsageError(`Could not read interchange input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
593
- }
594
- if (!input.trim())
595
- throw new CliUsageError('interchange-report requires one JSON file or an artefact on stdin.');
596
- const passphrase = args.passphraseSource ? await readPassphraseSource(args.passphraseSource) : null;
597
- const report = await buildInterchangeFidelityReport(input, {
598
- generatedAt: commandContext.now(),
599
- passphrase,
600
- });
601
- if (!args.quiet)
602
- write(stdout, args.output === 'json'
603
- ? formatJsonDocument(report)
604
- : terminal(formatInterchangeFidelityReport(report), args.color));
605
- return EXIT_CODES.SUCCESS;
606
- }
607
- if (args.action === 'source-report') {
608
- failureLabel = 'Source reliability report';
609
- let input;
610
- try {
611
- input = dependencies.readSourceReliabilityInput
612
- ? await dependencies.readSourceReliabilityInput(args.source)
613
- : await readInput(args.source, MAX_SOURCE_RELIABILITY_INPUT_BYTES, 'Source reliability input');
614
- }
615
- catch (error) {
616
- if (error instanceof CliUsageError)
617
- throw error;
618
- throw new CliUsageError(`Could not read source reliability input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
619
- }
620
- if (!input.trim())
621
- throw new CliUsageError('source-report requires one JSON file or lookup documents on stdin.');
622
- const report = buildSourceReliabilityReport(input, dependencies.now ? dependencies.now() : new Date().toISOString());
623
- if (!args.quiet) {
624
- write(stdout, args.output === 'json'
625
- ? formatJsonDocument(report)
626
- : terminal(formatSourceReliabilityReport(report), args.color));
627
- }
628
- return EXIT_CODES.SUCCESS;
629
- }
630
- if (args.action === 'compare') {
631
- failureLabel = 'Registry comparison';
632
- let input;
633
- try {
634
- input = dependencies.readCompareInput
635
- ? await dependencies.readCompareInput(args.source)
636
- : await readInput(args.source, MAX_COMPARE_INPUT_BYTES, 'Comparison input');
637
- }
638
- catch (error) {
639
- if (error instanceof CliUsageError)
640
- throw error;
641
- throw new CliUsageError(`Could not read comparison input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
642
- }
643
- if (!input.trim())
644
- throw new CliUsageError('compare requires one lookup JSON file or a lookup document on stdin.');
645
- const parsed = parseCliLookupDocument(input);
646
- const loadComparison = dependencies.loadRegistryComparison || (() => import("../lib/registry-comparison.mjs"));
647
- const comparisonModule = await loadComparison();
648
- const result = compareLookupDocument(parsed, comparisonModule.compareRegistrySources, comparisonModule.compareRdapPublications);
649
- const now = dependencies.now ? dependencies.now() : new Date().toISOString();
650
- const document = buildCliCompareDocument(result, now);
651
- if (!args.quiet)
652
- write(stdout, args.output === 'json' ? formatJsonDocument(document) : terminal(formatTerminalCompare(document), args.color));
653
- return EXIT_CODES.SUCCESS;
654
- }
655
- if (args.action === 'page-compare') {
656
- failureLabel = 'Static page comparison';
657
- const readDiffInput = dependencies.readDiffInput
658
- || ((source) => readInput(source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Page comparison input'));
659
- let leftInput;
660
- let rightInput;
661
- try {
662
- [leftInput, rightInput] = await Promise.all([
663
- readDiffInput(args.leftSource),
664
- readDiffInput(args.rightSource),
665
- ]);
666
- }
667
- catch (error) {
668
- if (error instanceof CliUsageError)
669
- throw error;
670
- throw new CliUsageError(`Could not read page comparison input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
671
- }
672
- const document = buildCliPageComparison(leftInput, rightInput, commandContext.now());
673
- if (!args.quiet)
674
- write(stdout, args.output === 'json'
675
- ? formatJsonDocument(document)
676
- : terminal(formatCliPageComparison(document), args.color));
677
- return EXIT_CODES.SUCCESS;
678
- }
679
- if (args.action === 'mail-review') {
680
- failureLabel = 'Passive mail review';
681
- let input;
682
- try {
683
- input = dependencies.readMailReviewInput
684
- ? await dependencies.readMailReviewInput(args.source)
685
- : await readInput(args.source, MAX_MAIL_REVIEW_INPUT_BYTES, 'Mail review input');
686
- }
687
- catch (error) {
688
- if (error instanceof CliUsageError)
689
- throw error;
690
- throw new CliUsageError(`Could not read mail review input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
691
- }
692
- const document = buildCliMailReview(input, commandContext.now());
693
- if (!args.quiet)
694
- write(stdout, args.output === 'json'
695
- ? formatJsonDocument(document)
696
- : terminal(formatCliMailReview(document), args.color));
697
- return EXIT_CODES.SUCCESS;
698
- }
699
- if (args.action === 'review-evidence') {
700
- failureLabel = 'Offline evidence review';
701
- let input;
702
- try {
703
- input = dependencies.readArtifactInput
704
- ? await dependencies.readArtifactInput(args.source)
705
- : await readInput(args.source, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Offline evidence input');
706
- }
707
- catch (error) {
708
- if (error instanceof CliUsageError)
709
- throw error;
710
- throw new CliUsageError(`Could not read offline evidence input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
711
- }
712
- if (!input.trim())
713
- throw new CliUsageError('review-evidence requires one JSON file or a document on stdin.');
714
- const document = args.mmdbSource
715
- ? await buildOfflineEvidenceReviewWithLocalResources(input, commandContext.now(), { mmdbPath: args.mmdbSource })
716
- : buildOfflineEvidenceReview(input, commandContext.now());
717
- if (!args.quiet)
718
- write(stdout, args.output === 'json'
719
- ? formatJsonDocument(document)
720
- : terminal(formatOfflineEvidenceReview(document), args.color));
721
- if (args.strictExit) {
722
- const result = document.result && typeof document.result === 'object' && !Array.isArray(document.result)
723
- ? document.result
724
- : {};
725
- const gate = result.gate && typeof result.gate === 'object' && !Array.isArray(result.gate)
726
- ? result.gate
727
- : null;
728
- const zoneMismatch = document.kind === 'zone_intent' && (result.complete !== true
729
- || (result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
730
- && ['different', 'missing', 'unexpected', 'incomplete'].some((key) => Number(result.counts[key]) > 0)));
731
- if (gate?.pass === false || zoneMismatch)
732
- return EXIT_CODES.PARTIAL_FAILURE;
733
- }
734
- return EXIT_CODES.SUCCESS;
735
- }
736
- if (args.action === 'brief' || args.action === 'case-pack') {
737
- const isBrief = args.action === 'brief';
738
- failureLabel = isBrief ? 'Lookup brief' : 'Case pack';
739
- let input;
740
- try {
741
- input = dependencies.readArtifactInput
742
- ? await dependencies.readArtifactInput(args.source)
743
- : await readInput(args.source, isBrief ? MAX_SAVED_LOOKUP_INPUT_BYTES : 4 * 1024 * 1024, isBrief ? 'Lookup brief input' : 'Case-pack input');
744
- }
745
- catch (error) {
746
- if (error instanceof CliUsageError)
747
- throw error;
748
- throw new CliUsageError(`Could not read ${isBrief ? 'Lookup brief' : 'case-pack'} input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
749
- }
750
- if (!input.trim())
751
- throw new CliUsageError(`${args.action} requires one JSON file or a document on stdin.`);
752
- if (args.action === 'brief') {
753
- const document = buildCliLookupBrief(input, commandContext.now());
754
- if (!args.quiet)
755
- write(stdout, args.output === 'json'
756
- ? formatJsonDocument(document)
757
- : terminal(formatCliLookupBrief(document), args.color));
758
- }
759
- else {
760
- const document = buildCliCasePack(input, { audience: args.audience, reviewed: args.reviewed }, commandContext.now());
761
- if (!args.quiet)
762
- write(stdout, args.output === 'json'
763
- ? formatJsonDocument(document)
764
- : terminal(formatCliCasePack(document), args.color));
765
- }
766
- return EXIT_CODES.SUCCESS;
767
- }
768
- if (args.action === 'domain-control') {
769
- failureLabel = 'Domain control review';
770
- let input;
771
- try {
772
- input = dependencies.readArtifactInput
773
- ? await dependencies.readArtifactInput(args.source)
774
- : await readInput(args.source, MAX_DOMAIN_CONTROL_REVIEW_COMMAND_BYTES, 'Domain control input');
775
- }
776
- catch (error) {
777
- if (error instanceof CliUsageError)
778
- throw error;
779
- throw new CliUsageError(`Could not read domain control input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
780
- }
781
- if (!input.trim())
782
- throw new CliUsageError('domain-control requires one JSON file or a document on stdin.');
783
- const normalizedDomainControlInput = input.replace(/^\uFEFF/u, '');
784
- let parsed;
785
- try {
786
- scanBoundedJson(normalizedDomainControlInput, {
787
- maximumDepth: MAX_DOMAIN_CONTROL_REVIEW_JSON_DEPTH,
788
- maximumKeys: MAX_DOMAIN_CONTROL_REVIEW_JSON_KEYS,
789
- maximumValues: MAX_DOMAIN_CONTROL_REVIEW_JSON_VALUES,
790
- });
791
- parsed = JSON.parse(normalizedDomainControlInput);
792
- }
793
- catch (cause) {
794
- const detail = cause instanceof Error ? cause.message : '';
795
- if (detail.startsWith('Artefact JSON ')) {
796
- throw new CliUsageError(`Domain control input ${detail.slice('Artefact JSON '.length)}`);
797
- }
798
- throw new CliUsageError('Domain control input is not valid JSON.');
799
- }
800
- const schema = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
801
- ? parsed.schema
802
- : null;
803
- const document = schema === DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA
804
- ? buildDomainControlManifest(parsed, commandContext.now())
805
- : schema === DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
806
- ? reviewDomainControlManifest(parsed, commandContext.now())
807
- : schema === CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
808
- ? buildCliDomainControlReview(input, commandContext.now())
809
- : schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
810
- ? buildDomainControlFlightRecorder(parsed, commandContext.now())
811
- : null;
812
- if (!document) {
813
- throw new CliUsageError(`Domain control input must use a supported manifest, review, saved-Lookup review, or flight-recorder schema.`);
814
- }
815
- const terminalDocument = schema === CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
816
- ? formatCliDomainControlReview(document)
817
- : schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
818
- ? formatDomainControlFlightRecorder(document)
819
- : formatDomainControlResult(document);
820
- if (!args.quiet)
821
- write(stdout, args.output === 'json'
822
- ? schema === DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA
823
- ? serializeDomainControlManifest(document)
824
- : schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
825
- ? serializeDomainControlFlightRecorder(document)
826
- : formatJsonDocument(document)
827
- : terminal(terminalDocument, args.color));
828
- return EXIT_CODES.SUCCESS;
829
- }
830
- if (args.action === 'monitor-once') {
831
- failureLabel = 'One-shot domain control review';
832
- let manifestInput;
833
- let previousInput = null;
834
- try {
835
- manifestInput = dependencies.readArtifactInput
836
- ? await dependencies.readArtifactInput(args.source)
837
- : await readInput(args.source, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Domain-control manifest input');
838
- if (args.previousSource) {
839
- previousInput = dependencies.readDiffInput
840
- ? await dependencies.readDiffInput(args.previousSource)
841
- : await readInput(args.previousSource, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Prior monitor snapshot');
842
- }
843
- }
844
- catch (error) {
845
- if (error instanceof CliUsageError)
846
- throw error;
847
- throw new CliUsageError(`Could not read monitor input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
848
- }
849
- if (!manifestInput.trim())
850
- throw new CliUsageError('monitor-once requires one domain-control manifest file or a document on stdin.');
851
- const executeLookup = dependencies.runUnifiedLookup || (await import("../lib/lookup.mjs")).runUnifiedLookup;
852
- const progress = commandContext.beginProgress('Collecting bounded domain-control evidence');
853
- let document;
854
- try {
855
- document = await runDomainControlMonitor(manifestInput, previousInput, {
856
- executeLookup,
857
- now: commandContext.now,
858
- limit: args.limit,
859
- concurrency: args.concurrency,
860
- ...(dependencies.signal ? { signal: dependencies.signal } : {}),
861
- onSettled: (completed, total) => progress.update(`Collected ${completed} of ${total} owned domains`),
862
- });
863
- }
864
- finally {
865
- commandContext.endProgress();
866
- }
867
- if (!args.quiet)
868
- write(stdout, args.output === 'json'
869
- ? formatJsonDocument(document)
870
- : args.output === 'junit'
871
- ? formatCliJunit(document)
872
- : terminal(formatDomainControlMonitor(document), args.color));
873
- const policyFindings = evaluateCliFailPolicies(document, args.failOn || []);
874
- if (policyFindings.length)
875
- write(stderr, formatFailPolicyNotice(policyFindings));
876
- return document.collection.failed || policyFindings.length ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
877
- }
878
- if (args.action === 'assurance') {
879
- failureLabel = 'Domain assurance review';
880
- let input;
881
- try {
882
- input = dependencies.readArtifactInput
883
- ? await dependencies.readArtifactInput(args.source)
884
- : await readInput(args.source, MAX_ASSURANCE_INPUT_BYTES, 'Domain assurance input');
885
- }
886
- catch (error) {
887
- if (error instanceof CliUsageError)
888
- throw error;
889
- throw new CliUsageError(`Could not read domain assurance input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
890
- }
891
- if (!input.trim())
892
- throw new CliUsageError('assurance requires one versioned JSON file or a document on stdin.');
893
- let parsed;
894
- try {
895
- scanBoundedJson(input);
896
- parsed = JSON.parse(input);
897
- }
898
- catch {
899
- throw new CliUsageError('Domain assurance input is not valid bounded JSON without duplicate keys.');
900
- }
901
- let document;
902
- try {
903
- document = buildDomainAssurance(parsed, commandContext.now());
904
- }
905
- catch (error) {
906
- throw new CliUsageError(boundedCliErrorMessage(error, 'Domain assurance input is invalid'));
907
- }
908
- if (!args.quiet)
909
- write(stdout, args.output === 'json'
910
- ? formatJsonDocument(document)
911
- : terminal(formatDomainAssurance(document), args.color));
912
- return EXIT_CODES.SUCCESS;
913
- }
914
- if (args.action === 'change-packet') {
915
- failureLabel = 'Domain change packet';
916
- let input;
917
- try {
918
- input = dependencies.readArtifactInput
919
- ? await dependencies.readArtifactInput(args.source)
920
- : await readInput(args.source, MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES, 'Domain change packet input');
921
- }
922
- catch (error) {
923
- if (error instanceof CliUsageError)
924
- throw error;
925
- throw new CliUsageError(`Could not read domain change packet input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
926
- }
927
- if (!input.trim())
928
- throw new CliUsageError('change-packet requires one versioned JSON file or a document on stdin.');
929
- let parsed;
930
- try {
931
- scanBoundedJson(input);
932
- parsed = JSON.parse(input);
933
- }
934
- catch {
935
- throw new CliUsageError('Domain change packet input is not valid bounded JSON without duplicate keys.');
936
- }
937
- let document;
938
- try {
939
- document = await buildDomainChangePacket(parsed, commandContext.now());
940
- }
941
- catch (error) {
942
- throw new CliUsageError(boundedCliErrorMessage(error, 'Domain change packet input is invalid'));
943
- }
944
- if (!args.quiet)
945
- write(stdout, args.output === 'json'
946
- ? formatJsonDocument(document)
947
- : terminal(formatDomainChangePacket(document), args.color));
948
- return document.gate.pass ? EXIT_CODES.SUCCESS : EXIT_CODES.PARTIAL_FAILURE;
949
- }
950
- if (args.action === 'sharing-review') {
951
- failureLabel = 'Evidence sharing review';
952
- let input;
953
- try {
954
- input = dependencies.readArtifactInput
955
- ? await dependencies.readArtifactInput(args.source)
956
- : await readInput(args.source, MAX_SHARING_REVIEW_BYTES, 'Sharing review input');
957
- }
958
- catch (error) {
959
- if (error instanceof CliUsageError)
960
- throw error;
961
- throw new CliUsageError(`Could not read sharing review input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
962
- }
963
- if (!input.trim())
964
- throw new CliUsageError('sharing-review requires one artefact JSON file or a document on stdin.');
965
- let document;
966
- try {
967
- document = await buildSharingReview(input, {
968
- marking: args.marking,
969
- recipientScope: args.recipientScope,
970
- purpose: args.purpose,
971
- humanReviewed: args.humanReviewed,
972
- personalDataReviewed: args.personalDataReviewed,
973
- redactionsConfirmed: args.redactionsConfirmed,
974
- }, commandContext.now());
975
- }
976
- catch (error) {
977
- throw new CliUsageError(boundedCliErrorMessage(error, 'Sharing review input is invalid'));
978
- }
979
- if (!args.quiet)
980
- write(stdout, args.output === 'json'
981
- ? formatJsonDocument(document)
982
- : terminal(formatSharingReview(document), args.color));
983
- return document.summary.status === 'blocked' ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
984
- }
985
- if (args.action === 'workflow-plan') {
986
- failureLabel = 'Investigation plan';
987
- if ('discovery' in args) {
988
- const catalogue = buildWorkflowRecipeCatalogue(args.discovery === 'explain' ? args.recipe : null);
989
- if (!args.quiet)
990
- write(stdout, args.output === 'json'
991
- ? formatJsonDocument(catalogue)
992
- : terminal(formatWorkflowRecipeCatalogue(catalogue), args.color));
993
- return EXIT_CODES.SUCCESS;
994
- }
995
- const document = buildInvestigationPlan(args.recipe, args.subject, commandContext.now());
996
- if (!args.quiet)
997
- write(stdout, args.output === 'json'
998
- ? formatJsonDocument(document)
999
- : terminal(formatInvestigationPlan(document), args.color));
1000
- return EXIT_CODES.SUCCESS;
1001
- }
1002
- if (args.action === 'workflow-run') {
1003
- failureLabel = 'Investigation workflow';
1004
- let resumeInput = null;
1005
- if (args.resumeSource) {
1006
- try {
1007
- resumeInput = dependencies.readDiffInput
1008
- ? await dependencies.readDiffInput(args.resumeSource)
1009
- : await readInput(args.resumeSource, MAX_INVESTIGATION_RUN_BYTES, 'Investigation resume state');
1010
- }
1011
- catch (error) {
1012
- if (error instanceof CliUsageError)
1013
- throw error;
1014
- throw new CliUsageError(`Could not read investigation resume state: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
1015
- }
1016
- }
1017
- const document = await runInvestigationRecipe(args.recipe, args.subject, {
1018
- approveNetwork: args.approveNetwork,
1019
- resumeInput,
1020
- generatedAt: commandContext.now(),
1021
- ...(dependencies.signal ? { signal: dependencies.signal } : {}),
1022
- execute: async (command, stepArguments) => {
1023
- const stepStdout = createBufferedOutput();
1024
- const stepStderr = createBufferedOutput();
1025
- const exitCode = await runCli([command, ...stepArguments], {
1026
- ...dependencies,
1027
- stdout: stepStdout.stream,
1028
- stderr: stepStderr.stream,
1029
- });
1030
- return { exitCode, stdout: stepStdout.value() };
1031
- },
1032
- });
1033
- if (!args.quiet)
1034
- write(stdout, args.output === 'json'
1035
- ? formatJsonDocument(document)
1036
- : terminal(formatInvestigationRun(document), args.color));
1037
- return document.state === 'step_failed' ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
1038
- }
1039
- if (args.action === 'diff') {
1040
- failureLabel = 'Retained artifact diff';
1041
- const readDiffInput = dependencies.readDiffInput
1042
- || ((source) => readInput(source, MAX_RETAINED_ARTIFACT_DIFF_BYTES, 'Retained diff input'));
1043
- let leftInput;
1044
- let rightInput;
1045
- try {
1046
- [leftInput, rightInput] = await Promise.all([
1047
- readDiffInput(args.leftSource),
1048
- readDiffInput(args.rightSource),
1049
- ]);
1050
- }
1051
- catch (error) {
1052
- if (error instanceof CliUsageError)
1053
- throw error;
1054
- throw new CliUsageError(`Could not read retained diff input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
1055
- }
1056
- const document = buildCliRetainedArtifactDiff(leftInput, rightInput, { leftSessionId: args.leftSessionId, rightSessionId: args.rightSessionId }, dependencies.now ? dependencies.now() : new Date().toISOString());
1057
- if (!args.quiet)
1058
- write(stdout, args.output === 'json'
1059
- ? formatJsonDocument(document)
1060
- : terminal(formatCliRetainedArtifactDiff(document), args.color));
1061
- return EXIT_CODES.SUCCESS;
1062
- }
1063
- if (args.action === 'reconcile') {
1064
- failureLabel = 'Lookup observation reconciliation';
1065
- const readReconciliationInput = dependencies.readDiffInput
1066
- || ((source) => readInput(source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Lookup reconciliation input'));
1067
- const inputs = [];
1068
- let totalBytes = 0;
1069
- try {
1070
- for (const source of args.sources) {
1071
- const input = await readReconciliationInput(source);
1072
- totalBytes += Buffer.byteLength(input, 'utf8');
1073
- if (totalBytes > MAX_LOOKUP_RECONCILIATION_INPUT_BYTES) {
1074
- throw new CliUsageError(`Lookup reconciliation input is limited to ${MAX_LOOKUP_RECONCILIATION_INPUT_BYTES} bytes in total.`);
1075
- }
1076
- inputs.push(input);
1077
- }
1078
- }
1079
- catch (error) {
1080
- if (error instanceof CliUsageError)
1081
- throw error;
1082
- throw new CliUsageError(`Could not read Lookup reconciliation input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
1083
- }
1084
- const document = buildCliLookupReconciliation(inputs, commandContext.now());
1085
- if (!args.quiet)
1086
- write(stdout, args.output === 'json'
1087
- ? formatJsonDocument(document)
1088
- : terminal(formatCliLookupReconciliation(document), args.color));
1089
- return EXIT_CODES.SUCCESS;
1090
- }
1091
- if (args.action === 'timeline') {
1092
- failureLabel = 'Lookup observation timeline';
1093
- const readTimelineInput = dependencies.readDiffInput
1094
- || ((source) => readInput(source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Lookup timeline input'));
1095
- const inputs = [];
1096
- let totalBytes = 0;
1097
- try {
1098
- for (const source of args.sources) {
1099
- const input = await readTimelineInput(source);
1100
- totalBytes += Buffer.byteLength(input, 'utf8');
1101
- if (totalBytes > MAX_LOOKUP_TIMELINE_INPUT_BYTES) {
1102
- throw new CliUsageError(`Lookup timeline input is limited to ${MAX_LOOKUP_TIMELINE_INPUT_BYTES} bytes in total.`);
1103
- }
1104
- inputs.push(input);
1105
- }
1106
- }
1107
- catch (error) {
1108
- if (error instanceof CliUsageError)
1109
- throw error;
1110
- throw new CliUsageError(`Could not read Lookup timeline input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
1111
- }
1112
- const document = buildCliLookupTimeline(inputs, commandContext.now());
1113
- if (!args.quiet)
1114
- write(stdout, args.output === 'json'
1115
- ? formatJsonDocument(document)
1116
- : terminal(formatCliLookupTimeline(document), args.color));
1117
- return EXIT_CODES.SUCCESS;
1118
- }
1119
- if (args.action === 'export') {
1120
- failureLabel = 'Evidence export';
1121
- let input;
1122
- try {
1123
- input = dependencies.readExportInput
1124
- ? await dependencies.readExportInput(args.source)
1125
- : await readInput(args.source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Evidence export input');
1126
- }
1127
- catch (error) {
1128
- if (error instanceof CliUsageError)
1129
- throw error;
1130
- throw new CliUsageError(`Could not read evidence export input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
1131
- }
1132
- if (!input.trim())
1133
- throw new CliUsageError('export requires one lookup JSON file or a lookup document on stdin.');
1134
- const loadEvidence = dependencies.loadEvidenceExport || (() => import("../lib/evidence-export.mjs"));
1135
- const evidenceModule = await loadEvidence();
1136
- const now = dependencies.now ? dependencies.now() : new Date().toISOString();
1137
- const document = buildCliEvidenceExport(input, evidenceModule, now);
1138
- const output = args.format === 'markdown'
1139
- ? formatLookupEvidenceMarkdown(document, { includeAttribution: args.includeAttribution })
1140
- : args.format === 'html'
1141
- ? formatLookupEvidenceHtml(document, { includeAttribution: args.includeAttribution })
1142
- : formatCliEvidenceExport(document, args.compact);
1143
- write(stdout, output);
1144
- return EXIT_CODES.SUCCESS;
1145
- }
1146
- if (args.action === 'ct-intake') {
1147
- failureLabel = 'Certificate event intake';
1148
- let input;
1149
- try {
1150
- input = dependencies.readArtifactInput
1151
- ? await dependencies.readArtifactInput(args.source)
1152
- : await readInput(args.source, MAX_CT_EVENT_INPUT_BYTES, 'Certificate event input');
1153
- }
1154
- catch (error) {
1155
- if (error instanceof CliUsageError)
1156
- throw error;
1157
- throw new CliUsageError(`Could not read certificate event input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
1158
- }
1159
- if (!input.trim())
1160
- throw new CliUsageError('ct-intake requires one versioned JSON file or a document on stdin.');
1161
- let parsed;
1162
- try {
1163
- scanBoundedJson(input);
1164
- parsed = JSON.parse(input);
1165
- }
1166
- catch {
1167
- throw new CliUsageError('Certificate event input is not valid bounded JSON without duplicate keys.');
1168
- }
1169
- let document;
1170
- try {
1171
- document = buildCtEventFindings(parsed);
1172
- }
1173
- catch (error) {
1174
- throw new CliUsageError(boundedCliErrorMessage(error, 'Certificate event input is invalid'));
1175
- }
1176
- if (!args.quiet)
1177
- write(stdout, args.output === 'json'
1178
- ? formatJsonDocument(document)
1179
- : terminal(formatCtEventFindings(document), args.color));
1180
- return EXIT_CODES.SUCCESS;
1181
- }
1182
- throw new Error('No CLI execution route is registered for the parsed command.');
225
+ return await runInlineCommand(args, dependencies, commandContext);
1183
226
  }
1184
227
  catch (error) {
1185
228
  progress?.stop();