215 lines
6.9 KiB
TypeScript
215 lines
6.9 KiB
TypeScript
import { z } from "incur";
|
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
|
|
import {
|
|
runCapabilityStatus,
|
|
runProviderAnalysis,
|
|
runProviderStatus,
|
|
} from "../composition/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 { swiftSymbolsSchema } from "../contracts/native/nativeToolContracts.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<Record<string, string | undefined>> = 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()
|
|
.optional()
|
|
.describe(
|
|
"Plist path relative to the app root (default: Contents/Info.plist)",
|
|
),
|
|
}),
|
|
alias: { relativePath: "relative-path" },
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "inspect-plist", () =>
|
|
runProviderAnalysis(
|
|
args.path,
|
|
"inspect_plist",
|
|
options.relativePath === undefined
|
|
? {}
|
|
: { 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: swiftSymbolsSchema.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<Record<string, string | undefined>> = 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,
|
|
),
|
|
});
|
|
};
|