@slicedearth/whoisleuth-cli 1.39.1 → 1.43.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.
- package/README.md +15 -0
- package/bin/whoisleuth.mjs +13 -2
- package/cli/archive-inspect.mjs +37 -2
- package/cli/arguments.mjs +361 -256
- package/cli/artifact-verify.mjs +35 -1
- package/cli/bulk-checkpoint.mjs +7 -11
- package/cli/bulk-command-runner.mjs +19 -2
- package/cli/case-pack.mjs +136 -0
- package/cli/ci-report.mjs +73 -0
- package/cli/collection-preflight.mjs +59 -0
- package/cli/command-reference.mjs +395 -0
- package/cli/completion.mjs +33 -10
- package/cli/config-profile.mjs +180 -0
- package/cli/ct-event-intake.mjs +159 -0
- package/cli/discovery-scan-command-runner.mjs +15 -1
- package/cli/domain-control-monitor.mjs +104 -0
- package/cli/domain-control-observations.mjs +235 -0
- package/cli/evidence-command-arguments.mjs +11 -0
- package/cli/evidence-command-runner.mjs +1 -0
- package/cli/external-observation-mapping.mjs +162 -0
- package/cli/fail-policy.mjs +64 -0
- package/cli/formatters/html.mjs +1 -1
- package/cli/investigation-manifest.mjs +113 -0
- package/cli/investigation-run.mjs +112 -0
- package/cli/lookup-brief.mjs +116 -0
- package/cli/lookup-command-runner.mjs +8 -1
- package/cli/lookup-diff.mjs +17 -19
- package/cli/network-command-runner.mjs +4 -1
- package/cli/offline-evidence-review.mjs +105 -8
- package/cli/open-asset-model-bridge.mjs +169 -0
- package/cli/registry-cohort.mjs +121 -0
- package/cli/registry-doctor.mjs +106 -11
- package/cli/registry-fixture-scaffold.mjs +73 -0
- package/cli/runner.mjs +368 -306
- package/docs/cli-reference.md +240 -14
- package/docs/cli.md +42 -7
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +1 -1
- package/frontend/src/lib/analysis/brand-profile-model.js +46 -4
- package/frontend/src/lib/analysis/case-report.js +573 -0
- package/frontend/src/lib/analysis/case-response-model.js +30 -1
- package/frontend/src/lib/analysis/detection-rule-model.js +36 -0
- package/frontend/src/lib/analysis/external-findings-import.js +320 -0
- package/frontend/src/lib/analysis/lookup-decision-support.js +7 -0
- package/frontend/src/lib/analysis/lookup-presentation.js +10 -20
- package/frontend/src/lib/analysis/scoring.js +1 -1
- package/frontend/src/lib/analysis/visualization-models.js +2 -3
- package/frontend/src/lib/analysis/workspace-archive.js +2 -0
- package/lib/abusech-auth.mjs +1 -1
- package/lib/availability.mjs +1 -1
- package/lib/bounded-contract-normalizers.mjs +33 -0
- package/lib/dns-convergence-review.mjs +183 -0
- package/lib/dns-delegation-health.mjs +196 -3
- package/lib/domain-change-packet.mjs +133 -0
- package/lib/domain-change-review.mjs +307 -0
- package/lib/domain-control-flight-recorder.mjs +268 -0
- package/lib/domain-portfolio-review.mjs +151 -0
- package/lib/evidence-export.mjs +1 -1
- package/lib/html-signals.mjs +5 -11
- package/lib/nameserver-preflight-review.mjs +159 -0
- package/lib/page-language-signals.mjs +124 -0
- package/lib/risk-scoring.mjs +42 -1
- package/lib/static-html-analysis.mjs +29 -1
- package/lib/trust-store-comparison.mjs +146 -0
- package/lib/website-technology.mjs +24 -10
- package/lib/zone-intent-review.mjs +534 -0
- 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
|
-
|
|
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 &&
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
809
|
-
|
|
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
|
-
|
|
835
|
-
|
|
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
|
-
|
|
861
|
-
|
|
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
|
-
|
|
919
|
-
|
|
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
|
|
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 (
|
|
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('
|
|
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: '
|
|
1096
|
+
return { action: 'case-pack', source, output, audience, reviewed, quiet, color };
|
|
1004
1097
|
}
|
|
1005
|
-
function
|
|
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 (
|
|
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('
|
|
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: '
|
|
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
|
-
|
|
1232
|
-
|
|
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
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
throw new CliUsageError(
|
|
1273
|
-
|
|
1274
|
-
|
|
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(
|
|
1446
|
+
throw new CliUsageError(`Unknown or repeated option "${option || ''}".`);
|
|
1277
1447
|
}
|
|
1278
|
-
if (
|
|
1279
|
-
throw new CliUsageError('
|
|
1280
|
-
|
|
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
|
-
|
|
1284
|
-
|
|
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
|
-
|
|
1310
|
-
|
|
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
|
-
|
|
1375
|
-
|
|
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;
|