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 ```bash
npm ci npm ci
npm run rebuild:native # only when the packaged PTY binary is incompatible
npm test 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: 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 ```bash
npx -y rea-agents --help npx -y rea-agents@latest --help
npx -y rea-agents doctor npx -y rea-agents@latest doctor
npx -y rea-agents setup --yes npx -y rea-agents@latest setup --yes
npx -y rea-agents mcp 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 ### From Terminal — no installation
```bash ```bash
npx -y rea-agents setup npx -y rea-agents@latest setup
npx -y rea-agents doctor npx -y rea-agents@latest doctor
npx -y rea-agents analyze /Applications/Notes.app npx -y rea-agents@latest analyze /Applications/Notes.app
``` ```
Review the setup plan before confirming it. Restart a configured agent so it loads REA. 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. 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 ### Requirements
- macOS 12 or newer - 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: If something is not working, run:
```bash ```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. `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": { "mcpServers": {
"rea": { "rea": {
"command": "npx", "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: The agent workflow above is the easiest way to use REA. For a one-off overview from the Terminal:
```bash ```bash
npx -y rea-agents analyze /Applications/Notes.app npx -y rea-agents@latest analyze /Applications/Notes.app
npx -y rea-agents inspect /Applications/Notes.app npx -y rea-agents@latest inspect /Applications/Notes.app
npx -y rea-agents inspect /Applications/Notes.app --detail detailed --limit 20 npx -y rea-agents@latest inspect /Applications/Notes.app --detail detailed --limit 20
npx -y rea-agents search /Applications/Notes.app "offline" npx -y rea-agents@latest search /Applications/Notes.app "offline"
npx -y rea-agents function /Applications/Notes.app 0x1000 npx -y rea-agents@latest function /Applications/Notes.app 0x1000
npx -y rea-agents xrefs /Applications/Notes.app 0x1000 npx -y rea-agents@latest xrefs /Applications/Notes.app 0x1000
npx -y rea-agents trace /Applications/Notes.app "offline" npx -y rea-agents@latest 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@latest 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@latest investigate-versions /path/to/v1 /path/to/v2 /absolute/path/to/evidence/releases.json --yes
npx -y rea-agents capabilities npx -y rea-agents@latest capabilities
npx -y rea-agents providers 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; other options. `analyze` and `inspect` share the same overview workflow;
`function`, `xrefs`, and `trace` return the same Evidence v2 envelopes as MCP. `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 divergence. See [Process Capture v4](docs/process-capture.md) for scenario
fields, command-shim replay, checkpoint triggers, limits, and safety behavior. fields, command-shim replay, checkpoint triggers, limits, and safety behavior.
If the native PTY backend is unavailable, install Xcode command-line tools and REA installs a prebuilt PTY backend for supported macOS, Linux, and Windows
run `npm run rebuild:native`. Linux source builds require Python, `make`, and a architectures. If the capability check reports that the backend is unavailable,
C++ toolchain. Compatible packaged binaries do not require this rebuild. reinstall REA for the current platform and architecture.
ASAR inventory verifies Electron integrity metadata for both archive entries ASAR inventory verifies Electron integrity metadata for both archive entries
and `.asar.unpacked` companion files. Integrity failures identify the logical and `.asar.unpacked` companion files. Integrity failures identify the logical

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@
</div> </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"> <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> <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="col-sidebar">
<div class="page-menu"> <div class="page-menu">
<div class="tsd-navigation settings"> <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> <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"> <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> <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="col-sidebar">
<div class="page-menu"> <div class="page-menu">
<div class="tsd-navigation settings"> <div class="tsd-navigation settings">

View File

@ -13,7 +13,7 @@
<h1>Type Alias ProcessCaptureCapability</h1></div> <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"> <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> <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="col-sidebar">
<div class="page-menu"> <div class="page-menu">
<div class="tsd-navigation settings"> <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. 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: The optional curl wrapper installs only the global npm package:
```bash ```bash

111
package-lock.json generated
View File

@ -7,13 +7,13 @@
"": { "": {
"name": "rea-agents", "name": "rea-agents",
"version": "2.0.0", "version": "2.0.0",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "8.0.4", "@babel/parser": "8.0.4",
"@babel/types": "8.0.4", "@babel/types": "8.0.4",
"@electron/asar": "4.2.0", "@electron/asar": "4.2.0",
"@jridgewell/trace-mapping": "0.3.31", "@jridgewell/trace-mapping": "0.3.31",
"@lydell/node-pty": "1.1.0",
"@modelcontextprotocol/server": "2.0.0-beta.4", "@modelcontextprotocol/server": "2.0.0-beta.4",
"@xterm/addon-serialize": "0.14.0", "@xterm/addon-serialize": "0.14.0",
"@xterm/headless": "6.0.0", "@xterm/headless": "6.0.0",
@ -25,7 +25,6 @@
"ignore": "7.0.6", "ignore": "7.0.6",
"incur": "0.4.13", "incur": "0.4.13",
"isomorphic-git": "1.38.7", "isomorphic-git": "1.38.7",
"node-pty": "1.1.0",
"pino": "9.14.0", "pino": "9.14.0",
"plist": "5.0.0", "plist": "5.0.0",
"semver": "7.8.5", "semver": "7.8.5",
@ -245,6 +244,98 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@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": { "node_modules/@modelcontextprotocol/client": {
"version": "2.0.0-beta.4", "version": "2.0.0-beta.4",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0-beta.4.tgz", "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": "^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": { "node_modules/obliterator": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz",

View File

@ -42,8 +42,6 @@
"bridge/hopper_bridge.py", "bridge/hopper_bridge.py",
"bridge/ghidra/ReaGhidraBridge.java", "bridge/ghidra/ReaGhidraBridge.java",
"scripts/rea.mjs", "scripts/rea.mjs",
"scripts/prepare-node-pty.mjs",
"scripts/rebuild-native.mjs",
"scripts/hopper-demo-x11.py", "scripts/hopper-demo-x11.py",
"skills", "skills",
"README.md", "README.md",
@ -66,7 +64,6 @@
"scan:todos": "node scripts/scan-todos.mjs", "scan:todos": "node scripts/scan-todos.mjs",
"start": "node scripts/rea.mjs mcp", "start": "node scripts/rea.mjs mcp",
"pretest": "npm run build", "pretest": "npm run build",
"postinstall": "node scripts/prepare-node-pty.mjs",
"test": "vitest run", "test": "vitest run",
"pretest:coverage": "npm run build", "pretest:coverage": "npm run build",
"test:coverage": "vitest run --coverage.enabled --coverage.provider=v8", "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", "docs:error-schema": "npm run build && node scripts/generate-error-schema.mjs",
"prepack": "npm run deps:check && node scripts/generate-skill-metadata.mjs --check", "prepack": "npm run deps:check && node scripts/generate-skill-metadata.mjs --check",
"prepublishOnly": "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", "prepare": "npm run build && husky"
"rebuild:native": "node scripts/rebuild-native.mjs"
}, },
"lint-staged": { "lint-staged": {
"*.{ts,mjs,json,md,yml,yaml}": "prettier --write", "*.{ts,mjs,json,md,yml,yaml}": "prettier --write",
@ -108,6 +104,7 @@
"@babel/types": "8.0.4", "@babel/types": "8.0.4",
"@electron/asar": "4.2.0", "@electron/asar": "4.2.0",
"@jridgewell/trace-mapping": "0.3.31", "@jridgewell/trace-mapping": "0.3.31",
"@lydell/node-pty": "1.1.0",
"@modelcontextprotocol/server": "2.0.0-beta.4", "@modelcontextprotocol/server": "2.0.0-beta.4",
"@xterm/addon-serialize": "0.14.0", "@xterm/addon-serialize": "0.14.0",
"@xterm/headless": "6.0.0", "@xterm/headless": "6.0.0",
@ -119,7 +116,6 @@
"ignore": "7.0.6", "ignore": "7.0.6",
"incur": "0.4.13", "incur": "0.4.13",
"isomorphic-git": "1.38.7", "isomorphic-git": "1.38.7",
"node-pty": "1.1.0",
"pino": "9.14.0", "pino": "9.14.0",
"plist": "5.0.0", "plist": "5.0.0",
"semver": "7.8.5", "semver": "7.8.5",
@ -145,8 +141,5 @@
"typedoc": "0.28.20", "typedoc": "0.28.20",
"typescript": "5.9.3", "typescript": "5.9.3",
"vitest": "4.1.10" "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", command: "npx",
args: [ args: [
"-y", "-y",
JSON.parse( `${
await readFile( JSON.parse(
resolve( await readFile(
fileURLToPath(new URL("..", import.meta.url)), resolve(
"package.json", fileURLToPath(new URL("..", import.meta.url)),
"package.json",
),
"utf8",
), ),
"utf8", ).name
), }@latest`,
).name,
"mcp", "mcp",
], ],
env, 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"; } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client"; import { Client } from "@modelcontextprotocol/client";
@ -37,6 +38,16 @@ try {
const packedFiles = ( const packedFiles = (
await exec("tar", ["-tf", join(root, tarball)]) await exec("tar", ["-tf", join(root, tarball)])
).stdout.split("\n"); ).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 ( if (
packedFiles.some( packedFiles.some(
(path) => path.includes("__pycache__") || path.endsWith(".pyc"), (path) => path.includes("__pycache__") || path.endsWith(".pyc"),
@ -121,10 +132,89 @@ try {
}; };
await exec( await exec(
"npm", "npm",
["install", "--global", "--prefix", prefix, join(root, tarball)], [
"install",
"--global",
"--ignore-scripts",
"--prefix",
prefix,
join(root, tarball),
],
{ env: environment }, { env: environment },
); );
const cli = join(prefix, "bin", "rea"); 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 help = await run(cli, ["--help"], environment);
const llms = await run(cli, ["--llms"], environment); const llms = await run(cli, ["--llms"], environment);
const doctorExecution = await runWithStatus( const doctorExecution = await runWithStatus(
@ -662,8 +752,10 @@ try {
); );
await run(cli, ["mcp", "add"], environment); await run(cli, ["mcp", "add"], environment);
const mcpRegistration = await readFile(npxLog, "utf8"); const mcpRegistration = await readFile(npxLog, "utf8");
if (!mcpRegistration.includes("add-mcp npx -y rea-agents mcp --name rea")) if (
throw new Error("Incur mcp add did not register the floating npx command"); !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 skillPath = join(home, ".agents/skills/rea-analysis/SKILL.md");
const siblingSkillPath = join(home, ".agents/skills/unrelated/SKILL.md"); const siblingSkillPath = join(home, ".agents/skills/unrelated/SKILL.md");
if (supportedSetupHost) { if (supportedSetupHost) {
@ -1036,7 +1128,7 @@ try {
} }
process.stdout.write( 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 { } finally {
if (tarball) await rm(join(root, tarball), { force: true }); if (tarball) await rm(join(root, tarball), { force: true });

View File

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

View File

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

View File

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

View File

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

View File

@ -229,8 +229,10 @@ const isOwnedRegistration = (value: unknown): boolean => {
args.length === 1 && args.length === 1 &&
args[0] === "mcp") || args[0] === "mcp") ||
(command === "npx" && (command === "npx" &&
JSON.stringify(args) === (JSON.stringify(args) ===
JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"])) 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 = { export const PRODUCT_IDENTITY = {
displayName: "REA", displayName: "REA",
packageName: PACKAGE_METADATA.name, packageName: PACKAGE_METADATA.name,
packageSpecifier: `${PACKAGE_METADATA.name}@latest`,
packageVersion: PACKAGE_METADATA.version, packageVersion: PACKAGE_METADATA.version,
cliBinary: "rea", cliBinary: "rea",
mcpCommand: "npx -y rea-agents mcp", mcpCommand: `npx -y ${PACKAGE_METADATA.name}@latest mcp`,
mcpServerKey: "rea", mcpServerKey: "rea",
skillName: "rea-analysis", skillName: "rea-analysis",
skillVersion: PACKAGE_METADATA.skillVersion, skillVersion: PACKAGE_METADATA.skillVersion,

View File

@ -17,7 +17,7 @@ describe("client registration status", () => {
]); ]);
await writeFile( await writeFile(
join(home, ".codex/config.toml"), 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( await writeFile(
join(home, ".cursor/mcp.json"), join(home, ".cursor/mcp.json"),
@ -44,4 +44,23 @@ describe("client registration status", () => {
); );
expect(JSON.stringify(statuses)).not.toContain("SECRET"); 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"; import { PRODUCT_IDENTITY } from "../src/identity.js";
describe("package identity", () => { 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 const packageJson = z
.object({ .object({
name: z.string(), name: z.string(),
@ -28,7 +28,12 @@ describe("package identity", () => {
); );
expect(packageJson.name).toBe(PRODUCT_IDENTITY.packageName); expect(packageJson.name).toBe(PRODUCT_IDENTITY.packageName);
expect(Object.keys(packageJson.bin)).toEqual([PRODUCT_IDENTITY.cliBinary]); 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.start).toBe("node scripts/rea.mjs mcp");
expect(packageJson.scripts.prepare).toBe("npm run build && husky"); expect(packageJson.scripts.prepare).toBe("npm run build && husky");
expect(packageJson.scripts.prepack).toBe( expect(packageJson.scripts.prepack).toBe(

View File

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

View File

@ -55,7 +55,7 @@ describe("JSON client configuration transaction", () => {
other: { command: "other" }, other: { command: "other" },
rea: { rea: {
command: "npx", command: "npx",
args: ["-y", "rea-agents", "mcp"], args: ["-y", "rea-agents@latest", "mcp"],
}, },
}, },
}); });
@ -79,7 +79,7 @@ describe("JSON client configuration transaction", () => {
mcpServers: { mcpServers: {
rea: { rea: {
command: "npx", 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"); const configPath = join(directory, "mcp.json");
await writeFile( await writeFile(
configPath, 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({ expect(await configureJsonClient({ name: "cursor", configPath })).toEqual({
status: "unchanged", status: "unchanged",
@ -118,6 +118,30 @@ describe("JSON client configuration transaction", () => {
).rejects.toThrow(); ).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 () => { it("persists a custom Hopper launcher and remains idempotent", async () => {
directory = await mkdtemp(join(tmpdir(), "rea-setup-")); directory = await mkdtemp(join(tmpdir(), "rea-setup-"));
const configPath = join(directory, "mcp.json"); const configPath = join(directory, "mcp.json");
@ -132,7 +156,7 @@ describe("JSON client configuration transaction", () => {
mcpServers: { mcpServers: {
rea: { rea: {
command: "npx", command: "npx",
args: ["-y", "rea-agents", "mcp"], args: ["-y", "rea-agents@latest", "mcp"],
env: { HOPPER_LAUNCHER_PATH: hopperPath }, env: { HOPPER_LAUNCHER_PATH: hopperPath },
}, },
}, },