cc-session-control 0.7.0__tar.gz → 0.7.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.
- {cc_session_control-0.7.0/src/cc_session_control.egg-info → cc_session_control-0.7.2}/PKG-INFO +3 -1
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/README.md +2 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/__init__.py +1 -1
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/app.py +6 -23
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/cli.py +9 -3
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/config.py +2 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/rc.py +10 -1
- cc_session_control-0.7.2/src/cc_session_control/theme.py +189 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/rc.py +2 -2
- {cc_session_control-0.7.0 → cc_session_control-0.7.2/src/cc_session_control.egg-info}/PKG-INFO +3 -1
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/SOURCES.txt +2 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_cli.py +14 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_rc.py +11 -5
- cc_session_control-0.7.2/tests/test_theme.py +178 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/LICENSE +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/pyproject.toml +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/setup.cfg +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/__main__.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/__init__.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/agent_ops.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/resume_list.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/session_ops.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/skill_ops.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/clipboard.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/__init__.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/cleanup.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/environments.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/liveness.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/proc.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/registry.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/sessions.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/snapshot.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/tmux.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/models.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/skill/SKILL.md +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/__init__.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_base.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_colspec.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_confirm.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_keytable.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_rows.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_session_row.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_sessions_cleanup.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/agents.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/sessions.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/dependency_links.txt +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/entry_points.txt +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/requires.txt +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/top_level.txt +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_agent_ops.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_agents.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_app.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_cleanup.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_data.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_environments.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_liveness.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_proc.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_registry.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_resume_list.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_sessions.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_skill_ops.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_smoke.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_snapshot.py +0 -0
- {cc_session_control-0.7.0 → cc_session_control-0.7.2}/tests/test_views.py +0 -0
{cc_session_control-0.7.0/src/cc_session_control.egg-info → cc_session_control-0.7.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cc-session-control
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: TUI manager for Claude Code sessions and Remote Control
|
|
5
5
|
Author: dzshzx
|
|
6
6
|
License-Expression: MIT
|
|
@@ -104,6 +104,7 @@ csctl skill uninstall
|
|
|
104
104
|
|
|
105
105
|
# Options
|
|
106
106
|
csctl --workspace ~/projects # Override workspace root
|
|
107
|
+
csctl --theme light # Force the TUI palette (auto/dark/light)
|
|
107
108
|
csctl --version
|
|
108
109
|
```
|
|
109
110
|
|
|
@@ -114,6 +115,7 @@ csctl --version
|
|
|
114
115
|
| `CSCTL_WORKSPACE` | `~/workspace` | Workspace root directory |
|
|
115
116
|
| `CSCTL_RC_SESSION` | `rc` | tmux session name for RC servers |
|
|
116
117
|
| `CSCTL_RC_STAGGER` | `2` | Seconds between starting RC servers |
|
|
118
|
+
| `CSCTL_THEME` | `auto` | TUI palette: `auto` (detect the terminal background via OSC 11 / `$COLORFGBG`) / `dark` / `light`. tmux typically doesn't answer the OSC 11 query, so inside tmux `auto` falls back to `dark` — set this (or `--theme`) explicitly for a light terminal |
|
|
117
119
|
| `XDG_CONFIG_HOME` | `~/.config` | Config directory base |
|
|
118
120
|
|
|
119
121
|
RC auto-start list is stored at `$XDG_CONFIG_HOME/csctl/rc-enabled`.
|
|
@@ -81,6 +81,7 @@ csctl skill uninstall
|
|
|
81
81
|
|
|
82
82
|
# Options
|
|
83
83
|
csctl --workspace ~/projects # Override workspace root
|
|
84
|
+
csctl --theme light # Force the TUI palette (auto/dark/light)
|
|
84
85
|
csctl --version
|
|
85
86
|
```
|
|
86
87
|
|
|
@@ -91,6 +92,7 @@ csctl --version
|
|
|
91
92
|
| `CSCTL_WORKSPACE` | `~/workspace` | Workspace root directory |
|
|
92
93
|
| `CSCTL_RC_SESSION` | `rc` | tmux session name for RC servers |
|
|
93
94
|
| `CSCTL_RC_STAGGER` | `2` | Seconds between starting RC servers |
|
|
95
|
+
| `CSCTL_THEME` | `auto` | TUI palette: `auto` (detect the terminal background via OSC 11 / `$COLORFGBG`) / `dark` / `light`. tmux typically doesn't answer the OSC 11 query, so inside tmux `auto` falls back to `dark` — set this (or `--theme`) explicitly for a light terminal |
|
|
94
96
|
| `XDG_CONFIG_HOME` | `~/.config` | Config directory base |
|
|
95
97
|
|
|
96
98
|
RC auto-start list is stored at `$XDG_CONFIG_HOME/csctl/rc-enabled`.
|
|
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
|
10
10
|
|
|
11
11
|
import urwid
|
|
12
12
|
|
|
13
|
+
from . import theme
|
|
13
14
|
from .data import proc
|
|
14
15
|
from .data.snapshot import WorldSnapshot, build_world_snapshot
|
|
15
16
|
from .views.agents import AgentsView
|
|
@@ -47,28 +48,6 @@ class TabView(Protocol):
|
|
|
47
48
|
def handle_key(self, key: str) -> None: ...
|
|
48
49
|
def captures_text(self) -> bool: ... # True while a text mode owns EVERY key (incl. tab/q)
|
|
49
50
|
|
|
50
|
-
# 6-tuple: (name, fg_16, bg_16, mono, fg_256, bg_256)
|
|
51
|
-
# ONE semantic set — views reference these names only (no per-tab duplicates;
|
|
52
|
-
# the old rc_running/rc_stopped were aliases of alive/dead and are gone).
|
|
53
|
-
# 256-color foregrounds are kept ≥ 4.5:1 against their background (WCAG-AA-ish,
|
|
54
|
-
# checked via relative luminance): #aaa/#111 ≈ 8.1, #7ab/#111 ≈ 7.4,
|
|
55
|
-
# #9cc/#181818 ≈ 10.0, #bbb/#111 ≈ 9.8, #d66/#111 ≈ 5.5.
|
|
56
|
-
PALETTE = [
|
|
57
|
-
("header", "white,bold", "black", "bold", "#fff,bold", "#111"),
|
|
58
|
-
("footer", "light gray", "black", None, "#aaa", "#111"),
|
|
59
|
-
("tab_on", "white,bold", "dark cyan", "bold,standout", "#fff,bold", "#068"),
|
|
60
|
-
("tab_off", "light cyan", "black", None, "#7ab", "#111"),
|
|
61
|
-
("alive", "light green", "black", None, "#6d6", "#111"),
|
|
62
|
-
("status_busy", "light green,bold", "black", "bold", "#6d6,bold", "#111"),
|
|
63
|
-
("status_err", "light red", "black", None, "#d66", "#111"),
|
|
64
|
-
("dead", "light gray", "black", None, "#ccc", "#111"),
|
|
65
|
-
("selected", "white,bold", "dark cyan", "standout", "#fff,bold", "#068"),
|
|
66
|
-
("notify", "yellow,bold", "black", "bold", "#ff0,bold", "#111"),
|
|
67
|
-
("status", "light gray", "black", None, "#bbb", "#111"),
|
|
68
|
-
("body", "light gray", "black", None, "#ccc", "#111"),
|
|
69
|
-
("col_header", "dark cyan", "black", None, "#9cc", "#181818"),
|
|
70
|
-
]
|
|
71
|
-
|
|
72
51
|
# Launcher-first order (ADR-0001): startup lands on 项目 (the tmux-first
|
|
73
52
|
# dispatch entry), then 会话 / 后台. In lockstep with `self.views` below.
|
|
74
53
|
TAB_NAMES = ["项目", "会话", "后台"]
|
|
@@ -80,6 +59,10 @@ FOOTER_PREFIX = " Tab 切换 · q 退出 · r 刷新 · "
|
|
|
80
59
|
|
|
81
60
|
|
|
82
61
|
def _make_screen() -> urwid.raw_display.Screen:
|
|
62
|
+
# The theme query (OSC 11) runs here, before `loop.run()` puts the tty
|
|
63
|
+
# into urwid's hands; the palette itself lives in `theme.py` (ONE semantic
|
|
64
|
+
# attr set, dark/light generated from a single spec).
|
|
65
|
+
mode = theme.detect_mode()
|
|
83
66
|
screen = urwid.raw_display.Screen()
|
|
84
67
|
try:
|
|
85
68
|
curses.setupterm()
|
|
@@ -88,7 +71,7 @@ def _make_screen() -> urwid.raw_display.Screen:
|
|
|
88
71
|
screen.set_terminal_properties(colors=256)
|
|
89
72
|
except Exception:
|
|
90
73
|
pass
|
|
91
|
-
screen.register_palette(
|
|
74
|
+
screen.register_palette(theme.palette(mode))
|
|
92
75
|
return screen
|
|
93
76
|
|
|
94
77
|
|
|
@@ -17,6 +17,10 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
17
17
|
)
|
|
18
18
|
parser.add_argument("--version", action="version", version=f"csctl {__version__}")
|
|
19
19
|
parser.add_argument("--workspace", type=Path, help="Override workspace root directory")
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--theme", choices=("auto", "dark", "light"),
|
|
22
|
+
help="TUI palette (default: auto-detect the terminal background; env CSCTL_THEME)",
|
|
23
|
+
)
|
|
20
24
|
|
|
21
25
|
sub = parser.add_subparsers(dest="command")
|
|
22
26
|
|
|
@@ -67,10 +71,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
67
71
|
return parser
|
|
68
72
|
|
|
69
73
|
|
|
70
|
-
def
|
|
74
|
+
def _apply_global_flags(args: argparse.Namespace) -> None:
|
|
75
|
+
from .config import cfg
|
|
71
76
|
if args.workspace:
|
|
72
|
-
from .config import cfg
|
|
73
77
|
cfg.workspace = args.workspace
|
|
78
|
+
if args.theme:
|
|
79
|
+
cfg.theme = args.theme
|
|
74
80
|
|
|
75
81
|
|
|
76
82
|
def _cmd_rc(args: argparse.Namespace) -> None:
|
|
@@ -311,7 +317,7 @@ def _cmd_tui(args: argparse.Namespace) -> None:
|
|
|
311
317
|
def main() -> None:
|
|
312
318
|
parser = _build_parser()
|
|
313
319
|
args = parser.parse_args()
|
|
314
|
-
|
|
320
|
+
_apply_global_flags(args)
|
|
315
321
|
|
|
316
322
|
if args.command == "rc":
|
|
317
323
|
_cmd_rc(args)
|
|
@@ -18,6 +18,8 @@ class Config:
|
|
|
18
18
|
self.rc_stagger: int = int(os.environ.get("CSCTL_RC_STAGGER", "2"))
|
|
19
19
|
# Age threshold (days) for the time/global-keyed cleanup strategy.
|
|
20
20
|
self.cleanup_age_days: int = int(os.environ.get("CSCTL_CLEANUP_AGE_DAYS", "14"))
|
|
21
|
+
# TUI palette: "auto" (detect the terminal background) | "dark" | "light".
|
|
22
|
+
self.theme: str = os.environ.get("CSCTL_THEME", "auto")
|
|
21
23
|
self._workspace: Path | None = None
|
|
22
24
|
|
|
23
25
|
@property
|
|
@@ -183,6 +183,15 @@ def scan() -> list[RCProject]:
|
|
|
183
183
|
for name in all_names:
|
|
184
184
|
directory = str(cfg.workspace / name)
|
|
185
185
|
in_windows = name in windows
|
|
186
|
+
dir_exists = os.path.isdir(directory)
|
|
187
|
+
if not dir_exists and name not in enabled and not in_windows:
|
|
188
|
+
# Pure trust residue: the directory is gone and only claude's own
|
|
189
|
+
# trust record (~/.claude.json) still references it. csctl can't
|
|
190
|
+
# act on it (no start, and it never edits claude's files), so it
|
|
191
|
+
# is dropped instead of rendered as a ✖ 缺失 row. Missing-dir
|
|
192
|
+
# projects that ARE actionable (in the autostart list, or with a
|
|
193
|
+
# live/dead tmux window) stay listed.
|
|
194
|
+
continue
|
|
186
195
|
if in_windows:
|
|
187
196
|
status = "running" if _is_alive(name) else "dead"
|
|
188
197
|
else:
|
|
@@ -195,7 +204,7 @@ def scan() -> list[RCProject]:
|
|
|
195
204
|
auto_start=name in enabled,
|
|
196
205
|
rc_at_startup=_read_rc_at_startup(directory),
|
|
197
206
|
spawn_mode=_read_spawn_mode(name),
|
|
198
|
-
dir_exists=
|
|
207
|
+
dir_exists=dir_exists,
|
|
199
208
|
))
|
|
200
209
|
return result
|
|
201
210
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Terminal-theme seam: background detection + the dark/light palettes.
|
|
2
|
+
|
|
3
|
+
Top-level helper like `clipboard.py` — it touches only the controlling tty
|
|
4
|
+
(an OSC 11 background-color query) and the process env; `app._make_screen`
|
|
5
|
+
is the sole consumer. Views keep referencing the ONE semantic attr set; this
|
|
6
|
+
module only decides which fg/bg pair each name resolves to.
|
|
7
|
+
|
|
8
|
+
Detection order (`detect_mode`): explicit `CSCTL_THEME` (dark/light) →
|
|
9
|
+
OSC 11 tty query → `$COLORFGBG` → dark. The query MUST run before urwid
|
|
10
|
+
takes over the tty — `App.__init__` builds the screen before `loop.run()`,
|
|
11
|
+
so stdin is still in normal mode there.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import select
|
|
19
|
+
import sys
|
|
20
|
+
import termios
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
from .config import cfg
|
|
24
|
+
|
|
25
|
+
Mode = str # "light" | anything else = dark
|
|
26
|
+
|
|
27
|
+
# (name, mono, dark (fg16, bg16, fg256, bg256), light (fg16, bg16, fg256, bg256))
|
|
28
|
+
# ONE semantic set, table-driven like `_colspec`/`_keytable`: both palettes are
|
|
29
|
+
# generated from this single spec so their attr names can never diverge.
|
|
30
|
+
# Body-content attrs use bg "default" (inherit the terminal's own background —
|
|
31
|
+
# the adaptive part); only the structural bands (header/footer/tabs/status/
|
|
32
|
+
# notify) and the selection keep an explicit bg. 256-color foregrounds stay
|
|
33
|
+
# ≥ 4.5:1 (WCAG-AA-ish, relative luminance) against the assumed backdrop —
|
|
34
|
+
# dark: near-black (#6d6/#d66 ≈ 5.5+, #aaa ≈ 8.1); light: near-white
|
|
35
|
+
# (#070 ≈ 5.7, #a00 ≈ 7.7, #666 ≈ 5.7, #850/#ddd ≈ 4.7).
|
|
36
|
+
_SPEC: list[tuple[str, str | None, tuple[str, str, str, str], tuple[str, str, str, str]]] = [
|
|
37
|
+
("header", "bold",
|
|
38
|
+
("white,bold", "black", "#fff,bold", "#111"),
|
|
39
|
+
("black,bold", "light gray", "#000,bold", "#ddd")),
|
|
40
|
+
("footer", None,
|
|
41
|
+
("light gray", "black", "#aaa", "#111"),
|
|
42
|
+
("dark gray", "light gray", "#555", "#ddd")),
|
|
43
|
+
("tab_on", "bold,standout",
|
|
44
|
+
("white,bold", "dark cyan", "#fff,bold", "#068"),
|
|
45
|
+
("white,bold", "dark cyan", "#fff,bold", "#068")),
|
|
46
|
+
("tab_off", None,
|
|
47
|
+
("light cyan", "black", "#7ab", "#111"),
|
|
48
|
+
("dark cyan", "light gray", "#067", "#ddd")),
|
|
49
|
+
("alive", None,
|
|
50
|
+
("light green", "default", "#6d6", "default"),
|
|
51
|
+
("dark green", "default", "#070", "default")),
|
|
52
|
+
("status_busy", "bold",
|
|
53
|
+
("light green,bold", "default", "#6d6,bold", "default"),
|
|
54
|
+
("dark green,bold", "default", "#070,bold", "default")),
|
|
55
|
+
("status_err", None,
|
|
56
|
+
("light red", "default", "#d66", "default"),
|
|
57
|
+
("dark red", "default", "#a00", "default")),
|
|
58
|
+
("dead", None,
|
|
59
|
+
("light gray", "default", "#ccc", "default"),
|
|
60
|
+
("dark gray", "default", "#666", "default")),
|
|
61
|
+
("selected", "standout",
|
|
62
|
+
("white,bold", "dark cyan", "#fff,bold", "#068"),
|
|
63
|
+
("white,bold", "dark cyan", "#fff,bold", "#068")),
|
|
64
|
+
("notify", "bold",
|
|
65
|
+
("yellow,bold", "black", "#ff0,bold", "#111"),
|
|
66
|
+
("brown,bold", "light gray", "#850,bold", "#ddd")),
|
|
67
|
+
("status", None,
|
|
68
|
+
("light gray", "black", "#bbb", "#111"),
|
|
69
|
+
("dark gray", "light gray", "#444", "#ddd")),
|
|
70
|
+
("body", None,
|
|
71
|
+
("light gray", "default", "#ccc", "default"),
|
|
72
|
+
("black", "default", "#222", "default")),
|
|
73
|
+
("col_header", None,
|
|
74
|
+
("dark cyan", "default", "#9cc", "default"),
|
|
75
|
+
("dark cyan", "default", "#067", "default")),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def palette(mode: Mode) -> list[tuple[str, str, str, str | None, str, str]]:
|
|
80
|
+
"""The urwid 6-tuple palette (name, fg16, bg16, mono, fg256, bg256)."""
|
|
81
|
+
out = []
|
|
82
|
+
for name, mono, dark, light in _SPEC:
|
|
83
|
+
fg16, bg16, fg256, bg256 = light if mode == "light" else dark
|
|
84
|
+
out.append((name, fg16, bg16, mono, fg256, bg256))
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def detect_mode() -> Mode:
|
|
89
|
+
"""`CSCTL_THEME` override → OSC 11 query → `$COLORFGBG` → dark."""
|
|
90
|
+
forced = cfg.theme.strip().lower()
|
|
91
|
+
if forced in ("dark", "light"):
|
|
92
|
+
return forced
|
|
93
|
+
rgb = _query_bg_rgb()
|
|
94
|
+
if rgb is not None:
|
|
95
|
+
return _mode_from_rgb(*rgb)
|
|
96
|
+
return _parse_colorfgbg(os.environ.get("COLORFGBG", "")) or "dark"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Reply looks like `ESC]11;rgb:1111/1111/1111 BEL` (xterm also emits `rgba:`
|
|
100
|
+
# and 1-4 hex digits per channel; terminator is BEL or ST `ESC \`).
|
|
101
|
+
_OSC11_RGB = re.compile(r"rgba?:([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})")
|
|
102
|
+
# OSC 11 query + DA1 (Primary Device Attributes) as a SENTINEL: terminals
|
|
103
|
+
# answer queries in order and virtually every terminal answers DA1, so its
|
|
104
|
+
# reply (`ESC[?...c`) means "the OSC 11 answer, if any, has already arrived".
|
|
105
|
+
# Terminals that ignore OSC 11 (tmux 3.7 without an explicit bg — measured
|
|
106
|
+
# 0.3ms — the common csctl case) return in one round-trip instead of eating
|
|
107
|
+
# the full timeout on EVERY startup. Reading through to the DA1 reply also
|
|
108
|
+
# keeps the tty buffer clean: no reply bytes are left over to leak into urwid
|
|
109
|
+
# as phantom keys.
|
|
110
|
+
_QUERY = b"\x1b]11;?\x07\x1b[c"
|
|
111
|
+
_DA1_REPLY = re.compile(r"\x1b\[\?[0-9;]*c")
|
|
112
|
+
# Hard cap only — with the sentinel, any real terminal answers in one RTT;
|
|
113
|
+
# this bounds fake ptys that answer neither query.
|
|
114
|
+
_REPLY_TIMEOUT = 0.25
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _parse_osc11_reply(buf: str) -> tuple[float, float, float] | None:
|
|
118
|
+
m = _OSC11_RGB.search(buf)
|
|
119
|
+
if not m:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def chan(h: str) -> float:
|
|
123
|
+
return int(h, 16) / (16 ** len(h) - 1)
|
|
124
|
+
|
|
125
|
+
return chan(m.group(1)), chan(m.group(2)), chan(m.group(3))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _mode_from_rgb(r: float, g: float, b: float) -> Mode:
|
|
129
|
+
lum = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
130
|
+
return "light" if lum > 0.5 else "dark"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _parse_colorfgbg(value: str) -> Mode | None:
|
|
134
|
+
"""rxvt/konsole convention: "fg;bg" or "fg;default;bg".
|
|
135
|
+
|
|
136
|
+
vim's rule for the bg code: 0-6 and 8 mean a dark background."""
|
|
137
|
+
parts = value.split(";")
|
|
138
|
+
if len(parts) < 2:
|
|
139
|
+
return None
|
|
140
|
+
try:
|
|
141
|
+
bg = int(parts[-1])
|
|
142
|
+
except ValueError:
|
|
143
|
+
return None
|
|
144
|
+
return "dark" if bg in (0, 1, 2, 3, 4, 5, 6, 8) else "light"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _query_bg_rgb(timeout: float = _REPLY_TIMEOUT) -> tuple[float, float, float] | None:
|
|
148
|
+
"""Ask the terminal its background color via OSC 11; None on any failure."""
|
|
149
|
+
try:
|
|
150
|
+
if not (sys.stdin.isatty() and sys.stdout.isatty()):
|
|
151
|
+
return None
|
|
152
|
+
return _query_bg_rgb_on(sys.stdin.fileno(), sys.stdout.fileno(), timeout)
|
|
153
|
+
except Exception:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _query_bg_rgb_on(
|
|
158
|
+
in_fd: int, out_fd: int, timeout: float
|
|
159
|
+
) -> tuple[float, float, float] | None:
|
|
160
|
+
"""The fd-level query — separated so tests can drive a bare pty pair
|
|
161
|
+
(pytest's capture owns `sys.stdin`/`sys.stdout` during a test, so the
|
|
162
|
+
wrapper above cannot be exercised end-to-end). May raise; the wrapper
|
|
163
|
+
swallows."""
|
|
164
|
+
old = termios.tcgetattr(in_fd)
|
|
165
|
+
try:
|
|
166
|
+
# Raw enough to read the reply: no line buffering, no echo (the reply
|
|
167
|
+
# bytes must not be painted onto the screen). Set directly instead of
|
|
168
|
+
# via tty.setcbreak, whose ECHO handling changed across 3.12.x.
|
|
169
|
+
new = termios.tcgetattr(in_fd)
|
|
170
|
+
new[3] &= ~(termios.ICANON | termios.ECHO)
|
|
171
|
+
termios.tcsetattr(in_fd, termios.TCSANOW, new)
|
|
172
|
+
os.write(out_fd, _QUERY)
|
|
173
|
+
buf = ""
|
|
174
|
+
deadline = time.monotonic() + timeout
|
|
175
|
+
# Stop on the DA1 sentinel, NOT on the OSC terminator — see _QUERY.
|
|
176
|
+
while not _DA1_REPLY.search(buf):
|
|
177
|
+
remaining = deadline - time.monotonic()
|
|
178
|
+
if remaining <= 0:
|
|
179
|
+
break
|
|
180
|
+
ready, _, _ = select.select([in_fd], [], [], remaining)
|
|
181
|
+
if not ready:
|
|
182
|
+
break
|
|
183
|
+
buf += os.read(in_fd, 128).decode("ascii", "ignore")
|
|
184
|
+
return _parse_osc11_reply(buf)
|
|
185
|
+
finally:
|
|
186
|
+
try:
|
|
187
|
+
termios.tcsetattr(in_fd, termios.TCSADRAIN, old)
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
@@ -179,8 +179,8 @@ class RCView(ListTabView):
|
|
|
179
179
|
sections=("项目操作(仅对「项目」行生效):", "批量操作:"),
|
|
180
180
|
suffix=(
|
|
181
181
|
"目录缺失(✖ 缺失):",
|
|
182
|
-
"
|
|
183
|
-
"
|
|
182
|
+
" 项目目录已删除,但自启列表仍引用它(或其远控服务还在跑);用 a 键移出自启列表。",
|
|
183
|
+
" 只剩 claude 信任记录(~/.claude.json)引用的已删项目不再显示;信任记录需手动清理。",
|
|
184
184
|
"",
|
|
185
185
|
"RC 服务(只读):",
|
|
186
186
|
" 外部服务只展示,不接管、不重启。",
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2/src/cc_session_control.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cc-session-control
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: TUI manager for Claude Code sessions and Remote Control
|
|
5
5
|
Author: dzshzx
|
|
6
6
|
License-Expression: MIT
|
|
@@ -104,6 +104,7 @@ csctl skill uninstall
|
|
|
104
104
|
|
|
105
105
|
# Options
|
|
106
106
|
csctl --workspace ~/projects # Override workspace root
|
|
107
|
+
csctl --theme light # Force the TUI palette (auto/dark/light)
|
|
107
108
|
csctl --version
|
|
108
109
|
```
|
|
109
110
|
|
|
@@ -114,6 +115,7 @@ csctl --version
|
|
|
114
115
|
| `CSCTL_WORKSPACE` | `~/workspace` | Workspace root directory |
|
|
115
116
|
| `CSCTL_RC_SESSION` | `rc` | tmux session name for RC servers |
|
|
116
117
|
| `CSCTL_RC_STAGGER` | `2` | Seconds between starting RC servers |
|
|
118
|
+
| `CSCTL_THEME` | `auto` | TUI palette: `auto` (detect the terminal background via OSC 11 / `$COLORFGBG`) / `dark` / `light`. tmux typically doesn't answer the OSC 11 query, so inside tmux `auto` falls back to `dark` — set this (or `--theme`) explicitly for a light terminal |
|
|
117
119
|
| `XDG_CONFIG_HOME` | `~/.config` | Config directory base |
|
|
118
120
|
|
|
119
121
|
RC auto-start list is stored at `$XDG_CONFIG_HOME/csctl/rc-enabled`.
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/SOURCES.txt
RENAMED
|
@@ -8,6 +8,7 @@ src/cc_session_control/cli.py
|
|
|
8
8
|
src/cc_session_control/clipboard.py
|
|
9
9
|
src/cc_session_control/config.py
|
|
10
10
|
src/cc_session_control/models.py
|
|
11
|
+
src/cc_session_control/theme.py
|
|
11
12
|
src/cc_session_control.egg-info/PKG-INFO
|
|
12
13
|
src/cc_session_control.egg-info/SOURCES.txt
|
|
13
14
|
src/cc_session_control.egg-info/dependency_links.txt
|
|
@@ -57,4 +58,5 @@ tests/test_sessions.py
|
|
|
57
58
|
tests/test_skill_ops.py
|
|
58
59
|
tests/test_smoke.py
|
|
59
60
|
tests/test_snapshot.py
|
|
61
|
+
tests/test_theme.py
|
|
60
62
|
tests/test_views.py
|
|
@@ -96,3 +96,17 @@ def test_prune_sweep_zombies_refuses_without_proc(tmp_path, monkeypatch, capsys)
|
|
|
96
96
|
out = capsys.readouterr().out
|
|
97
97
|
assert "Refused" in out
|
|
98
98
|
assert os.path.exists(os.path.join(sessions_dir, "1.json")) # nothing removed
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_theme_flag_sets_cfg(monkeypatch):
|
|
102
|
+
monkeypatch.setattr(cfg, "theme", "auto")
|
|
103
|
+
args = cli._build_parser().parse_args(["--theme", "light"])
|
|
104
|
+
cli._apply_global_flags(args)
|
|
105
|
+
assert cfg.theme == "light"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_theme_flag_absent_keeps_cfg(monkeypatch):
|
|
109
|
+
monkeypatch.setattr(cfg, "theme", "auto")
|
|
110
|
+
args = cli._build_parser().parse_args([])
|
|
111
|
+
cli._apply_global_flags(args)
|
|
112
|
+
assert cfg.theme == "auto"
|
|
@@ -204,21 +204,27 @@ def test_scan_populates_spawn_mode(tmp_path, monkeypatch):
|
|
|
204
204
|
|
|
205
205
|
|
|
206
206
|
def test_scan_marks_missing_directory(tmp_path, monkeypatch):
|
|
207
|
-
"""A
|
|
208
|
-
|
|
207
|
+
"""A missing-dir project stays listed (dir_exists=False) only while it is
|
|
208
|
+
still actionable — in the autostart list or holding a tmux window. Pure
|
|
209
|
+
trust residue (only ~/.claude.json references the deleted dir) is dropped:
|
|
210
|
+
csctl can't act on it and never edits claude's files."""
|
|
209
211
|
ws = tmp_path / "workspace"
|
|
210
212
|
(ws / "alive").mkdir(parents=True)
|
|
211
213
|
cj = _write_claude_json(tmp_path, {
|
|
212
214
|
str(ws / "alive"): {"hasTrustDialogAccepted": True},
|
|
213
215
|
str(ws / "deleted"): {"hasTrustDialogAccepted": True},
|
|
216
|
+
str(ws / "gone-running"): {"hasTrustDialogAccepted": True},
|
|
214
217
|
})
|
|
215
218
|
monkeypatch.setattr(rc.cfg, "claude_json", cj)
|
|
216
219
|
monkeypatch.setattr(rc.cfg, "workspace", ws)
|
|
217
220
|
monkeypatch.setattr(rc, "list_enabled", lambda: ["gone-enabled"])
|
|
218
|
-
monkeypatch.setattr(rc, "_tmux_windows", lambda: [])
|
|
221
|
+
monkeypatch.setattr(rc, "_tmux_windows", lambda: ["gone-running"])
|
|
222
|
+
monkeypatch.setattr(rc, "_is_alive", lambda name: True)
|
|
219
223
|
|
|
220
224
|
rows = {p.name: p for p in rc.scan()}
|
|
221
|
-
assert set(rows) == {"alive", "
|
|
225
|
+
assert set(rows) == {"alive", "gone-enabled", "gone-running"}
|
|
226
|
+
assert "deleted" not in rows # trust-only residue hidden
|
|
222
227
|
assert rows["alive"].dir_exists is True
|
|
223
|
-
assert rows["deleted"].dir_exists is False # stale trust entry
|
|
224
228
|
assert rows["gone-enabled"].dir_exists is False # stale rc-enabled entry
|
|
229
|
+
assert rows["gone-running"].dir_exists is False # window survives dir removal
|
|
230
|
+
assert rows["gone-running"].status == "running"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Adaptive terminal theme: palette generation + background detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from cc_session_control import theme
|
|
12
|
+
from cc_session_control.config import cfg
|
|
13
|
+
|
|
14
|
+
# The ONE semantic attr set views reference (see views/*).
|
|
15
|
+
EXPECTED_NAMES = {
|
|
16
|
+
"header", "footer", "tab_on", "tab_off", "alive", "status_busy",
|
|
17
|
+
"status_err", "dead", "selected", "notify", "status", "body", "col_header",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_palettes_share_one_semantic_set() -> None:
|
|
22
|
+
dark = theme.palette("dark")
|
|
23
|
+
light = theme.palette("light")
|
|
24
|
+
assert {e[0] for e in dark} == EXPECTED_NAMES
|
|
25
|
+
assert [e[0] for e in dark] == [e[0] for e in light]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_palette_entries_are_urwid_6_tuples() -> None:
|
|
29
|
+
for entry in theme.palette("dark") + theme.palette("light"):
|
|
30
|
+
assert len(entry) == 6
|
|
31
|
+
name, fg16, bg16, _mono, fg256, bg256 = entry
|
|
32
|
+
for field in (fg16, bg16, fg256, bg256):
|
|
33
|
+
assert isinstance(field, str) and field, (name, field)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_unknown_mode_falls_back_to_dark() -> None:
|
|
37
|
+
assert theme.palette("weird") == theme.palette("dark")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.parametrize(
|
|
41
|
+
("rgb", "expected"),
|
|
42
|
+
[
|
|
43
|
+
((1.0, 1.0, 1.0), "light"), # white
|
|
44
|
+
((0.0, 0.0, 0.0), "dark"), # black
|
|
45
|
+
((0.99, 0.96, 0.89), "light"), # solarized light #fdf6e3
|
|
46
|
+
((0.0, 0.17, 0.21), "dark"), # solarized dark #002b36
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
def test_mode_from_rgb(rgb: tuple[float, float, float], expected: str) -> None:
|
|
50
|
+
assert theme._mode_from_rgb(*rgb) == expected
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize(
|
|
54
|
+
("reply", "expected"),
|
|
55
|
+
[
|
|
56
|
+
("\x1b]11;rgb:ffff/ffff/ffff\x07", (1.0, 1.0, 1.0)),
|
|
57
|
+
("\x1b]11;rgb:0000/0000/0000\x1b\\", (0.0, 0.0, 0.0)),
|
|
58
|
+
("\x1b]11;rgb:ff/00/00\x07", (1.0, 0.0, 0.0)), # 2-digit channels
|
|
59
|
+
("\x1b]11;rgba:ffff/ffff/ffff/ffff\x07", (1.0, 1.0, 1.0)),
|
|
60
|
+
("", None),
|
|
61
|
+
("\x1b]11;?\x07", None), # our own query echoed back
|
|
62
|
+
("garbage without rgb", None),
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
def test_parse_osc11_reply(reply: str, expected: tuple | None) -> None:
|
|
66
|
+
got = theme._parse_osc11_reply(reply)
|
|
67
|
+
if expected is None:
|
|
68
|
+
assert got is None
|
|
69
|
+
else:
|
|
70
|
+
assert got == pytest.approx(expected)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.mark.parametrize(
|
|
74
|
+
("value", "expected"),
|
|
75
|
+
[
|
|
76
|
+
("15;0", "dark"),
|
|
77
|
+
("0;15", "light"),
|
|
78
|
+
("12;8", "dark"),
|
|
79
|
+
("0;default;15", "light"), # 3-field rxvt form: bg is the LAST field
|
|
80
|
+
("0;7", "light"),
|
|
81
|
+
("", None),
|
|
82
|
+
("15", None), # single field — no bg
|
|
83
|
+
("15;default", None), # non-numeric bg
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
def test_parse_colorfgbg(value: str, expected: str | None) -> None:
|
|
87
|
+
assert theme._parse_colorfgbg(value) == expected
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_detect_mode_env_override_skips_probing(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
91
|
+
monkeypatch.setattr(cfg, "theme", "LIGHT")
|
|
92
|
+
|
|
93
|
+
def boom() -> None:
|
|
94
|
+
raise AssertionError("forced theme must not probe the tty")
|
|
95
|
+
|
|
96
|
+
monkeypatch.setattr(theme, "_query_bg_rgb", boom)
|
|
97
|
+
assert theme.detect_mode() == "light"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_detect_mode_uses_osc_reply(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
101
|
+
monkeypatch.setattr(cfg, "theme", "auto")
|
|
102
|
+
monkeypatch.setattr(theme, "_query_bg_rgb", lambda: (1.0, 1.0, 1.0))
|
|
103
|
+
assert theme.detect_mode() == "light"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_detect_mode_colorfgbg_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
107
|
+
monkeypatch.setattr(cfg, "theme", "auto")
|
|
108
|
+
monkeypatch.setattr(theme, "_query_bg_rgb", lambda: None)
|
|
109
|
+
monkeypatch.setenv("COLORFGBG", "0;15")
|
|
110
|
+
assert theme.detect_mode() == "light"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_detect_mode_defaults_dark(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
114
|
+
monkeypatch.setattr(cfg, "theme", "auto")
|
|
115
|
+
monkeypatch.setattr(theme, "_query_bg_rgb", lambda: None)
|
|
116
|
+
monkeypatch.delenv("COLORFGBG", raising=False)
|
|
117
|
+
assert theme.detect_mode() == "dark"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_query_bg_rgb_refuses_non_tty(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
121
|
+
class NotATty:
|
|
122
|
+
def isatty(self) -> bool:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
monkeypatch.setattr(theme.sys, "stdin", NotATty())
|
|
126
|
+
assert theme._query_bg_rgb() is None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# --- end-to-end against a real pty. Tests drive the fd-level
|
|
130
|
+
# `_query_bg_rgb_on` directly: pytest's capture plugin owns
|
|
131
|
+
# `sys.stdin`/`sys.stdout` during a test, so the isatty wrapper can't be
|
|
132
|
+
# exercised here (it is covered by the non-tty refusal test above). The fake
|
|
133
|
+
# terminal answers on the master side. ---
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.fixture
|
|
137
|
+
def pty_pair():
|
|
138
|
+
master, slave = os.openpty()
|
|
139
|
+
yield master, slave
|
|
140
|
+
os.close(master)
|
|
141
|
+
os.close(slave)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _answer(master: int, reply: bytes) -> None:
|
|
145
|
+
os.read(master, 64) # wait until the query arrives
|
|
146
|
+
os.write(master, reply)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_query_bg_rgb_end_to_end(pty_pair: tuple[int, int]) -> None:
|
|
150
|
+
master, slave = pty_pair
|
|
151
|
+
# xterm-style: OSC 11 answer, then the DA1 sentinel reply.
|
|
152
|
+
t = threading.Thread(
|
|
153
|
+
target=_answer, args=(master, b"\x1b]11;rgb:ffff/ffff/ffff\x07\x1b[?1;2c")
|
|
154
|
+
)
|
|
155
|
+
t.start()
|
|
156
|
+
got = theme._query_bg_rgb_on(slave, slave, timeout=5.0)
|
|
157
|
+
t.join()
|
|
158
|
+
assert got == pytest.approx((1.0, 1.0, 1.0))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_query_bg_rgb_da1_sentinel_short_circuits(pty_pair: tuple[int, int]) -> None:
|
|
162
|
+
master, slave = pty_pair
|
|
163
|
+
# tmux-style: OSC 11 ignored, only DA1 answered — must return on the
|
|
164
|
+
# sentinel immediately, NOT sit out the timeout (given generously here so
|
|
165
|
+
# a cap-hit fails loudly even on a slow CI box).
|
|
166
|
+
t = threading.Thread(target=_answer, args=(master, b"\x1b[?1;2;4c"))
|
|
167
|
+
t.start()
|
|
168
|
+
t0 = time.monotonic()
|
|
169
|
+
got = theme._query_bg_rgb_on(slave, slave, timeout=5.0)
|
|
170
|
+
elapsed = time.monotonic() - t0
|
|
171
|
+
t.join()
|
|
172
|
+
assert got is None
|
|
173
|
+
assert elapsed < 2.0
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_query_bg_rgb_times_out_on_silent_pty(pty_pair: tuple[int, int]) -> None:
|
|
177
|
+
_master, slave = pty_pair
|
|
178
|
+
assert theme._query_bg_rgb_on(slave, slave, timeout=0.05) is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/__init__.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/agent_ops.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/resume_list.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/session_ops.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/actions/skill_ops.py
RENAMED
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/__init__.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/cleanup.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/environments.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/liveness.py
RENAMED
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/registry.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/sessions.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/data/snapshot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_colspec.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_confirm.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_keytable.py
RENAMED
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/_session_row.py
RENAMED
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/agents.py
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control/views/sessions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/requires.txt
RENAMED
|
File without changes
|
{cc_session_control-0.7.0 → cc_session_control-0.7.2}/src/cc_session_control.egg-info/top_level.txt
RENAMED
|
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
|