@slicedearth/whoisleuth-cli 1.47.4 → 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/README.md +74 -177
- package/SECURITY.md +6 -3
- package/bin/whoisleuth.mjs +2 -1
- package/cli/archive-inspect.mjs +28 -10
- package/cli/arguments.mjs +204 -174
- package/cli/artifact-structure.mjs +837 -163
- package/cli/artifact-verify.mjs +90 -61
- package/cli/bounded-json.mjs +1 -1
- package/cli/bulk-checkpoint.mjs +33 -20
- package/cli/bulk.mjs +9 -2
- package/cli/case-pack.mjs +180 -576
- 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 +910 -92
- package/cli/completion.mjs +504 -226
- package/cli/config-profile.mjs +13 -3
- package/cli/discover.mjs +7 -1
- package/cli/discovery-observation-snapshot.mjs +24 -28
- package/cli/discovery-scan-command-runner.mjs +9 -9
- package/cli/discovery-scan.mjs +17 -3
- package/cli/discovery-snapshot.mjs +15 -7
- package/cli/domain-control-monitor.mjs +203 -30
- package/cli/domain-control-observations.mjs +15 -21
- package/cli/errors.mjs +8 -3
- package/cli/evidence-signing.mjs +10 -20
- package/cli/external-observation-mapping.mjs +3 -3
- package/cli/formatters/json.mjs +20 -32
- package/cli/formatters/terminal.mjs +583 -22
- package/cli/input.mjs +7 -2
- package/cli/interactive-launcher.mjs +226 -0
- package/cli/interchange-report.mjs +19 -19
- package/cli/investigation-manifest.mjs +1 -1
- package/cli/investigation-plan.mjs +167 -16
- package/cli/investigation-run.mjs +9 -1
- 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 +94 -31
- package/cli/lookup-diff.mjs +2 -2
- package/cli/lookup-plan.mjs +1 -0
- package/cli/mail-review.mjs +206 -94
- package/cli/manual.mjs +5 -5
- package/cli/offline-evidence-review.mjs +18 -7
- package/cli/open-asset-model-bridge.mjs +1 -1
- package/cli/output-file.mjs +70 -14
- package/cli/page-compare.mjs +11 -6
- package/cli/progress-events.mjs +2 -1
- package/cli/progress.mjs +3 -3
- package/cli/registry-cohort.mjs +9 -51
- package/cli/registry-doctor.mjs +67 -28
- package/cli/retained-artifact-diff.mjs +7 -8
- package/cli/risk-calibration.mjs +85 -38
- package/cli/runner.mjs +189 -75
- package/cli/saved-lookup.mjs +424 -57
- package/cli/sharing-review.mjs +6 -1
- package/cli/source-reliability.mjs +75 -36
- package/cli/terminal-presentation.mjs +43 -18
- package/docs/cli-reference.md +125 -1708
- package/docs/cli.md +86 -254
- package/frontend/src/lib/analysis/domain-control-manifest-core.js +3 -145
- package/frontend/src/lib/analysis/domain-control-records.js +3 -88
- package/lib/availability.mjs +58 -40
- package/lib/bounded-contract-normalizers.mjs +2 -4
- package/lib/bounded-file.mjs +78 -4
- package/lib/bounded-json.mjs +51 -13
- package/lib/browser-library-profile.mjs +1 -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 +35 -17
- package/lib/ct-query.mjs +3 -3
- package/lib/ct-search.mjs +37 -21
- package/lib/distributed-operation-budget.mjs +4 -2
- package/lib/dns-delegation-health.mjs +9 -6
- package/lib/dns-intelligence.mjs +1 -1
- package/lib/dnssec-chain-validation.mjs +2 -3
- package/lib/dnssec-evidence-validation.mjs +13 -7
- package/lib/domain-assurance.mjs +12 -12
- package/lib/domain-change-packet.mjs +1 -1
- package/lib/domain-control-flight-recorder.mjs +442 -78
- package/lib/domain-control-manifest.mjs +314 -59
- package/lib/domain-posture-analysis.mjs +5 -2
- package/lib/encrypted-dns-contract.mjs +2 -3
- package/lib/evidence-export.mjs +518 -36
- package/lib/external-intelligence-risk.mjs +26 -10
- 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-intelligence.mjs +331 -2
- package/lib/interchange-fidelity-registry.mjs +28 -26
- package/lib/lookup-cache.mjs +6 -4
- package/lib/lookup.mjs +25 -2
- package/lib/observed-network-context.mjs +1 -1
- package/lib/page-fingerprints.mjs +2 -2
- package/lib/page-role-profile.mjs +1 -1
- package/lib/portable-generator.mjs +8 -0
- package/lib/provider-policy-admission.mjs +3 -1
- package/lib/rdap-bootstrap.mjs +76 -16
- package/lib/rdap-client.mjs +20 -12
- package/lib/rdap-normalization.mjs +306 -75
- 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-insights.mjs +7 -0
- package/lib/response-policy.mjs +27 -4
- package/lib/risk-calibration-summary.mjs +35 -33
- package/lib/risk-scoring.mjs +4 -3
- package/lib/safe-fetch.mjs +62 -40
- package/lib/security-txt.mjs +1 -1
- package/lib/smtp-transport-review.mjs +8 -5
- 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 +3 -5
- package/lib/threat-intelligence-types.mjs +3 -0
- package/lib/threatfox-intelligence.mjs +7 -3
- package/lib/tls-intelligence.mjs +4 -4
- 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/{frontend/src/lib/analysis/case-brand-profile-references.js → packages/cases/case-brand-profile-references.mjs} +2 -2
- package/{frontend/src/lib/analysis/case-evidence-model.js → packages/cases/case-evidence-model.mjs} +35 -14
- package/{frontend/src/lib/analysis/case-investigation-branch-model.js → packages/cases/case-investigation-branch-model.mjs} +10 -12
- 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 -21
- 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} +72 -29
- package/{frontend/src/lib/analysis/case-report.js → packages/cases/case-report.mjs} +108 -14
- 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/{frontend/src/lib/analysis/comparison-ledger-bulk.js → packages/comparison/comparison-ledger-bulk.mjs} +2 -2
- package/{frontend/src/lib/analysis/comparison-ledger-contract.js → packages/comparison/comparison-ledger-contract.mjs} +4 -4
- package/{frontend/src/lib/analysis/comparison-ledger-json.js → packages/comparison/comparison-ledger-json.mjs} +1 -0
- package/{frontend/src/lib/analysis/comparison-ledger-serialization.js → packages/comparison/comparison-ledger-serialization.mjs} +1 -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/{frontend/src/lib/analysis/artifact-integrity.js → packages/evidence/artifact-integrity.mjs} +13 -11
- 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} +24 -22
- package/packages/interchange/retained-text.mjs +8 -0
- package/{frontend/src/lib/analysis/acquisition-decision-packet.js → packages/investigation/acquisition-decision-packet.mjs} +9 -6
- package/{frontend/src/lib/analysis/bulk-domain-comparison.js → packages/investigation/bulk-domain-comparison.mjs} +5 -6
- package/{frontend/src/lib/analysis/bulk-mail-exposure.js → packages/investigation/bulk-mail-exposure.mjs} +4 -6
- package/{frontend/src/lib/analysis/bulk-review-export.js → packages/investigation/bulk-review-export.mjs} +5 -5
- package/packages/investigation/bulk-review-policy.mjs +1 -0
- package/{frontend/src/lib/analysis/investigation-capsule.js → packages/investigation/investigation-capsule.mjs} +30 -32
- 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/{frontend/src/lib/analysis/lookup-claim-passport.js → packages/investigation/lookup-claim-passport.mjs} +6 -10
- package/{frontend/src/lib/analysis/lookup-claim-readiness.js → packages/investigation/lookup-claim-readiness.mjs} +9 -2
- 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} +70 -47
- package/{frontend/src/lib/analysis/bulk-review-model.js → packages/workspace/bulk-review-model.mjs} +13 -14
- package/{frontend/src/lib/analysis/bulk-session-model.js → packages/workspace/bulk-session-model.mjs} +33 -59
- package/{frontend/src/lib/analysis/bulk-sort.js → packages/workspace/bulk-sort.mjs} +8 -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} +8 -45
- package/{frontend/src/lib/analysis/investigation-template-model.js → packages/workspace/investigation-template-model.mjs} +18 -21
- 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} +13 -13
- 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} +110 -89
- package/third-party-notices.txt +14 -14
- 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 -305
- package/frontend/src/lib/analysis/case-record-model.js +0 -9
- package/frontend/src/lib/analysis/case-response-model.js +0 -728
- package/frontend/src/lib/analysis/case-response-packet.js +0 -583
- package/frontend/src/lib/analysis/lookup-decision-support.js +0 -542
- package/frontend/src/lib/analysis/lookup-investigation-brief.js +0 -153
- package/frontend/src/lib/analysis/lookup-presentation.js +0 -120
- 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 -255
- 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 -131
- package/frontend/src/lib/analysis/visualization-models.js +0 -338
- package/frontend/src/lib/analysis/visualization-time-series.js +0 -185
- package/lib/bounded-json-response.mjs +0 -170
- package/lib/lookup-response-contract.mjs +0 -1034
- package/lib/observation.mjs +0 -115
- /package/{frontend/src/lib/analysis/opaque-reference-id.js → packages/cases/opaque-reference-id.mjs} +0 -0
- /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,186 +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
|
-
'dnssec-validate': ['--resolver', '--trust-anchor', '--owned-or-authorized', '--json', '--quiet', '--no-color'],
|
|
18
|
-
'mail-transport': ['--resolver', '--trust-anchor', '--owned-or-authorized', '--active-probe', '--json', '--quiet', '--no-color'],
|
|
19
|
-
'registry-support': ['--json', '--quiet', '--no-color'],
|
|
20
|
-
'registry-doctor': ['--json', '--quiet', '--no-color'],
|
|
21
|
-
'registry-cohort': ['--json', '--quiet', '--no-color'],
|
|
22
|
-
'registry-scaffold': ['--profile', '--suffix', '--scenario'],
|
|
23
|
-
'risk-calibrate': ['--json', '--summary-json', '--quiet', '--no-color'],
|
|
24
|
-
'lookalike-calibrate': ['--json', '--quiet', '--no-color'],
|
|
25
|
-
'verify-artifact': ['--passphrase-file', '--manifest', '--manifest-entry', '--json', '--strict-exit', '--quiet', '--no-color'],
|
|
26
|
-
'interchange-report': ['--passphrase-file', '--json', '--quiet', '--no-color'],
|
|
27
|
-
'inspect-archive': ['--passphrase-file', '--search', '--require-match', '--reveal', '--expect-content-digest', '--json', '--quiet', '--no-color'],
|
|
28
|
-
'sign-artifact': ['--private-key-file'],
|
|
29
|
-
'verify-signature': ['--public-key-file', '--json', '--quiet', '--no-color'],
|
|
30
|
-
'source-report': ['--json', '--quiet', '--no-color'],
|
|
31
|
-
compare: ['--json', '--quiet', '--no-color'],
|
|
32
|
-
'page-compare': ['--json', '--quiet', '--no-color'],
|
|
33
|
-
'mail-review': ['--json', '--quiet', '--no-color'],
|
|
34
|
-
'review-evidence': ['--mmdb', '--json', '--strict-exit', '--quiet', '--no-color'],
|
|
35
|
-
brief: ['--json', '--quiet', '--no-color'],
|
|
36
|
-
'case-pack': ['--audience', '--reviewed', '--json', '--quiet', '--no-color'],
|
|
37
|
-
'domain-control': ['--json', '--quiet', '--no-color'],
|
|
38
|
-
'monitor-once': ['--previous', '--limit', '--concurrency', '--fail-on', '--json', '--junit', '--quiet', '--no-color'],
|
|
39
|
-
assurance: ['--json', '--quiet', '--no-color'],
|
|
40
|
-
'change-packet': ['--json', '--quiet', '--no-color'],
|
|
41
|
-
'sharing-review': ['--marking', '--recipient-scope', '--purpose', '--human-reviewed', '--personal-data-reviewed', '--redactions-confirmed', '--json', '--quiet', '--no-color'],
|
|
42
|
-
'workflow-plan': ['--json', '--quiet', '--no-color'],
|
|
43
|
-
'workflow-run': ['--approve-network', '--resume', '--json', '--quiet', '--no-color'],
|
|
44
|
-
diff: ['--left-session', '--right-session', '--json', '--quiet', '--no-color'],
|
|
45
|
-
reconcile: ['--json', '--quiet', '--no-color'],
|
|
46
|
-
timeline: ['--json', '--quiet', '--no-color'],
|
|
47
|
-
export: ['--markdown', '--html', '--compact', '--no-attribution'],
|
|
48
|
-
completion: [],
|
|
49
|
-
commands: ['--json', '--quiet', '--no-color'],
|
|
50
|
-
doctor: ['--network', '--json', '--quiet', '--no-color'],
|
|
51
|
-
manual: [],
|
|
52
|
-
});
|
|
53
|
-
const COMMAND_DESCRIPTIONS = Object.freeze({
|
|
54
|
-
manifest: 'Build an evidence manifest offline',
|
|
55
|
-
'map-observations': 'Normalise passive DNS observations offline',
|
|
56
|
-
'oam-export': 'Export an observation-archive map offline',
|
|
57
|
-
lookup: 'Collect one domain, IP, or ASN',
|
|
58
|
-
bulk: 'Run bounded multi-target collection',
|
|
59
|
-
'ct-search': 'Search certificate observations',
|
|
60
|
-
'ct-intake': 'Normalise certificate observations offline',
|
|
61
|
-
discover: 'Generate lookalike candidates offline',
|
|
62
|
-
'discover-scan': 'Collect a supervised candidate review queue',
|
|
63
|
-
posture: 'Review DNS and mail posture',
|
|
64
|
-
http: 'Inspect one homepage request',
|
|
65
|
-
tls: 'Inspect one TLS connection',
|
|
66
|
-
'dnssec-validate': 'Validate an authorised DNSSEC chain',
|
|
67
|
-
'mail-transport': 'Review selected authorised SMTP transports',
|
|
68
|
-
'registry-support': 'Explain local registry coverage',
|
|
69
|
-
'registry-doctor': 'Diagnose saved registry collection',
|
|
70
|
-
'registry-cohort': 'Build target-free registry quality timelines',
|
|
71
|
-
'registry-scaffold': 'Create a sanitised registry fixture scaffold',
|
|
72
|
-
'risk-calibrate': 'Replay reviewed Risk labels offline',
|
|
73
|
-
'lookalike-calibrate': 'Summarise reviewed lookalike yield offline',
|
|
74
|
-
'verify-artifact': 'Validate saved evidence offline',
|
|
75
|
-
'interchange-report': 'Report portable artefact fidelity offline',
|
|
76
|
-
'inspect-archive': 'Inspect an archive locally',
|
|
77
|
-
'sign-artifact': 'Sign a reviewed artefact locally',
|
|
78
|
-
'verify-signature': 'Verify a signed evidence package',
|
|
79
|
-
'source-report': 'Build a target-free source report',
|
|
80
|
-
compare: 'Compare registry publications in one lookup',
|
|
81
|
-
'page-compare': 'Compare saved static page evidence',
|
|
82
|
-
'mail-review': 'Review saved passive mail evidence',
|
|
83
|
-
'review-evidence': 'Review supplied evidence offline',
|
|
84
|
-
brief: 'Build a decision brief from a saved lookup',
|
|
85
|
-
'case-pack': 'Build a reviewed case package',
|
|
86
|
-
'domain-control': 'Build or review a domain control manifest',
|
|
87
|
-
'monitor-once': 'Run one bounded domain control review',
|
|
88
|
-
assurance: 'Review domain change, recovery, or retirement plans',
|
|
89
|
-
'change-packet': 'Build a reviewed change packet offline',
|
|
90
|
-
'sharing-review': 'Lint an artefact before deliberate sharing',
|
|
91
|
-
'workflow-plan': 'Plan a fixed investigation recipe',
|
|
92
|
-
'workflow-run': 'Execute approved fixed-recipe steps',
|
|
93
|
-
diff: 'Compare two compatible retained artefacts',
|
|
94
|
-
reconcile: 'Reconcile independently labelled observations',
|
|
95
|
-
timeline: 'Build same-domain history from saved lookups',
|
|
96
|
-
export: 'Convert a lookup to an evidence report',
|
|
97
|
-
completion: 'Print shell completion',
|
|
98
|
-
commands: 'List installed command contracts',
|
|
99
|
-
doctor: 'Check the local CLI runtime',
|
|
100
|
-
manual: 'Print the generated manual page',
|
|
101
|
-
});
|
|
102
|
-
const VALUE_OPTIONS = Object.freeze({
|
|
103
|
-
'--preset': ['common', 'impersonation', 'all'],
|
|
104
|
-
'--keyboard': ['qwerty', 'azerty', 'qwertz', 'all'],
|
|
105
|
-
'--mail-profile': ['standard', 'defensive-no-mail', 'parked'],
|
|
106
|
-
'--marking': ['clear', 'green', 'amber', 'amber-strict', 'red'],
|
|
107
|
-
'--recipient-scope': ['public', 'community', 'organization', 'named-recipients'],
|
|
108
|
-
'--audience': ['internal', 'trusted', 'public'],
|
|
109
|
-
'--concurrency': ['1', '2', '3', '4', '5', '6', '7', '8'],
|
|
110
|
-
});
|
|
111
|
-
const FILE_OPTIONS = Object.freeze([
|
|
112
|
-
'--checkpoint',
|
|
113
|
-
'--config',
|
|
114
|
-
'--allowlist',
|
|
115
|
-
'--dictionary',
|
|
116
|
-
'--output',
|
|
117
|
-
'--passphrase-file',
|
|
118
|
-
'--private-key-file',
|
|
119
|
-
'--public-key-file',
|
|
120
|
-
'--snapshot',
|
|
121
|
-
'--observation-snapshot',
|
|
122
|
-
'--previous',
|
|
123
|
-
'--resume',
|
|
124
|
-
'--trust-anchor',
|
|
125
|
-
]);
|
|
126
|
-
const FILE_POSITIONAL_COMMANDS = new Set([
|
|
127
|
-
'manifest',
|
|
128
|
-
'map-observations',
|
|
129
|
-
'oam-export',
|
|
130
|
-
'ct-intake',
|
|
131
|
-
'change-packet',
|
|
132
|
-
'mail-transport',
|
|
133
|
-
]);
|
|
134
|
-
const TEXT_OPTIONS = Object.freeze([
|
|
135
|
-
'--families',
|
|
136
|
-
'--resolver',
|
|
137
|
-
'--fail-on',
|
|
138
|
-
'--expect-content-digest',
|
|
139
|
-
'--profile',
|
|
140
|
-
'--purpose',
|
|
141
|
-
'--observer',
|
|
142
|
-
'--retired-selectors',
|
|
143
|
-
'--search',
|
|
144
|
-
'--selectors',
|
|
145
|
-
'--tlds',
|
|
146
|
-
'--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,
|
|
147
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
|
+
}
|
|
148
65
|
function commandOptions(command) {
|
|
149
|
-
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('|');
|
|
150
71
|
}
|
|
151
72
|
function bashCompletion() {
|
|
152
|
-
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');
|
|
153
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');
|
|
154
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
|
+
}
|
|
155
99
|
_whoisleuth_completion() {
|
|
156
|
-
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
|
|
157
101
|
current="\${COMP_WORDS[COMP_CWORD]}"
|
|
158
102
|
previous="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
159
103
|
command="\${COMP_WORDS[1]}"
|
|
160
104
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
161
|
-
COMPREPLY=( $(compgen -W "${CLI_COMMANDS.join(' ')}
|
|
162
|
-
return
|
|
163
|
-
fi
|
|
164
|
-
if [[ "\${command}" == "completion" && \${COMP_CWORD} -eq 2 ]]; then
|
|
165
|
-
COMPREPLY=( $(compgen -W "bash zsh fish powershell" -- "\${current}") )
|
|
105
|
+
COMPREPLY=( $(compgen -W "${CLI_COMMANDS.join(' ')} ${ROOT_META_ALIASES.join(' ')}" -- "\${current}") )
|
|
166
106
|
return
|
|
167
107
|
fi
|
|
168
|
-
if
|
|
169
|
-
|
|
170
|
-
return
|
|
108
|
+
if _whoisleuth_direct_lookup_target "\${command}"; then
|
|
109
|
+
command="lookup"
|
|
171
110
|
fi
|
|
172
|
-
case "\${previous}" in
|
|
173
|
-
${valueCases}
|
|
174
|
-
${FILE_OPTIONS.join('|')}) COMPREPLY=( $(compgen -f -- "\${current}") ); return ;;
|
|
175
|
-
${TEXT_OPTIONS.join('|')}) COMPREPLY=(); return ;;
|
|
176
|
-
esac
|
|
177
111
|
case "\${command}" in
|
|
178
112
|
${cases}
|
|
179
|
-
*) options="
|
|
113
|
+
*) options="${COMMAND_META_ALIASES.join(' ')}"; value_options=""; file_limit=0 ;;
|
|
180
114
|
esac
|
|
181
|
-
|
|
182
|
-
|
|
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}
|
|
183
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
|
|
184
147
|
COMPREPLY=( $(compgen -W "\${options}" -- "\${current}") )
|
|
185
148
|
}
|
|
186
149
|
complete -F _whoisleuth_completion whoisleuth
|
|
@@ -188,37 +151,77 @@ complete -F _whoisleuth_completion whoisleuth
|
|
|
188
151
|
}
|
|
189
152
|
function zshCompletion() {
|
|
190
153
|
const commandEntries = CLI_COMMANDS.map((command) => `'${command}:${COMMAND_DESCRIPTIONS[command] || command}'`).join(' ');
|
|
191
|
-
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');
|
|
192
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');
|
|
193
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
|
+
}
|
|
194
179
|
_whoisleuth() {
|
|
195
|
-
local command previous
|
|
196
|
-
local -a options commands
|
|
197
|
-
|
|
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(' ')})
|
|
198
187
|
if (( CURRENT == 2 )); then
|
|
199
188
|
_describe 'command' commands
|
|
200
189
|
return
|
|
201
190
|
fi
|
|
202
191
|
command="\${words[2]}"
|
|
203
192
|
previous="\${words[CURRENT-1]}"
|
|
204
|
-
if
|
|
205
|
-
|
|
206
|
-
return
|
|
207
|
-
fi
|
|
208
|
-
if [[ "\${command}" == "workflow-plan" && CURRENT -eq 3 ]]; then
|
|
209
|
-
compadd -- ${INVESTIGATION_PLAN_RECIPES.join(' ')}
|
|
210
|
-
return
|
|
193
|
+
if _whoisleuth_direct_lookup_target "\${command}"; then
|
|
194
|
+
command="lookup"
|
|
211
195
|
fi
|
|
212
|
-
case "\${previous}" in
|
|
213
|
-
${valueCases}
|
|
214
|
-
${FILE_OPTIONS.join('|')}) _files; return ;;
|
|
215
|
-
${TEXT_OPTIONS.join('|')}) _message 'value'; return ;;
|
|
216
|
-
esac
|
|
217
196
|
case "\${command}" in
|
|
218
197
|
${cases}
|
|
219
|
-
*) options=(
|
|
198
|
+
*) options=(${COMMAND_META_ALIASES.join(' ')}); value_options=(); file_limit=0 ;;
|
|
220
199
|
esac
|
|
221
|
-
|
|
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
|
|
222
225
|
_files
|
|
223
226
|
return
|
|
224
227
|
fi
|
|
@@ -232,84 +235,359 @@ fi
|
|
|
232
235
|
`;
|
|
233
236
|
}
|
|
234
237
|
function fishCompletion() {
|
|
235
|
-
const commandLines = CLI_COMMANDS.map((command) => (`complete -c whoisleuth -n '__fish_use_subcommand' -a '${command}' -d '${COMMAND_DESCRIPTIONS[command]}'
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
});
|
|
246
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
|
|
247
394
|
complete -c whoisleuth -f
|
|
248
|
-
complete -c whoisleuth -n '__fish_use_subcommand' -l
|
|
249
|
-
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')}
|
|
250
396
|
${commandLines.join('\n')}
|
|
251
|
-
|
|
252
|
-
complete -c whoisleuth -n '
|
|
253
|
-
${commonOptionLines.join('\n')}
|
|
397
|
+
${positionalEnumLines.join('\n')}
|
|
398
|
+
complete -c whoisleuth -n '__whoisleuth_file_position' -F
|
|
254
399
|
${optionLines.join('\n')}
|
|
255
400
|
${valueLines.join('\n')}
|
|
401
|
+
${integerLines.join('\n')}
|
|
256
402
|
`;
|
|
257
403
|
}
|
|
258
404
|
function powershellCompletion() {
|
|
259
405
|
const commandOptions = Object.fromEntries(CLI_COMMANDS.map((command) => [
|
|
260
406
|
command,
|
|
261
|
-
|
|
407
|
+
optionNames(command),
|
|
262
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
|
+
})]))));
|
|
263
433
|
return `# WHOISleuth PowerShell completion
|
|
264
434
|
Register-ArgumentCompleter -Native -CommandName whoisleuth -ScriptBlock {
|
|
265
435
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
436
|
+
$noMatch = { [System.Management.Automation.CompletionResult]::new(' ', 'no matches', 'ParameterValue', 'No completion available') }
|
|
266
437
|
$commands = @(${CLI_COMMANDS.map((command) => `'${command}'`).join(', ')})
|
|
267
438
|
$options = @{
|
|
268
439
|
${Object.entries(commandOptions).map(([command, options]) => ` '${command}' = @(${options.map((option) => `'${option}'`).join(', ')})`).join('\n')}
|
|
269
440
|
}
|
|
270
441
|
$values = @{
|
|
271
442
|
${Object.entries(VALUE_OPTIONS).map(([option, values]) => ` '${option}' = @(${values.map((value) => `'${value}'`).join(', ')})`).join('\n')}
|
|
272
|
-
'completion' = @('bash', 'zsh', 'fish', 'powershell')
|
|
273
|
-
'workflow-plan' = @(${INVESTIGATION_PLAN_RECIPES.map((value) => `'${value}'`).join(', ')})
|
|
274
443
|
}
|
|
275
|
-
$
|
|
276
|
-
|
|
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
|
+
})
|
|
277
469
|
$command = if ($elements.Count -gt 1) { $elements[1] } else { '' }
|
|
278
|
-
$
|
|
279
|
-
if ($
|
|
280
|
-
|
|
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 {
|
|
281
487
|
[System.Management.Automation.CompletionResult]::new($_.FullName, $_.Name, 'ProviderItem', $_.FullName)
|
|
282
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')
|
|
283
496
|
return
|
|
284
497
|
}
|
|
285
|
-
$
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
$
|
|
291
|
-
|
|
292
|
-
$
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
295
566
|
} else {
|
|
296
|
-
@('
|
|
567
|
+
@(${COMMAND_META_ALIASES.map((alias) => `'${alias}'`).join(', ')})
|
|
297
568
|
}
|
|
569
|
+
$matched = $false
|
|
298
570
|
foreach ($candidate in $candidates) {
|
|
299
571
|
if ($candidate.StartsWith($wordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
300
572
|
[System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
|
|
573
|
+
$matched = $true
|
|
301
574
|
}
|
|
302
575
|
}
|
|
576
|
+
if (-not $matched) { & $noMatch }
|
|
303
577
|
}
|
|
304
578
|
`;
|
|
305
579
|
}
|
|
306
580
|
function buildShellCompletion(shell) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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;
|
|
314
592
|
}
|
|
315
|
-
export { buildShellCompletion, };
|
|
593
|
+
export { MAX_CLI_COMPLETION_BYTES, buildShellCompletion, };
|