為什麼最強的 coding agent 都住在終端機裡
CLI agent 是一個用文字跟你對話的 coding agent。它跑在你自己擁有的機器上,一台 Mac、一台 Linux 主機、或一台伺服器,並沿用你在那台機器上的權限:讀整個專案的檔案、執行任何指令、把結果讀回來。它住在終端機裡,因為沒有別的介面能同時給你這三樣。
「CLI」三個字聽起來像懲罰:沒有滑鼠、沒有按鈕、只有一個閃爍的游標。可是 Claude Code、Codex、Gemini CLI、OpenCode 全都住在這裡。做它們的人不是硬派,是因為終端機給了 agent 兩樣別的介面給不了的東西:完整讀寫檔案的權限,和執行指令的權限。也是因為這兩樣,它跑任何一條指令之前會先問你。
終端機其實是兩層程式疊在一起
這裡有兩個程式疊在一起,而初學者通常怪錯那一個。終端機是負責顯示的那一層:它畫出一格一格的字,把你的按鍵轉出去,就這樣。它後面的 shell(zsh、bash、fish)等一行文字出現,跑那行文字,然後把程式寫出來的東西印在螢幕上。
就是因為薄到這個程度,agent 才操作得動。它打的每一行指令,跟你自己手動打的一模一樣;它讀到的錯誤訊息,跟你會看到的一字不差。
螢幕上真正有意義的三樣東西
這份指南後面每一篇都假設你會看螢幕。真正承載意義的是三樣,其餘都是雜訊。
yuki@mini ~/projects/auth (main)
$ npm test
✓ src/session/refresh.test.ts (6 tests)
✗ src/session/expire.test.ts > drops a session after expiry
Tests 1 failed | 41 passed (42)
$ echo $?
1
提示字元就是第一行。它告訴你四件事:你是誰、在哪台機器、哪個目錄、哪個分支。機器不在你眼前的時候,這一行就不再是裝飾。SSH 登入之後,它就是讓你分清楚這是自己的筆電、還是你忘記自己登進去過的那台伺服器。
標準輸出(stdout)是那個程式寫出來的文字。第二條串流 stderr 分開存放,真正的輸出不會跟抱怨混在一起。
exit status。每一條跑完的指令都留下一個整數,shell 用 $? 交給你。0 表示那個程式宣稱自己成功。agent 讀的就是這個數字。它讀的不是「1 failed」那些字,是那個 1,那是它的迴圈還沒閉上的訊號。
介面決定 agent 能碰到什麼
網頁版的 agent 看不到你電腦裡的任何一個檔案,它只活在瀏覽器分頁裡。IDE 外掛看得到你打開的檔案,但通常停在「建議」這一步:它動不了 git,跑不了測試,更裝不了套件。
CLI agent 拿的是你自己那組權限:讀整個 repo、下 git commit、跑測試套件、裝依賴。存取分三層,風險一層比一層高:
- 讀。你家目錄下的每一個檔案,包含
.ssh裡的金鑰。 - 寫。在 git 管著的專案裡,改壞了一個
git checkout就救得回來;專案之外,沒有東西會幫你 rollback。 - 執行。任何你打得出來的指令,包括 migration、force push,還有指向錯誤路徑的
rm -rf。
這個差距沒有哪個廠商刻意設計過。選了終端機當介面,這組權限就跟著來了。別的介面都扣掉了一部分,所以也做不了同樣的事。
哪些指令是沙箱裡的 agent 跑不了的?
「執行權限」是一個抽象的說法,點頭之前先把它還原成螢幕上的行:
npm test
npx vitest run src/session/expire.test.ts
git status --short
git commit -m "fix: expire sessions on the dot"
npm install date-fns@3
npm run build
psql "$DATABASE_URL" -f db/schema.sql
curl -sf localhost:8000/health | jq .db
這些指令搬到別人的雲端沙箱裡,要跑在哪裡?那行 psql 連的是一個只存在於你機器上的資料庫,用的 URL 來自一個從來沒上傳過的 .env。那行 npm install 從你 .npmrc 裡寫明的私有 registry 拉內部套件。那些 git 動作發生在一個帶著你的 remote、你的 hook、你的 signing key 的 clone 上。
沙箱裡的 agent 可以寫一份 patch 給你,但看不見那份 patch 造成了什麼結果。看不見一次 edit 的結果,就沒辦法把工作做完。
不需要你按任何鍵就會閉上的迴圈
你丟一句「把這個 bug 修好」。接著是十幾行同樣的指令,而你的手不用碰到鍵盤:
- 它 grep 那條錯誤訊息,讀三個檔案,改一個。
- 它跑
npm test,讀 exit status,看到1。 - 它縮到那一個失敗的案例,再改一次,整組重跑,拿到
0。 - 它 commit,在本地把 migration 套上去,再 curl 那個壞掉的 endpoint。
整條鏈上沒有一個按鈕需要按。換成瀏覽器分頁或 IDE 外掛,它在第二步就停下:它能產出那個 edit,卻跑不了測試,於是等你去跑,再等你把結果回報給它。到最後你變成了測試執行器。這是權限的問題,不是模型強弱的問題。
為什麼位元組串比一個視窗值錢?
你螢幕上那個終端機視窗不是程式住的地方。程式接在一個 pseudo-terminal 上,那是 kernel 裡一個有兩端的物件:一端寫進去位元組,另一端就出來位元組,而你的視窗只是目前握著另一端的那個人。從這個定義跟著來三件事:
- 可以存下來。
npm test > build.log把那串文字寫進檔案,一年後另一個程式還能讀它。GUI 的畫布是一張圖,圖本身不是紀錄。 - 可以接起來。
git ls-files | grep .env把一個程式的輸出,變成下一個程式的輸入。 - 可以搬走。那個定義沒有一句話說兩端必須在同一個地方。tmux 把 session 擱在伺服器上,你的螢幕接上去、detach、再從另一台裝置接回來。
第三點才是這篇的重點。位元組串可以被轉向、存起來、重播,也可以從別的地方接回去;渲染好的視窗做不到。之後要講的持久 session、SSH、在手機上接回同一個 agent,每一件事都是從這個性質來的。
完整存取要付什麼代價?
讓 agent 能對資料庫做 migration 的那個權限,也能把那個資料庫刪掉。它知道這一點,所以會在工作做到一半時停下來問:「要跑這條指令嗎?」那個 prompt 不是膽小。一個什麼都不問就直接跑的 agent,等於把你的 .ssh 目錄和正式環境的資料庫押在你身上。prompt 畫的是第二層跟第三層之間那條線,而且只能在那個時刻畫:指令已經知道了,還沒有執行。
兩個習慣擋掉大部分風險。讀指令本身,不要讀摘要:「我要清掉 build cache」是一段描述,只有它下面那一行才有後果。同一類只批一次:一天四十次要你准許跑測試,就用一條規則讓 npm test 直接通過。
這裡的代價不對稱。一個你本來就會准許的 prompt 花你十秒。一個你誤准許、跑在正式資料庫上的指令,花你一天和一次 restore。就是這個比例讓 AI 自動權限審查存在:先 blacklist,再 allowlist,剩下的交給模型判斷。
什麼時候 GUI 比較好
終端機不是對 code 做任何事的正確介面。下面這些事情,用文字做就是比較差:
- 即時 debugger。設 breakpoint、單步走過那個迴圈、在第四個 frame 看一個變數。
- 任何視覺的東西。排版、間距、一個差一點點的顏色。叫 agent 描述一張截圖,比你自己看一眼差得多。
- 讀一個大的 diff。兩千行並排著看。所以就算 code 是 agent 寫的,你多半會在 GUI 裡 review 它的工作。
- 還在學的時候。編輯器在你寫錯的那個 token 下面畫一條線,比一整牆的 stack trace 教得快。
這條分界分的不是熟練跟懶惰,分的是「你在場、你在開」跟「它在跑、你去看」。坐在桌前,IDE 常常是比較好用的那個工具,而把 CLI agent 用到最好的人,兩邊都留著。那條分工線怎麼畫,自己就是一篇。
終端機把 agent 放進你自己的機器,而它的輸出是可以住在任何地方的字元。所以那台機器不必是你正坐著的這一台。從別的地方連過去,是 SSH 的工作。