@slicedearth/whoisleuth-cli 2.2.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 +12 -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 +60 -11
- 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 +6 -4
- 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 +171 -51
- 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 +3 -2
- 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
|
@@ -2,6 +2,7 @@ import { WHOISLEUTH_SOURCE_REPOSITORY_URL } from "../lib/project-metadata.mjs";
|
|
|
2
2
|
import { LOOKUP_EVIDENCE_SCHEMA_VERSION, PUBLISHED_V2_LOOKUP_EVIDENCE_SCHEMA_VERSION, V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION, } from "../lib/evidence-export.mjs";
|
|
3
3
|
import { RISK_CALIBRATION_DATASET_SCHEMA, RISK_CALIBRATION_REPORT_SCHEMA, } from "../packages/contracts/risk-calibration.mjs";
|
|
4
4
|
import { CASE_SCHEMA_VERSION, SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS, WORKSPACE_ARCHIVE_VERSION, } from "../packages/contracts/case-portability.mjs";
|
|
5
|
+
import { CLI_COMMAND_SEMANTICS, CLI_HELP_GROUP_ORDER, } from "../packages/contracts/cli-command-semantics.mjs";
|
|
5
6
|
import { CLI_FAIL_POLICIES_BY_COMMAND } from "./fail-policy.mjs";
|
|
6
7
|
const LEGACY_WORKSPACE_ARCHIVE_VERSIONS = SUPPORTED_WORKSPACE_ARCHIVE_VERSIONS
|
|
7
8
|
.filter((version) => version !== WORKSPACE_ARCHIVE_VERSION);
|
|
@@ -80,48 +81,13 @@ must be requested explicitly and can disclose a target to additional sources.
|
|
|
80
81
|
Copyright 2026 slicedearth. Licensed under AGPL-3.0-only.
|
|
81
82
|
Source and licence: ${WHOISLEUTH_SOURCE_REPOSITORY_URL}
|
|
82
83
|
`;
|
|
83
|
-
const COMMON_OPTIONS = Object.freeze([
|
|
84
|
+
const COMMON_OPTIONS = Object.freeze([
|
|
85
|
+
'--help', '--output', '--force', '--config', '--profile', '--palette',
|
|
86
|
+
]);
|
|
84
87
|
const REGISTRY_SCAFFOLD_COMMON_OPTIONS = Object.freeze(COMMON_OPTIONS.filter((option) => option !== '--config' && option !== '--profile'));
|
|
85
88
|
function commonOptionsSeedForCommand(command) {
|
|
86
89
|
return command === 'registry-scaffold' ? REGISTRY_SCAFFOLD_COMMON_OPTIONS : COMMON_OPTIONS;
|
|
87
90
|
}
|
|
88
|
-
const VALUE_OPTIONS = Object.freeze({
|
|
89
|
-
'--group': Object.freeze(['investigate', 'respond', 'assure', 'utilities']),
|
|
90
|
-
'--mode': Object.freeze(['offline', 'network']),
|
|
91
|
-
'--explain': INVESTIGATION_PLAN_RECIPES,
|
|
92
|
-
'--preset': Object.freeze(['common', 'impersonation', 'all']),
|
|
93
|
-
'--keyboard': Object.freeze(['qwerty', 'azerty', 'qwertz', 'all']),
|
|
94
|
-
'--mail-profile': Object.freeze(['standard', 'defensive-no-mail', 'parked']),
|
|
95
|
-
'--marking': Object.freeze(['clear', 'green', 'amber', 'amber-strict', 'red']),
|
|
96
|
-
'--recipient-scope': Object.freeze(['public', 'community', 'organization', 'named-recipients']),
|
|
97
|
-
'--audience': Object.freeze(['internal', 'trusted', 'public']),
|
|
98
|
-
'--fail-on': Object.freeze(['source-failure', 'inconclusive', 'danger', 'material-drift']),
|
|
99
|
-
'--manifest-entry': Object.freeze(Array.from({ length: 16 }, (_, index) => `artifact-${index + 1}`)),
|
|
100
|
-
'--palette': Object.freeze(['auto', 'light', 'dark']),
|
|
101
|
-
'--scenario': Object.freeze(['registered', 'not_found', 'inconclusive']),
|
|
102
|
-
});
|
|
103
|
-
const STANDARD_CONCURRENCY_VALUES = Object.freeze(['1', '2', '3', '4', '5', '6', '7', '8']);
|
|
104
|
-
const LIMITED_CONCURRENCY_VALUES = Object.freeze(['1', '2', '3']);
|
|
105
|
-
const FILE_OPTIONS = Object.freeze([
|
|
106
|
-
'--checkpoint',
|
|
107
|
-
'--config',
|
|
108
|
-
'--allowlist',
|
|
109
|
-
'--dictionary',
|
|
110
|
-
'--manifest',
|
|
111
|
-
'--mmdb',
|
|
112
|
-
'--output',
|
|
113
|
-
'--passphrase-file',
|
|
114
|
-
'--private-key-file',
|
|
115
|
-
'--public-key-file',
|
|
116
|
-
'--snapshot',
|
|
117
|
-
'--observation-snapshot',
|
|
118
|
-
'--previous',
|
|
119
|
-
'--save-lookup',
|
|
120
|
-
'--trust-anchor',
|
|
121
|
-
]);
|
|
122
|
-
const FILE_OPTIONS_BY_COMMAND = Object.freeze({
|
|
123
|
-
'workflow-run': Object.freeze(['--resume']),
|
|
124
|
-
});
|
|
125
91
|
function positional(name, valueKind, minimum, maximum, values = [], inputSource = 'argv', requiredWhenOptions = []) {
|
|
126
92
|
return Object.freeze({
|
|
127
93
|
name,
|
|
@@ -136,61 +102,127 @@ function positional(name, valueKind, minimum, maximum, values = [], inputSource
|
|
|
136
102
|
const NO_POSITIONALS = Object.freeze([]);
|
|
137
103
|
const OPTIONAL_FILE_POSITIONAL = Object.freeze([positional('source', 'file', 0, 1, [], 'argv_or_stdin')]);
|
|
138
104
|
const OPTIONAL_TEXT_POSITIONAL = Object.freeze([positional('subject', 'text', 0, 1, [], 'argv_or_stdin')]);
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
'--
|
|
162
|
-
'--
|
|
163
|
-
'--
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
'
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
'
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
]),
|
|
105
|
+
const NO_INTEGER_RANGES = () => Object.freeze([]);
|
|
106
|
+
const BASE_INTEGER_RANGE = (minimum, maximum) => (Object.freeze({ minimum, maximum, whenOptionPresent: null }));
|
|
107
|
+
const DEEP_INTEGER_RANGE = (minimum, maximum) => (Object.freeze({ minimum, maximum, whenOptionPresent: '--deep' }));
|
|
108
|
+
function optionDefinition(valueKind, options = {}) {
|
|
109
|
+
const configuredValues = options.values;
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
valueKind: typeof valueKind === 'function' ? valueKind : () => valueKind,
|
|
112
|
+
values: typeof configuredValues === 'function'
|
|
113
|
+
? configuredValues
|
|
114
|
+
: () => Object.freeze([...(configuredValues ?? [])]),
|
|
115
|
+
occurrence: options.occurrence ?? 'once',
|
|
116
|
+
acceptsOptionLikeValue: options.acceptsOptionLikeValue ?? false,
|
|
117
|
+
metaAction: options.metaAction ?? null,
|
|
118
|
+
integerRanges: options.integerRanges ?? NO_INTEGER_RANGES,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const flag = (occurrence = 'once') => optionDefinition('flag', { occurrence });
|
|
122
|
+
const file = () => optionDefinition('file');
|
|
123
|
+
const text = (acceptsOptionLikeValue = false) => optionDefinition('text', { acceptsOptionLikeValue });
|
|
124
|
+
const enumeration = (values) => optionDefinition('enum', { values });
|
|
125
|
+
const integer = (ranges) => (optionDefinition('integer', { integerRanges: ranges }));
|
|
126
|
+
const CLI_OPTION_DEFINITIONS = Object.freeze({
|
|
127
|
+
'--help': optionDefinition('flag', { metaAction: 'help' }),
|
|
128
|
+
'--output': file(),
|
|
129
|
+
'--force': flag(),
|
|
130
|
+
'--config': file(),
|
|
131
|
+
'--profile': text(true),
|
|
132
|
+
'--palette': enumeration(['auto', 'light', 'dark']),
|
|
133
|
+
'--network': flag(),
|
|
134
|
+
'--json': flag(),
|
|
135
|
+
'--quiet': flag('idempotent'),
|
|
136
|
+
'--no-color': flag('idempotent'),
|
|
137
|
+
'--common': flag(),
|
|
138
|
+
'--group': enumeration(['investigate', 'respond', 'assure', 'utilities']),
|
|
139
|
+
'--mode': enumeration(['offline', 'network']),
|
|
140
|
+
'--workflow': text(true),
|
|
141
|
+
'--configuration-digest': text(true),
|
|
142
|
+
'--junit': flag(),
|
|
143
|
+
'--markdown': flag(),
|
|
144
|
+
'--html': flag(),
|
|
145
|
+
'--no-attribution': flag(),
|
|
146
|
+
'--fast': flag(),
|
|
147
|
+
'--deep': flag(),
|
|
148
|
+
'--observer': text(),
|
|
149
|
+
'--vantage': text(),
|
|
150
|
+
'--plan': flag(),
|
|
151
|
+
'--summary': flag(),
|
|
152
|
+
'--verbose': flag(),
|
|
153
|
+
'--browse': flag(),
|
|
154
|
+
'--save-lookup': file(),
|
|
155
|
+
'--strict-exit': flag(),
|
|
156
|
+
'--fail-on': optionDefinition('policy_list', {
|
|
157
|
+
values: (command) => CLI_FAIL_POLICIES_BY_COMMAND[command] ?? [],
|
|
193
158
|
}),
|
|
159
|
+
'--events': flag(),
|
|
160
|
+
'--jsonl': flag(),
|
|
161
|
+
'--csv': flag(),
|
|
162
|
+
'--domains': flag(),
|
|
163
|
+
'--queries': flag(),
|
|
164
|
+
'--registered-only': flag(),
|
|
165
|
+
'--inconclusive-only': flag(),
|
|
166
|
+
'--errors-only': flag(),
|
|
167
|
+
'--concurrency': integer((command) => command === 'monitor-once'
|
|
168
|
+
? Object.freeze([BASE_INTEGER_RANGE(1, 3)])
|
|
169
|
+
: Object.freeze([BASE_INTEGER_RANGE(1, 8), DEEP_INTEGER_RANGE(1, 3)])),
|
|
170
|
+
'--checkpoint': file(),
|
|
171
|
+
'--resume': optionDefinition((command) => command === 'workflow-run' ? 'file' : 'flag'),
|
|
172
|
+
'--tlds': text(true),
|
|
173
|
+
'--preset': enumeration(['common', 'impersonation', 'all']),
|
|
174
|
+
'--families': text(),
|
|
175
|
+
'--keyboard': enumeration(['qwerty', 'azerty', 'qwertz', 'all']),
|
|
176
|
+
'--dictionary': file(),
|
|
177
|
+
'--snapshot': file(),
|
|
178
|
+
'--scan-limit': integer(() => Object.freeze([
|
|
179
|
+
BASE_INTEGER_RANGE(1, 500),
|
|
180
|
+
DEEP_INTEGER_RANGE(1, 50),
|
|
181
|
+
])),
|
|
182
|
+
'--chunk-size': integer(() => Object.freeze([BASE_INTEGER_RANGE(1, 100)])),
|
|
183
|
+
'--resolver': text(),
|
|
184
|
+
'--allowlist': file(),
|
|
185
|
+
'--observation-snapshot': file(),
|
|
186
|
+
'--acquisition-only': flag(),
|
|
187
|
+
'--suppressed-only': flag(),
|
|
188
|
+
'--selectors': text(true),
|
|
189
|
+
'--retired-selectors': text(true),
|
|
190
|
+
'--mail-profile': enumeration(['standard', 'defensive-no-mail', 'parked']),
|
|
191
|
+
'--sarif': flag(),
|
|
192
|
+
'--owned-domain': flag(),
|
|
193
|
+
'--trust-anchor': file(),
|
|
194
|
+
'--owned-or-authorized': flag(),
|
|
195
|
+
'--active-probe': flag(),
|
|
196
|
+
'--suffix': text(true),
|
|
197
|
+
'--scenario': enumeration(['registered', 'not_found', 'inconclusive']),
|
|
198
|
+
'--summary-json': flag(),
|
|
199
|
+
'--passphrase-file': file(),
|
|
200
|
+
'--manifest': file(),
|
|
201
|
+
'--manifest-entry': enumeration(Array.from({ length: 16 }, (_, index) => `artifact-${index + 1}`)),
|
|
202
|
+
'--search': text(),
|
|
203
|
+
'--require-match': flag(),
|
|
204
|
+
'--reveal': flag(),
|
|
205
|
+
'--expect-content-digest': text(true),
|
|
206
|
+
'--private-key-file': file(),
|
|
207
|
+
'--public-key-file': file(),
|
|
208
|
+
'--mmdb': file(),
|
|
209
|
+
'--audience': enumeration(['internal', 'trusted', 'public']),
|
|
210
|
+
'--reviewed': flag(),
|
|
211
|
+
'--previous': file(),
|
|
212
|
+
'--limit': integer(() => Object.freeze([BASE_INTEGER_RANGE(1, 20)])),
|
|
213
|
+
'--marking': enumeration(['clear', 'green', 'amber', 'amber-strict', 'red']),
|
|
214
|
+
'--recipient-scope': enumeration(['public', 'community', 'organization', 'named-recipients']),
|
|
215
|
+
'--purpose': text(),
|
|
216
|
+
'--human-reviewed': flag('idempotent'),
|
|
217
|
+
'--personal-data-reviewed': flag('idempotent'),
|
|
218
|
+
'--redactions-confirmed': flag('idempotent'),
|
|
219
|
+
'--list': flag(),
|
|
220
|
+
'--explain': enumeration(INVESTIGATION_PLAN_RECIPES),
|
|
221
|
+
'--select': optionDefinition('text', { occurrence: 'repeatable', acceptsOptionLikeValue: true }),
|
|
222
|
+
'--approve-network': flag(),
|
|
223
|
+
'--left-session': text(true),
|
|
224
|
+
'--right-session': text(true),
|
|
225
|
+
'--compact': flag(),
|
|
194
226
|
});
|
|
195
227
|
function constraint(value) {
|
|
196
228
|
if (value.kind === 'mutually_exclusive') {
|
|
@@ -216,33 +248,20 @@ const MACHINE_OUTPUT_OPTIONS = Object.freeze([
|
|
|
216
248
|
'--json', '--jsonl', '--junit', '--csv', '--domains', '--queries', '--markdown',
|
|
217
249
|
'--html', '--sarif', '--summary-json',
|
|
218
250
|
]);
|
|
219
|
-
function optionValueKind(command, option) {
|
|
220
|
-
if (Object.hasOwn(VALUE_OPTIONS, option))
|
|
221
|
-
return option === '--fail-on' ? 'policy_list' : 'enum';
|
|
222
|
-
if (INTEGER_RANGE_SEED[command]?.[option])
|
|
223
|
-
return 'integer';
|
|
224
|
-
if (FILE_OPTIONS.includes(option) || FILE_OPTIONS_BY_COMMAND[command]?.includes(option))
|
|
225
|
-
return 'file';
|
|
226
|
-
if (TEXT_OPTIONS.includes(option))
|
|
227
|
-
return 'text';
|
|
228
|
-
return 'flag';
|
|
229
|
-
}
|
|
230
251
|
function optionSpec(command, option, scope) {
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
: VALUE_OPTIONS[option] ?? [];
|
|
252
|
+
const definition = CLI_OPTION_DEFINITIONS[option];
|
|
253
|
+
const valueKind = definition.valueKind(command);
|
|
254
|
+
const values = definition.values(command);
|
|
235
255
|
return Object.freeze({
|
|
236
256
|
option,
|
|
237
257
|
scope,
|
|
238
258
|
arity: valueKind === 'flag' ? 0 : 1,
|
|
239
259
|
valueKind,
|
|
240
260
|
values: Object.freeze([...values]),
|
|
241
|
-
integerRanges: Object.freeze([...(
|
|
242
|
-
occurrence:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
metaAction: option === '--help' ? 'help' : null,
|
|
261
|
+
integerRanges: Object.freeze([...definition.integerRanges(command)]),
|
|
262
|
+
occurrence: definition.occurrence,
|
|
263
|
+
acceptsOptionLikeValue: definition.acceptsOptionLikeValue,
|
|
264
|
+
metaAction: definition.metaAction,
|
|
246
265
|
});
|
|
247
266
|
}
|
|
248
267
|
function grammarConstraints(commandOptions, commandConstraints) {
|
|
@@ -278,7 +297,6 @@ function commandSeed(seed) {
|
|
|
278
297
|
const COMMAND_SEEDS = Object.freeze({
|
|
279
298
|
completion: commandSeed({
|
|
280
299
|
reference: {
|
|
281
|
-
usage: 'whoisleuth completion <bash|zsh|fish|powershell>',
|
|
282
300
|
description: 'Print a static shell-completion script for the installed CLI.',
|
|
283
301
|
example: 'whoisleuth completion zsh > ~/.zfunc/_whoisleuth',
|
|
284
302
|
boundary: 'Generation is offline and writes only the script to stdout. The command never modifies shell configuration.',
|
|
@@ -288,7 +306,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
288
306
|
options: [],
|
|
289
307
|
positionals: Object.freeze([positional('shell', 'enum', 1, 1, ['bash', 'zsh', 'fish', 'powershell'])]),
|
|
290
308
|
constraints: EMPTY_CONSTRAINTS,
|
|
291
|
-
helpGroup: 'utilities',
|
|
292
309
|
handlerOwner: 'inline',
|
|
293
310
|
networkEffect: 'offline',
|
|
294
311
|
common: false,
|
|
@@ -300,7 +317,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
300
317
|
}),
|
|
301
318
|
doctor: commandSeed({
|
|
302
319
|
reference: {
|
|
303
|
-
usage: 'whoisleuth doctor [--network] [--json] [--quiet] [--no-color]',
|
|
304
320
|
description: 'Check the supported runtime and local terminal capabilities.',
|
|
305
321
|
example: 'whoisleuth doctor --json',
|
|
306
322
|
boundary: 'The default check is offline. Public DNS and port 43 checks run only when --network is explicitly supplied.',
|
|
@@ -310,7 +326,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
310
326
|
options: ['--network', '--json', '--quiet', '--no-color'],
|
|
311
327
|
positionals: NO_POSITIONALS,
|
|
312
328
|
constraints: EMPTY_CONSTRAINTS,
|
|
313
|
-
helpGroup: 'utilities',
|
|
314
329
|
handlerOwner: 'inline',
|
|
315
330
|
networkEffect: 'conditional_network',
|
|
316
331
|
common: true,
|
|
@@ -322,7 +337,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
322
337
|
}),
|
|
323
338
|
commands: commandSeed({
|
|
324
339
|
reference: {
|
|
325
|
-
usage: 'whoisleuth commands [--common] [--group <group>] [--mode <offline|network>] [--json] [--quiet] [--no-color]',
|
|
326
340
|
description: 'List the installed command contracts in terminal or versioned JSON form.',
|
|
327
341
|
example: 'whoisleuth commands --json',
|
|
328
342
|
boundary: 'Catalogue generation is offline. It reports declared command modes and limits without executing collection or inspecting local evidence.',
|
|
@@ -332,7 +346,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
332
346
|
options: ['--common', '--group', '--mode', '--json', '--quiet', '--no-color'],
|
|
333
347
|
positionals: NO_POSITIONALS,
|
|
334
348
|
constraints: Object.freeze([]),
|
|
335
|
-
helpGroup: 'utilities',
|
|
336
349
|
handlerOwner: 'inline',
|
|
337
350
|
networkEffect: 'offline',
|
|
338
351
|
common: true,
|
|
@@ -344,7 +357,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
344
357
|
}),
|
|
345
358
|
manual: commandSeed({
|
|
346
359
|
reference: {
|
|
347
|
-
usage: 'whoisleuth manual',
|
|
348
360
|
description: 'Print a generated roff manual page for local installation.',
|
|
349
361
|
example: 'whoisleuth manual | man -l -',
|
|
350
362
|
boundary: 'Generation is offline and derives from the same command catalogue as focused help.',
|
|
@@ -354,7 +366,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
354
366
|
options: [],
|
|
355
367
|
positionals: NO_POSITIONALS,
|
|
356
368
|
constraints: EMPTY_CONSTRAINTS,
|
|
357
|
-
helpGroup: 'utilities',
|
|
358
369
|
handlerOwner: 'inline',
|
|
359
370
|
networkEffect: 'offline',
|
|
360
371
|
common: false,
|
|
@@ -366,7 +377,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
366
377
|
}),
|
|
367
378
|
manifest: commandSeed({
|
|
368
379
|
reference: {
|
|
369
|
-
usage: 'whoisleuth manifest <artefact.json> [...] --workflow <label> [--configuration-digest <sha256:digest>] [--json] [--quiet] [--no-color]',
|
|
370
380
|
description: 'Record an ordered, path-free manifest for up to 16 local JSON artefacts.',
|
|
371
381
|
example: 'whoisleuth manifest lookup.json comparison.json --workflow "domain review" --json',
|
|
372
382
|
boundary: 'The command records hashes and bounded schema metadata only. It omits source paths and artefact contents and performs no network collection.',
|
|
@@ -378,7 +388,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
378
388
|
constraints: Object.freeze([
|
|
379
389
|
constraint({ kind: 'required', options: ['--workflow'] }),
|
|
380
390
|
]),
|
|
381
|
-
helpGroup: 'assure',
|
|
382
391
|
handlerOwner: 'inline',
|
|
383
392
|
networkEffect: 'offline',
|
|
384
393
|
common: false,
|
|
@@ -390,7 +399,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
390
399
|
}),
|
|
391
400
|
"map-observations": commandSeed({
|
|
392
401
|
reference: {
|
|
393
|
-
usage: 'whoisleuth map-observations [mapping.json] [--json] [--quiet] [--no-color]',
|
|
394
402
|
description: 'Apply one bounded declarative field-mapping profile to local source observations.',
|
|
395
403
|
example: 'whoisleuth map-observations mapping.json --json',
|
|
396
404
|
boundary: 'Profiles select allowlisted dotted fields only. They execute no scripts, make no requests, and emit the browser-compatible external-findings contract.',
|
|
@@ -400,7 +408,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
400
408
|
options: ['--json', '--quiet', '--no-color'],
|
|
401
409
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
402
410
|
constraints: EMPTY_CONSTRAINTS,
|
|
403
|
-
helpGroup: 'respond',
|
|
404
411
|
handlerOwner: 'inline',
|
|
405
412
|
networkEffect: 'offline',
|
|
406
413
|
common: false,
|
|
@@ -412,7 +419,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
412
419
|
}),
|
|
413
420
|
"oam-export": commandSeed({
|
|
414
421
|
reference: {
|
|
415
|
-
usage: 'whoisleuth oam-export [external-findings.json] [--json] [--quiet] [--no-color]',
|
|
416
422
|
description: 'Project browser-compatible external findings into a bounded Open Asset Model bridge document.',
|
|
417
423
|
example: 'whoisleuth oam-export external-findings.json --json',
|
|
418
424
|
boundary: 'The projection is offline, preserves source completeness without inventing confidence, and covers only bounded FQDN, IP address, certificate, and related edge vocabulary.',
|
|
@@ -422,7 +428,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
422
428
|
options: ['--json', '--quiet', '--no-color'],
|
|
423
429
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
424
430
|
constraints: EMPTY_CONSTRAINTS,
|
|
425
|
-
helpGroup: 'respond',
|
|
426
431
|
handlerOwner: 'inline',
|
|
427
432
|
networkEffect: 'offline',
|
|
428
433
|
common: false,
|
|
@@ -434,7 +439,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
434
439
|
}),
|
|
435
440
|
lookup: commandSeed({
|
|
436
441
|
reference: {
|
|
437
|
-
usage: 'whoisleuth lookup [domain|IP|ASN] [--json|--junit|--markdown|--html] [--no-attribution] [--fast|--deep] [--observer <label>] [--vantage <label>] [--plan] [--summary|--verbose|--browse [--save-lookup <file>]] [--palette <auto|light|dark>] [--strict-exit] [--fail-on <policies>] [--events] [--quiet] [--no-color]',
|
|
438
442
|
description: 'Collect registration evidence for one domain, IP, or ASN.',
|
|
439
443
|
example: 'whoisleuth lookup example.test --deep --browse',
|
|
440
444
|
boundary: 'Fast is the default. An ICANN-recognised public domain, reserved documentation domain, IP, or ASN may occupy command position as shorthand; it delegates to this same parser and URL-like input requires the explicit lookup command. Deep mode adds bounded WHOIS, DNS, HTTP, TLS, technology, posture, and network context where applicable. A full Deep homepage observation can derive fixed publication and delivery/cache summaries from the same response without retaining raw metadata values or making another request. --browse opens before collection, shows aggregate Fast progress or independently settled planned Deep sources, and then navigates allowlisted retained fields in the completed document. Press ? for help and / to search rendered panel text only. Closing during collection cancels without a partial document. --save-lookup writes the exact completed private JSON only after a normal browser close; it can contain normalised evidence omitted from panels and refuses an existing path.',
|
|
@@ -454,7 +458,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
454
458
|
constraint({ kind: 'requires_any', option: '--no-attribution', requiredOptions: ['--markdown', '--html'] }),
|
|
455
459
|
constraint({ kind: 'excludes_all', option: '--plan', excludedOptions: ['--junit', '--markdown', '--html', '--summary', '--verbose', '--strict-exit', '--events', '--quiet', '--fail-on'] }),
|
|
456
460
|
]),
|
|
457
|
-
helpGroup: 'investigate',
|
|
458
461
|
handlerOwner: 'lookup',
|
|
459
462
|
networkEffect: 'conditional_network',
|
|
460
463
|
common: true,
|
|
@@ -466,7 +469,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
466
469
|
}),
|
|
467
470
|
bulk: commandSeed({
|
|
468
471
|
reference: {
|
|
469
|
-
usage: 'whoisleuth bulk [file] [--json|--jsonl|--junit|--csv|--domains|--queries] [--registered-only|--inconclusive-only|--errors-only] [--fast|--deep] [--concurrency <1-8>] [--checkpoint <file> [--resume]] [--events] [--plan] [--fail-on <policies>] [--quiet] [--no-color]',
|
|
470
472
|
description: 'Triage newline-delimited domains, IPs, or ASNs with bounded concurrency.',
|
|
471
473
|
example: 'cat domains.txt | whoisleuth bulk --jsonl',
|
|
472
474
|
boundary: 'Fast and deep jobs use separate concurrency ceilings. Filters affect output only; collection failures and inconclusive authority states remain explicit in JSON, JSONL, and CSV.',
|
|
@@ -482,7 +484,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
482
484
|
constraint({ kind: 'requires_all', option: '--resume', requiredOptions: ['--checkpoint'] }),
|
|
483
485
|
constraint({ kind: 'excludes_all', option: '--plan', excludedOptions: ['--jsonl', '--junit', '--csv', '--domains', '--queries', '--events', '--checkpoint', '--resume', '--quiet', '--fail-on'] }),
|
|
484
486
|
]),
|
|
485
|
-
helpGroup: 'investigate',
|
|
486
487
|
handlerOwner: 'bulk',
|
|
487
488
|
networkEffect: 'conditional_network',
|
|
488
489
|
common: true,
|
|
@@ -494,7 +495,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
494
495
|
}),
|
|
495
496
|
"ct-search": commandSeed({
|
|
496
497
|
reference: {
|
|
497
|
-
usage: 'whoisleuth ct-search [keyword] [--json] [--quiet] [--no-color]',
|
|
498
498
|
description: 'Search certificate-transparency observations for one bounded keyword.',
|
|
499
499
|
example: 'whoisleuth ct-search "example brand" --json',
|
|
500
500
|
boundary: 'Certificate observations do not prove website activity, registration ownership, or malicious intent.',
|
|
@@ -504,7 +504,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
504
504
|
options: ['--json', '--quiet', '--no-color'],
|
|
505
505
|
positionals: Object.freeze([positional('keyword', 'text', 0, 1, [], 'argv_or_stdin')]),
|
|
506
506
|
constraints: EMPTY_CONSTRAINTS,
|
|
507
|
-
helpGroup: 'investigate',
|
|
508
507
|
handlerOwner: 'network',
|
|
509
508
|
networkEffect: 'always_network',
|
|
510
509
|
common: false,
|
|
@@ -516,7 +515,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
516
515
|
}),
|
|
517
516
|
"ct-intake": commandSeed({
|
|
518
517
|
reference: {
|
|
519
|
-
usage: 'whoisleuth ct-intake [events.json] [--json] [--quiet] [--no-color]',
|
|
520
518
|
description: 'Normalise source-qualified local certificate events into browser-compatible findings.',
|
|
521
519
|
example: 'whoisleuth ct-intake certificate-events.json --json',
|
|
522
520
|
boundary: 'The command is offline, caps output at 100 findings, and treats every event as a review lead rather than proof of serving or control.',
|
|
@@ -526,7 +524,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
526
524
|
options: ['--json', '--quiet', '--no-color'],
|
|
527
525
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
528
526
|
constraints: EMPTY_CONSTRAINTS,
|
|
529
|
-
helpGroup: 'investigate',
|
|
530
527
|
handlerOwner: 'inline',
|
|
531
528
|
networkEffect: 'offline',
|
|
532
529
|
common: false,
|
|
@@ -538,7 +535,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
538
535
|
}),
|
|
539
536
|
discover: commandSeed({
|
|
540
537
|
reference: {
|
|
541
|
-
usage: 'whoisleuth discover [brand|domain] [--tlds <list>] [--preset <name>|--families <ids>] [--keyboard <layout>] [--dictionary <file>] [--snapshot <file>] [--json|--jsonl|--domains] [--quiet] [--no-color]',
|
|
542
538
|
description: 'Generate bounded lookalike-domain candidates from local mutation rules.',
|
|
543
539
|
example: 'whoisleuth discover example.test --preset common --jsonl',
|
|
544
540
|
boundary: 'Generation and optional local snapshot comparison are offline. Candidates are leads only and are not resolved, registered, or classified as malicious.',
|
|
@@ -552,7 +548,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
552
548
|
constraint({ kind: 'mutually_exclusive', options: ['--preset', '--families'] }),
|
|
553
549
|
constraint({ kind: 'value_excludes', option: '--preset', value: 'common', excludedOptions: ['--dictionary'] }),
|
|
554
550
|
]),
|
|
555
|
-
helpGroup: 'investigate',
|
|
556
551
|
handlerOwner: 'discovery',
|
|
557
552
|
networkEffect: 'offline',
|
|
558
553
|
common: true,
|
|
@@ -564,7 +559,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
564
559
|
}),
|
|
565
560
|
"discover-scan": commandSeed({
|
|
566
561
|
reference: {
|
|
567
|
-
usage: 'whoisleuth discover-scan [brand|domain] [--tlds <list>] [--preset <name>|--families <ids>] [--keyboard <layout>] [--dictionary <file>] [--fast|--deep] [--scan-limit <n>] [--chunk-size <n>] [--concurrency <n>] [--resolver <IPs>] [--allowlist <file>] [--checkpoint <file> [--resume]] [--observation-snapshot <file>] [--registered-only|--inconclusive-only|--acquisition-only|--suppressed-only] [--events] [--json|--jsonl|--csv|--domains] [--plan] [--fail-on <policies>] [--quiet] [--no-color]',
|
|
568
562
|
description: 'Generate a bounded candidate set, collect a selected subset, and produce a supervised review queue.',
|
|
569
563
|
example: 'whoisleuth discover-scan example.test --scan-limit 50 --checkpoint scan.json --json',
|
|
570
564
|
boundary: 'This command performs network collection. Fast compact lookup is the default; deep mode is capped at 50 candidates. Allowlisting changes review priority only and shared infrastructure remains a lead, not attribution.',
|
|
@@ -582,7 +576,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
582
576
|
constraint({ kind: 'value_excludes', option: '--preset', value: 'common', excludedOptions: ['--dictionary'] }),
|
|
583
577
|
constraint({ kind: 'excludes_all', option: '--plan', excludedOptions: ['--jsonl', '--csv', '--domains', '--events', '--checkpoint', '--resume', '--observation-snapshot', '--quiet', '--fail-on'] }),
|
|
584
578
|
]),
|
|
585
|
-
helpGroup: 'investigate',
|
|
586
579
|
handlerOwner: 'discovery_scan',
|
|
587
580
|
networkEffect: 'conditional_network',
|
|
588
581
|
common: true,
|
|
@@ -594,7 +587,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
594
587
|
}),
|
|
595
588
|
posture: commandSeed({
|
|
596
589
|
reference: {
|
|
597
|
-
usage: 'whoisleuth posture [domain] [--selectors <list>] [--retired-selectors <list>] [--mail-profile <profile>] [--json|--sarif --owned-domain] [--quiet] [--no-color]',
|
|
598
590
|
description: 'Review bounded DNS mail, delegation, and domain-control posture.',
|
|
599
591
|
example: 'whoisleuth posture example.test --mail-profile standard --json',
|
|
600
592
|
boundary: 'Missing or failed DNS observations remain inconclusive and are not reported as absent controls.',
|
|
@@ -607,7 +599,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
607
599
|
constraint({ kind: 'mutually_exclusive', options: ['--json', '--sarif'] }),
|
|
608
600
|
constraint({ kind: 'requires_all', option: '--sarif', requiredOptions: ['--owned-domain'] }),
|
|
609
601
|
]),
|
|
610
|
-
helpGroup: 'investigate',
|
|
611
602
|
handlerOwner: 'network',
|
|
612
603
|
networkEffect: 'always_network',
|
|
613
604
|
common: false,
|
|
@@ -619,7 +610,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
619
610
|
}),
|
|
620
611
|
http: commandSeed({
|
|
621
612
|
reference: {
|
|
622
|
-
usage: 'whoisleuth http [domain] [--json] [--quiet] [--no-color]',
|
|
623
613
|
description: 'Inspect one homepage request, redirects, and bounded response metadata.',
|
|
624
614
|
example: 'whoisleuth http example.test --json',
|
|
625
615
|
boundary: 'Requests use the shared public-address and redirect guards. Fixed content-coding and cache-policy metadata describes only the selected response, excludes raw header values, and does not prove caching, transfer savings, performance, privacy, or safety. This is not a rendered browser or vulnerability scan.',
|
|
@@ -629,7 +619,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
629
619
|
options: ['--json', '--quiet', '--no-color'],
|
|
630
620
|
positionals: Object.freeze([positional('domain', 'text', 0, 1, [], 'argv_or_stdin')]),
|
|
631
621
|
constraints: EMPTY_CONSTRAINTS,
|
|
632
|
-
helpGroup: 'investigate',
|
|
633
622
|
handlerOwner: 'network',
|
|
634
623
|
networkEffect: 'always_network',
|
|
635
624
|
common: false,
|
|
@@ -641,7 +630,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
641
630
|
}),
|
|
642
631
|
tls: commandSeed({
|
|
643
632
|
reference: {
|
|
644
|
-
usage: 'whoisleuth tls [hostname] [--json] [--quiet] [--no-color]',
|
|
645
633
|
description: 'Inspect one hostname certificate through a bounded TLS connection.',
|
|
646
634
|
example: 'whoisleuth tls example.test --json',
|
|
647
635
|
boundary: 'One observed connection is point-in-time evidence and does not establish every address, edge, or historical certificate.',
|
|
@@ -651,7 +639,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
651
639
|
options: ['--json', '--quiet', '--no-color'],
|
|
652
640
|
positionals: Object.freeze([positional('hostname', 'text', 0, 1, [], 'argv_or_stdin')]),
|
|
653
641
|
constraints: EMPTY_CONSTRAINTS,
|
|
654
|
-
helpGroup: 'investigate',
|
|
655
642
|
handlerOwner: 'network',
|
|
656
643
|
networkEffect: 'always_network',
|
|
657
644
|
common: false,
|
|
@@ -663,7 +650,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
663
650
|
}),
|
|
664
651
|
"dnssec-validate": commandSeed({
|
|
665
652
|
reference: {
|
|
666
|
-
usage: 'whoisleuth dnssec-validate <domain> --resolver <public-IP> --trust-anchor <anchor.json> --owned-or-authorized [--json] [--quiet] [--no-color]',
|
|
667
653
|
description: 'Cryptographically validate one authorised DNSSEC chain from a supplied trust anchor through one selected public resolver.',
|
|
668
654
|
example: 'whoisleuth dnssec-validate example.test --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --json',
|
|
669
655
|
boundary: 'This isolated action is never invoked by Lookup, Bulk, monitoring, or recipes. It caps DNS queries, aliases, delegations, bytes, and duration; transport and validation failures remain separate, and secure is not a general safety verdict.',
|
|
@@ -675,7 +661,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
675
661
|
constraints: Object.freeze([
|
|
676
662
|
constraint({ kind: 'required', options: ['--resolver', '--trust-anchor', '--owned-or-authorized'] }),
|
|
677
663
|
]),
|
|
678
|
-
helpGroup: 'assure',
|
|
679
664
|
handlerOwner: 'network',
|
|
680
665
|
networkEffect: 'always_network',
|
|
681
666
|
common: false,
|
|
@@ -687,7 +672,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
687
672
|
}),
|
|
688
673
|
"mail-transport": commandSeed({
|
|
689
674
|
reference: {
|
|
690
|
-
usage: 'whoisleuth mail-transport [input.json] --resolver <public-IP> --trust-anchor <anchor.json> --owned-or-authorized --active-probe [--json] [--quiet] [--no-color]',
|
|
691
675
|
description: 'Review selected authorised MX endpoints, DNSSEC-qualified TLSA evidence, SMTP capabilities, and optional STARTTLS certificates.',
|
|
692
676
|
example: 'whoisleuth mail-transport selected-mx.json --resolver "$PUBLIC_RESOLVER_IP" --trust-anchor anchor.json --owned-or-authorized --active-probe --json',
|
|
693
677
|
boundary: 'This isolated action probes at most three selected MX hosts sequentially, reports selection, public revalidation, connection, and address authentication separately, sends only EHLO and optional STARTTLS, never retries, and performs no authentication, relay, recipient, mailbox, catch-all, or message test. If a DANE-TA TLSA usage 2 association is published, active collection retains only the leaf certificate and leaves that comparison partial without certificate-path construction and trust-anchor path validation. SMTP relay PKIX-TA usage 0 and PKIX-EE usage 1 records remain unsupported and cannot complete SMTP DANE assurance; a separate usage 3 match remains eligible.',
|
|
@@ -699,7 +683,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
699
683
|
constraints: Object.freeze([
|
|
700
684
|
constraint({ kind: 'required', options: ['--resolver', '--trust-anchor', '--owned-or-authorized', '--active-probe'] }),
|
|
701
685
|
]),
|
|
702
|
-
helpGroup: 'assure',
|
|
703
686
|
handlerOwner: 'network',
|
|
704
687
|
networkEffect: 'always_network',
|
|
705
688
|
common: false,
|
|
@@ -711,7 +694,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
711
694
|
}),
|
|
712
695
|
"registry-support": commandSeed({
|
|
713
696
|
reference: {
|
|
714
|
-
usage: 'whoisleuth registry-support [domain|suffix] [--json] [--quiet] [--no-color]',
|
|
715
697
|
description: 'Explain the local registry capability profile for one domain or suffix.',
|
|
716
698
|
example: 'whoisleuth registry-support example.test --json',
|
|
717
699
|
boundary: 'This command is offline. Catalogue coverage does not test live reachability or decide registration or availability.',
|
|
@@ -721,7 +703,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
721
703
|
options: ['--json', '--quiet', '--no-color'],
|
|
722
704
|
positionals: Object.freeze([positional('domain-or-suffix', 'text', 0, 1, [], 'argv_or_stdin')]),
|
|
723
705
|
constraints: EMPTY_CONSTRAINTS,
|
|
724
|
-
helpGroup: 'investigate',
|
|
725
706
|
handlerOwner: 'inline',
|
|
726
707
|
networkEffect: 'offline',
|
|
727
708
|
common: false,
|
|
@@ -733,7 +714,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
733
714
|
}),
|
|
734
715
|
"registry-doctor": commandSeed({
|
|
735
716
|
reference: {
|
|
736
|
-
usage: 'whoisleuth registry-doctor [lookup.json] [--json] [--quiet] [--no-color]',
|
|
737
717
|
description: 'Compare a saved Lookup registry result with the reviewed local capability profile.',
|
|
738
718
|
example: 'whoisleuth registry-doctor lookup.json --json',
|
|
739
719
|
boundary: 'The command is offline. It distinguishes expected access constraints from collection results and does not contact a live registry.',
|
|
@@ -743,7 +723,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
743
723
|
options: ['--json', '--quiet', '--no-color'],
|
|
744
724
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
745
725
|
constraints: EMPTY_CONSTRAINTS,
|
|
746
|
-
helpGroup: 'investigate',
|
|
747
726
|
handlerOwner: 'inline',
|
|
748
727
|
networkEffect: 'offline',
|
|
749
728
|
common: false,
|
|
@@ -755,7 +734,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
755
734
|
}),
|
|
756
735
|
"registry-cohort": commandSeed({
|
|
757
736
|
reference: {
|
|
758
|
-
usage: 'whoisleuth registry-cohort [lookups-or-reports.json|jsonl] [--json] [--quiet] [--no-color]',
|
|
759
737
|
description: 'Build privacy-safe suffix and capability-profile timelines from saved observations or retained cohort reports.',
|
|
760
738
|
example: 'whoisleuth registry-cohort saved-lookups.jsonl --json',
|
|
761
739
|
boundary: 'This command is offline and omits domains, queries, and raw evidence. Input families cannot be mixed, and retained samples are never assumed independent.',
|
|
@@ -765,7 +743,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
765
743
|
options: ['--json', '--quiet', '--no-color'],
|
|
766
744
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
767
745
|
constraints: EMPTY_CONSTRAINTS,
|
|
768
|
-
helpGroup: 'investigate',
|
|
769
746
|
handlerOwner: 'inline',
|
|
770
747
|
networkEffect: 'offline',
|
|
771
748
|
common: false,
|
|
@@ -777,7 +754,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
777
754
|
}),
|
|
778
755
|
"registry-scaffold": commandSeed({
|
|
779
756
|
reference: {
|
|
780
|
-
usage: 'whoisleuth registry-scaffold --profile <id> --suffix <suffix> --scenario <registered|not_found|inconclusive>',
|
|
781
757
|
description: 'Create a bounded synthetic WHOIS fixture scaffold for one existing capability profile.',
|
|
782
758
|
example: 'whoisleuth registry-scaffold --profile example-profile --suffix test --scenario registered',
|
|
783
759
|
boundary: 'The output is a sanitised template only. Its command-owned --profile selects fixture capability, --config is rejected, and contributors must not paste live responses or personal registration data into fixtures.',
|
|
@@ -789,7 +765,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
789
765
|
constraints: Object.freeze([
|
|
790
766
|
constraint({ kind: 'required', options: ['--profile', '--suffix', '--scenario'] }),
|
|
791
767
|
]),
|
|
792
|
-
helpGroup: 'utilities',
|
|
793
768
|
handlerOwner: 'inline',
|
|
794
769
|
networkEffect: 'offline',
|
|
795
770
|
common: false,
|
|
@@ -801,7 +776,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
801
776
|
}),
|
|
802
777
|
"risk-calibrate": commandSeed({
|
|
803
778
|
reference: {
|
|
804
|
-
usage: 'whoisleuth risk-calibrate [dataset.json] [--json|--summary-json] [--quiet] [--no-color]',
|
|
805
779
|
description: 'Replay reviewed labels against the current explainable Risk model.',
|
|
806
780
|
example: 'whoisleuth risk-calibrate calibration.json --summary-json',
|
|
807
781
|
boundary: 'Calibration is offline and diagnostic. The summary form omits record identifiers, domains, and evidence; neither form trains, tunes, or changes the scoring model automatically.',
|
|
@@ -813,7 +787,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
813
787
|
constraints: Object.freeze([
|
|
814
788
|
constraint({ kind: 'mutually_exclusive', options: ['--json', '--summary-json'] }),
|
|
815
789
|
]),
|
|
816
|
-
helpGroup: 'assure',
|
|
817
790
|
handlerOwner: 'inline',
|
|
818
791
|
networkEffect: 'offline',
|
|
819
792
|
common: false,
|
|
@@ -825,7 +798,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
825
798
|
}),
|
|
826
799
|
"lookalike-calibrate": commandSeed({
|
|
827
800
|
reference: {
|
|
828
|
-
usage: 'whoisleuth lookalike-calibrate [dataset.json] [--json] [--quiet] [--no-color]',
|
|
829
801
|
description: 'Summarise reviewed candidate dispositions by mutation family without retaining domains.',
|
|
830
802
|
example: 'whoisleuth lookalike-calibrate reviewed-candidates.json --json',
|
|
831
803
|
boundary: 'Calibration is offline and diagnostic. It omits candidate identifiers, domains, notes, and evidence and never tunes generation or filtering automatically.',
|
|
@@ -835,7 +807,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
835
807
|
options: ['--json', '--quiet', '--no-color'],
|
|
836
808
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
837
809
|
constraints: EMPTY_CONSTRAINTS,
|
|
838
|
-
helpGroup: 'assure',
|
|
839
810
|
handlerOwner: 'inline',
|
|
840
811
|
networkEffect: 'offline',
|
|
841
812
|
common: false,
|
|
@@ -847,7 +818,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
847
818
|
}),
|
|
848
819
|
"verify-artifact": commandSeed({
|
|
849
820
|
reference: {
|
|
850
|
-
usage: 'whoisleuth verify-artifact [artifact.json] [--passphrase-file <file>] [--manifest <manifest.json> --manifest-entry <artifact-N>] [--json] [--strict-exit] [--quiet] [--no-color]',
|
|
851
821
|
description: 'Validate a supported archive, claim passport, packet, manifest, saved Lookup, or supported Lookup-evidence export without printing evidence contents.',
|
|
852
822
|
example: 'whoisleuth verify-artifact report.json --manifest manifest.json --manifest-entry artifact-2 --json --strict-exit',
|
|
853
823
|
boundary: 'Verification is offline and redacted. Encrypted archives require an explicitly supplied passphrase file; --strict-exit returns 4 when only an envelope or legacy projection integrity was verified.',
|
|
@@ -860,7 +830,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
860
830
|
constraint({ kind: 'requires_all', option: '--manifest', requiredOptions: ['--manifest-entry'] }),
|
|
861
831
|
constraint({ kind: 'requires_all', option: '--manifest-entry', requiredOptions: ['--manifest'] }),
|
|
862
832
|
]),
|
|
863
|
-
helpGroup: 'assure',
|
|
864
833
|
handlerOwner: 'inline',
|
|
865
834
|
networkEffect: 'offline',
|
|
866
835
|
common: true,
|
|
@@ -872,7 +841,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
872
841
|
}),
|
|
873
842
|
"interchange-report": commandSeed({
|
|
874
843
|
reference: {
|
|
875
|
-
usage: 'whoisleuth interchange-report [artifact.json] [--passphrase-file <file>] [--json] [--quiet] [--no-color]',
|
|
876
844
|
description: 'Report what one recognised portable artefact preserves, excludes, and supports across browser and CLI workflows.',
|
|
877
845
|
example: 'whoisleuth interchange-report workspace.json --json',
|
|
878
846
|
boundary: 'The report is offline and metadata-only. It does not echo targets, contacts, notes, passphrases, evidence values, or an unrecognised schema string.',
|
|
@@ -882,7 +850,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
882
850
|
options: ['--passphrase-file', '--json', '--quiet', '--no-color'],
|
|
883
851
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
884
852
|
constraints: EMPTY_CONSTRAINTS,
|
|
885
|
-
helpGroup: 'assure',
|
|
886
853
|
handlerOwner: 'inline',
|
|
887
854
|
networkEffect: 'offline',
|
|
888
855
|
common: false,
|
|
@@ -894,7 +861,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
894
861
|
}),
|
|
895
862
|
"inspect-archive": commandSeed({
|
|
896
863
|
reference: {
|
|
897
|
-
usage: 'whoisleuth inspect-archive [archive.json] [--passphrase-file <file>] [--search <value>] [--require-match] [--reveal] [--expect-content-digest <sha256:digest>] [--json] [--quiet] [--no-color]',
|
|
898
864
|
description: `Summarise or search one current version-${WORKSPACE_ARCHIVE_VERSION} workspace archive, with exact ${LEGACY_WORKSPACE_ARCHIVE_DESCRIPTION} support and redacted output by default.`,
|
|
899
865
|
example: 'whoisleuth inspect-archive workspace.json --search example.test --json',
|
|
900
866
|
boundary: 'Exact matches require --reveal. Retired and future archive versions are rejected without changing data. The archive is read locally and is never uploaded.',
|
|
@@ -907,7 +873,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
907
873
|
constraint({ kind: 'requires_all', option: '--reveal', requiredOptions: ['--search'] }),
|
|
908
874
|
constraint({ kind: 'requires_all', option: '--require-match', requiredOptions: ['--search'] }),
|
|
909
875
|
]),
|
|
910
|
-
helpGroup: 'assure',
|
|
911
876
|
handlerOwner: 'evidence',
|
|
912
877
|
networkEffect: 'offline',
|
|
913
878
|
common: false,
|
|
@@ -919,7 +884,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
919
884
|
}),
|
|
920
885
|
"sign-artifact": commandSeed({
|
|
921
886
|
reference: {
|
|
922
|
-
usage: 'whoisleuth sign-artifact [artifact.json] --private-key-file <file>',
|
|
923
887
|
description: 'Sign one reviewed response packet or supported manifest with a local private key.',
|
|
924
888
|
example: 'whoisleuth sign-artifact packet.json --private-key-file analyst-private.pem',
|
|
925
889
|
boundary: 'The command never creates, stores, or transmits keys. Key custody and signer identity remain the operator\'s responsibility.',
|
|
@@ -931,7 +895,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
931
895
|
constraints: Object.freeze([
|
|
932
896
|
constraint({ kind: 'required', options: ['--private-key-file'] }),
|
|
933
897
|
]),
|
|
934
|
-
helpGroup: 'assure',
|
|
935
898
|
handlerOwner: 'evidence',
|
|
936
899
|
networkEffect: 'offline',
|
|
937
900
|
common: false,
|
|
@@ -943,7 +906,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
943
906
|
}),
|
|
944
907
|
"verify-signature": commandSeed({
|
|
945
908
|
reference: {
|
|
946
|
-
usage: 'whoisleuth verify-signature [package.json] [--public-key-file <file>] [--json] [--quiet] [--no-color]',
|
|
947
909
|
description: 'Verify the cryptographic signature of one signed evidence package and report embedded-artefact assurance separately.',
|
|
948
910
|
example: 'whoisleuth verify-signature packet.signed.json --json',
|
|
949
911
|
boundary: 'A valid signature proves package consistency for the embedded key. It does not upgrade failed or unsupported embedded-artefact assurance or establish the holder\'s real-world identity or authority.',
|
|
@@ -953,7 +915,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
953
915
|
options: ['--public-key-file', '--json', '--quiet', '--no-color'],
|
|
954
916
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
955
917
|
constraints: EMPTY_CONSTRAINTS,
|
|
956
|
-
helpGroup: 'assure',
|
|
957
918
|
handlerOwner: 'evidence',
|
|
958
919
|
networkEffect: 'offline',
|
|
959
920
|
common: false,
|
|
@@ -965,7 +926,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
965
926
|
}),
|
|
966
927
|
"source-report": commandSeed({
|
|
967
928
|
reference: {
|
|
968
|
-
usage: 'whoisleuth source-report [lookup.json] [--json] [--quiet] [--no-color]',
|
|
969
929
|
description: 'Create a target-free reliability summary from a saved lookup.',
|
|
970
930
|
example: 'whoisleuth source-report lookup.json --json',
|
|
971
931
|
boundary: 'The report retains source states and timings but excludes targets, queries, endpoints, and raw evidence.',
|
|
@@ -975,7 +935,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
975
935
|
options: ['--json', '--quiet', '--no-color'],
|
|
976
936
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
977
937
|
constraints: EMPTY_CONSTRAINTS,
|
|
978
|
-
helpGroup: 'investigate',
|
|
979
938
|
handlerOwner: 'inline',
|
|
980
939
|
networkEffect: 'offline',
|
|
981
940
|
common: false,
|
|
@@ -987,7 +946,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
987
946
|
}),
|
|
988
947
|
compare: commandSeed({
|
|
989
948
|
reference: {
|
|
990
|
-
usage: 'whoisleuth compare [lookup.json] [--json] [--quiet] [--no-color]',
|
|
991
949
|
description: 'Compare separately attributed registry publications in a saved lookup.',
|
|
992
950
|
example: 'whoisleuth compare lookup.json --json',
|
|
993
951
|
boundary: 'Comparison is offline. Differences are review context and do not by themselves prove which publication is current.',
|
|
@@ -997,7 +955,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
997
955
|
options: ['--json', '--quiet', '--no-color'],
|
|
998
956
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
999
957
|
constraints: EMPTY_CONSTRAINTS,
|
|
1000
|
-
helpGroup: 'investigate',
|
|
1001
958
|
handlerOwner: 'inline',
|
|
1002
959
|
networkEffect: 'offline',
|
|
1003
960
|
common: false,
|
|
@@ -1009,7 +966,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1009
966
|
}),
|
|
1010
967
|
"page-compare": commandSeed({
|
|
1011
968
|
reference: {
|
|
1012
|
-
usage: 'whoisleuth page-compare <left.json> <right.json> [--json] [--quiet] [--no-color]',
|
|
1013
969
|
description: 'Compare static page identity, favicon, technology, and TLS evidence in two saved deep lookups.',
|
|
1014
970
|
example: 'whoisleuth page-compare official.json candidate.json --json',
|
|
1015
971
|
boundary: 'Comparison is offline and component-based. It executes no page code and produces no aggregate similarity or maliciousness score.',
|
|
@@ -1019,7 +975,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1019
975
|
options: ['--json', '--quiet', '--no-color'],
|
|
1020
976
|
positionals: Object.freeze([positional('sources', 'file', 2, 2)]),
|
|
1021
977
|
constraints: EMPTY_CONSTRAINTS,
|
|
1022
|
-
helpGroup: 'investigate',
|
|
1023
978
|
handlerOwner: 'inline',
|
|
1024
979
|
networkEffect: 'offline',
|
|
1025
980
|
common: false,
|
|
@@ -1031,7 +986,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1031
986
|
}),
|
|
1032
987
|
"mail-review": commandSeed({
|
|
1033
988
|
reference: {
|
|
1034
|
-
usage: 'whoisleuth mail-review [bulk.json|bulk.jsonl] [--json] [--quiet] [--no-color]',
|
|
1035
989
|
description: 'Review passive MX, null MX, SPF, DMARC, and shared mail-provider evidence from saved Bulk results.',
|
|
1036
990
|
example: 'whoisleuth mail-review candidates.json --json',
|
|
1037
991
|
boundary: 'Review is offline and sends no SMTP traffic. Missing or partial DNS evidence remains inconclusive.',
|
|
@@ -1041,7 +995,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1041
995
|
options: ['--json', '--quiet', '--no-color'],
|
|
1042
996
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
1043
997
|
constraints: EMPTY_CONSTRAINTS,
|
|
1044
|
-
helpGroup: 'investigate',
|
|
1045
998
|
handlerOwner: 'inline',
|
|
1046
999
|
networkEffect: 'offline',
|
|
1047
1000
|
common: false,
|
|
@@ -1051,9 +1004,28 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1051
1004
|
additionalOutputFormats: Object.freeze([]),
|
|
1052
1005
|
bootstrapProfile: 'allowed',
|
|
1053
1006
|
}),
|
|
1007
|
+
"mail-headers": commandSeed({
|
|
1008
|
+
reference: {
|
|
1009
|
+
description: 'Review identity, reported authentication, domain alignment, and Received routing from selected message headers.',
|
|
1010
|
+
example: 'whoisleuth mail-headers message.eml --json',
|
|
1011
|
+
boundary: 'Review is offline. It makes no DNS, SMTP, HTTP, registry, or provider request, and does not retain address local parts, display names, subjects, message bodies, attachments, or raw header values. Reported authentication is not independently validated.',
|
|
1012
|
+
},
|
|
1013
|
+
collection: { mode: 'offline', scope: 'Parses only the bounded header block from one selected message or standard input.' },
|
|
1014
|
+
summary: 'Review message headers offline',
|
|
1015
|
+
options: ['--json', '--quiet', '--no-color'],
|
|
1016
|
+
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
1017
|
+
constraints: EMPTY_CONSTRAINTS,
|
|
1018
|
+
handlerOwner: 'inline',
|
|
1019
|
+
networkEffect: 'offline',
|
|
1020
|
+
common: false,
|
|
1021
|
+
schemaIdentifiers: Object.freeze(['whoisleuth.cli.mail-header-review']),
|
|
1022
|
+
primaryArtefacts: Object.freeze([]),
|
|
1023
|
+
planSupport: false,
|
|
1024
|
+
additionalOutputFormats: Object.freeze([]),
|
|
1025
|
+
bootstrapProfile: 'allowed',
|
|
1026
|
+
}),
|
|
1054
1027
|
"review-evidence": commandSeed({
|
|
1055
1028
|
reference: {
|
|
1056
|
-
usage: 'whoisleuth review-evidence [evidence.json] [--mmdb <database-file>] [--json] [--strict-exit] [--quiet] [--no-color]',
|
|
1057
1029
|
description: 'Review one versioned DNS, domain-change, routing, GeoIP, RDAP, or trust-store document offline.',
|
|
1058
1030
|
example: 'whoisleuth review-evidence domain-change.json --json --strict-exit',
|
|
1059
1031
|
boundary: 'The command reads only the supplied document. It performs no DNS, RDAP, BGP, GeoIP-provider, TLS, HTTP, certificate-authority, or SMTP request.',
|
|
@@ -1063,7 +1035,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1063
1035
|
options: ['--mmdb', '--json', '--strict-exit', '--quiet', '--no-color'],
|
|
1064
1036
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
1065
1037
|
constraints: EMPTY_CONSTRAINTS,
|
|
1066
|
-
helpGroup: 'investigate',
|
|
1067
1038
|
handlerOwner: 'inline',
|
|
1068
1039
|
networkEffect: 'offline',
|
|
1069
1040
|
common: true,
|
|
@@ -1083,7 +1054,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1083
1054
|
}),
|
|
1084
1055
|
brief: commandSeed({
|
|
1085
1056
|
reference: {
|
|
1086
|
-
usage: 'whoisleuth brief [lookup.json] [--json] [--quiet] [--no-color]',
|
|
1087
1057
|
description: 'Turn one saved Lookup into a compact decision brief with facts, unknowns, contradictions, and next actions.',
|
|
1088
1058
|
example: 'whoisleuth brief lookup.json --json',
|
|
1089
1059
|
boundary: 'The command is offline, excludes raw upstream payloads, and does not create an analyst assertion or claim that the saved observation is current.',
|
|
@@ -1093,7 +1063,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1093
1063
|
options: ['--json', '--quiet', '--no-color'],
|
|
1094
1064
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
1095
1065
|
constraints: EMPTY_CONSTRAINTS,
|
|
1096
|
-
helpGroup: 'investigate',
|
|
1097
1066
|
handlerOwner: 'inline',
|
|
1098
1067
|
networkEffect: 'offline',
|
|
1099
1068
|
common: false,
|
|
@@ -1105,7 +1074,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1105
1074
|
}),
|
|
1106
1075
|
"case-pack": commandSeed({
|
|
1107
1076
|
reference: {
|
|
1108
|
-
usage: 'whoisleuth case-pack [cases.json] --audience <internal|trusted|public> --reviewed [--json] [--quiet] [--no-color]',
|
|
1109
1077
|
description: `Package browser-created Case-schema-${CASE_SCHEMA_VERSION} records as a reviewed, audience-specific Case-pack v2.`,
|
|
1110
1078
|
example: 'whoisleuth case-pack cases.json --audience trusted --reviewed --json',
|
|
1111
1079
|
boundary: 'The command is an offline handoff from the browser Case workflow: it creates a new package, never creates or mutates a durable Case, never mutates the source archive, and requires an explicit review acknowledgement.',
|
|
@@ -1117,7 +1085,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1117
1085
|
constraints: Object.freeze([
|
|
1118
1086
|
constraint({ kind: 'required', options: ['--audience', '--reviewed'] }),
|
|
1119
1087
|
]),
|
|
1120
|
-
helpGroup: 'respond',
|
|
1121
1088
|
handlerOwner: 'inline',
|
|
1122
1089
|
networkEffect: 'offline',
|
|
1123
1090
|
common: true,
|
|
@@ -1129,7 +1096,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1129
1096
|
}),
|
|
1130
1097
|
"domain-control": commandSeed({
|
|
1131
1098
|
reference: {
|
|
1132
|
-
usage: 'whoisleuth domain-control [manifest-input.json|review-input.json] [--json] [--quiet] [--no-color]',
|
|
1133
1099
|
description: 'Build an integrity-protected desired-state manifest or compare one with supplied observations.',
|
|
1134
1100
|
example: 'whoisleuth domain-control domain-control-input.json --json',
|
|
1135
1101
|
boundary: 'The command is offline and changes no registrar, DNS, mail, or certificate configuration. Only complete supplied observations can produce drift.',
|
|
@@ -1139,7 +1105,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1139
1105
|
options: ['--json', '--quiet', '--no-color'],
|
|
1140
1106
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
1141
1107
|
constraints: EMPTY_CONSTRAINTS,
|
|
1142
|
-
helpGroup: 'assure',
|
|
1143
1108
|
handlerOwner: 'inline',
|
|
1144
1109
|
networkEffect: 'offline',
|
|
1145
1110
|
common: false,
|
|
@@ -1151,7 +1116,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1151
1116
|
}),
|
|
1152
1117
|
"monitor-once": commandSeed({
|
|
1153
1118
|
reference: {
|
|
1154
|
-
usage: 'whoisleuth monitor-once [manifest.json] [--previous <snapshot.json>] [--limit <1-20>] [--concurrency <1-3>] [--fail-on <policies>] [--json|--junit] [--quiet] [--no-color]',
|
|
1155
1119
|
description: 'Collect one bounded owned-domain review and compare it with an optional prior checkpoint.',
|
|
1156
1120
|
example: 'whoisleuth monitor-once manifest.json --previous previous.json --json --output next.json',
|
|
1157
1121
|
boundary: 'This is an operator-scheduled one-shot collection, not a daemon. It caps targets and concurrency, retains normalised observations, and never changes domain configuration.',
|
|
@@ -1163,7 +1127,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1163
1127
|
constraints: Object.freeze([
|
|
1164
1128
|
constraint({ kind: 'mutually_exclusive', options: ['--json', '--junit'] }),
|
|
1165
1129
|
]),
|
|
1166
|
-
helpGroup: 'assure',
|
|
1167
1130
|
handlerOwner: 'inline',
|
|
1168
1131
|
networkEffect: 'always_network',
|
|
1169
1132
|
common: false,
|
|
@@ -1175,7 +1138,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1175
1138
|
}),
|
|
1176
1139
|
assurance: commandSeed({
|
|
1177
1140
|
reference: {
|
|
1178
|
-
usage: 'whoisleuth assurance [assurance-input.json] [--json] [--quiet] [--no-color]',
|
|
1179
1141
|
description: 'Review a versioned domain change, recovery-dependency, or retirement plan.',
|
|
1180
1142
|
example: 'whoisleuth assurance domain-assurance.json --json',
|
|
1181
1143
|
boundary: 'The command is offline and treats every provider label, readiness state, and evidence reference as analyst-authored input. It changes no configuration.',
|
|
@@ -1185,7 +1147,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1185
1147
|
options: ['--json', '--quiet', '--no-color'],
|
|
1186
1148
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
1187
1149
|
constraints: EMPTY_CONSTRAINTS,
|
|
1188
|
-
helpGroup: 'assure',
|
|
1189
1150
|
handlerOwner: 'inline',
|
|
1190
1151
|
networkEffect: 'offline',
|
|
1191
1152
|
common: false,
|
|
@@ -1197,7 +1158,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1197
1158
|
}),
|
|
1198
1159
|
"change-packet": commandSeed({
|
|
1199
1160
|
reference: {
|
|
1200
|
-
usage: 'whoisleuth change-packet [change-packet-input.json] [--json] [--quiet] [--no-color]',
|
|
1201
1161
|
description: 'Assemble pre-change, post-change, and planning evidence into one integrity-protected packet.',
|
|
1202
1162
|
example: 'whoisleuth change-packet change-review.json --json',
|
|
1203
1163
|
boundary: 'Assembly is offline. Readiness reflects only the supplied bounded evidence and does not authorise or perform a domain change.',
|
|
@@ -1207,7 +1167,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1207
1167
|
options: ['--json', '--quiet', '--no-color'],
|
|
1208
1168
|
positionals: OPTIONAL_FILE_POSITIONAL,
|
|
1209
1169
|
constraints: EMPTY_CONSTRAINTS,
|
|
1210
|
-
helpGroup: 'respond',
|
|
1211
1170
|
handlerOwner: 'inline',
|
|
1212
1171
|
networkEffect: 'offline',
|
|
1213
1172
|
common: false,
|
|
@@ -1219,7 +1178,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1219
1178
|
}),
|
|
1220
1179
|
"sharing-review": commandSeed({
|
|
1221
1180
|
reference: {
|
|
1222
|
-
usage: 'whoisleuth sharing-review [artifact.json] --marking <level> --recipient-scope <scope> --purpose <text> [--human-reviewed] [--personal-data-reviewed] [--redactions-confirmed] [--json] [--quiet] [--no-color]',
|
|
1223
1181
|
description: 'Lint one reviewed artefact against local integrity, marking, recipient, personal-data, and redaction controls.',
|
|
1224
1182
|
example: 'whoisleuth sharing-review packet.json --marking amber --recipient-scope organization --purpose "Reviewed incident handoff" --human-reviewed --personal-data-reviewed --redactions-confirmed --json',
|
|
1225
1183
|
boundary: 'The command is offline and emits only bounded schema/version metadata, no content values, and no raw evidence. Its result is a review aid, not legal advice or recipient authorisation.',
|
|
@@ -1231,7 +1189,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1231
1189
|
constraints: Object.freeze([
|
|
1232
1190
|
constraint({ kind: 'required', options: ['--marking', '--recipient-scope', '--purpose'] }),
|
|
1233
1191
|
]),
|
|
1234
|
-
helpGroup: 'respond',
|
|
1235
1192
|
handlerOwner: 'inline',
|
|
1236
1193
|
networkEffect: 'offline',
|
|
1237
1194
|
common: false,
|
|
@@ -1243,7 +1200,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1243
1200
|
}),
|
|
1244
1201
|
"workflow-plan": commandSeed({
|
|
1245
1202
|
reference: {
|
|
1246
|
-
usage: 'whoisleuth workflow-plan <recipe> <domain|brand> | --list | --explain <recipe> [--json] [--quiet] [--no-color]',
|
|
1247
1203
|
description: 'Build a fixed domain-investigation plan from existing bounded CLI commands.',
|
|
1248
1204
|
example: 'whoisleuth workflow-plan domain-triage example.test --json',
|
|
1249
1205
|
boundary: 'Planning is offline and plan-only. It does not execute commands, expand placeholders, read files, make requests, or submit evidence.',
|
|
@@ -1258,7 +1214,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1258
1214
|
constraints: Object.freeze([
|
|
1259
1215
|
constraint({ kind: 'mutually_exclusive', options: ['--list', '--explain'] }),
|
|
1260
1216
|
]),
|
|
1261
|
-
helpGroup: 'assure',
|
|
1262
1217
|
handlerOwner: 'inline',
|
|
1263
1218
|
networkEffect: 'offline',
|
|
1264
1219
|
common: true,
|
|
@@ -1270,7 +1225,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1270
1225
|
}),
|
|
1271
1226
|
"workflow-run": commandSeed({
|
|
1272
1227
|
reference: {
|
|
1273
|
-
usage: 'whoisleuth workflow-run <recipe> <domain|brand> [--select <step-id>=<path-or-value>]... [--approve-network] [--resume <state.json>] [--json] [--quiet] [--no-color]',
|
|
1274
1228
|
description: 'Execute approved steps from a fixed investigation recipe and emit a resumable checkpoint.',
|
|
1275
1229
|
example: 'whoisleuth workflow-run domain-triage example.test --resume run.json --select export=saved-lookup.json --json --output run-next.json',
|
|
1276
1230
|
boundary: 'Only installed recipe commands can run. Network steps require explicit approval for each invocation. Repeat --select in placeholder order for one step; each bounded value replaces one exact placeholder and cannot start with a hyphen, become an option, or invoke a shell.',
|
|
@@ -1283,7 +1237,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1283
1237
|
positional('subject', 'text', 1, 1),
|
|
1284
1238
|
]),
|
|
1285
1239
|
constraints: EMPTY_CONSTRAINTS,
|
|
1286
|
-
helpGroup: 'assure',
|
|
1287
1240
|
handlerOwner: 'inline',
|
|
1288
1241
|
networkEffect: 'conditional_network',
|
|
1289
1242
|
common: false,
|
|
@@ -1295,7 +1248,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1295
1248
|
}),
|
|
1296
1249
|
diff: commandSeed({
|
|
1297
1250
|
reference: {
|
|
1298
|
-
usage: 'whoisleuth diff <left.json> <right.json> [--left-session <id> --right-session <id>] [--json] [--quiet] [--no-color]',
|
|
1299
1251
|
description: 'Compare an earlier and later artefact from the same retained Lookup, Bulk-session, or domain-portfolio family.',
|
|
1300
1252
|
example: 'whoisleuth diff earlier.json later.json --json',
|
|
1301
1253
|
boundary: 'Comparison is offline: the left input is earlier and the right input is later. Inputs must belong to the same supported family. For a multi-session Bulk export, --left-session selects a session from the left file and --right-session selects one from the right; missing, unavailable, equal, and different evidence remain separate states.',
|
|
@@ -1305,7 +1257,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1305
1257
|
options: ['--left-session', '--right-session', '--json', '--quiet', '--no-color'],
|
|
1306
1258
|
positionals: Object.freeze([positional('sources', 'file', 2, 2)]),
|
|
1307
1259
|
constraints: EMPTY_CONSTRAINTS,
|
|
1308
|
-
helpGroup: 'assure',
|
|
1309
1260
|
handlerOwner: 'inline',
|
|
1310
1261
|
networkEffect: 'offline',
|
|
1311
1262
|
common: true,
|
|
@@ -1317,7 +1268,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1317
1268
|
}),
|
|
1318
1269
|
reconcile: commandSeed({
|
|
1319
1270
|
reference: {
|
|
1320
|
-
usage: 'whoisleuth reconcile <observation.json> <observation.json> [...] [--json] [--quiet] [--no-color]',
|
|
1321
1271
|
description: 'Reconcile bounded values across independently labelled observations of one domain.',
|
|
1322
1272
|
example: 'whoisleuth reconcile office.json mobile.json external.json --json',
|
|
1323
1273
|
boundary: 'The command is offline, accepts 2 to 5 saved observations for one domain, and never treats labels as proof of network independence or majority agreement as truth.',
|
|
@@ -1327,7 +1277,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1327
1277
|
options: ['--json', '--quiet', '--no-color'],
|
|
1328
1278
|
positionals: Object.freeze([positional('sources', 'file', 2, 5)]),
|
|
1329
1279
|
constraints: EMPTY_CONSTRAINTS,
|
|
1330
|
-
helpGroup: 'assure',
|
|
1331
1280
|
handlerOwner: 'inline',
|
|
1332
1281
|
networkEffect: 'offline',
|
|
1333
1282
|
common: false,
|
|
@@ -1339,7 +1288,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1339
1288
|
}),
|
|
1340
1289
|
timeline: commandSeed({
|
|
1341
1290
|
reference: {
|
|
1342
|
-
usage: 'whoisleuth timeline <observation.json> <observation.json> [...] [--json] [--quiet] [--no-color]',
|
|
1343
1291
|
description: 'Build an ordered same-domain history from saved Lookup observations.',
|
|
1344
1292
|
example: 'whoisleuth timeline first.json second.json latest.json --json',
|
|
1345
1293
|
boundary: 'The command is offline, accepts 2 to 20 bounded inputs for one domain, retains no filenames or raw registry payloads, and does not treat changed collection conditions as a domain change.',
|
|
@@ -1349,7 +1297,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1349
1297
|
options: ['--json', '--quiet', '--no-color'],
|
|
1350
1298
|
positionals: Object.freeze([positional('sources', 'file', 2, 20)]),
|
|
1351
1299
|
constraints: EMPTY_CONSTRAINTS,
|
|
1352
|
-
helpGroup: 'assure',
|
|
1353
1300
|
handlerOwner: 'inline',
|
|
1354
1301
|
networkEffect: 'offline',
|
|
1355
1302
|
common: false,
|
|
@@ -1361,7 +1308,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1361
1308
|
}),
|
|
1362
1309
|
export: commandSeed({
|
|
1363
1310
|
reference: {
|
|
1364
|
-
usage: 'whoisleuth export [lookup.json] [--markdown|--html|--compact] [--no-attribution]',
|
|
1365
1311
|
description: 'Convert one saved lookup into a versioned evidence report.',
|
|
1366
1312
|
example: 'whoisleuth export lookup.json --markdown',
|
|
1367
1313
|
boundary: `Saved Lookup versions 1 and 2 are capped at 8 MiB and scanned for duplicate keys, the prototype-sensitive __proto__ key, and bounded nesting, key, value, and per-container counts before parsing. Current schema-${LOOKUP_EVIDENCE_SCHEMA_VERSION} exports preserve evidence-source attribution and limitations; published v2 schema ${PUBLISHED_V2_LOOKUP_EVIDENCE_SCHEMA_VERSION} and exact v1 schema ${V1_PUBLIC_LOOKUP_EVIDENCE_SCHEMA_VERSION} remain readable, while other historical and unreleased shapes are unsupported. Markdown and HTML include a presentation-only generator footer unless --no-attribution is selected; JSON retains bounded generator provenance. Compact output intentionally omits raw registry payloads.`,
|
|
@@ -1375,7 +1321,6 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1375
1321
|
constraint({ kind: 'excludes_all', option: '--compact', excludedOptions: ['--markdown', '--html'] }),
|
|
1376
1322
|
constraint({ kind: 'requires_any', option: '--no-attribution', requiredOptions: ['--markdown', '--html'] }),
|
|
1377
1323
|
]),
|
|
1378
|
-
helpGroup: 'respond',
|
|
1379
1324
|
handlerOwner: 'inline',
|
|
1380
1325
|
networkEffect: 'offline',
|
|
1381
1326
|
common: true,
|
|
@@ -1387,10 +1332,9 @@ const COMMAND_SEEDS = Object.freeze({
|
|
|
1387
1332
|
}),
|
|
1388
1333
|
});
|
|
1389
1334
|
const COMMAND_ORDER = Object.freeze(Object.keys(COMMAND_SEEDS));
|
|
1390
|
-
const
|
|
1391
|
-
const HELP_COMMANDS_BY_GROUP = Object.freeze(Object.fromEntries(HELP_GROUP_ORDER.map((group) => [
|
|
1335
|
+
const HELP_COMMANDS_BY_GROUP = Object.freeze(Object.fromEntries(CLI_HELP_GROUP_ORDER.map((group) => [
|
|
1392
1336
|
group,
|
|
1393
|
-
Object.freeze(COMMAND_ORDER.filter((command) =>
|
|
1337
|
+
Object.freeze(COMMAND_ORDER.filter((command) => CLI_COMMAND_SEMANTICS[command].group === group)),
|
|
1394
1338
|
])));
|
|
1395
1339
|
function documentationMetadata(seed, commonOptions, commandOptions, positionals) {
|
|
1396
1340
|
const explicitAuthorisationRequired = commandOptions.some((option) => (option === '--owned-or-authorized' || option === '--active-probe' || option === '--approve-network'));
|
|
@@ -1432,6 +1376,45 @@ function documentationMetadata(seed, commonOptions, commandOptions, positionals)
|
|
|
1432
1376
|
primaryEvidenceArtefacts: seed.primaryArtefacts,
|
|
1433
1377
|
});
|
|
1434
1378
|
}
|
|
1379
|
+
function optionUsage(specification) {
|
|
1380
|
+
if (specification.arity === 0)
|
|
1381
|
+
return specification.option;
|
|
1382
|
+
if (specification.valueKind === 'enum') {
|
|
1383
|
+
return `${specification.option} <${specification.values.join('|')}>`;
|
|
1384
|
+
}
|
|
1385
|
+
if (specification.valueKind === 'policy_list')
|
|
1386
|
+
return `${specification.option} <policy[,policy...]>`;
|
|
1387
|
+
if (specification.valueKind === 'integer')
|
|
1388
|
+
return `${specification.option} <integer>`;
|
|
1389
|
+
if (specification.valueKind === 'file')
|
|
1390
|
+
return `${specification.option} <file>`;
|
|
1391
|
+
return `${specification.option} <value>`;
|
|
1392
|
+
}
|
|
1393
|
+
function positionalUsage(specification) {
|
|
1394
|
+
const value = specification.valueKind === 'enum'
|
|
1395
|
+
? `<${specification.values.join('|')}>`
|
|
1396
|
+
: `<${specification.name}${specification.maximum > 1 ? '...' : ''}>`;
|
|
1397
|
+
return specification.minimum === 0 ? `[${value}]` : value;
|
|
1398
|
+
}
|
|
1399
|
+
function generatedCommandUsage(command, options, positionals, constraints) {
|
|
1400
|
+
const commandOptions = options.filter((option) => option.scope === 'command');
|
|
1401
|
+
const required = new Set(constraints
|
|
1402
|
+
.filter((item) => item.kind === 'required')
|
|
1403
|
+
.flatMap((item) => item.options));
|
|
1404
|
+
const mutuallyExclusive = constraints
|
|
1405
|
+
.filter((item) => item.kind === 'mutually_exclusive')
|
|
1406
|
+
.map((item) => item.options.filter((option) => commandOptions.some((candidate) => candidate.option === option)))
|
|
1407
|
+
.filter((group) => group.length > 1);
|
|
1408
|
+
const grouped = new Set(mutuallyExclusive.flat());
|
|
1409
|
+
const renderedGroups = mutuallyExclusive.map((group) => `[${group.map((option) => (optionUsage(commandOptions.find((candidate) => candidate.option === option)))).join('|')}]`);
|
|
1410
|
+
const renderedOptions = commandOptions
|
|
1411
|
+
.filter((option) => !grouped.has(option.option))
|
|
1412
|
+
.map((option) => {
|
|
1413
|
+
const rendered = optionUsage(option);
|
|
1414
|
+
return required.has(option.option) ? rendered : `[${rendered}]`;
|
|
1415
|
+
});
|
|
1416
|
+
return ['whoisleuth', command, ...positionals.map(positionalUsage), ...renderedGroups, ...renderedOptions].join(' ');
|
|
1417
|
+
}
|
|
1435
1418
|
const CLI_COMMAND_REGISTRY = Object.freeze(COMMAND_ORDER.map((command, order) => {
|
|
1436
1419
|
const seed = COMMAND_SEEDS[command];
|
|
1437
1420
|
const commonOptions = Object.freeze([...commonOptionsSeedForCommand(command)]);
|
|
@@ -1440,10 +1423,14 @@ const CLI_COMMAND_REGISTRY = Object.freeze(COMMAND_ORDER.map((command, order) =>
|
|
|
1440
1423
|
...commonOptions.map((option) => optionSpec(command, option, 'common')),
|
|
1441
1424
|
...commandOptions.map((option) => optionSpec(command, option, 'command')),
|
|
1442
1425
|
]);
|
|
1426
|
+
const constraints = grammarConstraints(commandOptions, seed.constraints);
|
|
1443
1427
|
return Object.freeze({
|
|
1444
1428
|
command,
|
|
1445
1429
|
order,
|
|
1446
|
-
reference:
|
|
1430
|
+
reference: Object.freeze({
|
|
1431
|
+
...seed.reference,
|
|
1432
|
+
usage: generatedCommandUsage(command, grammarOptions, seed.positionals, constraints),
|
|
1433
|
+
}),
|
|
1447
1434
|
collection: seed.collection,
|
|
1448
1435
|
completion: Object.freeze({
|
|
1449
1436
|
description: seed.summary,
|
|
@@ -1455,7 +1442,7 @@ const CLI_COMMAND_REGISTRY = Object.freeze(COMMAND_ORDER.map((command, order) =>
|
|
|
1455
1442
|
bootstrapProfile: seed.bootstrapProfile,
|
|
1456
1443
|
options: grammarOptions,
|
|
1457
1444
|
positionals: seed.positionals,
|
|
1458
|
-
constraints
|
|
1445
|
+
constraints,
|
|
1459
1446
|
metaActions: Object.freeze(['help']),
|
|
1460
1447
|
}),
|
|
1461
1448
|
execution: Object.freeze({
|
|
@@ -1463,7 +1450,7 @@ const CLI_COMMAND_REGISTRY = Object.freeze(COMMAND_ORDER.map((command, order) =>
|
|
|
1463
1450
|
networkEffect: seed.networkEffect,
|
|
1464
1451
|
}),
|
|
1465
1452
|
help: Object.freeze({
|
|
1466
|
-
group:
|
|
1453
|
+
group: CLI_COMMAND_SEMANTICS[command].group,
|
|
1467
1454
|
summary: seed.summary,
|
|
1468
1455
|
}),
|
|
1469
1456
|
documentation: documentationMetadata(seed, commonOptions, commandOptions, seed.positionals),
|
|
@@ -1566,4 +1553,4 @@ function commandHelp(command) {
|
|
|
1566
1553
|
const collection = COMMAND_COLLECTION[command];
|
|
1567
1554
|
return `WHOISleuth ${command}\n${detail.description}\n\nUsage:\n ${COMMAND_USAGE[command]}\n\nExample:\n ${detail.example}\n\nCollection:\n ${collection.mode === 'offline' ? 'Offline' : 'Network'}: ${collection.scope}\n\nBoundary:\n ${detail.boundary}\n\nRun "whoisleuth --help" to see the grouped command list.\n`;
|
|
1568
1555
|
}
|
|
1569
|
-
export { CLI_COMMAND_REGISTRY, CLI_COMMANDS, CLI_META_ACTIONS, COMMAND_COLLECTION, COMMAND_DESCRIPTIONS, COMMAND_DETAILS, COMMAND_USAGE,
|
|
1556
|
+
export { CLI_COMMAND_REGISTRY, CLI_COMMANDS, CLI_META_ACTIONS, COMMAND_COLLECTION, COMMAND_DESCRIPTIONS, COMMAND_DETAILS, COMMAND_USAGE, FILE_POSITIONAL_COMMANDS, HELP, HELP_COMMANDS_BY_GROUP, INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES, OPTIONS_BY_COMMAND, cliMetaActionForInvocation, cliInvocationNetworkEffect, commandOptionSpec, commandOwnsOption, commandPositionalSpecs, commonOptionsForCommand, commandDefinition, commandHelp, isCliCommand, metaActionDefinition, };
|