@slicedearth/whoisleuth-cli 1.45.0 → 2.0.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/DISCLOSURE +7 -3
- package/NOTICE +5 -4
- package/README.md +74 -124
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +7 -4
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +450 -173
- package/cli/artifact-structure.mjs +2507 -0
- package/cli/artifact-verify.mjs +286 -64
- package/cli/bounded-json.mjs +1 -0
- package/cli/bulk-checkpoint.mjs +35 -23
- package/cli/bulk-command-runner.mjs +2 -5
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +435 -46
- package/cli/ci-report.mjs +6 -1
- package/cli/collection-preflight.mjs +2 -2
- package/cli/command-catalogue.mjs +12 -3
- package/cli/command-reference.mjs +948 -106
- package/cli/completion.mjs +504 -218
- package/cli/config-profile.mjs +13 -3
- package/cli/ct-event-intake.mjs +32 -2
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +13 -14
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/discovery-workflow.mjs +2 -3
- package/cli/doctor.mjs +2 -2
- package/cli/domain-control-monitor.mjs +215 -36
- package/cli/domain-control-observations.mjs +34 -17
- package/cli/errors.mjs +8 -3
- package/cli/evidence-command-runner.mjs +17 -11
- package/cli/evidence-signing.mjs +68 -50
- package/cli/export-evidence.mjs +11 -4
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/html.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +586 -22
- package/cli/input.mjs +62 -0
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +226 -0
- package/cli/investigation-manifest.mjs +16 -17
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +80 -24
- package/cli/local-mmdb-review.mjs +45 -14
- package/cli/lookalike-calibration.mjs +3 -1
- package/cli/lookup-brief.mjs +13 -8
- package/cli/lookup-browser.mjs +844 -0
- package/cli/lookup-command-runner.mjs +96 -33
- package/cli/lookup-diff.mjs +11 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/network-command-runner.mjs +78 -0
- package/cli/offline-evidence-review.mjs +45 -14
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +18 -10
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +343 -61
- package/cli/registry-doctor.mjs +67 -28
- package/cli/registry-support.mjs +2 -1
- package/cli/retained-artifact-diff.mjs +375 -0
- package/cli/risk-calibration.mjs +89 -40
- package/cli/runner.mjs +322 -246
- package/cli/saved-lookup.mjs +439 -49
- package/cli/sharing-review.mjs +18 -7
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1334
- package/docs/cli.md +87 -236
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -0
- package/frontend/src/lib/analysis/domain-control-records.js +3 -0
- package/lib/application-version.mjs +3 -0
- package/lib/availability.mjs +72 -52
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +91 -4
- package/lib/bounded-json.mjs +287 -0
- package/lib/browser-library-profile.mjs +1 -1
- package/lib/certificate-extension-profile.mjs +2 -1
- package/lib/classify.mjs +56 -5
- package/lib/client-behavior-profile.mjs +1 -1
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/cryptographic-assurance.mjs +173 -0
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +120 -64
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +4 -3
- package/lib/dns-resolver-selection.mjs +6 -4
- package/lib/dnssec-chain-validation.mjs +1052 -0
- package/lib/dnssec-evidence-validation.mjs +23 -10
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +7 -5
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -171
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/domain-posture.mjs +7 -4
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +1243 -88
- package/lib/evidence-report-markdown.mjs +3 -0
- package/lib/evidence-report.mjs +5 -0
- package/lib/external-intelligence-risk.mjs +26 -10
- package/lib/favicon.mjs +8 -5
- package/lib/feature-policy.mjs +1 -1
- package/lib/homepage-metadata-contract.mjs +290 -0
- package/lib/hostname.mjs +1 -1
- package/lib/html-signals.mjs +89 -5
- package/lib/http-evidence-bounds.mjs +3 -0
- package/lib/http-intelligence.mjs +332 -6
- package/lib/interchange-fidelity-registry.mjs +124 -0
- package/lib/local-geoip-evidence.mjs +2 -1
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup-network-evidence-bounds.mjs +9 -0
- package/lib/lookup.mjs +27 -4
- package/lib/observed-network-context.mjs +1 -1
- package/lib/outbound-identity.mjs +2 -3
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/perceptual-hash.mjs +12 -4
- package/lib/portable-generator.mjs +29 -0
- package/lib/provider-policy-admission.mjs +11 -4
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +324 -80
- package/lib/rdap-registrar.mjs +5 -1
- package/lib/rdap-transport.mjs +2 -2
- package/lib/rdap-validation.mjs +65 -14
- package/lib/rdap.mjs +2 -2
- package/lib/registrable-domain.mjs +36 -0
- package/lib/registry-capabilities.mjs +3 -0
- package/lib/registry-capability-catalogue.mjs +12 -2
- package/lib/registry-comparison.mjs +28 -11
- package/lib/registry-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +340 -0
- package/lib/risk-scoring.mjs +4 -3
- package/lib/rpki-evidence.mjs +9 -8
- package/lib/safe-fetch.mjs +121 -50
- package/lib/security-txt.mjs +27 -4
- package/lib/service-binding-dns.mjs +6 -1
- package/lib/smtp-transport-review.mjs +787 -0
- package/lib/sslbl-certificates.generated.mjs +136 -134
- package/lib/sslbl-intelligence.mjs +6 -8
- package/lib/static-html-analysis.mjs +301 -4
- package/lib/structured-data-identity.mjs +51 -19
- package/lib/threat-intelligence-fixtures.mjs +2 -4
- package/lib/threat-intelligence-manifests.mjs +2 -7
- package/lib/threat-intelligence-runtime.mjs +5 -15
- package/lib/threat-intelligence-types.mjs +21 -1
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +15 -9
- package/lib/tlsa-evidence.mjs +37 -7
- package/lib/urlhaus-intelligence.mjs +4 -2
- package/lib/urlscan-intelligence.mjs +6 -5
- package/lib/website-security-posture.mjs +1 -1
- package/lib/website-technology.mjs +1 -1
- package/lib/whois-authority.mjs +43 -8
- package/lib/whois-chain.mjs +25 -3
- package/lib/whois-normalization.mjs +3 -1
- package/lib/whois-transport.mjs +12 -3
- package/lib/zone-intent-review.mjs +1 -1
- package/package.json +12 -4
- package/packages/cases/case-brand-profile-references.mjs +85 -0
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +43 -14
- package/packages/cases/case-investigation-branch-model.mjs +132 -0
- package/packages/cases/case-migration-model.mjs +476 -0
- package/{frontend/src/lib/analysis/case-model.js → packages/cases/case-model.mjs} +3 -3
- package/{frontend/src/lib/analysis/case-record-contracts.js → packages/cases/case-record-contracts.mjs} +3 -19
- package/{frontend/src/lib/analysis/case-record-core.js → packages/cases/case-record-core.mjs} +58 -31
- package/packages/cases/case-record-model.mjs +9 -0
- package/{frontend/src/lib/analysis/case-record-operations.js → packages/cases/case-record-operations.mjs} +106 -33
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +143 -16
- package/packages/cases/case-response-model.mjs +1604 -0
- package/packages/cases/case-response-packet.mjs +1312 -0
- package/{frontend/src/lib/analysis/case-storage-model.js → packages/cases/case-storage-model.mjs} +2 -2
- package/{frontend/src/lib/analysis/http-summary.js → packages/cases/http-summary.mjs} +1 -1
- package/packages/cases/opaque-reference-id.mjs +9 -0
- package/packages/comparison/comparison-ledger-bulk.mjs +366 -0
- package/packages/comparison/comparison-ledger-contract.mjs +241 -0
- package/packages/comparison/comparison-ledger-json.mjs +80 -0
- package/packages/comparison/comparison-ledger-serialization.mjs +52 -0
- package/packages/comparison/favicon-similarity.mjs +78 -0
- package/{frontend/src/lib/analysis/page-similarity.js → packages/comparison/page-similarity.mjs} +4 -3
- package/{frontend/src/lib/analysis/relationship-evidence.js → packages/comparison/relationship-evidence.mjs} +6 -7
- package/packages/contracts/analyst-review-state-contract.mjs +61 -0
- package/packages/contracts/analyst-review-state.mjs +50 -0
- package/packages/contracts/case-portability.mjs +1310 -0
- package/packages/contracts/cli-command-catalogue.mjs +2 -0
- package/packages/contracts/cli-lookup.mjs +394 -0
- package/packages/contracts/domain-control-flight-recorder.mjs +461 -0
- package/packages/contracts/domain-control-manifest.mjs +1027 -0
- package/packages/contracts/domain-control-monitor.mjs +310 -0
- package/packages/contracts/domain-control-review.mjs +828 -0
- package/packages/contracts/domain-name.mjs +4 -0
- package/packages/contracts/external-observation-interchange.mjs +224 -0
- package/packages/contracts/extracted-domain-lifecycle.mjs +218 -0
- package/packages/contracts/investigation-portability.mjs +331 -0
- package/packages/contracts/offline-comparison.mjs +160 -0
- package/packages/contracts/risk-calibration.mjs +1288 -0
- package/packages/contracts/schema-compatibility.mjs +71 -0
- package/packages/contracts/schema-lifecycle.mjs +1702 -0
- package/packages/contracts/sslbl-snapshot.mjs +17 -0
- package/packages/contracts/workspace-portability.mjs +1347 -0
- package/packages/evidence/artifact-integrity.mjs +65 -0
- package/packages/evidence/decision-fact.mjs +735 -0
- package/packages/evidence/domain-control-runtime.mjs +547 -0
- package/packages/evidence/domain-name.mjs +41 -0
- package/packages/evidence/observation.mjs +384 -0
- package/{frontend/src/lib/analysis/external-findings-import.js → packages/interchange/external-findings-import.mjs} +86 -20
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +11 -8
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +28 -9
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +67 -18
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +10 -9
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +50 -18
- package/packages/investigation/lookup-artefact-inputs.mjs +1 -0
- package/{frontend/src/lib/analysis/lookup-asset-graph.js → packages/investigation/lookup-asset-graph.mjs} +4 -2
- package/packages/investigation/lookup-claim-passport.mjs +232 -0
- package/packages/investigation/lookup-claim-readiness.mjs +287 -0
- package/packages/investigation/lookup-investigation-brief.mjs +172 -0
- package/packages/monitoring/analyst-review-state.mjs +680 -0
- package/{frontend/src/lib/analysis/brand-profile-model.js → packages/workspace/brand-profile-model.mjs} +97 -50
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +15 -15
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +179 -39
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +12 -5
- package/{frontend/src/lib/analysis/campaign-model.js → packages/workspace/campaign-model.mjs} +20 -22
- package/{frontend/src/lib/analysis/detection-rule-model.js → packages/workspace/detection-rule-model.mjs} +15 -17
- package/packages/workspace/hostile-input.mjs +172 -0
- package/{frontend/src/lib/analysis/investigation-guide.js → packages/workspace/investigation-guide.mjs} +51 -46
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +15 -15
- package/{frontend/src/lib/analysis/page-baseline.js → packages/workspace/page-baseline.mjs} +6 -13
- package/{frontend/src/lib/analysis/relationship-observation-model.js → packages/workspace/relationship-observation-model.mjs} +13 -17
- package/{frontend/src/lib/analysis/shortlist-model.js → packages/workspace/shortlist-model.mjs} +20 -18
- package/{frontend/src/lib/analysis/watchlist-history.js → packages/workspace/watchlist-history.mjs} +9 -16
- package/{frontend/src/lib/analysis/watchlist-store.js → packages/workspace/watchlist-store.mjs} +24 -17
- package/{frontend/src/lib/analysis/website-snapshot-model.js → packages/workspace/website-snapshot-model.mjs} +18 -12
- package/{frontend/src/lib/analysis/workspace-archive-crypto.js → packages/workspace/workspace-archive-crypto.mjs} +49 -19
- package/{frontend/src/lib/analysis/workspace-archive.js → packages/workspace/workspace-archive.mjs} +195 -84
- package/third-party-notices.txt +112 -5834
- package/frontend/src/lib/analysis/artifact-integrity.js +0 -21
- package/frontend/src/lib/analysis/bulk-retry-plan.js +0 -79
- package/frontend/src/lib/analysis/bulk-source-coverage.js +0 -76
- package/frontend/src/lib/analysis/case-migration-model.js +0 -253
- package/frontend/src/lib/analysis/case-record-model.js +0 -7
- package/frontend/src/lib/analysis/case-response-model.js +0 -669
- package/frontend/src/lib/analysis/case-response-packet.js +0 -573
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -487
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -58
- package/frontend/src/lib/analysis/lookup-response.js +0 -2
- package/frontend/src/lib/analysis/lookup-source-refresh.js +0 -277
- package/frontend/src/lib/analysis/lookup-summary-model.js +0 -398
- package/frontend/src/lib/analysis/scoring.js +0 -55
- package/frontend/src/lib/analysis/utils.js +0 -232
- package/frontend/src/lib/analysis/visualization-bounds.js +0 -26
- package/frontend/src/lib/analysis/visualization-force-graph.js +0 -352
- package/frontend/src/lib/analysis/visualization-matrix.js +0 -88
- package/frontend/src/lib/analysis/visualization-models.js +0 -369
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -144
- package/lib/bounded-json-response.mjs +0 -157
- package/lib/lookup-response-contract.mjs +0 -346
- package/lib/observation.mjs +0 -114
- /package/{frontend/src/lib/analysis/acquisition-due-diligence.js → packages/investigation/acquisition-due-diligence.mjs} +0 -0
- /package/{frontend/src/lib/analysis/evidence-coverage-ledger.js → packages/investigation/evidence-coverage-ledger.mjs} +0 -0
- /package/{frontend/src/lib/analysis/dns-record-normalization.js → packages/workspace/dns-record-normalization.mjs} +0 -0
package/cli/completion.mjs
CHANGED
|
@@ -1,178 +1,149 @@
|
|
|
1
|
-
import { CLI_COMMANDS } from "./
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
bulk: ['--json', '--jsonl', '--junit', '--csv', '--domains', '--queries', '--registered-only', '--inconclusive-only', '--errors-only', '--fast', '--deep', '--concurrency', '--checkpoint', '--resume', '--events', '--plan', '--fail-on', '--quiet', '--no-color'],
|
|
10
|
-
'ct-search': ['--json', '--quiet', '--no-color'],
|
|
11
|
-
'ct-intake': ['--json', '--quiet', '--no-color'],
|
|
12
|
-
discover: ['--tlds', '--preset', '--families', '--keyboard', '--dictionary', '--snapshot', '--json', '--jsonl', '--domains', '--quiet', '--no-color'],
|
|
13
|
-
'discover-scan': ['--tlds', '--preset', '--families', '--keyboard', '--dictionary', '--fast', '--deep', '--scan-limit', '--chunk-size', '--concurrency', '--resolver', '--allowlist', '--checkpoint', '--resume', '--observation-snapshot', '--registered-only', '--inconclusive-only', '--acquisition-only', '--suppressed-only', '--events', '--plan', '--fail-on', '--json', '--jsonl', '--csv', '--domains', '--quiet', '--no-color'],
|
|
14
|
-
posture: ['--selectors', '--retired-selectors', '--mail-profile', '--json', '--sarif', '--owned-domain', '--quiet', '--no-color'],
|
|
15
|
-
http: ['--json', '--quiet', '--no-color'],
|
|
16
|
-
tls: ['--json', '--quiet', '--no-color'],
|
|
17
|
-
'registry-support': ['--json', '--quiet', '--no-color'],
|
|
18
|
-
'registry-doctor': ['--json', '--quiet', '--no-color'],
|
|
19
|
-
'registry-cohort': ['--json', '--quiet', '--no-color'],
|
|
20
|
-
'registry-scaffold': ['--profile', '--suffix', '--scenario'],
|
|
21
|
-
'risk-calibrate': ['--json', '--quiet', '--no-color'],
|
|
22
|
-
'lookalike-calibrate': ['--json', '--quiet', '--no-color'],
|
|
23
|
-
'verify-artifact': ['--passphrase-file', '--json', '--quiet', '--no-color'],
|
|
24
|
-
'inspect-archive': ['--passphrase-file', '--search', '--require-match', '--reveal', '--expect-content-digest', '--json', '--quiet', '--no-color'],
|
|
25
|
-
'sign-artifact': ['--private-key-file'],
|
|
26
|
-
'verify-signature': ['--public-key-file', '--json', '--quiet', '--no-color'],
|
|
27
|
-
'source-report': ['--json', '--quiet', '--no-color'],
|
|
28
|
-
compare: ['--json', '--quiet', '--no-color'],
|
|
29
|
-
'page-compare': ['--json', '--quiet', '--no-color'],
|
|
30
|
-
'mail-review': ['--json', '--quiet', '--no-color'],
|
|
31
|
-
'review-evidence': ['--mmdb', '--json', '--strict-exit', '--quiet', '--no-color'],
|
|
32
|
-
brief: ['--json', '--quiet', '--no-color'],
|
|
33
|
-
'case-pack': ['--audience', '--reviewed', '--json', '--quiet', '--no-color'],
|
|
34
|
-
'domain-control': ['--json', '--quiet', '--no-color'],
|
|
35
|
-
'monitor-once': ['--previous', '--limit', '--concurrency', '--fail-on', '--json', '--junit', '--quiet', '--no-color'],
|
|
36
|
-
assurance: ['--json', '--quiet', '--no-color'],
|
|
37
|
-
'change-packet': ['--json', '--quiet', '--no-color'],
|
|
38
|
-
'sharing-review': ['--marking', '--recipient-scope', '--purpose', '--human-reviewed', '--personal-data-reviewed', '--redactions-confirmed', '--json', '--quiet', '--no-color'],
|
|
39
|
-
'workflow-plan': ['--json', '--quiet', '--no-color'],
|
|
40
|
-
'workflow-run': ['--approve-network', '--resume', '--json', '--quiet', '--no-color'],
|
|
41
|
-
diff: ['--json', '--quiet', '--no-color'],
|
|
42
|
-
reconcile: ['--json', '--quiet', '--no-color'],
|
|
43
|
-
timeline: ['--json', '--quiet', '--no-color'],
|
|
44
|
-
export: ['--markdown', '--html', '--compact'],
|
|
45
|
-
completion: [],
|
|
46
|
-
commands: ['--json', '--quiet', '--no-color'],
|
|
47
|
-
doctor: ['--network', '--json', '--quiet', '--no-color'],
|
|
48
|
-
manual: [],
|
|
49
|
-
});
|
|
50
|
-
const COMMAND_DESCRIPTIONS = Object.freeze({
|
|
51
|
-
manifest: 'Build an evidence manifest offline',
|
|
52
|
-
'map-observations': 'Normalise passive DNS observations offline',
|
|
53
|
-
'oam-export': 'Export an observation-archive map offline',
|
|
54
|
-
lookup: 'Collect one domain, IP, or ASN',
|
|
55
|
-
bulk: 'Run bounded multi-target collection',
|
|
56
|
-
'ct-search': 'Search certificate observations',
|
|
57
|
-
'ct-intake': 'Normalise certificate observations offline',
|
|
58
|
-
discover: 'Generate lookalike candidates offline',
|
|
59
|
-
'discover-scan': 'Collect a supervised candidate review queue',
|
|
60
|
-
posture: 'Review DNS and mail posture',
|
|
61
|
-
http: 'Inspect one homepage request',
|
|
62
|
-
tls: 'Inspect one TLS connection',
|
|
63
|
-
'registry-support': 'Explain local registry coverage',
|
|
64
|
-
'registry-doctor': 'Diagnose saved registry collection',
|
|
65
|
-
'registry-cohort': 'Aggregate target-free registry quality cohorts',
|
|
66
|
-
'registry-scaffold': 'Create a sanitised registry fixture scaffold',
|
|
67
|
-
'risk-calibrate': 'Replay reviewed Risk labels offline',
|
|
68
|
-
'lookalike-calibrate': 'Summarise reviewed lookalike yield offline',
|
|
69
|
-
'verify-artifact': 'Validate saved evidence offline',
|
|
70
|
-
'inspect-archive': 'Inspect an archive locally',
|
|
71
|
-
'sign-artifact': 'Sign a reviewed artefact locally',
|
|
72
|
-
'verify-signature': 'Verify a signed evidence package',
|
|
73
|
-
'source-report': 'Build a target-free source report',
|
|
74
|
-
compare: 'Compare registry publications in one lookup',
|
|
75
|
-
'page-compare': 'Compare saved static page evidence',
|
|
76
|
-
'mail-review': 'Review saved passive mail evidence',
|
|
77
|
-
'review-evidence': 'Review supplied evidence offline',
|
|
78
|
-
brief: 'Build a decision brief from a saved lookup',
|
|
79
|
-
'case-pack': 'Build a reviewed case package',
|
|
80
|
-
'domain-control': 'Build or review a domain control manifest',
|
|
81
|
-
'monitor-once': 'Run one bounded domain control review',
|
|
82
|
-
assurance: 'Review domain change, recovery, or retirement plans',
|
|
83
|
-
'change-packet': 'Build a reviewed change packet offline',
|
|
84
|
-
'sharing-review': 'Lint an artefact before deliberate sharing',
|
|
85
|
-
'workflow-plan': 'Plan a fixed investigation recipe',
|
|
86
|
-
'workflow-run': 'Execute approved fixed-recipe steps',
|
|
87
|
-
diff: 'Compare two saved domain lookups',
|
|
88
|
-
reconcile: 'Reconcile independently labelled observations',
|
|
89
|
-
timeline: 'Build same-domain history from saved lookups',
|
|
90
|
-
export: 'Convert a lookup to an evidence report',
|
|
91
|
-
completion: 'Print shell completion',
|
|
92
|
-
commands: 'List installed command contracts',
|
|
93
|
-
doctor: 'Check the local CLI runtime',
|
|
94
|
-
manual: 'Print the generated manual page',
|
|
95
|
-
});
|
|
96
|
-
const VALUE_OPTIONS = Object.freeze({
|
|
97
|
-
'--preset': ['common', 'impersonation', 'all'],
|
|
98
|
-
'--keyboard': ['qwerty', 'azerty', 'qwertz', 'all'],
|
|
99
|
-
'--mail-profile': ['standard', 'defensive-no-mail', 'parked'],
|
|
100
|
-
'--marking': ['clear', 'green', 'amber', 'amber-strict', 'red'],
|
|
101
|
-
'--recipient-scope': ['public', 'community', 'organization', 'named-recipients'],
|
|
102
|
-
'--audience': ['internal', 'trusted', 'public'],
|
|
103
|
-
'--concurrency': ['1', '2', '3', '4', '5', '6', '7', '8'],
|
|
104
|
-
});
|
|
105
|
-
const FILE_OPTIONS = Object.freeze([
|
|
106
|
-
'--checkpoint',
|
|
107
|
-
'--config',
|
|
108
|
-
'--allowlist',
|
|
109
|
-
'--dictionary',
|
|
110
|
-
'--output',
|
|
111
|
-
'--passphrase-file',
|
|
112
|
-
'--private-key-file',
|
|
113
|
-
'--public-key-file',
|
|
114
|
-
'--snapshot',
|
|
115
|
-
'--observation-snapshot',
|
|
116
|
-
'--previous',
|
|
117
|
-
'--resume',
|
|
118
|
-
]);
|
|
119
|
-
const FILE_POSITIONAL_COMMANDS = new Set([
|
|
120
|
-
'manifest',
|
|
121
|
-
'map-observations',
|
|
122
|
-
'oam-export',
|
|
123
|
-
'ct-intake',
|
|
124
|
-
'change-packet',
|
|
125
|
-
]);
|
|
126
|
-
const TEXT_OPTIONS = Object.freeze([
|
|
127
|
-
'--families',
|
|
128
|
-
'--resolver',
|
|
129
|
-
'--fail-on',
|
|
130
|
-
'--expect-content-digest',
|
|
131
|
-
'--profile',
|
|
132
|
-
'--purpose',
|
|
133
|
-
'--observer',
|
|
134
|
-
'--retired-selectors',
|
|
135
|
-
'--search',
|
|
136
|
-
'--selectors',
|
|
137
|
-
'--tlds',
|
|
138
|
-
'--vantage',
|
|
1
|
+
import { CLI_COMMAND_REGISTRY, CLI_COMMANDS, COMMAND_DESCRIPTIONS, FILE_POSITIONAL_COMMANDS, commandOptionSpec, commandDefinition, commandPositionalSpecs, metaActionDefinition, } from "./command-reference.mjs";
|
|
2
|
+
const MAX_CLI_COMPLETION_BYTES = 32 * 1024;
|
|
3
|
+
const HELP_META_ACTION = metaActionDefinition('help');
|
|
4
|
+
const VERSION_META_ACTION = metaActionDefinition('version');
|
|
5
|
+
const COMMAND_META_ALIASES = Object.freeze([...HELP_META_ACTION.aliases]);
|
|
6
|
+
const ROOT_META_ALIASES = Object.freeze([
|
|
7
|
+
...HELP_META_ACTION.aliases,
|
|
8
|
+
...VERSION_META_ACTION.aliases,
|
|
139
9
|
]);
|
|
10
|
+
function longMetaAlias(action) {
|
|
11
|
+
const alias = action.aliases.find((candidate) => candidate.startsWith('--'));
|
|
12
|
+
if (!alias)
|
|
13
|
+
throw new Error(`Missing long CLI meta-action alias for ${action.id}.`);
|
|
14
|
+
return alias;
|
|
15
|
+
}
|
|
16
|
+
function shortMetaAlias(action) {
|
|
17
|
+
const alias = action.aliases.find((candidate) => /^-[^-]$/u.test(candidate));
|
|
18
|
+
if (!alias)
|
|
19
|
+
throw new Error(`Missing short CLI meta-action alias for ${action.id}.`);
|
|
20
|
+
return alias;
|
|
21
|
+
}
|
|
22
|
+
const HELP_LONG_ALIAS = longMetaAlias(HELP_META_ACTION);
|
|
23
|
+
const HELP_SHORT_ALIAS = shortMetaAlias(HELP_META_ACTION);
|
|
24
|
+
function optionNames(command) {
|
|
25
|
+
const definition = commandDefinition(command);
|
|
26
|
+
return Object.freeze([...new Set([
|
|
27
|
+
...definition.grammar.options.map((option) => option.option),
|
|
28
|
+
...definition.grammar.metaActions.flatMap((id) => metaActionDefinition(id).aliases),
|
|
29
|
+
])]);
|
|
30
|
+
}
|
|
31
|
+
function optionNamesByKind(command, kinds) {
|
|
32
|
+
return commandDefinition(command).grammar.options
|
|
33
|
+
.filter((option) => kinds.includes(option.valueKind))
|
|
34
|
+
.map((option) => option.option);
|
|
35
|
+
}
|
|
36
|
+
function positionalValues(command, oneBasedIndex) {
|
|
37
|
+
let offset = 0;
|
|
38
|
+
for (const specification of commandPositionalSpecs(command)) {
|
|
39
|
+
if (oneBasedIndex > offset && oneBasedIndex <= offset + specification.maximum) {
|
|
40
|
+
return specification.values;
|
|
41
|
+
}
|
|
42
|
+
offset += specification.maximum;
|
|
43
|
+
}
|
|
44
|
+
return Object.freeze([]);
|
|
45
|
+
}
|
|
46
|
+
function maximumFilePositionals(command) {
|
|
47
|
+
const positionals = commandPositionalSpecs(command);
|
|
48
|
+
return positionals.length > 0 && positionals.every((specification) => specification.valueKind === 'file')
|
|
49
|
+
? positionals.reduce((total, specification) => total + specification.maximum, 0)
|
|
50
|
+
: 0;
|
|
51
|
+
}
|
|
52
|
+
function positionalEnumValues(command) {
|
|
53
|
+
return positionalValues(command, 1);
|
|
54
|
+
}
|
|
55
|
+
const VALUE_OPTIONS = Object.freeze(Object.fromEntries(CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options)
|
|
56
|
+
.filter((option) => option.values.length > 0)
|
|
57
|
+
.map((option) => [option.option, option.values])));
|
|
58
|
+
function integerValues(command, option, whenOptionPresent) {
|
|
59
|
+
const range = commandOptionSpec(command, option)?.integerRanges
|
|
60
|
+
.find((candidate) => candidate.whenOptionPresent === whenOptionPresent);
|
|
61
|
+
if (!range)
|
|
62
|
+
throw new Error(`Missing completion range for ${command} ${option}.`);
|
|
63
|
+
return Object.freeze(Array.from({ length: range.maximum - range.minimum + 1 }, (_, index) => String(range.minimum + index)));
|
|
64
|
+
}
|
|
140
65
|
function commandOptions(command) {
|
|
141
|
-
return
|
|
66
|
+
return optionNames(command).join(' ');
|
|
67
|
+
}
|
|
68
|
+
function commandOptionPatterns(kinds) {
|
|
69
|
+
return CLI_COMMANDS.flatMap((command) => optionNamesByKind(command, kinds)
|
|
70
|
+
.map((option) => `${command}:${option}`)).join('|');
|
|
142
71
|
}
|
|
143
72
|
function bashCompletion() {
|
|
144
|
-
const cases = CLI_COMMANDS.map((command) => ` ${command}) options="${commandOptions(command)}" ;;`).join('\n');
|
|
73
|
+
const cases = CLI_COMMANDS.map((command) => ` ${command}) options="${commandOptions(command)}"; value_options="${optionNamesByKind(command, ['enum', 'file', 'integer', 'policy_list', 'text']).join(' ')}"; file_limit=${maximumFilePositionals(command)} ;;`).join('\n');
|
|
145
74
|
const valueCases = Object.entries(VALUE_OPTIONS).map(([option, values]) => ` ${option}) COMPREPLY=( $(compgen -W "${values.join(' ')}" -- "\${current}") ); return ;;`).join('\n');
|
|
75
|
+
const integerCases = CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options
|
|
76
|
+
.filter((option) => option.valueKind === 'integer')
|
|
77
|
+
.map((option) => {
|
|
78
|
+
const ordinary = integerValues(definition.command, option.option, null).join(' ');
|
|
79
|
+
const conditional = option.integerRanges.find((range) => range.whenOptionPresent !== null);
|
|
80
|
+
if (!conditional) {
|
|
81
|
+
return ` ${definition.command}:${option.option}) COMPREPLY=( $(compgen -W "${ordinary}" -- "\${current}") ); return ;;`;
|
|
82
|
+
}
|
|
83
|
+
const narrowed = integerValues(definition.command, option.option, conditional.whenOptionPresent).join(' ');
|
|
84
|
+
return ` ${definition.command}:${option.option}) if [[ " \${seen_options} " == *" ${conditional.whenOptionPresent} "* ]]; then integer_values="${narrowed}"; else integer_values="${ordinary}"; fi; COMPREPLY=( $(compgen -W "\${integer_values}" -- "\${current}") ); return ;;`;
|
|
85
|
+
})).join('\n');
|
|
86
|
+
const positionalValueCases = CLI_COMMANDS.flatMap((command) => {
|
|
87
|
+
const values = positionalEnumValues(command);
|
|
88
|
+
return values.length > 0
|
|
89
|
+
? [` ${command}:0) if (( foreign_option == 0 && expect_value == 0 )) && [[ "\${current}" != -* ]]; then COMPREPLY=( $(compgen -W "${values.join(' ')}" -- "\${current}") ); return; fi ;;`]
|
|
90
|
+
: [];
|
|
91
|
+
}).join('\n');
|
|
146
92
|
return `# WHOISleuth bash completion
|
|
93
|
+
_whoisleuth_direct_lookup_target() {
|
|
94
|
+
local candidate="\${1}"
|
|
95
|
+
[[ "\${candidate}" != -* ]] || return 1
|
|
96
|
+
case " ${CLI_COMMANDS.join(' ')} " in *" \${candidate} "*) return 1 ;; esac
|
|
97
|
+
"\${COMP_WORDS[0]}" "\${candidate}" --plan --json >/dev/null 2>&1
|
|
98
|
+
}
|
|
147
99
|
_whoisleuth_completion() {
|
|
148
|
-
local current previous command options
|
|
100
|
+
local current previous command options value_options file_limit positional_count expect_value foreign_option word integer_values seen_options i
|
|
149
101
|
current="\${COMP_WORDS[COMP_CWORD]}"
|
|
150
102
|
previous="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
151
103
|
command="\${COMP_WORDS[1]}"
|
|
152
104
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
153
|
-
COMPREPLY=( $(compgen -W "${CLI_COMMANDS.join(' ')}
|
|
154
|
-
return
|
|
155
|
-
fi
|
|
156
|
-
if [[ "\${command}" == "completion" && \${COMP_CWORD} -eq 2 ]]; then
|
|
157
|
-
COMPREPLY=( $(compgen -W "bash zsh fish powershell" -- "\${current}") )
|
|
105
|
+
COMPREPLY=( $(compgen -W "${CLI_COMMANDS.join(' ')} ${ROOT_META_ALIASES.join(' ')}" -- "\${current}") )
|
|
158
106
|
return
|
|
159
107
|
fi
|
|
160
|
-
if
|
|
161
|
-
|
|
162
|
-
return
|
|
108
|
+
if _whoisleuth_direct_lookup_target "\${command}"; then
|
|
109
|
+
command="lookup"
|
|
163
110
|
fi
|
|
164
|
-
case "\${previous}" in
|
|
165
|
-
${valueCases}
|
|
166
|
-
${FILE_OPTIONS.join('|')}) COMPREPLY=( $(compgen -f -- "\${current}") ); return ;;
|
|
167
|
-
${TEXT_OPTIONS.join('|')}) COMPREPLY=(); return ;;
|
|
168
|
-
esac
|
|
169
111
|
case "\${command}" in
|
|
170
112
|
${cases}
|
|
171
|
-
*) options="
|
|
113
|
+
*) options="${COMMAND_META_ALIASES.join(' ')}"; value_options=""; file_limit=0 ;;
|
|
172
114
|
esac
|
|
173
|
-
|
|
174
|
-
|
|
115
|
+
positional_count=0
|
|
116
|
+
expect_value=0
|
|
117
|
+
foreign_option=0
|
|
118
|
+
seen_options=""
|
|
119
|
+
for ((i=2; i<COMP_CWORD; i++)); do
|
|
120
|
+
word="\${COMP_WORDS[i]}"
|
|
121
|
+
if (( expect_value )); then expect_value=0; continue; fi
|
|
122
|
+
if [[ "\${word}" == -* ]]; then
|
|
123
|
+
if [[ " \${options} " != *" \${word} "* ]]; then foreign_option=1; continue; fi
|
|
124
|
+
seen_options="\${seen_options} \${word}"
|
|
125
|
+
[[ " \${value_options} " == *" \${word} "* ]] && expect_value=1
|
|
126
|
+
else
|
|
127
|
+
((positional_count+=1))
|
|
128
|
+
fi
|
|
129
|
+
done
|
|
130
|
+
if [[ " \${options} " == *" \${previous} "* ]]; then
|
|
131
|
+
case "\${previous}" in
|
|
132
|
+
${valueCases}
|
|
133
|
+
esac
|
|
134
|
+
case "\${command}:\${previous}" in
|
|
135
|
+
${commandOptionPatterns(['file'])}) COMPREPLY=( $(compgen -f -- "\${current}") ); return ;;
|
|
136
|
+
${integerCases}
|
|
137
|
+
${commandOptionPatterns(['text'])}) COMPREPLY=(); return ;;
|
|
138
|
+
esac
|
|
139
|
+
fi
|
|
140
|
+
case "\${command}:\${positional_count}" in
|
|
141
|
+
${positionalValueCases}
|
|
175
142
|
esac
|
|
143
|
+
if (( file_limit > positional_count && foreign_option == 0 && expect_value == 0 )) && [[ "\${current}" != -* ]]; then
|
|
144
|
+
COMPREPLY=( $(compgen -f -- "\${current}") )
|
|
145
|
+
return
|
|
146
|
+
fi
|
|
176
147
|
COMPREPLY=( $(compgen -W "\${options}" -- "\${current}") )
|
|
177
148
|
}
|
|
178
149
|
complete -F _whoisleuth_completion whoisleuth
|
|
@@ -180,37 +151,77 @@ complete -F _whoisleuth_completion whoisleuth
|
|
|
180
151
|
}
|
|
181
152
|
function zshCompletion() {
|
|
182
153
|
const commandEntries = CLI_COMMANDS.map((command) => `'${command}:${COMMAND_DESCRIPTIONS[command] || command}'`).join(' ');
|
|
183
|
-
const cases = CLI_COMMANDS.map((command) => ` ${command}) options=(${commandOptions(command)}) ;;`).join('\n');
|
|
154
|
+
const cases = CLI_COMMANDS.map((command) => ` ${command}) options=(${commandOptions(command)}); value_options=(${optionNamesByKind(command, ['enum', 'file', 'integer', 'policy_list', 'text']).join(' ')}); file_limit=${maximumFilePositionals(command)} ;;`).join('\n');
|
|
184
155
|
const valueCases = Object.entries(VALUE_OPTIONS).map(([option, values]) => ` ${option}) compadd -- ${values.join(' ')}; return ;;`).join('\n');
|
|
156
|
+
const integerCases = CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options
|
|
157
|
+
.filter((option) => option.valueKind === 'integer')
|
|
158
|
+
.map((option) => {
|
|
159
|
+
const ordinary = integerValues(definition.command, option.option, null).join(' ');
|
|
160
|
+
const conditional = option.integerRanges.find((range) => range.whenOptionPresent !== null);
|
|
161
|
+
if (!conditional)
|
|
162
|
+
return ` ${definition.command}:${option.option}) compadd -- ${ordinary}; return ;;`;
|
|
163
|
+
const narrowed = integerValues(definition.command, option.option, conditional.whenOptionPresent).join(' ');
|
|
164
|
+
return ` ${definition.command}:${option.option}) if [[ " \${seen_options[*]} " == *" ${conditional.whenOptionPresent} "* ]]; then compadd -- ${narrowed}; else compadd -- ${ordinary}; fi; return ;;`;
|
|
165
|
+
})).join('\n');
|
|
166
|
+
const positionalValueCases = CLI_COMMANDS.flatMap((command) => {
|
|
167
|
+
const values = positionalEnumValues(command);
|
|
168
|
+
return values.length > 0
|
|
169
|
+
? [` ${command}:0) if (( foreign_option == 0 && expect_value == 0 )) && [[ "\${words[CURRENT]}" != -* ]]; then compadd -- ${values.join(' ')}; return; fi ;;`]
|
|
170
|
+
: [];
|
|
171
|
+
}).join('\n');
|
|
185
172
|
return `#compdef whoisleuth
|
|
173
|
+
_whoisleuth_direct_lookup_target() {
|
|
174
|
+
local candidate="\${1}"
|
|
175
|
+
[[ "\${candidate}" != -* ]] || return 1
|
|
176
|
+
[[ " ${CLI_COMMANDS.join(' ')} " == *" \${candidate} "* ]] && return 1
|
|
177
|
+
"\${words[1]}" "\${candidate}" --plan --json >/dev/null 2>&1
|
|
178
|
+
}
|
|
186
179
|
_whoisleuth() {
|
|
187
|
-
local command previous
|
|
188
|
-
local -a options commands
|
|
189
|
-
|
|
180
|
+
local command previous word
|
|
181
|
+
local -a options commands value_options seen_options
|
|
182
|
+
integer file_limit=0 positional_count=0 expect_value=0 foreign_option=0 i
|
|
183
|
+
commands=(${commandEntries} ${[
|
|
184
|
+
...HELP_META_ACTION.aliases.map((alias) => `'${alias}:Show help'`),
|
|
185
|
+
...VERSION_META_ACTION.aliases.map((alias) => `'${alias}:Show version'`),
|
|
186
|
+
].join(' ')})
|
|
190
187
|
if (( CURRENT == 2 )); then
|
|
191
188
|
_describe 'command' commands
|
|
192
189
|
return
|
|
193
190
|
fi
|
|
194
191
|
command="\${words[2]}"
|
|
195
192
|
previous="\${words[CURRENT-1]}"
|
|
196
|
-
if
|
|
197
|
-
|
|
198
|
-
return
|
|
199
|
-
fi
|
|
200
|
-
if [[ "\${command}" == "workflow-plan" && CURRENT -eq 3 ]]; then
|
|
201
|
-
compadd -- ${INVESTIGATION_PLAN_RECIPES.join(' ')}
|
|
202
|
-
return
|
|
193
|
+
if _whoisleuth_direct_lookup_target "\${command}"; then
|
|
194
|
+
command="lookup"
|
|
203
195
|
fi
|
|
204
|
-
case "\${previous}" in
|
|
205
|
-
${valueCases}
|
|
206
|
-
${FILE_OPTIONS.join('|')}) _files; return ;;
|
|
207
|
-
${TEXT_OPTIONS.join('|')}) _message 'value'; return ;;
|
|
208
|
-
esac
|
|
209
196
|
case "\${command}" in
|
|
210
197
|
${cases}
|
|
211
|
-
*) options=(
|
|
198
|
+
*) options=(${COMMAND_META_ALIASES.join(' ')}); value_options=(); file_limit=0 ;;
|
|
212
199
|
esac
|
|
213
|
-
|
|
200
|
+
for ((i=3; i<CURRENT; i++)); do
|
|
201
|
+
word="\${words[i]}"
|
|
202
|
+
if (( expect_value )); then expect_value=0; continue; fi
|
|
203
|
+
if [[ "\${word}" == -* ]]; then
|
|
204
|
+
if [[ " \${options[*]} " != *" \${word} "* ]]; then foreign_option=1; continue; fi
|
|
205
|
+
seen_options+=("\${word}")
|
|
206
|
+
[[ " \${value_options[*]} " == *" \${word} "* ]] && expect_value=1
|
|
207
|
+
else
|
|
208
|
+
((positional_count+=1))
|
|
209
|
+
fi
|
|
210
|
+
done
|
|
211
|
+
if [[ " \${options[*]} " == *" \${previous} "* ]]; then
|
|
212
|
+
case "\${previous}" in
|
|
213
|
+
${valueCases}
|
|
214
|
+
esac
|
|
215
|
+
case "\${command}:\${previous}" in
|
|
216
|
+
${commandOptionPatterns(['file'])}) _files; return ;;
|
|
217
|
+
${integerCases}
|
|
218
|
+
${commandOptionPatterns(['text'])}) _message 'value'; return ;;
|
|
219
|
+
esac
|
|
220
|
+
fi
|
|
221
|
+
case "\${command}:\${positional_count}" in
|
|
222
|
+
${positionalValueCases}
|
|
223
|
+
esac
|
|
224
|
+
if (( file_limit > positional_count && foreign_option == 0 && expect_value == 0 )) && [[ "\${words[CURRENT]}" != -* ]]; then
|
|
214
225
|
_files
|
|
215
226
|
return
|
|
216
227
|
fi
|
|
@@ -224,84 +235,359 @@ fi
|
|
|
224
235
|
`;
|
|
225
236
|
}
|
|
226
237
|
function fishCompletion() {
|
|
227
|
-
const commandLines = CLI_COMMANDS.map((command) => (`complete -c whoisleuth -n '__fish_use_subcommand' -a '${command}' -d '${COMMAND_DESCRIPTIONS[command]}'
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
const commandLines = CLI_COMMANDS.map((command) => (`complete -c whoisleuth -n '__fish_use_subcommand' -a '${command}' -d '${COMMAND_DESCRIPTIONS[command]}'`));
|
|
239
|
+
const optionGroups = new Map();
|
|
240
|
+
for (const definition of CLI_COMMAND_REGISTRY) {
|
|
241
|
+
for (const option of definition.grammar.options) {
|
|
242
|
+
const key = `${option.option}\u0000${option.arity}\u0000${option.valueKind === 'file'}`;
|
|
243
|
+
const group = optionGroups.get(key) ?? {
|
|
244
|
+
option: option.option,
|
|
245
|
+
arity: option.arity,
|
|
246
|
+
file: option.valueKind === 'file',
|
|
247
|
+
commands: [],
|
|
248
|
+
};
|
|
249
|
+
group.commands.push(definition.command);
|
|
250
|
+
optionGroups.set(key, group);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const optionLines = [...optionGroups.values()].map((group) => {
|
|
254
|
+
const condition = `__whoisleuth_command_is ${group.commands.join(' ')}`
|
|
255
|
+
+ (group.commands.includes('lookup') ? '; or __whoisleuth_direct_lookup_target' : '');
|
|
256
|
+
return `complete -c whoisleuth -n '${condition}'${group.option === HELP_LONG_ALIAS ? ` -s ${HELP_SHORT_ALIAS.slice(1)}` : ''} -l ${group.option.slice(2)}${group.arity === 1 ? ' -r' : ''}${group.file ? ' -F' : ''}`;
|
|
257
|
+
});
|
|
258
|
+
const valueLines = Object.entries(VALUE_OPTIONS).flatMap(([option, values]) => {
|
|
259
|
+
const commands = CLI_COMMANDS.filter((command) => commandOptionSpec(command, option) !== null);
|
|
260
|
+
const namedCondition = commands.length === CLI_COMMANDS.length
|
|
261
|
+
? `__fish_prev_arg_in ${option}`
|
|
262
|
+
: `__whoisleuth_command_is ${commands.join(' ')}; and __fish_prev_arg_in ${option}`;
|
|
263
|
+
const lines = commands.length > 0
|
|
264
|
+
? [`complete -c whoisleuth -n '${namedCondition}' -a '${values.join(' ')}'`]
|
|
265
|
+
: [];
|
|
266
|
+
if (commands.includes('lookup')) {
|
|
267
|
+
lines.push(`complete -c whoisleuth -n '__whoisleuth_direct_lookup_target; and __fish_prev_arg_in ${option}' -a '${values.join(' ')}'`);
|
|
268
|
+
}
|
|
269
|
+
return lines;
|
|
270
|
+
});
|
|
271
|
+
const integerLines = CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options
|
|
272
|
+
.filter((option) => option.valueKind === 'integer')
|
|
273
|
+
.flatMap((option) => option.integerRanges.map((range) => {
|
|
274
|
+
const qualifiers = [
|
|
275
|
+
`__fish_prev_arg_in ${option.option}`,
|
|
276
|
+
`and __whoisleuth_command_is ${definition.command}`,
|
|
277
|
+
...(range.whenOptionPresent
|
|
278
|
+
? [`and __whoisleuth_seen ${definition.command} ${range.whenOptionPresent}`]
|
|
279
|
+
: option.integerRanges.length > 1
|
|
280
|
+
? [`and not __whoisleuth_seen ${definition.command} ${option.integerRanges.find((candidate) => candidate.whenOptionPresent)?.whenOptionPresent}`]
|
|
281
|
+
: []),
|
|
282
|
+
].join('; ');
|
|
283
|
+
return `complete -c whoisleuth -n '${qualifiers}' -a '(__whoisleuth_integer_values ${range.minimum} ${range.maximum})'`;
|
|
284
|
+
})));
|
|
285
|
+
const positionalCases = [...new Set([
|
|
286
|
+
...FILE_POSITIONAL_COMMANDS,
|
|
287
|
+
...CLI_COMMANDS.filter((command) => positionalEnumValues(command).length > 0),
|
|
288
|
+
...CLI_COMMAND_REGISTRY.filter((definition) => definition.grammar.options
|
|
289
|
+
.some((option) => option.integerRanges.some((range) => range.whenOptionPresent !== null)))
|
|
290
|
+
.map((definition) => definition.command),
|
|
291
|
+
])].map((command) => {
|
|
292
|
+
const definition = commandDefinition(command);
|
|
293
|
+
const commandValueOptions = definition.grammar.options
|
|
294
|
+
.filter((option) => option.scope === 'command' && option.arity === 1)
|
|
295
|
+
.map((option) => option.option);
|
|
296
|
+
const positioning = FILE_POSITIONAL_COMMANDS.includes(command)
|
|
297
|
+
|| positionalEnumValues(command).length > 0;
|
|
298
|
+
return ` case ${command}\n${positioning
|
|
299
|
+
? ` set options $options ${definition.completion.options.join(' ')}\n`
|
|
300
|
+
: ' set strict 0\n'} set value_options $value_options ${commandValueOptions.join(' ')}\n set limit ${maximumFilePositionals(command)}`;
|
|
301
|
+
}).join('\n');
|
|
302
|
+
const positionalEnumLines = CLI_COMMANDS.flatMap((command) => {
|
|
303
|
+
const values = positionalEnumValues(command);
|
|
304
|
+
return values.length > 0
|
|
305
|
+
? [`complete -c whoisleuth -n '__whoisleuth_position_is 0 ${command}' -a '${values.join(' ')}'`]
|
|
306
|
+
: [];
|
|
307
|
+
});
|
|
238
308
|
return `# WHOISleuth fish completion
|
|
309
|
+
function __whoisleuth_clear_direct_lookup_cache --on-event fish_prompt
|
|
310
|
+
set -e __whoisleuth_direct_lookup_cache_key
|
|
311
|
+
set -e __whoisleuth_direct_lookup_cache_status
|
|
312
|
+
end
|
|
313
|
+
function __whoisleuth_direct_lookup_target
|
|
314
|
+
set -l words (commandline -opc)
|
|
315
|
+
if test (count \$words) -lt 2
|
|
316
|
+
return 1
|
|
317
|
+
end
|
|
318
|
+
set -l first \$words[2]
|
|
319
|
+
string match -q -- '-*' \$first; and return 1
|
|
320
|
+
contains -- \$first ${CLI_COMMANDS.join(' ')}; and return 1
|
|
321
|
+
set -l cache_key "\$words[1]:\$first"
|
|
322
|
+
if test "\$__whoisleuth_direct_lookup_cache_key" = "\$cache_key"
|
|
323
|
+
return \$__whoisleuth_direct_lookup_cache_status
|
|
324
|
+
end
|
|
325
|
+
\$words[1] \$first --plan --json >/dev/null 2>/dev/null
|
|
326
|
+
set -l result \$status
|
|
327
|
+
set -g __whoisleuth_direct_lookup_cache_key \$cache_key
|
|
328
|
+
set -g __whoisleuth_direct_lookup_cache_status \$result
|
|
329
|
+
return \$result
|
|
330
|
+
end
|
|
331
|
+
function __whoisleuth_command_is
|
|
332
|
+
set -l words (commandline -opc)
|
|
333
|
+
test (count \$words) -ge 2; or return 1
|
|
334
|
+
contains -- \$words[2] $argv
|
|
335
|
+
end
|
|
336
|
+
function __whoisleuth_integer_values
|
|
337
|
+
set -l value $argv[1]
|
|
338
|
+
while test $value -le $argv[2]
|
|
339
|
+
echo $value
|
|
340
|
+
set value (math $value + 1)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
function __whoisleuth_position_state
|
|
344
|
+
set -l expected_command $argv[1]
|
|
345
|
+
set -l words (commandline -opc)
|
|
346
|
+
test (count $words) -ge 2; or return 1
|
|
347
|
+
set -l command $words[2]
|
|
348
|
+
test "$command" = "$expected_command"; or return 1
|
|
349
|
+
set -l options ${COMMAND_META_ALIASES.join(' ')} --output --force --config --profile --palette
|
|
350
|
+
set -l value_options --output --config --profile --palette
|
|
351
|
+
set -l limit 0
|
|
352
|
+
set -l strict 1
|
|
353
|
+
switch $command
|
|
354
|
+
${positionalCases}
|
|
355
|
+
case '*'
|
|
356
|
+
return 1
|
|
357
|
+
end
|
|
358
|
+
set -l expect_value 0
|
|
359
|
+
set -l positional_count 0
|
|
360
|
+
set -l seen
|
|
361
|
+
for word in $words[3..-1]
|
|
362
|
+
if test $expect_value -eq 1
|
|
363
|
+
set expect_value 0
|
|
364
|
+
continue
|
|
365
|
+
end
|
|
366
|
+
if string match -q -- '-*' $word
|
|
367
|
+
if test $strict -eq 1
|
|
368
|
+
contains -- $word $options; or return 1
|
|
369
|
+
end
|
|
370
|
+
set -a seen $word
|
|
371
|
+
contains -- $word $value_options; and set expect_value 1
|
|
372
|
+
else
|
|
373
|
+
set positional_count (math $positional_count + 1)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
test $expect_value -eq 0; or return 1
|
|
377
|
+
echo $positional_count $limit $seen
|
|
378
|
+
end
|
|
379
|
+
function __whoisleuth_position_is
|
|
380
|
+
set -l state (__whoisleuth_position_state $argv[2]); or return 1
|
|
381
|
+
test $state[1] -eq $argv[1]
|
|
382
|
+
end
|
|
383
|
+
function __whoisleuth_file_position
|
|
384
|
+
set -l words (commandline -opc)
|
|
385
|
+
test (count $words) -ge 2; or return 1
|
|
386
|
+
set -l state (__whoisleuth_position_state $words[2]); or return 1
|
|
387
|
+
test $state[2] -gt $state[1]; or return 1
|
|
388
|
+
not string match -qr -- ^- (commandline -ct)
|
|
389
|
+
end
|
|
390
|
+
function __whoisleuth_seen
|
|
391
|
+
set -l s (__whoisleuth_position_state $argv[1]); or return 1
|
|
392
|
+
contains -- $argv[2] $s[3..-1]
|
|
393
|
+
end
|
|
239
394
|
complete -c whoisleuth -f
|
|
240
|
-
complete -c whoisleuth -n '__fish_use_subcommand' -l
|
|
241
|
-
complete -c whoisleuth -n '__fish_use_subcommand' -l version
|
|
395
|
+
${[HELP_META_ACTION, VERSION_META_ACTION].map((action) => (`complete -c whoisleuth -n '__fish_use_subcommand' -s ${shortMetaAlias(action).slice(1)} -l ${longMetaAlias(action).slice(2)}`)).join('\n')}
|
|
242
396
|
${commandLines.join('\n')}
|
|
243
|
-
|
|
244
|
-
complete -c whoisleuth -n '
|
|
245
|
-
${commonOptionLines.join('\n')}
|
|
397
|
+
${positionalEnumLines.join('\n')}
|
|
398
|
+
complete -c whoisleuth -n '__whoisleuth_file_position' -F
|
|
246
399
|
${optionLines.join('\n')}
|
|
247
400
|
${valueLines.join('\n')}
|
|
401
|
+
${integerLines.join('\n')}
|
|
248
402
|
`;
|
|
249
403
|
}
|
|
250
404
|
function powershellCompletion() {
|
|
251
405
|
const commandOptions = Object.fromEntries(CLI_COMMANDS.map((command) => [
|
|
252
406
|
command,
|
|
253
|
-
|
|
407
|
+
optionNames(command),
|
|
254
408
|
]));
|
|
409
|
+
const positionalValuesByCommand = Object.fromEntries(CLI_COMMANDS.flatMap((command) => {
|
|
410
|
+
const values = positionalEnumValues(command);
|
|
411
|
+
return values.length > 0 ? [[`${command}:0`, values]] : [];
|
|
412
|
+
}));
|
|
413
|
+
const integerRanges = Object.fromEntries(CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options
|
|
414
|
+
.filter((option) => option.valueKind === 'integer')
|
|
415
|
+
.map((option) => {
|
|
416
|
+
const range = option.integerRanges.find((candidate) => candidate.whenOptionPresent === null);
|
|
417
|
+
if (!range)
|
|
418
|
+
throw new Error(`Missing ordinary completion range for ${definition.command} ${option.option}.`);
|
|
419
|
+
return [`${definition.command}:${option.option}`, Object.freeze({
|
|
420
|
+
minimum: range.minimum,
|
|
421
|
+
maximum: range.maximum,
|
|
422
|
+
})];
|
|
423
|
+
})));
|
|
424
|
+
const conditionalIntegerRanges = Object.fromEntries(CLI_COMMAND_REGISTRY.flatMap((definition) => definition.grammar.options
|
|
425
|
+
.filter((option) => option.valueKind === 'integer')
|
|
426
|
+
.flatMap((option) => option.integerRanges
|
|
427
|
+
.filter((range) => range.whenOptionPresent !== null)
|
|
428
|
+
.map((range) => [`${definition.command}:${option.option}`, Object.freeze({
|
|
429
|
+
option: range.whenOptionPresent,
|
|
430
|
+
minimum: range.minimum,
|
|
431
|
+
maximum: range.maximum,
|
|
432
|
+
})]))));
|
|
255
433
|
return `# WHOISleuth PowerShell completion
|
|
256
434
|
Register-ArgumentCompleter -Native -CommandName whoisleuth -ScriptBlock {
|
|
257
435
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
436
|
+
$noMatch = { [System.Management.Automation.CompletionResult]::new(' ', 'no matches', 'ParameterValue', 'No completion available') }
|
|
258
437
|
$commands = @(${CLI_COMMANDS.map((command) => `'${command}'`).join(', ')})
|
|
259
438
|
$options = @{
|
|
260
439
|
${Object.entries(commandOptions).map(([command, options]) => ` '${command}' = @(${options.map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
261
440
|
}
|
|
262
441
|
$values = @{
|
|
263
442
|
${Object.entries(VALUE_OPTIONS).map(([option, values]) => ` '${option}' = @(${values.map((value) => `'${value}'`).join(', ')})`).join('\n')}
|
|
264
|
-
'completion' = @('bash', 'zsh', 'fish', 'powershell')
|
|
265
|
-
'workflow-plan' = @(${INVESTIGATION_PLAN_RECIPES.map((value) => `'${value}'`).join(', ')})
|
|
266
443
|
}
|
|
267
|
-
$
|
|
268
|
-
|
|
444
|
+
$fileOptions = @{
|
|
445
|
+
${CLI_COMMANDS.map((command) => ` '${command}' = @(${optionNamesByKind(command, ['file']).map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
446
|
+
}
|
|
447
|
+
$textOptions = @{
|
|
448
|
+
${CLI_COMMANDS.map((command) => ` '${command}' = @(${optionNamesByKind(command, ['text']).map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
449
|
+
}
|
|
450
|
+
$valueOptions = @{
|
|
451
|
+
${CLI_COMMANDS.map((command) => ` '${command}' = @(${optionNamesByKind(command, ['enum', 'file', 'integer', 'policy_list', 'text']).map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
452
|
+
}
|
|
453
|
+
$fileLimits = @{
|
|
454
|
+
${FILE_POSITIONAL_COMMANDS.map((command) => ` '${command}' = ${maximumFilePositionals(command)}`).join('\n')}
|
|
455
|
+
}
|
|
456
|
+
$positionValues = @{
|
|
457
|
+
${Object.entries(positionalValuesByCommand).map(([key, values]) => ` '${key}' = @(${values.map((value) => `'${value}'`).join(', ')})`).join('\n')}
|
|
458
|
+
}
|
|
459
|
+
$integerRanges = @{
|
|
460
|
+
${Object.entries(integerRanges).map(([key, range]) => ` '${key}' = @(${range.minimum}, ${range.maximum})`).join('\n')}
|
|
461
|
+
}
|
|
462
|
+
$conditionalIntegerRanges = @{
|
|
463
|
+
${Object.entries(conditionalIntegerRanges).map(([key, range]) => ` '${key}' = @('${range.option}', ${range.minimum}, ${range.maximum})`).join('\n')}
|
|
464
|
+
}
|
|
465
|
+
$elements = @($commandAst.CommandElements | ForEach-Object {
|
|
466
|
+
if ($_ -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $_.Value }
|
|
467
|
+
else { $_.Extent.Text }
|
|
468
|
+
})
|
|
269
469
|
$command = if ($elements.Count -gt 1) { $elements[1] } else { '' }
|
|
270
|
-
$
|
|
271
|
-
if ($
|
|
272
|
-
|
|
470
|
+
$previousIndex = if ($wordToComplete) { $elements.Count - 2 } else { $elements.Count - 1 }
|
|
471
|
+
$previous = if ($previousIndex -ge 0) { $elements[$previousIndex] } else { '' }
|
|
472
|
+
$rootCompletion = $elements.Count -eq 1 -or (
|
|
473
|
+
$elements.Count -eq 2 -and $cursorPosition -le $commandAst.Extent.EndOffset
|
|
474
|
+
)
|
|
475
|
+
$directLookup = $false
|
|
476
|
+
if ($command -notmatch '^-' -and $elements.Count -gt 1 -and $commands -notcontains $command) {
|
|
477
|
+
& $elements[0] $command '--plan' '--json' *> $null
|
|
478
|
+
$directLookup = $LASTEXITCODE -eq 0
|
|
479
|
+
}
|
|
480
|
+
if ($directLookup) { $command = 'lookup' }
|
|
481
|
+
$commandKnown = $options.ContainsKey($command)
|
|
482
|
+
$activeOptions = if ($commandKnown) { $options[$command] } else { @(${COMMAND_META_ALIASES.map((alias) => `'${alias}'`).join(', ')}) }
|
|
483
|
+
$previousOwned = $activeOptions -contains $previous
|
|
484
|
+
if ($previousOwned -and $fileOptions.ContainsKey($command) -and $fileOptions[$command] -contains $previous) {
|
|
485
|
+
$files = @(Get-ChildItem -Path "${'$'}wordToComplete*" -File -ErrorAction SilentlyContinue)
|
|
486
|
+
$files | ForEach-Object {
|
|
273
487
|
[System.Management.Automation.CompletionResult]::new($_.FullName, $_.Name, 'ProviderItem', $_.FullName)
|
|
274
488
|
}
|
|
489
|
+
if ($files.Count -eq 0) { & $noMatch }
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
if ($previousOwned -and $textOptions.ContainsKey($command) -and $textOptions[$command] -contains $previous) {
|
|
493
|
+
$completionText = if ([string]::IsNullOrEmpty($wordToComplete)) { ' ' } else { [string]$wordToComplete }
|
|
494
|
+
$listItemText = if ([string]::IsNullOrEmpty($wordToComplete)) { 'value' } else { [string]$wordToComplete }
|
|
495
|
+
[System.Management.Automation.CompletionResult]::new($completionText, $listItemText, 'ParameterValue', 'Enter a value')
|
|
275
496
|
return
|
|
276
497
|
}
|
|
277
|
-
$
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
$
|
|
283
|
-
|
|
284
|
-
$
|
|
285
|
-
|
|
286
|
-
|
|
498
|
+
$positionCount = 0
|
|
499
|
+
$expectValue = $false
|
|
500
|
+
$foreignOption = $false
|
|
501
|
+
$seenOptions = @()
|
|
502
|
+
for ($index = 2; $index -le $previousIndex; $index += 1) {
|
|
503
|
+
$word = $elements[$index]
|
|
504
|
+
if ($expectValue) { $expectValue = $false; continue }
|
|
505
|
+
if ($word.StartsWith('-')) {
|
|
506
|
+
if ($activeOptions -notcontains $word) { $foreignOption = $true; continue }
|
|
507
|
+
$seenOptions += $word
|
|
508
|
+
if ($valueOptions.ContainsKey($command) -and $valueOptions[$command] -contains $word) { $expectValue = $true }
|
|
509
|
+
} else {
|
|
510
|
+
$positionCount += 1
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
$integerKey = "${'$'}{command}:${'$'}previous"
|
|
514
|
+
if ($previousOwned -and $integerRanges.ContainsKey($integerKey)) {
|
|
515
|
+
$range = $integerRanges[$integerKey]
|
|
516
|
+
if ($conditionalIntegerRanges.ContainsKey($integerKey)) {
|
|
517
|
+
$condition = $conditionalIntegerRanges[$integerKey]
|
|
518
|
+
if ($seenOptions -contains $condition[0]) { $range = @($condition[1], $condition[2]) }
|
|
519
|
+
}
|
|
520
|
+
$matched = $false
|
|
521
|
+
foreach ($number in $range[0]..$range[1]) {
|
|
522
|
+
$candidate = [string]$number
|
|
523
|
+
if ($candidate.StartsWith($wordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
524
|
+
[System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
|
|
525
|
+
$matched = $true
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (-not $matched) { & $noMatch }
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
if ($previousOwned -and $values.ContainsKey($previous)) {
|
|
532
|
+
$matched = $false
|
|
533
|
+
foreach ($candidate in $values[$previous]) {
|
|
534
|
+
if ($candidate.StartsWith($wordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
535
|
+
[System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
|
|
536
|
+
$matched = $true
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (-not $matched) { & $noMatch }
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
$positionKey = "${'$'}{command}:${'$'}positionCount"
|
|
543
|
+
if ($positionValues.ContainsKey($positionKey) -and -not $foreignOption -and -not $expectValue -and -not $wordToComplete.StartsWith('-')) {
|
|
544
|
+
$matched = $false
|
|
545
|
+
foreach ($candidate in $positionValues[$positionKey]) {
|
|
546
|
+
if ($candidate.StartsWith($wordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
547
|
+
[System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
|
|
548
|
+
$matched = $true
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (-not $matched) { & $noMatch }
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
if ($fileLimits.ContainsKey($command) -and $fileLimits[$command] -gt $positionCount -and -not $foreignOption -and -not $expectValue -and -not $wordToComplete.StartsWith('-')) {
|
|
555
|
+
$files = @(Get-ChildItem -Path "${'$'}wordToComplete*" -File -ErrorAction SilentlyContinue)
|
|
556
|
+
$files | ForEach-Object {
|
|
557
|
+
[System.Management.Automation.CompletionResult]::new($_.FullName, $_.Name, 'ProviderItem', $_.FullName)
|
|
558
|
+
}
|
|
559
|
+
if ($files.Count -eq 0) { & $noMatch }
|
|
560
|
+
return
|
|
561
|
+
}
|
|
562
|
+
$candidates = if ($rootCompletion -and -not $directLookup) {
|
|
563
|
+
@($commands) + @(${ROOT_META_ALIASES.map((alias) => `'${alias}'`).join(', ')})
|
|
564
|
+
} elseif ($commandKnown) {
|
|
565
|
+
$activeOptions
|
|
287
566
|
} else {
|
|
288
|
-
@('
|
|
567
|
+
@(${COMMAND_META_ALIASES.map((alias) => `'${alias}'`).join(', ')})
|
|
289
568
|
}
|
|
569
|
+
$matched = $false
|
|
290
570
|
foreach ($candidate in $candidates) {
|
|
291
571
|
if ($candidate.StartsWith($wordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
292
572
|
[System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
|
|
573
|
+
$matched = $true
|
|
293
574
|
}
|
|
294
575
|
}
|
|
576
|
+
if (-not $matched) { & $noMatch }
|
|
295
577
|
}
|
|
296
578
|
`;
|
|
297
579
|
}
|
|
298
580
|
function buildShellCompletion(shell) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
581
|
+
const script = shell === 'bash'
|
|
582
|
+
? bashCompletion()
|
|
583
|
+
: shell === 'zsh'
|
|
584
|
+
? zshCompletion()
|
|
585
|
+
: shell === 'fish'
|
|
586
|
+
? fishCompletion()
|
|
587
|
+
: powershellCompletion();
|
|
588
|
+
if (Buffer.byteLength(script, 'utf8') > MAX_CLI_COMPLETION_BYTES) {
|
|
589
|
+
throw new RangeError(`Generated completion is limited to ${MAX_CLI_COMPLETION_BYTES} UTF-8 bytes.`);
|
|
590
|
+
}
|
|
591
|
+
return script;
|
|
306
592
|
}
|
|
307
|
-
export { buildShellCompletion, };
|
|
593
|
+
export { MAX_CLI_COMPLETION_BYTES, buildShellCompletion, };
|