@slicedearth/whoisleuth-cli 2.0.0 → 2.1.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/cli/arguments.mjs +21 -5
- package/cli/artifact-validation/case-response.mjs +771 -0
- package/cli/artifact-validation/investigation-capsule.mjs +426 -0
- package/cli/artifact-validation/lookup-evidence.mjs +718 -0
- package/cli/artifact-validation/signed-review.mjs +598 -0
- package/cli/artifact-validation/structure-primitives.mjs +105 -0
- package/cli/artifact-verify.mjs +1 -1
- package/cli/assurance-command-runner.mjs +324 -0
- package/cli/command-reference.mjs +1175 -843
- package/cli/completion.mjs +56 -9
- package/cli/discriminated-command-handlers.mjs +11 -0
- package/cli/doctor.mjs +2 -2
- package/cli/evidence-command-runner.mjs +41 -31
- package/cli/export-evidence.mjs +1 -1
- package/cli/fail-policy.mjs +33 -8
- package/cli/formatters/terminal-command-formats.mjs +437 -0
- package/cli/formatters/terminal-lookup.mjs +758 -0
- package/cli/formatters/terminal-metadata.mjs +58 -0
- package/cli/formatters/terminal-shared.mjs +83 -0
- package/cli/formatters/terminal.mjs +5 -1232
- package/cli/history-command-runner.mjs +132 -0
- package/cli/inline-command-families.mjs +45 -0
- package/cli/inline-command-runner.mjs +55 -0
- package/cli/investigation-plan.mjs +9 -2
- package/cli/investigation-run.mjs +114 -9
- package/cli/lookup-brief.mjs +141 -23
- package/cli/lookup-browser-view.mjs +289 -0
- package/cli/lookup-browser.mjs +3 -288
- package/cli/mail-review.mjs +2 -6
- package/cli/offline-artifact-validation.mjs +19 -0
- package/cli/review-command-runner.mjs +269 -0
- package/cli/runner.mjs +15 -972
- package/cli/saved-lookup.mjs +19 -2
- package/cli/support-command-runner.mjs +229 -0
- package/cli/workflow-command-runner.mjs +129 -0
- package/docs/cli-reference.md +17 -10
- package/docs/cli.md +5 -3
- package/lib/availability.mjs +12 -9
- package/lib/bounded-contract-normalizers.mjs +2 -9
- package/lib/browser-library-profile.mjs +1 -2
- package/lib/client-behavior-profile.mjs +1 -2
- package/lib/credential-surface-profile.mjs +1 -1
- package/lib/domain-posture-analysis.mjs +10 -1
- package/lib/domain-posture.mjs +51 -23
- package/lib/evidence-export.mjs +46 -5
- package/lib/evidence-report-markdown.mjs +4 -0
- package/lib/evidence-report.mjs +50 -0
- package/lib/generated/cisa-kev-catalog.mjs +34 -4
- package/lib/generated/registrar-standing-catalogue.mjs +367 -0
- package/lib/json-record.mjs +12 -0
- package/lib/lookup-child-profile-contract.mjs +858 -0
- package/lib/lookup-contract-primitives.mjs +98 -0
- package/lib/lookup-diagnostics.mjs +96 -0
- package/lib/lookup-network-evidence-bounds.mjs +1 -0
- package/lib/lookup-response.mjs +367 -0
- package/lib/lookup.mjs +28 -443
- package/lib/page-role-profile.mjs +1 -3
- package/lib/project-metadata.mjs +1 -1
- package/lib/registrar-standing-catalogue-contract.mjs +70 -0
- package/lib/registrar-standing-contract.mjs +300 -0
- package/lib/registrar-standing.mjs +269 -0
- package/lib/registry-insights.mjs +2 -8
- package/lib/risk-scoring.mjs +26 -10
- package/lib/sslbl-certificates.generated.mjs +138 -136
- package/lib/structured-data-identity.mjs +1 -3
- package/lib/technology-evidence-role.mjs +51 -0
- package/lib/threat-intelligence-runtime.mjs +3 -3
- package/lib/threat-intelligence-types.mjs +13 -0
- package/lib/threatfox-intelligence.mjs +1 -3
- package/lib/tls-intelligence.mjs +1 -2
- package/lib/urlhaus-intelligence.mjs +1 -3
- package/lib/urlscan-intelligence.mjs +1 -3
- package/lib/website-security-posture.mjs +1 -2
- package/lib/website-technology.mjs +72 -23
- package/lib/whois-common-formats.mjs +276 -0
- package/lib/whois-dialects.mjs +534 -0
- package/lib/whois-parser-support.mjs +305 -0
- package/lib/whois-parser.mjs +135 -0
- package/lib/whois.mjs +6 -1187
- package/package.json +3 -3
- package/packages/cases/case-evidence-model.mjs +9 -0
- package/packages/cases/case-migration-model.mjs +17 -11
- package/packages/cases/case-response-model.mjs +13 -9
- package/packages/cases/case-response-packet.mjs +297 -112
- package/packages/comparison/page-similarity.mjs +23 -0
- package/packages/contracts/case-portability.mjs +227 -63
- package/packages/contracts/cli-lookup.mjs +4 -3
- package/packages/contracts/domain-control-manifest.mjs +1 -1
- package/packages/contracts/investigation-portability.mjs +1 -1
- package/packages/contracts/risk-calibration.mjs +2 -2
- package/packages/contracts/workspace-portability.mjs +34 -6
- package/packages/investigation/evidence-coverage-ledger.mjs +21 -0
- package/packages/investigation/lookup-claim-readiness.mjs +2 -2
- package/packages/workspace/brand-profile-model.mjs +1 -1
- package/packages/workspace/website-snapshot-model.mjs +79 -6
- package/third-party-notices.txt +1 -1
- package/cli/artifact-structure.mjs +0 -2507
package/cli/runner.mjs
CHANGED
|
@@ -1,64 +1,16 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer';
|
|
2
1
|
import { createRequire } from 'node:module';
|
|
3
2
|
import { abortable } from "../lib/abort.mjs";
|
|
4
|
-
import { scanBoundedJson } from "../lib/bounded-json.mjs";
|
|
5
|
-
import { REGISTRY_CAPABILITIES_VERSION, registryCapabilityFor } from "../lib/registry-capabilities.mjs";
|
|
6
|
-
import { explainRiskScore, explainRiskScoreV6, RISK_MODEL_VERSION, RISK_REVIEW_THRESHOLD } from "../lib/risk-scoring.mjs";
|
|
7
|
-
import { buildRiskCalibrationSummaryReport } from "../lib/risk-calibration-summary.mjs";
|
|
8
3
|
import { parseCliArguments } from "./arguments.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import { COMMAND_COLLECTION, COMMAND_DETAILS, COMMAND_USAGE, HELP, CLI_COMMANDS, CLI_COMMAND_REGISTRY, commandDefinition, commandHelp, } from "./command-reference.mjs";
|
|
11
|
-
import { buildShellCompletion } from "./completion.mjs";
|
|
12
|
-
import { buildDoctorReport, formatDoctorReport } from "./doctor.mjs";
|
|
13
|
-
import { MAX_COMPARE_INPUT_BYTES, compareLookupDocument, parseCliLookupDocument, } from "./compare.mjs";
|
|
4
|
+
import { HELP, commandDefinition, commandHelp, } from "./command-reference.mjs";
|
|
14
5
|
import { boundedCliErrorMessage, CliUsageError } from "./errors.mjs";
|
|
15
6
|
import { evidenceCommandFailureLabel, isEvidenceCommand, runEvidenceCommand, } from "./evidence-command-runner.mjs";
|
|
16
|
-
import { buildCliEvidenceExport, formatCliEvidenceExport } from "./export-evidence.mjs";
|
|
17
7
|
import EXIT_CODES from "./exit-codes.mjs";
|
|
18
|
-
import { formatLookupEvidenceHtml } from "./formatters/html.mjs";
|
|
19
|
-
import { buildCliCompareDocument, formatJsonDocument, } from "./formatters/json.mjs";
|
|
20
|
-
import { formatLookupEvidenceMarkdown } from "./formatters/markdown.mjs";
|
|
21
|
-
import { formatTerminalCompare, formatTerminalRegistrySupport, formatTerminalRiskCalibration, } from "./formatters/terminal.mjs";
|
|
22
|
-
import { MAX_RETAINED_ARTIFACT_DIFF_BYTES, buildCliRetainedArtifactDiff, formatCliRetainedArtifactDiff, } from "./retained-artifact-diff.mjs";
|
|
23
|
-
import { MAX_LOOKUP_RECONCILIATION_INPUT_BYTES, buildCliLookupReconciliation, formatCliLookupReconciliation, } from "./lookup-reconcile.mjs";
|
|
24
|
-
import { MAX_SHARING_REVIEW_BYTES, buildSharingReview, formatSharingReview, } from "./sharing-review.mjs";
|
|
25
|
-
import { MAX_LOOKUP_TIMELINE_INPUT_BYTES, buildCliLookupTimeline, formatCliLookupTimeline, } from "./lookup-timeline.mjs";
|
|
26
|
-
import { buildCliPageComparison, formatCliPageComparison } from "./page-compare.mjs";
|
|
27
|
-
import { MAX_MAIL_REVIEW_INPUT_BYTES, buildCliMailReview, formatCliMailReview, } from "./mail-review.mjs";
|
|
28
|
-
import { MAX_OFFLINE_EVIDENCE_INPUT_BYTES, buildOfflineEvidenceReview, buildOfflineEvidenceReviewWithLocalResources, formatOfflineEvidenceReview, } from "./offline-evidence-review.mjs";
|
|
29
|
-
import { DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA, DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA, buildDomainControlManifest, formatDomainControlResult, reviewDomainControlManifest, } from "../lib/domain-control-manifest.mjs";
|
|
30
|
-
import { serializeDomainControlManifest } from "../packages/evidence/domain-control-runtime.mjs";
|
|
31
|
-
import { MAX_DOMAIN_CONTROL_REVIEW_COMMAND_BYTES } from "../packages/contracts/domain-control-review.mjs";
|
|
32
|
-
import { DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA, buildDomainControlFlightRecorder, formatDomainControlFlightRecorder, serializeDomainControlFlightRecorder, } from "../lib/domain-control-flight-recorder.mjs";
|
|
33
|
-
import { CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA, MAX_DOMAIN_CONTROL_REVIEW_JSON_DEPTH, MAX_DOMAIN_CONTROL_REVIEW_JSON_KEYS, MAX_DOMAIN_CONTROL_REVIEW_JSON_VALUES, buildCliDomainControlReview, formatCliDomainControlReview, } from "./domain-control-observations.mjs";
|
|
34
|
-
import { buildCliLookupBrief, formatCliLookupBrief } from "./lookup-brief.mjs";
|
|
35
|
-
import { buildCliCasePack, formatCliCasePack } from "./case-pack.mjs";
|
|
36
|
-
import { formatDomainControlMonitor, runDomainControlMonitor } from "./domain-control-monitor.mjs";
|
|
37
|
-
import { MAX_ASSURANCE_INPUT_BYTES, buildDomainAssurance, formatDomainAssurance, } from "../lib/domain-assurance.mjs";
|
|
38
|
-
import { MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES, buildDomainChangePacket, formatDomainChangePacket, } from "../lib/domain-change-packet.mjs";
|
|
39
|
-
import { buildCliManual } from "./manual.mjs";
|
|
40
|
-
import { MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES, MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES, buildInvestigationManifest, formatInvestigationManifest, } from "./investigation-manifest.mjs";
|
|
41
|
-
import { MAX_EXTERNAL_OBSERVATION_MAPPING_BYTES, mapExternalObservations, formatExternalObservationMapping, } from "./external-observation-mapping.mjs";
|
|
42
|
-
import { buildOpenAssetModelBridge, formatOpenAssetModelBridge, } from "./open-asset-model-bridge.mjs";
|
|
43
|
-
import { MAX_CT_EVENT_INPUT_BYTES, buildCtEventFindings, formatCtEventFindings, } from "./ct-event-intake.mjs";
|
|
44
|
-
import { buildInvestigationPlan, buildWorkflowRecipeCatalogue, formatInvestigationPlan, formatWorkflowRecipeCatalogue, } from "./investigation-plan.mjs";
|
|
45
|
-
import { MAX_INVESTIGATION_RUN_BYTES, formatInvestigationRun, runInvestigationRecipe } from "./investigation-run.mjs";
|
|
46
8
|
import { readCliTextInput } from "./input.mjs";
|
|
47
|
-
import {
|
|
48
|
-
import { formatCliJunit } from "./ci-report.mjs";
|
|
49
|
-
import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile } from "./output-file.mjs";
|
|
9
|
+
import { INLINE_CLI_COMMANDS, runInlineCommand } from "./inline-command-runner.mjs";
|
|
50
10
|
import { canLaunchInteractiveCli, launchInteractiveCli, } from "./interactive-launcher.mjs";
|
|
11
|
+
import { MAX_OFFLINE_PASSPHRASE_FILE_BYTES, } from "./artifact-verify.mjs";
|
|
12
|
+
import { cleanupPendingOutputFiles, createBufferedOutput, writePrivateFile } from "./output-file.mjs";
|
|
51
13
|
import { createTerminalProgress } from "./progress.mjs";
|
|
52
|
-
import { buildRegistrySupportDocument } from "./registry-support.mjs";
|
|
53
|
-
import { buildRegistryDoctorReport, formatRegistryDoctorReport } from "./registry-doctor.mjs";
|
|
54
|
-
import { MAX_REGISTRY_COHORT_INPUT_BYTES, buildRegistryCohortReport, formatRegistryCohortReport, } from "./registry-cohort.mjs";
|
|
55
|
-
import { buildRegistryFixtureScaffold } from "./registry-fixture-scaffold.mjs";
|
|
56
|
-
import { MAX_OFFLINE_ARTIFACT_BYTES, MAX_OFFLINE_PASSPHRASE_FILE_BYTES, formatOfflineArtifactVerification, isCompleteOfflineArtifactVerification, verifyOfflineArtifact, } from "./artifact-verify.mjs";
|
|
57
|
-
import { buildInterchangeFidelityReport, formatInterchangeFidelityReport, } from "./interchange-report.mjs";
|
|
58
|
-
import { MAX_SOURCE_RELIABILITY_INPUT_BYTES, buildSourceReliabilityReport, formatSourceReliabilityReport, } from "./source-reliability.mjs";
|
|
59
|
-
import { MAX_RISK_CALIBRATION_INPUT_BYTES, buildRiskCalibrationReport, parseRiskCalibrationDataset, serializeRiskCalibrationReport, } from "./risk-calibration.mjs";
|
|
60
|
-
import { MAX_LOOKALIKE_CALIBRATION_BYTES, buildLookalikeCalibration, formatLookalikeCalibration, } from "./lookalike-calibration.mjs";
|
|
61
|
-
import { MAX_SAVED_LOOKUP_INPUT_BYTES } from "./saved-lookup.mjs";
|
|
62
14
|
import { presentTerminalOutput, terminalPresentation, } from "./terminal-presentation.mjs";
|
|
63
15
|
const require = createRequire(import.meta.url);
|
|
64
16
|
const { version: VERSION } = require('../package.json');
|
|
@@ -98,16 +50,6 @@ function usageEventReason(error) {
|
|
|
98
50
|
return 'missing_input';
|
|
99
51
|
return 'invalid_input';
|
|
100
52
|
}
|
|
101
|
-
const INLINE_CLI_COMMANDS = Object.freeze([
|
|
102
|
-
'completion', 'doctor', 'commands', 'manual', 'manifest', 'map-observations',
|
|
103
|
-
'oam-export', 'ct-intake', 'registry-support', 'registry-doctor',
|
|
104
|
-
'registry-cohort', 'registry-scaffold', 'risk-calibrate', 'lookalike-calibrate',
|
|
105
|
-
'verify-artifact', 'interchange-report', 'source-report', 'compare',
|
|
106
|
-
'page-compare', 'mail-review', 'review-evidence', 'brief', 'case-pack',
|
|
107
|
-
'domain-control', 'monitor-once', 'assurance', 'change-packet',
|
|
108
|
-
'sharing-review', 'workflow-plan', 'workflow-run', 'diff', 'reconcile',
|
|
109
|
-
'timeline', 'export',
|
|
110
|
-
]);
|
|
111
53
|
async function runParsedCli(args, dependencies = {}) {
|
|
112
54
|
const stdout = dependencies.stdout || process.stdout;
|
|
113
55
|
const stderr = dependencies.stderr || process.stderr;
|
|
@@ -175,13 +117,16 @@ async function runParsedCli(args, dependencies = {}) {
|
|
|
175
117
|
return passphrase;
|
|
176
118
|
};
|
|
177
119
|
const commandContext = Object.freeze({
|
|
120
|
+
packageVersion: VERSION,
|
|
178
121
|
stdout,
|
|
179
122
|
stderr,
|
|
180
123
|
terminal,
|
|
124
|
+
presentation: (color) => terminalPresentation(stdout, color, environment, palette),
|
|
181
125
|
writeStdout: (value) => write(stdout, value),
|
|
182
126
|
writeStderr: (value) => write(stderr, value),
|
|
183
127
|
readSingleInput,
|
|
184
128
|
readInput,
|
|
129
|
+
readPassphraseSource,
|
|
185
130
|
now: () => dependencies.now ? dependencies.now() : new Date().toISOString(),
|
|
186
131
|
beginProgress,
|
|
187
132
|
endProgress,
|
|
@@ -189,6 +134,13 @@ async function runParsedCli(args, dependencies = {}) {
|
|
|
189
134
|
setEventProgress: (next) => {
|
|
190
135
|
eventProgress.current = next;
|
|
191
136
|
},
|
|
137
|
+
setFailureLabel: (label) => {
|
|
138
|
+
failureLabel = label;
|
|
139
|
+
},
|
|
140
|
+
executeCli: (argv, overrides = {}) => runCli(argv, {
|
|
141
|
+
...dependencies,
|
|
142
|
+
...overrides,
|
|
143
|
+
}),
|
|
192
144
|
});
|
|
193
145
|
if (args.action === 'help') {
|
|
194
146
|
write(stdout, terminal(args.command ? commandHelp(args.command) : HELP));
|
|
@@ -270,916 +222,7 @@ async function runParsedCli(args, dependencies = {}) {
|
|
|
270
222
|
if (handlerOwner !== 'inline' || !INLINE_CLI_COMMANDS.includes(args.action)) {
|
|
271
223
|
throw new Error('No CLI execution route is registered for the parsed command.');
|
|
272
224
|
}
|
|
273
|
-
|
|
274
|
-
write(stdout, buildShellCompletion(args.shell));
|
|
275
|
-
return EXIT_CODES.SUCCESS;
|
|
276
|
-
}
|
|
277
|
-
if (args.action === 'commands') {
|
|
278
|
-
const selectedCommands = selectCliCommands(CLI_COMMAND_REGISTRY, {
|
|
279
|
-
common: args.common,
|
|
280
|
-
group: args.group,
|
|
281
|
-
mode: args.mode,
|
|
282
|
-
});
|
|
283
|
-
const catalogue = buildCliCommandCatalogue({
|
|
284
|
-
commands: selectedCommands,
|
|
285
|
-
collections: COMMAND_COLLECTION,
|
|
286
|
-
details: COMMAND_DETAILS,
|
|
287
|
-
usage: COMMAND_USAGE,
|
|
288
|
-
packageVersion: VERSION,
|
|
289
|
-
});
|
|
290
|
-
if (!args.quiet)
|
|
291
|
-
write(stdout, args.output === 'json'
|
|
292
|
-
? formatJsonDocument(catalogue)
|
|
293
|
-
: terminal(formatCliCommandCatalogue(catalogue), args.color));
|
|
294
|
-
return EXIT_CODES.SUCCESS;
|
|
295
|
-
}
|
|
296
|
-
if (args.action === 'manual') {
|
|
297
|
-
write(stdout, buildCliManual({
|
|
298
|
-
commands: CLI_COMMANDS,
|
|
299
|
-
collections: COMMAND_COLLECTION,
|
|
300
|
-
details: COMMAND_DETAILS,
|
|
301
|
-
usage: COMMAND_USAGE,
|
|
302
|
-
version: VERSION,
|
|
303
|
-
}));
|
|
304
|
-
return EXIT_CODES.SUCCESS;
|
|
305
|
-
}
|
|
306
|
-
if (args.action === 'manifest') {
|
|
307
|
-
failureLabel = 'Investigation manifest';
|
|
308
|
-
const artifacts = [];
|
|
309
|
-
let totalBytes = 0;
|
|
310
|
-
try {
|
|
311
|
-
for (const source of args.sources) {
|
|
312
|
-
const content = dependencies.readDiffInput
|
|
313
|
-
? await dependencies.readDiffInput(source)
|
|
314
|
-
: await readInput(source, MAX_INVESTIGATION_MANIFEST_ARTIFACT_BYTES, 'Manifest artefact input');
|
|
315
|
-
totalBytes += Buffer.byteLength(content, 'utf8');
|
|
316
|
-
if (totalBytes > MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES) {
|
|
317
|
-
throw new CliUsageError(`Manifest artefacts exceed the ${MAX_INVESTIGATION_MANIFEST_TOTAL_BYTES}-byte combined limit.`);
|
|
318
|
-
}
|
|
319
|
-
artifacts.push({ content });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
catch (error) {
|
|
323
|
-
if (error instanceof CliUsageError)
|
|
324
|
-
throw error;
|
|
325
|
-
throw new CliUsageError(`Could not read manifest artefact input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
326
|
-
}
|
|
327
|
-
let document;
|
|
328
|
-
try {
|
|
329
|
-
document = await buildInvestigationManifest({
|
|
330
|
-
workflow: args.workflow,
|
|
331
|
-
configurationDigestSha256: args.configurationDigestSha256,
|
|
332
|
-
artifacts,
|
|
333
|
-
}, commandContext.now(), VERSION);
|
|
334
|
-
}
|
|
335
|
-
catch (error) {
|
|
336
|
-
throw new CliUsageError(boundedCliErrorMessage(error, 'Investigation manifest input is invalid'));
|
|
337
|
-
}
|
|
338
|
-
if (!args.quiet)
|
|
339
|
-
write(stdout, args.output === 'json'
|
|
340
|
-
? formatJsonDocument(document)
|
|
341
|
-
: terminal(formatInvestigationManifest(document), args.color));
|
|
342
|
-
return EXIT_CODES.SUCCESS;
|
|
343
|
-
}
|
|
344
|
-
if (args.action === 'map-observations' || args.action === 'oam-export') {
|
|
345
|
-
const mapping = args.action === 'map-observations';
|
|
346
|
-
failureLabel = mapping ? 'External observation mapping' : 'Open Asset Model export';
|
|
347
|
-
let input;
|
|
348
|
-
try {
|
|
349
|
-
input = dependencies.readArtifactInput
|
|
350
|
-
? await dependencies.readArtifactInput(args.source)
|
|
351
|
-
: await readInput(args.source, MAX_EXTERNAL_OBSERVATION_MAPPING_BYTES, mapping ? 'External observation mapping input' : 'Open Asset Model bridge input');
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
if (error instanceof CliUsageError)
|
|
355
|
-
throw error;
|
|
356
|
-
throw new CliUsageError(`Could not read ${mapping ? 'observation mapping' : 'asset bridge'} input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
357
|
-
}
|
|
358
|
-
if (!input.trim())
|
|
359
|
-
throw new CliUsageError(`${args.action} requires one versioned JSON file or a document on stdin.`);
|
|
360
|
-
let parsed;
|
|
361
|
-
try {
|
|
362
|
-
scanBoundedJson(input);
|
|
363
|
-
parsed = JSON.parse(input);
|
|
364
|
-
}
|
|
365
|
-
catch {
|
|
366
|
-
throw new CliUsageError(`${mapping ? 'External observation mapping' : 'Open Asset Model bridge'} input is not valid bounded JSON without duplicate keys.`);
|
|
367
|
-
}
|
|
368
|
-
let document;
|
|
369
|
-
try {
|
|
370
|
-
document = mapping
|
|
371
|
-
? mapExternalObservations(parsed)
|
|
372
|
-
: buildOpenAssetModelBridge(parsed, commandContext.now());
|
|
373
|
-
}
|
|
374
|
-
catch (error) {
|
|
375
|
-
throw new CliUsageError(boundedCliErrorMessage(error, `${mapping ? 'External observation mapping' : 'Open Asset Model bridge'} input is invalid`));
|
|
376
|
-
}
|
|
377
|
-
if (!args.quiet)
|
|
378
|
-
write(stdout, args.output === 'json'
|
|
379
|
-
? formatJsonDocument(document)
|
|
380
|
-
: terminal(mapping
|
|
381
|
-
? formatExternalObservationMapping(document)
|
|
382
|
-
: formatOpenAssetModelBridge(document), args.color));
|
|
383
|
-
return EXIT_CODES.SUCCESS;
|
|
384
|
-
}
|
|
385
|
-
if (args.action === 'doctor') {
|
|
386
|
-
failureLabel = 'CLI diagnostics';
|
|
387
|
-
const buildReport = () => buildDoctorReport({
|
|
388
|
-
version: VERSION,
|
|
389
|
-
generatedAt: dependencies.now ? dependencies.now() : new Date().toISOString(),
|
|
390
|
-
network: args.network,
|
|
391
|
-
presentation: terminalPresentation(stdout, args.color, environment, palette),
|
|
392
|
-
...(dependencies.resolvePublicAddresses ? { resolveAddresses: dependencies.resolvePublicAddresses } : {}),
|
|
393
|
-
...(dependencies.safeFetch ? { fetchHttps: dependencies.safeFetch } : {}),
|
|
394
|
-
...(dependencies.whoisQuery ? { queryWhois: dependencies.whoisQuery } : {}),
|
|
395
|
-
});
|
|
396
|
-
const report = args.network
|
|
397
|
-
? await withProgress('Checking public DNS, HTTPS, and WHOIS connectivity', buildReport)
|
|
398
|
-
: await buildReport();
|
|
399
|
-
if (!args.quiet) {
|
|
400
|
-
write(stdout, args.output === 'json'
|
|
401
|
-
? formatJsonDocument(report)
|
|
402
|
-
: terminal(formatDoctorReport(report), args.color));
|
|
403
|
-
}
|
|
404
|
-
return report.state === 'partial' ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
|
|
405
|
-
}
|
|
406
|
-
if (args.action === 'registry-support') {
|
|
407
|
-
failureLabel = 'Registry support';
|
|
408
|
-
const readInput = dependencies.readStdin || (() => readStdinBounded(dependencies.stdin || process.stdin, MAX_STDIN_BYTES, dependencies.signal));
|
|
409
|
-
const requestedInput = args.target || await readInput();
|
|
410
|
-
if (!requestedInput)
|
|
411
|
-
throw new CliUsageError('registry-support requires one domain or suffix as an argument or on stdin.');
|
|
412
|
-
const lookupCapability = dependencies.registryCapabilityFor || registryCapabilityFor;
|
|
413
|
-
const capability = lookupCapability(requestedInput);
|
|
414
|
-
if (!capability)
|
|
415
|
-
throw new CliUsageError('registry-support requires a valid domain or suffix.');
|
|
416
|
-
const now = dependencies.now ? dependencies.now() : new Date().toISOString();
|
|
417
|
-
const catalogueVersion = dependencies.registryCapabilitiesVersion || REGISTRY_CAPABILITIES_VERSION;
|
|
418
|
-
const document = buildRegistrySupportDocument(requestedInput, capability, catalogueVersion, now);
|
|
419
|
-
if (!args.quiet)
|
|
420
|
-
write(stdout, args.output === 'json' ? formatJsonDocument(document) : terminal(formatTerminalRegistrySupport(document), args.color));
|
|
421
|
-
return EXIT_CODES.SUCCESS;
|
|
422
|
-
}
|
|
423
|
-
if (args.action === 'registry-doctor') {
|
|
424
|
-
failureLabel = 'Registry compatibility diagnostic';
|
|
425
|
-
let input;
|
|
426
|
-
try {
|
|
427
|
-
input = dependencies.readCompareInput
|
|
428
|
-
? await dependencies.readCompareInput(args.source)
|
|
429
|
-
: await readInput(args.source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Registry doctor input');
|
|
430
|
-
}
|
|
431
|
-
catch (error) {
|
|
432
|
-
if (error instanceof CliUsageError)
|
|
433
|
-
throw error;
|
|
434
|
-
throw new CliUsageError(`Could not read registry doctor input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
435
|
-
}
|
|
436
|
-
if (!input.trim())
|
|
437
|
-
throw new CliUsageError('registry-doctor requires one saved Lookup JSON file or a document on stdin.');
|
|
438
|
-
const report = buildRegistryDoctorReport(input, commandContext.now());
|
|
439
|
-
if (!args.quiet)
|
|
440
|
-
write(stdout, args.output === 'json'
|
|
441
|
-
? formatJsonDocument(report)
|
|
442
|
-
: terminal(formatRegistryDoctorReport(report), args.color));
|
|
443
|
-
return report.summary.investigate ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
|
|
444
|
-
}
|
|
445
|
-
if (args.action === 'registry-cohort') {
|
|
446
|
-
failureLabel = 'Registry quality cohort';
|
|
447
|
-
let input;
|
|
448
|
-
try {
|
|
449
|
-
input = dependencies.readCompareInput
|
|
450
|
-
? await dependencies.readCompareInput(args.source)
|
|
451
|
-
: await readInput(args.source, MAX_REGISTRY_COHORT_INPUT_BYTES, 'Registry cohort input');
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
if (error instanceof CliUsageError)
|
|
455
|
-
throw error;
|
|
456
|
-
throw new CliUsageError(`Could not read registry cohort input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
457
|
-
}
|
|
458
|
-
const report = buildRegistryCohortReport(input, commandContext.now());
|
|
459
|
-
if (!args.quiet)
|
|
460
|
-
write(stdout, args.output === 'json'
|
|
461
|
-
? formatJsonDocument(report)
|
|
462
|
-
: terminal(formatRegistryCohortReport(report), args.color));
|
|
463
|
-
return report.cohorts.some((cohort) => cohort.state === 'review')
|
|
464
|
-
? EXIT_CODES.PARTIAL_FAILURE
|
|
465
|
-
: EXIT_CODES.SUCCESS;
|
|
466
|
-
}
|
|
467
|
-
if (args.action === 'registry-scaffold') {
|
|
468
|
-
failureLabel = 'Registry fixture scaffold';
|
|
469
|
-
try {
|
|
470
|
-
write(stdout, buildRegistryFixtureScaffold(args.profile, args.suffix, args.scenario));
|
|
471
|
-
}
|
|
472
|
-
catch (error) {
|
|
473
|
-
throw new CliUsageError(boundedCliErrorMessage(error, 'Registry fixture scaffold failed'));
|
|
474
|
-
}
|
|
475
|
-
return EXIT_CODES.SUCCESS;
|
|
476
|
-
}
|
|
477
|
-
if (args.action === 'risk-calibrate') {
|
|
478
|
-
failureLabel = 'Risk calibration';
|
|
479
|
-
let input;
|
|
480
|
-
try {
|
|
481
|
-
input = dependencies.readRiskCalibrationInput
|
|
482
|
-
? await dependencies.readRiskCalibrationInput(args.source)
|
|
483
|
-
: await readInput(args.source, MAX_RISK_CALIBRATION_INPUT_BYTES, 'Risk calibration input');
|
|
484
|
-
}
|
|
485
|
-
catch (error) {
|
|
486
|
-
if (error instanceof CliUsageError)
|
|
487
|
-
throw error;
|
|
488
|
-
throw new CliUsageError(`Could not read Risk calibration input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
489
|
-
}
|
|
490
|
-
if (!input.trim())
|
|
491
|
-
throw new CliUsageError('risk-calibrate requires one dataset JSON file or a dataset on stdin.');
|
|
492
|
-
const dataset = parseRiskCalibrationDataset(input);
|
|
493
|
-
const report = buildRiskCalibrationReport(dataset, dependencies.explainRiskScore || explainRiskScore, {
|
|
494
|
-
generatedAt: dependencies.now ? dependencies.now() : new Date().toISOString(),
|
|
495
|
-
modelVersion: dependencies.riskModelVersion || RISK_MODEL_VERSION,
|
|
496
|
-
reviewThreshold: dependencies.riskReviewThreshold || RISK_REVIEW_THRESHOLD,
|
|
497
|
-
...(!dependencies.explainRiskScore ? {
|
|
498
|
-
previousModelVersion: 6,
|
|
499
|
-
explainPreviousRiskScore: explainRiskScoreV6,
|
|
500
|
-
} : {}),
|
|
501
|
-
});
|
|
502
|
-
if (!args.quiet)
|
|
503
|
-
write(stdout, args.output === 'summary_json'
|
|
504
|
-
? serializeRiskCalibrationReport(buildRiskCalibrationSummaryReport(report))
|
|
505
|
-
: args.output === 'json'
|
|
506
|
-
? serializeRiskCalibrationReport(report)
|
|
507
|
-
: terminal(formatTerminalRiskCalibration(report), args.color));
|
|
508
|
-
return EXIT_CODES.SUCCESS;
|
|
509
|
-
}
|
|
510
|
-
if (args.action === 'lookalike-calibrate') {
|
|
511
|
-
failureLabel = 'Lookalike review-yield calibration';
|
|
512
|
-
let input;
|
|
513
|
-
try {
|
|
514
|
-
input = dependencies.readRiskCalibrationInput
|
|
515
|
-
? await dependencies.readRiskCalibrationInput(args.source)
|
|
516
|
-
: await readInput(args.source, MAX_LOOKALIKE_CALIBRATION_BYTES, 'Lookalike calibration input');
|
|
517
|
-
}
|
|
518
|
-
catch (error) {
|
|
519
|
-
if (error instanceof CliUsageError)
|
|
520
|
-
throw error;
|
|
521
|
-
throw new CliUsageError(`Could not read lookalike calibration input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
522
|
-
}
|
|
523
|
-
if (!input.trim())
|
|
524
|
-
throw new CliUsageError('lookalike-calibrate requires one dataset JSON file or a document on stdin.');
|
|
525
|
-
let report;
|
|
526
|
-
try {
|
|
527
|
-
report = buildLookalikeCalibration(input, commandContext.now());
|
|
528
|
-
}
|
|
529
|
-
catch (error) {
|
|
530
|
-
throw new CliUsageError(boundedCliErrorMessage(error, 'Lookalike calibration input is invalid'));
|
|
531
|
-
}
|
|
532
|
-
if (!args.quiet)
|
|
533
|
-
write(stdout, args.output === 'json'
|
|
534
|
-
? formatJsonDocument(report)
|
|
535
|
-
: terminal(formatLookalikeCalibration(report), args.color));
|
|
536
|
-
return EXIT_CODES.SUCCESS;
|
|
537
|
-
}
|
|
538
|
-
if (args.action === 'verify-artifact') {
|
|
539
|
-
failureLabel = 'Artefact verification';
|
|
540
|
-
let input;
|
|
541
|
-
try {
|
|
542
|
-
input = dependencies.readArtifactInput
|
|
543
|
-
? await dependencies.readArtifactInput(args.source)
|
|
544
|
-
: await readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Artefact input');
|
|
545
|
-
}
|
|
546
|
-
catch (error) {
|
|
547
|
-
if (error instanceof CliUsageError)
|
|
548
|
-
throw error;
|
|
549
|
-
throw new CliUsageError(`Could not read artifact input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
550
|
-
}
|
|
551
|
-
if (!input.trim())
|
|
552
|
-
throw new CliUsageError('verify-artifact requires one JSON file or an artefact on stdin.');
|
|
553
|
-
const passphrase = args.passphraseSource ? await readPassphraseSource(args.passphraseSource) : null;
|
|
554
|
-
let manifest = null;
|
|
555
|
-
if (args.manifestSource && args.manifestEntryId) {
|
|
556
|
-
let raw;
|
|
557
|
-
try {
|
|
558
|
-
raw = dependencies.readArtifactInput
|
|
559
|
-
? await dependencies.readArtifactInput(args.manifestSource)
|
|
560
|
-
: await readInput(args.manifestSource, MAX_OFFLINE_ARTIFACT_BYTES, 'Investigation manifest input');
|
|
561
|
-
}
|
|
562
|
-
catch (error) {
|
|
563
|
-
if (error instanceof CliUsageError)
|
|
564
|
-
throw error;
|
|
565
|
-
throw new CliUsageError(`Could not read investigation manifest input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
566
|
-
}
|
|
567
|
-
if (!raw.trim())
|
|
568
|
-
throw new CliUsageError('The investigation manifest input is empty.');
|
|
569
|
-
manifest = Object.freeze({ raw, entryId: args.manifestEntryId });
|
|
570
|
-
}
|
|
571
|
-
const report = await verifyOfflineArtifact(input, { passphrase, manifest });
|
|
572
|
-
if (!args.quiet) {
|
|
573
|
-
write(stdout, args.output === 'json'
|
|
574
|
-
? formatJsonDocument(report)
|
|
575
|
-
: terminal(formatOfflineArtifactVerification(report), args.color));
|
|
576
|
-
}
|
|
577
|
-
return args.strictExit && !isCompleteOfflineArtifactVerification(report)
|
|
578
|
-
? EXIT_CODES.PARTIAL_FAILURE
|
|
579
|
-
: EXIT_CODES.SUCCESS;
|
|
580
|
-
}
|
|
581
|
-
if (args.action === 'interchange-report') {
|
|
582
|
-
failureLabel = 'Interchange fidelity report';
|
|
583
|
-
let input;
|
|
584
|
-
try {
|
|
585
|
-
input = dependencies.readArtifactInput
|
|
586
|
-
? await dependencies.readArtifactInput(args.source)
|
|
587
|
-
: await readInput(args.source, MAX_OFFLINE_ARTIFACT_BYTES, 'Interchange input');
|
|
588
|
-
}
|
|
589
|
-
catch (error) {
|
|
590
|
-
if (error instanceof CliUsageError)
|
|
591
|
-
throw error;
|
|
592
|
-
throw new CliUsageError(`Could not read interchange input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
593
|
-
}
|
|
594
|
-
if (!input.trim())
|
|
595
|
-
throw new CliUsageError('interchange-report requires one JSON file or an artefact on stdin.');
|
|
596
|
-
const passphrase = args.passphraseSource ? await readPassphraseSource(args.passphraseSource) : null;
|
|
597
|
-
const report = await buildInterchangeFidelityReport(input, {
|
|
598
|
-
generatedAt: commandContext.now(),
|
|
599
|
-
passphrase,
|
|
600
|
-
});
|
|
601
|
-
if (!args.quiet)
|
|
602
|
-
write(stdout, args.output === 'json'
|
|
603
|
-
? formatJsonDocument(report)
|
|
604
|
-
: terminal(formatInterchangeFidelityReport(report), args.color));
|
|
605
|
-
return EXIT_CODES.SUCCESS;
|
|
606
|
-
}
|
|
607
|
-
if (args.action === 'source-report') {
|
|
608
|
-
failureLabel = 'Source reliability report';
|
|
609
|
-
let input;
|
|
610
|
-
try {
|
|
611
|
-
input = dependencies.readSourceReliabilityInput
|
|
612
|
-
? await dependencies.readSourceReliabilityInput(args.source)
|
|
613
|
-
: await readInput(args.source, MAX_SOURCE_RELIABILITY_INPUT_BYTES, 'Source reliability input');
|
|
614
|
-
}
|
|
615
|
-
catch (error) {
|
|
616
|
-
if (error instanceof CliUsageError)
|
|
617
|
-
throw error;
|
|
618
|
-
throw new CliUsageError(`Could not read source reliability input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
619
|
-
}
|
|
620
|
-
if (!input.trim())
|
|
621
|
-
throw new CliUsageError('source-report requires one JSON file or lookup documents on stdin.');
|
|
622
|
-
const report = buildSourceReliabilityReport(input, dependencies.now ? dependencies.now() : new Date().toISOString());
|
|
623
|
-
if (!args.quiet) {
|
|
624
|
-
write(stdout, args.output === 'json'
|
|
625
|
-
? formatJsonDocument(report)
|
|
626
|
-
: terminal(formatSourceReliabilityReport(report), args.color));
|
|
627
|
-
}
|
|
628
|
-
return EXIT_CODES.SUCCESS;
|
|
629
|
-
}
|
|
630
|
-
if (args.action === 'compare') {
|
|
631
|
-
failureLabel = 'Registry comparison';
|
|
632
|
-
let input;
|
|
633
|
-
try {
|
|
634
|
-
input = dependencies.readCompareInput
|
|
635
|
-
? await dependencies.readCompareInput(args.source)
|
|
636
|
-
: await readInput(args.source, MAX_COMPARE_INPUT_BYTES, 'Comparison input');
|
|
637
|
-
}
|
|
638
|
-
catch (error) {
|
|
639
|
-
if (error instanceof CliUsageError)
|
|
640
|
-
throw error;
|
|
641
|
-
throw new CliUsageError(`Could not read comparison input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
642
|
-
}
|
|
643
|
-
if (!input.trim())
|
|
644
|
-
throw new CliUsageError('compare requires one lookup JSON file or a lookup document on stdin.');
|
|
645
|
-
const parsed = parseCliLookupDocument(input);
|
|
646
|
-
const loadComparison = dependencies.loadRegistryComparison || (() => import("../lib/registry-comparison.mjs"));
|
|
647
|
-
const comparisonModule = await loadComparison();
|
|
648
|
-
const result = compareLookupDocument(parsed, comparisonModule.compareRegistrySources, comparisonModule.compareRdapPublications);
|
|
649
|
-
const now = dependencies.now ? dependencies.now() : new Date().toISOString();
|
|
650
|
-
const document = buildCliCompareDocument(result, now);
|
|
651
|
-
if (!args.quiet)
|
|
652
|
-
write(stdout, args.output === 'json' ? formatJsonDocument(document) : terminal(formatTerminalCompare(document), args.color));
|
|
653
|
-
return EXIT_CODES.SUCCESS;
|
|
654
|
-
}
|
|
655
|
-
if (args.action === 'page-compare') {
|
|
656
|
-
failureLabel = 'Static page comparison';
|
|
657
|
-
const readDiffInput = dependencies.readDiffInput
|
|
658
|
-
|| ((source) => readInput(source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Page comparison input'));
|
|
659
|
-
let leftInput;
|
|
660
|
-
let rightInput;
|
|
661
|
-
try {
|
|
662
|
-
[leftInput, rightInput] = await Promise.all([
|
|
663
|
-
readDiffInput(args.leftSource),
|
|
664
|
-
readDiffInput(args.rightSource),
|
|
665
|
-
]);
|
|
666
|
-
}
|
|
667
|
-
catch (error) {
|
|
668
|
-
if (error instanceof CliUsageError)
|
|
669
|
-
throw error;
|
|
670
|
-
throw new CliUsageError(`Could not read page comparison input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
671
|
-
}
|
|
672
|
-
const document = buildCliPageComparison(leftInput, rightInput, commandContext.now());
|
|
673
|
-
if (!args.quiet)
|
|
674
|
-
write(stdout, args.output === 'json'
|
|
675
|
-
? formatJsonDocument(document)
|
|
676
|
-
: terminal(formatCliPageComparison(document), args.color));
|
|
677
|
-
return EXIT_CODES.SUCCESS;
|
|
678
|
-
}
|
|
679
|
-
if (args.action === 'mail-review') {
|
|
680
|
-
failureLabel = 'Passive mail review';
|
|
681
|
-
let input;
|
|
682
|
-
try {
|
|
683
|
-
input = dependencies.readMailReviewInput
|
|
684
|
-
? await dependencies.readMailReviewInput(args.source)
|
|
685
|
-
: await readInput(args.source, MAX_MAIL_REVIEW_INPUT_BYTES, 'Mail review input');
|
|
686
|
-
}
|
|
687
|
-
catch (error) {
|
|
688
|
-
if (error instanceof CliUsageError)
|
|
689
|
-
throw error;
|
|
690
|
-
throw new CliUsageError(`Could not read mail review input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
691
|
-
}
|
|
692
|
-
const document = buildCliMailReview(input, commandContext.now());
|
|
693
|
-
if (!args.quiet)
|
|
694
|
-
write(stdout, args.output === 'json'
|
|
695
|
-
? formatJsonDocument(document)
|
|
696
|
-
: terminal(formatCliMailReview(document), args.color));
|
|
697
|
-
return EXIT_CODES.SUCCESS;
|
|
698
|
-
}
|
|
699
|
-
if (args.action === 'review-evidence') {
|
|
700
|
-
failureLabel = 'Offline evidence review';
|
|
701
|
-
let input;
|
|
702
|
-
try {
|
|
703
|
-
input = dependencies.readArtifactInput
|
|
704
|
-
? await dependencies.readArtifactInput(args.source)
|
|
705
|
-
: await readInput(args.source, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Offline evidence input');
|
|
706
|
-
}
|
|
707
|
-
catch (error) {
|
|
708
|
-
if (error instanceof CliUsageError)
|
|
709
|
-
throw error;
|
|
710
|
-
throw new CliUsageError(`Could not read offline evidence input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
711
|
-
}
|
|
712
|
-
if (!input.trim())
|
|
713
|
-
throw new CliUsageError('review-evidence requires one JSON file or a document on stdin.');
|
|
714
|
-
const document = args.mmdbSource
|
|
715
|
-
? await buildOfflineEvidenceReviewWithLocalResources(input, commandContext.now(), { mmdbPath: args.mmdbSource })
|
|
716
|
-
: buildOfflineEvidenceReview(input, commandContext.now());
|
|
717
|
-
if (!args.quiet)
|
|
718
|
-
write(stdout, args.output === 'json'
|
|
719
|
-
? formatJsonDocument(document)
|
|
720
|
-
: terminal(formatOfflineEvidenceReview(document), args.color));
|
|
721
|
-
if (args.strictExit) {
|
|
722
|
-
const result = document.result && typeof document.result === 'object' && !Array.isArray(document.result)
|
|
723
|
-
? document.result
|
|
724
|
-
: {};
|
|
725
|
-
const gate = result.gate && typeof result.gate === 'object' && !Array.isArray(result.gate)
|
|
726
|
-
? result.gate
|
|
727
|
-
: null;
|
|
728
|
-
const zoneMismatch = document.kind === 'zone_intent' && (result.complete !== true
|
|
729
|
-
|| (result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
|
|
730
|
-
&& ['different', 'missing', 'unexpected', 'incomplete'].some((key) => Number(result.counts[key]) > 0)));
|
|
731
|
-
if (gate?.pass === false || zoneMismatch)
|
|
732
|
-
return EXIT_CODES.PARTIAL_FAILURE;
|
|
733
|
-
}
|
|
734
|
-
return EXIT_CODES.SUCCESS;
|
|
735
|
-
}
|
|
736
|
-
if (args.action === 'brief' || args.action === 'case-pack') {
|
|
737
|
-
const isBrief = args.action === 'brief';
|
|
738
|
-
failureLabel = isBrief ? 'Lookup brief' : 'Case pack';
|
|
739
|
-
let input;
|
|
740
|
-
try {
|
|
741
|
-
input = dependencies.readArtifactInput
|
|
742
|
-
? await dependencies.readArtifactInput(args.source)
|
|
743
|
-
: await readInput(args.source, isBrief ? MAX_SAVED_LOOKUP_INPUT_BYTES : 4 * 1024 * 1024, isBrief ? 'Lookup brief input' : 'Case-pack input');
|
|
744
|
-
}
|
|
745
|
-
catch (error) {
|
|
746
|
-
if (error instanceof CliUsageError)
|
|
747
|
-
throw error;
|
|
748
|
-
throw new CliUsageError(`Could not read ${isBrief ? 'Lookup brief' : 'case-pack'} input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
749
|
-
}
|
|
750
|
-
if (!input.trim())
|
|
751
|
-
throw new CliUsageError(`${args.action} requires one JSON file or a document on stdin.`);
|
|
752
|
-
if (args.action === 'brief') {
|
|
753
|
-
const document = buildCliLookupBrief(input, commandContext.now());
|
|
754
|
-
if (!args.quiet)
|
|
755
|
-
write(stdout, args.output === 'json'
|
|
756
|
-
? formatJsonDocument(document)
|
|
757
|
-
: terminal(formatCliLookupBrief(document), args.color));
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
const document = buildCliCasePack(input, { audience: args.audience, reviewed: args.reviewed }, commandContext.now());
|
|
761
|
-
if (!args.quiet)
|
|
762
|
-
write(stdout, args.output === 'json'
|
|
763
|
-
? formatJsonDocument(document)
|
|
764
|
-
: terminal(formatCliCasePack(document), args.color));
|
|
765
|
-
}
|
|
766
|
-
return EXIT_CODES.SUCCESS;
|
|
767
|
-
}
|
|
768
|
-
if (args.action === 'domain-control') {
|
|
769
|
-
failureLabel = 'Domain control review';
|
|
770
|
-
let input;
|
|
771
|
-
try {
|
|
772
|
-
input = dependencies.readArtifactInput
|
|
773
|
-
? await dependencies.readArtifactInput(args.source)
|
|
774
|
-
: await readInput(args.source, MAX_DOMAIN_CONTROL_REVIEW_COMMAND_BYTES, 'Domain control input');
|
|
775
|
-
}
|
|
776
|
-
catch (error) {
|
|
777
|
-
if (error instanceof CliUsageError)
|
|
778
|
-
throw error;
|
|
779
|
-
throw new CliUsageError(`Could not read domain control input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
780
|
-
}
|
|
781
|
-
if (!input.trim())
|
|
782
|
-
throw new CliUsageError('domain-control requires one JSON file or a document on stdin.');
|
|
783
|
-
const normalizedDomainControlInput = input.replace(/^\uFEFF/u, '');
|
|
784
|
-
let parsed;
|
|
785
|
-
try {
|
|
786
|
-
scanBoundedJson(normalizedDomainControlInput, {
|
|
787
|
-
maximumDepth: MAX_DOMAIN_CONTROL_REVIEW_JSON_DEPTH,
|
|
788
|
-
maximumKeys: MAX_DOMAIN_CONTROL_REVIEW_JSON_KEYS,
|
|
789
|
-
maximumValues: MAX_DOMAIN_CONTROL_REVIEW_JSON_VALUES,
|
|
790
|
-
});
|
|
791
|
-
parsed = JSON.parse(normalizedDomainControlInput);
|
|
792
|
-
}
|
|
793
|
-
catch (cause) {
|
|
794
|
-
const detail = cause instanceof Error ? cause.message : '';
|
|
795
|
-
if (detail.startsWith('Artefact JSON ')) {
|
|
796
|
-
throw new CliUsageError(`Domain control input ${detail.slice('Artefact JSON '.length)}`);
|
|
797
|
-
}
|
|
798
|
-
throw new CliUsageError('Domain control input is not valid JSON.');
|
|
799
|
-
}
|
|
800
|
-
const schema = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
801
|
-
? parsed.schema
|
|
802
|
-
: null;
|
|
803
|
-
const document = schema === DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA
|
|
804
|
-
? buildDomainControlManifest(parsed, commandContext.now())
|
|
805
|
-
: schema === DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
|
|
806
|
-
? reviewDomainControlManifest(parsed, commandContext.now())
|
|
807
|
-
: schema === CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
|
|
808
|
-
? buildCliDomainControlReview(input, commandContext.now())
|
|
809
|
-
: schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
|
|
810
|
-
? buildDomainControlFlightRecorder(parsed, commandContext.now())
|
|
811
|
-
: null;
|
|
812
|
-
if (!document) {
|
|
813
|
-
throw new CliUsageError(`Domain control input must use a supported manifest, review, saved-Lookup review, or flight-recorder schema.`);
|
|
814
|
-
}
|
|
815
|
-
const terminalDocument = schema === CLI_DOMAIN_CONTROL_REVIEW_INPUT_SCHEMA
|
|
816
|
-
? formatCliDomainControlReview(document)
|
|
817
|
-
: schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
|
|
818
|
-
? formatDomainControlFlightRecorder(document)
|
|
819
|
-
: formatDomainControlResult(document);
|
|
820
|
-
if (!args.quiet)
|
|
821
|
-
write(stdout, args.output === 'json'
|
|
822
|
-
? schema === DOMAIN_CONTROL_MANIFEST_INPUT_SCHEMA
|
|
823
|
-
? serializeDomainControlManifest(document)
|
|
824
|
-
: schema === DOMAIN_CONTROL_FLIGHT_RECORDER_INPUT_SCHEMA
|
|
825
|
-
? serializeDomainControlFlightRecorder(document)
|
|
826
|
-
: formatJsonDocument(document)
|
|
827
|
-
: terminal(terminalDocument, args.color));
|
|
828
|
-
return EXIT_CODES.SUCCESS;
|
|
829
|
-
}
|
|
830
|
-
if (args.action === 'monitor-once') {
|
|
831
|
-
failureLabel = 'One-shot domain control review';
|
|
832
|
-
let manifestInput;
|
|
833
|
-
let previousInput = null;
|
|
834
|
-
try {
|
|
835
|
-
manifestInput = dependencies.readArtifactInput
|
|
836
|
-
? await dependencies.readArtifactInput(args.source)
|
|
837
|
-
: await readInput(args.source, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Domain-control manifest input');
|
|
838
|
-
if (args.previousSource) {
|
|
839
|
-
previousInput = dependencies.readDiffInput
|
|
840
|
-
? await dependencies.readDiffInput(args.previousSource)
|
|
841
|
-
: await readInput(args.previousSource, MAX_OFFLINE_EVIDENCE_INPUT_BYTES, 'Prior monitor snapshot');
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
catch (error) {
|
|
845
|
-
if (error instanceof CliUsageError)
|
|
846
|
-
throw error;
|
|
847
|
-
throw new CliUsageError(`Could not read monitor input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
848
|
-
}
|
|
849
|
-
if (!manifestInput.trim())
|
|
850
|
-
throw new CliUsageError('monitor-once requires one domain-control manifest file or a document on stdin.');
|
|
851
|
-
const executeLookup = dependencies.runUnifiedLookup || (await import("../lib/lookup.mjs")).runUnifiedLookup;
|
|
852
|
-
const progress = commandContext.beginProgress('Collecting bounded domain-control evidence');
|
|
853
|
-
let document;
|
|
854
|
-
try {
|
|
855
|
-
document = await runDomainControlMonitor(manifestInput, previousInput, {
|
|
856
|
-
executeLookup,
|
|
857
|
-
now: commandContext.now,
|
|
858
|
-
limit: args.limit,
|
|
859
|
-
concurrency: args.concurrency,
|
|
860
|
-
...(dependencies.signal ? { signal: dependencies.signal } : {}),
|
|
861
|
-
onSettled: (completed, total) => progress.update(`Collected ${completed} of ${total} owned domains`),
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
finally {
|
|
865
|
-
commandContext.endProgress();
|
|
866
|
-
}
|
|
867
|
-
if (!args.quiet)
|
|
868
|
-
write(stdout, args.output === 'json'
|
|
869
|
-
? formatJsonDocument(document)
|
|
870
|
-
: args.output === 'junit'
|
|
871
|
-
? formatCliJunit(document)
|
|
872
|
-
: terminal(formatDomainControlMonitor(document), args.color));
|
|
873
|
-
const policyFindings = evaluateCliFailPolicies(document, args.failOn || []);
|
|
874
|
-
if (policyFindings.length)
|
|
875
|
-
write(stderr, formatFailPolicyNotice(policyFindings));
|
|
876
|
-
return document.collection.failed || policyFindings.length ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
|
|
877
|
-
}
|
|
878
|
-
if (args.action === 'assurance') {
|
|
879
|
-
failureLabel = 'Domain assurance review';
|
|
880
|
-
let input;
|
|
881
|
-
try {
|
|
882
|
-
input = dependencies.readArtifactInput
|
|
883
|
-
? await dependencies.readArtifactInput(args.source)
|
|
884
|
-
: await readInput(args.source, MAX_ASSURANCE_INPUT_BYTES, 'Domain assurance input');
|
|
885
|
-
}
|
|
886
|
-
catch (error) {
|
|
887
|
-
if (error instanceof CliUsageError)
|
|
888
|
-
throw error;
|
|
889
|
-
throw new CliUsageError(`Could not read domain assurance input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
890
|
-
}
|
|
891
|
-
if (!input.trim())
|
|
892
|
-
throw new CliUsageError('assurance requires one versioned JSON file or a document on stdin.');
|
|
893
|
-
let parsed;
|
|
894
|
-
try {
|
|
895
|
-
scanBoundedJson(input);
|
|
896
|
-
parsed = JSON.parse(input);
|
|
897
|
-
}
|
|
898
|
-
catch {
|
|
899
|
-
throw new CliUsageError('Domain assurance input is not valid bounded JSON without duplicate keys.');
|
|
900
|
-
}
|
|
901
|
-
let document;
|
|
902
|
-
try {
|
|
903
|
-
document = buildDomainAssurance(parsed, commandContext.now());
|
|
904
|
-
}
|
|
905
|
-
catch (error) {
|
|
906
|
-
throw new CliUsageError(boundedCliErrorMessage(error, 'Domain assurance input is invalid'));
|
|
907
|
-
}
|
|
908
|
-
if (!args.quiet)
|
|
909
|
-
write(stdout, args.output === 'json'
|
|
910
|
-
? formatJsonDocument(document)
|
|
911
|
-
: terminal(formatDomainAssurance(document), args.color));
|
|
912
|
-
return EXIT_CODES.SUCCESS;
|
|
913
|
-
}
|
|
914
|
-
if (args.action === 'change-packet') {
|
|
915
|
-
failureLabel = 'Domain change packet';
|
|
916
|
-
let input;
|
|
917
|
-
try {
|
|
918
|
-
input = dependencies.readArtifactInput
|
|
919
|
-
? await dependencies.readArtifactInput(args.source)
|
|
920
|
-
: await readInput(args.source, MAX_DOMAIN_CHANGE_PACKET_INPUT_BYTES, 'Domain change packet input');
|
|
921
|
-
}
|
|
922
|
-
catch (error) {
|
|
923
|
-
if (error instanceof CliUsageError)
|
|
924
|
-
throw error;
|
|
925
|
-
throw new CliUsageError(`Could not read domain change packet input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
926
|
-
}
|
|
927
|
-
if (!input.trim())
|
|
928
|
-
throw new CliUsageError('change-packet requires one versioned JSON file or a document on stdin.');
|
|
929
|
-
let parsed;
|
|
930
|
-
try {
|
|
931
|
-
scanBoundedJson(input);
|
|
932
|
-
parsed = JSON.parse(input);
|
|
933
|
-
}
|
|
934
|
-
catch {
|
|
935
|
-
throw new CliUsageError('Domain change packet input is not valid bounded JSON without duplicate keys.');
|
|
936
|
-
}
|
|
937
|
-
let document;
|
|
938
|
-
try {
|
|
939
|
-
document = await buildDomainChangePacket(parsed, commandContext.now());
|
|
940
|
-
}
|
|
941
|
-
catch (error) {
|
|
942
|
-
throw new CliUsageError(boundedCliErrorMessage(error, 'Domain change packet input is invalid'));
|
|
943
|
-
}
|
|
944
|
-
if (!args.quiet)
|
|
945
|
-
write(stdout, args.output === 'json'
|
|
946
|
-
? formatJsonDocument(document)
|
|
947
|
-
: terminal(formatDomainChangePacket(document), args.color));
|
|
948
|
-
return document.gate.pass ? EXIT_CODES.SUCCESS : EXIT_CODES.PARTIAL_FAILURE;
|
|
949
|
-
}
|
|
950
|
-
if (args.action === 'sharing-review') {
|
|
951
|
-
failureLabel = 'Evidence sharing review';
|
|
952
|
-
let input;
|
|
953
|
-
try {
|
|
954
|
-
input = dependencies.readArtifactInput
|
|
955
|
-
? await dependencies.readArtifactInput(args.source)
|
|
956
|
-
: await readInput(args.source, MAX_SHARING_REVIEW_BYTES, 'Sharing review input');
|
|
957
|
-
}
|
|
958
|
-
catch (error) {
|
|
959
|
-
if (error instanceof CliUsageError)
|
|
960
|
-
throw error;
|
|
961
|
-
throw new CliUsageError(`Could not read sharing review input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
962
|
-
}
|
|
963
|
-
if (!input.trim())
|
|
964
|
-
throw new CliUsageError('sharing-review requires one artefact JSON file or a document on stdin.');
|
|
965
|
-
let document;
|
|
966
|
-
try {
|
|
967
|
-
document = await buildSharingReview(input, {
|
|
968
|
-
marking: args.marking,
|
|
969
|
-
recipientScope: args.recipientScope,
|
|
970
|
-
purpose: args.purpose,
|
|
971
|
-
humanReviewed: args.humanReviewed,
|
|
972
|
-
personalDataReviewed: args.personalDataReviewed,
|
|
973
|
-
redactionsConfirmed: args.redactionsConfirmed,
|
|
974
|
-
}, commandContext.now());
|
|
975
|
-
}
|
|
976
|
-
catch (error) {
|
|
977
|
-
throw new CliUsageError(boundedCliErrorMessage(error, 'Sharing review input is invalid'));
|
|
978
|
-
}
|
|
979
|
-
if (!args.quiet)
|
|
980
|
-
write(stdout, args.output === 'json'
|
|
981
|
-
? formatJsonDocument(document)
|
|
982
|
-
: terminal(formatSharingReview(document), args.color));
|
|
983
|
-
return document.summary.status === 'blocked' ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
|
|
984
|
-
}
|
|
985
|
-
if (args.action === 'workflow-plan') {
|
|
986
|
-
failureLabel = 'Investigation plan';
|
|
987
|
-
if ('discovery' in args) {
|
|
988
|
-
const catalogue = buildWorkflowRecipeCatalogue(args.discovery === 'explain' ? args.recipe : null);
|
|
989
|
-
if (!args.quiet)
|
|
990
|
-
write(stdout, args.output === 'json'
|
|
991
|
-
? formatJsonDocument(catalogue)
|
|
992
|
-
: terminal(formatWorkflowRecipeCatalogue(catalogue), args.color));
|
|
993
|
-
return EXIT_CODES.SUCCESS;
|
|
994
|
-
}
|
|
995
|
-
const document = buildInvestigationPlan(args.recipe, args.subject, commandContext.now());
|
|
996
|
-
if (!args.quiet)
|
|
997
|
-
write(stdout, args.output === 'json'
|
|
998
|
-
? formatJsonDocument(document)
|
|
999
|
-
: terminal(formatInvestigationPlan(document), args.color));
|
|
1000
|
-
return EXIT_CODES.SUCCESS;
|
|
1001
|
-
}
|
|
1002
|
-
if (args.action === 'workflow-run') {
|
|
1003
|
-
failureLabel = 'Investigation workflow';
|
|
1004
|
-
let resumeInput = null;
|
|
1005
|
-
if (args.resumeSource) {
|
|
1006
|
-
try {
|
|
1007
|
-
resumeInput = dependencies.readDiffInput
|
|
1008
|
-
? await dependencies.readDiffInput(args.resumeSource)
|
|
1009
|
-
: await readInput(args.resumeSource, MAX_INVESTIGATION_RUN_BYTES, 'Investigation resume state');
|
|
1010
|
-
}
|
|
1011
|
-
catch (error) {
|
|
1012
|
-
if (error instanceof CliUsageError)
|
|
1013
|
-
throw error;
|
|
1014
|
-
throw new CliUsageError(`Could not read investigation resume state: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
const document = await runInvestigationRecipe(args.recipe, args.subject, {
|
|
1018
|
-
approveNetwork: args.approveNetwork,
|
|
1019
|
-
resumeInput,
|
|
1020
|
-
generatedAt: commandContext.now(),
|
|
1021
|
-
...(dependencies.signal ? { signal: dependencies.signal } : {}),
|
|
1022
|
-
execute: async (command, stepArguments) => {
|
|
1023
|
-
const stepStdout = createBufferedOutput();
|
|
1024
|
-
const stepStderr = createBufferedOutput();
|
|
1025
|
-
const exitCode = await runCli([command, ...stepArguments], {
|
|
1026
|
-
...dependencies,
|
|
1027
|
-
stdout: stepStdout.stream,
|
|
1028
|
-
stderr: stepStderr.stream,
|
|
1029
|
-
});
|
|
1030
|
-
return { exitCode, stdout: stepStdout.value() };
|
|
1031
|
-
},
|
|
1032
|
-
});
|
|
1033
|
-
if (!args.quiet)
|
|
1034
|
-
write(stdout, args.output === 'json'
|
|
1035
|
-
? formatJsonDocument(document)
|
|
1036
|
-
: terminal(formatInvestigationRun(document), args.color));
|
|
1037
|
-
return document.state === 'step_failed' ? EXIT_CODES.PARTIAL_FAILURE : EXIT_CODES.SUCCESS;
|
|
1038
|
-
}
|
|
1039
|
-
if (args.action === 'diff') {
|
|
1040
|
-
failureLabel = 'Retained artifact diff';
|
|
1041
|
-
const readDiffInput = dependencies.readDiffInput
|
|
1042
|
-
|| ((source) => readInput(source, MAX_RETAINED_ARTIFACT_DIFF_BYTES, 'Retained diff input'));
|
|
1043
|
-
let leftInput;
|
|
1044
|
-
let rightInput;
|
|
1045
|
-
try {
|
|
1046
|
-
[leftInput, rightInput] = await Promise.all([
|
|
1047
|
-
readDiffInput(args.leftSource),
|
|
1048
|
-
readDiffInput(args.rightSource),
|
|
1049
|
-
]);
|
|
1050
|
-
}
|
|
1051
|
-
catch (error) {
|
|
1052
|
-
if (error instanceof CliUsageError)
|
|
1053
|
-
throw error;
|
|
1054
|
-
throw new CliUsageError(`Could not read retained diff input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
1055
|
-
}
|
|
1056
|
-
const document = buildCliRetainedArtifactDiff(leftInput, rightInput, { leftSessionId: args.leftSessionId, rightSessionId: args.rightSessionId }, dependencies.now ? dependencies.now() : new Date().toISOString());
|
|
1057
|
-
if (!args.quiet)
|
|
1058
|
-
write(stdout, args.output === 'json'
|
|
1059
|
-
? formatJsonDocument(document)
|
|
1060
|
-
: terminal(formatCliRetainedArtifactDiff(document), args.color));
|
|
1061
|
-
return EXIT_CODES.SUCCESS;
|
|
1062
|
-
}
|
|
1063
|
-
if (args.action === 'reconcile') {
|
|
1064
|
-
failureLabel = 'Lookup observation reconciliation';
|
|
1065
|
-
const readReconciliationInput = dependencies.readDiffInput
|
|
1066
|
-
|| ((source) => readInput(source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Lookup reconciliation input'));
|
|
1067
|
-
const inputs = [];
|
|
1068
|
-
let totalBytes = 0;
|
|
1069
|
-
try {
|
|
1070
|
-
for (const source of args.sources) {
|
|
1071
|
-
const input = await readReconciliationInput(source);
|
|
1072
|
-
totalBytes += Buffer.byteLength(input, 'utf8');
|
|
1073
|
-
if (totalBytes > MAX_LOOKUP_RECONCILIATION_INPUT_BYTES) {
|
|
1074
|
-
throw new CliUsageError(`Lookup reconciliation input is limited to ${MAX_LOOKUP_RECONCILIATION_INPUT_BYTES} bytes in total.`);
|
|
1075
|
-
}
|
|
1076
|
-
inputs.push(input);
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
catch (error) {
|
|
1080
|
-
if (error instanceof CliUsageError)
|
|
1081
|
-
throw error;
|
|
1082
|
-
throw new CliUsageError(`Could not read Lookup reconciliation input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
1083
|
-
}
|
|
1084
|
-
const document = buildCliLookupReconciliation(inputs, commandContext.now());
|
|
1085
|
-
if (!args.quiet)
|
|
1086
|
-
write(stdout, args.output === 'json'
|
|
1087
|
-
? formatJsonDocument(document)
|
|
1088
|
-
: terminal(formatCliLookupReconciliation(document), args.color));
|
|
1089
|
-
return EXIT_CODES.SUCCESS;
|
|
1090
|
-
}
|
|
1091
|
-
if (args.action === 'timeline') {
|
|
1092
|
-
failureLabel = 'Lookup observation timeline';
|
|
1093
|
-
const readTimelineInput = dependencies.readDiffInput
|
|
1094
|
-
|| ((source) => readInput(source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Lookup timeline input'));
|
|
1095
|
-
const inputs = [];
|
|
1096
|
-
let totalBytes = 0;
|
|
1097
|
-
try {
|
|
1098
|
-
for (const source of args.sources) {
|
|
1099
|
-
const input = await readTimelineInput(source);
|
|
1100
|
-
totalBytes += Buffer.byteLength(input, 'utf8');
|
|
1101
|
-
if (totalBytes > MAX_LOOKUP_TIMELINE_INPUT_BYTES) {
|
|
1102
|
-
throw new CliUsageError(`Lookup timeline input is limited to ${MAX_LOOKUP_TIMELINE_INPUT_BYTES} bytes in total.`);
|
|
1103
|
-
}
|
|
1104
|
-
inputs.push(input);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
catch (error) {
|
|
1108
|
-
if (error instanceof CliUsageError)
|
|
1109
|
-
throw error;
|
|
1110
|
-
throw new CliUsageError(`Could not read Lookup timeline input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
1111
|
-
}
|
|
1112
|
-
const document = buildCliLookupTimeline(inputs, commandContext.now());
|
|
1113
|
-
if (!args.quiet)
|
|
1114
|
-
write(stdout, args.output === 'json'
|
|
1115
|
-
? formatJsonDocument(document)
|
|
1116
|
-
: terminal(formatCliLookupTimeline(document), args.color));
|
|
1117
|
-
return EXIT_CODES.SUCCESS;
|
|
1118
|
-
}
|
|
1119
|
-
if (args.action === 'export') {
|
|
1120
|
-
failureLabel = 'Evidence export';
|
|
1121
|
-
let input;
|
|
1122
|
-
try {
|
|
1123
|
-
input = dependencies.readExportInput
|
|
1124
|
-
? await dependencies.readExportInput(args.source)
|
|
1125
|
-
: await readInput(args.source, MAX_SAVED_LOOKUP_INPUT_BYTES, 'Evidence export input');
|
|
1126
|
-
}
|
|
1127
|
-
catch (error) {
|
|
1128
|
-
if (error instanceof CliUsageError)
|
|
1129
|
-
throw error;
|
|
1130
|
-
throw new CliUsageError(`Could not read evidence export input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
1131
|
-
}
|
|
1132
|
-
if (!input.trim())
|
|
1133
|
-
throw new CliUsageError('export requires one lookup JSON file or a lookup document on stdin.');
|
|
1134
|
-
const loadEvidence = dependencies.loadEvidenceExport || (() => import("../lib/evidence-export.mjs"));
|
|
1135
|
-
const evidenceModule = await loadEvidence();
|
|
1136
|
-
const now = dependencies.now ? dependencies.now() : new Date().toISOString();
|
|
1137
|
-
const document = buildCliEvidenceExport(input, evidenceModule, now);
|
|
1138
|
-
const output = args.format === 'markdown'
|
|
1139
|
-
? formatLookupEvidenceMarkdown(document, { includeAttribution: args.includeAttribution })
|
|
1140
|
-
: args.format === 'html'
|
|
1141
|
-
? formatLookupEvidenceHtml(document, { includeAttribution: args.includeAttribution })
|
|
1142
|
-
: formatCliEvidenceExport(document, args.compact);
|
|
1143
|
-
write(stdout, output);
|
|
1144
|
-
return EXIT_CODES.SUCCESS;
|
|
1145
|
-
}
|
|
1146
|
-
if (args.action === 'ct-intake') {
|
|
1147
|
-
failureLabel = 'Certificate event intake';
|
|
1148
|
-
let input;
|
|
1149
|
-
try {
|
|
1150
|
-
input = dependencies.readArtifactInput
|
|
1151
|
-
? await dependencies.readArtifactInput(args.source)
|
|
1152
|
-
: await readInput(args.source, MAX_CT_EVENT_INPUT_BYTES, 'Certificate event input');
|
|
1153
|
-
}
|
|
1154
|
-
catch (error) {
|
|
1155
|
-
if (error instanceof CliUsageError)
|
|
1156
|
-
throw error;
|
|
1157
|
-
throw new CliUsageError(`Could not read certificate event input: ${boundedCliErrorMessage(error, 'Input could not be read')}`);
|
|
1158
|
-
}
|
|
1159
|
-
if (!input.trim())
|
|
1160
|
-
throw new CliUsageError('ct-intake requires one versioned JSON file or a document on stdin.');
|
|
1161
|
-
let parsed;
|
|
1162
|
-
try {
|
|
1163
|
-
scanBoundedJson(input);
|
|
1164
|
-
parsed = JSON.parse(input);
|
|
1165
|
-
}
|
|
1166
|
-
catch {
|
|
1167
|
-
throw new CliUsageError('Certificate event input is not valid bounded JSON without duplicate keys.');
|
|
1168
|
-
}
|
|
1169
|
-
let document;
|
|
1170
|
-
try {
|
|
1171
|
-
document = buildCtEventFindings(parsed);
|
|
1172
|
-
}
|
|
1173
|
-
catch (error) {
|
|
1174
|
-
throw new CliUsageError(boundedCliErrorMessage(error, 'Certificate event input is invalid'));
|
|
1175
|
-
}
|
|
1176
|
-
if (!args.quiet)
|
|
1177
|
-
write(stdout, args.output === 'json'
|
|
1178
|
-
? formatJsonDocument(document)
|
|
1179
|
-
: terminal(formatCtEventFindings(document), args.color));
|
|
1180
|
-
return EXIT_CODES.SUCCESS;
|
|
1181
|
-
}
|
|
1182
|
-
throw new Error('No CLI execution route is registered for the parsed command.');
|
|
225
|
+
return await runInlineCommand(args, dependencies, commandContext);
|
|
1183
226
|
}
|
|
1184
227
|
catch (error) {
|
|
1185
228
|
progress?.stop();
|