rea-shim 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.1] - 2026-10-21
9
+
10
+ ### Fixed
11
+
12
+ - README/npm-page patch.
13
+
14
+ ## [0.1.0] - 2026-10-18
15
+
16
+ ### Added
17
+
18
+ - Initial `rea-shim` launcher: resolve JDK 21+ and Ghidra, then serve upstream `rea-agents --mcp` over stdio.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nathan <137482711+nathwn12@users.noreply.github.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,114 @@
1
- # Temporary Holding Version
1
+ <h1 align="center">rea-shim</h1>
2
+ <p align="center"><strong>Give your coding agent reverse-engineering tools with a one-line install.</strong></p>
2
3
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
+ Unofficial, personal-use wrapper around [rea](https://github.com/morluto/rea) by its original authors. It ships no upstream code: it resolves your JDK and Ghidra installs, then serves upstream `rea-agents` over stdio at run time.
5
+
6
+ ## Install
7
+
8
+ ```powershell
9
+ npx -y rea-shim@latest --doctor
10
+ ```
11
+
12
+ You need Node 22.14 or newer on Windows x64, plus a JDK 21+ and Ghidra, first. The first run resolves both, then `--doctor` prints the JDK home and java major, the Ghidra dir, the upstream version, and the exact child command, and exits 0.
13
+
14
+ ### Or let your AI install it
15
+
16
+ ```text
17
+ Install rea-shim with npx -y rea-shim@latest --doctor into my MCP client config, served with npx rea-shim@latest over stdio.
18
+ Then verify with npx -y rea-shim@latest --doctor and show me the full output.
19
+ If it fails, stop and paste the full output; do not improvise a fix.
20
+ ```
21
+
22
+ ### Wire it yourself
23
+
24
+ Wire the server into your MCP client config:
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "rea": { "type": "local", "timeout": { "startup": 45000 }, "command": ["cmd","/c","npx","-y","rea-shim@latest"] }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### Offline or pinned
35
+
36
+ Pin an exact shim version instead of `@latest`:
37
+
38
+ ```powershell
39
+ npx -y rea-shim@0.1.0 --doctor
40
+ ```
41
+
42
+ Pin upstream instead of its latest with `REA_SHIM_AGENTS_VERSION`:
43
+
44
+ ```powershell
45
+ $env:REA_SHIM_AGENTS_VERSION="6.5.0"
46
+ npx -y rea-shim@latest --doctor
47
+ ```
48
+
49
+ There is no fully offline fresh install: every run resolves upstream through npx, so treat these as pins, not as a no-network setup.
50
+
51
+ ## Use it
52
+
53
+ Run with no arguments and the shim resolves your JDK and Ghidra, then starts upstream as its child over stdio:
54
+
55
+ ```text
56
+ cmd.exe /d /c npx -y rea-agents@latest --mcp
57
+ ```
58
+
59
+ Your MCP client spawns the shim and talks to upstream through it; the shim writes only to stderr so stdout stays clean JSON-RPC. Check health any time:
60
+
61
+ ```powershell
62
+ npx -y rea-shim@latest --doctor
63
+ ```
64
+
65
+ When a dependency is missing the shim refuses with exit 2 and the exact fix: point `REA_SHIM_JDK_HOME` at a JDK 21+ home, or set `REA_SHIM_GHIDRA_DIR` to the Ghidra install dir. A `JAVA_HOME` below 21 is ignored on purpose.
66
+
67
+ ## What you get
68
+
69
+ - A tiny launcher with no dependencies: builtins only, and the package holds the launcher plus docs (`bin/`, `README.md`, `LICENSE`, `CHANGELOG.md` per `package.json` files).
70
+ - JDK discovery you can trust: the override first, then installed JDKs, highest java major 21 or newer wins; `--doctor` prints the home, the major, and the source.
71
+ - Ghidra discovery with the same shape: the override first, then existing installs; `--doctor` prints the dir and the source.
72
+ - A faithful child environment: `GHIDRA_INSTALL_DIR`, `JAVA_HOME`, the JDK `bin` first on `PATH`, and a 1500000 ms Ghidra startup timeout; the child exit code is forwarded.
73
+
74
+ ## Commands
75
+
76
+ You run the shim with no arguments to serve, or with one flag:
77
+
78
+ - `(no args)` — resolve your JDK and Ghidra, then serve upstream over stdio. This is the default run path.
79
+ - `--doctor` — print the JDK home and java major, the Ghidra dir, the upstream version, and the exact child command, then exit 0. Default: off, you pass it per run.
80
+ - `--version` — print the shim version, then exit 0. Default: off, you pass it per run.
81
+ - `--print-config` — print the opencode.jsonc block for this machine, then exit 0. Default: off, you pass it per run.
82
+ - `--help, -h` — print usage, then exit 0. Default: off, you pass it per run.
83
+
84
+ You override discovery with three optional env vars, all unset by default:
85
+
86
+ - `REA_SHIM_JDK_HOME` — pin the JDK home, must be java 21 or newer. Default: unset, the highest discovered JDK 21 or newer wins.
87
+ - `REA_SHIM_GHIDRA_DIR` — pin the Ghidra install dir. Default: unset, the discovered install wins.
88
+ - `REA_SHIM_AGENTS_VERSION` — pin `rea-agents@<v>` instead of `@latest`. Default: unset, `latest` wins.
89
+
90
+ ## Requirements
91
+
92
+ - Node 22.14 or newer (`engines` in `package.json`; `--doctor` ran on v24.22.0).
93
+ - Windows x64.
94
+ - JDK 21 or newer (`--doctor` resolved java major 21; anything below 21 is refused).
95
+ - Ghidra (`--doctor` resolved the install dir).
96
+ - Three optional overrides, named in `--help`: `REA_SHIM_JDK_HOME`, `REA_SHIM_GHIDRA_DIR`, `REA_SHIM_AGENTS_VERSION`.
97
+
98
+ ## Links
99
+
100
+ - Repo: https://github.com/nathwn12/rea-shim
101
+ - Issues: https://github.com/nathwn12/rea-shim/issues
102
+ - Releases: https://github.com/nathwn12/rea-shim/releases
103
+ - Upstream: https://github.com/morluto/rea
104
+ - Upstream package: https://www.npmjs.com/package/rea-agents
105
+ - Docs: [publishing](docs/PUBLISHING.md)
106
+ - License: MIT, see [LICENSE](LICENSE)
107
+
108
+ <!-- nathwn12 house footer: identical in every repo. Do not edit. -->
109
+ <p align="center">
110
+ <a href="https://github.com/nathwn12"><img src="assets/signature.svg" width="96" alt="nathwn12" /></a>
111
+ </p>
112
+ <p align="center">
113
+ <sub>Built by <a href="https://github.com/nathwn12">nathwn12</a> · <a href="https://github.com/nathwn12?tab=repositories">All projects</a></sub>
114
+ </p>
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ //
4
+ // rea-shim launcher (unofficial, personal use).
5
+ //
6
+ // Published package model: this tarball holds ONLY this launcher. It holds
7
+ // NO upstream code, NO `os` field, NO `scripts`/postinstall field. Upstream
8
+ // (`rea-agents`) is resolved at RUN time via npx, always at latest unless
9
+ // pinned with REA_SHIM_AGENTS_VERSION.
10
+ //
11
+ // Run path (mirrors the house pump scripts/mcp-rea.ps1 child exec + env
12
+ // contract exactly):
13
+ // - child: npx -y rea-agents@<latest|pin> --mcp (top-level `--mcp`
14
+ // flag = "Start as MCP stdio server" per upstream `--help`;
15
+ // bare `rea mcp` takes only `add`/`doctor` subcommands and
16
+ // does NOT serve)
17
+ // - spawned as cmd.exe /d /c <npx ...> with stdio inherited, so the child
18
+ // IS the MCP channel; ALL launcher chatter goes to stderr, stdout
19
+ // belongs to the JSON-RPC frames. No stdout writes exist on the run path.
20
+ // - env injected verbatim: GHIDRA_INSTALL_DIR, JAVA_HOME (+ <jdk>\bin
21
+ // prepended to PATH), REA_GHIDRA_STARTUP_TIMEOUT_MS=1500000.
22
+ // - Windows process handling mirrors puppet-mcp: shared console, so Ctrl+C
23
+ // reaches the whole tree; the child exit code is forwarded.
24
+ // - the pump's REA checkout (WorkingDirectory = ReaDir) has no equivalent
25
+ // here: npx resolves the upstream package into the npm cache, so cwd is
26
+ // inherited unchanged.
27
+ //
28
+ // Builtins only: node:fs, node:path, node:os, node:child_process.
29
+ // No dependencies, so the published tarball stays tiny and auditable.
30
+ //
31
+
32
+ const fs = require('node:fs');
33
+ const path = require('node:path');
34
+ const os = require('node:os');
35
+ const { spawn, spawnSync, execFileSync } = require('node:child_process');
36
+
37
+ const PKG = require('../package.json');
38
+ const SHIM_NAME = 'rea-shim';
39
+
40
+ // Env contract names, verbatim from the house pump (scripts/mcp-rea.ps1).
41
+ const GHIDRA_ENV = 'GHIDRA_INSTALL_DIR';
42
+ const JDK_ENV = 'REA_SHIM_JDK_HOME';
43
+ const GHIDRA_OVERRIDE_ENV = 'REA_SHIM_GHIDRA_DIR';
44
+ const AGENTS_VERSION_ENV = 'REA_SHIM_AGENTS_VERSION';
45
+ const STARTUP_TIMEOUT_ENV = 'REA_GHIDRA_STARTUP_TIMEOUT_MS';
46
+ const STARTUP_TIMEOUT_MS = '1500000';
47
+ // Legacy pump names (read, never invented): honoured as fallbacks below the
48
+ // rea-shim overrides.
49
+ const LEGACY_GHIDRA_ENVS = ['REA_GHIDRA_DIR', 'GHIDRA_INSTALL_DIR'];
50
+ const LEGACY_JDK_ENVS = ['REA_JDK_HOME', 'JAVA_HOME'];
51
+ const MIN_JAVA_MAJOR = 21;
52
+
53
+ function fail(message) {
54
+ process.stderr.write(`${SHIM_NAME}: error: ${message}\n`);
55
+ process.exit(1);
56
+ }
57
+
58
+ function refuse(message) {
59
+ // Missing-dependency refusal: non-zero with exact fix steps (mirrors the
60
+ // pump's exit-2 boundary for a missing dep).
61
+ process.stderr.write(`${SHIM_NAME}: refusing: ${message}\n`);
62
+ process.exit(2);
63
+ }
64
+
65
+ function log(message) {
66
+ process.stderr.write(`${SHIM_NAME}: ${message}\n`);
67
+ }
68
+
69
+ function dirExists(p) {
70
+ try {
71
+ return fs.statSync(p).isDirectory();
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ function javaExe(home) {
78
+ return path.join(home, 'bin', 'java.exe');
79
+ }
80
+
81
+ function javaMajor(home) {
82
+ // Returns the java major version, or null when unresolvable.
83
+ // `java -version` exits 0 and writes to stderr, so capture both streams
84
+ // with spawnSync (which never throws on exit code) and parse the merged
85
+ // text: `openjdk version "21.0.12.1"` or legacy `java version "1.8.0_..."`.
86
+ let text = '';
87
+ try {
88
+ const r = spawnSync(javaExe(home), ['-version'], { timeout: 15000, encoding: 'utf8' });
89
+ text = String((r && r.stdout) || '') + String((r && r.stderr) || '');
90
+ if (r && r.error) return null;
91
+ } catch {
92
+ return null;
93
+ }
94
+ const m = text.match(/version "(\d+)(?:\.(\d+))?/);
95
+ if (!m) return null;
96
+ if (m[1] === '1' && m[2]) return Number(m[2]);
97
+ return Number(m[1]);
98
+ }
99
+
100
+ function scoopRoot() {
101
+ return process.env.SCOOP || path.join(os.homedir(), 'scoop');
102
+ }
103
+
104
+ function scoopAppCurrent(app) {
105
+ return path.join(scoopRoot(), 'apps', app, 'current');
106
+ }
107
+
108
+ function resolveGhidra() {
109
+ // Discovery: REA_SHIM_GHIDRA_DIR -> REA_GHIDRA_DIR -> GHIDRA_INSTALL_DIR
110
+ // -> scoop apps\ghidra\current.
111
+ const candidates = [];
112
+ if (process.env[GHIDRA_OVERRIDE_ENV]) {
113
+ candidates.push({ dir: process.env[GHIDRA_OVERRIDE_ENV], source: GHIDRA_OVERRIDE_ENV });
114
+ }
115
+ for (const name of LEGACY_GHIDRA_ENVS) {
116
+ if (process.env[name]) candidates.push({ dir: process.env[name], source: name });
117
+ }
118
+ candidates.push({ dir: scoopAppCurrent('ghidra'), source: 'scoop apps/ghidra/current' });
119
+ for (const c of candidates) {
120
+ if (c.dir && dirExists(c.dir)) return c;
121
+ }
122
+ return null;
123
+ }
124
+
125
+ function collectJdkCandidates() {
126
+ // Discovery: REA_SHIM_JDK_HOME -> REA_JDK_HOME -> JAVA_HOME (each only
127
+ // honoured at major >= 21; a < 21 JAVA_HOME is never trusted) -> scoop
128
+ // JDK app currents -> C:\Program Files\Eclipse Adoptium\jdk-*.
129
+ const homes = [];
130
+ if (process.env[JDK_ENV]) {
131
+ homes.push({ home: process.env[JDK_ENV], source: JDK_ENV, override: true });
132
+ }
133
+ for (const name of LEGACY_JDK_ENVS) {
134
+ if (process.env[name]) homes.push({ home: process.env[name], source: name, override: false });
135
+ }
136
+ // Scoop: any app whose current holds bin\java.exe.
137
+ try {
138
+ const appsDir = path.join(scoopRoot(), 'apps');
139
+ for (const entry of fs.readdirSync(appsDir, { withFileTypes: true })) {
140
+ if (!entry.isDirectory()) continue;
141
+ const current = path.join(appsDir, entry.name, 'current');
142
+ if (dirExists(current) && fs.existsSync(javaExe(current))) {
143
+ homes.push({ home: current, source: `scoop ${entry.name}`, override: false });
144
+ }
145
+ }
146
+ } catch {
147
+ // No scoop tree: not an error, just no candidates from it.
148
+ }
149
+ // Adoptium: jdk-* dirs (JDKs only, never jre-*).
150
+ try {
151
+ const adoptium = 'C:\\Program Files\\Eclipse Adoptium';
152
+ for (const entry of fs.readdirSync(adoptium, { withFileTypes: true })) {
153
+ if (!entry.isDirectory() || !/^jdk-/i.test(entry.name)) continue;
154
+ const home = path.join(adoptium, entry.name);
155
+ if (fs.existsSync(javaExe(home))) {
156
+ homes.push({ home, source: 'Eclipse Adoptium', override: false });
157
+ }
158
+ }
159
+ } catch {
160
+ // Absent Adoptium root: not an error.
161
+ }
162
+ return homes;
163
+ }
164
+
165
+ function versionTuple(home) {
166
+ const m = String(home).match(/jdk-(\d+(?:[._]\d+)*)/i);
167
+ if (!m) return [];
168
+ return m[1].split(/[._]/).map(Number);
169
+ }
170
+
171
+ function compareHomes(a, b) {
172
+ if (a.major !== b.major) return b.major - a.major;
173
+ const ta = versionTuple(a.home);
174
+ const tb = versionTuple(b.home);
175
+ for (let i = 0; i < Math.max(ta.length, tb.length); i++) {
176
+ const d = (tb[i] || 0) - (ta[i] || 0);
177
+ if (d !== 0) return d;
178
+ }
179
+ return a.home < b.home ? -1 : 1;
180
+ }
181
+
182
+ function resolveJdk() {
183
+ const candidates = collectJdkCandidates();
184
+ const seen = new Set();
185
+ const viable = [];
186
+ for (const c of candidates) {
187
+ if (!c.home || seen.has(c.home.toLowerCase())) continue;
188
+ seen.add(c.home.toLowerCase());
189
+ if (!fs.existsSync(javaExe(c.home))) {
190
+ if (c.override) {
191
+ refuse(
192
+ `${JDK_ENV}='${c.home}' has no bin\\java.exe. ` +
193
+ `Fix: set ${JDK_ENV} to a JDK 21+ home (e.g. C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.12.101-hotspot), ` +
194
+ `or unset it to use auto-discovery.`
195
+ );
196
+ }
197
+ continue;
198
+ }
199
+ const major = javaMajor(c.home);
200
+ if (major === null) {
201
+ if (c.override) {
202
+ refuse(
203
+ `${JDK_ENV}='${c.home}' exists but 'bin\\java.exe -version' gave no parseable version. ` +
204
+ `Fix: point ${JDK_ENV} at a working JDK 21+ home.`
205
+ );
206
+ }
207
+ continue;
208
+ }
209
+ if (major < MIN_JAVA_MAJOR) {
210
+ if (c.override) {
211
+ refuse(
212
+ `${JDK_ENV}='${c.home}' is java major ${major}; rea needs >= ${MIN_JAVA_MAJOR}. ` +
213
+ `Fix: set ${JDK_ENV} to a JDK 21+ home (e.g. C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.12.101-hotspot).`
214
+ );
215
+ }
216
+ // Never trust a < 21 JAVA_HOME (or any legacy/scoop/Adoptium < 21): skip.
217
+ continue;
218
+ }
219
+ if (c.override) return { home: c.home, major, source: c.source };
220
+ viable.push({ home: c.home, major, source: c.source });
221
+ }
222
+ if (viable.length === 0) return null;
223
+ viable.sort(compareHomes);
224
+ return viable[0];
225
+ }
226
+
227
+ function resolveAgentsVersion() {
228
+ const pin = process.env[AGENTS_VERSION_ENV];
229
+ if (!pin) return { spec: 'rea-agents@latest', version: 'latest', source: 'dist-tag latest' };
230
+ if (!/^[A-Za-z0-9._-]+$/.test(pin)) {
231
+ fail(`${AGENTS_VERSION_ENV}='${pin}' is not a plain version/tag; refusing to interpolate it into the child command.`);
232
+ }
233
+ return { spec: `rea-agents@${pin}`, version: pin, source: `${AGENTS_VERSION_ENV}=${pin}` };
234
+ }
235
+
236
+ function upstreamVersion() {
237
+ // Resolved upstream version (dist-tag): `npm view` over cmd so npm.cmd
238
+ // resolves on Windows without a shell dependency in the caller.
239
+ try {
240
+ const out = execFileSync('cmd.exe', ['/d', '/c', 'npm', 'view', 'rea-agents', 'version'], {
241
+ timeout: 30000,
242
+ encoding: 'utf8',
243
+ stdio: ['ignore', 'pipe', 'pipe'],
244
+ });
245
+ const v = String(out).trim();
246
+ return v || 'unknown';
247
+ } catch {
248
+ return 'unknown';
249
+ }
250
+ }
251
+
252
+ function childArgv(agentsSpec) {
253
+ return ['/d', '/c', 'npx', '-y', agentsSpec, '--mcp'];
254
+ }
255
+
256
+ function childEnv(jdkHome, ghidraDir) {
257
+ return {
258
+ ...process.env,
259
+ [GHIDRA_ENV]: ghidraDir,
260
+ JAVA_HOME: jdkHome,
261
+ PATH: `${path.join(jdkHome, 'bin')};${process.env.PATH || ''}`,
262
+ [STARTUP_TIMEOUT_ENV]: STARTUP_TIMEOUT_MS,
263
+ };
264
+ }
265
+
266
+ function runServer(jdk, ghidra, agents) {
267
+ const args = childArgv(agents.spec);
268
+ log(`starting upstream: cmd.exe ${args.join(' ')}`);
269
+ log(`env: ${GHIDRA_ENV}=${ghidra.dir} JAVA_HOME=${jdk.home} ${STARTUP_TIMEOUT_ENV}=${STARTUP_TIMEOUT_MS}`);
270
+ // Shared console (same as puppet-mcp): Ctrl+C reaches the whole tree, so
271
+ // no signal relay is needed; the child exit code is forwarded below and no
272
+ // orphaned process tree is left behind on a clean exit.
273
+ const child = spawn('cmd.exe', args, { stdio: 'inherit', env: childEnv(jdk.home, ghidra.dir) });
274
+ child.on('error', (err) => fail(`cannot start child: ${err.message}`));
275
+ child.on('exit', (code, signal) => {
276
+ if (signal) {
277
+ log(`child terminated by signal ${signal}`);
278
+ process.exit(1);
279
+ }
280
+ process.exit(code === null ? 1 : code);
281
+ });
282
+ }
283
+
284
+ function printDoctor() {
285
+ const jdk = resolveJdk();
286
+ const ghidra = resolveGhidra();
287
+ const agents = resolveAgentsVersion();
288
+ const up = upstreamVersion();
289
+ const out = [];
290
+ out.push(`${SHIM_NAME} doctor`);
291
+ out.push(`node: ${process.version}`);
292
+ if (jdk) out.push(`jdk: ${jdk.home} (major ${jdk.major}, source ${jdk.source})`);
293
+ else out.push(`jdk: MISSING (need java major >= ${MIN_JAVA_MAJOR})`);
294
+ if (ghidra) out.push(`ghidra: ${ghidra.dir} (source ${ghidra.source})`);
295
+ else out.push(`ghidra: MISSING`);
296
+ out.push(`upstream: rea-agents@${up} (${agents.source})`);
297
+ out.push(`child: cmd.exe ${childArgv(agents.spec).join(' ')}`);
298
+ process.stdout.write(out.join('\n') + '\n');
299
+ if (!jdk) {
300
+ refuse(
301
+ `no JDK >= ${MIN_JAVA_MAJOR} found (env override -> scoop -> Eclipse Adoptium). ` +
302
+ `Fix: install Temurin 21+ and set ${JDK_ENV} to its home, e.g. ` +
303
+ `C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.12.101-hotspot. ` +
304
+ `Note: a JAVA_HOME pointing at JDK < 21 is ignored on purpose.`
305
+ );
306
+ }
307
+ if (!ghidra) {
308
+ refuse(
309
+ `no Ghidra dir found (env override -> scoop apps/ghidra/current). ` +
310
+ `Fix: install Ghidra (scoop: 'scoop install ghidra') or set ${GHIDRA_OVERRIDE_ENV} to its install dir.`
311
+ );
312
+ }
313
+ process.exit(0);
314
+ }
315
+
316
+ function usage(exitCode, stream) {
317
+ const text = [
318
+ `usage: ${SHIM_NAME} [--doctor | --version | --print-config | --help]`,
319
+ '',
320
+ ` (no args) resolve JDK 21+ and Ghidra, then serve upstream MCP over stdio:`,
321
+ ` cmd.exe /d /c npx -y rea-agents@<latest|pin> --mcp`,
322
+ ' --doctor print node, resolved JDK + java major, Ghidra dir,',
323
+ ' upstream version, and the exact child command; exit 0.',
324
+ ' Refuses (non-zero) when java major < 21 or Ghidra is missing.',
325
+ ' --version print the shim version',
326
+ ' --print-config print the opencode.jsonc block for this machine',
327
+ ' --help, -h print this usage',
328
+ '',
329
+ 'overrides (env):',
330
+ ` ${JDK_ENV} pin the JDK home (must be java >= 21)`,
331
+ ` ${GHIDRA_OVERRIDE_ENV} pin the Ghidra install dir`,
332
+ ` ${AGENTS_VERSION_ENV} pin rea-agents@<v> instead of @latest`,
333
+ `discovery: env override -> scoop (apps/ghidra/current, scoop JDKs)`,
334
+ ` -> C:\\Program Files\\Eclipse Adoptium\\jdk-* -> highest java >= 21.`,
335
+ ' A JAVA_HOME below 21 is never trusted.',
336
+ ].join('\n') + '\n';
337
+ stream.write(text);
338
+ process.exit(exitCode);
339
+ }
340
+
341
+ function printVersion() {
342
+ process.stdout.write(`${PKG.version}\n`);
343
+ process.exit(0);
344
+ }
345
+
346
+ function printConfig() {
347
+ // Exact opencode.jsonc block for this machine.
348
+ const block = [
349
+ '"rea": { "type": "local", "timeout": { "startup": 45000 }, "command": ["cmd","/c","npx","-y","rea-shim@latest"] }',
350
+ ].join('\n') + '\n';
351
+ process.stdout.write(block);
352
+ process.exit(0);
353
+ }
354
+
355
+ function main() {
356
+ const args = process.argv.slice(2);
357
+ if (args.includes('--help') || args.includes('-h')) usage(0, process.stdout);
358
+ if (args.includes('--version')) {
359
+ if (args.length !== 1) usage(2, process.stderr);
360
+ printVersion();
361
+ }
362
+ if (args.includes('--print-config')) {
363
+ if (args.length !== 1) usage(2, process.stderr);
364
+ printConfig();
365
+ }
366
+ if (args.includes('--doctor')) {
367
+ if (args.length !== 1) usage(2, process.stderr);
368
+ printDoctor(); // exits
369
+ }
370
+ // Every known flag returns above; anything left is unknown.
371
+ if (args.length > 0) usage(2, process.stderr);
372
+
373
+ const jdk = resolveJdk();
374
+ if (!jdk) {
375
+ refuse(
376
+ `no JDK >= ${MIN_JAVA_MAJOR} found (env override -> scoop -> Eclipse Adoptium). ` +
377
+ `Fix: install Temurin 21+ and set ${JDK_ENV} to its home, or run '${SHIM_NAME} --doctor'. ` +
378
+ `Note: a JAVA_HOME pointing at JDK < 21 is ignored on purpose.`
379
+ );
380
+ }
381
+ const ghidra = resolveGhidra();
382
+ if (!ghidra) {
383
+ refuse(
384
+ `no Ghidra dir found (env override -> scoop apps/ghidra/current). ` +
385
+ `Fix: set ${GHIDRA_OVERRIDE_ENV} to its install dir, or run '${SHIM_NAME} --doctor'.`
386
+ );
387
+ }
388
+ runServer(jdk, ghidra, resolveAgentsVersion());
389
+ }
390
+
391
+ main();
package/package.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "name": "rea-shim",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Unofficial personal-use one-line MCP launcher for rea: resolves a JDK 21+ and Ghidra, then serves upstream rea-agents over stdio at run time. Ships no upstream code. All credit to the original rea authors.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/nathwn12/rea-shim"
9
+ },
10
+ "homepage": "https://github.com/nathwn12/rea-shim",
11
+ "engines": {
12
+ "node": ">=22.14.0"
13
+ },
14
+ "bin": {
15
+ "rea-shim": "bin/rea-shim.js"
16
+ },
17
+ "files": [
18
+ "bin/",
19
+ "README.md",
20
+ "LICENSE",
21
+ "CHANGELOG.md"
22
+ ],
23
+ "keywords": [
24
+ "mcp",
25
+ "reverse-engineering",
26
+ "ghidra",
27
+ "launcher",
28
+ "stdio"
29
+ ]
30
+ }