RiNG 🎤¶
安裝、產品介紹與首次操作請先看文件首頁;本頁集中收錄操作與進階參考。
常用指令¶
| 指令 | 用途 |
|---|---|
ring |
印一張當下快照 |
ring --watch |
持續刷新;有 Textual 時進互動 TUI |
ring --watch --interval 1 |
每 1 秒刷新 |
ring --watch --count N |
刷新 N 次後結束,方便測試 / CI |
ring --all |
顯示已離場 session |
ring --no-legend |
隱藏圖例 |
ring --lang en |
切英文 UI |
ring focus SESSION_ID |
聚焦指定 session;可用唯一前綴 |
ring config |
顯示設定檔路徑與生效設定 |
ring config set KEY VALUE |
寫入單一設定 |
ring quiet |
顯示暫時全域靜音(quiet)現況 |
ring quiet on / off / 30m |
開啟 / 解除 quiet,或開啟一段時間後自動解除 |
ring doctor |
唯讀環境診斷 |
ring digest --since 4h |
離席摘要:最近 session 狀態與等待統計 |
ring gc --dry-run |
預覽 RiNG 自己的 stale 狀態檔清理 |
ring gc |
清理 RiNG 自己的 stale 狀態檔 |
ring --format json |
整個看板的機器可讀快照(給 jq / 腳本) |
ring --format oneline |
🔴2 🟢1 🟡3 單行摘要(給 status bar) |
ring stats |
等待統計:最近 7 天你讓 agent 🔴 等了多久 |
ring completion zsh |
印 shell 補全腳本(zsh / bash) |
塞進 status bar(--format)¶
- tmux:
set -g status-right '#(ring --format oneline) …'(配status-interval 5)。 - SwiftBar / xbar / waybar:包一層腳本呼叫
ring --format oneline或吃 JSON 自己排版。 - JSON 的鍵名視為穩定介面(只加不改),放心接腳本。
每個 session 的 waiting_requests 列出目前有效的等待,欄位為 id(不透明的請求識別)、
owner(foreground、agent:<id> 或 unknown)、kind 與 detail;非等待狀態回傳空陣列。
對照同一 session 前後快照新增的請求 ID,可辨識輪詢間已回應、又再次等待的情況;
不要用 last_active 或 heartbeat_at 當作等待版本,背景活動也會更新它們。
只移除請求不算新等待,計數仍以 session 為單位。
Shell 補全(ring completion)¶
子命令、旗標、config set 的鍵都補得到;ring focus 會提示 session id / 唯一前綴參數。
--watch 的兩種樣子¶
- 裝了 Textual(
[tui]extra)且在真終端 → 互動 TUI:↑/↓選 session、Enter/Space跳到它所在的終端、p就地回覆權限請求、n命名、a切換是否顯示已離場、dd隱藏 session(有新活動會自動重新出現)、r刷新、q離場。 如果你跟我一樣有 vim 手癖,也可以用j/k上下移動、g/G跳到第一列 / 最後一列。 同一組 j/k/g/G 在p的權限回覆浮層裡也能用。 選中 🔴 等你的列時,表格下方會多一行顯示它具體在等什麼(要跑的指令、問的問題;hook 模式才有)。 Claude Code 背景 agent 以⚙標示;它沒有可跳轉的終端,選取時會顯示claude --resume接回提示,完成後預設收進已離場(按a仍可查看)。 - 否則 → Rich poll(清除畫面重畫);連 Rich 都沒有就純文字。三層優雅降級。
跳到 session(Enter / Space / ring focus)¶
這裡的
Space是鍵盤上的空白鍵,不是 BanG Dream! 裡的場館「SPACE」。
選一個 session 按 Enter,或用 ring focus SESSION_ID(完整 id 或唯一前綴),RiNG 把焦點帶到它真正所在的終端。
如果 TUI 正在跑,ring focus 會先把請求交給 TUI,讓游標選中該 session;沒有 TUI 時才直接跳終端。目前內建支援:
- tmux:
switch-client直接切到那個 pane(你跟它要在同一個 tmux server)。 - iTerm2 / Terminal.app(macOS):用 session 的
tty透過 AppleScript 聚焦對應分頁, 自動分辨是哪個 app(沒在跑的 app 不會被喚醒)。第一次會跳系統「自動化」授權,准一次即可。 - kitty:連上 kitty 的 remote control socket,列出視窗、用
tty比對出正確的那個, 精準focus-window;macOS 上另外固定補一次 AppleScript activate(視窗縮到 Dock 時focus-window只負責還原,不會把整個 app 帶到最前)。前置條件:kitty 預設關閉 remote control,要在kitty.conf設allow_remote_control yes/socket/socket-only, 並設listen_on(例:listen_on unix:/tmp/kitty-{kitty_pid})——沒設就會安靜跳過, 不影響其他 focuser。限制:只掃/tmp底下的 socket;Linux 端尚未實測。 注意:focusers若已在你的 config 明確列出清單(不是省略走內建預設),新版不會自動幫你加"kitty"——要用就自己把它加進清單。 - Linux X11 視窗(
wmctrl,best-effort fallback):Linux 上沒跑 tmux 時的後備—— 從tty追到擁有它的終端視窗,用wmctrl帶到前景。限制:只支援 X11(Wayland 通常無效)、 只能聚焦整個視窗無法選分頁、gnome-terminal 的 client/server 架構可能配不到。要先apt install wmctrl。
「哪個 session 在哪個終端」靠它的 tty 對應——hook 模式最精準;
zero-config 下每個專案只開一個 session 時也對得上。Codex 沒裝 hook 時會走 zero-config:
同一個 cwd 只開一個 live Codex 時可跳轉;同 cwd 多個 Codex 只能保守顯示,避免跳錯。
跳不過去時,每次跳轉的經過都記在 ~/.config/ring/focus.jsonl(一行一次,含每個 focuser 的回覆),
事後可以直接分辨是哪一種失敗:tty 是空的(沒抓到 tty)、每個 focuser 都 skip(tty 已失效,
分頁多半關掉了)、或 iTerm2 回 unraised(分頁找到了但視窗沒浮上來,通常是它在別的 Space)。
從 Neovim 選取等待中的 session(RiNG.nvim)¶
安裝並設定 RiNG.nvim 後,:RingJump 會取得新快照,
用 vim.ui.select() 列出所有等待中的 session,顯示專案/自訂名稱、工具、縮短 ID 與等待原因。
已有輪詢進行中時會等它完成;選定後才以完整 session ID 執行 ring focus,取消不會跳轉。
可自行加一個快捷鍵(plugin 不預設綁鍵):
RiNG TUI 正在跑時,跳回看板並選中該列;沒有 TUI 時,直接嘗試聚焦 session 的終端。 這仍需要上節說明的終端設定與可辨識的 session 位置,不會替沒有終端的背景 agent 開啟新 session。 查詢失敗或資料不完整時不拿舊名單猜目標,錯誤與逾時也不受自動通知的開關影響。
若自訂 RiNG 執行檔或 wrapper,請同時設定 plugin 的 command 與 focus_command,
讓查詢與跳轉指向同一環境;focus_timeout 可獨立調整跳轉的等待時間(預設 15 秒)。
在 Neovim 用 :checkhealth ring 檢查兩個指令,完整設定見 :help ring.nvim。
搭配支援 waiting_requests 的版本,RiNG.nvim 也會通知同一 session 的新一輪等待,
並在已有前景等待時指出新加入的背景請求。開啟 Neovim 的第一份快照仍保持安靜。
舊版 RiNG/自訂輸出缺少請求識別時,退回 session ID 去重;只有計數時則只通知數量增加。
若上游沒有提供請求 ID、沒有任何進度事件,而且新舊提示完全相同,就無法可靠區分新一輪等待。
就地回覆權限請求(p)¶
游標停在 🔴 等你的列按 p,RiNG 讀出那個 session 畫面上的權限對話框
(Claude Code 的「Do you want to proceed?」框,含背景 subagent 帶
「from the … agent」標頭的),把編號選項原文列成浮層讓你選;選定後 RiNG 會再抓一次
畫面確認對話框還在且沒變(防止你考慮期間它已被回掉),才代你按下那個數字,
並回頭驗證對話框確實消失。
確認回覆成功後,若該 session 只有一個等待事項,TUI 會立即清掉該筆「等你」;只有時間較新的 hook 事件(例如下一個權限請求)能再次把它標成等待。
若同一 session 有多個 agent 在等,回覆一個對話框不代表其他事項也已解決,因此保留等待標記,直到對應 agent 的 hook 確認解除。
- tmux 內的 session:用
tmux capture-pane抓畫面、tmux send-keys送鍵。 - macOS 上直接開在 iTerm2 分頁的 session(沒有 tmux):用 session 的
tty透過 AppleScript 找到對應 iTerm2 分頁,抓畫面、送鍵都走osascript。第一次用時 macOS 會跳「允許控制 iTerm2」的自動化授權框,允許一次即可。 - kitty 視窗裡的 session(沒有 tmux):用
kitty @ get-text抓畫面、kitty @ send-text送數字,跟上面「跳轉」用的是同一套tty→ window 定位。前置條件同上面 kitty focuser 那段——remote control 要開。限制:tmux 內的 session 一律走 tmux backend, 不會被 kitty 搶走;Linux 端尚未實測。
安全底線:畫面上解析不到可辨識的對話框(標記、編號、游標任一缺)就只提示、絕不送鍵—— 對話框不在時按鍵會落進聊天輸入框變成文字;萬一送出的瞬間對話框剛好消失、數字落進輸入框, RiNG 會自動補一個 Backspace 清掉並警告你。
限制:要有 tmux pane 座標、(macOS 上)測得到 tty 的 iTerm2 session、或 remote control
有開且配得到 window 的 kitty session,才抓得到畫面;其餘 session 請按 Enter 跳過去回。
等你時發通知¶
預設行為:裝了 hook 後,session 從工作中轉成 🔴 等你的當下,hook 就地響鈴 + 發系統通知—— 不必開著 RiNG 看板,關掉終端也照樣 ring 你(通知由事件觸發,不靠輪詢)。 開著 TUI 時,若它一直停在等你,TUI 還會在 30s / 120s / 300s 各補一次 in-app 響鈴提醒。 (zero-config 測不到「等你」,所以這個需要 hook 模式。)
macOS 上若要點通知後直接跳回 RiNG TUI 並選中那個 session,需要安裝 terminal-notifier(brew 外部 binary):
沒裝時退化為 macOS 原生純文字通知(不可點擊跳轉),RiNG 會在第一次走到這條路時提示一次。
通知聲音、重複提醒時間、後端選擇都能在 config 裡調整;通知後端也是可插拔的,見後面的
Notifier 擴充說明。
推到手機(ntfy / webhook)¶
人不在座位時,桌面通知等於沒響。設定 ntfy topic URL 就能推到手機:
# ~/.config/ring/config.toml
notify_ntfy_url = "https://ntfy.sh/my-ring-topic" # 手機裝 ntfy app、訂同一個 topic
notify_also = ["ntfy"] # 桌面通知照發,再「加發」一份到手機
notify_backend = "ntfy" 則是只推手機、不發桌面通知。要接 Slack / 自家 bot / IFTTT,
用 notify_webhook_url 走通用 webhook 後端(JSON POST,欄位穩定只加不改)。
通知合流與暫時靜音(notify_debounce_seconds / ring quiet)¶
多個 session 同時轉 🔴 等你時,各自發一則系統通知會被轟炸。兩個獨立、可疊加的機制:
- 通知合流(debounce):
notify_debounce_seconds(預設0,關閉、向後相容)設成正數 (例如5)後,n 秒內冒出的多則通知只有第一則立即發(leading edge),之後的合併成一則 「另有 N 個 session 在等你」彙總——RiNG 沒有常駐 daemon,彙總由下一個 hook 事件、TUI 輪詢 或 quiet 解除時懶惰補發,不會吞掉唯一的通知。 - 暫時全域靜音(
ring quiet):手動靜音一段時間,期間所有通知先進 queue,解除時一併 補發彙總;跟 debounce 共用同一個 queue,但兩者互相獨立——quiet 不受notify_debounce_seconds影響,永遠可用。
補發前會重新確認等待請求:已回覆、已離場或被下一個請求取代的項目會移除,彙總只計算仍在等待的 session。來源暫時讀取失敗時會保留排隊項目,等資料恢復後再確認。
ring quiet # 顯示目前現況(開/關、剩餘時間)
ring quiet on # 開啟,手動解除前一直靜音
ring quiet 30m # 開啟 30 分鐘,到期自動解除
ring quiet off # 解除,立即補發合流中的彙總通知
開著 TUI 時,header 會顯示 quiet 狀態與排隊計數(例如 🔇 QUIET · 剩 12 分 | queue: 3),
沒有靜音、queue 也是空的時候不佔版面。quiet 是全域粒度,不分特定 session。
清理 RiNG 狀態檔(ring gc)¶
RiNG 正常收到 SessionEnd 時會刪掉自己的 hook registry;如果 agent crash 或 hook 沒跑到結尾,
可能留下已離場的 ~/.config/ring/sessions/*.json。這些檔案預設不會顯示在看板上,但可以用
ring gc 清掉。新寫入的 hook 狀態也會綁定真正的 agent process PID;即使之後在同一目錄
開了另一個 Claude,舊的等待狀態也不會被誤認成仍然存活。舊格式狀態則繼續用 cwd / tty
做相容判定;新舊格式並存時,已由 PID 認領的 process 不會重複分配給舊等待紀錄。
一般 hook 暫時查不到 PID 時會保留既有綁定;SessionStart 或成功偵測到另一個 PID 時則重新綁定。
同一 session 的等待依 agent 分開保存:A、B 都在等時,A 恢復工作或離場只解除 A 的等待, B 仍會留在「等你」。前景 transcript 的回應證據也不能代替背景 agent 解除等待; 背景進度不會覆寫前景終端位置。摘要優先顯示前景等待,否則顯示最先加入且尚未解除的等待; 看板與 JSON 計數仍以 session 為單位,不把每個 subagent 算成一列。
權限指令摘要也依 agent 與工具請求 ID 分開暫存,其他 agent 的工具完成不會清掉它。
缺少 agent ID 的 permission_prompt 只有唯一候選時才借用其摘要;多個候選無法區分時,
顯示通知本身的泛用訊息,不猜是哪條指令。舊版單一等待紀錄仍可讀取,新 SessionStart
或確定換成另一個前景 PID 時會清掉舊綁定的待決事項。
同一 session 的 hook 更新會序列化,避免並行寫入互撞;系統通知在釋放鎖後才發送, 不會持鎖阻擋後續 hook。
ring gc --dry-run # 預覽會刪哪些檔案
ring gc # 清掉已離場且超過 7 天的 registry,以及過期 IPC 檔
ring gc --older-than 1d # 改成 1 天
ring gc --all-ended # 清掉所有目前判定已離場的 registry
ring gc 只清 RiNG 自己寫在 ~/.config/ring/ 底下的狀態檔,不會碰 Claude Code / Codex 的
transcript 或 state。ring doctor 維持唯讀診斷,不會替你刪檔。
等待統計(ring stats)¶
hook 模式下,RiNG 會把 session 的狀態轉換記進 ~/.config/ring/events.jsonl
(只記轉換、量很小、超過上限自動砍半保新)。ring stats 據此告訴你:最近這段時間,
每個專案 🔴 等了你幾次、平均 / 最長 / 總共等多久。
跟精準通知一樣,zero-config 測不到 🔴,所以 stats 也需要 hook 模式。
Session 來源¶
RiNG 會從已註冊的 source 收集 session;目前內建這幾種:
| Source | 來源 | 狀態精度 |
|---|---|---|
| Claude Code zero-config(預設) | 掃 ~/.claude/projects/**/*.jsonl 的 mtime + 記錄裡的 cwd 欄位 |
免設定;可辨識近期活動與回合結束。需要回應的通知要靠 hook 才精準 |
| Codex zero-config(預設) | 讀 ~/.codex/state_5.sqlite threads + rollout JSONL,並用 live codex process 配 tty |
免設定;可辨識 live / ended / 回合結束。同 cwd 多 session 建議裝 hook 取得精準跳轉 |
| Ollama zero-config(預設) | 偵測有控制終端的 ollama run process |
行程存活層級;顯示 cwd、TTY 與模型,不把 ollama serve 當 session |
| llama.cpp zero-config(預設) | 偵測有控制終端的 llama-cli 或 llama cli process |
行程存活層級;顯示 cwd、TTY 與模型,不把 llama-server/llama server 當 session |
| hook registry(opt-in,精準) | RiNG hook 在 Notification / UserPromptSubmit / Stop / SessionEnd 即時寫 ~/.config/ring/sessions/ |
準(🔴 等你 / 🟢 工作中 / 🟡 跑完停著 / ⚫ 已離場) |
zero-config 不必設定就能用;想要精準的「誰在等你」,就讓 provider 的 hook 餵進 RiNG registry。
RiNG 內建 Claude Code / Codex hook 安裝器;其他工具可直接走 provider-neutral ring hook protocol。
Ollama 與 llama.cpp 本身沒有 RiNG 可讀的 session transcript 或互動 hook,因此 zero-config 列固定以
🟡 顯示:它代表互動式 CLI 還活著,不代表 RiNG 能分辨它正在生成或等下一個 prompt。CLI 結束後列
就會移除。若外層 agent 能送出生命週期事件,可用下方的中立 hook protocol 取得精準狀態。
這條來源只收直接在終端執行的 ollama run、llama-cli 與新版 llama cli;Ollama API client、
GUI、Docker container 與常駐 server 不會冒充互動 session。process 的 cwd 由 lsof 取得,因此
macOS/Linux 環境都需要 ps 與 lsof;ring doctor 會在缺少工具或掃描失敗時明確標示。
狀態機¶
RiNG 把每個 session 壓成四種狀態。看板排序時,🔴 等你永遠排最上面並 highlight。
| 狀態 | 你可以怎麼理解 | RiNG 看到什麼 |
|---|---|---|
| 🔴 等你 | 需要你現在回去處理 | hook 收到權限請求、選項提問等需要回應的通知 |
| 🟢 工作中 | agent 正在跑,先不用管 | 你剛送出 prompt,或 session 最近仍有活動 |
| 🟡 跑完停著 | 這輪跑完了,停在那裡 | 收到 Stop,或超過 working_threshold_seconds 沒有新活動 |
| ⚫ 已離場 | session 已結束 | 收到 SessionEnd,或 process / 本機紀錄顯示它已離場 |
狀態更新規則:
| 發生的事 | 新狀態 |
|---|---|
SessionStart / UserPromptSubmit |
🟢 工作中 |
Stop |
🟡 跑完停著 |
權限請求、選項提問、requires_action = true |
🔴 等你 |
SessionEnd / process 結束 |
⚫ 已離場 |
| zero-config 來源太久沒更新 | 🟢 工作中 退成 🟡 跑完停著 |
🔴 等你只有 hook 模式測得到;zero-config 只能判斷「最近有沒有活動」, 分不出「需要你做決策」和「剛跑完停著」。
hook 模式(精準的「等你」)¶
zero-config 只靠檔案 mtime 或本機 state,分不出「需要你做決策」還是「剛跑完」。 裝 hook 後,RiNG 直接收 agent CLI 的事件,狀態才會精準——🔴 等你 + 響鈴都要靠它。
Claude Code / Codex:內建安裝器¶
hook 會被寫成「執行 ring hook」,所以 ring 要在 PATH 上、指向穩定路徑:
註冊 hook:
ring install-hooks # 寫進 Claude Code / Codex hook 設定(合併,不覆蓋既有 hooks)
ring install-hooks --dry-run # 只想先看會寫什麼、不動檔
Claude Code 會寫進 ~/.claude/settings.json。如果你有在用 Codex(~/.codex 存在),
RiNG 也會寫進 ~/.codex/hooks.json;Codex 會要求你信任新 hook,信任後才會執行。
Claude Code 註冊這幾個事件,對應到狀態:
| Claude Code 事件 | RiNG 狀態 |
|---|---|
SessionStart / UserPromptSubmit |
🟢 工作中 |
Stop |
🟡 跑完停著 |
Notification 的 permission_prompt / elicitation_dialog |
🔴 等你(卡權限 / 需要選項) |
PermissionRequest / PreToolUse 的 AskUserQuestion |
🔴 等你(權限 / 選項需要你決策) |
SessionEnd |
從看板消失 |
Stop 還有一種例外:agent 有時不用 AskUserQuestion、不觸發權限請求,只在文字裡問了一句話
就停下來(例如「要不要順便修 B?」)。這種情況下 RiNG 會看最後一則 assistant 訊息的結尾——
結尾是問句(? / ?,圍欄程式碼區塊先剝掉再看)就升級成 🔴 等你(waiting_kind="question"),
detail 帶那句問句;問句只出現在訊息中段、結尾是陳述句則不算,保守判斷、寧可漏報不要誤報。
Claude Code 與 Codex 兩邊都適用(兩者的 Stop payload 都帶 last_assistant_message);
detect_stop_questions 設定鍵可關閉這個行為(預設開)。
Codex 目前註冊 Codex 支援的互動事件:PreToolUse、PermissionRequest、PostToolUse、Stop。
Codex 的 PermissionRequest 也會在既有 policy 自動放行前送出,所以裸事件在 hook 層維持 🟢
工作中。Codex 的 hook 沒有「使用者已核可」事件、沒有心跳,等核可期間 hook 通道完全靜默
——RiNG 因此把靜默本身當訊號:最後一個 hook 事件是 PermissionRequest、又超過
codex_permission_wait_seconds(預設 10 秒)沒有任何後續事件,看板就把它標成 🔴 等你,
detail 帶當初要跑的指令摘要;任何後續事件(下一個工具、Stop)到了就自然轉回。
Codex 側的已知限制(0.144.4 實證,hook payload 無任何欄位可區分):
- 分不出核可還是拒絕:你按下 allow 或 deny 時,hook 都不會收到事件。allow 之後若那條指令
本身跑很久,🔴 會多亮到指令跑完(
PostToolUse)為止;deny 之後若 agent 沒有立即的後續動作, 🔴 會亮到下一個事件或Stop才清。 - 核可等待轉 🔴 的系統通知由 TUI 的提醒排程器代發(沒有 hook 事件可在當下發);headless
--watch不發這一種通知。
hook 只對新開的 session 生效,所以裝完要重開。確認方法:
RiNG 一偵測到 ~/.config/ring/sessions/ 有資料就自動切精準模式(hook 來源優先、
zero-config 掃描補上沒裝 hook 的 session)。
其他 provider:中立 hook protocol¶
RiNG 不相依 agent-hooks,也不要求 provider 一定要用內建安裝器。任何工具只要在事件發生時把 JSON
餵給 ring hook 即可:
payload 欄位採寬鬆命名,至少需要 session id、event、cwd:
{
"provider": "codex",
"session_id": "thread-123",
"event": "Notification",
"notification_type": "permission_prompt",
"requires_action": true,
"waiting_for": "permission",
"cwd": "/repo/app",
"tty": "/dev/ttys003",
"last_action": "waiting for permission"
}
支援的事件語意與內建 provider 一致:SessionStart / UserPromptSubmit → 🟢 工作中;
Stop 預設 → 🟡 跑完停著,但結尾純文字提問可升成 🔴 等你;需要回應的 Notification 或帶明確互動 payload 的 PermissionRequest → 🔴 等你;裸 PermissionRequest → 🟢 權限判定中;SessionEnd →
從看板移除。非 Claude provider 的 session id 會自動加上 provider prefix
(例如 codex:thread-123),避免不同工具撞 id。
provider 若能分辨「立刻需要使用者」與「只是等下一步」,請直接給明確欄位:
requires_action = true/false 或 waiting_for = "permission" | "options" | "next_step"。
RiNG 會優先相信這些欄位;沒有時才退回 event / notification type 推論。
系統通知(🔔 等你時自動通知 + 點擊聚焦)¶
這裡補細節:系統通知由 ring hook 在事件當下送出(headless --watch 與 TUI 都只負責顯示
看板,不發系統通知)。預設後端是 auto:有可點擊的 terminal-notifier 就優先用它;沒有時
退回 osascript(macOS)或 notify-send(Linux)。如果全部不可用,就只保留看板,不讓通知失敗打斷主流程。
- 設定:
notify_sound、notify_sound_name、notify_ignore_dnd、notify_repeat_seconds、notify_repeat_max、notify_backend、waiting_cooldown_seconds、notify_debounce_seconds都可在~/.config/ring/config.toml調整。 - 防翻轉轟炸:背景 subagent 的權限請求可能讓 session 在等你/工作中之間快速翻轉。
waiting_cooldown_seconds(預設 180 秒)讓ring hook的系統通知與 TUI 的響鈴/提醒, 在 session 離開等你又很快轉回時,距上次通知未滿冷卻期就不再立即發;設0關閉冷卻 (回到每次轉入都發)。 - 點擊聚焦 + 聲音:需先安裝
terminal-notifier(brew)。點擊通知後會直接跳回 RiNG TUI 並選中 session;若沒有 TUI 在跑,則直接跳到對應終端。
- 純文字通知:未裝
terminal-notifier則退化為 macOS 原生純文字通知(可帶聲音,點擊不可聚焦)。 - agent CLI 自己也會通知:
ring install-hooks只寫~/.claude/settings.json的hooks, 不會碰 Claude Code 自己的preferredNotifChannel。兩條路平行:同一個權限請求會有兩則通知, 而且 Claude Code 那條還會在 Stop 後約 60 秒發idle_prompt(閒著、換你)——那是 RiNG 刻意 判 🟡 不發的類別,跳過去只會看到空 prompt,沒有東西可回。ring doctor的「Claude Code 自帶通知」一節會印出目前通道與終端;想讓 RiNG 當唯一來源,把preferredNotifChannel設成notifications_disabled(代價是「跑完換你」就沒人通知了)。 -
擴充:要接其他桌面通知、webhook 或自訂提醒方式,可以新增
Notifier後端並註冊, 詳見「其他通知後端」。 -
交給 agent-hooks:
agent-hooks是另一個可選的 hook helper / 決策 UI。若你已安裝它, 並把notify_backend設成agent-hooks,ring hook會照樣寫 RiNG registry, 同時把原始 payload 交給agent-hooks callback處理同步決策視窗;RiNG 的--watch不會再重複發通知。若 PATH 上找不到agent-hooks,會自動退回auto。
移除 hooks¶
不想再讓 agent CLI 事件寫進 RiNG registry 時,可以移除 RiNG 安裝的 hook 條目。
這不會刪掉 ~/.config/ring/ 裡的 session 紀錄,也不會動到 Claude Code / Codex 的其他 hooks。
ring remove-hooks # 從 Claude Code / Codex hook 設定移除 RiNG hook 條目
ring remove-hooks --dry-run # 只預覽,不改檔
設定(選用)¶
~/.config/ring/config.toml,全部選填、缺了就用預設:
lang = "zh-Hant" # 預設語言(CLI --lang 最優先,再來 RING_LANG / LANG)
interval = 2.0 # watch 刷新秒數
show_all = false # 是否預設顯示已離場
legend = true # 是否預設顯示圖例
active_window_seconds = 21600 # 只看最近這段時間動過的 session(預設 6h)
working_threshold_seconds = 90 # 多久沒動 → 🟢 工作中 變 🟡 閒置
waiting_window_seconds = 1800 # 零設定掃描中,近期回合結束列收斂成 🟡 的時間窗
codex_permission_wait_seconds = 10 # Codex 裸 PermissionRequest 後 hook 靜默超過這秒數 → 🔴 等你;0 = 關閉
detect_stop_questions = true # Stop 結尾是純文字提問時升級成 🔴 等你;false = Stop 一律 🟡
notify_sound = true # 系統通知是否播放聲音
notify_sound_name = "Glass" # macOS / terminal-notifier sound name
notify_ignore_dnd = false # macOS terminal-notifier 是否穿透勿擾 / Focus
notify_backend = "auto" # auto / terminal-notifier / osascript / notify-send / agent-hooks / none
notify_repeat_seconds = [30, 120, 300] # 持續等你時,幾秒後重複提醒
notify_repeat_max = 3 # 重複提醒上限;0 = 不限
waiting_cooldown_seconds = 180 # 離開等你又轉回時,距上次提醒未滿這段時間就不再立即提醒;0 = 關閉
notify_debounce_seconds = 0 # 跨 session 通知合流窗口秒數;0 = 關閉(預設,向後相容)
notify_ntfy_url = "" # 設完整 ntfy topic URL 啟用手機推播(如 https://ntfy.sh/my-topic)
notify_webhook_url = "" # 設 URL 啟用通用 webhook 後端(JSON POST)
notify_also = [] # 主後端之外「加發」的後端,如 ["ntfy"](桌面+手機各一份)
focusers = ["Neovim", "tmux", "iTerm2", "Terminal", "kitty", "linux-wm"] # 跳轉嘗試順序
plugins = [] # 啟動時 import 的外部 plugin 模組(見「支援的工具與擴充」)
debug_payload_log = false # 診斷時記錄原始 hook payload;可能含使用者輸入,預設關閉
[colors] # Rich 樣式字串,逐項覆寫(深淺底安全色為預設)
waiting = "bold red"
working = "green"
idle = "yellow"
ended = "grey50"
project = "cyan"
location = "bright_blue"
muted = "grey50"
語言 / 翻譯¶
UI 用 gettext,msgid 直接是台灣漢語——所以預設(zh-Hant)不需要任何 .mo,
原始碼即翻譯。切語言:--lang en(或 RING_LANG=en / config 的 lang)。複數用
ngettext 正確處理(1 session vs 2 sessions)。
加一個語言、或改了字串要重抽:
poe i18n:extract # 從原始碼抽 msgid → src/ring/locale/ring.pot
# 複製 ring.pot 成 src/ring/locale/<lang>/LC_MESSAGES/ring.po,填 msgstr
poe i18n:compile # 各 .po → .mo(.mo 要 commit;wheel 會自動帶上)
支援的工具與擴充¶
core 不綁死任何特定工具或終端。三個維度都可插拔,每個都是「寫個小類別 + 註冊」、主流程零改動:
| 維度 | 在做什麼 | 內建 |
|---|---|---|
SessionSource |
從哪裡找到 session | Claude Code、Codex、Ollama、llama.cpp、RiNG hook registry |
Focuser |
跳轉時把焦點帶去哪個終端 | tmux、iTerm2、Terminal.app、kitty、Linux X11(wmctrl) |
Notifier |
等你時怎麼發系統通知 | terminal-notifier、osascript、notify-send、ntfy、webhook |
每個維度各自一個 package(ring/sources/、ring/focus/、ring/notify/),每個後端是裡面
一個模組;要加新的=丟一個模組 + register_*()。
讓裝好的 ring 載入你的 plugin¶
register_*() 要有人執行才算數。裝好的 ring 指令啟動時會自動載入兩種來源的 plugin:
- entry point(發佈成套件時)——在你套件的
pyproject.toml宣告,指向一個模組或 callable(模組在 import 時自行register_*();callable 會被無參數呼叫一次):
- config(本機腳本)——
~/.config/ring/config.toml寫plugins = ["my_module"], 模組要在sys.path上(site-packages 或PYTHONPATH)。
單一 plugin 壞掉只會在 stderr 警告一行、不擋看板。
其他 agent CLI(SessionSource)¶
內建 HookRegistrySource(讀 ~/.config/ring/sessions/)、ClaudeCodeSource(掃
~/.claude)、CodexSource(讀 ~/.codex/state_5.sqlite),以及 Ollama / llama.cpp
互動式 CLI 的 process source。要監測其他工具,
可以優先餵 ring hook;若工具沒有 hook,再寫一個 source 吐出 Session、註冊即可:
from ring.registry import Session, Status
from ring.sources import register_source
class MyToolSource:
name = "mytool"
def discover(self) -> list[Session]:
return [
Session(session_id="…", cwd="…", status=Status.WORKING, last_active=0.0, last_action="→ …", source="mytool")
]
register_source(MyToolSource())
Session 是工具中立的(id / cwd / status / last_action / tty / todo…),各 source
自己決定怎麼填。
其他終端(Focuser)¶
跳轉的終端整合也一樣——寫個符合 Focuser 協定的類別(try_focus(session) ->
(ok, msg) | None),呼叫 ring.focus.register_focuser(MyFocuser())。內建 Neovim terminal / tmux /
iTerm2 / Terminal.app / kitty / Linux X11 視窗(wmctrl),各自一個模組(ring/focus/neovim.py、
ring/focus/tmux.py、ring/focus/kitty.py …)。Neovim focuser 會先透過 $NVIM server socket 切到
承載該 session 的 :terminal buffer,再交給外層 focuser 聚焦 pane 或視窗。
要再加 Ghostty / WezTerm,就照這個模式新增 focuser;嘗試順序也能用 config 的 focusers 調整。
其他通知後端(Notifier)¶
系統通知也是可插拔的——寫個符合 Notifier 協定的類別,呼叫
ring.notify.register_notifier(MyNotifier())。內建 terminal-notifier(可點擊跳轉)/
osascript / notify-send,各自一個模組(ring/notify/terminal_notifier.py …):
from ring.notify import register_notifier
class MyNotifier:
name = "mytool-notify"
def available(self) -> bool: ... # 這個後端現在能不能用(通常看 binary 在不在)
def supports_click(self) -> bool: ... # 點通知能不能跳回 session
def send(self, sessions): ... # 逐 session 各發一則通知
register_notifier(MyNotifier())
選哪個後端由 config 的 notify_backend 決定(auto / 指定名稱 / none 純看板不發通知)。