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,30 @@
1
+ import { McpServer } from "@modelcontextprotocol/server";
2
+ import { PRODUCT_IDENTITY } from "../identity.js";
3
+ import { registerEnhancedTools } from "./registerEnhancedTools.js";
4
+ import { registerOfficialTools } from "./registerOfficialTools.js";
5
+ import { registerSessionTools } from "./registerSessionTools.js";
6
+ import { silentLogger } from "../logger.js";
7
+ /**
8
+ * Construct one MCP server without acquiring subprocess resources.
9
+ * Supplying a session adds target lifecycle tools; omitting it retains the
10
+ * fixed-target seam used by focused tests and embedders.
11
+ */
12
+ export const createServer = (analysis, session, logger = silentLogger, processPolicy) => {
13
+ const server = new McpServer({ name: PRODUCT_IDENTITY.mcpServerKey, version: "0.1.0" }, {
14
+ capabilities: { tools: {} },
15
+ instructions: session === undefined
16
+ ? "Reverse-engineering tools for an active analysis target. Start with binary_overview."
17
+ : "Reverse-engineering tools for configured analysis providers. Open a target with open_binary, then start with binary_overview.",
18
+ });
19
+ const toolLogger = logger.child({ layer: "server" });
20
+ const activeTarget = session === undefined ? undefined : () => session.activeTarget();
21
+ const provider = session?.providerIdentity();
22
+ const recordEvidence = session === undefined
23
+ ? undefined
24
+ : (evidence) => session.recordEvidence(evidence);
25
+ registerOfficialTools(server, analysis, toolLogger, activeTarget, provider, recordEvidence);
26
+ registerEnhancedTools(server, analysis, toolLogger, activeTarget, provider, recordEvidence);
27
+ if (session !== undefined)
28
+ registerSessionTools(server, session, toolLogger, processPolicy);
29
+ return server;
30
+ };
@@ -0,0 +1,53 @@
1
+ import { EnhancedTools } from "../application/EnhancedTools.js";
2
+ import { enhancedToolNameSchema } from "../contracts/enhancedInputs.js";
3
+ import { ENHANCED_TOOL_CONTRACTS, } from "../contracts/toolContracts.js";
4
+ import { toCallToolResult } from "./toolResult.js";
5
+ import { logToolExecution } from "./toolLogging.js";
6
+ import { createEvidence } from "../domain/evidence.js";
7
+ import { jsonValueSchema } from "../domain/jsonValue.js";
8
+ import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
9
+ /** Register composed workflows against the same port as direct bridge tools. */
10
+ export const registerEnhancedTools = (server, analysis, logger, activeTarget, provider = {
11
+ id: "unidentified",
12
+ name: "Unidentified provider",
13
+ version: null,
14
+ }, recordEvidence) => {
15
+ const services = new EnhancedTools(analysis);
16
+ for (const contract of ENHANCED_TOOL_CONTRACTS) {
17
+ registerEnhancedTool(server, services, contract, {
18
+ logger,
19
+ activeTarget,
20
+ provider,
21
+ recordEvidence,
22
+ });
23
+ }
24
+ };
25
+ const registerEnhancedTool = (server, services, contract, registration) => {
26
+ const name = enhancedToolNameSchema.parse(contract.name);
27
+ server.registerTool(name, {
28
+ description: contract.description,
29
+ inputSchema: contract.inputSchema,
30
+ outputSchema: contract.outputSchema,
31
+ annotations: contract.annotations,
32
+ }, async (input, context) => {
33
+ const parsedInput = jsonValueSchema.parse(enhancedInputSchemas[name].parse(input));
34
+ if (typeof parsedInput !== "object" ||
35
+ parsedInput === null ||
36
+ Array.isArray(parsedInput))
37
+ throw new Error("Validated enhanced input was not a JSON object");
38
+ const parameters = { ...parsedInput };
39
+ const result = await logToolExecution(registration.logger, name, () => services.execute(name, input, context.mcpReq.signal));
40
+ if (result.ok) {
41
+ const evidence = createEvidence(registration.activeTarget?.(), registration.provider, {
42
+ operation: name,
43
+ parameters,
44
+ result: result.value,
45
+ confidence: "derived",
46
+ redactedRawPayload: result.value,
47
+ });
48
+ registration.recordEvidence?.(evidence);
49
+ return toCallToolResult({ ok: true, value: evidence }, contract);
50
+ }
51
+ return toCallToolResult(result, contract);
52
+ });
53
+ };
@@ -0,0 +1,59 @@
1
+ import { OFFICIAL_TOOL_CONTRACTS, } from "../contracts/toolContracts.js";
2
+ import { jsonValueSchema } from "../domain/jsonValue.js";
3
+ import { toCallToolResult } from "./toolResult.js";
4
+ import { logToolExecution } from "./toolLogging.js";
5
+ import { createEvidence } from "../domain/evidence.js";
6
+ /** Register direct bridge proxies, preserving MCP cancellation and typed errors. */
7
+ export const registerOfficialTools = (server, analysis, logger, activeTarget, provider = {
8
+ id: "unidentified",
9
+ name: "Unidentified provider",
10
+ version: null,
11
+ }, recordEvidence) => {
12
+ for (const contract of OFFICIAL_TOOL_CONTRACTS) {
13
+ registerOfficialTool(server, analysis, contract, {
14
+ logger,
15
+ activeTarget,
16
+ provider,
17
+ recordEvidence,
18
+ });
19
+ }
20
+ };
21
+ const registerOfficialTool = (server, analysis, contract, registration) => {
22
+ server.registerTool(contract.name, {
23
+ description: contract.description,
24
+ inputSchema: contract.inputSchema,
25
+ outputSchema: contract.outputSchema,
26
+ annotations: contract.annotations,
27
+ }, async (input, context) => {
28
+ const arguments_ = projectOfficialArguments(contract, input);
29
+ const result = await logToolExecution(registration.logger, contract.name, () => analysis.execute(contract.name, arguments_, {
30
+ signal: context.mcpReq.signal,
31
+ }));
32
+ if (result.ok) {
33
+ const evidence = createEvidence(registration.activeTarget?.(), registration.provider, {
34
+ operation: contract.name,
35
+ parameters: arguments_,
36
+ result: result.value,
37
+ redactedRawPayload: result.value,
38
+ });
39
+ registration.recordEvidence?.(evidence);
40
+ return toCallToolResult({ ok: true, value: evidence }, contract);
41
+ }
42
+ return toCallToolResult(result, contract);
43
+ });
44
+ };
45
+ const projectOfficialArguments = (contract, input) => {
46
+ const parsed = jsonValueSchema.safeParse(input);
47
+ if (!parsed.success ||
48
+ typeof parsed.data !== "object" ||
49
+ parsed.data === null ||
50
+ Array.isArray(parsed.data)) {
51
+ // The SDK validates this with the same schema before invoking the callback.
52
+ throw new Error("Validated MCP tool input was not a JSON object");
53
+ }
54
+ const projected = {};
55
+ for (const key of Object.keys(contract.inputSchema.shape)) {
56
+ projected[key] = parsed.data[key] ?? null;
57
+ }
58
+ return projected;
59
+ };
@@ -0,0 +1,159 @@
1
+ import { z } from "zod";
2
+ import { SESSION_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
3
+ import { toCallToolResult } from "./toolResult.js";
4
+ import { logToolExecution } from "./toolLogging.js";
5
+ import { err, ok } from "../domain/result.js";
6
+ import { EvidenceLedgerError } from "../domain/errors.js";
7
+ import { compareProcessCaptures, processCaptureSchema, processScenarioSchema, } from "../domain/processCapture.js";
8
+ import { captureProcessScenario } from "../application/ProcessHarness.js";
9
+ import { createEvidence } from "../domain/evidence.js";
10
+ import { jsonValueSchema } from "../domain/jsonValue.js";
11
+ const DENY_PROCESS_POLICY = {
12
+ enabled: false,
13
+ executableRoots: [],
14
+ workingRoots: [],
15
+ allowedEnvironment: [],
16
+ };
17
+ const PROCESS_PROVIDER = {
18
+ id: "rea-process",
19
+ name: "REA deterministic process harness",
20
+ version: "1",
21
+ };
22
+ const registerProcessTools = ({ server, session, logger, processPolicy, captureContract, compareContract, }) => {
23
+ server.registerTool(captureContract.name, {
24
+ description: captureContract.description,
25
+ inputSchema: captureContract.inputSchema,
26
+ outputSchema: captureContract.outputSchema,
27
+ annotations: captureContract.annotations,
28
+ }, async (input, context) => {
29
+ const scenario = processScenarioSchema.parse(input);
30
+ const captured = await logToolExecution(logger, captureContract.name, () => captureProcessScenario(scenario, processPolicy, context.mcpReq.signal));
31
+ if (!captured.ok)
32
+ return toCallToolResult(captured, captureContract);
33
+ const evidence = createEvidence(undefined, PROCESS_PROVIDER, {
34
+ predicateType: "rea.process-capture/v1",
35
+ operation: captureContract.name,
36
+ parameters: {
37
+ executable_name: scenario.executable.split("/").at(-1) ?? scenario.executable,
38
+ argument_count: scenario.arguments.length,
39
+ event_count: scenario.events.length,
40
+ filesystem_root_count: scenario.filesystem_roots.length,
41
+ },
42
+ result: jsonValueSchema.parse(captured.value),
43
+ confidence: "observed",
44
+ authority: "controlled-replay",
45
+ environment: {
46
+ id: `${process.platform}-${process.arch}`,
47
+ platform: process.platform,
48
+ architecture: process.arch,
49
+ isolation: "process",
50
+ },
51
+ limitations: captured.value.limitations,
52
+ });
53
+ session.recordEvidence(evidence);
54
+ return toCallToolResult(ok(evidence), captureContract);
55
+ });
56
+ server.registerTool(compareContract.name, {
57
+ description: compareContract.description,
58
+ inputSchema: compareContract.inputSchema,
59
+ outputSchema: compareContract.outputSchema,
60
+ annotations: compareContract.annotations,
61
+ }, (input) => {
62
+ const parsed = z
63
+ .object({
64
+ left_evidence_id: z.string(),
65
+ left: processCaptureSchema,
66
+ right_evidence_id: z.string(),
67
+ right: processCaptureSchema,
68
+ })
69
+ .parse(input);
70
+ const comparison = compareProcessCaptures(parsed.left, parsed.right);
71
+ const evidence = createEvidence(undefined, PROCESS_PROVIDER, {
72
+ predicateType: "rea.process-comparison/v1",
73
+ operation: compareContract.name,
74
+ parameters: {},
75
+ result: jsonValueSchema.parse(comparison),
76
+ confidence: "derived",
77
+ authority: "analyst-inference",
78
+ limitations: comparison.limitations,
79
+ evidenceLinks: [parsed.left_evidence_id, parsed.right_evidence_id],
80
+ });
81
+ session.recordEvidence(evidence);
82
+ return toCallToolResult(ok(evidence), compareContract);
83
+ });
84
+ };
85
+ const registerEvidenceTools = ({ server, session, exportContract, importContract, }) => {
86
+ server.registerTool(exportContract.name, {
87
+ description: exportContract.description,
88
+ inputSchema: exportContract.inputSchema,
89
+ outputSchema: exportContract.outputSchema,
90
+ annotations: exportContract.annotations,
91
+ }, () => toCallToolResult(ok(session.exportEvidenceBundle()), exportContract));
92
+ server.registerTool(importContract.name, {
93
+ description: importContract.description,
94
+ inputSchema: importContract.inputSchema,
95
+ outputSchema: importContract.outputSchema,
96
+ annotations: importContract.annotations,
97
+ }, (input) => {
98
+ try {
99
+ const bundle = z.object({ bundle: z.unknown() }).parse(input).bundle;
100
+ const imported = session.importEvidenceBundle(bundle);
101
+ return toCallToolResult(ok({
102
+ imported,
103
+ total: session.exportEvidenceBundle().records.length,
104
+ }), importContract);
105
+ }
106
+ catch (cause) {
107
+ return toCallToolResult(err(new EvidenceLedgerError("Evidence bundle validation failed", {
108
+ cause,
109
+ })), importContract);
110
+ }
111
+ });
112
+ };
113
+ /** Register MCP-only target lifecycle operations on a long-lived session. */
114
+ export const registerSessionTools = (server, session, logger, processPolicy = DENY_PROCESS_POLICY) => {
115
+ const [openContract, closeContract, statusContract, exportContract, importContract, captureContract, compareContract,] = SESSION_TOOL_CONTRACTS;
116
+ server.registerTool(openContract.name, {
117
+ description: openContract.description,
118
+ inputSchema: openContract.inputSchema,
119
+ outputSchema: openContract.outputSchema,
120
+ annotations: openContract.annotations,
121
+ }, async (input, context) => {
122
+ const parsed = z.object({ path: z.string().min(1) }).parse(input);
123
+ const opened = await logToolExecution(logger, openContract.name, () => session.open(parsed.path, { signal: context.mcpReq.signal }));
124
+ return opened.ok
125
+ ? toCallToolResult({
126
+ ok: true,
127
+ value: {
128
+ path: opened.value.path,
129
+ format: opened.value.format,
130
+ kind: opened.value.kind,
131
+ loaderArgs: [...opened.value.loaderArgs],
132
+ sha256: opened.value.sha256,
133
+ architecture: opened.value.architecture ?? null,
134
+ },
135
+ }, openContract)
136
+ : toCallToolResult(opened, openContract);
137
+ });
138
+ server.registerTool(closeContract.name, {
139
+ description: closeContract.description,
140
+ inputSchema: closeContract.inputSchema,
141
+ outputSchema: closeContract.outputSchema,
142
+ annotations: closeContract.annotations,
143
+ }, async () => toCallToolResult(await logToolExecution(logger, closeContract.name, () => session.close()), closeContract));
144
+ server.registerTool(statusContract.name, {
145
+ description: statusContract.description,
146
+ inputSchema: statusContract.inputSchema,
147
+ outputSchema: statusContract.outputSchema,
148
+ annotations: statusContract.annotations,
149
+ }, () => toCallToolResult({ ok: true, value: session.status() }, statusContract));
150
+ registerEvidenceTools({ server, session, exportContract, importContract });
151
+ registerProcessTools({
152
+ server,
153
+ session,
154
+ logger,
155
+ processPolicy,
156
+ captureContract,
157
+ compareContract,
158
+ });
159
+ };
@@ -0,0 +1,11 @@
1
+ /** Log one MCP tool's duration and status without recording caller input. */
2
+ export const logToolExecution = async (logger, tool, execute) => {
3
+ const startedAt = performance.now();
4
+ const result = await execute();
5
+ logger[result.ok ? "info" : "warn"]({
6
+ tool,
7
+ durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
8
+ status: result.ok ? "ok" : "error",
9
+ }, "MCP tool execution completed");
10
+ return result;
11
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Translate an application result into MCP text content.
3
+ * Error tags and safe messages remain visible while underlying causes, process
4
+ * output, and other potentially sensitive details stay private.
5
+ */
6
+ export const toCallToolResult = (result, contract) => result.ok
7
+ ? successResult(result.value, contract)
8
+ : {
9
+ content: [
10
+ {
11
+ type: "text",
12
+ text: `${result.error._tag}: ${result.error.message}`,
13
+ },
14
+ ],
15
+ isError: true,
16
+ };
17
+ const successResult = (value, contract) => {
18
+ const candidate = contract.kind === "session" ? { result: value } : value;
19
+ const parsed = contract.outputSchema.safeParse(candidate);
20
+ if (!parsed.success) {
21
+ return {
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: "HopperProtocolError: Analysis output does not match the tool contract",
26
+ },
27
+ ],
28
+ isError: true,
29
+ };
30
+ }
31
+ return {
32
+ content: [{ type: "text", text: formatValue(value) }],
33
+ structuredContent: candidate,
34
+ };
35
+ };
36
+ const formatValue = (value) => {
37
+ if (value === null)
38
+ return "OK";
39
+ if (typeof value === "string")
40
+ return value;
41
+ if (typeof value === "object")
42
+ return JSON.stringify(value, null, 2);
43
+ return String(value);
44
+ };
package/package.json ADDED
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "rea-agents",
3
+ "version": "0.2.1",
4
+ "description": "Reverse engineering CLI and MCP server for coding agents to understand apps and build features.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/morluto/rea.git"
9
+ },
10
+ "homepage": "https://github.com/morluto/rea#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/morluto/rea/issues"
13
+ },
14
+ "keywords": [
15
+ "reverse-engineering",
16
+ "binary-analysis",
17
+ "coding-agents",
18
+ "agent-skills",
19
+ "cli",
20
+ "mcp-server",
21
+ "model-context-protocol",
22
+ "static-analysis",
23
+ "disassembler",
24
+ "decompiler",
25
+ "mcp",
26
+ "hopper"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "type": "module",
32
+ "bin": {
33
+ "rea": "scripts/rea.mjs"
34
+ },
35
+ "engines": {
36
+ "node": ">=24.18.0 <25",
37
+ "npm": ">=11.16.0 <12"
38
+ },
39
+ "packageManager": "npm@11.16.0",
40
+ "files": [
41
+ "dist",
42
+ "bridge/hopper_bridge.py",
43
+ "scripts/rea.mjs",
44
+ "scripts/rebuild-native.mjs",
45
+ "skills",
46
+ "README.md",
47
+ "LICENSE"
48
+ ],
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.build.json",
51
+ "check": "npm run typecheck && npm run lint && npm run format:check && npm test",
52
+ "format": "prettier --write .",
53
+ "format:check": "prettier --check .",
54
+ "lint": "oxlint",
55
+ "lint:fix": "oxlint --fix",
56
+ "knip": "knip",
57
+ "lint:dead": "knip",
58
+ "jscpd": "jscpd src/ tests/",
59
+ "scan:todos": "node scripts/scan-todos.mjs",
60
+ "start": "node dist/main.js",
61
+ "pretest": "npm run build",
62
+ "test": "vitest run",
63
+ "pretest:coverage": "npm run build",
64
+ "test:coverage": "vitest run --coverage.enabled --coverage.provider=v8",
65
+ "test:verbose": "vitest run --reporter=verbose",
66
+ "test:watch": "vitest",
67
+ "typecheck": "tsc --noEmit",
68
+ "verify:hopper": "npm run build && node scripts/verify-real-hopper.mjs",
69
+ "build:fixtures": "node scripts/build-conformance-fixtures.mjs",
70
+ "verify:fixtures": "npm run build:fixtures && node scripts/verify-conformance-fixtures.mjs",
71
+ "verify:package": "npm run build && node scripts/verify-package.mjs",
72
+ "config:print": "node scripts/print-mcp-config.mjs",
73
+ "docs:generate": "typedoc",
74
+ "prepack": "npm run build",
75
+ "prepare": "husky",
76
+ "rebuild:native": "node scripts/rebuild-native.mjs"
77
+ },
78
+ "lint-staged": {
79
+ "*.{ts,mjs,json,md,yml,yaml}": "prettier --write",
80
+ "*.ts": "oxlint --fix"
81
+ },
82
+ "dependencies": {
83
+ "@modelcontextprotocol/server": "2.0.0-beta.3",
84
+ "canonicalize": "3.0.0",
85
+ "incur": "0.4.13",
86
+ "node-pty": "1.1.0",
87
+ "pino": "9.14.0",
88
+ "ws": "8.21.0",
89
+ "zod": "4.4.3"
90
+ },
91
+ "devDependencies": {
92
+ "@modelcontextprotocol/client": "2.0.0-beta.3",
93
+ "@types/node": "24.10.1",
94
+ "@types/ws": "8.18.1",
95
+ "@vitest/coverage-v8": "4.1.10",
96
+ "husky": "9.1.7",
97
+ "jscpd": "5.0.12",
98
+ "knip": "6.26.0",
99
+ "lint-staged": "16.4.0",
100
+ "oxlint": "1.73.0",
101
+ "prettier": "3.6.2",
102
+ "typedoc": "0.28.20",
103
+ "typescript": "5.9.3",
104
+ "vitest": "4.1.10"
105
+ },
106
+ "allowScripts": {
107
+ "node-pty@1.1.0": true
108
+ }
109
+ }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Route production MCP before importing Incur. Incur owns registration helpers
4
+ // such as `mcp add`, while only dist/main.js may serve the 50-tool stdio server.
5
+ const args = process.argv.slice(2);
6
+ const isMcpMode =
7
+ args[0] === "--mcp" || (args.length === 1 && args[0] === "mcp");
8
+
9
+ if (isMcpMode) {
10
+ const { run } = await import("../dist/main.js");
11
+ process.exitCode = await run();
12
+ } else {
13
+ const { createCli } = await import("../dist/cli.js");
14
+ await createCli().serve(args);
15
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+
5
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
6
+ const child = spawn(npm, ["rebuild", "node-pty"], {
7
+ stdio: "inherit",
8
+ env: { ...process.env, npm_config_build_from_source: "true" },
9
+ });
10
+
11
+ child.once("error", (error) => {
12
+ console.error(`Unable to rebuild node-pty: ${error.message}`);
13
+ process.exitCode = 1;
14
+ });
15
+ child.once("exit", (code, signal) => {
16
+ if (signal !== null) {
17
+ console.error(`node-pty rebuild terminated by ${signal}`);
18
+ process.exitCode = 1;
19
+ return;
20
+ }
21
+ process.exitCode = code ?? 1;
22
+ });
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: rea-analysis
3
+ description: Reverse engineer apps with REA. Explore how features work, then build a version tailored to your project.
4
+ ---
5
+
6
+ # REA
7
+
8
+ Use REA when the user wants to understand how an app or feature works, compare app versions, decompile code, or build a similar feature.
9
+
10
+ ## Understand the request
11
+
12
+ Identify the app and what the user wants to understand or build. Do not ask for information they already provided.
13
+
14
+ If the app is missing, ask which app they want to reverse engineer. If the app is known but the goal is unclear, ask what they want to understand or build, and offer to start with an overview. Never require the user to supply a program path, address, architecture, or reverse-engineering terminology.
15
+
16
+ Notes is only a documentation example. Never select an app unless the user names it or confirms it.
17
+
18
+ ## Ensure REA is ready
19
+
20
+ 1. Run `npx -y rea-agents doctor`.
21
+ 2. If setup is needed, explain that REA may install Homebrew and Hopper. Hopper is separate software and requires its own license.
22
+ 3. Obtain approval before installing external software, then run `npx -y rea-agents setup --yes`.
23
+ 4. If macOS or an installer requests human input, tell the user exactly what needs attention. After they finish, rerun setup and doctor.
24
+ 5. If setup registers a new MCP server, tell the user to restart their coding agent to load all REA tools. Direct CLI commands remain available before restart.
25
+
26
+ ## Locate the app
27
+
28
+ Accept a human-readable app name. Search macOS application locations and system metadata. If one clear match is found, continue without asking for a path. If several apps match, show their names and locations and ask which one the user means. If none match, ask where the app is installed.
29
+
30
+ REA accepts a `.app` bundle directly. Do not expose its internal `Contents/MacOS` path unless it helps explain an error.
31
+
32
+ ## Investigate
33
+
34
+ Briefly tell the user what you will investigate. Open the app with `open_binary`, begin with `binary_overview`, and narrow the investigation around the requested feature. Use decompilation, strings, names, callers, callees, and cross-references as needed.
35
+
36
+ Explain conclusions in plain language. Point to the relevant decompiled code, strings, names, and connections so the user can see how the explanation was reached. Do not claim to recover original source code or automatically clone an application.
37
+
38
+ ## Build
39
+
40
+ When requested, use normal coding tools to build a version suited to the user's project, stack, interface, and requirements. Keep the implementation tied to what the investigation established, and distinguish observed behavior from assumptions or design choices.
41
+
42
+ ## Human input and cleanup
43
+
44
+ Hopper or macOS may show a window that needs human input. Tell the user what appeared and ask them to handle it; do not guess or take over unrelated UI. Call `close_binary` when the investigation is complete.