import { Cli, z } from "incur"; import { listJavaScriptRuntimeTargets, observeJavaScriptRuntime, } from "./application/JavaScriptRuntimeObservationService.js"; import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js"; import { V8InspectorProvider } from "./browser/V8InspectorProvider.js"; import { CLI_COMMANDS } from "./cliCommandNames.js"; import { logCliCommand } from "./cliLogging.js"; import { parseConfig } from "./config.js"; import { javascriptRuntimeKindSchema, listJavaScriptRuntimeTargetsInputSchema, observeJavaScriptRuntimeInputSchema, } from "./domain/javascriptRuntimeObservation.js"; import { AnalysisInputError, projectAnalysisError } from "./domain/errors.js"; import type { JsonValue } from "./domain/jsonValue.js"; import type { Logger } from "./logger.js"; const scopeOptions = { allowedFileRoots: z .array(z.string().min(1)) .optional() .describe("Exact roots; defaults to REA_V8_INSPECTOR_FILE_ROOTS_JSON"), allowedOrigins: z .array(z.string().min(1)) .optional() .describe( "Exact origins; defaults to REA_V8_INSPECTOR_ALLOWED_ORIGINS_JSON", ), approved: z.boolean().default(false).describe("Approve passive attachment"), }; const listOptionsSchema = z.object({ ...scopeOptions, offset: z .number() .int() .min(0) .default(0) .describe("Zero-based target offset"), limit: z .number() .int() .min(1) .max(200) .default(100) .describe("Maximum targets to return"), }); const observeOptionsSchema = z.object({ ...scopeOptions, runtimeKind: javascriptRuntimeKindSchema.describe( "Declared target role; Inspector cannot authenticate the Electron role", ), observationMs: z .number() .int() .min(0) .max(10_000) .default(100) .describe("Bounded observation window in milliseconds"), maxEvents: z .number() .int() .min(1) .max(50_000) .default(10_000) .describe("Maximum retained protocol events"), maxScripts: z .number() .int() .min(1) .max(10_000) .default(2_000) .describe("Maximum retained scripts"), maxExecutionContexts: z .number() .int() .min(1) .max(5_000) .default(1_000) .describe("Maximum retained execution contexts"), maxLocationBytes: z .number() .int() .min(64) .max(65_536) .default(16_384) .describe("Maximum bytes in one protocol location"), maxTotalMetadataBytes: z .number() .int() .min(1_024) .max(32 * 1_024 * 1_024) .default(4 * 1_024 * 1_024) .describe("Maximum retained metadata bytes"), }); /** Register CLI equivalents of passive V8 Inspector tools. */ export const registerJavaScriptRuntimeObservationCommands = ( cli: ReturnType, logger: Logger, ): void => { cli.command(CLI_COMMANDS.listJavaScriptRuntimeTargets, { description: "List approved Node/Electron V8 Inspector targets", args: z.object({ endpoint: z.string().describe("Configured literal-loopback endpoint"), }), options: listOptionsSchema, run: ({ args, options }) => logCliCommand( logger, CLI_COMMANDS.listJavaScriptRuntimeTargets, async () => { const context = await runtimeContext(); if (!context.ok) return context.error; const parsed = listJavaScriptRuntimeTargetsInputSchema.safeParse({ inspector_endpoint: args.endpoint, allowed_file_roots: options.allowedFileRoots ?? context.allowedFileRoots, allowed_origins: options.allowedOrigins ?? context.allowedOrigins, approved: options.approved, offset: options.offset, limit: options.limit, }); if (!parsed.success) return inputError("list_javascript_runtime_targets"); const result = await listJavaScriptRuntimeTargets( context.provider, context.authority, parsed.data, ); return result.ok ? result.value : cliError(result.error); }, ), }); cli.command(CLI_COMMANDS.observeJavaScriptRuntime, { description: "Passively observe one exact Node/Electron Inspector target", args: z.object({ endpoint: z.string().describe("Configured literal-loopback endpoint"), targetId: z .string() .describe("Target from list-javascript-runtime-targets"), }), options: observeOptionsSchema, run: ({ args, options }) => logCliCommand(logger, CLI_COMMANDS.observeJavaScriptRuntime, async () => { const context = await runtimeContext(); if (!context.ok) return context.error; const parsed = observeJavaScriptRuntimeInputSchema.safeParse({ inspector_endpoint: args.endpoint, allowed_file_roots: options.allowedFileRoots ?? context.allowedFileRoots, allowed_origins: options.allowedOrigins ?? context.allowedOrigins, target_id: args.targetId, runtime_kind: options.runtimeKind, approved: options.approved, observation_ms: options.observationMs, limits: { max_events: options.maxEvents, max_scripts: options.maxScripts, max_execution_contexts: options.maxExecutionContexts, max_location_bytes: options.maxLocationBytes, max_total_metadata_bytes: options.maxTotalMetadataBytes, }, }); if (!parsed.success) return inputError("observe_javascript_runtime"); const result = await observeJavaScriptRuntime( context.provider, context.authority, parsed.data, ); return result.ok ? result.value : cliError(result.error); }), }); }; const runtimeContext = async () => { const config = parseConfig(process.env); if (!config.ok) return { ok: false as const, error: cliError(config.error) }; const authority = await loadConfiguredPermissionAuthority(config.value); if (!authority.ok) return { ok: false as const, error: cliError(authority.error) }; return { ok: true as const, authority: authority.value, provider: new V8InspectorProvider(), allowedFileRoots: config.value.v8InspectorFileRoots, allowedOrigins: config.value.v8InspectorAllowedOrigins, }; }; const inputError = (operation: string): JsonValue => cliError(new AnalysisInputError(operation)); const cliError = ( error: Parameters[0], ): JsonValue => ({ error: "JavaScript runtime observation failed", ...projectAnalysisError(error), });