Merge pull request #292 from morluto/fix/npm-entry-points
fix(npm): use latest entry points, remove install scripts, and rename skill
This commit is contained in:
commit
bf85ee62c9
|
|
@ -105,7 +105,6 @@ jobs:
|
|||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run rebuild:native
|
||||
- run: npm run test:coverage -- --reporter=verbose --reporter=junit --outputFile.junit=test-results.xml
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
|
|
@ -128,7 +127,6 @@ jobs:
|
|||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run rebuild:native
|
||||
- run: npm run verify:package
|
||||
|
||||
windows-curated:
|
||||
|
|
|
|||
|
|
@ -48,13 +48,12 @@ jobs:
|
|||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run rebuild:native
|
||||
- name: Install packed CLI in isolation
|
||||
shell: bash
|
||||
run: |
|
||||
tarball="$(npm pack --silent)"
|
||||
prefix="${RUNNER_TEMP}/rea-prefix"
|
||||
npm install --global --prefix "${prefix}" "./${tarball}"
|
||||
npm install --global --ignore-scripts --prefix "${prefix}" "./${tarball}"
|
||||
echo "REA_VERIFY_SERVER_COMMAND=${prefix}/bin/rea" >> "${GITHUB_ENV}"
|
||||
echo 'REA_VERIFY_SERVER_ARGS_JSON=["mcp"]' >> "${GITHUB_ENV}"
|
||||
- name: Verify two binaries through an alternate launcher symlink
|
||||
|
|
|
|||
|
|
@ -27,13 +27,12 @@ jobs:
|
|||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run rebuild:native
|
||||
- name: Install packed CLI in isolation
|
||||
shell: bash
|
||||
run: |
|
||||
tarball="$(npm pack --silent)"
|
||||
prefix="${RUNNER_TEMP}/rea-prefix"
|
||||
npm install --global --prefix "${prefix}" "./${tarball}"
|
||||
npm install --global --ignore-scripts --prefix "${prefix}" "./${tarball}"
|
||||
echo "REA_VERIFY_SERVER_COMMAND=${prefix}/bin/rea" >> "${GITHUB_ENV}"
|
||||
echo 'REA_VERIFY_SERVER_ARGS_JSON=["mcp"]' >> "${GITHUB_ENV}"
|
||||
- name: Verify two real binaries
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
45
README.md
45
README.md
|
|
@ -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
|
||||
|
|
|
|||
12
README_ar.md
12
README_ar.md
|
|
@ -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 مباشرة. إذا لم يعثر الوكيل على التطبيق، فأخبره بمكان تثبيته.
|
||||
|
||||
|
|
|
|||
10
README_ja.md
10
README_ja.md
|
|
@ -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` コマンドとしてもインストールできます。
|
||||
|
||||
|
|
|
|||
10
README_ko.md
10
README_ko.md
|
|
@ -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` 명령으로 설치할 수도 있습니다.
|
||||
|
||||
|
|
|
|||
10
README_zh.md
10
README_zh.md
|
|
@ -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` 命令:
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Promise</span><span class="tsd-signature-symbol"><</span><a href="../types/application_ProcessCaptureCapability.ProcessCaptureCapability.html" class="tsd-signature-type tsd-kind-type-alias">ProcessCaptureCapability</a><span class="tsd-signature-symbol">></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">
|
||||
|
|
|
|||
|
|
@ -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"><</span><a href="../types/domain_result.Result.html" class="tsd-signature-type tsd-kind-type-alias">Result</a><span class="tsd-signature-symbol"><</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">></span><span class="tsd-signature-symbol">></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">
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<li><span><span class="tsd-kind-parameter">providerEnvironment</span>: <a href="../types/application_Setup.SetupProviderEnvironment.html" class="tsd-signature-type tsd-kind-type-alias">SetupProviderEnvironment</a></span></li></ul></div>
|
||||
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-keyword">readonly</span> <a href="../interfaces/application_Setup.SetupAction.html" class="tsd-signature-type tsd-kind-interface">SetupAction</a><span class="tsd-signature-symbol">[]</span></h4><aside class="tsd-sources">
|
||||
<ul>
|
||||
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/SetupPlan.ts#L8">src/application/SetupPlan.ts:8</a></li></ul></aside></div></li></ul></section></div>
|
||||
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/SetupPlan.ts#L9">src/application/SetupPlan.ts:9</a></li></ul></aside></div></li></ul></section></div>
|
||||
<div class="col-sidebar">
|
||||
<div class="page-menu">
|
||||
<div class="tsd-navigation settings">
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<li><span><span class="tsd-kind-parameter">home</span>: <span class="tsd-signature-type">string</span></span></li></ul></div>
|
||||
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Promise</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol">></span></h4><aside class="tsd-sources">
|
||||
<ul>
|
||||
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/SetupSkill.ts#L26">src/application/SetupSkill.ts:26</a></li></ul></aside></div></li></ul></section></div>
|
||||
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/SetupSkill.ts#L34">src/application/SetupSkill.ts:34</a></li></ul></aside></div></li></ul></section></div>
|
||||
<div class="col-sidebar">
|
||||
<div class="page-menu">
|
||||
<div class="tsd-navigation settings">
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<li><span><span class="tsd-kind-parameter">home</span>: <span class="tsd-signature-type">string</span></span></li></ul></div>
|
||||
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Promise</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">"failed"</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">"unchanged"</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">"installed"</span><span class="tsd-signature-symbol">></span></h4><aside class="tsd-sources">
|
||||
<ul>
|
||||
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/SetupSkill.ts#L38">src/application/SetupSkill.ts:38</a></li></ul></aside></div></li></ul></section></div>
|
||||
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/application/SetupSkill.ts#L50">src/application/SetupSkill.ts:50</a></li></ul></aside></div></li></ul></section></div>
|
||||
<div class="col-sidebar">
|
||||
<div class="page-menu">
|
||||
<div class="tsd-navigation settings">
|
||||
|
|
|
|||
|
|
@ -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">"node-pty"</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">"node-pty"</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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"client": "2.0.0-beta.4",
|
||||
"core": "2.0.0-beta.4"
|
||||
},
|
||||
"skill_version": "20"
|
||||
"skill_version": "21"
|
||||
},
|
||||
"tools": {
|
||||
"total": 95,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ const lock = JSON.parse(
|
|||
await readFile(join(root, "package-lock.json"), "utf8"),
|
||||
);
|
||||
const skill = await readFile(
|
||||
join(root, "skills/rea-analysis/SKILL.md"),
|
||||
join(root, "skills/reverse-engineer-anything/SKILL.md"),
|
||||
"utf8",
|
||||
);
|
||||
const skillVersion = /^\s{2}version:\s*"([^"]+)"\s*$/mu.exec(skill)?.[1];
|
||||
if (skillVersion === undefined)
|
||||
throw new Error("Missing rea-analysis skill metadata version");
|
||||
throw new Error("Missing reverse-engineer-anything skill metadata version");
|
||||
const versionOf = (name) => {
|
||||
const value = lock.packages?.[`node_modules/${name}`]?.version;
|
||||
if (typeof value !== "string")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ for (const argument of arguments_)
|
|||
throw new Error(`Unknown skill metadata option: ${argument}`);
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const path = join(root, "skills/rea-analysis/SKILL.md");
|
||||
const path = join(root, "skills/reverse-engineer-anything/SKILL.md");
|
||||
const current = await readFile(path, "utf8");
|
||||
const { CATALOG_IDENTITY } = await import(
|
||||
`${pathToFileURL(join(root, "dist/catalogIdentity.js")).href}?${String(Date.now())}`
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -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,16 +752,22 @@ 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");
|
||||
const skillPath = join(home, ".agents/skills/rea-analysis/SKILL.md");
|
||||
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/reverse-engineer-anything/SKILL.md",
|
||||
);
|
||||
const legacySkillPath = join(home, ".agents/skills/rea-analysis/SKILL.md");
|
||||
const siblingSkillPath = join(home, ".agents/skills/unrelated/SKILL.md");
|
||||
if (supportedSetupHost) {
|
||||
await mkdir(join(home, ".agents/skills/rea-analysis"), {
|
||||
recursive: true,
|
||||
});
|
||||
await mkdir(join(home, ".agents/skills/unrelated"), { recursive: true });
|
||||
await writeFile(skillPath, "stale managed skill\n");
|
||||
await writeFile(legacySkillPath, "stale managed skill\n");
|
||||
await writeFile(siblingSkillPath, "unrelated skill\n");
|
||||
}
|
||||
const plannedExecution = await runWithStatus(
|
||||
|
|
@ -684,13 +780,16 @@ try {
|
|||
const plannedClaudeConfig = await readFile(claudeConfig, "utf8");
|
||||
const plannedCodexConfig = await readFile(codexTarget, "utf8");
|
||||
const plannedCursorConfig = await readFile(cursorConfig, "utf8");
|
||||
const plannedSkill = await readFile(skillPath, "utf8");
|
||||
const plannedSkill = await readFile(legacySkillPath, "utf8");
|
||||
if (
|
||||
planned.status !== "needs_confirmation" ||
|
||||
plannedExecution.status !== 1 ||
|
||||
planned.appliedActions.length !== 0 ||
|
||||
!planned.plannedActions.some(({ kind }) => kind === "configure_client") ||
|
||||
!planned.plannedActions.some(({ kind }) => kind === "install_skill") ||
|
||||
!planned.plannedActions.some(
|
||||
({ kind, detail }) =>
|
||||
kind === "install_skill" && detail.includes("back up its SKILL.md"),
|
||||
) ||
|
||||
plannedClaudeConfig !== '{"existing":true}\n' ||
|
||||
plannedCodexConfig !== 'model = "gpt-5"\n' ||
|
||||
plannedCursorConfig !== '{"existing":true}\n' ||
|
||||
|
|
@ -764,17 +863,18 @@ try {
|
|||
throw new Error("packaged setup did not preserve config symlinks");
|
||||
const skill = await readFile(skillPath, "utf8");
|
||||
const canonicalSkill = await readFile(
|
||||
join(root, "skills/rea-analysis/SKILL.md"),
|
||||
join(root, "skills/reverse-engineer-anything/SKILL.md"),
|
||||
"utf8",
|
||||
);
|
||||
if (skill !== canonicalSkill)
|
||||
throw new Error("packaged skill did not match its canonical source");
|
||||
if (
|
||||
(await readFile(`${skillPath}.rea.backup`, "utf8")) !==
|
||||
(await pathExists(legacySkillPath)) ||
|
||||
(await readFile(`${legacySkillPath}.rea.backup`, "utf8")) !==
|
||||
"stale managed skill\n" ||
|
||||
(await readFile(siblingSkillPath, "utf8")) !== "unrelated skill\n"
|
||||
)
|
||||
throw new Error("packaged stale-skill upgrade was not isolated");
|
||||
throw new Error("packaged skill-name migration was not isolated");
|
||||
const alignedDoctorExecution = await runWithStatus(
|
||||
cli,
|
||||
["doctor", "--json"],
|
||||
|
|
@ -1036,7 +1136,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 });
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: rea-analysis
|
||||
name: reverse-engineer-anything
|
||||
description: Reverse engineer native, Electron/JavaScript, and web applications with REA. Connect static artifacts to passive runtime evidence, explain how features work, and build a version tailored to the user's project.
|
||||
metadata:
|
||||
version: "20"
|
||||
version: "21"
|
||||
tool_count: 95
|
||||
catalog_digest: "b3db06bedadc1ad21a6cfe76f58a9c6cebbf9bce4f9663733b6bab07a57203c3"
|
||||
---
|
||||
|
|
@ -101,9 +101,9 @@ Notes is only a documentation example. Never select an app unless the user names
|
|||
|
||||
## Ensure REA is ready
|
||||
|
||||
1. Run `npx -y rea-agents doctor`.
|
||||
1. Run `npx -y rea-agents@latest doctor`.
|
||||
2. If setup is needed, tell the user REA needs to install its local binary-analysis tools. Do not lead with implementation details or assume the user knows reverse-engineering products.
|
||||
3. Before installing external software, obtain approval and identify what will be installed. If deeper analysis needs Hopper, describe it as REA's local analysis engine and note that it is a separate Mac app with its own license. Then run `npx -y rea-agents setup --yes`.
|
||||
3. Before installing external software, obtain approval and identify what will be installed. If deeper analysis needs Hopper, describe it as REA's local analysis engine and note that it is a separate Mac app with its own license. Then run `npx -y rea-agents@latest setup --yes`.
|
||||
4. If macOS or an installer requests human input, tell the user exactly what needs attention. After they finish, rerun setup and doctor.
|
||||
5. If setup registers a new MCP server, tell the user to restart their agent to load all REA tools. Direct CLI commands remain available before restart.
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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> => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type {
|
|||
SetupClient,
|
||||
SetupProviderEnvironment,
|
||||
} from "./Setup.js";
|
||||
import { PRODUCT_IDENTITY } from "../identity.js";
|
||||
|
||||
/** Build the complete setup mutation plan before approval. */
|
||||
export const setupPlan = (
|
||||
|
|
@ -42,8 +43,9 @@ export const setupPlan = (
|
|||
? [
|
||||
{
|
||||
kind: "install_skill" as const,
|
||||
target: "~/.agents/skills/rea-analysis/SKILL.md",
|
||||
detail: "Install or update the bundled REA analysis skill.",
|
||||
target: `~/.agents/skills/${PRODUCT_IDENTITY.skillName}/SKILL.md`,
|
||||
detail:
|
||||
"Install or update the bundled REA reverse-engineering skill. If the retired rea-analysis skill exists, back up its SKILL.md beside the retired path, then remove that legacy entry.",
|
||||
external: false,
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,18 +1,26 @@
|
|||
import { mkdir, readFile, rm } from "node:fs/promises";
|
||||
import { lstat, mkdir, readFile, rm } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import writeFileAtomic from "write-file-atomic";
|
||||
|
||||
import { PRODUCT_IDENTITY } from "../identity.js";
|
||||
|
||||
const skillDestination = (home: string, name: string): string =>
|
||||
join(home, ".agents/skills", name, "SKILL.md");
|
||||
|
||||
const readOptionalText = async (path: string): Promise<string | undefined> => {
|
||||
try {
|
||||
return await readFile(path, "utf8");
|
||||
} catch (cause: unknown) {
|
||||
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
||||
return undefined;
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
|
||||
const canonicalSkillFiles = async (
|
||||
home: string,
|
||||
): Promise<{ readonly content: string; readonly destination: string }> => ({
|
||||
destination: join(
|
||||
home,
|
||||
".agents/skills",
|
||||
PRODUCT_IDENTITY.skillName,
|
||||
"SKILL.md",
|
||||
),
|
||||
destination: skillDestination(home, PRODUCT_IDENTITY.skillName),
|
||||
content: await readFile(
|
||||
new URL(
|
||||
`../../skills/${PRODUCT_IDENTITY.skillName}/SKILL.md`,
|
||||
|
|
@ -28,7 +36,11 @@ export const canonicalSkillNeedsInstall = async (
|
|||
): Promise<boolean> => {
|
||||
try {
|
||||
const { content, destination } = await canonicalSkillFiles(home);
|
||||
return (await readFile(destination, "utf8")) !== content;
|
||||
if ((await readOptionalText(destination)) !== content) return true;
|
||||
for (const name of PRODUCT_IDENTITY.legacySkillNames)
|
||||
if ((await readOptionalText(skillDestination(home, name))) !== undefined)
|
||||
return true;
|
||||
return false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -38,45 +50,80 @@ export const canonicalSkillNeedsInstall = async (
|
|||
export const installCanonicalSkill = async (
|
||||
home: string,
|
||||
): Promise<"installed" | "unchanged" | "failed"> => {
|
||||
let destination = join(
|
||||
home,
|
||||
".agents/skills",
|
||||
PRODUCT_IDENTITY.skillName,
|
||||
"SKILL.md",
|
||||
);
|
||||
let destination = skillDestination(home, PRODUCT_IDENTITY.skillName);
|
||||
let backup = `${destination}.rea.backup`;
|
||||
let original: string | undefined;
|
||||
let canonicalChanged = false;
|
||||
const removedLegacy: Array<{
|
||||
readonly content: string;
|
||||
readonly destination: string;
|
||||
}> = [];
|
||||
try {
|
||||
const canonical = await canonicalSkillFiles(home);
|
||||
destination = canonical.destination;
|
||||
backup = `${destination}.rea.backup`;
|
||||
const { content } = canonical;
|
||||
original = await readFile(destination, "utf8").catch(() => undefined);
|
||||
if (original === content) return "unchanged";
|
||||
original = await readOptionalText(destination);
|
||||
const legacy: Array<{
|
||||
readonly content: string;
|
||||
readonly destination: string;
|
||||
}> = [];
|
||||
for (const name of PRODUCT_IDENTITY.legacySkillNames) {
|
||||
const legacyDestination = skillDestination(home, name);
|
||||
const legacyContent = await readOptionalText(legacyDestination);
|
||||
if (legacyContent !== undefined) {
|
||||
if ((await lstat(dirname(legacyDestination))).isSymbolicLink())
|
||||
throw new Error("legacy skill directory is a symbolic link");
|
||||
legacy.push({ content: legacyContent, destination: legacyDestination });
|
||||
}
|
||||
}
|
||||
if (original === content && legacy.length === 0) return "unchanged";
|
||||
await mkdir(dirname(destination), { recursive: true });
|
||||
if (original !== undefined)
|
||||
if (original !== undefined && original !== content)
|
||||
await writeFileAtomic(backup, original, {
|
||||
encoding: "utf8",
|
||||
mode: 0o600,
|
||||
});
|
||||
await writeFileAtomic(destination, content, {
|
||||
encoding: "utf8",
|
||||
mode: 0o600,
|
||||
});
|
||||
canonicalChanged = original !== content;
|
||||
if (canonicalChanged)
|
||||
await writeFileAtomic(destination, content, {
|
||||
encoding: "utf8",
|
||||
mode: 0o600,
|
||||
});
|
||||
if ((await readFile(destination, "utf8")) !== content)
|
||||
throw new Error("skill readback mismatch");
|
||||
for (const legacySkill of legacy) {
|
||||
await writeFileAtomic(
|
||||
`${legacySkill.destination}.rea.backup`,
|
||||
legacySkill.content,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
await rm(legacySkill.destination, { force: true });
|
||||
removedLegacy.push(legacySkill);
|
||||
}
|
||||
return "installed";
|
||||
} catch {
|
||||
try {
|
||||
if (original === undefined) await rm(destination, { force: true });
|
||||
else
|
||||
if (canonicalChanged && original === undefined)
|
||||
await rm(destination, { force: true });
|
||||
else if (canonicalChanged && original !== undefined)
|
||||
await writeFileAtomic(destination, original, {
|
||||
encoding: "utf8",
|
||||
mode: 0o600,
|
||||
});
|
||||
} catch {
|
||||
// The backup remains beside the skill for explicit operator recovery.
|
||||
// The canonical backup remains beside the skill for operator recovery.
|
||||
}
|
||||
for (const legacySkill of removedLegacy)
|
||||
try {
|
||||
await mkdir(dirname(legacySkill.destination), { recursive: true });
|
||||
await writeFileAtomic(legacySkill.destination, legacySkill.content, {
|
||||
encoding: "utf8",
|
||||
mode: 0o600,
|
||||
});
|
||||
} catch {
|
||||
// The legacy backup remains beside the skill for operator recovery.
|
||||
}
|
||||
return "failed";
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -87,12 +87,7 @@ export const systemUninstallHost = (
|
|||
): UninstallHost => ({
|
||||
clients: () => Promise.resolve(supportedClients(home)),
|
||||
removeClient: (client) => removeClient(client, fileSystem),
|
||||
removeSkill: () =>
|
||||
removeManagedPath(
|
||||
join(home, ".agents/skills", PRODUCT_IDENTITY.skillName),
|
||||
"skill",
|
||||
fileSystem,
|
||||
),
|
||||
removeSkill: () => removeManagedSkills(home, fileSystem),
|
||||
purgeData: async () => [
|
||||
await removeManagedPath(join(home, ".rea/cache"), "cache", fileSystem),
|
||||
await removeManagedPath(join(home, ".rea/state"), "state", fileSystem),
|
||||
|
|
@ -229,8 +224,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"])))
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -260,6 +257,36 @@ const removeManagedPath = async (
|
|||
}
|
||||
};
|
||||
|
||||
const removeManagedSkills = async (
|
||||
home: string,
|
||||
fileSystem: UninstallFileSystem,
|
||||
): Promise<UninstallItem> => {
|
||||
const results: UninstallItem[] = [];
|
||||
for (const name of [
|
||||
PRODUCT_IDENTITY.skillName,
|
||||
...PRODUCT_IDENTITY.legacySkillNames,
|
||||
])
|
||||
results.push(
|
||||
await removeManagedPath(
|
||||
join(home, ".agents/skills", name),
|
||||
"skill",
|
||||
fileSystem,
|
||||
),
|
||||
);
|
||||
const failed = results.find(({ status }) => status === "failed");
|
||||
if (failed !== undefined) return failed;
|
||||
const retained = results.find(({ status }) => status === "retained");
|
||||
if (retained !== undefined) return retained;
|
||||
const removed = results.filter(({ status }) => status === "removed");
|
||||
if (removed.length > 0)
|
||||
return item(
|
||||
"skill",
|
||||
"removed",
|
||||
removed.map(({ detail }) => detail).join(" "),
|
||||
);
|
||||
return item("skill", "skipped", "No managed REA skill installation exists.");
|
||||
};
|
||||
|
||||
const item = (
|
||||
name: string,
|
||||
status: UninstallItem["status"],
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ export const PACKAGE_METADATA = {
|
|||
serverSdkVersion: "2.0.0-beta.4",
|
||||
clientSdkVersion: "2.0.0-beta.4",
|
||||
coreSdkVersion: "2.0.0-beta.4",
|
||||
skillVersion: "20",
|
||||
skillVersion: "21",
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@
|
|||
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",
|
||||
skillName: "reverse-engineer-anything",
|
||||
legacySkillNames: ["rea-analysis"],
|
||||
skillVersion: PACKAGE_METADATA.skillVersion,
|
||||
configFileName: "rea.json",
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ describe("analysis snapshots", () => {
|
|||
});
|
||||
|
||||
it("writes canonical private v2 JSON and rejects changed query identity", async () => {
|
||||
directory = await mkdtemp(join(tmpdir(), "rea-analysis-snapshot-"));
|
||||
directory = await mkdtemp(join(tmpdir(), "rea-snapshot-"));
|
||||
const path = join(directory, "analysis.json");
|
||||
const target = snapshotTarget(TARGET);
|
||||
const binding = snapshotBinding(PROFILE);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import { readFile } from "node:fs/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("package installation workflows", () => {
|
||||
it("runs package E2E without the retired native rebuild script", async () => {
|
||||
const continuousIntegration = await readFile(
|
||||
new URL("../.github/workflows/ci.yml", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const realHopperLinux = await readFile(
|
||||
new URL("../.github/workflows/real-hopper-linux.yml", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const realHopperMac = await readFile(
|
||||
new URL("../.github/workflows/real-hopper.yml", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(continuousIntegration).toContain("os: [ubuntu-latest, macos-14]");
|
||||
expect(continuousIntegration).toContain("npm run verify:package");
|
||||
for (const workflow of [
|
||||
continuousIntegration,
|
||||
realHopperLinux,
|
||||
realHopperMac,
|
||||
])
|
||||
expect(workflow).not.toContain("npm run rebuild:native");
|
||||
for (const workflow of [realHopperLinux, realHopperMac])
|
||||
expect(workflow).toContain("npm install --global --ignore-scripts");
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -152,6 +152,10 @@ describe("setup workflow", () => {
|
|||
"configure_client",
|
||||
"install_skill",
|
||||
]);
|
||||
expect(
|
||||
result.plannedActions.find(({ kind }) => kind === "install_skill")
|
||||
?.detail,
|
||||
).toContain("back up its SKILL.md");
|
||||
expect(host.hopperInstalls).toBe(0);
|
||||
expect(host.configurations).toBe(0);
|
||||
expect(result.remediation).toBe(
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -146,9 +146,14 @@ describe("agent lifecycle", () => {
|
|||
const home = await mkdtemp(join(tmpdir(), "rea-uninstall-"));
|
||||
roots.push(home);
|
||||
const cursor = join(home, ".cursor/mcp.json");
|
||||
const skill = join(home, ".agents/skills/rea-analysis/SKILL.md");
|
||||
const skill = join(
|
||||
home,
|
||||
".agents/skills/reverse-engineer-anything/SKILL.md",
|
||||
);
|
||||
const legacySkill = join(home, ".agents/skills/rea-analysis/SKILL.md");
|
||||
await mkdir(dirname(cursor), { recursive: true });
|
||||
await mkdir(dirname(skill), { recursive: true });
|
||||
await mkdir(dirname(legacySkill), { recursive: true });
|
||||
await writeFile(
|
||||
cursor,
|
||||
JSON.stringify({
|
||||
|
|
@ -159,6 +164,7 @@ describe("agent lifecycle", () => {
|
|||
}),
|
||||
);
|
||||
await writeFile(skill, "managed");
|
||||
await writeFile(legacySkill, "legacy managed");
|
||||
await mkdir(join(home, ".rea"), { recursive: true });
|
||||
await import("node:fs/promises").then(({ symlink }) =>
|
||||
symlink(home, join(home, ".rea/cache")),
|
||||
|
|
@ -168,6 +174,8 @@ describe("agent lifecycle", () => {
|
|||
expect(JSON.parse(await readFile(cursor, "utf8"))).toEqual({
|
||||
mcpServers: { other: { command: "other" } },
|
||||
});
|
||||
await expect(readFile(skill, "utf8")).rejects.toThrow();
|
||||
await expect(readFile(legacySkill, "utf8")).rejects.toThrow();
|
||||
expect(first.items).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "cache",
|
||||
|
|
@ -300,7 +308,7 @@ describe("agent lifecycle", () => {
|
|||
it("reports a managed-path removal failure", async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), "rea-uninstall-remove-"));
|
||||
roots.push(home);
|
||||
const skillRoot = join(home, ".agents/skills/rea-analysis");
|
||||
const skillRoot = join(home, ".agents/skills/reverse-engineer-anything");
|
||||
await mkdir(skillRoot, { recursive: true });
|
||||
const result = await runUninstall(
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import {
|
||||
mkdtemp,
|
||||
mkdir,
|
||||
readFile,
|
||||
rm,
|
||||
symlink,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
|
@ -19,7 +26,10 @@ describe("canonical skill transaction", () => {
|
|||
it("backs up and upgrades a stale managed skill without touching siblings", async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), "rea-skill-test-"));
|
||||
roots.push(home);
|
||||
const destination = join(home, ".agents/skills/rea-analysis/SKILL.md");
|
||||
const destination = join(
|
||||
home,
|
||||
".agents/skills/reverse-engineer-anything/SKILL.md",
|
||||
);
|
||||
const sibling = join(home, ".agents/skills/unrelated/SKILL.md");
|
||||
await mkdir(dirname(destination), { recursive: true });
|
||||
await mkdir(dirname(sibling), { recursive: true });
|
||||
|
|
@ -31,7 +41,7 @@ describe("canonical skill transaction", () => {
|
|||
expect(await readFile(`${destination}.rea.backup`, "utf8")).toBe(
|
||||
"stale managed skill\n",
|
||||
);
|
||||
expect(await readFile(destination, "utf8")).toContain('version: "20"');
|
||||
expect(await readFile(destination, "utf8")).toContain('version: "21"');
|
||||
expect(await readFile(destination, "utf8")).toContain(
|
||||
`tool_count: ${String(TOOL_CONTRACTS.length)}`,
|
||||
);
|
||||
|
|
@ -39,4 +49,49 @@ describe("canonical skill transaction", () => {
|
|||
expect(await canonicalSkillNeedsInstall(home)).toBe(false);
|
||||
expect(await installCanonicalSkill(home)).toBe("unchanged");
|
||||
});
|
||||
|
||||
it("backs up and retires the legacy skill name", async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), "rea-skill-test-"));
|
||||
roots.push(home);
|
||||
const legacy = join(home, ".agents/skills/rea-analysis/SKILL.md");
|
||||
const destination = join(
|
||||
home,
|
||||
".agents/skills/reverse-engineer-anything/SKILL.md",
|
||||
);
|
||||
await mkdir(dirname(legacy), { recursive: true });
|
||||
await writeFile(legacy, "legacy managed skill\n");
|
||||
|
||||
expect(await canonicalSkillNeedsInstall(home)).toBe(true);
|
||||
expect(await installCanonicalSkill(home)).toBe("installed");
|
||||
expect(await readFile(destination, "utf8")).toContain(
|
||||
"name: reverse-engineer-anything",
|
||||
);
|
||||
expect(await readFile(`${legacy}.rea.backup`, "utf8")).toBe(
|
||||
"legacy managed skill\n",
|
||||
);
|
||||
await expect(readFile(legacy, "utf8")).rejects.toThrow();
|
||||
expect(await canonicalSkillNeedsInstall(home)).toBe(false);
|
||||
});
|
||||
|
||||
it("does not migrate a legacy skill through a directory symlink", async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), "rea-skill-test-"));
|
||||
roots.push(home);
|
||||
const skillsRoot = join(home, ".agents/skills");
|
||||
const external = join(home, "external-skill");
|
||||
const legacyRoot = join(skillsRoot, "rea-analysis");
|
||||
const legacy = join(legacyRoot, "SKILL.md");
|
||||
const destination = join(skillsRoot, "reverse-engineer-anything/SKILL.md");
|
||||
await mkdir(skillsRoot, { recursive: true });
|
||||
await mkdir(external, { recursive: true });
|
||||
await writeFile(join(external, "SKILL.md"), "external skill\n");
|
||||
await symlink(
|
||||
external,
|
||||
legacyRoot,
|
||||
process.platform === "win32" ? "junction" : "dir",
|
||||
);
|
||||
|
||||
expect(await installCanonicalSkill(home)).toBe("failed");
|
||||
expect(await readFile(legacy, "utf8")).toBe("external skill\n");
|
||||
await expect(readFile(destination, "utf8")).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue