ring-cli 0.14.1__tar.gz → 0.14.2__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 (62) hide show
  1. {ring_cli-0.14.1 → ring_cli-0.14.2}/PKG-INFO +1 -1
  2. ring_cli-0.14.2/pyproject.toml +212 -0
  3. ring_cli-0.14.1/pyproject.toml → ring_cli-0.14.2/pyproject.toml.orig +1 -1
  4. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/registry.py +38 -0
  5. {ring_cli-0.14.1 → ring_cli-0.14.2}/LICENSE +0 -0
  6. {ring_cli-0.14.1 → ring_cli-0.14.2}/README.en.md +0 -0
  7. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/__init__.py +0 -0
  8. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/__main__.py +0 -0
  9. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/cli.py +0 -0
  10. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/__init__.py +0 -0
  11. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/_args.py +0 -0
  12. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/completion.py +0 -0
  13. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/digest.py +0 -0
  14. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/doctor.py +0 -0
  15. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/focus.py +0 -0
  16. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/gc.py +0 -0
  17. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/hook.py +0 -0
  18. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/quiet.py +0 -0
  19. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/commands/stats.py +0 -0
  20. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/config.py +0 -0
  21. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/__init__.py +0 -0
  22. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/applescript.py +0 -0
  23. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/base.py +0 -0
  24. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/iterm2.py +0 -0
  25. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/linux_wm.py +0 -0
  26. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/neovim.py +0 -0
  27. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/terminal.py +0 -0
  28. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/tmux.py +0 -0
  29. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/focus/trace.py +0 -0
  30. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/gc.py +0 -0
  31. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/hook.py +0 -0
  32. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/hook_protocol.py +0 -0
  33. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/i18n.py +0 -0
  34. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/ipc.py +0 -0
  35. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/labels.py +0 -0
  36. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
  37. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/locale/en/LC_MESSAGES/ring.po +0 -0
  38. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/locale/ring.pot +0 -0
  39. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/__init__.py +0 -0
  40. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/base.py +0 -0
  41. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/command.py +0 -0
  42. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/notify_send.py +0 -0
  43. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/ntfy.py +0 -0
  44. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/osascript_notifier.py +0 -0
  45. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/terminal_notifier.py +0 -0
  46. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify/webhook.py +0 -0
  47. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/notify_queue.py +0 -0
  48. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/osascript.py +0 -0
  49. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/payload_log.py +0 -0
  50. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/permission.py +0 -0
  51. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/plugins.py +0 -0
  52. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/question_detect.py +0 -0
  53. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/sources/__init__.py +0 -0
  54. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/sources/base.py +0 -0
  55. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/sources/claude_code.py +0 -0
  56. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/sources/codex.py +0 -0
  57. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/sources/hook_registry.py +0 -0
  58. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/sources/local_llm.py +0 -0
  59. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/stats.py +0 -0
  60. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/transcript.py +0 -0
  61. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/tui.py +0 -0
  62. {ring_cli-0.14.1 → ring_cli-0.14.2}/src/ring/watcher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ring-cli
3
- Version: 0.14.1
3
+ Version: 0.14.2
4
4
  Summary: RiNG — Realtime Instance Notification Grid for active agent CLI sessions.
5
5
  Keywords: claude-code,codex,ollama,llama-cpp,tui,dashboard,session,monitor,rich
6
6
  Author: Wei Lee
@@ -0,0 +1,212 @@
1
+ [project]
2
+ name = "ring-cli"
3
+ version = "0.14.2"
4
+ description = "RiNG — Realtime Instance Notification Grid for active agent CLI sessions."
5
+ readme = "README.en.md"
6
+ requires-python = ">=3.13"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ keywords = [
10
+ "claude-code",
11
+ "codex",
12
+ "ollama",
13
+ "llama-cpp",
14
+ "tui",
15
+ "dashboard",
16
+ "session",
17
+ "monitor",
18
+ "rich",
19
+ ]
20
+ classifiers = [
21
+ "Development Status :: 4 - Beta",
22
+ "Environment :: Console",
23
+ "Intended Audience :: Developers",
24
+ "Operating System :: MacOS",
25
+ "Operating System :: POSIX :: Linux",
26
+ "Programming Language :: Python :: 3",
27
+ "Topic :: Software Development",
28
+ "Topic :: Utilities",
29
+ ]
30
+ dependencies = ["rich>=13"]
31
+
32
+ [[project.authors]]
33
+ name = "Wei Lee"
34
+ email = "weilee.rx@gmail.com"
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/Lee-W/ring"
38
+ Repository = "https://github.com/Lee-W/ring"
39
+ Documentation = "https://lee-w.github.io/ring/"
40
+
41
+ [project.optional-dependencies]
42
+ tui = ["textual>=0.80"]
43
+
44
+ [project.scripts]
45
+ ring = "ring.cli:main"
46
+
47
+ [dependency-groups]
48
+ base = [
49
+ "poethepoet>=0.41.0",
50
+ "babel>=2.14",
51
+ ]
52
+ test = [
53
+ "pytest>=9",
54
+ "pytest-cov>=4",
55
+ "pytest-xdist>=3.1.0",
56
+ "pytest-asyncio>=0.24",
57
+ "textual>=0.80",
58
+ ]
59
+ linters = [
60
+ "ruff>=0.15.1",
61
+ "mypy>=1.19.1",
62
+ "prek>=0.3.3",
63
+ "commitizen>=4.13.9",
64
+ ]
65
+ docs = [
66
+ "mkdocs-material>=9,<10",
67
+ "mkdocs-static-i18n>=1.3,<2",
68
+ ]
69
+
70
+ [[dependency-groups.dev]]
71
+ include-group = "base"
72
+
73
+ [[dependency-groups.dev]]
74
+ include-group = "test"
75
+
76
+ [[dependency-groups.dev]]
77
+ include-group = "linters"
78
+
79
+ [build-system]
80
+ requires = ["uv_build >= 0.10.3, <0.12.0"]
81
+ build-backend = "uv_build"
82
+
83
+ [tool.uv.build-backend]
84
+ module-name = "ring"
85
+ module-root = "src"
86
+
87
+ [tool.mypy]
88
+ files = [
89
+ "src",
90
+ "tests",
91
+ ]
92
+ strict = true
93
+ warn_redundant_casts = true
94
+ warn_unused_ignores = true
95
+ warn_unused_configs = true
96
+
97
+ [tool.ruff]
98
+ target-version = "py313"
99
+ line-length = 120
100
+
101
+ [tool.ruff.lint]
102
+ select = [
103
+ "E",
104
+ "F",
105
+ "W",
106
+ "I",
107
+ "UP",
108
+ "RUF",
109
+ ]
110
+ ignore = [
111
+ "RUF001",
112
+ "RUF002",
113
+ "RUF003",
114
+ ]
115
+
116
+ [tool.codespell]
117
+ skip = ".git*,*.lock"
118
+ check-hidden = true
119
+ ignore-words-list = "otification"
120
+
121
+ [tool.poe.tasks.format]
122
+ help = "Format the code"
123
+
124
+ [[tool.poe.tasks.format.sequence]]
125
+ cmd = "ruff check --fix"
126
+
127
+ [[tool.poe.tasks.format.sequence]]
128
+ cmd = "ruff format"
129
+
130
+ [tool.poe.tasks.lint]
131
+ help = "Lint the code"
132
+
133
+ [[tool.poe.tasks.lint.sequence]]
134
+ cmd = "ruff check"
135
+
136
+ [[tool.poe.tasks.lint.sequence]]
137
+ cmd = "mypy"
138
+
139
+ [tool.poe.tasks.test]
140
+ help = "Run the test suite"
141
+ cmd = "pytest"
142
+
143
+ [tool.poe.tasks.cover]
144
+ help = "Run the test suite with coverage"
145
+ cmd = "pytest --cov=ring --cov-report=term-missing --cov-report=xml:coverage.xml --junitxml=junit.xml -o junit_family=legacy"
146
+
147
+ [tool.poe.tasks.all]
148
+ help = "Run all tasks"
149
+ sequence = [
150
+ "format",
151
+ "lint",
152
+ "cover",
153
+ ]
154
+
155
+ [tool.poe.tasks.ci]
156
+ help = "Run all tasks in CI"
157
+ sequence = [
158
+ { cmd = "prek run --all-files" },
159
+ "cover",
160
+ ]
161
+
162
+ [tool.poe.tasks.ci.env]
163
+ SKIP = "no-commit-to-branch"
164
+
165
+ [tool.poe.tasks.setup-pre-commit]
166
+ help = "Install pre-commit hooks"
167
+ cmd = "prek install"
168
+
169
+ [tool.poe.tasks."i18n:extract"]
170
+ help = "從原始碼抽出可翻譯字串到 .pot(改字串後重抽。不要用 pybabel update:會塞空 msgstr 進 .po,弄壞 i18n 測試。新字串請手動把已翻譯條目加進各 .po)"
171
+ cmd = "pybabel extract -F babel.cfg -o src/ring/locale/ring.pot src/ring"
172
+
173
+ [tool.poe.tasks."i18n:check"]
174
+ help = "列出原始碼有、但某語言 .po 還沒翻譯的字串(純資訊,不失敗)。先跑 i18n:extract"
175
+ cmd = "python scripts/i18n_check.py"
176
+
177
+ [tool.poe.tasks."i18n:compile"]
178
+ help = "把各語言的 .po 編成 .mo(改 .po 後要重編並 commit)"
179
+ cmd = "pybabel compile -d src/ring/locale -D ring"
180
+
181
+ [tool.poe.tasks."docs:serve"]
182
+ help = "Serve the docs site locally with live reload"
183
+ cmd = "mkdocs serve"
184
+
185
+ [tool.poe.tasks."docs:build"]
186
+ help = "Build the docs site into site/"
187
+ cmd = "mkdocs build --strict"
188
+
189
+ [tool.commitizen]
190
+ name = "cz_conventional_commits"
191
+ tag_format = "$version"
192
+ version_scheme = "pep440"
193
+ version_provider = "uv"
194
+ update_changelog_on_bump = true
195
+ major_version_zero = true
196
+
197
+ [tool.pytest]
198
+ testpaths = ["tests"]
199
+ addopts = [
200
+ "-n",
201
+ "auto",
202
+ "--dist=loadfile",
203
+ ]
204
+
205
+ [tool.coverage.run]
206
+ source = ["ring"]
207
+
208
+ [tool.coverage.paths]
209
+ source = [
210
+ "src/ring",
211
+ "*/ring",
212
+ ]
@@ -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.1"
5
+ version = "0.14.2"
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"
@@ -69,6 +69,8 @@ WAITING_KIND_ICONS = {
69
69
  "idle": "⏸",
70
70
  }
71
71
  HOOK_HEARTBEAT_STALE_GRACE_SECONDS = 60.0
72
+ # 只送過 SessionStart 就再無下文的 hook row,寬限多久後判離場(見 _is_bare_session_start_row)
73
+ BARE_SESSION_START_GRACE_SECONDS = 120.0
72
74
 
73
75
  # Provider → 「當下 live process 的 (cwd, tty) 清單」偵測器。core 不認識任何具體工具:
74
76
  # 要支援新工具的存活偵測=註冊一個偵測器,_hook_sessions / sources 零改動。
@@ -423,6 +425,34 @@ def _hook_heartbeat_stale(
423
425
  return source_mtime - heartbeat_at > grace_seconds
424
426
 
425
427
 
428
+ def _is_bare_session_start_row(
429
+ tty: str,
430
+ last_event: str,
431
+ age_seconds: float,
432
+ *,
433
+ grace_seconds: float = BARE_SESSION_START_GRACE_SECONDS,
434
+ ) -> bool:
435
+ """這筆 hook row 是不是「只送過 SessionStart 就再無下文、又沒有終端」的承載 session。
436
+
437
+ Claude Code 的背景 job/agent(``--bg-pty-host`` / ``--agent``)起來時會送一筆
438
+ SessionStart,之後可能整段生命週期都不再送任何事件。hook row 的狀態只靠事件推進
439
+ (不像 scan row 會隨 idle 秒數自己衰減),所以這種 row 會以 🟢 working 卡在看板上
440
+ 直到 ``ACTIVE_WINDOW_SECONDS`` 過期,而且它沒有 tty、跳也跳不過去——2026-07-28
441
+ 現場取證:一次背景 job 讓看板憑空多一列,registry 裡最久的一筆已經掛了三天。
442
+
443
+ 真正在做事的背景 job 起步幾秒內就會送出 UserPromptSubmit/PreToolUse,寬限期過後
444
+ 仍只有 SessionStart 才判離場;之後只要它真的動起來,下一個事件會把 row 寫回在場,
445
+ 不是永久刪除。有 tty 的前景 session 一律不套用——剛開好還沒輸入的終端 session 也
446
+ 只有 SessionStart,那是正常在場狀態。
447
+
448
+ :param tty: row 記錄到的終端;空字串代表沒有可聚焦終端。
449
+ :param last_event: registry 檔裡的 ``last_event``。
450
+ :param age_seconds: 距離 ``last_active`` 過了多久。
451
+ :param grace_seconds: 寬限秒數,超過才判離場。
452
+ """
453
+ return not tty and last_event == "SessionStart" and age_seconds > grace_seconds
454
+
455
+
426
456
  _tmux_cache: tuple[float, dict[str, str]] = (-1.0, {})
427
457
  _tmux_panes_cache: tuple[float, list[TmuxPane]] = (-1.0, [])
428
458
 
@@ -1408,6 +1438,14 @@ def _hook_sessions(
1408
1438
  # 「下一個事件來時還新不新鮮」;這裡 hook 靜默 = 同一筆請求還掛著,
1409
1439
  # 摘要必然還是它,直接沿用)。
1410
1440
  row.waiting_detail = pending_detail
1441
+ if _is_bare_session_start_row(
1442
+ row.tty or "",
1443
+ str(data.get("last_event", "")),
1444
+ time.time() - row.last_active,
1445
+ ):
1446
+ # 背景 job/agent 的承載 session:只報到、不做事、沒有終端。判離場後
1447
+ # 預設收起(`--all` / TUI 的 a 仍查得到),它真的動起來時會自己回來。
1448
+ row.status = Status.ENDED
1411
1449
  out.append(row)
1412
1450
  except (KeyError, ValueError):
1413
1451
  continue
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