* fix(process): normalize reactive rendered-frame timestamps A reactive `resize` action stamped `rendered_frames[].at_ms` with the raw elapsed time while every other rendered frame used `normalizeProcessElapsedTime`. Under the default 10ms time bucket the next frame buckets down and appears to move backwards, so `parseProcessCapture` rejected the entire capture with "rendered_frames: timestamps must be ordered". No test combined a reactive resize with time bucketing, so the path was never exercised. Thread the scenario's bucket into the reactive effect host and normalize the renderer call, matching what the scheduled-event resize path already did. `dispatched_at_ms` and `captured_at_ms` stay raw on both paths, so this changes only the one inconsistent clock. Also replace the wall-clock-scheduled interactive PTY scenario with terminal_text triggers. A fixed `at_ms` could fire before the child wrote its prompt under host contention, dropping the input and the resize echo. That rewrite is what surfaced this bug, and it now passes deterministically. Replace the 11 `if (!capability.available) return;` guards across the PTY suite with a shared it.skipIf gate. The old form reported a green pass with zero assertions on any host lacking native PTY authority; skips are now named and reported. Verified by reverting only the normalization, which reproduces the validation failure immediately. * fix(install): add recovery guidance to version-mismatch failure Every installer failure except one told the user what to do next. The version-mismatch failure reported only that the installed version did not match, with no remedy, which contradicts the requirement to keep diagnostics actionable for mismatch locations. Add a remedy in the same style as the neighbouring failures and drop the exemption the test carried for it, so all ten installer failures now assert they carry a recovery action. * ci: repair stale curated Windows lane and unblind the duplication gate The `windows-curated` job listed fifteen test files and nine of them no longer existed, including six `processOwnership*` files moved to `src/process/` in4ecc7a72. Vitest silently drops unmatched paths, so the job ran six files and reported success. Repoint the paths at their current locations and add a test that fails when any listed path is renamed or removed, so the lane cannot silently lose coverage again. The duplication gate ignored `**/*.test.ts`, so it reported zero clones across the test tree while running on every PR. Include test files, remove the two largest duplicated clusters, and ratchet the threshold to the measured result so the gate can no longer pass by not looking. Run local tests with two workers instead of one. A single worker made a full local run cost ~140s of wall time at ~100% CPU on a 10-core host, which was the largest dev-cycle cost in the repository. Local now matches the concurrency CI already proves green, and PTY scenarios moved to a serial project because they contend for host terminal resources. Full local suite: 140s to ~89s. Regenerate the managed conformance manifest. Commit4ecc7a72edited SKILL.md without refreshing the digest, so `npm run check:pr` has been failing on main since that commit. * test: replace implementation lock-in with behavioural assertions Audit the suite for tests that cost maintenance without catching defects, then remove the lock-in rather than the coverage. Replace assertions that froze incidental detail: - toolContracts.test.ts pinned group sizes and 50 lines of literal tool names, so any additive catalog change required a test edit. Replace with four structural invariants: global name uniqueness, group containment in the published inventory, group disjointness, and non-empty groups. Both invariants are mutation-checked by orphaning and duplicating a tool. - Six tests asserted only that a schema accepts its own canonical fixture. Replace five with real rejections and two with property tests covering the invariants those tests were gesturing at: `isDbSaveComplete` is downward closed in every expected count, and frame retention holds at any size. - A concurrency test whose fake returned `? [] : []` could not fail. Give the fake real shared state so a lost update is observable. - providerProcess.test.ts asserted listener and timer counts plus exact cleanup retry counts. Assert detachment behaviour instead, so an unrelated internal timer no longer breaks the test. - Scale tests that rebuilt removed caps: 16 MiB and 10,001 files to prove limits that no longer exist, 100,001 frames compared against their own length, and a projection asserting a literal 10,201 bridge candidates. Replace with derived relationships that hold at any size. - installScript.test.ts merged two tables and now asserts alongside the exact wording that every failure carries a recovery action. - cdpBrowserProvider.sensitive_data asserted outcomes produced after the observation window closes while using a zero-length window; give it a real window so it measures the malformed-body rejection rather than host speed. - runtime.test.ts repeated the catalog assertion from the MCP boundary suite while spawning a second stdio server. Give the database-kind scenario its own claim and make stale-build failures legible. Extract two byte-identical fixture preambles that the max-lines rule had split across five and three files respectively, removing ~1,000 duplicated lines. Profile the heaviest files rather than only running them: the Apple projection test drops from 6.4s to 0.28s and the reference source import from 2.3s to 0.2s, with stronger assertions in both cases. * ci: keep native-authority suites out of the curated Windows lane CI showed the repointed curated list was red. Three of the restored files require verified Windows native authority, which windows-latest does not provide by design: - src/ghidra/GhidraProvider.test.ts, the replacement for the deleted tests/boundary/providers/ghidra/ghidraProvider.test.ts - tests/boundary/providers/ghidra/ghidraClient.test.ts - tests/boundary/process/providerProcess.test.ts All three fail with PrivateRuntimeRootUnavailableError, because Job Object process ownership and private-runtime DACL enforcement are unimplemented. The previous ghidraClient and providerProcess entries were added in this branch and never belonged here; the GhidraProvider replacement does not inherit the deleted file's Windows behaviour. Those claims belong behind the same approval boundary as the real Ghidra Windows workflow rather than the default lane. Record the exclusion in the guard test so the lane cannot regress to red, and in ci.yml so the next reader knows the omission is deliberate. The lane now runs 14 files, up from the 6 it silently ran before, all verified green on windows-latest. * test: stop max-lines from forcing test files to split The 500-line cap on test files only ever produced duplication. It chopped one binary session suite across nine files whose names were truncated mid-sentence, and it is why the curated Windows lane referenced six `processOwnership*` paths that had been split apart in the first place. Turn the cap off for tests and fixtures, keeping the function-length limit, then merge the nine binary session files back into one suite. That restores all 22 cases under a single module registry instead of nine and cuts the composition project from 20 files to 12. The merged cases become nine top-level describes grouped by concern, because a single wrapping describe would itself breach the function-length limit. Drop applicationWorkflowVerifier.test.ts. Its first case asserted `verified: true`, which the script emits as a literal after its own checks, so it could not fail, and every remaining assertion was `expect.any`. Its second case asserted an export-shape comparison that applicationWorkflowCli.test.ts already asserts through the product CLI, which is the stronger claim. Finally, five javascriptSemanticAnalysis files imported `it` from @fast-check/vitest while containing no properties. Import `it` from vitest so those files no longer read as property-based coverage. * test: stop creating temp scaffolding the temp helper immediately deletes `createTestTempDirectory` called `createTestWorkspace`, which creates `home` and `xdg/{config,cache}`, then deleted two of those three directories so the caller would receive an empty root. Every caller paid three `mkdir` calls and two `rm` calls per temporary directory for scaffolding it never used, across roughly a hundred test files. Create the directory directly instead. Callers that genuinely need a home or XDG tree can call `createTestWorkspace`. * test: merge the split process reactive runtime suite Five files shared one describe name in pairs and re-imported the same fixture helpers. With the shared preamble already extracted into processReactiveRuntime.fixture.ts, merge them into one suite of five top-level describes grouped by concern. All 12 cases are preserved and the domain project drops from 92 files to 88. * test: drop two identity round-trips from node runtime characterization One test built a plan with `max_calls: 10_001` and asserted the field came back as 10_001, which asserts that the plan constructor copies a field the test just set, and freezes a former ceiling in the process. The other built a 201-character alias and asserted it parsed back to the same string, which round-trips a value the test created. Neither could fail for a reason that matters. The remaining cases still cover preparation, stale-plan rejection, single dispatch, and artifact write. * test: hoist duplicated graph fixtures into the shared fixture module Eleven copies of three helpers were spread across the seven javascriptApplicationGraph files: `completeCoverage` five times, `nodeByLabel` four times, and `firstOf` twice, all byte-identical. The fixture module already carried a private `completeCoverage`. Export all three from javascriptApplicationGraph.fixture.ts and import them, removing 95 duplicated lines. * test: separate the reactive fixture's two PTY writes The multi-source run resolved target_lost instead of passed intermittently. The ready transition matches terminal text "Ready" and consumes that observation, advancing the terminal frontier; the collecting state then needs "Collecting" in a later observation. The fixture wrote both back to back, so the PTY sometimes coalesced them into one chunk and the collecting trigger could never match. Move the "Collecting" write after the synchronous shim probe. The probe takes real wall time, so the reader has drained the first chunk before the second is written, which makes the ordering deterministic rather than timing dependent. Measured 33/34 isolated passes, against roughly even odds before. Under four concurrent copies it still failed 2 of 12, so this narrows the window rather than closing it: the underlying race is that node-pty delivers buffered output through a callback the exit path never awaits, so the harness can declare the target lost before the last chunk is ingested. The test is left strict because it is correctly detecting that. * test: replace expect.any placeholders with real results Four acceptance scenarios and one composition scenario asserted only that a field was a string, number, object, or array. Those assertions cannot fail for a reason that matters, and three of them sit behind real CLI process spawns that cost about 1.5s. Capture the actual results once and assert them exactly. The CLI parity scenario now pins the trace summary and coverage, the version comparison summary and coverage, and the source-to-bundle summary and coverage, so each spawn earns its cost. The MCP rejection case now asserts `invalid_request` with category `invalid_input`, which is what makes it a caller-input failure rather than a provider one. The artifact comparison case asserted three `expect.any` values immediately before an assertion that subsumed them. Replace them with the claim that actually matters above 500 entries: manifest counts must reconcile with the arrays returned, and every written file must appear as an occurrence, so a reintroduced truncation would fail. * test: remove impossible and incidental prompt assertions Two assertions in toolResult could never fail: each asserted that the serialized result did not contain a string that appears nowhere in its own inputs, so only in the assertion itself. promptContracts asserted that a `Set` built from a locally declared six element literal had no duplicates, and pinned thirteen incidental fragments of rendered prompt prose. Drop the tautology and reduce the prose block to the five properties an analyst actually depends on: caller input framed as data rather than instruction, tools offered as options rather than a mandated sequence, the epistemic vocabulary present, tools never framed as authorization, and deterministic suggestion order. * test: hoist the remaining duplicated graph fixtures Eight more byte-identical helper copies across the javascriptApplicationGraph files: `graphForNode` four times, `unknownEvidence` twice, and `contentNode` twice. Move them into javascriptApplicationGraph.fixture.ts alongside the three hoisted earlier, and drop the imports they left behind. Net -92 lines across the seven files. * test: drop enhancedTools helpers already in the shared harness Three of the six enhancedTools files still carried local copies of PROCEDURES, inventory, and emptyBounded that are byte-identical to the exports added to enhancedToolsHarness.ts. Import them instead. Only fixturePort, connect, and jsonResult differ per file, so those stay local. * test: replace repeated process-tree event literals with fixture builders The file spelled out nineteen thirteen-field event literals and thirteen-field process nodes inline, varying only three or four fields each, so a test read as a wall of repeated boilerplate rather than the transitions under test. Add eventProcessTree.fixture.ts with spawn, exit, re-exec and reparent builders plus a process-node builder, and express each case as the sequence it describes. The test drops from 422 to 214 lines with all nine cases intact. * test: share the process-ownership validation fixtures The two process-ownership validation files each declared byte-identical copies of an `ownership` constant and a 24-line `host` builder. Move both into ProcessOwnership.fixture.ts and import them. * test: make rejection and retention assertions exact The reactive scenario rejection table asserted only that each of its six fixtures failed to parse. A typo anywhere in a fixture would satisfy that, including one that invalidated an unrelated field. Record the field each row is meant to invalidate and require the reported issue to point at that path with the expected code. Verified by making one row valid except for an unknown key: the row now fails instead of passing for the wrong reason. The large-body replay test asserted that the serialized result exceeded five million characters as a proxy for the bodies being retained, which escaping or padding could satisfy even under truncation. Compare each retained body to the input byte for byte and total the retained length. Verified by shaving one byte off the expected value, which now fails. * test: share the elicitation grants fixtures The three processCaptureElicitationGrants files each declared the same five helpers inline: an elicitation request, a fixed clock, a ceiling-only permission authority, a request-context builder, and a verified-state stub. Move them into elicitationGrantsHarness.ts and import them. * fix(process): wait for trailing PTY chunks before declaring target loss The exit path drained the reactive coordinator and then submitted target_lost, but node-pty delivers buffered output through onData callbacks that run outside the coordinator's queue. A trailing chunk that had not been delivered yet produced no journal entry, so the drain had nothing to wait for and the target was declared lost while a terminal trigger was still satisfiable. That is what made the multi-source reactive fixture resolve target_lost instead of passed at roughly even odds. Yield to the event loop until the capture journal stops growing before draining, bounded so a pathological stream cannot stall exit. The fixture change that accompanied this is no longer carrying the weight on its own; the ordering it introduced remains correct and worth keeping. Measured 18 consecutive passes across isolated and four-way concurrent runs, against roughly even odds beforehand. A control run with the wait disabled did not reproduce the failure, so the machine was not loaded enough for a controlled comparison; the before/after numbers are from separate sessions. * test: hoist the last duplicated domain and coordinator fixtures processTraceComparison and its Part2 declared the same eight helpers, and the two ProcessReactiveCoordinatorFailures files declared the same three. All were byte-identical. Move them into shared fixture modules and import them, netting 429 fewer lines. The earlier attempts at this failed because depth-based scanning mis-detected helper boundaries: bracket characters inside type annotations, such as `options: { truncated?: boolean }`, unbalance the count. Extracting by verified line range instead worked first time, and unused imports were then settled by alternating typecheck and lint rather than by guessing which names a body uses. * ci: ratchet the duplication gate to the reduced baseline Test-file duplication is down to 2.58% of lines across src and tests, from 3.39% when tests were first included in the gate. Lower the threshold from 3.2 to 2.7 so the reduction cannot regress. * test: merge the remaining artificially split test files Five suites were each split across two or three files by the max-lines rule that is no longer applied to tests, and every split produced a duplicate describe name and a duplicated import block. Merge them back: processTraceComparison, ProcessReactiveCoordinatorFailures, ProcessOwnership.validation, processCaptureElicitationGrants, and processCaptureElicitation. Net -62 lines and five fewer files. Give each merged describe a name describing what it covers, since the splits were arbitrary and the names now collide. Test bodies are unchanged; every describe already fitted the function-length limit, so merging them as separate top-level blocks satisfies it. Imports were settled by writing each block explicitly after typecheck and lint disagreed. Lint is right about names that appear only inside string literals, such as `values(["terminal", "process", "http", "websocket"])`, while typecheck is right about names a body genuinely references, so neither alone was sufficient. * test: merge the graph suite and share the enhancedTools result helper The seven javascriptApplicationGraph files were one suite with one describe name repeated seven times. Merge them into a single file with seven describes named for what each covers, keeping all 18 cases. Three of the enhancedTools files declared byte-identical copies of a jsonResult helper. Move it into enhancedToolsHarness.ts alongside the rest of the shared fixtures. The remaining enhancedTools files stay split: their fixturePort helpers are three genuinely different provider doubles rather than duplicates, so merging them would mean renaming per-file seams and lose the reason each exists. * ci: repoint the Windows lane at the merged ownership validation file Merging the split ProcessOwnership.validation files removed the two Part paths the curated Windows lane referenced. The curated-lane guard test caught it on CI, which is the failure it was added to prevent. * test(process): make lifecycle assertions falsifiable and scope them to POSIX Two assertions could not fail, so neither observed the behaviour it claimed. The deadline disposal test aborted an already-aborted controller and then read an immutable interruption, so it passed whether or not disposal released the timer or the listener. Dispose before aborting instead and assert the owned timer count reaches zero, which distinguishes a released deadline from a retained one. Verified by mutation: emptying dispose, dropping the listener removal, and dropping the timer clear each fail the test. The supervisor test asserted that a repeated stop still resolved, but stop caches its promise, so it proved nothing about detachment. Replace it with a late error and close event after disposal and assert the diagnostic channel stays untouched, keeping an attached control that proves the channel works. Verified by mutation: retaining the error listener fails the test. Also mark the TERM-to-KILL escalation POSIX-only. libuv maps every signal to TerminateProcess on Windows, where a child that ignores SIGTERM cannot survive the TERM step, so the escalation and SIGKILL signal code are unreachable there. * fix(vitest): restore cross-project scheduling for local runs Dropping the per-project `sequence.groupOrder` made every project run concurrently. `fileParallelism: false` only serialises files inside a single project, so `process-boundary` could still overlap with the boundary projects that observe host process and terminal state, which is the contention that drops input and resize echoes. The removed blanket per-project serialisation was slower, but it was never what isolated projects from each other. Keep the two-worker intra-project parallelism that made local runs fast and apply the ordering locally only, so CI keeps the concurrency it proves green. * fix(acceptance): make the database startup record check observable The check asserted stderr contained no fatal record, but a database-kind startup that only lists the catalog starts no bridge work and writes no records at all, so the assertion passed without observing anything. Reading stderr while the child was still running also raced the pipe, and splitting the buffer could parse a partial trailing line. Call a tool so startup records are actually produced, parse only complete newline-delimited records, and read after the transport closes so delivery is no longer in question. Guard the result with an explicit non-vacuity assertion so the fatal check cannot silently stop observing anything. Verified by mutation: injecting a level-60 record through the stderr logger now fails the test. * test(acceptance): restore the application workflow verifier coverage Nothing else in the repository executes scripts/verify-local-application-workflows.mjs, so deleting this file left the only automated check on a shipped script. Regressions in its dist imports, argument handling, or two-version and export-shape output could ship with a green suite. The two cases cost about 350ms and 500ms, so the duplication that motivated removing them was never the reason to drop the coverage. Verified by mutation: flipping the reported verification status fails the suite. * test(contracts): add a published tool name floor Every remaining inventory invariant is structural, so deleting a contract from both its group and the aggregate left the whole file green while the catalog regenerated without it. Structural checks cannot detect removal; only a declared baseline can. Declare the 114 published names as a floor rather than an equality. Adding a tool still needs no edit, while removing one now fails with a message naming the tool. Verified by mutation: dropping a contract from its group so the aggregate loses it too fails the floor. * test: restore no-ceiling coverage above plausible truncation limits Four suites claimed a result was complete at any scale while their own inputs were bounded below where a truncation would appear, so the guarantee they described was not actually under test. Scale independence cannot be asserted as a relationship over a fixture smaller than the cap being ruled out. Raise each fixture past any ceiling a regression would plausibly introduce, which is far cheaper than the removed cases: referenceSourceImport 600 entries/64 KiB -> 5,000 entries with a 4 MiB member customProtocolCapture generated max 40 frames -> 300..1,000 appleApplication 12 frameworks/16 candidates -> 250/1,600 nodeRuntimeCharacterization assert the absent call-budget and alias bounds where they would be added, since the budget derives from the case count Verified by mutation: a 1,000-entry import cap, a 500-frame capture cap, a 100-framework projection cap, a 100,000-call budget cap, and a 200-character alias cap each fail the corresponding suite. * fix(process): require a quiet period before declaring the target lost Returning on the first unchanged journal sample granted a single event-loop turn of grace. A chunk queued on a later iteration therefore still let `target_lost` be submitted before the final terminal observation, discarding a satisfiable reactive trigger under the same load-dependent exit scenario the drain was added for, and `maxTicks` never bounded that because the loop exited early every time. Require several consecutive unchanged turns so a callback arriving on any of the next few iterations still lands first, while keeping the overall tick bound so a pathological stream cannot stall exit. * fix: stop a 4 GiB fixture and measure the drain quiet period in time The reference import fixture cycled its five sizes across all 5,000 entries, which made a thousand 4 MiB members and nearly 4 GiB of fixture written concurrently. It happened to fit locally but cannot fit a runner's disk. Cross the two boundaries independently instead: entry count by volume of tiny members, byte size by a single 4 MiB member, for about 4.2 MiB total. Verified by mutation that a 1,000-entry cap and a 1 MiB per-member cap both still fail. The drain check counted scheduler turns, but consecutive setImmediate turns elapse in microseconds, so four of them finish long before a pty callback delayed under host contention, which is when the trailing chunk matters. Measure the quiet interval in elapsed time instead, so a late callback within the window still lands before target loss. Verified by simulation: the turn-count version missed any delay beyond a few microseconds, the timed version observes delays up to the quiet interval.
English · 简体中文 · 日本語 · 한국어 · العربية
REA: Reverse Engineer Anything
Reverse engineer anything with agents, from app behavior down to native binaries.
See a feature you like. Understand how it works, down to the binary level.
Quick start · Current status · Investigation model · Tool catalog · Roadmap · How it works
npm install --global rea-agents && rea setup
See a feature in an app that you want in your own product? Give the app to your agent—even without its source code. With REA, the agent can investigate the feature, explain how it works, show its evidence, and build a version adapted to your stack and requirements.
REA gives agents one consistent way to investigate software. Today that includes deep native analysis and function dossiers through Hopper or bring-your-own Ghidra on Linux, plus an experimental Windows x64 Ghidra P0 for approved native PE applications; execution-free managed PE/CLI triage; reproducible Evidence records; controlled process capture; passive website, Electron page, and Node/Electron V8 Inspector observation; JavaScript/source-map reconstruction; and provider-neutral graphs for connecting application layers without confusing static inference with runtime observation. The longer-term toolkit extends the same agent workflow to APIs, protocols, mobile artifacts, firmware, richer runtime behavior, and differences between versions.
Reverse engineering normally makes the operator choose a tool, learn its API, move evidence between programs, and decide what to inspect next. REA gives that work to the agent through commands, skills, structured results, and repeatable investigation workflows.
Just ask your agent
Run setup once. Agent integration installs an aligned MCP registration and the bundled routing skill together:
npx rea-agents setup
Then ask:
Understand how search works in the Notes app, show me the evidence, and build a
similar feature for my project.
Notes is only an example. Name any app you want to understand, or ask the agent to start with an overview.
The investigation model
|
Decompile Open an app and recover readable code, strings, names, and other clues about how it works. |
Understand Follow the code from one part of the app to another until the agent can explain how a feature actually works. |
Recreate Turn what the agent learned into a feature for your own product, adapted to your stack, interface, and requirements. |
REA shows how it reached its conclusions. It does not claim to recover original source code or automatically clone an application.
Why REA
| Built for agents | Ask what an app does and let your agent inspect it instead of guessing. |
| CLI and MCP | Run the same reverse-engineering capabilities from your terminal or agent. |
| Complexity handled | REA installs and manages the reverse-engineering tools behind the scenes. |
| From insight to code | Understand a feature, then build your own version in the same coding session. |
| Local by design | Analysis runs on your supported local host. REA does not upload the app to a hosted analysis service. |
| Keeps context | Investigate several apps without starting over for every question. |
Quick start
Run setup — recommended
npx --yes rea-agents@latest setup
The npm package-runner prompt, when shown, approves downloading REA for this invocation; it does not approve any setup changes. The REA wizard separately shows its complete plan and asks before applying it. Setup does not update Homebrew, Node.js, or npm. The setup command opens with the work it enables: investigate local apps from an agent, recover evidence through a deep-analysis provider, and reuse REA's guided workflow. It summarizes the detected agents, then asks which capabilities to set up: agent integration (MCP plus the matching guided workflow) and—when needed—the Hopper provider. Nothing is preselected. Choosing agent integration opens a second empty checklist for the specific detected agents that should receive a registration.
@latest makes the requested release explicit and asks npm for the release
currently published under that tag. REA does not silently replace the package
version npm selected. Intentional rollbacks therefore remain available through
an exact package request.
REA keeps the journey inline so its history remains in the terminal. Selecting a capability does not select every detected target or authorize a change. Before anything changes, REA validates existing configuration, prints exact paths and external effects, and asks for final approval with No as the default. The screen keeps the available keys visible while you choose; Ctrl-C and declining leave the system unchanged.
REA detects Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, and Devin. It configures the first six when detected; Devin is reported but left unchanged because it has no documented local MCP configuration boundary. Registrations are additive, backup-first, and read back after writing. You can safely rerun setup.
Use rea setup --dry-run to inspect the plan, repeat --client to select exact
agents, and --accessible for sequential vertical prompts. Machine output
remains available through --json; prompt UI and progress go to stderr.
After a successful setup, REA reports the capabilities now ready to use and a concrete next step, such as restarting a configured agent before asking it to investigate an application. It does not claim an integration or provider is ready unless setup and its final diagnostic check verified it.
An optional curl wrapper installs the same CLI package and starts setup only when a terminal is available:
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
Pass installer options after bash -s --, for example --dry-run, --no-setup, or --version 1.0.0. The curl wrapper never installs prerequisites or configures integrations itself. See Installation and setup for its exact mutation boundary.
With an agent — recommended
npx --yes rea-agents@latest setup
Choose Agent Integration in the reviewed setup plan. REA installs the pinned MCP registration and its matching routing skill as one transaction. After setup, restart the configured agent so it loads the aligned integration.
Review the setup plan, approve it if appropriate, then describe the app or feature you want to understand. Hopper can run in its free demo mode; if it shows a first-run prompt, choose the demo or enter an existing license.
From Terminal — no installation
npx --yes rea-agents@latest setup
npx -y rea-agents@latest doctor
npx -y rea-agents@latest analyze /Applications/Notes.app
Review the setup plan before confirming it. Restart a configured agent so it loads REA.
From Terminal — install the rea command
npm install --global rea-agents
rea setup
rea doctor
rea analyze /Applications/Notes.app
Update that global installation in place:
rea upgrade
REA checks npm for the latest release and verifies that the running package is
the global installation it will replace. Source, local, and npx copies report
the manual npm install --global rea-agents@latest command instead of updating
an unrelated global package.
Choose either the no-install commands or the global installation. You do not need both.
npm install rea-agents without --global installs rea only into the
current project's node_modules/.bin; it does not add rea to your shell
PATH. Use the npx commands above for one-off runs or --global when you
want a shell-visible rea command.
Requirements
- macOS 12 or newer
- Ubuntu 24.04+, Fedora 41+, or 64-bit Arch Linux
- Windows x64 for the experimental, Ghidra-only native PE P0 boundary
- Node.js 22.19+ or 24.11+ (including newer releases)
- npm; REA does not require or install a particular npm version
Deep binary operations use Hopper, a separate desktop application with its own license, or a caller-selected Ghidra provider. Ghidra supplies read-only inventory, function metadata, decompilation, assembly, resolved calls, typed references, xrefs, CFG, and function dossiers; GUI state and mutations remain unavailable through that provider. Setup reuses an existing Hopper installation or an operator-supplied Ghidra installation. It never downloads Ghidra or installs Java. If neither provider is ready, interactive setup proposes Hopper; unattended Hopper installation requires rea setup --yes --install-hopper.
If something is not working, run:
npx -y rea-agents@latest doctor
rea doctor --json is read-only and distinguishes unsupported hosts, missing dependencies, a missing local analysis engine, configuration drift, and healthy checks. Paid-license activation is optional: on Linux, REA runs the supported Hopper demo build on a private Xvfb display and selects Hopper's offered demo mode for each analysis session.
Linux installation and troubleshooting
On macOS, approved setup downloads Hopper's official DMG, verifies it, and installs the app into ~/Applications without Homebrew or administrator privileges. Hopper may show its demo or license prompt when first opened; no manual drag-and-drop is required.
On Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux, approved setup downloads the pinned official Hopper 6.4.2 package, restricts downloads to Hopper's public origin, verifies the published size and checksum, and invokes apt-get, dnf, or pacman to install Hopper and the Xvfb, Python, X11, and XTEST packages used by demo sessions. When REA is not already running as root, pkexec presents the system authorization prompt. REA never invokes sudo. Demo sessions run on an isolated 1280×1024 Xvfb display. REA verifies the exact supported Hopper binary, its owned process ancestry, the expected dialog geometry, and bridge state before selecting Try the Demo; any mismatch fails closed.
The normal Linux launcher is /opt/hopper/bin/Hopper. If Hopper was installed elsewhere:
export HOPPER_LAUNCHER_PATH=/absolute/path/to/Hopper
rea doctor --json
If doctor reports a missing analysis engine even though the file exists, inspect shared-library resolution with:
ldd /opt/hopper/bin/Hopper | grep 'not found'
Install the missing distribution packages and rerun rea setup. Linux demo automation requires Xvfb, Python 3, libX11.so.6, and libXtst.so.6; approved setup installs those direct runtime dependencies and does not interact with the user's desktop display. Hopper's free demo supports analysis with vendor-defined limits, and a paid license is optional. The curl installer places the rea command in ~/.local/bin on Linux; add that directory to future shell PATH values if it is not already present.
REA defaults HOPPER_LAUNCHER_PATH to /Applications/Hopper Disassembler.app/Contents/MacOS/hopper on macOS and /opt/hopper/bin/Hopper on Linux. Explicit configuration always takes precedence.
Ghidra read-only analysis provider
The Ghidra adapter supports the exact official Ghidra 12.1.4 release with a 64-bit full JDK 21 on Linux x64. macOS is not an admitted Ghidra host. The adapter also provides an experimental Windows x64 P0 limited to approved native x86-64 PE applications. Download and extract those projects yourself, then configure absolute paths:
export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC
export JAVA_HOME=/absolute/path/to/jdk-21 # optional when java and javac resolve from PATH
rea doctor --json
rea setup
rea providers --json
Doctor distinguishes missing configuration, a bad installation root, the wrong Ghidra or Java version, a JRE without javac, a missing support/analyzeHeadless, and an unsupported platform or architecture. Approved setup only copies the verified non-secret paths into detected MCP registrations; it does not modify the Ghidra installation or install/download Ghidra or Java.
On Windows, set the same variables in PowerShell and run rea doctor --json; automated rea setup and Hopper installation remain unavailable. The P0 target boundary rejects DLLs, managed PE files, non-x86-64 images, mutable/hostile inputs, and non-PE formats. See the Windows Ghidra P0 operations guide for registration, exact limitations, CI evidence, and acceptance gates.
REA loads its packaged Java HeadlessScript with -scriptPath, copies and digest-verifies the target in an ephemeral runtime, enables -readOnly and -deleteProject, and authenticates every request. Auto-analysis completes before operations are served; startup has a deadline, while tool requests run until a result, caller cancellation, or provider shutdown. Linux uses a mode-0600 Unix socket. Windows P0 uses token-authenticated IPv4 loopback and a token-free endpoint record because Node path-based IPC does not connect to Java AF_UNIX sockets on Windows. The bridge verifies Ghidra's imported-byte SHA-256 before serving any operation.
The Ghidra adapter declares 19 direct and enhanced operations. Its ten inventory operations are list_documents, list_procedures, list_strings, list_names, list_segments, address_name, procedure_address, resolve_containing_procedure, search_procedures, and search_strings. It also admits procedure_info, procedure_pseudo_code, procedure_assembly, read_function_instructions, procedure_callers, procedure_callees, procedure_references, xrefs, and analyze_function. read_function_instructions is the offset-paginated fast path for raw instruction windows: it does not invoke the decompiler or whole-program name/string inventories, and is also exposed as rea instructions. These capabilities enable the shared Swift/Objective-C inventory workflows, binary_overview, batch_decompile, get_call_graph, find_xrefs_to_name, trace_feature, and complete function dossiers. Default-space addresses are lowercase 0x hexadecimal. Other spaces, including EXTERNAL, use <percent-encoded-space>:0x<hex>. Symbol results identify primary, dynamic, external, type, and source facts; procedures distinguish external functions and thunks; strings identify charset, missing-terminator state, byte length, and value truncation; memory-block ends are exclusive and permissions come directly from Ghidra.
The bridge serves operations only after auto-analysis completes. Each Program owns one persistent DecompInterface, and a serial FIFO keeps Ghidra API access on the owning program's thread; it has no fixed queue length or per-operation deadline. Reference results preserve Ghidra's call/jump/data/read/write/indirect/computed/external facts, while unresolved targetless flows remain explicitly unknown. Synthetic entry-point references without actionable memory sources are omitted. Pseudocode and assembly are provider-specific observations, not original source or Hopper-equivalent text. Caller cancellation and provider shutdown remain available; provider results are returned without a fixed response-size ceiling.
npm run verify:ghidra builds debug and stripped fixtures for the supported Linux x64 ELF host. Against real Ghidra 12.1.4 it validates every admitted operation, direct and indirect calls, imports/exports/thunks, typed references, strings/xrefs, multi-block CFG, cancellation, deadlines, concurrency, malformed inputs, and complete process/project cleanup. It needs a host C compiler and the Ghidra/JDK prerequisites above.
npm run verify:ghidra:cross-format adds AArch64 ELF, x86-64 PE, and x86-64 Mach-O fixtures. This separate lane requires clang, LLD, and lld-link; use REA_CLANG, REA_LLD, or REA_LLD_LINK to select alternate command paths. It preflights the required toolchain before compiling fixtures.
npm run verify:ghidra:windows uses a deterministic source-owned native x86-64 PE application and requires all 19 operations, target/snapshot/import digest linkage, authenticated loopback transport, and cleanup on a controlled Windows x64 Ghidra 12.1.4 runner. This proof does not establish Job Object ownership, private DACLs, or reparse-point-safe authority.
To remove only REA-owned MCP registrations and the managed skill:
rea uninstall
rea uninstall --purge-data # also removes only ~/.rea/cache and ~/.rea/state
Uninstall preserves Hopper, Node.js, Evidence files, captures, unrelated skills, and other MCP servers. It refuses malformed client configuration and never follows purge-data symlinks.
CLI or agent?
| If you want to… | Use |
|---|---|
| Ask an agent to investigate an app and build a feature | Install the skill, then talk to your agent |
| Inspect or decompile one part of an app from the Terminal | rea analyze or rea decompile |
| Validate, canonicalize, or compare Evidence bundles | rea evidence-import, rea evidence-export, or rea compare |
| Map a local JavaScript/Electron application without executing it | rea analyze PATH or rea analyze-javascript-application |
| Reuse immutable analysis results without relaunching a provider | Pass --snapshot /approved/path/analysis.json to a deep-analysis command |
| Import source as historical reference | rea import-reference-source |
| Capture or compare controlled process behavior | rea capture-process or rea compare-process-captures |
rea evidence-import /absolute/path/to/evidence/bundle.json
rea evidence-export /absolute/path/to/evidence/bundle.json /absolute/path/to/evidence/canonical.json
rea compare /absolute/path/to/evidence/left.json /absolute/path/to/evidence/right.json
JavaScript application analysis reads the selected directory or ASAR directly; the command uses the supplied path directly:
rea analyze /absolute/path/to/releases/app.asar --json
rea analyze-javascript-application /absolute/path/to/releases/app.asar --json
For a directory or .asar, generic rea analyze automatically selects the
static JavaScript application provider when neither --provider nor
--snapshot is supplied. Both routes return the analysis and its Evidence
context inline.
Historical source import takes the directory directly and never treats source as current behavioral authority:
rea import-reference-source /absolute/path/to/source
Imports read the path supplied to the command and validate every Evidence ID and manifest. Exports never replace an existing file unless --overwrite is explicit.
Provider-neutral analysis snapshots persist successful, immutable REA calls and their Evidence records. They are exact caches rather than Hopper databases: REA reuses a v2 entry only when the binary digest, kind, format, architecture, operation parameters, concrete provider build, and canonical analysis-profile digest match. Hopper loader defaults and configured overrides are normalized by the Hopper adapter and committed to that profile, so overrides occupy a distinct safe cache partition instead of disabling snapshots. Cursor-dependent and mutating calls are never cached. Snapshot files can contain proprietary analysis results and local paths, so REA keeps them local and writes them with owner-only permissions. The caller supplies the snapshot path directly:
rea analyze /absolute/path/to/app --snapshot /absolute/path/to/analysis/app.json
# The same exact query can be answered from that snapshot.
rea analyze /absolute/path/to/app --snapshot /absolute/path/to/analysis/app.json
Exact CLI evidence replays happen before any provider process starts. In MCP sessions,
pass snapshot_path to open_binary to import a snapshot atomically while
opening its matching target; MCP providers may still start before a cached call
is replayed. Pass snapshot_path and, when required, overwrite: true to
close_binary to save atomically before Hopper resources are released. If the
save fails, REA deliberately leaves the session open.
One prompt, a full investigation
Reverse engineer the Notes app. Find how offline search works, explain it,
and build a version for my project using TypeScript and SQLite.
REA gives the agent a clear path from that request to working code:
| Step | What the agent does | REA tools |
|---|---|---|
| 1 | Opens and identifies the binary | open_binary, binary_overview |
| 2 | Finds likely offline-search clues | search_strings, search_procedures, list_names |
| 3 | Connects those clues to executable code | find_xrefs_to_name, xrefs, procedure_callers |
| 4 | Reconstructs the relevant control flow | get_call_graph, procedure_callees, procedure_info |
| 5 | Decompiles the relevant routines | procedure_pseudo_code, procedure_assembly, batch_decompile |
| 6 | Builds the feature in your project | code adapted to your stack, product, and requirements |
REA handles the app analysis in steps 1–5. The agent performs step 6 with its normal file-editing and test tools, using what it learned about the app.
What agents can do
- Investigate a feature you like and build a version tailored to your own product.
- Explain how a feature works when its source code is unavailable.
- Reconstruct an app's authentication, storage, update, or networking flow.
- Recover enough structure to document an undocumented format or interface.
- Trace a suspicious behavior from a string or symbol to the code that implements it.
- Turn recovered behavior into product features, tests, migration notes, ports, or interoperable replacements.
- Analyze Swift and Objective-C metadata without manually untangling every mangled symbol.
- Leave names, comments, and bookmarks in Hopper so human and agent analysis reinforce each other.
Tool catalog for investigation
| Tool family | Count | Examples |
|---|---|---|
| Native inspection | 36 | procedures, pseudocode, assembly, strings, names, segments, callers, callees, xrefs, annotations, bounded byte reads, file-offset translation |
| Investigation workflows | 14 | binary_overview, analyze_function, inspect_native_api, inspect_native_dispatch_metadata, batch_decompile, trace_feature, trace_native_investigation, exact string-to-code lookup, bounded call paths, call graphs, Swift and Objective-C discovery |
| Native macOS utilities | 5 | Mach-O metadata, code signatures, plists, architectures, Swift demangling; Hopper-free and provenance-bearing |
| Artifact graph | 3 | complete inline inspection of directories and supported packages, compiled Interface Builder UI graph decoding, plus explicitly selected extraction into an absent owned tree |
| Managed PE/CLI | 8 | PE/CLI identity, metadata members, CIL hashes, P/Invoke/native-boundary declarations and verification, application-graph projection, decompiler reconstruction import, token remapping, runtime-correlation plans, and version comparison |
| Browser observation | 9 | exact-origin passive CDP capture, bundle and source-map analysis, WebMCP discovery, session timelines, capture diff, visual evidence, and bounded Playwright scenarios |
| Electron analysis | 5 | passive root-confined observation, static application mapping, evidence-backed static/runtime reconciliation, and provider-owned click/wait scenarios |
| JavaScript runtime | 2 | approved attach-only Node/Electron Inspector target discovery plus bounded script and execution-context observation without evaluation or instrumentation |
| Application workflows | 10 | complete cross-layer traces, unique-only version matching, historical-source to bundle mapping, static export return-shape comparison, approved Linux-isolated extracted-module replay, managed-runtime characterization, reconstruction coverage closure, and deterministic obligation ledgers |
| Workspace and observation | 22 | target lifecycle, inline Evidence bundle retrieval, aggregate navigation/address context, direct finite replay-machine evaluation, process/artifact/function comparison, evidence-linked residual-unknown lifecycle |
The public interface describes what the agent is trying to learn. Providers decide how to answer. macOS utilities handle common semantic inspection without launching Hopper; Hopper handles deeper native analysis; the process harness implements controlled behavioral capture.
Current status
REA is already useful for native application, browser, and Electron investigation on supported macOS and Linux hosts, plus the bounded Windows Ghidra P0 described above:
- Open Mach-O, ELF, PE,
.app, ZIP, APK, IPA, ASAR, plist, JavaScript, source-map, and generic analysis-database targets; Hopper remains the only adapter that accepts legacy.hopdatabases. - Discover deep-analysis candidates without starting them, choose deterministically, and retain one immutable provider/profile binding until an explicit switch or close; provider failures never trigger transparent fallback.
- Attach to a user-owned Chrome-family browser over a configured loopback CDP endpoint; capture exact-origin web structure, safe metadata, approved value-free payload shapes, bundle/source-map evidence, WebMCP declarations, user-action timelines, capture diffs, and explicitly approved screenshots without navigation or JavaScript evaluation.
- Inspect Electron
file://renderer pages through a separate canonical-root permission boundary without invoking Electron APIs; script contents remain separately approved and byte bounded. - Attach to one exact approved Node or Electron V8 Inspector target and retain bounded
scriptParsedplus execution-context lifecycle metadata without evaluation, breakpoints, resume, source reads, or instrumentation. require/import edges, EventEmitter activity, Electron IPC, PID identity, and role identity remain unknown. See passive Node and Electron runtime observation. - Validate and canonically serialize a provider-neutral JavaScript Application Graph spanning packages, ASAR entries, Electron roles, JavaScript/source-map entities, browser/runtime instances, IPC, endpoints, storage, and native add-ons. This shipped domain contract performs no extraction or I/O by itself.
- Reconstruct static package, entrypoint, Webpack/Rspack module, import, worker, endpoint, storage, source-map, BrowserWindow, preload, contextBridge, IPC, utility-process, and native-add-on structure from a selected local directory or ASAR through
analyze_javascript_applicationorrea analyze-javascript-application. Results and Evidence context are returned inline. The AST-only application service never executes bootstrap code, pairs only unique exact literal IPC channels, and reports dynamic or ambiguous channels as unresolved. - Reconcile that static graph with existing passive web or Electron Evidence through
reconcile_javascript_runtimeorrea reconcile-javascript-runtime. Exact captured bytes outrank caller-declared file/URL mappings; target, frame, script, worker, cache, and asset ambiguity stays explicit, source-map authority stays separate, and a module resident in an observed bundle is never reported as executed. See JavaScript static/runtime reconciliation. - Trace a literal route, string, API, IPC channel, module, or native export through the complete reachable graph in authenticated application Evidence, then hand exact native artifact digests and requested exports to retained Ghidra or Hopper Evidence without automatic provider switching. Compare application versions using unique-only digest, source-map, structural, and semantic tiers; map a committed historical source inventory to bundle nodes with explicit digest and path scores; compare one exact JavaScript export's static return shapes through unique literal discriminants and JSON Pointer changes. Duplicate, dynamic, incomplete, ambiguous, and truncated facts stay unknown. See cross-layer JavaScript application workflows.
- Classify PE/CLI managed artifacts with
inspect_managed_artifact/rea inspect-managed-artifact, inspect file-backed metadata members, signatures, raw CIL hashes, decoded-instruction-tuple fingerprints, separately reported exception regions, call edges, and field-access anchors withinspect_managed_members/rea inspect-managed-members, inventory declared ModuleRef/ImplMap/PInvoke and non-IL method boundary indicators withinspect_managed_native_boundaries/rea inspect-managed-native-boundaries, then compare two authenticated member observations withcompare_managed_members/rea compare-managed-members.verify_managed_native_boundaries/rea verify-managed-native-boundarieschecks managed P/Invoke declarations against authenticated native export or function Evidence while keeping verified, inferred, contradicted, and unresolved states distinct. The comparison treats build-local tokens as build-local and uses unique decoded-CIL/signature and structural method-shape tiers, never names alone; tuple fingerprints do not themselves resolve tokens or fully commit control flow.project_managed_application_graph/rea project-managed-application-graphprojects authenticated managed artifact/member/native-boundary Evidence into the existing application graph for cross-layer feature tracing.import_managed_reconstruction/rea import-managed-reconstructionadmits user-supplied decompiler C#/IL/pseudocode as analyst inference only after exact artifact SHA-256, MVID, signature, and decoded-IL commitments match. Separately,plan_managed_runtime_correlation/rea plan-managed-runtime-correlationcan admit a default-disabled, permission-gated runtime-correlation plan locked to the same build evidence. These paths never load the assembly, resolve CLR dependencies, execute target code, run a decompiler, or translate managed tokens into native addresses; complete normalized-CIL semantics, native-body bridge mapping, and an actual runtime executor remain future managed-code contracts. - Configure
REA_ILSPY_CMD_PATH=/absolute/path/to/ilspycmdonly when you want doctor andverify:managedto inspect a bring-your-own ILSpy command as a real reconstruction oracle. REA does not install ILSpy and does not treat decompiler text as canonical metadata or CIL observation. - Traverse content-addressed artifact graphs without extraction; on macOS, read-only DMG traversal additionally requires
native_mount_approved: trueandREA_ARTIFACT_NATIVE_MOUNT_ENABLED=true. Materialize only approved occurrences into absent output roots. - Build function dossiers with pseudocode, assembly, CFG edges, comments, calls, references, strings, and names.
- Search and trace features across symbols, strings, metadata, references, and call paths.
- Record every successful result as deterministic Evidence with artifact and provider identity, confidence, authority, limitations, and locations.
- Export and import evidence bundles across sessions.
- Capture approved PTY scenarios as Process Capture Evidence, including committed run manifests, raw and rendered terminal frames, scripted interactions, descendant settlement, named filesystem checkpoints, deterministic command shims, and loopback HTTP/WebSocket exchanges.
- Validate finite replay machines without launching a target through
run_replay_machineorrea run-replay-machine; ordered events return typed decisions, actions, captured aliases, transition journals, final state, and exact limit use without echoing request or captured values. - Compare complete artifact inventories by stable path, content, metadata, and relations; incomplete evidence never implies equivalence.
- Compare explicit function dossiers across text, calls, references, strings, and address-normalized CFG topology with per-facet unknowns.
- Compare canonical Evidence bundles by exact membership, explicit observation pairs, and residual-unknown histories without turning omissions into behavioral absence.
- Aggregate runtime comparisons into observed behavior changes while keeping static artifact/function differences labeled as candidates.
- Build Evidence-cited direct call paths by exact address without treating missing dossiers as graph leaves.
- Correlate exact static/runtime findings through explicit hypotheses without claiming causality from cochange.
- Verify finite behavioral and structural reconstruction specifications with pass, fail, and unknown kept distinct.
- Track residual unknowns through immutable CAS revisions, evidence-qualified resolution, contradictions, probes, and validated dependency relationships.
- Evidence-producing workflows record returned residual uncertainty as residual unknowns linked to their result Evidence. Errors without supporting Evidence do not create registry records.
- Start six guided MCP workflows with live, session-aware completion for documents, procedures, providers, evidence, captures, artifact IDs, and active unknowns.
Hopper is the first provider, not the boundary of the project. Some current workflows still require Hopper and macOS; every evidence record identifies the provider and limitations behind its result.
Website observation with CDP
REA can inspect an already-running Chrome-family browser that you own. Browser observation is disabled by default and requires a literal loopback CDP endpoint plus exact approved page origins:
export REA_BROWSER_OBSERVE_ENABLED=true
export REA_BROWSER_CDP_ENDPOINTS_JSON='["http://127.0.0.1:9222"]'
export REA_BROWSER_ALLOWED_ORIGINS_JSON='["http://127.0.0.1:3000"]'
rea list-browser-targets http://127.0.0.1:9222 --approved --json
rea inspect-web-page http://127.0.0.1:9222 TARGET_ID --approved --json
All eight browser tools expose the same Evidence contracts over CLI and MCP. Inspection is passive: REA does not evaluate page JavaScript, navigate, click, close the page, or close the browser. Query values, credentials, cookies, authorization headers, storage values, and raw JSON or WebSocket values are never retained. Separately approved captures can retain bounded redacted console primitives, value-free JSON/WebSocket shapes, script sources, accessibility text, or screenshot pixels. Existing activity before attach is explicitly unavailable. See Website observation with CDP for browser startup, schemas, limits, and the threat model.
Controlled browser scenarios
capture_browser_scenario is a separate, explicitly mutating browser boundary.
It runs only the fixed, versioned scenario vocabulary through Playwright and
returns step-indexed Evidence for screenshots, DOM, accessibility, URL/history,
storage, console/errors, network, WebSockets, frames, workers, popups, and
cancelled downloads. Missing or truncated sections can never support equality
claims.
export REA_BROWSER_SCENARIO_ENABLED=true
export REA_BROWSER_SCENARIO_EXECUTABLE_ROOTS_JSON='["/usr/bin"]'
export REA_BROWSER_SCENARIO_CDP_ENDPOINTS_JSON='["http://127.0.0.1:9222"]'
export REA_BROWSER_SCENARIO_ALLOWED_ORIGINS_JSON='["http://127.0.0.1:3000"]'
export REA_BROWSER_SCENARIO_ALLOWED_ENV_JSON='["REA_TEST_PASSWORD"]'
rea capture-browser-scenario ./scenario.json --json
Launch mode owns a temporary browser profile and removes it after terminating
the launched browser. Connect mode accepts one exact loopback CDP target and
disconnects without closing the external browser. Automation has no default
grant: use the shared project/session policy, or set
REA_BROWSER_SCENARIO_AUTO_GRANT=true only for a trusted unattended
environment. Scenario JSON contains secret references and environment-variable
names, never secret values. See the
browser scenario contract.
Node and Electron V8 Inspector observation
Attach-only JavaScript runtime observation is separately disabled by default:
export REA_V8_INSPECTOR_OBSERVE_ENABLED=true
rea list-javascript-runtime-targets http://127.0.0.1:9229 --json
rea observe-javascript-runtime http://127.0.0.1:9229 TARGET_ID \
--runtime-kind node --json
REA sends only Runtime.enable and Debugger.enable. It retains validated
script locations and execution-context lifecycle events;
require/import edges, EventEmitter activity, Electron IPC, PID identity, and
Electron role identity stay explicit unknowns. See
passive Node and Electron runtime observation.
Exact package, tool-family, provider, setup-client, schema, and CLI facts are generated from source in docs/product-catalog.json. PR CI verifies this catalog, narrative documentation, generated schemas, and a clean TypeDoc render.
Roadmap
REA is growing into a toolkit for understanding software across static artifacts and observed behavior. The current status above is the shipped baseline; the items below are planned work.
Now
- Maintain truthful product metadata — extend the shipped canonical catalog and drift checks whenever versions, tools, providers, schemas, setup clients, or CLI capabilities change.
- Cross-provider conformance growth — add source-owned architectures and difficult indirect/thunk cases while preserving semantic comparison and provider-specific text boundaries.
Next
- Controlled replay conformance growth — extend the shipped Linux extracted-module sandbox with more source-owned hostile fixtures and cross-kernel conformance; browser and Electron scenarios remain separately permissioned authorities.
- Broader application graph evidence — extend authenticated cross-layer traces with additional static extractors and additional runtime authorities.
- Professional managed-code analysis — extend shipped PE/CLI triage, CIL evidence, managed/native declaration inventory, source-owned conformance, and obfuscation-resistant comparisons toward verified native-provider composition under the accepted managed-code boundary.
- Deterministic behavior harnesses — extend process ownership, protocol fixtures, filesystem observation, reconnects, and cross-version behavioral comparison.
Later
- Broader controlled application interaction — extend the separately authorized browser and Electron scenario surfaces beyond the current bounded click/wait actions without widening passive observation or extracted-module replay authority.
- Native runtime observation — approval-gated LLDB, Frida, system logs, process/filesystem observers, and native API tracing.
- Additional providers and targets — evaluate IDA/Hex-Rays, Binary Ninja, Rizin, LIEF, Windows-native providers, mobile artifacts, firmware, document formats, and other software-defined systems.
New providers must produce the same evidence and safety metadata as existing capabilities before they become part of the public workflow. Once REA has multiple optional toolchains, setup can become capability-selective; the consent rules for that future work are recorded in the installation roadmap.
See the static-analysis provider evaluation for the shipped Ghidra function-analysis boundary, remaining admission gates, and provider comparison matrix; ADR-0001 for binding, selection, profile, snapshot, and compatibility decisions; the controlled replay guide plus ADR-0002 for the shipped JavaScript replay boundary; and ADR-0003 for the managed-code evidence and provider design.
See the native UI and dispatch investigation guide for compiled Interface Builder decoding, symbol-derived metadata limits, and the current p-code value-flow boundary.
Using REA with other agents
Setup detects Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, and Devin. It automatically configures the first six when present; detected Devin installations are reported but left unchanged. Any agent that supports local MCP servers can use REA with the configuration below.
Manual MCP configuration
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents@3.1.0", "mcp"]
}
}
}
Persistent registrations should use one exact package version. rea setup
maintains that pin, upgrades the bundled skill at the same time, and gives Codex
a 30-second startup allowance for a cold package-runner start. An interactive
rea upgrade opens the updated setup plan after installing the new executable;
structured or non-interactive upgrades tell you to run that sync explicitly.
Restart clients whose approved registration changed.
MCP clients that support prompts can also discover six ordered investigation
workflows through prompts/list. Their optional identifier arguments use the
current session for bounded completion/complete suggestions; see
Guided MCP prompts and completion.
How it works
flowchart LR
Agent["Agent"] --> REA["REA<br/>CLI + MCP"]
Terminal --> REA
REA --> Session["Target-bound session router"]
Session --> Registry["Deep-provider registry<br/>deterministic selection"]
Registry --> Hopper["Hopper provider"]
Registry --> Ghidra["Ghidra provider<br/>read-only inventory + function analysis"]
Hopper --> Runtime["Owned provider runtime<br/>deadline + bounded diagnostics + cleanup"]
Ghidra --> Runtime
Session --> Native["Native macOS provider"]
Session --> Artifact["Artifact graph provider"]
REA --> Browser["Browser CDP provider"]
REA --> Process["Process capture provider"]
Runtime --> Target["Target software"]
Process --> Target
Native --> Target
Artifact --> Target
The CLI and MCP server use the same application workflows and evidence contracts. A provider declares which capabilities it supports and the side effects those capabilities may have. Terminal commands are short-lived; an MCP session can retain an active target and evidence ledger for the session.
CLI
The agent workflow above is the easiest way to use REA. For a one-off overview from the Terminal:
npx -y rea-agents@latest analyze /Applications/Notes.app
npx -y rea-agents@latest inspect /Applications/Notes.app
npx -y rea-agents@latest search /Applications/Notes.app "offline"
npx -y rea-agents@latest function /Applications/Notes.app 0x1000
npx -y rea-agents@latest xrefs /Applications/Notes.app 0x1000
npx -y rea-agents@latest trace /Applications/Notes.app "offline"
npx -y rea-agents@latest compare /absolute/path/to/left-evidence.json /absolute/path/to/right-evidence.json
npx -y rea-agents@latest capabilities
npx -y rea-agents@latest providers
Run npx -y rea-agents@latest --help for direct decompilation, bounded search and
other options. analyze and inspect share the same overview workflow;
function, xrefs, and trace return the same Evidence envelopes as MCP.
Or install the rea command globally:
npm install --global rea-agents
rea --help
rea upgrade
rea mcp
REA accepts a Mac .app folder directly. If an agent cannot find an app by name, tell it where the app is installed.
Choosing a deep-analysis provider
Every deep-analysis open resolves a provider before creating its client. The same selector and precedence apply to the CLI, MCP, and startup configuration:
rea providers --json
rea analyze /absolute/path/to/program --provider hopper
REA_ANALYSIS_PROVIDER=hopper rea decompile /absolute/path/to/program 0x1000
For MCP, pass the optional selector on open_binary:
{
"path": "/absolute/path/to/program",
"provider_id": "hopper"
}
The request-level provider_id or --provider wins over
REA_ANALYSIS_PROVIDER; all accept a provider ID or auto. Automatic selection
binds the sole usable deep candidate, reports ambiguous when several are
usable, and can leave an artifact-only target unbound so its disjoint artifact
operations still work. An explicit unknown, unavailable, or unsupported
provider fails with candidate IDs, stable rejection codes, and actionable local
diagnostics. binary_session, rea providers, and rea capabilities expose
the authoritative analysis_provider_candidates and
analysis_provider_binding fields. Each open target also has an
analysis_run.run_id allocated before provider startup. When dynamic providers
start, analysis_run.process_lineage changes from not_observed to snapshots
and retains one provider-attributed, token-verified observation per started
provider. Each observation carries observed_at and remains unavailable or
verified; a verified empty descendant list is distinct from both. Snapshots
describe bounded observations, not current live state or historical absence.
For providers with serial work, analysis_activity distinguishes idle,
busy, and timed_out_busy, and reports the active operation, elapsed time,
caller state, timeout, and queued-request count. A caller timeout therefore
does not falsely imply that Hopper's Python thread is available. close_binary
clears the REA session but returns cleanup_incomplete when authenticated
document shutdown, owned process cleanup, or private runtime removal cannot be
verified.
Reopening same target without a selector keeps its binding; runtime failure
never selects another provider silently.
Ghidra can appear as an available, target-compatible candidate after doctor
validates its exact installation. Its capability list contains the 19 admitted
read-only inventory and function-analysis operations; selecting it still does
not make Hopper-only GUI or mutation operations available and never triggers a
silent fallback.
CLI exit status
| Status | Meaning |
|---|---|
0 |
The requested operation completed. Truthful unknowns, warnings, and partial or truncated evidence remain successful results. |
1 |
Arguments, policy, permission, provider analysis, integrity checking, cancellation, timeout, setup, diagnostics, update, uninstall, output encoding, or output writing prevented completion. Structured output identifies the failure category when REA could encode it. |
128 + N |
The process ended from signal N, where the shell or runtime preserves the conventional signal-derived status. |
setup returns 1 for planned, needs_confirmation, or needs_human
because configuration is not ready; rerun it after approval or remediation.
doctor returns 1 when any check is unhealthy. Output format, full envelopes,
filters, and token controls never change the operation status.
When REA feeds a shell pipeline, enable pipefail so a downstream formatter
cannot hide its failure:
set -o pipefail
rea inventory-artifact ./app.asar --json | jq . > inventory.json
Current Hopper provider
REA starts Hopper when needed; Hopper does not need to be running first. Hopper's launcher internally activates the application, so opening a target may bring Hopper to the foreground. REA asks macOS to start Hopper hidden and in the background when possible, but cannot guarantee that it will remain behind the current application.
REA derives explicit format and architecture arguments to prevent common FAT and ARM selection dialogs. Other Hopper or macOS dialogs may still require a person. REA reports startup failures and remediation through CLI or MCP results instead of attempting to answer UI prompts.
Hopper bridge calls pass through a serial FIFO because Hopper's Python API runs on one dedicated thread. Calls wait for a response or caller cancellation. Cancelling an active call settles that caller but keeps its wire slot until Hopper replies, preserving request/response correlation; binary_session.analysis_activity exposes active work and MCP progress reports elapsed time. Successful decompilation text is cached per document and procedure, shared by pseudocode and dossier requests, and invalidated after rename or comment mutation.
Hopper's synchronous public Python calls return when the operation completes; callers may cancel their wait. Use read_function_instructions or rea instructions when raw instruction orientation answers the question, without requesting a decompilation or whole-program name/string inventory.
Closing a REA session shuts down its bridge and removes its private socket directory. It does not quit a Hopper application the user may be using. If shutdown or cleanup cannot be verified, close_binary returns cleanup_incomplete with the affected local resources.
Advanced process-capture setup
Process capture is disabled by default. Enabling it requires
REA_PROCESS_CAPTURE_ENABLED=true, approved executable and working roots in
REA_PROCESS_EXECUTABLE_ROOTS_JSON and REA_PROCESS_WORKING_ROOTS_JSON, and an
environment allowlist in REA_PROCESS_ALLOWED_ENV_JSON. Because the current PTY
adapter uses host networking, it also requires
REA_PROCESS_ALLOW_EXTERNAL_NETWORK=true.
Set REA_PROCESS_CAPTURE_AUTO_GRANT=false to configure those process-capture
limits as a ceiling without implicitly granting them. This mode remains
fail-closed until a narrower grant is established.
Capture a scenario or compare two saved Process Capture Evidence records:
rea capture-process ./scenario.json > authority.json
rea capture-process ./reconstruction.json > reconstruction.json
rea compare-process-captures authority.json reconstruction.json
The comparison reports each observed dimension separately and identifies the first terminal, interaction, exit, filesystem, protocol, process, or shim divergence. See Process Capture for scenario fields, command-shim replay, checkpoint triggers, limits, and safety behavior.
REA installs a prebuilt PTY backend for supported macOS, Linux, and Windows architectures. If the capability check reports that the backend is unavailable, reinstall REA for the current platform and architecture.
ASAR inventory verifies Electron integrity metadata for both archive entries
and .asar.unpacked companion files. Integrity failures identify the logical
path, declared and calculated SHA-256 values, and whether the entry was
unpacked; REA does not silently accept the mismatched artifact. If a supplied
ASAR declares unpacked companion bytes that are absent from the local artifact
set, REA keeps that occurrence as unavailable and continues analyzing the
embedded JavaScript instead of treating the missing native/resource bytes as
verified or absent.
Security model
REA does not provide a hosted analysis service. Hopper and Linux Ghidra bridge communication uses authenticated private local sockets. Windows Ghidra P0 uses authenticated IPv4 loopback but does not claim named-pipe DACL or hostile-local-user isolation. Dynamic capabilities are disabled by default and require both operator policy and explicit per-call approval. Shipped providers, passive observers, and Process Capture are not security sandboxes: providers and launched targets run with the current user's permissions. Extracted JavaScript replay is a distinct Linux-only capability that fails closed unless Bubblewrap namespaces, architecture-checked seccomp, private runtime mounts, and delegated cgroup limits are available; it never inherits browser, Electron, or Process Capture authority. See ADR-0002. Report vulnerabilities through the private process in SECURITY.md.
FAQ
Does Hopper need to be running before I start REA?
No. REA starts Hopper when an operation needs it. An already-running Hopper application is also supported.
Why did Hopper appear in front of my other windows?
Hopper's launcher internally activates the application. REA requests background startup, but macOS and Hopper may still bring a window or dialog forward. See Hopper application behavior.
Does REA include Hopper?
No. Setup can install Hopper for you, but Hopper remains separate software with its own license. REA supplies the CLI, MCP server, and workflows that make it usable by agents.
Does REA install or include Ghidra or Java?
No. Ghidra support is bring-your-own. REA packages only its Java bridge source, validates the exact supported Ghidra 12.1.4 and 64-bit JDK 21 installation, and loads that bridge through Ghidra's external script path after setup approval records the paths.
Does REA upload the app?
REA has no hosted analysis service. Current providers analyze artifacts and capture behavior locally. Your agent or model provider may have its own data policy, so review that separately.
Can REA recover the original source code?
No decompiler can guarantee the original source. REA gives an agent pseudocode, assembly, symbols, strings, metadata, and relationships that it can use to explain or compatibly recreate observed behavior.
Which agents can use REA?
Any agent that can run a local MCP server can use the manual configuration. Setup detects Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, and Devin; it automatically configures the first six when present and reports Devin without modifying it.
Development
See CONTRIBUTING.md for setup, architecture, and release
instructions, and docs/testing.md for the behavioral test
depths, focused developer commands, coverage floors, and CI evidence. PR CI
publishes generated API documentation as the api-docs workflow artifact.
npm run verify:agent runs brandless native, JavaScript-application, managed,
and browser prompts through a real local Codex CLI. Its JSON report measures
natural MCP use, first-tool routing, repeated calls, actual Codex token usage,
completion quality, and explicit treatment of authority and unknowns.
npm run evidence:generate regenerates the managed conformance manifest and
Evidence completion ledger from live verifier output. npm run evidence:check reruns the verifier and fails when artifacts, scenarios,
providers, schemas, claim counts, Evidence IDs, or the bundled skill have
drifted. Unsupported claims remain explicit and never count as passes.
Verifier JSON reports include an ephemeral verifier_run UUID allocated before
the verifier performs work and inherited by its child processes through
REA_PROCESS_RUN_ID. The final report includes the verifier and parent PIDs
plus process_lineage and its ISO observed_at timestamp: POSIX verifiers
report a token-verified, point-in-time launcher process group and live
descendants, while platforms without an owned
lineage primitive report status: "unavailable" and a reason. An empty verified
descendant list means no child was live during the final observation; it does
not claim the verifier launched no children earlier. Nested verifier entrypoints
in the same process reuse its UUID; each new verifier process replaces any
inherited parent token with a fresh run identity.
Generated completion commitments deliberately exclude this per-execution
identity, so check mode remains deterministic while live reports remain
attributable.
Owned Hopper shutdown logs retain the launcher PID, process-group ID, cleanup
status, and whether a verified group signal was required. They omit the run
token and unexpected exception text. The real-Hopper verifier also starts an
unrelated Hopper-named sentinel in a distinct process group and fails unless
that process survives every session close; the sentinel is removed only by the
verifier after the survival check.
Project links
npm · Issues · Security · Contributing · Hopper · Ghidra