@slicedearth/whoisleuth-cli 1.39.1 → 1.45.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 (90) hide show
  1. package/README.md +23 -4
  2. package/bin/whoisleuth.mjs +31 -9
  3. package/cli/archive-inspect.mjs +37 -2
  4. package/cli/arguments.mjs +361 -256
  5. package/cli/artifact-verify.mjs +35 -1
  6. package/cli/bulk-checkpoint.mjs +72 -39
  7. package/cli/bulk-command-runner.mjs +34 -4
  8. package/cli/bulk.mjs +18 -7
  9. package/cli/case-pack.mjs +136 -0
  10. package/cli/ci-report.mjs +73 -0
  11. package/cli/collection-preflight.mjs +59 -0
  12. package/cli/command-reference.mjs +395 -0
  13. package/cli/completion.mjs +67 -13
  14. package/cli/config-profile.mjs +183 -0
  15. package/cli/ct-event-intake.mjs +159 -0
  16. package/cli/discovery-observation-snapshot.mjs +8 -14
  17. package/cli/discovery-scan-command-runner.mjs +29 -2
  18. package/cli/discovery-snapshot.mjs +8 -15
  19. package/cli/domain-control-monitor.mjs +104 -0
  20. package/cli/domain-control-observations.mjs +235 -0
  21. package/cli/errors.mjs +7 -1
  22. package/cli/evidence-command-arguments.mjs +11 -0
  23. package/cli/evidence-command-runner.mjs +1 -0
  24. package/cli/external-observation-mapping.mjs +162 -0
  25. package/cli/fail-policy.mjs +64 -0
  26. package/cli/formatters/html.mjs +1 -1
  27. package/cli/formatters/json.mjs +12 -4
  28. package/cli/formatters/terminal.mjs +1 -1
  29. package/cli/http.mjs +2 -1
  30. package/cli/investigation-manifest.mjs +113 -0
  31. package/cli/investigation-run.mjs +112 -0
  32. package/cli/lookup-brief.mjs +116 -0
  33. package/cli/lookup-command-runner.mjs +8 -1
  34. package/cli/lookup-diff.mjs +17 -19
  35. package/cli/lookup-reconcile.mjs +1 -1
  36. package/cli/mail-review.mjs +5 -5
  37. package/cli/manual.mjs +1 -1
  38. package/cli/network-command-runner.mjs +4 -1
  39. package/cli/offline-evidence-review.mjs +105 -8
  40. package/cli/open-asset-model-bridge.mjs +169 -0
  41. package/cli/registry-cohort.mjs +121 -0
  42. package/cli/registry-doctor.mjs +106 -11
  43. package/cli/registry-fixture-scaffold.mjs +74 -0
  44. package/cli/runner.mjs +388 -315
  45. package/docs/cli-reference.md +266 -21
  46. package/docs/cli.md +43 -7
  47. package/frontend/src/lib/analysis/acquisition-due-diligence.js +2 -2
  48. package/frontend/src/lib/analysis/brand-profile-model.js +50 -6
  49. package/frontend/src/lib/analysis/case-evidence-model.js +1 -1
  50. package/frontend/src/lib/analysis/case-record-core.js +1 -1
  51. package/frontend/src/lib/analysis/case-record-operations.js +1 -1
  52. package/frontend/src/lib/analysis/case-report.js +573 -0
  53. package/frontend/src/lib/analysis/case-response-model.js +30 -1
  54. package/frontend/src/lib/analysis/detection-rule-model.js +36 -0
  55. package/frontend/src/lib/analysis/external-findings-import.js +320 -0
  56. package/frontend/src/lib/analysis/lookup-asset-graph.js +8 -8
  57. package/frontend/src/lib/analysis/lookup-decision-support.js +9 -2
  58. package/frontend/src/lib/analysis/lookup-presentation.js +14 -30
  59. package/frontend/src/lib/analysis/lookup-source-refresh.js +36 -63
  60. package/frontend/src/lib/analysis/scoring.js +1 -1
  61. package/frontend/src/lib/analysis/utils.js +1 -1
  62. package/frontend/src/lib/analysis/visualization-models.js +2 -3
  63. package/frontend/src/lib/analysis/workspace-archive-crypto.js +1 -4
  64. package/frontend/src/lib/analysis/workspace-archive.js +5 -7
  65. package/lib/abusech-auth.mjs +1 -1
  66. package/lib/availability.mjs +1 -1
  67. package/lib/bounded-contract-normalizers.mjs +33 -0
  68. package/lib/bounded-file.mjs +69 -0
  69. package/lib/bounded-json-response.mjs +157 -0
  70. package/lib/browser-library-profile.mjs +167 -38
  71. package/lib/dns-convergence-review.mjs +183 -0
  72. package/lib/dns-delegation-health.mjs +196 -3
  73. package/lib/domain-change-packet.mjs +133 -0
  74. package/lib/domain-change-review.mjs +307 -0
  75. package/lib/domain-control-flight-recorder.mjs +268 -0
  76. package/lib/domain-portfolio-review.mjs +151 -0
  77. package/lib/evidence-export.mjs +1 -1
  78. package/lib/html-signals.mjs +5 -11
  79. package/lib/lookup-cache.mjs +5 -1
  80. package/lib/nameserver-preflight-review.mjs +159 -0
  81. package/lib/page-language-signals.mjs +124 -0
  82. package/lib/rdap-transport.mjs +6 -6
  83. package/lib/risk-scoring.mjs +42 -1
  84. package/lib/safe-fetch.mjs +37 -32
  85. package/lib/static-html-analysis.mjs +29 -1
  86. package/lib/threat-intelligence-runtime.mjs +1 -2
  87. package/lib/trust-store-comparison.mjs +146 -0
  88. package/lib/website-technology.mjs +22 -12
  89. package/lib/zone-intent-review.mjs +534 -0
  90. package/package.json +1 -1
package/cli/arguments.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { parseInspectArchiveArguments, parseSignArtifactArguments, parseVerifySignatureArguments, } from "./evidence-command-arguments.mjs";
2
2
  import { CliUsageError } from "./errors.mjs";
3
3
  import { INVESTIGATION_PLAN_RECIPES, } from "./investigation-plan.mjs";
4
+ import { parseCliFailPolicies } from "./fail-policy.mjs";
4
5
  const MAX_CLI_ARGUMENTS = 32;
5
6
  const MAX_CLI_ARGUMENT_LENGTH = 1024;
6
7
  const CLI_COMMANDS = [
@@ -8,9 +9,13 @@ const CLI_COMMANDS = [
8
9
  'doctor',
9
10
  'commands',
10
11
  'manual',
12
+ 'manifest',
13
+ 'map-observations',
14
+ 'oam-export',
11
15
  'lookup',
12
16
  'bulk',
13
17
  'ct-search',
18
+ 'ct-intake',
14
19
  'discover',
15
20
  'discover-scan',
16
21
  'posture',
@@ -18,6 +23,8 @@ const CLI_COMMANDS = [
18
23
  'tls',
19
24
  'registry-support',
20
25
  'registry-doctor',
26
+ 'registry-cohort',
27
+ 'registry-scaffold',
21
28
  'risk-calibrate',
22
29
  'lookalike-calibrate',
23
30
  'verify-artifact',
@@ -29,10 +36,15 @@ const CLI_COMMANDS = [
29
36
  'page-compare',
30
37
  'mail-review',
31
38
  'review-evidence',
39
+ 'brief',
40
+ 'case-pack',
32
41
  'domain-control',
42
+ 'monitor-once',
33
43
  'assurance',
44
+ 'change-packet',
34
45
  'sharing-review',
35
46
  'workflow-plan',
47
+ 'workflow-run',
36
48
  'diff',
37
49
  'reconcile',
38
50
  'timeline',
@@ -111,7 +123,8 @@ function parseCliArgumentsCore(argv) {
111
123
  }
112
124
  const command = firstArgument;
113
125
  if (!isCliCommand(command)) {
114
- throw new CliUsageError(`Unknown command "${command}". This release supports: ${CLI_COMMANDS.join(', ')}.`);
126
+ const displayedCommand = command.length > 80 ? `${command.slice(0, 79)}…` : command;
127
+ throw new CliUsageError(`Unknown command "${displayedCommand}". Run "whoisleuth commands" to list supported commands.`);
115
128
  }
116
129
  if (command === 'bulk')
117
130
  return parseBulkArguments(argv.slice(1));
@@ -123,8 +136,16 @@ function parseCliArgumentsCore(argv) {
123
136
  return parseCommandsArguments(argv.slice(1));
124
137
  if (command === 'manual')
125
138
  return parseManualArguments(argv.slice(1));
139
+ if (command === 'manifest')
140
+ return parseManifestArguments(argv.slice(1));
141
+ if (command === 'map-observations')
142
+ return parseOfflineImportArguments('map-observations', argv.slice(1));
143
+ if (command === 'oam-export')
144
+ return parseOfflineImportArguments('oam-export', argv.slice(1));
126
145
  if (command === 'ct-search')
127
146
  return parseCtSearchArguments(argv.slice(1));
147
+ if (command === 'ct-intake')
148
+ return parseCtIntakeArguments(argv.slice(1));
128
149
  if (command === 'discover')
129
150
  return parseDiscoverArguments(argv.slice(1));
130
151
  if (command === 'discover-scan')
@@ -139,6 +160,10 @@ function parseCliArgumentsCore(argv) {
139
160
  return parseRegistrySupportArguments(argv.slice(1));
140
161
  if (command === 'registry-doctor')
141
162
  return parseRegistryDoctorArguments(argv.slice(1));
163
+ if (command === 'registry-cohort')
164
+ return parseRegistryCohortArguments(argv.slice(1));
165
+ if (command === 'registry-scaffold')
166
+ return parseRegistryScaffoldArguments(argv.slice(1));
142
167
  if (command === 'risk-calibrate')
143
168
  return parseRiskCalibrateArguments(argv.slice(1));
144
169
  if (command === 'lookalike-calibrate')
@@ -161,14 +186,24 @@ function parseCliArgumentsCore(argv) {
161
186
  return parseMailReviewArguments(argv.slice(1));
162
187
  if (command === 'review-evidence')
163
188
  return parseReviewEvidenceArguments(argv.slice(1));
189
+ if (command === 'brief')
190
+ return parseBriefArguments(argv.slice(1));
191
+ if (command === 'case-pack')
192
+ return parseCasePackArguments(argv.slice(1));
164
193
  if (command === 'domain-control')
165
194
  return parseDomainControlArguments(argv.slice(1));
195
+ if (command === 'monitor-once')
196
+ return parseMonitorOnceArguments(argv.slice(1));
166
197
  if (command === 'assurance')
167
198
  return parseAssuranceArguments(argv.slice(1));
199
+ if (command === 'change-packet')
200
+ return parseChangePacketArguments(argv.slice(1));
168
201
  if (command === 'sharing-review')
169
202
  return parseSharingReviewArguments(argv.slice(1));
170
203
  if (command === 'workflow-plan')
171
204
  return parseWorkflowPlanArguments(argv.slice(1));
205
+ if (command === 'workflow-run')
206
+ return parseWorkflowRunArguments(argv.slice(1));
172
207
  if (command === 'diff')
173
208
  return parseDiffArguments(argv.slice(1));
174
209
  if (command === 'reconcile')
@@ -188,6 +223,7 @@ function parseCliArgumentsCore(argv) {
188
223
  let strictExit = false;
189
224
  let events = false;
190
225
  let plan = false;
226
+ let failOn = null;
191
227
  let observerLabel = null;
192
228
  let vantageLabel = null;
193
229
  const lookupArguments = argv.slice(1);
@@ -200,6 +236,11 @@ function parseCliArgumentsCore(argv) {
200
236
  throw new CliUsageError('Choose only one output format.');
201
237
  output = 'json';
202
238
  }
239
+ else if (argument === '--junit') {
240
+ if (output !== 'terminal')
241
+ throw new CliUsageError('Choose only one output format.');
242
+ output = 'junit';
243
+ }
203
244
  else if (argument === '--markdown' || argument === '--html') {
204
245
  if (output !== 'terminal')
205
246
  throw new CliUsageError('Choose only one output format.');
@@ -238,6 +279,11 @@ function parseCliArgumentsCore(argv) {
238
279
  throw new CliUsageError('--plan may be supplied only once.');
239
280
  plan = true;
240
281
  }
282
+ else if (argument === '--fail-on') {
283
+ if (failOn !== null)
284
+ throw new CliUsageError('--fail-on may be supplied only once.');
285
+ failOn = parseCliFailPolicies(lookupArguments[++index]);
286
+ }
241
287
  else if (argument === '--observer' || argument === '--vantage') {
242
288
  const isObserver = argument === '--observer';
243
289
  if (isObserver ? observerLabel !== null : vantageLabel !== null) {
@@ -269,12 +315,12 @@ function parseCliArgumentsCore(argv) {
269
315
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
270
316
  if (detailSet && output !== 'terminal')
271
317
  throw new CliUsageError('--summary and --verbose apply only to terminal output.');
272
- if (plan && (output === 'markdown' || output === 'html'))
318
+ if (plan && !['terminal', 'json'].includes(output))
273
319
  throw new CliUsageError('--plan supports terminal or JSON output only.');
274
- if (plan && (detailSet || strictExit || events || quiet)) {
320
+ if (plan && (detailSet || strictExit || events || quiet || failOn !== null)) {
275
321
  throw new CliUsageError('--plan cannot be combined with detail, strict-exit, event, or quiet options.');
276
322
  }
277
- return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, observerLabel, vantageLabel, quiet, color };
323
+ return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, observerLabel, vantageLabel, quiet, color, ...(failOn ? { failOn } : {}) };
278
324
  }
279
325
  function parseCompletionArguments(argv) {
280
326
  if (argv.length !== 1 || !['bash', 'zsh', 'fish', 'powershell'].includes(argv[0] || '')) {
@@ -287,6 +333,84 @@ function parseManualArguments(argv) {
287
333
  throw new CliUsageError('manual does not accept command arguments.');
288
334
  return { action: 'manual' };
289
335
  }
336
+ function parseManifestArguments(argv) {
337
+ const sources = [];
338
+ let workflow = null;
339
+ let configurationDigestSha256 = null;
340
+ let output = 'terminal';
341
+ let quiet = false;
342
+ let color = true;
343
+ for (let index = 0; index < argv.length; index += 1) {
344
+ const argument = argv[index];
345
+ if (argument === '--json') {
346
+ if (output !== 'terminal')
347
+ throw new CliUsageError('--json may be supplied only once.');
348
+ output = 'json';
349
+ }
350
+ else if (argument === '--workflow') {
351
+ if (workflow !== null)
352
+ throw new CliUsageError('--workflow may be supplied only once.');
353
+ workflow = argv[++index] ?? null;
354
+ if (!workflow)
355
+ throw new CliUsageError('--workflow requires a bounded label.');
356
+ }
357
+ else if (argument === '--configuration-digest') {
358
+ if (configurationDigestSha256 !== null)
359
+ throw new CliUsageError('--configuration-digest may be supplied only once.');
360
+ configurationDigestSha256 = argv[++index] ?? null;
361
+ if (!configurationDigestSha256 || !/^sha256:[a-f0-9]{64}$/u.test(configurationDigestSha256)) {
362
+ throw new CliUsageError('--configuration-digest requires a sha256: hexadecimal digest.');
363
+ }
364
+ }
365
+ else if (argument === '--quiet')
366
+ quiet = true;
367
+ else if (argument === '--no-color')
368
+ color = false;
369
+ else if (argument?.startsWith('-'))
370
+ throw new CliUsageError(`Unknown option "${argument}".`);
371
+ else if (argument)
372
+ sources.push(argument);
373
+ }
374
+ if (!workflow)
375
+ throw new CliUsageError('manifest requires --workflow <label>.');
376
+ if (sources.length < 1 || sources.length > 16)
377
+ throw new CliUsageError('manifest requires from 1 to 16 JSON artefact files.');
378
+ if (new Set(sources).size !== sources.length)
379
+ throw new CliUsageError('manifest artefact files must be different.');
380
+ if (quiet && output !== 'terminal')
381
+ throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
382
+ return { action: 'manifest', sources, workflow, configurationDigestSha256, output, quiet, color };
383
+ }
384
+ function parseSingleJsonInput(argv, overflowMessage) {
385
+ let source = null;
386
+ let output = 'terminal';
387
+ let quiet = false;
388
+ let color = true;
389
+ for (const argument of argv) {
390
+ if (argument === '--json') {
391
+ if (output !== 'terminal')
392
+ throw new CliUsageError('--json may be supplied only once.');
393
+ output = 'json';
394
+ }
395
+ else if (argument === '--quiet')
396
+ quiet = true;
397
+ else if (argument === '--no-color')
398
+ color = false;
399
+ else if (argument.startsWith('-'))
400
+ throw new CliUsageError(`Unknown option "${argument}".`);
401
+ else if (source === null)
402
+ source = argument;
403
+ else
404
+ throw new CliUsageError(overflowMessage);
405
+ }
406
+ if (quiet && output !== 'terminal')
407
+ throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
408
+ return { source, output, quiet, color };
409
+ }
410
+ function parseOfflineImportArguments(action, argv) {
411
+ const parsed = parseSingleJsonInput(argv, `${action} accepts one optional versioned JSON input file.`);
412
+ return { action, ...parsed };
413
+ }
290
414
  function parseCommandsArguments(argv) {
291
415
  let output = 'terminal';
292
416
  let quiet = false;
@@ -346,12 +470,14 @@ function parseBulkArguments(argv) {
346
470
  let checkpoint = null;
347
471
  let resume = false;
348
472
  let events = false;
473
+ let plan = false;
474
+ let failOn = null;
349
475
  let filter = 'all';
350
476
  for (let index = 0; index < argv.length; index++) {
351
477
  const argument = argv[index];
352
478
  if (argument === undefined)
353
479
  break;
354
- if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains' || argument === '--queries') {
480
+ if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains' || argument === '--queries' || argument === '--junit') {
355
481
  if (output !== 'terminal')
356
482
  throw new CliUsageError('Choose only one output format.');
357
483
  output = argument === '--json'
@@ -362,7 +488,9 @@ function parseBulkArguments(argv) {
362
488
  ? 'csv'
363
489
  : argument === '--domains'
364
490
  ? 'domains'
365
- : 'queries';
491
+ : argument === '--queries'
492
+ ? 'queries'
493
+ : 'junit';
366
494
  }
367
495
  else if (argument === '--deep' || argument === '--fast') {
368
496
  if (scanMode)
@@ -398,6 +526,16 @@ function parseBulkArguments(argv) {
398
526
  throw new CliUsageError('--events may be supplied only once.');
399
527
  events = true;
400
528
  }
529
+ else if (argument === '--plan') {
530
+ if (plan)
531
+ throw new CliUsageError('--plan may be supplied only once.');
532
+ plan = true;
533
+ }
534
+ else if (argument === '--fail-on') {
535
+ if (failOn !== null)
536
+ throw new CliUsageError('--fail-on may be supplied only once.');
537
+ failOn = parseCliFailPolicies(argv[++index]);
538
+ }
401
539
  else if (argument === '--registered-only' || argument === '--inconclusive-only' || argument === '--errors-only') {
402
540
  if (filter !== 'all')
403
541
  throw new CliUsageError('Bulk output filters are mutually exclusive and may be supplied only once.');
@@ -422,37 +560,22 @@ function parseBulkArguments(argv) {
422
560
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
423
561
  if (resume && checkpoint === null)
424
562
  throw new CliUsageError('--resume requires --checkpoint.');
563
+ if (plan && (events || resume || checkpoint !== null || quiet || failOn !== null || !['terminal', 'json'].includes(output))) {
564
+ throw new CliUsageError('--plan supports terminal or JSON output and cannot be combined with events, checkpoint, resume, or quiet options.');
565
+ }
425
566
  const maximum = deep ? 3 : 8;
426
567
  if (concurrency !== null && concurrency > maximum) {
427
568
  throw new CliUsageError(`--concurrency is capped at ${maximum} in ${deep ? 'deep' : 'fast'} bulk mode.`);
428
569
  }
429
- return { action: 'bulk', source, output, deep, quiet, color, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint, resume, events, filter };
570
+ return { action: 'bulk', source, output, deep, quiet, color, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint, resume, events, plan, filter, ...(failOn ? { failOn } : {}) };
430
571
  }
431
572
  function parseCtSearchArguments(argv) {
432
- let keyword = null;
433
- let output = 'terminal';
434
- let quiet = false;
435
- let color = true;
436
- for (const argument of argv) {
437
- if (argument === '--json') {
438
- if (output !== 'terminal')
439
- throw new CliUsageError('--json may be supplied only once.');
440
- output = 'json';
441
- }
442
- else if (argument === '--quiet')
443
- quiet = true;
444
- else if (argument === '--no-color')
445
- color = false;
446
- else if (argument.startsWith('-'))
447
- throw new CliUsageError(`Unknown option "${argument}".`);
448
- else if (keyword === null)
449
- keyword = argument;
450
- else
451
- throw new CliUsageError('ct-search accepts one keyword. Quote multi-word keywords as one argument.');
452
- }
453
- if (quiet && output !== 'terminal')
454
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
455
- return { action: 'ct-search', keyword, output, quiet, color };
573
+ const { source: keyword, ...options } = parseSingleJsonInput(argv, 'ct-search accepts one keyword. Quote multi-word keywords as one argument.');
574
+ return { action: 'ct-search', keyword, ...options };
575
+ }
576
+ function parseCtIntakeArguments(argv) {
577
+ const parsed = parseSingleJsonInput(argv, 'ct-intake accepts one optional versioned JSON event file.');
578
+ return { action: 'ct-intake', ...parsed };
456
579
  }
457
580
  function parseDiscoverArguments(argv) {
458
581
  let seed = null;
@@ -576,6 +699,8 @@ function parseDiscoverScanArguments(argv) {
576
699
  let filter = 'all';
577
700
  let filterSet = false;
578
701
  let events = false;
702
+ let plan = false;
703
+ let failOn = null;
579
704
  for (let index = 0; index < argv.length; index++) {
580
705
  const argument = argv[index];
581
706
  if (argument === undefined)
@@ -706,6 +831,16 @@ function parseDiscoverScanArguments(argv) {
706
831
  throw new CliUsageError('--events may be supplied only once.');
707
832
  events = true;
708
833
  }
834
+ else if (argument === '--plan') {
835
+ if (plan)
836
+ throw new CliUsageError('--plan may be supplied only once.');
837
+ plan = true;
838
+ }
839
+ else if (argument === '--fail-on') {
840
+ if (failOn !== null)
841
+ throw new CliUsageError('--fail-on may be supplied only once.');
842
+ failOn = parseCliFailPolicies(argv[++index]);
843
+ }
709
844
  else if (argument === '--quiet')
710
845
  quiet = true;
711
846
  else if (argument === '--no-color')
@@ -726,6 +861,9 @@ function parseDiscoverScanArguments(argv) {
726
861
  }
727
862
  if (resume && checkpoint === null)
728
863
  throw new CliUsageError('--resume requires --checkpoint.');
864
+ if (plan && (events || resume || checkpoint !== null || observationSnapshot !== null || quiet || failOn !== null || !['terminal', 'json'].includes(output))) {
865
+ throw new CliUsageError('--plan supports terminal or JSON output and cannot be combined with events, checkpoint, resume, observation snapshots, or quiet options.');
866
+ }
729
867
  if (quiet && output !== 'terminal')
730
868
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
731
869
  if (dictionarySource && preset === 'common')
@@ -734,7 +872,7 @@ function parseDiscoverScanArguments(argv) {
734
872
  action: 'discover-scan', seed, output, quiet, color, preset, keyboardLayout, tldText,
735
873
  dictionarySource, familyText, deep, scanLimit: scanLimit ?? Math.min(100, maximum),
736
874
  chunkSize: chunkSize ?? 25, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint,
737
- resume, resolverText, observationSnapshot, allowlistSource, filter, events,
875
+ resume, resolverText, observationSnapshot, allowlistSource, filter, events, plan, ...(failOn ? { failOn } : {}),
738
876
  };
739
877
  }
740
878
  function positiveIntegerOption(value, option, maximum) {
@@ -754,6 +892,7 @@ function parsePostureArguments(argv) {
754
892
  let retiredSelectorText = null;
755
893
  let mailProfile = 'standard';
756
894
  let mailProfileSeen = false;
895
+ let ownedDomain = false;
757
896
  for (let index = 0; index < argv.length; index++) {
758
897
  const argument = argv[index];
759
898
  if (argument === undefined)
@@ -763,6 +902,16 @@ function parsePostureArguments(argv) {
763
902
  throw new CliUsageError('--json may be supplied only once.');
764
903
  output = 'json';
765
904
  }
905
+ else if (argument === '--sarif') {
906
+ if (output !== 'terminal')
907
+ throw new CliUsageError('Choose only one output format.');
908
+ output = 'sarif';
909
+ }
910
+ else if (argument === '--owned-domain') {
911
+ if (ownedDomain)
912
+ throw new CliUsageError('--owned-domain may be supplied only once.');
913
+ ownedDomain = true;
914
+ }
766
915
  else if (argument === '--selectors') {
767
916
  if (selectorText !== null)
768
917
  throw new CliUsageError('--selectors may be supplied only once.');
@@ -802,85 +951,21 @@ function parsePostureArguments(argv) {
802
951
  }
803
952
  if (quiet && output !== 'terminal')
804
953
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
805
- return { action: 'posture', domain, output, quiet, color, selectorText, retiredSelectorText, mailProfile };
954
+ if (output === 'sarif' && !ownedDomain)
955
+ throw new CliUsageError('--sarif requires --owned-domain to confirm the passive posture target is controlled by the operator.');
956
+ return { action: 'posture', domain, output, quiet, color, selectorText, retiredSelectorText, mailProfile, ownedDomain };
806
957
  }
807
958
  function parseHttpArguments(argv) {
808
- let domain = null;
809
- let output = 'terminal';
810
- let quiet = false;
811
- let color = true;
812
- for (const argument of argv) {
813
- if (argument === '--json') {
814
- if (output !== 'terminal')
815
- throw new CliUsageError('--json may be supplied only once.');
816
- output = 'json';
817
- }
818
- else if (argument === '--quiet')
819
- quiet = true;
820
- else if (argument === '--no-color')
821
- color = false;
822
- else if (argument.startsWith('-'))
823
- throw new CliUsageError(`Unknown option "${argument}".`);
824
- else if (domain === null)
825
- domain = argument;
826
- else
827
- throw new CliUsageError('http accepts one domain.');
828
- }
829
- if (quiet && output !== 'terminal')
830
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
831
- return { action: 'http', domain, output, quiet, color };
959
+ const { source: domain, ...options } = parseSingleJsonInput(argv, 'http accepts one domain.');
960
+ return { action: 'http', domain, ...options };
832
961
  }
833
962
  function parseTlsArguments(argv) {
834
- let hostname = null;
835
- let output = 'terminal';
836
- let quiet = false;
837
- let color = true;
838
- for (const argument of argv) {
839
- if (argument === '--json') {
840
- if (output !== 'terminal')
841
- throw new CliUsageError('--json may be supplied only once.');
842
- output = 'json';
843
- }
844
- else if (argument === '--quiet')
845
- quiet = true;
846
- else if (argument === '--no-color')
847
- color = false;
848
- else if (argument.startsWith('-'))
849
- throw new CliUsageError(`Unknown option "${argument}".`);
850
- else if (hostname === null)
851
- hostname = argument;
852
- else
853
- throw new CliUsageError('tls accepts one hostname.');
854
- }
855
- if (quiet && output !== 'terminal')
856
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
857
- return { action: 'tls', hostname, output, quiet, color };
963
+ const { source: hostname, ...options } = parseSingleJsonInput(argv, 'tls accepts one hostname.');
964
+ return { action: 'tls', hostname, ...options };
858
965
  }
859
966
  function parseCompareArguments(argv) {
860
- let source = null;
861
- let output = 'terminal';
862
- let quiet = false;
863
- let color = true;
864
- for (const argument of argv) {
865
- if (argument === '--json') {
866
- if (output !== 'terminal')
867
- throw new CliUsageError('--json may be supplied only once.');
868
- output = 'json';
869
- }
870
- else if (argument === '--quiet')
871
- quiet = true;
872
- else if (argument === '--no-color')
873
- color = false;
874
- else if (argument.startsWith('-'))
875
- throw new CliUsageError(`Unknown option "${argument}".`);
876
- else if (source === null)
877
- source = argument;
878
- else
879
- throw new CliUsageError('compare accepts one optional lookup JSON file. Otherwise pipe one lookup document on stdin.');
880
- }
881
- if (quiet && output !== 'terminal')
882
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
883
- return { action: 'compare', source, output, quiet, color };
967
+ const parsed = parseSingleJsonInput(argv, 'compare accepts one optional lookup JSON file. Otherwise pipe one lookup document on stdin.');
968
+ return { action: 'compare', ...parsed };
884
969
  }
885
970
  function parsePageCompareArguments(argv) {
886
971
  const parsed = parseTwoFileComparisonArguments(argv, 'page-compare');
@@ -915,35 +1000,14 @@ function parseTwoFileComparisonArguments(argv, command) {
915
1000
  return { leftSource: sources[0], rightSource: sources[1], output, quiet, color };
916
1001
  }
917
1002
  function parseMailReviewArguments(argv) {
918
- let source = null;
919
- let output = 'terminal';
920
- let quiet = false;
921
- let color = true;
922
- for (const argument of argv) {
923
- if (argument === '--json') {
924
- if (output !== 'terminal')
925
- throw new CliUsageError('--json may be supplied only once.');
926
- output = 'json';
927
- }
928
- else if (argument === '--quiet')
929
- quiet = true;
930
- else if (argument === '--no-color')
931
- color = false;
932
- else if (argument.startsWith('-'))
933
- throw new CliUsageError(`Unknown option "${argument}".`);
934
- else if (source === null)
935
- source = argument;
936
- else
937
- throw new CliUsageError('mail-review accepts one optional Bulk JSON or JSONL input file.');
938
- }
939
- if (quiet && output !== 'terminal')
940
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
941
- return { action: 'mail-review', source, output, quiet, color };
1003
+ const parsed = parseSingleJsonInput(argv, 'mail-review accepts one optional Bulk JSON or JSONL input file.');
1004
+ return { action: 'mail-review', ...parsed };
942
1005
  }
943
1006
  function parseReviewEvidenceArguments(argv) {
944
1007
  let source = null;
945
1008
  let mmdbSource = null;
946
1009
  let output = 'terminal';
1010
+ let strictExit = false;
947
1011
  let quiet = false;
948
1012
  let color = true;
949
1013
  for (let index = 0; index < argv.length; index += 1) {
@@ -961,6 +1025,11 @@ function parseReviewEvidenceArguments(argv) {
961
1025
  throw new CliUsageError('--mmdb requires one database file and may be supplied only once.');
962
1026
  mmdbSource = value;
963
1027
  }
1028
+ else if (argument === '--strict-exit') {
1029
+ if (strictExit)
1030
+ throw new CliUsageError('--strict-exit may be supplied only once.');
1031
+ strictExit = true;
1032
+ }
964
1033
  else if (argument === '--quiet')
965
1034
  quiet = true;
966
1035
  else if (argument === '--no-color')
@@ -974,19 +1043,41 @@ function parseReviewEvidenceArguments(argv) {
974
1043
  }
975
1044
  if (quiet && output !== 'terminal')
976
1045
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
977
- return { action: 'review-evidence', source, mmdbSource, output, quiet, color };
1046
+ return { action: 'review-evidence', source, mmdbSource, output, strictExit, quiet, color };
978
1047
  }
979
- function parseDomainControlArguments(argv) {
1048
+ function parseBriefArguments(argv) {
1049
+ const parsed = parseSingleJsonInput(argv, 'brief accepts one optional saved Lookup file.');
1050
+ return { action: 'brief', ...parsed };
1051
+ }
1052
+ function parseCasePackArguments(argv) {
980
1053
  let source = null;
981
1054
  let output = 'terminal';
1055
+ let audience = null;
1056
+ let reviewed = false;
982
1057
  let quiet = false;
983
1058
  let color = true;
984
- for (const argument of argv) {
1059
+ for (let index = 0; index < argv.length; index += 1) {
1060
+ const argument = argv[index];
1061
+ if (argument === undefined)
1062
+ break;
985
1063
  if (argument === '--json') {
986
1064
  if (output !== 'terminal')
987
1065
  throw new CliUsageError('--json may be supplied only once.');
988
1066
  output = 'json';
989
1067
  }
1068
+ else if (argument === '--audience') {
1069
+ if (audience !== null)
1070
+ throw new CliUsageError('--audience may be supplied only once.');
1071
+ const value = argv[++index];
1072
+ if (!['internal', 'trusted', 'public'].includes(value || ''))
1073
+ throw new CliUsageError('--audience requires internal, trusted, or public.');
1074
+ audience = value;
1075
+ }
1076
+ else if (argument === '--reviewed') {
1077
+ if (reviewed)
1078
+ throw new CliUsageError('--reviewed may be supplied only once.');
1079
+ reviewed = true;
1080
+ }
990
1081
  else if (argument === '--quiet')
991
1082
  quiet = true;
992
1083
  else if (argument === '--no-color')
@@ -996,23 +1087,60 @@ function parseDomainControlArguments(argv) {
996
1087
  else if (source === null)
997
1088
  source = argument;
998
1089
  else
999
- throw new CliUsageError('domain-control accepts one optional versioned JSON input file.');
1090
+ throw new CliUsageError('case-pack accepts one optional case-export file.');
1000
1091
  }
1092
+ if (audience === null)
1093
+ throw new CliUsageError('case-pack requires --audience internal, trusted, or public.');
1001
1094
  if (quiet && output !== 'terminal')
1002
1095
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1003
- return { action: 'domain-control', source, output, quiet, color };
1096
+ return { action: 'case-pack', source, output, audience, reviewed, quiet, color };
1004
1097
  }
1005
- function parseAssuranceArguments(argv) {
1098
+ function parseMonitorOnceArguments(argv) {
1006
1099
  let source = null;
1100
+ let previousSource = null;
1007
1101
  let output = 'terminal';
1102
+ let limit = 20;
1103
+ let concurrency = 2;
1104
+ let failOn = null;
1008
1105
  let quiet = false;
1009
1106
  let color = true;
1010
- for (const argument of argv) {
1107
+ for (let index = 0; index < argv.length; index += 1) {
1108
+ const argument = argv[index];
1109
+ if (argument === undefined)
1110
+ break;
1011
1111
  if (argument === '--json') {
1012
1112
  if (output !== 'terminal')
1013
1113
  throw new CliUsageError('--json may be supplied only once.');
1014
1114
  output = 'json';
1015
1115
  }
1116
+ else if (argument === '--junit') {
1117
+ if (output !== 'terminal')
1118
+ throw new CliUsageError('Choose only one output format.');
1119
+ output = 'junit';
1120
+ }
1121
+ else if (argument === '--previous') {
1122
+ if (previousSource !== null)
1123
+ throw new CliUsageError('--previous may be supplied only once.');
1124
+ const value = argv[++index];
1125
+ if (!value || value.startsWith('-'))
1126
+ throw new CliUsageError('--previous requires one prior monitor snapshot file.');
1127
+ previousSource = value;
1128
+ }
1129
+ else if (argument === '--limit' || argument === '--concurrency') {
1130
+ const value = Number(argv[++index]);
1131
+ const maximum = argument === '--limit' ? 20 : 3;
1132
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
1133
+ throw new CliUsageError(`${argument} requires an integer from 1 to ${maximum}.`);
1134
+ if (argument === '--limit')
1135
+ limit = value;
1136
+ else
1137
+ concurrency = value;
1138
+ }
1139
+ else if (argument === '--fail-on') {
1140
+ if (failOn !== null)
1141
+ throw new CliUsageError('--fail-on may be supplied only once.');
1142
+ failOn = parseCliFailPolicies(argv[++index]);
1143
+ }
1016
1144
  else if (argument === '--quiet')
1017
1145
  quiet = true;
1018
1146
  else if (argument === '--no-color')
@@ -1022,11 +1150,23 @@ function parseAssuranceArguments(argv) {
1022
1150
  else if (source === null)
1023
1151
  source = argument;
1024
1152
  else
1025
- throw new CliUsageError('assurance accepts one optional versioned JSON input file.');
1153
+ throw new CliUsageError('monitor-once accepts one optional domain-control manifest file.');
1026
1154
  }
1027
1155
  if (quiet && output !== 'terminal')
1028
1156
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1029
- return { action: 'assurance', source, output, quiet, color };
1157
+ return { action: 'monitor-once', source, previousSource, output, limit, concurrency, quiet, color, ...(failOn ? { failOn } : {}) };
1158
+ }
1159
+ function parseDomainControlArguments(argv) {
1160
+ const parsed = parseSingleJsonInput(argv, 'domain-control accepts one optional versioned JSON input file.');
1161
+ return { action: 'domain-control', ...parsed };
1162
+ }
1163
+ function parseAssuranceArguments(argv) {
1164
+ const parsed = parseSingleJsonInput(argv, 'assurance accepts one optional versioned JSON input file.');
1165
+ return { action: 'assurance', ...parsed };
1166
+ }
1167
+ function parseChangePacketArguments(argv) {
1168
+ const parsed = parseSingleJsonInput(argv, 'change-packet accepts one optional versioned JSON input file.');
1169
+ return { action: 'change-packet', ...parsed };
1030
1170
  }
1031
1171
  function parseSharingReviewArguments(argv) {
1032
1172
  let source = null;
@@ -1134,6 +1274,54 @@ function parseWorkflowPlanArguments(argv) {
1134
1274
  throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1135
1275
  return { action: 'workflow-plan', recipe: recipe, subject: positional[1], output, quiet, color };
1136
1276
  }
1277
+ function parseWorkflowRunArguments(argv) {
1278
+ const positional = [];
1279
+ let resumeSource = null;
1280
+ let approveNetwork = false;
1281
+ let output = 'terminal';
1282
+ let quiet = false;
1283
+ let color = true;
1284
+ for (let index = 0; index < argv.length; index += 1) {
1285
+ const argument = argv[index];
1286
+ if (argument === undefined)
1287
+ break;
1288
+ if (argument === '--json') {
1289
+ if (output !== 'terminal')
1290
+ throw new CliUsageError('--json may be supplied only once.');
1291
+ output = 'json';
1292
+ }
1293
+ else if (argument === '--resume') {
1294
+ if (resumeSource !== null)
1295
+ throw new CliUsageError('--resume may be supplied only once.');
1296
+ const value = argv[++index];
1297
+ if (!value || value.startsWith('-'))
1298
+ throw new CliUsageError('--resume requires one prior workflow-run state file.');
1299
+ resumeSource = value;
1300
+ }
1301
+ else if (argument === '--approve-network') {
1302
+ if (approveNetwork)
1303
+ throw new CliUsageError('--approve-network may be supplied only once.');
1304
+ approveNetwork = true;
1305
+ }
1306
+ else if (argument === '--quiet')
1307
+ quiet = true;
1308
+ else if (argument === '--no-color')
1309
+ color = false;
1310
+ else if (argument.startsWith('-'))
1311
+ throw new CliUsageError(`Unknown option "${argument}".`);
1312
+ else
1313
+ positional.push(argument);
1314
+ }
1315
+ if (positional.length !== 2)
1316
+ throw new CliUsageError('workflow-run requires one fixed recipe and one subject.');
1317
+ const recipe = positional[0];
1318
+ if (!INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
1319
+ throw new CliUsageError(`workflow-run recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
1320
+ }
1321
+ if (quiet && output !== 'terminal')
1322
+ throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1323
+ return { action: 'workflow-run', recipe: recipe, subject: positional[1], resumeSource, approveNetwork, output, quiet, color };
1324
+ }
1137
1325
  function parseDiffArguments(argv) {
1138
1326
  let leftSource = null;
1139
1327
  let rightSource = null;
@@ -1228,108 +1416,47 @@ function parseTimelineArguments(argv) {
1228
1416
  return { action: 'timeline', sources, output, quiet, color };
1229
1417
  }
1230
1418
  function parseRegistrySupportArguments(argv) {
1231
- let target = null;
1232
- let output = 'terminal';
1233
- let quiet = false;
1234
- let color = true;
1235
- for (const argument of argv) {
1236
- if (argument === '--json') {
1237
- if (output !== 'terminal')
1238
- throw new CliUsageError('--json may be supplied only once.');
1239
- output = 'json';
1240
- }
1241
- else if (argument === '--quiet')
1242
- quiet = true;
1243
- else if (argument === '--no-color')
1244
- color = false;
1245
- else if (argument.startsWith('-'))
1246
- throw new CliUsageError(`Unknown option "${argument}".`);
1247
- else if (target === null)
1248
- target = argument;
1249
- else
1250
- throw new CliUsageError('registry-support accepts one domain or suffix.');
1251
- }
1252
- if (quiet && output !== 'terminal')
1253
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1254
- return { action: 'registry-support', target, output, quiet, color };
1419
+ const { source: target, ...options } = parseSingleJsonInput(argv, 'registry-support accepts one domain or suffix.');
1420
+ return { action: 'registry-support', target, ...options };
1255
1421
  }
1256
1422
  function parseRegistryDoctorArguments(argv) {
1257
- let source = null;
1258
- let output = 'terminal';
1259
- let quiet = false;
1260
- let color = true;
1261
- for (const argument of argv) {
1262
- if (argument === '--json') {
1263
- if (output !== 'terminal')
1264
- throw new CliUsageError('--json may be supplied only once.');
1265
- output = 'json';
1266
- }
1267
- else if (argument === '--quiet')
1268
- quiet = true;
1269
- else if (argument === '--no-color')
1270
- color = false;
1271
- else if (argument.startsWith('-'))
1272
- throw new CliUsageError(`Unknown option "${argument}".`);
1273
- else if (source === null)
1274
- source = argument;
1423
+ const parsed = parseSingleJsonInput(argv, 'registry-doctor accepts one optional saved Lookup JSON file.');
1424
+ return { action: 'registry-doctor', ...parsed };
1425
+ }
1426
+ function parseRegistryCohortArguments(argv) {
1427
+ const parsed = parseSingleJsonInput(argv, 'registry-cohort accepts one optional JSON or JSONL input file.');
1428
+ return { action: 'registry-cohort', ...parsed };
1429
+ }
1430
+ function parseRegistryScaffoldArguments(argv) {
1431
+ let profile = '';
1432
+ let suffix = '';
1433
+ let scenario = '';
1434
+ for (let index = 0; index < argv.length; index += 2) {
1435
+ const option = argv[index];
1436
+ const value = argv[index + 1];
1437
+ if (!value)
1438
+ throw new CliUsageError(`${option || 'Option'} requires a value.`);
1439
+ if (option === '--profile' && !profile)
1440
+ profile = value;
1441
+ else if (option === '--suffix' && !suffix)
1442
+ suffix = value;
1443
+ else if (option === '--scenario' && !scenario)
1444
+ scenario = value;
1275
1445
  else
1276
- throw new CliUsageError('registry-doctor accepts one optional saved Lookup JSON file.');
1446
+ throw new CliUsageError(`Unknown or repeated option "${option || ''}".`);
1277
1447
  }
1278
- if (quiet && output !== 'terminal')
1279
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1280
- return { action: 'registry-doctor', source, output, quiet, color };
1448
+ if (!profile || !suffix || !['registered', 'not_found', 'inconclusive'].includes(scenario)) {
1449
+ throw new CliUsageError('registry-scaffold requires --profile, --suffix, and --scenario <registered|not_found|inconclusive>.');
1450
+ }
1451
+ return { action: 'registry-scaffold', profile, suffix, scenario: scenario };
1281
1452
  }
1282
1453
  function parseRiskCalibrateArguments(argv) {
1283
- let source = null;
1284
- let output = 'terminal';
1285
- let quiet = false;
1286
- let color = true;
1287
- for (const argument of argv) {
1288
- if (argument === '--json') {
1289
- if (output !== 'terminal')
1290
- throw new CliUsageError('--json may be supplied only once.');
1291
- output = 'json';
1292
- }
1293
- else if (argument === '--quiet')
1294
- quiet = true;
1295
- else if (argument === '--no-color')
1296
- color = false;
1297
- else if (argument.startsWith('-'))
1298
- throw new CliUsageError(`Unknown option "${argument}".`);
1299
- else if (source === null)
1300
- source = argument;
1301
- else
1302
- throw new CliUsageError('risk-calibrate accepts one optional dataset file. Otherwise pipe one dataset on stdin.');
1303
- }
1304
- if (quiet && output !== 'terminal')
1305
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1306
- return { action: 'risk-calibrate', source, output, quiet, color };
1454
+ const parsed = parseSingleJsonInput(argv, 'risk-calibrate accepts one optional dataset file. Otherwise pipe one dataset on stdin.');
1455
+ return { action: 'risk-calibrate', ...parsed };
1307
1456
  }
1308
1457
  function parseLookalikeCalibrateArguments(argv) {
1309
- let source = null;
1310
- let output = 'terminal';
1311
- let quiet = false;
1312
- let color = true;
1313
- for (const argument of argv) {
1314
- if (argument === '--json') {
1315
- if (output !== 'terminal')
1316
- throw new CliUsageError('--json may be supplied only once.');
1317
- output = 'json';
1318
- }
1319
- else if (argument === '--quiet')
1320
- quiet = true;
1321
- else if (argument === '--no-color')
1322
- color = false;
1323
- else if (argument.startsWith('-'))
1324
- throw new CliUsageError(`Unknown option "${argument}".`);
1325
- else if (source === null)
1326
- source = argument;
1327
- else
1328
- throw new CliUsageError('lookalike-calibrate accepts one optional reviewed dataset JSON file.');
1329
- }
1330
- if (quiet && output !== 'terminal')
1331
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1332
- return { action: 'lookalike-calibrate', source, output, quiet, color };
1458
+ const parsed = parseSingleJsonInput(argv, 'lookalike-calibrate accepts one optional reviewed dataset JSON file.');
1459
+ return { action: 'lookalike-calibrate', ...parsed };
1333
1460
  }
1334
1461
  function parseVerifyArtifactArguments(argv) {
1335
1462
  let source = null;
@@ -1371,30 +1498,8 @@ function parseVerifyArtifactArguments(argv) {
1371
1498
  return { action: 'verify-artifact', source, passphraseSource, output, quiet, color };
1372
1499
  }
1373
1500
  function parseSourceReportArguments(argv) {
1374
- let source = null;
1375
- let output = 'terminal';
1376
- let quiet = false;
1377
- let color = true;
1378
- for (const argument of argv) {
1379
- if (argument === '--json') {
1380
- if (output !== 'terminal')
1381
- throw new CliUsageError('--json may be supplied only once.');
1382
- output = 'json';
1383
- }
1384
- else if (argument === '--quiet')
1385
- quiet = true;
1386
- else if (argument === '--no-color')
1387
- color = false;
1388
- else if (argument.startsWith('-'))
1389
- throw new CliUsageError(`Unknown option "${argument}".`);
1390
- else if (source === null)
1391
- source = argument;
1392
- else
1393
- throw new CliUsageError('source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
1394
- }
1395
- if (quiet && output !== 'terminal')
1396
- throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
1397
- return { action: 'source-report', source, output, quiet, color };
1501
+ const parsed = parseSingleJsonInput(argv, 'source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
1502
+ return { action: 'source-report', ...parsed };
1398
1503
  }
1399
1504
  function parseExportArguments(argv) {
1400
1505
  let source = null;