ring-cli 0.6.0__tar.gz → 0.7.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.
Files changed (53) hide show
  1. {ring_cli-0.6.0 → ring_cli-0.7.0}/PKG-INFO +3 -2
  2. {ring_cli-0.6.0 → ring_cli-0.7.0}/README.en.md +2 -1
  3. {ring_cli-0.6.0 → ring_cli-0.7.0}/pyproject.toml +1 -1
  4. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/doctor.py +4 -1
  5. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/config.py +1 -1
  6. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/__init__.py +16 -4
  7. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/base.py +4 -1
  8. ring_cli-0.7.0/src/ring/focus/neovim.py +184 -0
  9. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
  10. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/locale/en/LC_MESSAGES/ring.po +3 -0
  11. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/locale/ring.pot +23 -19
  12. {ring_cli-0.6.0 → ring_cli-0.7.0}/LICENSE +0 -0
  13. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/__init__.py +0 -0
  14. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/__main__.py +0 -0
  15. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/cli.py +0 -0
  16. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/__init__.py +0 -0
  17. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/_args.py +0 -0
  18. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/completion.py +0 -0
  19. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/focus.py +0 -0
  20. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/gc.py +0 -0
  21. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/hook.py +0 -0
  22. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/stats.py +0 -0
  23. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/applescript.py +0 -0
  24. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/iterm2.py +0 -0
  25. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/linux_wm.py +0 -0
  26. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/terminal.py +0 -0
  27. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/tmux.py +0 -0
  28. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/gc.py +0 -0
  29. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/hook.py +0 -0
  30. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/hook_protocol.py +0 -0
  31. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/i18n.py +0 -0
  32. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/ipc.py +0 -0
  33. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/labels.py +0 -0
  34. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/__init__.py +0 -0
  35. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/base.py +0 -0
  36. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/command.py +0 -0
  37. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/notify_send.py +0 -0
  38. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/ntfy.py +0 -0
  39. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/osascript_notifier.py +0 -0
  40. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/terminal_notifier.py +0 -0
  41. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/webhook.py +0 -0
  42. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/osascript.py +0 -0
  43. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/plugins.py +0 -0
  44. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/registry.py +0 -0
  45. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/__init__.py +0 -0
  46. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/base.py +0 -0
  47. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/claude_code.py +0 -0
  48. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/codex.py +0 -0
  49. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/hook_registry.py +0 -0
  50. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/stats.py +0 -0
  51. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/transcript.py +0 -0
  52. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/tui.py +0 -0
  53. {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/watcher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ring-cli
3
- Version: 0.6.0
3
+ Version: 0.7.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
@@ -162,6 +162,7 @@ Completes subcommands, flags, and `config set` keys.
162
162
  Select a session and press `Enter`. RiNG focuses the terminal where that session is running.
163
163
 
164
164
  - **tmux**: switches directly to the pane via `switch-client`.
165
+ - **Neovim `:terminal`**: uses the terminal job's inherited `$NVIM` server socket to switch to the exact buffer, then lets the outer tmux or terminal focuser raise its pane/window.
165
166
  - **iTerm2 / Terminal.app** on macOS: uses the session `tty` and AppleScript to focus the matching tab. The first run may ask for macOS Automation permission.
166
167
  - **Linux X11 window** (`wmctrl`, best-effort fallback): for Linux without tmux — walks from the `tty` up to the terminal window that owns it and raises it via `wmctrl`. **Limits**: X11 only (usually a no-op on Wayland), raises the whole window but cannot pick the tab, and gnome-terminal's client/server model may not match. Requires `apt install wmctrl`.
167
168
 
@@ -371,7 +372,7 @@ notify_repeat_max = 3
371
372
  notify_ntfy_url = "" # full ntfy topic URL enables phone push (e.g. https://ntfy.sh/my-topic)
372
373
  notify_webhook_url = "" # URL enables the generic webhook backend (JSON POST)
373
374
  notify_also = [] # extra backends fired besides the primary, e.g. ["ntfy"]
374
- focusers = ["tmux", "iTerm2", "Terminal", "linux-wm"]
375
+ focusers = ["Neovim", "tmux", "iTerm2", "Terminal", "linux-wm"]
375
376
  plugins = [] # external plugin modules imported at startup (see Extending)
376
377
 
377
378
  [colors]
@@ -137,6 +137,7 @@ Completes subcommands, flags, and `config set` keys.
137
137
  Select a session and press `Enter`. RiNG focuses the terminal where that session is running.
138
138
 
139
139
  - **tmux**: switches directly to the pane via `switch-client`.
140
+ - **Neovim `:terminal`**: uses the terminal job's inherited `$NVIM` server socket to switch to the exact buffer, then lets the outer tmux or terminal focuser raise its pane/window.
140
141
  - **iTerm2 / Terminal.app** on macOS: uses the session `tty` and AppleScript to focus the matching tab. The first run may ask for macOS Automation permission.
141
142
  - **Linux X11 window** (`wmctrl`, best-effort fallback): for Linux without tmux — walks from the `tty` up to the terminal window that owns it and raises it via `wmctrl`. **Limits**: X11 only (usually a no-op on Wayland), raises the whole window but cannot pick the tab, and gnome-terminal's client/server model may not match. Requires `apt install wmctrl`.
142
143
 
@@ -346,7 +347,7 @@ notify_repeat_max = 3
346
347
  notify_ntfy_url = "" # full ntfy topic URL enables phone push (e.g. https://ntfy.sh/my-topic)
347
348
  notify_webhook_url = "" # URL enables the generic webhook backend (JSON POST)
348
349
  notify_also = [] # extra backends fired besides the primary, e.g. ["ntfy"]
349
- focusers = ["tmux", "iTerm2", "Terminal", "linux-wm"]
350
+ focusers = ["Neovim", "tmux", "iTerm2", "Terminal", "linux-wm"]
350
351
  plugins = [] # external plugin modules imported at startup (see Extending)
351
352
 
352
353
  [colors]
@@ -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.6.0"
5
+ version = "0.7.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"
@@ -87,7 +87,10 @@ def run_doctor(args: list[str]) -> int:
87
87
  width_f = max(len(f.name) for f in focuser_list) if focuser_list else 10
88
88
  for f in focuser_list:
89
89
  name_lower = f.name.lower()
90
- if name_lower == "tmux":
90
+ if name_lower == "neovim":
91
+ avail = shutil.which("nvim") is not None
92
+ avail_str = _("可用") if avail else _("不可用(nvim 不在 PATH)")
93
+ elif name_lower == "tmux":
91
94
  avail = shutil.which("tmux") is not None
92
95
  avail_str = _("可用") if avail else _("不可用(tmux 不在 PATH)")
93
96
  elif shutil.which("osascript") is None:
@@ -23,7 +23,7 @@
23
23
  notify_ntfy_url = "https://ntfy.sh/my-topic" # 設了才啟用 ntfy 後端(推到手機)
24
24
  notify_webhook_url = "https://example.com/hook" # 設了才啟用 webhook 後端(JSON POST)
25
25
  notify_also = ["ntfy"] # 主後端之外「加發」的後端(例如桌面通知+手機各一份)
26
- focusers = ["tmux", "iTerm2", "Terminal", "linux-wm"] # 跳轉嘗試順序;省略=內建預設
26
+ focusers = ["Neovim", "tmux", "iTerm2", "Terminal", "linux-wm"] # 跳轉嘗試順序;省略=內建預設
27
27
  plugins = ["my_ring_plugin"] # 啟動時 import 的外部 plugin 模組(自行 register_*)
28
28
  """
29
29
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  每個終端是一個 ``Focuser``(見 ``base.Focuser``):core 不認識任何具體終端,
4
4
  只依序問每個 focuser「這個 session 歸不歸你管」。要支援新終端=寫一個模組、
5
- ``register_focuser()`` 註冊,core 零改動。內建:tmux / iTerm2 / Terminal.app /
6
- Linux X11 視窗(wmctrl,best-effort fallback)。
5
+ ``register_focuser()`` 註冊,core 零改動。內建:Neovim terminal / tmux / iTerm2 /
6
+ Terminal.app / Linux X11 視窗(wmctrl,best-effort fallback)。
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -12,14 +12,16 @@ from ring.config import get_config
12
12
  from ring.focus.base import Focuser
13
13
  from ring.focus.iterm2 import focuser as _iterm2
14
14
  from ring.focus.linux_wm import focuser as _linux_wm
15
+ from ring.focus.neovim import focuser as _neovim
15
16
  from ring.focus.terminal import focuser as _terminal
16
17
  from ring.focus.tmux import focuser as _tmux
17
18
  from ring.i18n import gettext as _
18
19
  from ring.registry import Session, Status
19
20
 
20
- # 內建 focuser。順序可由 config 的 `focusers` 覆寫。tmux 跨平台、最快,排最前;
21
- # macOS app 在非 macOS 會自己 return None;linux-wm 殿後當 X11 fallback。
21
+ # 內建 focuser。順序可由 config 的 `focusers` 覆寫。Neovim 先切內層 terminal buffer,
22
+ # 再由 tmux / macOS app / linux-wm 把外層帶到前景。
22
23
  _BUILTIN: dict[str, Focuser] = {
24
+ "Neovim": _neovim,
23
25
  "tmux": _tmux,
24
26
  "iTerm2": _iterm2,
25
27
  "Terminal": _terminal,
@@ -55,14 +57,24 @@ def jump(session: Session) -> tuple[bool, str]:
55
57
  if session.status is Status.ENDED:
56
58
  return False, _("已離場的 session 無法跳轉")
57
59
  failures: list[str] = []
60
+ preparations: list[str] = []
58
61
  for focuser in _FOCUSERS:
59
62
  result = focuser.try_focus(session)
60
63
  if result is None:
61
64
  continue
62
65
  ok, msg = result
63
66
  if ok:
67
+ if getattr(focuser, "continue_after_success", False):
68
+ preparations.append(msg)
69
+ continue
70
+ if preparations:
71
+ return True, "; ".join([*preparations, msg])
64
72
  return True, msg
65
73
  failures.append(f"{focuser.name}: {msg}")
74
+ if preparations and failures:
75
+ return False, "; ".join([*preparations, *failures])
76
+ if preparations:
77
+ return True, "; ".join(preparations)
66
78
  if failures:
67
79
  return False, "; ".join(failures)
68
80
  if session.tty:
@@ -3,7 +3,10 @@
3
3
  ``try_focus`` 的回傳語意:
4
4
  None → 不歸我管(dispatcher 換下一個)
5
5
  (True, msg) → 我接手且成功
6
- (False, err) → 我接手但失敗(回報原因,不再往下試)
6
+ (False, err) → 我接手但失敗(記下原因,再試其他 focuser)
7
+
8
+ 一般 focuser 成功後 dispatcher 就停止;需要先處理內層、再交給外層 focuser 的實作可設
9
+ ``continue_after_success = True``(例如 Neovim 先切 terminal buffer,再讓 tmux 聚焦 pane)。
7
10
  """
8
11
 
9
12
  from __future__ import annotations
@@ -0,0 +1,184 @@
1
+ """Neovim terminal focuser:切回承載 session 的 ``:terminal`` buffer。
2
+
3
+ Neovim 的 terminal job 會繼承 ``NVIM``(server socket)環境變數。從 session 的 tty
4
+ 找到該 job,再沿 parent process chain 找到 owning nvim,就能透過 remote API 精準切換
5
+ buffer。這個 focuser 是「前置聚焦」:切完內層 buffer 後,dispatcher 仍會繼續讓 tmux /
6
+ iTerm2 / Terminal.app 把外層 pane 或視窗帶到前景。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ import subprocess
15
+ import sys
16
+ from pathlib import Path
17
+ from shutil import which
18
+
19
+ from ring.registry import Session
20
+
21
+ _NVIM_NAMES = {"nvim", "nvim-bin"}
22
+ _NVIM_ENV_RE = re.compile(r"(?:^|\s)NVIM=([^\s]+)")
23
+
24
+
25
+ def _run(cmd: list[str]) -> subprocess.CompletedProcess[str] | None:
26
+ try:
27
+ return subprocess.run(cmd, capture_output=True, text=True, timeout=3)
28
+ except (OSError, subprocess.SubprocessError):
29
+ return None
30
+
31
+
32
+ def _tty_pids(tty: str) -> list[int]:
33
+ result = _run(["ps", "-o", "pid=", "-t", tty.removeprefix("/dev/")])
34
+ if result is None or result.returncode != 0:
35
+ return []
36
+ pids: list[int] = []
37
+ for value in result.stdout.split():
38
+ try:
39
+ pids.append(int(value))
40
+ except ValueError:
41
+ continue
42
+ return pids
43
+
44
+
45
+ def _process_table() -> tuple[dict[int, int], dict[int, str]]:
46
+ result = _run(["ps", "-eo", "pid=,ppid=,comm="])
47
+ if result is None or result.returncode != 0:
48
+ return {}, {}
49
+ parents: dict[int, int] = {}
50
+ commands: dict[int, str] = {}
51
+ for line in result.stdout.splitlines():
52
+ parts = line.split(None, 2)
53
+ if len(parts) != 3:
54
+ continue
55
+ try:
56
+ pid, ppid = int(parts[0]), int(parts[1])
57
+ except ValueError:
58
+ continue
59
+ parents[pid] = ppid
60
+ commands[pid] = os.path.basename(parts[2])
61
+ return parents, commands
62
+
63
+
64
+ def _ancestors(pid: int, parents: dict[int, int]) -> list[int]:
65
+ chain: list[int] = []
66
+ seen: set[int] = set()
67
+ current = pid
68
+ while current > 1 and current not in seen:
69
+ seen.add(current)
70
+ chain.append(current)
71
+ current = parents.get(current, 0)
72
+ return chain
73
+
74
+
75
+ def _nvim_address(pid: int) -> str:
76
+ """讀 process 繼承到的 ``NVIM``;Linux 優先用不會混入 argv 的 procfs。"""
77
+ if sys.platform.startswith("linux"):
78
+ try:
79
+ entries = Path(f"/proc/{pid}/environ").read_bytes().split(b"\0")
80
+ except OSError:
81
+ entries = []
82
+ for entry in entries:
83
+ if entry.startswith(b"NVIM="):
84
+ return entry.removeprefix(b"NVIM=").decode(errors="surrogateescape")
85
+
86
+ result = _run(["ps", "eww", "-p", str(pid), "-o", "command="])
87
+ if result is None or result.returncode != 0:
88
+ return ""
89
+ match = _NVIM_ENV_RE.search(result.stdout)
90
+ return match.group(1) if match else ""
91
+
92
+
93
+ def _pid_tty(pid: int) -> str:
94
+ result = _run(["ps", "-o", "tty=", "-p", str(pid)])
95
+ if result is None or result.returncode != 0:
96
+ return ""
97
+ tty = result.stdout.strip()
98
+ if not tty or tty in {"?", "??"}:
99
+ return ""
100
+ return tty if tty.startswith("/dev/") else f"/dev/{tty}"
101
+
102
+
103
+ def _find_neovim(tty: str) -> tuple[str, int] | None:
104
+ parents, commands = _process_table()
105
+ for pid in _tty_pids(tty):
106
+ address = _nvim_address(pid)
107
+ if not address:
108
+ continue
109
+ for ancestor in _ancestors(pid, parents):
110
+ if commands.get(ancestor) in _NVIM_NAMES:
111
+ return address, ancestor
112
+ return None
113
+
114
+
115
+ def _remote_expr(tty: str) -> str:
116
+ """建立在 server 端找 terminal job tty、切 tab/window/buffer 的 Lua expression。"""
117
+ target = json.dumps(tty)
118
+ lua = f"""(function()
119
+ local target = {target}
120
+ local function job_tty(pid)
121
+ local value = vim.trim(vim.fn.system({{'ps', '-o', 'tty=', '-p', tostring(pid)}}))
122
+ if value == '' or value == '?' or value == '??' then return '' end
123
+ if vim.startswith(value, '/dev/') then return value end
124
+ return '/dev/' .. value
125
+ end
126
+ for _, buf in ipairs(vim.api.nvim_list_bufs()) do
127
+ local ok, job = pcall(vim.api.nvim_buf_get_var, buf, 'terminal_job_id')
128
+ -- 已死的 :terminal buffer([Process exited])還留著 terminal_job_id,但 channel
129
+ -- 已關,jobpid 會丟 E900——pcall 跳過它,才輪得到後面活著的 buffer。
130
+ local ok_pid, pid = pcall(vim.fn.jobpid, job)
131
+ if ok and ok_pid and job_tty(pid) == target then
132
+ for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
133
+ for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
134
+ if vim.api.nvim_win_get_buf(win) == buf then
135
+ vim.api.nvim_set_current_tabpage(tab)
136
+ vim.api.nvim_set_current_win(win)
137
+ vim.cmd('startinsert')
138
+ return buf
139
+ end
140
+ end
141
+ end
142
+ vim.api.nvim_set_current_buf(buf)
143
+ vim.cmd('startinsert')
144
+ return buf
145
+ end
146
+ end
147
+ return 0
148
+ end)()"""
149
+ return f"luaeval({json.dumps(lua)})"
150
+
151
+
152
+ class NeovimFocuser:
153
+ name = "Neovim"
154
+ continue_after_success = True
155
+
156
+ def try_focus(self, session: Session) -> tuple[bool, str] | None:
157
+ tty = session.tty
158
+ nvim = which("nvim")
159
+ if not tty or not nvim:
160
+ return None
161
+ found = _find_neovim(tty)
162
+ if found is None:
163
+ return None
164
+ address, nvim_pid = found
165
+ result = _run([nvim, "--server", address, "--remote-expr", _remote_expr(tty)])
166
+ if result is None:
167
+ return False, "nvim remote request failed"
168
+ if result.returncode != 0:
169
+ return False, result.stderr.strip() or "nvim remote request failed"
170
+ try:
171
+ buffer = int(result.stdout.strip())
172
+ except ValueError:
173
+ buffer = 0
174
+ if buffer <= 0:
175
+ return False, f"terminal buffer for {tty} not found"
176
+
177
+ # 後續外層 focuser 要比對 nvim 自己所在的 tty,而不是內層 :terminal job 的 tty。
178
+ outer_tty = _pid_tty(nvim_pid)
179
+ if outer_tty:
180
+ session.tty = outer_tty
181
+ return True, f"Neovim buffer {buffer}"
182
+
183
+
184
+ focuser = NeovimFocuser()
@@ -382,6 +382,9 @@ msgstr "all unavailable"
382
382
  msgid "聚焦終端(focuser)"
383
383
  msgstr "Terminal Focuser"
384
384
 
385
+ msgid "不可用(nvim 不在 PATH)"
386
+ msgstr "unavailable (nvim not in PATH)"
387
+
385
388
  msgid "不可用(tmux 不在 PATH)"
386
389
  msgstr "unavailable (tmux not in PATH)"
387
390
 
@@ -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-03 00:39+0800\n"
11
+ "POT-Creation-Date: 2026-07-03 08:44+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"
@@ -61,7 +61,7 @@ msgid "(場館目前沒人上台)"
61
61
  msgstr ""
62
62
 
63
63
  #: src/ring/cli.py:178 src/ring/cli.py:326 src/ring/commands/doctor.py:31
64
- #: src/ring/commands/doctor.py:119 src/ring/tui.py:163
64
+ #: src/ring/commands/doctor.py:122 src/ring/tui.py:163
65
65
  msgid "狀態"
66
66
  msgstr ""
67
67
 
@@ -98,15 +98,15 @@ msgstr ""
98
98
  msgid "RiNG 設定檔"
99
99
  msgstr ""
100
100
 
101
- #: src/ring/cli.py:325 src/ring/commands/doctor.py:118
101
+ #: src/ring/cli.py:325 src/ring/commands/doctor.py:121
102
102
  msgid "路徑"
103
103
  msgstr ""
104
104
 
105
- #: src/ring/cli.py:326 src/ring/commands/doctor.py:119
105
+ #: src/ring/cli.py:326 src/ring/commands/doctor.py:122
106
106
  msgid "已存在"
107
107
  msgstr ""
108
108
 
109
- #: src/ring/cli.py:326 src/ring/commands/doctor.py:119
109
+ #: src/ring/cli.py:326 src/ring/commands/doctor.py:122
110
110
  msgid "不存在(全部用內建預設)"
111
111
  msgstr ""
112
112
 
@@ -434,7 +434,7 @@ msgstr ""
434
434
  msgid "偵測到 {n} 個 session"
435
435
  msgstr ""
436
436
 
437
- #: src/ring/commands/doctor.py:45 src/ring/commands/doctor.py:113
437
+ #: src/ring/commands/doctor.py:45 src/ring/commands/doctor.py:116
438
438
  msgid "偵測失敗"
439
439
  msgstr ""
440
440
 
@@ -463,7 +463,7 @@ msgid "目前設定"
463
463
  msgstr ""
464
464
 
465
465
  #: src/ring/commands/doctor.py:68 src/ring/commands/doctor.py:92
466
- #: src/ring/commands/doctor.py:99
466
+ #: src/ring/commands/doctor.py:95 src/ring/commands/doctor.py:102
467
467
  msgid "可用"
468
468
  msgstr ""
469
469
 
@@ -500,40 +500,44 @@ msgid "聚焦終端(focuser)"
500
500
  msgstr ""
501
501
 
502
502
  #: src/ring/commands/doctor.py:92
503
+ msgid "不可用(nvim 不在 PATH)"
504
+ msgstr ""
505
+
506
+ #: src/ring/commands/doctor.py:95
503
507
  msgid "不可用(tmux 不在 PATH)"
504
508
  msgstr ""
505
509
 
506
- #: src/ring/commands/doctor.py:94
510
+ #: src/ring/commands/doctor.py:97
507
511
  msgid "不可用(osascript 不在 PATH)"
508
512
  msgstr ""
509
513
 
510
- #: src/ring/commands/doctor.py:99 src/ring/commands/doctor.py:101
514
+ #: src/ring/commands/doctor.py:102 src/ring/commands/doctor.py:104
511
515
  msgid "不可用(app 沒在跑)"
512
516
  msgstr ""
513
517
 
514
- #: src/ring/commands/doctor.py:105
518
+ #: src/ring/commands/doctor.py:108
515
519
  msgid "維護"
516
520
  msgstr ""
517
521
 
518
- #: src/ring/commands/doctor.py:109 src/ring/commands/doctor.py:111
519
- #: src/ring/commands/doctor.py:113
522
+ #: src/ring/commands/doctor.py:112 src/ring/commands/doctor.py:114
523
+ #: src/ring/commands/doctor.py:116
520
524
  msgid "可清理"
521
525
  msgstr ""
522
526
 
523
- #: src/ring/commands/doctor.py:109
527
+ #: src/ring/commands/doctor.py:112
524
528
  #, python-brace-format
525
529
  msgid "{n} 個 RiNG stale 狀態檔(執行 ring gc --dry-run 預覽)"
526
530
  msgstr ""
527
531
 
528
- #: src/ring/commands/doctor.py:111
532
+ #: src/ring/commands/doctor.py:114
529
533
  msgid "沒有 RiNG stale 狀態檔"
530
534
  msgstr ""
531
535
 
532
- #: src/ring/commands/doctor.py:116
536
+ #: src/ring/commands/doctor.py:119
533
537
  msgid "設定檔"
534
538
  msgstr ""
535
539
 
536
- #: src/ring/commands/doctor.py:120
540
+ #: src/ring/commands/doctor.py:123
537
541
  msgid "完整生效值請看 `ring config`。"
538
542
  msgstr ""
539
543
 
@@ -618,15 +622,15 @@ msgstr ""
618
622
  msgid "⚠️ 其中 {n} 段還在 🔴 等你(計到現在為止)。"
619
623
  msgstr ""
620
624
 
621
- #: src/ring/focus/__init__.py:56
625
+ #: src/ring/focus/__init__.py:58
622
626
  msgid "已離場的 session 無法跳轉"
623
627
  msgstr ""
624
628
 
625
- #: src/ring/focus/__init__.py:69
629
+ #: src/ring/focus/__init__.py:81
626
630
  msgid "找不到這個終端分頁(可能已關閉;刷新後若仍存在,請裝 hook 取得更精準狀態)"
627
631
  msgstr ""
628
632
 
629
- #: src/ring/focus/__init__.py:70
633
+ #: src/ring/focus/__init__.py:82
630
634
  msgid "沒有 focuser 接得住(裝 hook,或一個專案只開一個 session 才測得到 tty)"
631
635
  msgstr ""
632
636
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes