dsh-plugin-rea 1.0.6 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,139 @@
2
2
 
3
3
  本插件遵循语义化版本。版本号变化说明「配置组合」这层的变化,与 REA 自身的版本无关。
4
4
 
5
+ ## 2.0.1 — 2026-10-15
6
+
7
+ **仅文档与版式,无行为变更。**
8
+
9
+ - 两份 README 的**语言行正下方**现在先说明「这是哪个版本、1.x 与 2.x 各做什么」——此前版本说明排在
10
+ 首屏那张大截图之后,npm 页面首屏看不到,容易被误判成「README 没改」;对比表与升级章节位置不变。
11
+ - `docs/bundle-design.md` 的措辞跟上「`main` 已经是一体包线」这一事实(原先写的是「本分支新增、
12
+ `main` 不变」)。
13
+ - 为什么纯文档也发一个补丁版:npm 的包页渲染的是**发布进 tarball 的 README**,不重新发布就更新不了首屏。
14
+ ## 2.0.0 — 2026-10-15
15
+
16
+ **一体包线的首个正式版**(此前的 `2.0.0-bundle.1` / `2.0.0-bundle.2` 是它的两个预发布)。
17
+ `main` 现在就是这一条;1.x 的纯配置适配器保留在
18
+ [`v1` 分支](https://github.com/czcdr/dsh-plugin-rea/tree/v1),npm 上仍可装 `dsh-plugin-rea@1.0.6`。
19
+
20
+ ### 🔴 从 1.x 升级:先跑一次供给,再重启
21
+
22
+ 1.x 的行指向 PATH 上的 `rea mcp`;2.x 的行指向 `<DSH_HOME>/plugins/rea-bundle/bin/rea-shim.mjs`,
23
+ 而**那个文件只有 `setup.mjs` 会创建**。所以从 1.x 升上来后直接重启会:
24
+
25
+ - 让该行以 `MODULE_NOT_FOUND` 退出(harness 本身不受影响 —— `failOnStartupError: false` 是刻意的),
26
+ 于是 `mcp__rea__*` 工具消失,而且**不会得到任何修复提示**:会打提示的 shim 本身还不存在;
27
+ - 正确做法是在插件目录跑一次 `node scripts/setup.mjs`(或 `scripts\rea-bundle-bootstrap.ps1 -Reuse`),
28
+ 再重启 dsh,工具就回来了。此后每一次「状态缺失」都由 shim 自己发现并在后台补供给。
29
+
30
+ (如果你在 profile 的 `cordis.patch.yml` 里按 id 覆盖过 `mcp-rea`(例如钉 `cmd.exe /c rea mcp`),
31
+ 那条覆盖会继续生效,升级不影响你。)
32
+
33
+ ### 三个致命缺陷(按旧版说明走的用户每一步都会撞上)
34
+
35
+ - **MCP 行里的 `!!js` 标量没有加引号。** 裸标量同时含 `? ` 与 `: ` 时,YAML 把它读成**显式键值对**,
36
+ loader 拿到的是 mapping 而不是表达式节点。用 harness 自己的方言实测(`JSON_SCHEMA` + `app.asar` 里的
37
+ `!!js` 类型定义),`args[0]` 变成 `{"[object Object]": "..."}` —— 这一行**从来没有可能启动**。
38
+ 现改为双引号标量,内部 JS 字符串用单引号。
39
+ - **`DSH_HOME` 的兜底少了一层 `.dsh`。** 行回退到裸 home,而 shim、`setup.mjs`、`bootstrap.ps1` 都回退到
40
+ `<home>/.dsh`。全归档扫描确认 **DSH 从不设置 `DSH_HOME`**(宿主进程内 0 处写入,只在 shell 工具子进程的
41
+ 执行覆盖层注入),所以这是**默认路径**而不是边缘情况:行永远指向供给不会写的位置。
42
+ - **镜像副本缺 manifest。** 镜像后的 `setup.mjs` 用它自己所在的位置推导 plugin root,于是 `loadManifest()`
43
+ 去读 `<bundle>/manifest/bundle.manifest.json` —— 一个只有镜像步骤才能创建的文件。由 shim 触发的后台供给
44
+ 会以裸 ENOENT 崩溃;`stdio: "ignore"` 让它完全不可见,而它已写下的 lock 又让重试被抑制六小时。
45
+
46
+ ### 🟡 一并修掉的
47
+
48
+ - **shim 改用自身位置定位 bundle。** MCP 子进程的环境会剥掉所有 `DSH_*`(大小写不敏感),所以自定义
49
+ `DSH_HOME` 时旧 shim 会去 `~/.dsh` 找 `bundle.json`、报"未供给",然后**在错位置再供给一次 800 MB**。
50
+ `bundleRoot()` 现在返回 `<shim 所在目录>/..`,与 `DSH_HOME` 是否可见无关。
51
+ - **新增镜像记录 `mirror.json`。** 真源运行会写下插件 checkout 的位置;镜像副本据此回到真源,插件升级后
52
+ 不会永远用旧 manifest 供给。记录失效(目录被移动/删除)时优雅回退到本地副本。
53
+ - **空值环境变量。** `REA_BUNDLE_ROOT=""` / `DSH_HOME=""` 原先会把 root 算成 `""` 或相对路径 —— 前者
54
+ `mkdir ''` 崩在任何日志之前,后者把 bundle 装进 cwd。现在统一 trim 后回退;`--root`/`--cache` 一律
55
+ `path.resolve`(相对 root 会让 `bundle.json` 记下相对路径,shim 随即误判组件"已消失"并重新供给)。
56
+ - **`fail()` 的修复命令**不再指向"存在但必然崩溃"的镜像副本;没有可信副本时给出明确指引。
57
+ - **`check-updates.ps1`** 增加「镜像 manifest 是否就位」检查,指向上面那条升级路径。
58
+ - **shim 的两份副本各自解析出正确的 bundle root。** 上面那条"用自身位置"只在**镜像副本**上成立:
59
+ 插件 checkout 里的那份是插件而不是 bundle,于是 `npm run bundle:status` 会在已供给的机器上报
60
+ "未供给",而裸跑 `node bin/rea-shim.mjs mcp` 会把约 800 MB 下载进**插件目录**。现在用
61
+ `cordis.patch.yml`(只有 checkout 会带)区分两者:镜像副本用自身位置(MCP 子进程里读不到
62
+ `DSH_HOME`),checkout 副本按与 setup 相同的规则解析。setup 结尾打印的 `status:` 命令也改为指向
63
+ 镜像副本,这样它对自定义 `--root` 同样成立。
64
+ - **裸跑 shim 不再启动任何东西。** `package.json` 把这个文件暴露为 `rea-bundle-status` bin,而 npm 的 bin
65
+ 转发只能原样传参、**无法追加 flag**,于是"裸跑一个叫 status 的命令"会掉进启动路径:在已供给的机器上
66
+ 启动 REA,在未供给的机器上**开始约 800 MB 下载** —— 与这个名字承诺的正好相反。现在无参数时打印用法并
67
+ 退出(真实退出码 0),MCP 行不受影响,因为它始终显式传 `mcp`。
68
+ - **`--dry-run` 在裸机上误报失败并退出 1。** 计划项返回 `{ planned: true }`(没有 `path`),而只有真实
69
+ `path` 才被计入 `context.resolved`,于是每个依赖前序组件的计划项都被判成"依赖不可用":在**裸机** —— 恰恰
70
+ 是 README 建议先跑 `--dry-run` 的那台机器 —— 整条链报 4 条 FAIL 且退出码为 1。现在计划项同样满足下游的
71
+ `requires`。(本机 `--reuse` 会 adopt 成功,所以这个缺陷只在干净机器上可见。)
72
+ - **`release.yml` 的 npm 自更新**由 `npm@latest` 钉到 `npm@^11`:发布当天不该让流水线获得新行为。
73
+ - **`check-updates.ps1` 的 engines 判定**:不认识的 pattern(`22.x`、`>=22` 之类)此前返回"不满足",误导
74
+ 用户去升级一个本来就满足的 Node;现在返回"无法解析"。三条实测:`22.x` → 无法解析、`>=99.0.0` → 不满足、
75
+ `^24.11.0` → 满足。
76
+ - **README 的「先装插件也可以」改为诚实描述。** 发布的行指向的镜像 shim **由 setup 创建**,所以在第一次
77
+ 供给之前该行以 `MODULE_NOT_FOUND` 退出 —— 没有任何东西能启动后台供给。首次必须手动跑一次
78
+ `setup.mjs`,此后才由 shim 自动补供给。
79
+
80
+ ### 🔴 真机验证(Windows 11 x64,Electron 宿主 0.2.0-rc.2)当场抓到的
81
+
82
+ - **bootstrap 的便携 Node 路径必崩。** `$nodeFolder` 被连续剥了两次扩展名:`node-v24.21.0-win-x64.zip`
83
+ → `node-v24.21`,而官方 zip 解出来的目录是 `node-v24.21.0-win-x64`。于是在**没有任何 Node 的机器**上
84
+ —— 也就是这个脚本唯一的读者 —— 它会先成功下载(35 MiB)、校验通过、解压完成,然后抛
85
+ `the Node zip did not contain node-v24.21`。本机有 Node,永远走不到这段分支,所以单测、审计、WSL 与
86
+ 沙箱端到端全都看不见它;真机复现(清空 PATH 模拟无 Node)一次即中。
87
+ 现在只剥一次,且解压后**接受任何真的带 `node.exe` 的目录**:已经付过下载代价的机器不该因为一次命名
88
+ 漂移而卡住。`-DryRun` 也会去认已经解压好的便携 Node(只认、不下载),这正是下面那条回归检查能
89
+ **零下载**判定它的原因。
90
+
91
+ ### 🟡 真机验证顺带修掉的(判据都来自仓库自身)
92
+
93
+ - **manifest 的 `optional: true` 被忽略。** `androidPlatformTools` 明写可选("没有设备时这些工具只是报告
94
+ 没有设备"),而 `setup.mjs` 里 `optional` 出现 **0 次**:dl.google.com 不可达时整轮 exit 1 —— 其余组件
95
+ 都供给好了,却因为 adb 失败。现在可选组件的失败**照实报告**(`optional component(s) unavailable`),但
96
+ 不计入退出码、也不写进 `bundle.json`(shim 因此不会导出 `REA_ADB_PATH`);被别的组件 `requires` 的组件
97
+ 永不被当作可选,免得"依赖失败却退出 0"。
98
+ - **`--reuse` 采纳 Node 只看"文件存在"。** manifest 的 `probe.why` 明写"reuse 路径必须在这里拒绝",但旧代码
99
+ 只做 `fileExists`:PATH 上第一个是 Node 20 的机器会把它记进 `bundle.json`,失败推迟到 REA 启动才以难读的
100
+ 形式出现。现在每个候选(`REA_BUNDLE_NODE` 与**每一个** PATH 命中)都要先过 manifest 的版本闸门;跑不起来
101
+ 的候选(Windows 上 `.cmd`/`.bat` 包装器即 EINVAL)同样拒绝,并把原因打出来。
102
+ - **README:apktool 在 Windows 上实际不可用**(能力矩阵新增一行)——上游 `execFile` 不加 shell 直接起该命令,
103
+ 而 Windows 不能这样执行 `.bat`/`.cmd`。setup 的 apktool 探针走 `java -jar`(绕过包装器)所以报 `[ok]`,
104
+ 落差只在运行时暴露。
105
+ - **README:Electron 宿主不能用 `dsh plugin --profile desktop …`**(CLI 硬编码拒绝 `desktop` 这个 profile 名),
106
+ 改为指向应用内插件管理器。
107
+
108
+ ### 回归防护
109
+
110
+ - `audit-manifest.mjs` 新增六项,全部做过反向验证(**故意写坏 6/6 都被抓住**):`!!js` 裸标量 mapping
111
+ 检测、`DSH_HOME` 三态(未设/空/已设)求值一致性、README 与 patch 表达式不得漂移、镜像清单必须含
112
+ manifest、镜像清单**不得**携带 `cordis.patch.yml`(它是 shim 区分两份副本的判据,被镜像进去会让镜像
113
+ 副本退回 `DSH_HOME` 规则)、**两份 README 结构平行**(节/子节数量,双语靠手工同步)。另外把
114
+ 「`DSH_HOME`/`USERPROFILE`/`HOME` 全空 → 根路径」这个**已知无害行为**钉成 warn(此前无覆盖):该情形下
115
+ 该行报 `MODULE_NOT_FOUND`,不写文件、不下载,harness 照常。
116
+ - `validate.yml`:YAML 步骤注册 `!!js` 标量构造器(原先 `safe_load` 直接拒绝这个仓库要发布的行 ——
117
+ **CI 一直是红的**);行契约步骤断言 `args[0]` 是标量、不是 mapping;新增 `bundle:test` 与 `bundle:audit` 两步。
118
+ - `verify-branch.ps1`:dry-run 断言改为**不带 `--reuse`**(旧写法在本机 adopt 成功时正好掩盖了上面那个
119
+ 裸机缺陷),并新增"不得出现依赖不可用"一条;它现在也检查**自己**的 BOM,因为它是被编辑得最频繁的脚本。
120
+ - `verify-branch.ps1` 新增第 8 节:**空 PATH + 官方 zip 布局**下,bootstrap 必须认出已解压的便携 Node、
121
+ 且不得计划下载(`-DryRun`,零下载、零副作用)。反向验证:把该脚本 checkout 回 HEAD 版(双重剥离)
122
+ → 两条断言立刻变红(`2 CHECK(S) FAILED`/`plan: download and extract ...`)。
123
+ - 单元测试新增四条**真的去 spawn shim**的用例(镜像布局、checkout 布局、`REA_BUNDLE_ROOT` 覆盖、裸跑只
124
+ 打印用法),并逐条做过反向验证:把 shim 改回"一律用自身位置"时 checkout 那条会红;去掉裸跑守卫时裸跑
125
+ 那条会红(后者自带桩保护,即使回退也只会跑到桩、不会下载)。
126
+ - 单元测试里的 POSIX 退化断言加平台守卫(`EXE` 为空时 `endsWith("java")` 恒真,在 Linux 上必红;
127
+ WSL 实测修前 12/13、修后 17/17)。
128
+ - 单元测试新增四条(共 **21** 条;Windows 21/21、WSL 真 Linux 21/21):`--reuse` 仍采纳兼容 Node、拒绝
129
+ manifest 版本闸门不认的候选(用**改了正则的合成 manifest** 判定,跨平台确定,不依赖机器上恰好有什么)、
130
+ 拒绝跑不起来的候选、以及"可选组件失败不致命"。反向验证:把可选记账改回旧行为 → 那条立刻变红;把复用
131
+ 检查改回旧行为 → 另两条立刻变红;两次都确认了源码按字节复原。
132
+
133
+ ### 升级说明
134
+
135
+ 已部署过 `2.0.0-bundle.1` 的机器**无法自愈**(旧镜像既缺 manifest 也没有真源记录):请从**插件目录**
136
+ 重跑一次 `node scripts/setup.mjs`。
137
+
5
138
  ## 1.0.6 — 2026-10-13
6
139
 
7
140
  文档结构调整,无行为变更。
package/README.md CHANGED
@@ -4,27 +4,37 @@
4
4
 
5
5
  **English** · [简体中文](README.zh-CN.md)
6
6
 
7
+ > **Which version is this? 2.x — and `main` is it.** 2.x provisions the runtime itself (Node, the REA CLI, a
8
+ > JDK, Ghidra) and then wires the row: install the plugin, run `scripts/setup.mjs` once, restart dsh.
9
+ > **1.x** is the older configuration-only adapter — it expects `rea` on PATH and Ghidra installed by you —
10
+ > kept on the [`v1` branch](https://github.com/czcdr/dsh-plugin-rea/tree/v1) and on npm as `dsh-plugin-rea@1.0.6`.
11
+ > Full comparison: [Two lines: 1.x and 2.x](#two-lines-1x-and-2x) · upgrading: [Upgrading from 1.x](#upgrading-from-1x).
12
+
7
13
  Brings the upstream **[REA — Reverse Engineer Anything](https://github.com/morluto/rea)** MCP server into
8
14
  **DeepSeek Harness** (dsh), so its tools show up in a session as `mcp__rea__*`.
9
- ![dsh-plugin-rea: a session calls the local rea mcp through the official MCP client row, and the Evidence comes back](assets/screenshot-1.png)
15
+
16
+ ![dsh-plugin-rea: the session calls the official MCP client row, which launches the bundle's shim; the shim starts the provisioned REA and the Evidence comes back](assets/screenshot-1.png)
10
17
 
11
18
  ## What this is
12
19
 
13
- A **configuration-only Cordis bundle**. `package.json` declares `dsh.bundle.patch`, and that patch inserts
14
- one row of the official MCP client plugin (`@deepseek-ai/dsh-mcp-client`) pointing at the `rea mcp` stdio
15
- server already installed on your machine. There is no analysis code here.
20
+ A **Cordis bundle with a provisioning layer**. `package.json` declares `dsh.bundle.patch`, and that patch
21
+ inserts one row of the official MCP client plugin (`@deepseek-ai/dsh-mcp-client`). The row launches
22
+ `bin/rea-shim.mjs` with the running Node executable; the shim reads the machine-local state that
23
+ `scripts/setup.mjs` wrote and starts the REA CLI it provisioned. There is no analysis code here, and the
24
+ published patch contains no machine path.
16
25
 
17
26
  > **Third-party adapter.** This repository is not part of REA, ships none of REA's code, and is not
18
- > maintained by REA's authors. It composes one configuration row.
27
+ > maintained by REA's authors. It composes one configuration row and fetches upstream's own releases.
19
28
  > Upstream: [github.com/morluto/rea](https://github.com/morluto/rea) (MIT) ·
20
29
  > [docs](https://github.com/morluto/rea#what-you-can-analyze) ·
21
- > [中文文档](https://github.com/morluto/rea/blob/main/README_zh.md#可以分析什么) ·
22
30
  > [rea.tools](https://rea.tools/) · npm package `rea-agents`.
23
31
  > REA's own problems go to its tracker; **this plugin's** problems go to
24
32
  > [this repository's issues](https://github.com/czcdr/dsh-plugin-rea/issues).
25
33
 
26
- **It adds exactly two things:** the tool registration under the `mcp__rea__*` names, and a way to scope the
27
- engine environment (`GHIDRA_INSTALL_DIR`, …) to that MCP child process instead of the whole harness.
34
+ **It adds exactly three things:** the tool registration under the `mcp__rea__*` names, the per-machine
35
+ engine environment (`GHIDRA_INSTALL_DIR`, `JAVA_HOME`, `REA_ADB_PATH`, `REA_APKTOOL_COMMAND`,
36
+ `REA_JADX_MCP_JAR`, `REA_BROWSER_EXECUTABLE`, …) scoped to that MCP child instead of the whole harness, and
37
+ the provisioning that puts those components on disk.
28
38
 
29
39
  ## What it can and cannot do
30
40
 
@@ -43,73 +53,120 @@ The analysis is upstream's; this section only restates upstream's own contract s
43
53
  re-implementation, security review, migration reference. To obtain actual source code, use the legitimate
44
54
  route — the upstream repository, vendor authorisation, or the vendor's own SDK.
45
55
 
46
- ## Verified against
56
+ **What provisioning cannot buy you.** These are upstream host gates, not settings this plugin forgot:
47
57
 
48
- Author's machine, 2026-10-12/13:
49
-
50
- | Component | Version |
58
+ | Capability | Gate |
51
59
  |---|---|
52
- | REA (`rea-agents`) | **6.4.0** |
53
- | DeepSeek Harness | desktop runtime **0.2.0-rc.2**, with its bundled dsh CLI 0.1.7-rc.2 |
54
- | `@deepseek-ai/dsh-mcp-client` (row provider) | bundled by the harness; npm latest `0.0.1-rc.1` |
55
- | Ghidra | 12.1.4 (REA currently accepts 12.1.x) |
56
- | Node.js | 24.21.0 (REA engines: `^22.19.0 \|\| ^24.11.0 \|\| >=26.0.0`) |
57
- | Host | Windows 11 x64 |
58
-
59
- The shipped row was exercised live: `list_documents` answered through the `cmd.exe` form, and `open_binary`
60
- completed with the `ghidra` provider while the engine directory came only from a profile-level override.
60
+ | Hopper provider | macOS only |
61
+ | IDA provider | needs your own IDA and `REA_IDA_MCP_CONFIG`; REA never launches IDA |
62
+ | firmware `binwalk` / `unblob` | Linux only |
63
+ | Wakaru JS recovery, pwntools ELF/recorded-crash paths | Linux x64 only |
64
+ | pwndbg / gdb core context | Linux, macOS |
65
+ | native mitmproxy captures | Linux (`REA_MITMDUMP_COMMAND`); HAR needs no engine |
66
+ | Apple toolchain (Mach-O, plist, asset catalog, LLDB) | macOS only |
67
+ | `capture_process_scenario` | Linux/macOS PTY |
68
+ | apktool (Android resource decoding) | available only when `REA_APKTOOL_COMMAND` points at something Node can **execute directly**: upstream `execFile`s that command without a shell, and Windows cannot execute a `.bat`/`.cmd` that way (EINVAL). This bundle wires the official `apktool.bat` as upstream documents, so on Windows the launcher reports no version and the capability stays unavailable; JADX (a jar) is unaffected |
69
+
70
+ ## Two lines: 1.x and 2.x
71
+
72
+ | | **2.x** — this line, `main` | **1.x** — [`v1`](https://github.com/czcdr/dsh-plugin-rea/tree/v1) |
73
+ |---|---|---|
74
+ | What it is | the adapter **plus provisioning**: it prepares Node, the REA CLI, a JDK and Ghidra, then wires the row | configuration-only adapter: one MCP row pointing at a `rea` you installed yourself |
75
+ | What you need first | nothing for the runtime — provisioning fetches what is missing (with `--reuse` it adopts an existing Ghidra/JDK instead) | Node.js on REA's engines, `npm i -g rea-agents`, and Ghidra 12.1.x for native analysis |
76
+ | First run | run `scripts/setup.mjs` (or the bootstrap) once, then restart dsh | nothing to run |
77
+ | The row launches | the running Node executable plus `<DSH_HOME>/plugins/rea-bundle/bin/rea-shim.mjs`, which reads that machine's state | `cmd.exe /c rea mcp` on Windows, `rea mcp` on macOS/Linux |
78
+ | Machine state | `<DSH_HOME>/plugins/rea-bundle/` — `bundle.json`, `vendor/`, `cache/` | your own REA and Ghidra installs |
79
+ | Install | `dsh plugin --profile <profile> add dsh-plugin-rea` (npm `latest` ≥ 2.0.0) | `… add dsh-plugin-rea@1.0.6`, or `github:czcdr/dsh-plugin-rea#v1.0.6` |
80
+
81
+ Both lines ship the **same patch shape** — one `insert` row of `@deepseek-ai/dsh-mcp-client` — and offer the
82
+ same `mcp__rea__*` surface; they differ in who prepares the runtime. Moving from 1.x to 2.x takes two
83
+ commands: see [Upgrading from 1.x](#upgrading-from-1x).
61
84
 
62
85
  ## Install
63
86
 
64
- **Prerequisites**
87
+ **Nothing to install first.** On a bare Windows x64 machine:
65
88
 
66
- - Node.js on a version REA accepts (`^22.19.0 || ^24.11.0 || >=26.0.0`).
67
- - The REA CLI installed globally: `npm i -g rea-agents`.
68
- - For native analysis, Ghidra **12.1.x** (Hopper / IDA are alternatives under their own licences).
69
- Static JavaScript/Electron and .NET analysis need no engine at all.
89
+ ```
90
+ powershell -ExecutionPolicy Bypass -File scripts\rea-bundle-bootstrap.ps1 -Reuse
91
+ ```
70
92
 
71
- **The plugin**
93
+ `-Reuse` adopts a Ghidra/JDK you already have; omit it to download both (about 775 MB from the official
94
+ releases, checksum-verified). Then install the plugin for your profile:
72
95
 
73
96
  ```
74
- dsh plugin --profile <profile> add github:czcdr/dsh-plugin-rea
75
- # or, from npm:
76
97
  dsh plugin --profile <profile> add dsh-plugin-rea
98
+ # or pin this release instead of tracking npm latest:
99
+ dsh plugin --profile <profile> add github:czcdr/dsh-plugin-rea#v2.0.0
77
100
  ```
78
101
 
79
- A prebuilt tarball is attached to [Releases](https://github.com/czcdr/dsh-plugin-rea/releases)
80
- (version-free asset name, so the link keeps resolving).
102
+ > **On an Electron host (the DeepSeek Harness desktop app) the profile is owned by the application.**
103
+ > `dsh plugin --profile desktop …` is refused by the CLI (`profile "desktop" is managed exclusively by
104
+ > the Electron application`) — install or update through the in-app plugin manager instead. The CLI form
105
+ > above is for a profile you created yourself with the CLI under another name.
81
106
 
82
- ## Configure — usually nothing
107
+ If Node is already installed, you can skip the bootstrap and run provisioning directly:
83
108
 
84
- The shipped row carries **no machine-specific path**:
109
+ ```
110
+ node scripts/setup.mjs --reuse # or --dry-run to see the plan first
111
+ ```
85
112
 
86
- - Windows: `command: cmd.exe`, `args: ['/c', 'rea', 'mcp']` — the npm shim is a `.cmd`, so a shell is
87
- required.
88
- - macOS/Linux: `command: rea`, `args: ['mcp']` — see
89
- [`examples/cordis.patch.posix.yml`](examples/cordis.patch.posix.yml).
113
+ Installing the plugin **first** also works, but it takes two steps. The row resolves to
114
+ `<DSH_HOME>/plugins/rea-bundle/bin/rea-shim.mjs`, and that file is created by setup — so until the first
115
+ provisioning run the row exits with `MODULE_NOT_FOUND`, and nothing is there to start a background
116
+ provisioning (the harness itself is unaffected: `failOnStartupError: false` is deliberate). Run
117
+ `node scripts/setup.mjs` once; from then on the shim is in place, and every later missing state is picked up
118
+ by the shim itself, which provisions in the background.
90
119
 
91
- The only optional piece is the analysis engine. REA reads `GHIDRA_INSTALL_DIR` from its environment, so
92
- either:
120
+ ## Configure — nothing
93
121
 
94
- 1. set it as an **OS-level environment variable** and (re)start the harness, or
95
- 2. pin it for the MCP child only — see
96
- [`examples/cordis.patch.with-env.yml`](examples/cordis.patch.with-env.yml).
122
+ The shipped row carries **no machine path**:
97
123
 
98
- Either way no literal path is needed: the client hands the parent environment to the child (minus names
99
- that look like secrets), and the patch dialect evaluates `!!js` expressions, so
100
- `GHIDRA_INSTALL_DIR: !!js process.env.GHIDRA_INSTALL_DIR` also works.
124
+ ```yaml
125
+ - insert:
126
+ - id: mcp-rea
127
+ name: '@deepseek-ai/dsh-mcp-client'
128
+ config:
129
+ serverName: rea
130
+ transport: stdio
131
+ command: !!js process.execPath
132
+ args:
133
+ - !!js "(process.env.DSH_HOME && process.env.DSH_HOME.trim() !== '' ? process.env.DSH_HOME : (process.env.USERPROFILE || process.env.HOME || '') + '/.dsh') + '/plugins/rea-bundle/bin/rea-shim.mjs'"
134
+ - mcp
135
+ env:
136
+ ELECTRON_RUN_AS_NODE: '1'
137
+ failOnStartupError: false
138
+ ```
101
139
 
102
- **Keep machine-specific values out of the plugin.** Override the row from your own profile's
103
- `cordis.patch.yml` (id-targeted config override). That file is machine-local and never published:
140
+ Three details in that row are load-bearing, and all three were bugs first:
141
+
142
+ - **The `!!js` scalar is quoted.** A bare scalar containing `? ` and `: ` is not one scalar: YAML reads it as an explicit key/value pair, so the loader receives a *mapping* where an expression belongs. Measured against the harness's own dialect (`JSON_SCHEMA` plus the `!!js` type from `app.asar`), the unquoted form made `args[0]` into `{"[object Object]": "..."}` — an object the row cannot launch with. Quoting removes the ambiguity, and the JS string literals inside then use single quotes.
143
+ - **Forward slashes, never backslashes.** A single-quoted YAML scalar keeps `\` literal, and JavaScript then consumes it: `'\plugins'` is not a path but the BEL control character, `'\rea-bundle'` becomes a carriage return and `'\bin'` a backspace — the path is destroyed silently. Forward slashes work on Windows and on POSIX.
144
+ - **The `DSH_HOME` fallback is `<home>/.dsh`, not `<home>`.** The shim, `setup.mjs` and `bootstrap.ps1` all resolve an unset `DSH_HOME` to `<home>/.dsh/...`; a fallback that stopped one level higher made the row point at a file provisioning never writes — invisible on any machine where `DSH_HOME` happens to be set.
145
+ - **No `path`/`require` in a `!!js` expression**: `ctx` is the loader scope, so the DSH_HOME fallback is written as plain concatenation.
146
+
147
+ `npm run bundle:audit` enforces the first three: it evaluates this exact expression with `DSH_HOME` unset, empty and set, and rejects a bare scalar that YAML would read as a mapping.
148
+
149
+ `ELECTRON_RUN_AS_NODE` only matters if the harness itself runs on Electron; it is harmless for a plain Node host, and stating it explicitly beats relying on it surviving the child-env scrub.
150
+
151
+ `command` is the running Node executable and `args[0]` is computed from `DSH_HOME`, so the same patch works
152
+ on Windows, macOS and Linux without a PATH entry. Everything machine-specific lives in
153
+ `<DSH_HOME>/plugins/rea-bundle/bundle.json`, written by setup and readable with:
104
154
 
105
- ```yaml
106
- - id: mcp-rea
107
- config:
108
- env:
109
- GHIDRA_INSTALL_DIR: '<your Ghidra 12.1.x directory>'
110
155
  ```
156
+ node bin/rea-shim.mjs --shim-status # resolved paths, versions, verification state
157
+ node bin/rea-shim.mjs --shim-print-env # the environment the MCP child will see
158
+ ```
159
+
160
+ Both commands answer with the same bundle whether you run them from the plugin directory (the checkout copy)
161
+ or from `<DSH_HOME>/plugins/rea-bundle` (the mirrored copy): the checkout resolves the location by the same
162
+ rule setup uses, while the mirror uses its own position — it cannot rely on `DSH_HOME`, which the harness
163
+ strips from an MCP child's environment.
164
+
165
+ Setting `REA_BUNDLE_AUTO_PROVISION=0` disables the first-run background provisioning.
111
166
 
112
- Only one row may use `serverName: rea` in a profile.
167
+ To pin a different launch command on this machine (for example your own globally installed `rea`), override
168
+ the row from the profile's own `cordis.patch.yml` — that file is machine-local and never published. Only one
169
+ row may use `serverName: rea` in a profile.
113
170
 
114
171
  ## Verify
115
172
 
@@ -117,30 +174,83 @@ Ask for a `mcp__rea__*` call — `binary_session` reports the engine checks
117
174
  (`configuration` / `installation` / `headless` / `native_decompiler` / `java`), all of which should be
118
175
  `ok: true`. Then `open_binary` a PE, `binary_overview`, and `procedure_pseudo_code`.
119
176
 
177
+ Provisioning itself ends with two probes: `rea doctor --json` (reported, with unmet checks named) and the
178
+ exact shim launch the MCP row will use.
179
+
180
+ ## Verified against
181
+
182
+ Author's machine, 2026-10-13 / 14:
183
+
184
+ | Component | Version |
185
+ |---|---|
186
+ | REA (`rea-agents`) | **6.4.0** (170 tools) |
187
+ | DeepSeek Harness | desktop runtime **0.2.0-rc.2**, with its bundled dsh CLI 0.1.7-rc.2 |
188
+ | `@deepseek-ai/dsh-mcp-client` (row provider) | bundled by the harness |
189
+ | Ghidra | 12.1.4 |
190
+ | JDK | Temurin 21 (provisioned) / Microsoft OpenJDK 25.0.4 (adopted) |
191
+ | Node.js | 24.21.0 (REA engines: `^22.19.0 \|\| ^24.11.0 \|\| >=26.0.0`) |
192
+ | Host | Windows 11 x64 (build 26200) |
193
+
194
+ Exercised live: a clean `setup.mjs --reuse` run reached `setup complete`; an MCP `initialize` handshake
195
+ **through the shim with no `rea` on PATH** answered `serverInfo {name: rea, version: 6.4.0}`; and the whole
196
+ chain ran under the real desktop host — its own Electron binary launched
197
+ `<DSH_HOME>/plugins/rea-bundle/bin/rea-shim.mjs mcp`, the shim started the **vendored** REA, and a live tool
198
+ call reported `path_source: environment` for a path only that machine's `bundle.json` knows.
199
+
200
+ Not exercised end to end here, and therefore not claimed: the ~775 MB JDK + Ghidra download branch (planned
201
+ by `--dry-run`, but this machine adopted its existing installs with `--reuse`), and first runs on
202
+ macOS/Linux.
203
+
120
204
  ## Scope and caveats
121
205
 
122
- REA exposes 170+ tools at 6.4.0, so every session's tool list grows; the row sets
123
- `failOnStartupError: false` so a missing REA never blocks harness startup. Some REA tools launch processes,
124
- write files and access the network — DSH's permission policy applies. Analyse only artifacts you are
125
- authorized to inspect.
206
+ - REA exposes 170 tools at 6.4.0, so every session's tool list grows.
207
+ - Provisioning is honest about checksums: where a vendor publishes one it is pinned and verified; where none
208
+ exists (jadx-headless-mcp, the apktool wrapper, Android platform-tools, the rolling "latest" URLs) the
209
+ hash is **measured once and recorded**, and later drift is reported rather than silently accepted.
210
+ `bundle.json` lists which components are in that state under `unverifiedChecksums`.
211
+ - Downloads are slow on some networks (measured 0.35–0.54 MiB/s from the GitHub release CDN here, so Ghidra
212
+ alone takes ~25–45 min). Fetching is resumable: the cache is kept and re-verified, and a killed run
213
+ continues instead of restarting.
214
+ - Some REA tools launch processes, write files and access the network — DSH's permission policy applies.
215
+ Analyse only artifacts you are authorized to inspect.
216
+ - `scripts/setup.mjs` runs `npm install` for the REA CLI with `--ignore-scripts`: the published tarball
217
+ carries no `node_modules`, so a dependency closure must be resolved, and package lifecycle scripts are
218
+ skipped on purpose during provisioning.
126
219
 
127
- ## Updating — two independent things, both manual
220
+ ## Updating — three independent things
128
221
 
129
- - **This plugin** never updates itself. Installed from a plugin market? Update it **in that market**.
130
- Installed from a local `file:` checkout? Overwrite the directory and install it again. Updating REA below
131
- does **not** update this plugin, and updating this plugin does not touch REA.
132
- - **Upstream REA**: `npm i -g rea-agents@latest`. Its `bin` entry (`scripts/rea.mjs`) is stable across
133
- releases, so this needs no change here.
134
- - **Ghidra** is version-gated by REA (12.1.x today): update REA first, then check that it accepts a newer
135
- Ghidra.
136
- - **DeepSeek Harness**: a harness upgrade may change the MCP client's config schema — inspect the row with
137
- `Config.listConfigs name=@deepseek-ai/dsh-mcp-client` and adjust the field names.
222
+ ### Upgrading from 1.x
223
+
224
+ Install 2.x, then run provisioning **once** before restarting dsh:
225
+
226
+ ```
227
+ node scripts/setup.mjs # or: powershell -File scripts\rea-bundle-bootstrap.ps1 -Reuse
228
+ ```
138
229
 
139
- `check-updates.ps1` (Windows PowerShell) reports the state of all four at once, and
140
- `.github/workflows/validate.yml` enforces the bundle's shape — including "the shipped patch must not
141
- contain a machine-specific path" — in CI.
230
+ Why it matters: the 1.x row launched `rea mcp` from PATH, while the 2.x row launches
231
+ `<DSH_HOME>/plugins/rea-bundle/bin/rea-shim.mjs` — a file only `setup.mjs` creates. Upgrade and restart
232
+ without provisioning and that row exits with `MODULE_NOT_FOUND`: the harness stays up
233
+ (`failOnStartupError: false` is deliberate), but the `mcp__rea__*` tools are gone and you get **no
234
+ remediation message**, because the shim that prints one does not exist yet. After the setup run they are
235
+ back on the next start.
236
+
237
+ - **This plugin** never updates itself. Installed from a market? Update it there. Installed from a `file:`
238
+ checkout? Overwrite the directory and reinstall.
239
+ - **Upgrading from `2.0.0-bundle.1`**: that release mirrored the shim and `setup.mjs` but not the manifest,
240
+ and recorded nothing about the plugin checkout, so a bundle it provisioned cannot repair itself — its
241
+ background provisioning dies on the missing manifest, invisibly, and its own lock suppresses retries for
242
+ six hours. Re-run `node scripts/setup.mjs` **from the plugin directory** once; it re-mirrors all three
243
+ files and writes the record that lets later runs find the checkout. `check-updates.ps1` reports this state.
244
+ - **Provisioned components**: re-run `node scripts/setup.mjs`; it re-verifies what is on disk and only
245
+ fetches what is missing or wrong. Pins live in `manifest/bundle.manifest.json`.
246
+ - **Upstream REA / Ghidra**: update the pins in the manifest, then re-run setup. REA version-gates Ghidra
247
+ (12.1.x today), so update REA first.
248
+
249
+ `check-updates.ps1` (Windows PowerShell) reports the state of all of these at once.
142
250
 
143
251
  ## License
144
252
 
145
- MIT (this adapter). REA is MIT and owned by its authors; Ghidra is Apache-2.0 and owned by the NSA. No
146
- upstream code is redistributed here.
253
+ MIT (this adapter). REA is MIT and owned by its authors; Ghidra is Apache-2.0 and owned by the NSA; Temurin
254
+ is GPLv2+CE; Node.js is MIT; apktool, jadx-headless-mcp and Android platform-tools keep their own licences.
255
+ **No upstream code or binary is redistributed in this package** — provisioning fetches each component from
256
+ its official source.