@slicedearth/whoisleuth-cli 2.1.0 → 2.3.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 +2 -1
- package/cli/arguments.mjs +389 -1745
- package/cli/artifact-validation/case-response.mjs +55 -30
- package/cli/artifact-validation/signed-review.mjs +30 -12
- package/cli/bulk-checkpoint.mjs +34 -3
- package/cli/case-pack.mjs +39 -92
- package/cli/command-argument-grammar.mjs +211 -0
- package/cli/command-reference.mjs +200 -213
- package/cli/completion.mjs +5 -1
- package/cli/discovery-observation-snapshot.mjs +16 -3
- package/cli/discovery-scan-command-runner.mjs +19 -0
- package/cli/discovery-scan.mjs +6 -0
- package/cli/domain-control-observations.mjs +1 -1
- package/cli/inline-command-families.mjs +1 -0
- package/cli/input.mjs +116 -0
- package/cli/interchange-report.mjs +14 -0
- package/cli/lookalike-calibration.mjs +7 -2
- package/cli/lookup-brief.mjs +4 -2
- package/cli/lookup-browser-view.mjs +1 -1
- package/cli/mail-header-review.mjs +467 -0
- package/cli/review-command-runner.mjs +25 -0
- package/cli/risk-calibration.mjs +19 -17
- package/cli/runner.mjs +7 -1
- package/docs/cli-reference.md +8 -1
- package/docs/cli.md +24 -1
- package/lib/bounded-json.mjs +9 -6
- package/lib/dns-convergence-review.mjs +1 -0
- package/lib/dns-intelligence.mjs +37 -3
- package/lib/dnssec-chain-validation.mjs +6 -0
- package/lib/domain-change-packet.mjs +42 -14
- package/lib/domain-control-flight-recorder.mjs +4 -1
- package/lib/domain-control-manifest.mjs +16 -16
- package/lib/domain-posture-parsers.mjs +114 -3
- package/lib/domain-posture.mjs +34 -3
- package/lib/evidence-export.mjs +63 -13
- package/lib/evidence-report.mjs +2 -2
- package/lib/html-signals.mjs +39 -33
- package/lib/local-geoip-evidence.mjs +19 -3
- package/lib/lookup-response.mjs +5 -4
- package/lib/lookup-source-progress.mjs +27 -4
- package/lib/nameserver-preflight-review.mjs +1 -0
- package/lib/rdap-capabilities.mjs +1 -1
- package/lib/registry-insights.mjs +35 -22
- package/lib/risk-calibration-summary.mjs +1 -3
- package/lib/security-txt.mjs +33 -1
- package/lib/static-html-analysis.mjs +69 -13
- package/lib/website-technology.mjs +79 -11
- package/lib/zone-intent-review.mjs +16 -6
- package/package.json +1 -1
- package/packages/cases/case-migration-model.mjs +71 -22
- package/packages/cases/case-record-contracts.mjs +5 -20
- package/packages/cases/case-record-core.mjs +10 -21
- package/packages/cases/case-record-decisions.mjs +72 -0
- package/packages/cases/case-record-model.mjs +1 -0
- package/packages/cases/case-record-operations.mjs +240 -3
- package/packages/cases/case-record-projection.mjs +218 -0
- package/packages/cases/case-report.mjs +11 -6
- package/packages/cases/case-response-model.mjs +43 -6
- package/packages/cases/case-response-packet.mjs +78 -27
- package/packages/cases/case-storage-model.mjs +6 -2
- package/packages/comparison/comparison-ledger-bulk.mjs +9 -10
- package/packages/comparison/page-similarity.mjs +3 -1
- package/packages/contracts/case-portability.mjs +183 -53
- package/packages/contracts/cli-command-semantics.mjs +93 -0
- package/packages/contracts/risk-calibration.mjs +15 -10
- package/packages/contracts/workspace-portability.mjs +44 -7
- package/packages/evidence/domain-control-runtime.mjs +46 -6
- package/packages/interchange/external-findings-import.mjs +115 -46
- package/packages/investigation/lookup-asset-graph.mjs +35 -27
- package/packages/workspace/brand-profile-model.mjs +118 -2
- package/packages/workspace/bulk-review-model.mjs +13 -7
- package/packages/workspace/bulk-session-model.mjs +5 -4
- package/packages/workspace/detection-rule-model.mjs +70 -22
- package/packages/workspace/relationship-observation-model.mjs +4 -1
- package/packages/workspace/website-snapshot-model.mjs +106 -8
- package/packages/workspace/workspace-archive.mjs +2 -0
- package/cli/evidence-command-arguments.mjs +0 -156
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { CliUsageError } from "./errors.mjs";
|
|
2
|
+
import { commandDefinition, } from "./command-reference.mjs";
|
|
3
|
+
function humanList(values, conjunction = 'or') {
|
|
4
|
+
if (values.length < 2)
|
|
5
|
+
return values[0] ?? '';
|
|
6
|
+
if (values.length === 2)
|
|
7
|
+
return `${values[0]} ${conjunction} ${values[1]}`;
|
|
8
|
+
return `${values.slice(0, -1).join(', ')}, ${conjunction} ${values.at(-1)}`;
|
|
9
|
+
}
|
|
10
|
+
function valueError(specification) {
|
|
11
|
+
if (specification.valueKind === 'file') {
|
|
12
|
+
return new CliUsageError(`${specification.option} requires one bounded file path.`);
|
|
13
|
+
}
|
|
14
|
+
if (specification.valueKind === 'integer') {
|
|
15
|
+
const baseRange = specification.integerRanges[0];
|
|
16
|
+
return new CliUsageError(baseRange
|
|
17
|
+
? `${specification.option} requires an integer from ${baseRange.minimum} to ${baseRange.maximum}.`
|
|
18
|
+
: `${specification.option} requires one bounded integer.`);
|
|
19
|
+
}
|
|
20
|
+
if (specification.values.length > 0) {
|
|
21
|
+
return new CliUsageError(`${specification.option} requires ${humanList(specification.values)}.`);
|
|
22
|
+
}
|
|
23
|
+
return new CliUsageError(`${specification.option} requires one bounded value.`);
|
|
24
|
+
}
|
|
25
|
+
function parseTokens(command, argv) {
|
|
26
|
+
const specificationByOption = new Map(commandDefinition(command).grammar.options.map((specification) => [specification.option, specification]));
|
|
27
|
+
const options = new Map();
|
|
28
|
+
const positionals = [];
|
|
29
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
30
|
+
const argument = argv[index];
|
|
31
|
+
if (!argument.startsWith('-')) {
|
|
32
|
+
positionals.push(argument);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const specification = specificationByOption.get(argument);
|
|
36
|
+
if (!specification || specification.metaAction !== null) {
|
|
37
|
+
throw new CliUsageError(`Unknown option "${argument}".`);
|
|
38
|
+
}
|
|
39
|
+
const existing = options.get(argument);
|
|
40
|
+
if (existing && specification.occurrence === 'once') {
|
|
41
|
+
throw new CliUsageError(`${argument} may be supplied only once.`);
|
|
42
|
+
}
|
|
43
|
+
if (existing && specification.occurrence === 'idempotent')
|
|
44
|
+
continue;
|
|
45
|
+
let value = '';
|
|
46
|
+
if (specification.arity === 1) {
|
|
47
|
+
const candidate = argv[++index];
|
|
48
|
+
if (candidate === undefined || candidate === ''
|
|
49
|
+
|| (!specification.acceptsOptionLikeValue && candidate.startsWith('-'))) {
|
|
50
|
+
throw valueError(specification);
|
|
51
|
+
}
|
|
52
|
+
value = candidate;
|
|
53
|
+
}
|
|
54
|
+
if (existing)
|
|
55
|
+
existing.push(value);
|
|
56
|
+
else
|
|
57
|
+
options.set(argument, [value]);
|
|
58
|
+
}
|
|
59
|
+
return { options, positionals };
|
|
60
|
+
}
|
|
61
|
+
function selectedIntegerRange(specification, options) {
|
|
62
|
+
return specification.integerRanges
|
|
63
|
+
.filter((range) => range.whenOptionPresent === null || options.has(range.whenOptionPresent))
|
|
64
|
+
.at(-1) ?? null;
|
|
65
|
+
}
|
|
66
|
+
function validateOptionValues(command, options) {
|
|
67
|
+
for (const specification of commandDefinition(command).grammar.options) {
|
|
68
|
+
const values = options.get(specification.option);
|
|
69
|
+
if (!values || specification.arity === 0)
|
|
70
|
+
continue;
|
|
71
|
+
if (specification.valueKind === 'enum') {
|
|
72
|
+
if (values.some((value) => !specification.values.includes(value)))
|
|
73
|
+
throw valueError(specification);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (specification.valueKind === 'integer') {
|
|
77
|
+
const range = selectedIntegerRange(specification, options);
|
|
78
|
+
if (!range)
|
|
79
|
+
throw new Error(`Integer option ${command} ${specification.option} has no applicable range.`);
|
|
80
|
+
for (const value of values) {
|
|
81
|
+
if (!/^\d+$/u.test(value))
|
|
82
|
+
throw valueError(specification);
|
|
83
|
+
const parsed = Number(value);
|
|
84
|
+
if (!Number.isSafeInteger(parsed) || parsed < range.minimum || parsed > range.maximum) {
|
|
85
|
+
throw new CliUsageError(`${specification.option} must be from ${range.minimum} to ${range.maximum}.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (specification.valueKind === 'policy_list') {
|
|
91
|
+
for (const value of values) {
|
|
92
|
+
const policies = [...new Set(value.split(',').map((item) => item.trim().toLowerCase()).filter(Boolean))];
|
|
93
|
+
if (!policies.length || policies.some((policy) => !specification.values.includes(policy))) {
|
|
94
|
+
throw new CliUsageError(`${specification.option} for ${command} supports: ${specification.values.join(', ')}.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function allocatePositionals(command, rawPositionals) {
|
|
101
|
+
const specifications = commandDefinition(command).grammar.positionals;
|
|
102
|
+
const maximum = specifications.reduce((sum, specification) => sum + specification.maximum, 0);
|
|
103
|
+
if (rawPositionals.length > maximum) {
|
|
104
|
+
throw new CliUsageError(`${command} accepts at most ${maximum} positional value${maximum === 1 ? '' : 's'}.`);
|
|
105
|
+
}
|
|
106
|
+
const allocated = {};
|
|
107
|
+
let offset = 0;
|
|
108
|
+
for (const [specificationIndex, specification] of specifications.entries()) {
|
|
109
|
+
const remainingMinimum = specifications.slice(specificationIndex + 1)
|
|
110
|
+
.reduce((sum, later) => sum + later.minimum, 0);
|
|
111
|
+
const available = Math.max(0, rawPositionals.length - offset - remainingMinimum);
|
|
112
|
+
const count = Math.min(specification.maximum, available);
|
|
113
|
+
const values = Object.freeze(rawPositionals.slice(offset, offset + count));
|
|
114
|
+
offset += count;
|
|
115
|
+
if (values.length < specification.minimum) {
|
|
116
|
+
throw new CliUsageError(`${command} requires ${specification.minimum} ${specification.name} value${specification.minimum === 1 ? '' : 's'}.`);
|
|
117
|
+
}
|
|
118
|
+
if (specification.valueKind === 'enum'
|
|
119
|
+
&& values.some((value) => !specification.values.includes(value))) {
|
|
120
|
+
throw new CliUsageError(`${command} ${specification.name} must be ${humanList(specification.values)}.`);
|
|
121
|
+
}
|
|
122
|
+
allocated[specification.name] = values;
|
|
123
|
+
}
|
|
124
|
+
return Object.freeze(allocated);
|
|
125
|
+
}
|
|
126
|
+
function present(options, option) {
|
|
127
|
+
return options.has(option);
|
|
128
|
+
}
|
|
129
|
+
function validateConstraint(command, constraint, options) {
|
|
130
|
+
if (constraint.kind === 'required') {
|
|
131
|
+
const missing = constraint.options.filter((option) => !present(options, option));
|
|
132
|
+
if (missing.length > 0)
|
|
133
|
+
throw new CliUsageError(`${command} requires ${missing.join(', ')}.`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (constraint.kind === 'mutually_exclusive') {
|
|
137
|
+
const selected = constraint.options.filter((option) => present(options, option));
|
|
138
|
+
if (selected.length > 1) {
|
|
139
|
+
throw new CliUsageError(`${selected.join(' and ')} are mutually exclusive and may be supplied only once.`);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (constraint.kind === 'excludes_all') {
|
|
144
|
+
if (!present(options, constraint.option))
|
|
145
|
+
return;
|
|
146
|
+
const selected = constraint.excludedOptions.filter((option) => present(options, option));
|
|
147
|
+
if (selected.length > 0) {
|
|
148
|
+
throw new CliUsageError(`${constraint.option} cannot be combined with ${selected.join(', ')}.`);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (constraint.kind === 'value_excludes') {
|
|
153
|
+
if (!options.get(constraint.option)?.includes(constraint.value))
|
|
154
|
+
return;
|
|
155
|
+
const selected = constraint.excludedOptions.filter((option) => present(options, option));
|
|
156
|
+
if (selected.length > 0) {
|
|
157
|
+
throw new CliUsageError(`${constraint.option} ${constraint.value} cannot be combined with ${selected.join(', ')}.`);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!present(options, constraint.option))
|
|
162
|
+
return;
|
|
163
|
+
const selected = constraint.requiredOptions.filter((option) => present(options, option));
|
|
164
|
+
if (constraint.kind === 'requires_all' && selected.length !== constraint.requiredOptions.length) {
|
|
165
|
+
throw new CliUsageError(`${constraint.option} requires ${constraint.requiredOptions.join(' and ')}.`);
|
|
166
|
+
}
|
|
167
|
+
if (constraint.kind === 'requires_any' && selected.length === 0) {
|
|
168
|
+
throw new CliUsageError(`${constraint.option} requires ${constraint.requiredOptions.join(' or ')}.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function validatePositionalRequirements(command, allocated, options) {
|
|
172
|
+
for (const specification of commandDefinition(command).grammar.positionals) {
|
|
173
|
+
if (specification.requiredWhenOptions.some((option) => options.has(option))
|
|
174
|
+
&& (allocated[specification.name]?.length ?? 0) === 0) {
|
|
175
|
+
throw new CliUsageError(`${specification.requiredWhenOptions.join(' or ')} requires a positional ${specification.name}.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function freezeOptions(options) {
|
|
180
|
+
return new Map([...options].map(([option, values]) => [option, Object.freeze([...values])]));
|
|
181
|
+
}
|
|
182
|
+
export function parseCommandArguments(command, argv) {
|
|
183
|
+
const parsed = parseTokens(command, argv);
|
|
184
|
+
const options = freezeOptions(parsed.options);
|
|
185
|
+
validateOptionValues(command, options);
|
|
186
|
+
const allocated = allocatePositionals(command, parsed.positionals);
|
|
187
|
+
validatePositionalRequirements(command, allocated, options);
|
|
188
|
+
for (const constraint of commandDefinition(command).grammar.constraints) {
|
|
189
|
+
validateConstraint(command, constraint, options);
|
|
190
|
+
}
|
|
191
|
+
for (const option of ['--config', '--profile']) {
|
|
192
|
+
const specification = commandDefinition(command).grammar.options.find((candidate) => candidate.option === option);
|
|
193
|
+
if (specification?.scope === 'common' && options.has(option)) {
|
|
194
|
+
throw new CliUsageError(`${option} must be resolved before command parsing.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const allPositionals = Object.freeze([...parsed.positionals]);
|
|
198
|
+
return Object.freeze({
|
|
199
|
+
command,
|
|
200
|
+
allPositionals,
|
|
201
|
+
hasOption: (option) => options.has(option),
|
|
202
|
+
optionValue: (option) => options.get(option)?.at(-1) ?? null,
|
|
203
|
+
optionValues: (option) => options.get(option) ?? Object.freeze([]),
|
|
204
|
+
integerOption: (option) => {
|
|
205
|
+
const value = options.get(option)?.at(-1);
|
|
206
|
+
return value === undefined ? null : Number(value);
|
|
207
|
+
},
|
|
208
|
+
positionalValue: (name) => allocated[name]?.[0] ?? null,
|
|
209
|
+
positionalValues: (name) => allocated[name] ?? Object.freeze([]),
|
|
210
|
+
});
|
|
211
|
+
}
|