! 本篇文章將會介紹 headless 執行與 server 模式,讓 opencode 沒有畫面也能上戰場 :D
TL;DR: https://dev.benben.me/slides/s/ironman-18-headless-ci
讀完這篇你會學到:
opencode run 一行執行任務(非互動模式)opencode run:一行執行Day 17 說過:opencode 的本體是一個 server,TUI 只是客戶端。把這句話推到底,就會得到三種用法:
| 用法 | 指令 | 適合 |
|---|---|---|
| 互動 | opencode(TUI) |
人坐在螢幕前 |
| 一次性 | opencode run |
腳本、cron、CI |
| 常駐 | opencode serve |
多個客戶端共享一台 |
「headless」說的就是後兩者——沒有畫面、沒有人盯著,照樣把事做完。opencode run 是「跑完就走」模式:prompt 進去、答案出來、程式結束:
opencode run "Explain the use of context in Go"
輸出直接印在 stdout,管道、重導向隨你接:
opencode run "本週 git log 摘要" > weekly-report.md
| Flag | 用途 |
|---|---|
--model / -m |
指定 provider/model |
--agent |
指定 agent(例如 plan) |
--continue / -c |
接續上一個 session |
--session / -s |
接續指定 session |
--file / -f |
附加檔案給 prompt |
--share |
順手產生分享連結 |
--format json |
輸出 raw JSON 事件流(給程式吃) |
--title |
指定 session 標題 |
幾個容易搞混的 flag:
-c 接「上一個」session(該目錄最新的那個),-s 才是指定 ID——腳本裡建議用 -s,免得「上一個」被別的執行插隊--agent plan 直接派 Day 05 的建築師上場:唯讀分析、不動檔案,很適合接在 CI 的 review 步驟--auto 先記著,等等「兩道鎖」會談它有多危險--format json:把輸出變成管線預設輸出是給人看的;加上 --format json,stdout 變成事件流——每行一個 JSON 物件,照發生順序排列:session 建立、訊息更新、工具被呼叫、回應完成,通通是事件。這跟 Day 17 SDK 訂閱的事件流是同一套系統,只是改成從 stdout 吐出來。
餵給 jq 就是自己的自動化管線,一些輕量 n8n 做的事也撐得住:
# 只看訊息類事件
opencode run --format json "摘要這週的變更" | jq -c 'select(.type | startswith("message"))'
每次 run 都是一個全新行程:讀 config、載 plugins、把設定檔裡的每個 MCP server 重新 spawn 一次——npx 下載、連線、handshake 整套重來。MCP 只掛一兩個還好,掛了一排的話,跑一個五秒鐘的任務,半分鐘都花在暖機。
解法:開一台常駐的,讓 run 掛上去:
# 終端機一
opencode serve
# 終端機二
opencode run --attach http://localhost:4096 "Explain async/await"
--attach 一次解決兩件事:MCP、LSP 只在 serve 那台暖一次,之後每次 run 都秒起;session 照樣存在機器上,跟有沒有 attach 無關。
opencode serve:常駐 API serveropencode serve --port 4096 --hostname 127.0.0.1
起來之後:
http://localhost:4096/doc(Swagger 逛一圈,所有 endpoint 一目瞭然)createOpencodeClient({ baseUrl }) 接上--port / --hostname 也可以寫進 opencode.json 的 server 區塊,不用每次背參數先看懂 --hostname,再談安全:
127.0.0.1(預設):只有本機連得到0.0.0.0:同一個網路裡的其他機器都連得到——等於把門推開而這個 server 開著,就是一個能讀寫檔案、跑指令的 API。掛到網路上之前,一定要設密碼:
OPENCODE_SERVER_PASSWORD=your-password opencode serve
HTTP basic auth,帳號預設 opencode(可用 OPENCODE_SERVER_USERNAME 改)。
再多想一層:basic auth 走純 HTTP 時是明文傳輸。要連遠端,更穩的做法是 server 留在 127.0.0.1、用 SSH tunnel 把埠帶回本機:
ssh -L 4096:localhost:4096 user@remote-host
opencode attach http://localhost:4096 # 連的其實是遠端那台
搭配 opencode attach,可以把本機 TUI 掛到遠端機器上的 server:
# 遠端主機上
opencode web --port 4096 --hostname 0.0.0.0
# 你的電腦上
opencode attach http://10.20.30.40:4096
attach 跟 web 的差別記一下:attach 是把本機的 TUI 掛到遠端 server,操作體驗跟你平常的 terminal 一模一樣;opencode web 則開一個瀏覽器版操作介面,手機、平板也能用。背後是同一個 server 本體,挑順手的就好。
SSH 上 VM 工作但想在本地 terminal 看畫面?就這麼簡單。
三個馬上能用的場景:
on:
schedule:
- cron: '0 9 * * 1'
jobs:
docs-audit:
runs-on: ubuntu-latest
permissions:
issues: write # 開 issue 要用
steps:
- uses: actions/checkout@v4
- run: npm install -g opencode-ai # runner 上沒有 opencode,先裝
- run: |
opencode run "掃描 codebase 裡的 TODO,寫成清單。\
若有值得處理的,用 gh CLI 在本 repo 開 issue。" \
--model opencode/gpt-5.1-codex
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
看起來短短一段,藏了幾個細節:
'0 9 * * 1' 是每週一 09:00 UTC,台灣時間是 17:00——排程時間對不上,第一個先查時區gh 指令,所以要給它 GH_TOKEN、workflow 也要宣告 permissions: issues: write,它才有手可用OPENCODE_API_KEY 付(放 repo secrets);GITHUB_TOKEN 則是 GitHub 每次執行自動發的,兩者角色不同release notes 的原料是 commit log。先用 git log 撈出這次要發佈的範圍、存成檔案,再當附件餵進去:
git log v1.4.0..HEAD --oneline > /tmp/commits.txt
opencode run -f /tmp/commits.txt \
"上面是這次要發佈的 commits。請生成 release notes:中文,分 Added / Fixed / Breaking 三節" \
> RELEASE_NOTES.md
-f 把檔案附在 prompt 上(Day 06 的檔案引用,CLI 版的樣子)。輸入輸出都是檔案,可以直接接進發佈流程。
平常 lint 失敗,CI 就是紅給你看;機器人版是多一個 job,紅了就換它上場:
lint 失敗
→ 開 branch:fix/lint-<date>
→ opencode run "修掉所有 lint error,不要動業務邏輯"
→ gh pr create --fill
「發現問題 → 修問題 → 開 PR」全自動,但 merge 與否仍由人拍板——無人值守不等於無人管理。
明天 Day 19 會講更完整的 GitHub 整合(
/opencodebot 直接在 issue / PR 裡工作),那是本篇 CI 概念的官方豪華版。
讓 AI 在 CI 裡自由跑,要先上兩道鎖。
headless 沒有人坐在螢幕前按 y/n,所以權限要事先寫死。opencode.json 的 permission 可以對每個工具設 allow / ask / deny,bash 還能吃 glob;規則越後面越優先,所以 * 放最前面、特定指令放後面:
{
"permission": {
"edit": "deny",
"bash": {
"*": "deny",
"grep *": "allow",
"gh issue create*": "allow"
}
}
}
不想為了 CI 多 commit 一個 config 檔?環境變數直接注入:OPENCODE_PERMISSION='{"edit":"deny"}'。
唯讀的 review 類工作更簡單——--agent plan 派 Day 05 的建築師上場,編輯與 bash 預設全擋。
至於 --auto,它是把鎖整個拆掉:「沒被明確拒絕的一律自動核准」。deny 名單沒寫齊,就等於全部放行,用之前想清楚。
模型沒有下班時間,要給它極限:
steps 上限——跑到上限就強制收尾、回一份文字總結,不會無限燒 token:{
"agent": {
"docs-audit": {"steps": 20}
}
}
opencode stats --days 7 看用量,數字突然暴增,通常是有任務在繞圈API key 一律走 CI secrets,絕不寫進 repo——跟你不會把 AWS key commit 進去是同一門課。
Q:run 跑到一半當掉,session 會留著嗎?
A:會。用 opencode run -c 接續,或 opencode session list 找回 ID。
Q:可以把 share 連結自動貼到 Slack 嗎?
A:可以——--share 加 --format json,從 JSON 事件流裡抽出連結再 curl 到 webhook,五行腳本的事。
Q:run 跟 Day 17 的 SDK,什麼時候用哪個?
A:一行能解決、寫在 shell 腳本或 CI 裡的,用 run;要型別安全、要訂閱事件、要塞進自己的程式邏輯,用 SDK。兩者可以 attach 到同一台 serve,不衝突。
Q:headless 模式下 MCP、LSP 還能用嗎?
A:能,行為與 TUI 一致(同一個 server 本體)。只是 headless 常常短命,MCP 冷啟動成本才需要用 --attach 閃掉。
run、serve 是同一個本體的三種叫法:互動、一次性、常駐opencode run:非互動一行執行,--format json 接程式opencode serve:常駐 OpenAPI server,OPENCODE_SERVER_PASSWORD 上鎖,attach 遠端連入Day 19:GitHub 與 GitLab 整合——在 issue comment 打 /oc,AI 直接開分支修好開 PR 給你。
有任何疑問但沒有 iT 邦幫忙帳號,或是想匿名提問?
歡迎到 https://dev.benben.me/q/P3C5U6 提問或加油打氣,沒意外的話會在完賽之後一起回答 :D