mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
* fix(cli): advertise domain constraints for selector and path options Android class, method, and mapping fields, ADB setting keys, and browser endpoints used bare strings in --schema. They now reuse the domain fields, so empty selectors, relative mapping paths, and malformed setting keys fail in the advertised schema. * fix(cli): keep selector limits when argument parsing rejects them Empty browser and Electron selectors now fail in the CLI parser, so their diagnostics are VALIDATION_ERROR with the one-character minimum. ADB setting keys stay on the selector repair path, which restores maximum 256 and the identifier pattern. * test(cli): assert empty-selector codes without frozen prose Empty browser and Electron selectors still fail as VALIDATION_ERROR too_small on the named field. The tests no longer copy Zod's sentence. * test(cli): compare endpoint diagnostics with the domain schema Browser and Inspector tests take custom issue text from the schema that owns it, and empty capture comparison must name both missing inspection fields. * fix(cli): remove duplicate merged schema import --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
289 lines
10 KiB
TypeScript
289 lines
10 KiB
TypeScript
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
|
|
import { z } from "incur";
|
|
import { AndroidAnalysisService } from "../application/android/AndroidAnalysisService.js";
|
|
import { createAndroidAnalysisProvider } from "../composition/android.js";
|
|
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
|
import { logCliCommand } from "../cliLogging.js";
|
|
import type { AndroidOperation } from "../domain/android/androidAnalysis.js";
|
|
import type { Logger } from "pino";
|
|
import type { CliInstance } from "./types.js";
|
|
import { withCommandCancellation } from "./commandCancellation.js";
|
|
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
|
|
import { bindCliEvidenceCallerPath } from "./cliEvidenceCallerPath.js";
|
|
import {
|
|
analysisEvidenceDeliveryOptions,
|
|
deliverCliAnalysisEvidence,
|
|
parseCliAnalysisEvidenceDelivery,
|
|
} from "./analysisEvidenceDelivery.js";
|
|
import { androidPackageDeliverySchema } from "../domain/android/androidPackageQuery.js";
|
|
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
|
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
|
import { parseAnalysisInput } from "../application/AnalysisInput.js";
|
|
import { androidJniSelectionSchema } from "../domain/android/androidJniBindings.js";
|
|
import { androidJniDeliverySchema } from "../domain/android/androidJniQuery.js";
|
|
import { androidInputSchemas } from "../domain/android/androidAnalysis.js";
|
|
import { cliIntegerArgument } from "./options.js";
|
|
|
|
/** Expose the same explicit APK requests through one-shot CLI commands. */
|
|
export const registerAndroidCommands = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
environment: Readonly<Record<string, string | undefined>>,
|
|
): void => {
|
|
const provider = createAndroidAnalysisProvider(environment);
|
|
const service = new AndroidAnalysisService(provider);
|
|
const execute = (
|
|
name: string,
|
|
operation: AndroidOperation,
|
|
input: unknown,
|
|
callerPath: string,
|
|
delivery?: {
|
|
readonly detail: "complete" | "summary";
|
|
readonly evidence_output?: string | undefined;
|
|
},
|
|
) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(logger, name, () => {
|
|
const run = bindCliEvidenceCallerPath(callerPath, () =>
|
|
service.execute(operation, input, { signal }),
|
|
);
|
|
const cleanup = { close: () => provider.close() };
|
|
if (delivery !== undefined)
|
|
return deliverCliAnalysisEvidence({
|
|
run,
|
|
service: cleanup,
|
|
providerId: "jadx",
|
|
operation,
|
|
signal,
|
|
detail: delivery.detail,
|
|
evidenceOutput: delivery.evidence_output,
|
|
});
|
|
return finishAnalysisCommand(
|
|
run,
|
|
cleanup,
|
|
"jadx",
|
|
operation,
|
|
signal,
|
|
).then(projectCliAnalysisResult);
|
|
}),
|
|
);
|
|
const path = androidInputSchemas.inspect_android_package.shape.path;
|
|
const className = androidInputSchemas.inspect_android_class.shape.class_name;
|
|
const mappingPath =
|
|
androidInputSchemas.inspect_android_class.shape.mapping_path;
|
|
const mappingInput = (path: string | undefined) =>
|
|
path === undefined ? {} : { mapping_path: path };
|
|
cli.command(CLI_COMMANDS.inspectAndroidJniBindings, {
|
|
description:
|
|
"Link raw DEX native declarations to static JNI export/table candidates without executing the APK",
|
|
args: z.object({ path }),
|
|
options: z.object({
|
|
...analysisEvidenceDeliveryOptions,
|
|
query: z
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
"JSON selection: class_name, method_names, descriptors, abis, library_paths; arrays preserve exact names",
|
|
),
|
|
view: z
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
"JSON android-jni collection page; requires --detail summary and --evidence_output",
|
|
),
|
|
}),
|
|
run: ({ args, options }) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(
|
|
logger,
|
|
CLI_COMMANDS.inspectAndroidJniBindings,
|
|
async () => {
|
|
const operation = "inspect_android_jni_bindings";
|
|
const delivery = parseCliAnalysisEvidenceDelivery(
|
|
operation,
|
|
options,
|
|
androidJniDeliverySchema,
|
|
);
|
|
if (!delivery.ok) return projectAnalysisError(delivery.error);
|
|
let query: unknown = {};
|
|
try {
|
|
if (options.query !== undefined)
|
|
query = JSON.parse(options.query);
|
|
} catch (cause: unknown) {
|
|
return projectAnalysisError(
|
|
new AnalysisInputError(operation, { cause }, [
|
|
{
|
|
path: ["query"],
|
|
reason: "invalid_format",
|
|
message: "query must be a JSON selection object.",
|
|
},
|
|
]),
|
|
);
|
|
}
|
|
const selected = parseAnalysisInput(
|
|
operation,
|
|
query,
|
|
androidJniSelectionSchema,
|
|
signal,
|
|
);
|
|
if (!selected.ok) return projectAnalysisError(selected.error);
|
|
return deliverCliAnalysisEvidence({
|
|
callerInputPath: args.path,
|
|
run: () =>
|
|
service.execute(
|
|
operation,
|
|
{ ...selected.value, path: args.path },
|
|
{ signal },
|
|
),
|
|
service: { close: () => provider.close() },
|
|
providerId: "jadx",
|
|
operation,
|
|
signal,
|
|
detail: delivery.value.detail,
|
|
evidenceOutput: delivery.value.evidenceOutput,
|
|
...(delivery.value.view === undefined
|
|
? {}
|
|
: { view: delivery.value.view }),
|
|
});
|
|
},
|
|
),
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.inspectAndroidPackage, {
|
|
description:
|
|
"Inspect APK declarations and decoded manifest without executing it",
|
|
args: z.object({ path }),
|
|
options: z.object({
|
|
...analysisEvidenceDeliveryOptions,
|
|
view: z
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
"JSON android manifest/components/component-summary view; requires --detail summary and --evidence_output",
|
|
),
|
|
}),
|
|
run: ({ args, options }) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(logger, CLI_COMMANDS.inspectAndroidPackage, async () => {
|
|
const delivery = parseCliAnalysisEvidenceDelivery(
|
|
"inspect_android_package",
|
|
options,
|
|
androidPackageDeliverySchema,
|
|
);
|
|
if (!delivery.ok) return projectAnalysisError(delivery.error);
|
|
return deliverCliAnalysisEvidence({
|
|
callerInputPath: args.path,
|
|
run: () =>
|
|
service.execute("inspect_android_package", args, { signal }),
|
|
service: { close: () => provider.close() },
|
|
providerId: "jadx",
|
|
operation: "inspect_android_package",
|
|
signal,
|
|
detail: delivery.value.detail,
|
|
evidenceOutput: delivery.value.evidenceOutput,
|
|
...(delivery.value.view === undefined
|
|
? {}
|
|
: { view: delivery.value.view }),
|
|
});
|
|
}),
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.searchAndroidClasses, {
|
|
description: "Search APK class names; empty query inventories all classes",
|
|
args: z.object({
|
|
path,
|
|
query: androidInputSchemas.search_android_classes.shape.query,
|
|
}),
|
|
run: ({ args }) =>
|
|
execute(
|
|
CLI_COMMANDS.searchAndroidClasses,
|
|
"search_android_classes",
|
|
args,
|
|
args.path,
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.inspectAndroidClass, {
|
|
description: "Inspect one exact APK class's member inventory",
|
|
args: z.object({ path, className }),
|
|
options: z.object({ mappingPath, ...analysisEvidenceDeliveryOptions }),
|
|
run: ({ args, options }) =>
|
|
execute(
|
|
CLI_COMMANDS.inspectAndroidClass,
|
|
"inspect_android_class",
|
|
{
|
|
path: args.path,
|
|
class_name: args.className,
|
|
...mappingInput(options.mappingPath),
|
|
},
|
|
args.path,
|
|
options,
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.inspectAndroidMethod, {
|
|
description: "Decompile one APK method with explicit overload selection",
|
|
args: z.object({
|
|
path,
|
|
className,
|
|
methodName:
|
|
androidInputSchemas.inspect_android_method.shape.method_name.describe(
|
|
"Exact declared method name",
|
|
),
|
|
}),
|
|
options: z.object({
|
|
overloadIndex: cliIntegerArgument(0)
|
|
.optional()
|
|
.describe(
|
|
"Explicit zero-based index among same-name methods; required when ambiguous",
|
|
),
|
|
mappingPath,
|
|
...analysisEvidenceDeliveryOptions,
|
|
}),
|
|
run: ({ args, options }) =>
|
|
execute(
|
|
CLI_COMMANDS.inspectAndroidMethod,
|
|
"inspect_android_method",
|
|
{
|
|
path: args.path,
|
|
class_name: args.className,
|
|
method_name: args.methodName,
|
|
...mappingInput(options.mappingPath),
|
|
...(options.overloadIndex === undefined
|
|
? {}
|
|
: { overload_index: options.overloadIndex }),
|
|
},
|
|
args.path,
|
|
options,
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.traceAndroidReferences, {
|
|
description:
|
|
"Trace APK using nodes for a defined class/method or an externally invoked DEX method",
|
|
args: z.object({ path, className }),
|
|
options: z.object({
|
|
methodName:
|
|
androidInputSchemas.trace_android_references.shape.method_name,
|
|
overloadIndex: cliIntegerArgument(0)
|
|
.optional()
|
|
.describe(
|
|
"Index among defined methods or observed external prototypes listed by an ambiguous query",
|
|
),
|
|
}),
|
|
run: ({ args, options }) =>
|
|
execute(
|
|
CLI_COMMANDS.traceAndroidReferences,
|
|
"trace_android_references",
|
|
{
|
|
path: args.path,
|
|
class_name: args.className,
|
|
...(options.methodName === undefined
|
|
? {}
|
|
: { method_name: options.methodName }),
|
|
...(options.overloadIndex === undefined
|
|
? {}
|
|
: { overload_index: options.overloadIndex }),
|
|
},
|
|
args.path,
|
|
),
|
|
});
|
|
};
|