@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.
Files changed (77) hide show
  1. package/README.md +2 -1
  2. package/cli/arguments.mjs +389 -1745
  3. package/cli/artifact-validation/case-response.mjs +55 -30
  4. package/cli/artifact-validation/signed-review.mjs +30 -12
  5. package/cli/bulk-checkpoint.mjs +34 -3
  6. package/cli/case-pack.mjs +39 -92
  7. package/cli/command-argument-grammar.mjs +211 -0
  8. package/cli/command-reference.mjs +200 -213
  9. package/cli/completion.mjs +5 -1
  10. package/cli/discovery-observation-snapshot.mjs +16 -3
  11. package/cli/discovery-scan-command-runner.mjs +19 -0
  12. package/cli/discovery-scan.mjs +6 -0
  13. package/cli/domain-control-observations.mjs +1 -1
  14. package/cli/inline-command-families.mjs +1 -0
  15. package/cli/input.mjs +116 -0
  16. package/cli/interchange-report.mjs +14 -0
  17. package/cli/lookalike-calibration.mjs +7 -2
  18. package/cli/lookup-brief.mjs +4 -2
  19. package/cli/lookup-browser-view.mjs +1 -1
  20. package/cli/mail-header-review.mjs +467 -0
  21. package/cli/review-command-runner.mjs +25 -0
  22. package/cli/risk-calibration.mjs +19 -17
  23. package/cli/runner.mjs +7 -1
  24. package/docs/cli-reference.md +8 -1
  25. package/docs/cli.md +24 -1
  26. package/lib/bounded-json.mjs +9 -6
  27. package/lib/dns-convergence-review.mjs +1 -0
  28. package/lib/dns-intelligence.mjs +37 -3
  29. package/lib/dnssec-chain-validation.mjs +6 -0
  30. package/lib/domain-change-packet.mjs +42 -14
  31. package/lib/domain-control-flight-recorder.mjs +4 -1
  32. package/lib/domain-control-manifest.mjs +16 -16
  33. package/lib/domain-posture-parsers.mjs +114 -3
  34. package/lib/domain-posture.mjs +34 -3
  35. package/lib/evidence-export.mjs +63 -13
  36. package/lib/evidence-report.mjs +2 -2
  37. package/lib/html-signals.mjs +39 -33
  38. package/lib/local-geoip-evidence.mjs +19 -3
  39. package/lib/lookup-response.mjs +5 -4
  40. package/lib/lookup-source-progress.mjs +27 -4
  41. package/lib/nameserver-preflight-review.mjs +1 -0
  42. package/lib/rdap-capabilities.mjs +1 -1
  43. package/lib/registry-insights.mjs +35 -22
  44. package/lib/risk-calibration-summary.mjs +1 -3
  45. package/lib/security-txt.mjs +33 -1
  46. package/lib/static-html-analysis.mjs +69 -13
  47. package/lib/website-technology.mjs +79 -11
  48. package/lib/zone-intent-review.mjs +16 -6
  49. package/package.json +1 -1
  50. package/packages/cases/case-migration-model.mjs +71 -22
  51. package/packages/cases/case-record-contracts.mjs +5 -20
  52. package/packages/cases/case-record-core.mjs +10 -21
  53. package/packages/cases/case-record-decisions.mjs +72 -0
  54. package/packages/cases/case-record-model.mjs +1 -0
  55. package/packages/cases/case-record-operations.mjs +240 -3
  56. package/packages/cases/case-record-projection.mjs +218 -0
  57. package/packages/cases/case-report.mjs +11 -6
  58. package/packages/cases/case-response-model.mjs +43 -6
  59. package/packages/cases/case-response-packet.mjs +78 -27
  60. package/packages/cases/case-storage-model.mjs +6 -2
  61. package/packages/comparison/comparison-ledger-bulk.mjs +9 -10
  62. package/packages/comparison/page-similarity.mjs +3 -1
  63. package/packages/contracts/case-portability.mjs +183 -53
  64. package/packages/contracts/cli-command-semantics.mjs +93 -0
  65. package/packages/contracts/risk-calibration.mjs +15 -10
  66. package/packages/contracts/workspace-portability.mjs +44 -7
  67. package/packages/evidence/domain-control-runtime.mjs +46 -6
  68. package/packages/interchange/external-findings-import.mjs +115 -46
  69. package/packages/investigation/lookup-asset-graph.mjs +35 -27
  70. package/packages/workspace/brand-profile-model.mjs +118 -2
  71. package/packages/workspace/bulk-review-model.mjs +13 -7
  72. package/packages/workspace/bulk-session-model.mjs +5 -4
  73. package/packages/workspace/detection-rule-model.mjs +70 -22
  74. package/packages/workspace/relationship-observation-model.mjs +4 -1
  75. package/packages/workspace/website-snapshot-model.mjs +106 -8
  76. package/packages/workspace/workspace-archive.mjs +2 -0
  77. 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(['--help', '--output', '--force', '--config', '--profile', '--palette']);
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 TEXT_OPTIONS = Object.freeze([
140
- '--families',
141
- '--workflow',
142
- '--configuration-digest',
143
- '--resolver',
144
- '--expect-content-digest',
145
- '--profile',
146
- '--suffix',
147
- '--left-session',
148
- '--right-session',
149
- '--purpose',
150
- '--observer',
151
- '--retired-selectors',
152
- '--search',
153
- '--select',
154
- '--selectors',
155
- '--tlds',
156
- '--vantage',
157
- ]);
158
- const IDEMPOTENT_OPTIONS = Object.freeze([
159
- '--human-reviewed',
160
- '--no-color',
161
- '--personal-data-reviewed',
162
- '--quiet',
163
- '--redactions-confirmed',
164
- ]);
165
- const REPEATABLE_OPTIONS = Object.freeze(['--select']);
166
- const INTEGER_RANGE_SEED = Object.freeze({
167
- bulk: Object.freeze({
168
- '--concurrency': Object.freeze([
169
- Object.freeze({ minimum: 1, maximum: 8, whenOptionPresent: null }),
170
- Object.freeze({ minimum: 1, maximum: 3, whenOptionPresent: '--deep' }),
171
- ]),
172
- }),
173
- 'discover-scan': Object.freeze({
174
- '--scan-limit': Object.freeze([
175
- Object.freeze({ minimum: 1, maximum: 500, whenOptionPresent: null }),
176
- Object.freeze({ minimum: 1, maximum: 50, whenOptionPresent: '--deep' }),
177
- ]),
178
- '--chunk-size': Object.freeze([
179
- Object.freeze({ minimum: 1, maximum: 100, whenOptionPresent: null }),
180
- ]),
181
- '--concurrency': Object.freeze([
182
- Object.freeze({ minimum: 1, maximum: 8, whenOptionPresent: null }),
183
- Object.freeze({ minimum: 1, maximum: 3, whenOptionPresent: '--deep' }),
184
- ]),
185
- }),
186
- 'monitor-once': Object.freeze({
187
- '--limit': Object.freeze([
188
- Object.freeze({ minimum: 1, maximum: 20, whenOptionPresent: null }),
189
- ]),
190
- '--concurrency': Object.freeze([
191
- Object.freeze({ minimum: 1, maximum: 3, whenOptionPresent: null }),
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 valueKind = optionValueKind(command, option);
232
- const values = option === '--fail-on'
233
- ? CLI_FAIL_POLICIES_BY_COMMAND[command] ?? []
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([...(INTEGER_RANGE_SEED[command]?.[option] ?? [])]),
242
- occurrence: REPEATABLE_OPTIONS.includes(option)
243
- ? 'repeatable'
244
- : IDEMPOTENT_OPTIONS.includes(option) ? 'idempotent' : 'once',
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 HELP_GROUP_ORDER = Object.freeze(['investigate', 'respond', 'assure', 'utilities']);
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) => COMMAND_SEEDS[command].helpGroup === group)),
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: seed.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: grammarConstraints(commandOptions, seed.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: seed.helpGroup,
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, COMMON_OPTIONS, FILE_OPTIONS, FILE_OPTIONS_BY_COMMAND, FILE_POSITIONAL_COMMANDS, HELP, HELP_COMMANDS_BY_GROUP, INVESTIGATION_PLAN_RECIPES, RUNNABLE_INVESTIGATION_PLAN_RECIPES, LIMITED_CONCURRENCY_VALUES, OPTIONS_BY_COMMAND, STANDARD_CONCURRENCY_VALUES, TEXT_OPTIONS, VALUE_OPTIONS, cliMetaActionForInvocation, cliInvocationNetworkEffect, commandOptionSpec, commandOwnsOption, commandPositionalSpecs, commonOptionsForCommand, commandDefinition, commandHelp, isCliCommand, metaActionDefinition, };
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, };