🔍 Reverse engineering with REA: read a program you did not write · วิศวกรรมย้อนกลับด้วย REA: อ่านโปรแกรมที่คุณไม่ได้เขียน · 用 REA 学逆向工程:读懂不是你写的程序

Reverse engineering is finding out how a finished program works when nobody gives you its plan. This page teaches it with REA, a free tool whose name stands for Reverse Engineer Anything, and with basic Python. You start with an overview of everything REA can do, and you run two short Python files against it. Then you study a real Mac app that is free and open source: Rectangle. You read the card it carries, who signed it and what it is built on, and you follow one of its menu items to the code it runs. Every file here was run, and what it printed is shown under it. · วิศวกรรมย้อนกลับคือการหาให้ได้ว่าโปรแกรมที่ทำเสร็จแล้วทำงานอย่างไร ในเวลาที่ไม่มีใครให้แบบแปลนของมันกับคุณ หน้านี้สอนเรื่องนี้ด้วย REA ซึ่งเป็นเครื่องมือฟรีที่ชื่อย่อมาจาก Reverse Engineer Anything (ทำวิศวกรรมย้อนกลับได้ทุกอย่าง) และด้วย Python พื้นฐาน คุณเริ่มจากภาพรวมของทุกสิ่งที่ REA ทำได้ และรันไฟล์ Python สั้น ๆ สองไฟล์กับมัน จากนั้นคุณศึกษาแอปแมคของจริงที่ฟรีและเป็นโอเพนซอร์ส คือ Rectangle คุณอ่านบัตรที่มันพกติดตัว ใครเซ็นรับรองมัน และมันสร้างบนอะไร แล้วตามรอยรายการเมนูหนึ่งรายการของมันไปถึงโค้ดที่มันรัน ไฟล์ทุกไฟล์ในหน้านี้ถูกรันจริง และสิ่งที่มันพิมพ์ออกมาก็แสดงไว้ใต้ไฟล์นั้น · 逆向工程,就是在没有人给你设计图的时候,弄清楚一个做好的程序是怎样工作的。这一页用免费的工具 REA 和基础的 Python 来教它,REA 是 Reverse Engineer Anything(逆向任何东西)的缩写。你先从 REA 能做的一切的概览开始,并对它运行两个很短的 Python 文件。然后你研究一个真实的、免费又开源的 Mac 应用:Rectangle。你读它随身带的卡片,看谁给它签了名、它建立在什么之上,再从它的一个菜单项一直跟到它运行的代码。这里的每个文件都真正运行过,它打印的内容就显示在文件下面。

🇬🇧 English

A program is written as source code: files that a person can read. What you receive is often something else. It is the finished program, turned into a form that the computer runs and a person cannot easily read. Reverse engineering goes the other way. You start from what was shipped, and you work back to how it is built.

People do this to repair a program whose author has gone, to check that a program does only what it says, to make their own program work together with another one, and to learn how good software is made.

REA is free software. It reads the files of a program without running the program, and it writes down what it finds. Each finding says where it was seen and how sure REA is. REA was made for AI coding agents, and it also works from a terminal. This page uses the terminal, so that you see every step yourself.

You need a little Python: a dictionary, a list, a loop and a function. Each file is explained.

On this page you will:

  1. install REA, sort its commands into families, and see what it can study and what each kind of program needs;
  2. ask REA which analysis engines your own computer has;
  3. download Rectangle, a real open-source Mac app, together with its source code;
  4. read the card that the app carries about itself, and see who signed it;
  5. list the libraries that the app is built on;
  6. open the design of its menu, and follow a menu item to the code it runs;
  7. mark REA against the app's source code, and against Apple's own tools;
  8. see how the same work is given to an AI agent.

🇹🇭 ไทย

โปรแกรมถูกเขียนเป็นซอร์สโค้ด คือไฟล์ที่คนอ่านได้ แต่สิ่งที่คุณได้รับมักเป็นอย่างอื่น มันคือโปรแกรมที่ทำเสร็จแล้ว ซึ่งถูกเปลี่ยนเป็นรูปแบบที่คอมพิวเตอร์รันได้ แต่คนอ่านไม่ง่าย วิศวกรรมย้อนกลับเดินไปอีกทางหนึ่ง คุณเริ่มจากสิ่งที่ถูกส่งมอบ แล้วย้อนกลับไปหาว่ามันถูกสร้างขึ้นอย่างไร

คนทำแบบนี้เพื่อซ่อมโปรแกรมที่ผู้เขียนไม่อยู่แล้ว เพื่อตรวจว่าโปรแกรมทำเฉพาะสิ่งที่มันบอก เพื่อให้โปรแกรมของตัวเองทำงานร่วมกับอีกโปรแกรมหนึ่งได้ และเพื่อเรียนรู้ว่าซอฟต์แวร์ที่ดีสร้างขึ้นอย่างไร

REA เป็นซอฟต์แวร์ฟรี มันอ่านไฟล์ของโปรแกรมโดยไม่รันโปรแกรมนั้น แล้วจดสิ่งที่มันพบ สิ่งที่พบแต่ละอย่างบอกว่าเห็นที่ไหน และ REA มั่นใจแค่ไหน REA ถูกสร้างมาเพื่อเอเจนต์ AI ที่เขียนโค้ด และใช้จากเทอร์มินัลได้ด้วย หน้านี้ใช้เทอร์มินัล เพื่อให้คุณเห็นทุกขั้นด้วยตัวเอง

คุณต้องรู้ Python นิดหน่อย คือดิกชันนารี ลิสต์ ลูป และฟังก์ชัน แต่ละไฟล์มีคำอธิบาย

ในหน้านี้คุณจะ:

  1. ติดตั้ง REA จัดคำสั่งของมันเป็นกลุ่ม และดูว่ามันศึกษาอะไรได้ และโปรแกรมแต่ละชนิดต้องใช้อะไร
  2. ถาม REA ว่าคอมพิวเตอร์ของคุณเองมีเอนจินวิเคราะห์ตัวไหน
  3. ดาวน์โหลด Rectangle ซึ่งเป็นแอปแมคโอเพนซอร์สของจริง พร้อมซอร์สโค้ดของมัน
  4. อ่านบัตรที่แอปพกติดตัวเกี่ยวกับตัวมันเอง และดูว่าใครเซ็นรับรองมัน
  5. แสดงรายการไลบรารีที่แอปสร้างอยู่บนนั้น
  6. เปิดแบบของเมนูของมัน แล้วตามรอยรายการเมนูไปถึงโค้ดที่มันรัน
  7. ตรวจ REA เทียบกับซอร์สโค้ดของแอป และเทียบกับเครื่องมือของ Apple เอง
  8. ดูว่างานเดียวกันนี้ถูกมอบให้เอเจนต์ AI ได้อย่างไร

🇨🇳 中文

程序是用源代码写成的,也就是人能读懂的文件。但你拿到的常常是另一种东西:做好的程序,它已经被变成电脑能运行、人却不容易读的形式。逆向工程走的是相反的方向:从发布出来的东西出发,倒推出它是怎样构建的。

人们这样做,是为了修好作者已经离开的程序,为了检查一个程序是不是只做了它说的事,为了让自己的程序能和另一个程序配合,也为了学习好的软件是怎样做出来的。

REA 是免费的软件。它读取一个程序的文件,但不运行这个程序,然后把它发现的东西记下来。每一条发现都会说明是在哪里看到的,以及 REA 有多大把握。REA 是为 AI 编程智能体做的,也可以在终端里用。这一页用终端,这样每一步你都能亲眼看到。

你需要会一点 Python:字典、列表、循环和函数。每个文件都有解释。

在这一页里,你会:

  1. 安装 REA,把它的命令分成几族,看看它能研究什么、每一类程序需要什么;
  2. 问 REA 你自己的电脑上有哪些分析引擎;
  3. 下载 Rectangle,一个真实的开源 Mac 应用,连同它的源代码;
  4. 读这个应用随身带着的、介绍它自己的卡片,看看谁给它签了名;
  5. 列出这个应用建立在哪些库之上;
  6. 打开它的菜单的设计,从一个菜单项跟到它运行的代码;
  7. 对照这个应用的源代码,再对照 Apple 自己的工具,给 REA 打分;
  8. 看看同样的工作怎样交给 AI 智能体去做。
1Analyze
REA reads a program and says what it found.REA อ่านโปรแกรม แล้วบอกว่ามันพบอะไรREA 读一个程序,说出它发现了什么。
npx rea ...
2Ask
A short Python file picks out the answer to one question.ไฟล์ Python สั้น ๆ หยิบคำตอบของคำถามหนึ่งข้อออกมา一个很短的 Python 文件,挑出一个问题的答案。
commands.py, app_card.py ...
3Check
You compare the answer with something you trust.คุณเปรียบเทียบคำตอบกับสิ่งที่คุณเชื่อถือได้你把答案和你信得过的东西比一比。
source_says.py, apple_says.py
A FINDING IS NOT A FACT UNTIL YOU CHECK IT REA sorts what it says into three kinds. Observed: it saw this in the files, at this place. Inferred: it worked this out from what it saw, and it can be wrong. Unknown: it could not tell, and it says so.That is the whole idea of this page. A reverse engineer does not ask what does the tool say? and stop. They ask where did the tool see that? and then they go and look. In part 2 you check against the source code of the app, and against a second tool.REA แบ่งสิ่งที่มันบอกเป็นสามชนิด Observed (เห็นจริง): มันเห็นสิ่งนี้ในไฟล์ ตรงตำแหน่งนี้ Inferred (อนุมาน): มันคิดต่อจากสิ่งที่เห็น และอาจผิดได้ Unknown (ไม่ทราบ): มันบอกไม่ได้ และมันก็บอกตรง ๆ ว่าไม่ทราบนั่นคือแนวคิดทั้งหมดของหน้านี้ นักวิศวกรรมย้อนกลับไม่ได้ถามว่า เครื่องมือบอกว่าอะไร แล้วหยุด เขาถามว่า เครื่องมือเห็นสิ่งนั้นที่ไหน แล้วเขาก็ไปดู ในส่วนที่ 2 คุณตรวจเทียบกับซอร์สโค้ดของแอป และกับเครื่องมือตัวที่สองREA 把它说的话分成三种。Observed(看到的):它在文件里的这个位置看到了这一点。Inferred(推断的):它根据看到的东西推出来的,可能是错的。Unknown(未知的):它判断不了,并且直接说出来。这就是这一页的全部想法。做逆向工程的人不会只问工具说了什么?就停下来。他会问工具是在哪里看到的?然后亲自去看。在第 2 部分,你对照应用的源代码,再对照第二个工具来检查。
Fileไฟล์文件What it isคืออะไร它是什么Where you use itใช้ตรงไหน用在哪里
commands.pySorts REA's commands into families.จัดคำสั่งของ REA เป็นกลุ่ม把 REA 的命令分成几族。Step 2ขั้นที่ 2第 2 步
ready.pyAsks REA which analysis engines this computer has.ถาม REA ว่าคอมพิวเตอร์เครื่องนี้มีเอนจินวิเคราะห์ตัวไหน问 REA 这台电脑上有哪些分析引擎。Step 3ขั้นที่ 3第 3 步
get_rectangle.pyDownloads the app Rectangle and its source code.ดาวน์โหลดแอป Rectangle และซอร์สโค้ดของมัน下载应用 Rectangle 和它的源代码。Step 4ขั้นที่ 4第 4 步
app_card.pyReads the card that the app carries about itself.อ่านบัตรที่แอปพกติดตัวเกี่ยวกับตัวมันเอง读应用随身带着的、介绍它自己的卡片。Step 5ขั้นที่ 5第 5 步
app_signature.pySays who signed the app and what it is allowed to do.บอกว่าใครเซ็นรับรองแอป และมันได้รับอนุญาตให้ทำอะไร说出谁给应用签了名,以及它被允许做什么。Step 6ขั้นที่ 6第 6 步
app_libraries.pyLists the libraries the app loads, and how much it takes from each.แสดงรายการไลบรารีที่แอปโหลด และมันนำอะไรมาจากแต่ละตัวมากแค่ไหน列出应用加载的库,以及它从每个库里拿了多少东西。Step 7ขั้นที่ 7第 7 步
app_windows.py, lines.pyLooks inside one of the app's designs, and prints numbered lines of a file.ดูข้างในแบบหนึ่งแบบของแอป และพิมพ์บรรทัดของไฟล์พร้อมหมายเลข看应用的一个设计里面有什么,以及打印文件里带行号的几行。Step 8ขั้นที่ 8第 8 步
source_says.pyMarks REA's list of actions against the app's source code.ตรวจรายการ action ของ REA เทียบกับซอร์สโค้ดของแอป对照应用的源代码,给 REA 的 action 列表打分。Step 9ขั้นที่ 9第 9 步
apple_says.pyAsks Apple's own tools the same questions, to mark REA.ถามคำถามเดียวกันกับเครื่องมือของ Apple เอง เพื่อตรวจ REA向 Apple 自己的工具问同样的问题,用来给 REA 打分。Step 9ขั้นที่ 9第 9 步
agent_setup.pyConnects REA to a coding agent, or first shows what that would change; for OpenCode it first names an Ollama server.เชื่อม REA เข้ากับเอเจนต์เขียนโค้ด หรือแสดงก่อนว่าจะเปลี่ยนอะไร สำหรับ OpenCode มันระบุเซิร์ฟเวอร์ Ollama ให้ก่อน把 REA 接到编程智能体上,或者先显示会改什么;对 OpenCode 它先指定一个 Ollama 服务器。Step 10ขั้นที่ 10第 10 步
YOU NEED A MAC, NODE.JS AND APPLE'S COMMAND LINE TOOLS REA is a Node.js program. It needs Node.js 22.19 or newer in the 22 line, 24.11 or newer in the 24 line, or 26 and above. Part 2 also needs Apple's Command Line Tools, which are free. They bring Python and the small tools that REA calls on a Mac. If you do not have them, the command xcode-select --install asks macOS to install them. We did not run that command: our Mac already had them. The Python files on this page use only what comes with Python, so there is nothing to install with pip. You do not need an account or an AI model. Step 4 downloads the one program you study, and it is about 5 MB.You type the commands into Terminal. On a Mac, type python3 wherever part 1 types python. Part 1 also works on Windows, and that is where we ran it. Part 2 works on a Mac only.REA เป็นโปรแกรม Node.js มันต้องใช้ Node.js 22.19 ขึ้นไปในสาย 22 หรือ 24.11 ขึ้นไปในสาย 24 หรือ 26 ขึ้นไป ส่วนที่ 2 ยังต้องใช้ Command Line Tools ของ Apple ซึ่งใช้ได้ฟรี มันนำ Python และเครื่องมือเล็ก ๆ ที่ REA เรียกใช้บนแมคมาให้ ถ้าคุณยังไม่มี คำสั่ง xcode-select --install จะขอให้ macOS ติดตั้งให้ เราไม่ได้รันคำสั่งนั้น เพราะแมคของเรามีอยู่แล้ว ไฟล์ Python ในหน้านี้ใช้เฉพาะสิ่งที่มากับ Python จึงไม่มีอะไรต้องติดตั้งด้วย pip คุณไม่ต้องมีบัญชีหรือโมเดล AI ขั้นที่ 4 ดาวน์โหลดโปรแกรมเดียวที่คุณศึกษา และมันมีขนาดประมาณ 5 MBคำสั่งพิมพ์ลงใน Terminal บนแมคให้พิมพ์ python3 ในทุกที่ที่ส่วนที่ 1 พิมพ์ python ส่วนที่ 1 ใช้บน Windows ได้ด้วย และเรารันมันบน Windows ส่วนที่ 2 ใช้ได้บนแมคเท่านั้นREA 是一个 Node.js 程序。它需要 Node.js 22 系列的 22.19 或更新版本,或 24 系列的 24.11 或更新版本,或 26 及以上。第 2 部分还需要 Apple 的 Command Line Tools,它是免费的。它带来 Python,以及 REA 在 Mac 上要调用的那些小工具。如果你还没有,命令 xcode-select --install 会请 macOS 来安装。这条命令我们没有运行过:我们的 Mac 上已经有了。这一页的 Python 文件只用 Python 自带的东西,所以不用 pip 安装任何库。你不需要账号,也不需要 AI 模型。第 4 步会下载你要研究的唯一一个程序,大约 5 MB。命令要在“终端”里输入。在 Mac 上,凡是第 1 部分写 python 的地方,都要写成 python3。第 1 部分在 Windows 上也能用,我们就是在 Windows 上运行它的。第 2 部分只能在 Mac 上用。
STUDY ONLY WHAT ITS LICENCE LETS YOU STUDY Every program on this page is one you are allowed to take apart. Rectangle is open source under the MIT licence, which gives everyone the right to use, copy and change it without restriction. Its source code is public, so nothing you find in it is a secret.This page does not study any program that comes with macOS, such as Finder or Safari. Apple's licence for macOS has a section named No Reverse Engineering, and we keep to it. Apple's small tools for developers, such as otool and codesign, are used here only as tools, on Rectangle. Before you study any other program, read its licence first. REA's own documents do not say what you are allowed to study, and nothing on this page is legal advice.โปรแกรมทุกตัวในหน้านี้เป็นโปรแกรมที่คุณได้รับอนุญาตให้แยกออกดู Rectangle เป็นโอเพนซอร์สภายใต้สัญญาอนุญาต MIT ซึ่งให้สิทธิ์ทุกคนใช้ คัดลอก และแก้ไขได้โดยไม่มีข้อจำกัด ซอร์สโค้ดของมันเปิดเผย ดังนั้นไม่มีอะไรที่คุณพบในนั้นเป็นความลับหน้านี้ไม่ศึกษาโปรแกรมใดที่มากับ macOS เช่น Finder หรือ Safari สัญญาอนุญาตของ Apple สำหรับ macOS มีหัวข้อหนึ่งชื่อ No Reverse Engineering และเราทำตามนั้น เครื่องมือเล็ก ๆ ของ Apple สำหรับนักพัฒนา เช่น otool และ codesign ถูกใช้ที่นี่ในฐานะเครื่องมือเท่านั้น กับ Rectangle ก่อนจะศึกษาโปรแกรมอื่นใด ให้อ่านสัญญาอนุญาตของมันก่อน เอกสารของ REA เองไม่ได้บอกว่าคุณได้รับอนุญาตให้ศึกษาอะไร และไม่มีอะไรในหน้านี้เป็นคำแนะนำทางกฎหมาย这一页的每个程序,都是你有权拆开来看的。Rectangle 是开源的,用的是 MIT 许可证,它给了每个人不受限制地使用、复制和修改的权利。它的源代码是公开的,所以你在里面找到的东西没有一样是秘密。这一页不研究任何随 macOS 一起来的程序,比如 Finder 或 Safari。Apple 的 macOS 许可协议里有一节,标题是 No Reverse Engineering(禁止逆向工程),我们遵守它。Apple 给开发者用的小工具,比如 otool 和 codesign,在这里只是当作工具,用在 Rectangle 上。研究任何别的程序之前,先读它的许可证。REA 自己的文档没有说你可以研究什么,这一页的任何内容也都不是法律意见。
Wordคำ词What it means on this pageความหมายในหน้านี้在这一页里的意思
source codeThe files of a program as its author wrote them, with full names and comments.ซอร์สโค้ด: ไฟล์ของโปรแกรมตามที่ผู้เขียนเขียนไว้ มีชื่อเต็มและคอมเมนต์ครบ源代码:作者写下的程序文件,名字完整,注释齐全。
to shipTo give the finished program to its users. What is shipped is often not the source code.ส่งมอบ: นำโปรแกรมที่ทำเสร็จแล้วไปให้ผู้ใช้ สิ่งที่ถูกส่งมอบมักไม่ใช่ซอร์สโค้ด发布:把做好的程序交给用户。发布出去的常常不是源代码。
static analysisStudying a program by reading its files, without running it.การวิเคราะห์แบบสถิต: ศึกษาโปรแกรมด้วยการอ่านไฟล์ของมัน โดยไม่รันมัน静态分析:通过读文件来研究一个程序,而不运行它。
analysis engineA separate program that reads machine code: Hopper, Ghidra or IDA. REA uses one for the commands that turn machine code back into code.เอนจินวิเคราะห์: โปรแกรมแยกต่างหากที่อ่านโค้ดเครื่อง คือ Hopper, Ghidra หรือ IDA REA ใช้มันสำหรับคำสั่งที่เปลี่ยนโค้ดเครื่องกลับเป็นโค้ด分析引擎:一个读机器码的独立程序,Hopper、Ghidra 或 IDA。REA 用它来运行那些把机器码变回代码的命令。
MCPModel Context Protocol: the way a coding agent is given tools. REA's setup registers REA with the agent as an MCP server, and the agent then runs REA's commands as tools.MCP: วิธีที่เอเจนต์เขียนโค้ดได้รับเครื่องมือ setup ของ REA ลงทะเบียน REA กับเอเจนต์เป็นเซิร์ฟเวอร์ MCP แล้วเอเจนต์ก็รันคำสั่งของ REA เป็นเครื่องมือMCP:编程智能体获得工具的方式。REA 的 setup 把 REA 作为 MCP 服务器注册给智能体,智能体随后把 REA 的命令当作工具来运行。
skillA page of instructions that a coding agent reads when a task matches it. REA's skill tells the agent when to use REA and how to report what it found.สกิล: หน้าคำแนะนำที่เอเจนต์เขียนโค้ดอ่านเมื่องานตรงกับมัน สกิลของ REA บอกเอเจนต์ว่าควรใช้ REA เมื่อไร และจะรายงานสิ่งที่พบอย่างไร技能(skill):一页说明,编程智能体在任务与之匹配时会去读。REA 的技能告诉智能体什么时候用 REA,以及怎样汇报它查到的东西。
evidenceREA's whole answer to one command, as JSON.หลักฐาน: คำตอบทั้งหมดของ REA ต่อคำสั่งหนึ่งคำสั่ง ในรูป JSON证据:REA 对一条命令的全部回答,用 JSON 写成。
graph, node, edgeA graph is things and the links between them. A node is one thing, such as a file. An edge is one link, such as this file imports that file.กราฟคือสิ่งต่าง ๆ และเส้นที่เชื่อมระหว่างกัน โหนด (node) คือสิ่งหนึ่งอย่าง เช่น ไฟล์ เส้นเชื่อม (edge) คือการเชื่อมหนึ่งเส้น เช่น ไฟล์นี้ import ไฟล์นั้น图就是一些东西和它们之间的连线。节点(node)是一个东西,比如一个文件。边(edge)是一条连线,比如这个文件导入了那个文件。
to importTo take something that another file or a library made.import: นำสิ่งที่อีกไฟล์หนึ่งหรือไลบรารีสร้างไว้มาใช้导入:拿来另一个文件或一个库做好的东西。
to compileTo turn source code into machine code. The program that does it is a compiler.คอมไพล์: เปลี่ยนซอร์สโค้ดเป็นโค้ดเครื่อง โปรแกรมที่ทำสิ่งนี้คือคอมไพเลอร์编译:把源代码变成机器码。做这件事的程序叫编译器。
machine codeThe very small steps that a processor runs. A compiled program is made of them.โค้ดเครื่อง: ขั้นตอนเล็กมาก ๆ ที่โปรเซสเซอร์รัน โปรแกรมที่คอมไพล์แล้วประกอบขึ้นจากสิ่งเหล่านี้机器码:处理器执行的非常小的步骤。编译好的程序就是由它们组成的。
library, frameworkFinished code that many programs share. On a Mac a big library is called a framework.ไลบรารี: โค้ดสำเร็จรูปที่หลายโปรแกรมใช้ร่วมกัน บนแมค ไลบรารีขนาดใหญ่เรียกว่าเฟรมเวิร์ก库:很多程序共用的现成代码。在 Mac 上,大的库叫做框架(framework)。
bundleA Mac app is a folder whose name ends in .app. The folder is called a bundle. The program and everything it needs are inside.บันเดิล: แอปของแมคคือโฟลเดอร์ที่ชื่อลงท้ายด้วย .app โฟลเดอร์นั้นเรียกว่าบันเดิล โปรแกรมและทุกอย่างที่มันต้องใช้อยู่ข้างในMac 应用是一个名字以 .app 结尾的文件夹,这个文件夹叫做 bundle。程序和它需要的一切都在里面。
to signTo mark a program so that anyone can check who made it and that nobody changed it.เซ็นรับรอง: ทำเครื่องหมายให้โปรแกรม เพื่อให้ใครก็ตรวจได้ว่าใครสร้างมัน และไม่มีใครแก้ไขมัน签名:给程序做一个标记,让任何人都能检查它是谁做的,以及有没有人改动过它。
entitlementOne permission that is written into a signed Mac program.entitlement: สิทธิ์หนึ่งข้อที่เขียนไว้ในโปรแกรมแมคที่เซ็นรับรองแล้วentitlement:写在已签名的 Mac 程序里的一项权限。
methodA function that belongs to a class.เมธอด: ฟังก์ชันที่เป็นของคลาส方法:属于某个类的函数。
agent, MCPAn agent is an AI program that can use tools by itself. MCP is the standard way to hand a tool to an agent.เอเจนต์คือโปรแกรม AI ที่ใช้เครื่องมือได้ด้วยตัวเอง MCP คือวิธีมาตรฐานในการมอบเครื่องมือให้เอเจนต์智能体是能自己使用工具的 AI 程序。MCP 是把工具交给智能体的标准方式。

🧰 Part 1 — What REA can do · ส่วนที่ 1 — REA ทำอะไรได้บ้าง · 第 1 部分 —— REA 能做什么

🇬🇧 English

Before you point REA at a real program, it helps to know the whole shape of the tool. REA is one program with many commands. Each command does one small job: read the card inside an app, list the libraries a program loads, say who signed it, follow a menu item to its code, or compare two versions of the same program. You will use four of those commands in part 2. This part shows you all of them, so that you know what else is there when you need it.

You also install REA here, and you run two short Python files against it. The first sorts its commands into families. The second asks REA what your own computer is ready for.

🇹🇭 ไทย

ก่อนจะเอา REA ไปใช้กับโปรแกรมจริง การรู้รูปร่างทั้งหมดของเครื่องมือจะช่วยได้ REA เป็นโปรแกรมเดียวที่มีคำสั่งมากมาย คำสั่งแต่ละตัวทำงานเล็ก ๆ หนึ่งอย่าง: อ่านบัตรในแอป แสดงรายการไลบรารีที่โปรแกรมโหลด บอกว่าใครเซ็นรับรองมัน ตามรอยรายการเมนูไปถึงโค้ดของมัน หรือเปรียบเทียบโปรแกรมเดียวกันสองเวอร์ชัน คุณจะใช้คำสั่งเหล่านั้นสี่ตัวในส่วนที่ 2 ส่วนนี้แสดงคำสั่งทั้งหมดให้คุณดู เพื่อให้คุณรู้ว่ายังมีอะไรอีกเมื่อคุณต้องใช้

คุณยังติดตั้ง REA ที่นี่ และรันไฟล์ Python สั้น ๆ สองไฟล์กับมัน ไฟล์แรกจัดคำสั่งของมันเป็นกลุ่ม ไฟล์ที่สองถาม REA ว่าคอมพิวเตอร์ของคุณเองพร้อมสำหรับอะไร

🇨🇳 中文

在把 REA 用到一个真实程序上之前,先了解这个工具的整体样子会很有帮助。REA 是一个程序,带着很多命令。每条命令只做一件小事:读应用里面的卡片,列出一个程序加载的库,说出谁给它签了名,从一个菜单项跟到它的代码,或者比较同一个程序的两个版本。在第 2 部分,你会用到其中四条命令。这一部分把全部命令都展示给你,这样在你需要的时候,就知道还有什么可用。

你也在这里安装 REA,并对它运行两个很短的 Python 文件。第一个把它的命令分成几族。第二个问 REA,你自己的电脑已经为哪些工作做好了准备。

1 Install REA · ติดตั้ง REA · 安装 REA

🇬🇧 English

First ask Node.js for its version. If the command is not found, install Node.js from nodejs.org and open a new terminal.

Then make a new folder for this page and open the terminal in it. The second command below installs REA into that folder. It makes a folder named node_modules, about 100 MB in size. The name of the package is rea-agents, and @6.3.0 asks for the exact version this page was run with.

The third command asks REA for its version. npx comes with Node.js: it runs a program that is installed in this folder.

🇹🇭 ไทย

ก่อนอื่นถามเวอร์ชันจาก Node.js ถ้าไม่พบคำสั่งนี้ ให้ติดตั้ง Node.js จาก nodejs.org แล้วเปิดเทอร์มินัลใหม่

จากนั้นสร้างโฟลเดอร์ใหม่สำหรับหน้านี้ แล้วเปิดเทอร์มินัลในโฟลเดอร์นั้น คำสั่งที่สองข้างล่างติดตั้ง REA ลงในโฟลเดอร์นั้น มันสร้างโฟลเดอร์ชื่อ node_modules ขนาดประมาณ 100 MB ชื่อของแพ็กเกจคือ rea-agents และ @6.3.0 ขอเวอร์ชันเดียวกับที่ใช้รันหน้านี้

คำสั่งที่สามถามเวอร์ชันจาก REA npx มากับ Node.js มันรันโปรแกรมที่ติดตั้งอยู่ในโฟลเดอร์นี้

🇨🇳 中文

先问一下 Node.js 的版本。如果提示找不到这个命令,就从 nodejs.org 安装 Node.js,再打开一个新的终端。

然后为这一页新建一个文件夹,并在里面打开终端。下面第二条命令把 REA 装进这个文件夹。它会创建一个名叫 node_modules 的文件夹,大约 100 MB。包的名字是 rea-agents,@6.3.0 指定的正是运行这一页时用的版本。

第三条命令问 REA 的版本。npx 是 Node.js 自带的:它运行装在这个文件夹里的程序。

node --version
npm install rea-agents@6.3.0
npx rea --version

what the last command printedสิ่งที่คำสั่งสุดท้ายพิมพ์ออกมา最后一条命令打印的内容

6.3.0

2 commands.py: REA's commands, sorted into families · commands.py: คำสั่งของ REA จัดเป็นกลุ่ม · commands.py:把 REA 的命令分成几族

🇬🇧 English

npx rea --help prints every command with one line about it. The list is long, so commands.py reads it for you. It takes the first word of each command, inspect in inspect-plist, trace in trace-dylib-resolution, and puts the commands with the same first word into one family. The first word says what kind of work the command does. The rest of the name says what it does it to.

subprocess.run runs the command from Python and keeps what it printed. partition splits a name at its first hyphen. setdefault adds a family to the dictionary the first time it is met. textwrap.fill folds a long line so that it fits on the screen.

🇹🇭 ไทย

npx rea --help พิมพ์คำสั่งทุกตัวพร้อมคำอธิบายหนึ่งบรรทัด รายการนั้นยาว commands.py จึงอ่านให้คุณ มันหยิบคำแรกของคำสั่งแต่ละตัว คือ inspect ใน inspect-plist และ trace ใน trace-dylib-resolution แล้วรวมคำสั่งที่มีคำแรกเหมือนกันไว้ในกลุ่มเดียว คำแรกบอกว่าคำสั่งทำงานชนิดไหน ส่วนที่เหลือของชื่อบอกว่าทำกับอะไร

subprocess.run รันคำสั่งจาก Python และเก็บสิ่งที่มันพิมพ์ไว้ partition แยกชื่อตรงยัติภังค์ตัวแรก setdefault เพิ่มกลุ่มเข้าดิกชันนารีเมื่อพบกลุ่มนั้นครั้งแรก textwrap.fill พับบรรทัดยาวให้พอดีกับหน้าจอ

🇨🇳 中文

npx rea --help 打印出每一条命令,每条配一行说明。这个列表很长,所以 commands.py 替你来读。它取每条命令的第一个词,比如 inspect-plist 里的 inspect、trace-dylib-resolution 里的 trace,把第一个词相同的命令放进同一族。第一个词说明这条命令做哪一类工作,名字的其余部分说明它对什么做。

subprocess.run 从 Python 里运行命令,并保存它打印的内容。partition 在第一个连字符处把名字切开。setdefault 在第一次遇到一个族时,把它加进字典。textwrap.fill 把长行折叠起来,好让它放得进屏幕。

commands.py

"""commands.py - ask REA for its list of commands, and sort them into families.

    python commands.py
"""
import subprocess
import textwrap


def main():
    # --help prints every command with one line of explanation.
    finished = subprocess.run("npx rea --help", shell=True, capture_output=True, text=True, encoding="utf-8")
    if finished.returncode != 0:
        print("REA stopped with an error. Type npx rea --help yourself to read it.")
        return

    # The list begins after the line "Commands:" and ends at the first empty line.
    commands = []
    inside = False
    for line in finished.stdout.split("\n"):
        if line.strip() == "Commands:":
            inside = True
        elif inside and line.strip() == "":
            break
        elif inside:
            commands.append(line.split()[0])

    # The first word of a command says what kind of work it does: inspect-plist,
    # inspect-macho and inspect-signature are all of the family "inspect".
    families = {}
    for name in commands:
        verb, _, rest = name.partition("-")
        families.setdefault(verb, []).append(rest or "(by itself)")

    print("commands in all", len(commands))
    print("families       ", len(families))
    print()
    for verb in sorted(families, key=lambda v: (-len(families[v]), v)):
        members = ", ".join(families[verb])
        head = verb + " (" + str(len(families[verb])) + "): "
        print(textwrap.fill(members, width=78, initial_indent=head, subsequent_indent=" " * len(head),
                            break_on_hyphens=False))


if __name__ == "__main__":
    main()
python commands.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

commands in all 95
families        35

inspect (27): (by itself), analysis-view, android-class, android-method,
              android-package, artifact, asset-catalog, binary-layout,
              electron-page, evm-interface, firmware-regions, keyed-archive,
              macho, managed-artifact, managed-members,
              managed-native-boundaries, native-api, native-data-type,
              native-dispatch-metadata, native-instruction, native-load-image,
              plist, recorded-crash, signature, web-event-listeners,
              web-network-capture, web-page
trace (9): (by itself), android-references, application-feature,
           dylib-resolution, javascript-semantics, native-ui-action,
           native-values, web-module-imports, web-source-location
compare (8): (by itself), application-versions, javascript-export-shapes,
             managed-members, process-captures, source-to-bundle,
             web-captures, web-screenshots
capture (5): browser-scenario, electron-scenario, native-ui-scenario, process,
             web-screenshot
observe (5): javascript-runtime, native-calls, native-ui, web-execution,
             web-session
list (4): architectures, browser-targets, electron-targets,
          javascript-runtime-targets
analyze (3): (by itself), javascript-application, web-bundle
project (3): android-application-graph, apple-application-graph,
             managed-application-graph
evidence (2): export, import
extract (2): artifact, firmware
import (2): managed-reconstruction, reference-source
search (2): (by itself), android-classes
address (1): to-file-offset
annotate (1): native-function
build (1): reconstruction-obligation-ledger
capabilities (1): (by itself)
decode (1): interface-builder
decompile (1): (by itself)
demangle (1): swift
discover (1): webmcp-tools
doctor (1): (by itself)
evaluate (1): reconstruction-coverage
export (1): web-scripts
function (1): (by itself)
instructions (1): (by itself)
providers (1): (by itself)
read (1): bytes
reconcile (1): javascript-runtime
recover (1): javascript-sources
resolve (1): native-call-targets
setup (1): (by itself)
uninstall (1): (by itself)
update (1): (by itself)
verify (1): managed-native-boundaries
xrefs (1): (by itself)

🇬🇧 English

95 commands in 35 families, and five families hold more than half of them. Read the first words from the top. They are the verbs of reverse engineering, and the table below says what each family does.

🇹🇭 ไทย

คำสั่ง 95 ตัวใน 35 กลุ่ม และห้ากลุ่มมีคำสั่งเกินครึ่งของทั้งหมด อ่านคำแรกจากบนลงมา มันคือกริยาของวิศวกรรมย้อนกลับ และตารางข้างล่างบอกว่าแต่ละกลุ่มทำอะไร

🇨🇳 中文

95 条命令分成 35 族,其中五族就占了一半以上。从上往下读这些第一个词。它们就是逆向工程的动词,下面的表格说明每一族做什么。

Familyกลุ่ม族What its commands doคำสั่งในกลุ่มทำอะไร这一族的命令做什么Examplesตัวอย่าง例子
inspect (27)Read one thing and report what is in it: the card of an app, its signature, a compiled Mac program, an Android package, a .NET assembly, a firmware image, a web page. Nothing is run.อ่านสิ่งหนึ่งแล้วรายงานว่าข้างในมีอะไร: บัตรของแอป ลายเซ็นของมัน โปรแกรมแมคที่คอมไพล์แล้ว แพ็กเกจ Android แอสเซมบลี .NET อิมเมจเฟิร์มแวร์ หน้าเว็บ ไม่มีอะไรถูกรัน读一个东西,报告它里面有什么:应用的卡片、它的签名、一个编译好的 Mac 程序、一个 Android 包、一个 .NET 程序集、一个固件镜像、一个网页。什么都不运行。inspect-plist, inspect-signature, inspect-macho
trace (9)Follow one thing through a program: a button's action to the code that handles it, a library to the file it is loaded from, a word through the modules of a JavaScript app.ตามรอยสิ่งหนึ่งผ่านโปรแกรม: action ของปุ่มไปถึงโค้ดที่จัดการมัน ไลบรารีไปถึงไฟล์ที่มันถูกโหลดมา คำหนึ่งคำผ่านโมดูลของแอป JavaScript在程序里跟踪一个东西:从按钮的 action 跟到处理它的代码,从一个库跟到加载它的文件,让一个词穿过 JavaScript 应用的各个模块。trace-native-ui-action, trace-dylib-resolution
compare (8)Put two answers side by side: two versions of an app, two captures of a web page, two screenshots, or a program and the source code it says it was built from.วางคำตอบสองอย่างเทียบกัน: แอปสองเวอร์ชัน การบันทึกหน้าเว็บสองครั้ง ภาพหน้าจอสองภาพ หรือโปรแกรมกับซอร์สโค้ดที่มันบอกว่าถูกสร้างมาจาก把两个答案放在一起比:一个应用的两个版本,一个网页的两次捕获,两张截图,或者一个程序和它声称自己来自的源代码。compare-application-versions, compare-source-to-bundle
capture (5), observe (5)Run or watch one program and record what it does: a scenario in a browser, in an Electron app or in a native window, a process in a terminal, the calls a Mac program makes. These are the only families that run anything, and they run only the program you name.รันหรือเฝ้าดูโปรแกรมหนึ่งตัวแล้วบันทึกสิ่งที่มันทำ: สถานการณ์ในเบราว์เซอร์ ในแอป Electron หรือในหน้าต่างเนทีฟ โปรเซสในเทอร์มินัล การเรียกที่โปรแกรมแมคทำ นี่คือสองกลุ่มเดียวที่รันอะไรบางอย่าง และมันรันเฉพาะโปรแกรมที่คุณระบุ运行或观察一个程序,记录它做了什么:浏览器里、Electron 应用里或原生窗口里的一段场景,终端里的一个进程,一个 Mac 程序发出的调用。只有这两族会运行东西,而且只运行你指定的那个程序。capture-browser-scenario, observe-native-calls
list (4)Name what can be looked at: the processors a program is built for, the pages open in a browser, the Electron or Node.js programs running on this computer.บอกชื่อสิ่งที่ดูได้: โปรเซสเซอร์ที่โปรแกรมถูกสร้างมาสำหรับ หน้าที่เปิดอยู่ในเบราว์เซอร์ โปรแกรม Electron หรือ Node.js ที่กำลังรันบนคอมพิวเตอร์นี้说出有哪些东西可以看:一个程序是为哪些处理器构建的,浏览器里打开了哪些页面,这台电脑上正在运行哪些 Electron 或 Node.js 程序。list-architectures, list-browser-targets
analyze (3), project (3), decode, decompile, recover, function, instructions, xrefs, search (2)Build a bigger picture from a whole program: the graph of a JavaScript app, the parts of an Apple or Android app, readable JavaScript recovered from a bundle, the compiled design of a window turned back into text, or the machine code of one function turned back into something like source code.สร้างภาพใหญ่จากโปรแกรมทั้งตัว: กราฟของแอป JavaScript ส่วนประกอบของแอป Apple หรือ Android โค้ด JavaScript ที่อ่านได้ซึ่งกู้คืนมาจากบันเดิล แบบของหน้าต่างที่คอมไพล์แล้วเปลี่ยนกลับเป็นข้อความ หรือโค้ดเครื่องของฟังก์ชันหนึ่งเปลี่ยนกลับเป็นสิ่งที่คล้ายซอร์สโค้ด从整个程序建立更大的图景:一个 JavaScript 应用的图,一个 Apple 或 Android 应用的各个部分,从打包文件里恢复出来的可读 JavaScript,把窗口编译好的设计变回文字,或者把一个函数的机器码变回类似源代码的东西。analyze-javascript-application, decode-interface-builder, decompile
evidence (2), extract (2), export, import (2)Keep, move and unpack: save an answer as a file and load it again later, pull the files out of an archive or a firmware image, bring in a source tree to compare against.เก็บ ย้าย และแกะ: บันทึกคำตอบเป็นไฟล์แล้วโหลดกลับมาทีหลัง ดึงไฟล์ออกจากอาร์ไคฟ์หรืออิมเมจเฟิร์มแวร์ นำซอร์สทรีเข้ามาเพื่อใช้เปรียบเทียบ保存、搬运和解包:把一个答案存成文件,以后再读回来;把文件从压缩包或固件镜像里取出来;把一棵源代码树导进来用作对照。evidence-export, extract-artifact
doctor, setup, update, uninstall, capabilities, providersLook after REA itself: check that it is ready, connect it to an AI agent, update it, and say which analysis engines it can use.ดูแล REA เอง: ตรวจว่ามันพร้อม เชื่อมมันกับเอเจนต์ AI อัปเดตมัน และบอกว่ามันใช้เอนจินวิเคราะห์ตัวไหนได้照看 REA 本身:检查它是否就绪,把它连到 AI 智能体,更新它,并说出它能用哪些分析引擎。doctor, setup

🇬🇧 English

The families say how REA works. The next table says what it works on. It is REA's own list, from the manual of version 6.3.0, with what each kind of program needs beyond REA itself.

🇹🇭 ไทย

กลุ่มบอกว่า REA ทำงานอย่างไร ตารางถัดไปบอกว่ามันทำงานกับอะไร นี่คือรายการของ REA เอง จากคู่มือของเวอร์ชัน 6.3.0 พร้อมสิ่งที่โปรแกรมแต่ละชนิดต้องการนอกเหนือจาก REA

🇨🇳 中文

这些族说的是 REA 怎样工作。下一张表说的是它对什么工作。这是 REA 自己的清单,来自 6.3.0 版的手册,并写明每一类程序在 REA 之外还需要什么。

What you can studyสิ่งที่คุณศึกษาได้你可以研究什么What REA tells youREA บอกอะไรคุณREA 告诉你什么What it needsต้องใช้อะไร需要什么
Compiled programs: machine code for Mac, Windows or Linuxโปรแกรมที่คอมไพล์แล้ว: โค้ดเครื่องสำหรับแมค Windows หรือ Linux编译好的程序:Mac、Windows 或 Linux 的机器码Something like source code, the machine code itself, texts, names, calls and referencesสิ่งที่คล้ายซอร์สโค้ด โค้ดเครื่องเอง ข้อความ ชื่อ การเรียก และการอ้างอิง类似源代码的东西、机器码本身、文字、名字、调用和引用One analysis engine: Hopper, Ghidra or IDAเอนจินวิเคราะห์หนึ่งตัว: Hopper, Ghidra หรือ IDA一个分析引擎:Hopper、Ghidra 或 IDA
Mac and iOS apps as bundlesแอปแมคและ iOS ในรูปบันเดิล作为 bundle 的 Mac 和 iOS 应用The card, the signature, the libraries, the designs of its windows, its catalogs of images, the parts of the bundleบัตร ลายเซ็น ไลบรารี แบบของหน้าต่าง แคตตาล็อกรูปภาพ ส่วนประกอบของบันเดิล卡片、签名、库、窗口的设计、图片目录、bundle 的各个部分A Mac with its Command Line Tools. This is part 2.แมคที่มี Command Line Tools นี่คือส่วนที่ 2一台装有 Command Line Tools 的 Mac。这就是第 2 部分。
JavaScript and Electron appsแอป JavaScript และ ElectronJavaScript 和 Electron 应用Modules, imports, source maps, routes, messages between its processes, native add-onsโมดูล การ import ซอร์สแมป เส้นทาง ข้อความระหว่างโปรเซสของมัน แอดออนเนทีฟ模块、导入、源码映射、路由、进程之间的消息、原生插件Node.js onlyNode.js เท่านั้น只要 Node.js
Websites open in a browserเว็บไซต์ที่เปิดอยู่ในเบราว์เซอร์在浏览器里打开的网站The structure of the page, its scripts, what it sends and receives, screenshotsโครงสร้างของหน้า สคริปต์ของมัน สิ่งที่มันส่งและรับ ภาพหน้าจอ页面的结构、它的脚本、它发送和接收的东西、截图A Chrome-family browser that you have openเบราว์เซอร์ตระกูล Chrome ที่คุณเปิดอยู่一个你已经打开的 Chrome 系浏览器
Saved network captures (HAR, mitmproxy)การบันทึกเครือข่ายที่เซฟไว้ (HAR, mitmproxy)保存下来的网络捕获(HAR、mitmproxy)Requests, responses, what was sent, and the places in the scripts that sent itคำขอ คำตอบ สิ่งที่ถูกส่ง และตำแหน่งในสคริปต์ที่ส่งมัน请求、响应、发送了什么,以及脚本里发送它的位置The capture fileไฟล์บันทึก捕获文件
.NET assembliesแอสเซมบลี .NET.NET 程序集Metadata, its instructions, the native libraries it declares, and comparisons between buildsเมทาดาตา คำสั่งของมัน ไลบรารีเนทีฟที่มันประกาศ และการเปรียบเทียบระหว่างบิลด์元数据、它的指令、它声明的原生库,以及不同构建之间的比较Nothing: it only readsไม่ต้องใช้อะไร: มันแค่อ่าน不需要什么:它只读
Android apps (APK)แอป Android (APK)Android 应用(APK)The manifest, the classes, methods turned back into code, and who refers to whatแมนิเฟสต์ คลาส เมธอดที่เปลี่ยนกลับเป็นโค้ด และใครอ้างถึงอะไร清单、类、变回代码的方法,以及谁引用了什么JADX and a Java JDKJADX และ Java JDKJADX 和一个 Java JDK
Firmware imagesอิมเมจเฟิร์มแวร์固件镜像Its regions, the files pulled out of it, and a hand-over to the compiled-program commandsส่วนต่าง ๆ ของมัน ไฟล์ที่ดึงออกมา และการส่งต่อให้คำสั่งสำหรับโปรแกรมที่คอมไพล์แล้ว它的各个区域、从里面取出的文件,以及交接给编译程序命令Binwalk or Unblob, on LinuxBinwalk หรือ Unblob บน LinuxLinux 上的 Binwalk 或 Unblob
Linux programs, recorded crashes, smart-contract bytecodeโปรแกรม Linux การแครชที่บันทึกไว้ ไบต์โค้ดของสมาร์ตคอนแทรกต์Linux 程序、记录下来的崩溃、智能合约字节码Sections and symbols; the registers and signals of a crash; the functions a contract answers toเซกชันและสัญลักษณ์ รีจิสเตอร์และสัญญาณของการแครช ฟังก์ชันที่คอนแทรกต์ตอบรับ节和符号;崩溃时的寄存器和信号;合约响应的函数pwntools on Linux for the first two; nothing for the thirdpwntools บน Linux สำหรับสองอย่างแรก; ไม่ต้องใช้อะไรสำหรับอย่างที่สาม前两项要 Linux 上的 pwntools;第三项不需要什么
Packages and archives (ZIP, DMG, APK, IPA, MSIX)แพ็กเกจและอาร์ไคฟ์ (ZIP, DMG, APK, IPA, MSIX)包和压缩文件(ZIP、DMG、APK、IPA、MSIX)The list of files inside, their digests, their plists, and the files themselves when extractedรายการไฟล์ข้างใน ไดเจสต์ของมัน plist ของมัน และตัวไฟล์เองเมื่อแกะออกมา里面的文件清单、它们的摘要、它们的 plist,以及解包后的文件本身Nothingไม่ต้องใช้อะไร不需要什么
Running programsโปรแกรมที่กำลังรัน正在运行的程序What a process printed, what it was told, how it ended, which files it touched, and how two runs differโปรเซสพิมพ์อะไร ถูกบอกอะไร จบอย่างไร แตะไฟล์ไหน และสองการรันต่างกันอย่างไร一个进程打印了什么、收到了什么、怎样结束、碰了哪些文件,以及两次运行有什么不同Linux or macOSLinux หรือ macOSLinux 或 macOS
IT READS, OR IT WATCHES, AND IT ALWAYS SAYS WHICH Most of REA's commands do static analysis: they read the files of a program and never run it. That is why they are safe to use on a program you do not trust, and why they cannot see what a program decides while it runs. A menu item whose receiver is chosen at run time has no receiver in the files, and REA says so instead of guessing. You will meet this wall in part 2.The capture and observe families are the other kind, together with the web commands that look at a browser you have open. They run or watch one program that you name, and they record what it does. REA keeps the two apart: an answer from reading never claims to have seen the program run.คำสั่งส่วนใหญ่ของ REA ทำการวิเคราะห์แบบสถิต: มันอ่านไฟล์ของโปรแกรมและไม่รันโปรแกรมเลย นั่นคือเหตุผลที่มันใช้กับโปรแกรมที่คุณไม่ไว้ใจได้อย่างปลอดภัย และเป็นเหตุผลที่มันมองไม่เห็นสิ่งที่โปรแกรมตัดสินในขณะที่รัน รายการเมนูที่ผู้รับถูกเลือกตอนรันไม่มีผู้รับอยู่ในไฟล์ และ REA ก็บอกอย่างนั้นแทนที่จะเดา คุณจะเจอกำแพงนี้ในส่วนที่ 2กลุ่ม capture และ observe เป็นอีกชนิดหนึ่ง เช่นเดียวกับคำสั่งเว็บที่ดูเบราว์เซอร์ที่คุณเปิดอยู่ มันรันหรือเฝ้าดูโปรแกรมหนึ่งตัวที่คุณระบุ แล้วบันทึกสิ่งที่โปรแกรมทำ REA แยกสองอย่างนี้ออกจากกัน: คำตอบที่ได้จากการอ่านไม่เคยอ้างว่าเห็นโปรแกรมรันREA 的大多数命令做的是静态分析:它们读一个程序的文件,从不运行它。所以把它们用在你不信任的程序上是安全的,也所以它们看不见程序在运行时才做的决定。一个菜单项的接收者如果是运行时才选定的,文件里就没有接收者,REA 会照实说出来,而不是去猜。在第 2 部分你会遇到这堵墙。capture 和 observe 两族是另一种,还有那些查看你已打开的浏览器的 web 命令。它们运行或观察你指定的那一个程序,并记录它做了什么。REA 把这两种分得很清:从读文件得来的答案,绝不会声称看见了程序运行。
WHAT REA DOES NOT DO Without one of the three engines, Hopper, Ghidra or IDA, REA cannot turn machine code back into code: decompile, function, instructions and xrefs need an engine, while the inspect commands for a Mac app do not. REA does not find bugs or secrets for you. It answers the question you ask, about the files you give it, and it marks each answer observed, inferred or unknown. It does not run a program unless you choose a capture or observe command.It does not check that you are allowed to study a program: that is the licence box above. And it changes fast. This page pins version 6.3.0, and a newer REA may have commands that this page does not list. npx rea --help shows yours.ถ้าไม่มีเอนจินหนึ่งในสามตัว คือ Hopper, Ghidra หรือ IDA, REA เปลี่ยนโค้ดเครื่องกลับเป็นโค้ดไม่ได้: decompile, function, instructions และ xrefs ต้องใช้เอนจิน ส่วนคำสั่ง inspect สำหรับแอปแมคไม่ต้อง REA ไม่ได้หาบั๊กหรือความลับให้คุณ มันตอบคำถามที่คุณถาม เกี่ยวกับไฟล์ที่คุณให้มัน และมันทำเครื่องหมายคำตอบแต่ละข้อว่า observed, inferred หรือ unknown มันไม่รันโปรแกรม เว้นแต่คุณเลือกคำสั่งในกลุ่ม capture หรือ observeมันไม่ตรวจว่าคุณได้รับอนุญาตให้ศึกษาโปรแกรมนั้นหรือไม่: เรื่องนั้นอยู่ในกรอบเรื่องสัญญาอนุญาตข้างบน และมันเปลี่ยนเร็ว หน้านี้ตรึงเวอร์ชัน 6.3.0 ไว้ และ REA ที่ใหม่กว่าอาจมีคำสั่งที่หน้านี้ไม่ได้แสดง npx rea --help แสดงของคุณ没有 Hopper、Ghidra 或 IDA 这三个引擎之一,REA 就不能把机器码变回代码:decompile、function、instructions 和 xrefs 需要引擎,而针对 Mac 应用的那些 inspect 命令不需要。REA 不会替你找 bug 或秘密。它回答你问的问题,针对你给它的文件,并把每个答案标成 observed、inferred 或 unknown。除非你选了 capture 或 observe 族的命令,它不会运行程序。它不检查你是否有权研究一个程序:那是上面那个许可证框里的事。而且它变得很快。这一页钉住的是 6.3.0 版,更新的 REA 可能有这一页没列出的命令。npx rea --help 会显示你那一版的。

3 ready.py: what your computer is ready for · ready.py: คอมพิวเตอร์ของคุณพร้อมสำหรับอะไร · ready.py:你的电脑已经为什么做好了准备

🇬🇧 English

rea doctor checks REA's surroundings: Node.js, the operating system, the three engines, and any AI agents it is connected to. It answers with a long report, so ready.py keeps five rows of it. The report is JSON again. Each row has a name, whether it is ok, and a detail.

doctor ends with code 1 when anything is missing. That is its way of saying no. ready.py does not stop on that code, because the report is complete all the same.

🇹🇭 ไทย

rea doctor ตรวจสิ่งแวดล้อมของ REA: Node.js ระบบปฏิบัติการ เอนจินสามตัว และเอเจนต์ AI ที่มันเชื่อมอยู่ มันตอบด้วยรายงานยาว ready.py จึงเก็บไว้ห้าแถว รายงานเป็น JSON อีกเช่นกัน แต่ละแถวมีชื่อ บอกว่าโอเคหรือไม่ และรายละเอียด

doctor จบด้วยโค้ด 1 เมื่อมีอะไรขาดไป นั่นคือวิธีที่มันบอกว่าไม่ ready.py ไม่หยุดเพราะโค้ดนั้น เพราะรายงานก็ยังครบถ้วนอยู่ดี

🇨🇳 中文

rea doctor 检查 REA 周围的环境:Node.js、操作系统、三个引擎,以及它连着的 AI 智能体。它用一份很长的报告来回答,所以 ready.py 只留下其中五行。报告又是 JSON。每一行有一个名字、是否正常,以及一条细节。

缺了任何东西时,doctor 以代码 1 结束。这是它说“不”的方式。ready.py 不会因为这个代码停下,因为报告照样是完整的。

ready.py

"""ready.py - ask REA which analysis engines this computer has.

    python ready.py
"""
import json
import subprocess

# The engines that REA can use to read machine code. It needs one of them for
# the commands that turn a compiled program back into code. Node.js and the
# host are listed too, because every REA command needs those.
ENGINES = ["node", "host", "hopper", "ghidra", "ida-registration"]


def main():
    # doctor ends with code 1 when anything is missing. Its answer is complete all the same,
    # so this file does not stop on that code.
    finished = subprocess.run("npx rea doctor --json", shell=True, capture_output=True, text=True, encoding="utf-8")
    report = json.loads(finished.stdout)

    print("ENGINES ON THIS COMPUTER")
    for check in report["scope_checks"]:
        if check["name"] in ENGINES:
            state = "yes" if check["ok"] else "no "
            print("  " + state, check["name"].ljust(17), check.get("detail", ""))


if __name__ == "__main__":
    main()
python ready.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

ENGINES ON THIS COMPUTER
  yes node              24.15.0
  yes host              win32 x64
  no  hopper            
  no  ghidra            GHIDRA_INSTALL_DIR is not set
  no  ida-registration  IDA MCP registration is not configured.

🇬🇧 English

This laptop has Node.js and nothing else: no Hopper, no Ghidra, no IDA. So on this computer REA can do everything that reads files, and nothing that reads machine code as code. Part 2 needs none of the engines. It uses inspect-plist, inspect-signature, inspect-macho and decode-interface-builder, and on a Mac those get their facts from Apple's own small tools and from REA's own readers.

🇹🇭 ไทย

แล็ปท็อปเครื่องนี้มี Node.js และไม่มีอย่างอื่น: ไม่มี Hopper ไม่มี Ghidra ไม่มี IDA ดังนั้นบนคอมพิวเตอร์เครื่องนี้ REA ทำได้ทุกอย่างที่เป็นการอ่านไฟล์ และทำไม่ได้เลยในสิ่งที่อ่านโค้ดเครื่องเป็นโค้ด ส่วนที่ 2 ไม่ต้องใช้เอนจินเลย มันใช้ inspect-plist, inspect-signature, inspect-macho และ decode-interface-builder และบนแมค คำสั่งเหล่านั้นได้ข้อเท็จจริงจากเครื่องมือเล็ก ๆ ของ Apple เอง และจากตัวอ่านของ REA เอง

🇨🇳 中文

这台笔记本有 Node.js,别的都没有:没有 Hopper,没有 Ghidra,没有 IDA。所以在这台电脑上,凡是读文件的事 REA 都能做,凡是把机器码当作代码来读的事它都做不了。第 2 部分一个引擎都不需要。它用 inspect-plist、inspect-signature、inspect-macho 和 decode-interface-builder,在 Mac 上,这些命令的事实来自 Apple 自己的小工具和 REA 自己的读取器。

YOUR TURN
  1. Run npx rea inspect-plist --help. What does the command take, and which of it is required? Every command answers --help this way.
  2. Pick one family from the output of commands.py that this page never uses, and read the --help of one command in it. Say in one sentence what it needs and what it gives back.
  3. Change ENGINES in ready.py so that it prints every row of the report. Which rows are about AI agents? Those rows name files on your computer, so do not paste that output anywhere public.
  1. รัน npx rea inspect-plist --help คำสั่งนี้รับอะไร และส่วนไหนจำเป็นต้องมี คำสั่งทุกตัวตอบ --help แบบนี้
  2. เลือกกลุ่มหนึ่งจากผลลัพธ์ของ commands.py ที่หน้านี้ไม่ได้ใช้เลย แล้วอ่าน --help ของคำสั่งหนึ่งตัวในกลุ่มนั้น บอกในหนึ่งประโยคว่ามันต้องการอะไรและให้อะไรกลับมา
  3. แก้ ENGINES ใน ready.py ให้พิมพ์ทุกแถวของรายงาน แถวไหนเกี่ยวกับเอเจนต์ AI แถวเหล่านั้นระบุชื่อไฟล์บนคอมพิวเตอร์ของคุณ จึงอย่าวางผลลัพธ์นั้นในที่สาธารณะ
  1. 运行 npx rea inspect-plist --help。这条命令接受什么,其中哪些是必填的?每条命令都这样回答 --help。
  2. 从 commands.py 的输出里挑一个这一页从没用过的族,读其中一条命令的 --help。用一句话说出它需要什么、给回什么。
  3. 改一改 ready.py 里的 ENGINES,让它打印报告的每一行。哪些行是关于 AI 智能体的?那些行写着你电脑上的文件名,所以不要把那段输出贴到任何公开的地方。

🪟 Part 2 — A real Mac app: Rectangle · ส่วนที่ 2 — แอปแมคของจริง: Rectangle · 第 2 部分 —— 一个真实的 Mac 应用:Rectangle

🇬🇧 English

Now a real program that somebody else wrote. Rectangle is a free Mac app by Ryan Hanson. It moves and resizes windows with keyboard shortcuts. It is open source under the MIT licence, which lets anyone use, copy and change it without restriction. So you are allowed to take it apart.

Rectangle is a good program to study for a second reason. Its author publishes two things: the finished app, and the source code it was built from. You study the finished app as a stranger would. Then you open the source code and mark your work.

You do not install Rectangle and you do not start it. You only read its files.

🇹🇭 ไทย

ทีนี้มาดูโปรแกรมจริงที่คนอื่นเขียน Rectangle คือแอปแมคฟรีของ Ryan Hanson มันย้ายและปรับขนาดหน้าต่างด้วยคีย์ลัด มันเป็นโอเพนซอร์สภายใต้สัญญาอนุญาต MIT ซึ่งให้ทุกคนใช้ คัดลอก และแก้ไขได้ without restriction (โดยไม่มีข้อจำกัด) คุณจึงได้รับอนุญาตให้แยกมันออกดู

Rectangle เป็นโปรแกรมที่ดีสำหรับการศึกษาด้วยเหตุผลข้อที่สอง ผู้เขียนเผยแพร่สองอย่าง: แอปที่ทำเสร็จแล้ว และซอร์สโค้ดที่ใช้สร้างมัน คุณศึกษาแอปที่ทำเสร็จแล้วแบบที่คนแปลกหน้าจะทำ จากนั้นเปิดซอร์สโค้ดแล้วตรวจงานของคุณ

คุณไม่ต้องติดตั้ง Rectangle และไม่ต้องเปิดมัน คุณแค่อ่านไฟล์ของมัน

🇨🇳 中文

现在来看一个别人写的真实程序。Rectangle 是 Ryan Hanson 做的免费 Mac 应用。它用键盘快捷键移动窗口、调整窗口大小。它是开源的,用的是 MIT 许可证,这个许可证允许任何人without restriction(不受限制地)使用、复制和修改它。所以你可以把它拆开来看。

Rectangle 适合拿来研究,还有第二个原因。它的作者发布了两样东西:做好的应用,和用来构建它的源代码。你像一个陌生人那样研究做好的应用。然后打开源代码,给自己的工作打分。

你不用安装 Rectangle,也不用启动它。你只读它的文件。

4 get_rectangle.py: download the app and its source · get_rectangle.py: ดาวน์โหลดแอปและซอร์สของมัน · get_rectangle.py:下载应用和它的源代码

🇬🇧 English

get_rectangle.py downloads one exact version of Rectangle from GitHub, two times over. The first download is the app as its author ships it, in a .dmg file. A .dmg is a disk inside a file. The script opens the disk, copies Rectangle.app out of it into your folder, and closes the disk again. The second download is the source code of the same version, and it goes into a folder named rectangle_src.

The script does its work with four programs that every Mac has: curl downloads, hdiutil opens and closes disks, cp copies, and tar unpacks. Python starts each of them with subprocess.run.

🇹🇭 ไทย

get_rectangle.py ดาวน์โหลด Rectangle เวอร์ชันหนึ่งที่ระบุแน่นอนจาก GitHub สองครั้ง การดาวน์โหลดครั้งแรกคือแอปตามที่ผู้เขียนส่งมอบ ในไฟล์ .dmg ไฟล์ .dmg คือดิสก์ที่อยู่ในไฟล์ สคริปต์เปิดดิสก์ คัดลอก Rectangle.app ออกมาไว้ในโฟลเดอร์ของคุณ แล้วปิดดิสก์อีกครั้ง การดาวน์โหลดครั้งที่สองคือซอร์สโค้ดของเวอร์ชันเดียวกัน และมันไปอยู่ในโฟลเดอร์ชื่อ rectangle_src

สคริปต์ทำงานด้วยโปรแกรมสี่ตัวที่แมคทุกเครื่องมี: curl ดาวน์โหลด hdiutil เปิดและปิดดิสก์ cp คัดลอก และ tar แตกไฟล์ Python เริ่มแต่ละตัวด้วย subprocess.run

🇨🇳 中文

get_rectangle.py 从 GitHub 下载 Rectangle 的一个确定的版本,一共下载两次。第一次下载的是作者发布的应用,放在一个 .dmg 文件里。.dmg 是装在文件里的一张磁盘。脚本打开这张磁盘,把 Rectangle.app 从里面复制到你的文件夹,再把磁盘关上。第二次下载的是同一个版本的源代码,它被放进名叫 rectangle_src 的文件夹。

脚本靠每台 Mac 都有的四个程序来干活:curl 负责下载,hdiutil 打开和关闭磁盘,cp 负责复制,tar 负责解包。Python 用 subprocess.run 启动它们中的每一个。

get_rectangle.py

"""get_rectangle.py - download the app Rectangle and its source code, to study them.

    python3 get_rectangle.py

Rectangle is only downloaded and copied here. It is not installed and not started.
"""
import os
import subprocess

# One exact version: the app as its author ships it, and the source code it was built from.
VERSION = "2.0.3"
HOME_PAGE = "https://github.com/rxhanson/Rectangle"
APP_ADDRESS = HOME_PAGE + "/releases/download/v" + VERSION + "/Rectangle" + VERSION + ".dmg"
SOURCE_ADDRESS = HOME_PAGE + "/archive/refs/tags/v" + VERSION + ".tar.gz"


def run(command):
    # check=True stops this file at the first command that fails, and timeout=120
    # stops a command that has not finished after two minutes.
    # input=b"Y\n" answers yes if a disk asks you to agree to its licence.
    subprocess.run(command, check=True, capture_output=True, timeout=120, input=b"Y\n")


def count_files(folder):
    total = 0
    for path, folders, files in os.walk(folder):
        total = total + len(files)
    return total


def main():
    if not os.path.exists("Rectangle.app"):
        run(["curl", "-sL", "-o", "Rectangle.dmg", APP_ADDRESS])
        # A .dmg file is a disk in a file. hdiutil opens it as a folder named "disk".
        disk = os.path.abspath("disk")
        run(["hdiutil", "attach", "Rectangle.dmg", "-nobrowse", "-readonly", "-mountpoint", disk])
        run(["cp", "-R", "disk/Rectangle.app", "Rectangle.app"])
        run(["hdiutil", "detach", disk, "-force"])

    if not os.path.exists("rectangle_src"):
        run(["curl", "-sL", "-o", "rectangle_src.tar.gz", SOURCE_ADDRESS])
        os.mkdir("rectangle_src")
        # --strip-components 1 leaves out the one folder that wraps everything.
        run(["tar", "-xzf", "rectangle_src.tar.gz", "-C", "rectangle_src", "--strip-components", "1"])

    print("Rectangle.app  ", count_files("Rectangle.app"), "files")
    print("rectangle_src  ", count_files("rectangle_src"), "files")
    with open("rectangle_src/LICENSE", encoding="utf-8") as licence:
        print("licence        ", licence.readline().strip())


if __name__ == "__main__":
    main()
python3 get_rectangle.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

Rectangle.app   149 files
rectangle_src   454 files
licence         MIT License

🇬🇧 English

The last line is the first line of the file named LICENSE in the source code. Read that file yourself before you go on. Checking the licence is the first step of every study, and it is the step that people skip.

Rectangle.app looks like one file in Finder, but it is a folder of 149 files. A folder like this is called a bundle. The compiled program is one file inside it, and the other files sit beside it.

🇹🇭 ไทย

บรรทัดสุดท้ายคือบรรทัดแรกของไฟล์ชื่อ LICENSE ในซอร์สโค้ด อ่านไฟล์นั้นด้วยตัวเองก่อนไปต่อ การตรวจสัญญาอนุญาตคือขั้นแรกของการศึกษาทุกครั้ง และเป็นขั้นที่คนมักข้าม

Rectangle.app ดูเหมือนไฟล์เดียวใน Finder แต่มันคือโฟลเดอร์ที่มี 149 ไฟล์ โฟลเดอร์แบบนี้เรียกว่าบันเดิล โปรแกรมที่คอมไพล์แล้วคือไฟล์หนึ่งไฟล์ข้างในนั้น และไฟล์อื่น ๆ อยู่ข้าง ๆ

🇨🇳 中文

最后一行是源代码里名叫 LICENSE 的文件的第一行。继续之前,你自己把那个文件读一遍。查许可证是每一次研究的第一步,也是人们最常跳过的一步。

Rectangle.app 在 Finder 里看起来是一个文件,其实是一个有 149 个文件的文件夹。这样的文件夹叫做 bundle。编译好的程序是里面的一个文件,其他文件在它旁边。

5 app_card.py: the card the app carries · app_card.py: บัตรที่แอปพกติดตัว · app_card.py:应用随身带的卡片

🇬🇧 English

Every Mac app carries a file named Info.plist. It is like a name card: the app tells macOS its name, its version, and what it can open. macOS reads this card before it starts the app. REA's command for it is inspect-plist.

app_card.py runs the command and prints a few entries of the card. The answer is JSON, so in Python the card is a dictionary, and card["CFBundleName"] is one entry of it. Not every card has every entry, so for some the file uses card.get, which gives None when an entry is missing.

🇹🇭 ไทย

แอปแมคทุกตัวพกไฟล์ชื่อ Info.plist มันเหมือนนามบัตร: แอปบอก macOS ว่ามันชื่ออะไร เวอร์ชันอะไร และเปิดอะไรได้บ้าง macOS อ่านบัตรนี้ก่อนจะเริ่มแอป คำสั่งของ REA สำหรับบัตรนี้คือ inspect-plist

app_card.py รันคำสั่งนี้ แล้วพิมพ์บางรายการของบัตร คำตอบเป็น JSON ดังนั้นใน Python บัตรจึงเป็นดิกชันนารี และ card["CFBundleName"] คือรายการหนึ่งของมัน ไม่ใช่ทุกบัตรจะมีทุกรายการ สำหรับบางรายการไฟล์จึงใช้ card.get ซึ่งให้ค่า None เมื่อไม่มีรายการนั้น

🇨🇳 中文

每个 Mac 应用都带着一个名叫 Info.plist 的文件。它就像一张名片:应用告诉 macOS 自己叫什么、是什么版本、能打开什么。macOS 在启动应用之前会先读这张卡片。REA 读它的命令是 inspect-plist。

app_card.py 运行这条命令,再打印卡片上的几项。回答是 JSON,所以在 Python 里这张卡片是一个字典,card["CFBundleName"] 就是其中的一项。不是每张卡片都有每一项,所以对其中几项,这个文件用的是 card.get,没有那一项时它给出 None。

app_card.py

"""app_card.py - ask REA for the card that a Mac app carries about itself.

    python3 app_card.py
"""
import json
import subprocess

APP = "Rectangle.app"


def main():
    command = "npx rea inspect-plist " + APP + " --json"
    finished = subprocess.run(command, shell=True, capture_output=True, text=True)
    if finished.returncode != 0:
        print(finished.stdout)
        return
    card = json.loads(finished.stdout)["normalized_result"]["value"]

    print("name            ", card["CFBundleName"])
    print("identifier      ", card["CFBundleIdentifier"])
    print("version         ", card["CFBundleShortVersionString"])
    print("needs macOS     ", card["LSMinimumSystemVersion"], "or newer")
    print("program file    ", card["CFBundleExecutable"])
    print("first class     ", card["NSPrincipalClass"])
    print("first design    ", card["NSMainStoryboardFile"])
    # .get gives None when the card has no such entry.
    print("no Dock icon    ", card.get("LSUIElement"))
    print("asks for updates", card.get("SUFeedURL"))
    print("entries in all  ", len(card))

    # The kinds of address that macOS hands to this app.
    print("ADDRESSES IT OPENS")
    for kind in card.get("CFBundleURLTypes", []):
        print("  " + ", ".join(kind["CFBundleURLSchemes"]))


if __name__ == "__main__":
    main()
python3 app_card.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

name             Rectangle
identifier       com.knollsoft.Rectangle
version          2.0.3
needs macOS      14.0 or newer
program file     Rectangle
first class      NSApplication
first design     Main
no Dock icon     True
asks for updates https://rectangleapp.com/downloads/updates.xml
entries in all   30
ADDRESSES IT OPENS
  rectangle

🇬🇧 English

You have not started the app, and you already know how it behaves. no Dock icon True says that Rectangle shows no icon in the Dock: it lives in the menu bar at the top of the screen. asks for updates is the address where it looks for a newer version of itself. And it tells macOS that an address beginning with rectangle: belongs to it, so another program can send it a request.

The identifier, com.knollsoft.Rectangle, is the name macOS knows the app by. The first design it loads is named Main. You will open that design in step 8.

🇹🇭 ไทย

คุณยังไม่ได้เปิดแอป แต่คุณรู้แล้วว่ามันทำตัวอย่างไร no Dock icon True บอกว่า Rectangle ไม่แสดงไอคอนใน Dock: มันอยู่ในแถบเมนูที่ด้านบนของจอ asks for updates คือที่อยู่ที่มันใช้มองหาเวอร์ชันใหม่ของตัวเอง และมันบอก macOS ว่าที่อยู่ที่ขึ้นต้นด้วย rectangle: เป็นของมัน โปรแกรมอื่นจึงส่งคำขอมาให้มันได้

ตัวระบุ com.knollsoft.Rectangle คือชื่อที่ macOS ใช้เรียกแอปนี้ แบบแรกที่มันโหลดชื่อ Main คุณจะเปิดแบบนั้นในขั้นที่ 8

🇨🇳 中文

你还没有启动这个应用,却已经知道它的表现了。no Dock icon True 说明 Rectangle 不在 Dock 里显示图标:它待在屏幕顶部的菜单栏里。asks for updates 是它查找自己新版本的地址。它还告诉 macOS,以 rectangle: 开头的地址归它管,所以别的程序可以向它发请求。

标识符 com.knollsoft.Rectangle 是 macOS 认这个应用用的名字。它加载的第一个设计叫 Main。你会在第 8 步打开这个设计。

6 app_signature.py: who signed it · app_signature.py: ใครเซ็นรับรองมัน · app_signature.py:谁给它签了名

🇬🇧 English

A Mac program is signed. The signature is a mark that only its maker can put on it. With it, macOS can check two things before it runs the program: who made it, and that nobody has changed it since. The signature also holds the program's entitlements: a list of things the program is allowed to do that an ordinary program is not. REA's command is inspect-signature.

🇹🇭 ไทย

โปรแกรมแมคถูกเซ็นรับรอง ลายเซ็นคือเครื่องหมายที่มีแต่ผู้สร้างเท่านั้นที่ใส่ให้ได้ ด้วยลายเซ็นนี้ macOS ตรวจได้สองอย่างก่อนรันโปรแกรม: ใครสร้างมัน และไม่มีใครแก้ไขมันตั้งแต่นั้น ลายเซ็นยังเก็บ entitlement ของโปรแกรมไว้ด้วย: รายการของสิ่งที่โปรแกรมนี้ได้รับอนุญาตให้ทำ ซึ่งโปรแกรมธรรมดาทำไม่ได้ คำสั่งของ REA คือ inspect-signature

🇨🇳 中文

Mac 程序是签过名的。签名是只有制作者才能加上去的标记。有了它,macOS 在运行程序之前可以检查两件事:是谁做的,以及做好之后有没有人改动过。签名里还存着程序的 entitlement:一份清单,列出这个程序被允许做、而普通程序不能做的事。REA 的命令是 inspect-signature。

app_signature.py

"""app_signature.py - ask REA who signed a Mac app, and what the app is allowed to do.

    python3 app_signature.py
"""
import json
import subprocess

APP = "Rectangle.app"


def main():
    command = "npx rea inspect-signature " + APP + " --json"
    finished = subprocess.run(command, shell=True, capture_output=True, text=True)
    if finished.returncode != 0:
        print(finished.stdout)
        return
    signature = json.loads(finished.stdout)["normalized_result"]

    print("signed          ", signature["signed"])
    print("identifier      ", signature["identifier"])
    print("team            ", signature["team_identifier"])
    print("format          ", signature["format"])
    print("hardened runtime", signature["hardened_runtime"])
    print("SIGNED BY, FROM THE SIGNER UP TO THE ROOT")
    for authority in signature["authorities"]:
        print("  " + authority)

    # An entitlement is one permission that the signer gave to this program.
    entitlements = signature["entitlements"]
    print("ENTITLEMENTS:", len(entitlements))
    for name in sorted(entitlements):
        print("  " + name, "=", entitlements[name])


if __name__ == "__main__":
    main()
python3 app_signature.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

signed           True
identifier       com.knollsoft.Rectangle
team             XSYZ3E4B7D
format           Mach-O universal (x86_64 arm64)
hardened runtime True
SIGNED BY, FROM THE SIGNER UP TO THE ROOT
  Developer ID Application: Ryan Hanson (XSYZ3E4B7D)
  Developer ID Certification Authority
  Apple Root CA
ENTITLEMENTS: 0

🇬🇧 English

Read the three names under SIGNED BY from the bottom up. Apple Root CA is the top: your Mac trusts it from the day it was made. It vouches for the second name, and the second vouches for the first, which is the developer himself. This is called a chain. It says that Apple knows who this developer is, and that this copy of the app is the one he made. If one link is wrong, macOS warns you before it runs the program.

Rectangle has no entitlements at all. That does not mean it can do nothing special: an app that moves other apps' windows needs your permission, and it has to ask you for it while it runs. You will see the text of that question in step 8.

🇹🇭 ไทย

อ่านสามชื่อใต้ SIGNED BY จากล่างขึ้นบน Apple Root CA คือยอดสุด: แมคของคุณเชื่อถือมันตั้งแต่วันที่ถูกผลิต มันรับรองชื่อที่สอง และชื่อที่สองรับรองชื่อแรก ซึ่งก็คือตัวนักพัฒนาเอง สิ่งนี้เรียกว่าสายโซ่ มันบอกว่า Apple รู้ว่านักพัฒนาคนนี้คือใคร และสำเนาของแอปชุดนี้คือชุดที่เขาสร้าง ถ้าข้อใดข้อหนึ่งผิด macOS จะเตือนคุณก่อนรันโปรแกรม

Rectangle ไม่มี entitlement เลย นั่นไม่ได้แปลว่ามันทำอะไรพิเศษไม่ได้: แอปที่ย้ายหน้าต่างของแอปอื่นต้องได้รับอนุญาตจากคุณ และมันต้องขอจากคุณในขณะที่มันรัน คุณจะได้เห็นข้อความของคำขอนั้นในขั้นที่ 8

🇨🇳 中文

把 SIGNED BY 下面的三个名字从下往上读。Apple Root CA 在最顶上:你的 Mac 从出厂那天起就信任它。它为第二个名字担保,第二个再为第一个担保,第一个就是开发者本人。这叫做一条链。它说明 Apple 知道这位开发者是谁,也说明应用的这一份拷贝正是他做的那一份。只要有一环不对,macOS 就会在运行程序之前提醒你。

Rectangle 一项 entitlement 都没有。这并不表示它做不了特别的事:一个要移动别的应用窗口的应用,需要得到你的许可,而它必须在运行的时候向你要。你会在第 8 步看到那句询问的文字。

7 app_libraries.py: what it is built on · app_libraries.py: มันสร้างบนอะไร · app_libraries.py:它建立在什么之上

🇬🇧 English

REA's command for a compiled Mac program is inspect-macho. Mach-O is the name of the file format of Mac programs. You give it the compiled program inside the bundle.

The answer lists more than a thousand functions, so app_libraries.py counts them. It keeps a dictionary from the name of a library to the number of functions taken from it. counts.get(source, 0) gives the number so far, or 0 the first time.

🇹🇭 ไทย

คำสั่งของ REA สำหรับโปรแกรมแมคที่คอมไพล์แล้วคือ inspect-macho Mach-O คือชื่อของรูปแบบไฟล์ของโปรแกรมแมค คุณให้โปรแกรมที่คอมไพล์แล้วซึ่งอยู่ในบันเดิลกับมัน

คำตอบแสดงรายการฟังก์ชันมากกว่าหนึ่งพันตัว app_libraries.py จึงนับมัน มันเก็บดิกชันนารีจากชื่อไลบรารีไปยังจำนวนฟังก์ชันที่นำมาจากไลบรารีนั้น counts.get(source, 0) ให้จำนวนที่นับได้จนถึงตอนนี้ หรือ 0 ในครั้งแรก

🇨🇳 中文

REA 用来读编译好的 Mac 程序的命令是 inspect-macho。Mach-O 是 Mac 程序文件格式的名字。你给它的是 bundle 里面那个编译好的程序。

回答里列出了一千多个函数,所以 app_libraries.py 来数一数。它用一个字典,从库的名字对应到从这个库拿来的函数个数。counts.get(source, 0) 给出到目前为止的个数,第一次则给出 0。

app_libraries.py

"""app_libraries.py - ask REA which libraries a Mac app loads, and how much it takes from each.

    python3 app_libraries.py
"""
import json
import subprocess

PROGRAM = "Rectangle.app/Contents/MacOS/Rectangle"


def main():
    command = "npx rea inspect-macho " + PROGRAM + " --json"
    finished = subprocess.run(command, shell=True, capture_output=True, text=True)
    if finished.returncode != 0:
        print(finished.stdout)
        return
    result = json.loads(finished.stdout)["normalized_result"]

    print("BUILT FOR")
    for processor in result["architectures"]["items"]:
        megabytes = processor["size"] / 1000000
        print("  " + processor["name"].ljust(8), format(megabytes, ".1f"), "MB")

    from_macos = 0
    private = 0
    own = []
    for library in result["dependencies"]["items"]:
        # One entry is not a library: the small program that loads all the others.
        if library["kind"] == "LC_LOAD_DYLINKER":
            continue
        if "/PrivateFrameworks/" in library["path"]:
            private = private + 1
        elif library["path"].startswith("/System/") or library["path"].startswith("/usr/lib/"):
            from_macos = from_macos + 1
        else:
            own.append(library["path"])
    print("LIBRARIES IT LOADS:", from_macos + private + len(own))
    print("  from macOS              ", from_macos)
    print("  Apple's private ones    ", private)
    print("  brought along in the app", len(own))
    for path in own:
        print("    " + path)

    # Count the imported functions of each library in a dictionary: library -> how many.
    counts = {}
    for function in result["imports"]["items"]:
        source = function["source"]
        counts[source] = counts.get(source, 0) + 1
    print("FUNCTIONS IT TAKES FROM LIBRARIES:", len(result["imports"]["items"]))
    print("  THE 10 LIBRARIES IT TAKES MOST FROM")
    most = sorted(counts, key=counts.get, reverse=True)[:10]
    for source in most:
        print("  " + str(counts[source]).rjust(5), source)


if __name__ == "__main__":
    main()
python3 app_libraries.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

BUILT FOR
  x86_64   2.0 MB
  arm64    1.7 MB
LIBRARIES IT LOADS: 33
  from macOS               32
  Apple's private ones     0
  brought along in the app 1
    @rpath/Sparkle.framework/Versions/B/Sparkle
FUNCTIONS IT TAKES FROM LIBRARIES: 1206
  THE 10 LIBRARIES IT TAKES MOST FROM
    344 SwiftUI
    342 libswiftCore
    138 Foundation
    119 AppKit
     39 CoreGraphics
     37 libSystem
     33 libswiftDispatch
     30 CoreFoundation
     26 libobjc
     19 ApplicationServices

🇬🇧 English

Rectangle holds two copies of itself: one for Intel processors, x86_64, and one for Apple's own processors, arm64. The Mac runs the copy that fits it. This is why the same app works on old and new Macs.

Rectangle loads 33 libraries, and it takes 1206 functions from them. The list of the ten most used tells you how the app is made, though you have not seen one line of its code. SwiftUI and libswiftCore are at the top, so Rectangle is written in Swift, Apple's newer language, and it draws its windows with SwiftUI.

32 of the libraries are part of macOS. One is not: Sparkle, which Rectangle brings along inside its own bundle. Sparkle is an open-source library that many Mac apps use to update themselves. That fits what the card said in step 5: this app looks for updates. Two findings from two different files now agree, and that is how a reverse engineer grows sure of something.

🇹🇭 ไทย

Rectangle เก็บสำเนาของตัวเองไว้สองชุด: ชุดหนึ่งสำหรับโปรเซสเซอร์ Intel คือ x86_64 และอีกชุดสำหรับโปรเซสเซอร์ของ Apple เอง คือ arm64 แมครันชุดที่เข้ากับมัน นี่คือเหตุผลที่แอปตัวเดียวกันใช้ได้ทั้งบนแมคเก่าและแมคใหม่

Rectangle โหลดไลบรารี 33 ตัว และนำ 1206 ฟังก์ชันมาจากไลบรารีเหล่านั้น รายการสิบตัวที่ใช้มากที่สุดบอกคุณว่าแอปถูกสร้างอย่างไร ทั้งที่คุณยังไม่ได้เห็นโค้ดของมันแม้แต่บรรทัดเดียว SwiftUI และ libswiftCore อยู่บนสุด ดังนั้น Rectangle เขียนด้วย Swift ซึ่งเป็นภาษาที่ใหม่กว่าของ Apple และวาดหน้าต่างของมันด้วย SwiftUI

ไลบรารี 32 ตัวเป็นส่วนหนึ่งของ macOS มีหนึ่งตัวที่ไม่ใช่: Sparkle ซึ่ง Rectangle นำติดมาไว้ในบันเดิลของตัวเอง Sparkle คือไลบรารีโอเพนซอร์สที่แอปแมคจำนวนมากใช้อัปเดตตัวเอง ซึ่งตรงกับสิ่งที่บัตรบอกไว้ในขั้นที่ 5: แอปนี้มองหาการอัปเดต ตอนนี้สิ่งที่พบสองอย่างจากสองไฟล์ที่ต่างกันตรงกัน และนั่นคือวิธีที่นักวิศวกรรมย้อนกลับมั่นใจในบางอย่างมากขึ้น

🇨🇳 中文

Rectangle 里存着它自己的两份拷贝:一份给 Intel 处理器,叫 x86_64;一份给 Apple 自己的处理器,叫 arm64。Mac 运行适合自己的那一份。所以同一个应用在旧 Mac 和新 Mac 上都能用。

Rectangle 加载 33 个库,并从中拿了 1206 个函数。虽然你一行它的代码都没看过,用得最多的十个库这份名单已经告诉你这个应用是怎么做出来的。排在最前面的是 SwiftUI 和 libswiftCore,所以 Rectangle 是用 Swift 写的,那是 Apple 较新的语言,而且它用 SwiftUI 来画窗口。

有 32 个库是 macOS 的一部分。有一个不是:Sparkle,Rectangle 把它带在自己的 bundle 里。Sparkle 是一个开源的库,很多 Mac 应用用它来更新自己。这和第 5 步里卡片说的对得上:这个应用会查找更新。来自两个不同文件的两个发现现在一致了,做逆向工程的人就是这样一点点确信一件事的。

8 app_windows.py: from a menu item to its code · app_windows.py: จากรายการเมนูไปถึงโค้ดของมัน · app_windows.py:从菜单项到它的代码

🇬🇧 English

The question a reverse engineer asks most often is: what happens when I choose this? For a Mac app, the first half of the answer is not in the code. It is in the app's designs.

A developer draws each window and menu in a design tool, and the drawing is saved in the app as a file whose name ends in .nib. The file lists the texts of the menu. For each menu item it also holds an action: the name of the method to call when the item is chosen, and the class that the method belongs to. REA's command decode-interface-builder reads these files.

app_windows.py lists the design files, and then looks inside the one whose name holds the word you give it.

🇹🇭 ไทย

คำถามที่นักวิศวกรรมย้อนกลับถามบ่อยที่สุดคือ: เกิดอะไรขึ้นเมื่อฉันเลือกสิ่งนี้ สำหรับแอปแมค ครึ่งแรกของคำตอบไม่ได้อยู่ในโค้ด มันอยู่ในแบบของแอป

นักพัฒนาวาดหน้าต่างและเมนูแต่ละอันในเครื่องมือออกแบบ และภาพวาดถูกบันทึกไว้ในแอปเป็นไฟล์ที่ชื่อลงท้ายด้วย .nib ไฟล์นี้แสดงรายการข้อความของเมนู สำหรับแต่ละรายการเมนู มันยังเก็บ action ไว้ด้วย: ชื่อของเมธอดที่จะเรียกเมื่อรายการนั้นถูกเลือก และคลาสที่เมธอดนั้นเป็นของ คำสั่ง decode-interface-builder ของ REA อ่านไฟล์เหล่านี้

app_windows.py แสดงรายการไฟล์แบบ แล้วดูข้างในไฟล์ที่ชื่อมีคำที่คุณให้

🇨🇳 中文

做逆向工程的人最常问的问题是:我选了这一项,会发生什么?对 Mac 应用来说,答案的前一半不在代码里,而在应用的设计里。

开发者在设计工具里画出每个窗口和菜单,画好的东西存在应用里,是一个名字以 .nib 结尾的文件。这个文件列出菜单里的文字。对每个菜单项,它还存着一个 action:这一项被选中时要调用的方法的名字,以及这个方法所属的类。REA 的命令 decode-interface-builder 能读这些文件。

app_windows.py 列出设计文件,然后去看名字里带有你给的那个词的那一个。

app_windows.py

"""app_windows.py - ask REA for the designs of a Mac app's windows and menus, and look inside one.

    python3 app_windows.py MainMenu
"""
import json
import subprocess
import sys

APP = "Rectangle.app"
# The places in a design where a text for the screen is kept.
TEXT_KEYS = ["NSDev", "NSContents", "NSWindowTitle", "NSTitle"]


def main():
    wanted = sys.argv[1]
    command = "npx rea decode-interface-builder " + APP + " --json"
    finished = subprocess.run(command, shell=True, capture_output=True, text=True)
    if finished.returncode != 0:
        print(finished.stdout)
        return
    result = json.loads(finished.stdout)["normalized_result"]

    designs = result["documents"]
    print("DESIGN FILES:", len(designs))
    for design in designs:
        print("  " + design["relative_path"])
    print("answer cut short    ", result["graph"]["truncated"])
    # REA lists what it left out, and why, among the limits of its answer.
    left_out = 0
    for limit in result["limitations"]:
        if "omitted because max documents" in limit:
            left_out = left_out + 1
    print("files left out      ", left_out)
    chosen = None
    for design in designs:
        if wanted in design["relative_path"]:
            chosen = design
    if chosen is None:
        print("No design file has", wanted, "in its name.")
        return
    path = chosen["relative_path"]
    print("LOOKING INSIDE      ", path)
    print("objects in it       ", chosen["object_count"])

    # Every node of this design has an id that begins with the path of the design.
    parts = []
    for node in result["graph"]["nodes"]:
        if node["id"].startswith("ib:" + path + ":"):
            parts.append(node)

    texts = []
    names = {}
    for node in parts:
        attributes = node["attributes"]
        if "objectID" in attributes:
            names[attributes["objectID"]] = node["name"]
        for key in TEXT_KEYS:
            text = attributes.get("nibValues", {}).get(key)
            if isinstance(text, str) and text != "" and text not in texts:
                texts.append(text)

    print("TEXTS ON THE SCREEN:", len(texts))
    for text in texts[:12]:
        print("  " + json.dumps(text))

    # An action says: when this control is used, call the method with this name.
    print("ACTIONS")
    for node in parts:
        if node["kind"] != "action":
            continue
        attributes = node["attributes"]
        control = names.get(attributes["source_id"], "?")
        # Some actions name no receiver in the design: the app finds one while it runs.
        receiver = names.get(attributes["destination_id"], "(no fixed receiver)")
        print("  " + control, "calls", attributes["selector"], "of", receiver)


if __name__ == "__main__":
    main()
python3 app_windows.py MainMenu

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

DESIGN FILES: 7
  Contents/Frameworks/Sparkle.framework/Versions/B/Resources/SUUpdateAlert.nib
  Contents/Frameworks/Sparkle.framework/Versions/B/Resources/SUUpdatePermissionPrompt.nib
  Contents/Resources/MessageView.nib
  Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib
  Contents/Resources/LogViewer.storyboardc/LogViewController.nib
  Contents/Resources/LogViewer.storyboardc/LogWindowController.nib
  Contents/Resources/LogViewer.storyboardc/PiU-lX-bsA-view-ptq-Cu-Bka.nib
answer cut short     False
files left out       0
LOOKING INSIDE       Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib
objects in it        128
TEXTS ON THE SCREEN: 43
  "Ignore frontmost.app"
  "Settings\u2026"
  "About"
  "View Logging\u2026"
  "Check for Updates\u2026"
  "Quit Rectangle"
  "Not Authorized to Control Your Computer"
  "Authorize\u2026"
  "Main Menu"
  "Rectangle"
  "About Rectangle"
  "Preferences\u2026"
ACTIONS
  NSMenuItem calls ignoreFrontMostApp: of _TtC9Rectangle11AppDelegate
  NSMenuItem calls openPreferences: of _TtC9Rectangle11AppDelegate
  NSMenuItem calls showAbout: of _TtC9Rectangle11AppDelegate
  NSMenuItem calls viewLogging: of _TtC9Rectangle11AppDelegate
  NSMenuItem calls checkForUpdates: of _TtC9Rectangle11AppDelegate
  NSMenuItem calls terminate: of NSApplication
  NSMenuItem calls authorizeAccessibility: of _TtC9Rectangle11AppDelegate
  NSMenuItem calls orderFrontStandardAboutPanel: of NSApplication
  NSMenuItem calls terminate: of NSApplication
  NSMenuItem calls orderFrontStandardAboutPanel: of (no fixed receiver)
  NSMenuItem calls hide: of (no fixed receiver)
  NSMenuItem calls hideOtherApplications: of (no fixed receiver)
  NSMenuItem calls unhideAllApplications: of (no fixed receiver)
  NSMenuItem calls terminate: of (no fixed receiver)
  NSMenuItem calls newDocument: of (no fixed receiver)
  NSMenuItem calls openDocument: of (no fixed receiver)
  NSMenuItem calls performClose: of (no fixed receiver)
  NSMenuItem calls saveDocument: of (no fixed receiver)
  NSMenuItem calls undo: of (no fixed receiver)
  NSMenuItem calls redo: of (no fixed receiver)
  NSMenuItem calls cut: of (no fixed receiver)
  NSMenuItem calls copy: of (no fixed receiver)
  NSMenuItem calls paste: of (no fixed receiver)
  NSMenuItem calls pasteAsPlainText: of (no fixed receiver)
  NSMenuItem calls delete: of (no fixed receiver)
  NSMenuItem calls selectAll: of (no fixed receiver)
  NSMenuItem calls performFindPanelAction: of (no fixed receiver)
  NSMenuItem calls performFindPanelAction: of (no fixed receiver)
  NSMenuItem calls performFindPanelAction: of (no fixed receiver)
  NSMenuItem calls performFindPanelAction: of (no fixed receiver)
  NSMenuItem calls performFindPanelAction: of (no fixed receiver)
  NSMenuItem calls centerSelectionInVisibleArea: of (no fixed receiver)
  NSMenuItem calls toggleFullScreen: of (no fixed receiver)
  NSMenuItem calls performMiniaturize: of (no fixed receiver)
  NSMenuItem calls showHelp: of (no fixed receiver)

🇬🇧 English

The app has seven design files. Two of them are in the folder of Sparkle, the library from step 7: they are the windows that offer you an update.

Look at the texts of MainMenu.nib. The first six are the menu that drops down from Rectangle's icon in the menu bar: Ignore frontmost.app, Settings…, About, View Logging…, Check for Updates… and Quit Rectangle. The next two, Not Authorized to Control Your Computer and Authorize…, are the question that step 6 promised: this is how the app asks for your permission.

Now the actions. Six of them belong to a class with a strange name, _TtC9Rectangle11AppDelegate. This is how Swift writes a class name into a compiled program. Read the numbers: 9 and then the 9 letters of Rectangle, 11 and then the 11 letters of AppDelegate. It is the class AppDelegate of the program Rectangle. One of its six methods is openPreferences:. A good guess is that the menu item Settings… calls it.

Many other actions have (no fixed receiver). Those are the usual items of every Mac app, such as Copy and Paste. The design names no class for them, because the app decides who gets the action while it runs. This is the wall that part 1 described: what is decided at run time is not written in the files.

With a closed program the trail would end here, at a name. Rectangle is open source, so you can take the last step and read the method itself. The class AppDelegate is in the file AppDelegate.swift. lines.py prints the lines of a file that you ask for.

🇹🇭 ไทย

แอปมีไฟล์แบบเจ็ดไฟล์ สองไฟล์ในนั้นอยู่ในโฟลเดอร์ของ Sparkle ซึ่งเป็นไลบรารีจากขั้นที่ 7: มันคือหน้าต่างที่เสนอการอัปเดตให้คุณ

ดูข้อความของ MainMenu.nib หกข้อความแรกคือเมนูที่เปิดลงมาจากไอคอนของ Rectangle ในแถบเมนู: Ignore frontmost.app, Settings…, About, View Logging…, Check for Updates… และ Quit Rectangle สองข้อความถัดมา คือ Not Authorized to Control Your Computer และ Authorize… คือคำขอที่ขั้นที่ 6 บอกไว้: นี่คือวิธีที่แอปขออนุญาตจากคุณ

ทีนี้ดู action หก action เป็นของคลาสที่มีชื่อแปลก ๆ คือ _TtC9Rectangle11AppDelegate นี่คือวิธีที่ Swift เขียนชื่อคลาสลงในโปรแกรมที่คอมไพล์แล้ว อ่านตัวเลข: 9 ตามด้วยตัวอักษร 9 ตัวของ Rectangle แล้ว 11 ตามด้วยตัวอักษร 11 ตัวของ AppDelegate มันคือคลาส AppDelegate ของโปรแกรม Rectangle หนึ่งในหกเมธอดของมันคือ openPreferences: เดาได้ไม่ยากว่ารายการเมนู Settings… เป็นตัวเรียกมัน

action อื่นอีกหลายตัวมี (no fixed receiver) นั่นคือรายการปกติของแอปแมคทุกตัว เช่น Copy และ Paste แบบไม่ได้ระบุคลาสให้มัน เพราะแอปตัดสินว่าใครจะได้รับ action ในขณะที่มันรัน นี่คือกำแพงที่ส่วนที่ 1 บอกไว้: สิ่งที่ถูกตัดสินตอนรันไม่ได้ถูกเขียนไว้ในไฟล์

ถ้าเป็นโปรแกรมปิด รอยจะจบตรงนี้ ที่ชื่อชื่อหนึ่ง แต่ Rectangle เป็นโอเพนซอร์ส คุณจึงเดินก้าวสุดท้ายแล้วอ่านตัวเมธอดได้ คลาส AppDelegate อยู่ในไฟล์ AppDelegate.swift lines.py พิมพ์บรรทัดของไฟล์ตามที่คุณขอ

🇨🇳 中文

这个应用有七个设计文件。其中两个在 Sparkle 的文件夹里,就是第 7 步的那个库:它们是向你提示有更新的窗口。

看 MainMenu.nib 的文字。前六个是从菜单栏里 Rectangle 的图标上拉下来的菜单:Ignore frontmost.app、Settings…、About、View Logging…、Check for Updates… 和 Quit Rectangle。接下来的两个,Not Authorized to Control Your Computer 和 Authorize…,就是第 6 步预告过的那句询问:应用就是这样向你要许可的。

再看 action。其中六个属于一个名字很怪的类:_TtC9Rectangle11AppDelegate。这是 Swift 把类名写进编译好的程序里的方式。读那些数字:9 后面是 Rectangle 的 9 个字母,11 后面是 AppDelegate 的 11 个字母。它就是程序 Rectangle 里的类 AppDelegate。它的六个方法里有一个是 openPreferences:。不难猜到,调用它的是菜单项 Settings…。

另外很多 action 写着 (no fixed receiver)。它们是每个 Mac 应用都有的普通菜单项,比如 Copy 和 Paste。设计里没有给它们指定类,因为由谁来接收这个 action,是应用在运行时决定的。这就是第 1 部分说过的那堵墙:运行时才决定的东西,不会写在文件里。

如果是一个不公开源代码的程序,线索到这里就断了,只剩一个名字。Rectangle 是开源的,所以你可以走完最后一步,读一读这个方法本身。类 AppDelegate 在文件 AppDelegate.swift 里。lines.py 打印你指定的文件里的那几行。

lines.py

"""lines.py - print some lines of a file, each with its number.

    python3 lines.py rectangle_src/Rectangle/AppDelegate.swift 258 264
"""
import sys

path = sys.argv[1]
first = int(sys.argv[2])
last = int(sys.argv[3])

with open(path, encoding="utf-8") as source:
    number = 0
    for line in source:
        number = number + 1
        if first <= number <= last:
            print(str(number).rjust(4), line.rstrip())
python3 lines.py rectangle_src/Rectangle/AppDelegate.swift 258 264

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

 258     @IBAction func openPreferences(_ sender: Any) {
 259         if settingsWindowController == nil {
 260             settingsWindowController = SettingsWindowController()
 261         }
 262         NSApp.activate(ignoringOtherApps: true)
 263         settingsWindowController?.showWindow(self)
 264     }

🇬🇧 English

This is Swift, and you can read it without knowing Swift. If there is no settings window yet, make one. Bring the app to the front. Show the window. You started at a menu, and you have arrived at the seven lines that the menu item runs.

🇹🇭 ไทย

นี่คือ Swift และคุณอ่านมันได้โดยไม่ต้องรู้ Swift ถ้ายังไม่มีหน้าต่างการตั้งค่า ให้สร้างขึ้นมา นำแอปมาไว้ข้างหน้า แสดงหน้าต่าง คุณเริ่มที่เมนู และคุณมาถึงเจ็ดบรรทัดที่รายการเมนูนั้นรัน

🇨🇳 中文

这是 Swift,你不懂 Swift 也读得懂。如果还没有设置窗口,就新建一个。把应用带到最前面。把窗口显示出来。你从一个菜单出发,现在到了这个菜单项运行的那七行代码。

YOUR TURN
  1. Run python3 app_windows.py SUUpdateAlert. That design belongs to Sparkle. Which texts would a user see, and which class do its buttons call?
  2. The method checkForUpdates: is a few lines below openPreferences:. Print lines 283 to 285 of AppDelegate.swift. Which object does the work?
  3. Run python3 app_windows.py LogWindow. Which window is that, and which menu item of step 8 opens it?
  1. รัน python3 app_windows.py SUUpdateAlert แบบนั้นเป็นของ Sparkle ผู้ใช้จะเห็นข้อความอะไรบ้าง และปุ่มของมันเรียกคลาสไหน
  2. เมธอด checkForUpdates: อยู่ใต้ openPreferences: ลงไปไม่กี่บรรทัด พิมพ์บรรทัดที่ 283 ถึง 285 ของ AppDelegate.swift ออบเจกต์ตัวไหนเป็นผู้ทำงาน
  3. รัน python3 app_windows.py LogWindow นั่นคือหน้าต่างอะไร และรายการเมนูไหนในขั้นที่ 8 เป็นตัวเปิดมัน
  1. 运行 python3 app_windows.py SUUpdateAlert。那个设计属于 Sparkle。用户会看到哪些文字?它的按钮调用的是哪个类?
  2. 方法 checkForUpdates: 在 openPreferences: 下面几行的地方。打印 AppDelegate.swift 的第 283 行到第 285 行。干活的是哪个对象?
  3. 运行 python3 app_windows.py LogWindow。那是哪个窗口?第 8 步里的哪个菜单项会打开它?

9 Mark REA, in two ways · ตรวจ REA ด้วยสองวิธี · 用两种方法给 REA 打分

🇬🇧 English

You have believed REA for four steps. Now mark it. The best answer key is the source code. In the source code a design is not a .nib file. It is a text file whose name ends in .storyboard or .xib, and each action in it is one line that begins <action selector=. REA never saw those files: it read the compiled designs inside the app.

source_says.py collects the names of all the actions from both sides and compares them. ElementTree comes with Python and reads this kind of text file. The two collections are sets, and found & truth is the set of names that are in both.

🇹🇭 ไทย

คุณเชื่อ REA มาสี่ขั้นแล้ว ตอนนี้ตรวจมัน เฉลยที่ดีที่สุดคือซอร์สโค้ด ในซอร์สโค้ด แบบไม่ใช่ไฟล์ .nib มันเป็นไฟล์ข้อความที่ชื่อลงท้ายด้วย .storyboard หรือ .xib และ action แต่ละตัวในนั้นคือหนึ่งบรรทัดที่ขึ้นต้นด้วย <action selector= REA ไม่เคยเห็นไฟล์เหล่านั้น: มันอ่านแบบที่คอมไพล์แล้วซึ่งอยู่ในแอป

source_says.py รวบรวมชื่อของ action ทั้งหมดจากทั้งสองฝั่งแล้วเปรียบเทียบกัน ElementTree มากับ Python และอ่านไฟล์ข้อความชนิดนี้ได้ ชุดที่รวบรวมมาทั้งสองเป็นเซต และ found & truth คือเซตของชื่อที่อยู่ในทั้งสองชุด

🇨🇳 中文

你已经相信了 REA 四步。现在给它打分。最好的标准答案是源代码。在源代码里,设计不是 .nib 文件。它是名字以 .storyboard 或 .xib 结尾的文本文件,里面每个 action 是以 <action selector= 开头的一行。这些文件 REA 从没见过:它读的是应用里面编译好的设计。

source_says.py 从两边收集所有 action 的名字,再做比较。ElementTree 是 Python 自带的,能读这种文本文件。两边收集到的都是集合,found & truth 就是两边都有的名字组成的集合。

source_says.py

"""source_says.py - mark REA's list of actions against the source code of Rectangle.

    python3 source_says.py

In the source code a design is a text file, and each action in it is one line
such as <action selector="terminate:" ...>. REA never saw those files: it read
the compiled designs inside Rectangle.app.
"""
import json
import os
import subprocess
import xml.etree.ElementTree as ElementTree

APP = "Rectangle.app"
SOURCE = "rectangle_src/Rectangle"


def actions_in_source():
    found = set()
    for path, folders, files in os.walk(SOURCE):
        for name in files:
            if name.endswith(".storyboard") or name.endswith(".xib"):
                design = ElementTree.parse(os.path.join(path, name))
                # iter("action") visits every <action> in the file, however deep it is.
                for action in design.iter("action"):
                    found.add(action.get("selector"))
    return found


def actions_from_rea():
    command = "npx rea decode-interface-builder " + APP + " --json"
    finished = subprocess.run(command, shell=True, capture_output=True, text=True)
    result = json.loads(finished.stdout)["normalized_result"]
    found = set()
    for node in result["graph"]["nodes"]:
        # Rectangle brings a library of its own with its own designs. Leave those out.
        if node["kind"] == "action" and "/Frameworks/" not in node["id"]:
            found.add(node["attributes"]["selector"])
    return found, result["graph"]["truncated"]


def main():
    truth = actions_in_source()
    found, cut_short = actions_from_rea()
    print("actions in the source code ", len(truth))
    print("REA found                  ", len(found & truth), "of", len(truth))
    print("in the source, not found   ", sorted(truth - found))
    print("found, not in the source   ", sorted(found - truth))
    print("REA's answer cut short     ", cut_short)


if __name__ == "__main__":
    main()
python3 source_says.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

actions in the source code  29
REA found                   29 of 29
in the source, not found    []
found, not in the source    []
REA's answer cut short      False

🇬🇧 English

The source code has 29 different actions, and REA found 29 of 29. It found none that are not there. The last line matters too. REA has a limit on how many design files it reads for one answer, and it says in its answer when it stopped early. Here it did not.

In step 8 you guessed that Settings… calls openPreferences:. The source code can settle that as well. Print five lines of the design as its author wrote it.

🇹🇭 ไทย

ซอร์สโค้ดมี action ที่ต่างกัน 29 ตัว และ REA พบ 29 จาก 29 มันไม่ได้พบตัวที่ไม่มีอยู่จริงเลย บรรทัดสุดท้ายก็สำคัญ REA มีขีดจำกัดว่าอ่านไฟล์แบบได้กี่ไฟล์ต่อหนึ่งคำตอบ และมันบอกไว้ในคำตอบเมื่อมันหยุดก่อน ครั้งนี้มันไม่ได้หยุดก่อน

ในขั้นที่ 8 คุณเดาว่า Settings… เรียก openPreferences: ซอร์สโค้ดตัดสินเรื่องนั้นได้เช่นกัน พิมพ์ห้าบรรทัดของแบบตามที่ผู้เขียนเขียนไว้

🇨🇳 中文

源代码里有 29 个不同的 action,REA 找到了 29 个中的 29 个。它没有找出任何不存在的。最后一行也很重要。REA 对一次回答能读多少个设计文件是有上限的,如果它提前停下,会在回答里说出来。这一次它没有提前停。

在第 8 步,你猜 Settings… 调用的是 openPreferences:。这一点源代码也能定下来。把作者写的设计打印五行出来。

python3 lines.py rectangle_src/Rectangle/Base.lproj/Main.storyboard 238 242

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

 238                         <menuItem title="Settings…" id="YRC-4a-xGg">
 239                             <modifierMask key="keyEquivalentModifierMask"/>
 240                             <connections>
 241                                 <action selector="openPreferences:" target="Voe-Tx-rLC" id="EgB-O8-H7c"/>
 242                             </connections>

🇬🇧 English

The menu item with the title Settings… has one action, and its selector is openPreferences:. The guess was right, and now it is not a guess.

For a program without source code you cannot do this. Then you need a second opinion of another kind: a different tool that answers the same questions. apple_says.py asks five things without REA. plistlib comes with Python and reads the card. lipo, otool and codesign are Apple's small tools for developers. And os.walk visits every folder of the bundle, so that the file can count the designs on the disk.

🇹🇭 ไทย

รายการเมนูที่มีชื่อ Settings… มีหนึ่ง action และ selector ของมันคือ openPreferences: การเดานั้นถูก และตอนนี้มันไม่ใช่การเดาแล้ว

สำหรับโปรแกรมที่ไม่มีซอร์สโค้ด คุณทำแบบนี้ไม่ได้ คุณจึงต้องมีความเห็นที่สองอีกแบบหนึ่ง: เครื่องมืออีกตัวที่ตอบคำถามเดียวกัน apple_says.py ถามห้าเรื่องโดยไม่ใช้ REA plistlib มากับ Python และอ่านบัตรได้ lipo, otool และ codesign เป็นเครื่องมือเล็ก ๆ ของ Apple สำหรับนักพัฒนา และ os.walk เข้าไปดูทุกโฟลเดอร์ของบันเดิล เพื่อให้ไฟล์นับแบบที่อยู่บนดิสก์ได้

🇨🇳 中文

标题是 Settings… 的那个菜单项有一个 action,它的 selector 是 openPreferences:。猜对了,而且现在它不再是猜测。

对没有源代码的程序,你没法这样做。那时你需要另一种第二意见:一个不同的工具,回答同样的问题。apple_says.py 不用 REA,问了五件事。plistlib 是 Python 自带的,能读那张卡片。lipo、otool 和 codesign 是 Apple 给开发者用的小工具。os.walk 会走遍 bundle 里的每个文件夹,这样这个文件就能数出磁盘上有多少个设计。

apple_says.py

"""apple_says.py - ask Apple's own tools the questions that REA answered, to mark REA.

    python3 apple_says.py
"""
import os
import plistlib
import subprocess

APP = "Rectangle.app"
PROGRAM = APP + "/Contents/MacOS/Rectangle"


def printed_by(command):
    finished = subprocess.run(command, capture_output=True)
    return finished.stdout


def main():
    # 1. The card. plistlib comes with Python and reads this kind of file.
    with open(APP + "/Contents/Info.plist", "rb") as source:
        card = plistlib.load(source)
    print("identifier          ", card["CFBundleIdentifier"])
    print("version             ", card["CFBundleShortVersionString"])
    print("entries on the card ", len(card))

    # 2. The processors. lipo is Apple's tool for programs built for more than one.
    processors = printed_by(["lipo", "-archs", PROGRAM]).decode().strip()
    print("built for           ", processors)

    # 3. The libraries of the first processor's copy. otool -L prints the program's
    # name, then one library on each line.
    first = processors.split()[0]
    answer = printed_by(["otool", "-arch", first, "-L", PROGRAM])
    lines = answer.decode().strip().split("\n")
    print("libraries it loads  ", len(lines) - 1)

    # 4. The entitlements. codesign prints them as a file of the same kind as the card.
    answer = printed_by(["codesign", "-d", "--entitlements", "-", "--xml", APP])
    if answer == b"":
        print("entitlements         0")
    else:
        print("entitlements        ", len(plistlib.loads(answer)))

    # 5. The designs: count the files whose names end in .nib.
    designs = 0
    for path, folders, files in os.walk(APP):
        for name in files:
            if name.endswith(".nib"):
                designs = designs + 1
    print("design files        ", designs)


if __name__ == "__main__":
    main()
python3 apple_says.py

what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容

identifier           com.knollsoft.Rectangle
version              2.0.3
entries on the card  30
built for            x86_64 arm64
libraries it loads   33
entitlements         0
design files         7
Questionคำถาม问题REA saidREA บอกว่าREA 的回答The second opinionความเห็นที่สอง第二种意见
Identifier and versionตัวระบุและเวอร์ชัน标识符和版本com.knollsoft.Rectangle, 2.0.3com.knollsoft.Rectangle, 2.0.3
Entries on the cardจำนวนรายการบนบัตร卡片上的条目数3030
Built forสร้างมาสำหรับ为哪些处理器构建x86_64, arm64x86_64, arm64
Libraries it loadsไลบรารีที่มันโหลด它加载的库3333
Entitlementsentitlemententitlement00
Design filesไฟล์แบบ设计文件77

🇬🇧 English

All six agree. Before you trust that, ask how each side got its answer. REA's answer says how it got each fact, and on a Mac it gets most of them by running the same tools of Apple's: plutil, codesign, lipo, otool. So for those rows the second opinion is not fully a second one. It shows that REA read the tools correctly. The count of design files and the list of actions were made in truly different ways, and those are the two that carry weight.

🇹🇭 ไทย

ทั้งหกข้อตรงกัน ก่อนจะเชื่อ ให้ถามว่าแต่ละฝั่งได้คำตอบมาอย่างไร คำตอบของ REA บอกว่ามันได้ข้อเท็จจริงแต่ละข้อมาอย่างไร และบนแมคมันได้ข้อเท็จจริงส่วนใหญ่มาด้วยการรันเครื่องมือเดียวกันของ Apple: plutil, codesign, lipo, otool ดังนั้นสำหรับแถวเหล่านั้น ความเห็นที่สองจึงไม่ใช่ความเห็นที่สองอย่างเต็มที่ มันแสดงว่า REA อ่านเครื่องมือได้ถูกต้อง ส่วนการนับไฟล์แบบและรายการ action ทำด้วยวิธีที่ต่างกันจริง ๆ และสองข้อนั้นเองที่มีน้ำหนัก

🇨🇳 中文

六项全部一致。在相信它之前,先问问两边各自的答案是怎么来的。REA 的回答说明了每个事实是怎么得来的,而在 Mac 上,大多数是它运行 Apple 的同一批工具得来的:plutil、codesign、lipo、otool。所以对那几行来说,第二种意见并不完全算是第二种。它说明 REA 把工具的结果读对了。设计文件的数目和 action 的列表是用真正不同的方法得到的,有分量的正是这两项。

A SECOND OPINION MUST COME FROM A DIFFERENT PLACE Two answers that come from the same source will agree even when both are wrong. When you check a tool, ask how the checker gets its answer. If it asks the same source, look for another way.The best second opinion is the source code. That is one more reason to begin with open-source programs: you can always find out if you were right.คำตอบสองคำตอบที่มาจากแหล่งเดียวกันจะตรงกัน แม้ในเวลาที่ทั้งสองผิด เมื่อคุณตรวจเครื่องมือ ให้ถามว่าตัวตรวจได้คำตอบมาอย่างไร ถ้ามันถามแหล่งเดียวกัน ให้มองหาอีกวิธีหนึ่งความเห็นที่สองที่ดีที่สุดคือซอร์สโค้ด นั่นคืออีกเหตุผลหนึ่งที่ควรเริ่มจากโปรแกรมโอเพนซอร์ส: คุณหาได้เสมอว่าคุณถูกหรือไม่来自同一个来源的两个答案,即使都错了也会一致。检查一个工具的时候,要问检查者的答案是怎么来的。如果它问的是同一个来源,就去找另一种方法。最好的第二意见是源代码。这也是应该从开源程序开始的又一个理由:你总能查出自己对不对。

🤖 Part 3 — Give the tool to an agent · ส่วนที่ 3 — มอบเครื่องมือให้เอเจนต์ · 第 3 部分 —— 把工具交给智能体

🇬🇧 English

On this page you did four things again and again: you asked REA a question, you read its answer, you checked it against something else, and you chose the next question. An AI coding agent can do that loop by itself. REA was made for this. Now that you have done the loop by hand, you know what the agent is doing, and you know how to check it.

🇹🇭 ไทย

ในหน้านี้คุณทำสี่อย่างซ้ำแล้วซ้ำอีก: คุณถามคำถามกับ REA คุณอ่านคำตอบของมัน คุณตรวจมันเทียบกับสิ่งอื่น และคุณเลือกคำถามถัดไป เอเจนต์ AI ที่เขียนโค้ดทำวงรอบนี้เองได้ REA ถูกสร้างมาเพื่อสิ่งนี้ ตอนนี้คุณทำวงรอบนี้ด้วยมือมาแล้ว คุณจึงรู้ว่าเอเจนต์กำลังทำอะไร และรู้วิธีตรวจมัน

🇨🇳 中文

在这一页,你把四件事做了一遍又一遍:向 REA 提一个问题,读它的回答,对照别的东西检查它,再选下一个问题。AI 编程智能体可以自己完成这个循环。REA 就是为此而做的。现在你已经亲手做过这个循环,所以你知道智能体在做什么,也知道怎么检查它。

10 agent_setup.py: connect REA to your agent · agent_setup.py: เชื่อม REA เข้ากับเอเจนต์ของคุณ · agent_setup.py:把 REA 接到你的智能体上

🇬🇧 English

One command of REA's, npx rea setup, connects it to the coding agents on your computer. It writes two things for each agent: a line in the agent's settings file that says how to start REA, and a skill, a page of instructions that tells the agent when to use REA and how. REA knows fifteen agents. agent_setup.py runs the command three ways: with no argument it asks for the list of agents, with an agent's name it asks what setup would change, and with --yes as well it makes the changes. For OpenCode it can do one thing more, which comes below.

The output below is the list, printed on one of GitHub's Macs right after Codex and OpenCode were installed on it. The second column is the name the command takes: claude_code, with an underline, codex and opencode. REA looks for each agent on the computer, and says whether it found it: on that Mac it found Codex and OpenCode, and not Claude Code, which that Mac does not have.

🇹🇭 ไทย

คำสั่งหนึ่งของ REA คือ npx rea setup เชื่อมมันเข้ากับเอเจนต์เขียนโค้ดบนคอมพิวเตอร์ของคุณ มันเขียนสองอย่างให้แต่ละเอเจนต์: บรรทัดหนึ่งในไฟล์ตั้งค่าของเอเจนต์ที่บอกวิธีเริ่ม REA และ สกิล คือหน้าคำแนะนำที่บอกเอเจนต์ว่าควรใช้ REA เมื่อไรและอย่างไร REA รู้จักเอเจนต์สิบห้าตัว agent_setup.py รันคำสั่งนี้ได้สามแบบ: ไม่ใส่อาร์กิวเมนต์คือขอรายชื่อเอเจนต์ ใส่ชื่อเอเจนต์คือถามว่า setup จะเปลี่ยนอะไร และเพิ่ม --yes ด้วยคือลงมือเปลี่ยนจริง สำหรับ OpenCode มันทำได้อีกอย่างหนึ่ง ซึ่งอยู่ข้างล่าง

ผลลัพธ์ข้างล่างคือรายชื่อนั้น พิมพ์บน Mac ของ GitHub เครื่องหนึ่งทันทีหลังติดตั้ง Codex และ OpenCode ลงไป คอลัมน์ที่สองคือชื่อที่คำสั่งรับ: claude_code ที่มีขีดล่าง codex และ opencode REA มองหาเอเจนต์แต่ละตัวบนคอมพิวเตอร์ แล้วบอกว่าพบหรือไม่: บน Mac เครื่องนั้นมันพบ Codex และ OpenCode แต่ไม่พบ Claude Code ซึ่ง Mac เครื่องนั้นไม่มี

🇨🇳 中文

REA 的一条命令 npx rea setup 能把它接到你电脑上的编程智能体上。它为每个智能体写两样东西:在智能体的设置文件里加一行,说明怎样启动 REA;再加一个技能(skill),也就是一页说明,告诉智能体什么时候用 REA、怎么用。REA 认识十五个智能体。agent_setup.py 用三种方式运行这条命令:不带参数时要一份智能体列表;带上一个智能体的名字时问 setup 会改什么;再加上 --yes 就真的去改。对 OpenCode 它还多做一件事,见下文。

下面的输出就是那份列表,在 GitHub 的一台 Mac 上、刚装好 Codex 和 OpenCode 之后打印的。第二列是命令接受的名字:带下划线的 claude_code,codex 和 opencode。REA 会在电脑上找每个智能体,并说出找到没有:在那台 Mac 上它找到了 Codex 和 OpenCode,没找到 Claude Code,那台 Mac 上没有它。

agent_setup.py

"""agent_setup.py - connect REA to a coding agent, or first see what that would change.

    python agent_setup.py                    the agents REA knows, and which ones it finds here
    python agent_setup.py codex              what setup would change for one agent; changes nothing
    python agent_setup.py claude_code --yes  make the changes
    python agent_setup.py opencode --yes --ollama http://your-spark.local:11434 --model qwen2.5:14b
                                             first point OpenCode at an Ollama server, then connect REA
"""
import json
import os
import pathlib
import subprocess
import sys

HOME = os.path.expanduser("~")

# OpenCode reads this file on every computer: its global settings, as JSON.
OPENCODE_CONFIG = pathlib.Path(HOME) / ".config" / "opencode" / "opencode.json"


def short(text):
    # The report names files in your home folder. Written as ~, the line is
    # true on any computer, and it says nothing about this one.
    return text.replace(HOME, "~").replace(HOME.replace("\\", "\\\\"), "~")


def ask_setup(*words):
    command = "npx rea setup --format json " + " ".join(words)
    finished = subprocess.run(command, shell=True, capture_output=True, text=True, encoding="utf-8")
    return json.loads(finished.stdout)


def point_opencode_at_ollama(url, model):
    """Give OpenCode a provider named "ollama": an Ollama server, spoken to the way OpenAI's
    API is spoken to, and one model on it. The rest of the file is kept as it was."""
    settings = {}
    if OPENCODE_CONFIG.exists():
        settings = json.loads(OPENCODE_CONFIG.read_text(encoding="utf-8"))
    settings.setdefault("$schema", "https://opencode.ai/config.json")
    settings.setdefault("provider", {})["ollama"] = {
        "npm": "@ai-sdk/openai-compatible",
        "name": "Ollama (local)",
        "options": {"baseURL": url.rstrip("/") + "/v1"},
        "models": {model: {"name": model}},
    }
    settings["model"] = "ollama/" + model
    OPENCODE_CONFIG.parent.mkdir(parents=True, exist_ok=True)
    OPENCODE_CONFIG.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
    print("wrote  ", short(str(OPENCODE_CONFIG)))
    print("   provider ollama at", url, "| model", model)


def main():
    words = sys.argv[1:]
    agent, ollama, model = None, None, "qwen2.5:14b"
    while words:
        word = words.pop(0)
        if word == "--ollama":
            ollama = words.pop(0)
        elif word == "--model":
            model = words.pop(0)
        elif not word.startswith("--") and agent is None:
            agent = word

    if agent is None:
        # --dry-run makes the plan for every agent REA finds, and changes nothing.
        report = ask_setup("--dry-run")
        print("agent               name for --client   found here   set up")
        for state in report["clientStates"]:
            found = "yes" if state["detected"] else "no"
            done = "yes" if state["configured"] else "no"
            print(f"{state['client']['displayName']:<19} {state['client']['name']:<19} {found:<12} {done}")
        return

    if agent == "opencode" and ollama and "--yes" in sys.argv:
        point_opencode_at_ollama(ollama, model)
        print()

    # Without --yes, REA only says what it would do.
    report = ask_setup("--client", agent, "--yes" if "--yes" in sys.argv else "--dry-run")
    print("agent  ", agent)
    print("status ", report["status"])
    if not report["plannedActions"]:
        print("nothing to do: this agent is set up already")
    for action in report["plannedActions"]:
        print()
        print(action["operation"], action["label"])
        print("   file ", short(action["target"]))
        print("   what ", short(action["detail"]))
    if report["appliedActions"]:
        print()
        print("done   ", ", ".join(report["appliedActions"]))
        for name, state in report["clients"].items():
            print("backup ", short(state.get("backupPath") or "none"))


if __name__ == "__main__":
    main()
python3 agent_setup.py

what it printed, on GitHub's Macสิ่งที่มันพิมพ์ออกมา บน Mac ของ GitHub它在 GitHub 的 Mac 上打印的内容

agent               name for --client   found here   set up
Claude Code         claude_code         no           no
Claude Desktop      claude_desktop      no           no
Codex               codex               yes          no
Cursor              cursor              no           no
Gemini CLI          gemini_cli          no           no
Windsurf            windsurf            no           no
Devin               devin               no           no
OpenCode            opencode            yes          no
Antigravity         antigravity         no           no
GitHub Copilot CLI  copilot_cli         no           no
Command Code        commandcode         no           no
VS Code             vscode              no           no
Grok Build          grok_build          no           no
OMP                 omp                 no           no
Grok Bot            grok_bot            no           no
Agentเอเจนต์智能体Its programตัวโปรแกรม它的程序Its modelโมเดลของมัน它的模型Free, on your own computers?ฟรี บนคอมพิวเตอร์ของคุณเอง?免费、全在自己的电脑上?
OpenCodeOpen source, MIT licenceโอเพนซอร์ส สัญญาอนุญาต MIT开源,MIT 许可Any provider you name; Ollama is oneผู้ให้บริการใดก็ได้ที่คุณระบุ Ollama เป็นหนึ่งในนั้น你指定的任何提供方;Ollama 是其中之一Yes: the agent on the laptop, the model on your own serverใช่: เอเจนต์บนแล็ปท็อป โมเดลบนเซิร์ฟเวอร์ของคุณเอง是:智能体在笔记本上,模型在你自己的服务器上
CodexOpen source, Apache-2.0 licenceโอเพนซอร์ส สัญญาอนุญาต Apache-2.0开源,Apache-2.0 许可OpenAI's, with a ChatGPT plan or an API keyของ OpenAI ต้องมีแผน ChatGPT หรือคีย์ APIOpenAI 的,需要 ChatGPT 套餐或 API 密钥Noไม่否
Claude CodeNot open sourceไม่ใช่โอเพนซอร์ส不开源Anthropic's, with a Claude plan or an API keyของ Anthropic ต้องมีแผน Claude หรือคีย์ APIAnthropic 的,需要 Claude 套餐或 API 密钥Noไม่否

🇬🇧 English

OpenCode is an open-source coding agent, under the MIT licence. It comes with no model: you name a provider, and one provider it knows is Ollama, a program that serves models on a computer of your own. So OpenCode can be free from end to end, and nothing leaves your house: the agent runs on the laptop, the model on your own server. Ours is a small computer on the home network that runs qwen2.5:14b; the address on this page is an example, so put your server's in its place. Ollama answers on port 11434.

On a Mac, install OpenCode with Homebrew or with npm. Then one command does two things. First it writes OpenCode's settings file, opencode.json in the folder .config/opencode of your home folder: a provider named ollama at that address, spoken to the way OpenAI's API is spoken to, with the one model you named, which becomes the default. Then it connects REA to OpenCode as it does for the others: the registration goes into the same file, which is why the output says update and names a backup. The skill would go into .agents/skills, but on that Mac it was already there from the Codex step below, because the two agents share that folder, so only the registration was written. Start OpenCode in a folder afterwards, and it talks to the model on your server, with REA's commands as its tools.

🇹🇭 ไทย

OpenCode คือเอเจนต์เขียนโค้ดโอเพนซอร์ส ภายใต้สัญญาอนุญาต MIT มันไม่ได้มาพร้อมโมเดล: คุณระบุผู้ให้บริการ และผู้ให้บริการหนึ่งที่มันรู้จักคือ Ollama โปรแกรมที่ให้บริการโมเดลบนคอมพิวเตอร์ของคุณเอง OpenCode จึงฟรีได้ตั้งแต่ต้นจนจบ และไม่มีอะไรออกไปจากบ้านคุณ: เอเจนต์รันบนแล็ปท็อป โมเดลรันบนเซิร์ฟเวอร์ของคุณเอง ของเราคือคอมพิวเตอร์เครื่องเล็กในเครือข่ายบ้านที่รัน qwen2.5:14b ที่อยู่บนหน้านี้เป็นตัวอย่าง จึงต้องใส่ที่อยู่ของเซิร์ฟเวอร์ของคุณแทน Ollama ตอบที่พอร์ต 11434

บน Mac ติดตั้ง OpenCode ด้วย Homebrew หรือ npm จากนั้นคำสั่งเดียวทำสองอย่าง อย่างแรกมันเขียนไฟล์ตั้งค่าของ OpenCode คือ opencode.json ในโฟลเดอร์ .config/opencode ของโฟลเดอร์บ้านของคุณ: ผู้ให้บริการชื่อ ollama ที่ที่อยู่นั้น คุยด้วยแบบเดียวกับ API ของ OpenAI พร้อมโมเดลหนึ่งตัวที่คุณระบุ ซึ่งกลายเป็นค่าเริ่มต้น จากนั้นมันเชื่อม REA เข้ากับ OpenCode เหมือนที่ทำกับตัวอื่น: การลงทะเบียนลงไฟล์เดียวกัน ผลลัพธ์จึงบอกว่า update และระบุไฟล์สำรอง สกิลควรจะลง .agents/skills แต่บน Mac เครื่องนั้นมันอยู่ที่นั่นแล้วจากขั้นของ Codex ข้างล่าง เพราะเอเจนต์สองตัวใช้โฟลเดอร์นั้นร่วมกัน จึงเขียนเฉพาะการลงทะเบียน หลังจากนั้นเริ่ม OpenCode ในโฟลเดอร์หนึ่ง มันจะคุยกับโมเดลบนเซิร์ฟเวอร์ของคุณ โดยมีคำสั่งของ REA เป็นเครื่องมือ

🇨🇳 中文

OpenCode 是一个开源的编程智能体,MIT 许可。它不自带模型:你指定一个提供方,而它认识的提供方之一是 Ollama,一个在你自己的电脑上提供模型的程序。所以 OpenCode 可以从头到尾都免费,而且什么都不离开你家:智能体在笔记本上跑,模型在你自己的服务器上跑。我们的是家庭网络里的一台小电脑,跑着 qwen2.5:14b;这一页上的地址是示例,请换成你服务器的地址。Ollama 在 11434 端口应答。

在 Mac 上,用 Homebrew 或 npm 安装 OpenCode。然后一条命令做两件事。它先写 OpenCode 的设置文件,也就是你主文件夹下 .config/opencode 文件夹里的 opencode.json:一个叫 ollama 的提供方,指向那个地址,用和 OpenAI 的 API 一样的方式对话,带上你指定的那一个模型,并把它设为默认。然后它像对其他智能体一样把 REA 接到 OpenCode 上:注册写进同一个文件,所以输出写着 update,并给出了一个备份。技能本应放进 .agents/skills,但在那台 Mac 上它已经在那里了,来自下面的 Codex 那一步,因为两个智能体共用那个文件夹,所以这次只写了注册。之后在某个文件夹里启动 OpenCode,它就会和你服务器上的模型对话,并把 REA 的命令当作工具。

brew install anomalyco/tap/opencode
python3 agent_setup.py opencode --yes --ollama http://your-spark.local:11434 --model qwen2.5:14b

what it printed, on GitHub's Macสิ่งที่มันพิมพ์ออกมา บน Mac ของ GitHub它在 GitHub 的 Mac 上打印的内容

wrote   ~/.config/opencode/opencode.json
   provider ollama at http://your-spark.local:11434 | model qwen2.5:14b

agent   opencode
status  ready

update OpenCode
   file  ~/.config/opencode/opencode.json
   what  Add the REA MCP registration for OpenCode; preserve unrelated configuration. Command: "npx" "-y" "rea-agents@6.3.0" "mcp". No additional environment variables.

done    configured_opencode
backup  ~/.config/opencode/opencode.json.rea.backup

🇬🇧 English

Codex is OpenAI's coding agent. Its program is open source too, under the Apache-2.0 licence, but it works with OpenAI's models, which need a ChatGPT plan or an API key: it is not free. Install it on a Mac with Homebrew or with npm, then run the same command. REA creates config.toml in the folder .codex of your home folder, with the registration, and copies the skill to .agents/skills, the folder OpenCode shares. Read the registration's command. On the Mac it is npx -y rea-agents@6.3.0 mcp: npm fetches REA 6.3.0 itself when the agent starts it, so no folder has to be kept. On the Windows laptop, further down, the command names the folder where step 1 installed REA, and that folder has to stay: delete or move it, and the agent's REA stops working until you run setup again.

🇹🇭 ไทย

Codex คือเอเจนต์เขียนโค้ดของ OpenAI ตัวโปรแกรมก็โอเพนซอร์สเช่นกัน ภายใต้สัญญาอนุญาต Apache-2.0 แต่มันทำงานกับโมเดลของ OpenAI ซึ่งต้องมีแผน ChatGPT หรือคีย์ API: มันไม่ฟรี ติดตั้งบน Mac ด้วย Homebrew หรือ npm แล้วรันคำสั่งเดิม REA สร้าง config.toml ในโฟลเดอร์ .codex ของโฟลเดอร์บ้านของคุณ พร้อมการลงทะเบียน และคัดลอกสกิลไปที่ .agents/skills โฟลเดอร์ที่ใช้ร่วมกับ OpenCode อ่านคำสั่งในการลงทะเบียนดู บน Mac มันคือ npx -y rea-agents@6.3.0 mcp: npm จะดึง REA 6.3.0 มาเองเมื่อเอเจนต์เริ่มมัน จึงไม่ต้องเก็บโฟลเดอร์ใดไว้ บนแล็ปท็อป Windows ถัดลงไป คำสั่งระบุโฟลเดอร์ที่ขั้นที่ 1 ติดตั้ง REA ไว้ และโฟลเดอร์นั้นต้องคงอยู่: ลบหรือย้ายมัน REA ของเอเจนต์จะหยุดทำงานจนกว่าคุณจะรัน setup อีกครั้ง

🇨🇳 中文

Codex 是 OpenAI 的编程智能体。它的程序也是开源的,Apache-2.0 许可,但它使用 OpenAI 的模型,需要 ChatGPT 套餐或 API 密钥:它不免费。在 Mac 上用 Homebrew 或 npm 安装,然后运行同一条命令。REA 在你主文件夹下的 .codex 文件夹里创建 config.toml,里面是注册;并把技能复制到 .agents/skills,和 OpenCode 共用的那个文件夹。读一读注册里的命令。在 Mac 上它是 npx -y rea-agents@6.3.0 mcp:智能体启动它时,npm 会自己取来 REA 6.3.0,所以不必留着任何文件夹。在下面的 Windows 笔记本上,命令写的是第 1 步安装 REA 的那个文件夹,那个文件夹就得留着:一删或一移,智能体的 REA 就停了,直到你再跑一次 setup。

brew install --cask codex
python3 agent_setup.py codex --yes

what it printed, on GitHub's Macสิ่งที่มันพิมพ์ออกมา บน Mac ของ GitHub它在 GitHub 的 Mac 上打印的内容

agent   codex
status  ready

create Codex
   file  ~/.codex/config.toml
   what  Add the REA MCP registration for Codex; preserve unrelated configuration. Command: "npx" "-y" "rea-agents@6.3.0" "mcp". No additional environment variables.

install REA reverse-engineering skill
   file  ~/.agents/skills/reverse-engineer-anything
   what  Install or update the bundled REA reverse-engineering skill and on-demand references.

done    configured_codex, installed_skill
backup  none

🇬🇧 English

Claude Code is Anthropic's agent; it needs a Claude plan or an API key. We set it up on the Windows laptop. The same two actions, with --yes: REA added its registration to Claude Code's settings file .claude.json, and put the skill in Claude Code's folder for skills. Before it wrote the settings file it kept a copy of the old one, with .rea.backup added to the name; if anything goes wrong, you put that copy back. status ready and the done line say that both actions were made. Then start Claude Code again. In a new session, claude mcp list shows a line for rea that says Connected, and the skill is in the list of skills.

🇹🇭 ไทย

Claude Code คือเอเจนต์ของ Anthropic ต้องมีแผน Claude หรือคีย์ API เราตั้งค่ามันบนแล็ปท็อป Windows การกระทำสองอย่างเดิม พร้อม --yes: REA เพิ่มการลงทะเบียนของมันลงในไฟล์ตั้งค่าของ Claude Code คือ .claude.json และวางสกิลไว้ในโฟลเดอร์สกิลของ Claude Code ก่อนเขียนไฟล์ตั้งค่า มันเก็บสำเนาของไฟล์เดิมไว้ โดยเพิ่ม .rea.backup ต่อท้ายชื่อ ถ้ามีอะไรผิดพลาด คุณเอาสำเนานั้นกลับมาวางได้ status ready และบรรทัด done บอกว่าทำทั้งสองอย่างแล้ว จากนั้นเริ่ม Claude Code ใหม่ ในเซสชันใหม่ claude mcp list แสดงบรรทัดของ rea ที่บอกว่า Connected และสกิลอยู่ในรายการสกิล

🇨🇳 中文

Claude Code 是 Anthropic 的智能体,需要 Claude 套餐或 API 密钥。我们在 Windows 笔记本上把它设置好了。同样的两个动作,加上 --yes:REA 把它的注册加进 Claude Code 的设置文件 .claude.json,并把技能放进 Claude Code 的技能文件夹。写设置文件之前,它先留了一份旧文件的副本,名字后面加上 .rea.backup;要是出了问题,把那份副本放回去就行。status ready 和 done 那一行说明两个动作都做了。然后重新启动 Claude Code。在新的会话里,claude mcp list 会显示一行 rea,写着 Connected,技能也在技能列表里。

python agent_setup.py claude_code --yes

what it printed, on the Windows laptopสิ่งที่มันพิมพ์ออกมา บนแล็ปท็อป Windows它在 Windows 笔记本上打印的内容

agent   claude_code
status  ready

update Claude Code
   file  ~\.claude.json
   what  Add the REA MCP registration for Claude Code; preserve unrelated configuration. Command: "C:\\Program Files\\nodejs\\node.exe" "C:\\relab\\node_modules\\rea-agents\\scripts\\rea.mjs" "mcp". No additional environment variables.

install REA skill for Claude Code
   file  ~\.claude\skills\reverse-engineer-anything
   what  Install or update the REA skill in Claude Code's personal skill directory.

done    configured_claude_code, installed_skill
backup  ~\.claude.json.rea.backup

🇬🇧 English

After that, the agent has REA's commands as tools. They reach it through MCP, and the manual says that the terminal commands and the MCP tools do the same work. You do not type commands. You ask in your own words. For Rectangle you could write:

Look at the app Rectangle.app in this folder. Tell me which libraries it loads and what its menu items call. For every fact, say which tool gave it to you, and say what you could not find out.

The last sentence of that request matters most. An agent that answers quickly can be wrong, and you have seen in step 8 that even a good guess has to be checked. Ask where each fact came from, every time. Then check two or three of them yourself, as you did with source_says.py and with apple_says.py. It takes a minute, and it is the difference between knowing and believing.

🇹🇭 ไทย

หลังจากนั้น เอเจนต์มีคำสั่งของ REA เป็นเครื่องมือ คำสั่งเหล่านั้นไปถึงเอเจนต์ผ่าน MCP และคู่มือบอกว่าคำสั่งในเทอร์มินัลกับเครื่องมือ MCP ทำงานเดียวกัน คุณไม่ต้องพิมพ์คำสั่ง คุณถามด้วยคำพูดของคุณเอง สำหรับ Rectangle คุณอาจเขียนว่า:

Look at the app Rectangle.app in this folder. Tell me which libraries it loads and what its menu items call. For every fact, say which tool gave it to you, and say what you could not find out. (ดูแอป Rectangle.app ในโฟลเดอร์นี้ บอกฉันว่ามันโหลดไลบรารีอะไร และรายการเมนูของมันเรียกอะไร สำหรับข้อเท็จจริงทุกข้อ ให้บอกว่าเครื่องมือตัวไหนให้มา และบอกว่าคุณหาอะไรไม่ได้)

ประโยคสุดท้ายของคำขอนั้นสำคัญที่สุด เอเจนต์ที่ตอบเร็วอาจผิดได้ และคุณได้เห็นในขั้นที่ 8 แล้วว่าแม้การเดาที่ดีก็ยังต้องตรวจ ถามทุกครั้งว่าข้อเท็จจริงแต่ละข้อมาจากไหน จากนั้นตรวจสองหรือสามข้อด้วยตัวเอง แบบที่คุณทำกับ source_says.py และกับ apple_says.py มันใช้เวลาหนึ่งนาที และมันคือความต่างระหว่างการรู้กับการเชื่อ

🇨🇳 中文

在那之后,智能体就把 REA 的命令当作工具来用。这些命令通过 MCP 交到它手里,手册说,终端命令和 MCP 工具做的是同样的工作。你不用输入命令。你用自己的话来问。对 Rectangle,你可以这样写:

Look at the app Rectangle.app in this folder. Tell me which libraries it loads and what its menu items call. For every fact, say which tool gave it to you, and say what you could not find out.(看看这个文件夹里的应用 Rectangle.app。告诉我它加载了哪些库,它的菜单项调用了什么。每一个事实都要说明是哪个工具给你的,并说出你没能查出来的东西。)

这个请求的最后一句最重要。回答得很快的智能体可能是错的,而你在第 8 步已经看到,即使是很有把握的猜测也要检查。每次都要问每个事实是从哪里来的。然后自己检查其中的两三个,就像你用 source_says.py 和 apple_says.py 做过的那样。这只要一分钟,而这就是知道和相信的区别。