rea-agents 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/bridge/hopper_bridge.py +557 -0
  4. package/dist/application/AnalysisProvider.js +1 -0
  5. package/dist/application/BinarySession.js +173 -0
  6. package/dist/application/DirectAnalysis.js +31 -0
  7. package/dist/application/Doctor.js +110 -0
  8. package/dist/application/EnhancedTools.js +357 -0
  9. package/dist/application/EvidenceLedger.js +65 -0
  10. package/dist/application/HopperToolPort.js +1 -0
  11. package/dist/application/LoopbackReplay.js +84 -0
  12. package/dist/application/ProcessHarness.js +347 -0
  13. package/dist/application/Setup.js +240 -0
  14. package/dist/application/homebrew.js +19 -0
  15. package/dist/application/runtime.js +11 -0
  16. package/dist/cli.js +88 -0
  17. package/dist/config.js +86 -0
  18. package/dist/contracts/enhancedInputs.js +73 -0
  19. package/dist/contracts/toolContracts.js +144 -0
  20. package/dist/contracts/toolOutputSchemas.js +267 -0
  21. package/dist/domain/binaryTarget.js +321 -0
  22. package/dist/domain/errors.js +81 -0
  23. package/dist/domain/evidence.js +146 -0
  24. package/dist/domain/evidenceBundle.js +11 -0
  25. package/dist/domain/hopperValues.js +253 -0
  26. package/dist/domain/jsonValue.js +3 -0
  27. package/dist/domain/processCapture.js +252 -0
  28. package/dist/domain/result.js +4 -0
  29. package/dist/domain/symbolAnalysis.js +68 -0
  30. package/dist/hopper/BridgeLauncher.js +130 -0
  31. package/dist/hopper/HopperClient.js +314 -0
  32. package/dist/hopper/HopperProvider.js +66 -0
  33. package/dist/hopper/protocol.js +32 -0
  34. package/dist/identity.js +14 -0
  35. package/dist/logger.js +27 -0
  36. package/dist/main.js +81 -0
  37. package/dist/server/createServer.js +30 -0
  38. package/dist/server/registerEnhancedTools.js +53 -0
  39. package/dist/server/registerOfficialTools.js +59 -0
  40. package/dist/server/registerSessionTools.js +159 -0
  41. package/dist/server/toolLogging.js +11 -0
  42. package/dist/server/toolResult.js +44 -0
  43. package/package.json +109 -0
  44. package/scripts/rea.mjs +15 -0
  45. package/scripts/rebuild-native.mjs +22 -0
  46. package/skills/rea-analysis/SKILL.md +44 -0
@@ -0,0 +1,240 @@
1
+ import { access, copyFile, mkdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { execFile } from "node:child_process";
5
+ import { promisify } from "node:util";
6
+ import { z } from "zod";
7
+ import { PRODUCT_IDENTITY } from "../identity.js";
8
+ import { runDoctor, systemDoctorHost } from "./Doctor.js";
9
+ import { probeHomebrew } from "./homebrew.js";
10
+ const execFileAsync = promisify(execFile);
11
+ /**
12
+ * Install prerequisites and configure detected clients idempotently.
13
+ * With `yes` false, mutations requiring consent are reported as `needs_human`;
14
+ * with `yes` true, external installers still fail closed if they require UI.
15
+ */
16
+ export const runSetup = async (yes, host = systemSetupHost()) => {
17
+ const actions = [];
18
+ const clients = {};
19
+ const fail = async (remediation) => ({
20
+ status: "needs_human",
21
+ actions,
22
+ clients,
23
+ doctor: await host.doctor(),
24
+ remediation,
25
+ });
26
+ if (host.platform !== "darwin")
27
+ return fail("Hopper requires macOS 12 or newer.");
28
+ const macosVersion = await host.macosVersion();
29
+ if (macosVersion === undefined || major(macosVersion) < 12)
30
+ return fail("Upgrade to macOS 12 or newer.");
31
+ if (major(host.nodeVersion) < 22)
32
+ return fail("Install Node.js 22 or newer.");
33
+ if (!(await host.hasHomebrew())) {
34
+ if (!yes)
35
+ return fail("Re-run with --yes to install Homebrew.");
36
+ if (!(await host.installHomebrew()))
37
+ return fail("Homebrew installation was interrupted; resolve the installer prompt and re-run setup.");
38
+ actions.push("installed_homebrew");
39
+ }
40
+ let hopperPath = await host.hopperPath();
41
+ if (hopperPath === undefined) {
42
+ if (!yes)
43
+ return fail("Re-run with --yes to install Hopper.");
44
+ if (!(await host.installHopper()))
45
+ return fail("Hopper installation was interrupted; resolve the system prompt and re-run setup.");
46
+ actions.push("installed_hopper");
47
+ hopperPath = await host.hopperPath();
48
+ if (hopperPath === undefined)
49
+ return fail("Hopper installation completed but its launcher was not found.");
50
+ }
51
+ for (const client of await host.detectedClients()) {
52
+ const result = await host.configureClient(client, hopperPath);
53
+ clients[client.name] = result;
54
+ if (result.status === "failed")
55
+ return fail(`${client.name} configuration ${result.reason} verification failed; no successful configuration was reported.`);
56
+ if (result.status === "configured")
57
+ actions.push(`configured_${client.name}`);
58
+ }
59
+ const skill = await host.installSkill();
60
+ if (skill === "failed")
61
+ return fail("Agent skill installation or readback failed.");
62
+ if (skill === "installed")
63
+ actions.push("installed_skill");
64
+ const doctor = await host.doctor();
65
+ return {
66
+ status: doctor.healthy ? "ready" : "needs_human",
67
+ actions,
68
+ clients,
69
+ doctor,
70
+ ...(doctor.healthy
71
+ ? {}
72
+ : {
73
+ remediation: "Run rea doctor and apply each reported remediation.",
74
+ }),
75
+ };
76
+ };
77
+ /** Production setup effects for macOS, Homebrew, JSON MCP clients, and the canonical skill directory. */
78
+ const systemSetupHost = () => {
79
+ const doctorHost = systemDoctorHost();
80
+ return {
81
+ platform: process.platform,
82
+ nodeVersion: process.versions.node,
83
+ macosVersion: () => doctorHost.macosVersion(),
84
+ hasHomebrew: () => brewSucceeds(["--version"]),
85
+ installHomebrew: () => commandSucceeds("/bin/bash", [
86
+ "-c",
87
+ "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)",
88
+ ], { ...process.env, NONINTERACTIVE: "1" }),
89
+ hopperPath: async () => (await runDoctor(undefined, doctorHost)).hopperPath,
90
+ installHopper: () => brewSucceeds(["install", "--cask", "hopper-disassembler"]),
91
+ detectedClients: () => detectJsonClients(homedir()),
92
+ configureClient: configureJsonClient,
93
+ installSkill: () => installCanonicalSkill(homedir()),
94
+ doctor: () => runDoctor(undefined, doctorHost),
95
+ };
96
+ };
97
+ const detectJsonClients = async (home) => {
98
+ const candidates = [
99
+ {
100
+ name: "claude_desktop",
101
+ configPath: join(home, "Library/Application Support/Claude/claude_desktop_config.json"),
102
+ marker: join(home, "Library/Application Support/Claude"),
103
+ },
104
+ {
105
+ name: "cursor",
106
+ configPath: join(home, ".cursor/mcp.json"),
107
+ marker: join(home, ".cursor"),
108
+ },
109
+ ];
110
+ const detected = [];
111
+ for (const candidate of candidates)
112
+ if (await exists(candidate.marker))
113
+ detected.push({ name: candidate.name, configPath: candidate.configPath });
114
+ return detected;
115
+ };
116
+ /** Back up, atomically update, and semantically read back one JSON MCP configuration. */
117
+ export const configureJsonClient = async (client, hopperPath) => {
118
+ let document = {};
119
+ let original;
120
+ try {
121
+ original = await readFile(client.configPath, "utf8");
122
+ document = parseObject(original);
123
+ }
124
+ catch (cause) {
125
+ if (!isMissing(cause))
126
+ return { status: "failed", reason: "readback" };
127
+ }
128
+ let servers;
129
+ try {
130
+ servers = parseOptionalObject(document.mcpServers);
131
+ }
132
+ catch {
133
+ return { status: "failed", reason: "readback" };
134
+ }
135
+ const desired = {
136
+ command: "npx",
137
+ args: ["-y", PRODUCT_IDENTITY.packageName, "mcp"],
138
+ ...(hopperPath === undefined
139
+ ? {}
140
+ : { env: { HOPPER_LAUNCHER_PATH: hopperPath } }),
141
+ };
142
+ if (JSON.stringify(servers[PRODUCT_IDENTITY.mcpServerKey]) ===
143
+ JSON.stringify(desired))
144
+ return { status: "unchanged" };
145
+ let backupPath;
146
+ if (original !== undefined) {
147
+ backupPath = `${client.configPath}.rea.backup`;
148
+ try {
149
+ await copyFile(client.configPath, backupPath);
150
+ }
151
+ catch {
152
+ return { status: "failed", reason: "backup" };
153
+ }
154
+ }
155
+ document.mcpServers = {
156
+ ...servers,
157
+ [PRODUCT_IDENTITY.mcpServerKey]: desired,
158
+ };
159
+ const encoded = `${JSON.stringify(document, null, 2)}\n`;
160
+ const temporary = `${client.configPath}.rea.tmp`;
161
+ try {
162
+ await mkdir(dirname(client.configPath), { recursive: true });
163
+ await writeFile(temporary, encoded, { encoding: "utf8", mode: 0o600 });
164
+ await rename(temporary, client.configPath);
165
+ }
166
+ catch {
167
+ await rm(temporary, { force: true }).catch(() => undefined);
168
+ return { status: "failed", reason: "write" };
169
+ }
170
+ try {
171
+ const readback = parseObject(await readFile(client.configPath, "utf8"));
172
+ const value = parseOptionalObject(readback.mcpServers)[PRODUCT_IDENTITY.mcpServerKey];
173
+ if (JSON.stringify(value) !== JSON.stringify(desired)) {
174
+ await restoreConfig(client.configPath, original);
175
+ return { status: "failed", reason: "readback" };
176
+ }
177
+ }
178
+ catch {
179
+ await restoreConfig(client.configPath, original);
180
+ return { status: "failed", reason: "readback" };
181
+ }
182
+ return {
183
+ status: "configured",
184
+ ...(backupPath === undefined ? {} : { backupPath }),
185
+ };
186
+ };
187
+ const installCanonicalSkill = async (home) => {
188
+ const destination = join(home, ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md");
189
+ try {
190
+ const content = await readFile(new URL(`../../skills/${PRODUCT_IDENTITY.skillName}/SKILL.md`, import.meta.url), "utf8");
191
+ if ((await readFile(destination, "utf8").catch(() => undefined)) === content)
192
+ return "unchanged";
193
+ await mkdir(dirname(destination), { recursive: true });
194
+ await writeFile(destination, content, { encoding: "utf8", mode: 0o600 });
195
+ return (await readFile(destination, "utf8")) === content
196
+ ? "installed"
197
+ : "failed";
198
+ }
199
+ catch {
200
+ return "failed";
201
+ }
202
+ };
203
+ const commandSucceeds = async (command, args, env) => {
204
+ try {
205
+ await execFileAsync(command, args, env === undefined ? {} : { env });
206
+ return true;
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ };
212
+ const brewSucceeds = async (args) => {
213
+ const result = await probeHomebrew(async (command) => (await commandSucceeds(command, args)) ? true : undefined);
214
+ return result === true;
215
+ };
216
+ const major = (version) => Number.parseInt(version.split(".")[0] ?? "0", 10);
217
+ const exists = async (path) => {
218
+ try {
219
+ await access(path);
220
+ return true;
221
+ }
222
+ catch {
223
+ return false;
224
+ }
225
+ };
226
+ const objectSchema = z.record(z.string(), z.unknown());
227
+ const parseOptionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
228
+ const parseObject = (text) => objectSchema.parse(JSON.parse(text));
229
+ const isMissing = (cause) => cause instanceof Error && "code" in cause && cause.code === "ENOENT";
230
+ const restoreConfig = async (path, original) => {
231
+ try {
232
+ if (original === undefined)
233
+ await rm(path, { force: true });
234
+ else
235
+ await writeFile(path, original, { encoding: "utf8", mode: 0o600 });
236
+ }
237
+ catch {
238
+ // The backup remains available for the remediation reported by setup.
239
+ }
240
+ };
@@ -0,0 +1,19 @@
1
+ /** Standard executable names and locations used to discover Homebrew. */
2
+ const HOMEBREW_COMMANDS = [
3
+ "brew",
4
+ "/opt/homebrew/bin/brew",
5
+ "/usr/local/bin/brew",
6
+ ];
7
+ /**
8
+ * Return the first defined result from standard Homebrew executables.
9
+ * A probe returning `undefined` means that location was unavailable or failed;
10
+ * other falsy values are successful results and stop discovery.
11
+ */
12
+ export const probeHomebrew = async (probe) => {
13
+ for (const command of HOMEBREW_COMMANDS) {
14
+ const result = await probe(command);
15
+ if (result !== undefined)
16
+ return result;
17
+ }
18
+ return undefined;
19
+ };
@@ -0,0 +1,11 @@
1
+ import { BinarySession } from "./BinarySession.js";
2
+ import { HopperProvider } from "../hopper/HopperProvider.js";
3
+ import { silentLogger } from "../logger.js";
4
+ /**
5
+ * Compose the target-switching runtime shared directly by CLI and MCP adapters.
6
+ * This is the sole production wiring point for Hopper so neither adapter shells
7
+ * out to the other and both retain identical loader and bridge semantics.
8
+ */
9
+ export const createBinarySession = (config, logger = silentLogger) => {
10
+ return new BinarySession(new HopperProvider(config, logger));
11
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,88 @@
1
+ import { Cli, z } from "incur";
2
+ import { fileURLToPath } from "node:url";
3
+ import { runDoctor } from "./application/Doctor.js";
4
+ import { runDirectAnalysis } from "./application/DirectAnalysis.js";
5
+ import { runSetup } from "./application/Setup.js";
6
+ import { PRODUCT_IDENTITY } from "./identity.js";
7
+ import { createLogger, parseLogLevel } from "./logger.js";
8
+ /**
9
+ * Build the one-shot Incur CLI without starting Hopper at import time.
10
+ * Analysis commands acquire and close their own sessions; bare `mcp` and
11
+ * `--mcp` are intercepted by the executable dispatcher before this module loads.
12
+ */
13
+ export const createCli = () => {
14
+ const logger = createLogger("cli", process.env.REA_LOG_LEVEL === undefined
15
+ ? "silent"
16
+ : parseLogLevel(process.env.REA_LOG_LEVEL));
17
+ const cli = Cli.create(PRODUCT_IDENTITY.cliBinary, {
18
+ version: "0.1.0",
19
+ description: "Reverse engineer apps from your terminal or coding agent.",
20
+ mcp: {
21
+ command: PRODUCT_IDENTITY.mcpCommand,
22
+ instructions: "Ask which app and feature the user wants to understand, then open the app and investigate it.",
23
+ },
24
+ sync: {
25
+ cwd: fileURLToPath(new URL("..", import.meta.url)),
26
+ include: ["skills/*"],
27
+ suggestions: [
28
+ "understand how an app feature works",
29
+ "decompile part of an app",
30
+ "check my REA setup",
31
+ ],
32
+ },
33
+ });
34
+ cli.command("setup", {
35
+ description: "Install requirements and configure coding agents",
36
+ options: z.object({
37
+ yes: z
38
+ .boolean()
39
+ .default(false)
40
+ .describe("Approve prerequisite installation"),
41
+ }),
42
+ alias: { yes: "y" },
43
+ run: ({ options }) => logCliCommand(logger, "setup", () => runSetup(options.yes)),
44
+ });
45
+ cli.command("doctor", {
46
+ description: "Check whether REA is ready",
47
+ options: z.object({
48
+ target: z.string().optional().describe("Optional app path to check"),
49
+ }),
50
+ run: ({ options }) => logCliCommand(logger, "doctor", () => runDoctor(options.target)),
51
+ });
52
+ cli.command("analyze", {
53
+ description: "Get an overview of an app",
54
+ args: z.object({
55
+ path: z.string().describe("App, program, or Hopper database path"),
56
+ }),
57
+ run: ({ args }) => logCliCommand(logger, "analyze", () => runDirectAnalysis(args.path, "binary_overview", {}, logger)),
58
+ });
59
+ cli.command("decompile", {
60
+ description: "Read one part of an app as code",
61
+ args: z.object({
62
+ path: z.string().describe("App or program path"),
63
+ address: z.string().describe("Procedure address"),
64
+ }),
65
+ run: ({ args }) => logCliCommand(logger, "decompile", () => runDirectAnalysis(args.path, "procedure_pseudo_code", { procedure: args.address }, logger)),
66
+ });
67
+ return cli;
68
+ };
69
+ const logCliCommand = async (logger, command, execute) => {
70
+ const startedAt = performance.now();
71
+ try {
72
+ const value = await execute();
73
+ logger.info({
74
+ command,
75
+ durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
76
+ status: "ok",
77
+ }, "CLI command completed");
78
+ return value;
79
+ }
80
+ catch (cause) {
81
+ logger.error({
82
+ command,
83
+ durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
84
+ status: "error",
85
+ }, "CLI command failed");
86
+ throw cause;
87
+ }
88
+ };
package/dist/config.js ADDED
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+ import { ConfigurationError } from "./domain/errors.js";
3
+ import { err, ok } from "./domain/result.js";
4
+ const DEFAULT_HOPPER_LAUNCHER_PATH = "/Applications/Hopper Disassembler.app/Contents/MacOS/hopper";
5
+ const environmentSchema = z.object({
6
+ HOPPER_LAUNCHER_PATH: z.string().min(1).optional(),
7
+ HOPPER_TARGET_PATH: z.string().min(1).optional(),
8
+ HOPPER_TARGET_KIND: z.enum(["executable", "database"]).default("executable"),
9
+ HOPPER_LOADER_ARGS_JSON: z.string().optional(),
10
+ REA_LOG_LEVEL: z
11
+ .enum(["trace", "debug", "info", "warn", "error", "fatal", "silent"])
12
+ .default("info"),
13
+ REA_PROCESS_CAPTURE_ENABLED: z.enum(["true", "false"]).default("false"),
14
+ REA_PROCESS_EXECUTABLE_ROOTS_JSON: z.string().default("[]"),
15
+ REA_PROCESS_WORKING_ROOTS_JSON: z.string().default("[]"),
16
+ REA_PROCESS_ALLOWED_ENV_JSON: z.string().default("[]"),
17
+ });
18
+ const parseStringArray = (encoded, name) => {
19
+ try {
20
+ const parsed = z
21
+ .array(z.string().min(1))
22
+ .max(128)
23
+ .safeParse(JSON.parse(encoded));
24
+ return parsed.success
25
+ ? ok(parsed.data)
26
+ : err(new ConfigurationError(`${name} must encode an array of strings`, {
27
+ cause: parsed.error,
28
+ }));
29
+ }
30
+ catch (cause) {
31
+ return err(new ConfigurationError(`${name} must be valid JSON`, { cause }));
32
+ }
33
+ };
34
+ /**
35
+ * Parse Hopper launcher configuration once at the composition root.
36
+ * Explicit loader arguments override REA's header-derived defaults for a
37
+ * supported target, allowing callers to refine Hopper's loader behavior without
38
+ * bypassing REA's path and executable-header checks.
39
+ */
40
+ export const parseConfig = (environment) => {
41
+ const parsedEnvironment = environmentSchema.safeParse(environment);
42
+ if (!parsedEnvironment.success) {
43
+ return err(new ConfigurationError("Invalid Hopper environment configuration", {
44
+ cause: parsedEnvironment.error,
45
+ }));
46
+ }
47
+ let loaderArgs = [];
48
+ const encoded = parsedEnvironment.data.HOPPER_LOADER_ARGS_JSON;
49
+ if (encoded !== undefined) {
50
+ try {
51
+ loaderArgs = JSON.parse(encoded);
52
+ }
53
+ catch (cause) {
54
+ return err(new ConfigurationError("HOPPER_LOADER_ARGS_JSON must be valid JSON", {
55
+ cause,
56
+ }));
57
+ }
58
+ }
59
+ const parsedArgs = z.array(z.string()).safeParse(loaderArgs);
60
+ if (!parsedArgs.success) {
61
+ return err(new ConfigurationError("HOPPER_LOADER_ARGS_JSON must encode an array of strings", { cause: parsedArgs.error }));
62
+ }
63
+ const executableRoots = parseStringArray(parsedEnvironment.data.REA_PROCESS_EXECUTABLE_ROOTS_JSON, "REA_PROCESS_EXECUTABLE_ROOTS_JSON");
64
+ if (!executableRoots.ok)
65
+ return executableRoots;
66
+ const workingRoots = parseStringArray(parsedEnvironment.data.REA_PROCESS_WORKING_ROOTS_JSON, "REA_PROCESS_WORKING_ROOTS_JSON");
67
+ if (!workingRoots.ok)
68
+ return workingRoots;
69
+ const allowedEnvironment = parseStringArray(parsedEnvironment.data.REA_PROCESS_ALLOWED_ENV_JSON, "REA_PROCESS_ALLOWED_ENV_JSON");
70
+ if (!allowedEnvironment.ok)
71
+ return allowedEnvironment;
72
+ return ok({
73
+ hopperLauncherPath: parsedEnvironment.data.HOPPER_LAUNCHER_PATH ??
74
+ DEFAULT_HOPPER_LAUNCHER_PATH,
75
+ hopperTargetPath: parsedEnvironment.data.HOPPER_TARGET_PATH,
76
+ hopperTargetKind: parsedEnvironment.data.HOPPER_TARGET_KIND,
77
+ hopperLoaderArgs: parsedArgs.data,
78
+ logLevel: parsedEnvironment.data.REA_LOG_LEVEL,
79
+ processExecutionPolicy: {
80
+ enabled: parsedEnvironment.data.REA_PROCESS_CAPTURE_ENABLED === "true",
81
+ executableRoots: executableRoots.value,
82
+ workingRoots: workingRoots.value,
83
+ allowedEnvironment: allowedEnvironment.value,
84
+ },
85
+ });
86
+ };
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ /** Input schemas shared by MCP registration and enhanced application dispatch. */
3
+ export const enhancedInputSchemas = {
4
+ swift_classes: z.object({ pattern: z.string().default("") }),
5
+ get_objc_classes: z.object({ pattern: z.string().default("") }),
6
+ get_objc_protocols: z.object({}),
7
+ batch_decompile: z.object({
8
+ addresses: z
9
+ .array(z.string().describe("A Hopper address"))
10
+ .max(20)
11
+ .default([]),
12
+ }),
13
+ get_call_graph: z.object({
14
+ address: z.string().describe("A Hopper address"),
15
+ direction: z.enum(["forward", "backward"]).default("forward"),
16
+ depth: z.number().int().min(1).max(5).default(2),
17
+ }),
18
+ analyze_swift_types: z.object({}),
19
+ find_xrefs_to_name: z.object({ name: z.string() }),
20
+ binary_overview: z.object({
21
+ detail: z.enum(["concise", "detailed"]).default("concise"),
22
+ limit: z.number().int().min(1).max(50).default(10),
23
+ }),
24
+ analyze_function: z.object({
25
+ procedure: z.string().describe("A procedure name or address"),
26
+ include_assembly: z.boolean().default(false),
27
+ limit: z.number().int().min(1).max(500).default(100),
28
+ max_pseudocode_chars: z.number().int().min(1).max(100_000).default(20_000),
29
+ max_instructions: z.number().int().min(1).max(5_000).default(500),
30
+ pseudocode_offset: z.number().int().min(0).default(0),
31
+ assembly_offset: z.number().int().min(0).default(0),
32
+ collection_offset: z
33
+ .object({
34
+ comments: z.number().int().min(0).default(0),
35
+ callers: z.number().int().min(0).default(0),
36
+ callees: z.number().int().min(0).default(0),
37
+ incoming_references: z.number().int().min(0).default(0),
38
+ outgoing_references: z.number().int().min(0).default(0),
39
+ referenced_strings: z.number().int().min(0).default(0),
40
+ referenced_names: z.number().int().min(0).default(0),
41
+ basic_blocks: z.number().int().min(0).default(0),
42
+ })
43
+ .default({
44
+ comments: 0,
45
+ callers: 0,
46
+ callees: 0,
47
+ incoming_references: 0,
48
+ outgoing_references: 0,
49
+ referenced_strings: 0,
50
+ referenced_names: 0,
51
+ basic_blocks: 0,
52
+ }),
53
+ }),
54
+ trace_feature: z.object({
55
+ query: z.string().min(1),
56
+ case_sensitive: z.boolean().default(false),
57
+ limit: z.number().int().min(1).max(100).default(20),
58
+ max_operations: z.number().int().min(1).max(100).default(20),
59
+ }),
60
+ };
61
+ /** Runtime parser for dispatching only the eight closed enhanced names. */
62
+ export const enhancedToolNameSchema = z.enum([
63
+ "swift_classes",
64
+ "get_objc_classes",
65
+ "get_objc_protocols",
66
+ "batch_decompile",
67
+ "get_call_graph",
68
+ "analyze_swift_types",
69
+ "find_xrefs_to_name",
70
+ "binary_overview",
71
+ "analyze_function",
72
+ "trace_feature",
73
+ ]);