dsh-plugin-rea 1.0.6 → 2.0.0

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.
@@ -0,0 +1,186 @@
1
+ # one-plugin bundle — design notes
2
+
3
+ Goal: a fresh machine installs **one DSH plugin** and gets a working `mcp__rea__*`
4
+ tool surface, without first installing Node, REA, a JDK, or Ghidra by hand.
5
+
6
+ `main` keeps the original **config-only adapter** (a single MCP row pointing at a
7
+ `rea` the user installed themselves). This branch adds the self-provisioning
8
+ variant next to it. Nothing on `main` changes.
9
+
10
+ ---
11
+
12
+ ## 1. What upstream will and will not do
13
+
14
+ REA is explicit: it never installs or upgrades unrelated software, and every
15
+ dependency is "caller-supplied" (`SKILL.md`, `install.sh`, and the remediation
16
+ strings in `dist/`). That is a deliberate upstream boundary, not an oversight.
17
+ Consequently **all provisioning must live on our side**, and it must not pretend
18
+ to be REA.
19
+
20
+ Two upstream rules shape the runtime wiring:
21
+
22
+ - Only `GHIDRA_INSTALL_DIR` + `JAVA_HOME` (and `REA_IDA_MCP_CONFIG`,
23
+ `HOPPER_LAUNCHER_PATH`) are ever published by `rea setup`. Every other knob —
24
+ `REA_ADB_PATH`, `REA_APKTOOL_COMMAND`, `REA_JADX_MCP_JAR`,
25
+ `REA_BROWSER_EXECUTABLE`, `REA_ILSPY_CMD_PATH` — must be supplied by the
26
+ launching environment. We supply them per machine from one JSON file.
27
+ - REA hard-requires an **absolute** Node/runtime path in practice: it rejects
28
+ relative paths for `GHIDRA_INSTALL_DIR` and resolves Windows `java.exe` by
29
+ walking PATH itself. We therefore never rely on PATH at all.
30
+
31
+ ## 2. Verified facts this design rests on
32
+
33
+ Measured on Windows 11 x64, REA 6.4.0, Ghidra 12.1.4, Temurin JDK 25.0.4:
34
+
35
+ | Fact | Evidence |
36
+ | --- | --- |
37
+ | A row can launch REA with **no PATH `rea`** | `node <pkg>/scripts/rea.mjs mcp` answered the MCP `initialize` handshake (`serverInfo.name = rea`, version 6.4.0) and `rea doctor --json` reported 170 tools, `healthy: true` |
38
+ | `!!js` expressions are supported in this profile layer | the existing profile patch already used one; the loader evaluates them |
39
+ | A row-level `env:` block reaches the MCP child | `binary_session` reported `registrationEnvironment` = `GHIDRA_INSTALL_DIR`, `JAVA_HOME` |
40
+ | No fixed home/layout is promised | the disk layout differs per install; nothing may assume one |
41
+ | Windows is an explicit POC boundary upstream | `DoctorDiagnostics`: "the experimental Windows x64 Ghidra P0 boundary" |
42
+
43
+ ## 3. Architecture
44
+
45
+ Three responsibilities, kept strictly apart:
46
+
47
+ 1. **Shim (runtime, per launch).** A tiny Node script the MCP row points at. It
48
+ loads the machine-local `bundle.json`, exports the discovered paths into the
49
+ child environment, and execs REA. It never downloads anything on the normal
50
+ path and never writes to the plugin directory.
51
+ 2. **Setup (provisioning, explicit or first-run).** A Node script that resolves
52
+ the component manifest, downloads what is missing into a cache, verifies
53
+ checksums, unpacks, runs the readiness probe, and writes `bundle.json`.
54
+ Idempotent and re-runnable; `--reuse` adopts pre-existing installs.
55
+ 3. **Patch (wiring, published).** `cordis.patch.yml` from this branch, carrying
56
+ **no machine path**: the row points at `<plugin>/bin/rea-shim.mjs` and the
57
+ shim is responsible for the rest.
58
+
59
+ The published plugin therefore stays portable, while the machine-specific truth
60
+ lives in one small JSON file under the DSH home.
61
+
62
+ ```
63
+ <dsh home>/plugins/rea-bundle/
64
+ bundle.json # machine-local truth: resolved paths + versions + hashes
65
+ mirror.json # which plugin checkout the mirrored files came from
66
+ cache/ # downloaded archives (resumable, checksum-verified)
67
+ vendor/ # unpacked payloads (node, jdk, ghidra, apktool, jadx, adb)
68
+ logs/setup.log
69
+ ```
70
+
71
+ ## 4. Why a shim instead of an `!!js` expression
72
+
73
+ `!!js` can inject values into the row at load time, but it cannot:
74
+ download, verify, unpack, run a readiness probe, or report progress. It also
75
+ couples the published patch to expressions that must parse on every platform.
76
+ A shim is boring, testable, and can report an action's clear remediation text
77
+ through `failOnStartupError: false` without breaking the harness.
78
+
79
+ ## 5. Control flow
80
+
81
+ ```
82
+ dsh starts
83
+ └─ MCP row: node <bundle root>/bin/rea-shim.mjs (setup mirrors it there)
84
+ ├─ bundle.json present & components verify → exec REA (normal path)
85
+ ├─ missing, auto-provision allowed → start setup detached, print
86
+ │ remediation, exit non-zero
87
+ └─ missing, auto-provision refused → print exact command, exit
88
+ ```
89
+
90
+ The shim resolves its bundle root from **its own location**, not from `DSH_HOME`:
91
+ the harness strips every `DSH_*` name from an MCP child's environment, so with a
92
+ custom `DSH_HOME` the shim would look in `~/.dsh`, find nothing, and start a second
93
+ provisioning run there.
94
+
95
+ `setup` stages, each idempotent and individually verifiable:
96
+
97
+ 1. `detect` — host platform/arch, DSH home, plugin dir, existing installs
98
+ 2. `resolve` — manifest entry per component; candidate URLs are ordered
99
+ 3. `fetch` — download to cache with resume + retries; skip if checksum matches
100
+ 4. `verify` — sha256 against the manifest (and vendor checksum file when present)
101
+ 5. `unpack` — platform-native extraction, atomic into `vendor/`
102
+ 6. `wire` — write `bundle.json`, export the environment variables REA reads
103
+ 7. `mirror` — copy the launcher to the path the published patch computes
104
+ 8. `probe` — `rea doctor --json`, then launch the **mirrored** shim exactly as the row would
105
+ 9. `report` — human summary + exit code; every failure names its remediation
106
+
107
+ Two notes on stages that exist because their absence was a bug:
108
+
109
+ - **7 (mirror).** The patch points at `<bundle root>/bin/rea-shim.mjs`, and the
110
+ plugin itself is not guaranteed to live anywhere the patch can compute. Setup
111
+ therefore copies the launcher (and the setup script it re-invokes) into the
112
+ bundle root. Without this, provisioning could succeed and the row would still
113
+ fail — and the "install the plugin first, it provisions itself" path could
114
+ never start, because the shim that does the provisioning would not exist at
115
+ the path being launched. The copy list is exactly what a mirrored run reads
116
+ back, **including `manifest/bundle.manifest.json`**: a mirrored `setup.mjs`
117
+ resolves its plugin root to the bundle root, so `loadManifest()` reads
118
+ `<bundle>/manifest/bundle.manifest.json` — a file only this stage can create.
119
+ It also writes `<bundle>/mirror.json`, naming the checkout the files came
120
+ from: the bundle outlives any particular checkout location, and without that
121
+ record a later mirrored run can only copy the bundle onto itself (provisioning
122
+ the manifest it was mirrored with, forever) and its remediation text would
123
+ point the operator at a copy that cannot start.
124
+ - **The row's `!!js` scalar is quoted.** A bare scalar containing `? ` and `: `
125
+ is not one scalar: YAML reads it as an explicit key/value pair, so the loader
126
+ receives a mapping where an expression belongs — measured against the
127
+ harness's own dialect, `args[0]` became `{"[object Object]": "..."}` and the
128
+ row could not launch at all. Any future expression with a ternary needs the
129
+ quotes; `audit-manifest.mjs` rejects the bare form.
130
+ - **3 (fetch).** Route order is deliberate and environment-dependent: direct
131
+ first, then the system proxy, and on Windows the .NET route ahead of both
132
+ because it follows the GitHub release-asset redirect that the Node routes
133
+ cannot. Neither "always direct" nor "always proxy" is correct — measured on
134
+ the development machine, the local proxy answers github.com but returns 403
135
+ for nodejs.org and 404 for dl.google.com, while the reverse also happens.
136
+
137
+ ## 6. What is genuinely impossible on Windows
138
+
139
+ Declared up front so no release note over-promises. These are **upstream
140
+ host gates**, not configuration gaps:
141
+
142
+ | Capability | Gate |
143
+ | --- | --- |
144
+ | Hopper provider | macOS only |
145
+ | IDA provider | requires a user-owned IDA + `REA_IDA_MCP_CONFIG`; REA never launches it |
146
+ | firmware `binwalk` / `unblob` | Linux only |
147
+ | Wakaru JS recovery | Linux x64 only |
148
+ | pwntools paths (ELF layout, recorded crash) | Linux x64 only |
149
+ | pwndbg / gdb core context | Linux/macOS |
150
+ | `mitmdump` native capture decode | Linux only (HAR needs no engine) |
151
+ | Apple toolchain (Mach-O, plist, asset catalog, LLDB) | macOS only |
152
+ | process capture (`capture_process_scenario`) | Linux/macOS PTY |
153
+
154
+ Everything else — native Ghidra analysis, static JS/Electron, .NET, archives,
155
+ Android (JADX/apktool/adb), browser scenarios, EVM, HAR — is reachable on
156
+ Windows and is what this bundle targets to complete.
157
+
158
+ ## 7. Component matrix (Windows x64 first)
159
+
160
+ | Component | Needed for | Provisioned as |
161
+ | --- | --- | --- |
162
+ | Node runtime | running REA at all | reuse a compatible existing node, else portable `node.exe` |
163
+ | `rea-agents` | the MCP server itself | vendored copy under `vendor/rea-agents` |
164
+ | Temurin JDK 21/25 | Ghidra headless + JADX bridge | portable JDK zip (full JDK, not a JRE) |
165
+ | Ghidra 12.1.x | native analysis | official release zip, extracted |
166
+ | apktool | Android resource decode | `apktool.bat` + jar in one directory |
167
+ | jadx-headless-mcp jar | Android class/method decompile | pinned 0.7.1 jar (REA validates its classes) |
168
+ | Android platform-tools | `adb` device tools | official zip |
169
+ | a Chromium-family browser | browser/CDP scenarios | reuse an installed Edge/Chrome; never downloaded |
170
+ | `ilspycmd` (optional) | .NET decompilation | optional; only wired when present |
171
+
172
+ Ghidra itself is **not** redistributed inside the plugin package: the release is
173
+ ~570 MB, which no npm package can carry. It is fetched from the official release
174
+ and checksum-verified, or adopted from an existing install with `--reuse`.
175
+
176
+ ## 8. Honest limits of this branch
177
+
178
+ - npm's per-package limits (unpacked/compressed size) rule out shipping the JDK
179
+ and Ghidra inside the tarball. "One plugin install" therefore means *one
180
+ install plus an automatic, checksum-verified fetch*, not a 600 MB npm package.
181
+ - First run is network- and time-bound: the full payload is roughly 800 MB
182
+ (Ghidra 570 MB, JDK 205 MB) plus the REA package. Progress is reported and the
183
+ download is resumable; a pre-seeded cache makes it offline-capable.
184
+ - Version pins age. `manifest/bundle.manifest.json` carries the pins, and
185
+ `check-updates.ps1` gains a bundle section so drift is reported rather than
186
+ discovered later.
@@ -0,0 +1,399 @@
1
+ {
2
+ "$comment": [
3
+ "Component manifest for the one-plugin bundle. Every entry is fetched from an",
4
+ "official vendor source and verified before use. Fields:",
5
+ " sha256 - REQUIRED for offline integrity. Vendor-published where one exists;",
6
+ " otherwise measured once and recorded here, and drift is reported",
7
+ " rather than silently accepted. 'unverified' means neither was done",
8
+ " yet, and setup then pins the hash it observes on first fetch.",
9
+ " size - expected byte count, used for truncation checks and resume math.",
10
+ " license - license of the upstream project, not a redistribution claim.",
11
+ " requires - components whose binaries this one needs to run.",
12
+ " binaries - workspace-relative entries inside the unpacked directory that are",
13
+ " directly spawnable; the first that exists becomes the component's",
14
+ " recorded path so another component can depend on it.",
15
+ "Nothing here is redistributed inside the npm package; setup downloads it."
16
+ ],
17
+ "manifestVersion": 1,
18
+ "generatedFor": "dsh-plugin-rea one-plugin bundle",
19
+ "pins": {
20
+ "reaAgents": "6.4.0",
21
+ "ghidra": "12.1.4",
22
+ "jdk": "21",
23
+ "apktool": "3.0.3",
24
+ "jadxHeadlessMcp": "0.7.1",
25
+ "node": "24.21.0",
26
+ "nodeEngines": "^22.19.0 || ^24.11.0 || >=26.0.0"
27
+ },
28
+ "components": {
29
+ "node": {
30
+ "description": "Portable Node.js runtime used to execute vendored REA. Only provisioned when no compatible node is already reachable.",
31
+ "kind": "archive",
32
+ "license": "MIT (Node.js)",
33
+ "requires": [],
34
+ "unpack": { "stripRootComponents": 1, "moveTo": "node" },
35
+ "binaries": ["bin/node", "node{exe}"],
36
+ "probe": {
37
+ "argv": ["{node}", "-v"],
38
+ "expect": { "regex": "^v(22\\.(1[9]|[2-9][0-9])|24\\.(1[1-9]|[2-9][0-9])|2[6-9]\\.|[3-9][0-9]\\.)" },
39
+ "why": "REA's engines are ^22.19.0 || ^24.11.0 || >=26.0.0. A loose '^v\\d+' accepted Node 20, which then fails at REA runtime; the reuse path must reject it here."
40
+ },
41
+ "sources": {
42
+ "win32-x64": {
43
+ "version": "24.21.0",
44
+ "url": "https://nodejs.org/dist/v24.21.0/node-v24.21.0-win-x64.zip",
45
+ "format": "zip",
46
+ "size": 37618919,
47
+ "sha256": "158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541",
48
+ "sha256Source": "https://nodejs.org/dist/v24.21.0/SHASUMS256.txt (line: node-v24.21.0-win-x64.zip)",
49
+ "sha256VerifiedOn": "2026-10-13",
50
+ "note": "Checksum and unpack verified on this machine: the extracted node.exe runs standalone (v24.21.0), no installer and no registry writes."
51
+ },
52
+ "win32-arm64": {
53
+ "version": "24.21.0",
54
+ "url": "https://nodejs.org/dist/v24.21.0/node-v24.21.0-win-arm64.zip",
55
+ "format": "zip",
56
+ "size": 33679608,
57
+ "sha256": "8779b1bde1d39f8d420e3b57aa657b39891af434d3de44a919044cec06785921",
58
+ "sha256Source": "https://nodejs.org/dist/v24.21.0/SHASUMS256.txt (line: node-v24.21.0-win-arm64.zip), size confirmed by HEAD",
59
+ "sha256VerifiedOn": "2026-10-13"
60
+ },
61
+ "darwin-x64": {
62
+ "version": "24.21.0",
63
+ "url": "https://nodejs.org/dist/v24.21.0/node-v24.21.0-darwin-x64.tar.gz",
64
+ "format": "tar.gz",
65
+ "size": 54203979,
66
+ "sha256": "1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097",
67
+ "sha256Source": "https://nodejs.org/dist/v24.21.0/SHASUMS256.txt (line: node-v24.21.0-darwin-x64.tar.gz)",
68
+ "sha256VerifiedOn": "2026-10-13"
69
+ },
70
+ "darwin-arm64": {
71
+ "version": "24.21.0",
72
+ "url": "https://nodejs.org/dist/v24.21.0/node-v24.21.0-darwin-arm64.tar.gz",
73
+ "format": "tar.gz",
74
+ "size": 52909993,
75
+ "sha256": "bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057",
76
+ "sha256Source": "https://nodejs.org/dist/v24.21.0/SHASUMS256.txt (line: node-v24.21.0-darwin-arm64.tar.gz)",
77
+ "sha256VerifiedOn": "2026-10-13"
78
+ },
79
+ "linux-x64": {
80
+ "version": "24.21.0",
81
+ "url": "https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-x64.tar.xz",
82
+ "format": "tar.xz",
83
+ "size": 31890184,
84
+ "sha256": "fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6",
85
+ "sha256Source": "https://nodejs.org/dist/v24.21.0/SHASUMS256.txt (line: node-v24.21.0-linux-x64.tar.xz)",
86
+ "sha256VerifiedOn": "2026-10-13",
87
+ "note": ".tar.xz is not unpackable by Windows Expand-Archive; bsdtar handles it. A .tar.gz twin exists for every platform if xz support is ever in doubt."
88
+ },
89
+ "linux-arm64": {
90
+ "version": "24.21.0",
91
+ "url": "https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-arm64.tar.xz",
92
+ "format": "tar.xz",
93
+ "size": 30859684,
94
+ "sha256": "6ad1325edbdb5649c379b75a237147a666c95d4f9ae8d340fef2d1575d289ad2",
95
+ "sha256Source": "https://nodejs.org/dist/v24.21.0/SHASUMS256.txt (line: node-v24.21.0-linux-arm64.tar.xz)",
96
+ "sha256VerifiedOn": "2026-10-13"
97
+ }
98
+ }
99
+ },
100
+ "jdk": {
101
+ "description": "Full JDK (not a JRE) for Ghidra headless and the JADX bridge. REA validates it with 'java --list-modules' and requires jdk.compiler.",
102
+ "kind": "archive",
103
+ "license": "GPL-2.0-with-classpath-exception (Eclipse Temurin)",
104
+ "requires": [],
105
+ "unpack": { "stripRootComponents": 1, "moveTo": "jdk" },
106
+ "probe": { "argv": ["{jvm}", "--list-modules"], "expect": { "contains": "jdk.compiler" } },
107
+ "sources": {
108
+ "win32-x64": {
109
+ "version": "21.0.12.1+1",
110
+ "url": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.12.1%2B1/OpenJDK21U-jdk_x64_windows_hotspot_21.0.12.1_1.zip",
111
+ "format": "zip",
112
+ "size": 205073461,
113
+ "sha256": "f9d6e191ab098c0d416e7d588a24420a8621cd2f4720dab2459b8b7b2d2d8b4e",
114
+ "sha256Source": "Adoptium API /v3/assets/latest/21/hotspot (binary.package.checksum), cross-checked against the per-asset .zip.sha256.txt sidecar",
115
+ "sha256VerifiedOn": "2026-10-13",
116
+ "metaApi": "https://api.adoptium.net/v3/assets/latest/21/hotspot?architecture=x64&image_type=jdk&os=windows&vendor=eclipse",
117
+ "note": "Unpacked and exercised on this machine: bin/javac.exe and jmods are present, java --list-modules reports jdk.compiler, and REA's Ghidra provider accepted it as a 64-bit full JDK 21+."
118
+ },
119
+ "darwin-x64": {
120
+ "version": "21.0.12.1+1",
121
+ "url": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.12.1%2B1/OpenJDK21U-jdk_x64_mac_hotspot_21.0.12.1_1.tar.gz",
122
+ "format": "tar.gz",
123
+ "size": 194316575,
124
+ "sha256": "44db0f08196daf19a47f90d13388b0c943b67663cb537f998fe29e836fa842ce",
125
+ "sha256Source": "Adoptium API (binary.package.checksum)",
126
+ "sha256VerifiedOn": "2026-10-13",
127
+ "metaApi": "https://api.adoptium.net/v3/assets/latest/21/hotspot?architecture=x64&image_type=jdk&os=mac&vendor=eclipse"
128
+ },
129
+ "darwin-arm64": {
130
+ "version": "21.0.12.1+1",
131
+ "url": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.12.1%2B1/OpenJDK21U-jdk_aarch64_mac_hotspot_21.0.12.1_1.tar.gz",
132
+ "format": "tar.gz",
133
+ "size": 200073404,
134
+ "sha256": "3623232f33a9c3baadf304480b2535f9a3cba8a58d42ecbb438ba267315d9998",
135
+ "sha256Source": "Adoptium API (binary.package.checksum)",
136
+ "sha256VerifiedOn": "2026-10-13",
137
+ "metaApi": "https://api.adoptium.net/v3/assets/latest/21/hotspot?architecture=aarch64&image_type=jdk&os=mac&vendor=eclipse"
138
+ },
139
+ "linux-x64": {
140
+ "version": "21.0.12.1+1",
141
+ "url": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.12.1%2B1/OpenJDK21U-jdk_x64_linux_hotspot_21.0.12.1_1.tar.gz",
142
+ "format": "tar.gz",
143
+ "size": 207473347,
144
+ "sha256": "ce79869e1307ed8ee1e2baa86a412b1eb5b75d10a01006d788a6f968bcfaee94",
145
+ "sha256Source": "Adoptium API (binary.package.checksum)",
146
+ "sha256VerifiedOn": "2026-10-13",
147
+ "metaApi": "https://api.adoptium.net/v3/assets/latest/21/hotspot?architecture=x64&image_type=jdk&os=linux&vendor=eclipse"
148
+ },
149
+ "linux-arm64": {
150
+ "version": "21.0.12.1+1",
151
+ "url": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.12.1%2B1/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.12.1_1.tar.gz",
152
+ "format": "tar.gz",
153
+ "size": 205641175,
154
+ "sha256": "23e37e026f12f3e706f18938ff611db3032d075b09d0879a25d06718c773e223",
155
+ "sha256Source": "Adoptium API (binary.package.checksum)",
156
+ "sha256VerifiedOn": "2026-10-13",
157
+ "metaApi": "https://api.adoptium.net/v3/assets/latest/21/hotspot?architecture=aarch64&image_type=jdk&os=linux&vendor=eclipse"
158
+ }
159
+ }
160
+ },
161
+ "ghidra": {
162
+ "description": "Ghidra release, extracted. REA locates support/analyzeHeadless(.bat) and Features/Decompiler/os/<platform>/decompile[.exe] inside it.",
163
+ "kind": "archive",
164
+ "license": "Apache-2.0",
165
+ "requires": ["jdk"],
166
+ "unpack": { "stripRootComponents": 1, "moveTo": "ghidra" },
167
+ "probe": { "file": "{ghidra}/Ghidra/application.properties", "expect": { "contains": "application.version=" } },
168
+ "sources": {
169
+ "all": {
170
+ "version": "12.1.4",
171
+ "url": "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_12.1.4_build/ghidra_12.1.4_PUBLIC_20260921.zip",
172
+ "format": "zip",
173
+ "size": 569649598,
174
+ "sha256": "ddac49f903da9d5bac833e5cc79395098b9c33cfd3279be5f31bd00387d2d4db",
175
+ "sha256Source": "GitHub release body of the pinned tag: api.github.com/repos/NationalSecurityAgency/ghidra/releases/tags/Ghidra_12.1.4_build -> .body, line '* SHA-256: `ddac49f9...`'. No checksum asset exists (six candidate names 404). Always read the pinned tag, never releases/latest, because the body changes.",
176
+ "releaseApi": "https://api.github.com/repos/NationalSecurityAgency/ghidra/releases/tags/Ghidra_12.1.4_build",
177
+ "sha256VerifiedOn": "2026-10-13",
178
+ "note": "No platform split for the public release: one zip serves Windows/macOS/Linux. Unpacked and sniffed here (support/analyzeHeadless.bat and the win_x86_64 native decompiler are present, and REA reported provider version 12.1.4). Windows traps: a path containing '!' or '^' breaks Ghidra, and macOS native components are not shipped (they must be built with support/gradle/gradlew buildNatives). Ghidra requires JDK 21+; application.java.max is empty at this tag, so a newer JDK is permitted."
179
+ }
180
+ }
181
+ },
182
+ "apktool": {
183
+ "description": "Android resource decoding. REA is pointed at the wrapper script, which must sit next to the jar.",
184
+ "kind": "multi-file",
185
+ "license": "Apache-2.0",
186
+ "requires": ["jdk"],
187
+ "entries": {
188
+ "all": [
189
+ {
190
+ "id": "jar",
191
+ "version": "3.0.3",
192
+ "url": "https://github.com/iBotPeaches/Apktool/releases/download/v3.0.3/apktool_3.0.3.jar",
193
+ "format": "file",
194
+ "size": 15478013,
195
+ "sha256": "dbf930b076c6b9be08d57c449cacefc3bdd6b71ebd59b3066fc0e1f5b14f9423",
196
+ "sha256Source": "Vendor-published in the 3.0.3 release post (apktool.org/blog/apktool-3.0.3/), md5 50d98c920b005d2fcdc18d3d52e3f23b. Independently reproduced here: this is the hash of the 15,478,013-byte apktool_3.0.3.jar.",
197
+ "sha256VerifiedOn": "2026-10-13",
198
+ "moveTo": "apktool",
199
+ "saveAs": "apktool.jar"
200
+ }
201
+ ],
202
+ "byPlatform": {
203
+ "win32-x64": [
204
+ {
205
+ "id": "wrapper",
206
+ "version": "master",
207
+ "url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/windows/apktool.bat",
208
+ "format": "file",
209
+ "size": 2348,
210
+ "sha256": "9440ec9d98efdef14136f4cefd79ac200d72baa016b5991d128234a3a3ef3b43",
211
+ "sha256Source": "measured on 2026-10-13 (raw.githubusercontent.com publishes no checksum for the wrapper); a single 2,348-byte file, so the archive-free case needs no TOFU",
212
+ "sha256VerifiedOn": "2026-10-13",
213
+ "notes": [
214
+ "The wrapper pushd's to its own directory and resolves apktool.jar first, then globs apktool_*.jar, so the renamed jar next to it is found.",
215
+ "Its final line is `for %%i in (%cmdcmdline%) do if /i \"%%~i\"==\"/c\" pause & exit /b`: measured here it returns exit 0 in 0.2 s, but it appends 'Press any key to continue . . .' to stdout. That trailing text is upstream's, not a provisioning defect; tools that parse apktool's stdout should tolerate it.",
216
+ "REA is pointed at this .bat (REA_APKTOOL_COMMAND), not at the jar, exactly as the wrapper expects."
217
+ ],
218
+ "moveTo": "apktool",
219
+ "saveAs": "apktool.bat"
220
+ }
221
+ ],
222
+ "darwin-x64": [
223
+ {
224
+ "id": "wrapper",
225
+ "version": "master",
226
+ "url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/osx/apktool",
227
+ "format": "file",
228
+ "size": 2672,
229
+ "sha256": "d0c86f0475ed73bfffa6ac39faa69de7aa2dd40ff5e7e6540f82002be3a38f0b",
230
+ "sha256Source": "measured on 2026-10-13 from the osx wrapper (the correct one for macOS; the linux script is a different file)",
231
+ "sha256VerifiedOn": "2026-10-13",
232
+ "moveTo": "apktool",
233
+ "saveAs": "apktool"
234
+ }
235
+ ],
236
+ "darwin-arm64": [
237
+ {
238
+ "id": "wrapper",
239
+ "version": "master",
240
+ "url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/osx/apktool",
241
+ "format": "file",
242
+ "size": 2672,
243
+ "sha256": "d0c86f0475ed73bfffa6ac39faa69de7aa2dd40ff5e7e6540f82002be3a38f0b",
244
+ "sha256Source": "measured on 2026-10-13 from the osx wrapper",
245
+ "sha256VerifiedOn": "2026-10-13",
246
+ "moveTo": "apktool",
247
+ "saveAs": "apktool"
248
+ }
249
+ ],
250
+ "linux-x64": [
251
+ {
252
+ "id": "wrapper",
253
+ "version": "master",
254
+ "url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool",
255
+ "format": "file",
256
+ "size": 2648,
257
+ "sha256": "a851371023c43ca9b9a30218f87f9fd536304283de7b85447183ae811dfd033c",
258
+ "sha256Source": "measured on 2026-10-13 from the linux wrapper",
259
+ "sha256VerifiedOn": "2026-10-13",
260
+ "moveTo": "apktool",
261
+ "saveAs": "apktool"
262
+ }
263
+ ],
264
+ "linux-arm64": [
265
+ {
266
+ "id": "wrapper",
267
+ "version": "master",
268
+ "url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool",
269
+ "format": "file",
270
+ "size": 2648,
271
+ "sha256": "a851371023c43ca9b9a30218f87f9fd536304283de7b85447183ae811dfd033c",
272
+ "sha256Source": "measured on 2026-10-13 from the linux wrapper",
273
+ "sha256VerifiedOn": "2026-10-13",
274
+ "moveTo": "apktool",
275
+ "saveAs": "apktool"
276
+ }
277
+ ]
278
+ }
279
+ },
280
+ "probe": { "argv": ["{jvm}", "-jar", "{vendor}/apktool/apktool.jar", "--version"], "expect": { "regex": "\\d+\\.\\d+" } }
281
+ },
282
+ "jadxHeadlessMcp": {
283
+ "description": "REA validates this exact jar's bridge classes; it is not interchangeable with a plain jadx build.",
284
+ "kind": "file",
285
+ "license": "Apache-2.0",
286
+ "requires": ["jdk"],
287
+ "probe": { "file": "{vendor}/jadx/jadx-headless-mcp-0.7.1-all.jar" },
288
+ "entries": {
289
+ "all": [
290
+ {
291
+ "id": "jar",
292
+ "version": "0.7.1",
293
+ "url": "https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar",
294
+ "format": "file",
295
+ "size": 42619858,
296
+ "sha256": "6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75",
297
+ "sha256Source": "Vendor-published sidecar asset releases/download/v0.7.1/jadx-headless-mcp.sha256 (98 bytes, one line); the value was independently reproduced here by hashing the downloaded 42,619,858-byte jar. 0.7.1 is the newest release and REA requires exactly this bridge build: the project's own build.gradle.kts documents that the fat jar must be shaded with duplicatesStrategy INCLUDE, otherwise only KotlinMetadataPlugin registers and every APK resolves to a single R class. Versions 0.6.0 and earlier predate that fix.",
298
+ "sha256VerifiedOn": "2026-10-13",
299
+ "moveTo": "jadx",
300
+ "saveAs": "jadx-headless-mcp-0.7.1-all.jar"
301
+ }
302
+ ]
303
+ }
304
+ },
305
+ "androidPlatformTools": {
306
+ "description": "adb, for REA's device tools. Optional: without a device these tools simply report no devices.",
307
+ "kind": "archive",
308
+ "license": "Android SDK Terms (Google)",
309
+ "requires": [],
310
+ "optional": true,
311
+ "unpack": { "stripRootComponents": 1, "moveTo": "platform-tools" },
312
+ "probe": { "argv": ["{vendor}/platform-tools/adb{exe}", "version"], "expect": { "contains": "Android Debug Bridge" } },
313
+ "sources": {
314
+ "win32-x64": {
315
+ "version": "latest (37.0.1)",
316
+ "url": "https://dl.google.com/android/repository/platform-tools-latest-windows.zip",
317
+ "format": "zip",
318
+ "size": 8044989,
319
+ "sha256": "45f4d63113e895ebde0c90f194099a4676b6ac653bd28d54314a9e022bbc1a99",
320
+ "sha256Source": "Google publishes no checksum for this rolling asset; measured on 2026-10-13 and the archive verified readable (15 entries incl. adb.exe). The archive's own platform-tools/source.properties reports Pkg.Revision=37.0.1, so the real pin is 'platform-tools 37.0.1'. Google's repository2-3.xml carries size+checksum for versioned archives if a vendor hash is ever required.",
321
+ "sha256VerifiedOn": "2026-10-13",
322
+ "note": "Rolling 'latest' URL pinned by observation; setup re-verifies against this value and reports drift. Android SDK components are not OSI-licensed and Google restricts redistribution, so this is fetched from Google at install time and never re-hosted. Also contains fastboot, sqlite3, mke2fs and the USB API DLLs."
323
+ },
324
+ "darwin-x64": {
325
+ "version": "latest",
326
+ "url": "https://dl.google.com/android/repository/platform-tools-latest-darwin.zip",
327
+ "format": "zip",
328
+ "size": 16110554,
329
+ "sha256": "ee39ad5967e95c2a07f04dbcbde96b1a0c916ba376096db5d2f498b7727a5d1d",
330
+ "sha256Source": "Google publishes no checksum for this rolling asset; measured on 2026-10-13 by downloading the archive and hashing it locally (16,110,554 bytes)",
331
+ "sha256VerifiedOn": "2026-10-13",
332
+ "note": "Rolling 'latest' URL pinned by observation; setup re-verifies against this value and reports drift."
333
+ },
334
+ "darwin-arm64": {
335
+ "version": "latest",
336
+ "url": "https://dl.google.com/android/repository/platform-tools-latest-darwin.zip",
337
+ "format": "zip",
338
+ "size": 16110554,
339
+ "sha256": "ee39ad5967e95c2a07f04dbcbde96b1a0c916ba376096db5d2f498b7727a5d1d",
340
+ "sha256Source": "Same darwin archive as x64 (measured 2026-10-13); it serves Intel and Apple silicon.",
341
+ "sha256VerifiedOn": "2026-10-13",
342
+ "note": "The same darwin archive serves Intel and Apple silicon."
343
+ },
344
+ "linux-x64": {
345
+ "version": "latest",
346
+ "url": "https://dl.google.com/android/repository/platform-tools-latest-linux.zip",
347
+ "format": "zip",
348
+ "size": 9054187,
349
+ "sha256": "d230f13842f60f782a8645f9c813f8f845bf36089ea7289f28c48f17979313f1",
350
+ "sha256Source": "Google publishes no checksum for this rolling asset; measured on 2026-10-13 by downloading the archive and hashing it locally (9,054,187 bytes)",
351
+ "sha256VerifiedOn": "2026-10-13",
352
+ "note": "The same linux archive serves x86-64 and arm64."
353
+ },
354
+ "linux-arm64": {
355
+ "version": "latest",
356
+ "url": "https://dl.google.com/android/repository/platform-tools-latest-linux.zip",
357
+ "format": "zip",
358
+ "size": 9054187,
359
+ "sha256": "d230f13842f60f782a8645f9c813f8f845bf36089ea7289f28c48f17979313f1",
360
+ "sha256Source": "Same linux archive as x64 (measured 2026-10-13).",
361
+ "sha256VerifiedOn": "2026-10-13"
362
+ }
363
+ }
364
+ },
365
+ "reaAgents": {
366
+ "description": "The REA MCP server itself, installed into vendor/node_modules so the bundle owns the runtime copy. The resolved entry point is vendor/node_modules/rea-agents/scripts/rea.mjs.",
367
+ "kind": "npm",
368
+ "license": "MIT",
369
+ "requires": ["node"],
370
+ "unpack": { "moveTo": "rea-agents" },
371
+ "probe": {
372
+ "argv": ["{node}", "{vendor}/node_modules/rea-agents/scripts/rea.mjs", "--version"],
373
+ "expect": { "regex": "^6\\.4\\.0" }
374
+ },
375
+ "sources": {
376
+ "npm": {
377
+ "version": "6.4.0",
378
+ "registry": "https://registry.npmjs.org/rea-agents",
379
+ "integrity": "npm's own dist.integrity is the authoritative check for this tarball: the registry tarball ships NO node_modules (verified: 1161 entries, 0 under node_modules), so setup resolves the dependency closure with `npm install --ignore-scripts` instead of unpacking a tarball.",
380
+ "note": "`just unpacking the published tarball cannot work` is a verified fact, not a caution: the CLI would fail with ERR_MODULE_NOT_FOUND on zod."
381
+ }
382
+ }
383
+ }
384
+ },
385
+ "notProvisionable": {
386
+ "$comment": "Upstream host gates. Listed so the setup report can explain an unavailable capability instead of silently omitting it.",
387
+ "hopper": "macOS only (HOPPER_LAUNCHER_PATH).",
388
+ "ida": "requires a user-owned IDA plus REA_IDA_MCP_CONFIG; REA never launches IDA.",
389
+ "firmware": "binwalk/unblob are Linux-only (REA_BINWALK_COMMAND / REA_UNBLOB_COMMAND).",
390
+ "wakaru": "Linux x64 only (REA_WAKARU_COMMAND).",
391
+ "pwntools": "Linux x64 only (REA_PWNTOOLS_PYTHON) — ELF layout and recorded-crash tools.",
392
+ "pwndbg": "Linux/macOS only (gdb + REA_PWNDBG_GDB).",
393
+ "mitmdump": "Linux only, native mitmproxy captures (REA_MITMDUMP_COMMAND); HAR needs no engine.",
394
+ "appleToolchain": "macOS only (xcrun, plutil, assetutil, hdiutil, lldb).",
395
+ "processCapture": "Linux/macOS PTY only.",
396
+ "ilspycmd": "Optional .NET decompiler (REA_ILSPY_CMD_PATH). Only wired when a runnable ilspycmd already exists; the static managed provider works without it.",
397
+ "browser": "Reused from the host (REA_BROWSER_EXECUTABLE). Never downloaded by this bundle."
398
+ }
399
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-rea",
3
- "version": "1.0.6",
4
- "description": "Registers the upstream REA MCP server into DeepSeek Harness as mcp__rea__* tools — a third-party adapter, analysis is upstream's. · 把上游 REA 的 MCP 服务接入 DeepSeek Harness,提供 mcp__rea__* 工具(第三方适配,分析能力属上游)。",
3
+ "version": "2.0.0",
4
+ "description": "One-plugin bundle: provisions Node/REA/JDK/Ghidra for you, then registers the upstream REA MCP server as mcp__rea__* tools. · 一体包:自动准备 Node/REA/JDK/Ghidra,并把上游 REA 的 MCP 服务接入 DeepSeek Harness。",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "czcdr",
@@ -27,8 +27,16 @@
27
27
  "ghidra",
28
28
  "binary-analysis"
29
29
  ],
30
+ "bin": {
31
+ "rea-bundle-setup": "./scripts/setup.mjs",
32
+ "rea-bundle-status": "./bin/rea-shim.mjs"
33
+ },
30
34
  "files": [
31
35
  "cordis.patch.yml",
36
+ "bin/",
37
+ "scripts/",
38
+ "manifest/",
39
+ "docs/bundle-design.md",
32
40
  "README.md",
33
41
  "README.zh-CN.md",
34
42
  "LICENSE",
@@ -44,6 +52,17 @@
44
52
  }
45
53
  },
46
54
  "scripts": {
47
- "check-updates": "powershell -NoProfile -File ./check-updates.ps1"
55
+ "check-updates": "powershell -NoProfile -File ./check-updates.ps1",
56
+ "bundle:setup": "node ./scripts/setup.mjs",
57
+ "bundle:status": "node ./bin/rea-shim.mjs --shim-status",
58
+ "bundle:dry-run": "node ./scripts/setup.mjs --reuse --dry-run",
59
+ "bundle:audit": "node ./scripts/audit-manifest.mjs",
60
+ "bundle:test": "node --test ./scripts/setup.test.mjs"
61
+ },
62
+ "engines": {
63
+ "node": "^22.19.0 || ^24.11.0 || >=26.0.0"
64
+ },
65
+ "publishConfig": {
66
+ "registry": "https://registry.npmjs.org/"
48
67
  }
49
68
  }