From fc21c9a2d724965d35f5a987f6a93047a88f9107 Mon Sep 17 00:00:00 2001
From: morluto <76467478+morluto@users.noreply.github.com>
Date: Sun, 12 Jul 2026 12:26:16 +0800
Subject: [PATCH] Simplify REA onboarding and app targeting
---
README.md | 168 +++++++++++++++-----------------
README_ar.md | 125 +++++++++++-------------
README_ja.md | 117 +++++++++++-----------
README_ko.md | 117 +++++++++++-----------
README_zh.md | 117 +++++++++++-----------
package.json | 13 ++-
scripts/verify-package.mjs | 8 +-
skills/rea-analysis/SKILL.md | 42 +++++++-
src/application/Doctor.ts | 16 ++-
src/application/Setup.ts | 8 +-
src/cli.ts | 24 ++---
src/domain/binaryTarget.ts | 99 ++++++++++++++++++-
src/domain/errors.ts | 8 +-
tests/binaryTarget.test.ts | 67 ++++++++++++-
tests/doctor.test.ts | 15 ++-
tests/readmeConsistency.test.ts | 31 ++++++
16 files changed, 608 insertions(+), 367 deletions(-)
create mode 100644 tests/readmeConsistency.test.ts
diff --git a/README.md b/README.md
index f0ad14249..eb3fc7cff 100644
--- a/README.md
+++ b/README.md
@@ -10,15 +10,15 @@
[](https://www.npmjs.com/package/@morluto/rea)
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
-[](#the-42-tool-workbench)
+[](#42-tools-for-deeper-investigation)
[](https://nodejs.org/)
[](LICENSE)
-[Quick start](#quick-start) · [See the workflow](#one-prompt-a-full-investigation) · [From binary to behavior](#from-binary-to-behavior) · [42 tools](#the-42-tool-workbench) · [How it works](#how-it-works) · [FAQ](#faq)
+[Quick start](#quick-start) · [See the workflow](#one-prompt-a-full-investigation) · [From app to feature](#from-app-to-feature) · [42 tools](#42-tools-for-deeper-investigation) · [How it works](#how-it-works) · [FAQ](#faq)
-npx -y @morluto/rea setup --yes
+npx skills add morluto/rea
@@ -26,19 +26,36 @@
See a feature in an app that you want in your own product? Give the app to your coding agent—even without its source code. With REA, the agent can investigate the feature, understand how it works, and build a version adapted to your stack, design, and requirements.
-REA makes that possible through one CLI and MCP server. Your agent can recover pseudocode, trace behavior across functions, inspect strings and types, and carry the evidence directly into its normal coding workflow. REA handles the reverse-engineering toolchain, analysis session, and target lifecycle behind one interface.
+REA makes that possible through one CLI and MCP server. Your agent can inspect the compiled app, follow how features work, and use what it learns in its normal coding workflow. REA handles the reverse-engineering tools behind one interface.
-## From binary to behavior
+## Just ask your agent
+
+Install the REA skill:
+
+```bash
+npx skills add morluto/rea
+```
+
+Then ask:
+
+```text
+Set up REA and reverse engineer the Notes app. Explain how search works,
+show me how you know, and build a similar feature for my project.
+```
+
+Notes is only an example. Name any app you want to understand, or ask the agent to start with an overview.
+
+## From app to feature
Decompile
-Open a native app or executable and recover procedures, pseudocode, assembly, strings, symbols, segments, and metadata.
+Open an app and recover readable code, strings, names, and other clues about how it works.
|
Understand
-Follow callers, callees, cross-references, and call graphs until the agent can explain how a feature or algorithm actually works.
+Follow the code from one part of the app to another until the agent can explain how a feature actually works.
|
Recreate
@@ -47,66 +64,65 @@ Turn what the agent learned into a feature for your own product, adapted to your
|
-REA keeps the investigation grounded in binary evidence. It does not claim to recover original source code or automatically clone an application.
+REA shows how it reached its conclusions. It does not claim to recover original source code or automatically clone an application.
## Why REA
-| | |
-| ------------------------ | -------------------------------------------------------------------------------------------------------- |
-| **Built for agents** | Ask what a compiled app does and let your agent gather evidence instead of guessing. |
-| **CLI and MCP** | Run the same reverse-engineering capabilities from your terminal or coding agent. |
-| **Complexity handled** | REA sets up the tools, opens the app, keeps the investigation running, and cleans up afterward. |
-| **From insight to code** | Understand a feature, then use the evidence to build your own implementation in the same coding session. |
-| **Local by design** | Analysis runs on your Mac. REA does not upload the binary to a hosted analysis service. |
-| **Keeps context** | Investigate several binaries without starting the analysis over for every question. |
+| | |
+| ------------------------ | ------------------------------------------------------------------------------------ |
+| **Built for agents** | Ask what an app does and let your agent inspect it instead of guessing. |
+| **CLI and MCP** | Run the same reverse-engineering capabilities from your terminal or coding agent. |
+| **Complexity handled** | REA installs and manages the reverse-engineering tools behind the scenes. |
+| **From insight to code** | Understand a feature, then build your own version in the same coding session. |
+| **Local by design** | Analysis runs on your Mac. REA does not upload the app to a hosted analysis service. |
+| **Keeps context** | Investigate several apps without starting over for every question. |
## Quick start
-### Requirements
+### With a coding agent — recommended
-- macOS 12 or newer
-- Node.js 22 or newer
-- [Hopper Disassembler](https://www.hopperapp.com/)
+```bash
+npx skills add morluto/rea
+```
-REA does not bundle Hopper. Setup can install Homebrew when absent, install the `hopper-disassembler` cask, configure detected Claude Desktop and Cursor MCP clients, and install the REA agent skill. Existing client configuration is backed up, updated atomically, and read back before setup reports success.
+Ask your agent to set up REA. It will check your Mac, explain anything it needs to install, ask for approval, and guide you through system prompts. After setup, restart the agent if it asks you to load the full REA toolset.
-Hopper is the separate macOS analysis engine REA controls behind the scenes. Most users interact with REA through the CLI or their agent, but Hopper must be installed and licensed.
-
-### 1. Set up REA
+### From Terminal
```bash
npx -y @morluto/rea setup --yes
```
-### 2. Check the integration
+If macOS or an installer asks for confirmation, complete the prompt and run the same command again. Restart a configured coding agent so it loads REA.
+
+### What setup handles
+
+- macOS 12 or newer
+- Node.js 22 or newer
+
+You do not need to install the reverse-engineering tools manually. Setup installs Homebrew and [Hopper](https://www.hopperapp.com/) when needed, configures detected Claude Desktop and Cursor installations, and installs the REA skill. Hopper is separate software and requires its own license; setup installs it but does not provide a license.
+
+If something is not working, run:
```bash
npx -y @morluto/rea doctor
```
-### 3. Start the MCP server
+### CLI or coding agent?
-```bash
-npx -y @morluto/rea mcp
-```
-
-The MCP command speaks stdio, so it waits silently for a connected client. If setup detected your client, restart that client and use the registered `rea` server instead of running the command by hand.
-
-Then ask your agent:
-
-```text
-Open /path/to/MyApp, summarize the binary, find the authentication flow,
-decompile the relevant procedures, and show the evidence behind your conclusion.
-```
+| If you want to… | Use |
+| --------------------------------------------------------- | ------------------------------------------ |
+| Ask an agent to investigate an app and build a feature | Install the skill, then talk to your agent |
+| Inspect or decompile one part of an app from the Terminal | `rea analyze` or `rea decompile` |
## One prompt, a full investigation
```text
-Open MyApp, find how its offline search feature works, explain the control flow,
+Reverse engineer the Notes app. Find how offline search works, explain it,
and build a version for my project using TypeScript and SQLite.
```
-REA gives the agent a grounded path from that request to working code:
+REA gives the agent a clear path from that request to working code:
| Step | What the agent does | REA tools |
| ---: | --------------------------------------- | ---------------------------------------------------------------- |
@@ -114,10 +130,10 @@ REA gives the agent a grounded path from that request to working code:
| 2 | Finds likely offline-search clues | `search_strings`, `search_procedures`, `list_names` |
| 3 | Connects those clues to executable code | `find_xrefs_to_name`, `xrefs`, `procedure_callers` |
| 4 | Reconstructs the relevant control flow | `get_call_graph`, `procedure_callees`, `procedure_info` |
-| 5 | Recovers the implementation | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
+| 5 | Decompiles the relevant routines | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
| 6 | Builds the feature in your project | code adapted to your stack, product, and requirements |
-REA handles the binary-analysis work in steps 1–5. The agent performs step 6 with its normal file-editing and test tools, keeping the recreated behavior tied to evidence from the binary.
+REA handles the app analysis in steps 1–5. The agent performs step 6 with its normal file-editing and test tools, using what it learned about the app.
## What agents can do
@@ -131,7 +147,7 @@ REA handles the binary-analysis work in steps 1–5. The agent performs step 6 w
- Analyze Swift and Objective-C metadata without manually untangling every mangled symbol.
- Leave names, comments, and bookmarks in Hopper so human and agent analysis reinforce each other.
-## The 42-tool workbench
+## 42 tools for deeper investigation
| Tool family | Count | Examples |
| ----------------- | ----: | ------------------------------------------------------------------------------------------------------ |
@@ -139,16 +155,9 @@ REA handles the binary-analysis work in steps 1–5. The agent performs step 6 w
| Composed analysis | 8 | `binary_overview`, `batch_decompile`, `get_call_graph`, `find_xrefs_to_name`, Swift and ObjC discovery |
| Binary session | 3 | `open_binary`, `binary_session`, `close_binary` |
-The public inventory is contract-tested and verified through an isolated packaged MCP client. Real-Hopper verification separately checks the same 42-tool surface against two binaries.
+## Using REA with other coding agents
-## MCP workflow
-
-1. Call `open_binary` with a readable local binary or `.hop` path.
-2. Start with `binary_overview`, then narrow the investigation with strings, symbols, decompilation, callers, callees, and xrefs.
-3. Call `open_binary` again to switch targets. If the new target fails, REA attempts to reopen the previous target.
-4. Call `close_binary` when finished. `binary_session` reports the current state at any time.
-
-Relative paths resolve from the MCP server's working directory. REA detects Mach-O/FAT, ELF, PE, and Hopper databases automatically. FAT binaries select the host-compatible architecture without presenting Hopper's common architecture picker.
+Setup currently configures Claude Desktop and Cursor automatically. Any coding agent that supports local MCP servers can use REA with the configuration below.
### Manual MCP configuration
@@ -167,25 +176,24 @@ Relative paths resolve from the MCP server's working directory. REA detects Mach
```mermaid
flowchart LR
- Agent["Coding agent"] -->|stdio MCP| REA["REA server
42 tools"]
- REA --> Session["Binary session
open · switch · close"]
- Session -->|authenticated Unix socket| Bridge["REA Python bridge
inside Hopper"]
- Bridge --> API["Hopper public
Python API"]
+ Agent["Coding agent"] --> REA["REA
CLI + MCP"]
+ Terminal --> REA
+ REA --> Hopper["Analysis engine"]
+ Hopper --> App["Your app"]
```
-The CLI and MCP adapter call the same binary-session and analysis runtime directly; neither invokes the other. One-shot commands acquire and close their resources for each operation. MCP mode keeps one session alive across calls and target switches.
+The CLI and MCP server use the same analysis engine. Terminal commands close the app when they finish; an agent session keeps it open while the investigation continues.
## CLI
-Use the package without installing it globally:
+The agent workflow above is the easiest way to use REA. For a one-off overview from the Terminal:
```bash
-npx -y @morluto/rea --help
-npx -y @morluto/rea doctor --target /path/to/binary
-npx -y @morluto/rea analyze /path/to/binary
-npx -y @morluto/rea decompile /path/to/binary 0x100003f20
+npx -y @morluto/rea analyze /Applications/Notes.app
```
+Run `npx -y @morluto/rea --help` for direct decompilation and other options.
+
Or install the `rea` command globally:
```bash
@@ -194,14 +202,7 @@ rea --help
rea mcp
```
-## Configuration
-
-| Variable | Purpose |
-| ------------------------- | ------------------------------------------------------------------------------------- |
-| `HOPPER_TARGET_PATH` | Optional initial binary or `.hop` target. Target-free MCP sessions use `open_binary`. |
-| `HOPPER_LAUNCHER_PATH` | Override the Hopper launcher path. |
-| `HOPPER_TARGET_KIND` | Select `executable` or `database` for an initial target. |
-| `HOPPER_LOADER_ARGS_JSON` | Override REA's derived Hopper loader arguments with a JSON array of strings. |
+REA accepts a Mac `.app` folder directly. If an agent cannot find an app by name, tell it where the app is installed.
## Hopper application behavior
@@ -213,9 +214,7 @@ Closing a REA session shuts down its bridge and removes its private socket direc
## Security model
-Each bridge session has a random capability token and a Unix socket restricted to the current user. Protocol messages are bounded, and caller-visible errors omit launcher stderr and internal exception causes.
-
-This boundary rejects unauthenticated connections and other local user accounts, but it is not a sandbox and does not defend against a malicious process already running as the same user. Opening an untrusted binary delegates parsing and analysis to Hopper with the current macOS user's permissions. Report vulnerabilities through the private process in [SECURITY.md](SECURITY.md).
+REA communicates with Hopper over a private local connection and does not provide a hosted analysis service. This is not a sandbox: Hopper opens apps with your current macOS permissions. Report vulnerabilities through the private process in [SECURITY.md](SECURITY.md).
## FAQ
@@ -236,12 +235,12 @@ Hopper's launcher internally activates the application. REA requests background
Does REA include Hopper?
-No. Hopper is separately installed and licensed software. REA supplies the CLI, MCP server, session management, analysis workflows, and authenticated bridge that make Hopper usable by agents.
+No. Setup can install Hopper for you, but Hopper remains separate software with its own license. REA supplies the CLI, MCP server, and workflows that make it usable by agents.
-Does REA upload my binary?
+Does REA upload the app?
REA has no hosted analysis service. It passes local operations to Hopper through a current-user Unix socket. Your coding agent or model provider may have its own data policy, so review that separately.
@@ -257,28 +256,13 @@ No decompiler can guarantee the original source. REA gives an agent pseudocode,
Which agents can use REA?
-Any client that can launch a stdio MCP server can use the manual configuration. Setup currently detects and configures Claude Desktop and Cursor automatically.
+Any coding agent that can run a local MCP server can use the manual configuration. Setup currently detects and configures Claude Desktop and Cursor automatically.
## Development
-```bash
-npm ci
-npm run check
-npm run verify:package
-npm pack --dry-run
-```
-
-Real-Hopper verification requires two distinct binaries:
-
-```bash
-HOPPER_TARGET_PATH=/path/to/target-a \
-HOPPER_SECOND_TARGET_PATH=/path/to/distinct-target-b \
-npm run verify:hopper
-```
-
-See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture guidelines, pull-request expectations, and the maintainer release checklist. Generated API documentation is available under [`docs/api`](docs/api/index.html).
+See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, architecture, tests, and release instructions. Generated API documentation is available under [`docs/api`](docs/api/index.html).
## Project links
diff --git a/README_ar.md b/README_ar.md
index 84857d98d..8f416a082 100644
--- a/README_ar.md
+++ b/README_ar.md
@@ -18,13 +18,28 @@
-npx -y @morluto/rea setup --yes
+npx skills add morluto/rea
هل وجدت في تطبيق ما ميزة تريدها في منتجك؟ أعط التطبيق إلى وكيل البرمجة حتى من دون شيفرته المصدرية. باستخدام REA، يستطيع الوكيل استقصاء الميزة وفهم آلية عملها ثم بناء نسخة ملائمة لتقنياتك وتصميمك ومتطلباتك.
-يوفر REA هذه العملية عبر CLI وخادم MCP واحدين. يستطيع الوكيل استعادة الشيفرة شبه المصدرية، وتتبع السلوك بين الدوال، وفحص السلاسل والأنواع، ثم استخدام الأدلة مباشرة في عمله البرمجي المعتاد. ويجمع REA سلسلة أدوات الهندسة العكسية وجلسة التحليل ودورة حياة الهدف خلف واجهة واحدة.
+يوفر REA هذه العملية عبر CLI وخادم MCP واحدين. يستطيع الوكيل قراءة شيفرة التطبيق وتتبع طريقة عمل الميزات ثم استخدام الأدلة مباشرة في عمله البرمجي المعتاد. ويتولى REA أدوات الهندسة العكسية المعقدة خلف واجهة واحدة.
+
+## اطلب من وكيلك مباشرة
+
+```bash
+npx skills add morluto/rea
+```
+
+ثم اطلب:
+
+```text
+أعد REA وأجر هندسة عكسية لتطبيق Notes. اشرح طريقة عمل البحث وكيف توصلت إلى ذلك،
+ثم ابنِ ميزة مشابهة لمشروعي.
+```
+
+تطبيق Notes مجرد مثال. يمكنك تسمية أي تطبيق تريد فهمه أو طلب البدء بنظرة عامة.
## من الملف التنفيذي إلى السلوك
@@ -32,7 +47,7 @@
| ----------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------- |
| استخرج الشيفرة شبه المصدرية، والتعليمات، والسلاسل النصية، والرموز | تتبع تدفق التحكم والمراجع المتبادلة والاستدعاءات والأنواع | حوّل ما تعلمه الوكيل إلى ميزة تناسب تقنياتك وواجهتك ومتطلباتك |
-يربط REA الاستقصاء بأدلة من الملف التنفيذي. وهو لا يدّعي استعادة الشيفرة المصدرية الأصلية أو استنساخ التطبيق تلقائيًا.
+يوضح REA كيف وصل إلى نتائجه. وهو لا يدّعي استعادة الشيفرة المصدرية الأصلية أو استنساخ التطبيق تلقائيًا.
## لماذا REA؟
@@ -47,45 +62,48 @@
## البدء السريع
-### 1. المتطلبات
+### باستخدام وكيل برمجة — موصى به
+
+```bash
+npx skills add morluto/rea
+```
+
+اطلب من الوكيل إعداد REA. سيفحص جهاز Mac ويشرح ما يحتاج إلى تثبيته ويطلب الموافقة ويرشدك خلال رسائل النظام. أعد تشغيل الوكيل بعد الإعداد إذا طلب ذلك لتحميل مجموعة الأدوات الكاملة.
+
+### قبل البدء
- macOS 12 أو أحدث
- Node.js 22 أو أحدث
-- [Hopper Disassembler](https://www.hopperapp.com/) مثبتًا ومرخصًا
-لا تتضمن حزمة REA برنامج Hopper. يستطيع الإعداد تثبيت Homebrew عند غيابه، وتثبيت حزمة `hopper-disassembler`، وتهيئة عملاء Claude Desktop وCursor المكتشفين، وتثبيت مهارة وكيل REA. تُنسخ إعدادات العملاء الحالية احتياطيًا، ثم تُحدّث ذريًا وتُقرأ مرة أخرى قبل الإبلاغ عن النجاح.
+لا تحتاج إلى تثبيت أدوات الهندسة العكسية يدويًا. يثبت Setup برنامج Homebrew و[Hopper](https://www.hopperapp.com/) عند الحاجة، ثم يهيئ وكلاء البرمجة المدعومين. Hopper برنامج منفصل يحتاج إلى ترخيص خاص به؛ يثبته Setup لكنه لا يوفر الترخيص.
-### 2. إعداد وكلائك
+### 1. إعداد REA
```bash
npx -y @morluto/rea setup --yes
```
-يكتشف هذا الأمر عملاء الوكلاء المدعومين ويسجل خادم MCP باستخدام أمر عائم:
+إذا طلب macOS أو برنامج التثبيت تأكيدًا، فأكمل الخطوة ثم شغّل الأمر نفسه مرة أخرى.
+
+### 2. إعادة تشغيل وكيل البرمجة
+
+يهيئ Setup تلقائيًا نسخ Claude Desktop وCursor التي يعثر عليها. أعد تشغيل التطبيق كي يحمّل REA.
+
+### 3. اطلب من الوكيل
+
+يمكنك ذكر التطبيق باسمه. سيعثر وكيل البرمجة عليه ويمرر ملف البرنامج الذي يحتاجه REA.
```text
-npx -y @morluto/rea mcp
+استخدم REA لإجراء هندسة عكسية لتطبيق Notes. اشرح طريقة عمل البحث واعرض الأدلة،
+ثم ابنِ ميزة مشابهة لمشروعي باستخدام SQLite.
```
-### 3. تحقق من البيئة
+إذا واجهت مشكلة، فشغّل:
```bash
npx -y @morluto/rea doctor
```
-### 4. حلّل ملفًا تنفيذيًا
-
-```bash
-npx -y @morluto/rea analyze /path/to/app
-npx -y @morluto/rea decompile /path/to/app 0x100003f20
-```
-
-أو شغّل خادم MCP مباشرة:
-
-```bash
-npx -y @morluto/rea mcp
-```
-
## استقصاء كامل بطلب واحد
بعد الإعداد، يمكنك أن تطلب من وكيلك:
@@ -135,11 +153,9 @@ npx -y @morluto/rea mcp
| التحليل المركب | 8 | النظرة العامة، وفك الترجمة الدفعي، ومخططات الاستدعاء، والمراجع، واكتشاف الأنواع |
| جلسة الملف التنفيذي | 3 | `open_binary` و`binary_session` و`close_binary` |
-تُختبر قائمة الأدوات العامة بعقود آلية وعميل MCP معزول من الحزمة. ويتحقق الاختبار باستخدام محرك التحليل الفعلي من واجهة الأدوات الـ42 نفسها على ملفين تنفيذيين.
+## استخدام REA مع وكلاء برمجة آخرين
-## استخدام REA مع وكيل MCP
-
-يسجل `rea setup` تكوينًا مكافئًا لما يلي:
+يهيئ Setup حاليًا Claude Desktop وCursor تلقائيًا. يمكن لأي وكيل برمجة يدعم خوادم MCP المحلية استخدام REA بالإعداد التالي:
```json
{
@@ -152,49 +168,31 @@ npx -y @morluto/rea mcp
}
```
-يمكنك أيضًا تشغيل وضع MCP مباشرة:
-
-```bash
-rea mcp
-rea --mcp
-```
-
-كلا الأمرين يشغل خادم stdio نفسه. تبقى جلسة Hopper حية بين طلبات الأدوات، ويمكن للوكيل تبديل الأهداف من دون إعادة بناء الخادم.
-
## كيف يعمل؟
```mermaid
flowchart LR
Human[المستخدم] --> Agent[وكيل البرمجة]
- Agent -->|MCP| REA[REA]
+ Agent --> REA[REA]
Human -->|CLI| REA
- REA --> Runtime[طبقة الجلسة والتحليل المشتركة]
- Runtime --> Bridge[جسر Python داخل العملية]
- Bridge --> Hopper[Hopper Disassembler]
- Hopper --> Binary[ملف تنفيذي أو قاعدة .hop]
+ REA --> Hopper[محرك التحليل]
+ Hopper --> App[تطبيقك]
```
-لا يستدعي محول CLI خادم MCP، ولا يستدعي خادم MCP واجهة CLI. كلاهما يعتمد مباشرة على طبقة التشغيل المشتركة نفسها.
+يستخدم CLI وخادم MCP محرك التحليل نفسه. تغلق أوامر الطرفية التطبيق عند انتهائها، بينما تبقيه جلسة الوكيل مفتوحًا أثناء الاستقصاء.
## أوامر CLI
-```text
-rea setup [--yes] تسجيل REA لدى عملاء الوكلاء
-rea doctor فحص Node.js وHopper وتكوين العملاء
-rea analyze تحليل ملف تنفيذي أو قاعدة Hopper
-rea decompile فك ترجمة دالة عند عنوان محدد
-rea mcp تشغيل خادم MCP عبر stdio
-rea mcp add إضافة تسجيلات MCP
-rea mcp doctor تشخيص تسجيلات MCP
-rea --mcp اسم بديل متوافق لوضع MCP
-```
-
-اعرض جميع الخيارات باستخدام:
+سير عمل الوكيل أعلاه هو أسهل طريقة لاستخدام REA. للحصول على نظرة سريعة على تطبيق من الطرفية:
```bash
-npx -y @morluto/rea --help
+npx -y @morluto/rea analyze /Applications/Notes.app
```
+شغّل `npx -y @morluto/rea --help` لمعرفة خيارات فك الترجمة المباشر والخيارات الأخرى.
+
+يمكن لـ REA فتح مجلد `.app` على Mac مباشرة. إذا لم يعثر الوكيل على التطبيق، فأخبره بمكان تثبيته.
+
## سلوك Hopper
يستطيع REA تشغيل Hopper تلقائيًا؛ لا حاجة إلى افتراض أنه يعمل مسبقًا. لكنه تطبيق macOS رسومي، لذلك قد يطلب التركيز أو يعرض حوارًا عندما يفرض Hopper قرارًا تفاعليًا. لا يمكن لـ REA ضمان تشغيل غير مرئي تمامًا لأن هذه الواجهة يملكها Hopper وmacOS.
@@ -219,7 +217,7 @@ export HOPPER_LOADER_ARGS_JSON='["-l", "Mach-O", "--aarch64"]'
هل يتضمن REA محرك فك ترجمة خاصًا به؟
-لا. يوفر REA طبقة الوكيل وواجهة CLI وإدارة الجلسات وسير العمل، بينما يوفر Hopper محرك التحليل وفك الترجمة.
+لا. يستطيع Setup تثبيت Hopper، لكنه يظل برنامجًا منفصلًا بترخيص خاص به. يوفر REA واجهة CLI وخادم MCP وسير العمل المخصص للوكلاء.
@@ -246,20 +244,7 @@ export HOPPER_LOADER_ARGS_JSON='["-l", "Mach-O", "--aarch64"]'
## التطوير
-```bash
-git clone https://github.com/morluto/rea.git
-cd rea
-npm ci
-npm run check
-```
-
-يتطلب التحقق الحقيقي من Hopper تثبيتًا محليًا وملفات اختبار مناسبة:
-
-```bash
-npm run verify:hopper
-```
-
-راجع [CONTRIBUTING.md](CONTRIBUTING.md) لإرشادات التطوير والإصدار.
+راجع [CONTRIBUTING.md](CONTRIBUTING.md) لإعداد بيئة التطوير وبنية المشروع والاختبارات وإرشادات الإصدار.
## المشروع
diff --git a/README_ja.md b/README_ja.md
index 030534f89..5e25816b1 100644
--- a/README_ja.md
+++ b/README_ja.md
@@ -18,7 +18,7 @@
-npx -y @morluto/rea setup --yes
+npx skills add morluto/rea
@@ -26,7 +26,22 @@
アプリの気になる機能を自分のプロダクトにも取り入れたいと思ったことはありませんか。ソースコードがなくても、そのアプリをコーディングエージェントに渡せます。REA を使えば、エージェントが機能を調査して仕組みを理解し、あなたの技術スタック、デザイン、要件に合わせた形で実装できます。
-REA はこの流れをひとつの CLI / MCP サーバーで実現します。エージェントは疑似コードを復元し、関数をまたぐ動作を追跡し、文字列や型を調べ、その根拠を通常のコーディング作業へ直接持ち込めます。REA はツールチェーン、解析セッション、ターゲットのライフサイクルをひとつのインターフェースで扱います。
+REA はこの流れをひとつの CLI / MCP サーバーで実現します。エージェントはコンパイル済みのアプリを調べ、機能の流れを追い、分かったことを通常のコーディング作業に活用できます。複雑なリバースエンジニアリングツールは REA がひとつのインターフェースの裏側で扱います。
+
+## エージェントに頼むだけ
+
+```bash
+npx skills add morluto/rea
+```
+
+次のように依頼します:
+
+```text
+REA をセットアップしてメモアプリをリバースエンジニアリングしてください。
+検索の仕組みと判断の理由を示し、私のプロジェクト向けに同様の機能を実装してください。
+```
+
+メモアプリは例にすぎません。調べたいアプリを指定するか、まず概要から始めるよう依頼できます。
## バイナリから動作へ
@@ -49,38 +64,51 @@ REA は調査をバイナリ上の根拠に結び付けます。元のソース
## クイックスタート
-### 必要環境
+### コーディングエージェントから(推奨)
+
+```bash
+npx skills add morluto/rea
+```
+
+エージェントに REA のセットアップを依頼してください。Mac を確認し、必要なインストールを説明して承認を求め、システムプロンプトを案内します。完全なツールを読み込むため再起動を求められた場合は、セットアップ後に再起動してください。
+
+### 始める前に
- macOS 12 以降
- Node.js 22 以降
-- [Hopper Disassembler](https://www.hopperapp.com/)
-REA に Hopper は含まれません。Setup は必要に応じて Homebrew と `hopper-disassembler` cask をインストールし、検出した Claude Desktop / Cursor の MCP 設定と REA エージェントスキルを構成します。既存設定はバックアップされ、アトミックに更新された後、読み戻し検証されます。
+リバースエンジニアリングツールを手動でインストールする必要はありません。Setup は必要に応じて Homebrew と [Hopper](https://www.hopperapp.com/) をインストールし、対応するコーディングエージェントを設定します。Hopper は別製品で、ライセンスは別途必要です。Setup はインストールしますが、ライセンスは提供しません。
```bash
-# 1. インストールと設定
+# 1. REA をセットアップ
npx -y @morluto/rea setup --yes
-
-# 2. 統合を診断
-npx -y @morluto/rea doctor
-
-# 3. stdio MCP サーバーを起動
-npx -y @morluto/rea mcp
```
-MCP コマンドは stdio で通信するため、クライアント接続を静かに待ちます。Setup がクライアントを構成した場合は、そのクライアントを再起動して登録済みの `rea` サーバーを使用してください。
+macOS やインストーラーから確認を求められた場合は、その操作を完了してから同じコマンドをもう一度実行してください。
-エージェントへの依頼例:
+### 2. コーディングエージェントを再起動
+
+Setup は検出した Claude Desktop と Cursor を自動設定します。REA を読み込むため、アプリを再起動してください。
+
+### 3. エージェントに依頼
+
+アプリは名前で指定できます。コーディングエージェントがアプリを探し、REA に必要なプログラムファイルを渡します。
```text
-/path/to/MyApp を開き、バイナリの概要を示し、認証フローを見つけ、
-関連プロシージャを逆コンパイルして、結論を支える根拠を提示してください。
+REA でメモアプリをリバースエンジニアリングしてください。検索機能の仕組みと
+根拠を示し、SQLite を使って私のプロジェクト向けに同様の機能を実装してください。
+```
+
+問題がある場合は次を実行します:
+
+```bash
+npx -y @morluto/rea doctor
```
## ひとつのプロンプトで調査を完結
```text
-MyApp を開き、オフライン検索機能の仕組みと制御フローを説明し、
+メモアプリをリバースエンジニアリングし、オフライン検索機能の仕組みを説明して、
TypeScript と SQLite を使って私のプロジェクト向けに実装してください。
```
@@ -90,7 +118,7 @@ TypeScript と SQLite を使って私のプロジェクト向けに実装して
| 2 | オフライン検索の手掛かりを探す | `search_strings`, `search_procedures`, `list_names` |
| 3 | 手掛かりと実行コードを接続 | `find_xrefs_to_name`, `xrefs`, `procedure_callers` |
| 4 | 制御フローを復元 | `get_call_graph`, `procedure_callees`, `procedure_info` |
-| 5 | 実装を復元 | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
+| 5 | 関連する処理を逆コンパイル | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
| 6 | プロジェクトに機能を実装する | 技術スタック、プロダクト、要件に合わせたコード |
REA は手順 1〜5 のバイナリ解析を処理し、手順 6 はエージェントの通常の編集・テストツールが行います。
@@ -115,18 +143,9 @@ REA は手順 1〜5 のバイナリ解析を処理し、手順 6 はエージェ
| 合成解析 | 8 | `binary_overview`, `batch_decompile`, `get_call_graph`, `find_xrefs_to_name`, Swift / ObjC 検出 |
| バイナリセッション | 3 | `open_binary`, `binary_session`, `close_binary` |
-公開ツール一覧は契約テストと分離されたパッケージ MCP クライアントで検証されています。実 Hopper 検証では 2 つのバイナリを使って同じ 42 ツールを確認します。
+## 他のコーディングエージェントで使う
-## MCP ワークフロー
-
-1. `open_binary` で読み取り可能なローカルバイナリまたは `.hop` を開きます。
-2. `binary_overview` から始め、文字列、シンボル、逆コンパイル、callers、callees、xrefs で範囲を絞ります。
-3. `open_binary` を再度呼び出してターゲットを切り替えます。失敗時は以前のターゲットを再度開きます。
-4. 終了時に `close_binary` を呼びます。`binary_session` はいつでも状態を返します。
-
-REA は Mach-O/FAT、ELF、PE、Hopper データベースを自動判定します。相対パスは MCP サーバーの作業ディレクトリ基準で、FAT バイナリではホスト互換アーキテクチャを選択します。
-
-### MCP 手動設定
+Setup は現在 Claude Desktop と Cursor を自動設定します。ローカル MCP サーバーに対応するコーディングエージェントなら、次の設定で REA を利用できます。
```json
{
@@ -143,23 +162,24 @@ REA は Mach-O/FAT、ELF、PE、Hopper データベースを自動判定しま
```mermaid
flowchart LR
- Agent["コーディングエージェント"] -->|stdio MCP| REA["REA サーバー
42 ツール"]
- REA --> Session["バイナリセッション
開く · 切替 · 閉じる"]
- Session -->|認証済み Unix ソケット| Bridge["Hopper 内の
REA Python ブリッジ"]
- Bridge --> API["Hopper 公開
Python API"]
+ Agent["コーディングエージェント"] --> REA["REA
CLI + MCP"]
+ Terminal["ターミナル"] --> REA
+ REA --> Hopper["解析エンジン"]
+ Hopper --> App["あなたのアプリ"]
```
-CLI と MCP アダプターは同じセッション・解析ランタイムを直接使用し、互いを呼び出しません。単発コマンドは処理ごとにリソースを閉じ、MCP モードは複数の呼び出しやターゲット切り替えをまたいでセッションを維持します。
+CLI と MCP サーバーは同じ解析エンジンを使います。ターミナルのコマンドは終了時にアプリを閉じ、エージェントのセッションは調査中アプリを開いたままにします。
## CLI
+上のエージェントワークフローが、REA を使う最も簡単な方法です。ターミナルから一度だけアプリの概要を調べる場合は、次を実行します。
+
```bash
-npx -y @morluto/rea --help
-npx -y @morluto/rea doctor --target /path/to/binary
-npx -y @morluto/rea analyze /path/to/binary
-npx -y @morluto/rea decompile /path/to/binary 0x100003f20
+npx -y @morluto/rea analyze /Applications/Notes.app
```
+直接デコンパイルする方法やその他のオプションは、`npx -y @morluto/rea --help` で確認できます。
+
グローバルな `rea` コマンドとしてもインストールできます。
```bash
@@ -168,6 +188,8 @@ rea --help
rea mcp
```
+REA は Mac の `.app` フォルダーを直接開けます。エージェントがアプリを見つけられない場合は、インストール場所を伝えてください。
+
## Hopper アプリの動作
REA は必要なときに Hopper を起動します。Hopper のランチャーは内部でアプリをアクティブ化するため、ターゲットを開くと Hopper が前面に出る場合があります。REA はバックグラウンド起動を要求しますが、常に背面に留まる保証はありません。
@@ -188,7 +210,7 @@ REA は必要なときに Hopper を起動します。Hopper のランチャー
REA に Hopper は含まれますか?
-含まれません。Hopper は別途インストール・ライセンスが必要です。REA は CLI、MCP、セッション、解析ワークフロー、認証ブリッジを提供します。
+含まれません。Setup で Hopper をインストールできますが、Hopper は独自のライセンスを持つ別製品です。REA は CLI、MCP サーバー、エージェント向けワークフローを提供します。
@@ -206,22 +228,7 @@ REA にホスト型解析サービスはありません。ローカル Unix ソ
## 開発
-```bash
-npm ci
-npm run check
-npm run verify:package
-npm pack --dry-run
-```
-
-実 Hopper 検証には異なる 2 つのバイナリが必要です。
-
-```bash
-HOPPER_TARGET_PATH=/path/to/target-a \
-HOPPER_SECOND_TARGET_PATH=/path/to/distinct-target-b \
-npm run verify:hopper
-```
-
-アーキテクチャ規約、Pull Request、リリース手順は [CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。
+開発環境、アーキテクチャ、テスト、リリース手順は [CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。
## ライセンス
diff --git a/README_ko.md b/README_ko.md
index be112e0db..9d4273475 100644
--- a/README_ko.md
+++ b/README_ko.md
@@ -18,7 +18,7 @@
-npx -y @morluto/rea setup --yes
+npx skills add morluto/rea
@@ -26,7 +26,22 @@
어떤 앱에서 마음에 드는 기능을 자신의 제품에도 넣고 싶나요? 소스 코드가 없어도 앱을 코딩 에이전트에 전달할 수 있습니다. REA를 사용하면 에이전트가 기능을 조사하고 작동 방식을 이해한 뒤, 사용자의 기술 스택, 디자인, 요구 사항에 맞는 버전을 구현할 수 있습니다.
-REA는 이 과정을 하나의 CLI 및 MCP 서버로 제공합니다. 에이전트는 의사 코드를 복구하고, 함수 사이의 동작을 추적하고, 문자열과 타입을 조사한 뒤 그 증거를 일반 코딩 작업에 바로 활용할 수 있습니다. REA는 도구 체인, 분석 세션, 대상 수명 주기를 하나의 인터페이스로 처리합니다.
+REA는 이 과정을 하나의 CLI 및 MCP 서버로 제공합니다. 에이전트는 컴파일된 앱을 조사하고 기능의 작동 방식을 추적한 뒤 알아낸 내용을 일반 코딩 작업에 활용할 수 있습니다. 복잡한 리버스 엔지니어링 도구는 REA가 하나의 인터페이스 뒤에서 처리합니다.
+
+## 에이전트에게 바로 요청하기
+
+```bash
+npx skills add morluto/rea
+```
+
+그런 다음 요청하세요.
+
+```text
+REA를 설정하고 메모 앱을 리버스 엔지니어링해 주세요. 검색 기능의 작동 방식과
+그렇게 판단한 이유를 보여 주고, 제 프로젝트에 비슷한 기능을 구현해 주세요.
+```
+
+메모 앱은 예시일 뿐입니다. 이해하고 싶은 앱을 지정하거나 먼저 개요부터 살펴보라고 요청할 수 있습니다.
## 바이너리에서 동작까지
@@ -49,38 +64,51 @@ REA는 분석을 바이너리 증거에 근거하게 합니다. 원본 소스
## 빠른 시작
-### 요구 사항
+### 코딩 에이전트에서 시작하기(권장)
+
+```bash
+npx skills add morluto/rea
+```
+
+에이전트에게 REA 설정을 요청하세요. Mac을 확인하고 필요한 설치 내용을 설명한 뒤 승인을 요청하며 시스템 프롬프트를 안내합니다. 전체 도구를 불러오기 위해 재시작하라고 하면 설정 후 에이전트를 재시작하세요.
+
+### 시작하기 전에
- macOS 12 이상
- Node.js 22 이상
-- [Hopper Disassembler](https://www.hopperapp.com/)
-REA는 Hopper를 번들하지 않습니다. Setup은 필요할 때 Homebrew와 `hopper-disassembler` cask를 설치하고, 감지된 Claude Desktop 및 Cursor MCP 클라이언트와 REA 에이전트 스킬을 구성합니다. 기존 설정은 백업되고 원자적으로 갱신된 뒤 다시 읽어 검증합니다.
+리버스 엔지니어링 도구를 직접 설치할 필요는 없습니다. Setup은 필요할 때 Homebrew와 [Hopper](https://www.hopperapp.com/)를 설치하고 지원되는 코딩 에이전트를 구성합니다. Hopper는 별도 소프트웨어이며 별도 라이선스가 필요합니다. Setup은 Hopper를 설치하지만 라이선스를 제공하지 않습니다.
```bash
-# 1. 설치 및 구성
+# 1. REA 설정
npx -y @morluto/rea setup --yes
-
-# 2. 통합 진단
-npx -y @morluto/rea doctor
-
-# 3. stdio MCP 서버 시작
-npx -y @morluto/rea mcp
```
-MCP 명령은 stdio로 통신하므로 클라이언트 연결을 조용히 기다립니다. Setup이 클라이언트를 구성했다면 클라이언트를 다시 시작하고 등록된 `rea` 서버를 사용하세요.
+macOS나 설치 프로그램이 확인을 요청하면 해당 절차를 완료하고 같은 명령을 다시 실행하세요.
-에이전트에게 다음과 같이 요청할 수 있습니다.
+### 2. 코딩 에이전트 재시작
+
+Setup은 감지된 Claude Desktop과 Cursor를 자동으로 구성합니다. REA를 불러오도록 앱을 재시작하세요.
+
+### 3. 에이전트에게 요청
+
+앱 이름만 말해도 됩니다. 코딩 에이전트가 앱을 찾아 REA에 필요한 프로그램 파일을 전달합니다.
```text
-/path/to/MyApp을 열고 바이너리를 요약한 다음 인증 흐름을 찾아
-관련 프로시저를 디컴파일하고 결론을 뒷받침하는 증거를 보여 주세요.
+REA로 메모 앱을 리버스 엔지니어링해 주세요. 검색 기능의 작동 방식과 증거를 보여 주고,
+SQLite를 사용해 제 프로젝트에 비슷한 기능을 구현해 주세요.
+```
+
+문제가 있으면 다음을 실행하세요.
+
+```bash
+npx -y @morluto/rea doctor
```
## 하나의 프롬프트로 전체 조사
```text
-MyApp을 열고 오프라인 검색 기능의 작동 방식과 제어 흐름을 설명한 다음,
+메모 앱을 리버스 엔지니어링해 오프라인 검색 기능의 작동 방식을 설명한 다음,
TypeScript와 SQLite를 사용해 제 프로젝트에 맞는 버전을 구현해 주세요.
```
@@ -90,7 +118,7 @@ TypeScript와 SQLite를 사용해 제 프로젝트에 맞는 버전을 구현해
| 2 | 오프라인 검색 단서 찾기 | `search_strings`, `search_procedures`, `list_names` |
| 3 | 단서를 실행 코드에 연결 | `find_xrefs_to_name`, `xrefs`, `procedure_callers` |
| 4 | 제어 흐름 재구성 | `get_call_graph`, `procedure_callees`, `procedure_info` |
-| 5 | 구현 복구 | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
+| 5 | 관련 루틴 디컴파일 | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
| 6 | 프로젝트에 기능 구현 | 기술 스택, 제품, 요구 사항에 맞는 코드 |
REA는 1–5단계의 바이너리 분석을 처리합니다. 6단계는 에이전트가 일반 파일 편집 및 테스트 도구로 수행합니다.
@@ -115,18 +143,9 @@ REA는 1–5단계의 바이너리 분석을 처리합니다. 6단계는 에이
| 합성 분석 | 8 | `binary_overview`, `batch_decompile`, `get_call_graph`, `find_xrefs_to_name`, Swift 및 ObjC 탐색 |
| 바이너리 세션 | 3 | `open_binary`, `binary_session`, `close_binary` |
-공개 도구 목록은 계약 테스트와 격리된 패키지 MCP 클라이언트로 검증됩니다. 실제 Hopper 검증은 서로 다른 두 바이너리에서 같은 42개 도구를 확인합니다.
+## 다른 코딩 에이전트에서 사용하기
-## MCP 워크플로
-
-1. `open_binary`로 읽을 수 있는 로컬 바이너리 또는 `.hop` 파일을 엽니다.
-2. `binary_overview`로 시작하고 문자열, 심볼, 디컴파일, callers, callees, xrefs로 범위를 좁힙니다.
-3. `open_binary`를 다시 호출해 대상을 전환합니다. 새 대상이 실패하면 이전 대상을 다시 엽니다.
-4. 완료되면 `close_binary`를 호출합니다. `binary_session`은 언제든 현재 상태를 반환합니다.
-
-REA는 Mach-O/FAT, ELF, PE, Hopper 데이터베이스를 자동으로 감지합니다. 상대 경로는 MCP 서버 작업 디렉터리를 기준으로 하며 FAT 바이너리는 호스트와 호환되는 아키텍처를 선택합니다.
-
-### 수동 MCP 구성
+Setup은 현재 Claude Desktop과 Cursor를 자동으로 구성합니다. 로컬 MCP 서버를 지원하는 코딩 에이전트라면 다음 설정으로 REA를 사용할 수 있습니다.
```json
{
@@ -143,23 +162,24 @@ REA는 Mach-O/FAT, ELF, PE, Hopper 데이터베이스를 자동으로 감지합
```mermaid
flowchart LR
- Agent["코딩 에이전트"] -->|stdio MCP| REA["REA 서버
42개 도구"]
- REA --> Session["바이너리 세션
열기 · 전환 · 닫기"]
- Session -->|인증 Unix 소켓| Bridge["Hopper 내부
REA Python 브리지"]
- Bridge --> API["Hopper 공개
Python API"]
+ Agent["코딩 에이전트"] --> REA["REA
CLI + MCP"]
+ Terminal["터미널"] --> REA
+ REA --> Hopper["분석 엔진"]
+ Hopper --> App["사용자의 앱"]
```
-CLI와 MCP 어댑터는 같은 세션 및 분석 런타임을 직접 사용하며 서로를 호출하지 않습니다. 일회성 명령은 매 작업 후 리소스를 닫고 MCP 모드는 여러 호출과 대상 전환 동안 세션을 유지합니다.
+CLI와 MCP 서버는 같은 분석 엔진을 사용합니다. 터미널 명령은 끝날 때 앱을 닫고, 에이전트 세션은 조사하는 동안 앱을 열어 둡니다.
## CLI
+위의 에이전트 워크플로가 REA를 사용하는 가장 쉬운 방법입니다. 터미널에서 앱을 한 번 빠르게 살펴보려면 다음을 실행하세요.
+
```bash
-npx -y @morluto/rea --help
-npx -y @morluto/rea doctor --target /path/to/binary
-npx -y @morluto/rea analyze /path/to/binary
-npx -y @morluto/rea decompile /path/to/binary 0x100003f20
+npx -y @morluto/rea analyze /Applications/Notes.app
```
+직접 디컴파일하는 방법과 기타 옵션은 `npx -y @morluto/rea --help`에서 확인할 수 있습니다.
+
전역 `rea` 명령으로 설치할 수도 있습니다.
```bash
@@ -168,6 +188,8 @@ rea --help
rea mcp
```
+REA는 Mac의 `.app` 폴더를 직접 열 수 있습니다. 에이전트가 앱을 찾지 못하면 설치 위치를 알려 주세요.
+
## Hopper 앱 동작
REA는 필요할 때 Hopper를 시작합니다. Hopper 런처는 내부적으로 앱을 활성화하므로 대상을 열 때 Hopper가 다른 창 앞으로 나타날 수 있습니다. REA는 백그라운드 시작을 요청하지만 항상 뒤에 머무르는 것을 보장할 수 없습니다.
@@ -188,7 +210,7 @@ REA는 필요할 때 Hopper를 시작합니다. Hopper 런처는 내부적으로
REA에 Hopper가 포함되나요?
-아니요. Hopper는 별도로 설치하고 라이선스를 받아야 합니다. REA는 CLI, MCP, 세션 관리, 분석 워크플로 및 인증 브리지를 제공합니다.
+아니요. Setup이 Hopper를 설치할 수 있지만 Hopper는 자체 라이선스가 필요한 별도 소프트웨어입니다. REA는 CLI, MCP 서버, 에이전트용 워크플로를 제공합니다.
@@ -206,22 +228,7 @@ REA에는 호스팅 분석 서비스가 없습니다. 로컬 Unix 소켓을 통
## 개발
-```bash
-npm ci
-npm run check
-npm run verify:package
-npm pack --dry-run
-```
-
-실제 Hopper 검증에는 서로 다른 두 바이너리가 필요합니다.
-
-```bash
-HOPPER_TARGET_PATH=/path/to/target-a \
-HOPPER_SECOND_TARGET_PATH=/path/to/distinct-target-b \
-npm run verify:hopper
-```
-
-아키텍처 지침, Pull Request 요구 사항, 릴리스 체크리스트는 [CONTRIBUTING.md](CONTRIBUTING.md)를 참고하세요.
+개발 환경, 아키텍처, 테스트, 릴리스 지침은 [CONTRIBUTING.md](CONTRIBUTING.md)를 참고하세요.
## 라이선스
diff --git a/README_zh.md b/README_zh.md
index f4b562070..d0f3dc6a8 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -18,7 +18,7 @@
-npx -y @morluto/rea setup --yes
+npx skills add morluto/rea
@@ -26,7 +26,22 @@
看到某个应用中想加入自己产品的功能?即使没有源代码,也可以把应用交给编程智能体。借助 REA,智能体能够调查该功能、理解其工作原理,并按照你的技术栈、设计和需求构建适合你产品的版本。
-REA 通过一个 CLI 与 MCP 服务器实现这套流程。智能体可以恢复伪代码、跨函数追踪行为、检查字符串和类型,并把证据直接带入日常编码工作。REA 将逆向工程工具链、分析会话和目标生命周期统一在一个接口背后。
+REA 通过一个 CLI 与 MCP 服务器实现这套流程。智能体可以检查编译后的应用、追踪功能的工作方式,并把了解到的内容用于日常编码。REA 在一个接口背后处理复杂的逆向工程工具。
+
+## 直接询问智能体
+
+```bash
+npx skills add morluto/rea
+```
+
+然后说:
+
+```text
+设置 REA 并逆向工程“备忘录”应用。解释搜索功能的工作方式,
+说明你是如何得出结论的,然后为我的项目构建类似功能。
+```
+
+“备忘录”只是示例。你可以说出任何想了解的应用,也可以让智能体先从概览开始。
## 从二进制到行为
@@ -49,38 +64,51 @@ REA 让调查始终以二进制证据为依据。它不会声称能恢复原始
## 快速开始
-### 环境要求
+### 使用编程智能体(推荐)
+
+```bash
+npx skills add morluto/rea
+```
+
+让智能体设置 REA。它会检查你的 Mac,说明需要安装的内容,先征得同意,并引导你处理系统提示。如果智能体要求重启以加载完整工具,请在设置后重启。
+
+### 开始之前
- macOS 12 或更高版本
- Node.js 22 或更高版本
-- [Hopper Disassembler](https://www.hopperapp.com/)
-REA 不捆绑 Hopper。Setup 可以在需要时安装 Homebrew 和 `hopper-disassembler` cask,配置检测到的 Claude Desktop 与 Cursor MCP 客户端,并安装 REA 智能体技能。现有客户端配置会先备份,再原子写入并进行语义回读验证。
+你不需要手动安装逆向工程工具。Setup 会在需要时安装 Homebrew 和 [Hopper](https://www.hopperapp.com/),然后配置支持的编程智能体。Hopper 是独立软件,需要单独授权;Setup 可以安装它,但不提供许可证。
```bash
-# 1. 安装和配置
+# 1. 安装和配置 REA
npx -y @morluto/rea setup --yes
-
-# 2. 检查集成
-npx -y @morluto/rea doctor
-
-# 3. 启动 stdio MCP 服务器
-npx -y @morluto/rea mcp
```
-MCP 命令通过 stdio 通信,因此会静默等待客户端连接。如果 setup 已检测并配置你的客户端,请重启客户端并使用已注册的 `rea` 服务器。
+如果 macOS 或安装程序要求确认,请完成提示,然后再次运行同一命令。
-然后可以这样询问智能体:
+### 2. 重启编程智能体
+
+Setup 会自动配置检测到的 Claude Desktop 和 Cursor。重启应用,让它加载 REA。
+
+### 3. 直接询问智能体
+
+你可以直接说出应用名称。编程智能体会找到应用,并把 REA 所需的程序文件交给它。
```text
-打开 /path/to/MyApp,概述该二进制文件,找到身份验证流程,
-反编译相关过程,并展示支持结论的证据。
+使用 REA 逆向工程“备忘录”应用。解释搜索功能的工作方式,展示证据,
+然后使用 SQLite 为我的项目构建一个类似功能。
+```
+
+如果遇到问题,请运行:
+
+```bash
+npx -y @morluto/rea doctor
```
## 一个提示词,完成一次完整调查
```text
-打开 MyApp,找到离线搜索功能的工作方式,解释其控制流,
+逆向工程“备忘录”应用,找到离线搜索功能的工作方式,解释其控制流,
并使用 TypeScript 和 SQLite 为我的项目构建一个版本。
```
@@ -90,7 +118,7 @@ MCP 命令通过 stdio 通信,因此会静默等待客户端连接。如果 se
| 2 | 搜索可能的离线搜索线索 | `search_strings`, `search_procedures`, `list_names` |
| 3 | 将线索连接到可执行代码 | `find_xrefs_to_name`, `xrefs`, `procedure_callers` |
| 4 | 重建相关控制流 | `get_call_graph`, `procedure_callees`, `procedure_info` |
-| 5 | 恢复实现 | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
+| 5 | 反编译相关程序 | `procedure_pseudo_code`, `procedure_assembly`, `batch_decompile` |
| 6 | 在你的项目中构建该功能 | 适合你的技术栈、产品和需求的代码 |
REA 负责第 1–5 步中的二进制分析。第 6 步由智能体使用其常规文件编辑与测试工具完成。
@@ -115,18 +143,9 @@ REA 负责第 1–5 步中的二进制分析。第 6 步由智能体使用其常
| 组合分析 | 8 | `binary_overview`, `batch_decompile`, `get_call_graph`, `find_xrefs_to_name`、Swift 与 ObjC 发现 |
| 二进制会话 | 3 | `open_binary`, `binary_session`, `close_binary` |
-公开工具清单通过契约测试和隔离的打包 MCP 客户端验证。真实 Hopper 验证还会使用两个二进制文件检查同一套 42 工具接口。
+## 与其他编程智能体一起使用
-## MCP 工作流
-
-1. 使用 `open_binary` 打开可读的本地二进制文件或 `.hop` 路径。
-2. 从 `binary_overview` 开始,再通过字符串、符号、反编译、调用者、被调用者和交叉引用缩小范围。
-3. 再次调用 `open_binary` 切换目标;如果新目标失败,REA 会尝试重新打开之前的目标。
-4. 完成后调用 `close_binary`。`binary_session` 可随时报告当前状态。
-
-REA 自动识别 Mach-O/FAT、ELF、PE 和 Hopper 数据库。相对路径以 MCP 服务器工作目录为基准;FAT 二进制会自动选择与宿主兼容的架构。
-
-### 手动 MCP 配置
+Setup 目前会自动配置 Claude Desktop 和 Cursor。任何支持本地 MCP 服务器的编程智能体都可以使用以下配置连接 REA。
```json
{
@@ -143,23 +162,24 @@ REA 自动识别 Mach-O/FAT、ELF、PE 和 Hopper 数据库。相对路径以 MC
```mermaid
flowchart LR
- Agent["编程智能体"] -->|stdio MCP| REA["REA 服务器
42 个工具"]
- REA --> Session["二进制会话
打开 · 切换 · 关闭"]
- Session -->|认证 Unix 套接字| Bridge["Hopper 内部的
REA Python 桥"]
- Bridge --> API["Hopper 公开
Python API"]
+ Agent["编程智能体"] --> REA["REA
CLI + MCP"]
+ Terminal["终端"] --> REA
+ REA --> Hopper["分析引擎"]
+ Hopper --> App["你的应用"]
```
-CLI 与 MCP 适配器直接调用同一套二进制会话和分析运行时,二者不会互相调用。一次性命令会为每次操作获取并关闭资源;MCP 模式则在多次调用和目标切换之间保持会话。
+CLI 与 MCP 服务器使用相同的分析引擎。终端命令完成后会关闭应用;智能体会话则会在调查期间保持应用打开。
## CLI
+上面的智能体工作流是使用 REA 最简单的方式。如果只想在终端中快速了解一个应用:
+
```bash
-npx -y @morluto/rea --help
-npx -y @morluto/rea doctor --target /path/to/binary
-npx -y @morluto/rea analyze /path/to/binary
-npx -y @morluto/rea decompile /path/to/binary 0x100003f20
+npx -y @morluto/rea analyze /Applications/Notes.app
```
+运行 `npx -y @morluto/rea --help` 查看直接反编译和其他选项。
+
也可以全局安装 `rea` 命令:
```bash
@@ -168,6 +188,8 @@ rea --help
rea mcp
```
+REA 可以直接打开 Mac 的 `.app` 文件夹。如果智能体找不到应用,请告诉它应用安装在哪里。
+
## Hopper 应用行为
REA 会在需要时启动 Hopper,无需预先运行。Hopper 启动器内部会激活应用,因此打开目标时 Hopper 可能出现在其他窗口前。REA 会请求 macOS 在后台启动 Hopper,但无法保证窗口始终位于后台。
@@ -190,7 +212,7 @@ REA 会推导明确的格式和架构参数,以避免常见的 FAT 与 ARM 选
REA 是否包含 Hopper?
-不包含。Hopper 是独立安装和授权的软件。REA 提供 CLI、MCP 服务器、会话管理、分析工作流和认证桥。
+不包含。Setup 可以为你安装 Hopper,但 Hopper 仍是需要单独授权的软件。REA 提供 CLI、MCP 服务器和面向智能体的工作流。
@@ -208,22 +230,7 @@ REA 不提供托管分析服务,而是通过本地 Unix 套接字把操作交
## 开发
-```bash
-npm ci
-npm run check
-npm run verify:package
-npm pack --dry-run
-```
-
-真实 Hopper 验证需要两个不同的二进制文件:
-
-```bash
-HOPPER_TARGET_PATH=/path/to/target-a \
-HOPPER_SECOND_TARGET_PATH=/path/to/distinct-target-b \
-npm run verify:hopper
-```
-
-架构规范、Pull Request 要求与维护者发布清单请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
+开发环境、架构、测试和发布说明请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
## 许可证
diff --git a/package.json b/package.json
index d230a7a1e..b90c49e25 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@morluto/rea",
"version": "0.2.0",
- "description": "Reverse-engineering automation for Hopper Disassembler through one CLI and MCP server.",
+ "description": "Reverse engineering CLI and MCP server for coding agents to understand apps and build features.",
"license": "MIT",
"repository": {
"type": "git",
@@ -13,10 +13,17 @@
},
"keywords": [
"reverse-engineering",
- "hopper",
+ "binary-analysis",
+ "coding-agents",
+ "agent-skills",
+ "cli",
+ "mcp-server",
+ "model-context-protocol",
+ "static-analysis",
"disassembler",
"decompiler",
- "mcp"
+ "mcp",
+ "hopper"
],
"publishConfig": {
"access": "public"
diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs
index 85e32c9f3..c873ff6ac 100644
--- a/scripts/verify-package.mjs
+++ b/scripts/verify-package.mjs
@@ -125,8 +125,12 @@ try {
join(home, ".agents/skills/rea-analysis/SKILL.md"),
"utf8",
);
- if (!skill.includes("open_binary"))
- throw new Error("packaged skill readback failed");
+ const canonicalSkill = await readFile(
+ join(root, "skills/rea-analysis/SKILL.md"),
+ "utf8",
+ );
+ if (skill !== canonicalSkill)
+ throw new Error("packaged skill did not match its canonical source");
await writeFile(cursorConfig, "malformed");
const failed = json(
diff --git a/skills/rea-analysis/SKILL.md b/skills/rea-analysis/SKILL.md
index 98a3de2ac..7047a54d2 100644
--- a/skills/rea-analysis/SKILL.md
+++ b/skills/rea-analysis/SKILL.md
@@ -1,8 +1,44 @@
---
name: rea-analysis
-description: Analyze local binaries with REA and Hopper through its MCP session workflow.
+description: Reverse engineer apps with REA. Explore how features work, then build a version tailored to your project.
---
-# REA Analysis
+# REA
-Call `open_binary` with a readable local path before analysis. Begin with `binary_overview`, use strings and symbol searches to identify relevant procedures, then use `procedure_pseudo_code`, callers, callees, and xrefs to build evidence. Call `open_binary` again to switch targets and `close_binary` when finished. Never claim real-Hopper behavior from mock-only evidence.
+Use REA when the user wants to understand how an app or feature works, compare app versions, decompile code, or build a similar feature.
+
+## Understand the request
+
+Identify the app and what the user wants to understand or build. Do not ask for information they already provided.
+
+If the app is missing, ask which app they want to reverse engineer. If the app is known but the goal is unclear, ask what they want to understand or build, and offer to start with an overview. Never require the user to supply a program path, address, architecture, or reverse-engineering terminology.
+
+Notes is only a documentation example. Never select an app unless the user names it or confirms it.
+
+## Ensure REA is ready
+
+1. Run `npx -y @morluto/rea doctor`.
+2. If setup is needed, explain that REA may install Homebrew and Hopper. Hopper is separate software and requires its own license.
+3. Obtain approval before installing external software, then run `npx -y @morluto/rea 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 coding agent to load all REA tools. Direct CLI commands remain available before restart.
+
+## Locate the app
+
+Accept a human-readable app name. Search macOS application locations and system metadata. If one clear match is found, continue without asking for a path. If several apps match, show their names and locations and ask which one the user means. If none match, ask where the app is installed.
+
+REA accepts a `.app` bundle directly. Do not expose its internal `Contents/MacOS` path unless it helps explain an error.
+
+## Investigate
+
+Briefly tell the user what you will investigate. Open the app with `open_binary`, begin with `binary_overview`, and narrow the investigation around the requested feature. Use decompilation, strings, names, callers, callees, and cross-references as needed.
+
+Explain conclusions in plain language. Point to the relevant decompiled code, strings, names, and connections so the user can see how the explanation was reached. Do not claim to recover original source code or automatically clone an application.
+
+## Build
+
+When requested, use normal coding tools to build a version suited to the user's project, stack, interface, and requirements. Keep the implementation tied to what the investigation established, and distinguish observed behavior from assumptions or design choices.
+
+## Human input and cleanup
+
+Hopper or macOS may show a window that needs human input. Tell the user what appeared and ask them to handle it; do not guess or take over unrelated UI. Call `close_binary` when the investigation is complete.
diff --git a/src/application/Doctor.ts b/src/application/Doctor.ts
index a0c3f0c95..061cc0492 100644
--- a/src/application/Doctor.ts
+++ b/src/application/Doctor.ts
@@ -6,6 +6,7 @@ import { join } from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
+import { parseBinaryTarget } from "../domain/binaryTarget.js";
import { probeHomebrew } from "./homebrew.js";
const execFileAsync = promisify(execFile);
@@ -25,7 +26,7 @@ export interface DoctorHost {
readonly nodeVersion: string;
readonly configuredHopperPath?: string;
macosVersion(): Promise;
- readable(path: string): Promise;
+ validTarget(path: string): Promise;
executable(path: string): Promise;
brewHopperPath(): Promise;
manualHopperPaths(): Promise;
@@ -89,9 +90,9 @@ export const runDoctor = async (
checks.push(
check(
"target",
- await host.readable(target),
+ await host.validTarget(target),
target,
- "Supply a readable local binary path.",
+ "Supply a readable local app or program path.",
),
);
return {
@@ -117,13 +118,8 @@ export const systemDoctorHost = (): DoctorHost => ({
return undefined;
}
},
- async readable(path) {
- try {
- await access(path, constants.R_OK);
- return true;
- } catch {
- return false;
- }
+ async validTarget(path) {
+ return (await parseBinaryTarget(path)).ok;
},
async executable(path) {
try {
diff --git a/src/application/Setup.ts b/src/application/Setup.ts
index e71fe586f..36685bb3a 100644
--- a/src/application/Setup.ts
+++ b/src/application/Setup.ts
@@ -276,8 +276,14 @@ const installCanonicalSkill = async (
PRODUCT_IDENTITY.skillName,
"SKILL.md",
);
- const content = `---\nname: ${PRODUCT_IDENTITY.skillName}\ndescription: Analyze binaries with REA and Hopper.\n---\n\nOpen a target with open_binary, begin with binary_overview, and close it when finished.\n`;
try {
+ const content = await readFile(
+ new URL(
+ `../../skills/${PRODUCT_IDENTITY.skillName}/SKILL.md`,
+ import.meta.url,
+ ),
+ "utf8",
+ );
if (
(await readFile(destination, "utf8").catch(() => undefined)) === content
)
diff --git a/src/cli.ts b/src/cli.ts
index 0b1929ca6..64a2aa3c1 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -21,25 +21,25 @@ export const createCli = (): ReturnType => {
);
const cli = Cli.create(PRODUCT_IDENTITY.cliBinary, {
version: "0.1.0",
- description: "Install, diagnose, and automate Hopper binary analysis.",
+ description: "Reverse engineer apps from your terminal or coding agent.",
mcp: {
command: PRODUCT_IDENTITY.mcpCommand,
instructions:
- "Open a local target with open_binary, analyze it, and close the session when finished.",
+ "Ask which app and feature the user wants to understand, then open the app and investigate it.",
},
sync: {
cwd: fileURLToPath(new URL("..", import.meta.url)),
include: ["skills/*"],
suggestions: [
- "open a binary and summarize it",
- "decompile a procedure",
- "diagnose my Hopper setup",
+ "understand how an app feature works",
+ "decompile part of an app",
+ "check my REA setup",
],
},
});
cli.command("setup", {
- description: "Install prerequisites and configure binary analysis",
+ description: "Install requirements and configure coding agents",
options: z.object({
yes: z
.boolean()
@@ -51,17 +51,17 @@ export const createCli = (): ReturnType => {
logCliCommand(logger, "setup", () => runSetup(options.yes)),
});
cli.command("doctor", {
- description: "Diagnose the local Hopper integration",
+ description: "Check whether REA is ready",
options: z.object({
- target: z.string().optional().describe("Optional binary path to check"),
+ target: z.string().optional().describe("Optional app path to check"),
}),
run: ({ options }) =>
logCliCommand(logger, "doctor", () => runDoctor(options.target)),
});
cli.command("analyze", {
- description: "Analyze a binary through the shared Hopper core",
+ description: "Get an overview of an app",
args: z.object({
- path: z.string().describe("Binary or Hopper database path"),
+ path: z.string().describe("App, program, or Hopper database path"),
}),
run: ({ args }) =>
logCliCommand(logger, "analyze", () =>
@@ -69,9 +69,9 @@ export const createCli = (): ReturnType => {
),
});
cli.command("decompile", {
- description: "Decompile an address through the shared Hopper core",
+ description: "Read one part of an app as code",
args: z.object({
- path: z.string().describe("Binary path"),
+ path: z.string().describe("App or program path"),
address: z.string().describe("Procedure address"),
}),
run: ({ args }) =>
diff --git a/src/domain/binaryTarget.ts b/src/domain/binaryTarget.ts
index 2f8c83e0c..306fd4a3b 100644
--- a/src/domain/binaryTarget.ts
+++ b/src/domain/binaryTarget.ts
@@ -2,15 +2,20 @@ import { constants } from "node:fs";
import {
access,
open,
+ readFile,
realpath,
stat,
type FileHandle,
} from "node:fs/promises";
-import { isAbsolute, resolve } from "node:path";
+import { execFile } from "node:child_process";
+import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
+import { promisify } from "node:util";
import { BinaryTargetError } from "./errors.js";
import { err, ok, type Result } from "./result.js";
+const execFileAsync = promisify(execFile);
+
/** CPU families understood by the supported executable loaders. */
type BinaryArchitecture = "x86" | "x86_64" | "arm" | "arm64";
/**
@@ -41,7 +46,10 @@ export const parseBinaryTarget = async (
const candidate = isAbsolute(input) ? input : resolve(cwd, input);
try {
await access(candidate, constants.R_OK);
- const path = await realpath(candidate);
+ const canonical = await realpath(candidate);
+ const resolved = await resolveAppBundle(canonical);
+ if (!resolved.ok) return err(resolved.error);
+ const path = resolved.value;
if (!(await stat(path)).isFile())
return err(new BinaryTargetError(path, "target is not a regular file"));
if (
@@ -65,6 +73,93 @@ export const parseBinaryTarget = async (
}
};
+const resolveAppBundle = async (
+ path: string,
+): Promise> => {
+ const metadata = await stat(path);
+ if (!metadata.isDirectory()) return ok(path);
+ if (extname(path).toLowerCase() !== ".app")
+ return err(new BinaryTargetError(path, "target is not an app or file"));
+ const plistPath = join(path, "Contents", "Info.plist");
+ let name: string;
+ try {
+ const plist = await readFile(plistPath);
+ name =
+ plist.subarray(0, 6).toString("ascii") === "bplist"
+ ? await readBinaryPlistExecutable(plistPath)
+ : parseXmlPlistExecutable(plist.toString("utf8"));
+ } catch (cause: unknown) {
+ return err(
+ new BinaryTargetError(path, "app has no readable CFBundleExecutable", {
+ cause,
+ }),
+ );
+ }
+ if (!isSafeExecutableName(name))
+ return err(
+ new BinaryTargetError(path, "app has an unsafe CFBundleExecutable"),
+ );
+ const programs = join(path, "Contents", "MacOS");
+ const executable = join(programs, name);
+ try {
+ const [canonicalPrograms, canonicalExecutable] = await Promise.all([
+ realpath(programs),
+ realpath(executable),
+ ]);
+ const location = relative(canonicalPrograms, canonicalExecutable);
+ if (
+ location === ".." ||
+ location.startsWith(`..${sep}`) ||
+ isAbsolute(location)
+ )
+ return err(
+ new BinaryTargetError(path, "app program file leaves Contents/MacOS"),
+ );
+ return ok(canonicalExecutable);
+ } catch (cause: unknown) {
+ return err(
+ new BinaryTargetError(path, "app program file is missing", { cause }),
+ );
+ }
+};
+
+const parseXmlPlistExecutable = (plist: string): string => {
+ const match =
+ /\s*CFBundleExecutable\s*<\/key>\s*([^<]+)<\/string>/u.exec(
+ plist,
+ );
+ if (match?.[1] === undefined)
+ throw new Error("CFBundleExecutable is missing");
+ return decodeXml(match[1].trim());
+};
+
+const readBinaryPlistExecutable = async (plistPath: string): Promise =>
+ (
+ await execFileAsync("/usr/bin/plutil", [
+ "-extract",
+ "CFBundleExecutable",
+ "raw",
+ "-o",
+ "-",
+ plistPath,
+ ])
+ ).stdout.trim();
+
+const isSafeExecutableName = (name: string): boolean =>
+ name.length > 0 &&
+ name !== "." &&
+ name !== ".." &&
+ !name.includes("\0") &&
+ !/[/\\]/u.test(name);
+
+const decodeXml = (value: string): string =>
+ value
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll(""", '"')
+ .replaceAll("'", "'");
+
type ExecutableMetadata = Pick<
BinaryTarget,
"format" | "architecture" | "availableArchitectures" | "loaderArgs"
diff --git a/src/domain/errors.ts b/src/domain/errors.ts
index c9a2b3809..b7dd9514b 100644
--- a/src/domain/errors.ts
+++ b/src/domain/errors.ts
@@ -61,17 +61,17 @@ export class ConfigurationError extends HopperError {
readonly _tag = "ConfigurationError";
}
-/** No binary session exists for an analysis request. */
+/** No app or binary session exists for an analysis request. */
export class NoBinaryOpenError extends HopperError {
readonly _tag = "NoBinaryOpenError";
constructor() {
super(
- "No binary is open. Call open_binary with a readable local path first.",
+ "No app is open. Ask the user which app to investigate, then call open_binary with its local path.",
);
}
}
-/** A supplied target path could not be safely opened as a supported binary. */
+/** A supplied target path could not be safely opened as a supported app or binary. */
export class BinaryTargetError extends HopperError {
readonly _tag = "BinaryTargetError";
constructor(
@@ -79,6 +79,6 @@ export class BinaryTargetError extends HopperError {
reason: string,
options?: ErrorOptions,
) {
- super(`Cannot open binary: ${reason}: ${path}`, options);
+ super(`Cannot open app: ${reason}: ${path}`, options);
}
}
diff --git a/tests/binaryTarget.test.ts b/tests/binaryTarget.test.ts
index 19aec93d9..9ff7ece05 100644
--- a/tests/binaryTarget.test.ts
+++ b/tests/binaryTarget.test.ts
@@ -1,4 +1,11 @@
-import { mkdtemp, rm, writeFile } from "node:fs/promises";
+import {
+ mkdir,
+ mkdtemp,
+ realpath,
+ rm,
+ symlink,
+ writeFile,
+} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
@@ -80,6 +87,64 @@ describe("binary target I/O", () => {
expect((await parseBinaryTarget(directory)).ok).toBe(false);
});
+ it("resolves a macOS app bundle to its declared program file", async () => {
+ directory = await mkdtemp(join(tmpdir(), "rea-target-"));
+ const app = join(directory, "Example App.app");
+ const contents = join(app, "Contents");
+ const programs = join(contents, "MacOS");
+ await mkdir(programs, { recursive: true });
+ await writeFile(
+ join(contents, "Info.plist"),
+ 'CFBundleExecutableExample & Tool',
+ );
+ await writeFile(
+ join(programs, "Example & Tool"),
+ thinMach(0xfeedfacf, 0x0100000c),
+ );
+ const result = await parseBinaryTarget(app, directory, "arm64");
+ expect(result.ok && result.value).toMatchObject({
+ path: await realpath(join(programs, "Example & Tool")),
+ format: "mach-o",
+ });
+ });
+
+ it.each([
+ ["missing plist", undefined],
+ ["missing executable name", ""],
+ [
+ "unsafe executable name",
+ "CFBundleExecutable../escape",
+ ],
+ [
+ "missing program file",
+ "CFBundleExecutableMissing",
+ ],
+ ])("rejects an app bundle with %s", async (_case, plist) => {
+ directory = await mkdtemp(join(tmpdir(), "rea-target-"));
+ const app = join(directory, "Broken.app");
+ const contents = join(app, "Contents");
+ await mkdir(join(contents, "MacOS"), { recursive: true });
+ if (plist !== undefined)
+ await writeFile(join(contents, "Info.plist"), plist);
+ expect((await parseBinaryTarget(app, directory, "arm64")).ok).toBe(false);
+ });
+
+ it("rejects an app program symlink that leaves the bundle", async () => {
+ directory = await mkdtemp(join(tmpdir(), "rea-target-"));
+ const app = join(directory, "Escaping.app");
+ const contents = join(app, "Contents");
+ const programs = join(contents, "MacOS");
+ const outside = join(directory, "outside");
+ await mkdir(programs, { recursive: true });
+ await writeFile(outside, thinMach(0xfeedfacf, 0x0100000c));
+ await writeFile(
+ join(contents, "Info.plist"),
+ "CFBundleExecutableEscaping",
+ );
+ await symlink(outside, join(programs, "Escaping"));
+ expect((await parseBinaryTarget(app, directory, "arm64")).ok).toBe(false);
+ });
+
it("honors an explicit database kind without relying on the file suffix", async () => {
directory = await mkdtemp(join(tmpdir(), "rea-target-"));
await writeFile(join(directory, "saved-analysis"), "database");
diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts
index 9788e7dca..fc298ad94 100644
--- a/tests/doctor.test.ts
+++ b/tests/doctor.test.ts
@@ -5,7 +5,7 @@ const host = (overrides: Partial = {}): DoctorHost => ({
platform: "darwin",
nodeVersion: "22.1.0",
macosVersion: () => Promise.resolve("12.0"),
- readable: (path) => Promise.resolve(path.includes("Hopper")),
+ validTarget: (path) => Promise.resolve(path.includes("Hopper")),
executable: (path) => Promise.resolve(path.includes("Hopper")),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
@@ -61,13 +61,24 @@ describe("doctor", () => {
expect(result.hopperPath).toBe(path);
});
+ it("uses shared app target validation when a target is supplied", async () => {
+ const result = await runDoctor(
+ "/Applications/Notes.app",
+ host({
+ validTarget: (path) =>
+ Promise.resolve(path === "/Applications/Notes.app"),
+ }),
+ );
+ expect(result.checks.find(({ name }) => name === "target")?.ok).toBe(true);
+ });
+
it("rejects a readable Hopper launcher without execute permission", async () => {
const path = "/manual/Hopper";
const result = await runDoctor(
undefined,
host({
configuredHopperPath: path,
- readable: () => Promise.resolve(true),
+ validTarget: () => Promise.resolve(true),
executable: () => Promise.resolve(false),
}),
);
diff --git a/tests/readmeConsistency.test.ts b/tests/readmeConsistency.test.ts
new file mode 100644
index 000000000..39b59076c
--- /dev/null
+++ b/tests/readmeConsistency.test.ts
@@ -0,0 +1,31 @@
+import { readFile } from "node:fs/promises";
+import { resolve } from "node:path";
+
+import { describe, expect, it } from "vitest";
+
+const readmes = [
+ "README.md",
+ "README_zh.md",
+ "README_ja.md",
+ "README_ko.md",
+ "README_ar.md",
+] as const;
+
+describe("localized README product facts", () => {
+ it.each(readmes)(
+ "keeps commands and requirements aligned in %s",
+ async (path) => {
+ const content = await readFile(resolve(path), "utf8");
+ expect(content).toContain("npx skills add morluto/rea");
+ expect(content).toContain("npx -y @morluto/rea setup --yes");
+ expect(content).toContain("npx -y @morluto/rea doctor");
+ expect(content).toContain('"args": ["-y", "@morluto/rea", "mcp"]');
+ expect(content).toContain("Node.js 22");
+ expect(content).toContain("macOS 12");
+ expect(content).toMatch(/\b42\b/u);
+ expect(content).toMatch(/\b31\b/u);
+ expect(content).toMatch(/\b8\b/u);
+ expect(content).toMatch(/\b3\b/u);
+ },
+ );
+});