Files
rea/src/cli.ts
T
morluto b718ad166d fix(doctor): reject unusable Kuna caches and invalid native settings (#5647)
* fix(kuna): reject dangling cache roots in doctor

Treat a cache path as creatable only when no directory entry occupies it.
A dangling symbolic link, including one hidden by a trailing separator,
fails the cache check and clears provider availability while the installation
check and caller-selected registration settings remain.

Fixes #5554
Fixes #5105

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(doctor): admit the same settings native analysis rejects

Project parseConfig failures into scoped engine readiness. Invalid provider
preferences, startup timeouts, and other shared settings now fail the
selected provider and the full audit, while a JEB-only timeout stays outside
that admission.

Fixes #5454

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(doctor): report an invalid log level from doctor

The dispatcher rejected REA_LOG_LEVEL before every command, so doctor never
emitted its readiness check. Doctor now runs and names that setting; other
commands still stop before execution.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(doctor): require log-level failures in every scope

An invalid REA_LOG_LEVEL stops the executable and MCP server before a client
or skill can run, so those scopes now fail readiness too. Other native
admission failures stay limited to a selected provider.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(kuna): strip only the platform cache separator

On Darwin and Linux a trailing backslash is a filename character. Doctor now
removes only the selected platform separator before inspecting the cache path.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(doctor): fail every provider on an invalid Hopper timeout

REA_HOPPER_STARTUP_TIMEOUT_MS is rejected before any native engine starts.
A selected non-Hopper provider now includes the existing timeout check.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* style(doctor): format the Hopper timeout regression

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(doctor): fail client scopes on MCP startup errors

A registered client starts the MCP server, and parseConfig rejects every
native admission failure before that server starts. Client readiness now
includes those failures, not only REA_LOG_LEVEL.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-21 03:44:49 +08:00

168 lines
8.6 KiB
TypeScript

import { registerNativeFunctionMatchingCommands } from "./cli/nativeFunctionMatchingCommands.js";
import { registerCaptureCommands } from "./cli/captureCommands.js";
import { registerSignalCommands } from "./cli/signalCommands.js";
import { registerWasmCommands } from "./cli/wasmCommands.js";
import { snapshotEnvironment } from "./process/snapshotEnvironment.js";
import { createDirectAnalysis } from "./composition/directAnalysis.js";
import { registerEvmCommands } from "./cli/evmCommands.js";
import { registerDesktopApplicationCommands } from "./cli/desktopApplicationCommands.js";
import { registerGoCommands } from "./cli/goCommands.js";
import { registerModelCommands } from "./cli/modelCommands.js";
import { registerIl2cppCommands } from "./cli/il2cppCommands.js";
import { registerRustCommands } from "./cli/rustCommands.js";
import { registerSqliteCommands } from "./cli/sqliteCommands.js";
import { registerAnalysisViewCommands } from "./cli/analysisViewCommands.js";
import { registerBinaryDiagnosticsCommands } from "./cli/binaryDiagnosticsCommands.js";
import { registerFridaCommands } from "./cli/fridaCommands.js";
import type { FridaInstrumentationPort } from "./application/frida/FridaInstrumentationPort.js";
import { Cli } from "incur";
import { normalizeCliArguments } from "./cliOutput.js";
import { createLogger, parseLogLevel } from "./logger.js";
import { projectAnalysisError } from "./domain/analysisErrorProjection.js";
import { PRODUCT_IDENTITY } from "./identity.js";
import { npxRegistrationCommand } from "./application/ClientRegistrationIdentity.js";
import { registerSetupCommands } from "./cli/setupCommands.js";
import { registerCoreAnalysisCommands } from "./cli/coreAnalysisCommands.js";
import { registerUtilityCommands } from "./cli/utilityCommands.js";
import { registerArtifactCommands } from "./cli/artifactCommands.js";
import { registerManagedCommands } from "./cli/managedCommands.js";
import { registerFirmwareCommands } from "./cli/firmwareCommands.js";
import { registerAndroidCommands } from "./cli/androidCommands.js";
import { registerApktoolCommands } from "./cli/apktoolCommands.js";
import { registerPcapCommands } from "./cli/pcapCommands.js";
import { registerFlutterCommands } from "./cli/flutterCommands.js";
import { registerJebCommands } from "./cli/jebCommands.js";
import { registerAdbCommands } from "./cli/adbCommands.js";
import { registerEvidenceCommands } from "./cliEvidenceCommands.js";
import { registerEnvironmentCommands } from "./cli/environmentCommands.js";
import { registerProcessCommands } from "./cli/processCommands.js";
import { registerBrowserCommands } from "./cli/browserCommands.js";
import { registerWebScriptCommands } from "./cli/webScriptCommands.js";
import { registerWebModuleCommands } from "./cli/webModuleCommands.js";
import { registerWebSourceLocationCommands } from "./cli/webSourceLocationCommands.js";
import { registerWebRuntimeCommands } from "./cli/webRuntimeCommands.js";
import { registerWebNetworkCaptureCommands } from "./cli/webNetworkCaptureCommands.js";
import { registerJavaScriptRecoveryCommands } from "./cli/javascriptRecoveryCommands.js";
import { registerAdvancedBrowserCommands } from "./cli/browserAdvancedCommands.js";
import { registerBrowserScenarioCommands } from "./cliBrowserScenarioCommands.js";
import { registerElectronCommands } from "./cli/electronCommands.js";
import { registerJavaScriptRuntimeObservationCommands } from "./cliJavaScriptRuntimeCommands.js";
import { registerApplicationCommands } from "./cli/applicationCommands.js";
import { presentOmittableDefaults } from "./cli/omittableDefaults.js";
import type { CliInstance } from "./cli/types.js";
import {
streamCliCommandResults,
type CliResultOutput,
} from "./cli/cliResultOutput.js";
import { registerReverseEngineeringCommands } from "./cli/reverseEngineeringCommands.js";
import { registerCutterCommands } from "./cli/cutterCommands.js";
/**
* Describe CLI settings that are invalid before any command runs, as MCP does.
* Keep this aligned with PROCESS_START_CONFIGURATION_SETTINGS.
*/
export const cliConfigurationError = (
selectedEnvironment: Readonly<Record<string, string | undefined>>,
): string | undefined => {
const value = selectedEnvironment.REA_LOG_LEVEL;
if (value === undefined) return undefined;
const level = parseLogLevel(value);
return level.ok ? undefined : projectAnalysisError(level.error).message;
};
/**
* Build the one-shot Incur CLI without starting Hopper at import time.
* Analysis commands acquire and close their own sessions; bare `mcp` and
* `--mcp` are intercepted by the executable dispatcher before this module loads.
*/
export const createCli = (
selectedEnvironment: Readonly<
Record<string, string | undefined>
> = process.env,
resultOutput?: CliResultOutput,
fridaInstrumentation?: FridaInstrumentationPort,
): CliInstance => {
const environment = snapshotEnvironment(selectedEnvironment);
const analysis = createDirectAnalysis(environment);
// The executable reports an invalid level before every command except doctor.
// Doctor keeps going so its readiness report can name REA_LOG_LEVEL.
const level =
environment.REA_LOG_LEVEL === undefined
? undefined
: parseLogLevel(environment.REA_LOG_LEVEL);
const logger = createLogger(
"cli",
level?.ok === true ? level.value : "silent",
);
const cli = Cli.create(PRODUCT_IDENTITY.cliBinary, {
version: PRODUCT_IDENTITY.packageVersion,
description: "Reverse engineer anything from your terminal or agent.",
mcp: {
command: npxRegistrationCommand().join(" "),
instructions:
"Ask what software, artifact, protocol, or behavior the user wants to understand, then choose the available investigation capabilities that can produce evidence.",
},
sync: false,
update: {
// Incur's update:false only suppresses notices. Reject its explicit
// installer so ambiguous flags cannot bypass REA's verified workflow.
install: () => {
throw new Error(
"Use 'rea update' to update the identified installation. The '--update' alias accepts only global output flags.",
);
},
},
});
registerSetupCommands(cli, logger, environment);
registerReverseEngineeringCommands(cli, logger, environment);
registerCutterCommands(cli, logger, environment);
registerCoreAnalysisCommands(cli, logger, analysis.runDirectAnalysis);
registerUtilityCommands(cli, logger, environment, analysis);
registerWasmCommands(cli, logger, environment);
registerCaptureCommands(cli, logger, environment);
registerSignalCommands(cli, logger, environment);
registerEvmCommands(cli, logger, environment);
registerDesktopApplicationCommands(cli, logger);
registerGoCommands(cli, logger);
registerIl2cppCommands(cli, logger);
registerModelCommands(cli, logger);
registerRustCommands(cli, logger);
registerSqliteCommands(cli, logger, environment);
registerArtifactCommands(cli, logger, analysis.runProviderAnalysis);
registerManagedCommands(cli, logger, analysis.runProviderAnalysis);
registerAndroidCommands(cli, logger, environment);
registerApktoolCommands(cli, logger, environment);
registerPcapCommands(cli, logger, environment);
registerFlutterCommands(cli, logger);
registerJebCommands(cli, logger, environment);
registerAdbCommands(cli, logger, environment);
registerFirmwareCommands(cli, logger, environment);
registerBinaryDiagnosticsCommands(cli, logger, environment);
registerAnalysisViewCommands(cli, logger);
registerNativeFunctionMatchingCommands(cli, logger);
registerFridaCommands(cli, logger, fridaInstrumentation);
registerEvidenceCommands(cli, logger);
registerEnvironmentCommands(cli, logger, environment);
registerProcessCommands(cli, logger, environment);
registerBrowserCommands(cli, logger);
registerWebScriptCommands(cli, logger);
registerWebModuleCommands(cli, logger, environment);
registerWebSourceLocationCommands(cli, logger, environment);
registerWebRuntimeCommands(cli, logger);
registerWebNetworkCaptureCommands(cli, logger, environment);
registerJavaScriptRecoveryCommands(cli, logger, environment);
registerAdvancedBrowserCommands(cli, logger);
registerBrowserScenarioCommands(cli, logger, environment);
registerElectronCommands(cli, logger, environment);
registerJavaScriptRuntimeObservationCommands(cli, logger);
registerApplicationCommands(cli, logger);
presentOmittableDefaults(cli);
if (resultOutput !== undefined) streamCliCommandResults(cli, resultOutput);
const serve = cli.serve.bind(cli);
cli.serve = (arguments_ = process.argv.slice(2), options) =>
serve(normalizeCliArguments(arguments_), options);
return cli;
};