@slicedearth/whoisleuth-cli 1.43.0 → 1.47.4
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/DISCLOSURE +7 -3
- package/NOTICE +5 -4
- package/README.md +67 -10
- package/bin/whoisleuth.mjs +22 -9
- package/cli/arguments.mjs +256 -9
- package/cli/artifact-structure.mjs +1833 -0
- package/cli/artifact-verify.mjs +242 -49
- package/cli/bounded-json.mjs +1 -0
- package/cli/bulk-checkpoint.mjs +67 -31
- package/cli/bulk-command-runner.mjs +16 -6
- package/cli/bulk.mjs +18 -7
- package/cli/case-pack.mjs +819 -34
- package/cli/command-reference.mjs +48 -24
- package/cli/completion.mjs +50 -11
- package/cli/config-profile.mjs +15 -12
- package/cli/ct-event-intake.mjs +32 -2
- package/cli/discovery-observation-snapshot.mjs +8 -14
- package/cli/discovery-scan-command-runner.mjs +18 -6
- package/cli/discovery-snapshot.mjs +8 -15
- package/cli/discovery-workflow.mjs +2 -3
- package/cli/doctor.mjs +2 -2
- package/cli/domain-control-monitor.mjs +21 -15
- package/cli/domain-control-observations.mjs +25 -2
- package/cli/errors.mjs +7 -1
- package/cli/evidence-command-runner.mjs +17 -11
- package/cli/evidence-signing.mjs +73 -45
- package/cli/export-evidence.mjs +11 -4
- package/cli/formatters/html.mjs +3 -3
- package/cli/formatters/json.mjs +12 -4
- package/cli/formatters/terminal.mjs +4 -1
- package/cli/http.mjs +2 -1
- package/cli/input.mjs +57 -0
- package/cli/interchange-report.mjs +226 -0
- package/cli/investigation-manifest.mjs +16 -17
- package/cli/investigation-run.mjs +71 -23
- package/cli/local-mmdb-review.mjs +45 -14
- package/cli/lookup-command-runner.mjs +2 -2
- package/cli/lookup-diff.mjs +9 -0
- package/cli/lookup-reconcile.mjs +1 -1
- package/cli/mail-review.mjs +5 -5
- package/cli/manual.mjs +1 -1
- package/cli/network-command-runner.mjs +78 -0
- package/cli/offline-evidence-review.mjs +28 -8
- package/cli/page-compare.mjs +8 -5
- package/cli/registry-cohort.mjs +385 -61
- package/cli/registry-fixture-scaffold.mjs +4 -3
- package/cli/registry-support.mjs +2 -1
- package/cli/retained-artifact-diff.mjs +376 -0
- package/cli/risk-calibration.mjs +7 -5
- package/cli/runner.mjs +150 -177
- package/cli/saved-lookup.mjs +26 -3
- package/cli/sharing-review.mjs +12 -6
- package/docs/cli-reference.md +467 -74
- package/docs/cli.md +25 -5
- package/frontend/src/lib/analysis/acquisition-decision-packet.js +4 -4
- package/frontend/src/lib/analysis/acquisition-due-diligence.js +1 -1
- package/frontend/src/lib/analysis/artifact-integrity.js +42 -0
- package/frontend/src/lib/analysis/brand-profile-model.js +33 -7
- package/frontend/src/lib/analysis/bulk-domain-comparison.js +25 -5
- package/frontend/src/lib/analysis/bulk-mail-exposure.js +66 -15
- package/frontend/src/lib/analysis/bulk-retry-plan.js +1 -1
- package/frontend/src/lib/analysis/bulk-review-export.js +7 -6
- package/frontend/src/lib/analysis/bulk-review-model.js +3 -2
- package/frontend/src/lib/analysis/bulk-session-model.js +192 -26
- package/frontend/src/lib/analysis/bulk-sort.js +4 -0
- package/frontend/src/lib/analysis/case-brand-profile-references.js +85 -0
- package/frontend/src/lib/analysis/case-evidence-model.js +9 -1
- package/frontend/src/lib/analysis/case-investigation-branch-model.js +134 -0
- package/frontend/src/lib/analysis/case-migration-model.js +83 -31
- package/frontend/src/lib/analysis/case-record-contracts.js +4 -2
- package/frontend/src/lib/analysis/case-record-core.js +1 -1
- package/frontend/src/lib/analysis/case-record-model.js +2 -0
- package/frontend/src/lib/analysis/case-record-operations.js +43 -13
- package/frontend/src/lib/analysis/case-report.js +39 -6
- package/frontend/src/lib/analysis/case-response-model.js +66 -7
- package/frontend/src/lib/analysis/case-response-packet.js +15 -5
- package/frontend/src/lib/analysis/comparison-ledger-bulk.js +366 -0
- package/frontend/src/lib/analysis/comparison-ledger-contract.js +241 -0
- package/frontend/src/lib/analysis/comparison-ledger-json.js +79 -0
- package/frontend/src/lib/analysis/comparison-ledger-serialization.js +51 -0
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +145 -0
- package/frontend/src/lib/analysis/domain-control-records.js +88 -0
- package/frontend/src/lib/analysis/external-findings-import.js +70 -6
- package/frontend/src/lib/analysis/investigation-capsule.js +47 -13
- package/frontend/src/lib/analysis/investigation-guide.js +52 -10
- package/frontend/src/lib/analysis/investigation-template-model.js +9 -6
- package/frontend/src/lib/analysis/lookup-asset-graph.js +8 -8
- package/frontend/src/lib/analysis/lookup-claim-passport.js +236 -0
- package/frontend/src/lib/analysis/lookup-claim-readiness.js +280 -0
- package/frontend/src/lib/analysis/lookup-decision-support.js +63 -8
- package/frontend/src/lib/analysis/lookup-presentation.js +70 -14
- package/frontend/src/lib/analysis/lookup-source-refresh.js +36 -63
- package/frontend/src/lib/analysis/opaque-reference-id.js +9 -0
- package/frontend/src/lib/analysis/utils.js +27 -4
- package/frontend/src/lib/analysis/visualization-matrix.js +71 -28
- package/frontend/src/lib/analysis/visualization-models.js +63 -94
- package/frontend/src/lib/analysis/visualization-time-series.js +53 -12
- package/frontend/src/lib/analysis/website-snapshot-model.js +6 -0
- package/frontend/src/lib/analysis/workspace-archive-crypto.js +1 -4
- package/frontend/src/lib/analysis/workspace-archive.js +105 -19
- package/lib/application-version.mjs +3 -0
- package/lib/availability.mjs +14 -12
- package/lib/bounded-file.mjs +82 -0
- package/lib/bounded-json-response.mjs +170 -0
- package/lib/bounded-json.mjs +249 -0
- package/lib/browser-library-profile.mjs +167 -38
- package/lib/certificate-extension-profile.mjs +2 -1
- package/lib/cryptographic-assurance.mjs +155 -0
- package/lib/ct-search.mjs +87 -47
- package/lib/dns-intelligence.mjs +3 -2
- package/lib/dns-resolver-selection.mjs +6 -4
- package/lib/dnssec-chain-validation.mjs +1053 -0
- package/lib/dnssec-evidence-validation.mjs +10 -3
- package/lib/domain-change-packet.mjs +7 -5
- package/lib/domain-control-manifest.mjs +17 -129
- package/lib/domain-posture.mjs +7 -4
- package/lib/evidence-export.mjs +751 -78
- package/lib/evidence-report-markdown.mjs +3 -0
- package/lib/evidence-report.mjs +5 -0
- package/lib/favicon.mjs +8 -5
- package/lib/http-evidence-bounds.mjs +3 -0
- package/lib/http-intelligence.mjs +2 -5
- package/lib/interchange-fidelity-registry.mjs +122 -0
- package/lib/local-geoip-evidence.mjs +2 -1
- package/lib/lookup-cache.mjs +5 -1
- package/lib/lookup-network-evidence-bounds.mjs +9 -0
- package/lib/lookup-response-contract.mjs +691 -3
- package/lib/lookup.mjs +2 -2
- package/lib/observation.mjs +7 -6
- package/lib/outbound-identity.mjs +2 -3
- package/lib/perceptual-hash.mjs +12 -4
- package/lib/portable-generator.mjs +21 -0
- package/lib/provider-policy-admission.mjs +9 -4
- package/lib/rdap-normalization.mjs +18 -5
- package/lib/rdap-registrar.mjs +5 -1
- package/lib/rdap-transport.mjs +6 -6
- package/lib/registry-capabilities.mjs +3 -0
- package/lib/registry-capability-catalogue.mjs +12 -2
- package/lib/registry-comparison.mjs +28 -11
- package/lib/risk-calibration-summary.mjs +338 -0
- package/lib/rpki-evidence.mjs +9 -8
- package/lib/safe-fetch.mjs +96 -42
- package/lib/security-txt.mjs +26 -3
- package/lib/service-binding-dns.mjs +6 -1
- package/lib/smtp-transport-review.mjs +784 -0
- package/lib/sslbl-certificates.generated.mjs +136 -134
- package/lib/threat-intelligence-runtime.mjs +4 -13
- package/lib/threat-intelligence-types.mjs +18 -1
- package/lib/tls-intelligence.mjs +11 -5
- package/lib/tlsa-evidence.mjs +37 -7
- package/lib/website-technology.mjs +2 -6
- package/package.json +1 -1
- package/third-party-notices.txt +95 -5817
package/DISCLOSURE
CHANGED
|
@@ -8,9 +8,13 @@ can have both legitimate and potentially harmful uses.
|
|
|
8
8
|
WHOISleuth is intended for authorised defensive investigation, abuse response,
|
|
9
9
|
threat research, domain acquisition due diligence, asset review, and education.
|
|
10
10
|
Network collection is initiated explicitly by the operator, uses bounded
|
|
11
|
-
requests, and keeps source provenance and incomplete results visible.
|
|
12
|
-
does not exploit targets, bypass access controls,
|
|
13
|
-
remote page scripts, or automate enforcement
|
|
11
|
+
requests, and keeps source provenance and incomplete results visible. Hosted
|
|
12
|
+
and distributable collection does not exploit targets, bypass access controls,
|
|
13
|
+
submit credentials, execute remote page scripts, or automate enforcement
|
|
14
|
+
actions. The separate repo-local rendered-capture package is an explicit
|
|
15
|
+
authorised exception: it executes page JavaScript in a disposable,
|
|
16
|
+
network-bounded browser and writes only a local screenshot plus sanitised
|
|
17
|
+
digest and manifest artefacts for operator-controlled review and deletion.
|
|
14
18
|
|
|
15
19
|
Operators are responsible for using WHOISleuth only on systems and data they
|
|
16
20
|
are authorised to examine and in accordance with applicable laws, provider
|
package/NOTICE
CHANGED
|
@@ -32,10 +32,11 @@ warning-lists are available under CC0 1.0 Universal or the BSD 2-Clause
|
|
|
32
32
|
"Simplified" License. Source:
|
|
33
33
|
https://github.com/MISP/misp-warninglists
|
|
34
34
|
|
|
35
|
-
The exact locked production-dependency
|
|
36
|
-
distributed
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
The exact locked production-dependency inventories and licence documents are
|
|
36
|
+
distributed as third-party-notices.txt. The website inventory is served at
|
|
37
|
+
/third-party-notices.txt; the installed CLI contains its own notice file for
|
|
38
|
+
only the CLI runtime dependency closure. Both are generated from the reviewed
|
|
39
|
+
lockfile, and the website inventory is checked for lockfile drift in CI.
|
|
39
40
|
|
|
40
41
|
The generated Unicode confusable projection is derived from Unicode UTS #39
|
|
41
42
|
data file confusables.txt, Unicode version 17.0.0.
|
package/README.md
CHANGED
|
@@ -59,6 +59,8 @@ node bin/whoisleuth.mts discover example.test --dictionary private-terms.txt --s
|
|
|
59
59
|
node bin/whoisleuth.mts discover-scan example.test --scan-limit 50 --checkpoint candidate-scan.json --json
|
|
60
60
|
node bin/whoisleuth.mts page-compare official.json candidate.json --json
|
|
61
61
|
node bin/whoisleuth.mts mail-review bulk.json --json
|
|
62
|
+
node bin/whoisleuth.mts dnssec-validate example.test --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --json
|
|
63
|
+
node bin/whoisleuth.mts mail-transport selected-mx.json --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --active-probe --json
|
|
62
64
|
node bin/whoisleuth.mts domain-control domain-control-input.json --json
|
|
63
65
|
node bin/whoisleuth.mts assurance domain-assurance-input.json --json
|
|
64
66
|
node bin/whoisleuth.mts sharing-review response-packet.json --marking amber --recipient-scope organization --purpose 'Reviewed handoff' --human-reviewed --personal-data-reviewed --redactions-confirmed --json
|
|
@@ -88,10 +90,25 @@ explicit and are never interpreted as evidence of safety or absence.
|
|
|
88
90
|
`lookup --plan` classifies a target and explains the selected source families
|
|
89
91
|
and disclosures without making a network request. `commands --json` exposes the
|
|
90
92
|
installed command contracts for local wrappers without running collection.
|
|
93
|
+
The isolated `dnssec-validate` and `mail-transport` actions are not part of
|
|
94
|
+
Lookup, Bulk, monitoring, or recipes. Each run requires explicit
|
|
95
|
+
owned-or-authorised acknowledgement; mail transport also requires a separate
|
|
96
|
+
active-probe acknowledgement and never sends a message, authenticates, tests
|
|
97
|
+
relay, or enumerates recipients or mailboxes. Its output distinguishes selected,
|
|
98
|
+
public-revalidated, and connected addresses and explicitly reports that A, AAAA,
|
|
99
|
+
and CNAME authentication was not evaluated after a candidate is retained, or
|
|
100
|
+
that address authentication is unavailable when no candidate exists; only
|
|
101
|
+
confirmed connections can form address relationship leads. If a DANE-TA TLSA
|
|
102
|
+
usage 2 association is published, active collection retains only the observed
|
|
103
|
+
leaf certificate and leaves that comparison partial without certificate-path
|
|
104
|
+
construction and trust-anchor path validation. SMTP relay PKIX-TA usage 0 and
|
|
105
|
+
PKIX-EE usage 1 records are retained as unsupported and cannot complete SMTP
|
|
106
|
+
DANE assurance; a separate usage 3 match remains eligible. Saved
|
|
107
|
+
Lookup JSON is byte-, nesting-, entry-, and per-container-bounded before parsing and portable export.
|
|
91
108
|
Focused command help and the generated manual label every operation as offline
|
|
92
109
|
or networked and state its target, input, and concurrency boundaries. The
|
|
93
|
-
packaged [dual-use disclosure](DISCLOSURE)
|
|
94
|
-
of those capabilities.
|
|
110
|
+
packaged [dual-use disclosure](https://github.com/slicedearth/whoisleuth/blob/main/DISCLOSURE)
|
|
111
|
+
defines the supported defensive use of those capabilities.
|
|
95
112
|
|
|
96
113
|
Interactive output uses restrained semantic colour, width-aware wrapping, and
|
|
97
114
|
stderr-only progress for slower collection. Redirected and machine-readable
|
|
@@ -101,9 +118,13 @@ offline unless `--network` is explicitly supplied, and completion scripts are
|
|
|
101
118
|
printed without modifying shell configuration. Commands can use atomic private
|
|
102
119
|
`--output` files, Lookup can emit strict automation exits and target-free
|
|
103
120
|
versioned progress events, and Bulk can resume an exact validated compact
|
|
104
|
-
checkpoint.
|
|
121
|
+
checkpoint. Checkpoint schema 2 preserves each current row's observation time;
|
|
122
|
+
legacy schema-1 rows remain readable with unknown observation time rather than
|
|
123
|
+
being restamped on resume. Bulk machine output schema 3 distinguishes current
|
|
124
|
+
and resumed rows. A checkpoint write failure preserves completed output and returns
|
|
105
125
|
the partial-result exit code. Ctrl-C suppresses partial final output and
|
|
106
|
-
returns exit code 130
|
|
126
|
+
returns exit code 130. SIGTERM uses the same graceful cleanup path and returns
|
|
127
|
+
143; a second interrupt performs best-effort temporary-file
|
|
107
128
|
cleanup before exiting immediately.
|
|
108
129
|
Strict local profiles can supply only safe presentation, Fast-mode,
|
|
109
130
|
concurrency, and observer defaults; command-line options override them.
|
|
@@ -123,12 +144,48 @@ non-comparable evidence separate; the labels are context, not proof of
|
|
|
123
144
|
independence. `registry-doctor`, `assurance`, `sharing-review`, and
|
|
124
145
|
`lookalike-calibrate` add offline compatibility, change/recovery, sharing, and
|
|
125
146
|
review-yield workflows without contacting the hosted application.
|
|
126
|
-
`registry-cohort`
|
|
127
|
-
Lookups
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
`registry-cohort` emits target-free publication-quality timelines from either
|
|
148
|
+
saved Lookups or one unmixed family of retained version-1/version-2 cohort
|
|
149
|
+
reports; it never sums overlapping retained samples into independent
|
|
150
|
+
consistency. `registry-scaffold` produces a sanitised synthetic contributor
|
|
151
|
+
fixture. `diff` preserves saved-Lookup output and also emits a bounded
|
|
152
|
+
comparison ledger for explicitly paired Bulk sessions or domain portfolios.
|
|
153
|
+
`verify-artifact --manifest ... --manifest-entry ...` separates exact retained
|
|
154
|
+
byte identity from canonical-only or mismatched identity. `brief` and
|
|
155
|
+
`case-pack` create bounded local handoffs, `monitor-once` runs one explicit
|
|
156
|
+
control review rather than a daemon, and `workflow-run` executes only installed
|
|
157
|
+
fixed-recipe steps with per-run network approval and analyst-selection pauses.
|
|
158
|
+
The current schema-26 JSON produced by `export` is accepted directly by
|
|
159
|
+
`verify-artifact` and reported as `structure_valid`. It has no embedded checksum
|
|
160
|
+
or signature, so exact retained bytes require a verified investigation manifest
|
|
161
|
+
entry. The export can contain a bounded privacy-projected registry RDAP
|
|
162
|
+
publication, normalised WHOIS values, and bounded contacts and should be
|
|
163
|
+
reviewed before sharing. Request and response headers, cookies, session and
|
|
164
|
+
credential fields, and credential-bearing or query-bearing URLs are excluded.
|
|
165
|
+
Schema 25 remains readable through its historical wrapper contract: retained
|
|
166
|
+
diagnostics are authoritative, unavailable wrapper data is suppressed during
|
|
167
|
+
replay, and other contradictions fail closed. Verification and browser replay share a 5 MiB,
|
|
168
|
+
20,000-entry, 24-level portable boundary.
|
|
169
|
+
|
|
170
|
+
Lookup can also export a version-1 `whoisleuth.lookup-claim-passport` for one
|
|
171
|
+
selected readiness statement. `verify-artifact` checks its strict bounded
|
|
172
|
+
structure and sorted-json-v2 digest; `interchange-report` describes the exact
|
|
173
|
+
source-state fields it preserves and the raw payload, contact, page-value,
|
|
174
|
+
request-path, credential, browser-store, and signer-authentication fields it
|
|
175
|
+
excludes.
|
|
176
|
+
|
|
177
|
+
Current `case-pack` output keeps its version-1 envelope. Trusted and internal
|
|
178
|
+
audiences preserve exact analyst-selected Case-to-Brand Profile identifiers;
|
|
179
|
+
public output clears them from cases and embedded reports and discloses the
|
|
180
|
+
bounded `brandProfileReferencesOmitted` count. Verification also enforces the
|
|
181
|
+
actual audience projection after digest validation, rejects re-signed
|
|
182
|
+
sensitive-field leaks at expected or unexpected nested paths, and binds each
|
|
183
|
+
report to its canonical top-level Case and exact manifest counts. Current
|
|
184
|
+
schema-12 input must survive bounded normalisation exactly, provide safe unique
|
|
185
|
+
Case identities and a valid unique at-most-eight identifier list, and use
|
|
186
|
+
report v8. Schema 11 uses report v7; valid collections through schema 10 remain
|
|
187
|
+
compatible with supported reports through v6 and without later branch,
|
|
188
|
+
reference, or omission-count fields.
|
|
132
189
|
|
|
133
190
|
Bulk also supports fixed-column CSV, domain-only or exact-query output,
|
|
134
191
|
registered, inconclusive, and hard-failure output filters, and bounded A, AAAA,
|
package/bin/whoisleuth.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import EXIT_CODES from "../cli/exit-codes.mjs";
|
|
3
4
|
for (const stream of [process.stdout, process.stderr]) {
|
|
4
5
|
stream.on('error', (error) => {
|
|
5
6
|
if (error.code === 'EPIPE')
|
|
@@ -27,37 +28,49 @@ else {
|
|
|
27
28
|
import("../cli/runner.mjs"),
|
|
28
29
|
]);
|
|
29
30
|
let resolvedArgv;
|
|
31
|
+
let profileResolutionFailed = false;
|
|
30
32
|
try {
|
|
31
33
|
resolvedArgv = await configModule.resolveCliProfileArguments(argv);
|
|
32
34
|
}
|
|
33
35
|
catch (error) {
|
|
34
36
|
process.stderr.write(`Usage error: ${errorsModule.boundedCliErrorMessage(error, 'Invalid CLI profile')}\n`);
|
|
35
|
-
process.exitCode =
|
|
37
|
+
process.exitCode = EXIT_CODES.USAGE;
|
|
38
|
+
profileResolutionFailed = true;
|
|
36
39
|
resolvedArgv = [];
|
|
37
40
|
}
|
|
38
41
|
const cancellation = new AbortController();
|
|
39
42
|
let interruptionCount = 0;
|
|
40
|
-
|
|
43
|
+
let requestedExitCode = 130;
|
|
44
|
+
const interrupt = (signal) => {
|
|
41
45
|
interruptionCount += 1;
|
|
42
|
-
if (interruptionCount === 1)
|
|
46
|
+
if (interruptionCount === 1) {
|
|
47
|
+
requestedExitCode = signal === 'SIGTERM' ? 143 : 130;
|
|
43
48
|
cancellation.abort(new DOMException('Aborted', 'AbortError'));
|
|
49
|
+
}
|
|
44
50
|
else {
|
|
45
51
|
outputModule.cleanupPendingOutputFilesSync();
|
|
46
|
-
process.exit(
|
|
52
|
+
process.exit(requestedExitCode);
|
|
47
53
|
}
|
|
48
54
|
};
|
|
49
|
-
|
|
50
|
-
const
|
|
55
|
+
const onSigint = () => interrupt('SIGINT');
|
|
56
|
+
const onSigterm = () => interrupt('SIGTERM');
|
|
57
|
+
process.on('SIGINT', onSigint);
|
|
58
|
+
process.on('SIGTERM', onSigterm);
|
|
59
|
+
const execution = profileResolutionFailed
|
|
60
|
+
? Promise.resolve(process.exitCode || EXIT_CODES.USAGE)
|
|
61
|
+
: runnerModule.runCli(resolvedArgv, { signal: cancellation.signal });
|
|
51
62
|
execution.then((code) => {
|
|
52
63
|
if (code === 130) {
|
|
53
|
-
process.removeListener('SIGINT',
|
|
54
|
-
process.
|
|
64
|
+
process.removeListener('SIGINT', onSigint);
|
|
65
|
+
process.removeListener('SIGTERM', onSigterm);
|
|
66
|
+
process.exit(requestedExitCode);
|
|
55
67
|
}
|
|
56
68
|
process.exitCode = code;
|
|
57
69
|
}).catch((error) => {
|
|
58
70
|
process.stderr.write(`Internal CLI error: ${errorsModule.boundedCliErrorMessage(error)}\n`);
|
|
59
71
|
process.exitCode = 70;
|
|
60
72
|
}).finally(() => {
|
|
61
|
-
process.removeListener('SIGINT',
|
|
73
|
+
process.removeListener('SIGINT', onSigint);
|
|
74
|
+
process.removeListener('SIGTERM', onSigterm);
|
|
62
75
|
});
|
|
63
76
|
}
|
package/cli/arguments.mjs
CHANGED
|
@@ -21,6 +21,8 @@ const CLI_COMMANDS = [
|
|
|
21
21
|
'posture',
|
|
22
22
|
'http',
|
|
23
23
|
'tls',
|
|
24
|
+
'dnssec-validate',
|
|
25
|
+
'mail-transport',
|
|
24
26
|
'registry-support',
|
|
25
27
|
'registry-doctor',
|
|
26
28
|
'registry-cohort',
|
|
@@ -28,6 +30,7 @@ const CLI_COMMANDS = [
|
|
|
28
30
|
'risk-calibrate',
|
|
29
31
|
'lookalike-calibrate',
|
|
30
32
|
'verify-artifact',
|
|
33
|
+
'interchange-report',
|
|
31
34
|
'inspect-archive',
|
|
32
35
|
'sign-artifact',
|
|
33
36
|
'verify-signature',
|
|
@@ -156,6 +159,10 @@ function parseCliArgumentsCore(argv) {
|
|
|
156
159
|
return parseHttpArguments(argv.slice(1));
|
|
157
160
|
if (command === 'tls')
|
|
158
161
|
return parseTlsArguments(argv.slice(1));
|
|
162
|
+
if (command === 'dnssec-validate')
|
|
163
|
+
return parseDnssecValidateArguments(argv.slice(1));
|
|
164
|
+
if (command === 'mail-transport')
|
|
165
|
+
return parseMailTransportArguments(argv.slice(1));
|
|
159
166
|
if (command === 'registry-support')
|
|
160
167
|
return parseRegistrySupportArguments(argv.slice(1));
|
|
161
168
|
if (command === 'registry-doctor')
|
|
@@ -170,6 +177,8 @@ function parseCliArgumentsCore(argv) {
|
|
|
170
177
|
return parseLookalikeCalibrateArguments(argv.slice(1));
|
|
171
178
|
if (command === 'verify-artifact')
|
|
172
179
|
return parseVerifyArtifactArguments(argv.slice(1));
|
|
180
|
+
if (command === 'interchange-report')
|
|
181
|
+
return parseInterchangeReportArguments(argv.slice(1));
|
|
173
182
|
if (command === 'inspect-archive')
|
|
174
183
|
return parseInspectArchiveArguments(argv.slice(1));
|
|
175
184
|
if (command === 'sign-artifact')
|
|
@@ -223,6 +232,7 @@ function parseCliArgumentsCore(argv) {
|
|
|
223
232
|
let strictExit = false;
|
|
224
233
|
let events = false;
|
|
225
234
|
let plan = false;
|
|
235
|
+
let includeAttribution = true;
|
|
226
236
|
let failOn = null;
|
|
227
237
|
let observerLabel = null;
|
|
228
238
|
let vantageLabel = null;
|
|
@@ -279,6 +289,11 @@ function parseCliArgumentsCore(argv) {
|
|
|
279
289
|
throw new CliUsageError('--plan may be supplied only once.');
|
|
280
290
|
plan = true;
|
|
281
291
|
}
|
|
292
|
+
else if (argument === '--no-attribution') {
|
|
293
|
+
if (!includeAttribution)
|
|
294
|
+
throw new CliUsageError('--no-attribution may be supplied only once.');
|
|
295
|
+
includeAttribution = false;
|
|
296
|
+
}
|
|
282
297
|
else if (argument === '--fail-on') {
|
|
283
298
|
if (failOn !== null)
|
|
284
299
|
throw new CliUsageError('--fail-on may be supplied only once.');
|
|
@@ -315,12 +330,15 @@ function parseCliArgumentsCore(argv) {
|
|
|
315
330
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
316
331
|
if (detailSet && output !== 'terminal')
|
|
317
332
|
throw new CliUsageError('--summary and --verbose apply only to terminal output.');
|
|
333
|
+
if (!includeAttribution && output !== 'markdown' && output !== 'html') {
|
|
334
|
+
throw new CliUsageError('--no-attribution applies only to Markdown or HTML reports.');
|
|
335
|
+
}
|
|
318
336
|
if (plan && !['terminal', 'json'].includes(output))
|
|
319
337
|
throw new CliUsageError('--plan supports terminal or JSON output only.');
|
|
320
338
|
if (plan && (detailSet || strictExit || events || quiet || failOn !== null)) {
|
|
321
339
|
throw new CliUsageError('--plan cannot be combined with detail, strict-exit, event, or quiet options.');
|
|
322
340
|
}
|
|
323
|
-
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, observerLabel, vantageLabel, quiet, color, ...(failOn ? { failOn } : {}) };
|
|
341
|
+
return { action: 'lookup', query, output, deep, detail, strictExit, events, plan, includeAttribution, observerLabel, vantageLabel, quiet, color, ...(failOn ? { failOn } : {}) };
|
|
324
342
|
}
|
|
325
343
|
function parseCompletionArguments(argv) {
|
|
326
344
|
if (argv.length !== 1 || !['bash', 'zsh', 'fish', 'powershell'].includes(argv[0] || '')) {
|
|
@@ -963,6 +981,122 @@ function parseTlsArguments(argv) {
|
|
|
963
981
|
const { source: hostname, ...options } = parseSingleJsonInput(argv, 'tls accepts one hostname.');
|
|
964
982
|
return { action: 'tls', hostname, ...options };
|
|
965
983
|
}
|
|
984
|
+
function parseDnssecValidateArguments(argv) {
|
|
985
|
+
let target = null;
|
|
986
|
+
let resolver = null;
|
|
987
|
+
let trustAnchorSource = null;
|
|
988
|
+
let ownedOrAuthorized = false;
|
|
989
|
+
let output = 'terminal';
|
|
990
|
+
let quiet = false;
|
|
991
|
+
let color = true;
|
|
992
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
993
|
+
const argument = argv[index];
|
|
994
|
+
if (argument === '--resolver' || argument === '--trust-anchor') {
|
|
995
|
+
const selected = argument === '--resolver' ? resolver : trustAnchorSource;
|
|
996
|
+
if (selected !== null)
|
|
997
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
998
|
+
const value = argv[++index];
|
|
999
|
+
if (!value || value.startsWith('-'))
|
|
1000
|
+
throw new CliUsageError(`${argument} requires one bounded value.`);
|
|
1001
|
+
if (argument === '--resolver')
|
|
1002
|
+
resolver = value;
|
|
1003
|
+
else
|
|
1004
|
+
trustAnchorSource = value;
|
|
1005
|
+
}
|
|
1006
|
+
else if (argument === '--owned-or-authorized') {
|
|
1007
|
+
if (ownedOrAuthorized)
|
|
1008
|
+
throw new CliUsageError('--owned-or-authorized may be supplied only once.');
|
|
1009
|
+
ownedOrAuthorized = true;
|
|
1010
|
+
}
|
|
1011
|
+
else if (argument === '--json') {
|
|
1012
|
+
if (output !== 'terminal')
|
|
1013
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1014
|
+
output = 'json';
|
|
1015
|
+
}
|
|
1016
|
+
else if (argument === '--quiet')
|
|
1017
|
+
quiet = true;
|
|
1018
|
+
else if (argument === '--no-color')
|
|
1019
|
+
color = false;
|
|
1020
|
+
else if (argument?.startsWith('-'))
|
|
1021
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1022
|
+
else if (argument && target === null)
|
|
1023
|
+
target = argument;
|
|
1024
|
+
else
|
|
1025
|
+
throw new CliUsageError('dnssec-validate accepts one domain target.');
|
|
1026
|
+
}
|
|
1027
|
+
if (!target)
|
|
1028
|
+
throw new CliUsageError('dnssec-validate requires one domain target.');
|
|
1029
|
+
if (!resolver)
|
|
1030
|
+
throw new CliUsageError('dnssec-validate requires --resolver <public-IP>.');
|
|
1031
|
+
if (!trustAnchorSource)
|
|
1032
|
+
throw new CliUsageError('dnssec-validate requires --trust-anchor <anchor.json>.');
|
|
1033
|
+
if (!ownedOrAuthorized)
|
|
1034
|
+
throw new CliUsageError('dnssec-validate requires --owned-or-authorized for every run.');
|
|
1035
|
+
if (quiet && output !== 'terminal')
|
|
1036
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1037
|
+
return { action: 'dnssec-validate', target, resolver, trustAnchorSource, ownedOrAuthorized: true, output, quiet, color };
|
|
1038
|
+
}
|
|
1039
|
+
function parseMailTransportArguments(argv) {
|
|
1040
|
+
let source = null;
|
|
1041
|
+
let resolver = null;
|
|
1042
|
+
let trustAnchorSource = null;
|
|
1043
|
+
let ownedOrAuthorized = false;
|
|
1044
|
+
let activeProbeAcknowledged = false;
|
|
1045
|
+
let output = 'terminal';
|
|
1046
|
+
let quiet = false;
|
|
1047
|
+
let color = true;
|
|
1048
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1049
|
+
const argument = argv[index];
|
|
1050
|
+
if (argument === '--resolver' || argument === '--trust-anchor') {
|
|
1051
|
+
const selected = argument === '--resolver' ? resolver : trustAnchorSource;
|
|
1052
|
+
if (selected !== null)
|
|
1053
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
1054
|
+
const value = argv[++index];
|
|
1055
|
+
if (!value || value.startsWith('-'))
|
|
1056
|
+
throw new CliUsageError(`${argument} requires one bounded value.`);
|
|
1057
|
+
if (argument === '--resolver')
|
|
1058
|
+
resolver = value;
|
|
1059
|
+
else
|
|
1060
|
+
trustAnchorSource = value;
|
|
1061
|
+
}
|
|
1062
|
+
else if (argument === '--owned-or-authorized') {
|
|
1063
|
+
if (ownedOrAuthorized)
|
|
1064
|
+
throw new CliUsageError('--owned-or-authorized may be supplied only once.');
|
|
1065
|
+
ownedOrAuthorized = true;
|
|
1066
|
+
}
|
|
1067
|
+
else if (argument === '--active-probe') {
|
|
1068
|
+
if (activeProbeAcknowledged)
|
|
1069
|
+
throw new CliUsageError('--active-probe may be supplied only once.');
|
|
1070
|
+
activeProbeAcknowledged = true;
|
|
1071
|
+
}
|
|
1072
|
+
else if (argument === '--json') {
|
|
1073
|
+
if (output !== 'terminal')
|
|
1074
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1075
|
+
output = 'json';
|
|
1076
|
+
}
|
|
1077
|
+
else if (argument === '--quiet')
|
|
1078
|
+
quiet = true;
|
|
1079
|
+
else if (argument === '--no-color')
|
|
1080
|
+
color = false;
|
|
1081
|
+
else if (argument?.startsWith('-'))
|
|
1082
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1083
|
+
else if (argument && source === null)
|
|
1084
|
+
source = argument;
|
|
1085
|
+
else
|
|
1086
|
+
throw new CliUsageError('mail-transport accepts one optional versioned JSON input file.');
|
|
1087
|
+
}
|
|
1088
|
+
if (!resolver)
|
|
1089
|
+
throw new CliUsageError('mail-transport requires --resolver <public-IP>.');
|
|
1090
|
+
if (!trustAnchorSource)
|
|
1091
|
+
throw new CliUsageError('mail-transport requires --trust-anchor <anchor.json>.');
|
|
1092
|
+
if (!ownedOrAuthorized)
|
|
1093
|
+
throw new CliUsageError('mail-transport requires --owned-or-authorized for every run.');
|
|
1094
|
+
if (!activeProbeAcknowledged)
|
|
1095
|
+
throw new CliUsageError('mail-transport requires --active-probe for every run.');
|
|
1096
|
+
if (quiet && output !== 'terminal')
|
|
1097
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1098
|
+
return { action: 'mail-transport', source, resolver, trustAnchorSource, ownedOrAuthorized: true, activeProbeAcknowledged: true, output, quiet, color };
|
|
1099
|
+
}
|
|
966
1100
|
function parseCompareArguments(argv) {
|
|
967
1101
|
const parsed = parseSingleJsonInput(argv, 'compare accepts one optional lookup JSON file. Otherwise pipe one lookup document on stdin.');
|
|
968
1102
|
return { action: 'compare', ...parsed };
|
|
@@ -1325,15 +1459,32 @@ function parseWorkflowRunArguments(argv) {
|
|
|
1325
1459
|
function parseDiffArguments(argv) {
|
|
1326
1460
|
let leftSource = null;
|
|
1327
1461
|
let rightSource = null;
|
|
1462
|
+
let leftSessionId = null;
|
|
1463
|
+
let rightSessionId = null;
|
|
1328
1464
|
let output = 'terminal';
|
|
1329
1465
|
let quiet = false;
|
|
1330
1466
|
let color = true;
|
|
1331
|
-
for (
|
|
1467
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1468
|
+
const argument = argv[index];
|
|
1469
|
+
if (argument === undefined)
|
|
1470
|
+
break;
|
|
1332
1471
|
if (argument === '--json') {
|
|
1333
1472
|
if (output !== 'terminal')
|
|
1334
1473
|
throw new CliUsageError('--json may be supplied only once.');
|
|
1335
1474
|
output = 'json';
|
|
1336
1475
|
}
|
|
1476
|
+
else if (argument === '--left-session' || argument === '--right-session') {
|
|
1477
|
+
const current = argument === '--left-session' ? leftSessionId : rightSessionId;
|
|
1478
|
+
if (current !== null)
|
|
1479
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
1480
|
+
const value = argv[++index];
|
|
1481
|
+
if (!value || !/^[A-Za-z0-9_-]{1,128}$/u.test(value))
|
|
1482
|
+
throw new CliUsageError(`${argument} requires one bounded saved-session ID.`);
|
|
1483
|
+
if (argument === '--left-session')
|
|
1484
|
+
leftSessionId = value;
|
|
1485
|
+
else
|
|
1486
|
+
rightSessionId = value;
|
|
1487
|
+
}
|
|
1337
1488
|
else if (argument === '--quiet')
|
|
1338
1489
|
quiet = true;
|
|
1339
1490
|
else if (argument === '--no-color')
|
|
@@ -1345,15 +1496,15 @@ function parseDiffArguments(argv) {
|
|
|
1345
1496
|
else if (rightSource === null)
|
|
1346
1497
|
rightSource = argument;
|
|
1347
1498
|
else
|
|
1348
|
-
throw new CliUsageError('diff accepts exactly two
|
|
1499
|
+
throw new CliUsageError('diff accepts exactly two compatible retained JSON files.');
|
|
1349
1500
|
}
|
|
1350
1501
|
if (!leftSource || !rightSource)
|
|
1351
|
-
throw new CliUsageError('diff requires exactly two
|
|
1502
|
+
throw new CliUsageError('diff requires exactly two compatible retained JSON files.');
|
|
1352
1503
|
if (leftSource === rightSource)
|
|
1353
1504
|
throw new CliUsageError('diff requires two different input files.');
|
|
1354
1505
|
if (quiet && output !== 'terminal')
|
|
1355
1506
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1356
|
-
return { action: 'diff', leftSource, rightSource, output, quiet, color };
|
|
1507
|
+
return { action: 'diff', leftSource, rightSource, leftSessionId, rightSessionId, output, quiet, color };
|
|
1357
1508
|
}
|
|
1358
1509
|
function parseReconcileArguments(argv) {
|
|
1359
1510
|
const sources = [];
|
|
@@ -1451,8 +1602,30 @@ function parseRegistryScaffoldArguments(argv) {
|
|
|
1451
1602
|
return { action: 'registry-scaffold', profile, suffix, scenario: scenario };
|
|
1452
1603
|
}
|
|
1453
1604
|
function parseRiskCalibrateArguments(argv) {
|
|
1454
|
-
|
|
1455
|
-
|
|
1605
|
+
let source = null;
|
|
1606
|
+
let output = 'terminal';
|
|
1607
|
+
let quiet = false;
|
|
1608
|
+
let color = true;
|
|
1609
|
+
for (const argument of argv) {
|
|
1610
|
+
if (argument === '--json' || argument === '--summary-json') {
|
|
1611
|
+
if (output !== 'terminal')
|
|
1612
|
+
throw new CliUsageError('--json and --summary-json are mutually exclusive and may be supplied only once.');
|
|
1613
|
+
output = argument === '--json' ? 'json' : 'summary_json';
|
|
1614
|
+
}
|
|
1615
|
+
else if (argument === '--quiet')
|
|
1616
|
+
quiet = true;
|
|
1617
|
+
else if (argument === '--no-color')
|
|
1618
|
+
color = false;
|
|
1619
|
+
else if (argument.startsWith('-'))
|
|
1620
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1621
|
+
else if (source === null)
|
|
1622
|
+
source = argument;
|
|
1623
|
+
else
|
|
1624
|
+
throw new CliUsageError('risk-calibrate accepts one optional dataset file. Otherwise pipe one dataset on stdin.');
|
|
1625
|
+
}
|
|
1626
|
+
if (quiet && output !== 'terminal')
|
|
1627
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1628
|
+
return { action: 'risk-calibrate', source, output, quiet, color };
|
|
1456
1629
|
}
|
|
1457
1630
|
function parseLookalikeCalibrateArguments(argv) {
|
|
1458
1631
|
const parsed = parseSingleJsonInput(argv, 'lookalike-calibrate accepts one optional reviewed dataset JSON file.');
|
|
@@ -1461,7 +1634,10 @@ function parseLookalikeCalibrateArguments(argv) {
|
|
|
1461
1634
|
function parseVerifyArtifactArguments(argv) {
|
|
1462
1635
|
let source = null;
|
|
1463
1636
|
let passphraseSource = null;
|
|
1637
|
+
let manifestSource = null;
|
|
1638
|
+
let manifestEntryId = null;
|
|
1464
1639
|
let output = 'terminal';
|
|
1640
|
+
let strictExit = false;
|
|
1465
1641
|
let quiet = false;
|
|
1466
1642
|
let color = true;
|
|
1467
1643
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -1482,6 +1658,28 @@ function parseVerifyArtifactArguments(argv) {
|
|
|
1482
1658
|
}
|
|
1483
1659
|
passphraseSource = value;
|
|
1484
1660
|
}
|
|
1661
|
+
else if (argument === '--manifest') {
|
|
1662
|
+
if (manifestSource !== null)
|
|
1663
|
+
throw new CliUsageError('--manifest may be supplied only once.');
|
|
1664
|
+
const value = argv[++index];
|
|
1665
|
+
if (!value || value.startsWith('-'))
|
|
1666
|
+
throw new CliUsageError('--manifest requires one bounded investigation manifest file.');
|
|
1667
|
+
manifestSource = value;
|
|
1668
|
+
}
|
|
1669
|
+
else if (argument === '--manifest-entry') {
|
|
1670
|
+
if (manifestEntryId !== null)
|
|
1671
|
+
throw new CliUsageError('--manifest-entry may be supplied only once.');
|
|
1672
|
+
const value = argv[++index];
|
|
1673
|
+
if (!value || !/^artifact-(?:[1-9]|1[0-6])$/u.test(value)) {
|
|
1674
|
+
throw new CliUsageError('--manifest-entry requires an ID from artifact-1 through artifact-16.');
|
|
1675
|
+
}
|
|
1676
|
+
manifestEntryId = value;
|
|
1677
|
+
}
|
|
1678
|
+
else if (argument === '--strict-exit') {
|
|
1679
|
+
if (strictExit)
|
|
1680
|
+
throw new CliUsageError('--strict-exit may be supplied only once.');
|
|
1681
|
+
strictExit = true;
|
|
1682
|
+
}
|
|
1485
1683
|
else if (argument === '--quiet')
|
|
1486
1684
|
quiet = true;
|
|
1487
1685
|
else if (argument === '--no-color')
|
|
@@ -1493,9 +1691,50 @@ function parseVerifyArtifactArguments(argv) {
|
|
|
1493
1691
|
else
|
|
1494
1692
|
throw new CliUsageError('verify-artifact accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1495
1693
|
}
|
|
1694
|
+
if ((manifestSource === null) !== (manifestEntryId === null)) {
|
|
1695
|
+
throw new CliUsageError('--manifest and --manifest-entry must be supplied together.');
|
|
1696
|
+
}
|
|
1697
|
+
if (quiet && output !== 'terminal')
|
|
1698
|
+
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1699
|
+
return { action: 'verify-artifact', source, passphraseSource, manifestSource, manifestEntryId, output, strictExit, quiet, color };
|
|
1700
|
+
}
|
|
1701
|
+
function parseInterchangeReportArguments(argv) {
|
|
1702
|
+
let source = null;
|
|
1703
|
+
let passphraseSource = null;
|
|
1704
|
+
let output = 'terminal';
|
|
1705
|
+
let quiet = false;
|
|
1706
|
+
let color = true;
|
|
1707
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1708
|
+
const argument = argv[index];
|
|
1709
|
+
if (argument === undefined)
|
|
1710
|
+
break;
|
|
1711
|
+
if (argument === '--json') {
|
|
1712
|
+
if (output !== 'terminal')
|
|
1713
|
+
throw new CliUsageError('--json may be supplied only once.');
|
|
1714
|
+
output = 'json';
|
|
1715
|
+
}
|
|
1716
|
+
else if (argument === '--passphrase-file') {
|
|
1717
|
+
if (passphraseSource !== null)
|
|
1718
|
+
throw new CliUsageError('--passphrase-file may be supplied only once.');
|
|
1719
|
+
const value = argv[++index];
|
|
1720
|
+
if (!value || value.startsWith('-'))
|
|
1721
|
+
throw new CliUsageError('--passphrase-file requires one bounded UTF-8 file.');
|
|
1722
|
+
passphraseSource = value;
|
|
1723
|
+
}
|
|
1724
|
+
else if (argument === '--quiet')
|
|
1725
|
+
quiet = true;
|
|
1726
|
+
else if (argument === '--no-color')
|
|
1727
|
+
color = false;
|
|
1728
|
+
else if (argument.startsWith('-'))
|
|
1729
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
1730
|
+
else if (source === null)
|
|
1731
|
+
source = argument;
|
|
1732
|
+
else
|
|
1733
|
+
throw new CliUsageError('interchange-report accepts one optional JSON file. Otherwise pipe one artefact on stdin.');
|
|
1734
|
+
}
|
|
1496
1735
|
if (quiet && output !== 'terminal')
|
|
1497
1736
|
throw new CliUsageError('--quiet cannot be combined with machine-readable output.');
|
|
1498
|
-
return { action: '
|
|
1737
|
+
return { action: 'interchange-report', source, passphraseSource, output, quiet, color };
|
|
1499
1738
|
}
|
|
1500
1739
|
function parseSourceReportArguments(argv) {
|
|
1501
1740
|
const parsed = parseSingleJsonInput(argv, 'source-report accepts one optional JSON file. Otherwise pipe lookup documents on stdin.');
|
|
@@ -1504,6 +1743,7 @@ function parseSourceReportArguments(argv) {
|
|
|
1504
1743
|
function parseExportArguments(argv) {
|
|
1505
1744
|
let source = null;
|
|
1506
1745
|
let compact = false;
|
|
1746
|
+
let includeAttribution = true;
|
|
1507
1747
|
let format = 'json';
|
|
1508
1748
|
for (const argument of argv) {
|
|
1509
1749
|
if (argument === '--compact') {
|
|
@@ -1511,6 +1751,11 @@ function parseExportArguments(argv) {
|
|
|
1511
1751
|
throw new CliUsageError('--compact may be supplied only once.');
|
|
1512
1752
|
compact = true;
|
|
1513
1753
|
}
|
|
1754
|
+
else if (argument === '--no-attribution') {
|
|
1755
|
+
if (!includeAttribution)
|
|
1756
|
+
throw new CliUsageError('--no-attribution may be supplied only once.');
|
|
1757
|
+
includeAttribution = false;
|
|
1758
|
+
}
|
|
1514
1759
|
else if (argument === '--markdown' || argument === '--html') {
|
|
1515
1760
|
if (format !== 'json')
|
|
1516
1761
|
throw new CliUsageError('Choose only one evidence export format.');
|
|
@@ -1525,6 +1770,8 @@ function parseExportArguments(argv) {
|
|
|
1525
1770
|
}
|
|
1526
1771
|
if (compact && format !== 'json')
|
|
1527
1772
|
throw new CliUsageError('--compact applies to JSON export and cannot be combined with --markdown or --html.');
|
|
1528
|
-
|
|
1773
|
+
if (!includeAttribution && format === 'json')
|
|
1774
|
+
throw new CliUsageError('--no-attribution applies only to Markdown or HTML reports.');
|
|
1775
|
+
return { action: 'export', source, format, compact, includeAttribution };
|
|
1529
1776
|
}
|
|
1530
1777
|
export { CLI_COMMANDS, CliUsageError, MAX_CLI_ARGUMENTS, MAX_CLI_ARGUMENT_LENGTH, parseCliArguments };
|