ring-cli 0.2.0__py3-none-any.whl
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/__init__.py +12 -0
- ring/__main__.py +6 -0
- ring/cli.py +685 -0
- ring/config.py +276 -0
- ring/focus/__init__.py +65 -0
- ring/focus/applescript.py +34 -0
- ring/focus/base.py +19 -0
- ring/focus/iterm2.py +28 -0
- ring/focus/terminal.py +25 -0
- ring/focus/tmux.py +38 -0
- ring/hook.py +482 -0
- ring/hook_protocol.py +264 -0
- ring/i18n.py +52 -0
- ring/ipc.py +220 -0
- ring/labels.py +50 -0
- ring/locale/en/LC_MESSAGES/ring.mo +0 -0
- ring/locale/en/LC_MESSAGES/ring.po +395 -0
- ring/locale/ring.pot +484 -0
- ring/notify/__init__.py +120 -0
- ring/notify/base.py +37 -0
- ring/notify/command.py +18 -0
- ring/notify/notify_send.py +30 -0
- ring/notify/osascript_notifier.py +32 -0
- ring/notify/terminal_notifier.py +64 -0
- ring/osascript.py +17 -0
- ring/registry.py +851 -0
- ring/sources/__init__.py +94 -0
- ring/sources/base.py +17 -0
- ring/sources/claude_code.py +23 -0
- ring/sources/codex.py +16 -0
- ring/sources/hook_registry.py +16 -0
- ring/tui.py +360 -0
- ring/watcher.py +107 -0
- ring_cli-0.2.0.dist-info/METADATA +334 -0
- ring_cli-0.2.0.dist-info/RECORD +38 -0
- ring_cli-0.2.0.dist-info/WHEEL +4 -0
- ring_cli-0.2.0.dist-info/entry_points.txt +3 -0
- ring_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
ring/locale/ring.pot
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Translations template for PROJECT.
|
|
2
|
+
# Copyright (C) 2026 ORGANIZATION
|
|
3
|
+
# This file is distributed under the same license as the PROJECT project.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2026.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PROJECT VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
11
|
+
"POT-Creation-Date: 2026-06-25 10:35+0800\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"MIME-Version: 1.0\n"
|
|
16
|
+
"Content-Type: text/plain; charset=utf-8\n"
|
|
17
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
18
|
+
"Generated-By: Babel 2.18.0\n"
|
|
19
|
+
|
|
20
|
+
#: src/ring/cli.py:117
|
|
21
|
+
msgid "等你"
|
|
22
|
+
msgstr ""
|
|
23
|
+
|
|
24
|
+
#: src/ring/cli.py:118
|
|
25
|
+
msgid "工作中"
|
|
26
|
+
msgstr ""
|
|
27
|
+
|
|
28
|
+
#: src/ring/cli.py:119
|
|
29
|
+
msgid "跑完停著"
|
|
30
|
+
msgstr ""
|
|
31
|
+
|
|
32
|
+
#: src/ring/cli.py:120
|
|
33
|
+
msgid "已離場"
|
|
34
|
+
msgstr ""
|
|
35
|
+
|
|
36
|
+
#: src/ring/cli.py:142
|
|
37
|
+
#, python-brace-format
|
|
38
|
+
msgid "{n} 個 session 在場"
|
|
39
|
+
msgid_plural "{n} 個 session 在場"
|
|
40
|
+
msgstr[0] ""
|
|
41
|
+
msgstr[1] ""
|
|
42
|
+
|
|
43
|
+
#: src/ring/cli.py:143
|
|
44
|
+
#, python-brace-format
|
|
45
|
+
msgid "{n} 個 agent process 跑著"
|
|
46
|
+
msgid_plural "{n} 個 agent process 跑著"
|
|
47
|
+
msgstr[0] ""
|
|
48
|
+
msgstr[1] ""
|
|
49
|
+
|
|
50
|
+
#: src/ring/cli.py:144
|
|
51
|
+
#, python-brace-format
|
|
52
|
+
msgid "🎤 RiNG — {sess} · {proc}"
|
|
53
|
+
msgstr ""
|
|
54
|
+
|
|
55
|
+
#: src/ring/cli.py:149 src/ring/cli.py:197 src/ring/tui.py:204
|
|
56
|
+
msgid "圖例"
|
|
57
|
+
msgstr ""
|
|
58
|
+
|
|
59
|
+
#: src/ring/cli.py:161 src/ring/cli.py:199
|
|
60
|
+
msgid "(場館目前沒人上台)"
|
|
61
|
+
msgstr ""
|
|
62
|
+
|
|
63
|
+
#: src/ring/cli.py:165 src/ring/cli.py:270 src/ring/cli.py:368
|
|
64
|
+
#: src/ring/cli.py:470 src/ring/tui.py:163
|
|
65
|
+
msgid "狀態"
|
|
66
|
+
msgstr ""
|
|
67
|
+
|
|
68
|
+
#: src/ring/cli.py:167 src/ring/cli.py:215 src/ring/tui.py:165
|
|
69
|
+
msgid "工具"
|
|
70
|
+
msgstr ""
|
|
71
|
+
|
|
72
|
+
#: src/ring/cli.py:168 src/ring/cli.py:215 src/ring/tui.py:166
|
|
73
|
+
msgid "專案"
|
|
74
|
+
msgstr ""
|
|
75
|
+
|
|
76
|
+
#: src/ring/cli.py:169 src/ring/cli.py:215 src/ring/tui.py:166
|
|
77
|
+
msgid "進度"
|
|
78
|
+
msgstr ""
|
|
79
|
+
|
|
80
|
+
#: src/ring/cli.py:170 src/ring/cli.py:215 src/ring/tui.py:166
|
|
81
|
+
msgid "閒置"
|
|
82
|
+
msgstr ""
|
|
83
|
+
|
|
84
|
+
#: src/ring/cli.py:171 src/ring/cli.py:215 src/ring/tui.py:166
|
|
85
|
+
msgid "去哪"
|
|
86
|
+
msgstr ""
|
|
87
|
+
|
|
88
|
+
#: src/ring/cli.py:173 src/ring/cli.py:215 src/ring/tui.py:166
|
|
89
|
+
msgid "動作"
|
|
90
|
+
msgstr ""
|
|
91
|
+
|
|
92
|
+
#: src/ring/cli.py:250
|
|
93
|
+
msgid "(內建預設)"
|
|
94
|
+
msgstr ""
|
|
95
|
+
|
|
96
|
+
#: src/ring/cli.py:268
|
|
97
|
+
msgid "RiNG 設定檔"
|
|
98
|
+
msgstr ""
|
|
99
|
+
|
|
100
|
+
#: src/ring/cli.py:269 src/ring/cli.py:469
|
|
101
|
+
msgid "路徑"
|
|
102
|
+
msgstr ""
|
|
103
|
+
|
|
104
|
+
#: src/ring/cli.py:270 src/ring/cli.py:470
|
|
105
|
+
msgid "已存在"
|
|
106
|
+
msgstr ""
|
|
107
|
+
|
|
108
|
+
#: src/ring/cli.py:270 src/ring/cli.py:470
|
|
109
|
+
msgid "不存在(全部用內建預設)"
|
|
110
|
+
msgstr ""
|
|
111
|
+
|
|
112
|
+
#: src/ring/cli.py:272
|
|
113
|
+
msgid "目前生效的設定(← = 你覆寫過的)"
|
|
114
|
+
msgstr ""
|
|
115
|
+
|
|
116
|
+
#: src/ring/cli.py:280
|
|
117
|
+
msgid ""
|
|
118
|
+
"用 `ring config set KEY VALUE` 改,或直接編輯上面那個檔;完整選項見 src/ring/config.py 的 "
|
|
119
|
+
"docstring。"
|
|
120
|
+
msgstr ""
|
|
121
|
+
|
|
122
|
+
#: src/ring/cli.py:293 src/ring/cli.py:296
|
|
123
|
+
#, python-brace-format
|
|
124
|
+
msgid "未知的鍵:{key}"
|
|
125
|
+
msgstr ""
|
|
126
|
+
|
|
127
|
+
#: src/ring/cli.py:326
|
|
128
|
+
msgid "用法:ring config get KEY"
|
|
129
|
+
msgstr ""
|
|
130
|
+
|
|
131
|
+
#: src/ring/cli.py:337
|
|
132
|
+
msgid "用法:ring config set KEY VALUE"
|
|
133
|
+
msgstr ""
|
|
134
|
+
|
|
135
|
+
#: src/ring/cli.py:345
|
|
136
|
+
#, python-brace-format
|
|
137
|
+
msgid "✅ 已設定 {key} = {value}({path})"
|
|
138
|
+
msgstr ""
|
|
139
|
+
|
|
140
|
+
#: src/ring/cli.py:346
|
|
141
|
+
msgid "註:set 會重寫整個設定檔,原有註解不會保留。"
|
|
142
|
+
msgstr ""
|
|
143
|
+
|
|
144
|
+
#: src/ring/cli.py:349
|
|
145
|
+
#, python-brace-format
|
|
146
|
+
msgid "未知的 config 動作:{action}(用 get / set,或不帶參數看目前設定)"
|
|
147
|
+
msgstr ""
|
|
148
|
+
|
|
149
|
+
#: src/ring/cli.py:357
|
|
150
|
+
msgid "用法:ring doctor"
|
|
151
|
+
msgstr ""
|
|
152
|
+
|
|
153
|
+
#: src/ring/cli.py:367
|
|
154
|
+
msgid "RiNG 環境診斷"
|
|
155
|
+
msgstr ""
|
|
156
|
+
|
|
157
|
+
#: src/ring/cli.py:368
|
|
158
|
+
msgid "唯讀檢查,不會改動任何設定"
|
|
159
|
+
msgstr ""
|
|
160
|
+
|
|
161
|
+
#: src/ring/cli.py:372
|
|
162
|
+
msgid "Session 來源"
|
|
163
|
+
msgstr ""
|
|
164
|
+
|
|
165
|
+
#: src/ring/cli.py:382
|
|
166
|
+
msgid "活著"
|
|
167
|
+
msgstr ""
|
|
168
|
+
|
|
169
|
+
#: src/ring/cli.py:383
|
|
170
|
+
#, python-brace-format
|
|
171
|
+
msgid "偵測到 {n} 個 session"
|
|
172
|
+
msgstr ""
|
|
173
|
+
|
|
174
|
+
#: src/ring/cli.py:386
|
|
175
|
+
msgid "偵測失敗"
|
|
176
|
+
msgstr ""
|
|
177
|
+
|
|
178
|
+
#: src/ring/cli.py:390
|
|
179
|
+
msgid "Hook 安裝"
|
|
180
|
+
msgstr ""
|
|
181
|
+
|
|
182
|
+
#: src/ring/cli.py:400
|
|
183
|
+
msgid "未使用 Codex(zero-config)"
|
|
184
|
+
msgstr ""
|
|
185
|
+
|
|
186
|
+
#: src/ring/cli.py:402
|
|
187
|
+
msgid "已安裝"
|
|
188
|
+
msgstr ""
|
|
189
|
+
|
|
190
|
+
#: src/ring/cli.py:404
|
|
191
|
+
msgid "未安裝(執行 ring install-hooks)"
|
|
192
|
+
msgstr ""
|
|
193
|
+
|
|
194
|
+
#: src/ring/cli.py:409
|
|
195
|
+
msgid "通知後端"
|
|
196
|
+
msgstr ""
|
|
197
|
+
|
|
198
|
+
#: src/ring/cli.py:410
|
|
199
|
+
msgid "目前設定"
|
|
200
|
+
msgstr ""
|
|
201
|
+
|
|
202
|
+
#: src/ring/cli.py:417 src/ring/cli.py:451 src/ring/cli.py:460
|
|
203
|
+
msgid "可用"
|
|
204
|
+
msgstr ""
|
|
205
|
+
|
|
206
|
+
#: src/ring/cli.py:417
|
|
207
|
+
msgid "不可用"
|
|
208
|
+
msgstr ""
|
|
209
|
+
|
|
210
|
+
#: src/ring/cli.py:421 src/ring/cli.py:437
|
|
211
|
+
msgid "auto 實際選中"
|
|
212
|
+
msgstr ""
|
|
213
|
+
|
|
214
|
+
#: src/ring/cli.py:426
|
|
215
|
+
msgid "macOS 提醒:若只聽到聲音但沒有通知框,請到系統設定的通知項目啟用 Banner/Alert。"
|
|
216
|
+
msgstr ""
|
|
217
|
+
|
|
218
|
+
#: src/ring/cli.py:432
|
|
219
|
+
msgid "backend=none"
|
|
220
|
+
msgstr ""
|
|
221
|
+
|
|
222
|
+
#: src/ring/cli.py:434
|
|
223
|
+
msgid "agent-hooks 已接手"
|
|
224
|
+
msgstr ""
|
|
225
|
+
|
|
226
|
+
#: src/ring/cli.py:436
|
|
227
|
+
msgid "全部不可用"
|
|
228
|
+
msgstr ""
|
|
229
|
+
|
|
230
|
+
#: src/ring/cli.py:437
|
|
231
|
+
msgid "不發通知"
|
|
232
|
+
msgstr ""
|
|
233
|
+
|
|
234
|
+
#: src/ring/cli.py:441
|
|
235
|
+
msgid "聚焦終端(focuser)"
|
|
236
|
+
msgstr ""
|
|
237
|
+
|
|
238
|
+
#: src/ring/cli.py:451
|
|
239
|
+
msgid "不可用(tmux 不在 PATH)"
|
|
240
|
+
msgstr ""
|
|
241
|
+
|
|
242
|
+
#: src/ring/cli.py:455
|
|
243
|
+
msgid "不可用(osascript 不在 PATH)"
|
|
244
|
+
msgstr ""
|
|
245
|
+
|
|
246
|
+
#: src/ring/cli.py:460 src/ring/cli.py:462
|
|
247
|
+
msgid "不可用(app 沒在跑)"
|
|
248
|
+
msgstr ""
|
|
249
|
+
|
|
250
|
+
#: src/ring/cli.py:467
|
|
251
|
+
msgid "設定檔"
|
|
252
|
+
msgstr ""
|
|
253
|
+
|
|
254
|
+
#: src/ring/cli.py:471
|
|
255
|
+
msgid "完整生效值請看 `ring config`。"
|
|
256
|
+
msgstr ""
|
|
257
|
+
|
|
258
|
+
#: src/ring/cli.py:482
|
|
259
|
+
#, python-brace-format
|
|
260
|
+
msgid "每 {interval}s 刷新 · Ctrl-C 離場"
|
|
261
|
+
msgstr ""
|
|
262
|
+
|
|
263
|
+
#: src/ring/cli.py:541
|
|
264
|
+
msgid ""
|
|
265
|
+
"\n"
|
|
266
|
+
"commands:\n"
|
|
267
|
+
" hook [PROVIDER] 從 stdin 讀 provider hook payload,寫入 RiNG "
|
|
268
|
+
"registry\n"
|
|
269
|
+
" hook --provider PROVIDER 同上,明確指定 provider(例如 codex)\n"
|
|
270
|
+
" install-hooks [--dry-run] 安裝 Claude Code / Codex hooks\n"
|
|
271
|
+
" remove-hooks [--dry-run] 移除 Claude Code / Codex hooks\n"
|
|
272
|
+
" config 顯示設定檔位置與目前生效的設定\n"
|
|
273
|
+
" config get KEY 讀單一設定的目前值\n"
|
|
274
|
+
" config set KEY VALUE 寫入單一設定(會重寫設定檔,不保留註解)\n"
|
|
275
|
+
" focus SESSION_ID 聚焦指定 session;TUI 在跑時會回到 RiNG 並選中該列\n"
|
|
276
|
+
" doctor 顯示環境診斷(唯讀)——hook 安裝狀態、通知後端、focuser 可用性\n"
|
|
277
|
+
msgstr ""
|
|
278
|
+
|
|
279
|
+
#: src/ring/cli.py:559
|
|
280
|
+
msgid ""
|
|
281
|
+
"usage: ring hook [PROVIDER] [--provider PROVIDER]\n"
|
|
282
|
+
"\n"
|
|
283
|
+
"從 stdin 讀 hook JSON,依 provider 正規化後寫入 RiNG registry。\n"
|
|
284
|
+
msgstr ""
|
|
285
|
+
|
|
286
|
+
#: src/ring/cli.py:565
|
|
287
|
+
msgid ""
|
|
288
|
+
"usage: ring install-hooks [--dry-run]\n"
|
|
289
|
+
"\n"
|
|
290
|
+
"安裝 Claude Code / Codex hooks。\n"
|
|
291
|
+
msgstr ""
|
|
292
|
+
|
|
293
|
+
#: src/ring/cli.py:571
|
|
294
|
+
msgid ""
|
|
295
|
+
"usage: ring remove-hooks [--dry-run]\n"
|
|
296
|
+
"\n"
|
|
297
|
+
"從 Claude Code / Codex hook 設定移除 RiNG 安裝的 hooks。\n"
|
|
298
|
+
msgstr ""
|
|
299
|
+
|
|
300
|
+
#: src/ring/cli.py:577
|
|
301
|
+
msgid ""
|
|
302
|
+
"usage: ring config [get KEY | set KEY VALUE]\n"
|
|
303
|
+
"\n"
|
|
304
|
+
"不帶參數:顯示設定檔位置(~/.config/ring/config.toml)與目前生效的所有設定。\n"
|
|
305
|
+
" get KEY 印出單一設定的目前值(colors 子鍵用 colors.<name>)。\n"
|
|
306
|
+
" set KEY VALUE 寫入單一設定。注意:會重寫整個設定檔,原有註解不會保留。\n"
|
|
307
|
+
msgstr ""
|
|
308
|
+
|
|
309
|
+
#: src/ring/cli.py:585
|
|
310
|
+
msgid ""
|
|
311
|
+
"usage: ring focus SESSION_ID\n"
|
|
312
|
+
"\n"
|
|
313
|
+
"聚焦指定 session;若 RiNG TUI 正在執行,會回到 TUI 並選中該列。\n"
|
|
314
|
+
msgstr ""
|
|
315
|
+
|
|
316
|
+
#: src/ring/cli.py:591
|
|
317
|
+
msgid ""
|
|
318
|
+
"usage: ring doctor\n"
|
|
319
|
+
"\n"
|
|
320
|
+
"唯讀環境診斷:逐節報告 hook 安裝狀態、通知後端可用性、focuser 可用性與設定檔位置。\n"
|
|
321
|
+
"不寫任何檔案、不安裝、不發通知;固定回傳 0。\n"
|
|
322
|
+
msgstr ""
|
|
323
|
+
|
|
324
|
+
#: src/ring/cli.py:656
|
|
325
|
+
msgid "看所有 agent CLI session 上台。"
|
|
326
|
+
msgstr ""
|
|
327
|
+
|
|
328
|
+
#: src/ring/cli.py:661
|
|
329
|
+
msgid "持續刷新"
|
|
330
|
+
msgstr ""
|
|
331
|
+
|
|
332
|
+
#: src/ring/cli.py:662
|
|
333
|
+
msgid "watch 刷新秒數"
|
|
334
|
+
msgstr ""
|
|
335
|
+
|
|
336
|
+
#: src/ring/cli.py:663
|
|
337
|
+
msgid "watch 刷新幾格後自動結束(0=無限,預設)"
|
|
338
|
+
msgstr ""
|
|
339
|
+
|
|
340
|
+
#: src/ring/cli.py:664
|
|
341
|
+
msgid "連已離場的 session 也顯示"
|
|
342
|
+
msgstr ""
|
|
343
|
+
|
|
344
|
+
#: src/ring/cli.py:669
|
|
345
|
+
msgid "顯示顏色圖例(--no-legend 關閉)"
|
|
346
|
+
msgstr ""
|
|
347
|
+
|
|
348
|
+
#: src/ring/cli.py:671
|
|
349
|
+
msgid "語言(如 en / zh-Hant;也吃 config / RING_LANG / LANG)"
|
|
350
|
+
msgstr ""
|
|
351
|
+
|
|
352
|
+
#: src/ring/hook.py:73
|
|
353
|
+
msgid "重開 Claude Code session 後,🔴 等你狀態就會精準起來。"
|
|
354
|
+
msgstr ""
|
|
355
|
+
|
|
356
|
+
#: src/ring/hook.py:83
|
|
357
|
+
msgid "重開 Codex session、並在它詢問時「信任」這個 hook,🔴 等你狀態才會精準起來(Codex 不會執行未信任的 hook)。"
|
|
358
|
+
msgstr ""
|
|
359
|
+
|
|
360
|
+
#: src/ring/hook.py:331
|
|
361
|
+
#, python-brace-format
|
|
362
|
+
msgid "⚠️ 偵測到其他工具也掛在 {events}:{cmds}"
|
|
363
|
+
msgstr ""
|
|
364
|
+
|
|
365
|
+
#: src/ring/hook.py:332
|
|
366
|
+
msgid "它們會跟 RiNG 的通知重複觸發;要完全改用 RiNG,建議移除它們。"
|
|
367
|
+
msgstr ""
|
|
368
|
+
|
|
369
|
+
#: src/ring/hook.py:380 src/ring/hook.py:456
|
|
370
|
+
#, python-brace-format
|
|
371
|
+
msgid "⚠️ {path} 不是合法 JSON,先處理它再來。"
|
|
372
|
+
msgstr ""
|
|
373
|
+
|
|
374
|
+
#: src/ring/hook.py:418
|
|
375
|
+
#, python-brace-format
|
|
376
|
+
msgid "✅ RiNG hooks 已經裝過了,沒有變更。({path})"
|
|
377
|
+
msgstr ""
|
|
378
|
+
|
|
379
|
+
#: src/ring/hook.py:431
|
|
380
|
+
#, python-brace-format
|
|
381
|
+
msgid "✅ 已註冊 RiNG hooks({events})到 {path}"
|
|
382
|
+
msgstr ""
|
|
383
|
+
|
|
384
|
+
#: src/ring/hook.py:450
|
|
385
|
+
#, python-brace-format
|
|
386
|
+
msgid "ℹ️ {path} 不存在,沒有可移除的 hook。"
|
|
387
|
+
msgstr ""
|
|
388
|
+
|
|
389
|
+
#: src/ring/hook.py:477
|
|
390
|
+
#, python-brace-format
|
|
391
|
+
msgid "ℹ️ 沒有找到 RiNG hook 條目,無需移除。({path})"
|
|
392
|
+
msgstr ""
|
|
393
|
+
|
|
394
|
+
#: src/ring/hook.py:481
|
|
395
|
+
#, python-brace-format
|
|
396
|
+
msgid "✅ 已移除 RiNG hooks({events})從 {path}"
|
|
397
|
+
msgstr ""
|
|
398
|
+
|
|
399
|
+
#: src/ring/tui.py:88
|
|
400
|
+
#, python-brace-format
|
|
401
|
+
msgid "為 {project} 命名(Enter 存、Esc 取消、清空移除)"
|
|
402
|
+
msgstr ""
|
|
403
|
+
|
|
404
|
+
#: src/ring/tui.py:89
|
|
405
|
+
msgid "這個 session 在做什麼…"
|
|
406
|
+
msgstr ""
|
|
407
|
+
|
|
408
|
+
#: src/ring/tui.py:106
|
|
409
|
+
msgid "離場"
|
|
410
|
+
msgstr ""
|
|
411
|
+
|
|
412
|
+
#: src/ring/tui.py:107
|
|
413
|
+
msgid "刷新"
|
|
414
|
+
msgstr ""
|
|
415
|
+
|
|
416
|
+
#: src/ring/tui.py:108
|
|
417
|
+
msgid "含已離場"
|
|
418
|
+
msgstr ""
|
|
419
|
+
|
|
420
|
+
#: src/ring/tui.py:109
|
|
421
|
+
msgid "命名"
|
|
422
|
+
msgstr ""
|
|
423
|
+
|
|
424
|
+
#: src/ring/tui.py:110 src/ring/tui.py:111
|
|
425
|
+
msgid "跳過去"
|
|
426
|
+
msgstr ""
|
|
427
|
+
|
|
428
|
+
#: src/ring/tui.py:184
|
|
429
|
+
msgid "💡 同專案開了多個 session,裝 hook 跳轉才精準:ring install-hooks"
|
|
430
|
+
msgstr ""
|
|
431
|
+
|
|
432
|
+
#: src/ring/tui.py:186
|
|
433
|
+
msgid "↑/↓ 選一列,Enter 或 Space 跳到那個 session 的終端"
|
|
434
|
+
msgstr ""
|
|
435
|
+
|
|
436
|
+
#: src/ring/tui.py:214
|
|
437
|
+
#, python-brace-format
|
|
438
|
+
msgid "🔔 {names} 在等你回話"
|
|
439
|
+
msgstr ""
|
|
440
|
+
|
|
441
|
+
#: src/ring/tui.py:255
|
|
442
|
+
#, python-brace-format
|
|
443
|
+
msgid "→ 已跳到 {project}(來自通知)"
|
|
444
|
+
msgstr ""
|
|
445
|
+
|
|
446
|
+
#: src/ring/tui.py:260
|
|
447
|
+
msgid "那個 session 已不在場"
|
|
448
|
+
msgstr ""
|
|
449
|
+
|
|
450
|
+
#: src/ring/tui.py:324 src/ring/tui.py:345
|
|
451
|
+
msgid "(沒有選到 session)"
|
|
452
|
+
msgstr ""
|
|
453
|
+
|
|
454
|
+
#: src/ring/tui.py:351
|
|
455
|
+
#, python-brace-format
|
|
456
|
+
msgid "→ {project}({where})"
|
|
457
|
+
msgstr ""
|
|
458
|
+
|
|
459
|
+
#: src/ring/tui.py:353
|
|
460
|
+
#, python-brace-format
|
|
461
|
+
msgid "{project}:{msg}"
|
|
462
|
+
msgstr ""
|
|
463
|
+
|
|
464
|
+
#: src/ring/focus/__init__.py:48
|
|
465
|
+
msgid "已離場的 session 無法跳轉"
|
|
466
|
+
msgstr ""
|
|
467
|
+
|
|
468
|
+
#: src/ring/focus/__init__.py:61
|
|
469
|
+
msgid "找不到這個終端分頁(可能已關閉;刷新後若仍存在,請裝 hook 取得更精準狀態)"
|
|
470
|
+
msgstr ""
|
|
471
|
+
|
|
472
|
+
#: src/ring/focus/__init__.py:60
|
|
473
|
+
msgid "沒有 focuser 接得住(裝 hook,或一個專案只開一個 session 才測得到 tty)"
|
|
474
|
+
msgstr ""
|
|
475
|
+
|
|
476
|
+
#: src/ring/notify/__init__.py:112
|
|
477
|
+
msgid "💡 裝 terminal-notifier 可點擊通知直接跳回 session:brew install terminal-notifier"
|
|
478
|
+
msgstr ""
|
|
479
|
+
|
|
480
|
+
#: src/ring/notify/notify_send.py:22 src/ring/notify/osascript_notifier.py:24
|
|
481
|
+
#: src/ring/notify/terminal_notifier.py:48
|
|
482
|
+
#, python-brace-format
|
|
483
|
+
msgid "RiNG · {project} 在等你回話"
|
|
484
|
+
msgstr ""
|
ring/notify/__init__.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""系統通知 package——把「新轉為等你」的 session 發給使用者。
|
|
2
|
+
|
|
3
|
+
可插拔的 ``Notifier`` 抽象層(跟 ``focus`` 的 focuser、``sources`` 的 source 同一套
|
|
4
|
+
設計):core 不認識任何具體後端,只依序問每個已註冊 notifier「你能用嗎」。要支援新
|
|
5
|
+
平台=在這個 package 加一個模組、``register_notifier()`` 註冊,core 零改動。內建:
|
|
6
|
+
|
|
7
|
+
- ``terminal-notifier``(macOS,支援點擊 ``ring focus`` 跳轉)
|
|
8
|
+
- ``osascript``(macOS,純文字,點擊不可聚焦——terminal-notifier 被系統擋掉時的退路)
|
|
9
|
+
- ``notify-send``(Linux / libnotify,純文字)
|
|
10
|
+
|
|
11
|
+
選哪個由 config ``notify_backend`` 決定:``"auto"`` = 第一個「可用」的(優先支援點擊的),
|
|
12
|
+
或指定某個 notifier 名稱強制使用。通知失敗一律安靜吞掉——通知是錦上添花,絕不打斷主流程。
|
|
13
|
+
外部 binary(terminal-notifier / notify-send)不進 pyproject dependencies。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import shutil
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from ring.config import get_config
|
|
23
|
+
from ring.i18n import gettext as _
|
|
24
|
+
from ring.notify.base import Notifier
|
|
25
|
+
from ring.notify.notify_send import notifier as _notify_send
|
|
26
|
+
from ring.notify.osascript_notifier import notifier as _osascript
|
|
27
|
+
from ring.notify.terminal_notifier import notifier as _terminal_notifier
|
|
28
|
+
from ring.registry import Session
|
|
29
|
+
|
|
30
|
+
# 一次性安裝引導的 marker 檔路徑。
|
|
31
|
+
_HINT_MARKER: Path = Path.home() / ".config" / "ring" / ".tn-hint-shown"
|
|
32
|
+
|
|
33
|
+
# 內建後端。順序即 auto 的偏好順序(前面優先)。terminal-notifier 先於 osascript(macOS),
|
|
34
|
+
# notify-send 殿後(Linux)。register_notifier(first=True) 可插到最前。
|
|
35
|
+
_NOTIFIERS: list[Notifier] = [_terminal_notifier, _osascript, _notify_send]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register_notifier(notifier: Notifier, *, first: bool = False) -> None:
|
|
39
|
+
"""外部擴充入口:註冊一個自訂通知後端(first=True 插到最前、優先嘗試)。"""
|
|
40
|
+
if first:
|
|
41
|
+
_NOTIFIERS.insert(0, notifier)
|
|
42
|
+
else:
|
|
43
|
+
_NOTIFIERS.append(notifier)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def notifiers() -> list[Notifier]:
|
|
47
|
+
"""目前已註冊的 notifier,順序即 auto 的偏好順序。"""
|
|
48
|
+
return list(_NOTIFIERS)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _select_notifier(backend: str) -> Notifier | None:
|
|
52
|
+
"""依 config 的 notify_backend 選一個可用 notifier。
|
|
53
|
+
|
|
54
|
+
- ``"none"`` → ``None``,明確關閉通知(RiNG 當純看板用,不發任何 toast)。
|
|
55
|
+
- ``"agent-hooks"`` → 決策與提醒交給 agent-hooks(由 ``ring hook`` 同步出 modal)。
|
|
56
|
+
agent-hooks 在 PATH 上時回 ``None``(watch 不重複發 toast);不在時退回 auto,
|
|
57
|
+
讓 RiNG 自己通知——所以「沒裝 agent-hooks 也不會兩頭落空」是自動保證的。
|
|
58
|
+
- 指定名稱且該 notifier 可用 → 用它。
|
|
59
|
+
- ``"auto"``(或指定的後端不存在/不可用)→ 取第一個可用的,優先支援點擊跳轉的。
|
|
60
|
+
- 都不可用 → ``None``(不發、不崩)。
|
|
61
|
+
"""
|
|
62
|
+
if backend == "none":
|
|
63
|
+
return None
|
|
64
|
+
if backend == "agent-hooks":
|
|
65
|
+
if shutil.which("agent-hooks") is not None:
|
|
66
|
+
return None # agent-hooks 從 ring hook 那邊出 modal,watch 不重複發
|
|
67
|
+
backend = "auto" # 沒裝 → 退回 auto,RiNG 自己通知,不會瞎
|
|
68
|
+
available = [n for n in _NOTIFIERS if n.available()]
|
|
69
|
+
if backend != "auto":
|
|
70
|
+
for n in available:
|
|
71
|
+
if n.name == backend:
|
|
72
|
+
return n
|
|
73
|
+
# 指定的後端不可用 → 退回 auto 選法
|
|
74
|
+
if not available:
|
|
75
|
+
return None
|
|
76
|
+
clickable = [n for n in available if n.supports_click()]
|
|
77
|
+
return clickable[0] if clickable else available[0]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def notify_waiting(sessions: list[Session]) -> str | None:
|
|
81
|
+
"""對一批「新轉為等你」的 session 發系統通知。
|
|
82
|
+
|
|
83
|
+
依 config ``notify_backend`` 選一個 notifier 後端發送(見 ``_select_notifier``)。
|
|
84
|
+
auto 模式下若只選到不支援點擊的後端、且在 macOS 上,回傳一次性的安裝引導字串
|
|
85
|
+
(建議裝 terminal-notifier 取得點擊跳轉);其餘情況回 ``None``。失敗一律安靜吞掉。
|
|
86
|
+
|
|
87
|
+
:param sessions: 新轉為 waiting 的 session 清單;空清單時直接回傳。
|
|
88
|
+
:returns: 安裝引導提示字串或 ``None``。
|
|
89
|
+
"""
|
|
90
|
+
if not sessions:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
backend = get_config().notify_backend
|
|
94
|
+
notifier = _select_notifier(backend)
|
|
95
|
+
if notifier is None:
|
|
96
|
+
return None
|
|
97
|
+
notifier.send(sessions)
|
|
98
|
+
# auto 落到非點擊後端、且在 macOS(terminal-notifier 是可裝的點擊選項)→ 提示一次。
|
|
99
|
+
if backend == "auto" and not notifier.supports_click() and sys.platform == "darwin":
|
|
100
|
+
return _maybe_show_install_hint()
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _maybe_show_install_hint() -> str | None:
|
|
105
|
+
"""首次走 osascript 路徑時,回傳 terminal-notifier 安裝建議字串(marker 檔防重複)。
|
|
106
|
+
|
|
107
|
+
:returns: hint 字串(首次)或 ``None``(已提示過或寫 marker 失敗時)。
|
|
108
|
+
"""
|
|
109
|
+
if _HINT_MARKER.exists():
|
|
110
|
+
return None
|
|
111
|
+
try:
|
|
112
|
+
hint = _("💡 裝 terminal-notifier 可點擊通知直接跳回 session:brew install terminal-notifier")
|
|
113
|
+
_HINT_MARKER.parent.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
_HINT_MARKER.touch()
|
|
115
|
+
return hint
|
|
116
|
+
except Exception:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = ["Notifier", "notifiers", "notify_waiting", "register_notifier"]
|
ring/notify/base.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Notifier 協定 + 所有後端共用的純 helper(只放契約與共用文字,不放具體後端)。
|
|
2
|
+
|
|
3
|
+
core 只透過 ``Notifier`` 介面跟具體平台溝通;要支援新平台=在這個 package 裡新增一個
|
|
4
|
+
模組、定義一個 Notifier 並 ``register_notifier()`` 註冊,core 零改動。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Protocol
|
|
10
|
+
|
|
11
|
+
from ring.registry import Session
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Notifier(Protocol):
|
|
15
|
+
"""一個系統通知後端。core 只透過這個介面跟具體平台溝通。"""
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
|
|
19
|
+
def available(self) -> bool:
|
|
20
|
+
"""這個後端在當前系統能不能用(通常是對應 binary 是否存在)。"""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def supports_click(self) -> bool:
|
|
24
|
+
"""點擊通知能不能觸發 ``ring focus`` 跳轉。"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def send(self, sessions: list[Session]) -> None:
|
|
28
|
+
"""逐 session 各發一則通知;失敗安靜吞掉。"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def notify_message(session: Session) -> str:
|
|
33
|
+
"""通知內文——只給重要訊息:去哪(完整路徑或 tmux 座標)。
|
|
34
|
+
|
|
35
|
+
標題已經說了「哪個專案在等你」,內文就補「位置」讓你(尤其不能點擊跳轉時)知道去哪。
|
|
36
|
+
"""
|
|
37
|
+
return f"📍 {session.location}"
|
ring/notify/command.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""以「對應 CLI binary 在不在 PATH」判定 available 的 notifier 共用基底。
|
|
2
|
+
|
|
3
|
+
內建三個後端(terminal-notifier / osascript / notify-send)的 binary 名剛好等於 notifier
|
|
4
|
+
名,所以共用這段;binary 名與 notifier 名不同的後端覆寫 ``available()`` 即可。子類別仍要
|
|
5
|
+
自己定義 ``name`` 與 ``supports_click()`` / ``send()``——跟 ``focus.applescript`` 把共用實作
|
|
6
|
+
拆出 ``base`` 同一個道理。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommandNotifier:
|
|
15
|
+
name: str
|
|
16
|
+
|
|
17
|
+
def available(self) -> bool:
|
|
18
|
+
return shutil.which(self.name) is not None
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""notify-send 後端(Linux / libnotify 純文字,點擊不可聚焦)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from ring.i18n import gettext as _
|
|
8
|
+
from ring.notify.base import notify_message
|
|
9
|
+
from ring.notify.command import CommandNotifier
|
|
10
|
+
from ring.registry import Session
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NotifySendNotifier(CommandNotifier):
|
|
14
|
+
name = "notify-send"
|
|
15
|
+
|
|
16
|
+
def supports_click(self) -> bool:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
def send(self, sessions: list[Session]) -> None:
|
|
20
|
+
"""用 libnotify 的 ``notify-send`` 逐 session 各發一則純文字通知。"""
|
|
21
|
+
for s in sessions:
|
|
22
|
+
title = _("RiNG · {project} 在等你回話", project=s.project)
|
|
23
|
+
message = notify_message(s)
|
|
24
|
+
try:
|
|
25
|
+
subprocess.run(["notify-send", title, message], capture_output=True, timeout=10)
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
notifier = NotifySendNotifier()
|