cc-session-control 0.6.2__tar.gz → 0.6.3__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 (61) hide show
  1. {cc_session_control-0.6.2/src/cc_session_control.egg-info → cc_session_control-0.6.3}/PKG-INFO +1 -1
  2. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/__init__.py +1 -1
  3. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/app.py +8 -5
  4. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/_base.py +15 -1
  5. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/_keytable.py +2 -2
  6. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/agents.py +11 -15
  7. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/rc.py +13 -17
  8. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/sessions.py +19 -18
  9. {cc_session_control-0.6.2 → cc_session_control-0.6.3/src/cc_session_control.egg-info}/PKG-INFO +1 -1
  10. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/LICENSE +0 -0
  11. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/README.md +0 -0
  12. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/pyproject.toml +0 -0
  13. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/setup.cfg +0 -0
  14. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/__main__.py +0 -0
  15. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/actions/__init__.py +0 -0
  16. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/actions/agent_ops.py +0 -0
  17. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/actions/resume_list.py +0 -0
  18. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/actions/session_ops.py +0 -0
  19. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/actions/skill_ops.py +0 -0
  20. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/cli.py +0 -0
  21. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/clipboard.py +0 -0
  22. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/config.py +0 -0
  23. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/__init__.py +0 -0
  24. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/cleanup.py +0 -0
  25. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/environments.py +0 -0
  26. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/liveness.py +0 -0
  27. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/proc.py +0 -0
  28. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/rc.py +0 -0
  29. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/registry.py +0 -0
  30. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/sessions.py +0 -0
  31. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/data/snapshot.py +0 -0
  32. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/models.py +0 -0
  33. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/skill/SKILL.md +0 -0
  34. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/__init__.py +0 -0
  35. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/_colspec.py +0 -0
  36. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/_confirm.py +0 -0
  37. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/_rows.py +0 -0
  38. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/_session_row.py +0 -0
  39. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control/views/_sessions_cleanup.py +0 -0
  40. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control.egg-info/SOURCES.txt +0 -0
  41. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control.egg-info/dependency_links.txt +0 -0
  42. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control.egg-info/entry_points.txt +0 -0
  43. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control.egg-info/requires.txt +0 -0
  44. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/src/cc_session_control.egg-info/top_level.txt +0 -0
  45. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_agent_ops.py +0 -0
  46. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_agents.py +0 -0
  47. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_app.py +0 -0
  48. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_cleanup.py +0 -0
  49. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_cli.py +0 -0
  50. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_data.py +0 -0
  51. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_environments.py +0 -0
  52. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_liveness.py +0 -0
  53. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_proc.py +0 -0
  54. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_rc.py +0 -0
  55. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_registry.py +0 -0
  56. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_resume_list.py +0 -0
  57. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_sessions.py +0 -0
  58. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_skill_ops.py +0 -0
  59. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_smoke.py +0 -0
  60. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_snapshot.py +0 -0
  61. {cc_session_control-0.6.2 → cc_session_control-0.6.3}/tests/test_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-control
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: TUI manager for Claude Code sessions and Remote Control
5
5
  Author: dzshzx
6
6
  License-Expression: MIT
@@ -1,3 +1,3 @@
1
1
  """cc-session-control — TUI manager for Claude Code sessions and Remote Control."""
2
2
 
3
- __version__ = "0.6.2"
3
+ __version__ = "0.6.3"
@@ -6,7 +6,7 @@ import curses
6
6
  import os
7
7
  import threading
8
8
  from collections.abc import Callable
9
- from typing import Protocol, runtime_checkable
9
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
10
10
 
11
11
  import urwid
12
12
 
@@ -16,6 +16,9 @@ from .views.agents import AgentsView
16
16
  from .views.rc import RCView
17
17
  from .views.sessions import SessionsView
18
18
 
19
+ if TYPE_CHECKING:
20
+ from .actions.session_ops import ExitIntent
21
+
19
22
  # D7/R10: shown across all tabs when `/proc` is unavailable (e.g. macOS), where
20
23
  # the "current" session can't be determined and destructive ops are refused.
21
24
  _DEGRADED_BANNER = "⚠ liveness 降级(无 /proc):terminate/delete/cleanup 已受限"
@@ -89,7 +92,7 @@ def _make_screen() -> urwid.raw_display.Screen:
89
92
 
90
93
  class App:
91
94
  def __init__(self) -> None:
92
- self.result: object | None = None # a session_ops.ExitIntent, or None
95
+ self.result: ExitIntent | None = None
93
96
  self._exiting = False
94
97
  self._alarm_handle: object | None = None
95
98
  self._notify_alarm: object | None = None
@@ -211,14 +214,14 @@ class App:
211
214
  self._confirm_yes = None
212
215
  self.set_hints(self.views[self._active].keyhints())
213
216
 
214
- def _exit(self, result: object = None) -> None:
217
+ def _exit(self, result: ExitIntent | None = None) -> None:
215
218
  self._exiting = True
216
219
  if self._alarm_handle:
217
220
  self.loop.remove_alarm(self._alarm_handle)
218
221
  self.result = result
219
222
  raise urwid.ExitMainLoop()
220
223
 
221
- def exit_with(self, intent: object) -> None:
224
+ def exit_with(self, intent: ExitIntent) -> None:
222
225
  """Exit the MainLoop carrying a `session_ops.ExitIntent`.
223
226
 
224
227
  Views construct the intent (they know the verb); `cli._cmd_tui` calls
@@ -312,7 +315,7 @@ class App:
312
315
  view.apply_data()
313
316
  return True
314
317
 
315
- def run(self) -> object | None:
318
+ def run(self) -> ExitIntent | None:
316
319
  self._pipe_fd = self.loop.watch_pipe(self._on_pipe)
317
320
  self.views[self._active].load()
318
321
  self.set_hints(self.views[self._active].keyhints())
@@ -16,6 +16,7 @@ import urwid
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from ..app import App
19
+ from ._keytable import Key
19
20
 
20
21
 
21
22
  class ListTabView:
@@ -69,7 +70,20 @@ class ListTabView:
69
70
  # --- key-table dispatch (see views/_keytable.py) ---
70
71
 
71
72
  #: the view's single-source key table; subclasses override.
72
- KEY_TABLE: tuple = ()
73
+ KEY_TABLE: tuple[Key, ...] = ()
74
+
75
+ def handle_key(self, key: str) -> None:
76
+ """Default TabView key handling: overlay mode intercepts, list mode
77
+ dispatches from KEY_TABLE. A view with extra modes (e.g. the Sessions
78
+ filter/cleanup/preview) overrides, handles those, then calls super()."""
79
+ if self._overlay_active():
80
+ self._handle_overlay_key(key)
81
+ return
82
+ self._dispatch_key(key)
83
+
84
+ def _overlay_active(self) -> bool:
85
+ """Whether the view is in its overlay (help/watch) mode right now."""
86
+ raise NotImplementedError
73
87
 
74
88
  def _dispatch_key(self, key: str) -> None:
75
89
  """List-mode dispatch driven by `KEY_TABLE` — the same declaration that
@@ -19,7 +19,7 @@ class Key:
19
19
  keys: tuple[str, ...] # urwid key names, e.g. ("enter", "o")
20
20
  hint: str | None # footer label; None = shown only in help
21
21
  handler: str # view method name (resolved via getattr)
22
- help: tuple[str, ...] = () # pre-indented help-overlay lines
22
+ help_lines: tuple[str, ...] = () # pre-indented help-overlay display lines
23
23
  section: str | None = None # help section this entry lists under
24
24
  needs_selection: bool = True # call handler(_selected()) vs handler()
25
25
 
@@ -45,7 +45,7 @@ def help_lines(table: tuple[Key, ...], layout: HelpLayout) -> list[str]:
45
45
  lines.append(title)
46
46
  for e in table:
47
47
  if e.section == title:
48
- lines.extend(e.help)
48
+ lines.extend(e.help_lines)
49
49
  lines.append("")
50
50
  lines.extend(layout.suffix)
51
51
  return lines
@@ -79,23 +79,23 @@ class AgentsView(ListTabView):
79
79
  # App-level FOOTER_PREFIX, so its entry is hint-less.
80
80
  KEY_TABLE = (
81
81
  Key(("enter", "o"), "Enter/o 接回", "_takeover",
82
- section="后台 agent 生命周期:", help=(
82
+ section="后台 agent 生命周期:", help_lines=(
83
83
  " Enter/o 接回(拉回前台,复用 resume;接运行中的 agent 会先确认接管)",
84
84
  )),
85
- Key(("s",), "s 停止", "_stop", section="后台 agent 生命周期:", help=(
85
+ Key(("s",), "s 停止", "_stop", section="后台 agent 生命周期:", help_lines=(
86
86
  " s 停止(仅运行中,需确认)",
87
87
  )),
88
- Key(("d",), "d 删除", "_remove", section="后台 agent 生命周期:", help=(
88
+ Key(("d",), "d 删除", "_remove", section="后台 agent 生命周期:", help_lines=(
89
89
  " d 删除(仅已结束)",
90
90
  )),
91
- Key(("w",), "w 查看", "_watch", section="后台 agent 生命周期:", help=(
91
+ Key(("w",), "w 查看", "_watch", section="后台 agent 生命周期:", help_lines=(
92
92
  " w 查看 timeline(只读)",
93
93
  )),
94
- Key(("R",), "R 重启", "_key_respawn", section="后台 agent 生命周期:", help=(
94
+ Key(("R",), "R 重启", "_key_respawn", section="后台 agent 生命周期:", help_lines=(
95
95
  " R 重启(respawn)",
96
96
  )),
97
97
  Key(("r",), None, "_key_refresh", needs_selection=False,
98
- section="后台 agent 生命周期:", help=(
98
+ section="后台 agent 生命周期:", help_lines=(
99
99
  " r 刷新",
100
100
  )),
101
101
  Key(("?",), "? 详细说明", "_show_help", needs_selection=False),
@@ -126,12 +126,15 @@ class AgentsView(ListTabView):
126
126
  # --- TabView contract ---
127
127
 
128
128
  def keyhints(self) -> str:
129
- if self._mode in ("help", "watch"):
129
+ if self._overlay_active():
130
130
  # "其余" is honest: the prefix's Tab/q stay global (Tab switches
131
131
  # tabs, q QUITS — neither returns to the list).
132
132
  return "其余任意键返回"
133
133
  return footer_hints(self.KEY_TABLE)
134
134
 
135
+ def _overlay_active(self) -> bool:
136
+ return self._mode in ("help", "watch")
137
+
135
138
  def _enrich(self, jobs: list[AgentJob]) -> list[AgentJob]:
136
139
  """Fill host liveness for the self-fetch path (snapshot already enriched).
137
140
 
@@ -185,14 +188,7 @@ class AgentsView(ListTabView):
185
188
  return widget.job
186
189
  return None
187
190
 
188
- # --- key dispatch ---
189
-
190
- def handle_key(self, key: str) -> None:
191
- if self._mode in ("help", "watch"):
192
- self._handle_overlay_key(key)
193
- return
194
- # Normal list mode — dispatch straight from KEY_TABLE.
195
- self._dispatch_key(key)
191
+ # --- key handlers (bound by name in KEY_TABLE; dispatch lives in the base) ---
196
192
 
197
193
  def _key_respawn(self, job: AgentJob) -> None:
198
194
  cmd = agent_ops.respawn(job)
@@ -138,35 +138,35 @@ class RCView(ListTabView):
138
138
  # App-level FOOTER_PREFIX, so its entry is hint-less.
139
139
  KEY_TABLE = (
140
140
  Key(("t",), "t 新建会话", "_key_tmux_new",
141
- section="项目操作(仅对「项目」行生效):", help=(
141
+ section="项目操作(仅对「项目」行生效):", help_lines=(
142
142
  " t 在项目目录新建 tmux claude 会话并直接进入(离开 csctl)",
143
143
  )),
144
144
  Key(("enter",), "Enter 启动远控", "_key_start",
145
- section="项目操作(仅对「项目」行生效):", help=(
145
+ section="项目操作(仅对「项目」行生效):", help_lines=(
146
146
  " Enter 启动选中项目的远程控制服务",
147
147
  )),
148
148
  Key(("s",), "s 停止", "_key_stop",
149
- section="项目操作(仅对「项目」行生效):", help=(
149
+ section="项目操作(仅对「项目」行生效):", help_lines=(
150
150
  " s 停止选中项目的远程控制服务(需确认)",
151
151
  )),
152
152
  Key(("a",), "a 开机自启", "_key_autostart",
153
- section="项目操作(仅对「项目」行生效):", help=(
153
+ section="项目操作(仅对「项目」行生效):", help_lines=(
154
154
  " a 切换「开机自启」:A 键一键启动时是否带上本项目",
155
155
  )),
156
156
  Key(("c",), "c 自动远控", "_key_rc_toggle",
157
- section="项目操作(仅对「项目」行生效):", help=(
157
+ section="项目操作(仅对「项目」行生效):", help_lines=(
158
158
  " c 切换「自动远控」:claude 启动时自动开远程控制,手机即可接管",
159
159
  )),
160
160
  Key(("A",), "A 全部启动", "_key_start_all", needs_selection=False,
161
- section="批量操作:", help=(
161
+ section="批量操作:", help_lines=(
162
162
  " A 启动所有「开机自启」项目",
163
163
  )),
164
164
  Key(("S",), "S 全部停止", "_key_stop_all", needs_selection=False,
165
- section="批量操作:", help=(
165
+ section="批量操作:", help_lines=(
166
166
  " S 停止全部远程控制服务(需确认)",
167
167
  )),
168
168
  Key(("r",), None, "_key_refresh", needs_selection=False,
169
- section="批量操作:", help=(
169
+ section="批量操作:", help_lines=(
170
170
  " r 重新扫描刷新",
171
171
  )),
172
172
  Key(("?",), "? 详细说明", "_show_help", needs_selection=False),
@@ -198,7 +198,7 @@ class RCView(ListTabView):
198
198
  self._help = False
199
199
 
200
200
  def keyhints(self) -> str:
201
- if self._help:
201
+ if self._overlay_active():
202
202
  # "其余" is honest: the prefix's Tab/q stay global (Tab switches
203
203
  # tabs, q QUITS — neither returns to the list).
204
204
  return "其余任意键返回"
@@ -206,6 +206,9 @@ class RCView(ListTabView):
206
206
  # footer Text wraps on narrow terminals (vertical for width).
207
207
  return footer_hints(self.KEY_TABLE)
208
208
 
209
+ def _overlay_active(self) -> bool:
210
+ return self._help
211
+
209
212
  def load(self) -> None:
210
213
  self._projects = rc.scan()
211
214
  self._servers = rc.scan_servers()
@@ -268,14 +271,7 @@ class RCView(ListTabView):
268
271
  return widget.project
269
272
  return None
270
273
 
271
- def handle_key(self, key: str) -> None:
272
- if self._help:
273
- self._handle_overlay_key(key)
274
- return
275
- # Normal list mode — dispatch straight from KEY_TABLE.
276
- self._dispatch_key(key)
277
-
278
- # --- key handlers (bound by name in KEY_TABLE) ---
274
+ # --- key handlers (bound by name in KEY_TABLE; dispatch lives in the base) ---
279
275
 
280
276
  def _key_tmux_new(self, p: RCProject) -> None:
281
277
  if not p.dir_exists:
@@ -48,44 +48,44 @@ class SessionsView(CleanupMixin, ListTabView):
48
48
  # table in the footer is a user preference (2026-07-05); `r 刷新` stays in
49
49
  # the App-level FOOTER_PREFIX, so its entry is hint-less here.
50
50
  KEY_TABLE = (
51
- Key(("enter",), "Enter 接回", "_key_resume", section="会话操作:", help=(
51
+ Key(("enter",), "Enter 接回", "_key_resume", section="会话操作:", help_lines=(
52
52
  " Enter 接回选中的会话(在当前终端恢复;接运行中的会话会先确认接管)",
53
53
  )),
54
- Key(("t",), "t tmux接回", "_key_tmux", section="会话操作:", help=(
54
+ Key(("t",), "t tmux接回", "_key_tmux", section="会话操作:", help_lines=(
55
55
  " t tmux 接回(会话恢复进 tmux 窗口并接入前台——终端断线会话不死,",
56
56
  " 重连后 tmux attach 可捡回;已在 tmux 中的会话直接接入不重启;",
57
57
  " 接运行中的裸终端会话会先确认接管)",
58
58
  )),
59
- Key(("f",), "f 分叉", "_key_fork", section="会话操作:", help=(
59
+ Key(("f",), "f 分叉", "_key_fork", section="会话操作:", help_lines=(
60
60
  " f 分叉会话(创建副本后接回,不影响原会话)",
61
61
  )),
62
- Key(("s",), "s 停止", "_key_stop", section="会话操作:", help=(
62
+ Key(("s",), "s 停止", "_key_stop", section="会话操作:", help_lines=(
63
63
  " s 停止运行中的会话(发送 SIGTERM,需二次确认)",
64
64
  )),
65
- Key(("R",), "R 转后台", "_key_relaunch", section="会话操作:", help=(
65
+ Key(("R",), "R 转后台", "_key_relaunch", section="会话操作:", help_lines=(
66
66
  " R 转入 tmux 后台并开启远控(脱离终端,手机/网页可接管;",
67
67
  " 接运行中的会话会先确认接管)",
68
68
  )),
69
- Key(("d",), "d 删除", "_key_delete", section="会话操作:", help=(
69
+ Key(("d",), "d 删除", "_key_delete", section="会话操作:", help_lines=(
70
70
  " d 删除已结束的会话记录",
71
71
  )),
72
- Key(("y",), "y 复制命令", "_key_yank", section="会话操作:", help=(
72
+ Key(("y",), "y 复制命令", "_key_yank", section="会话操作:", help_lines=(
73
73
  " y 复制接回命令到剪贴板",
74
74
  )),
75
75
  Key(("h",), "h 桥接显隐", "_key_toggle_hidden", needs_selection=False,
76
- section="会话操作:", help=(
76
+ section="会话操作:", help_lines=(
77
77
  " h 显示/隐藏桥接、SDK 会话",
78
78
  )),
79
79
  Key(("c",), "c 清理", "_enter_cleanup", needs_selection=False,
80
- section="清理与过滤:", help=(
80
+ section="清理与过滤:", help_lines=(
81
81
  " c 打开清理子菜单",
82
82
  )),
83
83
  Key(("/",), "/ 过滤", "_enter_filter", needs_selection=False,
84
- section="清理与过滤:", help=(
84
+ section="清理与过滤:", help_lines=(
85
85
  " / 按关键词过滤会话列表",
86
86
  )),
87
87
  Key(("r",), None, "_key_refresh", needs_selection=False,
88
- section="清理与过滤:", help=(
88
+ section="清理与过滤:", help_lines=(
89
89
  " r 刷新",
90
90
  )),
91
91
  Key(("?",), "? 详细说明", "_show_help", needs_selection=False),
@@ -127,7 +127,7 @@ class SessionsView(CleanupMixin, ListTabView):
127
127
  self._cleanup_walker = urwid.SimpleFocusListWalker([])
128
128
 
129
129
  def keyhints(self) -> str:
130
- if self._mode == "help":
130
+ if self._overlay_active():
131
131
  # "其余" is honest: the prefix's Tab/q stay global (Tab switches
132
132
  # tabs, q QUITS — neither returns to the list).
133
133
  return "其余任意键返回"
@@ -362,11 +362,12 @@ class SessionsView(CleanupMixin, ListTabView):
362
362
 
363
363
  # --- Key dispatch ---
364
364
 
365
- def handle_key(self, key: str) -> None:
366
- if self._mode == "help":
367
- self._handle_overlay_key(key)
368
- return
365
+ def _overlay_active(self) -> bool:
366
+ return self._mode == "help"
369
367
 
368
+ def handle_key(self, key: str) -> None:
369
+ """Extra modes first (filter/preview/cleanup are Sessions-only); the
370
+ help overlay + list dispatch fall through to the base handle_key."""
370
371
  if self._mode == "filter":
371
372
  if key == "enter":
372
373
  self._exit_filter()
@@ -398,8 +399,8 @@ class SessionsView(CleanupMixin, ListTabView):
398
399
  self.app.refresh_with_notice()
399
400
  return
400
401
 
401
- # Normal list mode dispatch straight from KEY_TABLE.
402
- self._dispatch_key(key)
402
+ # Help overlay + normal list mode: the base handle_key.
403
+ super().handle_key(key)
403
404
 
404
405
  # --- key handlers (bound by name in KEY_TABLE) ---
405
406
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-control
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: TUI manager for Claude Code sessions and Remote Control
5
5
  Author: dzshzx
6
6
  License-Expression: MIT