opencode-zellij
v0.1.1
Published
OpenCode plugin for Zellij-backed panes and workflow integrations.
Readme
opencode-zellij
把需要持續運作的 command(dev servers、watchers、REPLs)跑在可見的 Zellij pane。
安裝
把 npm package name 加到 OpenCode config:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-zellij"]
}OpenCode 會在啟動時自動安裝 npm plugins。Zellij 也必須已安裝,且可在 PATH 中使用。
功能
zellij_pty_spawn 工具
開一個 Zellij pane 來跑可互動、持久的 command。
Quick reference:
Call:
{
"command": "npm",
"args": ["run", "dev"],
"probe": { "type": "http", "url": "http://127.0.0.1:3000", "expectStatus": 200 }
}Returns:
{
"session": { "id": "...", "paneId": "terminal_3" },
"probe": { "ok": true, "type": "http", "message": "Got 200 from http://127.0.0.1:3000" },
"output": { "text": "> next dev server running on :3000\n..." }
}Spec:
- 開一個 Zellij pane 跑持久或短命 command,並取得
session.id供後續操作。tests/e2e/zellij-pane.run.test.ts - 沒指定 ready 訊號時,給短命 command 一點時間輸出再回傳。
tests/e2e/zellij-pane.run.test.ts - 需要真正等到 ready 訊號時,用 output 匹配或 HTTP probe 等它。
tests/e2e/zellij-pane.run.test.ts - 壞的
grepregex 在 pane 建立前就擋下來,不要浪費 pane slot。tests/e2e/zellij-pane.run.test.ts - 想 spawn 之前先看看之前還有哪些 pane 還沒清。
tests/e2e/zellij-pane.run.test.ts - pane 結束時,喚醒擁有該 pane 的 OpenCode session 一次(多個 terminal signal 也只算一次);找不到
promptAsync時 fallback 到client.session.prompt;沒 pane 退出時不會亂叫。tests/e2e/zellij-pane.run.test.ts
zellij_pty_read 工具
讀取 pane 最近的 output。
Quick reference:
Call:
{ "id": "<session.id>", "grep": "error" }Returns:
{
"session": { "id": "...", "status": "running", "exitCode": null },
"output": { "text": "...matched lines...", "matched": 3 },
"cleanup": { "requested": false, "performed": false, "alreadyClosed": false }
}Spec:
- 拉最新 output、用
grep過濾、順便看 pane 目前的狀態。tests/e2e/zellij-pane.run.test.ts grep不是合法 regex 時,會回 warning 而不是整個 read 掛掉。tests/e2e/zellij-pane.run.test.ts- 想讀已退出的 pane 也可以;
pane_closed是被外部砍掉,exit_marker是 process 自己結束 —— 用這個分辨。tests/e2e/zellij-pane.run.test.ts - 想關掉「讀完就清」的預設行為,可以在 config 設
pty.cleanupExitedPaneOnRead,也可以在單次 call 用 tool arg 覆蓋。tests/e2e/zellij-pane.run.test.ts - 第一次 read 把已退出的 pane 關掉後,第二次 read 會回同一個
tombstone.paneClosedAt—— 用來判斷是不是同一個 lifecycle。tests/e2e/zellij-pane.run.test.ts
zellij_pty_write 工具
對 pane 送 input。
Quick reference:
Call:
{ "id": "<session.id>", "data": "yes\n" }Returns:
{ "output": { "text": "...recent output after write..." } }Spec:
- 對互動式 pane 送出回應或按鍵,並看到它怎麼反應。
tests/e2e/zellij-pane.run.test.ts - 寫入
humanInputOnlysession(任何zellij_pty_request_sudopane)會丟 error,不會 silently fail — agent 不該用zellij action write-chars直接繞過。user 才擁有那個 pane 的輸入權。tests/e2e/zellij-pane.run.test.ts
zellij_pty_list 工具
列出 plugin 正在追蹤的 pane。
Quick reference:
Call(目前 OpenCode session 的所有 pane):
{}Call(單一 pane):
{ "id": "<session.id>" }Returns:
{
"sessions": [
{ "id": "...", "paneId": "terminal_3", "status": "running", "command": "npm", "args": ["run", "dev"] }
],
"completedPaneIds": ["..."],
"completedPanes": [
{ "id": "...", "status": "exited", "reason": "exit_marker", "exitCode": 0 }
]
}Spec:
- 想 spawn 之前先看手邊有哪些 pane(活的或已退出的),或查某一個 pane 的狀態。
tests/e2e/zellij-pane.run.test.ts
zellij_pty_kill 工具
關閉 pane 並從 plugin 移除。
Quick reference:
Call:
{ "id": "<session.id>" }Returns(成功):
{
"killed": true,
"cleanedUp": true,
"id": "...",
"paneId": "terminal_3",
"output": { "text": "...", "lineCount": 3 },
"warnings": []
}Returns(close-pane 失敗、pane 還在):
{
"killed": false,
"cleanedUp": false,
"session": { "id": "...", "status": "unknown" },
"output": { "text": "..." },
"warnings": ["close-pane failed: ..."]
}Spec:
- pane 已經不在的話 kill 會 throw —— finally block 一定要 try/catch(e2e 的
killQuietlyhelper 就是這樣做)。tests/e2e/zellij-pane.run.test.ts - 把 pane 乾淨關掉:先 Ctrl-C、暫停一下、再
close-pane。Ctrl-C 失敗只回 warning,不 throw。tests/e2e/zellij-pane.run.test.ts close-pane失敗而且 pane 還在時,session 會保留下來 —— 等一下重試,或先看 warning。src/tools/kill.test.ts- OpenCode 還沒正常 cleanup 就被
Ctrl-D砍掉時,detached Node.js watchdog process 會接手把 plugin 建立的 pane 清掉。tests/e2e/ci-pane.test.ts
zellij_pty_request_sudo 工具
開啟 floating、human-input-only 的 review pane,顯示將要執行的 command,並等待使用者輸入 YES。
Quick reference:
Call:
{
"summary": "Need root to install apt packages",
"scripts": [
{ "command": "apt install -y libsqlite3-dev", "description": "Install build dependency for the local server." }
]
}Returns:
{
"session": { "id": "...", "paneId": "terminal_5", "humanInputOnly": true }
}Spec:
- 把 privileged command 交給使用者審核 —— 他們看到 script,用嚴格
[y/n]prompt 確認,沒有預設:user 必須明確打y(或Y)才會同意,打n(或N)取消。空 Enter 跟任何亂打都會被退回並 re-prompt(「Empty input. Please type y or n explicitly.」)— 防止 user 不小心按到 Enter 結果 sudo 莫名被取消。tests/e2e/zellij-pane.run.test.ts - 使用者打的 credentials 只留在 Zellij scrollback,不會進到 agent 或 LLM。
tests/e2e/zellij-pane.run.test.ts [y/n]prompt 之前會跑 3s 倒數,數字用\roverwrite 在同一行遞減(3 → 2 → 1);空 Enter 跟亂打會被退回並 re-prompt,只有明確的y/Y同意、n/N取消。倒數跟 prompt 會在 Zellij pane 照順序渲染出來。src/tools/request-sudo.test.ts,tests/e2e/zellij-pane.run.test.ts- Pane spawn 時帶
--close-on-exit,bash 一退出 Zellij 就關 pane —— 不會在使用者批准或取消後留下一個卡住的空 pane。src/lib/zellij/cli.test.ts,tests/e2e/zellij-pane.run.test.ts
Dynamic tab title
在當前 Zellij tab title 末尾追加狀態 emoji,顯示 OpenCode 目前的狀態(idle / running / needs-input)。狀態變化時只取代末尾的 emoji,保留你原本的 tab title。
Spec:
設定
Sidecar config 從 ~/.config/opencode/opencode-zellij.config.jsonc(user)與 .opencode/opencode-zellij.config.jsonc(project)載入。Project config 覆蓋 user config。
pty.enabled boolean,預設 true。設為 false 時移除所有 zellij_pty_* tools。
Spec: tests/integration/plugin-load.test.ts
pty.sudoPane "allow" | "deny" | "hide",預設 "allow"。控制 zellij_pty_request_sudo:"hide" 移除 tool,"deny" 保留但每次都拒絕呼叫,"allow"(預設)是正常 tool。
Spec: tests/integration/plugin-load.test.ts
pty.sudoPaneMode "floating" | "fullscreen",預設 "floating"。sudo 請求 pane 的呈現方式。floating 開一個大、pinned 的浮動 pane,不偷 keyboard focus;fullscreen 開一個非浮動 pane,Zellij 自動 focus 進去。如果你常常看不到浮動 pane,改用 fullscreen。
Spec: src/tools/request-sudo.test.ts, tests/e2e/zellij-pane.run.test.ts
pty.sudoPaneFloatingSize { width?: string, height?: string, pinned?: boolean }。sudoPaneMode 為 "floating" 時浮動 pane 的大小。width / height 接受百分比字串(如 "80%")或 bare integer(以 cells 為單位);pinned 讓 pane 永遠在最上層。預設:{ width: "80%", height: "60%", pinned: true }。
Spec: src/config.test.ts, src/lib/zellij/cli.test.ts
pty.cleanupExitedPaneOnRead boolean,預設 true。為 true 時,zellij_pty_read 對已退出的 pane 回傳 output 後會關閉該 pane。
Spec: tests/e2e/zellij-pane.run.test.ts
tabTitle.enabled boolean,預設 true。設為 false 時停用動態 tab title。
Spec: tests/integration/plugin-load.test.ts
tabTitle.emojiIdle / emojiRunning / emojiNeedsInput 字串,預設 🟢 / ⚡ / 💬。狀態區段的前綴。
Spec: tests/e2e/zellij-tab-title.run.test.ts
tabTitle.debounceMs number,預設 300。tab title 更新的 debounce 時間(毫秒)。
範例
// .opencode/opencode-zellij.config.jsonc
{
"$schema": "https://unpkg.com/opencode-zellij/opencode-zellij.schema.json",
"pty": {
"sudoPane": "deny",
"sudoPaneMode": "floating",
"sudoPaneFloatingSize": { "width": "80%", "height": "60%", "pinned": true }
},
"tabTitle": {
"emojiRunning": "🔥",
"emojiNeedsInput": "❓"
}
}opencode-zellij.schema.json 由 src/config.ts 產生,支援 JSON Schema 的編輯器可拿來做自動完成與驗證。
Spec: tests/integration/config-schema.test.ts
