@slicedearth/whoisleuth-cli 2.2.0 → 2.4.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/NOTICE +6 -0
- package/README.md +20 -15
- package/bin/whoisleuth.mjs +8 -1
- package/cli/archive-content-digest.mjs +14 -0
- package/cli/archive-inspect.mjs +20 -53
- package/cli/arguments.mjs +454 -1748
- package/cli/artifact-validation/case-response.mjs +66 -35
- package/cli/artifact-validation/investigation-capsule.mjs +52 -31
- package/cli/artifact-validation/lookup-evidence.mjs +12 -6
- package/cli/artifact-validation/signed-review.mjs +112 -72
- package/cli/artifact-validation/structure-primitives.mjs +1 -105
- package/cli/artifact-verify.mjs +90 -24
- package/cli/assurance-command-runner.mjs +39 -4
- package/cli/bagit-folder.mjs +57 -0
- package/cli/bagit-review.mjs +23 -0
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +34 -3
- package/cli/bulk-command-runner.mjs +2 -0
- package/cli/bulk-output.mjs +29 -3
- package/cli/case-command.mjs +195 -0
- package/cli/case-pack.mjs +66 -111
- package/cli/command-argument-grammar.mjs +215 -0
- package/cli/command-reference.mjs +342 -285
- package/cli/completion.mjs +303 -80
- package/cli/config-profile.mjs +7 -5
- package/cli/ct-event-intake.mjs +12 -12
- package/cli/discover.mjs +4 -3
- package/cli/discovery-observation-snapshot.mjs +59 -18
- package/cli/discovery-scan-command-runner.mjs +21 -0
- package/cli/discovery-scan.mjs +13 -4
- package/cli/discovery-workflow.mjs +1 -1
- package/cli/domain-control-monitor.mjs +50 -20
- package/cli/domain-control-observations.mjs +149 -102
- package/cli/errors.mjs +33 -1
- package/cli/evidence-command-runner.mjs +16 -5
- package/cli/evidence-signing.mjs +2 -2
- package/cli/external-observation-mapping.mjs +7 -8
- package/cli/formatters/terminal-lookup.mjs +18 -46
- package/cli/formatters/terminal-shared.mjs +5 -3
- package/cli/formatters/terminal-technology.mjs +114 -0
- package/cli/inline-command-families.mjs +2 -0
- package/cli/input.mjs +116 -0
- package/cli/interactive-launcher.mjs +2 -160
- package/cli/interchange-report.mjs +18 -8
- package/cli/investigation-artifacts.mjs +176 -0
- package/cli/investigation-checkpoint.mjs +10 -0
- package/cli/investigation-folder.mjs +114 -0
- package/cli/investigation-manifest.mjs +1 -112
- package/cli/investigation-package-review.mjs +103 -0
- package/cli/investigation-plan.mjs +32 -19
- package/cli/investigation-run.mjs +188 -33
- package/cli/local-document-checkpoint.mjs +170 -0
- package/cli/lookalike-calibration.mjs +7 -2
- package/cli/lookup-brief.mjs +4 -2
- package/cli/lookup-browser-view.mjs +1 -1
- package/cli/lookup-browser.mjs +5 -12
- package/cli/lookup-command-runner.mjs +17 -6
- package/cli/lookup-diff.mjs +37 -3
- package/cli/lookup-plan.mjs +11 -5
- package/cli/lookup-reconcile.mjs +10 -8
- package/cli/mail-header-review.mjs +467 -0
- package/cli/mail-review.mjs +66 -14
- package/cli/manual.mjs +1 -0
- package/cli/network-command-runner.mjs +2 -0
- package/cli/output-file.mjs +31 -5
- package/cli/registry-doctor.mjs +3 -1
- package/cli/registry-support.mjs +1 -1
- package/cli/retained-artifact-diff.mjs +2 -2
- package/cli/review-command-runner.mjs +70 -0
- package/cli/risk-calibration.mjs +19 -17
- package/cli/runner.mjs +62 -7
- package/cli/sharing-review.mjs +21 -34
- package/cli/signer-trust.mjs +104 -0
- package/cli/source-reliability.mjs +74 -83
- package/cli/terminal-input.mjs +168 -0
- package/cli/workflow-command-runner.mjs +68 -13
- package/docs/cli-reference.md +77 -24
- package/docs/cli.md +300 -25
- package/lib/abort.mjs +4 -1
- package/lib/availability.mjs +161 -112
- package/lib/bounded-file.mjs +3 -1
- package/lib/bounded-html-document.mjs +125 -0
- package/lib/bounded-json.mjs +51 -8
- package/lib/browser-library-profile.mjs +42 -51
- package/lib/browser-library-worker.mjs +112 -0
- package/lib/client-behavior-profile.mjs +3 -3
- package/lib/ct-search.mjs +11 -4
- package/lib/dns-convergence-review.mjs +1 -0
- package/lib/dns-delegation-health.mjs +24 -14
- package/lib/dns-inheritance-review.mjs +217 -0
- package/lib/dns-intelligence.mjs +73 -26
- package/lib/dns-mx.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +6 -0
- package/lib/dnssec-evidence-validation.mjs +11 -3
- package/lib/domain-change-packet.mjs +49 -16
- package/lib/domain-change-review.mjs +14 -8
- package/lib/domain-control-flight-recorder.mjs +221 -117
- package/lib/domain-control-manifest.mjs +129 -69
- package/lib/domain-posture-analysis.mjs +20 -7
- package/lib/domain-posture-parsers.mjs +201 -8
- package/lib/domain-posture.mjs +147 -29
- package/lib/evidence-export-privacy.mjs +561 -0
- package/lib/evidence-export.mjs +11 -543
- package/lib/evidence-report.mjs +5 -3
- package/lib/favicon.mjs +117 -41
- package/lib/generated/cisa-kev-catalog.mjs +12 -4
- package/lib/generated/registrar-standing-catalogue.mjs +7 -7
- package/lib/generated/retire-browser-catalog.mjs +2335 -940
- package/lib/hostname.mjs +2 -38
- package/lib/html-signals.mjs +111 -108
- package/lib/http-evidence-bounds.mjs +2 -1
- package/lib/http-intelligence.mjs +39 -26
- package/lib/local-geoip-evidence.mjs +19 -3
- package/lib/lookup-cache.mjs +34 -11
- package/lib/lookup-child-profile-contract.mjs +24 -15
- package/lib/lookup-diagnostics.mjs +24 -1
- package/lib/lookup-progress.mjs +30 -13
- package/lib/lookup-response-contract.mjs +1048 -0
- package/lib/lookup-response.mjs +8 -10
- package/lib/lookup-source-progress.mjs +40 -14
- package/lib/lookup.mjs +48 -27
- package/lib/nameserver-preflight-review.mjs +1 -0
- package/lib/observed-network-context.mjs +43 -27
- package/lib/outbound-request-bounds.mjs +4 -1
- package/lib/page-fingerprints.mjs +69 -133
- package/lib/page-role-profile.mjs +12 -8
- package/lib/perceptual-hash.mjs +43 -14
- package/lib/rdap-bootstrap.mjs +72 -22
- package/lib/rdap-capabilities.mjs +1 -1
- package/lib/rdap-client.mjs +15 -7
- package/lib/rdap-normalization.mjs +33 -18
- package/lib/rdap-search-workbench.mjs +1 -1
- package/lib/rdap-transport.mjs +47 -20
- package/lib/rdap-types.mjs +2 -1
- package/lib/rdap.mjs +18 -3
- package/lib/registrable-domain.mjs +21 -1
- package/lib/registrar-standing-contract.mjs +1 -0
- package/lib/registry-capabilities.mjs +5 -1
- package/lib/registry-capability-catalogue.mjs +2 -1
- package/lib/registry-dates.mjs +2 -190
- package/lib/registry-insights.mjs +85 -34
- package/lib/response-policy.mjs +86 -48
- package/lib/risk-calibration-summary.mjs +18 -6
- package/lib/rpki-evidence.mjs +8 -5
- package/lib/safe-fetch.mjs +54 -47
- package/lib/security-txt.mjs +42 -7
- package/lib/service-binding-dns.mjs +8 -0
- package/lib/smtp-transport-review.mjs +27 -15
- package/lib/sslbl-certificates.generated.mjs +136 -135
- package/lib/static-html-analysis.mjs +178 -151
- package/lib/structured-data-identity.mjs +1 -0
- package/lib/technology-markup.mjs +100 -0
- package/lib/threat-intelligence-manifests.mjs +11 -139
- package/lib/threat-intelligence-runtime.mjs +24 -52
- package/lib/threat-intelligence-targets.mjs +6 -34
- package/lib/threat-intelligence-types.mjs +39 -0
- package/lib/typosquat-generator.mjs +19 -15
- package/lib/urlhaus-intelligence.mjs +8 -3
- package/lib/urlscan-intelligence.mjs +1 -1
- package/lib/website-security-posture.mjs +30 -6
- package/lib/website-technology.mjs +153 -45
- package/lib/whois-authority.mjs +14 -2
- package/lib/whois-chain.mjs +9 -2
- package/lib/whois-common-formats.mjs +5 -5
- package/lib/whois-contracts.mjs +2 -1
- package/lib/whois-dialects.mjs +2 -2
- package/lib/whois-parser-support.mjs +8 -1
- package/lib/whois-transport.mjs +23 -7
- package/lib/zone-intent-review.mjs +16 -6
- package/package.json +2 -1
- package/packages/cases/case-attachment-model.mjs +83 -0
- package/packages/cases/case-evidence-model.mjs +156 -62
- package/packages/cases/case-export-input.mjs +53 -0
- package/packages/cases/case-migration-model.mjs +127 -44
- package/packages/cases/case-model.mjs +1 -0
- package/packages/cases/case-recheck-model.mjs +91 -0
- package/packages/cases/case-record-contracts.mjs +5 -20
- package/packages/cases/case-record-core.mjs +12 -22
- package/packages/cases/case-record-decisions.mjs +72 -0
- package/packages/cases/case-record-model.mjs +1 -0
- package/packages/cases/case-record-operations.mjs +95 -31
- package/packages/cases/case-record-projection.mjs +221 -0
- package/packages/cases/case-report.mjs +70 -54
- package/packages/cases/case-response-actions.mjs +654 -0
- package/packages/cases/case-response-model.mjs +64 -1115
- package/packages/cases/case-response-outcomes.mjs +317 -0
- package/packages/cases/case-response-packet-render.mjs +153 -0
- package/packages/cases/case-response-packet-types.mjs +1 -0
- package/packages/cases/case-response-packet-vocabulary.mjs +48 -0
- package/packages/cases/case-response-packet.mjs +74 -572
- package/packages/cases/case-response-records.mjs +94 -0
- package/packages/cases/case-response-review-inputs.mjs +371 -0
- package/packages/cases/case-response-values.mjs +85 -0
- package/packages/cases/case-review-package.mjs +45 -0
- package/packages/cases/case-selection.mjs +46 -0
- package/packages/cases/case-storage-model.mjs +31 -2
- package/packages/cases/response-route-freshness.mjs +16 -0
- package/packages/comparison/capture-context.mjs +61 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +13 -11
- package/packages/comparison/page-similarity.mjs +5 -2
- package/packages/comparison/relationship-evidence.mjs +85 -8
- package/packages/comparison/relationship-provenance.mjs +93 -0
- package/packages/contracts/analyst-interchange.mjs +216 -0
- package/packages/contracts/analyst-review-state-contract.mjs +11 -0
- package/packages/contracts/analyst-review-state.mjs +5 -4
- package/packages/contracts/case-portability.mjs +366 -83
- package/packages/contracts/case-views-contract.mjs +12 -0
- package/packages/contracts/case-views.mjs +26 -0
- package/packages/contracts/cli-command-semantics.mjs +94 -0
- package/packages/contracts/cli-lookup.mjs +0 -1
- package/packages/contracts/domain-control-flight-recorder.mjs +80 -42
- package/packages/contracts/domain-control-manifest.mjs +127 -73
- package/packages/contracts/domain-control-monitor.mjs +48 -45
- package/packages/contracts/domain-control-review.mjs +179 -121
- package/packages/contracts/domain-name.mjs +21 -0
- package/packages/contracts/external-observation-interchange.mjs +2 -1
- package/packages/contracts/extracted-domain-lifecycle.mjs +11 -32
- package/packages/contracts/http-url.mjs +2 -0
- package/packages/contracts/investigation-package-limits.mjs +24 -0
- package/packages/contracts/investigation-portability.mjs +56 -27
- package/packages/contracts/investigation-run.mjs +16 -0
- package/packages/contracts/ip-address.mjs +34 -0
- package/packages/contracts/lookup-evidence.mjs +35 -0
- package/packages/contracts/offline-comparison.mjs +1 -2
- package/packages/contracts/page-fingerprints.mjs +15 -0
- package/packages/contracts/risk-calibration.mjs +15 -11
- package/packages/contracts/schema-compatibility.mjs +1 -1
- package/packages/contracts/schema-lifecycle.mjs +83 -153
- package/packages/contracts/selected-file-limits.mjs +6 -0
- package/packages/contracts/vulnerability-identifiers.mjs +5 -0
- package/packages/contracts/web-capture.mjs +10 -0
- package/packages/contracts/workspace-portability.mjs +183 -211
- package/packages/evidence/artifact-integrity.mjs +8 -2
- package/packages/evidence/artifact-structure.mjs +105 -0
- package/packages/evidence/decision-fact.mjs +4 -1
- package/packages/evidence/domain-control-runtime.mjs +201 -33
- package/packages/evidence/domain-posture-context.mjs +79 -0
- package/packages/evidence/image-regions.mjs +56 -0
- package/packages/evidence/latest-observations.mjs +28 -0
- package/packages/evidence/lookup-target.mjs +104 -0
- package/packages/evidence/observation.mjs +16 -1
- package/packages/evidence/passphrase-encryption.mjs +44 -0
- package/packages/evidence/record-identity.mjs +72 -0
- package/packages/evidence/registry-dates.mjs +190 -0
- package/packages/evidence/retained-file.mjs +69 -0
- package/packages/evidence/review-age.mjs +14 -0
- package/packages/interchange/bagit.mjs +342 -0
- package/packages/interchange/bounded-zip-extraction.mjs +278 -0
- package/packages/interchange/crc32.mjs +17 -0
- package/packages/interchange/external-findings-import.mjs +92 -51
- package/packages/interchange/web-capture-import.mjs +382 -0
- package/packages/investigation/bulk-mail-exposure.mjs +2 -1
- package/packages/investigation/investigation-bagit.mjs +14 -0
- package/packages/investigation/investigation-capsule.mjs +33 -5
- package/packages/investigation/investigation-manifest.mjs +200 -0
- package/packages/investigation/investigation-package-crypto.mjs +98 -0
- package/packages/investigation/investigation-package.mjs +200 -0
- package/packages/investigation/lookup-asset-graph-inputs.mjs +38 -0
- package/packages/investigation/lookup-asset-graph.mjs +193 -165
- package/packages/investigation/lookup-claim-readiness.mjs +18 -0
- package/packages/investigation/lookup-investigation-brief.mjs +3 -3
- package/packages/investigation/lookup-task-guidance.mjs +58 -0
- package/packages/monitoring/analyst-review-state.mjs +38 -90
- package/packages/workspace/brand-profile-model.mjs +276 -22
- package/packages/workspace/bulk-columns.mjs +17 -0
- package/packages/workspace/bulk-review-model.mjs +23 -11
- package/packages/workspace/bulk-session-model.mjs +100 -24
- package/packages/workspace/bulk-sort.mjs +1 -0
- package/packages/workspace/case-views.mjs +102 -0
- package/packages/workspace/detection-rule-model.mjs +70 -22
- package/packages/workspace/hostile-input.mjs +29 -8
- package/packages/workspace/investigation-template-model.mjs +19 -4
- package/packages/workspace/page-baseline.mjs +18 -15
- package/packages/workspace/relationship-observation-model.mjs +41 -23
- package/packages/workspace/watchlist-history.mjs +2 -2
- package/packages/workspace/watchlist-store.mjs +8 -2
- package/packages/workspace/website-snapshot-model.mjs +164 -26
- package/packages/workspace/workspace-archive-crypto.mjs +41 -61
- package/packages/workspace/workspace-archive.mjs +87 -21
- package/third-party-notices.txt +29 -2
- package/cli/evidence-command-arguments.mjs +0 -156
- package/packages/investigation/bulk-review-export.mjs +0 -63
package/cli/arguments.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseCommandArguments } from "./command-argument-grammar.mjs";
|
|
2
2
|
import { CliUsageError, hasUnsafeCliText } from "./errors.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { parseArchiveContentDigest } from "./archive-content-digest.mjs";
|
|
4
4
|
import { parseCliFailPolicies } from "./fail-policy.mjs";
|
|
5
5
|
import { isDirectLookupTarget } from "../lib/classify.mjs";
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import { MAX_INVESTIGATION_MANIFEST_ARTIFACTS } from "../packages/investigation/investigation-manifest.mjs";
|
|
7
|
+
import { CLI_COMMANDS, CLI_CASE_OPERATIONS, cliMetaActionForInvocation, isCliCommand, } from "./command-reference.mjs";
|
|
8
|
+
const MAX_CLI_ARGUMENTS = MAX_INVESTIGATION_MANIFEST_ARTIFACTS + 32;
|
|
8
9
|
const MAX_CLI_ARGUMENT_LENGTH = 1024;
|
|
9
10
|
function boundedArgument(value) {
|
|
10
11
|
if (typeof value !== 'string' || value.length > MAX_CLI_ARGUMENT_LENGTH || hasUnsafeCliText(value)) {
|
|
@@ -12,1812 +13,517 @@ function boundedArgument(value) {
|
|
|
12
13
|
}
|
|
13
14
|
return value;
|
|
14
15
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
let destination = null;
|
|
18
|
-
let force = false;
|
|
19
|
-
let palette = null;
|
|
20
|
-
for (let index = 0; index < argv.length; index++) {
|
|
21
|
-
const argument = argv[index];
|
|
22
|
-
if (argument === undefined)
|
|
23
|
-
break;
|
|
24
|
-
if (index > 0 && argument === '--output') {
|
|
25
|
-
if (destination !== null)
|
|
26
|
-
throw new CliUsageError('--output may be supplied only once.');
|
|
27
|
-
const value = argv[++index];
|
|
28
|
-
if (!value || value.startsWith('-'))
|
|
29
|
-
throw new CliUsageError('--output requires one bounded file path.');
|
|
30
|
-
destination = value;
|
|
31
|
-
}
|
|
32
|
-
else if (index > 0 && argument === '--force') {
|
|
33
|
-
if (force)
|
|
34
|
-
throw new CliUsageError('--force may be supplied only once.');
|
|
35
|
-
force = true;
|
|
36
|
-
}
|
|
37
|
-
else if (index > 0 && argument === '--palette') {
|
|
38
|
-
if (palette !== null)
|
|
39
|
-
throw new CliUsageError('--palette may be supplied only once.');
|
|
40
|
-
const value = argv[++index];
|
|
41
|
-
if (value !== 'auto' && value !== 'light' && value !== 'dark') {
|
|
42
|
-
throw new CliUsageError('--palette requires auto, light, or dark.');
|
|
43
|
-
}
|
|
44
|
-
palette = value;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
retained.push(argument);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (force && destination === null)
|
|
51
|
-
throw new CliUsageError('--force requires --output.');
|
|
52
|
-
return { argv: retained, destination, force, palette };
|
|
16
|
+
function terminalOptions(parsed) {
|
|
17
|
+
return { quiet: parsed.hasOption('--quiet'), color: !parsed.hasOption('--no-color') };
|
|
53
18
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const boundedArguments = rawArgv.map(boundedArgument);
|
|
59
|
-
const metaAction = cliMetaActionForInvocation(boundedArguments);
|
|
60
|
-
if (metaAction)
|
|
61
|
-
return parseCliArgumentsCore(boundedArguments);
|
|
62
|
-
const extracted = extractFileOutputArguments(boundedArguments);
|
|
63
|
-
const parsed = parseCliArgumentsCore(extracted.argv);
|
|
64
|
-
if ('quiet' in parsed && parsed.quiet && extracted.destination !== null) {
|
|
65
|
-
throw new CliUsageError('--quiet cannot be combined with --output.');
|
|
66
|
-
}
|
|
67
|
-
if ('events' in parsed && parsed.events && extracted.destination !== null) {
|
|
68
|
-
throw new CliUsageError('--events cannot be combined with --output because completion must describe the final output delivery.');
|
|
19
|
+
function parseInspectArchiveArguments(parsed) {
|
|
20
|
+
const expectedContentDigest = parsed.optionValue('--expect-content-digest');
|
|
21
|
+
try {
|
|
22
|
+
parseArchiveContentDigest(expectedContentDigest);
|
|
69
23
|
}
|
|
70
|
-
|
|
71
|
-
throw new CliUsageError('--
|
|
24
|
+
catch {
|
|
25
|
+
throw new CliUsageError('--expect-content-digest requires sha256:<64 lowercase hex> or sorted-json-v2:sha256:<64 lowercase hex>.');
|
|
72
26
|
}
|
|
73
27
|
return {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
28
|
+
action: 'inspect-archive',
|
|
29
|
+
source: parsed.positionalValue('source'),
|
|
30
|
+
passphraseSource: parsed.optionValue('--passphrase-file'),
|
|
31
|
+
search: parsed.optionValue('--search'),
|
|
32
|
+
reveal: parsed.hasOption('--reveal'),
|
|
33
|
+
requireMatch: parsed.hasOption('--require-match'),
|
|
34
|
+
expectedContentDigest,
|
|
35
|
+
output: parsed.hasOption('--json') ? 'json' : 'terminal',
|
|
36
|
+
...terminalOptions(parsed),
|
|
78
37
|
};
|
|
79
38
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (argv.length === 1)
|
|
87
|
-
return { action: 'help' };
|
|
88
|
-
if (argv.length === 2 && (isCliCommand(firstArgument) || isDirectLookupTarget(firstArgument))) {
|
|
89
|
-
return { action: 'help', command: isCliCommand(firstArgument) ? firstArgument : 'lookup' };
|
|
90
|
-
}
|
|
91
|
-
throw new CliUsageError('Help accepts only an optional command name.');
|
|
92
|
-
}
|
|
93
|
-
if (metaAction?.id === 'version') {
|
|
94
|
-
if (argv.length !== 1)
|
|
95
|
-
throw new CliUsageError('--version does not accept other arguments.');
|
|
96
|
-
return { action: 'version' };
|
|
97
|
-
}
|
|
98
|
-
let command;
|
|
99
|
-
let directLookup = false;
|
|
100
|
-
if (isCliCommand(firstArgument))
|
|
101
|
-
command = firstArgument;
|
|
102
|
-
else if (isDirectLookupTarget(firstArgument)) {
|
|
103
|
-
command = 'lookup';
|
|
104
|
-
directLookup = true;
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
const displayedCommand = firstArgument.length > 80 ? `${firstArgument.slice(0, 79)}…` : firstArgument;
|
|
108
|
-
throw new CliUsageError(`Unknown command "${displayedCommand}". Run "whoisleuth commands" to list supported commands.`);
|
|
109
|
-
}
|
|
110
|
-
const parsed = CLI_PARSERS[command](directLookup ? argv : argv.slice(1));
|
|
111
|
-
if (parsed.action !== command) {
|
|
112
|
-
throw new Error(`CLI parser contract mismatch for ${command}.`);
|
|
113
|
-
}
|
|
114
|
-
return parsed;
|
|
115
|
-
}
|
|
116
|
-
function parseLookupArguments(lookupArguments) {
|
|
117
|
-
let query = null;
|
|
118
|
-
let output = 'terminal';
|
|
119
|
-
let deep = false;
|
|
120
|
-
let scanMode = null;
|
|
121
|
-
let quiet = false;
|
|
122
|
-
let color = true;
|
|
123
|
-
let detail = 'standard';
|
|
124
|
-
let detailSet = false;
|
|
125
|
-
let strictExit = false;
|
|
126
|
-
let events = false;
|
|
127
|
-
let plan = false;
|
|
128
|
-
let browse = false;
|
|
129
|
-
let includeAttribution = true;
|
|
130
|
-
let failOn = null;
|
|
131
|
-
let observerLabel = null;
|
|
132
|
-
let vantageLabel = null;
|
|
133
|
-
let saveLookup = null;
|
|
134
|
-
for (let index = 0; index < lookupArguments.length; index++) {
|
|
135
|
-
const argument = lookupArguments[index];
|
|
136
|
-
if (argument === undefined)
|
|
137
|
-
break;
|
|
138
|
-
if (argument === '--json') {
|
|
139
|
-
if (output !== 'terminal')
|
|
140
|
-
throw new CliUsageError('Choose only one output format.');
|
|
141
|
-
output = 'json';
|
|
142
|
-
}
|
|
143
|
-
else if (argument === '--junit') {
|
|
144
|
-
if (output !== 'terminal')
|
|
145
|
-
throw new CliUsageError('Choose only one output format.');
|
|
146
|
-
output = 'junit';
|
|
147
|
-
}
|
|
148
|
-
else if (argument === '--markdown' || argument === '--html') {
|
|
149
|
-
if (output !== 'terminal')
|
|
150
|
-
throw new CliUsageError('Choose only one output format.');
|
|
151
|
-
output = argument === '--markdown' ? 'markdown' : 'html';
|
|
152
|
-
}
|
|
153
|
-
else if (argument === '--deep') {
|
|
154
|
-
if (scanMode)
|
|
155
|
-
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
156
|
-
deep = true;
|
|
157
|
-
scanMode = 'deep';
|
|
158
|
-
}
|
|
159
|
-
else if (argument === '--fast') {
|
|
160
|
-
if (scanMode)
|
|
161
|
-
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
162
|
-
deep = false;
|
|
163
|
-
scanMode = 'fast';
|
|
164
|
-
}
|
|
165
|
-
else if (argument === '--summary' || argument === '--verbose') {
|
|
166
|
-
if (detailSet)
|
|
167
|
-
throw new CliUsageError('--summary and --verbose are mutually exclusive and may be supplied only once.');
|
|
168
|
-
detail = argument === '--summary' ? 'summary' : 'verbose';
|
|
169
|
-
detailSet = true;
|
|
170
|
-
}
|
|
171
|
-
else if (argument === '--strict-exit') {
|
|
172
|
-
if (strictExit)
|
|
173
|
-
throw new CliUsageError('--strict-exit may be supplied only once.');
|
|
174
|
-
strictExit = true;
|
|
175
|
-
}
|
|
176
|
-
else if (argument === '--events') {
|
|
177
|
-
if (events)
|
|
178
|
-
throw new CliUsageError('--events may be supplied only once.');
|
|
179
|
-
events = true;
|
|
180
|
-
}
|
|
181
|
-
else if (argument === '--plan') {
|
|
182
|
-
if (plan)
|
|
183
|
-
throw new CliUsageError('--plan may be supplied only once.');
|
|
184
|
-
plan = true;
|
|
185
|
-
}
|
|
186
|
-
else if (argument === '--browse') {
|
|
187
|
-
if (browse)
|
|
188
|
-
throw new CliUsageError('--browse may be supplied only once.');
|
|
189
|
-
browse = true;
|
|
190
|
-
}
|
|
191
|
-
else if (argument === '--save-lookup') {
|
|
192
|
-
if (saveLookup !== null)
|
|
193
|
-
throw new CliUsageError('--save-lookup may be supplied only once.');
|
|
194
|
-
const value = lookupArguments[++index];
|
|
195
|
-
if (!value || value.startsWith('-'))
|
|
196
|
-
throw new CliUsageError('--save-lookup requires one bounded file path.');
|
|
197
|
-
saveLookup = value;
|
|
198
|
-
}
|
|
199
|
-
else if (argument === '--no-attribution') {
|
|
200
|
-
if (!includeAttribution)
|
|
201
|
-
throw new CliUsageError('--no-attribution may be supplied only once.');
|
|
202
|
-
includeAttribution = false;
|
|
203
|
-
}
|
|
204
|
-
else if (argument === '--fail-on') {
|
|
205
|
-
if (failOn !== null)
|
|
206
|
-
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
207
|
-
failOn = parseCliFailPolicies(lookupArguments[++index], 'lookup');
|
|
208
|
-
}
|
|
209
|
-
else if (argument === '--observer' || argument === '--vantage') {
|
|
210
|
-
const isObserver = argument === '--observer';
|
|
211
|
-
if (isObserver ? observerLabel !== null : vantageLabel !== null) {
|
|
212
|
-
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
213
|
-
}
|
|
214
|
-
const value = lookupArguments[++index];
|
|
215
|
-
if (!value || value.startsWith('-'))
|
|
216
|
-
throw new CliUsageError(`${argument} requires one bounded label.`);
|
|
217
|
-
const normalized = value.replace(/\s+/gu, ' ').trim();
|
|
218
|
-
if (!normalized || normalized.length > 80)
|
|
219
|
-
throw new CliUsageError(`${argument} requires a label of at most 80 characters.`);
|
|
220
|
-
if (isObserver)
|
|
221
|
-
observerLabel = normalized;
|
|
222
|
-
else
|
|
223
|
-
vantageLabel = normalized;
|
|
224
|
-
}
|
|
225
|
-
else if (argument === '--quiet')
|
|
226
|
-
quiet = true;
|
|
227
|
-
else if (argument === '--no-color')
|
|
228
|
-
color = false;
|
|
229
|
-
else if (argument.startsWith('-'))
|
|
230
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
231
|
-
else if (query === null)
|
|
232
|
-
query = argument;
|
|
233
|
-
else
|
|
234
|
-
throw new CliUsageError('lookup accepts one query. Use the bulk command for multiple inputs.');
|
|
235
|
-
}
|
|
236
|
-
if (quiet && output !== 'terminal')
|
|
237
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
238
|
-
if (detailSet && output !== 'terminal')
|
|
239
|
-
throw new CliUsageError('--summary and --verbose apply only to terminal output.');
|
|
240
|
-
if (browse && output !== 'terminal')
|
|
241
|
-
throw new CliUsageError('--browse applies only to terminal output.');
|
|
242
|
-
if (browse && (detailSet || events || plan || quiet)) {
|
|
243
|
-
throw new CliUsageError('--browse cannot be combined with detail, event, plan, or quiet options.');
|
|
244
|
-
}
|
|
245
|
-
if (browse && query === null) {
|
|
246
|
-
throw new CliUsageError('--browse requires a positional target because interactive stdin is reserved for navigation.');
|
|
247
|
-
}
|
|
248
|
-
if (saveLookup !== null && !browse) {
|
|
249
|
-
throw new CliUsageError('--save-lookup requires --browse so the exact completed Lookup document is saved after the browser closes.');
|
|
250
|
-
}
|
|
251
|
-
if (!includeAttribution && output !== 'markdown' && output !== 'html') {
|
|
252
|
-
throw new CliUsageError('--no-attribution applies only to Markdown or HTML reports.');
|
|
253
|
-
}
|
|
254
|
-
if (plan && !['terminal', 'json'].includes(output))
|
|
255
|
-
throw new CliUsageError('--plan supports terminal or JSON output only.');
|
|
256
|
-
if (plan && (detailSet || strictExit || events || quiet || failOn !== null)) {
|
|
257
|
-
throw new CliUsageError('--plan cannot be combined with detail, strict-exit, event, or quiet options.');
|
|
258
|
-
}
|
|
259
|
-
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, includeAttribution, observerLabel, vantageLabel, quiet, color, ...(browse ? { browse: true } : {}), ...(saveLookup ? { saveLookup } : {}), ...(failOn ? { failOn } : {}) };
|
|
260
|
-
}
|
|
261
|
-
function parseCompletionArguments(argv) {
|
|
262
|
-
if (argv.length !== 1 || !['bash', 'zsh', 'fish', 'powershell'].includes(argv[0] || '')) {
|
|
263
|
-
throw new CliUsageError('completion requires exactly one shell: bash, zsh, fish, or powershell.');
|
|
264
|
-
}
|
|
265
|
-
return { action: 'completion', shell: argv[0] };
|
|
266
|
-
}
|
|
267
|
-
function parseManualArguments(argv) {
|
|
268
|
-
if (argv.length !== 0)
|
|
269
|
-
throw new CliUsageError('manual does not accept command arguments.');
|
|
270
|
-
return { action: 'manual' };
|
|
271
|
-
}
|
|
272
|
-
function parseManifestArguments(argv) {
|
|
273
|
-
const sources = [];
|
|
274
|
-
let workflow = null;
|
|
275
|
-
let configurationDigestSha256 = null;
|
|
276
|
-
let output = 'terminal';
|
|
277
|
-
let quiet = false;
|
|
278
|
-
let color = true;
|
|
279
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
280
|
-
const argument = argv[index];
|
|
281
|
-
if (argument === '--json') {
|
|
282
|
-
if (output !== 'terminal')
|
|
283
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
284
|
-
output = 'json';
|
|
285
|
-
}
|
|
286
|
-
else if (argument === '--workflow') {
|
|
287
|
-
if (workflow !== null)
|
|
288
|
-
throw new CliUsageError('--workflow may be supplied only once.');
|
|
289
|
-
workflow = argv[++index] ?? null;
|
|
290
|
-
if (!workflow)
|
|
291
|
-
throw new CliUsageError('--workflow requires a bounded label.');
|
|
292
|
-
}
|
|
293
|
-
else if (argument === '--configuration-digest') {
|
|
294
|
-
if (configurationDigestSha256 !== null)
|
|
295
|
-
throw new CliUsageError('--configuration-digest may be supplied only once.');
|
|
296
|
-
configurationDigestSha256 = argv[++index] ?? null;
|
|
297
|
-
if (!configurationDigestSha256 || !/^sha256:[a-f0-9]{64}$/u.test(configurationDigestSha256)) {
|
|
298
|
-
throw new CliUsageError('--configuration-digest requires a sha256: hexadecimal digest.');
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
else if (argument === '--quiet')
|
|
302
|
-
quiet = true;
|
|
303
|
-
else if (argument === '--no-color')
|
|
304
|
-
color = false;
|
|
305
|
-
else if (argument?.startsWith('-'))
|
|
306
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
307
|
-
else if (argument)
|
|
308
|
-
sources.push(argument);
|
|
309
|
-
}
|
|
310
|
-
if (!workflow)
|
|
311
|
-
throw new CliUsageError('manifest requires --workflow <label>.');
|
|
312
|
-
if (sources.length < 1 || sources.length > 16)
|
|
313
|
-
throw new CliUsageError('manifest requires from 1 to 16 JSON artefact files.');
|
|
314
|
-
if (new Set(sources).size !== sources.length)
|
|
315
|
-
throw new CliUsageError('manifest artefact files must be different.');
|
|
316
|
-
if (quiet && output !== 'terminal')
|
|
317
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
318
|
-
return { action: 'manifest', sources, workflow, configurationDigestSha256, output, quiet, color };
|
|
319
|
-
}
|
|
320
|
-
function parseSingleJsonInput(argv, overflowMessage) {
|
|
321
|
-
let source = null;
|
|
322
|
-
let output = 'terminal';
|
|
323
|
-
let quiet = false;
|
|
324
|
-
let color = true;
|
|
325
|
-
for (const argument of argv) {
|
|
326
|
-
if (argument === '--json') {
|
|
327
|
-
if (output !== 'terminal')
|
|
328
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
329
|
-
output = 'json';
|
|
330
|
-
}
|
|
331
|
-
else if (argument === '--quiet')
|
|
332
|
-
quiet = true;
|
|
333
|
-
else if (argument === '--no-color')
|
|
334
|
-
color = false;
|
|
335
|
-
else if (argument.startsWith('-'))
|
|
336
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
337
|
-
else if (source === null)
|
|
338
|
-
source = argument;
|
|
339
|
-
else
|
|
340
|
-
throw new CliUsageError(overflowMessage);
|
|
341
|
-
}
|
|
342
|
-
if (quiet && output !== 'terminal')
|
|
343
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
344
|
-
return { source, output, quiet, color };
|
|
39
|
+
function parseSignArtifactArguments(parsed) {
|
|
40
|
+
return {
|
|
41
|
+
action: 'sign-artifact',
|
|
42
|
+
source: parsed.positionalValue('source'),
|
|
43
|
+
privateKeySource: parsed.optionValue('--private-key-file'),
|
|
44
|
+
};
|
|
345
45
|
}
|
|
346
|
-
function
|
|
347
|
-
|
|
348
|
-
|
|
46
|
+
function parseVerifySignatureArguments(parsed) {
|
|
47
|
+
return {
|
|
48
|
+
action: 'verify-signature',
|
|
49
|
+
source: parsed.positionalValue('source'),
|
|
50
|
+
publicKeySource: parsed.optionValue('--public-key-file'),
|
|
51
|
+
trustStoreSource: parsed.optionValue('--trust-store-file'),
|
|
52
|
+
output: parsed.hasOption('--json') ? 'json' : 'terminal',
|
|
53
|
+
...terminalOptions(parsed),
|
|
54
|
+
};
|
|
349
55
|
}
|
|
350
|
-
function
|
|
351
|
-
|
|
352
|
-
let common = false;
|
|
353
|
-
let group = null;
|
|
354
|
-
let mode = null;
|
|
355
|
-
let quiet = false;
|
|
356
|
-
let color = true;
|
|
357
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
358
|
-
const argument = argv[index];
|
|
359
|
-
if (argument === '--json') {
|
|
360
|
-
if (output !== 'terminal')
|
|
361
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
362
|
-
output = 'json';
|
|
363
|
-
}
|
|
364
|
-
else if (argument === '--common') {
|
|
365
|
-
if (common)
|
|
366
|
-
throw new CliUsageError('--common may be supplied only once.');
|
|
367
|
-
common = true;
|
|
368
|
-
}
|
|
369
|
-
else if (argument === '--group') {
|
|
370
|
-
if (group !== null)
|
|
371
|
-
throw new CliUsageError('--group may be supplied only once.');
|
|
372
|
-
const value = argv[++index];
|
|
373
|
-
if (!['investigate', 'respond', 'assure', 'utilities'].includes(value ?? '')) {
|
|
374
|
-
throw new CliUsageError('--group requires investigate, respond, assure, or utilities.');
|
|
375
|
-
}
|
|
376
|
-
group = value;
|
|
377
|
-
}
|
|
378
|
-
else if (argument === '--mode') {
|
|
379
|
-
if (mode !== null)
|
|
380
|
-
throw new CliUsageError('--mode may be supplied only once.');
|
|
381
|
-
const value = argv[++index];
|
|
382
|
-
if (value !== 'offline' && value !== 'network') {
|
|
383
|
-
throw new CliUsageError('--mode requires offline or network.');
|
|
384
|
-
}
|
|
385
|
-
mode = value;
|
|
386
|
-
}
|
|
387
|
-
else if (argument === '--quiet')
|
|
388
|
-
quiet = true;
|
|
389
|
-
else if (argument === '--no-color')
|
|
390
|
-
color = false;
|
|
391
|
-
else
|
|
392
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
393
|
-
}
|
|
394
|
-
if (quiet && output !== 'terminal')
|
|
395
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
396
|
-
return { action: 'commands', output, common, group, mode, quiet, color };
|
|
56
|
+
function jsonOutput(parsed) {
|
|
57
|
+
return parsed.hasOption('--json') ? 'json' : 'terminal';
|
|
397
58
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (network)
|
|
406
|
-
throw new CliUsageError('--network may be supplied only once.');
|
|
407
|
-
network = true;
|
|
408
|
-
}
|
|
409
|
-
else if (argument === '--json') {
|
|
410
|
-
if (output !== 'terminal')
|
|
411
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
412
|
-
output = 'json';
|
|
413
|
-
}
|
|
414
|
-
else if (argument === '--quiet')
|
|
415
|
-
quiet = true;
|
|
416
|
-
else if (argument === '--no-color')
|
|
417
|
-
color = false;
|
|
418
|
-
else
|
|
419
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
59
|
+
function normalizedLabel(parsed, option, maximum) {
|
|
60
|
+
const value = parsed.optionValue(option);
|
|
61
|
+
if (value === null)
|
|
62
|
+
return null;
|
|
63
|
+
const normalized = value.replace(/\s+/gu, ' ').trim();
|
|
64
|
+
if (!normalized || normalized.length > maximum) {
|
|
65
|
+
throw new CliUsageError(`${option} requires a label of at most ${maximum} characters.`);
|
|
420
66
|
}
|
|
421
|
-
|
|
422
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
423
|
-
return { action: 'doctor', network, output, quiet, color };
|
|
67
|
+
return normalized;
|
|
424
68
|
}
|
|
425
|
-
function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
let deep = false;
|
|
429
|
-
let scanMode = null;
|
|
430
|
-
let quiet = false;
|
|
431
|
-
let color = true;
|
|
432
|
-
let concurrency = null;
|
|
433
|
-
let checkpoint = null;
|
|
434
|
-
let resume = false;
|
|
435
|
-
let events = false;
|
|
436
|
-
let plan = false;
|
|
437
|
-
let failOn = null;
|
|
438
|
-
let filter = 'all';
|
|
439
|
-
for (let index = 0; index < argv.length; index++) {
|
|
440
|
-
const argument = argv[index];
|
|
441
|
-
if (argument === undefined)
|
|
442
|
-
break;
|
|
443
|
-
if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains' || argument === '--queries' || argument === '--junit') {
|
|
444
|
-
if (output !== 'terminal')
|
|
445
|
-
throw new CliUsageError('Choose only one output format.');
|
|
446
|
-
output = argument === '--json'
|
|
447
|
-
? 'json'
|
|
448
|
-
: argument === '--jsonl'
|
|
449
|
-
? 'jsonl'
|
|
450
|
-
: argument === '--csv'
|
|
451
|
-
? 'csv'
|
|
452
|
-
: argument === '--domains'
|
|
453
|
-
? 'domains'
|
|
454
|
-
: argument === '--queries'
|
|
455
|
-
? 'queries'
|
|
456
|
-
: 'junit';
|
|
457
|
-
}
|
|
458
|
-
else if (argument === '--deep' || argument === '--fast') {
|
|
459
|
-
if (scanMode)
|
|
460
|
-
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
461
|
-
scanMode = argument === '--deep' ? 'deep' : 'fast';
|
|
462
|
-
deep = scanMode === 'deep';
|
|
463
|
-
}
|
|
464
|
-
else if (argument === '--concurrency') {
|
|
465
|
-
if (concurrency !== null)
|
|
466
|
-
throw new CliUsageError('--concurrency may be supplied only once.');
|
|
467
|
-
const raw = argv[++index];
|
|
468
|
-
if (!raw || !/^\d+$/.test(raw))
|
|
469
|
-
throw new CliUsageError('--concurrency requires an integer from 1 to 8.');
|
|
470
|
-
concurrency = Number(raw);
|
|
471
|
-
if (concurrency < 1 || concurrency > 8)
|
|
472
|
-
throw new CliUsageError('--concurrency must be from 1 to 8.');
|
|
473
|
-
}
|
|
474
|
-
else if (argument === '--checkpoint') {
|
|
475
|
-
if (checkpoint !== null)
|
|
476
|
-
throw new CliUsageError('--checkpoint may be supplied only once.');
|
|
477
|
-
const value = argv[++index];
|
|
478
|
-
if (!value || value.startsWith('-'))
|
|
479
|
-
throw new CliUsageError('--checkpoint requires one bounded file path.');
|
|
480
|
-
checkpoint = value;
|
|
481
|
-
}
|
|
482
|
-
else if (argument === '--resume') {
|
|
483
|
-
if (resume)
|
|
484
|
-
throw new CliUsageError('--resume may be supplied only once.');
|
|
485
|
-
resume = true;
|
|
486
|
-
}
|
|
487
|
-
else if (argument === '--events') {
|
|
488
|
-
if (events)
|
|
489
|
-
throw new CliUsageError('--events may be supplied only once.');
|
|
490
|
-
events = true;
|
|
491
|
-
}
|
|
492
|
-
else if (argument === '--plan') {
|
|
493
|
-
if (plan)
|
|
494
|
-
throw new CliUsageError('--plan may be supplied only once.');
|
|
495
|
-
plan = true;
|
|
496
|
-
}
|
|
497
|
-
else if (argument === '--fail-on') {
|
|
498
|
-
if (failOn !== null)
|
|
499
|
-
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
500
|
-
failOn = parseCliFailPolicies(argv[++index], 'bulk');
|
|
501
|
-
}
|
|
502
|
-
else if (argument === '--registered-only' || argument === '--inconclusive-only' || argument === '--errors-only') {
|
|
503
|
-
if (filter !== 'all')
|
|
504
|
-
throw new CliUsageError('Bulk output filters are mutually exclusive and may be supplied only once.');
|
|
505
|
-
filter = argument === '--registered-only'
|
|
506
|
-
? 'registered'
|
|
507
|
-
: argument === '--inconclusive-only'
|
|
508
|
-
? 'inconclusive'
|
|
509
|
-
: 'errors';
|
|
510
|
-
}
|
|
511
|
-
else if (argument === '--quiet')
|
|
512
|
-
quiet = true;
|
|
513
|
-
else if (argument === '--no-color')
|
|
514
|
-
color = false;
|
|
515
|
-
else if (argument.startsWith('-'))
|
|
516
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
517
|
-
else if (source === null)
|
|
518
|
-
source = argument;
|
|
519
|
-
else
|
|
520
|
-
throw new CliUsageError('bulk accepts one optional input file. Otherwise pipe newline-delimited queries on stdin.');
|
|
521
|
-
}
|
|
522
|
-
if (quiet && output !== 'terminal')
|
|
523
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
524
|
-
if (resume && checkpoint === null)
|
|
525
|
-
throw new CliUsageError('--resume requires --checkpoint.');
|
|
526
|
-
if (plan && (events || resume || checkpoint !== null || quiet || failOn !== null || !['terminal', 'json'].includes(output))) {
|
|
527
|
-
throw new CliUsageError('--plan supports terminal or JSON output and cannot be combined with events, checkpoint, resume, or quiet options.');
|
|
528
|
-
}
|
|
529
|
-
const maximum = deep ? 3 : 8;
|
|
530
|
-
if (concurrency !== null && concurrency > maximum) {
|
|
531
|
-
throw new CliUsageError(`--concurrency is capped at ${maximum} in ${deep ? 'deep' : 'fast'} bulk mode.`);
|
|
532
|
-
}
|
|
533
|
-
return { action: 'bulk', source, output, deep, quiet, color, concurrency: concurrency ?? (deep ? 2 : 4), checkpoint, resume, events, plan, filter, ...(failOn ? { failOn } : {}) };
|
|
69
|
+
function failPolicies(parsed, command) {
|
|
70
|
+
const value = parsed.optionValue('--fail-on');
|
|
71
|
+
return value === null ? null : parseCliFailPolicies(value, command);
|
|
534
72
|
}
|
|
535
|
-
function
|
|
536
|
-
|
|
537
|
-
return { action: 'ct-search', keyword, ...options };
|
|
73
|
+
function parseOutput(parsed, formats) {
|
|
74
|
+
return formats.find(([option]) => parsed.hasOption(option))?.[1] ?? 'terminal';
|
|
538
75
|
}
|
|
539
|
-
function
|
|
540
|
-
|
|
541
|
-
|
|
76
|
+
function singleInputAction(action, parsed, positionalName = 'source') {
|
|
77
|
+
return {
|
|
78
|
+
action,
|
|
79
|
+
[positionalName === 'source' ? 'source' : positionalName]: parsed.positionalValue(positionalName),
|
|
80
|
+
output: jsonOutput(parsed),
|
|
81
|
+
...terminalOptions(parsed),
|
|
82
|
+
};
|
|
542
83
|
}
|
|
543
|
-
function
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (presetSet)
|
|
567
|
-
throw new CliUsageError('--preset may be supplied only once.');
|
|
568
|
-
if (familyText !== null)
|
|
569
|
-
throw new CliUsageError('--preset cannot be combined with --families.');
|
|
570
|
-
const value = argv[++index];
|
|
571
|
-
if (value !== 'common' && value !== 'impersonation' && value !== 'all') {
|
|
572
|
-
throw new CliUsageError('--preset requires common, impersonation, or all.');
|
|
573
|
-
}
|
|
574
|
-
preset = value;
|
|
575
|
-
presetSet = true;
|
|
576
|
-
}
|
|
577
|
-
else if (argument === '--keyboard') {
|
|
578
|
-
if (keyboardSet)
|
|
579
|
-
throw new CliUsageError('--keyboard may be supplied only once.');
|
|
580
|
-
const value = argv[++index];
|
|
581
|
-
if (value !== 'qwerty' && value !== 'azerty' && value !== 'qwertz' && value !== 'all') {
|
|
582
|
-
throw new CliUsageError('--keyboard requires qwerty, azerty, qwertz, or all.');
|
|
583
|
-
}
|
|
584
|
-
keyboardLayout = value;
|
|
585
|
-
keyboardSet = true;
|
|
586
|
-
}
|
|
587
|
-
else if (argument === '--tlds') {
|
|
588
|
-
if (tldText !== null)
|
|
589
|
-
throw new CliUsageError('--tlds may be supplied only once.');
|
|
590
|
-
const value = argv[++index];
|
|
591
|
-
if (!value)
|
|
592
|
-
throw new CliUsageError('--tlds requires a comma-separated list.');
|
|
593
|
-
tldText = value;
|
|
594
|
-
}
|
|
595
|
-
else if (argument === '--dictionary') {
|
|
596
|
-
if (dictionarySource !== null)
|
|
597
|
-
throw new CliUsageError('--dictionary may be supplied only once.');
|
|
598
|
-
const value = argv[++index];
|
|
599
|
-
if (!value || value.startsWith('-'))
|
|
600
|
-
throw new CliUsageError('--dictionary requires one UTF-8 text file.');
|
|
601
|
-
dictionarySource = value;
|
|
602
|
-
}
|
|
603
|
-
else if (argument === '--families') {
|
|
604
|
-
if (familyText !== null)
|
|
605
|
-
throw new CliUsageError('--families may be supplied only once.');
|
|
606
|
-
if (presetSet)
|
|
607
|
-
throw new CliUsageError('--families cannot be combined with --preset.');
|
|
608
|
-
const value = argv[++index];
|
|
609
|
-
if (!value || value.startsWith('-'))
|
|
610
|
-
throw new CliUsageError('--families requires a comma-separated list of mutation family IDs.');
|
|
611
|
-
familyText = value;
|
|
612
|
-
preset = 'custom';
|
|
613
|
-
}
|
|
614
|
-
else if (argument === '--snapshot') {
|
|
615
|
-
if (snapshotSource !== null)
|
|
616
|
-
throw new CliUsageError('--snapshot may be supplied only once.');
|
|
617
|
-
const value = argv[++index];
|
|
618
|
-
if (!value || value.startsWith('-'))
|
|
619
|
-
throw new CliUsageError('--snapshot requires one bounded local state file path.');
|
|
620
|
-
snapshotSource = value;
|
|
621
|
-
}
|
|
622
|
-
else if (argument === '--quiet')
|
|
623
|
-
quiet = true;
|
|
624
|
-
else if (argument === '--no-color')
|
|
625
|
-
color = false;
|
|
626
|
-
else if (argument.startsWith('-'))
|
|
627
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
628
|
-
else if (seed === null)
|
|
629
|
-
seed = argument;
|
|
630
|
-
else
|
|
631
|
-
throw new CliUsageError('discover accepts one brand label or domain. Quote multi-word labels as one argument.');
|
|
632
|
-
}
|
|
633
|
-
if (quiet && output !== 'terminal')
|
|
634
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
635
|
-
if (dictionarySource && preset === 'common') {
|
|
636
|
-
throw new CliUsageError('--dictionary requires the impersonation or all preset.');
|
|
637
|
-
}
|
|
638
|
-
return { action: 'discover', seed, output, quiet, color, preset, keyboardLayout, tldText, dictionarySource, familyText, snapshotSource };
|
|
84
|
+
function parseLookupArguments(parsed) {
|
|
85
|
+
const output = parseOutput(parsed, [
|
|
86
|
+
['--json', 'json'], ['--junit', 'junit'], ['--markdown', 'markdown'], ['--html', 'html'],
|
|
87
|
+
]);
|
|
88
|
+
const selectedFailPolicies = failPolicies(parsed, 'lookup');
|
|
89
|
+
return {
|
|
90
|
+
action: 'lookup',
|
|
91
|
+
query: parsed.positionalValue('target'),
|
|
92
|
+
output,
|
|
93
|
+
deep: parsed.hasOption('--deep'),
|
|
94
|
+
detail: parsed.hasOption('--summary') ? 'summary' : parsed.hasOption('--verbose') ? 'verbose' : 'standard',
|
|
95
|
+
strictExit: parsed.hasOption('--strict-exit'),
|
|
96
|
+
events: parsed.hasOption('--events'),
|
|
97
|
+
plan: parsed.hasOption('--plan'),
|
|
98
|
+
includeAttribution: !parsed.hasOption('--no-attribution'),
|
|
99
|
+
observerLabel: normalizedLabel(parsed, '--observer', 80),
|
|
100
|
+
vantageLabel: normalizedLabel(parsed, '--vantage', 80),
|
|
101
|
+
...terminalOptions(parsed),
|
|
102
|
+
...(parsed.hasOption('--browse') ? { browse: true } : {}),
|
|
103
|
+
...(parsed.hasOption('--exact-url') ? { exactUrl: true } : {}),
|
|
104
|
+
...(parsed.optionValue('--save-lookup') ? { saveLookup: parsed.optionValue('--save-lookup') } : {}),
|
|
105
|
+
...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
|
|
106
|
+
};
|
|
639
107
|
}
|
|
640
|
-
function
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
let familyText = null;
|
|
650
|
-
let presetSet = false;
|
|
651
|
-
let keyboardSet = false;
|
|
652
|
-
let deep = false;
|
|
653
|
-
let scanModeSet = false;
|
|
654
|
-
let scanLimit = null;
|
|
655
|
-
let chunkSize = null;
|
|
656
|
-
let concurrency = null;
|
|
657
|
-
let checkpoint = null;
|
|
658
|
-
let resume = false;
|
|
659
|
-
let resolverText = null;
|
|
660
|
-
let observationSnapshot = null;
|
|
661
|
-
let allowlistSource = null;
|
|
662
|
-
let filter = 'all';
|
|
663
|
-
let filterSet = false;
|
|
664
|
-
let events = false;
|
|
665
|
-
let plan = false;
|
|
666
|
-
let failOn = null;
|
|
667
|
-
for (let index = 0; index < argv.length; index++) {
|
|
668
|
-
const argument = argv[index];
|
|
669
|
-
if (argument === undefined)
|
|
670
|
-
break;
|
|
671
|
-
if (argument === '--json' || argument === '--jsonl' || argument === '--csv' || argument === '--domains') {
|
|
672
|
-
if (output !== 'terminal')
|
|
673
|
-
throw new CliUsageError('Choose only one output format.');
|
|
674
|
-
output = argument === '--json' ? 'json' : argument === '--jsonl' ? 'jsonl' : argument === '--csv' ? 'csv' : 'domains';
|
|
675
|
-
}
|
|
676
|
-
else if (argument === '--preset') {
|
|
677
|
-
if (presetSet)
|
|
678
|
-
throw new CliUsageError('--preset may be supplied only once.');
|
|
679
|
-
if (familyText !== null)
|
|
680
|
-
throw new CliUsageError('--preset cannot be combined with --families.');
|
|
681
|
-
const value = argv[++index];
|
|
682
|
-
if (value !== 'common' && value !== 'impersonation' && value !== 'all') {
|
|
683
|
-
throw new CliUsageError('--preset requires common, impersonation, or all.');
|
|
684
|
-
}
|
|
685
|
-
preset = value;
|
|
686
|
-
presetSet = true;
|
|
687
|
-
}
|
|
688
|
-
else if (argument === '--keyboard') {
|
|
689
|
-
if (keyboardSet)
|
|
690
|
-
throw new CliUsageError('--keyboard may be supplied only once.');
|
|
691
|
-
const value = argv[++index];
|
|
692
|
-
if (value !== 'qwerty' && value !== 'azerty' && value !== 'qwertz' && value !== 'all') {
|
|
693
|
-
throw new CliUsageError('--keyboard requires qwerty, azerty, qwertz, or all.');
|
|
694
|
-
}
|
|
695
|
-
keyboardLayout = value;
|
|
696
|
-
keyboardSet = true;
|
|
697
|
-
}
|
|
698
|
-
else if (argument === '--tlds') {
|
|
699
|
-
if (tldText !== null)
|
|
700
|
-
throw new CliUsageError('--tlds may be supplied only once.');
|
|
701
|
-
const value = argv[++index];
|
|
702
|
-
if (!value)
|
|
703
|
-
throw new CliUsageError('--tlds requires a comma-separated list.');
|
|
704
|
-
tldText = value;
|
|
705
|
-
}
|
|
706
|
-
else if (argument === '--dictionary') {
|
|
707
|
-
if (dictionarySource !== null)
|
|
708
|
-
throw new CliUsageError('--dictionary may be supplied only once.');
|
|
709
|
-
const value = argv[++index];
|
|
710
|
-
if (!value || value.startsWith('-'))
|
|
711
|
-
throw new CliUsageError('--dictionary requires one UTF-8 text file.');
|
|
712
|
-
dictionarySource = value;
|
|
713
|
-
}
|
|
714
|
-
else if (argument === '--families') {
|
|
715
|
-
if (familyText !== null)
|
|
716
|
-
throw new CliUsageError('--families may be supplied only once.');
|
|
717
|
-
if (presetSet)
|
|
718
|
-
throw new CliUsageError('--families cannot be combined with --preset.');
|
|
719
|
-
const value = argv[++index];
|
|
720
|
-
if (!value || value.startsWith('-'))
|
|
721
|
-
throw new CliUsageError('--families requires a comma-separated list of mutation family IDs.');
|
|
722
|
-
familyText = value;
|
|
723
|
-
preset = 'custom';
|
|
724
|
-
}
|
|
725
|
-
else if (argument === '--deep' || argument === '--fast') {
|
|
726
|
-
if (scanModeSet)
|
|
727
|
-
throw new CliUsageError('--fast and --deep are mutually exclusive and may be supplied only once.');
|
|
728
|
-
deep = argument === '--deep';
|
|
729
|
-
scanModeSet = true;
|
|
730
|
-
}
|
|
731
|
-
else if (argument === '--scan-limit') {
|
|
732
|
-
if (scanLimit !== null)
|
|
733
|
-
throw new CliUsageError('--scan-limit may be supplied only once.');
|
|
734
|
-
scanLimit = positiveIntegerOption(argv[++index], '--scan-limit', 500);
|
|
735
|
-
}
|
|
736
|
-
else if (argument === '--chunk-size') {
|
|
737
|
-
if (chunkSize !== null)
|
|
738
|
-
throw new CliUsageError('--chunk-size may be supplied only once.');
|
|
739
|
-
chunkSize = positiveIntegerOption(argv[++index], '--chunk-size', 100);
|
|
740
|
-
}
|
|
741
|
-
else if (argument === '--concurrency') {
|
|
742
|
-
if (concurrency !== null)
|
|
743
|
-
throw new CliUsageError('--concurrency may be supplied only once.');
|
|
744
|
-
concurrency = positiveIntegerOption(argv[++index], '--concurrency', 8);
|
|
745
|
-
}
|
|
746
|
-
else if (argument === '--checkpoint') {
|
|
747
|
-
if (checkpoint !== null)
|
|
748
|
-
throw new CliUsageError('--checkpoint may be supplied only once.');
|
|
749
|
-
const value = argv[++index];
|
|
750
|
-
if (!value || value.startsWith('-'))
|
|
751
|
-
throw new CliUsageError('--checkpoint requires one bounded file path.');
|
|
752
|
-
checkpoint = value;
|
|
753
|
-
}
|
|
754
|
-
else if (argument === '--resume') {
|
|
755
|
-
if (resume)
|
|
756
|
-
throw new CliUsageError('--resume may be supplied only once.');
|
|
757
|
-
resume = true;
|
|
758
|
-
}
|
|
759
|
-
else if (argument === '--resolver') {
|
|
760
|
-
if (resolverText !== null)
|
|
761
|
-
throw new CliUsageError('--resolver may be supplied only once.');
|
|
762
|
-
const value = argv[++index];
|
|
763
|
-
if (!value || value.startsWith('-'))
|
|
764
|
-
throw new CliUsageError('--resolver requires a comma-separated list of IP addresses.');
|
|
765
|
-
resolverText = value;
|
|
766
|
-
}
|
|
767
|
-
else if (argument === '--observation-snapshot') {
|
|
768
|
-
if (observationSnapshot !== null)
|
|
769
|
-
throw new CliUsageError('--observation-snapshot may be supplied only once.');
|
|
770
|
-
const value = argv[++index];
|
|
771
|
-
if (!value || value.startsWith('-'))
|
|
772
|
-
throw new CliUsageError('--observation-snapshot requires one bounded local state file path.');
|
|
773
|
-
observationSnapshot = value;
|
|
774
|
-
}
|
|
775
|
-
else if (argument === '--allowlist') {
|
|
776
|
-
if (allowlistSource !== null)
|
|
777
|
-
throw new CliUsageError('--allowlist may be supplied only once.');
|
|
778
|
-
const value = argv[++index];
|
|
779
|
-
if (!value || value.startsWith('-'))
|
|
780
|
-
throw new CliUsageError('--allowlist requires one newline-delimited local file.');
|
|
781
|
-
allowlistSource = value;
|
|
782
|
-
}
|
|
783
|
-
else if (argument === '--registered-only' || argument === '--inconclusive-only'
|
|
784
|
-
|| argument === '--acquisition-only' || argument === '--suppressed-only') {
|
|
785
|
-
if (filterSet)
|
|
786
|
-
throw new CliUsageError('Discovery scan filters are mutually exclusive and may be supplied only once.');
|
|
787
|
-
filter = argument === '--registered-only' ? 'registered'
|
|
788
|
-
: argument === '--inconclusive-only' ? 'inconclusive'
|
|
789
|
-
: argument === '--acquisition-only' ? 'acquisition' : 'suppressed';
|
|
790
|
-
filterSet = true;
|
|
791
|
-
}
|
|
792
|
-
else if (argument === '--events') {
|
|
793
|
-
if (events)
|
|
794
|
-
throw new CliUsageError('--events may be supplied only once.');
|
|
795
|
-
events = true;
|
|
796
|
-
}
|
|
797
|
-
else if (argument === '--plan') {
|
|
798
|
-
if (plan)
|
|
799
|
-
throw new CliUsageError('--plan may be supplied only once.');
|
|
800
|
-
plan = true;
|
|
801
|
-
}
|
|
802
|
-
else if (argument === '--fail-on') {
|
|
803
|
-
if (failOn !== null)
|
|
804
|
-
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
805
|
-
failOn = parseCliFailPolicies(argv[++index], 'discover-scan');
|
|
806
|
-
}
|
|
807
|
-
else if (argument === '--quiet')
|
|
808
|
-
quiet = true;
|
|
809
|
-
else if (argument === '--no-color')
|
|
810
|
-
color = false;
|
|
811
|
-
else if (argument.startsWith('-'))
|
|
812
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
813
|
-
else if (seed === null)
|
|
814
|
-
seed = argument;
|
|
815
|
-
else
|
|
816
|
-
throw new CliUsageError('discover-scan accepts one brand label or domain. Quote multi-word labels as one argument.');
|
|
817
|
-
}
|
|
818
|
-
const maximum = deep ? 50 : 500;
|
|
819
|
-
if (scanLimit !== null && scanLimit > maximum)
|
|
820
|
-
throw new CliUsageError(`--scan-limit is capped at ${maximum} in ${deep ? 'deep' : 'fast'} mode.`);
|
|
821
|
-
const maximumConcurrency = deep ? 3 : 8;
|
|
822
|
-
if ((concurrency ?? (deep ? 2 : 4)) > maximumConcurrency) {
|
|
823
|
-
throw new CliUsageError(`--concurrency is capped at ${maximumConcurrency} in ${deep ? 'deep' : 'fast'} mode.`);
|
|
824
|
-
}
|
|
825
|
-
if (resume && checkpoint === null)
|
|
826
|
-
throw new CliUsageError('--resume requires --checkpoint.');
|
|
827
|
-
if (plan && (events || resume || checkpoint !== null || observationSnapshot !== null || quiet || failOn !== null || !['terminal', 'json'].includes(output))) {
|
|
828
|
-
throw new CliUsageError('--plan supports terminal or JSON output and cannot be combined with events, checkpoint, resume, observation snapshots, or quiet options.');
|
|
108
|
+
function parseManifestArguments(parsed) {
|
|
109
|
+
const sources = parsed.positionalValues('artefacts');
|
|
110
|
+
if (sources.some((source) => !source))
|
|
111
|
+
throw new CliUsageError(`manifest requires from 1 to ${MAX_INVESTIGATION_MANIFEST_ARTIFACTS} artefact files.`);
|
|
112
|
+
if (new Set(sources).size !== sources.length)
|
|
113
|
+
throw new CliUsageError('manifest artefact files must be different.');
|
|
114
|
+
const configurationDigestSha256 = parsed.optionValue('--configuration-digest');
|
|
115
|
+
if (configurationDigestSha256 !== null && !/^sha256:[a-f0-9]{64}$/u.test(configurationDigestSha256)) {
|
|
116
|
+
throw new CliUsageError('--configuration-digest requires a sha256: hexadecimal digest.');
|
|
829
117
|
}
|
|
830
|
-
if (quiet && output !== 'terminal')
|
|
831
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
832
|
-
if (dictionarySource && preset === 'common')
|
|
833
|
-
throw new CliUsageError('--dictionary requires the impersonation or all preset.');
|
|
834
118
|
return {
|
|
835
|
-
action: '
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
119
|
+
action: 'manifest',
|
|
120
|
+
sources,
|
|
121
|
+
workflow: parsed.optionValue('--workflow'),
|
|
122
|
+
configurationDigestSha256,
|
|
123
|
+
...(parsed.optionValue('--passphrase-file') ? { passphraseSource: parsed.optionValue('--passphrase-file') } : {}),
|
|
124
|
+
...(parsed.hasOption('--package') ? { package: true } : {}),
|
|
125
|
+
...(parsed.hasOption('--bagit') ? { bagit: true } : {}),
|
|
126
|
+
...(parsed.optionValue('--folder') ? { folder: parsed.optionValue('--folder') } : {}),
|
|
127
|
+
output: jsonOutput(parsed),
|
|
128
|
+
...terminalOptions(parsed),
|
|
839
129
|
};
|
|
840
130
|
}
|
|
841
|
-
function
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
output = 'json';
|
|
867
|
-
}
|
|
868
|
-
else if (argument === '--sarif') {
|
|
869
|
-
if (output !== 'terminal')
|
|
870
|
-
throw new CliUsageError('Choose only one output format.');
|
|
871
|
-
output = 'sarif';
|
|
872
|
-
}
|
|
873
|
-
else if (argument === '--owned-domain') {
|
|
874
|
-
if (ownedDomain)
|
|
875
|
-
throw new CliUsageError('--owned-domain may be supplied only once.');
|
|
876
|
-
ownedDomain = true;
|
|
877
|
-
}
|
|
878
|
-
else if (argument === '--selectors') {
|
|
879
|
-
if (selectorText !== null)
|
|
880
|
-
throw new CliUsageError('--selectors may be supplied only once.');
|
|
881
|
-
const value = argv[++index];
|
|
882
|
-
if (!value)
|
|
883
|
-
throw new CliUsageError('--selectors requires a comma-separated list.');
|
|
884
|
-
selectorText = value;
|
|
885
|
-
}
|
|
886
|
-
else if (argument === '--retired-selectors') {
|
|
887
|
-
if (retiredSelectorText !== null)
|
|
888
|
-
throw new CliUsageError('--retired-selectors may be supplied only once.');
|
|
889
|
-
const value = argv[++index];
|
|
890
|
-
if (!value)
|
|
891
|
-
throw new CliUsageError('--retired-selectors requires a comma-separated list.');
|
|
892
|
-
retiredSelectorText = value;
|
|
893
|
-
}
|
|
894
|
-
else if (argument === '--mail-profile') {
|
|
895
|
-
if (mailProfileSeen)
|
|
896
|
-
throw new CliUsageError('--mail-profile may be supplied only once.');
|
|
897
|
-
const value = argv[++index];
|
|
898
|
-
if (!value || !['standard', 'defensive-no-mail', 'parked'].includes(value)) {
|
|
899
|
-
throw new CliUsageError('--mail-profile must be standard, defensive-no-mail, or parked.');
|
|
900
|
-
}
|
|
901
|
-
mailProfile = value === 'defensive-no-mail' ? 'defensive_no_mail' : value;
|
|
902
|
-
mailProfileSeen = true;
|
|
903
|
-
}
|
|
904
|
-
else if (argument === '--quiet')
|
|
905
|
-
quiet = true;
|
|
906
|
-
else if (argument === '--no-color')
|
|
907
|
-
color = false;
|
|
908
|
-
else if (argument.startsWith('-'))
|
|
909
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
910
|
-
else if (domain === null)
|
|
911
|
-
domain = argument;
|
|
912
|
-
else
|
|
913
|
-
throw new CliUsageError('posture accepts one domain.');
|
|
914
|
-
}
|
|
915
|
-
if (quiet && output !== 'terminal')
|
|
916
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
917
|
-
if (output === 'sarif' && !ownedDomain)
|
|
918
|
-
throw new CliUsageError('--sarif requires --owned-domain to confirm the passive posture target is controlled by the operator.');
|
|
919
|
-
return { action: 'posture', domain, output, quiet, color, selectorText, retiredSelectorText, mailProfile, ownedDomain };
|
|
131
|
+
function parseBulkArguments(parsed) {
|
|
132
|
+
const deep = parsed.hasOption('--deep');
|
|
133
|
+
const selectedFailPolicies = failPolicies(parsed, 'bulk');
|
|
134
|
+
const output = parseOutput(parsed, [
|
|
135
|
+
['--json', 'json'], ['--jsonl', 'jsonl'], ['--csv', 'csv'], ['--domains', 'domains'],
|
|
136
|
+
['--csv-with-metadata', 'csv_metadata'],
|
|
137
|
+
['--queries', 'queries'], ['--junit', 'junit'],
|
|
138
|
+
]);
|
|
139
|
+
const filter = parsed.hasOption('--registered-only') ? 'registered'
|
|
140
|
+
: parsed.hasOption('--inconclusive-only') ? 'inconclusive'
|
|
141
|
+
: parsed.hasOption('--errors-only') ? 'errors' : 'all';
|
|
142
|
+
return {
|
|
143
|
+
action: 'bulk',
|
|
144
|
+
source: parsed.positionalValue('source'),
|
|
145
|
+
output,
|
|
146
|
+
deep,
|
|
147
|
+
concurrency: parsed.integerOption('--concurrency') ?? (deep ? 2 : 4),
|
|
148
|
+
checkpoint: parsed.optionValue('--checkpoint'),
|
|
149
|
+
resume: parsed.hasOption('--resume'),
|
|
150
|
+
events: parsed.hasOption('--events'),
|
|
151
|
+
plan: parsed.hasOption('--plan'),
|
|
152
|
+
filter,
|
|
153
|
+
...terminalOptions(parsed),
|
|
154
|
+
...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
|
|
155
|
+
};
|
|
920
156
|
}
|
|
921
|
-
function
|
|
922
|
-
const
|
|
923
|
-
return {
|
|
157
|
+
function discoveryValues(parsed) {
|
|
158
|
+
const familyText = parsed.optionValue('--families');
|
|
159
|
+
return {
|
|
160
|
+
preset: (familyText ? 'custom' : parsed.optionValue('--preset') ?? 'all'),
|
|
161
|
+
keyboardLayout: (parsed.optionValue('--keyboard') ?? 'qwerty'),
|
|
162
|
+
tldText: parsed.optionValue('--tlds'),
|
|
163
|
+
dictionarySource: parsed.optionValue('--dictionary'),
|
|
164
|
+
familyText,
|
|
165
|
+
};
|
|
924
166
|
}
|
|
925
|
-
function
|
|
926
|
-
|
|
927
|
-
|
|
167
|
+
function parseDiscoverArguments(parsed) {
|
|
168
|
+
return {
|
|
169
|
+
action: 'discover',
|
|
170
|
+
seed: parsed.positionalValue('subject'),
|
|
171
|
+
output: parseOutput(parsed, [['--json', 'json'], ['--jsonl', 'jsonl'], ['--domains', 'domains']]),
|
|
172
|
+
...discoveryValues(parsed),
|
|
173
|
+
snapshotSource: parsed.optionValue('--snapshot'),
|
|
174
|
+
...terminalOptions(parsed),
|
|
175
|
+
};
|
|
928
176
|
}
|
|
929
|
-
function
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
else if (argument === '--json') {
|
|
957
|
-
if (output !== 'terminal')
|
|
958
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
959
|
-
output = 'json';
|
|
960
|
-
}
|
|
961
|
-
else if (argument === '--quiet')
|
|
962
|
-
quiet = true;
|
|
963
|
-
else if (argument === '--no-color')
|
|
964
|
-
color = false;
|
|
965
|
-
else if (argument?.startsWith('-'))
|
|
966
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
967
|
-
else if (argument && target === null)
|
|
968
|
-
target = argument;
|
|
969
|
-
else
|
|
970
|
-
throw new CliUsageError('dnssec-validate accepts one domain target.');
|
|
971
|
-
}
|
|
972
|
-
if (!target)
|
|
973
|
-
throw new CliUsageError('dnssec-validate requires one domain target.');
|
|
974
|
-
if (!resolver)
|
|
975
|
-
throw new CliUsageError('dnssec-validate requires --resolver <public-IP>.');
|
|
976
|
-
if (!trustAnchorSource)
|
|
977
|
-
throw new CliUsageError('dnssec-validate requires --trust-anchor <anchor.json>.');
|
|
978
|
-
if (!ownedOrAuthorized)
|
|
979
|
-
throw new CliUsageError('dnssec-validate requires --owned-or-authorized for every run.');
|
|
980
|
-
if (quiet && output !== 'terminal')
|
|
981
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
982
|
-
return { action: 'dnssec-validate', target, resolver, trustAnchorSource, ownedOrAuthorized: true, output, quiet, color };
|
|
177
|
+
function parseDiscoverScanArguments(parsed) {
|
|
178
|
+
const deep = parsed.hasOption('--deep');
|
|
179
|
+
const selectedFailPolicies = failPolicies(parsed, 'discover-scan');
|
|
180
|
+
const filter = parsed.hasOption('--registered-only') ? 'registered'
|
|
181
|
+
: parsed.hasOption('--inconclusive-only') ? 'inconclusive'
|
|
182
|
+
: parsed.hasOption('--acquisition-only') ? 'acquisition'
|
|
183
|
+
: parsed.hasOption('--suppressed-only') ? 'suppressed' : 'all';
|
|
184
|
+
return {
|
|
185
|
+
action: 'discover-scan',
|
|
186
|
+
seed: parsed.positionalValue('subject'),
|
|
187
|
+
output: parseOutput(parsed, [['--json', 'json'], ['--jsonl', 'jsonl'], ['--csv', 'csv'], ['--csv-with-metadata', 'csv_metadata'], ['--domains', 'domains']]),
|
|
188
|
+
...discoveryValues(parsed),
|
|
189
|
+
deep,
|
|
190
|
+
scanLimit: parsed.integerOption('--scan-limit') ?? Math.min(100, deep ? 50 : 500),
|
|
191
|
+
chunkSize: parsed.integerOption('--chunk-size') ?? 25,
|
|
192
|
+
concurrency: parsed.integerOption('--concurrency') ?? (deep ? 2 : 4),
|
|
193
|
+
checkpoint: parsed.optionValue('--checkpoint'),
|
|
194
|
+
resume: parsed.hasOption('--resume'),
|
|
195
|
+
resolverText: parsed.optionValue('--resolver'),
|
|
196
|
+
observationSnapshot: parsed.optionValue('--observation-snapshot'),
|
|
197
|
+
allowlistSource: parsed.optionValue('--allowlist'),
|
|
198
|
+
filter,
|
|
199
|
+
events: parsed.hasOption('--events'),
|
|
200
|
+
plan: parsed.hasOption('--plan'),
|
|
201
|
+
...terminalOptions(parsed),
|
|
202
|
+
...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
|
|
203
|
+
};
|
|
983
204
|
}
|
|
984
|
-
function
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
if (selected !== null)
|
|
998
|
-
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
999
|
-
const value = argv[++index];
|
|
1000
|
-
if (!value || value.startsWith('-'))
|
|
1001
|
-
throw new CliUsageError(`${argument} requires one bounded value.`);
|
|
1002
|
-
if (argument === '--resolver')
|
|
1003
|
-
resolver = value;
|
|
1004
|
-
else
|
|
1005
|
-
trustAnchorSource = value;
|
|
1006
|
-
}
|
|
1007
|
-
else if (argument === '--owned-or-authorized') {
|
|
1008
|
-
if (ownedOrAuthorized)
|
|
1009
|
-
throw new CliUsageError('--owned-or-authorized may be supplied only once.');
|
|
1010
|
-
ownedOrAuthorized = true;
|
|
1011
|
-
}
|
|
1012
|
-
else if (argument === '--active-probe') {
|
|
1013
|
-
if (activeProbeAcknowledged)
|
|
1014
|
-
throw new CliUsageError('--active-probe may be supplied only once.');
|
|
1015
|
-
activeProbeAcknowledged = true;
|
|
1016
|
-
}
|
|
1017
|
-
else if (argument === '--json') {
|
|
1018
|
-
if (output !== 'terminal')
|
|
1019
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1020
|
-
output = 'json';
|
|
1021
|
-
}
|
|
1022
|
-
else if (argument === '--quiet')
|
|
1023
|
-
quiet = true;
|
|
1024
|
-
else if (argument === '--no-color')
|
|
1025
|
-
color = false;
|
|
1026
|
-
else if (argument?.startsWith('-'))
|
|
1027
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1028
|
-
else if (argument && source === null)
|
|
1029
|
-
source = argument;
|
|
1030
|
-
else
|
|
1031
|
-
throw new CliUsageError('mail-transport accepts one optional versioned JSON input file.');
|
|
1032
|
-
}
|
|
1033
|
-
if (!resolver)
|
|
1034
|
-
throw new CliUsageError('mail-transport requires --resolver <public-IP>.');
|
|
1035
|
-
if (!trustAnchorSource)
|
|
1036
|
-
throw new CliUsageError('mail-transport requires --trust-anchor <anchor.json>.');
|
|
1037
|
-
if (!ownedOrAuthorized)
|
|
1038
|
-
throw new CliUsageError('mail-transport requires --owned-or-authorized for every run.');
|
|
1039
|
-
if (!activeProbeAcknowledged)
|
|
1040
|
-
throw new CliUsageError('mail-transport requires --active-probe for every run.');
|
|
1041
|
-
if (quiet && output !== 'terminal')
|
|
1042
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1043
|
-
return { action: 'mail-transport', source, resolver, trustAnchorSource, ownedOrAuthorized: true, activeProbeAcknowledged: true, output, quiet, color };
|
|
205
|
+
function parsePostureArguments(parsed) {
|
|
206
|
+
const mailProfile = parsed.optionValue('--mail-profile') ?? 'standard';
|
|
207
|
+
return {
|
|
208
|
+
action: 'posture',
|
|
209
|
+
domain: parsed.positionalValue('domain'),
|
|
210
|
+
output: parseOutput(parsed, [['--json', 'json'], ['--sarif', 'sarif']]),
|
|
211
|
+
selectorText: parsed.optionValue('--selectors'),
|
|
212
|
+
retiredSelectorText: parsed.optionValue('--retired-selectors'),
|
|
213
|
+
mailProfile: mailProfile === 'defensive-no-mail' ? 'defensive_no_mail' : mailProfile,
|
|
214
|
+
ownedDomain: parsed.hasOption('--owned-domain'),
|
|
215
|
+
...(parsed.hasOption('--include-inherited-dns') ? { includeInheritedDns: true } : {}),
|
|
216
|
+
...terminalOptions(parsed),
|
|
217
|
+
};
|
|
1044
218
|
}
|
|
1045
|
-
function
|
|
1046
|
-
|
|
1047
|
-
|
|
219
|
+
function parseDnssecValidateArguments(parsed) {
|
|
220
|
+
return {
|
|
221
|
+
action: 'dnssec-validate',
|
|
222
|
+
target: parsed.positionalValue('domain'),
|
|
223
|
+
resolver: parsed.optionValue('--resolver'),
|
|
224
|
+
trustAnchorSource: parsed.optionValue('--trust-anchor'),
|
|
225
|
+
ownedOrAuthorized: true,
|
|
226
|
+
output: jsonOutput(parsed),
|
|
227
|
+
...terminalOptions(parsed),
|
|
228
|
+
};
|
|
1048
229
|
}
|
|
1049
|
-
function
|
|
1050
|
-
|
|
1051
|
-
|
|
230
|
+
function parseMailTransportArguments(parsed) {
|
|
231
|
+
return {
|
|
232
|
+
action: 'mail-transport',
|
|
233
|
+
source: parsed.positionalValue('source'),
|
|
234
|
+
resolver: parsed.optionValue('--resolver'),
|
|
235
|
+
trustAnchorSource: parsed.optionValue('--trust-anchor'),
|
|
236
|
+
ownedOrAuthorized: true,
|
|
237
|
+
activeProbeAcknowledged: true,
|
|
238
|
+
output: jsonOutput(parsed),
|
|
239
|
+
...terminalOptions(parsed),
|
|
240
|
+
};
|
|
1052
241
|
}
|
|
1053
|
-
function parseTwoFileComparisonArguments(
|
|
1054
|
-
const sources =
|
|
1055
|
-
let output = 'terminal';
|
|
1056
|
-
let quiet = false;
|
|
1057
|
-
let color = true;
|
|
1058
|
-
for (const argument of argv) {
|
|
1059
|
-
if (argument === '--json') {
|
|
1060
|
-
if (output !== 'terminal')
|
|
1061
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1062
|
-
output = 'json';
|
|
1063
|
-
}
|
|
1064
|
-
else if (argument === '--quiet')
|
|
1065
|
-
quiet = true;
|
|
1066
|
-
else if (argument === '--no-color')
|
|
1067
|
-
color = false;
|
|
1068
|
-
else if (argument.startsWith('-'))
|
|
1069
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1070
|
-
else
|
|
1071
|
-
sources.push(argument);
|
|
1072
|
-
}
|
|
1073
|
-
if (sources.length !== 2 || !sources[0] || !sources[1])
|
|
1074
|
-
throw new CliUsageError(`${command} requires two input JSON files.`);
|
|
242
|
+
function parseTwoFileComparisonArguments(parsed, command) {
|
|
243
|
+
const sources = parsed.positionalValues('sources');
|
|
1075
244
|
if (sources[0] === sources[1])
|
|
1076
245
|
throw new CliUsageError(`${command} requires two different input files.`);
|
|
1077
|
-
|
|
1078
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1079
|
-
return { leftSource: sources[0], rightSource: sources[1], output, quiet, color };
|
|
1080
|
-
}
|
|
1081
|
-
function parseMailReviewArguments(argv) {
|
|
1082
|
-
const parsed = parseSingleJsonInput(argv, 'mail-review accepts one optional Bulk JSON or JSONL input file.');
|
|
1083
|
-
return { action: 'mail-review', ...parsed };
|
|
1084
|
-
}
|
|
1085
|
-
function parseReviewEvidenceArguments(argv) {
|
|
1086
|
-
let source = null;
|
|
1087
|
-
let mmdbSource = null;
|
|
1088
|
-
let output = 'terminal';
|
|
1089
|
-
let strictExit = false;
|
|
1090
|
-
let quiet = false;
|
|
1091
|
-
let color = true;
|
|
1092
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
1093
|
-
const argument = argv[index];
|
|
1094
|
-
if (argument === undefined)
|
|
1095
|
-
break;
|
|
1096
|
-
if (argument === '--json') {
|
|
1097
|
-
if (output !== 'terminal')
|
|
1098
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1099
|
-
output = 'json';
|
|
1100
|
-
}
|
|
1101
|
-
else if (argument === '--mmdb') {
|
|
1102
|
-
const value = argv[++index];
|
|
1103
|
-
if (!value || value.startsWith('-') || mmdbSource !== null)
|
|
1104
|
-
throw new CliUsageError('--mmdb requires one database file and may be supplied only once.');
|
|
1105
|
-
mmdbSource = value;
|
|
1106
|
-
}
|
|
1107
|
-
else if (argument === '--strict-exit') {
|
|
1108
|
-
if (strictExit)
|
|
1109
|
-
throw new CliUsageError('--strict-exit may be supplied only once.');
|
|
1110
|
-
strictExit = true;
|
|
1111
|
-
}
|
|
1112
|
-
else if (argument === '--quiet')
|
|
1113
|
-
quiet = true;
|
|
1114
|
-
else if (argument === '--no-color')
|
|
1115
|
-
color = false;
|
|
1116
|
-
else if (argument.startsWith('-'))
|
|
1117
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1118
|
-
else if (source === null)
|
|
1119
|
-
source = argument;
|
|
1120
|
-
else
|
|
1121
|
-
throw new CliUsageError('review-evidence accepts one optional versioned JSON input file.');
|
|
1122
|
-
}
|
|
1123
|
-
if (quiet && output !== 'terminal')
|
|
1124
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1125
|
-
return { action: 'review-evidence', source, mmdbSource, output, strictExit, quiet, color };
|
|
1126
|
-
}
|
|
1127
|
-
function parseBriefArguments(argv) {
|
|
1128
|
-
const parsed = parseSingleJsonInput(argv, 'brief accepts one optional saved Lookup file.');
|
|
1129
|
-
return { action: 'brief', ...parsed };
|
|
246
|
+
return { leftSource: sources[0], rightSource: sources[1], output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
1130
247
|
}
|
|
1131
|
-
function
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
else if (argument === '--audience') {
|
|
1148
|
-
if (audience !== null)
|
|
1149
|
-
throw new CliUsageError('--audience may be supplied only once.');
|
|
1150
|
-
const value = argv[++index];
|
|
1151
|
-
if (!['internal', 'trusted', 'public'].includes(value || ''))
|
|
1152
|
-
throw new CliUsageError('--audience requires internal, trusted, or public.');
|
|
1153
|
-
audience = value;
|
|
1154
|
-
}
|
|
1155
|
-
else if (argument === '--reviewed') {
|
|
1156
|
-
if (reviewed)
|
|
1157
|
-
throw new CliUsageError('--reviewed may be supplied only once.');
|
|
1158
|
-
reviewed = true;
|
|
1159
|
-
}
|
|
1160
|
-
else if (argument === '--quiet')
|
|
1161
|
-
quiet = true;
|
|
1162
|
-
else if (argument === '--no-color')
|
|
1163
|
-
color = false;
|
|
1164
|
-
else if (argument.startsWith('-'))
|
|
1165
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1166
|
-
else if (source === null)
|
|
1167
|
-
source = argument;
|
|
1168
|
-
else
|
|
1169
|
-
throw new CliUsageError('case-pack accepts one optional case-export file.');
|
|
1170
|
-
}
|
|
1171
|
-
if (audience === null)
|
|
1172
|
-
throw new CliUsageError('case-pack requires --audience internal, trusted, or public.');
|
|
1173
|
-
if (!reviewed)
|
|
1174
|
-
throw new CliUsageError('case-pack requires --reviewed to confirm deliberate analyst review.');
|
|
1175
|
-
if (quiet && output !== 'terminal')
|
|
1176
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1177
|
-
return { action: 'case-pack', source, output, audience, reviewed: true, quiet, color };
|
|
1178
|
-
}
|
|
1179
|
-
function parseMonitorOnceArguments(argv) {
|
|
1180
|
-
let source = null;
|
|
1181
|
-
let previousSource = null;
|
|
1182
|
-
let output = 'terminal';
|
|
1183
|
-
let limit = 20;
|
|
1184
|
-
let concurrency = 2;
|
|
1185
|
-
let limitSupplied = false;
|
|
1186
|
-
let concurrencySupplied = false;
|
|
1187
|
-
let failOn = null;
|
|
1188
|
-
let quiet = false;
|
|
1189
|
-
let color = true;
|
|
1190
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
1191
|
-
const argument = argv[index];
|
|
1192
|
-
if (argument === undefined)
|
|
1193
|
-
break;
|
|
1194
|
-
if (argument === '--json') {
|
|
1195
|
-
if (output !== 'terminal')
|
|
1196
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1197
|
-
output = 'json';
|
|
1198
|
-
}
|
|
1199
|
-
else if (argument === '--junit') {
|
|
1200
|
-
if (output !== 'terminal')
|
|
1201
|
-
throw new CliUsageError('Choose only one output format.');
|
|
1202
|
-
output = 'junit';
|
|
1203
|
-
}
|
|
1204
|
-
else if (argument === '--previous') {
|
|
1205
|
-
if (previousSource !== null)
|
|
1206
|
-
throw new CliUsageError('--previous may be supplied only once.');
|
|
1207
|
-
const value = argv[++index];
|
|
1208
|
-
if (!value || value.startsWith('-'))
|
|
1209
|
-
throw new CliUsageError('--previous requires one prior monitor snapshot file.');
|
|
1210
|
-
previousSource = value;
|
|
1211
|
-
}
|
|
1212
|
-
else if (argument === '--limit' || argument === '--concurrency') {
|
|
1213
|
-
if (argument === '--limit' ? limitSupplied : concurrencySupplied) {
|
|
1214
|
-
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
1215
|
-
}
|
|
1216
|
-
const value = Number(argv[++index]);
|
|
1217
|
-
const maximum = argument === '--limit' ? 20 : 3;
|
|
1218
|
-
if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
1219
|
-
throw new CliUsageError(`${argument} requires an integer from 1 to ${maximum}.`);
|
|
1220
|
-
if (argument === '--limit') {
|
|
1221
|
-
limit = value;
|
|
1222
|
-
limitSupplied = true;
|
|
1223
|
-
}
|
|
1224
|
-
else {
|
|
1225
|
-
concurrency = value;
|
|
1226
|
-
concurrencySupplied = true;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
else if (argument === '--fail-on') {
|
|
1230
|
-
if (failOn !== null)
|
|
1231
|
-
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
1232
|
-
failOn = parseCliFailPolicies(argv[++index], 'monitor-once');
|
|
1233
|
-
}
|
|
1234
|
-
else if (argument === '--quiet')
|
|
1235
|
-
quiet = true;
|
|
1236
|
-
else if (argument === '--no-color')
|
|
1237
|
-
color = false;
|
|
1238
|
-
else if (argument.startsWith('-'))
|
|
1239
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1240
|
-
else if (source === null)
|
|
1241
|
-
source = argument;
|
|
1242
|
-
else
|
|
1243
|
-
throw new CliUsageError('monitor-once accepts one optional domain-control manifest file.');
|
|
1244
|
-
}
|
|
1245
|
-
if (quiet && output !== 'terminal')
|
|
1246
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1247
|
-
return { action: 'monitor-once', source, previousSource, output, limit, concurrency, quiet, color, ...(failOn ? { failOn } : {}) };
|
|
1248
|
-
}
|
|
1249
|
-
function parseDomainControlArguments(argv) {
|
|
1250
|
-
const parsed = parseSingleJsonInput(argv, 'domain-control accepts one optional versioned JSON input file.');
|
|
1251
|
-
return { action: 'domain-control', ...parsed };
|
|
1252
|
-
}
|
|
1253
|
-
function parseAssuranceArguments(argv) {
|
|
1254
|
-
const parsed = parseSingleJsonInput(argv, 'assurance accepts one optional versioned JSON input file.');
|
|
1255
|
-
return { action: 'assurance', ...parsed };
|
|
248
|
+
function parseVerifyArtifactArguments(parsed) {
|
|
249
|
+
if (parsed.hasOption('--folder') && parsed.positionalValue('source'))
|
|
250
|
+
throw new CliUsageError('--folder selects its own input; do not also supply a file or stdin marker.');
|
|
251
|
+
return {
|
|
252
|
+
action: 'verify-artifact',
|
|
253
|
+
source: parsed.positionalValue('source'),
|
|
254
|
+
passphraseSource: parsed.optionValue('--passphrase-file'),
|
|
255
|
+
manifestSource: parsed.optionValue('--manifest'),
|
|
256
|
+
manifestEntryId: parsed.optionValue('--manifest-entry'),
|
|
257
|
+
...(parsed.hasOption('--package') ? { package: true } : {}),
|
|
258
|
+
...(parsed.hasOption('--bagit') ? { bagit: true } : {}),
|
|
259
|
+
...(parsed.optionValue('--folder') ? { folder: parsed.optionValue('--folder') } : {}),
|
|
260
|
+
output: jsonOutput(parsed),
|
|
261
|
+
strictExit: parsed.hasOption('--strict-exit'),
|
|
262
|
+
...terminalOptions(parsed),
|
|
263
|
+
};
|
|
1256
264
|
}
|
|
1257
|
-
function
|
|
1258
|
-
const
|
|
1259
|
-
return {
|
|
265
|
+
function parseMonitorOnceArguments(parsed) {
|
|
266
|
+
const selectedFailPolicies = failPolicies(parsed, 'monitor-once');
|
|
267
|
+
return {
|
|
268
|
+
action: 'monitor-once',
|
|
269
|
+
source: parsed.positionalValue('source'),
|
|
270
|
+
previousSource: parsed.optionValue('--previous'),
|
|
271
|
+
output: parseOutput(parsed, [['--json', 'json'], ['--junit', 'junit']]),
|
|
272
|
+
limit: parsed.integerOption('--limit') ?? 20,
|
|
273
|
+
concurrency: parsed.integerOption('--concurrency') ?? 2,
|
|
274
|
+
...terminalOptions(parsed),
|
|
275
|
+
...(selectedFailPolicies ? { failOn: selectedFailPolicies } : {}),
|
|
276
|
+
};
|
|
1260
277
|
}
|
|
1261
|
-
function parseSharingReviewArguments(
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
let recipientScope = null;
|
|
1266
|
-
let purpose = null;
|
|
1267
|
-
let humanReviewed = false;
|
|
1268
|
-
let personalDataReviewed = false;
|
|
1269
|
-
let redactionsConfirmed = false;
|
|
1270
|
-
let quiet = false;
|
|
1271
|
-
let color = true;
|
|
1272
|
-
for (let index = 0; index < argv.length; index++) {
|
|
1273
|
-
const argument = argv[index];
|
|
1274
|
-
if (argument === undefined)
|
|
1275
|
-
break;
|
|
1276
|
-
if (argument === '--json') {
|
|
1277
|
-
if (output !== 'terminal')
|
|
1278
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1279
|
-
output = 'json';
|
|
1280
|
-
}
|
|
1281
|
-
else if (argument === '--marking') {
|
|
1282
|
-
if (marking !== null)
|
|
1283
|
-
throw new CliUsageError('--marking may be supplied only once.');
|
|
1284
|
-
const value = argv[++index];
|
|
1285
|
-
if (!['clear', 'green', 'amber', 'amber-strict', 'red'].includes(value || '')) {
|
|
1286
|
-
throw new CliUsageError('--marking requires clear, green, amber, amber-strict, or red.');
|
|
1287
|
-
}
|
|
1288
|
-
marking = value;
|
|
1289
|
-
}
|
|
1290
|
-
else if (argument === '--recipient-scope') {
|
|
1291
|
-
if (recipientScope !== null)
|
|
1292
|
-
throw new CliUsageError('--recipient-scope may be supplied only once.');
|
|
1293
|
-
const value = argv[++index];
|
|
1294
|
-
if (!['public', 'community', 'organization', 'named-recipients'].includes(value || '')) {
|
|
1295
|
-
throw new CliUsageError('--recipient-scope requires public, community, organization, or named-recipients.');
|
|
1296
|
-
}
|
|
1297
|
-
recipientScope = value;
|
|
1298
|
-
}
|
|
1299
|
-
else if (argument === '--purpose') {
|
|
1300
|
-
if (purpose !== null)
|
|
1301
|
-
throw new CliUsageError('--purpose may be supplied only once.');
|
|
1302
|
-
const value = argv[++index];
|
|
1303
|
-
if (!value || value.startsWith('-'))
|
|
1304
|
-
throw new CliUsageError('--purpose requires one bounded description.');
|
|
1305
|
-
const normalized = value.replace(/\s+/gu, ' ').trim();
|
|
1306
|
-
if (!normalized || normalized.length > 200)
|
|
1307
|
-
throw new CliUsageError('--purpose is limited to 200 characters.');
|
|
1308
|
-
purpose = normalized;
|
|
1309
|
-
}
|
|
1310
|
-
else if (argument === '--human-reviewed')
|
|
1311
|
-
humanReviewed = true;
|
|
1312
|
-
else if (argument === '--personal-data-reviewed')
|
|
1313
|
-
personalDataReviewed = true;
|
|
1314
|
-
else if (argument === '--redactions-confirmed')
|
|
1315
|
-
redactionsConfirmed = true;
|
|
1316
|
-
else if (argument === '--quiet')
|
|
1317
|
-
quiet = true;
|
|
1318
|
-
else if (argument === '--no-color')
|
|
1319
|
-
color = false;
|
|
1320
|
-
else if (argument.startsWith('-'))
|
|
1321
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1322
|
-
else if (source === null)
|
|
1323
|
-
source = argument;
|
|
1324
|
-
else
|
|
1325
|
-
throw new CliUsageError('sharing-review accepts one optional artefact JSON file.');
|
|
1326
|
-
}
|
|
1327
|
-
if (!marking || !recipientScope || !purpose) {
|
|
1328
|
-
throw new CliUsageError('sharing-review requires --marking, --recipient-scope, and --purpose.');
|
|
1329
|
-
}
|
|
1330
|
-
if (quiet && output !== 'terminal')
|
|
1331
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
278
|
+
function parseSharingReviewArguments(parsed) {
|
|
279
|
+
const purpose = parsed.optionValue('--purpose').replace(/\s+/gu, ' ').trim();
|
|
280
|
+
if (!purpose || purpose.length > 200)
|
|
281
|
+
throw new CliUsageError('--purpose is limited to 200 characters.');
|
|
1332
282
|
return {
|
|
1333
|
-
action: 'sharing-review',
|
|
1334
|
-
|
|
283
|
+
action: 'sharing-review',
|
|
284
|
+
source: parsed.positionalValue('source'),
|
|
285
|
+
output: jsonOutput(parsed),
|
|
286
|
+
marking: parsed.optionValue('--marking'),
|
|
287
|
+
recipientScope: parsed.optionValue('--recipient-scope'),
|
|
288
|
+
purpose,
|
|
289
|
+
humanReviewed: parsed.hasOption('--human-reviewed'),
|
|
290
|
+
personalDataReviewed: parsed.hasOption('--personal-data-reviewed'),
|
|
291
|
+
redactionsConfirmed: parsed.hasOption('--redactions-confirmed'),
|
|
292
|
+
...terminalOptions(parsed),
|
|
1335
293
|
};
|
|
1336
294
|
}
|
|
1337
|
-
function parseWorkflowPlanArguments(
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
let color = true;
|
|
1344
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
1345
|
-
const argument = argv[index];
|
|
1346
|
-
if (argument === '--json') {
|
|
1347
|
-
if (output !== 'terminal')
|
|
1348
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1349
|
-
output = 'json';
|
|
1350
|
-
}
|
|
1351
|
-
else if (argument === '--list') {
|
|
1352
|
-
if (discovery !== null)
|
|
1353
|
-
throw new CliUsageError('--list and --explain are mutually exclusive and may be supplied only once.');
|
|
1354
|
-
discovery = 'list';
|
|
1355
|
-
}
|
|
1356
|
-
else if (argument === '--explain') {
|
|
1357
|
-
if (discovery !== null)
|
|
1358
|
-
throw new CliUsageError('--list and --explain are mutually exclusive and may be supplied only once.');
|
|
1359
|
-
const value = argv[++index];
|
|
1360
|
-
if (!INVESTIGATION_PLAN_RECIPES.includes(value)) {
|
|
1361
|
-
throw new CliUsageError(`--explain recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
1362
|
-
}
|
|
1363
|
-
discovery = 'explain';
|
|
1364
|
-
explainedRecipe = value;
|
|
1365
|
-
}
|
|
1366
|
-
else if (argument === '--quiet')
|
|
1367
|
-
quiet = true;
|
|
1368
|
-
else if (argument === '--no-color')
|
|
1369
|
-
color = false;
|
|
1370
|
-
else if (argument.startsWith('-'))
|
|
1371
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1372
|
-
else
|
|
1373
|
-
positional.push(argument);
|
|
295
|
+
function parseWorkflowPlanArguments(parsed) {
|
|
296
|
+
const options = { output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
297
|
+
if (parsed.hasOption('--list')) {
|
|
298
|
+
if (parsed.allPositionals.length > 0)
|
|
299
|
+
throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
|
|
300
|
+
return { action: 'workflow-plan', discovery: 'list', ...options };
|
|
1374
301
|
}
|
|
1375
|
-
if (
|
|
1376
|
-
|
|
1377
|
-
if (discovery !== null) {
|
|
1378
|
-
if (positional.length !== 0)
|
|
302
|
+
if (parsed.hasOption('--explain')) {
|
|
303
|
+
if (parsed.allPositionals.length > 0)
|
|
1379
304
|
throw new CliUsageError('workflow-plan discovery options do not accept a subject.');
|
|
1380
|
-
|
|
1381
|
-
return { action: 'workflow-plan', discovery, output, quiet, color };
|
|
1382
|
-
return { action: 'workflow-plan', discovery, recipe: explainedRecipe, output, quiet, color };
|
|
305
|
+
return { action: 'workflow-plan', discovery: 'explain', recipe: parsed.optionValue('--explain'), ...options };
|
|
1383
306
|
}
|
|
1384
|
-
if (
|
|
307
|
+
if (parsed.allPositionals.length !== 2) {
|
|
1385
308
|
throw new CliUsageError('workflow-plan requires one fixed recipe and one subject, --list, or --explain <recipe>.');
|
|
1386
|
-
const recipe = positional[0];
|
|
1387
|
-
if (!INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
|
|
1388
|
-
throw new CliUsageError(`workflow-plan recipe must be one of: ${INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
1389
|
-
}
|
|
1390
|
-
return { action: 'workflow-plan', recipe: recipe, subject: positional[1], output, quiet, color };
|
|
1391
|
-
}
|
|
1392
|
-
function parseWorkflowRunArguments(argv) {
|
|
1393
|
-
const positional = [];
|
|
1394
|
-
const selections = [];
|
|
1395
|
-
let resumeSource = null;
|
|
1396
|
-
let approveNetwork = false;
|
|
1397
|
-
let output = 'terminal';
|
|
1398
|
-
let quiet = false;
|
|
1399
|
-
let color = true;
|
|
1400
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
1401
|
-
const argument = argv[index];
|
|
1402
|
-
if (argument === undefined)
|
|
1403
|
-
break;
|
|
1404
|
-
if (argument === '--json') {
|
|
1405
|
-
if (output !== 'terminal')
|
|
1406
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1407
|
-
output = 'json';
|
|
1408
|
-
}
|
|
1409
|
-
else if (argument === '--select') {
|
|
1410
|
-
const value = argv[++index];
|
|
1411
|
-
if (!value)
|
|
1412
|
-
throw new CliUsageError('--select requires <step-id>=<path-or-value>.');
|
|
1413
|
-
const separator = value.indexOf('=');
|
|
1414
|
-
const stepId = separator === -1 ? '' : value.slice(0, separator);
|
|
1415
|
-
const selection = separator === -1 ? '' : value.slice(separator + 1);
|
|
1416
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(stepId) || !selection.trim()) {
|
|
1417
|
-
throw new CliUsageError('--select requires <step-id>=<path-or-value>.');
|
|
1418
|
-
}
|
|
1419
|
-
if (selection.startsWith('-')) {
|
|
1420
|
-
throw new CliUsageError('--select values cannot start with a hyphen; prefix a file path with ./ when needed.');
|
|
1421
|
-
}
|
|
1422
|
-
selections.push(Object.freeze({ stepId, value: selection }));
|
|
1423
|
-
}
|
|
1424
|
-
else if (argument === '--resume') {
|
|
1425
|
-
if (resumeSource !== null)
|
|
1426
|
-
throw new CliUsageError('--resume may be supplied only once.');
|
|
1427
|
-
const value = argv[++index];
|
|
1428
|
-
if (!value || value.startsWith('-'))
|
|
1429
|
-
throw new CliUsageError('--resume requires one prior workflow-run state file.');
|
|
1430
|
-
resumeSource = value;
|
|
1431
|
-
}
|
|
1432
|
-
else if (argument === '--approve-network') {
|
|
1433
|
-
if (approveNetwork)
|
|
1434
|
-
throw new CliUsageError('--approve-network may be supplied only once.');
|
|
1435
|
-
approveNetwork = true;
|
|
1436
|
-
}
|
|
1437
|
-
else if (argument === '--quiet')
|
|
1438
|
-
quiet = true;
|
|
1439
|
-
else if (argument === '--no-color')
|
|
1440
|
-
color = false;
|
|
1441
|
-
else if (argument.startsWith('-'))
|
|
1442
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1443
|
-
else
|
|
1444
|
-
positional.push(argument);
|
|
1445
|
-
}
|
|
1446
|
-
if (positional.length !== 2)
|
|
1447
|
-
throw new CliUsageError('workflow-run requires one fixed recipe and one subject.');
|
|
1448
|
-
const recipe = positional[0];
|
|
1449
|
-
if (!RUNNABLE_INVESTIGATION_PLAN_RECIPES.includes(recipe)) {
|
|
1450
|
-
throw new CliUsageError(`workflow-run recipe must be one of: ${RUNNABLE_INVESTIGATION_PLAN_RECIPES.join(', ')}.`);
|
|
1451
|
-
}
|
|
1452
|
-
if (quiet && output !== 'terminal')
|
|
1453
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1454
|
-
return { action: 'workflow-run', recipe: recipe, subject: positional[1], resumeSource, selections: Object.freeze(selections), approveNetwork, output, quiet, color };
|
|
1455
|
-
}
|
|
1456
|
-
function parseDiffArguments(argv) {
|
|
1457
|
-
let leftSource = null;
|
|
1458
|
-
let rightSource = null;
|
|
1459
|
-
let leftSessionId = null;
|
|
1460
|
-
let rightSessionId = null;
|
|
1461
|
-
let output = 'terminal';
|
|
1462
|
-
let quiet = false;
|
|
1463
|
-
let color = true;
|
|
1464
|
-
for (let index = 0; index < argv.length; index++) {
|
|
1465
|
-
const argument = argv[index];
|
|
1466
|
-
if (argument === undefined)
|
|
1467
|
-
break;
|
|
1468
|
-
if (argument === '--json') {
|
|
1469
|
-
if (output !== 'terminal')
|
|
1470
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1471
|
-
output = 'json';
|
|
1472
|
-
}
|
|
1473
|
-
else if (argument === '--left-session' || argument === '--right-session') {
|
|
1474
|
-
const current = argument === '--left-session' ? leftSessionId : rightSessionId;
|
|
1475
|
-
if (current !== null)
|
|
1476
|
-
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
1477
|
-
const value = argv[++index];
|
|
1478
|
-
if (!value || !/^[A-Za-z0-9_-]{1,128}$/u.test(value))
|
|
1479
|
-
throw new CliUsageError(`${argument} requires one bounded saved-session ID.`);
|
|
1480
|
-
if (argument === '--left-session')
|
|
1481
|
-
leftSessionId = value;
|
|
1482
|
-
else
|
|
1483
|
-
rightSessionId = value;
|
|
1484
|
-
}
|
|
1485
|
-
else if (argument === '--quiet')
|
|
1486
|
-
quiet = true;
|
|
1487
|
-
else if (argument === '--no-color')
|
|
1488
|
-
color = false;
|
|
1489
|
-
else if (argument.startsWith('-'))
|
|
1490
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1491
|
-
else if (leftSource === null)
|
|
1492
|
-
leftSource = argument;
|
|
1493
|
-
else if (rightSource === null)
|
|
1494
|
-
rightSource = argument;
|
|
1495
|
-
else
|
|
1496
|
-
throw new CliUsageError('diff accepts exactly two compatible retained JSON files.');
|
|
1497
|
-
}
|
|
1498
|
-
if (!leftSource || !rightSource)
|
|
1499
|
-
throw new CliUsageError('diff requires exactly two compatible retained JSON files.');
|
|
1500
|
-
if (leftSource === rightSource)
|
|
1501
|
-
throw new CliUsageError('diff requires two different input files.');
|
|
1502
|
-
if (quiet && output !== 'terminal')
|
|
1503
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1504
|
-
return { action: 'diff', leftSource, rightSource, leftSessionId, rightSessionId, output, quiet, color };
|
|
1505
|
-
}
|
|
1506
|
-
function parseReconcileArguments(argv) {
|
|
1507
|
-
const sources = [];
|
|
1508
|
-
let output = 'terminal';
|
|
1509
|
-
let quiet = false;
|
|
1510
|
-
let color = true;
|
|
1511
|
-
for (const argument of argv) {
|
|
1512
|
-
if (argument === '--json') {
|
|
1513
|
-
if (output !== 'terminal')
|
|
1514
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1515
|
-
output = 'json';
|
|
1516
|
-
}
|
|
1517
|
-
else if (argument === '--quiet')
|
|
1518
|
-
quiet = true;
|
|
1519
|
-
else if (argument === '--no-color')
|
|
1520
|
-
color = false;
|
|
1521
|
-
else if (argument.startsWith('-'))
|
|
1522
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1523
|
-
else
|
|
1524
|
-
sources.push(argument);
|
|
1525
|
-
}
|
|
1526
|
-
if (sources.length < 2 || sources.length > 5) {
|
|
1527
|
-
throw new CliUsageError('reconcile requires from 2 to 5 saved lookup JSON files.');
|
|
1528
|
-
}
|
|
1529
|
-
if (new Set(sources).size !== sources.length) {
|
|
1530
|
-
throw new CliUsageError('reconcile input files must be different.');
|
|
1531
309
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
let output = 'terminal';
|
|
1539
|
-
let quiet = false;
|
|
1540
|
-
let color = true;
|
|
1541
|
-
for (const argument of argv) {
|
|
1542
|
-
if (argument === '--json') {
|
|
1543
|
-
if (output !== 'terminal')
|
|
1544
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1545
|
-
output = 'json';
|
|
1546
|
-
}
|
|
1547
|
-
else if (argument === '--quiet')
|
|
1548
|
-
quiet = true;
|
|
1549
|
-
else if (argument === '--no-color')
|
|
1550
|
-
color = false;
|
|
1551
|
-
else if (argument.startsWith('-'))
|
|
1552
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1553
|
-
else
|
|
1554
|
-
sources.push(argument);
|
|
1555
|
-
}
|
|
1556
|
-
if (sources.length < 2 || sources.length > 20) {
|
|
1557
|
-
throw new CliUsageError('timeline requires from 2 to 20 saved lookup JSON files.');
|
|
1558
|
-
}
|
|
1559
|
-
if (new Set(sources).size !== sources.length) {
|
|
1560
|
-
throw new CliUsageError('timeline input files must be different.');
|
|
1561
|
-
}
|
|
1562
|
-
if (quiet && output !== 'terminal')
|
|
1563
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1564
|
-
return { action: 'timeline', sources, output, quiet, color };
|
|
1565
|
-
}
|
|
1566
|
-
function parseRegistrySupportArguments(argv) {
|
|
1567
|
-
const { source: target, ...options } = parseSingleJsonInput(argv, 'registry-support accepts one domain or suffix.');
|
|
1568
|
-
return { action: 'registry-support', target, ...options };
|
|
1569
|
-
}
|
|
1570
|
-
function parseRegistryDoctorArguments(argv) {
|
|
1571
|
-
const parsed = parseSingleJsonInput(argv, 'registry-doctor accepts one optional saved Lookup JSON file.');
|
|
1572
|
-
return { action: 'registry-doctor', ...parsed };
|
|
1573
|
-
}
|
|
1574
|
-
function parseRegistryCohortArguments(argv) {
|
|
1575
|
-
const parsed = parseSingleJsonInput(argv, 'registry-cohort accepts one optional JSON or JSONL input file.');
|
|
1576
|
-
return { action: 'registry-cohort', ...parsed };
|
|
1577
|
-
}
|
|
1578
|
-
function parseRegistryScaffoldArguments(argv) {
|
|
1579
|
-
let profile = '';
|
|
1580
|
-
let suffix = '';
|
|
1581
|
-
let scenario = '';
|
|
1582
|
-
for (let index = 0; index < argv.length; index += 2) {
|
|
1583
|
-
const option = argv[index];
|
|
1584
|
-
const value = argv[index + 1];
|
|
1585
|
-
if (!value)
|
|
1586
|
-
throw new CliUsageError(`${option || 'Option'} requires a value.`);
|
|
1587
|
-
if (option === '--profile' && !profile)
|
|
1588
|
-
profile = value;
|
|
1589
|
-
else if (option === '--suffix' && !suffix)
|
|
1590
|
-
suffix = value;
|
|
1591
|
-
else if (option === '--scenario' && !scenario)
|
|
1592
|
-
scenario = value;
|
|
1593
|
-
else
|
|
1594
|
-
throw new CliUsageError(`Unknown or repeated option "${option || ''}".`);
|
|
1595
|
-
}
|
|
1596
|
-
if (!profile || !suffix || !['registered', 'not_found', 'inconclusive'].includes(scenario)) {
|
|
1597
|
-
throw new CliUsageError('registry-scaffold requires --profile, --suffix, and --scenario <registered|not_found|inconclusive>.');
|
|
1598
|
-
}
|
|
1599
|
-
return { action: 'registry-scaffold', profile, suffix, scenario: scenario };
|
|
1600
|
-
}
|
|
1601
|
-
function parseRiskCalibrateArguments(argv) {
|
|
1602
|
-
let source = null;
|
|
1603
|
-
let output = 'terminal';
|
|
1604
|
-
let quiet = false;
|
|
1605
|
-
let color = true;
|
|
1606
|
-
for (const argument of argv) {
|
|
1607
|
-
if (argument === '--json' || argument === '--summary-json') {
|
|
1608
|
-
if (output !== 'terminal')
|
|
1609
|
-
throw new CliUsageError('--json and --summary-json are mutually exclusive and may be supplied only once.');
|
|
1610
|
-
output = argument === '--json' ? 'json' : 'summary_json';
|
|
1611
|
-
}
|
|
1612
|
-
else if (argument === '--quiet')
|
|
1613
|
-
quiet = true;
|
|
1614
|
-
else if (argument === '--no-color')
|
|
1615
|
-
color = false;
|
|
1616
|
-
else if (argument.startsWith('-'))
|
|
1617
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1618
|
-
else if (source === null)
|
|
1619
|
-
source = argument;
|
|
1620
|
-
else
|
|
1621
|
-
throw new CliUsageError('risk-calibrate accepts one optional dataset file. Otherwise pipe one dataset on stdin.');
|
|
1622
|
-
}
|
|
1623
|
-
if (quiet && output !== 'terminal')
|
|
1624
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1625
|
-
return { action: 'risk-calibrate', source, output, quiet, color };
|
|
1626
|
-
}
|
|
1627
|
-
function parseLookalikeCalibrateArguments(argv) {
|
|
1628
|
-
const parsed = parseSingleJsonInput(argv, 'lookalike-calibrate accepts one optional reviewed dataset JSON file.');
|
|
1629
|
-
return { action: 'lookalike-calibrate', ...parsed };
|
|
310
|
+
return {
|
|
311
|
+
action: 'workflow-plan',
|
|
312
|
+
recipe: parsed.positionalValue('recipe'),
|
|
313
|
+
subject: parsed.positionalValue('subject'),
|
|
314
|
+
...options,
|
|
315
|
+
};
|
|
1630
316
|
}
|
|
1631
|
-
function
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
const
|
|
1642
|
-
if (
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
throw new CliUsageError('--manifest requires one bounded investigation manifest file.');
|
|
1664
|
-
manifestSource = value;
|
|
1665
|
-
}
|
|
1666
|
-
else if (argument === '--manifest-entry') {
|
|
1667
|
-
if (manifestEntryId !== null)
|
|
1668
|
-
throw new CliUsageError('--manifest-entry may be supplied only once.');
|
|
1669
|
-
const value = argv[++index];
|
|
1670
|
-
if (!value || !/^artifact-(?:[1-9]|1[0-6])$/u.test(value)) {
|
|
1671
|
-
throw new CliUsageError('--manifest-entry requires an ID from artifact-1 through artifact-16.');
|
|
1672
|
-
}
|
|
1673
|
-
manifestEntryId = value;
|
|
1674
|
-
}
|
|
1675
|
-
else if (argument === '--strict-exit') {
|
|
1676
|
-
if (strictExit)
|
|
1677
|
-
throw new CliUsageError('--strict-exit may be supplied only once.');
|
|
1678
|
-
strictExit = true;
|
|
1679
|
-
}
|
|
1680
|
-
else if (argument === '--quiet')
|
|
1681
|
-
quiet = true;
|
|
1682
|
-
else if (argument === '--no-color')
|
|
1683
|
-
color = false;
|
|
1684
|
-
else if (argument.startsWith('-'))
|
|
1685
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1686
|
-
else if (source === null)
|
|
1687
|
-
source = argument;
|
|
1688
|
-
else
|
|
1689
|
-
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1690
|
-
}
|
|
1691
|
-
if ((manifestSource === null) !== (manifestEntryId === null)) {
|
|
1692
|
-
throw new CliUsageError('--manifest and --manifest-entry must be supplied together.');
|
|
1693
|
-
}
|
|
1694
|
-
if (quiet && output !== 'terminal')
|
|
1695
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1696
|
-
return { action: 'verify-artifact', source, passphraseSource, manifestSource, manifestEntryId, output, strictExit, quiet, color };
|
|
317
|
+
function parseWorkflowRunArguments(parsed) {
|
|
318
|
+
const artifactBindings = parsed.optionValues('--use-artifact').map((value) => {
|
|
319
|
+
const match = /^([a-z0-9]+(?:-[a-z0-9]+)*):([1-9][0-9]?)=([a-z0-9]+(?:-[a-z0-9]+)*)$/u.exec(value);
|
|
320
|
+
if (!match)
|
|
321
|
+
throw new CliUsageError('--use-artifact requires <step-id>:<input-number>=<earlier-step-id>.');
|
|
322
|
+
return Object.freeze({ stepId: match[1], input: Number(match[2]), sourceStepId: match[3] });
|
|
323
|
+
});
|
|
324
|
+
const selections = parsed.optionValues('--select').map((value) => {
|
|
325
|
+
const separator = value.indexOf('=');
|
|
326
|
+
const stepId = separator === -1 ? '' : value.slice(0, separator);
|
|
327
|
+
const selection = separator === -1 ? '' : value.slice(separator + 1);
|
|
328
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(stepId) || !selection.trim()) {
|
|
329
|
+
throw new CliUsageError('--select requires <step-id>=<path-or-value>.');
|
|
330
|
+
}
|
|
331
|
+
if (selection.startsWith('-')) {
|
|
332
|
+
throw new CliUsageError('--select values cannot start with a hyphen; prefix a file path with ./ when needed.');
|
|
333
|
+
}
|
|
334
|
+
return Object.freeze({ stepId, value: selection });
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
action: 'workflow-run',
|
|
338
|
+
recipe: parsed.positionalValue('recipe'),
|
|
339
|
+
subject: parsed.positionalValue('subject'),
|
|
340
|
+
resumeSource: parsed.optionValue('--resume'),
|
|
341
|
+
selections: Object.freeze(selections),
|
|
342
|
+
artifactBindings: Object.freeze(artifactBindings),
|
|
343
|
+
confirmedReviews: Object.freeze(parsed.optionValues('--confirm-review')),
|
|
344
|
+
approveNetwork: parsed.hasOption('--approve-network'),
|
|
345
|
+
interactive: parsed.hasOption('--interactive'),
|
|
346
|
+
output: jsonOutput(parsed),
|
|
347
|
+
...terminalOptions(parsed),
|
|
348
|
+
};
|
|
1697
349
|
}
|
|
1698
|
-
function
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
const argument = argv[index];
|
|
1706
|
-
if (argument === undefined)
|
|
1707
|
-
break;
|
|
1708
|
-
if (argument === '--json') {
|
|
1709
|
-
if (output !== 'terminal')
|
|
1710
|
-
throw new CliUsageError('--json may be supplied only once.');
|
|
1711
|
-
output = 'json';
|
|
350
|
+
function parseDiffArguments(parsed) {
|
|
351
|
+
const compared = parseTwoFileComparisonArguments(parsed, 'diff');
|
|
352
|
+
const leftSessionId = parsed.optionValue('--left-session');
|
|
353
|
+
const rightSessionId = parsed.optionValue('--right-session');
|
|
354
|
+
for (const [option, value] of [['--left-session', leftSessionId], ['--right-session', rightSessionId]]) {
|
|
355
|
+
if (value !== null && !/^[A-Za-z0-9_-]{1,128}$/u.test(value)) {
|
|
356
|
+
throw new CliUsageError(`${option} requires one bounded saved-session ID.`);
|
|
1712
357
|
}
|
|
1713
|
-
else if (argument === '--passphrase-file') {
|
|
1714
|
-
if (passphraseSource !== null)
|
|
1715
|
-
throw new CliUsageError('--passphrase-file may be supplied only once.');
|
|
1716
|
-
const value = argv[++index];
|
|
1717
|
-
if (!value || value.startsWith('-'))
|
|
1718
|
-
throw new CliUsageError('--passphrase-file requires one bounded UTF-8 file.');
|
|
1719
|
-
passphraseSource = value;
|
|
1720
|
-
}
|
|
1721
|
-
else if (argument === '--quiet')
|
|
1722
|
-
quiet = true;
|
|
1723
|
-
else if (argument === '--no-color')
|
|
1724
|
-
color = false;
|
|
1725
|
-
else if (argument.startsWith('-'))
|
|
1726
|
-
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1727
|
-
else if (source === null)
|
|
1728
|
-
source = argument;
|
|
1729
|
-
else
|
|
1730
|
-
throw new CliUsageError('interchange-report accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1731
358
|
}
|
|
1732
|
-
|
|
1733
|
-
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1734
|
-
return { action: 'interchange-report', source, passphraseSource, output, quiet, color };
|
|
1735
|
-
}
|
|
1736
|
-
function parseSourceReportArguments(argv) {
|
|
1737
|
-
const parsed = parseSingleJsonInput(argv, 'source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
|
|
1738
|
-
return { action: 'source-report', ...parsed };
|
|
359
|
+
return { action: 'diff', ...compared, leftSessionId, rightSessionId };
|
|
1739
360
|
}
|
|
1740
|
-
function
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
361
|
+
function uniqueSources(parsed, command) {
|
|
362
|
+
const sources = parsed.positionalValues('sources');
|
|
363
|
+
if (new Set(sources).size !== sources.length)
|
|
364
|
+
throw new CliUsageError(`${command} input files must be different.`);
|
|
365
|
+
return sources;
|
|
366
|
+
}
|
|
367
|
+
function parseCaseArguments(parsed) {
|
|
368
|
+
const operation = parsed.positionalValue('operation');
|
|
369
|
+
const source = parsed.positionalValue('source');
|
|
370
|
+
const domain = parsed.optionValue('--domain');
|
|
371
|
+
const caseId = parsed.optionValue('--case-id');
|
|
372
|
+
const title = parsed.optionValue('--title');
|
|
373
|
+
const newIncident = parsed.hasOption('--new-incident');
|
|
374
|
+
const text = parsed.optionValue('--text');
|
|
375
|
+
const noteSource = parsed.optionValue('--note-file');
|
|
376
|
+
const inputSource = parsed.optionValue('--input');
|
|
377
|
+
const expectedFileDigest = parsed.optionValue('--expect-file-digest');
|
|
378
|
+
if (source === '-' || inputSource === '-' || noteSource === '-')
|
|
379
|
+
throw new CliUsageError('Case operations require selected files, not stdin.');
|
|
380
|
+
if (operation !== 'open' && !source)
|
|
381
|
+
throw new CliUsageError(`${operation} requires a Case file.`);
|
|
382
|
+
if (operation !== 'show' && !parsed.hasOption('--output'))
|
|
383
|
+
throw new CliUsageError('Case mutations require --output; nothing was changed.');
|
|
384
|
+
if (operation === 'open' && !domain)
|
|
385
|
+
throw new CliUsageError('case open requires --domain.');
|
|
386
|
+
if (operation !== 'open' && (title || newIncident))
|
|
387
|
+
throw new CliUsageError('--title and --new-incident belong to case open.');
|
|
388
|
+
if (newIncident && (!title || caseId))
|
|
389
|
+
throw new CliUsageError('--new-incident requires a distinguishing --title and cannot select an existing --case-id.');
|
|
390
|
+
if (operation === 'note' ? (text === null && !noteSource) : (text !== null || noteSource !== null)) {
|
|
391
|
+
throw new CliUsageError('Only case note accepts and requires --text or --note-file.');
|
|
392
|
+
}
|
|
393
|
+
if (['pin', 'assess', 'recheck'].includes(operation) ? !inputSource : inputSource !== null) {
|
|
394
|
+
throw new CliUsageError('Only case pin, assess and recheck accept and require --input.');
|
|
395
|
+
}
|
|
396
|
+
if (expectedFileDigest !== null && (!source || !/^sha256:[a-f0-9]{64}$/u.test(expectedFileDigest))) {
|
|
397
|
+
throw new CliUsageError('--expect-file-digest requires a source file and sha256:<64 lowercase hexadecimal characters>.');
|
|
398
|
+
}
|
|
399
|
+
return { action: 'case', operation, source, caseId, domain, title, newIncident, text, noteSource, inputSource, expectedFileDigest, output: jsonOutput(parsed), ...terminalOptions(parsed) };
|
|
1773
400
|
}
|
|
1774
401
|
const CLI_PARSERS = Object.freeze({
|
|
1775
|
-
completion:
|
|
1776
|
-
doctor:
|
|
1777
|
-
commands:
|
|
1778
|
-
|
|
402
|
+
completion: (parsed) => ({ action: 'completion', shell: parsed.positionalValue('shell') }),
|
|
403
|
+
doctor: (parsed) => ({ action: 'doctor', network: parsed.hasOption('--network'), output: jsonOutput(parsed), ...terminalOptions(parsed) }),
|
|
404
|
+
commands: (parsed) => ({
|
|
405
|
+
action: 'commands', output: jsonOutput(parsed), common: parsed.hasOption('--common'),
|
|
406
|
+
group: parsed.optionValue('--group'),
|
|
407
|
+
mode: parsed.optionValue('--mode'),
|
|
408
|
+
...terminalOptions(parsed),
|
|
409
|
+
}),
|
|
410
|
+
manual: () => ({ action: 'manual' }),
|
|
1779
411
|
manifest: parseManifestArguments,
|
|
1780
|
-
'map-observations': (
|
|
1781
|
-
'oam-export': (
|
|
412
|
+
'map-observations': (parsed) => singleInputAction('map-observations', parsed),
|
|
413
|
+
'oam-export': (parsed) => singleInputAction('oam-export', parsed),
|
|
1782
414
|
lookup: parseLookupArguments,
|
|
1783
415
|
bulk: parseBulkArguments,
|
|
1784
|
-
'ct-search':
|
|
1785
|
-
'ct-intake':
|
|
416
|
+
'ct-search': (parsed) => singleInputAction('ct-search', parsed, 'keyword'),
|
|
417
|
+
'ct-intake': (parsed) => singleInputAction('ct-intake', parsed),
|
|
1786
418
|
discover: parseDiscoverArguments,
|
|
1787
419
|
'discover-scan': parseDiscoverScanArguments,
|
|
1788
420
|
posture: parsePostureArguments,
|
|
1789
|
-
http:
|
|
1790
|
-
tls:
|
|
421
|
+
http: (parsed) => singleInputAction('http', parsed, 'domain'),
|
|
422
|
+
tls: (parsed) => singleInputAction('tls', parsed, 'hostname'),
|
|
1791
423
|
'dnssec-validate': parseDnssecValidateArguments,
|
|
1792
424
|
'mail-transport': parseMailTransportArguments,
|
|
1793
|
-
'registry-support':
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
'
|
|
1798
|
-
'
|
|
425
|
+
'registry-support': (parsed) => ({
|
|
426
|
+
action: 'registry-support', target: parsed.positionalValue('domain-or-suffix'),
|
|
427
|
+
output: jsonOutput(parsed), ...terminalOptions(parsed),
|
|
428
|
+
}),
|
|
429
|
+
'registry-doctor': (parsed) => singleInputAction('registry-doctor', parsed),
|
|
430
|
+
'registry-cohort': (parsed) => singleInputAction('registry-cohort', parsed),
|
|
431
|
+
'registry-scaffold': (parsed) => ({
|
|
432
|
+
action: 'registry-scaffold', profile: parsed.optionValue('--profile'), suffix: parsed.optionValue('--suffix'),
|
|
433
|
+
scenario: parsed.optionValue('--scenario'),
|
|
434
|
+
}),
|
|
435
|
+
'risk-calibrate': (parsed) => ({
|
|
436
|
+
action: 'risk-calibrate', source: parsed.positionalValue('source'),
|
|
437
|
+
output: parsed.hasOption('--json') ? 'json' : parsed.hasOption('--summary-json') ? 'summary_json' : 'terminal',
|
|
438
|
+
...terminalOptions(parsed),
|
|
439
|
+
}),
|
|
440
|
+
'lookalike-calibrate': (parsed) => singleInputAction('lookalike-calibrate', parsed),
|
|
1799
441
|
'verify-artifact': parseVerifyArtifactArguments,
|
|
1800
|
-
'interchange-report':
|
|
442
|
+
'interchange-report': (parsed) => ({
|
|
443
|
+
action: 'interchange-report', source: parsed.positionalValue('source'),
|
|
444
|
+
passphraseSource: parsed.optionValue('--passphrase-file'), output: jsonOutput(parsed), ...terminalOptions(parsed),
|
|
445
|
+
}),
|
|
1801
446
|
'inspect-archive': parseInspectArchiveArguments,
|
|
1802
447
|
'sign-artifact': parseSignArtifactArguments,
|
|
1803
448
|
'verify-signature': parseVerifySignatureArguments,
|
|
1804
|
-
'source-report':
|
|
1805
|
-
compare:
|
|
1806
|
-
'page-compare':
|
|
1807
|
-
'mail-review':
|
|
1808
|
-
'
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
449
|
+
'source-report': (parsed) => singleInputAction('source-report', parsed),
|
|
450
|
+
compare: (parsed) => singleInputAction('compare', parsed),
|
|
451
|
+
'page-compare': (parsed) => ({ action: 'page-compare', ...parseTwoFileComparisonArguments(parsed, 'page-compare') }),
|
|
452
|
+
'mail-review': (parsed) => singleInputAction('mail-review', parsed),
|
|
453
|
+
'mail-headers': (parsed) => singleInputAction('mail-headers', parsed),
|
|
454
|
+
'review-evidence': (parsed) => ({
|
|
455
|
+
action: 'review-evidence', source: parsed.positionalValue('source'), mmdbSource: parsed.optionValue('--mmdb'),
|
|
456
|
+
output: jsonOutput(parsed), strictExit: parsed.hasOption('--strict-exit'), ...terminalOptions(parsed),
|
|
457
|
+
}),
|
|
458
|
+
brief: (parsed) => singleInputAction('brief', parsed),
|
|
459
|
+
case: parseCaseArguments,
|
|
460
|
+
'case-pack': (parsed) => ({
|
|
461
|
+
action: 'case-pack', source: parsed.positionalValue('source'), output: jsonOutput(parsed),
|
|
462
|
+
audience: parsed.optionValue('--audience'), reviewed: true, ...terminalOptions(parsed),
|
|
463
|
+
}),
|
|
464
|
+
'domain-control': (parsed) => singleInputAction('domain-control', parsed),
|
|
1812
465
|
'monitor-once': parseMonitorOnceArguments,
|
|
1813
|
-
assurance:
|
|
1814
|
-
'change-packet':
|
|
466
|
+
assurance: (parsed) => singleInputAction('assurance', parsed),
|
|
467
|
+
'change-packet': (parsed) => singleInputAction('change-packet', parsed),
|
|
1815
468
|
'sharing-review': parseSharingReviewArguments,
|
|
1816
469
|
'workflow-plan': parseWorkflowPlanArguments,
|
|
1817
470
|
'workflow-run': parseWorkflowRunArguments,
|
|
1818
471
|
diff: parseDiffArguments,
|
|
1819
|
-
reconcile:
|
|
1820
|
-
timeline:
|
|
1821
|
-
export:
|
|
472
|
+
reconcile: (parsed) => ({ action: 'reconcile', sources: uniqueSources(parsed, 'reconcile'), output: jsonOutput(parsed), ...terminalOptions(parsed) }),
|
|
473
|
+
timeline: (parsed) => ({ action: 'timeline', sources: uniqueSources(parsed, 'timeline'), output: jsonOutput(parsed), ...terminalOptions(parsed) }),
|
|
474
|
+
export: (parsed) => ({
|
|
475
|
+
action: 'export', source: parsed.positionalValue('source'),
|
|
476
|
+
format: parsed.hasOption('--markdown') ? 'markdown' : parsed.hasOption('--html') ? 'html' : 'json',
|
|
477
|
+
compact: parsed.hasOption('--compact'), includeAttribution: !parsed.hasOption('--no-attribution'),
|
|
478
|
+
}),
|
|
1822
479
|
});
|
|
480
|
+
function parseCliArguments(rawArgv) {
|
|
481
|
+
if (!Array.isArray(rawArgv) || rawArgv.length > MAX_CLI_ARGUMENTS) {
|
|
482
|
+
throw new CliUsageError(`At most ${MAX_CLI_ARGUMENTS} command arguments are supported.`);
|
|
483
|
+
}
|
|
484
|
+
return parseCliArgumentsCore(rawArgv.map(boundedArgument));
|
|
485
|
+
}
|
|
486
|
+
function parseCliArgumentsCore(argv) {
|
|
487
|
+
if (!argv.length)
|
|
488
|
+
return { action: 'help' };
|
|
489
|
+
const firstArgument = argv[0] ?? '';
|
|
490
|
+
const metaAction = cliMetaActionForInvocation(argv);
|
|
491
|
+
if (metaAction?.id === 'help') {
|
|
492
|
+
if (argv.length === 1)
|
|
493
|
+
return { action: 'help' };
|
|
494
|
+
if (argv.length === 2 && (isCliCommand(firstArgument) || isDirectLookupTarget(firstArgument))) {
|
|
495
|
+
return { action: 'help', command: isCliCommand(firstArgument) ? firstArgument : 'lookup' };
|
|
496
|
+
}
|
|
497
|
+
throw new CliUsageError('Help accepts only an optional command name.');
|
|
498
|
+
}
|
|
499
|
+
if (metaAction?.id === 'version') {
|
|
500
|
+
if (argv.length !== 1)
|
|
501
|
+
throw new CliUsageError('--version does not accept other arguments.');
|
|
502
|
+
return { action: 'version' };
|
|
503
|
+
}
|
|
504
|
+
let command;
|
|
505
|
+
let directLookup = false;
|
|
506
|
+
if (isCliCommand(firstArgument))
|
|
507
|
+
command = firstArgument;
|
|
508
|
+
else if (isDirectLookupTarget(firstArgument)) {
|
|
509
|
+
command = 'lookup';
|
|
510
|
+
directLookup = true;
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
const displayedCommand = firstArgument.length > 80 ? `${firstArgument.slice(0, 79)}…` : firstArgument;
|
|
514
|
+
throw new CliUsageError(`Unknown command "${displayedCommand}". Run "whoisleuth commands" to list supported commands.`);
|
|
515
|
+
}
|
|
516
|
+
const parsedGrammar = parseCommandArguments(command, directLookup ? argv : argv.slice(1));
|
|
517
|
+
const parsed = CLI_PARSERS[command](parsedGrammar);
|
|
518
|
+
if (parsed.action !== command)
|
|
519
|
+
throw new Error(`CLI parser contract mismatch for ${command}.`);
|
|
520
|
+
const destination = parsedGrammar.optionValue('--output');
|
|
521
|
+
const palette = parsedGrammar.optionValue('--palette');
|
|
522
|
+
return {
|
|
523
|
+
...parsed,
|
|
524
|
+
...(destination !== null ? { destination } : {}),
|
|
525
|
+
...(parsedGrammar.hasOption('--force') ? { force: true } : {}),
|
|
526
|
+
...(palette !== null ? { palette } : {}),
|
|
527
|
+
};
|
|
528
|
+
}
|
|
1823
529
|
export { CLI_COMMANDS, CLI_PARSERS, CliUsageError, MAX_CLI_ARGUMENTS, MAX_CLI_ARGUMENT_LENGTH, parseCliArguments };
|