ring-cli 0.9.0__tar.gz → 0.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ring_cli-0.9.0 → ring_cli-0.11.0}/PKG-INFO +36 -4
- {ring_cli-0.9.0 → ring_cli-0.11.0}/README.en.md +35 -3
- {ring_cli-0.9.0 → ring_cli-0.11.0}/pyproject.toml +1 -1
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/config.py +5 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/hook.py +38 -6
- ring_cli-0.11.0/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/locale/en/LC_MESSAGES/ring.po +39 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/locale/ring.pot +104 -40
- ring_cli-0.11.0/src/ring/permission.py +334 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/registry.py +48 -3
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/tui.py +136 -3
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/watcher.py +29 -2
- ring_cli-0.9.0/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/LICENSE +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/__init__.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/__main__.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/cli.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/__init__.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/_args.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/completion.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/digest.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/doctor.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/focus.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/gc.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/hook.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/commands/stats.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/__init__.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/applescript.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/base.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/iterm2.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/linux_wm.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/neovim.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/terminal.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/focus/tmux.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/gc.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/hook_protocol.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/i18n.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/ipc.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/labels.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/__init__.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/base.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/command.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/notify_send.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/ntfy.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/osascript_notifier.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/terminal_notifier.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/notify/webhook.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/osascript.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/plugins.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/sources/__init__.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/sources/base.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/sources/claude_code.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/sources/codex.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/sources/hook_registry.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/stats.py +0 -0
- {ring_cli-0.9.0 → ring_cli-0.11.0}/src/ring/transcript.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ring-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: RiNG — Realtime Instance Notification Grid for active agent CLI sessions.
|
|
5
5
|
Keywords: claude-code,codex,tui,dashboard,session,monitor,rich
|
|
6
6
|
Author: Wei Lee
|
|
@@ -59,6 +59,7 @@ RiNG puts them on one board, with sessions waiting for you sorted first.
|
|
|
59
59
|
- **Waiting first**: sessions that need your response are highlighted and sorted above the rest.
|
|
60
60
|
- **Jump back to the terminal**: in the TUI, select a session and press `Enter` / `Space` to focus tmux, iTerm2, Terminal.app (macOS), or a Linux X11 window (`wmctrl`).
|
|
61
61
|
- **Notifies you without the board open**: with hooks installed, the moment a session turns 🔴 waiting it beeps and fires a system notification — even with no RiNG board running. With `terminal-notifier`, clicking the notification jumps back.
|
|
62
|
+
- **Reply to permission requests in place**: with the cursor on a 🔴 waiting row, press `p` — RiNG reads the permission dialog from that session's terminal screen, pops the numbered options in a modal, and presses your choice for you, so you don't have to jump over one by one (sessions inside tmux, plus plain iTerm2 tabs on macOS).
|
|
62
63
|
- **Name your sessions**: press `n` in the TUI to name a session, e.g. "auth refactor"; once named, the board and notifications show the name instead of the workspace directory.
|
|
63
64
|
- **See what it is waiting for**: in hook mode, a 🔴 waiting session carries the concrete pending item (the command to run, the question asked) — shown in the TUI and in the notification body.
|
|
64
65
|
- **Fits your status bar**: `ring --format oneline` prints a `🔴2 🟢1 🟡3` one-liner for tmux / SwiftBar / waybar; `--format json` feeds scripts.
|
|
@@ -153,7 +154,7 @@ Completes subcommands, flags, and `config set` keys; `ring focus` prompts for a
|
|
|
153
154
|
## Watch Mode
|
|
154
155
|
|
|
155
156
|
- With **Textual** (`[tui]` extra) in a real terminal: interactive TUI.
|
|
156
|
-
Use `↑/↓` to select, `Enter` / `Space` to jump, `n` to name a session, `a` to toggle ended sessions, `dd` to hide a session (it reappears automatically once it has new activity), `r` to refresh, and `q` to quit.
|
|
157
|
+
Use `↑/↓` to select, `Enter` / `Space` to jump, `p` to reply to a permission request in place, `n` to name a session, `a` to toggle ended sessions, `dd` to hide a session (it reappears automatically once it has new activity), `r` to refresh, and `q` to quit.
|
|
157
158
|
If you have vim muscle memory like I do, `j/k` move up/down and `g/G` jump to the first/last row.
|
|
158
159
|
When the selected row is 🔴 waiting, a line under the table shows **what it is concretely waiting for** (the command to run, the question asked; hook mode only).
|
|
159
160
|
- Otherwise: Rich polling; without Rich, plain text.
|
|
@@ -172,6 +173,30 @@ hands the request to the TUI so it selects that session; otherwise it focuses th
|
|
|
172
173
|
TTY matching is most accurate in hook mode. Without hooks, Codex falls back to zero-config matching:
|
|
173
174
|
one live Codex session per cwd can jump correctly; multiple live Codex sessions in the same cwd are shown conservatively to avoid focusing the wrong tab.
|
|
174
175
|
|
|
176
|
+
### Reply To Permission Requests In Place (`p`)
|
|
177
|
+
|
|
178
|
+
With the cursor on a 🔴 waiting row, press `p`. RiNG reads that session's screen, parses the
|
|
179
|
+
permission dialog (Claude Code's "Do you want to proceed?" box, including background-subagent
|
|
180
|
+
ones with a "from the … agent" header), and lists the numbered options verbatim in a modal.
|
|
181
|
+
After you pick one, RiNG **captures the screen again** to confirm the dialog is still there and
|
|
182
|
+
unchanged (it may have been answered while you were deciding), only then sends that single
|
|
183
|
+
digit, and re-checks that the dialog actually disappeared.
|
|
184
|
+
|
|
185
|
+
- **Sessions inside tmux**: reads the screen with `tmux capture-pane` and sends keys with
|
|
186
|
+
`tmux send-keys`.
|
|
187
|
+
- **Sessions in a plain iTerm2 tab on macOS** (no tmux): locates the matching iTerm2 session by
|
|
188
|
+
its `tty` via AppleScript, and both reads the screen and sends keys through `osascript`. The
|
|
189
|
+
first use triggers the macOS Automation permission prompt ("allow control of iTerm2") — allow
|
|
190
|
+
it once.
|
|
191
|
+
|
|
192
|
+
Safety first: if no recognizable dialog can be parsed (missing markers, numbering, or cursor),
|
|
193
|
+
RiNG only shows a toast and **never sends a key** — without the dialog, keystrokes would land in
|
|
194
|
+
the chat input box as text. If the dialog happens to vanish in the instant the digit is sent and
|
|
195
|
+
the digit lands in the input box, RiNG sends a Backspace to clean it up and warns you.
|
|
196
|
+
|
|
197
|
+
**Limits**: reading the screen requires a tmux pane coordinate, or (on macOS) an iTerm2 session
|
|
198
|
+
with a detectable tty; for other sessions, press `Enter` to jump over and reply there.
|
|
199
|
+
|
|
175
200
|
### Notifications
|
|
176
201
|
|
|
177
202
|
Default behavior: with hooks installed, the moment a session changes to 🔴 waiting, the hook beeps
|
|
@@ -336,8 +361,14 @@ RiNG prefers clickable `terminal-notifier`, then falls back to `osascript` on ma
|
|
|
336
361
|
on Linux. If none are available, RiNG keeps the board running and skips notifications.
|
|
337
362
|
|
|
338
363
|
Config keys include `notify_sound`, `notify_sound_name`, `notify_ignore_dnd`,
|
|
339
|
-
`notify_repeat_seconds`, `notify_repeat_max`, and `
|
|
340
|
-
can be added by registering another `Notifier`.
|
|
364
|
+
`notify_repeat_seconds`, `notify_repeat_max`, `notify_backend`, and `waiting_cooldown_seconds`.
|
|
365
|
+
Custom notification channels can be added by registering another `Notifier`.
|
|
366
|
+
|
|
367
|
+
Background subagent permission requests can make a session flap between waiting and working in
|
|
368
|
+
quick succession. `waiting_cooldown_seconds` (180s by default) stops `ring hook`'s system
|
|
369
|
+
notification and the TUI's bell/reminder from firing again the instant a session re-enters waiting
|
|
370
|
+
within the cooldown window of its last alert; set it to `0` to disable the cooldown (alert on
|
|
371
|
+
every re-entry, the old behavior).
|
|
341
372
|
|
|
342
373
|
`agent-hooks` is an optional external hook helper / decision UI. If it is installed and
|
|
343
374
|
`notify_backend = "agent-hooks"`, `ring hook` still writes RiNG registry state, then passes the
|
|
@@ -372,6 +403,7 @@ notify_ignore_dnd = false
|
|
|
372
403
|
notify_backend = "auto" # auto / terminal-notifier / osascript / notify-send / agent-hooks / none
|
|
373
404
|
notify_repeat_seconds = [30, 120, 300]
|
|
374
405
|
notify_repeat_max = 3
|
|
406
|
+
waiting_cooldown_seconds = 180 # suppress an immediate re-alert on re-entering waiting within this window; 0 = off
|
|
375
407
|
notify_ntfy_url = "" # full ntfy topic URL enables phone push (e.g. https://ntfy.sh/my-topic)
|
|
376
408
|
notify_webhook_url = "" # URL enables the generic webhook backend (JSON POST)
|
|
377
409
|
notify_also = [] # extra backends fired besides the primary, e.g. ["ntfy"]
|
|
@@ -34,6 +34,7 @@ RiNG puts them on one board, with sessions waiting for you sorted first.
|
|
|
34
34
|
- **Waiting first**: sessions that need your response are highlighted and sorted above the rest.
|
|
35
35
|
- **Jump back to the terminal**: in the TUI, select a session and press `Enter` / `Space` to focus tmux, iTerm2, Terminal.app (macOS), or a Linux X11 window (`wmctrl`).
|
|
36
36
|
- **Notifies you without the board open**: with hooks installed, the moment a session turns 🔴 waiting it beeps and fires a system notification — even with no RiNG board running. With `terminal-notifier`, clicking the notification jumps back.
|
|
37
|
+
- **Reply to permission requests in place**: with the cursor on a 🔴 waiting row, press `p` — RiNG reads the permission dialog from that session's terminal screen, pops the numbered options in a modal, and presses your choice for you, so you don't have to jump over one by one (sessions inside tmux, plus plain iTerm2 tabs on macOS).
|
|
37
38
|
- **Name your sessions**: press `n` in the TUI to name a session, e.g. "auth refactor"; once named, the board and notifications show the name instead of the workspace directory.
|
|
38
39
|
- **See what it is waiting for**: in hook mode, a 🔴 waiting session carries the concrete pending item (the command to run, the question asked) — shown in the TUI and in the notification body.
|
|
39
40
|
- **Fits your status bar**: `ring --format oneline` prints a `🔴2 🟢1 🟡3` one-liner for tmux / SwiftBar / waybar; `--format json` feeds scripts.
|
|
@@ -128,7 +129,7 @@ Completes subcommands, flags, and `config set` keys; `ring focus` prompts for a
|
|
|
128
129
|
## Watch Mode
|
|
129
130
|
|
|
130
131
|
- With **Textual** (`[tui]` extra) in a real terminal: interactive TUI.
|
|
131
|
-
Use `↑/↓` to select, `Enter` / `Space` to jump, `n` to name a session, `a` to toggle ended sessions, `dd` to hide a session (it reappears automatically once it has new activity), `r` to refresh, and `q` to quit.
|
|
132
|
+
Use `↑/↓` to select, `Enter` / `Space` to jump, `p` to reply to a permission request in place, `n` to name a session, `a` to toggle ended sessions, `dd` to hide a session (it reappears automatically once it has new activity), `r` to refresh, and `q` to quit.
|
|
132
133
|
If you have vim muscle memory like I do, `j/k` move up/down and `g/G` jump to the first/last row.
|
|
133
134
|
When the selected row is 🔴 waiting, a line under the table shows **what it is concretely waiting for** (the command to run, the question asked; hook mode only).
|
|
134
135
|
- Otherwise: Rich polling; without Rich, plain text.
|
|
@@ -147,6 +148,30 @@ hands the request to the TUI so it selects that session; otherwise it focuses th
|
|
|
147
148
|
TTY matching is most accurate in hook mode. Without hooks, Codex falls back to zero-config matching:
|
|
148
149
|
one live Codex session per cwd can jump correctly; multiple live Codex sessions in the same cwd are shown conservatively to avoid focusing the wrong tab.
|
|
149
150
|
|
|
151
|
+
### Reply To Permission Requests In Place (`p`)
|
|
152
|
+
|
|
153
|
+
With the cursor on a 🔴 waiting row, press `p`. RiNG reads that session's screen, parses the
|
|
154
|
+
permission dialog (Claude Code's "Do you want to proceed?" box, including background-subagent
|
|
155
|
+
ones with a "from the … agent" header), and lists the numbered options verbatim in a modal.
|
|
156
|
+
After you pick one, RiNG **captures the screen again** to confirm the dialog is still there and
|
|
157
|
+
unchanged (it may have been answered while you were deciding), only then sends that single
|
|
158
|
+
digit, and re-checks that the dialog actually disappeared.
|
|
159
|
+
|
|
160
|
+
- **Sessions inside tmux**: reads the screen with `tmux capture-pane` and sends keys with
|
|
161
|
+
`tmux send-keys`.
|
|
162
|
+
- **Sessions in a plain iTerm2 tab on macOS** (no tmux): locates the matching iTerm2 session by
|
|
163
|
+
its `tty` via AppleScript, and both reads the screen and sends keys through `osascript`. The
|
|
164
|
+
first use triggers the macOS Automation permission prompt ("allow control of iTerm2") — allow
|
|
165
|
+
it once.
|
|
166
|
+
|
|
167
|
+
Safety first: if no recognizable dialog can be parsed (missing markers, numbering, or cursor),
|
|
168
|
+
RiNG only shows a toast and **never sends a key** — without the dialog, keystrokes would land in
|
|
169
|
+
the chat input box as text. If the dialog happens to vanish in the instant the digit is sent and
|
|
170
|
+
the digit lands in the input box, RiNG sends a Backspace to clean it up and warns you.
|
|
171
|
+
|
|
172
|
+
**Limits**: reading the screen requires a tmux pane coordinate, or (on macOS) an iTerm2 session
|
|
173
|
+
with a detectable tty; for other sessions, press `Enter` to jump over and reply there.
|
|
174
|
+
|
|
150
175
|
### Notifications
|
|
151
176
|
|
|
152
177
|
Default behavior: with hooks installed, the moment a session changes to 🔴 waiting, the hook beeps
|
|
@@ -311,8 +336,14 @@ RiNG prefers clickable `terminal-notifier`, then falls back to `osascript` on ma
|
|
|
311
336
|
on Linux. If none are available, RiNG keeps the board running and skips notifications.
|
|
312
337
|
|
|
313
338
|
Config keys include `notify_sound`, `notify_sound_name`, `notify_ignore_dnd`,
|
|
314
|
-
`notify_repeat_seconds`, `notify_repeat_max`, and `
|
|
315
|
-
can be added by registering another `Notifier`.
|
|
339
|
+
`notify_repeat_seconds`, `notify_repeat_max`, `notify_backend`, and `waiting_cooldown_seconds`.
|
|
340
|
+
Custom notification channels can be added by registering another `Notifier`.
|
|
341
|
+
|
|
342
|
+
Background subagent permission requests can make a session flap between waiting and working in
|
|
343
|
+
quick succession. `waiting_cooldown_seconds` (180s by default) stops `ring hook`'s system
|
|
344
|
+
notification and the TUI's bell/reminder from firing again the instant a session re-enters waiting
|
|
345
|
+
within the cooldown window of its last alert; set it to `0` to disable the cooldown (alert on
|
|
346
|
+
every re-entry, the old behavior).
|
|
316
347
|
|
|
317
348
|
`agent-hooks` is an optional external hook helper / decision UI. If it is installed and
|
|
318
349
|
`notify_backend = "agent-hooks"`, `ring hook` still writes RiNG registry state, then passes the
|
|
@@ -347,6 +378,7 @@ notify_ignore_dnd = false
|
|
|
347
378
|
notify_backend = "auto" # auto / terminal-notifier / osascript / notify-send / agent-hooks / none
|
|
348
379
|
notify_repeat_seconds = [30, 120, 300]
|
|
349
380
|
notify_repeat_max = 3
|
|
381
|
+
waiting_cooldown_seconds = 180 # suppress an immediate re-alert on re-entering waiting within this window; 0 = off
|
|
350
382
|
notify_ntfy_url = "" # full ntfy topic URL enables phone push (e.g. https://ntfy.sh/my-topic)
|
|
351
383
|
notify_webhook_url = "" # URL enables the generic webhook backend (JSON POST)
|
|
352
384
|
notify_also = [] # extra backends fired besides the primary, e.g. ["ntfy"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# import 的 module(見 [tool.uv.build-backend] module-name)與 CLI 指令仍是 `ring`。
|
|
3
3
|
[project]
|
|
4
4
|
name = "ring-cli"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.11.0"
|
|
6
6
|
description = "RiNG — Realtime Instance Notification Grid for active agent CLI sessions."
|
|
7
7
|
readme = "README.en.md"
|
|
8
8
|
requires-python = ">=3.13"
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
# "none" = 完全不發通知(RiNG 當純看板)
|
|
21
21
|
notify_repeat_seconds = [30, 120, 300] # 持續等你時,多久後重複提醒
|
|
22
22
|
notify_repeat_max = 3 # 重複提醒上限;0 = 不限
|
|
23
|
+
waiting_cooldown_seconds = 180 # session 離開 WAITING 又轉回時,距上次提醒未滿這段時間就
|
|
24
|
+
# 不再當「新轉入」立即提醒(防翻轉轟炸);0 = 關閉(現行為)
|
|
23
25
|
notify_ntfy_url = "https://ntfy.sh/my-topic" # 設了才啟用 ntfy 後端(推到手機)
|
|
24
26
|
notify_webhook_url = "https://example.com/hook" # 設了才啟用 webhook 後端(JSON POST)
|
|
25
27
|
notify_also = ["ntfy"] # 主後端之外「加發」的後端(例如桌面通知+手機各一份)
|
|
@@ -69,6 +71,7 @@ class Config:
|
|
|
69
71
|
notify_backend: str = "auto"
|
|
70
72
|
notify_repeat_seconds: tuple[int, ...] = (30, 120, 300)
|
|
71
73
|
notify_repeat_max: int = 3 # 0 = 不限
|
|
74
|
+
waiting_cooldown_seconds: int = 180 # 離開 WAITING 又轉回時的提醒冷卻期;0 = 關閉(現行為)
|
|
72
75
|
notify_ntfy_url: str = "" # 完整 ntfy topic URL;空=ntfy 後端不可用
|
|
73
76
|
notify_webhook_url: str = "" # webhook URL;空=webhook 後端不可用
|
|
74
77
|
notify_also: tuple[str, ...] = () # 主後端之外加發的後端名(如 ["ntfy"])
|
|
@@ -138,6 +141,7 @@ def load(path: Path | None = None) -> Config:
|
|
|
138
141
|
notify_ignore_dnd=_as_bool(raw.get("notify_ignore_dnd"), d.notify_ignore_dnd),
|
|
139
142
|
notify_repeat_seconds=_as_positive_int_tuple(raw.get("notify_repeat_seconds"), d.notify_repeat_seconds),
|
|
140
143
|
notify_repeat_max=max(0, _as_int(raw.get("notify_repeat_max"), d.notify_repeat_max)),
|
|
144
|
+
waiting_cooldown_seconds=max(0, _as_int(raw.get("waiting_cooldown_seconds"), d.waiting_cooldown_seconds)),
|
|
141
145
|
notify_backend=notify_backend,
|
|
142
146
|
notify_ntfy_url=(raw["notify_ntfy_url"] if isinstance(raw.get("notify_ntfy_url"), str) else ""),
|
|
143
147
|
notify_webhook_url=(raw["notify_webhook_url"] if isinstance(raw.get("notify_webhook_url"), str) else ""),
|
|
@@ -208,6 +212,7 @@ _SETTERS: dict[str, Callable[[str], object]] = {
|
|
|
208
212
|
"notify_backend": str,
|
|
209
213
|
"notify_repeat_seconds": _coerce_int_list,
|
|
210
214
|
"notify_repeat_max": _coerce_int,
|
|
215
|
+
"waiting_cooldown_seconds": _coerce_int,
|
|
211
216
|
"notify_ntfy_url": str,
|
|
212
217
|
"notify_webhook_url": str,
|
|
213
218
|
"notify_also": _coerce_str_list,
|
|
@@ -169,7 +169,8 @@ def _record_session_state(data: dict[str, Any], selected_provider: str) -> None:
|
|
|
169
169
|
|
|
170
170
|
unhide_session(event.session_id)
|
|
171
171
|
path = RING_REGISTRY / f"{quote(event.session_id, safe=':')}.json"
|
|
172
|
-
|
|
172
|
+
prev_row = _previous_row(path)
|
|
173
|
+
prev_status = str(prev_row["status"]) if "status" in prev_row else None
|
|
173
174
|
if event.status is Status.ENDED:
|
|
174
175
|
path.unlink(missing_ok=True) # 乾淨離場:直接消失
|
|
175
176
|
if prev_status is not None and prev_status != Status.ENDED.value:
|
|
@@ -213,6 +214,19 @@ def _record_session_state(data: dict[str, Any], selected_provider: str) -> None:
|
|
|
213
214
|
if tty:
|
|
214
215
|
payload["tty"] = tty
|
|
215
216
|
|
|
217
|
+
# waiting 通知冷卻(防翻轉轟炸):hook 是短命 process,冷卻狀態持久化在 registry row
|
|
218
|
+
# 的 waiting_notified_at(上次 waiting 通知的 epoch 時間戳)。離開 WAITING 也要把時間戳
|
|
219
|
+
# 帶進新 row(payload 每次重建,不帶會遺失),快速翻回 WAITING 時才判得出「還在冷卻期」。
|
|
220
|
+
# 舊 row 沒這欄位 = 從未通知過 → 照發。並發寫沿用既有 tmp+replace 的 last-write-wins。
|
|
221
|
+
last_notified = _as_epoch(prev_row.get("waiting_notified_at"))
|
|
222
|
+
if last_notified is not None:
|
|
223
|
+
payload["waiting_notified_at"] = last_notified
|
|
224
|
+
should_ring = False
|
|
225
|
+
if event.status is Status.WAITING:
|
|
226
|
+
should_ring = _waiting_ring_allowed(last_notified, now)
|
|
227
|
+
if should_ring:
|
|
228
|
+
payload["waiting_notified_at"] = now
|
|
229
|
+
|
|
216
230
|
RING_REGISTRY.mkdir(parents=True, exist_ok=True)
|
|
217
231
|
tmp = path.with_suffix(".json.tmp")
|
|
218
232
|
tmp.write_text(json.dumps(payload))
|
|
@@ -221,17 +235,35 @@ def _record_session_state(data: dict[str, Any], selected_provider: str) -> None:
|
|
|
221
235
|
if prev_status != event.status.value:
|
|
222
236
|
log_transition(event.session_id, event.provider, event.cwd, event.status.value)
|
|
223
237
|
|
|
224
|
-
if
|
|
238
|
+
if should_ring:
|
|
225
239
|
_ring_waiting_now(event, payload, last_action)
|
|
226
240
|
|
|
227
241
|
|
|
228
|
-
def
|
|
229
|
-
"""讀 registry
|
|
242
|
+
def _previous_row(path: Path) -> dict[str, Any]:
|
|
243
|
+
"""讀 registry 檔目前的內容(給轉換偵測與通知冷卻用);沒檔 / 壞檔回空 dict。"""
|
|
230
244
|
try:
|
|
231
245
|
data = json.loads(path.read_text())
|
|
232
|
-
return
|
|
246
|
+
return data if isinstance(data, dict) else {}
|
|
233
247
|
except Exception:
|
|
234
|
-
return
|
|
248
|
+
return {}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _as_epoch(v: Any) -> float | None:
|
|
252
|
+
"""把 row 欄位值安全轉成 epoch float;缺欄位 / 型別不對回 None(視為從未通知)。"""
|
|
253
|
+
return float(v) if isinstance(v, (int, float)) and not isinstance(v, bool) else None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _waiting_ring_allowed(last_notified: float | None, now: float) -> bool:
|
|
257
|
+
"""hook 的 waiting 通知是否放行:距上次通知未滿 ``waiting_cooldown_seconds`` 就不發。
|
|
258
|
+
|
|
259
|
+
背景 subagent 的權限請求會讓 session 以 30 秒~2 分鐘頻率在 working↔waiting 間翻轉,
|
|
260
|
+
每次轉入都發一則系統通知會轟炸使用者。cooldown=0 = 關閉(維持現行為:每次轉入都發);
|
|
261
|
+
從未通知過(``last_notified`` 為 None,含舊版 row 沒有該欄位)一律放行。
|
|
262
|
+
"""
|
|
263
|
+
cooldown = get_config().waiting_cooldown_seconds
|
|
264
|
+
if cooldown <= 0 or last_notified is None:
|
|
265
|
+
return True
|
|
266
|
+
return now - last_notified >= cooldown
|
|
235
267
|
|
|
236
268
|
|
|
237
269
|
def _ring_waiting_now(event: Any, payload: dict[str, Any], last_action: str) -> None:
|
|
Binary file
|
|
@@ -653,3 +653,42 @@ msgstr "total wait"
|
|
|
653
653
|
|
|
654
654
|
msgid "⚠️ 其中 {n} 段還在 🔴 等你(計到現在為止)。"
|
|
655
655
|
msgstr "⚠️ {n} of these are still 🔴 waiting for you (counted up to now)."
|
|
656
|
+
|
|
657
|
+
msgid "回覆權限"
|
|
658
|
+
msgstr "reply perm"
|
|
659
|
+
|
|
660
|
+
msgid "回覆 {project} 的權限請求"
|
|
661
|
+
msgstr "Reply to the permission request from {project}"
|
|
662
|
+
|
|
663
|
+
msgid "Enter 或數字鍵送出、Esc 取消"
|
|
664
|
+
msgstr "Enter or a digit key sends, Esc cancels"
|
|
665
|
+
|
|
666
|
+
msgid "{project}:沒有 tmux 座標,且非 macOS 上的 iTerm2 session,無法就地回覆"
|
|
667
|
+
msgstr "{project}: no tmux coordinates, and not a macOS iTerm2 session either — cannot reply in place"
|
|
668
|
+
|
|
669
|
+
msgid "{project}:讀不到 {backend} 畫面"
|
|
670
|
+
msgstr "{project}: cannot read the {backend} screen"
|
|
671
|
+
|
|
672
|
+
msgid "{project}:畫面上沒有可辨識的權限對話框,未送出任何按鍵"
|
|
673
|
+
msgstr "{project}: no recognizable permission dialog on screen; no key was sent"
|
|
674
|
+
|
|
675
|
+
msgid "→ {project}:已回覆權限({option})"
|
|
676
|
+
msgstr "→ {project}: permission answered ({option})"
|
|
677
|
+
|
|
678
|
+
msgid "{project}:權限對話框已不在,未送出任何按鍵"
|
|
679
|
+
msgstr "{project}: the permission dialog is gone; no key was sent"
|
|
680
|
+
|
|
681
|
+
msgid "{project}:對話框內容已變化,未送出;請再按一次 p"
|
|
682
|
+
msgstr "{project}: the dialog changed; nothing sent — press p again"
|
|
683
|
+
|
|
684
|
+
msgid "{project}:tmux send-keys 失敗,沒能回覆"
|
|
685
|
+
msgstr "{project}: tmux send-keys failed; could not reply"
|
|
686
|
+
|
|
687
|
+
msgid "{project}:已送出但對話框還在,請跳過去確認"
|
|
688
|
+
msgstr "{project}: key sent but the dialog is still there — jump over to check"
|
|
689
|
+
|
|
690
|
+
msgid "{project}:對話框已不在,數字落進輸入框,已補 Backspace 清掉"
|
|
691
|
+
msgstr "{project}: the dialog was gone and the digit landed in the input box — sent Backspace to clean it up"
|
|
692
|
+
|
|
693
|
+
msgid "{project}:已送出但拿不到畫面驗證,請跳過去確認"
|
|
694
|
+
msgstr "{project}: key sent but the screen could not be re-read to verify — jump over to check"
|
|
@@ -8,7 +8,7 @@ msgid ""
|
|
|
8
8
|
msgstr ""
|
|
9
9
|
"Project-Id-Version: PROJECT VERSION\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
11
|
-
"POT-Creation-Date: 2026-07-
|
|
11
|
+
"POT-Creation-Date: 2026-07-12 09:22+0800\n"
|
|
12
12
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
13
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
14
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
@@ -52,7 +52,7 @@ msgstr[1] ""
|
|
|
52
52
|
msgid "🎤 RiNG — {sess} · {proc}"
|
|
53
53
|
msgstr ""
|
|
54
54
|
|
|
55
|
-
#: src/ring/cli.py:163 src/ring/cli.py:216 src/ring/tui.py:
|
|
55
|
+
#: src/ring/cli.py:163 src/ring/cli.py:216 src/ring/tui.py:274
|
|
56
56
|
msgid "圖例"
|
|
57
57
|
msgstr ""
|
|
58
58
|
|
|
@@ -63,32 +63,32 @@ msgstr ""
|
|
|
63
63
|
#: src/ring/cli.py:184 src/ring/cli.py:338 src/ring/commands/doctor.py:31
|
|
64
64
|
#: src/ring/commands/doctor.py:68 src/ring/commands/doctor.py:70
|
|
65
65
|
#: src/ring/commands/doctor.py:72 src/ring/commands/doctor.py:134
|
|
66
|
-
#: src/ring/tui.py:
|
|
66
|
+
#: src/ring/tui.py:229
|
|
67
67
|
msgid "狀態"
|
|
68
68
|
msgstr ""
|
|
69
69
|
|
|
70
|
-
#: src/ring/cli.py:186 src/ring/cli.py:235 src/ring/tui.py:
|
|
70
|
+
#: src/ring/cli.py:186 src/ring/cli.py:235 src/ring/tui.py:231
|
|
71
71
|
msgid "工具"
|
|
72
72
|
msgstr ""
|
|
73
73
|
|
|
74
74
|
#: src/ring/cli.py:187 src/ring/cli.py:235 src/ring/commands/stats.py:60
|
|
75
|
-
#: src/ring/tui.py:
|
|
75
|
+
#: src/ring/tui.py:232
|
|
76
76
|
msgid "專案"
|
|
77
77
|
msgstr ""
|
|
78
78
|
|
|
79
|
-
#: src/ring/cli.py:188 src/ring/cli.py:235 src/ring/tui.py:
|
|
79
|
+
#: src/ring/cli.py:188 src/ring/cli.py:235 src/ring/tui.py:232
|
|
80
80
|
msgid "進度"
|
|
81
81
|
msgstr ""
|
|
82
82
|
|
|
83
|
-
#: src/ring/cli.py:189 src/ring/cli.py:235 src/ring/tui.py:
|
|
83
|
+
#: src/ring/cli.py:189 src/ring/cli.py:235 src/ring/tui.py:232
|
|
84
84
|
msgid "閒置"
|
|
85
85
|
msgstr ""
|
|
86
86
|
|
|
87
|
-
#: src/ring/cli.py:190 src/ring/cli.py:235 src/ring/tui.py:
|
|
87
|
+
#: src/ring/cli.py:190 src/ring/cli.py:235 src/ring/tui.py:232
|
|
88
88
|
msgid "去哪"
|
|
89
89
|
msgstr ""
|
|
90
90
|
|
|
91
|
-
#: src/ring/cli.py:192 src/ring/cli.py:235 src/ring/tui.py:
|
|
91
|
+
#: src/ring/cli.py:192 src/ring/cli.py:235 src/ring/tui.py:232
|
|
92
92
|
msgid "動作"
|
|
93
93
|
msgstr ""
|
|
94
94
|
|
|
@@ -305,41 +305,41 @@ msgstr ""
|
|
|
305
305
|
msgid "重開 Codex session、並在它詢問時「信任」這個 hook,🔴 等你狀態才會精準起來(Codex 不會執行未信任的 hook)。"
|
|
306
306
|
msgstr ""
|
|
307
307
|
|
|
308
|
-
#: src/ring/hook.py:
|
|
308
|
+
#: src/ring/hook.py:438
|
|
309
309
|
#, python-brace-format
|
|
310
310
|
msgid "⚠️ 偵測到其他工具也掛在 {events}:{cmds}"
|
|
311
311
|
msgstr ""
|
|
312
312
|
|
|
313
|
-
#: src/ring/hook.py:
|
|
313
|
+
#: src/ring/hook.py:439
|
|
314
314
|
msgid "它們會跟 RiNG 的通知重複觸發;要完全改用 RiNG,建議移除它們。"
|
|
315
315
|
msgstr ""
|
|
316
316
|
|
|
317
|
-
#: src/ring/hook.py:
|
|
317
|
+
#: src/ring/hook.py:487 src/ring/hook.py:563
|
|
318
318
|
#, python-brace-format
|
|
319
319
|
msgid "⚠️ {path} 不是合法 JSON,先處理它再來。"
|
|
320
320
|
msgstr ""
|
|
321
321
|
|
|
322
|
-
#: src/ring/hook.py:
|
|
322
|
+
#: src/ring/hook.py:525
|
|
323
323
|
#, python-brace-format
|
|
324
324
|
msgid "✅ RiNG hooks 已經裝過了,沒有變更。({path})"
|
|
325
325
|
msgstr ""
|
|
326
326
|
|
|
327
|
-
#: src/ring/hook.py:
|
|
327
|
+
#: src/ring/hook.py:538
|
|
328
328
|
#, python-brace-format
|
|
329
329
|
msgid "✅ 已註冊 RiNG hooks({events})到 {path}"
|
|
330
330
|
msgstr ""
|
|
331
331
|
|
|
332
|
-
#: src/ring/hook.py:
|
|
332
|
+
#: src/ring/hook.py:557
|
|
333
333
|
#, python-brace-format
|
|
334
334
|
msgid "ℹ️ {path} 不存在,沒有可移除的 hook。"
|
|
335
335
|
msgstr ""
|
|
336
336
|
|
|
337
|
-
#: src/ring/hook.py:
|
|
337
|
+
#: src/ring/hook.py:584
|
|
338
338
|
#, python-brace-format
|
|
339
339
|
msgid "ℹ️ 沒有找到 RiNG hook 條目,無需移除。({path})"
|
|
340
340
|
msgstr ""
|
|
341
341
|
|
|
342
|
-
#: src/ring/hook.py:
|
|
342
|
+
#: src/ring/hook.py:588
|
|
343
343
|
#, python-brace-format
|
|
344
344
|
msgid "✅ 已移除 RiNG hooks({events})從 {path}"
|
|
345
345
|
msgstr ""
|
|
@@ -349,103 +349,167 @@ msgstr ""
|
|
|
349
349
|
msgid "⚠️ ring plugin '{name}' 載入失敗:{error}"
|
|
350
350
|
msgstr ""
|
|
351
351
|
|
|
352
|
-
#: src/ring/tui.py:
|
|
352
|
+
#: src/ring/tui.py:91
|
|
353
353
|
#, python-brace-format
|
|
354
354
|
msgid "為 {project} 命名(Enter 存、Esc 取消、清空移除)"
|
|
355
355
|
msgstr ""
|
|
356
356
|
|
|
357
|
-
#: src/ring/tui.py:
|
|
357
|
+
#: src/ring/tui.py:92
|
|
358
358
|
msgid "這個 session 在做什麼…"
|
|
359
359
|
msgstr ""
|
|
360
360
|
|
|
361
|
-
#: src/ring/tui.py:
|
|
361
|
+
#: src/ring/tui.py:136
|
|
362
|
+
#, python-brace-format
|
|
363
|
+
msgid "回覆 {project} 的權限請求"
|
|
364
|
+
msgstr ""
|
|
365
|
+
|
|
366
|
+
#: src/ring/tui.py:141
|
|
367
|
+
msgid "Enter 或數字鍵送出、Esc 取消"
|
|
368
|
+
msgstr ""
|
|
369
|
+
|
|
370
|
+
#: src/ring/tui.py:164
|
|
362
371
|
msgid "離場"
|
|
363
372
|
msgstr ""
|
|
364
373
|
|
|
365
|
-
#: src/ring/tui.py:
|
|
374
|
+
#: src/ring/tui.py:165
|
|
366
375
|
msgid "刷新"
|
|
367
376
|
msgstr ""
|
|
368
377
|
|
|
369
|
-
#: src/ring/tui.py:
|
|
378
|
+
#: src/ring/tui.py:166
|
|
370
379
|
msgid "含已離場"
|
|
371
380
|
msgstr ""
|
|
372
381
|
|
|
373
|
-
#: src/ring/tui.py:
|
|
382
|
+
#: src/ring/tui.py:167
|
|
374
383
|
msgid "命名"
|
|
375
384
|
msgstr ""
|
|
376
385
|
|
|
377
|
-
#: src/ring/tui.py:
|
|
386
|
+
#: src/ring/tui.py:168
|
|
378
387
|
msgid "隱藏"
|
|
379
388
|
msgstr ""
|
|
380
389
|
|
|
381
|
-
#: src/ring/tui.py:
|
|
390
|
+
#: src/ring/tui.py:169
|
|
382
391
|
msgid "最久等待"
|
|
383
392
|
msgstr ""
|
|
384
393
|
|
|
385
|
-
#: src/ring/tui.py:
|
|
394
|
+
#: src/ring/tui.py:170
|
|
395
|
+
msgid "回覆權限"
|
|
396
|
+
msgstr ""
|
|
397
|
+
|
|
398
|
+
#: src/ring/tui.py:171 src/ring/tui.py:172
|
|
386
399
|
msgid "跳過去"
|
|
387
400
|
msgstr ""
|
|
388
401
|
|
|
389
|
-
#: src/ring/tui.py:
|
|
402
|
+
#: src/ring/tui.py:250
|
|
390
403
|
msgid "💡 同專案開了多個 session,裝 hook 跳轉才精準:ring install-hooks"
|
|
391
404
|
msgstr ""
|
|
392
405
|
|
|
393
|
-
#: src/ring/tui.py:
|
|
406
|
+
#: src/ring/tui.py:252
|
|
394
407
|
msgid "↑/↓ 選一列,Enter 或 Space 跳到那個 session 的終端"
|
|
395
408
|
msgstr ""
|
|
396
409
|
|
|
397
|
-
#: src/ring/tui.py:
|
|
410
|
+
#: src/ring/tui.py:288
|
|
398
411
|
#, python-brace-format
|
|
399
412
|
msgid "🔔 {names} 在等你回話"
|
|
400
413
|
msgstr ""
|
|
401
414
|
|
|
402
|
-
#: src/ring/tui.py:
|
|
415
|
+
#: src/ring/tui.py:329
|
|
403
416
|
#, python-brace-format
|
|
404
417
|
msgid "→ 已跳到 {project}(來自通知)"
|
|
405
418
|
msgstr ""
|
|
406
419
|
|
|
407
|
-
#: src/ring/tui.py:
|
|
420
|
+
#: src/ring/tui.py:334
|
|
408
421
|
msgid "那個 session 已不在場"
|
|
409
422
|
msgstr ""
|
|
410
423
|
|
|
411
|
-
#: src/ring/tui.py:
|
|
424
|
+
#: src/ring/tui.py:393
|
|
412
425
|
msgid " ⚠ hook 可能失效:來源檔有更新但沒有 heartbeat"
|
|
413
426
|
msgstr ""
|
|
414
427
|
|
|
415
|
-
#: src/ring/tui.py:
|
|
428
|
+
#: src/ring/tui.py:422 src/ring/tui.py:446 src/ring/tui.py:479
|
|
429
|
+
#: src/ring/tui.py:544
|
|
416
430
|
msgid "(沒有選到 session)"
|
|
417
431
|
msgstr ""
|
|
418
432
|
|
|
419
|
-
#: src/ring/tui.py:
|
|
433
|
+
#: src/ring/tui.py:453
|
|
420
434
|
#, python-brace-format
|
|
421
435
|
msgid "→ {project}({where})"
|
|
422
436
|
msgstr ""
|
|
423
437
|
|
|
424
|
-
#: src/ring/tui.py:
|
|
438
|
+
#: src/ring/tui.py:455
|
|
425
439
|
#, python-brace-format
|
|
426
440
|
msgid "{project}:{msg}"
|
|
427
441
|
msgstr ""
|
|
428
442
|
|
|
429
|
-
#: src/ring/tui.py:
|
|
443
|
+
#: src/ring/tui.py:463
|
|
430
444
|
msgid "(沒有正在等待的 session)"
|
|
431
445
|
msgstr ""
|
|
432
446
|
|
|
433
|
-
#: src/ring/tui.py:
|
|
447
|
+
#: src/ring/tui.py:486
|
|
448
|
+
#, python-brace-format
|
|
449
|
+
msgid "{project}:沒有 tmux 座標,且非 macOS 上的 iTerm2 session,無法就地回覆"
|
|
450
|
+
msgstr ""
|
|
451
|
+
|
|
452
|
+
#: src/ring/tui.py:493
|
|
453
|
+
#, python-brace-format
|
|
454
|
+
msgid "{project}:讀不到 {backend} 畫面"
|
|
455
|
+
msgstr ""
|
|
456
|
+
|
|
457
|
+
#: src/ring/tui.py:497
|
|
458
|
+
#, python-brace-format
|
|
459
|
+
msgid "{project}:畫面上沒有可辨識的權限對話框,未送出任何按鍵"
|
|
460
|
+
msgstr ""
|
|
461
|
+
|
|
462
|
+
#: src/ring/tui.py:526
|
|
463
|
+
#, python-brace-format
|
|
464
|
+
msgid "→ {project}:已回覆權限({option})"
|
|
465
|
+
msgstr ""
|
|
466
|
+
|
|
467
|
+
#: src/ring/tui.py:530
|
|
468
|
+
#, python-brace-format
|
|
469
|
+
msgid "{project}:權限對話框已不在,未送出任何按鍵"
|
|
470
|
+
msgstr ""
|
|
471
|
+
|
|
472
|
+
#: src/ring/tui.py:531
|
|
473
|
+
#, python-brace-format
|
|
474
|
+
msgid "{project}:對話框內容已變化,未送出;請再按一次 p"
|
|
475
|
+
msgstr ""
|
|
476
|
+
|
|
477
|
+
#: src/ring/tui.py:532
|
|
478
|
+
#, python-brace-format
|
|
479
|
+
msgid "{project}:tmux send-keys 失敗,沒能回覆"
|
|
480
|
+
msgstr ""
|
|
481
|
+
|
|
482
|
+
#: src/ring/tui.py:533
|
|
483
|
+
#, python-brace-format
|
|
484
|
+
msgid "{project}:已送出但對話框還在,請跳過去確認"
|
|
485
|
+
msgstr ""
|
|
486
|
+
|
|
487
|
+
#: src/ring/tui.py:535
|
|
488
|
+
#, python-brace-format
|
|
489
|
+
msgid "{project}:對話框已不在,數字落進輸入框,已補 Backspace 清掉"
|
|
490
|
+
msgstr ""
|
|
491
|
+
|
|
492
|
+
#: src/ring/tui.py:537
|
|
493
|
+
#, python-brace-format
|
|
494
|
+
msgid "{project}:已送出但拿不到畫面驗證,請跳過去確認"
|
|
495
|
+
msgstr ""
|
|
496
|
+
|
|
497
|
+
#: src/ring/tui.py:552
|
|
434
498
|
#, python-brace-format
|
|
435
499
|
msgid "再按一次 d 隱藏 {project}(有新活動會自動重新出現)"
|
|
436
500
|
msgstr ""
|
|
437
501
|
|
|
438
|
-
#: src/ring/tui.py:
|
|
502
|
+
#: src/ring/tui.py:557
|
|
439
503
|
#, python-brace-format
|
|
440
504
|
msgid "{project} 是仍在執行的 process,無法隱藏;請跳過去結束它"
|
|
441
505
|
msgstr ""
|
|
442
506
|
|
|
443
|
-
#: src/ring/tui.py:
|
|
507
|
+
#: src/ring/tui.py:565
|
|
444
508
|
#, python-brace-format
|
|
445
509
|
msgid "已隱藏 {project},並清掉 RiNG 狀態;有新活動會自動重新出現"
|
|
446
510
|
msgstr ""
|
|
447
511
|
|
|
448
|
-
#: src/ring/tui.py:
|
|
512
|
+
#: src/ring/tui.py:567
|
|
449
513
|
#, python-brace-format
|
|
450
514
|
msgid "已隱藏 {project}(沒有 RiNG registry 可清;有新活動會自動重新出現)"
|
|
451
515
|
msgstr ""
|