🔌 Codex on a Mac: wire it to REA, GitHub, NotebookLM and a team of agents · Codex บนแมค: ต่อมันเข้ากับ REA, GitHub, NotebookLM และทีมเอเจนต์ · 在 Mac 上接好 Codex:REA、GitHub、NotebookLM 与一支智能体团队

Codex is OpenAI's coding agent: a program that reads your files, runs commands and changes code when you ask it in plain words, from the terminal. This page teaches how to set it up on a Mac and how to connect it to seven things: REA, the reverse-engineering tool of the REA page; GitHub; NotebookLM, through a Python command; a skill that you write yourself; a web browser, through Playwright's tool server, either Google Chrome or CloakBrowser; Chrome's own DevTools; and Context7, which keeps the current documents of many libraries. Then it teaches how to make Codex work as a team: three agents of your own that it can send off in parallel, and several Codex running at once, each in its own copy of a repository. Last, it shows Codex working while you are away: one task every night, started by the Mac's own scheduler, and the Codex on your Mac driven from your phone. Every command on this page was run on one of GitHub's Macs, and what it printed is shown under it. Three of the Python files you can also run right here: press ▶ Run, and Python runs in the page, on a pretend Mac. A quiz after each step checks the idea. The few commands that need a signed-in account are marked. · Codex คือเอเจนต์เขียนโค้ดของ OpenAI: โปรแกรมที่อ่านไฟล์ของคุณ รันคำสั่ง และแก้โค้ดเมื่อคุณขอด้วยภาษาธรรมดา จากเทอร์มินัล หน้านี้สอนวิธีติดตั้งมันบนแมค และวิธีต่อมันเข้ากับเจ็ดสิ่ง: REA เครื่องมือวิศวกรรมย้อนกลับของหน้า REA; GitHub; NotebookLM ผ่านคำสั่ง Python; สกิลที่คุณเขียนเอง; เว็บเบราว์เซอร์ ผ่านเซิร์ฟเวอร์เครื่องมือของ Playwright จะเป็น Google Chrome หรือ CloakBrowser ก็ได้; DevTools ของ Chrome เอง; และ Context7 ซึ่งเก็บเอกสารฉบับปัจจุบันของไลบรารีจำนวนมาก จากนั้นสอนวิธีทำให้ Codex ทำงานเป็นทีม: เอเจนต์สามตัวของคุณเองที่มันส่งออกไปทำงานพร้อมกันได้ และ Codex หลายตัวรันพร้อมกัน แต่ละตัวในสำเนาของตัวเองของรีโพซิทอรี สุดท้าย หน้านี้แสดง Codex ที่ทำงานตอนคุณไม่อยู่: งานหนึ่งทุกคืน ซึ่งตัวจัดตารางของแมคเองเป็นคนเริ่ม และ Codex บนแมคของคุณที่สั่งงานได้จากโทรศัพท์ คำสั่งทุกคำสั่งในหน้านี้ถูกรันบนแมคเครื่องหนึ่งของ GitHub และสิ่งที่มันพิมพ์ออกมาแสดงไว้ข้างใต้ ไฟล์ Python สามไฟล์คุณรันได้ตรงนี้เลยด้วย: กด ▶ Run แล้ว Python จะรันในหน้าเว็บบนแมคจำลอง และหลังแต่ละขั้นตอนมีแบบทดสอบเช็กความเข้าใจ คำสั่งไม่กี่คำสั่งที่ต้องใช้บัญชีที่ลงชื่อเข้าใช้แล้วมีเครื่องหมายกำกับไว้ · Codex 是 OpenAI 的编程智能体:一个程序,当你在终端里用平常的话提出要求时,它读你的文件、运行命令、修改代码。这一页教你怎样在 Mac 上装好它,并把它接到七样东西上:REA 那一页的逆向工程工具 REA;GitHub;通过一条 Python 命令接上的 NotebookLM;一个你自己写的技能;一个网页浏览器,通过 Playwright 的工具服务器接上,可以是 Google Chrome,也可以是 CloakBrowser;Chrome 自己的 DevTools;以及 Context7,它保存着许多库的最新文档。然后教你怎样让 Codex 以团队的方式工作:三个你自己的智能体,它可以把它们并行派出去;还有几个 Codex 同时运行,每个都在仓库的一份自己的副本里。最后,它展示你不在时工作的 Codex:每晚一个任务,由 Mac 自带的调度器启动;还有从你的手机驱动你 Mac 上的 Codex。这一页的每条命令都在 GitHub 的一台 Mac 上运行过,它打印的内容就显示在下面。其中三个 Python 文件你也可以就在这里运行:按 ▶ Run,Python 就在这一页里、在一台假装的 Mac 上运行。每一步之后还有一个小测验,检查你是否懂了。少数几条需要已登录账户的命令都做了标记。

Before you start · ก่อนเริ่ม · 开始之前

🇬🇧 English

An agent is a language model with hands: it can run a command, read what came back, and decide what to do next. Codex is OpenAI's agent for code. Out of the box it has the terminal and your files. Everything else it reaches through things you give it: a tool server that offers it tools, a skill that teaches it a command you already have, and settings that say what it may do.

Everything you give Codex on this page is a file. There are four kinds, and they are the whole of part 2 and part 3: ~/.codex/config.toml, the settings file; AGENTS.md, a page of instructions in a folder; folders in ~/.agents/skills, one per skill; and files in ~/.codex/agents, one per agent. A file can be read, copied to another Mac, and put in git. Nothing here is a setting that you click once and forget.

What you need:

  • a Mac. This page was run on one of GitHub's Macs, with an Apple processor;
  • Node.js, from nodejs.org, and Python 3, which Apple's Command Line Tools bring;
  • Google Chrome, which the browser steps, 8 and 9, drive;
  • a ChatGPT account on a paid plan, or an OpenAI API key, for Codex itself. Installing and wiring it needs neither; only using it does;
  • a GitHub account, and a Google account for NotebookLM.

On this page you will:

  1. install Codex and sign in;
  2. choose what Codex may do on its own: the sandbox and the approvals;
  3. read the three places Codex takes its instructions from;
  4. connect REA to it, with the file from the REA page;
  5. connect GitHub, in three ways: the gh command, GitHub's tool server, and Codex on GitHub's own side;
  6. connect NotebookLM, through the notebooklm Python command and the skill it brings;
  7. write a skill of your own, and see Codex find it;
  8. give Codex a browser with Playwright's tool server, once as Google Chrome and once as CloakBrowser, talk to both from Python, and search Google with each;
  9. let Codex read what a web page did, with Chrome's DevTools;
  10. give Codex the current documents of a library, with Context7;
  11. write three agents of your own, and set how many may run at once;
  12. run several Codex at the same time, each in its own copy of a repository;
  13. print the whole wiring from one Python file;
  14. make the Mac start one Codex task every night, with its own scheduler;
  15. drive the Codex on your Mac from your phone.

🇹🇭 ไทย

เอเจนต์คือโมเดลภาษาที่มีมือ: มันรันคำสั่งได้ อ่านสิ่งที่ตอบกลับมาได้ และตัดสินใจได้ว่าจะทำอะไรต่อ Codex คือเอเจนต์ของ OpenAI สำหรับโค้ด ทันทีที่ติดตั้ง มันมีเทอร์มินัลและไฟล์ของคุณ ทุกอย่างที่เหลือมันเข้าถึงผ่านสิ่งที่คุณมอบให้: เซิร์ฟเวอร์เครื่องมือที่เสนอเครื่องมือให้มัน สกิลที่สอนมันใช้คำสั่งที่คุณมีอยู่แล้ว และการตั้งค่าที่บอกว่ามันทำอะไรได้

ทุกสิ่งที่คุณมอบให้ Codex ในหน้านี้คือไฟล์ มีสี่ชนิด และมันคือทั้งหมดของส่วนที่ 2 และส่วนที่ 3: ~/.codex/config.toml ไฟล์ตั้งค่า; AGENTS.md หน้าคำสั่งในโฟลเดอร์หนึ่ง; โฟลเดอร์ใน ~/.agents/skills หนึ่งโฟลเดอร์ต่อหนึ่งสกิล; และไฟล์ใน ~/.codex/agents หนึ่งไฟล์ต่อหนึ่งเอเจนต์ ไฟล์อ่านได้ คัดลอกไปแมคอีกเครื่องได้ และเก็บใน git ได้ ไม่มีอะไรในที่นี้เป็นการตั้งค่าที่คุณคลิกครั้งเดียวแล้วลืมไป

สิ่งที่คุณต้องมี:

  • แมคหนึ่งเครื่อง หน้านี้ถูกรันบนแมคเครื่องหนึ่งของ GitHub ที่ใช้โปรเซสเซอร์ของ Apple;
  • Node.js จาก nodejs.org และ Python 3 ซึ่งมากับ Command Line Tools ของ Apple;
  • Google Chrome ซึ่งขั้นตอนเบราว์เซอร์ คือขั้นตอนที่ 8 และ 9 ใช้ควบคุม;
  • บัญชี ChatGPT แบบแผนเสียเงิน หรือคีย์ API ของ OpenAI สำหรับตัว Codex เอง การติดตั้งและเดินสายไม่ต้องใช้ทั้งสองอย่าง มีแต่การใช้งานเท่านั้นที่ต้องใช้;
  • บัญชี GitHub และบัญชี Google สำหรับ NotebookLM

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

  1. ติดตั้ง Codex และลงชื่อเข้าใช้;
  2. เลือกว่า Codex ทำอะไรได้เองบ้าง: แซนด์บ็อกซ์และการอนุมัติ;
  3. อ่านสามที่ที่ Codex รับคำสั่งมา;
  4. ต่อ REA เข้ากับมัน ด้วยไฟล์จากหน้า REA;
  5. ต่อ GitHub สามทาง: คำสั่ง gh เซิร์ฟเวอร์เครื่องมือของ GitHub และ Codex บนฝั่งของ GitHub เอง;
  6. ต่อ NotebookLM ผ่านคำสั่ง Python notebooklm และสกิลที่มันนำมาด้วย;
  7. เขียนสกิลของคุณเอง และเห็น Codex หามันเจอ;
  8. ให้ Codex มีเบราว์เซอร์ด้วยเซิร์ฟเวอร์เครื่องมือของ Playwright ครั้งหนึ่งเป็น Google Chrome และอีกครั้งเป็น CloakBrowser แล้วคุยกับทั้งสองจาก Python และค้นหาบน Google ด้วยแต่ละตัว;
  9. ให้ Codex อ่านว่าหน้าเว็บทำอะไรไปบ้าง ด้วย DevTools ของ Chrome;
  10. ให้ Codex มีเอกสารฉบับปัจจุบันของไลบรารี ด้วย Context7;
  11. เขียนเอเจนต์สามตัวของคุณเอง และกำหนดว่ารันพร้อมกันได้กี่ตัว;
  12. รัน Codex หลายตัวพร้อมกัน แต่ละตัวในสำเนาของตัวเองของรีโพซิทอรี;
  13. พิมพ์การเดินสายทั้งหมดออกมาจากไฟล์ Python ไฟล์เดียว;
  14. ให้แมคเริ่มงาน Codex หนึ่งงานทุกคืน ด้วยตัวจัดตารางของมันเอง;
  15. สั่งงาน Codex บนแมคของคุณจากโทรศัพท์

🇨🇳 中文

智能体是长了手的语言模型:它能运行一条命令,读返回的内容,再决定下一步做什么。Codex 是 OpenAI 做代码的智能体。装好就有的,是终端和你的文件。其余的一切,它都通过你交给它的东西来触及:一个向它提供工具的工具服务器,一个教它使用你已有命令的技能,以及规定它可以做什么的设置。

这一页里你交给 Codex 的每样东西都是文件。一共四种,它们就是第 2 部分和第 3 部分的全部:设置文件 ~/.codex/config.toml;放在某个文件夹里的一页说明 AGENTS.md;~/.agents/skills 里的文件夹,一个技能一个;以及 ~/.codex/agents 里的文件,一个智能体一个。文件可以读,可以复制到另一台 Mac,可以放进 git。这里没有哪一项是点一下就忘掉的设置。

你需要:

  • 一台 Mac。这一页在 GitHub 的一台 Mac 上运行,用的是 Apple 处理器;
  • 来自 nodejs.org 的 Node.js,以及 Apple 命令行工具自带的 Python 3;
  • Google Chrome,浏览器那两步,第 8 和第 9 步,驱动的就是它;
  • 一个付费方案的 ChatGPT 账户,或者一个 OpenAI API 密钥,给 Codex 本身用。安装和接线两样都不需要,只有使用才需要;
  • 一个 GitHub 账户,以及一个用于 NotebookLM 的 Google 账户。

在这一页你将:

  1. 安装 Codex 并登录;
  2. 选择 Codex 可以自行做什么:沙箱和审批;
  3. 读一读 Codex 获取指令的三个地方;
  4. 用 REA 那一页的文件,把 REA 接到它上面;
  5. 用三种方式接上 GitHub:gh 命令、GitHub 的工具服务器,以及 GitHub 那一侧的 Codex;
  6. 通过 Python 命令 notebooklm 和它自带的技能,接上 NotebookLM;
  7. 写一个你自己的技能,并看着 Codex 找到它;
  8. 用 Playwright 的工具服务器给 Codex 一个浏览器,一次是 Google Chrome,一次是 CloakBrowser,从 Python 和两者对话,并用每一个在 Google 上搜索;
  9. 用 Chrome 的 DevTools,让 Codex 读出一个网页做了什么;
  10. 用 Context7,给 Codex 一个库的最新文档;
  11. 写三个你自己的智能体,并设定同时最多运行几个;
  12. 同时运行几个 Codex,每个都在仓库的一份自己的副本里;
  13. 用一个 Python 文件把整个接线打印出来;
  14. 让 Mac 用它自己的调度器,每晚启动一个 Codex 任务;
  15. 从你的手机驱动你 Mac 上的 Codex。
1Codex itselfตัว Codex เองCodex 本身
Install it, sign in, choose what it may do, and read where it takes its instructions from.ติดตั้ง ลงชื่อเข้าใช้ เลือกว่ามันทำอะไรได้ และอ่านว่ามันรับคำสั่งมาจากไหน安装它,登录,选择它可以做什么,并读一读它从哪里获取指令。
codex, config.toml, AGENTS.md
→
2Its toolsเครื่องมือของมัน它的工具
REA, GitHub, a browser, DevTools and Context7 as tool servers, NotebookLM as a command with a skill, and a skill you write.REA, GitHub, เบราว์เซอร์, DevTools และ Context7 เป็นเซิร์ฟเวอร์เครื่องมือ NotebookLM เป็นคำสั่งพร้อมสกิล และสกิลที่คุณเขียนREA、GitHub、一个浏览器、DevTools 和 Context7 作为工具服务器,NotebookLM 作为带技能的命令,再加一个你写的技能。
codex mcp add, SKILL.md
→
3A teamทีม一支团队
Three agents of your own inside one Codex, and several Codex side by side in copies of a repository.เอเจนต์สามตัวของคุณเองใน Codex ตัวเดียว และ Codex หลายตัวเคียงข้างกันในสำเนาของรีโพซิทอรี一个 Codex 里三个你自己的智能体,以及几个 Codex 并排在仓库的副本里。
agents/*.toml, fan_out.py
→
4Without youตอนคุณไม่อยู่你不在时
A task every night from the Mac's scheduler, and the Mac's Codex driven from your phone.งานทุกคืนจากตัวจัดตารางของแมค และ Codex ของแมคที่สั่งงานจากโทรศัพท์ของคุณMac 的调度器每晚一个任务,以及从你的手机驱动 Mac 上的 Codex。
launchd, nightly.sh, ChatGPT app

🧰 Part 1 — Codex itself · ส่วนที่ 1 — ตัว Codex เอง · 第 1 部分 —— Codex 本身

🇬🇧 English

Codex is three things with one name. There is the terminal program, Codex CLI, which this page uses. There is the ChatGPT app for the Mac, which runs the same agent behind windows and buttons. And there is Codex on OpenAI's side, which can work on a GitHub repository without your Mac being on, and which reviews pull requests. This page uses the terminal program, because everything it does can be written down, run again, and shown.

🇹🇭 ไทย

Codex คือสามสิ่งในชื่อเดียว มีโปรแกรมเทอร์มินัล Codex CLI ซึ่งหน้านี้ใช้ มีแอป ChatGPT สำหรับแมค ซึ่งรันเอเจนต์ตัวเดียวกันอยู่หลังหน้าต่างและปุ่ม และมี Codex บนฝั่งของ OpenAI ซึ่งทำงานกับรีโพซิทอรีบน GitHub ได้โดยที่แมคของคุณไม่ต้องเปิดอยู่ และรีวิว pull request ให้ หน้านี้ใช้โปรแกรมเทอร์มินัล เพราะทุกอย่างที่มันทำเขียนลงไปได้ รันซ้ำได้ และแสดงให้ดูได้

🇨🇳 中文

Codex 是同一个名字下的三样东西。有终端程序 Codex CLI,这一页用的就是它。有 Mac 上的 ChatGPT 应用,它在窗口和按钮后面运行同一个智能体。还有 OpenAI 那一侧的 Codex,它可以在你的 Mac 没开机时处理一个 GitHub 仓库,并审查 pull request。这一页用终端程序,因为它做的每件事都能写下来、再运行一遍、拿出来展示。

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

🇬🇧 English

Codex is published as an npm package, so it installs the way REA did on the REA page, with -g added: -g puts it on your whole Mac, not in one folder, so that codex works from anywhere. Ask Node.js for its version first. If the command is not found, install Node.js from nodejs.org and open a new terminal. The second command installs Codex. The third asks it for its version.

Two other ways do the same thing: brew install --cask codex if you use Homebrew, and the one-line installer on OpenAI's page, curl -fsSL https://chatgpt.com/codex/install.sh | sh. The npm way is the one this page ran. The same command, run again later, updates Codex, and so does codex update.

🇹🇭 ไทย

Codex เผยแพร่เป็นแพ็กเกจ npm มันจึงติดตั้งแบบเดียวกับที่ REA ทำในหน้า REA โดยเพิ่ม -g: -g ติดตั้งมันลงทั้งเครื่องแมคของคุณ ไม่ใช่ในโฟลเดอร์เดียว เพื่อให้ codex ใช้ได้จากทุกที่ ถามเวอร์ชันจาก Node.js ก่อน ถ้าไม่พบคำสั่ง ให้ติดตั้ง Node.js จาก nodejs.org แล้วเปิดเทอร์มินัลใหม่ คำสั่งที่สองติดตั้ง Codex คำสั่งที่สามถามเวอร์ชันจากมัน

อีกสองวิธีทำสิ่งเดียวกัน: brew install --cask codex ถ้าคุณใช้ Homebrew และตัวติดตั้งบรรทัดเดียวบนหน้าของ OpenAI คือ curl -fsSL https://chatgpt.com/codex/install.sh | sh วิธี npm คือวิธีที่หน้านี้รัน คำสั่งเดียวกันนี้ เมื่อรันอีกครั้งในภายหลัง จะอัปเดต Codex และ codex update ก็ทำได้เช่นกัน

🇨🇳 中文

Codex 以 npm 包的形式发布,所以它的安装方式和 REA 那一页里 REA 的一样,只是多了 -g:-g 把它装到整台 Mac 上,而不是某一个文件夹里,这样 codex 在任何地方都能用。先问一下 Node.js 的版本。如果提示找不到这个命令,就从 nodejs.org 安装 Node.js,再打开一个新的终端。第二条命令安装 Codex。第三条问它的版本。

另外两种方法做的是同一件事:如果你用 Homebrew,就 brew install --cask codex;或者用 OpenAI 页面上的一行安装器 curl -fsSL https://chatgpt.com/codex/install.sh | sh。这一页运行的是 npm 这种。同一条命令以后再运行一次就会更新 Codex,codex update 也一样。

node --version
npm install -g @openai/codex
codex --version

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

codex-cli 0.163.0

🧠 Quiz 1🧠 แบบทดสอบ 1🧠 小测验 1

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

What does -g do in npm install -g @openai/codex?-g ใน npm install -g @openai/codex ทำอะไรnpm install -g @openai/codex 里的 -g 是做什么的?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

-g means global: the package goes on your whole Mac, not into one folder, so codex works from anywhere.-g หมายถึง global: แพ็กเกจไปอยู่ทั้งแมค ไม่ใช่ในโฟลเดอร์เดียว codex จึงใช้ได้จากทุกที่-g 的意思是全局:这个包装在整台 Mac 上,而不是装进某一个文件夹,所以 codex 在哪里都能用。

2 Sign in, and decide what Codex may do · ลงชื่อเข้าใช้ และกำหนดว่า Codex ทำอะไรได้บ้าง · 登录,并决定 Codex 可以做什么

🇬🇧 English

Type codex in a folder that is a git repository. The first time, it offers Sign in with ChatGPT, which opens your browser; a ChatGPT Plus, Pro, Business, Edu or Enterprise plan works. If you have an OpenAI API key instead, the second command below signs in with it: it reads the key from the environment variable OPENAI_API_KEY and hands it to Codex, so that the key never stands in your shell's history. The third command says which way you are signed in.

Nobody signed in on GitHub's Mac, and the third command said so. That is why every step on this page is one that works without a sign-in: installing, registering a tool, writing a settings file, and asking a program for its help text. The moments where Codex itself has to think are marked not run by us.

Then decide what Codex may do without asking you. Two settings decide it, and both can be given on the command line, in the settings file of step 3, or inside a session with /permissions:

🇹🇭 ไทย

พิมพ์ codex ในโฟลเดอร์ที่เป็นรีโพซิทอรี git ครั้งแรกมันเสนอ Sign in with ChatGPT (ลงชื่อเข้าใช้ด้วย ChatGPT) ซึ่งเปิดเบราว์เซอร์ของคุณ แผน ChatGPT Plus, Pro, Business, Edu หรือ Enterprise ใช้ได้ ถ้าคุณมีคีย์ API ของ OpenAI แทน คำสั่งที่สองข้างล่างลงชื่อเข้าใช้ด้วยคีย์นั้น: มันอ่านคีย์จากตัวแปรสภาพแวดล้อม OPENAI_API_KEY แล้วส่งให้ Codex คีย์จึงไม่เคยอยู่ในประวัติของเชลล์ของคุณ คำสั่งที่สามบอกว่าคุณลงชื่อเข้าใช้ด้วยวิธีไหน

ไม่มีใครลงชื่อเข้าใช้บนแมคของ GitHub และคำสั่งที่สามก็บอกอย่างนั้น นั่นคือเหตุผลที่ทุกขั้นตอนในหน้านี้เป็นขั้นตอนที่ทำได้โดยไม่ต้องลงชื่อเข้าใช้: ติดตั้ง ลงทะเบียนเครื่องมือ เขียนไฟล์ตั้งค่า และถามข้อความช่วยเหลือจากโปรแกรม จังหวะที่ Codex เองต้องคิดมีเครื่องหมาย not run by us (เราไม่ได้รันเอง) กำกับไว้

จากนั้นกำหนดว่า Codex ทำอะไรได้โดยไม่ต้องถามคุณ การตั้งค่าสองตัวเป็นตัวกำหนด และทั้งสองตัวระบุได้ในบรรทัดคำสั่ง ในไฟล์ตั้งค่าของขั้นตอนที่ 3 หรือภายในเซสชันด้วย /permissions:

🇨🇳 中文

在一个是 git 仓库的文件夹里输入 codex。第一次它会提供 Sign in with ChatGPT(用 ChatGPT 登录),这会打开你的浏览器;ChatGPT Plus、Pro、Business、Edu 或 Enterprise 方案都可以。如果你有的是 OpenAI API 密钥,下面第二条命令用它登录:它从环境变量 OPENAI_API_KEY 读出密钥再交给 Codex,这样密钥永远不会留在你的 shell 历史里。第三条命令说出你是用哪种方式登录的。

GitHub 的 Mac 上没有人登录,第三条命令也是这么说的。这就是为什么这一页的每一步都是不登录也能做的事:安装、注册一个工具、写一个设置文件、向程序要帮助文本。需要 Codex 自己思考的时刻,都标着 not run by us(我们没有运行过)。

然后决定 Codex 不问你就可以做什么。两项设置决定这件事,两项都可以在命令行上给出,也可以写在第 3 步的设置文件里,或者在会话里用 /permissions 设定:

codex
printenv OPENAI_API_KEY | codex login --with-api-key
codex login status

what the last command printed, on a Mac where nobody signed inสิ่งที่คำสั่งสุดท้ายพิมพ์ออกมา บนแมคที่ไม่มีใครลงชื่อเข้าใช้最后一条命令打印的内容,在一台没有人登录的 Mac 上

Not logged in
settingการตั้งค่า设置valuesค่า取值what it meansความหมาย含义
--sandbox, sandbox_moderead-only
workspace-write
danger-full-access
Where Codex's commands may write. read-only lets them read anything and write nothing. workspace-write lets them write inside the folder you started in. danger-full-access turns the sandbox off. On a Mac the sandbox is Apple's own, sandbox-exec, with one profile per mode. Inside the sandbox the network is off unless you turn it on.คำสั่งของ Codex เขียนที่ไหนได้บ้าง read-only ให้มันอ่านอะไรก็ได้แต่เขียนไม่ได้เลย workspace-write ให้มันเขียนในโฟลเดอร์ที่คุณเริ่มต้นได้ danger-full-access ปิดแซนด์บ็อกซ์ บนแมค แซนด์บ็อกซ์คือของ Apple เอง sandbox-exec มีโปรไฟล์หนึ่งชุดต่อหนึ่งโหมด ในแซนด์บ็อกซ์ เครือข่ายปิดอยู่จนกว่าคุณจะเปิดCodex 的命令可以写到哪里。read-only 让它们什么都能读、什么都不能写。workspace-write 让它们在你启动时所在的文件夹里写。danger-full-access 关掉沙箱。在 Mac 上,沙箱是 Apple 自己的 sandbox-exec,每种模式一个配置。沙箱里的网络是关着的,除非你打开它。
--ask-for-approval, approval_policyon-request
never
When Codex stops and asks you. on-request lets the model decide, and a command the sandbox allows runs without a question. never asks nothing; a command that fails goes straight back to the model.Codex หยุดแล้วถามคุณเมื่อไร on-request ให้โมเดลตัดสินใจ และคำสั่งที่แซนด์บ็อกซ์อนุญาตจะรันโดยไม่ถาม never ไม่ถามอะไรเลย คำสั่งที่ล้มเหลวจะถูกส่งกลับไปที่โมเดลทันทีCodex 什么时候停下来问你。on-request 让模型自己决定,沙箱允许的命令不问就运行。never 什么都不问;失败的命令直接回到模型那里。

🇬🇧 English

A good start is codex --sandbox workspace-write --ask-for-approval on-request: Codex edits the project you opened, nothing outside it, and asks before it goes further. Step 12 uses workspace-write for the same reason. The flag --dangerously-bypass-approvals-and-sandbox exists, and its name is the whole warning.

🇹🇭 ไทย

จุดเริ่มต้นที่ดีคือ codex --sandbox workspace-write --ask-for-approval on-request: Codex แก้โปรเจกต์ที่คุณเปิด ไม่แตะอะไรข้างนอก และถามก่อนจะไปไกลกว่านั้น ขั้นตอนที่ 12 ใช้ workspace-write ด้วยเหตุผลเดียวกัน แฟล็ก --dangerously-bypass-approvals-and-sandbox มีอยู่จริง และชื่อของมันคือคำเตือนทั้งหมดแล้ว

🇨🇳 中文

一个好的起点是 codex --sandbox workspace-write --ask-for-approval on-request:Codex 修改你打开的那个项目,不碰外面的任何东西,要走得更远时先问你。第 12 步出于同样的理由用 workspace-write。标志 --dangerously-bypass-approvals-and-sandbox 是存在的,它的名字就是全部的警告。

CAREFUL After you sign in, Codex keeps what it needs in ~/.codex/auth.json. That file is your account. Never paste it on a page, in a chat, or in a repository. codex logout removes it. The Python file of step 13 says whether the file is there, and prints nothing from it.หลังจากคุณลงชื่อเข้าใช้ Codex เก็บสิ่งที่มันต้องใช้ไว้ใน ~/.codex/auth.json ไฟล์นั้นคือบัญชีของคุณ อย่าวางมันลงในหน้าเว็บ ในแชต หรือในรีโพซิทอรีเด็ดขาด codex logout ลบมันออก ไฟล์ Python ของขั้นตอนที่ 13 บอกว่าไฟล์นั้นมีอยู่หรือไม่ และไม่พิมพ์อะไรจากข้างในออกมาเลย登录之后,Codex 把它需要的东西保存在 ~/.codex/auth.json 里。那个文件就是你的账户。永远不要把它贴到网页上、聊天里或仓库里。codex logout 会删除它。第 13 步的 Python 文件只说这个文件在不在,不打印里面的任何内容。

🧠 Quiz 2🧠 แบบทดสอบ 2🧠 小测验 2

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

Why does step 2 sign in with printenv OPENAI_API_KEY | codex login --with-api-key instead of typing the key?ทำไมขั้นตอนที่ 2 ถึงลงชื่อเข้าใช้ด้วย printenv OPENAI_API_KEY | codex login --with-api-key แทนการพิมพ์คีย์为什么第 2 步用 printenv OPENAI_API_KEY | codex login --with-api-key 登录,而不是把密钥打出来?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

The | hands what printenv prints straight to codex login. The key comes from the environment variable, so it is never written into a command that your shell keeps in its history.| ส่งสิ่งที่ printenv พิมพ์ตรงไปให้ codex login คีย์มาจากตัวแปรสภาพแวดล้อม จึงไม่ถูกเขียนลงในคำสั่งที่เชลล์เก็บไว้ในประวัติ| 把 printenv 打印的内容直接交给 codex login。密钥来自环境变量,所以它从来不会写进 shell 存进历史记录的命令里。

3 The three places Codex reads · สามที่ที่ Codex อ่าน · Codex 读取的三个地方

🇬🇧 English

Before you give Codex anything, see what it has. codex --help prints its commands and flags. Three of them matter on this page: mcp manages tool servers (part 2), exec runs Codex with no screen (step 12), and --worktree starts a session in a copy of the repository (step 12 too). The help is long; read the Commands list and the -s and -a flags, which are the two settings of step 2.

Everything Codex knows beyond its own program comes from three places, and each one is a file that you can read:

  1. The settings file, ~/.codex/config.toml. It is written in TOML: a line [name] opens a table, and the lines under it set keys. A project can carry its own .codex/config.toml, which Codex reads only after you have marked the project as trusted. Steps 4, 5, 8, 9, 10 and 11 write into this file, and step 13 prints it.
  2. Instructions, in files named AGENTS.md. Codex reads ~/.codex/AGENTS.md first, then the AGENTS.md of the repository's root folder, then those of each folder down to the one you are in, and joins them in that order, so the nearest file has the last word. Together they stop at 32 KiB unless you raise project_doc_max_bytes. The file below is the one this page's lab folder carries; step 11 explains its Delegation part, and step 5 its Code Review Rules.
  3. Skills, folders with a file SKILL.md in them: ~/.agents/skills for you, .agents/skills inside a repository for that repository, and /etc/codex/skills for a whole Mac. Steps 4, 6 and 7 each put one there.

🇹🇭 ไทย

ก่อนจะมอบอะไรให้ Codex ดูก่อนว่ามันมีอะไร codex --help พิมพ์คำสั่งและแฟล็กของมันออกมา สามตัวในนั้นสำคัญกับหน้านี้: mcp จัดการเซิร์ฟเวอร์เครื่องมือ (ส่วนที่ 2) exec รัน Codex โดยไม่มีหน้าจอ (ขั้นตอนที่ 12) และ --worktree เริ่มเซสชันในสำเนาของรีโพซิทอรี (ขั้นตอนที่ 12 เช่นกัน) ข้อความช่วยเหลือยาว ให้อ่านรายการ Commands และแฟล็ก -s กับ -a ซึ่งคือการตั้งค่าสองตัวของขั้นตอนที่ 2

ทุกสิ่งที่ Codex รู้นอกเหนือจากโปรแกรมของมันเองมาจากสามที่ และแต่ละที่คือไฟล์ที่คุณอ่านได้:

  1. ไฟล์ตั้งค่า ~/.codex/config.toml เขียนด้วย TOML: บรรทัด [name] เปิดตารางหนึ่ง และบรรทัดข้างใต้กำหนดคีย์ โปรเจกต์หนึ่งมี .codex/config.toml ของตัวเองได้ ซึ่ง Codex จะอ่านก็ต่อเมื่อคุณทำเครื่องหมายว่าโปรเจกต์นั้นเชื่อถือได้แล้ว ขั้นตอนที่ 4, 5, 8, 9, 10 และ 11 เขียนลงไฟล์นี้ และขั้นตอนที่ 13 พิมพ์มันออกมา
  2. คำสั่ง ในไฟล์ชื่อ AGENTS.md Codex อ่าน ~/.codex/AGENTS.md ก่อน แล้วอ่าน AGENTS.md ของโฟลเดอร์รากของรีโพซิทอรี แล้วของแต่ละโฟลเดอร์ลงมาจนถึงโฟลเดอร์ที่คุณอยู่ และต่อกันตามลำดับนั้น ไฟล์ที่ใกล้ที่สุดจึงได้พูดคำสุดท้าย รวมกันแล้วหยุดที่ 32 KiB เว้นแต่คุณจะเพิ่ม project_doc_max_bytes ไฟล์ข้างล่างคือไฟล์ที่โฟลเดอร์แล็บของหน้านี้พกอยู่ ขั้นตอนที่ 11 อธิบายส่วน Delegation (การมอบหมาย) ของมัน และขั้นตอนที่ 5 อธิบายส่วน Code Review Rules (กฎการรีวิวโค้ด)
  3. สกิล คือโฟลเดอร์ที่มีไฟล์ SKILL.md อยู่ข้างใน: ~/.agents/skills สำหรับคุณ .agents/skills ในรีโพซิทอรีสำหรับรีโพซิทอรีนั้น และ /etc/codex/skills สำหรับแมคทั้งเครื่อง ขั้นตอนที่ 4, 6 และ 7 แต่ละขั้นวางสกิลไว้ที่นั่นหนึ่งอัน

🇨🇳 中文

在交给 Codex 任何东西之前,先看看它有什么。codex --help 打印它的命令和标志。其中三个和这一页有关:mcp 管理工具服务器(第 2 部分),exec 不带屏幕地运行 Codex(第 12 步),--worktree 在仓库的一份副本里启动会话(也是第 12 步)。帮助很长;读 Commands 那一列,以及 -s 和 -a 两个标志,它们就是第 2 步的两项设置。

Codex 在它自己的程序之外知道的一切,都来自三个地方,每个地方都是一个你能读的文件:

  1. 设置文件 ~/.codex/config.toml。它用 TOML 写成:一行 [name] 打开一张表,下面的行设定各个键。一个项目可以带自己的 .codex/config.toml,Codex 只在你把项目标记为可信之后才读它。第 4、5、8、9、10、11 步写进这个文件,第 13 步把它打印出来。
  2. 指令,放在名为 AGENTS.md 的文件里。Codex 先读 ~/.codex/AGENTS.md,再读仓库根文件夹的 AGENTS.md,再读从那里往下直到你所在文件夹的每一个,并按这个顺序拼接起来,所以最近的文件说了算。加起来到 32 KiB 为止,除非你调高 project_doc_max_bytes。下面这个文件是这一页的实验文件夹带的那个;第 11 步解释它的 Delegation(委派)部分,第 5 步解释它的 Code Review Rules(代码审查规则)。
  3. 技能,是里面有一个 SKILL.md 文件的文件夹:~/.agents/skills 是你自己的,仓库里的 .agents/skills 属于那个仓库,/etc/codex/skills 属于整台 Mac。第 4、6、7 步各放一个进去。
codex --help

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

Codex CLI

If no subcommand is specified, options will be forwarded to the interactive CLI.

Usage: codex [OPTIONS] [PROMPT]
       codex [OPTIONS] <COMMAND> [ARGS]

Commands:
  agents            Browse all agent sessions on the shared local app-server daemon
  exec              Run Codex non-interactively [aliases: e]
  review            Run a code review non-interactively
  login             Manage login
  logout            Remove stored authentication credentials
  mcp               Manage external MCP servers for Codex
  plugin            Manage Codex plugins
  app-server        [experimental] Run the app server or related tooling
  remote-control    [experimental] Manage the app-server daemon with remote control enabled
  app               Launch the Desktop app (opens the app installer if missing)
  completion        Generate shell completion scripts
  update            Update Codex to the latest version
  doctor            Diagnose local Codex installation, config, auth, and runtime health
  sandbox           Run commands within a Codex-provided sandbox
  debug             Debugging tools
  apply             Apply the latest diff produced by Codex agent as a `git apply` to your local
                    working tree [aliases: a]
  resume            Resume a previous interactive session (picker by default; use --last to continue
                    the most recent)
  queue             Queue a message for an existing session
  archive           Archive a saved session by id or session name
  delete            Permanently delete a saved session by id or session name
  migrate-rollouts  Inspect or migrate legacy local sessions to paginated thread history
  unarchive         Unarchive a saved session by id or session name
  fork              Fork a previous interactive session (picker by default; use --last to fork the
                    most recent)
  cloud             [EXPERIMENTAL] Browse tasks from Codex Cloud and apply changes locally
  exec-server       [EXPERIMENTAL] Run the standalone exec-server service
  features          Inspect feature flags
  help              Print this message or the help of the given subcommand(s)

Arguments:
  [PROMPT]
          Optional user prompt to start the session

Options:
  -c, --config <key=value>
          Override a configuration value that would otherwise be loaded from `~/.codex/config.toml`.
          Use a dotted path (`foo.bar.baz`) to override nested values. The `value` portion is parsed
          as TOML. If it fails to parse as TOML, the raw string is used as a literal.
          
          Examples: - `-c model="o3"` - `-c 'sandbox_permissions=["disk-full-read-access"]'` - `-c
          shell_environment_policy.inherit=all`

      --enable <FEATURE>
          Enable a feature (repeatable). Equivalent to `-c features.<name>=true`

      --disable <FEATURE>
          Disable a feature (repeatable). Equivalent to `-c features.<name>=false`

      --remote <ADDR>
          Connect the TUI to a remote app server endpoint.
          
          Accepted forms: `ws://host:port`, `wss://host:port`, `unix://`, or `unix://PATH`.

      --remote-auth-token-env <ENV_VAR>
          Name of the environment variable containing the bearer token to send to a remote app
          server websocket

      --strict-config
          Error out when config.toml contains fields that are not recognized by this version of
          Codex

  -i, --image <FILE>...
          Optional image(s) to attach to the initial prompt

  -m, --model <MODEL>
          Model the agent should use

      --oss
          Use open-source provider

      --local-provider <OSS_PROVIDER>
          Specify which local provider to use (lmstudio or ollama). If not specified with --oss,
          will use config default or show selection

  -p, --profile <CONFIG_PROFILE_V2>
          Layer $CODEX_HOME/<name>.config.toml on top of the base user config

  -s, --sandbox <SANDBOX_MODE>
          Select the sandbox policy to use when executing model-generated shell commands
          
          [possible values: read-only, workspace-write, danger-full-access]

      --approve-for-me
          Route approval requests through automatic review using the workspace-write sandbox

      --dangerously-bypass-approvals-and-sandbox
          Skip all confirmation prompts and execute commands without sandboxing. EXTREMELY
          DANGEROUS. Intended solely for running in environments that are externally sandboxed

      --dangerously-bypass-hook-trust
          Run enabled hooks without requiring persisted hook trust for this invocation. DANGEROUS.
          Intended only for automation that already vets hook sources

  -C, --cd <DIR>
          Tell the agent to use the specified directory as its working root

      --worktree
          Run the session in a new managed Git worktree

      --add-dir <DIR>
          Additional directories that should be writable alongside the primary workspace

  -a, --ask-for-approval <APPROVAL_POLICY>
          Configure when the model requires human approval before executing a command

          Possible values:
          - on-request: The model decides when to ask the user for approval
          - never:      Never ask for user approval Execution failures are immediately returned to
            the model

      --search
          Enable live web search. When enabled, the native Responses `web_search` tool is available
          to the model (no per‑call approval)

      --no-alt-screen
          Disable alternate screen mode
          
          Runs the TUI in inline mode, preserving terminal scrollback history.

      --no-daemon
          Run without the shared background server, even if it is already running

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

AGENTS.md

# AGENTS.md: how to work in this folder

This folder is a lab for studying Mac apps that were downloaded and not installed.
The project's NotebookLM notebook id is NOTEBOOK_ID (replace with your own).

## Tools

- Use the REA tools to read an app. Never start the app you study.
- Use the `notebooklm` command to file reports and ask the notebook questions.
  Always pass `--notebook NOTEBOOK_ID --json`.
- Commit with `git`; open pull requests with `gh pr create`. Never push to main.

## Delegation

- A task that reads several apps, or several parts of one app, is split: spawn
  one `reader` subagent per app or part, wait for all of them, then write one
  summary.
- Spawn the `librarian` subagent to file a finished report in the notebook.
- Before a pull request, spawn the `reviewer` subagent on the branch and fix
  what it finds.
- Do not run two subagents that write to the same file.

## Code Review Rules

- Flag any script that runs or installs the app it studies.
- Flag any file that prints a path inside the home folder, or a token.
WHY FILES A setting you click in a window is on one Mac, and nobody can see what you clicked. A file can be read by the next person, copied to the next Mac, and kept in git with the project it belongs to. That is why every step on this page ends with a file, and why step 13 can print the whole of your setup at once.It is also why this page could be run on a Mac that nobody sat at.การตั้งค่าที่คุณคลิกในหน้าต่างอยู่บนแมคเครื่องเดียว และไม่มีใครเห็นว่าคุณคลิกอะไร ไฟล์ให้คนถัดไปอ่านได้ คัดลอกไปแมคเครื่องถัดไปได้ และเก็บใน git คู่กับโปรเจกต์ที่มันสังกัดได้ นั่นคือเหตุผลที่ทุกขั้นตอนในหน้านี้จบลงด้วยไฟล์ และเหตุผลที่ขั้นตอนที่ 13 พิมพ์การตั้งค่าทั้งหมดของคุณออกมาพร้อมกันได้และนั่นคือเหตุผลที่หน้านี้รันบนแมคที่ไม่มีใครนั่งอยู่ได้在窗口里点出来的设置只在一台 Mac 上,而且没有人看得见你点了什么。文件可以让下一个人读,复制到下一台 Mac,和它所属的项目一起放进 git。这就是为什么这一页的每一步都以一个文件收尾,也是为什么第 13 步能一次打印出你的全部设置。这也是为什么这一页可以在一台没有人坐在前面的 Mac 上运行。

🧠 Quiz 3🧠 แบบทดสอบ 3🧠 小测验 3

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

Your ~/.codex/AGENTS.md says to answer in English, and the AGENTS.md of the folder you are working in says to answer in Chinese. Which wins?~/.codex/AGENTS.md ของคุณบอกให้ตอบเป็นภาษาอังกฤษ และ AGENTS.md ของโฟลเดอร์ที่คุณทำงานอยู่บอกให้ตอบเป็นภาษาจีน อันไหนชนะ你的 ~/.codex/AGENTS.md 说用英语回答,而你正在工作的文件夹里的 AGENTS.md 说用中文回答。听哪个的?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

Codex reads ~/.codex/AGENTS.md first, then each AGENTS.md from the repository's root down to your folder, and joins them in that order, so the nearest file has the last word.Codex อ่าน ~/.codex/AGENTS.md ก่อน แล้วอ่าน AGENTS.md แต่ละไฟล์จากโฟลเดอร์รากของรีโพซิทอรีลงมาจนถึงโฟลเดอร์ของคุณ และต่อกันตามลำดับนั้น ไฟล์ที่ใกล้ที่สุดจึงได้พูดเป็นคนสุดท้ายCodex 先读 ~/.codex/AGENTS.md,再从仓库的根文件夹一路往下读到你的文件夹里的每个 AGENTS.md,按这个顺序连起来,所以最近的那个文件说了算。

🔧 Part 2 — Give Codex its tools · ส่วนที่ 2 — มอบเครื่องมือให้ Codex · 第 2 部分 —— 给 Codex 工具

🇬🇧 English

An agent reaches a tool in one of two ways. The first is a tool server: a separate program that offers a list of tools, each with a name and the arguments it takes. Codex starts the server, reads the list, and calls a tool when it needs one. The rules for this are called MCP, the Model Context Protocol, and codex mcp is the command that registers a server. REA is such a server, and so are GitHub's, Playwright's, Chrome DevTools' and Context7's.

The second way is a skill: a page of instructions that tells Codex when to use a command that is already on your Mac, and how. Codex reads the page, then types the commands itself. The NotebookLM command comes with such a page, and in step 7 you write one.

Both end as files, so step 13 can show them side by side.

🇹🇭 ไทย

เอเจนต์เข้าถึงเครื่องมือได้สองทาง ทางแรกคือเซิร์ฟเวอร์เครื่องมือ: โปรแกรมแยกต่างหากที่เสนอรายการเครื่องมือ แต่ละอันมีชื่อและอาร์กิวเมนต์ที่มันรับ Codex เริ่มเซิร์ฟเวอร์ อ่านรายการ และเรียกเครื่องมือเมื่อต้องการ กฎของเรื่องนี้เรียกว่า MCP หรือ Model Context Protocol และ codex mcp คือคำสั่งที่ลงทะเบียนเซิร์ฟเวอร์ REA คือเซิร์ฟเวอร์แบบนี้ ของ GitHub, ของ Playwright, ของ Chrome DevTools และของ Context7 ก็เช่นกัน

ทางที่สองคือสกิล: หน้าคำสั่งที่บอก Codex ว่าเมื่อไรควรใช้คำสั่งที่มีอยู่บนแมคของคุณแล้ว และใช้อย่างไร Codex อ่านหน้านั้น แล้วพิมพ์คำสั่งเอง คำสั่ง NotebookLM มาพร้อมหน้าแบบนี้ และในขั้นตอนที่ 7 คุณเขียนเอง

ทั้งสองทางลงเอยเป็นไฟล์ ขั้นตอนที่ 13 จึงแสดงมันเคียงข้างกันได้

🇨🇳 中文

智能体触及工具有两种方式。第一种是工具服务器:一个单独的程序,提供一张工具清单,每个工具有名字和它接受的参数。Codex 启动这个服务器,读清单,需要时就调用某个工具。这套规则叫 MCP,即 Model Context Protocol,codex mcp 就是注册服务器的命令。REA 是这样的服务器,GitHub 的、Playwright 的、Chrome DevTools 的和 Context7 的也是。

第二种是技能:一页说明,告诉 Codex 什么时候该用一条你 Mac 上已经有的命令,以及怎么用。Codex 读这一页,然后自己输入命令。NotebookLM 的命令自带这样一页,第 7 步你自己写一页。

两种最后都是文件,所以第 13 步能把它们并排展示出来。

4 REA: a tool server · REA: เซิร์ฟเวอร์เครื่องมือ · REA:一个工具服务器

🇬🇧 English

First ask what codex mcp can do. It has six commands, and add, list and remove are the three you will use. add takes either a command that starts a server on your Mac, after --, or a --url for a server on the internet. login is for servers that ask you to sign in.

REA registers itself, so you do not type the add. Step 10 of the REA page has a Python file, agent_setup.py, which runs REA's own setup command for the agent you name. Copy that file into a folder where REA is installed (step 1 of the REA page: npm install rea-agents@6.3.0), and run it for Codex. It writes two things, and says so: a table [mcp_servers.rea] in the settings file, with the command that starts REA's server, and REA's skill in ~/.agents/skills, which tells Codex when to reach for REA's tools.

After this, in a Codex session, you can type Use REA to read the card that Rectangle.app carries, and Codex calls REA's inspect-plist tool itself. That needs a signed-in Codex, so it was not run for this page; the REA page shows what the tool prints when you call it by hand.

🇹🇭 ไทย

ถามก่อนว่า codex mcp ทำอะไรได้ มันมีหกคำสั่ง และ add, list กับ remove คือสามคำสั่งที่คุณจะใช้ add รับได้ทั้งคำสั่งที่เริ่มเซิร์ฟเวอร์บนแมคของคุณ ซึ่งอยู่หลัง -- หรือ --url สำหรับเซิร์ฟเวอร์บนอินเทอร์เน็ต login มีไว้สำหรับเซิร์ฟเวอร์ที่ขอให้คุณลงชื่อเข้าใช้

REA ลงทะเบียนตัวเอง คุณจึงไม่ต้องพิมพ์ add ขั้นตอนที่ 10 ของหน้า REA มีไฟล์ Python ชื่อ agent_setup.py ซึ่งรันคำสั่งตั้งค่าของ REA เองสำหรับเอเจนต์ที่คุณระบุชื่อ คัดลอกไฟล์นั้นไปไว้ในโฟลเดอร์ที่ติดตั้ง REA แล้ว (ขั้นตอนที่ 1 ของหน้า REA: npm install rea-agents@6.3.0) แล้วรันมันสำหรับ Codex มันเขียนสองสิ่ง และบอกไว้ด้วย: ตาราง [mcp_servers.rea] ในไฟล์ตั้งค่า พร้อมคำสั่งที่เริ่มเซิร์ฟเวอร์ของ REA และสกิลของ REA ใน ~/.agents/skills ซึ่งบอก Codex ว่าเมื่อไรควรหยิบเครื่องมือของ REA

หลังจากนี้ ในเซสชันของ Codex คุณพิมพ์ได้ว่า Use REA to read the card that Rectangle.app carries (ใช้ REA อ่านบัตรที่ Rectangle.app พกอยู่) และ Codex จะเรียกเครื่องมือ inspect-plist ของ REA เอง นั่นต้องใช้ Codex ที่ลงชื่อเข้าใช้แล้ว จึงไม่ได้รันสำหรับหน้านี้ หน้า REA แสดงสิ่งที่เครื่องมือนั้นพิมพ์ออกมาเมื่อคุณเรียกมันด้วยมือ

🇨🇳 中文

先问问 codex mcp 能做什么。它有六条命令,add、list 和 remove 是你会用到的三条。add 接受两种东西:-- 之后的一条在你 Mac 上启动服务器的命令,或者一个指向互联网上某个服务器的 --url。login 是给那些要你登录的服务器用的。

REA 会自己注册,所以这个 add 不用你打。REA 那一页的第 10 步有一个 Python 文件 agent_setup.py,它为你指定的智能体运行 REA 自己的设置命令。把那个文件复制到一个装了 REA 的文件夹里(REA 那一页的第 1 步:npm install rea-agents@6.3.0),为 Codex 运行它。它写两样东西,并且会说出来:设置文件里的一张表 [mcp_servers.rea],带着启动 REA 服务器的命令;以及 ~/.agents/skills 里 REA 的技能,它告诉 Codex 什么时候该去拿 REA 的工具。

这之后,在 Codex 的会话里你可以输入 Use REA to read the card that Rectangle.app carries(用 REA 读 Rectangle.app 随身带的卡片),Codex 会自己调用 REA 的 inspect-plist 工具。这需要一个已登录的 Codex,所以这一页没有运行它;REA 那一页展示了你亲手调用这个工具时它打印的内容。

codex mcp --help

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

Manage external MCP servers for Codex

Usage: codex mcp [OPTIONS] <COMMAND>

Commands:
  list    
  get     
  add     
  remove  
  login   
  logout  
  help    Print this message or the help of the given subcommand(s)

Options:
  -c, --config <key=value>
          Override a configuration value that would otherwise be loaded from `~/.codex/config.toml`.
          Use a dotted path (`foo.bar.baz`) to override nested values. The `value` portion is parsed
          as TOML. If it fails to parse as TOML, the raw string is used as a literal.
          
          Examples: - `-c model="o3"` - `-c 'sandbox_permissions=["disk-full-read-access"]'` - `-c
          shell_environment_policy.inherit=all`

      --enable <FEATURE>
          Enable a feature (repeatable). Equivalent to `-c features.<name>=true`

      --disable <FEATURE>
          Disable a feature (repeatable). Equivalent to `-c features.<name>=false`

  -h, --help
          Print help (see a summary with '-h')
python3 agent_setup.py codex --yes

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

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

🧠 Quiz 4🧠 แบบทดสอบ 4🧠 小测验 4

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

codex mcp add takes either a command after --, or --url. When do you use --url?codex mcp add รับได้ทั้งคำสั่งหลัง -- หรือ --url คุณใช้ --url เมื่อไรcodex mcp add 后面可以跟 -- 加一条命令,也可以跟 --url。什么时候用 --url?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

A command after -- starts a server on your Mac. --url points Codex at a server that already runs on the internet, as step 5 does for GitHub.คำสั่งหลัง -- เริ่มเซิร์ฟเวอร์บนแมคของคุณ ส่วน --url ชี้ Codex ไปที่เซิร์ฟเวอร์ที่รันอยู่บนอินเทอร์เน็ตแล้ว อย่างที่ขั้นตอนที่ 5 ทำกับ GitHub-- 后面的命令会在你的 Mac 上启动一个服务器。--url 让 Codex 去找一个已经在互联网上运行的服务器,第 5 步对 GitHub 就是这样做的。

5 GitHub: three ways in · GitHub: สามทางเข้า · GitHub:三条路

🇬🇧 English

Codex and GitHub meet in three places, and you can use any of them alone.

The shell. Codex runs commands, so if GitHub's own command, gh, is signed in, Codex can make a branch, push it and open a pull request by typing gh pr create, exactly as you would. Install it with brew install gh, sign in once with gh auth login, and check with gh auth status. GitHub's Mac had gh and nobody signed in, so the check said so.

A tool server. GitHub runs an MCP server on the internet, at the address below. Register it with codex mcp add and --url. The server wants a token, and --bearer-token-env-var names the environment variable Codex reads it from, so the token itself never goes into the settings file. Make a fine-grained token on GitHub, and set GITHUB_TOKEN in the terminal before you start Codex. With this, Codex has tools named for GitHub's things: issues, pull requests, files, without going through the shell. codex mcp list then shows both servers, REA and GitHub.

GitHub's own side. Codex also lives on OpenAI's servers, and can be connected to a repository in Codex's settings on the web. Then, on a pull request, the comment @codex review makes Codex read the change and post a review; it only reports the two most serious grades of problem. @codex security review looks for security problems. @codex fix the P1 issue starts a task that can push a fix to the branch, when it has permission. A section named Code Review Rules in the repository's AGENTS.md tells it what to look for; the AGENTS.md of step 3 has one. For a review on every pull request without a comment, turn on Automatic review in Codex's settings; that needs push or admin permission on the repository. There is also a GitHub Action, openai/codex-action, which runs Codex inside a workflow with an API key kept as a repository secret. None of this runs on your Mac, so nothing of it was run for this page.

🇹🇭 ไทย

Codex กับ GitHub พบกันในสามที่ และคุณใช้ที่ใดที่หนึ่งเพียงอย่างเดียวก็ได้

เชลล์ Codex รันคำสั่ง ดังนั้นถ้าคำสั่งของ GitHub เอง คือ gh ลงชื่อเข้าใช้แล้ว Codex ก็สร้างสาขา พุชมัน และเปิด pull request ได้ด้วยการพิมพ์ gh pr create เหมือนที่คุณทำทุกประการ ติดตั้งด้วย brew install gh ลงชื่อเข้าใช้ครั้งเดียวด้วย gh auth login และตรวจด้วย gh auth status แมคของ GitHub มี gh และไม่มีใครลงชื่อเข้าใช้ การตรวจจึงบอกอย่างนั้น

เซิร์ฟเวอร์เครื่องมือ GitHub รันเซิร์ฟเวอร์ MCP บนอินเทอร์เน็ต ที่ที่อยู่ข้างล่าง ลงทะเบียนมันด้วย codex mcp add และ --url เซิร์ฟเวอร์ต้องการโทเค็น และ --bearer-token-env-var ระบุชื่อตัวแปรสภาพแวดล้อมที่ Codex อ่านโทเค็นจากมัน ตัวโทเค็นเองจึงไม่เคยเข้าไปอยู่ในไฟล์ตั้งค่า สร้างโทเค็นแบบ fine-grained บน GitHub แล้วตั้ง GITHUB_TOKEN ในเทอร์มินัลก่อนเริ่ม Codex ด้วยวิธีนี้ Codex มีเครื่องมือที่ตั้งชื่อตามสิ่งต่าง ๆ ของ GitHub: issue, pull request, ไฟล์ โดยไม่ต้องผ่านเชลล์ จากนั้น codex mcp list แสดงเซิร์ฟเวอร์ทั้งสอง คือ REA และ GitHub

ฝั่งของ GitHub เอง Codex ยังอยู่บนเซิร์ฟเวอร์ของ OpenAI ด้วย และเชื่อมกับรีโพซิทอรีได้ในการตั้งค่าของ Codex บนเว็บ จากนั้น บน pull request ความคิดเห็น @codex review ทำให้ Codex อ่านการเปลี่ยนแปลงและโพสต์รีวิว มันรายงานเฉพาะปัญหาสองระดับที่ร้ายแรงที่สุด @codex security review มองหาปัญหาความปลอดภัย @codex fix the P1 issue เริ่มงานที่พุชการแก้ไขกลับไปที่สาขาได้ เมื่อมีสิทธิ์ ส่วนที่ชื่อ Code Review Rules ใน AGENTS.md ของรีโพซิทอรีบอกมันว่าต้องมองหาอะไร AGENTS.md ของขั้นตอนที่ 3 มีส่วนนี้อยู่ สำหรับรีวิวทุก pull request โดยไม่ต้องมีความคิดเห็น ให้เปิด Automatic review (รีวิวอัตโนมัติ) ในการตั้งค่าของ Codex ซึ่งต้องมีสิทธิ์ push หรือ admin บนรีโพซิทอรี ยังมี GitHub Action ชื่อ openai/codex-action ซึ่งรัน Codex ในเวิร์กโฟลว์ด้วยคีย์ API ที่เก็บเป็นความลับของรีโพซิทอรี ไม่มีอะไรในนี้รันบนแมคของคุณ จึงไม่ได้รันอะไรในนี้สำหรับหน้านี้

🇨🇳 中文

Codex 和 GitHub 在三个地方相遇,你可以只用其中任何一个。

Shell。Codex 运行命令,所以如果 GitHub 自己的命令 gh 已经登录,Codex 就能像你一样,输入 gh pr create 来建分支、推上去、开 pull request。用 brew install gh 安装它,用 gh auth login 登录一次,用 gh auth status 检查。GitHub 的 Mac 上有 gh 而没有人登录,所以检查就是这么说的。

工具服务器。GitHub 在互联网上运行着一个 MCP 服务器,地址在下面。用 codex mcp add 和 --url 注册它。这个服务器要一个令牌,--bearer-token-env-var 给出 Codex 读取令牌的环境变量的名字,所以令牌本身永远不会进设置文件。在 GitHub 上做一个细粒度令牌,启动 Codex 之前在终端里设好 GITHUB_TOKEN。有了这个,Codex 就有了以 GitHub 的事物命名的工具:issue、pull request、文件,不必经过 shell。然后 codex mcp list 会显示两个服务器,REA 和 GitHub。

GitHub 自己那一侧。Codex 也住在 OpenAI 的服务器上,可以在网页版 Codex 的设置里连到一个仓库。之后,在 pull request 上,评论 @codex review 让 Codex 读这次改动并发一条审查;它只报告最严重的两个等级的问题。@codex security review 找安全问题。@codex fix the P1 issue 启动一个任务,在有权限时可以把修复推回分支。仓库 AGENTS.md 里名为 Code Review Rules 的一节告诉它要找什么;第 3 步的 AGENTS.md 就有这样一节。要让每个 pull request 不用评论就得到审查,在 Codex 的设置里打开 Automatic review(自动审查);这需要对仓库有 push 或 admin 权限。还有一个 GitHub Action,openai/codex-action,它在工作流里用一个存为仓库机密的 API 密钥运行 Codex。这些都不在你的 Mac 上运行,所以这一页一样也没有运行。

not run by usเราไม่ได้รันเอง我们没有运行过

brew install gh
gh auth login
gh auth status

what it printed, on a Mac where nobody signed inสิ่งที่มันพิมพ์ออกมา บนแมคที่ไม่มีใครลงชื่อเข้าใช้它打印的内容,在一台没有人登录的 Mac 上

You are not logged into any GitHub hosts. To log in, run: gh auth login
codex mcp add github --url https://api.githubcopilot.com/mcp/ --bearer-token-env-var GITHUB_TOKEN

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

Added global MCP server 'github'.
codex mcp list

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

Name  Command  Args                     Env  Cwd  Status   Auth       
rea   npx      -y rea-agents@6.3.0 mcp  -    -    enabled  Unsupported

Name    Url                                 Bearer Token Env Var  Status   Auth        
github  https://api.githubcopilot.com/mcp/  GITHUB_TOKEN          enabled  Bearer token
CAREFUL A GitHub token is a password with a list of doors. Make it fine-grained: one repository, and only the permissions the work needs, usually contents and pull requests. Keep it in the environment variable, never in config.toml, never in a file in the repository. Whatever Codex can do with the token, a mistake of Codex's can do too; that is the reason for the sandbox and the approvals of step 2.โทเค็นของ GitHub คือรหัสผ่านที่มีรายการประตู ทำให้มันเป็นแบบ fine-grained: รีโพซิทอรีเดียว และเฉพาะสิทธิ์ที่งานต้องใช้ โดยปกติคือ contents กับ pull requests เก็บมันในตัวแปรสภาพแวดล้อม ไม่ใช่ใน config.toml และไม่ใช่ในไฟล์ในรีโพซิทอรีเด็ดขาด อะไรก็ตามที่ Codex ทำได้ด้วยโทเค็น ความผิดพลาดของ Codex ก็ทำได้เช่นกัน นั่นคือเหตุผลของแซนด์บ็อกซ์และการอนุมัติในขั้นตอนที่ 2GitHub 令牌是一把带着门清单的密码。把它做成细粒度的:只对一个仓库,只给工作需要的权限,通常是 contents 和 pull requests。把它放在环境变量里,永远不放进 config.toml,永远不放进仓库里的文件。Codex 用这个令牌能做的事,Codex 的一个失误也能做;这正是第 2 步里沙箱和审批存在的理由。

🧠 Quiz 5🧠 แบบทดสอบ 5🧠 小测验 5

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

Why does step 5 give --bearer-token-env-var GITHUB_TOKEN instead of the token itself?ทำไมขั้นตอนที่ 5 ถึงใส่ --bearer-token-env-var GITHUB_TOKEN แทนตัวโทเค็นเอง为什么第 5 步给的是 --bearer-token-env-var GITHUB_TOKEN,而不是令牌本身?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

Codex reads the token from the variable GITHUB_TOKEN when it starts. The settings file holds only the variable's name, so the file is safe to show, as step 13 does.Codex อ่านโทเค็นจากตัวแปร GITHUB_TOKEN ตอนที่มันเริ่ม ไฟล์ตั้งค่าเก็บแค่ชื่อของตัวแปร ไฟล์จึงแสดงให้ดูได้อย่างปลอดภัย อย่างที่ขั้นตอนที่ 13 ทำCodex 启动时从变量 GITHUB_TOKEN 里读令牌。设置文件里只有变量的名字,所以这个文件可以放心给人看,第 13 步就是这样做的。

6 NotebookLM: a command and its skill · NotebookLM: คำสั่งหนึ่งและสกิลของมัน · NotebookLM:一条命令和它的技能

🇬🇧 English

NotebookLM is Google's research notebook: you give it sources, and it answers questions from them with citations. It has no public API. notebooklm-py is a free Python package that drives it anyway, through the same calls the web page makes, and it comes with a skill written for agents. So the wiring here is the second kind of part 2: a command on your Mac, and a page that teaches Codex to use it.

Install the package in a virtual environment of its own, as the first two commands do: the Python that comes with a Mac refuses to install packages into itself. Then sign in with the third command. It opens a browser for your Google account and waits for you, so it is yours to run; it was not run on GitHub's Mac. The fourth command installs the skill into ~/.agents/skills, where step 3 said Codex looks, and the fifth checks it. --target agents chooses that folder; without it the package also writes a copy for Claude Code.

One thing to know before you start Codex. The skill tells Codex to type notebooklm, the short name, so that name has to work in the terminal you start Codex from. Activate the environment first, with source venv/bin/activate, and start Codex in that same terminal.

The package also carries a page of advice for Codex, printed by the sixth command. The part that matters is at its end: pass the notebook's id with --notebook on every command, and ask for --json, so that several agents can use the notebook at once without stepping on each other. Step 11's librarian agent is written from that advice.

🇹🇭 ไทย

NotebookLM คือสมุดค้นคว้าของ Google: คุณให้แหล่งข้อมูลกับมัน แล้วมันตอบคำถามจากแหล่งเหล่านั้นพร้อมการอ้างอิง มันไม่มี API สาธารณะ notebooklm-py คือแพ็กเกจ Python ฟรีที่ควบคุมมันได้อยู่ดี ผ่านการเรียกแบบเดียวกับที่หน้าเว็บใช้ และมันมาพร้อมสกิลที่เขียนไว้สำหรับเอเจนต์ การเดินสายในที่นี้จึงเป็นแบบที่สองของส่วนที่ 2: คำสั่งบนแมคของคุณ และหน้าที่สอน Codex ให้ใช้มัน

ติดตั้งแพ็กเกจในสภาพแวดล้อมเสมือนของมันเอง อย่างที่สองคำสั่งแรกทำ: Python ที่มากับแมคปฏิเสธที่จะติดตั้งแพ็กเกจลงในตัวมันเอง จากนั้นลงชื่อเข้าใช้ด้วยคำสั่งที่สาม มันเปิดเบราว์เซอร์สำหรับบัญชี Google ของคุณแล้วรอคุณ จึงเป็นคำสั่งที่คุณต้องรันเอง มันไม่ได้ถูกรันบนแมคของ GitHub คำสั่งที่สี่ติดตั้งสกิลลงใน ~/.agents/skills ที่ขั้นตอนที่ 3 บอกว่า Codex มองหา และคำสั่งที่ห้าตรวจมัน --target agents เลือกโฟลเดอร์นั้น ถ้าไม่ใส่ แพ็กเกจจะเขียนสำเนาสำหรับ Claude Code ด้วย

สิ่งหนึ่งที่ควรรู้ก่อนเริ่ม Codex สกิลบอกให้ Codex พิมพ์ notebooklm ซึ่งเป็นชื่อสั้น ชื่อนั้นจึงต้องใช้ได้ในเทอร์มินัลที่คุณเริ่ม Codex เปิดใช้งานสภาพแวดล้อมก่อนด้วย source venv/bin/activate แล้วเริ่ม Codex ในเทอร์มินัลเดียวกันนั้น

แพ็กเกจยังพกหน้าคำแนะนำสำหรับ Codex มาด้วย ซึ่งคำสั่งที่หกพิมพ์ออกมา ส่วนที่สำคัญอยู่ท้ายสุด: ส่ง id ของสมุดด้วย --notebook ในทุกคำสั่ง และขอ --json เพื่อให้เอเจนต์หลายตัวใช้สมุดพร้อมกันได้โดยไม่เหยียบกันเอง เอเจนต์บรรณารักษ์ของขั้นตอนที่ 11 เขียนขึ้นจากคำแนะนำนั้น

🇨🇳 中文

NotebookLM 是 Google 的研究笔记本:你给它来源,它从这些来源回答问题并附上引用。它没有公开的 API。notebooklm-py 是一个免费的 Python 包,照样能驱动它,用的是网页本身发出的那些调用,而且它自带一个为智能体写的技能。所以这里的接线是第 2 部分的第二种:你 Mac 上的一条命令,加一页教 Codex 使用它的说明。

像前两条命令那样,把这个包装进它自己的虚拟环境:Mac 自带的 Python 拒绝往自己里面装包。然后用第三条命令登录。它为你的 Google 账户打开一个浏览器并等你,所以这条要你自己运行;GitHub 的 Mac 上没有运行它。第四条命令把技能装进 ~/.agents/skills,就是第 3 步说 Codex 会去找的地方,第五条检查它。--target agents 选的就是那个文件夹;不加的话,这个包还会为 Claude Code 写一份。

启动 Codex 之前要知道一件事。技能让 Codex 输入的是短名字 notebooklm,所以这个名字必须在你启动 Codex 的那个终端里能用。先用 source venv/bin/activate 激活环境,再在同一个终端里启动 Codex。

这个包还带着一页给 Codex 的建议,第六条命令把它打印出来。要紧的部分在末尾:每条命令都用 --notebook 传笔记本的 id,并要求 --json,这样几个智能体可以同时用这个笔记本而不互相踩到。第 11 步的图书管理员智能体就是照这条建议写的。

python3 -m venv venv
venv/bin/python -m pip install notebooklm-py==0.4.0

not run by us: it opens a browser and waits for youเราไม่ได้รันเอง: มันเปิดเบราว์เซอร์แล้วรอคุณ我们没有运行过:它会打开浏览器并等你

venv/bin/python -m notebooklm login
venv/bin/python -m notebooklm skill install --target agents
venv/bin/python -m notebooklm skill status

what the two commands printedสิ่งที่สองคำสั่งพิมพ์ออกมา两条命令打印的内容

Installed NotebookLM skill
  Version: 0.4.0
  Scope:   user
  Agent Skills: /Users/runner/.agents/skills/notebooklm/SKILL.md

NotebookLM commands are now available in the selected skill directories.
NotebookLM skill status (user scope)
  CLI version: 0.4.0
  Claude Code: Not installed
    Path: /Users/runner/.claude/skills/notebooklm/SKILL.md
  Agent Skills: Installed
    Path: /Users/runner/.agents/skills/notebooklm/SKILL.md
    Skill version: 0.4.0
venv/bin/python -m notebooklm agent show codex

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

# Repository Guidelines

**Status:** Active
**Last Updated:** 2026-05-09

## Project Structure & Module Organization

`src/notebooklm/` contains the async client and typed APIs. Internal feature 
modules use `_` prefixes such as `_sources.py` and `_artifacts.py`; 
`src/notebooklm/cli/` holds Click commands, and `src/notebooklm/rpc/` handles 
protocol encoding and decoding. Tests are split by scope: `tests/unit/`, 
`tests/integration/`, and `tests/e2e/`. Recorded HTTP fixtures live in 
`tests/cassettes/`. Examples are in `docs/examples/`, and diagnostics live in 
`scripts/`.

## Build, Test, and Development Commands

Use `uv` for local work:

```bash
uv sync --extra dev --extra browser
uv run pytest
uv run ruff check src/ tests/
uv run ruff format src/ tests/
uv run mypy src/notebooklm
uv run pre-commit run --all-files
```

Run `uv run pytest tests/e2e -m readonly` only after `notebooklm login` and 
setting test notebook env vars.

## Coding Style & Naming Conventions

Target Python 3.10+, 4-space indentation, and double quotes. Ruff enforces 
formatting and import order with a 100-character line length. Keep module and 
test file names in `snake_case`; prefer descriptive Click command names that 
match existing groups such as `source`, `artifact`, and `research`. Preserve the
internal/public split: `_*.py` for implementation, exported types in 
`src/notebooklm/__init__.py`.

## Testing Guidelines

Put pure logic in `tests/unit/`, VCR-backed flows in `tests/integration/`, and 
authenticated NotebookLM coverage in `tests/e2e/`. Name tests 
`test_<behavior>.py` and record cassettes with `NOTEBOOKLM_VCR_RECORD=1 uv run 
pytest tests/integration/test_vcr_*.py -v`. Coverage is expected to stay at or 
above the configured 90% threshold.

## Commit, PR, and Agent Notes

Follow the existing commit style: `feat(cli): ...`, `fix(cli): ...`, 
`refactor(test): ...`, `style: ...`. PRs should include a short summary, linked 
issue when relevant, and the commands run locally.

For Codex or other parallel agents:

- Prefer `--json` output and pass explicit notebook IDs instead of relying on 
`notebooklm use`.
- Isolate concurrent runs with `NOTEBOOKLM_PROFILE=agent-<id>` so each agent 
gets its own context file under `~/.notebooklm/profiles/<name>/`. Fall back to 
`NOTEBOOKLM_HOME=/tmp/agent-<id>` only when separate home directories are 
required.
- In headless environments where Playwright login is impractical, authenticate 
with `notebooklm login --browser-cookies <browser>` (requires `pip install 
"notebooklm-py"`).

🇬🇧 English

What does Codex do with the skill? It types commands like the one below. This one was run on our laptop, signed in, against the notebook that holds Codex's own manual, the one this page was checked against. The question is about Codex, and the answer comes back with numbered citations into the manual's pages. In a session, Codex reads such an answer and goes on from it; with --save-as-note it can keep the answer in the notebook as well.

🇹🇭 ไทย

Codex ทำอะไรกับสกิล? มันพิมพ์คำสั่งแบบคำสั่งข้างล่าง คำสั่งนี้ถูกรันบนแล็ปท็อปของเรา ซึ่งลงชื่อเข้าใช้แล้ว กับสมุดที่เก็บคู่มือของ Codex เอง สมุดเดียวกับที่ใช้ตรวจหน้านี้ คำถามเป็นเรื่อง Codex และคำตอบกลับมาพร้อมการอ้างอิงแบบมีหมายเลขไปยังหน้าต่าง ๆ ของคู่มือ ในเซสชัน Codex อ่านคำตอบแบบนี้แล้วทำงานต่อจากมัน ด้วย --save-as-note มันเก็บคำตอบไว้ในสมุดได้ด้วย

🇨🇳 中文

Codex 拿这个技能做什么?它会输入像下面这样的命令。这一条是在我们自己的、已登录的笔记本电脑上运行的,对象是存着 Codex 自家手册的那个笔记本,也就是用来核对这一页的那个。问题是关于 Codex 的,答案带着编号的引用回来,指向手册的各页。在会话里,Codex 读到这样的答案就接着往下做;加上 --save-as-note,它还能把答案存进笔记本。

venv/bin/python -m notebooklm ask "In two short paragraphs: where does Codex look for skills on a Mac, and how does a session call one by name?" --notebook a156862e-29f0-493d-9759-cf0cad03d76a

what it printed, on our laptopสิ่งที่มันพิมพ์ออกมา บนแล็ปท็อปของเรา它打印的内容,在我们的笔记本电脑上

Different notebook specified, starting new conversation...
Continuing conversation 382e466f...
Answer:
On a Mac, Codex discovers skills across four hierarchical scopes: 
**repository-level** `.agents/skills` directories (scanned from the current 
working directory `$CWD` up to the repository root `$REPO_ROOT`), 
**user-level** `$HOME/.agents/skills` in the user's home folder, 
**admin-level** `/etc/codex/skills` for machine-wide system defaults, and 
**system-level** built-in skills bundled directly with Codex by OpenAI [1, 2].

A session calls a skill by name via **explicit invocation** by typing `$` 
followed by the skill name (or opening the `/skills` menu) in the Codex CLI or 
IDE extension [3]. Codex can also trigger a skill via **implicit invocation** 
by automatically matching a user prompt against the skill's `description` 
metadata defined in its `SKILL.md` file [3], after which it uses progressive 
disclosure to load the full instruction file into context [4].

💡 Would you like help writing a custom `SKILL.md` template or configuring 
local skill overrides?

Resumed conversation: 382e466f-21ac-4f11-a2d7-181bb2c0f1a5

🧠 Quiz 6🧠 แบบทดสอบ 6🧠 小测验 6

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

Why does step 6 install notebooklm-py in a virtual environment, venv?ทำไมขั้นตอนที่ 6 ถึงติดตั้ง notebooklm-py ในสภาพแวดล้อมเสมือน venv为什么第 6 步要把 notebooklm-py 装在虚拟环境 venv 里?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

A venv is a folder with a Python of its own, where you may install packages. The Mac's own Python refuses, to keep the system safe.venv คือโฟลเดอร์ที่มี Python ของตัวเอง ซึ่งคุณติดตั้งแพ็กเกจได้ Python ของแมคเองไม่ยอม เพื่อรักษาระบบให้ปลอดภัยvenv 是一个带着自己的 Python 的文件夹,你可以在里面装包。Mac 自己的 Python 不肯,是为了保护系统。

7 A skill of your own · สกิลของคุณเอง · 你自己的技能

🇬🇧 English

You have installed two skills that other people wrote. A skill is only a folder with a file named SKILL.md in it, so you can write one. The file opens with a header between two lines of ---, and the header must have two fields: name, which is how you call the skill, and description, which is how Codex decides on its own that a task fits the skill. Then the instructions, in plain English, as a numbered list where the order matters.

The skill below, mac-app-report, joins steps 4 and 6: it tells Codex to read a downloaded Mac app with REA's tools, write a short report, and file the report in the project's notebook with the notebooklm command. Three things in it are worth copying into any skill you write. The description says when to use it and gives the words a person would say. Each step names the exact tool or command. And the rules at the end say what never to do, because an agent that is not told will try.

skill_setup.py copies the skill into ~/.agents/skills and then lists every skill in that folder: the one you wrote, NotebookLM's from step 6, and REA's from step 4. In a session, call a skill by name with $mac-app-report Rectangle.app, or pick it from the list that /skills shows. Codex also picks a skill by itself when a task matches its description. It reads the folder on every start, so a changed skill needs a new session.

🇹🇭 ไทย

คุณติดตั้งสกิลสองอันที่คนอื่นเขียนไปแล้ว สกิลเป็นแค่โฟลเดอร์ที่มีไฟล์ชื่อ SKILL.md อยู่ข้างใน คุณจึงเขียนเองได้ ไฟล์เปิดด้วยส่วนหัวที่อยู่ระหว่างบรรทัด --- สองบรรทัด และส่วนหัวต้องมีสองช่อง: name คือชื่อที่คุณใช้เรียกสกิล และ description คือสิ่งที่ Codex ใช้ตัดสินใจเองว่างานหนึ่งเข้ากับสกิลนี้ จากนั้นคือคำสั่ง เป็นภาษาอังกฤษธรรมดา เป็นรายการมีหมายเลขที่ลำดับมีความหมาย

สกิลข้างล่าง mac-app-report รวมขั้นตอนที่ 4 กับ 6 เข้าด้วยกัน: มันบอก Codex ให้อ่านแอปแมคที่ดาวน์โหลดมาด้วยเครื่องมือของ REA เขียนรายงานสั้น ๆ และเก็บรายงานลงสมุดของโปรเจกต์ด้วยคำสั่ง notebooklm สามสิ่งในนั้นควรค่าแก่การลอกไปใส่ในสกิลทุกอันที่คุณเขียน คำอธิบายบอกว่าเมื่อไรควรใช้ และให้คำที่คนจะพูด แต่ละขั้นระบุชื่อเครื่องมือหรือคำสั่งที่แน่นอน และกฎท้ายสุดบอกว่าอะไรห้ามทำเด็ดขาด เพราะเอเจนต์ที่ไม่ได้ถูกบอกจะลองทำ

skill_setup.py คัดลอกสกิลลงใน ~/.agents/skills แล้วแสดงรายการสกิลทุกอันในโฟลเดอร์นั้น: อันที่คุณเขียน ของ NotebookLM จากขั้นตอนที่ 6 และของ REA จากขั้นตอนที่ 4 ในเซสชัน เรียกสกิลด้วยชื่อด้วย $mac-app-report Rectangle.app หรือเลือกจากรายการที่ /skills แสดง Codex ยังเลือกสกิลเองเมื่องานตรงกับคำอธิบายของมัน มันอ่านโฟลเดอร์ทุกครั้งที่เริ่ม สกิลที่แก้แล้วจึงต้องใช้เซสชันใหม่

🇨🇳 中文

你已经装了两个别人写的技能。技能只是一个里面有名为 SKILL.md 的文件的文件夹,所以你可以自己写一个。这个文件以两行 --- 之间的头部开始,头部必须有两个字段:name,是你调用这个技能时用的名字;description,是 Codex 自行判断某个任务是否适合这个技能的依据。然后是说明,用平常的英语,写成一个顺序有意义的编号列表。

下面这个技能 mac-app-report 把第 4 步和第 6 步连在一起:它让 Codex 用 REA 的工具读一个下载来的 Mac 应用,写一份短报告,再用 notebooklm 命令把报告归档到项目的笔记本里。里面有三样东西值得抄进你写的每一个技能。描述说明什么时候用它,并给出一个人会说的话。每一步都写明确切的工具或命令。结尾的规则写明永远不做什么,因为没被告知的智能体会去试。

skill_setup.py 把这个技能复制到 ~/.agents/skills,然后列出那个文件夹里的每个技能:你写的这个、第 6 步 NotebookLM 的、第 4 步 REA 的。在会话里,用 $mac-app-report Rectangle.app 按名字调用一个技能,或者从 /skills 显示的清单里挑。当任务和描述匹配时,Codex 也会自己挑技能。它每次启动都读这个文件夹,所以改过的技能需要一个新会话。

skills/mac-app-report/SKILL.md

---
name: mac-app-report
description: Write a short report on a Mac app that was downloaded but not installed, using the REA tools, and file it in the project's NotebookLM notebook. Use when asked to "report on this app", "study this .app" or "what is this app built on".
---

# Report on a Mac app

You are given the path of a `.app` folder. Do not start the app. Use the REA
tools to read it, and the `notebooklm` command to file what you wrote.

## Steps

1. `inspect-plist` on `Contents/Info.plist`: the app's name, identifier, version
   and the macOS it needs.
2. `inspect-signature`: who signed it, and the chain up to Apple's root.
3. `inspect-macho` on the program file named in the plist: the processors it is
   built for and the libraries it loads. Count the libraries that come from
   macOS and the ones the app brings along.
4. `decode-interface-builder` on each `.nib` under `Contents/Resources`: the
   windows and menus, and which code each menu item calls.
5. Write `report.md` beside the app, with one heading per step above. Under
   each heading, say what you found and which tool showed it. End with a list
   of what you could not find.
6. Add the report to the notebook: `notebooklm source add report.md --notebook
   NOTEBOOK_ID --json`, where NOTEBOOK_ID is in the project's `AGENTS.md`.
   Report the source id it returns.

## Rules

- Read only. Never edit the app, never run it, never run `open` on it.
- If a tool says a result was cut short, say so in the report.
- Quote the licence file of the app if there is one; stop and ask if it forbids
  reverse engineering.

🇬🇧 English

🐍 The Python in this file. import pathlib and import shutil bring in two of Python's own tools: one for paths, the names of files and folders, and one for copying. skills and name are variables: names that hold a value. pathlib.Path.home() is your home folder, and / joins names into a longer path. "skills/" + name joins two strings with +. shutil.copytree copies a whole folder. for folder in sorted(skills.iterdir()): is a for loop: iterdir() gives each thing in the folder, sorted() puts them in order, and the indented line runs once for each. Run the file in the folder where you saved it, because skills/ must sit beside it. Press Run: the page's pretend Mac has your skill and nothing else, because what steps 4 and 6 install runs only on a real Mac.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ import pathlib และ import shutil นำเครื่องมือของ Python เองเข้ามาสองตัว: ตัวหนึ่งสำหรับพาธ คือชื่อของไฟล์และโฟลเดอร์ และอีกตัวสำหรับคัดลอก skills และ name คือตัวแปร: ชื่อที่เก็บค่าไว้ pathlib.Path.home() คือโฟลเดอร์บ้านของคุณ และ / ต่อชื่อต่าง ๆ เป็นพาธที่ยาวขึ้น "skills/" + name ต่อสองสตริงด้วย + shutil.copytree คัดลอกทั้งโฟลเดอร์ for folder in sorted(skills.iterdir()): คือลูป for: iterdir() ให้ของแต่ละอย่างในโฟลเดอร์ sorted() เรียงมัน และบรรทัดที่ย่อหน้าทำงานหนึ่งครั้งต่อหนึ่งอย่าง รันไฟล์ในโฟลเดอร์ที่คุณบันทึกไว้ เพราะ skills/ ต้องอยู่ข้าง ๆ มัน กด Run: แมคจำลองของหน้านี้มีแค่สกิลของคุณ เพราะสิ่งที่ขั้นตอนที่ 4 และ 6 ติดตั้งรันได้บนแมคจริงเท่านั้น

🇨🇳 中文

🐍 这个文件里的 Python。import pathlib 和 import shutil 引进 Python 自带的两个工具:一个处理路径,也就是文件和文件夹的名字,一个负责复制。skills 和 name 是变量:装着一个值的名字。pathlib.Path.home() 是你的主文件夹,/ 把名字接成更长的路径。"skills/" + name 用 + 把两个字符串接起来。shutil.copytree 复制整个文件夹。for folder in sorted(skills.iterdir()): 是一个 for 循环:iterdir() 给出文件夹里的每样东西,sorted() 把它们排好顺序,缩进的那一行对每样东西运行一次。要在你保存这个文件的文件夹里运行它,因为 skills/ 必须在它旁边。按 Run:这一页的假装 Mac 里只有你的技能,因为第 4 和第 6 步安装的东西只能在真正的 Mac 上运行。

skill_setup.py

# skill_setup.py - put this page's skill where Codex looks for skills, then list them.
#
#     python3 skill_setup.py       (in the folder where you saved this file)
#
# A skill is a folder with a file named SKILL.md in it.
# Codex looks for your skills in the folder ~/.agents/skills.

import pathlib
import shutil

# Path.home() is your home folder, ~. The / joins folder names into a path.
skills = pathlib.Path.home() / ".agents" / "skills"
name = "mac-app-report"

# Copy the skill's folder from skills/, beside this file, into Codex's folder.
# dirs_exist_ok=True means: if it is there already, copy over it.
shutil.copytree("skills/" + name, skills / name, dirs_exist_ok=True)
print("copied", name, "into ~/.agents/skills")

# Every folder in ~/.agents/skills is one skill that Codex will find.
print("Skills Codex will find:")
for folder in sorted(skills.iterdir()):
    print("  " + folder.name)
python3 skill_setup.py

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

copied mac-app-report into ~/.agents/skills
Skills Codex will find:
  mac-app-report
  notebooklm
  reverse-engineer-anything
A SKILL IS A RECIPE, A TOOL SERVER IS A KITCHEN A tool server gives Codex new hands: tools it did not have. A skill gives it a recipe for hands it already has. Reach for a skill first. It is one file, it needs no program to run, and when it goes wrong you read what it says and change a sentence.Write a tool server only when the thing Codex needs cannot be done by typing a command.เซิร์ฟเวอร์เครื่องมือให้มือใหม่กับ Codex: เครื่องมือที่มันไม่เคยมี สกิลให้สูตรสำหรับมือที่มันมีอยู่แล้ว หยิบสกิลก่อน มันคือไฟล์เดียว ไม่ต้องมีโปรแกรมรัน และเมื่อมันผิดพลาด คุณอ่านสิ่งที่มันเขียนแล้วแก้ประโยคหนึ่งเขียนเซิร์ฟเวอร์เครื่องมือก็ต่อเมื่อสิ่งที่ Codex ต้องการทำไม่ได้ด้วยการพิมพ์คำสั่งเท่านั้น工具服务器给 Codex 新的手:它本来没有的工具。技能给它一份菜谱,用的是它已经有的手。先拿技能。它只是一个文件,不需要任何程序来运行,出了问题你读一读它写的,改一句话就行。只有当 Codex 需要的事无法靠输入一条命令完成时,才去写工具服务器。

🧠 Quiz 7: what will this print?🧠 แบบทดสอบ 7: มันจะพิมพ์อะไร🧠 小测验 7:它会打印什么?

Pick an answer. Then the page shows which one is right and why, and you can run the program yourself.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูกและเพราะอะไร และคุณรันโปรแกรมเองได้选一个答案。然后这一页会告诉你哪个是对的、为什么,你也可以自己运行这个程序。

# Quiz 7: what will this print?
names = ["notebooklm", "mac-app-report", "reverse-engineer-anything"]

for name in sorted(names):
    print(name)

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

sorted() gives a new list in alphabetical order, and the for loop prints each name in that order. skill_setup.py lists the skills the same way: iterdir() gives the folders in no fixed order, and sorted() puts them in order.sorted() ให้ลิสต์ใหม่ที่เรียงตามตัวอักษร และลูป for พิมพ์แต่ละชื่อตามลำดับนั้น skill_setup.py แสดงรายการสกิลแบบเดียวกัน: iterdir() ให้โฟลเดอร์มาโดยไม่มีลำดับแน่นอน และ sorted() เรียงมันให้sorted() 给出一个按字母顺序排好的新列表,for 循环按这个顺序打印每个名字。skill_setup.py 也是这样列出技能的:iterdir() 给出的文件夹没有固定顺序,sorted() 把它们排好。

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

mac-app-report
notebooklm
reverse-engineer-anything

8 A browser: Playwright and CloakBrowser · เบราว์เซอร์: Playwright และ CloakBrowser · 浏览器:Playwright 和 CloakBrowser

🇬🇧 English

Some work needs a real browser: a page that its own JavaScript builds after it loads, a form to fill in, a site that answers a plain download with an empty page. The box above says to write a tool server only when typing a command cannot do the job, and a browser is that case. It has to stay open from one step to the next, holding the page, its cookies and its place, and no single command does that. Playwright, Microsoft's library for driving browsers, has a tool server for it, @playwright/mcp, and Codex's manual names it among the useful ones.

Ask it for its options first. The first npx also downloads the server, so Codex does not wait for the download when it starts the server, which it gives ten seconds. Then register it as you registered REA in step 4: a name, --, and the command that starts it. The version is pinned, as on the rest of this page. With no options, the server opens the Google Chrome that is on your Mac, in a window you can watch, with a profile of its own and not yours. Its tools start with browser_: browser_navigate opens an address, browser_snapshot reads the page as a list of its parts, each with a reference, and browser_click and browser_type act on a part by its reference.

🇹🇭 ไทย

งานบางอย่างต้องใช้เบราว์เซอร์จริง: หน้าที่ JavaScript ของมันเองสร้างขึ้นหลังโหลดเสร็จ ฟอร์มที่ต้องกรอก เว็บไซต์ที่ตอบการดาวน์โหลดธรรมดาด้วยหน้าว่าง กล่องข้างบนบอกให้เขียนเซิร์ฟเวอร์เครื่องมือก็ต่อเมื่อการพิมพ์คำสั่งทำงานนั้นไม่ได้ และเบราว์เซอร์คือกรณีนั้น มันต้องเปิดค้างไว้จากขั้นหนึ่งไปอีกขั้น พร้อมหน้าเว็บ คุกกี้ และตำแหน่งของมัน และไม่มีคำสั่งเดียวไหนทำแบบนั้นได้ Playwright ไลบรารีของ Microsoft สำหรับควบคุมเบราว์เซอร์ มีเซิร์ฟเวอร์เครื่องมือสำหรับเรื่องนี้ คือ @playwright/mcp และคู่มือของ Codex ระบุว่ามันเป็นหนึ่งในเซิร์ฟเวอร์ที่มีประโยชน์

ถามตัวเลือกของมันก่อน npx ครั้งแรกยังดาวน์โหลดเซิร์ฟเวอร์มาด้วย Codex จึงไม่ต้องรอการดาวน์โหลดตอนที่มันเริ่มเซิร์ฟเวอร์ ซึ่งมันให้เวลาสิบวินาที จากนั้นลงทะเบียนมันแบบที่คุณลงทะเบียน REA ในขั้นตอนที่ 4: ชื่อ -- และคำสั่งที่เริ่มมัน เวอร์ชันถูกตรึงไว้ เหมือนในส่วนอื่นของหน้านี้ ถ้าไม่ใส่ตัวเลือก เซิร์ฟเวอร์จะเปิด Google Chrome ที่อยู่บนแมคของคุณ ในหน้าต่างที่คุณดูได้ พร้อมโปรไฟล์ของมันเอง ไม่ใช่ของคุณ เครื่องมือของมันขึ้นต้นด้วย browser_: browser_navigate เปิดที่อยู่ browser_snapshot อ่านหน้าเว็บเป็นรายการส่วนต่าง ๆ ของมัน แต่ละส่วนมีตัวอ้างอิง และ browser_click กับ browser_type ทำงานกับส่วนหนึ่งด้วยตัวอ้างอิงของมัน

🇨🇳 中文

有些工作需要一个真正的浏览器:一个由自己的 JavaScript 在加载后才搭出来的网页,一张要填的表单,一个对普通下载只回一个空页面的网站。上面的方框说,只有当输入一条命令做不到时才去写工具服务器,浏览器就是这种情况。它得从一步到下一步一直开着,留着网页、它的 cookie 和它的位置,而没有哪一条命令能做到这一点。Playwright 是微软用来驱动浏览器的库,它有一个专门干这个的工具服务器 @playwright/mcp,Codex 的手册把它列为有用的服务器之一。

先问问它有哪些选项。第一次 npx 还会把这个服务器下载下来,这样 Codex 启动它时就不用等下载,而 Codex 只给它十秒。然后像第 4 步注册 REA 那样注册它:一个名字、--,以及启动它的命令。版本是固定的,和这一页其他地方一样。不加选项时,这个服务器打开你 Mac 上的 Google Chrome,在一个你看得见的窗口里,用它自己的配置文件,而不是你的。它的工具都以 browser_ 开头:browser_navigate 打开一个地址,browser_snapshot 把网页读成一张由各个部分组成的清单,每个部分带一个引用,browser_click 和 browser_type 凭这个引用对某个部分动手。

npx -y @playwright/mcp@0.0.83 --help

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

Usage: Playwright MCP [options]

Options:
  -V, --version                         output the version number
  --allowed-hosts <hosts...>            comma-separated list of hosts this
                                        server is allowed to serve from.
                                        Defaults to the host the server is bound
                                        to. Pass '*' to disable the host check.
  --allowed-origins <origins>           semicolon-separated list of TRUSTED
                                        origins to allow the browser to request.
                                        Default is to allow all.
                                        Important: *does not* serve as a
                                        security boundary and *does not* affect
                                        redirects.
  --allow-unrestricted-file-access      allow access to files outside of the
                                        workspace roots. Also allows
                                        unrestricted access to file:// URLs. By
                                        default access to file system is
                                        restricted to workspace root directories
                                        (or cwd if no roots are configured)
                                        only, and navigation to file:// URLs is
                                        blocked.
  --blocked-origins <origins>           semicolon-separated list of origins to
                                        block the browser from requesting.
                                        Blocklist is evaluated before allowlist.
                                        If used without the allowlist, requests
                                        not matching the blocklist are still
                                        allowed.
                                        Important: *does not* serve as a
                                        security boundary and *does not* affect
                                        redirects.
  --block-service-workers               block service workers
  --browser <browser>                   browser or chrome channel to use,
                                        possible values: chrome, firefox,
                                        webkit, msedge.
  --caps <caps>                         comma-separated list of additional
                                        capabilities to enable, possible values:
                                        vision, pdf, devtools.
  --cdp-endpoint <endpoint>             CDP endpoint to connect to.
  --cdp-header <headers...>             CDP headers to send with the connect
                                        request, multiple can be specified.
  --cdp-timeout <timeout>               timeout in milliseconds for connecting
                                        to CDP endpoint, defaults to 30000ms
  --codegen <lang>                      specify the language to use for code
                                        generation, possible values:
                                        "typescript", "python", "java",
                                        "csharp", "none". Default is
                                        "typescript".
  --config <path>                       path to the configuration file.
  --console-level <level>               level of console messages to return:
                                        "error", "warning", "info", "debug".
                                        Each level includes the messages of more
                                        severe levels.
  --device <device>                     device to emulate, for example: "iPhone
                                        15"
  --mobile                              emulate a generic mobile device (Pixel
                                        10 for Chromium, iPhone 17 for WebKit).
                                        Mobile pages are usually lighter, which
                                        saves tokens. Cannot be combined with
                                        --device.
  --executable-path <path>              path to the browser executable.
  --extension                           Connect to a running browser instance
                                        (Edge/Chrome only). Requires the
                                        "Playwright Extension" to be installed.
  --endpoint <endpoint>                 Bound browser endpoint to connect to.
  --file-paths <mode>                   how file paths are rendered in tool
                                        results, "relative" to the workspace
                                        root or "absolute". Default is
                                        "relative".
  --grant-permissions <permissions...>  List of permissions to grant to the
                                        browser context, for example
                                        "geolocation", "clipboard-read",
                                        "clipboard-write".
  --headless                            run browser in headless mode, headed by
                                        default
  --host <host>                         host to bind server to. Default is
                                        localhost. Use 0.0.0.0 to bind to all
                                        interfaces.
  --idle-timeout <timeout>              close the browser after this many
                                        milliseconds without a completed tool
                                        call, the next tool call relaunches it.
                                        Defaults to one hour for headless
                                        browsers, never for headed ones, 0
                                        disables.
  --ignore-https-errors                 ignore https errors
  --init-page <path...>                 path to TypeScript file to evaluate on
                                        Playwright page object
  --init-script <path...>               path to JavaScript file to add as an
                                        initialization script. The script will
                                        be evaluated in every page before any of
                                        the page's scripts. Can be specified
                                        multiple times.
  --isolated                            keep the browser profile in memory, do
                                        not save it to disk.
  --image-responses <mode>              whether to send image responses to the
                                        client. Can be "allow", "omit" or
                                        "only". With "only", a response that
                                        carries an image consists of the image
                                        parts alone, without the text part.
                                        Defaults to "allow".
  --no-sandbox                          disable the sandbox for all process
                                        types that are normally sandboxed.
  --no-webmcp                           do not collect or expose the tools that
                                        a page registers through the WebMCP API.
  --output-dir <path>                   path to the directory for automatically
                                        named output files, for example a
                                        screenshot taken without an explicit
                                        file name. Files with an explicit name
                                        are resolved against the workspace root
                                        instead and are not affected by this
                                        option.
  --output-max-size <bytes>             Threshold for evicting old output files,
                                        in bytes.
  --port <port>                         port to listen on for SSE transport.
  --profile-dir-name <name>             name of the profile directory in the
                                        user data dir to connect to with
                                        --extension, for example "Profile 1".
                                        Defaults to the last used profile that
                                        has the extension installed.
  --proxy-bypass <bypass>               comma-separated domains to bypass proxy,
                                        for example
                                        ".com,chromium.org,.domain.com"
  --proxy-server <proxy>                specify proxy server, for example
                                        "http://myproxy:3128" or
                                        "socks5://myproxy:8080"
  --sandbox                             enable the sandbox for all process types
                                        that are normally not sandboxed.
  --save-session                        Whether to save the Playwright MCP
                                        session into the output directory.
  --secrets <path>                      path to a file containing secrets in the
                                        dotenv format
  --shared-browser-context              reuse the same browser context between
                                        all connected HTTP clients.
  --snapshot-boxes                      include each element's bounding box as
                                        [box=x,y,width,height] in snapshots.
                                        Coordinates are viewport-relative, in
                                        CSS pixels.
  --snapshot-mode <mode>                when taking snapshots for responses,
                                        specifies the mode to use. Can be "full"
                                        or "none". Default is "full".
  --storage-state <path>                path to the storage state file for
                                        isolated sessions.
  --test-id-attribute <attribute>       specify the attribute to use for test
                                        ids, defaults to "data-testid"
  --timeout-action <timeout>            specify action timeout in milliseconds,
                                        defaults to 5000ms
  --timeout-navigation <timeout>        specify navigation timeout in
                                        milliseconds, defaults to 60000ms
  --timeout-settle <timeout>            how long to wait after each action for
                                        triggered work to settle, in
                                        milliseconds, defaults to 500ms
  --user-agent <ua string>              specify user agent string
  --user-data-dir <path>                path to the user data directory. If not
                                        specified, a temporary directory will be
                                        created.
  --viewport-size <size>                specify browser viewport size in pixels,
                                        for example "1280x720"
  -h, --help                            display help for command
codex mcp add playwright -- npx -y @playwright/mcp@0.0.83

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

Added global MCP server 'playwright'.

🇬🇧 English

Some websites turn a driven browser away, and they look for the signs. A Chrome started by a program, or one with no window, differs from yours in small ways that the page's own JavaScript can read. CloakBrowser is a Chromium changed in its source code so that those signs are gone; the CloakBrowser page uses it from Python. Playwright's tool server can start any Chromium you name. So the wiring is the same server registered a second time, under another name, with a settings file that names CloakBrowser's browser.

Install the cloakbrowser package into the environment of step 6, then let it fetch its browser. For a Mac with an Apple processor that is 140 MB, and it checks the download's signature and checksum before it unpacks it. Its banner says v146 on every computer; the lines above the banner name the file it fetched for the Mac, 145.0.7632.109.2.

browser_setup.py writes the settings file, ~/.codex/cloakbrowser.json, and types nothing into it by hand. It asks the package three things. Where its browser is. Which switches the package starts it with: a fingerprint number, and --fingerprint-platform, the system the browser says it runs on. On GitHub's Mac that was macos; in our Linux container, below, it is windows, with --no-sandbox too. And which of Playwright's own switches the package leaves out; the first is --enable-automation, the switch with which Chrome announces that a program drives it. Then register the second server with --config and that file, and codex mcp list shows four servers.

🇹🇭 ไทย

เว็บไซต์บางแห่งปฏิเสธเบราว์เซอร์ที่ถูกควบคุม และมันมองหาร่องรอย Chrome ที่โปรแกรมเป็นคนเริ่ม หรือที่ไม่มีหน้าต่าง ต่างจากของคุณในเรื่องเล็ก ๆ ที่ JavaScript ของหน้าเว็บเองอ่านได้ CloakBrowser คือ Chromium ที่ถูกแก้ในซอร์สโค้ดเพื่อให้ร่องรอยเหล่านั้นหายไป หน้า CloakBrowser ใช้มันจาก Python เซิร์ฟเวอร์เครื่องมือของ Playwright เริ่ม Chromium ตัวไหนก็ได้ที่คุณระบุ การเดินสายจึงเป็นเซิร์ฟเวอร์ตัวเดิมที่ลงทะเบียนเป็นครั้งที่สอง ภายใต้อีกชื่อหนึ่ง พร้อมไฟล์ตั้งค่าที่ระบุเบราว์เซอร์ของ CloakBrowser

ติดตั้งแพ็กเกจ cloakbrowser ลงในสภาพแวดล้อมของขั้นตอนที่ 6 แล้วให้มันดึงเบราว์เซอร์ของมันมา สำหรับแมคที่ใช้โปรเซสเซอร์ของ Apple ขนาดคือ 140 MB และมันตรวจลายเซ็นกับเช็กซัมของไฟล์ที่ดาวน์โหลดก่อนแตกไฟล์ แบนเนอร์ของมันเขียนว่า v146 บนทุกเครื่อง บรรทัดเหนือแบนเนอร์ระบุไฟล์ที่มันดึงมาสำหรับแมค คือ 145.0.7632.109.2

browser_setup.py เขียนไฟล์ตั้งค่า ~/.codex/cloakbrowser.json และไม่พิมพ์อะไรลงไปด้วยมือเลย มันถามแพ็กเกจสามเรื่อง เบราว์เซอร์ของมันอยู่ที่ไหน แพ็กเกจเริ่มเบราว์เซอร์ด้วยสวิตช์อะไร: หมายเลขลายนิ้วมือหนึ่งตัว และ --fingerprint-platform คือระบบที่เบราว์เซอร์บอกว่าตัวเองรันอยู่ บนแมคของ GitHub ค่านั้นคือ macos ในคอนเทนเนอร์ Linux ของเราข้างล่าง มันคือ windows พร้อม --no-sandbox ด้วย และสวิตช์ไหนของ Playwright เองที่แพ็กเกจละไว้ ตัวแรกคือ --enable-automation สวิตช์ที่ Chrome ใช้ประกาศว่ามีโปรแกรมควบคุมมันอยู่ จากนั้นลงทะเบียนเซิร์ฟเวอร์ตัวที่สองด้วย --config และไฟล์นั้น แล้ว codex mcp list จะแสดงเซิร์ฟเวอร์สี่ตัว

🇨🇳 中文

有些网站会把被驱动的浏览器挡在门外,它们会找迹象。一个由程序启动的 Chrome,或者一个没有窗口的 Chrome,和你的 Chrome 在一些小地方不一样,而网页自己的 JavaScript 读得出来。CloakBrowser 是一个在源代码里改过的 Chromium,那些迹象都被去掉了;CloakBrowser 那一页从 Python 里用它。Playwright 的工具服务器能启动你指定的任何 Chromium。所以接线就是把同一个服务器换个名字再注册一次,再配一个写明 CloakBrowser 浏览器的设置文件。

把 cloakbrowser 包装进第 6 步的环境,然后让它去取它的浏览器。对用 Apple 处理器的 Mac 来说是 140 MB,它在解包之前会核对下载文件的签名和校验和。它的横幅在每台电脑上都写着 v146;横幅上面那几行写的是它为 Mac 取来的文件,145.0.7632.109.2。

browser_setup.py 写出设置文件 ~/.codex/cloakbrowser.json,里面没有一样是手打进去的。它问这个包三件事。它的浏览器在哪里。这个包用哪些开关启动它:一个指纹编号,以及 --fingerprint-platform,也就是浏览器自称运行的系统。在 GitHub 的 Mac 上它是 macos;在我们的 Linux 容器里,也就是下面,它是 windows,另外还有 --no-sandbox。以及这个包去掉了 Playwright 自己的哪些开关;第一个是 --enable-automation,Chrome 用这个开关宣布有程序在驱动它。然后用 --config 和那个文件注册第二个服务器,codex mcp list 就会显示四个服务器。

venv/bin/python -m pip install cloakbrowser==0.6.3
venv/bin/python -m cloakbrowser install

what the second command printedสิ่งที่คำสั่งที่สองพิมพ์ออกมา第二条命令打印的内容

Stealth Chromium 145.0.7632.109.2 not found. Downloading for darwin-arm64...
Downloading from https://cloakbrowser.dev/chromium-v145.0.7632.109.2/cloakbrowser-darwin-arm64.tar.gz
Download progress: 9% (12/140 MB)
Download progress: 19% (26/140 MB)
Download progress: 29% (40/140 MB)
Download progress: 39% (54/140 MB)
Download progress: 49% (68/140 MB)
Download progress: 59% (82/140 MB)
Download progress: 69% (96/140 MB)
Download progress: 79% (111/140 MB)
Download progress: 89% (125/140 MB)
Download progress: 99% (139/140 MB)
Download complete: 140 MB
SHA256SUMS signature verified: Ed25519 OK
Checksum verified: SHA-256 OK
Extracting to /Users/runner/.cloakbrowser/chromium-145.0.7632.109.2
Binary ready: /Users/runner/.cloakbrowser/chromium-145.0.7632.109.2/Chromium.app/Contents/MacOS/Chromium

  CloakBrowser - stealth Chromium for automation
  https://github.com/CloakHQ/CloakBrowser

  Running the free binary (v146). The latest binary (v154) is free too, with 1 concurrent session.
  Get your key: run  cloakbrowser login  or visit https://cloakbrowser.dev/free
  For more than one concurrent session -> https://cloakbrowser.dev
  Star us if CloakBrowser helps your project!

/Users/runner/.cloakbrowser/chromium-145.0.7632.109.2/Chromium.app/Contents/MacOS/Chromium

🇬🇧 English

🐍 The Python in this file. from cloakbrowser import ... borrows functions from a package that pip installed. settings is a dictionary with dictionaries inside it, in the shape Playwright's tool server reads. json.dumps(settings, indent=2) writes it as JSON text, one setting on each line. " ".join(switches) joins a list of words into one line, with a space between them.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ from cloakbrowser import ... ยืมฟังก์ชันจากแพ็กเกจที่ pip ติดตั้งไว้ settings คือดิกชันนารีที่มีดิกชันนารีอยู่ข้างใน ในรูปแบบที่เซิร์ฟเวอร์เครื่องมือของ Playwright อ่าน json.dumps(settings, indent=2) เขียนมันเป็นข้อความ JSON บรรทัดละหนึ่งการตั้งค่า " ".join(switches) ต่อลิสต์ของคำเป็นบรรทัดเดียว โดยมีช่องว่างคั่น

🇨🇳 中文

🐍 这个文件里的 Python。from cloakbrowser import ... 从 pip 装好的一个包里借来函数。settings 是一个里面套着字典的字典,正是 Playwright 的工具服务器读的样子。json.dumps(settings, indent=2) 把它写成 JSON 文字,每项设置一行。" ".join(switches) 把一个词的列表连成一行,中间用空格隔开。

browser_setup.py

# browser_setup.py - a settings file that makes Playwright's tool server drive CloakBrowser.
#
#     venv/bin/python browser_setup.py
#
# Run it with the Python in which cloakbrowser is installed (step 8).

import json
import pathlib
from cloakbrowser import ensure_binary, get_default_stealth_args
from cloakbrowser.config import IGNORE_DEFAULT_ARGS

# ensure_binary() downloads CloakBrowser the first time, and gives its path.
# The two lists are switches: the ones to start the browser with,
# and the ones of Playwright's own to leave out.
switches = get_default_stealth_args()
settings = {
    "browser": {
        "browserName": "chromium",
        "launchOptions": {
            "executablePath": ensure_binary(),
            "args": switches,
            "ignoreDefaultArgs": IGNORE_DEFAULT_ARGS,
        },
    },
}

path = pathlib.Path.home() / ".codex" / "cloakbrowser.json"
path.parent.mkdir(exist_ok=True)
# json.dumps turns the dictionary into text; indent=2 puts one setting on each line.
path.write_text(json.dumps(settings, indent=2))
print("adds", " ".join(switches))
print("leaves out", " ".join(IGNORE_DEFAULT_ARGS))
print("wrote ~/.codex/cloakbrowser.json")
venv/bin/python browser_setup.py

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

adds --no-sandbox --fingerprint=14259 --fingerprint-platform=windows
leaves out --enable-automation --enable-unsafe-swiftshader
wrote ~/.codex/cloakbrowser.json
codex mcp add cloakbrowser -- npx -y @playwright/mcp@0.0.83 --config ~/.codex/cloakbrowser.json
codex mcp list

what the two commands printedสิ่งที่สองคำสั่งพิมพ์ออกมา两条命令打印的内容

Added global MCP server 'cloakbrowser'.
Name          Command  Args                                                                       Env  Cwd  Status   Auth       
cloakbrowser  npx      -y @playwright/mcp@0.0.83 --config /Users/runner/.codex/cloakbrowser.json  -    -    enabled  Unsupported
playwright    npx      -y @playwright/mcp@0.0.83                                                  -    -    enabled  Unsupported
rea           npx      -y rea-agents@6.3.0 mcp                                                    -    -    enabled  Unsupported

Name    Url                                 Bearer Token Env Var  Status   Auth        
github  https://api.githubcopilot.com/mcp/  GITHUB_TOKEN          enabled  Bearer token

🇬🇧 English

Codex needs a sign-in to use a tool. A tool server does not, so you can talk to one yourself, the way Codex does. A server reads requests as lines of JSON, and answers each with one line of JSON. mcp.py does that talking, in three functions: start starts a server and says hello, use calls one of its tools and hands back the text it answers, and stop closes it. browser_check.py imports them. With each server it opens example.com, a site kept for examples, and runs one line of JavaScript that reads what any website can read. Both servers run with --headless here: no window, the way they run on a Mac that nobody is watching.

🇹🇭 ไทย

Codex ต้องลงชื่อเข้าใช้จึงจะใช้เครื่องมือได้ แต่เซิร์ฟเวอร์เครื่องมือไม่ต้อง คุณจึงคุยกับมันเองได้ แบบที่ Codex ทำ เซิร์ฟเวอร์อ่านคำขอเป็นบรรทัดของ JSON และตอบแต่ละคำขอด้วย JSON หนึ่งบรรทัด mcp.py ทำหน้าที่คุยนี้ด้วยฟังก์ชันสามตัว: start เริ่มเซิร์ฟเวอร์และทักทาย use เรียกเครื่องมือตัวหนึ่งของมันแล้วส่งข้อความที่มันตอบกลับมา และ stop ปิดมัน browser_check.py นำเข้าฟังก์ชันเหล่านี้ กับเซิร์ฟเวอร์แต่ละตัว มันเปิด example.com เว็บไซต์ที่มีไว้สำหรับตัวอย่าง แล้วรัน JavaScript หนึ่งบรรทัดที่อ่านสิ่งที่เว็บไซต์ไหนก็อ่านได้ ในที่นี้เซิร์ฟเวอร์ทั้งสองรันด้วย --headless: ไม่มีหน้าต่าง แบบที่มันรันบนแมคที่ไม่มีใครดูอยู่

🇨🇳 中文

Codex 要登录才能用工具,工具服务器却不用,所以你可以自己和它对话,就像 Codex 那样。服务器以一行行 JSON 读取请求,每个请求用一行 JSON 回答。mcp.py 用三个函数负责这番对话:start 启动一个服务器并打招呼,use 调用它的一个工具并交回它回答的文字,stop 关掉它。browser_check.py 引入这三个函数。对每个服务器,它打开 example.com,一个专门留作示例的网站,再运行一行 JavaScript,读的是任何网站都读得到的东西。这里两个服务器都用 --headless 运行:没有窗口,就像它们在一台没有人看着的 Mac 上运行那样。

🇬🇧 English

🐍 The Python in this file. This file is a small toolbox: it holds only def blocks, functions, and the files of steps 8, 9 and 10 borrow them with from mcp import start, use, stop. A function takes values in its round brackets, does its job, and can hand a value back with return. A dictionary, between curly brackets, holds pairs written key: value; json.dumps turns one into a line of text to send, and json.loads turns a line that comes back into a dictionary again. while True: repeats for ever, until return ends the function: here it reads line after line until the answer comes. You do not run this file: save it beside the other three.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ ไฟล์นี้เป็นกล่องเครื่องมือเล็ก ๆ: มันมีแค่บล็อก def คือฟังก์ชัน และไฟล์ของขั้นตอนที่ 8, 9 และ 10 ยืมมันไปใช้ด้วย from mcp import start, use, stop ฟังก์ชันรับค่าในวงเล็บกลม ทำงานของมัน และส่งค่ากลับมาได้ด้วย return ดิกชันนารีที่อยู่ในวงเล็บปีกกาเก็บคู่ที่เขียนแบบ key: value json.dumps เปลี่ยนมันเป็นข้อความหนึ่งบรรทัดเพื่อส่งไป และ json.loads เปลี่ยนบรรทัดที่ตอบกลับมาให้เป็นดิกชันนารีอีกครั้ง while True: ทำซ้ำไปเรื่อย ๆ จนกว่า return จะจบฟังก์ชัน: ตรงนี้มันอ่านทีละบรรทัดจนกว่าคำตอบจะมา คุณไม่ได้รันไฟล์นี้เอง: บันทึกมันไว้ข้างอีกสามไฟล์

🇨🇳 中文

🐍 这个文件里的 Python。这个文件是一个小工具箱:里面只有 def 块,也就是函数,第 8、9、10 步的文件用 from mcp import start, use, stop 把它们借去用。函数在圆括号里接收值,做它的事,还可以用 return 交回一个值。字典写在花括号里,装着一对对 key: value;json.dumps 把它变成一行文字发出去,json.loads 把回来的那一行再变成字典。while True: 一直重复,直到 return 结束这个函数:这里它一行一行地读,直到答案来了。你不用运行这个文件:把它存在另外三个文件旁边就行。

mcp.py

# mcp.py - talk to a tool server the way Codex does. The files of steps 8 to 10 import it.
#
# A tool server is a program that Codex starts. Codex sends it one line of JSON
# for each request, and the server answers with one line of JSON.

import json
import subprocess


def start(command):
    # Start the server, with its input and output joined to this program.
    server = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                              text=True, encoding="utf-8")
    # The first request says hello: which version of the protocol we speak.
    ask(server, "initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
                               "clientInfo": {"name": "student", "version": "1.0"}})
    send(server, {"jsonrpc": "2.0", "method": "notifications/initialized"})
    return server


def send(server, message):
    # One message is one line of JSON.
    server.stdin.write(json.dumps(message) + "\n")
    server.stdin.flush()


def ask(server, method, params):
    send(server, {"jsonrpc": "2.0", "id": 1, "method": method, "params": params})
    # Read lines until the answer comes: it is the line that has an "id".
    while True:
        answer = json.loads(server.stdout.readline())
        if "id" in answer:
            return answer["result"]


def use(server, tool, arguments):
    # Use one of the server's tools, and give back the text it answers.
    result = ask(server, "tools/call", {"name": tool, "arguments": arguments})
    return result["content"][0]["text"]


def stop(server):
    server.stdin.close()
    server.wait()

🇬🇧 English

🐍 The Python in this file. servers is a dictionary: each server's name, and its command, a list of words. playwright + ["--config", str(settings)] adds two lists together into a longer one. for name, command in servers.items(): runs the indented lines once for each server, with its name and its command. ask(...)["tools"] looks up one key in the dictionary that came back, and len() counts the tools in it.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ servers คือดิกชันนารี: ชื่อของแต่ละเซิร์ฟเวอร์ และคำสั่งของมัน ซึ่งเป็นลิสต์ของคำ playwright + ["--config", str(settings)] บวกสองลิสต์เข้าด้วยกันเป็นลิสต์ที่ยาวขึ้น for name, command in servers.items(): รันบรรทัดที่ย่อหน้าหนึ่งครั้งต่อหนึ่งเซิร์ฟเวอร์ พร้อมชื่อและคำสั่งของมัน ask(...)["tools"] หาคีย์หนึ่งในดิกชันนารีที่ตอบกลับมา และ len() นับเครื่องมือที่อยู่ในนั้น

🇨🇳 中文

🐍 这个文件里的 Python。servers 是一个字典:每个服务器的名字,以及它的命令,一个由词组成的列表。playwright + ["--config", str(settings)] 把两个列表加成一个更长的列表。for name, command in servers.items(): 对每个服务器把缩进的那几行运行一次,带着它的名字和命令。ask(...)["tools"] 在回来的字典里查一个键,len() 数出里面有多少个工具。

browser_check.py

# browser_check.py - use the two browser tool servers of step 8, the way Codex does.
#
#     python3 browser_check.py
#
# Each server opens a web page and reads two things from it: navigator.webdriver,
# which is true when a program drives the browser, and the browser's own name
# for itself, its user agent. Any website can read both.

import pathlib
from mcp import start, ask, use, stop

settings = pathlib.Path.home() / ".codex" / "cloakbrowser.json"
playwright = ["npx", "-y", "@playwright/mcp@0.0.83", "--headless"]

# A dictionary: each server's name, and the command that starts it.
servers = {
    "playwright": playwright,
    "cloakbrowser": playwright + ["--config", str(settings)],
}

# A little JavaScript for the page to run. What it returns comes back to us.
look = "() => ({ webdriver: navigator.webdriver, userAgent: navigator.userAgent })"

for name, command in servers.items():
    print("SERVER", name)
    server = start(command)
    tools = ask(server, "tools/list", {})["tools"]
    print(len(tools), "tools")
    use(server, "browser_navigate", {"url": "https://example.com"})
    print(use(server, "browser_evaluate", {"function": look}))
    use(server, "browser_close", {})
    stop(server)
    print()
python3 browser_check.py

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

SERVER playwright
25 tools
### Result
{
  "webdriver": false,
  "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/156.0.0.0 Safari/537.36"
}
### Ran Playwright code
```js
await page.evaluate('() => ({ webdriver: navigator.webdriver, userAgent: navigator.userAgent })');
```

SERVER cloakbrowser
25 tools
### Result
{
  "webdriver": false,
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
}
### Ran Playwright code
```js
await page.evaluate('() => ({ webdriver: navigator.webdriver, userAgent: navigator.userAgent })');
```

🇬🇧 English

Read the two answers side by side. Both servers have the same 25 tools, because they are one server. Both answer "webdriver": false, because Playwright's server starts the browser with --disable-blink-features=AutomationControlled, a switch that hides that flag. The name is where they part. Chrome with no window calls itself HeadlessChrome/156.0.0.0, and a site needs nothing more to turn it away. CloakBrowser calls itself Chrome/146.0.0.0 on Windows NT 10.0: the name of an ordinary browser, on the system it chose to show. What it changes deeper down, how it draws on a canvas and which fonts and graphics chip it reports, is what bot-detection services measure, and one line of JavaScript does not show that.

Now a real errand: one Google search. browser_search.py uses the same three functions, with Google's address for its words, and a longer line of JavaScript. That line reads where the browser ended up, and the titles of the first five results. CloakBrowser goes first, then Playwright's Chrome, one search each.

🇹🇭 ไทย

อ่านคำตอบทั้งสองเคียงข้างกัน เซิร์ฟเวอร์ทั้งสองมีเครื่องมือ 25 อันเหมือนกัน เพราะมันคือเซิร์ฟเวอร์ตัวเดียวกัน ทั้งสองตอบ "webdriver": false เพราะเซิร์ฟเวอร์ของ Playwright เริ่มเบราว์เซอร์ด้วย --disable-blink-features=AutomationControlled สวิตช์ที่ซ่อนแฟล็กนั้น ชื่อคือจุดที่มันแยกทางกัน Chrome ที่ไม่มีหน้าต่างเรียกตัวเองว่า HeadlessChrome/156.0.0.0 และเว็บไซต์ไม่ต้องใช้อะไรมากกว่านั้นในการปฏิเสธมัน CloakBrowser เรียกตัวเองว่า Chrome/146.0.0.0 บน Windows NT 10.0: ชื่อของเบราว์เซอร์ธรรมดา บนระบบที่มันเลือกแสดง สิ่งที่มันเปลี่ยนลึกลงไป คือวิธีวาดบน canvas และฟอนต์กับชิปกราฟิกที่มันรายงาน คือสิ่งที่บริการตรวจจับบอตวัด และ JavaScript หนึ่งบรรทัดไม่แสดงสิ่งนั้น

ทีนี้มาทำงานจริงกัน: ค้นหาบน Google หนึ่งครั้ง browser_search.py ใช้ฟังก์ชันสามตัวเดียวกัน กับที่อยู่ของ Google สำหรับคำของมัน และ JavaScript บรรทัดที่ยาวกว่า บรรทัดนั้นอ่านว่าเบราว์เซอร์ไปจบที่ไหน และชื่อของผลลัพธ์ห้าอันแรก CloakBrowser ไปก่อน แล้วตามด้วย Chrome ของ Playwright คนละหนึ่งการค้นหา

🇨🇳 中文

把两个回答放在一起读。两个服务器都有同样的 25 个工具,因为它们是同一个服务器。两个都回答 "webdriver": false,因为 Playwright 的服务器用 --disable-blink-features=AutomationControlled 启动浏览器,这个开关把那个标志藏了起来。不同之处在名字上。没有窗口的 Chrome 自称 HeadlessChrome/156.0.0.0,网站凭这一点就足以把它拒之门外。CloakBrowser 自称运行在 Windows NT 10.0 上的 Chrome/146.0.0.0:一个普通浏览器的名字,系统则是它选择显示的那个。它在更深处改变的东西,比如怎样在 canvas 上画图、报告哪些字体和哪块显卡,才是检测机器人的服务要测的,一行 JavaScript 看不出来。

现在做一件真事:一次 Google 搜索。browser_search.py 用的是同样的三个函数,换成 Google 上搜它那几个词的地址,再加一行更长的 JavaScript。那一行读出浏览器最后停在哪里,以及前五个结果的标题。CloakBrowser 先来,然后是 Playwright 的 Chrome,各搜一次。

browser_search.py

# browser_search.py - search Google with the two browser tool servers, as Codex would.
#
#     python3 browser_search.py
#
# Google asks programs not to search it (its robots.txt says so), so this file
# sends only one search with each server. Do not put it in a loop.

import pathlib
from mcp import start, use, stop

settings = pathlib.Path.home() / ".codex" / "cloakbrowser.json"
playwright = ["npx", "-y", "@playwright/mcp@0.0.83", "--headless"]
servers = {
    "cloakbrowser": playwright + ["--config", str(settings)],
    "playwright": playwright,
}

words = "Wat Phra That Doi Suthep"
# In a web address a space is written as +.
address = "https://www.google.com/search?hl=en&q=" + words.replace(" ", "+")

# JavaScript for Google's page: the address the browser ended on,
# and the titles of the first five results.
read = """() => [location.href.split('?')[0],
  ...[...document.querySelectorAll('#search a h3')].slice(0, 5).map(h => h.innerText)]"""

for name, command in servers.items():
    print("SERVER", name)
    server = start(command)
    use(server, "browser_navigate", {"url": address})
    answer = use(server, "browser_evaluate", {"function": read})
    # The answer has sections; the value we asked for comes after "### Result".
    print(answer.split("### Result")[1].split("###")[0].strip())
    use(server, "browser_close", {})
    stop(server)
    print()
python3 browser_search.py

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

SERVER cloakbrowser
[
  "https://www.google.com/sorry/index"
]

SERVER playwright
[
  "https://www.google.com/sorry/index"
]

🇬🇧 English

This time Google sent both browsers to /sorry/index, its page that asks a person to prove they are one, and neither saw a result. On GitHub's Mac, on 16 October, it was different: CloakBrowser came back with five results and Playwright's Chrome was sent to /sorry/, with the same server and the same tools, a few seconds apart. So the browser can make the difference, but it does not decide alone: Google also looks at where the searches come from, as the next paragraph says.

Google also counts the searches that come from one address. The CloakBrowser page found that it answered two searches from one home connection and showed its CAPTCHA on the third, even through CloakBrowser. A program that searches for you every day needs a search service that allows it.

In a session, name the server you want: Use the cloakbrowser tools to search Google for Wat Phra That Doi Suthep and give me the first three results. Codex calls browser_navigate, then browser_snapshot, reads, and goes on from there. That needs a signed-in Codex, so it was not run for this page.

🇹🇭 ไทย

ครั้งนี้ Google ส่งเบราว์เซอร์ทั้งสองไปที่ /sorry/index หน้าของ Google ที่ขอให้คนพิสูจน์ว่าตัวเองเป็นคน และไม่มีตัวไหนเห็นผลลัพธ์เลย บนแมคของ GitHub เมื่อวันที่ 16 ตุลาคม ผลต่างออกไป: CloakBrowser กลับมาพร้อมผลลัพธ์ห้าอัน และ Chrome ของ Playwright ถูกส่งไปที่ /sorry/ ด้วยเซิร์ฟเวอร์ตัวเดียวกัน เครื่องมือเดียวกัน ห่างกันไม่กี่วินาที ดังนั้นเบราว์เซอร์ทำให้ต่างกันได้ แต่มันไม่ได้ตัดสินอยู่ฝ่ายเดียว: Google ยังดูด้วยว่าการค้นหามาจากที่ไหน อย่างที่ย่อหน้าถัดไปบอก

Google ยังนับการค้นหาที่มาจากที่อยู่เดียวกันด้วย หน้า CloakBrowser พบว่ามันตอบการค้นหาสองครั้งจากอินเทอร์เน็ตบ้านหนึ่งจุด แล้วแสดง CAPTCHA ในครั้งที่สาม แม้จะผ่าน CloakBrowser โปรแกรมที่ค้นหาให้คุณทุกวันต้องใช้บริการค้นหาที่อนุญาตให้ทำแบบนั้น

ในเซสชัน ให้ระบุชื่อเซิร์ฟเวอร์ที่คุณต้องการ: Use the cloakbrowser tools to search Google for Wat Phra That Doi Suthep and give me the first three results (ใช้เครื่องมือ cloakbrowser ค้นหา Wat Phra That Doi Suthep บน Google แล้วบอกผลลัพธ์สามอันแรก) Codex เรียก browser_navigate แล้ว browser_snapshot อ่าน และทำงานต่อจากตรงนั้น นั่นต้องใช้ Codex ที่ลงชื่อเข้าใช้แล้ว จึงไม่ได้รันสำหรับหน้านี้

🇨🇳 中文

这一次,Google 把两个浏览器都送到了 /sorry/index,也就是要求人证明自己是人的那一页,谁也没看到结果。10 月 16 日在 GitHub 的 Mac 上就不一样:CloakBrowser 带回了五个结果,Playwright 的 Chrome 被送到了 /sorry/,同一个服务器,同样的工具,只隔几秒。所以浏览器可以带来差别,但它不能单独决定:Google 还会看搜索是从哪里来的,就像下一段说的那样。

Google 还会数来自同一个地址的搜索。CloakBrowser 那一页发现,从一个家庭网络出发,它回答了两次搜索,第三次就显示了 CAPTCHA,即使用的是 CloakBrowser。一个每天替你搜索的程序,需要一个允许这样做的搜索服务。

在会话里,说出你要的服务器:Use the cloakbrowser tools to search Google for Wat Phra That Doi Suthep and give me the first three results(用 cloakbrowser 的工具在 Google 上搜 Wat Phra That Doi Suthep,告诉我前三个结果)。Codex 会调用 browser_navigate,再调用 browser_snapshot,读完接着往下做。这需要一个已登录的 Codex,所以这一页没有运行它。

CAREFUL A browser tool can do anything you can do in a browser: buy, post, delete, and sign in with whatever its profile remembers. Codex's manual has a setting for that: the line default_tools_approval_mode = "writes" in a server's table in config.toml makes Codex ask you before any of that server's tools that is not marked read-only. CloakBrowser hides that a program is driving; it does not make every use allowed. Keep to pages you may read, read a site's terms, and go slowly. Google's robots.txt says Disallow: /search: it asks programs to stay out of its results, which is why browser_search.py sends one search with each browser and stops. CloakBrowser's browser is free to use, also for work, but not to pass on, so each Mac fetches its own and it never goes into git.เครื่องมือเบราว์เซอร์ทำได้ทุกอย่างที่คุณทำได้ในเบราว์เซอร์: ซื้อ โพสต์ ลบ และลงชื่อเข้าใช้ด้วยอะไรก็ตามที่โปรไฟล์ของมันจำไว้ คู่มือของ Codex มีการตั้งค่าสำหรับเรื่องนี้: บรรทัด default_tools_approval_mode = "writes" ในตารางของเซิร์ฟเวอร์ใน config.toml ทำให้ Codex ถามคุณก่อนใช้เครื่องมือตัวใดก็ตามของเซิร์ฟเวอร์นั้นที่ไม่ได้ระบุว่าอ่านอย่างเดียว CloakBrowser ซ่อนว่ามีโปรแกรมควบคุมอยู่ มันไม่ได้ทำให้การใช้ทุกแบบได้รับอนุญาต ใช้กับหน้าที่คุณมีสิทธิ์อ่าน อ่านข้อกำหนดของเว็บไซต์ และไปอย่างช้า ๆ robots.txt ของ Google เขียนว่า Disallow: /search: มันขอให้โปรแกรมอยู่ห่างจากหน้าผลการค้นหา นั่นคือเหตุผลที่ browser_search.py ค้นหาเบราว์เซอร์ละครั้งเดียวแล้วหยุด เบราว์เซอร์ของ CloakBrowser ใช้ได้ฟรี รวมถึงในงาน แต่ห้ามส่งต่อ แมคแต่ละเครื่องจึงดึงมาเอง และมันไม่เคยเข้าไปอยู่ใน git浏览器工具能做你在浏览器里能做的任何事:买东西、发帖、删除,还能用它的配置文件记住的任何东西登录。Codex 的手册里有一个针对这一点的设置:在 config.toml 里某个服务器的表中加一行 default_tools_approval_mode = "writes",Codex 在使用这个服务器任何没有标为只读的工具之前,都会先问你。CloakBrowser 隐藏的是有程序在驱动;它并不让每一种用法都变得被允许。只用于你有权读的网页,读一读网站的条款,慢慢来。Google 的 robots.txt 写着 Disallow: /search:它请程序别碰它的结果页,所以 browser_search.py 每个浏览器只搜一次就停。CloakBrowser 的浏览器可以免费使用,工作中也可以,但不能转给别人,所以每台 Mac 都自己去取,它永远不进 git。

🧠 Quiz 8🧠 แบบทดสอบ 8🧠 小测验 8

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

Why is a browser wired to Codex as a tool server, and not used through single commands?ทำไมเบราว์เซอร์ถึงต่อเข้ากับ Codex เป็นเซิร์ฟเวอร์เครื่องมือ และไม่ใช้ผ่านคำสั่งทีละคำสั่ง为什么浏览器要作为工具服务器接到 Codex 上,而不是一条一条命令地用?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

A command starts, does one thing and ends. A browser has to keep the page open between steps, which only a server that keeps running can do.คำสั่งเริ่ม ทำอย่างหนึ่ง แล้วจบ เบราว์เซอร์ต้องเปิดหน้าเว็บค้างไว้ระหว่างขั้นตอน ซึ่งทำได้เฉพาะเซิร์ฟเวอร์ที่รันอยู่ตลอด一条命令启动、做一件事、然后结束。浏览器得在步骤之间一直开着网页,只有一直运行的服务器才做得到。

9 Chrome DevTools: what a page did · Chrome DevTools: หน้าเว็บทำอะไรไปบ้าง · Chrome DevTools:网页做了什么

🇬🇧 English

Playwright drives a browser. Chrome's DevTools look inside one: what a page wrote to its console, what it downloaded, how fast it drew, and what Lighthouse, Chrome's own checker, thinks of it. Chrome's team publishes a tool server for this, chrome-devtools-mcp, and Codex's manual lists it next to Playwright. With it, Codex can open a page you are building, read what went wrong, and fix the file in the same session.

Register it like the others. --no-usage-statistics is there because otherwise the server sends Google statistics about how its tools are used. The first lines of the output below are the server's own notes, written each time it starts: that whatever is in its browser is open to the agent, and that a performance trace may send the page's address to Google, which --no-performance-crux turns off. It drives the Google Chrome on your Mac, with a profile of its own.

devtools_check.py uses the three functions of mcp.py with this server. It opens the page you name in a new tab, reads the new tab's number from the answer, and asks three tools about that tab: the console messages, the network requests, and a Lighthouse audit. The audit ends with the paths of two report files, new on every run.

🇹🇭 ไทย

Playwright ควบคุมเบราว์เซอร์ DevTools ของ Chrome มองเข้าไปข้างใน: หน้าเว็บเขียนอะไรลงคอนโซลของมัน ดาวน์โหลดอะไร วาดเร็วแค่ไหน และ Lighthouse ตัวตรวจของ Chrome เองคิดอย่างไรกับมัน ทีมของ Chrome เผยแพร่เซิร์ฟเวอร์เครื่องมือสำหรับเรื่องนี้ คือ chrome-devtools-mcp และคู่มือของ Codex ระบุมันไว้ข้าง ๆ Playwright เมื่อมีมัน Codex เปิดหน้าที่คุณกำลังสร้างได้ อ่านว่าอะไรผิดพลาด และแก้ไฟล์ในเซสชันเดียวกันได้

ลงทะเบียนมันแบบเดียวกับตัวอื่น ๆ ที่มี --no-usage-statistics ก็เพราะถ้าไม่ใส่ เซิร์ฟเวอร์จะส่งสถิติว่าเครื่องมือของมันถูกใช้อย่างไรไปให้ Google บรรทัดแรก ๆ ของผลลัพธ์ข้างล่างคือหมายเหตุของเซิร์ฟเวอร์เอง ซึ่งมันเขียนทุกครั้งที่เริ่ม: ว่าทุกอย่างในเบราว์เซอร์ของมันเปิดให้เอเจนต์เห็น และว่าการติดตามประสิทธิภาพอาจส่งที่อยู่ของหน้าเว็บไปให้ Google ซึ่ง --no-performance-crux ปิดได้ มันควบคุม Google Chrome บนแมคของคุณ ด้วยโปรไฟล์ของมันเอง

devtools_check.py ใช้ฟังก์ชันสามตัวของ mcp.py กับเซิร์ฟเวอร์นี้ มันเปิดหน้าที่คุณระบุในแท็บใหม่ อ่านหมายเลขของแท็บใหม่จากคำตอบ แล้วถามเครื่องมือสามตัวเกี่ยวกับแท็บนั้น: ข้อความคอนโซล คำขอเครือข่าย และการตรวจของ Lighthouse การตรวจจบด้วยเส้นทางของไฟล์รายงานสองไฟล์ ซึ่งเปลี่ยนใหม่ทุกครั้งที่รัน

🇨🇳 中文

Playwright 驱动浏览器。Chrome 的 DevTools 则往里面看:网页往控制台写了什么,下载了什么,画得有多快,以及 Chrome 自己的检查工具 Lighthouse 怎么看它。Chrome 团队为此发布了一个工具服务器 chrome-devtools-mcp,Codex 的手册把它列在 Playwright 旁边。有了它,Codex 能打开你正在做的网页,读出哪里出了错,并在同一个会话里修好文件。

像其他服务器一样注册它。之所以加 --no-usage-statistics,是因为不加的话,服务器会把它的工具被怎样使用的统计发给 Google。下面输出的头几行是服务器自己的说明,它每次启动都会写:它浏览器里的一切都向智能体敞开;性能追踪可能会把网页的地址发给 Google,--no-performance-crux 可以关掉这一点。它驱动你 Mac 上的 Google Chrome,用它自己的配置文件。

devtools_check.py 对这个服务器用的是 mcp.py 的三个函数。它在一个新标签页里打开你指定的网页,从回答里读出新标签页的编号,然后让三个工具查看这个标签页:控制台消息、网络请求,以及一次 Lighthouse 检查。检查的结尾是两个报告文件的路径,每次运行都不同。

codex mcp add chrome-devtools -- npx -y chrome-devtools-mcp@1.11.0 --no-usage-statistics

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

Added global MCP server 'chrome-devtools'.

🇬🇧 English

🐍 The Python in this file. sys.argv is the list of words you typed; sys.argv[1] is the first one after the file's name, the page's address. The first loop looks at each line of the answer: if "[selected]" in line finds the new tab, line.split(":")[0] keeps what comes before the colon, and int() turns that text into a number. The second loop takes the three tool names one by one and asks each about that tab.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ sys.argv คือรายการคำที่คุณพิมพ์ sys.argv[1] คือคำแรกหลังชื่อไฟล์ ซึ่งคือที่อยู่ของหน้าเว็บ ลูปแรกดูแต่ละบรรทัดของคำตอบ: if "[selected]" in line หาแท็บใหม่ line.split(":")[0] เก็บส่วนที่อยู่ก่อนเครื่องหมายทวิภาค และ int() เปลี่ยนข้อความนั้นเป็นตัวเลข ลูปที่สองหยิบชื่อเครื่องมือสามตัวทีละตัว แล้วถามแต่ละตัวเกี่ยวกับแท็บนั้น

🇨🇳 中文

🐍 这个文件里的 Python。sys.argv 是你输入的那些词的列表;sys.argv[1] 是文件名后面的第一个词,也就是网页的地址。第一个循环看回答的每一行:if "[selected]" in line 找到新的标签页,line.split(":")[0] 留下冒号前面的部分,int() 把这段文字变成数字。第二个循环把三个工具的名字一个一个拿出来,让每个工具查看那个标签页。

devtools_check.py

# devtools_check.py - ask Chrome DevTools about a web page, through its tool server.
#
#     python3 devtools_check.py https://krueng.ai/codex_mac.html
#
# It opens the page, then asks three of the server's tools about it:
# the page's console messages, its network requests, and a Lighthouse audit.

import sys
from mcp import start, use, stop

# sys.argv is the list of words you typed: sys.argv[1] is the page's address.
page = sys.argv[1]

# --isolated keeps Chrome's profile in memory only: nothing is saved to disk.
server = start(["npx", "-y", "chrome-devtools-mcp@1.11.0", "--headless", "--isolated",
                "--no-usage-statistics"])
opened = use(server, "new_page", {"url": page})

# The answer lists the open tabs, one per line, like "2: https://... [selected]".
# The new tab is the selected one; its number comes before the colon.
for line in opened.splitlines():
    if "[selected]" in line:
        tab = int(line.split(":")[0])

for tool in ["list_console_messages", "list_network_requests", "lighthouse_audit"]:
    print("TOOL", tool)
    print(use(server, tool, {"pageId": tab}))
    print()

stop(server)
python3 devtools_check.py https://krueng.ai/codex_mac.html

what it printed, for the live pageสิ่งที่มันพิมพ์ออกมา สำหรับหน้าบนเว็บ它打印的内容,查看的是线上的网页

chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
debug, and modify any data in the browser or DevTools.
Avoid sharing sensitive or personal information that you do not want to share with MCP clients.
Performance tools may send trace URLs to the Google CrUX API to fetch real-user experience data. To disable, run with --no-performance-crux.
TOOL list_console_messages
## Console messages
<no console messages found>

TOOL list_network_requests
## Network requests
Showing 1-7 of 7 (Page 1 of 1).
reqid=1 GET https://krueng.ai/codex_mac.html [200]
reqid=2 GET https://krueng.ai/css/tefl-bilingual.css [200]
reqid=3 GET https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=optional [200]
reqid=4 GET https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;600;700&family=Nunito:wght@400;600;700;800&family=Fira+Code:wght@400;500&display=swap [200]
reqid=5 GET https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2 [200]
reqid=6 GET https://fonts.gstatic.com/s/firacode/v27/uU9NCBsR6Z2vfE9aq3bh3dSD.woff2 [200]
reqid=7 GET https://krueng.ai/favicon.ico [pending]

TOOL lighthouse_audit
## Lighthouse audit results
Mode: navigation
Device: desktop
URL: https://krueng.ai/codex_mac.html
### Category scores
- Accessibility: 100 (accessibility)
- Best Practices: 100 (best-practices)
- SEO: 100 (seo)
- Agentic Browsing: 100 (agentic-browsing)
### Audit summary
Passed: 52
Failed: 0
Total timing: 11120.95ms
### Reports
- /tmp/chrome-devtools-mcp-bwDCoz/report.json
- /tmp/chrome-devtools-mcp-8pvA4n/report.html

🇬🇧 English

The page it looked at is this one, as it is live today. Its console was empty, and six of its seven requests were answered with 200; the seventh, the site's icon, was still pending when the list was made. Lighthouse scored accessibility, best practices, SEO and agentic browsing 100, and all 52 checks passed. It was not so on 16 October. The first audit of this page, on GitHub's Mac, scored accessibility 96 and failed three checks: the page moved while it loaded (a layout shift of 0.139), its language labels were h3 headings that skipped a level, and it had no main landmark for a screen reader to jump to. Those were real faults of this page, found by a tool and not by a person.

Then the page was fixed, the way you would ask Codex to fix it. The opening section got a heading of its own, Before you start, so the headings no longer jump from h1 to h3. The page's content now sits in a main element. And the title's font, Inter, now loads with display=optional instead of display=swap: a late Inter had redrawn the title in fewer lines and moved the whole page up while it loaded, and now the browser uses Inter only if it arrives at once.

To audit a page before it is published, serve its folder with the web server that comes with Python, and point the file at that address. Run the first command in the page's folder, and leave it running in a Terminal window of its own.

🇹🇭 ไทย

หน้าที่มันดูคือหน้านี้เอง อย่างที่มันเป็นอยู่บนเว็บวันนี้ คอนโซลของมันว่างเปล่า และคำขอหกจากเจ็ดคำขอได้คำตอบ 200 คำขอที่เจ็ด คือไอคอนของเว็บไซต์ ยังรออยู่ตอนที่ทำรายการ Lighthouse ให้คะแนนการเข้าถึง แนวปฏิบัติที่ดี SEO และการท่องเว็บโดยเอเจนต์ 100 และการตรวจทั้ง 52 ข้อผ่านหมด เมื่อวันที่ 16 ตุลาคมไม่เป็นอย่างนั้น การตรวจครั้งแรกของหน้านี้บนแมคของ GitHub ให้คะแนนการเข้าถึง 96 และไม่ผ่านการตรวจสามข้อ: หน้าเว็บขยับระหว่างโหลด (การเลื่อนของเลย์เอาต์ 0.139) ป้ายภาษาของมันเป็นหัวข้อ h3 ที่ข้ามระดับ และมันไม่มีจุดหมาย main ให้โปรแกรมอ่านหน้าจอกระโดดไป สิ่งเหล่านี้คือข้อบกพร่องจริงของหน้านี้ ที่เครื่องมือหาเจอ ไม่ใช่คน

จากนั้นหน้านี้ก็ถูกแก้ แบบที่คุณจะขอให้ Codex แก้ ส่วนเปิดของหน้าได้หัวข้อของตัวเอง ก่อนเริ่ม หัวข้อจึงไม่กระโดดจาก h1 ไป h3 อีก เนื้อหาของหน้าตอนนี้อยู่ในองค์ประกอบ main และฟอนต์ของชื่อเรื่อง คือ Inter ตอนนี้โหลดด้วย display=optional แทน display=swap: Inter ที่มาช้าเคยวาดชื่อเรื่องใหม่ในจำนวนบรรทัดที่น้อยลง และดันทั้งหน้าขึ้นไประหว่างโหลด ตอนนี้เบราว์เซอร์ใช้ Inter ก็ต่อเมื่อมันมาถึงทันที

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

🇨🇳 中文

它查看的网页就是这一页,也就是今天线上的样子。它的控制台是空的,七个请求里有六个得到了 200 的回答;第七个,网站的图标,在列清单时还在等待。Lighthouse 给可访问性、最佳实践、SEO 和智能体浏览都打了 100 分,52 项检查全部通过。10 月 16 日可不是这样。这一页的第一次检查是在 GitHub 的 Mac 上做的,可访问性得了 96 分,有三项检查没通过:网页在加载时移动了(布局偏移 0.139),它的语言标签是跳了一级的 h3 标题,而且它没有可供屏幕阅读器跳转的 main 地标。这些都是这一页真实的毛病,是工具找到的,不是人。

然后这一页被修好了,就像你会请 Codex 去修那样。开头那一节有了自己的标题 开始之前,于是标题不再从 h1 跳到 h3。网页的内容现在放在一个 main 元素里。标题的字体 Inter 现在用 display=optional 加载,而不是 display=swap:晚到的 Inter 曾把标题重画成更少的几行,在加载时把整页往上推;现在浏览器只在 Inter 立刻到达时才用它。

要在发布之前检查一个网页,就用 Python 自带的网页服务器把它的文件夹提供出来,再让这个文件指向那个地址。在网页的文件夹里运行第一条命令,让它在单独的一个终端窗口里一直运行。

python3 -m http.server 8000

then, in a second Terminal windowจากนั้น ในหน้าต่าง Terminal ที่สอง然后,在第二个终端窗口里

python3 devtools_check.py http://localhost:8000/codex_mac.html

what it printed, for the copy in the page's folderสิ่งที่มันพิมพ์ออกมา สำหรับสำเนาในโฟลเดอร์ของหน้าเว็บ它打印的内容,查看的是网页文件夹里的副本

chrome-devtools-mcp exposes content of the browser instance to the MCP clients allowing them to inspect,
debug, and modify any data in the browser or DevTools.
Avoid sharing sensitive or personal information that you do not want to share with MCP clients.
Performance tools may send trace URLs to the Google CrUX API to fetch real-user experience data. To disable, run with --no-performance-crux.
TOOL list_console_messages
## Console messages
<no console messages found>

TOOL list_network_requests
## Network requests
Showing 1-7 of 7 (Page 1 of 1).
reqid=1 GET http://localhost:8000/codex_mac.html [200]
reqid=2 GET http://localhost:8000/css/tefl-bilingual.css [200]
reqid=3 GET https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=optional [200]
reqid=4 GET https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;600;700&family=Nunito:wght@400;600;700;800&family=Fira+Code:wght@400;500&display=swap [200]
reqid=5 GET https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2 [200]
reqid=6 GET https://fonts.gstatic.com/s/firacode/v27/uU9NCBsR6Z2vfE9aq3bh3dSD.woff2 [200]
reqid=7 GET http://localhost:8000/favicon.ico [200]

TOOL lighthouse_audit
## Lighthouse audit results
Mode: navigation
Device: desktop
URL: http://localhost:8000/codex_mac.html
### Category scores
- Accessibility: 100 (accessibility)
- Best Practices: 100 (best-practices)
- SEO: 100 (seo)
- Agentic Browsing: 100 (agentic-browsing)
### Audit summary
Passed: 52
Failed: 0
Total timing: 6549.45ms
### Reports
- /tmp/chrome-devtools-mcp-hveNCJ/report.json
- /tmp/chrome-devtools-mcp-iQZ95k/report.html

🇬🇧 English

Nothing failed here either. Accessibility, best practices, SEO and agentic browsing all scored 100, and all 52 checks passed. Every request was answered with 200, the icon too, from the server of the page's own folder.

In a session you would say: Use the chrome-devtools tools to audit http://localhost:8000/codex_mac.html, then fix the failed checks in codex_mac.html. Codex reads the same report, edits the file, and runs the audit again, as above. That needs a signed-in Codex, so it was not run for this page; the fixes above were made without Codex.

🇹🇭 ไทย

ที่นี่ก็ไม่มีอะไรล้มเหลวเช่นกัน การเข้าถึง แนวปฏิบัติที่ดี SEO และการท่องเว็บโดยเอเจนต์ได้ 100 ทั้งหมด และการตรวจทั้ง 52 ข้อผ่านหมด ทุกคำขอได้คำตอบ 200 รวมทั้งไอคอน จากเซิร์ฟเวอร์ของโฟลเดอร์หน้าเว็บเอง

ในเซสชัน คุณจะพูดว่า Use the chrome-devtools tools to audit http://localhost:8000/codex_mac.html, then fix the failed checks in codex_mac.html (ใช้เครื่องมือ chrome-devtools ตรวจ http://localhost:8000/codex_mac.html แล้วแก้ข้อที่ไม่ผ่านใน codex_mac.html) Codex อ่านรายงานเดียวกันนี้ แก้ไฟล์ แล้วรันการตรวจอีกครั้ง อย่างข้างบน นั่นต้องใช้ Codex ที่ลงชื่อเข้าใช้แล้ว จึงไม่ได้รันสำหรับหน้านี้ การแก้ข้างบนทำโดยไม่ใช้ Codex

🇨🇳 中文

这里也没有任何一项失败。可访问性、最佳实践、SEO 和智能体浏览都是 100 分,52 项检查全部通过。每个请求都得到了 200 的回答,图标也是,来自网页自己文件夹的服务器。

在会话里你会说:Use the chrome-devtools tools to audit http://localhost:8000/codex_mac.html, then fix the failed checks in codex_mac.html(用 chrome-devtools 的工具检查 http://localhost:8000/codex_mac.html,然后修好 codex_mac.html 里没通过的检查)。Codex 会读同一份报告,修改文件,再像上面那样重新检查一遍。这需要一个已登录的 Codex,所以这一页没有运行它;上面的修改没有用 Codex。

🧠 Quiz 9🧠 แบบทดสอบ 9🧠 小测验 9

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

What does --no-usage-statistics do in step 9?--no-usage-statistics ในขั้นตอนที่ 9 ทำอะไร第 9 步里的 --no-usage-statistics 是做什么的?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

Without it, Chrome DevTools' tool server sends Google statistics about how its tools are used. The server says so itself in the first lines it prints.ถ้าไม่ใส่ เซิร์ฟเวอร์เครื่องมือของ Chrome DevTools จะส่งสถิติการใช้เครื่องมือไปให้ Google เซิร์ฟเวอร์บอกเรื่องนี้เองในบรรทัดแรก ๆ ที่มันพิมพ์不加它,Chrome DevTools 的工具服务器会把工具的使用统计发给 Google。服务器自己在打印的头几行里就说了这一点。

10 Context7: today's library documents · Context7: เอกสารไลบรารีฉบับวันนี้ · Context7:库的最新文档

🇬🇧 English

A model knows a library as the internet showed it on the day its training stopped. Libraries change after that; the model does not, and it writes code for a version that is gone. Context7 keeps the current documents of many libraries and answers questions about them. Its tool server gives Codex two tools: resolve-library-id turns a library's name into Context7's id for it, and query-docs returns the parts of that library's documents that answer a question, code included. Codex's manual lists it among the useful servers, as it does Playwright and Chrome DevTools.

Register it the way Context7's own page shows for Codex. It works without a key, with lower limits; a free key from context7.com raises them, and goes after --api-key at the end of the command. Your questions go to Context7's servers, so put nothing private in them. The tool server is free software, under the MIT licence; the service behind it is Context7's own.

docs_check.py asks the question that step 8 needed answered: how does Playwright for Python start a Chromium of your own? It takes the first id that the first tool offers and asks the second tool, the way Codex does. A question in the library's own words, executable_path here, finds more than a question in yours: launch chromium from a browser file of your own, tried first, brought back only examples that launch the usual browser.

🇹🇭 ไทย

โมเดลรู้จักไลบรารีอย่างที่อินเทอร์เน็ตแสดงไว้ในวันที่การฝึกของมันหยุดลง หลังจากนั้นไลบรารีเปลี่ยนไป แต่โมเดลไม่เปลี่ยน และมันเขียนโค้ดสำหรับเวอร์ชันที่ไม่มีแล้ว Context7 เก็บเอกสารฉบับปัจจุบันของไลบรารีจำนวนมาก และตอบคำถามเกี่ยวกับมัน เซิร์ฟเวอร์เครื่องมือของมันให้เครื่องมือสองตัวกับ Codex: resolve-library-id เปลี่ยนชื่อไลบรารีเป็น id ของ Context7 สำหรับไลบรารีนั้น และ query-docs ส่งคืนส่วนของเอกสารไลบรารีนั้นที่ตอบคำถาม รวมทั้งโค้ด คู่มือของ Codex ระบุว่ามันเป็นหนึ่งในเซิร์ฟเวอร์ที่มีประโยชน์ เช่นเดียวกับ Playwright และ Chrome DevTools

ลงทะเบียนมันแบบที่หน้าของ Context7 เองแสดงไว้สำหรับ Codex มันใช้ได้โดยไม่ต้องมีคีย์ แต่มีขีดจำกัดต่ำกว่า คีย์ฟรีจาก context7.com เพิ่มขีดจำกัด และใส่ไว้หลัง --api-key ท้ายคำสั่ง คำถามของคุณไปที่เซิร์ฟเวอร์ของ Context7 อย่าใส่อะไรที่เป็นความลับลงไป เซิร์ฟเวอร์เครื่องมือเป็นซอฟต์แวร์เสรี ภายใต้สัญญาอนุญาต MIT บริการเบื้องหลังเป็นของ Context7 เอง

docs_check.py ถามคำถามที่ขั้นตอนที่ 8 ต้องการคำตอบ: Playwright สำหรับ Python เริ่ม Chromium ของคุณเองอย่างไร มันเอา id แรกที่เครื่องมือตัวแรกเสนอ แล้วถามเครื่องมือตัวที่สอง แบบที่ Codex ทำ คำถามที่ใช้คำของไลบรารีเอง ในที่นี้คือ executable_path หาเจอมากกว่าคำถามที่ใช้คำของคุณ: launch chromium from a browser file of your own (เปิด chromium จากไฟล์เบราว์เซอร์ของคุณเอง) ที่ลองก่อน ได้กลับมาแค่ตัวอย่างที่เปิดเบราว์เซอร์ตัวปกติ

🇨🇳 中文

模型对一个库的了解,停在它训练结束那天互联网上的样子。那之后库在变,模型不变,它会为一个已经不存在的版本写代码。Context7 保存着许多库的最新文档,并回答关于它们的问题。它的工具服务器给 Codex 两个工具:resolve-library-id 把库的名字变成 Context7 给它的 id,query-docs 返回这个库的文档里回答某个问题的部分,连同代码。Codex 的手册把它列为有用的服务器之一,和 Playwright、Chrome DevTools 一样。

照 Context7 自己的页面为 Codex 给出的方式注册它。不用密钥也能用,只是限额低一些;context7.com 的免费密钥会提高限额,放在命令末尾的 --api-key 后面。你的问题会发到 Context7 的服务器上,所以别在里面放任何私密的东西。这个工具服务器是自由软件,采用 MIT 许可证;它背后的服务是 Context7 自己的。

docs_check.py 问的是第 8 步需要答案的那个问题:Playwright 的 Python 版怎样启动一个你自己的 Chromium?它取第一个工具给出的第一个 id,再问第二个工具,就像 Codex 那样。用库自己的词提问,这里是 executable_path,比用你自己的词找到的多:先试的 launch chromium from a browser file of your own(从你自己的浏览器文件启动 chromium),带回来的只是启动普通浏览器的示例。

codex mcp add context7 -- npx -y @upstash/context7-mcp@4.4.2

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

Added global MCP server 'context7'.

🇬🇧 English

🐍 The Python in this file. found.splitlines() cuts the answer into lines. The loop looks at each one: if ... in line finds the line with the id, line.split(":")[1] keeps what comes after the colon, .strip() takes the spaces off, and break stops the loop at the first one it finds. lines[:40] is the first 40 items of a list.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ found.splitlines() ตัดคำตอบเป็นบรรทัด ลูปดูทีละบรรทัด: if ... in line หาบรรทัดที่มี id line.split(":")[1] เก็บส่วนที่อยู่หลังเครื่องหมายทวิภาค .strip() ตัดช่องว่างออก และ break หยุดลูปที่บรรทัดแรกที่หาเจอ lines[:40] คือ 40 ตัวแรกของลิสต์

🇨🇳 中文

🐍 这个文件里的 Python。found.splitlines() 把回答切成一行一行。循环一行一行地看:if ... in line 找到带 id 的那一行,line.split(":")[1] 留下冒号后面的部分,.strip() 去掉空格,break 在找到的第一行就让循环停下。lines[:40] 是列表的前 40 项。

docs_check.py

# docs_check.py - look a question up in a library's current documents, through Context7.
#
#     python3 docs_check.py "playwright python" "launch chromium with executable_path"
#
# Your words go to Context7's servers, so put nothing private in them.

import sys
from mcp import start, use, stop

library = sys.argv[1]
question = sys.argv[2]
server = start(["npx", "-y", "@upstash/context7-mcp@4.4.2"])

# The first tool turns a library's name into Context7's id for it.
found = use(server, "resolve-library-id", {"libraryName": library, "query": question})
# Take the id from the first line that names one.
for line in found.splitlines():
    if "Context7-compatible library ID:" in line:
        library_id = line.split(":")[1].strip()
        break
print("LIBRARY", library_id)

# The second tool answers the question from that library's documents.
docs = use(server, "query-docs", {"libraryId": library_id, "query": question})
lines = docs.splitlines()
# Print the first 40 lines, and say how many more there are.
for line in lines[:40]:
    print(line)
if len(lines) > 40:
    print("...", len(lines) - 40, "more lines")

stop(server)
python3 docs_check.py "playwright python" "launch chromium with executable_path"

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

Context7 Documentation MCP Server v4.4.2 running on stdio
LIBRARY /microsoft/playwright-python
### Launch Chrome via channel or executable_path

Source: https://github.com/microsoft/playwright-python/blob/main/playwright/sync_api/_generated.py

Shows how to launch a branded Chrome/Edge browser using `channel` (e.g. "chrome", "msedge") or `executable_path` to override the bundled Chromium, with the full sync API signature and docstring explaining supported channel values.

```python
    def launch(
        self,
        *,
        executable_path: typing.Optional[typing.Union[pathlib.Path, str]] = None,
        channel: typing.Optional[str] = None,
        args: typing.Optional[typing.Sequence[str]] = None,
        ignore_default_args: typing.Optional[
            typing.Union[bool, typing.Sequence[str]]
        ] = None,
        handle_sigint: typing.Optional[bool] = None,
        handle_sigterm: typing.Optional[bool] = None,
        handle_sighup: typing.Optional[bool] = None,
        timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None,
        env: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None,
        headless: typing.Optional[bool] = None,
        proxy: typing.Optional[ProxySettings] = None,
        downloads_path: typing.Optional[typing.Union[pathlib.Path, str]] = None,
        slow_mo: typing.Optional[float] = None,
        traces_dir: typing.Optional[typing.Union[pathlib.Path, str]] = None,
        artifacts_dir: typing.Optional[typing.Union[pathlib.Path, str]] = None,
        chromium_sandbox: typing.Optional[bool] = None,
        firefox_user_prefs: typing.Optional[
            typing.Dict[str, typing.Union[str, float, bool]]
        ] = None,
    ) -> "Browser":
        """BrowserType.launch

        Parameters
        ----------
        executable_path : Union[pathlib.Path, str, None]
            Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is
            resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium,
            Firefox or WebKit, use at your own risk.
... 96 more lines

🇬🇧 English

The answer is the definition of Playwright's own launch function, from its source on GitHub. executable_path, args and ignore_default_args are three of its settings, and they are the three that browser_setup.py wrote for CloakBrowser in step 8, there under the names the JavaScript side of Playwright uses. The file shows the first 40 lines of the answer and counts the rest.

In a session you would say: Use context7 to check how Playwright for Python starts a browser from a file of my own, then fix my script. Codex calls the two tools and writes code against the documents of today. That needs a signed-in Codex, so it was not run for this page.

🇹🇭 ไทย

คำตอบคือนิยามของฟังก์ชัน launch ของ Playwright เอง จากซอร์สของมันบน GitHub executable_path, args และ ignore_default_args คือสามในการตั้งค่าของมัน และเป็นสามอย่างที่ browser_setup.py เขียนให้ CloakBrowser ในขั้นตอนที่ 8 ที่นั่นใช้ชื่อแบบที่ฝั่ง JavaScript ของ Playwright ใช้ ไฟล์นี้แสดงคำตอบ 40 บรรทัดแรก แล้วนับส่วนที่เหลือ

ในเซสชัน คุณจะพูดว่า Use context7 to check how Playwright for Python starts a browser from a file of my own, then fix my script (ใช้ context7 ตรวจว่า Playwright สำหรับ Python เริ่มเบราว์เซอร์จากไฟล์ของฉันเองอย่างไร แล้วแก้สคริปต์ของฉัน) Codex เรียกเครื่องมือทั้งสองตัว แล้วเขียนโค้ดตามเอกสารฉบับวันนี้ นั่นต้องใช้ Codex ที่ลงชื่อเข้าใช้แล้ว จึงไม่ได้รันสำหรับหน้านี้

🇨🇳 中文

回答是 Playwright 自己的 launch 函数的定义,来自它在 GitHub 上的源代码。executable_path、args 和 ignore_default_args 是它的三项设置,也正是第 8 步里 browser_setup.py 为 CloakBrowser 写下的那三项,在那里用的是 Playwright 的 JavaScript 一侧用的名字。这个文件显示回答的前 40 行,并数出剩下的行数。

在会话里你会说:Use context7 to check how Playwright for Python starts a browser from a file of my own, then fix my script(用 context7 查一查 Playwright 的 Python 版怎样从我自己的文件启动浏览器,然后修好我的脚本)。Codex 会调用这两个工具,按今天的文档写代码。这需要一个已登录的 Codex,所以这一页没有运行它。

🧠 Quiz 10🧠 แบบทดสอบ 10🧠 小测验 10

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

Why give Codex Context7?ทำไมถึงให้ Context7 กับ Codex为什么要给 Codex 接上 Context7?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

Libraries change after a model's training stops. Context7 answers from the current documents, so Codex writes code for the version you really have.ไลบรารีเปลี่ยนไปหลังการฝึกของโมเดลหยุดลง Context7 ตอบจากเอกสารปัจจุบัน Codex จึงเขียนโค้ดสำหรับรุ่นที่คุณมีจริง模型训练停止以后,库还在变。Context7 根据当前的文档回答,所以 Codex 写出的代码适合你真正在用的版本。

👥 Part 3 — Make it a team · ส่วนที่ 3 — ทำให้มันเป็นทีม · 第 3 部分 —— 把它变成一支团队

🇬🇧 English

One Codex can send out subagents: separate agents, each with its own conversation, that work at the same time and hand their results back to the one you are talking to. Reading is where they shine: one reads an app, one reads a branch, one asks the notebook, and your own conversation stays clear of their noise. Writing is where they collide: two agents editing the same file will undo each other. So the rule of thumb is: fan out to read, come back to write.

Codex ships three subagents, named default, worker and explorer. You can add your own, as step 11 does. A subagent inherits the sandbox and the approval setting of the session that spawned it, and it cannot have tool servers of its own: it uses the ones you registered in part 2. Each one costs its own tokens.

The other way to have many agents is to have many Codex. Step 12 starts several, each in its own copy of the repository, so that their edits cannot touch.

🇹🇭 ไทย

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

Codex มาพร้อมเอเจนต์ย่อยสามตัว ชื่อ default, worker และ explorer คุณเพิ่มของคุณเองได้ อย่างที่ขั้นตอนที่ 11 ทำ เอเจนต์ย่อยรับแซนด์บ็อกซ์และการตั้งค่าการอนุมัติของเซสชันที่สร้างมันมา และมีเซิร์ฟเวอร์เครื่องมือของตัวเองไม่ได้: มันใช้ตัวที่คุณลงทะเบียนในส่วนที่ 2 แต่ละตัวใช้โทเค็นของตัวเอง

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

🇨🇳 中文

一个 Codex 可以派出子智能体:各自独立的智能体,每个有自己的对话,同时工作,再把结果交回给你正在对话的那个。读是它们发光的地方:一个读应用,一个读分支,一个问笔记本,而你自己的对话不被它们的噪音打扰。写是它们相撞的地方:两个智能体编辑同一个文件会互相抵消。所以经验法则是:散开去读,回来再写。

Codex 自带三个子智能体,名叫 default、worker 和 explorer。你可以像第 11 步那样加自己的。子智能体继承派出它的那个会话的沙箱和审批设置,并且不能有自己的工具服务器:它用你在第 2 部分注册的那些。每一个都花自己的 token。

拥有很多智能体的另一种办法,是拥有很多 Codex。第 12 步启动好几个,每个都在仓库的一份自己的副本里,这样它们的改动就碰不到一起。

11 team_setup.py: three agents of your own · team_setup.py: เอเจนต์สามตัวของคุณเอง · team_setup.py:你自己的三个智能体

🇬🇧 English

A custom agent is one TOML file in ~/.codex/agents. It needs three keys: name, description, and developer_instructions, the standing orders the agent reads before any task. It may also set a model and how hard the model thinks, model_reasoning_effort, from low to high and beyond. The name in the file is what counts, not the file's name.

The three files below are a team for the lab of this page. The reader uses REA's tools and never edits a file. The librarian keeps the project's notebook with the notebooklm command, and follows the advice of step 6: an id on every command, --json, and never notebooklm use. The reviewer reads a change and reports what is wrong, most serious first, and also never edits. Two of three are read-only on purpose; that is the rule of thumb of part 3 written into the team.

team_setup.py copies the three files into place, and adds one table to the settings file: [agents] with max_concurrent_threads_per_session = 3, which caps how many subagents may be open at once besides your own conversation. Run it once without --yes to see what it would do, then with --yes. Both runs are shown.

Then, in a session, you ask for the team in words: Spawn the reader on Rectangle.app and the reviewer on this branch, wait for both, then have the librarian file the reader's report. Codex spawns them, and /subagents lets you look into each one's conversation. The Delegation section of the AGENTS.md in step 3 makes this the habit: it says which agent to spawn for which kind of task, so you do not have to say it every time. That needs a signed-in Codex, so it was not run for this page.

🇹🇭 ไทย

เอเจนต์แบบกำหนดเองคือไฟล์ TOML หนึ่งไฟล์ใน ~/.codex/agents มันต้องมีสามคีย์: name, description และ developer_instructions คือคำสั่งประจำที่เอเจนต์อ่านก่อนงานใด ๆ มันยังกำหนดโมเดลและความหนักที่โมเดลคิดได้ด้วย model_reasoning_effort ตั้งแต่ low ถึง high และสูงกว่านั้น ชื่อในไฟล์คือสิ่งที่นับ ไม่ใช่ชื่อไฟล์

สามไฟล์ข้างล่างคือทีมสำหรับแล็บของหน้านี้ reader ใช้เครื่องมือของ REA และไม่แก้ไฟล์เลย librarian ดูแลสมุดของโปรเจกต์ด้วยคำสั่ง notebooklm และทำตามคำแนะนำของขั้นตอนที่ 6: id ในทุกคำสั่ง --json และไม่ใช้ notebooklm use เด็ดขาด reviewer อ่านการเปลี่ยนแปลงแล้วรายงานสิ่งที่ผิด เรียงจากร้ายแรงที่สุดก่อน และไม่แก้ไฟล์เช่นกัน สองในสามอ่านอย่างเดียวโดยตั้งใจ นั่นคือกฎง่าย ๆ ของส่วนที่ 3 ที่เขียนลงไปในทีม

team_setup.py คัดลอกสามไฟล์ไปไว้ในที่ของมัน และเพิ่มตารางหนึ่งลงในไฟล์ตั้งค่า: [agents] พร้อม max_concurrent_threads_per_session = 3 ซึ่งจำกัดว่าเอเจนต์ย่อยเปิดพร้อมกันได้กี่ตัวนอกเหนือจากบทสนทนาของคุณเอง รันมันครั้งหนึ่งโดยไม่ใส่ --yes เพื่อดูว่ามันจะทำอะไร แล้วรันด้วย --yes แสดงทั้งสองครั้งไว้

จากนั้น ในเซสชัน คุณขอทีมด้วยคำพูด: Spawn the reader on Rectangle.app and the reviewer on this branch, wait for both, then have the librarian file the reader's report. (ส่ง reader ไปที่ Rectangle.app และ reviewer ไปที่สาขานี้ รอทั้งสอง แล้วให้ librarian เก็บรายงานของ reader) Codex สร้างพวกมันขึ้นมา และ /subagents ให้คุณมองเข้าไปในบทสนทนาของแต่ละตัว ส่วน Delegation ของ AGENTS.md ในขั้นตอนที่ 3 ทำให้เรื่องนี้เป็นนิสัย: มันบอกว่าเอเจนต์ตัวไหนควรถูกสร้างสำหรับงานแบบไหน คุณจึงไม่ต้องพูดทุกครั้ง นั่นต้องใช้ Codex ที่ลงชื่อเข้าใช้แล้ว จึงไม่ได้รันสำหรับหน้านี้

🇨🇳 中文

一个自定义智能体就是 ~/.codex/agents 里的一个 TOML 文件。它需要三个键:name、description,以及 developer_instructions,也就是智能体在任何任务之前都要读的常规指令。它还可以设定模型,以及模型思考得多用力,model_reasoning_effort,从 low 到 high 以及更高。算数的是文件里的名字,不是文件名。

下面三个文件是这一页实验室的团队。reader 用 REA 的工具,从不编辑文件。librarian 用 notebooklm 命令管理项目的笔记本,并遵守第 6 步的建议:每条命令都带 id,用 --json,永远不用 notebooklm use。reviewer 读一次改动,报告哪里错了,最严重的在前,同样从不编辑。三个里有两个是故意只读的;这就是第 3 部分的经验法则写进了团队里。

team_setup.py 把这三个文件复制到位,并往设置文件里加一张表:[agents],带 max_concurrent_threads_per_session = 3,它限制在你自己的对话之外同时最多能开几个子智能体。先不带 --yes 运行一次,看看它会做什么,再带 --yes 运行。两次都展示了。

然后,在会话里,你用话语要这支团队:Spawn the reader on Rectangle.app and the reviewer on this branch, wait for both, then have the librarian file the reader's report.(把 reader 派到 Rectangle.app,把 reviewer 派到这个分支,等两个都完成,再让 librarian 归档 reader 的报告。)Codex 把它们派出去,/subagents 让你看进每一个的对话。第 3 步 AGENTS.md 里的 Delegation 一节让这成为习惯:它写明哪种任务派哪个智能体,你就不必每次都说。这需要一个已登录的 Codex,所以这一页没有运行它。

agents/reader.toml

# reader: reads a program with REA's tools and reports. Never edits a file.
# name is how you ask for this agent; description is how Codex decides to send it work.
name = "reader"
description ="Reverse-engineering reader. Uses the REA tools to inspect an app, a binary or a JavaScript bundle, and reports what it found with the evidence, without editing any file."
# How long the model thinks before it answers: more is slower and costs more tokens.
model_reasoning_effort = "medium"
# The agent's own instructions, given to it with every task. Three quotes hold many lines.
developer_instructions = """
You read programs that someone else wrote. Use the REA tools (inspect, trace,
compare, analyze) and the reverse-engineer-anything skill.
For every finding, say which tool showed it and how sure REA is.
Do not edit, create or delete any file. Do not run the program you study.
Answer with: what was asked, what you found, where it was seen, what you could not find.
"""

agents/librarian.toml

# librarian: keeps the project's NotebookLM notebook, through the notebooklm command.
# name is how you ask for this agent; description is how Codex decides to send it work.
name = "librarian"
description ="NotebookLM librarian. Adds sources to the project's notebook with the notebooklm command, asks the notebook questions, and brings back answers with their citations."
# low: its work is running commands it is told, not working things out.
model_reasoning_effort = "low"
# The agent's own instructions, given to it with every task. Three quotes hold many lines.
developer_instructions = """
You keep the project's notebook in NotebookLM. Use the notebooklm skill.
Always pass the notebook's id with --notebook, and use --json so that the
answer can be read by a program. Never run notebooklm use.
When asked a question, run notebooklm ask and return the answer together
with the sources it cites. When given a URL or a file, run notebooklm source add
and report the new source's id. Do not generate audio or video unless asked.
"""

agents/reviewer.toml

# reviewer: reads a change the way a careful colleague would. Never edits a file.
# name is how you ask for this agent; description is how Codex decides to send it work.
name = "reviewer"
description ="Code reviewer focused on correctness, security and missing tests. Reads a diff or a branch and reports problems in order of severity, without editing any file."
# How long the model thinks before it answers: more is slower and costs more tokens.
model_reasoning_effort = "medium"
# The agent's own instructions, given to it with every task. Three quotes hold many lines.
developer_instructions = """
Review code like an owner. Read the change with git diff and the files it touches.
Report only real problems: a wrong result, a crash, a security hole, a test that
is missing for a changed behaviour. Say the file and the line for each.
Order them most serious first. Do not edit files. Do not praise.
"""

🇬🇧 English

🐍 The Python in this file. "--yes" in sys.argv is True when you typed --yes, and that one value decides whether the file changes anything: every change sits under an if write:. for name in ["reader", "librarian", "reviewer"]: handles the three agents, one each time round. config.read_text() if config.exists() else "" reads the file, or takes an empty string if there is no file yet. "[agents]" in text looks for a piece of text inside a longer one, so the table is added only once. Press both Run buttons, the dry run first.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ "--yes" in sys.argv เป็น True เมื่อคุณพิมพ์ --yes และค่านี้ค่าเดียวตัดสินว่าไฟล์จะเปลี่ยนอะไรหรือไม่: การเปลี่ยนทุกอย่างอยู่ใต้ if write: for name in ["reader", "librarian", "reviewer"]: จัดการเอเจนต์สามตัว รอบละหนึ่งตัว config.read_text() if config.exists() else "" อ่านไฟล์ หรือใช้สตริงว่างถ้ายังไม่มีไฟล์ "[agents]" in text หาข้อความชิ้นหนึ่งในข้อความที่ยาวกว่า ตารางจึงถูกเพิ่มเพียงครั้งเดียว กดปุ่ม Run ทั้งสองปุ่ม เริ่มจากการรันแบบลองก่อน

🇨🇳 中文

🐍 这个文件里的 Python。你输入了 --yes 时,"--yes" in sys.argv 就是 True,就这一个值决定文件会不会改东西:每一处修改都放在 if write: 下面。for name in ["reader", "librarian", "reviewer"]: 处理三个智能体,每一轮一个。config.read_text() if config.exists() else "" 读这个文件,如果还没有文件,就用一个空字符串。"[agents]" in text 在一段长文字里找一小段文字,所以这个表只会加一次。两个 Run 按钮都按一下,先按试运行的那个。

team_setup.py

# team_setup.py - give Codex a team: three agents of your own, and a limit.
#
#     python3 team_setup.py          say what it would do; change nothing
#     python3 team_setup.py --yes    do it
#
# Each agent is one .toml file, in the folder agents/ beside this file.
# Codex reads its agents from ~/.codex/agents.

import pathlib
import shutil
import sys

codex = pathlib.Path.home() / ".codex"
config = codex / "config.toml"
# True when you typed --yes after the file's name.
write = "--yes" in sys.argv
if not write:
    print("dry run: nothing is changed")

for name in ["reader", "librarian", "reviewer"]:
    print("agent", name, "-> ~/.codex/agents/" + name + ".toml")
    if write:
        (codex / "agents").mkdir(parents=True, exist_ok=True)
        shutil.copy("agents/" + name + ".toml", codex / "agents")

# The limit: at most three agents at once. It goes into config.toml only once,
# because a file with two [agents] tables would be broken.
limit = "\n[agents]\nmax_concurrent_threads_per_session = 3\n"
text = config.read_text() if config.exists() else ""
if "[agents]" in text:
    print("config.toml already has an [agents] table")
else:
    print("add [agents] to ~/.codex/config.toml")
    if write:
        config.write_text(text + limit)
python3 team_setup.py
python3 team_setup.py --yes

what the two commands printedสิ่งที่สองคำสั่งพิมพ์ออกมา两条命令打印的内容

dry run: nothing is changed
agent reader -> ~/.codex/agents/reader.toml
agent librarian -> ~/.codex/agents/librarian.toml
agent reviewer -> ~/.codex/agents/reviewer.toml
add [agents] to ~/.codex/config.toml
agent reader -> ~/.codex/agents/reader.toml
agent librarian -> ~/.codex/agents/librarian.toml
agent reviewer -> ~/.codex/agents/reviewer.toml
add [agents] to ~/.codex/config.toml

not run by us: what you type inside a signed-in sessionเราไม่ได้รันเอง: สิ่งที่คุณพิมพ์ในเซสชันที่ลงชื่อเข้าใช้แล้ว我们没有运行过:这是你在已登录的会话里输入的内容

Spawn the reader on Rectangle.app and the reviewer on this branch, wait for both, then have the librarian file the reader's report.
/subagents
CAREFUL A subagent's file cannot set its own sandbox, approvals or tool servers: it gets the session's. Telling the reader do not edit files is an instruction, not a wall; the wall is the sandbox of step 2. And when a subagent runs where nobody can answer a question, as in step 12, anything that needs an approval fails and the failure goes back to the parent. Give it a sandbox that fits the task instead.ไฟล์ของเอเจนต์ย่อยกำหนดแซนด์บ็อกซ์ การอนุมัติ หรือเซิร์ฟเวอร์เครื่องมือของตัวเองไม่ได้: มันได้ของเซสชันมา การบอก reader ว่า do not edit files (ห้ามแก้ไฟล์) คือคำสั่ง ไม่ใช่กำแพง กำแพงคือแซนด์บ็อกซ์ของขั้นตอนที่ 2 และเมื่อเอเจนต์ย่อยรันในที่ที่ไม่มีใครตอบคำถามได้ อย่างในขั้นตอนที่ 12 อะไรก็ตามที่ต้องการการอนุมัติจะล้มเหลว และความล้มเหลวจะถูกส่งกลับไปที่ตัวแม่ ให้แซนด์บ็อกซ์ที่เหมาะกับงานแทน子智能体的文件不能设定自己的沙箱、审批或工具服务器:它拿到的是会话的。告诉 reader do not edit files(不要编辑文件)是一条指令,不是一堵墙;墙是第 2 步的沙箱。而当子智能体在没有人能回答问题的地方运行时,比如第 12 步,任何需要审批的动作都会失败,失败会回到父智能体那里。换成给它一个适合任务的沙箱。

🧠 Quiz 11: what will this print?🧠 แบบทดสอบ 11: มันจะพิมพ์อะไร🧠 小测验 11:它会打印什么?

Pick an answer. Then the page shows which one is right and why, and you can run the program yourself.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูกและเพราะอะไร และคุณรันโปรแกรมเองได้选一个答案。然后这一页会告诉你哪个是对的、为什么,你也可以自己运行这个程序。

# Quiz 11: what will this print?
argv = ["team_setup.py"]

write = "--yes" in argv
print("mode", "write" if write else "dry run")

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

argv holds only the file's name, so "--yes" in argv is False, and "write" if write else "dry run" picks the second word. team_setup.py asks the same question, so a run without --yes changes nothing.argv มีแค่ชื่อไฟล์ "--yes" in argv จึงเป็น False และ "write" if write else "dry run" เลือกคำที่สอง team_setup.py ถามคำถามเดียวกัน การรันโดยไม่มี --yes จึงไม่เปลี่ยนอะไรเลยargv 里只有文件名,所以 "--yes" in argv 是 False,"write" if write else "dry run" 选了第二个词。team_setup.py 问的是同一个问题,所以不带 --yes 运行什么也不会改。

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

mode dry run

12 fan_out.py: several Codex at once, each in its own copy · fan_out.py: Codex หลายตัวพร้อมกัน แต่ละตัวในสำเนาของตัวเอง · fan_out.py:几个 Codex 同时运行,各在自己的副本里

🇬🇧 English

Subagents share one folder. For work that writes, give each agent a folder of its own. Git has a tool for exactly this, the worktree: a second checked-out copy of a repository, on its own branch, sharing the same history. Codex can make one for a session with --worktree, and the ChatGPT app makes them behind its windows. fan_out.py does it by hand, so that you can see every piece.

For agents 1 to N it makes a branch agent/N and a worktree beside the repository, and starts one codex exec in each, all at once. codex exec is Codex without a screen: you give it the task as an argument, it works, and it prints only its final message; -o result.md keeps that message as a file in the agent's own copy. --sandbox workspace-write lets each agent edit its copy, and only its copy. The file waits for all of them, and leaves each agent's result.md and branch for you: git diff agent/2 shows what the second one did, and git merge agent/2 keeps it.

With --dry-run at the end, the file prints the commands it would run and runs none; that is the output below, from our laptop. The real run needs a signed-in Codex, a git repository named codexlab, and a task worth three agents. The same task to three agents gives you three answers to choose from; three different tasks, one each, is how you split a big job.

🇹🇭 ไทย

เอเจนต์ย่อยใช้โฟลเดอร์เดียวร่วมกัน สำหรับงานที่เขียน ให้โฟลเดอร์ของตัวเองกับเอเจนต์แต่ละตัว Git มีเครื่องมือสำหรับเรื่องนี้โดยเฉพาะ คือ worktree: สำเนาที่เช็กเอาต์ไว้ชุดที่สองของรีโพซิทอรี บนสาขาของตัวเอง ใช้ประวัติเดียวกัน Codex สร้างให้เซสชันหนึ่งได้ด้วย --worktree และแอป ChatGPT สร้างมันอยู่หลังหน้าต่างของมัน fan_out.py ทำด้วยมือ เพื่อให้คุณเห็นทุกชิ้น

สำหรับเอเจนต์ 1 ถึง N มันสร้างสาขา agent/N และ worktree ข้างรีโพซิทอรี แล้วเริ่ม codex exec หนึ่งตัวในแต่ละอัน พร้อมกันทั้งหมด codex exec คือ Codex ที่ไม่มีหน้าจอ: คุณให้งานเป็นอาร์กิวเมนต์ มันทำงาน และพิมพ์เฉพาะข้อความสุดท้าย -o result.md เก็บข้อความนั้นเป็นไฟล์ในสำเนาของเอเจนต์เอง --sandbox workspace-write ให้เอเจนต์แต่ละตัวแก้สำเนาของมัน และเฉพาะสำเนาของมัน ไฟล์รอทั้งหมด แล้วทิ้ง result.md และสาขาของเอเจนต์แต่ละตัวไว้ให้คุณ: git diff agent/2 แสดงว่าตัวที่สองทำอะไร และ git merge agent/2 เก็บมันไว้

เมื่อใส่ --dry-run ไว้ท้ายสุด ไฟล์พิมพ์คำสั่งที่มันจะรันออกมาและไม่รันอะไรเลย นั่นคือผลลัพธ์ข้างล่าง จากแล็ปท็อปของเรา การรันจริงต้องใช้ Codex ที่ลงชื่อเข้าใช้แล้ว รีโพซิทอรี git ชื่อ codexlab และงานที่คุ้มค่ากับเอเจนต์สามตัว งานเดียวกันให้เอเจนต์สามตัวให้คำตอบสามแบบให้คุณเลือก งานสามอย่างต่างกัน ตัวละหนึ่งงาน คือวิธีแบ่งงานใหญ่

🇨🇳 中文

子智能体共用一个文件夹。对要写东西的工作,给每个智能体一个自己的文件夹。Git 正好有这样的工具,worktree:仓库的第二份检出副本,在自己的分支上,共享同一份历史。Codex 可以用 --worktree 为一个会话做一个,ChatGPT 应用在它的窗口后面也做这个。fan_out.py 用手做,好让你看见每一个零件。

对智能体 1 到 N,它建一个分支 agent/N 和一个紧挨着仓库的 worktree,然后在每一个里启动一个 codex exec,全部同时启动。codex exec 是没有屏幕的 Codex:你把任务作为参数给它,它干活,只打印最后一条消息;-o result.md 把那条消息存成智能体自己副本里的一个文件。--sandbox workspace-write 让每个智能体编辑它的副本,而且只能编辑它的副本。这个文件等它们全部结束,把每个智能体的 result.md 和分支留给你:git diff agent/2 显示第二个做了什么,git merge agent/2 把它留下。

把 --dry-run 放在最后,这个文件打印它将要运行的命令,一条也不运行;下面就是这样的输出,来自我们的笔记本电脑。真正运行需要已登录的 Codex、一个名叫 codexlab 的 git 仓库,以及一个值得三个智能体做的任务。同一个任务给三个智能体,你得到三个答案来挑;三个不同的任务各给一个,就是拆分一件大活的方法。

🇬🇧 English

🐍 The Python in this file. sys.argv[1] and sys.argv[2] are the first two words you typed after the file's name, and int() turns the text "3" into the number 3. range(1, count + 1) counts 1, 2, 3: a range stops before its end. An f-string, with f before the quotes, fills each {name} with a value: f"agent/{n}" is agent/1, then agent/2. agents.append(...) adds each agent it starts to a list, and the second loop waits for each one. subprocess.Popen starts a program and does not wait for it, which is how the agents work at the same time. With --dry-run the file only prints, so the page can run it: press Run.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ sys.argv[1] และ sys.argv[2] คือสองคำแรกที่คุณพิมพ์หลังชื่อไฟล์ และ int() เปลี่ยนข้อความ "3" เป็นตัวเลข 3 range(1, count + 1) นับ 1, 2, 3: range หยุดก่อนถึงค่าปลาย f-string ที่มี f หน้าเครื่องหมายคำพูดจะเติมค่าลงในแต่ละ {name}: f"agent/{n}" คือ agent/1 แล้วก็ agent/2 agents.append(...) เพิ่มเอเจนต์แต่ละตัวที่เริ่มแล้วลงในลิสต์ และลูปที่สองรอแต่ละตัว subprocess.Popen เริ่มโปรแกรมโดยไม่รอมัน เอเจนต์จึงทำงานพร้อมกันได้ เมื่อใส่ --dry-run ไฟล์แค่พิมพ์ หน้านี้จึงรันมันได้: กด Run

🇨🇳 中文

🐍 这个文件里的 Python。sys.argv[1] 和 sys.argv[2] 是你在文件名后面输入的头两个词,int() 把文字 "3" 变成数字 3。range(1, count + 1) 数 1、2、3:range 在终点之前就停下。f-string,也就是引号前面带 f 的字符串,会把每个 {name} 填上值:f"agent/{n}" 先是 agent/1,再是 agent/2。agents.append(...) 把每个启动了的智能体加进一个列表,第二个循环一个一个地等它们。subprocess.Popen 启动一个程序但不等它,智能体就是这样同时工作的。带上 --dry-run,这个文件只打印,所以这一页能运行它:按 Run。

fan_out.py

# fan_out.py - give one task to several Codex agents at once, each in its own copy.
#
#     python3 fan_out.py "write tests for lines.py" 3 --dry-run    show the commands only
#     python3 fan_out.py "write tests for lines.py" 3              run them
#
# Run it in the top folder of a git repository named codexlab. For each agent
# it makes a branch and a second copy of the repository (a worktree) beside it,
# and starts one "codex exec" in that copy. All the agents work at the same time.

import subprocess
import sys

task = sys.argv[1]               # what each agent should do
count = int(sys.argv[2])         # int() turns the text "3" into the number 3
dry = "--dry-run" in sys.argv

agents = []
# range(1, count + 1) counts 1, 2, 3: a range stops before its end.
for n in range(1, count + 1):
    branch = f"agent/{n}"
    folder = f"../codexlab_agent{n}"
    print(f"agent {n}: git worktree add -b {branch} {folder}")
    print(f'agent {n}: codex exec --sandbox workspace-write -o result.md "{task}"')
    if not dry:
        subprocess.run(["git", "worktree", "add", "-b", branch, folder])
        command = ["codex", "exec", "--sandbox", "workspace-write", "-o", "result.md", task]
        # Popen starts the agent and does not wait for it, so all of them run at once.
        agents.append(subprocess.Popen(command, cwd=folder))

# Now wait for every agent to finish.
for agent in agents:
    agent.wait()

if dry:
    print("dry run: nothing was made")
else:
    print("done: read result.md in each codexlab_agent folder")
python3 fan_out.py "write tests for lines.py" 3 --dry-run

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

agent 1: git worktree add -b agent/1 ../codexlab_agent1
agent 1: codex exec --sandbox workspace-write -o result.md "write tests for lines.py"
agent 2: git worktree add -b agent/2 ../codexlab_agent2
agent 2: codex exec --sandbox workspace-write -o result.md "write tests for lines.py"
agent 3: git worktree add -b agent/3 ../codexlab_agent3
agent 3: codex exec --sandbox workspace-write -o result.md "write tests for lines.py"
dry run: nothing was made

not run by usเราไม่ได้รันเอง我们没有运行过

python3 fan_out.py "write tests for lines.py" 3
git diff agent/2
git merge agent/2

🧠 Quiz 12: what will this print?🧠 แบบทดสอบ 12: มันจะพิมพ์อะไร🧠 小测验 12:它会打印什么?

Pick an answer. Then the page shows which one is right and why, and you can run the program yourself.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูกและเพราะอะไร และคุณรันโปรแกรมเองได้选一个答案。然后这一页会告诉你哪个是对的、为什么,你也可以自己运行这个程序。

# Quiz 12: what will this print?
repo = "codexlab"
count = 3

for n in range(1, count + 1):
    print(f"agent/{n} -> {repo}_agent{n}")

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

range(1, count + 1) counts 1, 2, 3: a range stops before its end. The f before the string fills in {n} and {repo}. fan_out.py names its branches and copies this way.range(1, count + 1) นับ 1, 2, 3: range หยุดก่อนถึงค่าปลาย f หน้าสตริงเติม {n} และ {repo} ให้ fan_out.py ตั้งชื่อสาขาและสำเนาด้วยวิธีนี้range(1, count + 1) 数 1、2、3:range 在终点之前就停下。字符串前面的 f 把 {n} 和 {repo} 填进去。fan_out.py 就是这样给分支和副本起名字的。

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

agent/1 -> codexlab_agent1
agent/2 -> codexlab_agent2
agent/3 -> codexlab_agent3

13 config_show.py: the whole wiring · config_show.py: การเดินสายทั้งหมด · config_show.py:整个接线

🇬🇧 English

The steps before this one wrote files. This one reads them all back. config_show.py prints the settings file with any value that looks like a key or a token replaced by stars, then the agents of step 11, the skills of steps 4, 6 and 7, and whether the sign-in file of step 2 is there. It changes nothing, and it prints nothing that should not be on a page: paths are written with ~, even inside a command, so the output is true on any Mac.

Read the output against the page. The six [mcp_servers] tables are steps 4, 5, 8, 9 and 10: REA's holds the command that starts its server; GitHub's holds the address and the name of the token's variable, not the token; Playwright's two hold the same command, the second with --config and the settings file of step 8; and the last two, Chrome DevTools' and Context7's, are commands like REA's. In config.toml itself that path is written in full, with your home folder's name in it, because the shell turned ~ into the full path when you registered the server; codex mcp list in step 8 shows it so. config_show.py writes it back as ~. REA's setup also gave its server a longer start-up time, 30 seconds instead of Codex's usual 10, because npx may have to download it first. [agents] is step 11. The three agents and the three skills are steps 4, 6, 7 and 11. The sign-in file is not there, because nobody signed in to Codex in our Linux container; on your Mac, after step 2, it is.

Run this file whenever Codex does something you did not expect. The answer is usually in one of these files.

🇹🇭 ไทย

ขั้นตอนก่อนหน้านี้เขียนไฟล์ ขั้นตอนนี้อ่านทั้งหมดกลับมา config_show.py พิมพ์ไฟล์ตั้งค่าโดยแทนค่าใดก็ตามที่ดูเหมือนคีย์หรือโทเค็นด้วยดาว แล้วเอเจนต์ของขั้นตอนที่ 11 สกิลของขั้นตอนที่ 4, 6 และ 7 และบอกว่าไฟล์ลงชื่อเข้าใช้ของขั้นตอนที่ 2 มีอยู่หรือไม่ มันไม่เปลี่ยนอะไร และไม่พิมพ์อะไรที่ไม่ควรอยู่บนหน้าเว็บ: เส้นทางเขียนด้วย ~ แม้แต่ในคำสั่ง ผลลัพธ์จึงเป็นจริงบนแมคทุกเครื่อง

อ่านผลลัพธ์เทียบกับหน้านี้ ตาราง [mcp_servers] หกตารางคือขั้นตอนที่ 4, 5, 8, 9 และ 10: ของ REA เก็บคำสั่งที่เริ่มเซิร์ฟเวอร์ของมัน ของ GitHub เก็บที่อยู่และชื่อตัวแปรของโทเค็น ไม่ใช่ตัวโทเค็น สองตารางของ Playwright เก็บคำสั่งเดียวกัน ตารางที่สองมี --config กับไฟล์ตั้งค่าของขั้นตอนที่ 8 และสองตารางสุดท้าย ของ Chrome DevTools และของ Context7 เป็นคำสั่งแบบเดียวกับของ REA ใน config.toml เอง เส้นทางนั้นเขียนเต็ม มีชื่อโฟลเดอร์บ้านของคุณอยู่ในนั้น เพราะเชลล์เปลี่ยน ~ เป็นเส้นทางเต็มตอนที่คุณลงทะเบียนเซิร์ฟเวอร์ codex mcp list ในขั้นตอนที่ 8 แสดงมันแบบนั้น config_show.py เขียนมันกลับเป็น ~ การตั้งค่าของ REA ยังให้เวลาเริ่มต้นที่นานขึ้นกับเซิร์ฟเวอร์ของมัน คือ 30 วินาทีแทนที่จะเป็น 10 ตามปกติของ Codex เพราะ npx อาจต้องดาวน์โหลดมันก่อน [agents] คือขั้นตอนที่ 11 เอเจนต์สามตัวและสกิลสามอันคือขั้นตอนที่ 4, 6, 7 และ 11 ไฟล์ลงชื่อเข้าใช้ไม่มี เพราะไม่มีใครลงชื่อเข้าใช้ Codex ในคอนเทนเนอร์ Linux ของเรา บนแมคของคุณ หลังขั้นตอนที่ 2 มันจะมี

รันไฟล์นี้ทุกครั้งที่ Codex ทำอะไรที่คุณไม่คาดคิด คำตอบมักอยู่ในไฟล์เหล่านี้ไฟล์ใดไฟล์หนึ่ง

🇨🇳 中文

前面的步骤写了文件。这一步把它们全部读回来。config_show.py 打印设置文件,把任何看起来像密钥或令牌的值换成星号,然后是第 11 步的智能体、第 4、6、7 步的技能,以及第 2 步的登录文件在不在。它什么都不改,也不打印任何不该出现在网页上的东西:路径都用 ~ 写,连命令里的也一样,所以这份输出在任何一台 Mac 上都成立。

对照这一页读这份输出。六张 [mcp_servers] 表是第 4、5、8、9、10 步:REA 的那张放着启动它服务器的命令;GitHub 的那张放着地址和令牌变量的名字,而不是令牌;Playwright 的两张放着同一条命令,第二张多了 --config 和第 8 步的设置文件;最后两张,Chrome DevTools 的和 Context7 的,是和 REA 的一样的命令。在 config.toml 本身里,这个路径是写全的,里面有你主文件夹的名字,因为你注册这个服务器时,shell 已经把 ~ 换成了完整路径;第 8 步的 codex mcp list 就是这样显示的。config_show.py 把它写回成 ~。REA 的设置还给它的服务器一个更长的启动时间,30 秒而不是 Codex 通常的 10 秒,因为 npx 可能要先把它下载下来。[agents] 是第 11 步。三个智能体和三个技能是第 4、6、7、11 步。登录文件不在,因为在我们的 Linux 容器里没有人登录 Codex;在你的 Mac 上,做完第 2 步之后它就在了。

每当 Codex 做了你意料之外的事,就运行这个文件。答案通常就在这些文件中的某一个里。

🇬🇧 English

🐍 The Python in this file. splitlines() cuts the settings file into lines, and the loop looks at each. line.replace(str(home), "~") writes your home folder as ~. line.split("=")[0] is the part before the =, the setting's name, and .strip() takes the spaces off. The loop inside it tries four words; name.endswith(word) asks whether the name ends with that word, and if it does, the value becomes stars. sorted(....iterdir()) lists a folder in order, as in step 7. Press Run: it reads whatever the pretend Mac holds, so the page runs steps 7 and 11 first if you have not.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ splitlines() ตัดไฟล์ตั้งค่าเป็นบรรทัด และลูปดูทีละบรรทัด line.replace(str(home), "~") เขียนโฟลเดอร์บ้านของคุณเป็น ~ line.split("=")[0] คือส่วนที่อยู่ก่อน = คือชื่อของการตั้งค่า และ .strip() ตัดช่องว่างออก ลูปข้างในลองคำสี่คำ name.endswith(word) ถามว่าชื่อลงท้ายด้วยคำนั้นหรือไม่ ถ้าใช่ ค่าจะกลายเป็นดอกจัน sorted(....iterdir()) แสดงรายการในโฟลเดอร์ตามลำดับ เหมือนในขั้นตอนที่ 7 กด Run: มันอ่านสิ่งที่อยู่ในแมคจำลอง ดังนั้นถ้าคุณยังไม่ได้รันขั้นตอนที่ 7 และ 11 หน้านี้จะรันให้ก่อน

🇨🇳 中文

🐍 这个文件里的 Python。splitlines() 把设置文件切成一行一行,循环一行一行地看。line.replace(str(home), "~") 把你的主文件夹写成 ~。line.split("=")[0] 是 = 前面的部分,也就是设置的名字,.strip() 去掉空格。里面那个循环试四个词;name.endswith(word) 问这个名字是不是以那个词结尾,如果是,值就变成星号。sorted(....iterdir()) 按顺序列出一个文件夹,和第 7 步一样。按 Run:它读的是假装的 Mac 里现有的东西,所以如果你还没运行第 7 和第 11 步,这一页会先替你运行。

config_show.py

# config_show.py - show how Codex is set up on this computer, with every secret hidden.
#
#     python3 config_show.py
#
# It prints Codex's settings file, then the agents and the skills Codex will find.
# It changes nothing.

import pathlib

home = pathlib.Path.home()
codex = home / ".codex"

print("SETTINGS FILE ~/.codex/config.toml")
for line in (codex / "config.toml").read_text().splitlines():
    # Write your home folder as ~, so the line says nothing about whose computer this is.
    line = line.replace(str(home), "~")
    # A line like api_key = "abc123" holds a secret: show stars instead of the value.
    name = line.split("=")[0].strip()
    for word in ["token", "key", "secret", "password"]:
        if "=" in line and name.endswith(word):
            line = name + ' = "********"'
    print("  " + line)

print("AGENTS ~/.codex/agents")
for path in sorted((codex / "agents").iterdir()):
    print("  " + path.name)

print("SKILLS ~/.agents/skills")
for path in sorted((home / ".agents" / "skills").iterdir()):
    print("  " + path.name)

# The file that holds your sign-in: say only whether it is there. Never show it.
print("SIGN-IN FILE ~/.codex/auth.json there:", (codex / "auth.json").exists())
python3 config_show.py

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

SETTINGS FILE ~/.codex/config.toml
  [mcp_servers.rea]
  command = "npx"
  args = ["-y", "rea-agents@6.3.0", "mcp"]
  startup_timeout_sec = 30.0
  
  [mcp_servers.github]
  url = "https://api.githubcopilot.com/mcp/"
  bearer_token_env_var = "GITHUB_TOKEN"
  
  [mcp_servers.playwright]
  command = "npx"
  args = ["-y", "@playwright/mcp@0.0.83"]
  
  [mcp_servers.cloakbrowser]
  command = "npx"
  args = ["-y", "@playwright/mcp@0.0.83", "--config", "~/.codex/cloakbrowser.json"]
  
  [mcp_servers.chrome-devtools]
  command = "npx"
  args = ["-y", "chrome-devtools-mcp@1.11.0", "--no-usage-statistics"]
  
  [mcp_servers.context7]
  command = "npx"
  args = ["-y", "@upstash/context7-mcp@4.4.2"]
  
  [agents]
  max_concurrent_threads_per_session = 3
AGENTS ~/.codex/agents
  librarian.toml
  reader.toml
  reviewer.toml
SKILLS ~/.agents/skills
  mac-app-report
  notebooklm
  reverse-engineer-anything
SIGN-IN FILE ~/.codex/auth.json there: False

🧠 Quiz 13: what will this print?🧠 แบบทดสอบ 13: มันจะพิมพ์อะไร🧠 小测验 13:它会打印什么?

Pick an answer. Then the page shows which one is right and why, and you can run the program yourself.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูกและเพราะอะไร และคุณรันโปรแกรมเองได้选一个答案。然后这一页会告诉你哪个是对的、为什么,你也可以自己运行这个程序。

# Quiz 13: what will this print?
line = 'api_key = "abc123"'

name = line.split("=")[0].strip()
if name.endswith("key"):
    line = name + ' = "********"'
print(line)

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

line.split("=")[0] is api_key , and .strip() takes the space off. The name ends with key, so the if builds a new line with stars for the value. config_show.py keeps secrets off a page the same way.line.split("=")[0] คือ api_key และ .strip() ตัดช่องว่างออก ชื่อลงท้ายด้วย key ดังนั้น if จึงสร้างบรรทัดใหม่ที่มีดอกจันแทนค่า config_show.py กันความลับไม่ให้ขึ้นไปบนหน้าเว็บด้วยวิธีเดียวกันline.split("=")[0] 是 api_key ,.strip() 去掉空格。这个名字以 key 结尾,所以 if 造出新的一行,值换成了星号。config_show.py 也是这样让秘密不上网页的。

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

api_key = "********"

⏰ Part 4 — Codex without you · ส่วนที่ 4 — Codex ตอนที่คุณไม่อยู่ · 第 4 部分 —— 你不在时的 Codex

🇬🇧 English

Two more situations. You are asleep, and there is a task worth doing every night. Or you are out, with a phone, and the Codex on your Mac is in the middle of something. For the first, the Mac's own scheduler starts codex exec for you. For the second, the ChatGPT app pairs your phone with your Mac. Both need a Mac that is on, awake and signed in; neither needs a server of yours on the internet.

🇹🇭 ไทย

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

🇨🇳 中文

还有两种情形。你在睡觉,而有一件值得每晚做的任务。或者你在外面,手里只有手机,而你 Mac 上的 Codex 正做到一半。第一种,Mac 自带的调度器替你启动 codex exec。第二种,ChatGPT 应用把你的手机和你的 Mac 配对。两种都需要一台开着、醒着、已登录的 Mac;两种都不需要你在互联网上有一台服务器。

14 schedule_setup.py: a task every night · schedule_setup.py: งานทุกคืน · schedule_setup.py:每晚一个任务

🇬🇧 English

Codex has no scheduler of its own in the terminal. The ChatGPT desktop app has scheduled tasks, which can work in a project on your Mac while the app is open and the computer is on, and its manual says the terminal program does not offer them. The Mac has a scheduler of its own: launchd, which starts every background program on a Mac. A job of yours is one .plist file in ~/Library/LaunchAgents: what to run, when, and where to keep what it printed.

nightly.sh is the task. It sets PATH first, because launchd starts a job with almost no path and would not find codex; it goes to its own folder; and it runs codex exec with the sandbox of step 2 and -o, which keeps Codex's last message as a file named for the date. codex exec --help, below, shows both flags; it is the same program as step 12, with a prompt and no screen.

schedule_setup.py writes the job file for 02:30 every night, and that is all it does; it was run on our laptop. You load the job yourself, with the commands after it: plutil -lint checks that the file is well formed, launchctl bootstrap loads it into your user's part of launchd, and launchctl print shows what launchd knows: state = not running means loaded and waiting for the time. launchctl bootout and rm take it away again. Those commands need a Mac and were not run by us, and neither was the job itself, at 02:30, which needs a signed-in Codex. In the morning, read nightly_ and the date .md in the folder, and the log file the output names.

🇹🇭 ไทย

Codex ไม่มีตัวจัดตารางของตัวเองในเทอร์มินัล แอป ChatGPT สำหรับเดสก์ท็อปมี scheduled tasks (งานตามตาราง) ซึ่งทำงานในโปรเจกต์บนแมคของคุณได้ตอนที่แอปเปิดอยู่และคอมพิวเตอร์เปิดอยู่ และคู่มือของมันบอกว่าโปรแกรมเทอร์มินัลไม่มีสิ่งนี้ให้ แมคมีตัวจัดตารางของตัวเอง: launchd ซึ่งเริ่มโปรแกรมเบื้องหลังทุกโปรแกรมบนแมค งานของคุณคือไฟล์ .plist หนึ่งไฟล์ใน ~/Library/LaunchAgents: รันอะไร เมื่อไร และเก็บสิ่งที่มันพิมพ์ไว้ที่ไหน

nightly.sh คือตัวงาน มันตั้ง PATH ก่อน เพราะ launchd เริ่มงานโดยแทบไม่มีเส้นทางเลยและจะหา codex ไม่เจอ มันไปที่โฟลเดอร์ของตัวเอง และรัน codex exec ด้วยแซนด์บ็อกซ์ของขั้นตอนที่ 2 และ -o ซึ่งเก็บข้อความสุดท้ายของ Codex เป็นไฟล์ที่ตั้งชื่อตามวันที่ codex exec --help ข้างล่างแสดงแฟล็กทั้งสอง มันคือโปรแกรมเดียวกับขั้นตอนที่ 12 มีพรอมต์และไม่มีหน้าจอ

schedule_setup.py เขียนไฟล์งานสำหรับเวลา 02:30 ทุกคืน และทำแค่นั้น มันถูกรันบนแล็ปท็อปของเรา คุณโหลดงานเองด้วยคำสั่งที่ตามมา: plutil -lint ตรวจว่าไฟล์เขียนถูกรูปแบบ launchctl bootstrap โหลดมันเข้าส่วนของผู้ใช้ของคุณใน launchd และ launchctl print แสดงว่า launchd รู้อะไรบ้าง: state = not running หมายถึงโหลดแล้วและกำลังรอเวลา launchctl bootout และ rm เอามันออกอีกครั้ง คำสั่งเหล่านั้นต้องใช้แมค เราจึงไม่ได้รัน และตัวงานเองตอน 02:30 ที่ต้องใช้ Codex ที่ลงชื่อเข้าใช้แล้ว ก็ไม่ได้รันเช่นกัน ตอนเช้า อ่าน nightly_ ตามด้วยวันที่ .md ในโฟลเดอร์ และไฟล์บันทึกที่ผลลัพธ์ระบุชื่อ

🇨🇳 中文

Codex 在终端里没有自己的调度器。ChatGPT 桌面应用有 scheduled tasks(定时任务),在应用开着、电脑开着时可以在你 Mac 上的项目里工作,它的手册说终端程序不提供这个。Mac 有自己的调度器:launchd,Mac 上每一个后台程序都由它启动。你的一个作业就是 ~/Library/LaunchAgents 里的一个 .plist 文件:运行什么,什么时候,把打印的内容放在哪里。

nightly.sh 是任务本身。它先设置 PATH,因为 launchd 启动作业时几乎没有路径,会找不到 codex;它进入自己所在的文件夹;然后带着第 2 步的沙箱和 -o 运行 codex exec,-o 把 Codex 的最后一条消息存成一个以日期命名的文件。下面的 codex exec --help 显示这两个标志;它和第 12 步是同一个程序,带一个提示,没有屏幕。

schedule_setup.py 写出每晚 02:30 的作业文件,它只做这一件事;它是在我们的笔记本电脑上运行的。作业由你自己装入,用它后面的那些命令:plutil -lint 检查文件写得对不对,launchctl bootstrap 把它装进 launchd 里属于你这个用户的那部分,launchctl print 显示 launchd 知道些什么:state = not running 的意思是已装入、正在等时间。launchctl bootout 和 rm 再把它拿掉。这些命令需要 Mac,我们没有运行过;作业本身在 02:30 需要已登录的 Codex,也没有运行。早上,读文件夹里 nightly_ 加日期的 .md,以及输出里写明的日志文件。

nightly.sh

#!/bin/zsh
# nightly.sh - the task Codex does on its own each night. launchd starts it: see schedule_setup.py.

# launchd starts a job with almost no PATH, so say where codex and node are.
export PATH="/opt/homebrew/bin:/usr/local/bin:$HOME/.npm-global/bin:$PATH"

# Work in the folder this file is in, whatever it is called.
cd "$(dirname "$0")" || exit 1

# One task, no screen, edits allowed inside this folder only, last message kept as a file.
# date +%F is today's date, such as 2026-10-16, so each night keeps a file of its own.
codex exec --sandbox workspace-write -o "nightly_$(date +%F).md" \
  "Read AGENTS.md. Review every change committed since yesterday, run the tests, and write what you found."

🇬🇧 English

🐍 The Python in this file. plist is an f-string between three quote marks: a template over many lines, and each {label}, {script} and {log} is filled with the value of that variable. pathlib.Path.cwd() is the folder you are in, so run the file where nightly.sh is. mkdir(parents=True, exist_ok=True) makes the folder, and any folder above it that is missing, and does not mind if it is there already. write_text writes the job. Press Run: the page writes the job in its pretend Mac too; only a real Mac can load it.

🇹🇭 ไทย

🐍 Python ในไฟล์นี้ plist คือ f-string ที่อยู่ในเครื่องหมายคำพูดสามตัว: แม่แบบที่ยาวหลายบรรทัด และแต่ละ {label}, {script} และ {log} จะถูกเติมด้วยค่าของตัวแปรชื่อนั้น pathlib.Path.cwd() คือโฟลเดอร์ที่คุณอยู่ ดังนั้นให้รันไฟล์ในที่ที่มี nightly.sh mkdir(parents=True, exist_ok=True) สร้างโฟลเดอร์ และโฟลเดอร์ที่อยู่เหนือมันที่ยังไม่มี และไม่ว่าอะไรถ้ามันมีอยู่แล้ว write_text เขียนงาน กด Run: หน้านี้เขียนงานลงในแมคจำลองของมันด้วย แต่มีแค่แมคจริงที่โหลดมันได้

🇨🇳 中文

🐍 这个文件里的 Python。plist 是写在三个引号之间的 f-string:一个跨好几行的模板,每个 {label}、{script} 和 {log} 都会填上同名变量的值。pathlib.Path.cwd() 是你所在的文件夹,所以要在 nightly.sh 所在的地方运行这个文件。mkdir(parents=True, exist_ok=True) 建出这个文件夹,以及它上面缺少的文件夹,已经有了也没关系。write_text 写出这个作业。按 Run:这一页也会在它假装的 Mac 里写出这个作业;只有真正的 Mac 才能装入它。

schedule_setup.py

# schedule_setup.py - write a job for the Mac's scheduler: run nightly.sh every night at 02:30.
#
#     python3 schedule_setup.py      (in the folder where nightly.sh is)
#
# launchd is the scheduler of every Mac. A job is one .plist file in
# ~/Library/LaunchAgents: what to run, when, and where to keep what it printed.
# This file only writes the job. You load it with launchctl, as the page shows.

import pathlib

home = pathlib.Path.home()
label = "ai.krueng.codex.nightly"
# cwd() is the folder you are in: nightly.sh must be there.
script = pathlib.Path.cwd() / "nightly.sh"
log = home / "Library" / "Logs" / "codex-nightly.log"

# An f-string fills each {name} with the value of that variable.
plist = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>            <string>{label}</string>
  <key>ProgramArguments</key> <array><string>/bin/zsh</string><string>{script}</string></array>
  <key>StartCalendarInterval</key>
  <dict><key>Hour</key><integer>2</integer><key>Minute</key><integer>30</integer></dict>
  <key>StandardOutPath</key>  <string>{log}</string>
  <key>StandardErrorPath</key> <string>{log}</string>
</dict>
</plist>
"""

job = home / "Library" / "LaunchAgents" / (label + ".plist")
job.parent.mkdir(parents=True, exist_ok=True)
job.write_text(plist)
print("wrote ~/Library/LaunchAgents/" + label + ".plist")
print("it runs nightly.sh at 02:30 every night")
codex exec --help

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

Run Codex non-interactively

Usage: codex exec [OPTIONS] [PROMPT]
       codex exec [OPTIONS] <COMMAND> [ARGS]

Commands:
  resume  Resume a previous session by id or pick the most recent with --last
  fork    Fork a previous session by id into a new session
  review  Run a code review against the current repository
  help    Print this message or the help of the given subcommand(s)

Arguments:
  [PROMPT]
          Initial instructions for the agent. If not provided as an argument (or if `-` is used),
          instructions are read from stdin. If stdin is piped and a prompt is also provided, stdin
          is appended as a `<stdin>` block

Options:
  -c, --config <key=value>
          Override a configuration value that would otherwise be loaded from `~/.codex/config.toml`.
          Use a dotted path (`foo.bar.baz`) to override nested values. The `value` portion is parsed
          as TOML. If it fails to parse as TOML, the raw string is used as a literal.
          
          Examples: - `-c model="o3"` - `-c 'sandbox_permissions=["disk-full-read-access"]'` - `-c
          shell_environment_policy.inherit=all`

      --enable <FEATURE>
          Enable a feature (repeatable). Equivalent to `-c features.<name>=true`

      --disable <FEATURE>
          Disable a feature (repeatable). Equivalent to `-c features.<name>=false`

      --strict-config
          Error out when config.toml contains fields that are not recognized by this version of
          Codex

  -i, --image <FILE>...
          Optional image(s) to attach to the initial prompt

  -m, --model <MODEL>
          Model the agent should use

      --oss
          Use open-source provider

      --local-provider <OSS_PROVIDER>
          Specify which local provider to use (lmstudio or ollama). If not specified with --oss,
          will use config default or show selection

  -p, --profile <CONFIG_PROFILE_V2>
          Layer $CODEX_HOME/<name>.config.toml on top of the base user config

  -s, --sandbox <SANDBOX_MODE>
          Select the sandbox policy to use when executing model-generated shell commands
          
          [possible values: read-only, workspace-write, danger-full-access]

      --approve-for-me
          Route approval requests through automatic review using the workspace-write sandbox

      --dangerously-bypass-approvals-and-sandbox
          Skip all confirmation prompts and execute commands without sandboxing. EXTREMELY
          DANGEROUS. Intended solely for running in environments that are externally sandboxed

      --dangerously-bypass-hook-trust
          Run enabled hooks without requiring persisted hook trust for this invocation. DANGEROUS.
          Intended only for automation that already vets hook sources

  -C, --cd <DIR>
          Tell the agent to use the specified directory as its working root

      --worktree
          Run the session in a new managed Git worktree

      --add-dir <DIR>
          Additional directories that should be writable alongside the primary workspace

      --thread-source <SOURCE>
          Source classification for newly created or forked threads

      --cyber-access-program <PROGRAM>
          Request an experimental Cyber access program for this turn (OpenAI provider only). Omit to
          use server defaults. Not supported with review; fork requires a prompt
          
          [possible values: standard, daybreak_blue, daybreak_red]

      --skip-git-repo-check
          Allow running Codex outside a Git repository

      --ephemeral
          Run without persisting session files to disk

      --ignore-user-config
          Do not load `$CODEX_HOME/config.toml`; auth still uses `CODEX_HOME`

      --ignore-rules
          Do not load user or project execpolicy `.rules` files

      --output-schema <FILE>
          Path to a JSON Schema file describing the model's final response shape

      --color <COLOR>
          Specifies color settings for use in the output
          
          [default: auto]
          [possible values: always, never, auto]

      --json
          Print events to stdout as JSONL

  -o, --output-last-message <FILE>
          Specifies file where the last message from the agent should be written

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version
python3 schedule_setup.py

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

wrote ~/Library/LaunchAgents/ai.krueng.codex.nightly.plist
it runs nightly.sh at 02:30 every night

not run by us: load the job into launchd, and look at it (a Mac only)เราไม่ได้รันเอง: โหลดงานเข้า launchd แล้วดูมัน (แมคเท่านั้น)我们没有运行过:把作业装进 launchd,再看看它(只能在 Mac 上)

plutil -lint ~/Library/LaunchAgents/ai.krueng.codex.nightly.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.krueng.codex.nightly.plist
launchctl print gui/$(id -u)/ai.krueng.codex.nightly | grep state

not run by us: take the job away againเราไม่ได้รันเอง: เอางานออกอีกครั้ง我们没有运行过:再把作业拿掉

launchctl bootout gui/$(id -u)/ai.krueng.codex.nightly
rm ~/Library/LaunchAgents/ai.krueng.codex.nightly.plist

not run by us: the next morningเราไม่ได้รันเอง: เช้าวันถัดไป我们没有运行过:第二天早上

cat nightly_$(date +%F).md
tail -50 ~/Library/Logs/codex-nightly.log
CAREFUL At 02:30 nobody is there to approve anything. A command the sandbox refuses simply fails, and Codex works around it or gives up; that is what you want. So give a night job the sandbox its task needs and no more, and never danger-full-access. A Mac that is asleep runs nothing: a closed laptop sleeps, and launchd runs the missed job when the Mac wakes, not at 02:30.ตอน 02:30 ไม่มีใครอยู่อนุมัติอะไร คำสั่งที่แซนด์บ็อกซ์ปฏิเสธจะล้มเหลวเฉย ๆ และ Codex จะหาทางอื่นหรือยอมแพ้ ซึ่งคือสิ่งที่คุณต้องการ ดังนั้นให้งานกลางคืนมีแซนด์บ็อกซ์เท่าที่งานต้องการและไม่มากกว่านั้น และห้ามใช้ danger-full-access เด็ดขาด แมคที่หลับอยู่ไม่รันอะไรเลย: แล็ปท็อปที่ปิดฝาจะหลับ และ launchd จะรันงานที่พลาดไปตอนแมคตื่น ไม่ใช่ตอน 02:3002:30 时没有人在场审批任何东西。被沙箱拒绝的命令只是失败,Codex 会绕过它或放弃;这正是你想要的。所以给夜间作业的沙箱只要够它的任务用就好,不要更多,永远不要 danger-full-access。睡着的 Mac 什么都不运行:合上盖子的笔记本会睡觉,launchd 会在 Mac 醒来时补跑错过的作业,而不是在 02:30。

🧠 Quiz 14: what will this print?🧠 แบบทดสอบ 14: มันจะพิมพ์อะไร🧠 小测验 14:它会打印什么?

Pick an answer. Then the page shows which one is right and why, and you can run the program yourself.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูกและเพราะอะไร และคุณรันโปรแกรมเองได้选一个答案。然后这一页会告诉你哪个是对的、为什么,你也可以自己运行这个程序。

# Quiz 14: what will this print?
hour = 2
minute = 30

print(f"runs every night at {hour}:{minute}")

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

The f before the string fills {hour} and {minute} with the values of those variables, exactly as they are: the number 2 is written 2, not 02. schedule_setup.py fills its job file the same way.f หน้าสตริงเติม {hour} และ {minute} ด้วยค่าของตัวแปรเหล่านั้นตามที่มันเป็น: เลข 2 เขียนว่า 2 ไม่ใช่ 02 schedule_setup.py เติมไฟล์งานของมันด้วยวิธีเดียวกัน字符串前面的 f 用那两个变量的值原样填进 {hour} 和 {minute}:数字 2 写成 2,而不是 02。schedule_setup.py 也是这样填它的作业文件的。

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

runs every night at 2:30

15 From your phone · จากโทรศัพท์ของคุณ · 从你的手机

🇬🇧 English

The way Codex's manual gives is through two apps: the ChatGPT desktop app on the Mac, and the ChatGPT app on the phone, signed in to the same account. codex app, in the terminal, opens the desktop app, or its installer if it is not there. On the Mac, open Settings, then Connections, then Control this Mac or PC, and choose Set up; approve, and scan the QR code it shows with the phone. From then on the phone can start a chat in one of the Mac's projects or continue one, answer a question Codex asks, approve a command, read a diff or a test's output, and get a notification when a task ends. Everything still runs on the Mac: its files, the tool servers of part 2, the skills, the sandbox and approvals of step 2.

Three conditions. The Mac must be awake, online and in the app; a setting on the Mac, Prevent sleep when computer is plugged in and remote access is enabled, keeps it awake on power, and a lid closed on power can stay available, but choosing Sleep ends it. The manual also says the pairing cannot be set up from the terminal program. The terminal does have an experimental codex remote-control, with start, stop and pair; its help is below, and experimental means its shape may change. We could not try the pairing: GitHub's Mac has no screen, no ChatGPT app and no phone.

The older way needs no app: a terminal on the phone. Put the Mac and the phone on one private network, with Tailscale for example, and open an SSH connection from a terminal app on the phone. Start Codex inside tmux, so that the session goes on when the phone's connection drops, and come back to it with tmux attach. It is the same Codex, with the same files and settings; only the keyboard is small.

🇹🇭 ไทย

วิธีที่คู่มือของ Codex ให้ไว้คือผ่านสองแอป: แอป ChatGPT สำหรับเดสก์ท็อปบนแมค และแอป ChatGPT บนโทรศัพท์ ที่ลงชื่อเข้าใช้บัญชีเดียวกัน codex app ในเทอร์มินัลเปิดแอปเดสก์ท็อป หรือตัวติดตั้งของมันถ้ายังไม่มี บนแมค เปิด Settings แล้ว Connections แล้ว Control this Mac or PC (ควบคุมแมคหรือพีซีเครื่องนี้) และเลือก Set up อนุมัติ แล้วสแกนรหัส QR ที่มันแสดงด้วยโทรศัพท์ จากนั้นโทรศัพท์เริ่มแชตในโปรเจกต์หนึ่งของแมคหรือทำต่อจากเดิมได้ ตอบคำถามที่ Codex ถามได้ อนุมัติคำสั่งได้ อ่าน diff หรือผลลัพธ์ของเทสต์ได้ และได้รับการแจ้งเตือนเมื่องานจบ ทุกอย่างยังคงรันบนแมค: ไฟล์ของมัน เซิร์ฟเวอร์เครื่องมือของส่วนที่ 2 สกิล แซนด์บ็อกซ์และการอนุมัติของขั้นตอนที่ 2

สามเงื่อนไข แมคต้องตื่นอยู่ ออนไลน์ และอยู่ในแอป การตั้งค่าบนแมค Prevent sleep when computer is plugged in and remote access is enabled (ป้องกันการหลับเมื่อเสียบไฟและเปิดการเข้าถึงระยะไกล) ทำให้มันตื่นอยู่เมื่อเสียบไฟ และฝาที่ปิดอยู่เมื่อเสียบไฟยังคงใช้ได้ แต่การเลือก Sleep จะจบมัน คู่มือยังบอกว่าตั้งค่าการจับคู่จากโปรแกรมเทอร์มินัลไม่ได้ เทอร์มินัลมี codex remote-control แบบทดลอง พร้อม start, stop และ pair ข้อความช่วยเหลือของมันอยู่ข้างล่าง และแบบทดลองหมายความว่ารูปร่างของมันอาจเปลี่ยน เราลองการจับคู่ไม่ได้: แมคของ GitHub ไม่มีหน้าจอ ไม่มีแอป ChatGPT และไม่มีโทรศัพท์

วิธีเก่ากว่าไม่ต้องใช้แอป: เทอร์มินัลบนโทรศัพท์ เอาแมคกับโทรศัพท์ไว้บนเครือข่ายส่วนตัวเดียวกัน เช่นด้วย Tailscale แล้วเปิดการเชื่อมต่อ SSH จากแอปเทอร์มินัลบนโทรศัพท์ เริ่ม Codex ใน tmux เพื่อให้เซสชันดำเนินต่อเมื่อการเชื่อมต่อของโทรศัพท์หลุด และกลับมาหามันด้วย tmux attach มันคือ Codex ตัวเดียวกัน ไฟล์และการตั้งค่าเดียวกัน มีแต่แป้นพิมพ์ที่เล็กลง

🇨🇳 中文

Codex 手册给出的办法要用两个应用:Mac 上的 ChatGPT 桌面应用,和手机上的 ChatGPT 应用,登录同一个账户。在终端里,codex app 打开桌面应用,没有的话就打开它的安装器。在 Mac 上,打开 Settings,再到 Connections,再到 Control this Mac or PC(控制这台 Mac 或 PC),选 Set up;批准,然后用手机扫它显示的二维码。从此手机就能在 Mac 的某个项目里开始一段聊天或接着聊,回答 Codex 提出的问题,批准一条命令,读一份 diff 或一次测试的输出,并在任务结束时收到通知。一切仍然在 Mac 上运行:它的文件、第 2 部分的工具服务器、技能、第 2 步的沙箱和审批。

三个条件。Mac 必须醒着、在线、开着应用;Mac 上的一项设置 Prevent sleep when computer is plugged in and remote access is enabled(接通电源并开启远程访问时阻止睡眠)让它在通电时保持清醒,通电时合上盖子也能继续可用,但选择 Sleep 就会结束。手册还说,这种配对不能从终端程序设置。终端确实有一个实验性的 codex remote-control,带 start、stop 和 pair;它的帮助在下面,实验性意味着它的形状可能会变。我们没法试配对:GitHub 的 Mac 没有屏幕、没有 ChatGPT 应用、没有手机。

更老的办法不需要应用:手机上的一个终端。把 Mac 和手机放在同一个私有网络里,比如用 Tailscale,然后从手机上的终端应用打开一条 SSH 连接。在 tmux 里启动 Codex,这样手机断线时会话还在继续,再用 tmux attach 回到它。还是同一个 Codex,同样的文件和设置;只是键盘小了。

not run by us: the Mac has no screenเราไม่ได้รันเอง: แมคเครื่องนั้นไม่มีหน้าจอ我们没有运行过:那台 Mac 没有屏幕

codex app
codex remote-control --help

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

[experimental] Manage the app-server daemon with remote control enabled

Usage: codex remote-control [OPTIONS] [COMMAND]

Commands:
  start  Start the app-server daemon with remote control enabled
  stop   Stop the app-server daemon
  pair   Create and print a short-lived manual pairing code
  help   Print this message or the help of the given subcommand(s)

Options:
  -c, --config <key=value>
          Override a configuration value that would otherwise be loaded from `~/.codex/config.toml`.
          Use a dotted path (`foo.bar.baz`) to override nested values. The `value` portion is parsed
          as TOML. If it fails to parse as TOML, the raw string is used as a literal.
          
          Examples: - `-c model="o3"` - `-c 'sandbox_permissions=["disk-full-read-access"]'` - `-c
          shell_environment_policy.inherit=all`

      --json
          Emit machine-readable JSON

      --enable <FEATURE>
          Enable a feature (repeatable). Equivalent to `-c features.<name>=true`

      --no-daemon
          Run an embedded app-server in the foreground until Ctrl-C

      --disable <FEATURE>
          Disable a feature (repeatable). Equivalent to `-c features.<name>=false`

  -h, --help
          Print help (see a summary with '-h')

not run by us: from a terminal app on the phone, over SSHเราไม่ได้รันเอง: จากแอปเทอร์มินัลบนโทรศัพท์ ผ่าน SSH我们没有运行过:在手机的终端应用里,通过 SSH

brew install tmux
tmux new -s codex
codex
tmux attach -t codex
CAREFUL Whoever reaches your Mac's Codex can do what Codex can do. The manual says not to expose Codex's connection on a shared or public network, and to use a VPN or a mesh network instead; the pairing through the ChatGPT apps goes through OpenAI's servers and your account. For the SSH way, that means a private network such as Tailscale, keys instead of passwords, and no port of the Mac open to the internet.ใครก็ตามที่เข้าถึง Codex บนแมคของคุณได้ ทำสิ่งที่ Codex ทำได้ คู่มือบอกว่าอย่าเปิดการเชื่อมต่อของ Codex บนเครือข่ายที่ใช้ร่วมกันหรือเครือข่ายสาธารณะ ให้ใช้ VPN หรือเครือข่ายแบบ mesh แทน การจับคู่ผ่านแอป ChatGPT ผ่านเซิร์ฟเวอร์ของ OpenAI และบัญชีของคุณ สำหรับวิธี SSH นั่นหมายถึงเครือข่ายส่วนตัวอย่าง Tailscale ใช้คีย์แทนรหัสผ่าน และไม่มีพอร์ตของแมคเปิดสู่อินเทอร์เน็ต谁能碰到你 Mac 上的 Codex,谁就能做 Codex 能做的事。手册说不要把 Codex 的连接暴露在共享或公共网络上,要用 VPN 或网状网络代替;通过 ChatGPT 应用的配对走的是 OpenAI 的服务器和你的账户。对 SSH 这条路,这意味着一个像 Tailscale 这样的私有网络、用密钥而不是密码,以及 Mac 没有任何端口向互联网开放。

🧠 Quiz 15🧠 แบบทดสอบ 15🧠 小测验 15

Pick an answer. Then the page shows which one is right, and why.เลือกคำตอบ แล้วหน้านี้จะบอกว่าข้อไหนถูก และเพราะอะไร选一个答案。然后这一页会告诉你哪个是对的,以及为什么。

When your phone controls Codex, where do the files, tool servers and skills run?เมื่อโทรศัพท์ของคุณควบคุม Codex ไฟล์ เซิร์ฟเวอร์เครื่องมือ และสกิลรันอยู่ที่ไหน当你用手机控制 Codex 时,文件、工具服务器和技能在哪里运行?

✅ Right!✅ ถูกต้อง!✅ 答对了!

Not this one. The marked answer is the right one.ยังไม่ใช่ คำตอบที่มีเครื่องหมายคือคำตอบที่ถูก不是这个。标出来的那个才是对的。

Everything still runs on the Mac: its files, the tool servers, the skills, the sandbox and approvals. That is why the Mac must be awake, online and in the app.ทุกอย่างยังรันบนแมค: ไฟล์ เซิร์ฟเวอร์เครื่องมือ สกิล แซนด์บ็อกซ์และการอนุมัติ แมคจึงต้องตื่นอยู่ ออนไลน์ และเปิดแอปไว้一切仍然在 Mac 上运行:它的文件、工具服务器、技能、沙盒和审批。所以 Mac 必须醒着、联网,并且开着应用。