ring-cli 0.14.0__tar.gz → 0.14.1__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.14.0 → ring_cli-0.14.1}/PKG-INFO +1 -1
- {ring_cli-0.14.0 → ring_cli-0.14.1}/pyproject.toml +1 -1
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/focus/__init__.py +29 -8
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/focus/applescript.py +8 -0
- ring_cli-0.14.1/src/ring/focus/iterm2.py +82 -0
- ring_cli-0.14.1/src/ring/focus/trace.py +79 -0
- ring_cli-0.14.1/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/locale/en/LC_MESSAGES/ring.po +3 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/locale/ring.pot +9 -4
- ring_cli-0.14.0/src/ring/focus/iterm2.py +0 -28
- ring_cli-0.14.0/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/LICENSE +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/README.en.md +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/__init__.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/__main__.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/cli.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/__init__.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/_args.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/completion.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/digest.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/doctor.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/focus.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/gc.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/hook.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/quiet.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/commands/stats.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/config.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/focus/base.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/focus/linux_wm.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/focus/neovim.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/focus/terminal.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/focus/tmux.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/gc.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/hook.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/hook_protocol.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/i18n.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/ipc.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/labels.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/__init__.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/base.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/command.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/notify_send.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/ntfy.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/osascript_notifier.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/terminal_notifier.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify/webhook.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/notify_queue.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/osascript.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/payload_log.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/permission.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/plugins.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/question_detect.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/registry.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/sources/__init__.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/sources/base.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/sources/claude_code.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/sources/codex.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/sources/hook_registry.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/sources/local_llm.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/stats.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/transcript.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/tui.py +0 -0
- {ring_cli-0.14.0 → ring_cli-0.14.1}/src/ring/watcher.py +0 -0
|
@@ -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.14.
|
|
5
|
+
version = "0.14.1"
|
|
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"
|
|
@@ -15,6 +15,7 @@ from ring.focus.linux_wm import focuser as _linux_wm
|
|
|
15
15
|
from ring.focus.neovim import focuser as _neovim
|
|
16
16
|
from ring.focus.terminal import focuser as _terminal
|
|
17
17
|
from ring.focus.tmux import focuser as _tmux
|
|
18
|
+
from ring.focus.trace import log_jump
|
|
18
19
|
from ring.i18n import gettext as _
|
|
19
20
|
from ring.registry import Session, Status
|
|
20
21
|
|
|
@@ -53,33 +54,53 @@ def focusers() -> list[Focuser]:
|
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
def jump(session: Session) -> tuple[bool, str]:
|
|
56
|
-
"""依序問每個 focuser,誰先接手就用誰。回傳 (成功?, 訊息)。
|
|
57
|
+
"""依序問每個 focuser,誰先接手就用誰。回傳 (成功?, 訊息)。
|
|
58
|
+
|
|
59
|
+
每次跳轉(不論成敗)都記一行到 focus.jsonl,供事後歸因間歇性的跳不過去(見 ``trace``)。
|
|
60
|
+
"""
|
|
57
61
|
if session.status is Status.ENDED:
|
|
58
62
|
return False, _("已離場的 session 無法跳轉")
|
|
59
63
|
failures: list[str] = []
|
|
60
64
|
preparations: list[str] = []
|
|
65
|
+
attempts: list[tuple[str, str]] = []
|
|
66
|
+
|
|
67
|
+
def _done(ok: bool, msg: str) -> tuple[bool, str]:
|
|
68
|
+
log_jump(
|
|
69
|
+
session_id=session.session_id,
|
|
70
|
+
provider=session.provider,
|
|
71
|
+
tty=session.tty or "",
|
|
72
|
+
tmux_target=session.tmux_target or "",
|
|
73
|
+
attempts=attempts,
|
|
74
|
+
ok=ok,
|
|
75
|
+
msg=msg,
|
|
76
|
+
)
|
|
77
|
+
return ok, msg
|
|
78
|
+
|
|
61
79
|
for focuser in _FOCUSERS:
|
|
62
80
|
result = focuser.try_focus(session)
|
|
63
81
|
if result is None:
|
|
82
|
+
attempts.append((focuser.name, "skip"))
|
|
64
83
|
continue
|
|
65
84
|
ok, msg = result
|
|
66
85
|
if ok:
|
|
86
|
+
attempts.append((focuser.name, "ok"))
|
|
67
87
|
if getattr(focuser, "continue_after_success", False):
|
|
68
88
|
preparations.append(msg)
|
|
69
89
|
continue
|
|
70
90
|
if preparations:
|
|
71
|
-
return True, "; ".join([*preparations, msg])
|
|
72
|
-
return True, msg
|
|
91
|
+
return _done(True, "; ".join([*preparations, msg]))
|
|
92
|
+
return _done(True, msg)
|
|
93
|
+
attempts.append((focuser.name, f"fail: {msg}"))
|
|
73
94
|
failures.append(f"{focuser.name}: {msg}")
|
|
74
95
|
if preparations and failures:
|
|
75
|
-
return False, "; ".join([*preparations, *failures])
|
|
96
|
+
return _done(False, "; ".join([*preparations, *failures]))
|
|
76
97
|
if preparations:
|
|
77
|
-
return True, "; ".join(preparations)
|
|
98
|
+
return _done(True, "; ".join(preparations))
|
|
78
99
|
if failures:
|
|
79
|
-
return False, "; ".join(failures)
|
|
100
|
+
return _done(False, "; ".join(failures))
|
|
80
101
|
if session.tty:
|
|
81
|
-
return False, _("找不到這個終端分頁(可能已關閉;刷新後若仍存在,請裝 hook 取得更精準狀態)")
|
|
82
|
-
return False, _("沒有 focuser 接得住(裝 hook,或一個專案只開一個 session 才測得到 tty)")
|
|
102
|
+
return _done(False, _("找不到這個終端分頁(可能已關閉;刷新後若仍存在,請裝 hook 取得更精準狀態)"))
|
|
103
|
+
return _done(False, _("沒有 focuser 接得住(裝 hook,或一個專案只開一個 session 才測得到 tty)"))
|
|
83
104
|
|
|
84
105
|
|
|
85
106
|
__all__ = ["Focuser", "focusers", "jump", "register_focuser"]
|
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import shutil
|
|
10
10
|
|
|
11
|
+
from ring.i18n import gettext as _
|
|
11
12
|
from ring.osascript import osascript
|
|
12
13
|
from ring.registry import Session
|
|
13
14
|
|
|
@@ -16,6 +17,9 @@ class AppleScriptTTYFocuser:
|
|
|
16
17
|
"""用 session 的 tty 在某個 macOS 終端 app 裡找到對應分頁並聚焦。
|
|
17
18
|
|
|
18
19
|
各 app 提供自己的 AppleScript(用 ``is running`` 守衛,沒在跑的 app 不會被喚醒)。
|
|
20
|
+
|
|
21
|
+
script 的回傳值三選一:``ok``(找到且已在前景)、``notfound``(不歸這個 app 管)、
|
|
22
|
+
``unraised``(找到分頁但視窗沒浮到前景——腳本自己驗過才回這個,見 iterm2 模組)。
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
25
|
def __init__(self, app: str, script: str) -> None:
|
|
@@ -31,4 +35,8 @@ class AppleScriptTTYFocuser:
|
|
|
31
35
|
return True, f"{self.name} {tty}"
|
|
32
36
|
if rc == 0 and out == "notfound":
|
|
33
37
|
return None # 這個 app 沒有這個 tty → 換下一個 focuser
|
|
38
|
+
if rc == 0 and out == "unraised":
|
|
39
|
+
# 分頁在,但視窗沒被帶到前景(多半在別的 Space 或全螢幕)。照實回失敗,
|
|
40
|
+
# 不要讓 TUI 顯示「跳過去了」卻什麼都沒發生。
|
|
41
|
+
return False, _("{app} 找到分頁了,但視窗沒浮到前景(可能在別的 Space 或全螢幕)", app=self.name)
|
|
34
42
|
return False, err or out or f"returncode={rc}"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""iTerm2 focuser(macOS):用 tty 找到對應分頁並帶到前景。
|
|
2
|
+
|
|
3
|
+
跨視窗(不是同一視窗換分頁)跳轉踩過三個坑,腳本的形狀都是為了繞開它們(皆 2026-07-25 實測):
|
|
4
|
+
|
|
5
|
+
1. **位置型 specifier 會在迴圈中途失效**。``repeat with w in windows`` 的迴圈變數其實是
|
|
6
|
+
``item M of every window`` 這種「按位置」的參照;一旦 windows 的順序在掃描途中變動
|
|
7
|
+
(剛跳過一次視窗、使用者手動點了別的視窗都會),下一次取值就噴
|
|
8
|
+
``Invalid index (-1719)``,跳轉整個失敗——這正是「不同視窗有時抓不到」的主因。
|
|
9
|
+
對策:掃描包在 ``try`` 裡、順序被動到就重掃;``select`` 之後不再用掃描時的位置,
|
|
10
|
+
改以 ``current window`` 為根重找一次分頁。
|
|
11
|
+
2. **``select`` 是非同步的**:送出後立刻讀狀態會讀到舊值。所以最後隔 0.1 秒回頭驗
|
|
12
|
+
``current session of current window``,最多等 1 秒。驗不到就回 ``unraised``,讓上層
|
|
13
|
+
照實說「沒帶到前景」(例如視窗在別的 Space),而不是假裝跳成功。
|
|
14
|
+
3. **不能用的兩招**:``set frontmost of w to true``(Terminal.app 那招)在 iTerm2 噴
|
|
15
|
+
AppleEvent handler failed (-10000);``set index of w to 1`` 會主動重排 windows,
|
|
16
|
+
等於自己製造第 1 點的 -1719。
|
|
17
|
+
|
|
18
|
+
縮到 Dock 的視窗 ``select`` 就叫得回來(實測),``miniaturized`` 那段只是保險。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from ring.focus.applescript import AppleScriptTTYFocuser
|
|
24
|
+
|
|
25
|
+
_SCRIPT = """
|
|
26
|
+
if application "iTerm2" is running then
|
|
27
|
+
tell application "iTerm2"
|
|
28
|
+
repeat 3 times
|
|
29
|
+
try
|
|
30
|
+
set wIdx to 0
|
|
31
|
+
set tIdx to 0
|
|
32
|
+
set sIdx to 0
|
|
33
|
+
repeat with wi from 1 to (count of windows)
|
|
34
|
+
repeat with ti from 1 to (count of tabs of window wi)
|
|
35
|
+
repeat with si from 1 to (count of sessions of tab ti of window wi)
|
|
36
|
+
if tty of session si of tab ti of window wi is "{tty}" then
|
|
37
|
+
set wIdx to wi
|
|
38
|
+
set tIdx to ti
|
|
39
|
+
set sIdx to si
|
|
40
|
+
exit repeat
|
|
41
|
+
end if
|
|
42
|
+
end repeat
|
|
43
|
+
if wIdx is not 0 then exit repeat
|
|
44
|
+
end repeat
|
|
45
|
+
if wIdx is not 0 then exit repeat
|
|
46
|
+
end repeat
|
|
47
|
+
if wIdx is 0 then return "notfound"
|
|
48
|
+
activate
|
|
49
|
+
try
|
|
50
|
+
if miniaturized of window wIdx then set miniaturized of window wIdx to false
|
|
51
|
+
end try
|
|
52
|
+
select window wIdx
|
|
53
|
+
tell current window
|
|
54
|
+
repeat with ti2 from 1 to (count of tabs)
|
|
55
|
+
repeat with si2 from 1 to (count of sessions of tab ti2)
|
|
56
|
+
if tty of session si2 of tab ti2 is "{tty}" then
|
|
57
|
+
select tab ti2
|
|
58
|
+
select session si2 of tab ti2
|
|
59
|
+
exit repeat
|
|
60
|
+
end if
|
|
61
|
+
end repeat
|
|
62
|
+
end repeat
|
|
63
|
+
end tell
|
|
64
|
+
repeat 10 times
|
|
65
|
+
delay 0.1
|
|
66
|
+
try
|
|
67
|
+
if tty of current session of current window is "{tty}" then return "ok"
|
|
68
|
+
end try
|
|
69
|
+
end repeat
|
|
70
|
+
return "unraised"
|
|
71
|
+
on error number errNum
|
|
72
|
+
if errNum is -128 then error number -128
|
|
73
|
+
delay 0.2
|
|
74
|
+
end try
|
|
75
|
+
end repeat
|
|
76
|
+
return "unraised"
|
|
77
|
+
end tell
|
|
78
|
+
end if
|
|
79
|
+
return "notfound"
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
focuser = AppleScriptTTYFocuser("iTerm2", _SCRIPT)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""跳轉取證 log——每次 ``jump()`` append 一行到 ``~/.config/ring/focus.jsonl``。
|
|
2
|
+
|
|
3
|
+
為什麼要記:跳轉失敗是間歇性的,而 TUI 的 toast 看過就沒了,事後只剩「昨天有一次跳不
|
|
4
|
+
過去」這種無法歸因的敘述。這份 log 把每個 focuser 當下的回覆存下來,讓下一次重現時能
|
|
5
|
+
直接分辨是哪一種失敗:
|
|
6
|
+
|
|
7
|
+
- 每個 focuser 都回 ``skip``、而 session 有 tty → tty 已失效(分頁關了/registry 記的是舊的)
|
|
8
|
+
- ``tty`` 是空的 → 根本沒抓到 tty,focuser 沒得比對
|
|
9
|
+
- iTerm2 回 ``unraised`` → 分頁找到了但視窗沒浮上來(多半在別的 Space)
|
|
10
|
+
- iTerm2 回 osascript 錯誤 → 權限或 5 秒 timeout
|
|
11
|
+
|
|
12
|
+
跳轉是使用者手動觸發、頻率低(一行約 200 bytes),所以預設就開著、不做開關——要能在
|
|
13
|
+
「下次發生」時就有證據,而不是先想起來去開 debug 旗標。寫入失敗一律安靜吞掉。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
FOCUS_LOG_PATH = Path.home() / ".config" / "ring" / "focus.jsonl"
|
|
23
|
+
|
|
24
|
+
# 一行約 200 bytes,1MB ≈ 5000 次跳轉。超過就砍半保新。
|
|
25
|
+
_MAX_BYTES = 1024 * 1024
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def log_jump(
|
|
29
|
+
*,
|
|
30
|
+
session_id: str,
|
|
31
|
+
provider: str,
|
|
32
|
+
tty: str,
|
|
33
|
+
tmux_target: str,
|
|
34
|
+
attempts: list[tuple[str, str]],
|
|
35
|
+
ok: bool,
|
|
36
|
+
msg: str,
|
|
37
|
+
path: Path | None = None,
|
|
38
|
+
now: float | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""append 一次跳轉的完整經過;失敗安靜吞掉(呼叫端不需要自己包 try)。
|
|
41
|
+
|
|
42
|
+
:param attempts: ``[(focuser 名稱, 結果), …]``,結果為 ``skip`` / ``ok`` / ``fail: …``。
|
|
43
|
+
"""
|
|
44
|
+
p = path or FOCUS_LOG_PATH
|
|
45
|
+
try:
|
|
46
|
+
line = json.dumps(
|
|
47
|
+
{
|
|
48
|
+
"ts": now if now is not None else time.time(),
|
|
49
|
+
"session_id": session_id,
|
|
50
|
+
"provider": provider,
|
|
51
|
+
"tty": tty,
|
|
52
|
+
"tmux_target": tmux_target,
|
|
53
|
+
"attempts": [{"focuser": name, "result": result} for name, result in attempts],
|
|
54
|
+
"ok": ok,
|
|
55
|
+
"msg": msg,
|
|
56
|
+
},
|
|
57
|
+
ensure_ascii=False,
|
|
58
|
+
default=str,
|
|
59
|
+
)
|
|
60
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
_trim_if_oversized(p)
|
|
62
|
+
with p.open("a", encoding="utf-8") as f:
|
|
63
|
+
f.write(line + "\n")
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _trim_if_oversized(p: Path) -> None:
|
|
69
|
+
"""log 超過 ``_MAX_BYTES`` 時砍半保新,避免無上限成長。失敗安靜放棄(不擋 append)。"""
|
|
70
|
+
try:
|
|
71
|
+
if p.stat().st_size <= _MAX_BYTES:
|
|
72
|
+
return
|
|
73
|
+
lines = p.read_text(encoding="utf-8").splitlines()
|
|
74
|
+
keep = lines[len(lines) // 2 :]
|
|
75
|
+
tmp = p.with_suffix(".jsonl.tmp")
|
|
76
|
+
tmp.write_text("\n".join(keep) + "\n", encoding="utf-8")
|
|
77
|
+
tmp.replace(p) # atomic
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
Binary file
|
|
@@ -155,6 +155,9 @@ msgstr "Hidden {project} (no RiNG registry to clear; it reappears once there's n
|
|
|
155
155
|
msgid "已離場的 session 無法跳轉"
|
|
156
156
|
msgstr "ended sessions cannot be focused"
|
|
157
157
|
|
|
158
|
+
msgid "{app} 找到分頁了,但視窗沒浮到前景(可能在別的 Space 或全螢幕)"
|
|
159
|
+
msgstr "{app} found the tab but could not bring its window to the front (it may be on another Space or in full screen)"
|
|
160
|
+
|
|
158
161
|
msgid "找不到這個終端分頁(可能已關閉;刷新後若仍存在,請裝 hook 取得更精準狀態)"
|
|
159
162
|
msgstr "terminal tab not found (it may have been closed; if it still appears after refresh, install hooks for more precise state)"
|
|
160
163
|
|
|
@@ -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-25 10:42+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"
|
|
@@ -923,18 +923,23 @@ msgstr ""
|
|
|
923
923
|
msgid "⚠️ 其中 {n} 段還在 🔴 等你(計到現在為止)。"
|
|
924
924
|
msgstr ""
|
|
925
925
|
|
|
926
|
-
#: src/ring/focus/__init__.py:
|
|
926
|
+
#: src/ring/focus/__init__.py:62
|
|
927
927
|
msgid "已離場的 session 無法跳轉"
|
|
928
928
|
msgstr ""
|
|
929
929
|
|
|
930
|
-
#: src/ring/focus/__init__.py:
|
|
930
|
+
#: src/ring/focus/__init__.py:102
|
|
931
931
|
msgid "找不到這個終端分頁(可能已關閉;刷新後若仍存在,請裝 hook 取得更精準狀態)"
|
|
932
932
|
msgstr ""
|
|
933
933
|
|
|
934
|
-
#: src/ring/focus/__init__.py:
|
|
934
|
+
#: src/ring/focus/__init__.py:103
|
|
935
935
|
msgid "沒有 focuser 接得住(裝 hook,或一個專案只開一個 session 才測得到 tty)"
|
|
936
936
|
msgstr ""
|
|
937
937
|
|
|
938
|
+
#: src/ring/focus/applescript.py:41
|
|
939
|
+
#, python-brace-format
|
|
940
|
+
msgid "{app} 找到分頁了,但視窗沒浮到前景(可能在別的 Space 或全螢幕)"
|
|
941
|
+
msgstr ""
|
|
942
|
+
|
|
938
943
|
#: src/ring/notify/__init__.py:165
|
|
939
944
|
#, python-brace-format
|
|
940
945
|
msgid "另有 {n} 個 session 在等你"
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"""iTerm2 focuser(macOS):用 tty 找到對應分頁並帶到前景。"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from ring.focus.applescript import AppleScriptTTYFocuser
|
|
6
|
-
|
|
7
|
-
_SCRIPT = """
|
|
8
|
-
if application "iTerm2" is running then
|
|
9
|
-
tell application "iTerm2"
|
|
10
|
-
repeat with w in windows
|
|
11
|
-
repeat with t in tabs of w
|
|
12
|
-
repeat with s in sessions of t
|
|
13
|
-
if tty of s is "{tty}" then
|
|
14
|
-
select w
|
|
15
|
-
select t
|
|
16
|
-
select s
|
|
17
|
-
activate
|
|
18
|
-
return "ok"
|
|
19
|
-
end if
|
|
20
|
-
end repeat
|
|
21
|
-
end repeat
|
|
22
|
-
end repeat
|
|
23
|
-
end tell
|
|
24
|
-
end if
|
|
25
|
-
return "notfound"
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
focuser = AppleScriptTTYFocuser("iTerm2", _SCRIPT)
|
|
Binary file
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|