fix(npm): use latest entry points without install scripts

This commit is contained in:
morluto 2026-07-17 00:16:46 +00:00
parent 81ed68f67d
commit 34252f55e9
27 changed files with 333 additions and 166 deletions

View File

@ -8,7 +8,6 @@ REA development requires Node.js 24.18.x and npm 11.16.x. Real-Hopper verificati
```bash
npm ci
npm run rebuild:native # only when the packaged PTY binary is incompatible
npm test
```
@ -78,8 +77,8 @@ npm publish --access public
After npm registry propagation, verify the published CLI and connect the client SDK version pinned in `package.json` to the published server to confirm the canonical tool catalog:
```bash
npx -y rea-agents --help
npx -y rea-agents doctor
npx -y rea-agents setup --yes
npx -y rea-agents mcp
npx -y rea-agents@latest --help
npx -y rea-agents@latest doctor
npx -y rea-agents@latest setup --yes
npx -y rea-agents@latest mcp
```

View File

@ -117,9 +117,9 @@ Review the setup plan, approve it if appropriate, then describe the app or featu
### From Terminal — no installation
```bash
npx -y rea-agents setup
npx -y rea-agents doctor
npx -y rea-agents analyze /Applications/Notes.app
npx -y 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.
@ -146,6 +146,11 @@ 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
@ -159,7 +164,7 @@ Deep binary operations use [Hopper](https://www.hopperapp.com/), a separate desk
If something is not working, run:
```bash
npx -y rea-agents doctor
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.
@ -432,7 +437,7 @@ Setup detects Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf,
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents", "mcp"]
"args": ["-y", "rea-agents@latest", "mcp"]
}
}
}
@ -473,20 +478,20 @@ The CLI and MCP server use the same application workflows and evidence contracts
The agent workflow above is the easiest way to use REA. For a one-off overview from the Terminal:
```bash
npx -y rea-agents analyze /Applications/Notes.app
npx -y rea-agents inspect /Applications/Notes.app
npx -y rea-agents inspect /Applications/Notes.app --detail detailed --limit 20
npx -y rea-agents search /Applications/Notes.app "offline"
npx -y rea-agents function /Applications/Notes.app 0x1000
npx -y rea-agents xrefs /Applications/Notes.app 0x1000
npx -y rea-agents trace /Applications/Notes.app "offline"
npx -y rea-agents compare /absolute/path/to/left-evidence.json /absolute/path/to/right-evidence.json
npx -y rea-agents investigate-versions /path/to/v1 /path/to/v2 /absolute/path/to/evidence/releases.json --yes
npx -y rea-agents capabilities
npx -y rea-agents providers
npx -y rea-agents@latest analyze /Applications/Notes.app
npx -y rea-agents@latest inspect /Applications/Notes.app
npx -y rea-agents@latest inspect /Applications/Notes.app --detail detailed --limit 20
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 investigate-versions /path/to/v1 /path/to/v2 /absolute/path/to/evidence/releases.json --yes
npx -y rea-agents@latest capabilities
npx -y rea-agents@latest providers
```
Run `npx -y rea-agents --help` for direct decompilation, bounded search and
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 v2 envelopes as MCP.
@ -588,9 +593,9 @@ first terminal, interaction, exit, filesystem, protocol, process, or shim
divergence. See [Process Capture v4](docs/process-capture.md) for scenario
fields, command-shim replay, checkpoint triggers, limits, and safety behavior.
If the native PTY backend is unavailable, install Xcode command-line tools and
run `npm run rebuild:native`. Linux source builds require Python, `make`, and a
C++ toolchain. Compatible packaged binaries do not require this rebuild.
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

View File

@ -90,7 +90,7 @@ npx skills add morluto/rea
```bash
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
npx -y rea-agents setup
npx -y rea-agents@latest setup
```
إذا طلب macOS أو برنامج التثبيت تأكيدًا، فأكمل الخطوة ثم شغّل الأمر نفسه مرة أخرى.
@ -111,7 +111,7 @@ npx -y rea-agents setup
إذا واجهت مشكلة، فشغّل:
```bash
npx -y rea-agents doctor
npx -y rea-agents@latest doctor
rea uninstall
rea uninstall --purge-data
```
@ -180,7 +180,7 @@ rea uninstall --purge-data
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents", "mcp"]
"args": ["-y", "rea-agents@latest", "mcp"]
}
}
}
@ -206,11 +206,11 @@ flowchart LR
سير عمل الوكيل أعلاه هو أسهل طريقة لاستخدام REA. للحصول على نظرة سريعة على تطبيق من الطرفية:
```bash
npx -y rea-agents analyze /Applications/Notes.app
npx -y rea-agents compare /absolute/path/to/left-evidence.json /absolute/path/to/right-evidence.json
npx -y rea-agents@latest analyze /Applications/Notes.app
npx -y rea-agents@latest compare /absolute/path/to/left-evidence.json /absolute/path/to/right-evidence.json
```
شغّل `npx -y rea-agents --help` لمعرفة خيارات فك الترجمة المباشر والخيارات الأخرى.
شغّل `npx -y rea-agents@latest --help` لمعرفة خيارات فك الترجمة المباشر والخيارات الأخرى.
يمكن لـ REA فتح مجلد `.app` على Mac مباشرة. إذا لم يعثر الوكيل على التطبيق، فأخبره بمكان تثبيته.

View File

@ -91,7 +91,7 @@ Ubuntu 24.04+、Fedora 41+、64 ビット Arch Linux では、REA が公式の D
```bash
# 1. REA をセットアップ
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
npx -y rea-agents setup
npx -y rea-agents@latest setup
```
macOS やインストーラーから確認を求められた場合は、その操作を完了してから同じコマンドをもう一度実行してください。
@ -112,7 +112,7 @@ REA でメモアプリをリバースエンジニアリングしてください
問題がある場合は次を実行します:
```bash
npx -y rea-agents doctor
npx -y rea-agents@latest doctor
rea uninstall
rea uninstall --purge-data
```
@ -170,7 +170,7 @@ Setup は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents", "mcp"]
"args": ["-y", "rea-agents@latest", "mcp"]
}
}
}
@ -195,10 +195,10 @@ CLI と MCP サーバーは同じ解析エンジンを使います。ターミ
上のエージェントワークフローが、REA を使う最も簡単な方法です。ターミナルから一度だけアプリの概要を調べる場合は、次を実行します。
```bash
npx -y rea-agents analyze /Applications/Notes.app
npx -y rea-agents@latest analyze /Applications/Notes.app
```
直接デコンパイルする方法やその他のオプションは、`npx -y rea-agents --help` で確認できます。
直接デコンパイルする方法やその他のオプションは、`npx -y rea-agents@latest --help` で確認できます。
グローバルな `rea` コマンドとしてもインストールできます。

View File

@ -91,7 +91,7 @@ Ubuntu 24.04+, Fedora 41+, 64비트 Arch Linux에서 REA는 공식 DEB, RPM 또
```bash
# 1. REA 설정
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
npx -y rea-agents setup
npx -y rea-agents@latest setup
```
macOS나 설치 프로그램이 확인을 요청하면 해당 절차를 완료하고 같은 명령을 다시 실행하세요.
@ -112,7 +112,7 @@ SQLite를 사용해 제 프로젝트에 비슷한 기능을 구현해 주세요.
문제가 있으면 다음을 실행하세요.
```bash
npx -y rea-agents doctor
npx -y rea-agents@latest doctor
rea uninstall
rea uninstall --purge-data
```
@ -170,7 +170,7 @@ Setup은 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents", "mcp"]
"args": ["-y", "rea-agents@latest", "mcp"]
}
}
}
@ -195,10 +195,10 @@ CLI와 MCP 서버는 같은 분석 엔진을 사용합니다. 터미널 명령
위의 에이전트 워크플로가 REA를 사용하는 가장 쉬운 방법입니다. 터미널에서 앱을 한 번 빠르게 살펴보려면 다음을 실행하세요.
```bash
npx -y rea-agents analyze /Applications/Notes.app
npx -y rea-agents@latest analyze /Applications/Notes.app
```
직접 디컴파일하는 방법과 기타 옵션은 `npx -y rea-agents --help`에서 확인할 수 있습니다.
직접 디컴파일하는 방법과 기타 옵션은 `npx -y rea-agents@latest --help`에서 확인할 수 있습니다.
전역 `rea` 명령으로 설치할 수도 있습니다.

View File

@ -91,7 +91,7 @@ npx skills add morluto/rea
```bash
# 1. 安装和配置 REA
curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash
npx -y rea-agents setup
npx -y rea-agents@latest setup
```
如果 macOS 或安装程序要求确认,请完成提示,然后再次运行同一命令。
@ -112,7 +112,7 @@ Setup 会检测 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Win
如果遇到问题,请运行:
```bash
npx -y rea-agents doctor
npx -y rea-agents@latest doctor
rea uninstall
rea uninstall --purge-data
```
@ -170,7 +170,7 @@ Setup 会检测 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Win
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "rea-agents", "mcp"]
"args": ["-y", "rea-agents@latest", "mcp"]
}
}
}
@ -195,10 +195,10 @@ CLI 与 MCP 服务器使用相同的分析引擎。终端命令完成后会关
上面的智能体工作流是使用 REA 最简单的方式。如果只想在终端中快速了解一个应用:
```bash
npx -y rea-agents analyze /Applications/Notes.app
npx -y rea-agents@latest analyze /Applications/Notes.app
```
运行 `npx -y rea-agents --help` 查看直接反编译和其他选项。
运行 `npx -y rea-agents@latest --help` 查看直接反编译和其他选项。
也可以全局安装 `rea` 命令:

View File

@ -20,7 +20,7 @@
</div>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Promise</span><span class="tsd-signature-symbol">&lt;</span><a href="../types/application_ProcessCaptureCapability.ProcessCaptureCapability.html" class="tsd-signature-type tsd-kind-type-alias">ProcessCaptureCapability</a><span class="tsd-signature-symbol">&gt;</span></h4><aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/ProcessCaptureCapability.ts#L14">src/application/ProcessCaptureCapability.ts:14</a></li></ul></aside></div></li></ul></section></div>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/ProcessCaptureCapability.ts#L13">src/application/ProcessCaptureCapability.ts:13</a></li></ul></aside></div></li></ul></section></div>
<div class="col-sidebar">
<div class="page-menu">
<div class="tsd-navigation settings">

View File

@ -26,7 +26,7 @@
<li><span><code class="tsd-tag">Optional</code><span class="tsd-kind-parameter">signal</span>: <span class="tsd-signature-type">AbortSignal</span></span></li></ul></div>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Promise</span><span class="tsd-signature-symbol">&lt;</span><a href="../types/domain_result.Result.html" class="tsd-signature-type tsd-kind-type-alias">Result</a><span class="tsd-signature-symbol">&lt;</span><a href="../interfaces/domain_processCapture.ProcessCapture.html" class="tsd-signature-type tsd-kind-interface">ProcessCapture</a><span class="tsd-signature-symbol">,</span> <a href="../classes/application_ProcessCaptureError.ProcessCaptureError.html" class="tsd-signature-type tsd-kind-class">ProcessCaptureError</a><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span></h4><aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/ProcessHarness.ts#L494">src/application/ProcessHarness.ts:494</a></li></ul></aside></div></li></ul></section></div>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/ProcessHarness.ts#L495">src/application/ProcessHarness.ts:495</a></li></ul></aside></div></li></ul></section></div>
<div class="col-sidebar">
<div class="page-menu">
<div class="tsd-navigation settings">

View File

@ -13,7 +13,7 @@
<h1>Type Alias ProcessCaptureCapability</h1></div>
<div class="tsd-signature"><span class="tsd-kind-type-alias">ProcessCaptureCapability</span><span class="tsd-signature-symbol">:</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-symbol">{</span> <span class="tsd-kind-property">available</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">true</span><span class="tsd-signature-symbol">;</span> <span class="tsd-kind-property">backend</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">&quot;node-pty&quot;</span> <span class="tsd-signature-symbol">}</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-symbol">{</span> <span class="tsd-kind-property">available</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">false</span><span class="tsd-signature-symbol">;</span> <span class="tsd-kind-property">backend</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">&quot;node-pty&quot;</span><span class="tsd-signature-symbol">;</span> <span class="tsd-kind-property">reason</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span> <span class="tsd-signature-symbol">}</span></div><aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/ProcessCaptureCapability.ts#L4">src/application/ProcessCaptureCapability.ts:4</a></li></ul></aside></div>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/ProcessCaptureCapability.ts#L3">src/application/ProcessCaptureCapability.ts:3</a></li></ul></aside></div>
<div class="col-sidebar">
<div class="page-menu">
<div class="tsd-navigation settings">

View File

@ -13,6 +13,11 @@ rea setup
REA supports Node.js 22.19+ and 24.11+ (including newer releases). It uses the npm already paired with that runtime and never upgrades Node.js, npm, or Homebrew.
Running `npm install rea-agents` without `--global` installs the executable only
in the current project's `node_modules/.bin`; it does not make `rea` available
on the shell `PATH`. Use `npx -y rea-agents@latest` for one-off commands or the
global installation above for a shell-visible `rea` command.
The optional curl wrapper installs only the global npm package:
```bash

111
package-lock.json generated
View File

@ -7,13 +7,13 @@
"": {
"name": "rea-agents",
"version": "2.0.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "8.0.4",
"@babel/types": "8.0.4",
"@electron/asar": "4.2.0",
"@jridgewell/trace-mapping": "0.3.31",
"@lydell/node-pty": "1.1.0",
"@modelcontextprotocol/server": "2.0.0-beta.4",
"@xterm/addon-serialize": "0.14.0",
"@xterm/headless": "6.0.0",
@ -25,7 +25,6 @@
"ignore": "7.0.6",
"incur": "0.4.13",
"isomorphic-git": "1.38.7",
"node-pty": "1.1.0",
"pino": "9.14.0",
"plist": "5.0.0",
"semver": "7.8.5",
@ -245,6 +244,98 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lydell/node-pty": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lydell/node-pty/-/node-pty-1.1.0.tgz",
"integrity": "sha512-VDD8LtlMTOrPKWMXUAcB9+LTktzuunqrMwkYR1DMRBkS6LQrCt+0/Ws1o2rMml/n3guePpS7cxhHF7Nm5K4iMw==",
"license": "MIT",
"optionalDependencies": {
"@lydell/node-pty-darwin-arm64": "1.1.0",
"@lydell/node-pty-darwin-x64": "1.1.0",
"@lydell/node-pty-linux-arm64": "1.1.0",
"@lydell/node-pty-linux-x64": "1.1.0",
"@lydell/node-pty-win32-arm64": "1.1.0",
"@lydell/node-pty-win32-x64": "1.1.0"
}
},
"node_modules/@lydell/node-pty-darwin-arm64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-arm64/-/node-pty-darwin-arm64-1.1.0.tgz",
"integrity": "sha512-7kFD+owAA61qmhJCtoMbqj3Uvff3YHDiU+4on5F2vQdcMI3MuwGi7dM6MkFG/yuzpw8LF2xULpL71tOPUfxs0w==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@lydell/node-pty-darwin-x64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-x64/-/node-pty-darwin-x64-1.1.0.tgz",
"integrity": "sha512-XZdvqj5FjAMjH8bdp0YfaZjur5DrCIDD1VYiE9EkkYVMDQqRUPHYV3U8BVEQVT9hYfjmpr7dNaELF2KyISWSNA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@lydell/node-pty-linux-arm64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-arm64/-/node-pty-linux-arm64-1.1.0.tgz",
"integrity": "sha512-yyDBmalCfHpLiQMT2zyLcqL2Fay4Xy7rIs8GH4dqKLnEviMvPGOK7LADVkKAsbsyXBSISL3Lt1m1MtxhPH6ckg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@lydell/node-pty-linux-x64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-x64/-/node-pty-linux-x64-1.1.0.tgz",
"integrity": "sha512-NcNqRTD14QT+vXcEuqSSvmWY+0+WUBn2uRE8EN0zKtDpIEr9d+YiFj16Uqds6QfcLCHfZmC+Ls7YzwTaqDnanA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@lydell/node-pty-win32-arm64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-arm64/-/node-pty-win32-arm64-1.1.0.tgz",
"integrity": "sha512-JOMbCou+0fA7d/m97faIIfIU0jOv8sn2OR7tI45u3AmldKoKoLP8zHY6SAvDDnI3fccO1R2HeR1doVjpS7HM0w==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@lydell/node-pty-win32-x64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-x64/-/node-pty-win32-x64-1.1.0.tgz",
"integrity": "sha512-3N56BZ+WDFnUMYRtsrr7Ky2mhWGl9xXcyqR6cexfuCqcz9RNWL+KoXRv/nZylY5dYaXkft4JaR1uVu+roiZDAw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@modelcontextprotocol/client": {
"version": "2.0.0-beta.4",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0-beta.4.tgz",
@ -3792,22 +3883,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT"
},
"node_modules/node-pty": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz",
"integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-addon-api": "^7.1.0"
}
},
"node_modules/obliterator": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz",

View File

@ -42,8 +42,6 @@
"bridge/hopper_bridge.py",
"bridge/ghidra/ReaGhidraBridge.java",
"scripts/rea.mjs",
"scripts/prepare-node-pty.mjs",
"scripts/rebuild-native.mjs",
"scripts/hopper-demo-x11.py",
"skills",
"README.md",
@ -66,7 +64,6 @@
"scan:todos": "node scripts/scan-todos.mjs",
"start": "node scripts/rea.mjs mcp",
"pretest": "npm run build",
"postinstall": "node scripts/prepare-node-pty.mjs",
"test": "vitest run",
"pretest:coverage": "npm run build",
"test:coverage": "vitest run --coverage.enabled --coverage.provider=v8",
@ -96,8 +93,7 @@
"docs:error-schema": "npm run build && node scripts/generate-error-schema.mjs",
"prepack": "npm run deps:check && node scripts/generate-skill-metadata.mjs --check",
"prepublishOnly": "npm run deps:check && node scripts/generate-skill-metadata.mjs --check",
"prepare": "npm run build && husky",
"rebuild:native": "node scripts/rebuild-native.mjs"
"prepare": "npm run build && husky"
},
"lint-staged": {
"*.{ts,mjs,json,md,yml,yaml}": "prettier --write",
@ -108,6 +104,7 @@
"@babel/types": "8.0.4",
"@electron/asar": "4.2.0",
"@jridgewell/trace-mapping": "0.3.31",
"@lydell/node-pty": "1.1.0",
"@modelcontextprotocol/server": "2.0.0-beta.4",
"@xterm/addon-serialize": "0.14.0",
"@xterm/headless": "6.0.0",
@ -119,7 +116,6 @@
"ignore": "7.0.6",
"incur": "0.4.13",
"isomorphic-git": "1.38.7",
"node-pty": "1.1.0",
"pino": "9.14.0",
"plist": "5.0.0",
"semver": "7.8.5",
@ -145,8 +141,5 @@
"typedoc": "0.28.20",
"typescript": "5.9.3",
"vitest": "4.1.10"
},
"allowScripts": {
"node-pty@1.1.0": true
}
}

View File

@ -1,35 +0,0 @@
#!/usr/bin/env node
import { chmod, lstat } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
if (process.platform !== "win32") {
const require = createRequire(import.meta.url);
const packageRoot = dirname(
require.resolve("node-pty/package.json", {
paths: [dirname(fileURLToPath(import.meta.url))],
}),
);
const helperPath = join(
packageRoot,
"prebuilds",
`${process.platform}-${process.arch}`,
"spawn-helper",
);
try {
const helper = await lstat(helperPath);
if (!helper.isFile() || helper.isSymbolicLink()) {
throw new Error("node-pty spawn helper is not a regular file");
}
await chmod(helperPath, helper.mode | 0o111);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
// Source builds place their correctly-modeled helper under build/Release.
} else {
throw error;
}
}
}

View File

@ -35,15 +35,17 @@ const config = {
command: "npx",
args: [
"-y",
JSON.parse(
await readFile(
resolve(
fileURLToPath(new URL("..", import.meta.url)),
"package.json",
`${
JSON.parse(
await readFile(
resolve(
fileURLToPath(new URL("..", import.meta.url)),
"package.json",
),
"utf8",
),
"utf8",
),
).name,
).name
}@latest`,
"mcp",
],
env,

View File

@ -1,22 +0,0 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
const child = spawn(npm, ["rebuild", "node-pty"], {
stdio: "inherit",
env: { ...process.env, npm_config_build_from_source: "true" },
});
child.once("error", (error) => {
console.error(`Unable to rebuild node-pty: ${error.message}`);
process.exitCode = 1;
});
child.once("exit", (code, signal) => {
if (signal !== null) {
console.error(`node-pty rebuild terminated by ${signal}`);
process.exitCode = 1;
return;
}
process.exitCode = code ?? 1;
});

View File

@ -11,6 +11,7 @@ import {
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
@ -37,6 +38,16 @@ try {
const packedFiles = (
await exec("tar", ["-tf", join(root, tarball)])
).stdout.split("\n");
const packedManifest = JSON.parse(
(await exec("tar", ["-xOf", join(root, tarball), "package/package.json"]))
.stdout,
);
if (
packedManifest.scripts?.postinstall !== undefined ||
packedManifest.dependencies?.["node-pty"] !== undefined ||
packedManifest.dependencies?.["@lydell/node-pty"] !== "1.1.0"
)
throw new Error("package retained a lifecycle-dependent PTY installation");
if (
packedFiles.some(
(path) => path.includes("__pycache__") || path.endsWith(".pyc"),
@ -121,10 +132,89 @@ try {
};
await exec(
"npm",
["install", "--global", "--prefix", prefix, join(root, tarball)],
[
"install",
"--global",
"--ignore-scripts",
"--prefix",
prefix,
join(root, tarball),
],
{ env: environment },
);
const cli = join(prefix, "bin", "rea");
const processCaptureCapabilityUrl = pathToFileURL(
join(
prefix,
"lib/node_modules/rea-agents/dist/application/ProcessCaptureCapability.js",
),
).href;
const processCaptureCapability = json(
(
await exec(
process.execPath,
[
"--input-type=module",
"--eval",
`const { probeProcessCaptureCapability } = await import(${JSON.stringify(processCaptureCapabilityUrl)}); process.stdout.write(JSON.stringify(await probeProcessCaptureCapability()));`,
],
{ env: environment },
)
).stdout,
);
if (processCaptureCapability.available !== true)
throw new Error(
`packaged PTY backend failed without lifecycle scripts: ${JSON.stringify(processCaptureCapability)}`,
);
const noOptionalPrefix = join(workspace, "prefix-no-optional");
await exec(
"npm",
[
"install",
"--global",
"--ignore-scripts",
"--omit=optional",
"--prefix",
noOptionalPrefix,
join(root, tarball),
],
{ env: environment },
);
const noOptionalCli = join(noOptionalPrefix, "bin", "rea");
await rm(
join(
noOptionalPrefix,
"lib/node_modules/rea-agents/node_modules/@lydell",
`node-pty-${process.platform}-${process.arch}`,
),
{ recursive: true, force: true },
);
const noOptionalCapabilityUrl = pathToFileURL(
join(
noOptionalPrefix,
"lib/node_modules/rea-agents/dist/application/ProcessCaptureCapability.js",
),
).href;
const noOptionalCapability = json(
(
await exec(
process.execPath,
[
"--input-type=module",
"--eval",
`const { probeProcessCaptureCapability } = await import(${JSON.stringify(noOptionalCapabilityUrl)}); process.stdout.write(JSON.stringify(await probeProcessCaptureCapability()));`,
],
{ env: environment },
)
).stdout,
);
if (
!(await run(noOptionalCli, ["--help"], environment)).includes("setup") ||
noOptionalCapability.available !== false
)
throw new Error(
`packaged CLI did not degrade without the optional PTY binary: ${JSON.stringify(noOptionalCapability)}`,
);
const help = await run(cli, ["--help"], environment);
const llms = await run(cli, ["--llms"], environment);
const doctorExecution = await runWithStatus(
@ -662,8 +752,10 @@ try {
);
await run(cli, ["mcp", "add"], environment);
const mcpRegistration = await readFile(npxLog, "utf8");
if (!mcpRegistration.includes("add-mcp npx -y rea-agents mcp --name rea"))
throw new Error("Incur mcp add did not register the floating npx command");
if (
!mcpRegistration.includes("add-mcp npx -y rea-agents@latest mcp --name rea")
)
throw new Error("Incur mcp add did not register the latest npx command");
const skillPath = join(home, ".agents/skills/rea-analysis/SKILL.md");
const siblingSkillPath = join(home, ".agents/skills/unrelated/SKILL.md");
if (supportedSetupHost) {
@ -1036,7 +1128,7 @@ try {
}
process.stdout.write(
`${JSON.stringify({ cli: true, analysisCli: true, artifactCli: true, managedCli: true, managedReconstructionCli: true, managedNativeVerificationCli: true, managedRuntimePlanCli: true, managedApplicationGraphCli: true, evidenceCli: true, incurMcpCommand: "npx -y rea-agents mcp", doctor: "platform-appropriate", setup: supportedSetupHost ? "planned-then-idempotent" : "unsupported-host-rejected", setupPlanReadOnly: supportedSetupHost, existingHopperPreserved: supportedSetupHost, clients: supportedSetupHost ? 3 : 0, backupReadback: supportedSetupHost, failureRecovery: supportedSetupHost, configSymlinkLifecycle: supportedSetupHost, skill: supportedSetupHost, mcpTools: TOOL_CONTRACTS.length, mcpPrompts: prompts.names.length, promptCompletion: true, promptCompletionLifecycle: true, evidenceMcp: true, targetFree: true, targetLifecycle: true, boundedRegexBridge: true })}\n`,
`${JSON.stringify({ cli: true, analysisCli: true, artifactCli: true, managedCli: true, managedReconstructionCli: true, managedNativeVerificationCli: true, managedRuntimePlanCli: true, managedApplicationGraphCli: true, evidenceCli: true, incurMcpCommand: "npx -y rea-agents@latest mcp", lifecycleScriptsRequired: false, doctor: "platform-appropriate", setup: supportedSetupHost ? "planned-then-idempotent" : "unsupported-host-rejected", setupPlanReadOnly: supportedSetupHost, existingHopperPreserved: supportedSetupHost, clients: supportedSetupHost ? 3 : 0, backupReadback: supportedSetupHost, failureRecovery: supportedSetupHost, configSymlinkLifecycle: supportedSetupHost, skill: supportedSetupHost, mcpTools: TOOL_CONTRACTS.length, mcpPrompts: prompts.names.length, promptCompletion: true, promptCompletionLifecycle: true, evidenceMcp: true, targetFree: true, targetLifecycle: true, boundedRegexBridge: true })}\n`,
);
} finally {
if (tarball) await rm(join(root, tarball), { force: true });

View File

@ -78,7 +78,7 @@ const registrationAligned = (
command.length === 4 &&
command[0] === "npx" &&
command[1] === "-y" &&
command[2] === PRODUCT_IDENTITY.packageName &&
command[2] === PRODUCT_IDENTITY.packageSpecifier &&
command[3] === "mcp"
)
return true;

View File

@ -1,5 +1,4 @@
import { tmpdir } from "node:os";
import { spawn } from "node-pty";
export type ProcessCaptureCapability =
| { readonly available: true; readonly backend: "node-pty" }
@ -13,6 +12,7 @@ export type ProcessCaptureCapability =
export const probeProcessCaptureCapability =
async (): Promise<ProcessCaptureCapability> => {
try {
const { spawn } = await import("@lydell/node-pty");
const terminal = spawn(
process.platform === "win32" ? "cmd.exe" : "/bin/sh",
process.platform === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"],

View File

@ -3,7 +3,7 @@ import { createReadStream } from "node:fs";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { IPty } from "node-pty";
import type { IPty } from "@lydell/node-pty";
import type {
FilesystemCheckpoint,

View File

@ -1,4 +1,4 @@
import { spawn, type IPty } from "node-pty";
import type { IPty } from "@lydell/node-pty";
import type {
InteractionEvent,
ProcessCapture,
@ -204,6 +204,7 @@ const startCaptureRuntime = async (options: {
let shimReplay: CommandShimReplay | undefined;
let terminal: IPty | undefined;
try {
const { spawn } = await import("@lydell/node-pty");
replay = await startLoopbackReplay(scenario);
const startedAt = new Date();
const started = Date.now();

View File

@ -53,7 +53,7 @@ export {
const registrationCommand = (): readonly string[] =>
process.env.npm_command === "exec"
? ["npx", "-y", PRODUCT_IDENTITY.packageName, "mcp"]
? ["npx", "-y", PRODUCT_IDENTITY.packageSpecifier, "mcp"]
: [resolve(process.argv[1] ?? PRODUCT_IDENTITY.cliBinary), "mcp"];
/** Result of one backup/write/readback transaction. */
@ -405,7 +405,7 @@ export const configureJsonClient = async (
command: readonly string[] = [
"npx",
"-y",
PRODUCT_IDENTITY.packageName,
PRODUCT_IDENTITY.packageSpecifier,
"mcp",
],
): Promise<ClientConfigurationResult> => {
@ -483,7 +483,7 @@ export const configureTomlClient = async (
command: readonly string[] = [
"npx",
"-y",
PRODUCT_IDENTITY.packageName,
PRODUCT_IDENTITY.packageSpecifier,
"mcp",
],
): Promise<ClientConfigurationResult> => {

View File

@ -229,8 +229,10 @@ const isOwnedRegistration = (value: unknown): boolean => {
args.length === 1 &&
args[0] === "mcp") ||
(command === "npx" &&
JSON.stringify(args) ===
JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"]))
(JSON.stringify(args) ===
JSON.stringify(["-y", PRODUCT_IDENTITY.packageSpecifier, "mcp"]) ||
JSON.stringify(args) ===
JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"])))
);
};

View File

@ -6,9 +6,10 @@
export const PRODUCT_IDENTITY = {
displayName: "REA",
packageName: PACKAGE_METADATA.name,
packageSpecifier: `${PACKAGE_METADATA.name}@latest`,
packageVersion: PACKAGE_METADATA.version,
cliBinary: "rea",
mcpCommand: "npx -y rea-agents mcp",
mcpCommand: `npx -y ${PACKAGE_METADATA.name}@latest mcp`,
mcpServerKey: "rea",
skillName: "rea-analysis",
skillVersion: PACKAGE_METADATA.skillVersion,

View File

@ -17,7 +17,7 @@ describe("client registration status", () => {
]);
await writeFile(
join(home, ".codex/config.toml"),
'[mcp_servers.rea]\ncommand = "npx"\nargs = ["-y", "rea-agents", "mcp"]\n',
'[mcp_servers.rea]\ncommand = "npx"\nargs = ["-y", "rea-agents@latest", "mcp"]\n',
);
await writeFile(
join(home, ".cursor/mcp.json"),
@ -44,4 +44,23 @@ describe("client registration status", () => {
);
expect(JSON.stringify(statuses)).not.toContain("SECRET");
});
it("reports an unversioned npx registration as stale", async () => {
const home = await mkdtemp(join(tmpdir(), "rea-registrations-"));
await mkdir(join(home, ".codex"));
await writeFile(
join(home, ".codex/config.toml"),
'[mcp_servers.rea]\ncommand = "npx"\nargs = ["-y", "rea-agents", "mcp"]\n',
);
const statuses = await readClientRegistrationStatuses(home);
expect(statuses).toEqual([
expect.objectContaining({
client: "codex",
command: ["npx", "-y", "rea-agents", "mcp"],
state: "stale",
}),
]);
});
});

View File

@ -6,7 +6,7 @@ import { z } from "zod";
import { PRODUCT_IDENTITY } from "../src/identity.js";
describe("package identity", () => {
it("keeps the manifest, executable, and floating MCP command aligned", async () => {
it("keeps the manifest, executable, and latest MCP command aligned", async () => {
const packageJson = z
.object({
name: z.string(),
@ -28,7 +28,12 @@ describe("package identity", () => {
);
expect(packageJson.name).toBe(PRODUCT_IDENTITY.packageName);
expect(Object.keys(packageJson.bin)).toEqual([PRODUCT_IDENTITY.cliBinary]);
expect(PRODUCT_IDENTITY.mcpCommand).toBe(`npx -y ${packageJson.name} mcp`);
expect(PRODUCT_IDENTITY.packageSpecifier).toBe(
`${packageJson.name}@latest`,
);
expect(PRODUCT_IDENTITY.mcpCommand).toBe(
`npx -y ${packageJson.name}@latest mcp`,
);
expect(packageJson.scripts.start).toBe("node scripts/rea.mjs mcp");
expect(packageJson.scripts.prepare).toBe("npm run build && husky");
expect(packageJson.scripts.prepack).toBe(

View File

@ -61,10 +61,10 @@ describe("localized README product facts", () => {
expect(content).toContain(
"curl -fsSL https://raw.githubusercontent.com/morluto/rea/main/install.sh | bash",
);
expect(content).toContain("npx -y rea-agents setup");
expect(content).toContain("npx -y rea-agents doctor");
expect(content).toContain("npx -y rea-agents@latest setup");
expect(content).toContain("npx -y rea-agents@latest doctor");
expect(content).toContain("rea uninstall");
expect(content).toContain('"args": ["-y", "rea-agents", "mcp"]');
expect(content).toContain('"args": ["-y", "rea-agents@latest", "mcp"]');
expect(content).toContain("Node.js 22");
expect(content).toContain("macOS 12");
expect(content).toContain("Ubuntu 24.04");
@ -81,7 +81,8 @@ describe("localized README product facts", () => {
it("keeps both English CLI onboarding paths discoverable", async () => {
const content = await readFile(resolve("README.md"), "utf8");
expect(content).toContain("npx -y rea-agents analyze");
expect(content).toContain("npx -y rea-agents@latest analyze");
expect(content).toContain("npm install rea-agents` without `--global`");
expect(content).toContain("npm install --global rea-agents");
expect(content).toContain("rea setup");
expect(content).toContain("--install-hopper");

View File

@ -55,7 +55,7 @@ describe("JSON client configuration transaction", () => {
other: { command: "other" },
rea: {
command: "npx",
args: ["-y", "rea-agents", "mcp"],
args: ["-y", "rea-agents@latest", "mcp"],
},
},
});
@ -79,7 +79,7 @@ describe("JSON client configuration transaction", () => {
mcpServers: {
rea: {
command: "npx",
args: ["-y", "rea-agents", "mcp"],
args: ["-y", "rea-agents@latest", "mcp"],
},
},
});
@ -108,7 +108,7 @@ describe("JSON client configuration transaction", () => {
const configPath = join(directory, "mcp.json");
await writeFile(
configPath,
'{"mcpServers":{"rea":{"command":"npx","args":["-y","rea-agents","mcp"]}}}\n',
'{"mcpServers":{"rea":{"command":"npx","args":["-y","rea-agents@latest","mcp"]}}}\n',
);
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({
status: "unchanged",
@ -118,6 +118,30 @@ describe("JSON client configuration transaction", () => {
).rejects.toThrow();
});
it("migrates an unversioned npx registration and preserves sibling configuration", async () => {
directory = await mkdtemp(join(tmpdir(), "rea-setup-"));
const configPath = join(directory, "mcp.json");
const original =
'{"theme":"dark","mcpServers":{"rea":{"command":"npx","args":["-y","rea-agents","mcp"]},"other":{"command":"other"}}}\n';
await writeFile(configPath, original);
expect(await configureJsonClient({ name: "cursor", configPath })).toEqual(
expect.objectContaining({ status: "configured" }),
);
expect(JSON.parse(await readFile(configPath, "utf8"))).toEqual({
theme: "dark",
mcpServers: {
rea: {
command: "npx",
args: ["-y", "rea-agents@latest", "mcp"],
},
other: { command: "other" },
},
});
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
});
it("persists a custom Hopper launcher and remains idempotent", async () => {
directory = await mkdtemp(join(tmpdir(), "rea-setup-"));
const configPath = join(directory, "mcp.json");
@ -132,7 +156,7 @@ describe("JSON client configuration transaction", () => {
mcpServers: {
rea: {
command: "npx",
args: ["-y", "rea-agents", "mcp"],
args: ["-y", "rea-agents@latest", "mcp"],
env: { HOPPER_LAUNCHER_PATH: hopperPath },
},
},