import { z } from "incur"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { runCapabilityStatus, runProviderAnalysis, runProviderStatus, } from "../application/DirectAnalysis.js"; import { importReferenceSource } from "../application/ReferenceSourceImport.js"; import { projectReferenceSourceImportError } from "../application/ReferenceSourceImportTypes.js"; import { parseConfig } from "../config.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { PRODUCT_IDENTITY } from "../identity.js"; import { logCliCommand } from "../cliLogging.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; import type { Logger } from "../logger.js"; import { isReferenceSourceImportCliFailure } from "./referenceSourceImportStatus.js"; import type { CliInstance } from "./types.js"; export const registerUtilityCommands = ( cli: CliInstance, logger: Logger, environment: Readonly> = process.env, ): void => { registerCapabilityCommands(cli, logger); registerNativeCommands(cli, logger); for (const [command, operation] of [ [CLI_COMMANDS.observeNativeUi, "observe_native_ui"], [CLI_COMMANDS.captureNativeUiScenario, "capture_native_ui_scenario"], ] as const) { cli.command(command, { description: "Observe or run a scenario in one exact native application window", args: z.object({ path: z.string().describe("Local native executable or app path"), }), options: z.object({ pid: z.number().int().positive().describe("Existing application PID"), windowId: z .number() .int() .positive() .describe("Exact selected application window ID"), steps: z .string() .optional() .describe("JSON array of declarative AX actions or waits"), screenshot: z .boolean() .default(true) .describe("Capture only the selected window"), accessibility: z .boolean() .default(true) .describe("Read the selected window accessibility tree"), maxNodes: z .number() .int() .positive() .safe() .default(500) .describe("Maximum accessibility nodes per capture"), }), alias: { windowId: "window-id", maxNodes: "max-nodes", }, run: ({ args, options }) => logCliCommand(logger, command, () => runProviderAnalysis( args.path, operation, { pid: options.pid, window_id: options.windowId, screenshot: options.screenshot, accessibility: options.accessibility, max_nodes: options.maxNodes, ...(operation === "capture_native_ui_scenario" ? { steps: options.steps === undefined ? [] : jsonValueSchema.parse(JSON.parse(options.steps)), } : {}), }, logger, ), ), }); } registerReferenceSourceCommand(cli, logger, environment); }; const registerCapabilityCommands = (cli: CliInstance, logger: Logger): void => { for (const command of [ CLI_COMMANDS.capabilities, CLI_COMMANDS.providers, ] as const) { cli.command(command, { description: command === "capabilities" ? "List provider capabilities and side effects" : "List configured analysis providers", run: () => logCliCommand(logger, command, () => command === CLI_COMMANDS.providers ? runProviderStatus(logger) : runCapabilityStatus(logger), ), }); } }; const registerNativeCommands = (cli: CliInstance, logger: Logger): void => { for (const [command, tool] of [ [CLI_COMMANDS.inspectMacho, "inspect_macho"], [CLI_COMMANDS.inspectSignature, "inspect_signature"], [CLI_COMMANDS.listArchitectures, "list_architectures"], ] as const) { cli.command(command, { description: `Run ${tool} without launching Hopper`, args: z.object({ path: z.string().describe("Mach-O or app path") }), run: ({ args }) => logCliCommand(logger, command, () => runProviderAnalysis(args.path, tool, {}, logger), ), }); } cli.command(CLI_COMMANDS.inspectPlist, { description: "Parse app plist metadata without launching Hopper", args: z.object({ path: z.string().describe("App or Mach-O path") }), options: z.object({ relativePath: z .string() .default("Contents/Info.plist") .describe("Plist path relative to the app root"), }), alias: { relativePath: "relative-path" }, run: ({ args, options }) => logCliCommand(logger, "inspect-plist", () => runProviderAnalysis( args.path, "inspect_plist", { relative_path: options.relativePath }, logger, ), ), }); cli.command(CLI_COMMANDS.demangleSwift, { description: "Demangle Swift symbols without Hopper", args: z.object({ path: z.string().describe("Artifact path used for evidence identity"), symbols: z .array(z.string().min(1)) .min(1) .describe("Swift mangled symbols to demangle"), }), run: ({ args }) => logCliCommand(logger, "demangle-swift", () => runProviderAnalysis( args.path, "demangle_swift", { symbols: args.symbols }, logger, ), ), }); }; const registerReferenceSourceCommand = ( cli: CliInstance, logger: Logger, environment: Readonly> = process.env, ): void => { cli.command(CLI_COMMANDS.importReferenceSource, { description: "Import a source tree as historical reference only", args: z.object({ root: z .string() .describe("Local source directory to import as historical reference"), }), run: ({ args }) => logCliCommand( logger, "import-reference-source", async () => { const config = parseConfig(environment); if (!config.ok) return { error: "Import failed", ...projectAnalysisError(config.error), }; const imported = await importReferenceSource({ root: args.root, caller: "rea-cli", policy: config.value.referenceSourcePolicy, importer: PRODUCT_IDENTITY.packageName, importerVersion: null, }); return imported.ok ? imported.value : { error: "Import failed", ...projectReferenceSourceImportError(imported.error), }; }, isReferenceSourceImportCliFailure, ), }); };