cc-session-control 0.7.1__tar.gz → 0.7.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 (66) hide show
  1. {cc_session_control-0.7.1/src/cc_session_control.egg-info → cc_session_control-0.7.3}/PKG-INFO +9 -8
  2. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/README.md +8 -7
  3. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/__init__.py +1 -1
  4. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/app.py +6 -23
  5. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/cli.py +35 -32
  6. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/config.py +2 -37
  7. cc_session_control-0.7.3/src/cc_session_control/data/rc.py +429 -0
  8. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/tmux.py +43 -25
  9. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/models.py +27 -0
  10. cc_session_control-0.7.3/src/cc_session_control/theme.py +189 -0
  11. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/_base.py +16 -3
  12. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/rc.py +15 -9
  13. {cc_session_control-0.7.1 → cc_session_control-0.7.3/src/cc_session_control.egg-info}/PKG-INFO +9 -8
  14. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control.egg-info/SOURCES.txt +3 -0
  15. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_cli.py +14 -0
  16. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_data.py +58 -26
  17. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_rc.py +72 -48
  18. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_smoke.py +0 -7
  19. cc_session_control-0.7.3/tests/test_theme.py +178 -0
  20. cc_session_control-0.7.3/tests/test_trust.py +181 -0
  21. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_views.py +22 -3
  22. cc_session_control-0.7.1/src/cc_session_control/data/rc.py +0 -324
  23. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/LICENSE +0 -0
  24. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/pyproject.toml +0 -0
  25. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/setup.cfg +0 -0
  26. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/__main__.py +0 -0
  27. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/actions/__init__.py +0 -0
  28. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/actions/agent_ops.py +0 -0
  29. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/actions/resume_list.py +0 -0
  30. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/actions/session_ops.py +0 -0
  31. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/actions/skill_ops.py +0 -0
  32. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/clipboard.py +0 -0
  33. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/__init__.py +0 -0
  34. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/cleanup.py +0 -0
  35. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/environments.py +0 -0
  36. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/liveness.py +0 -0
  37. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/proc.py +0 -0
  38. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/registry.py +0 -0
  39. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/sessions.py +0 -0
  40. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/data/snapshot.py +0 -0
  41. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/skill/SKILL.md +0 -0
  42. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/__init__.py +0 -0
  43. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/_colspec.py +0 -0
  44. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/_confirm.py +0 -0
  45. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/_keytable.py +0 -0
  46. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/_rows.py +0 -0
  47. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/_session_row.py +0 -0
  48. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/_sessions_cleanup.py +0 -0
  49. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/agents.py +0 -0
  50. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control/views/sessions.py +0 -0
  51. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control.egg-info/dependency_links.txt +0 -0
  52. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control.egg-info/entry_points.txt +0 -0
  53. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control.egg-info/requires.txt +0 -0
  54. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/src/cc_session_control.egg-info/top_level.txt +0 -0
  55. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_agent_ops.py +0 -0
  56. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_agents.py +0 -0
  57. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_app.py +0 -0
  58. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_cleanup.py +0 -0
  59. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_environments.py +0 -0
  60. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_liveness.py +0 -0
  61. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_proc.py +0 -0
  62. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_registry.py +0 -0
  63. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_resume_list.py +0 -0
  64. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_sessions.py +0 -0
  65. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_skill_ops.py +0 -0
  66. {cc_session_control-0.7.1 → cc_session_control-0.7.3}/tests/test_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-control
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: TUI manager for Claude Code sessions and Remote Control
5
5
  Author: dzshzx
6
6
  License-Expression: MIT
@@ -66,8 +66,9 @@ uv tool install --reinstall git+https://github.com/dzshzx/cc-session-control.git
66
66
  ```
67
67
 
68
68
  `csctl` manages the Claude Code state on the machine where it is installed: the
69
- local `~/.claude`, local `tmux`, and local workspace. Install it separately on
70
- each machine whose sessions you want to manage. For working *on* the code
69
+ local `~/.claude`, local `tmux`, and the projects recorded in the local
70
+ `~/.claude.json`. Install it separately on each machine whose sessions you want
71
+ to manage. For working *on* the code
71
72
  instead of using it, see [CONTRIBUTING.md](CONTRIBUTING.md).
72
73
 
73
74
  ## Usage
@@ -78,9 +79,9 @@ csctl
78
79
 
79
80
  # Remote Control management (no TUI)
80
81
  csctl rc status # Show all projects and RC status
81
- csctl rc add . # Add current project to RC list and start
82
- csctl rc add myproject # Add by name
83
- csctl rc rm myproject # Remove and stop
82
+ csctl rc add . # Add current directory to RC list and start
83
+ csctl rc add ~/code/app # Add by directory path
84
+ csctl rc rm ~/code/app # Remove and stop
84
85
  csctl rc up # Start all listed projects
85
86
  csctl rc stop all # Stop all RC servers
86
87
  csctl rc list # Show auto-start list
@@ -103,7 +104,7 @@ csctl skill install --force # Replace an existing skill directory
103
104
  csctl skill uninstall
104
105
 
105
106
  # Options
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
 
@@ -111,9 +112,9 @@ csctl --version
111
112
 
112
113
  | Environment Variable | Default | Description |
113
114
  |---|---|---|
114
- | `CSCTL_WORKSPACE` | `~/workspace` | Workspace root directory |
115
115
  | `CSCTL_RC_SESSION` | `rc` | tmux session name for RC servers |
116
116
  | `CSCTL_RC_STAGGER` | `2` | Seconds between starting RC servers |
117
+ | `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
118
  | `XDG_CONFIG_HOME` | `~/.config` | Config directory base |
118
119
 
119
120
  RC auto-start list is stored at `$XDG_CONFIG_HOME/csctl/rc-enabled`.
@@ -43,8 +43,9 @@ uv tool install --reinstall git+https://github.com/dzshzx/cc-session-control.git
43
43
  ```
44
44
 
45
45
  `csctl` manages the Claude Code state on the machine where it is installed: the
46
- local `~/.claude`, local `tmux`, and local workspace. Install it separately on
47
- each machine whose sessions you want to manage. For working *on* the code
46
+ local `~/.claude`, local `tmux`, and the projects recorded in the local
47
+ `~/.claude.json`. Install it separately on each machine whose sessions you want
48
+ to manage. For working *on* the code
48
49
  instead of using it, see [CONTRIBUTING.md](CONTRIBUTING.md).
49
50
 
50
51
  ## Usage
@@ -55,9 +56,9 @@ csctl
55
56
 
56
57
  # Remote Control management (no TUI)
57
58
  csctl rc status # Show all projects and RC status
58
- csctl rc add . # Add current project to RC list and start
59
- csctl rc add myproject # Add by name
60
- csctl rc rm myproject # Remove and stop
59
+ csctl rc add . # Add current directory to RC list and start
60
+ csctl rc add ~/code/app # Add by directory path
61
+ csctl rc rm ~/code/app # Remove and stop
61
62
  csctl rc up # Start all listed projects
62
63
  csctl rc stop all # Stop all RC servers
63
64
  csctl rc list # Show auto-start list
@@ -80,7 +81,7 @@ csctl skill install --force # Replace an existing skill directory
80
81
  csctl skill uninstall
81
82
 
82
83
  # Options
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
 
@@ -88,9 +89,9 @@ csctl --version
88
89
 
89
90
  | Environment Variable | Default | Description |
90
91
  |---|---|---|
91
- | `CSCTL_WORKSPACE` | `~/workspace` | Workspace root directory |
92
92
  | `CSCTL_RC_SESSION` | `rc` | tmux session name for RC servers |
93
93
  | `CSCTL_RC_STAGGER` | `2` | Seconds between starting RC servers |
94
+ | `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
95
  | `XDG_CONFIG_HOME` | `~/.config` | Config directory base |
95
96
 
96
97
  RC auto-start list is stored at `$XDG_CONFIG_HOME/csctl/rc-enabled`.
@@ -1,3 +1,3 @@
1
1
  """cc-session-control — TUI manager for Claude Code sessions and Remote Control."""
2
2
 
3
- __version__ = "0.7.1"
3
+ __version__ = "0.7.3"
@@ -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(PALETTE)
74
+ screen.register_palette(theme.palette(mode))
92
75
  return screen
93
76
 
94
77
 
@@ -5,7 +5,6 @@ from __future__ import annotations
5
5
  import argparse
6
6
  import os
7
7
  import sys
8
- from pathlib import Path
9
8
 
10
9
 
11
10
  def _build_parser() -> argparse.ArgumentParser:
@@ -16,7 +15,10 @@ def _build_parser() -> argparse.ArgumentParser:
16
15
  description="TUI manager for Claude Code sessions and Remote Control",
17
16
  )
18
17
  parser.add_argument("--version", action="version", version=f"csctl {__version__}")
19
- parser.add_argument("--workspace", type=Path, help="Override workspace root directory")
18
+ parser.add_argument(
19
+ "--theme", choices=("auto", "dark", "light"),
20
+ help="TUI palette (default: auto-detect the terminal background; env CSCTL_THEME)",
21
+ )
20
22
 
21
23
  sub = parser.add_subparsers(dest="command")
22
24
 
@@ -25,12 +27,12 @@ def _build_parser() -> argparse.ArgumentParser:
25
27
  rc_sub = rc_parser.add_subparsers(dest="rc_command")
26
28
  rc_sub.add_parser("status", help="Show RC status for all projects")
27
29
  rc_add = rc_sub.add_parser("add", help="Add project to RC list and start")
28
- rc_add.add_argument("project", nargs="?", default=".", help="Project name or '.' for current dir")
30
+ rc_add.add_argument("project", nargs="?", default=".", help="Project directory (default: current dir)")
29
31
  rc_rm = rc_sub.add_parser("rm", help="Remove project from RC list and stop")
30
- rc_rm.add_argument("project", help="Project name")
32
+ rc_rm.add_argument("project", help="Project directory")
31
33
  rc_sub.add_parser("up", help="Start all listed projects")
32
34
  rc_stop = rc_sub.add_parser("stop", help="Stop RC for a project")
33
- rc_stop.add_argument("target", help="Project name or 'all'")
35
+ rc_stop.add_argument("target", help="Project directory or 'all'")
34
36
  rc_sub.add_parser("list", help="Show enabled project list")
35
37
 
36
38
  # prune subcommand
@@ -67,10 +69,10 @@ def _build_parser() -> argparse.ArgumentParser:
67
69
  return parser
68
70
 
69
71
 
70
- def _apply_workspace(args: argparse.Namespace) -> None:
71
- if args.workspace:
72
- from .config import cfg
73
- cfg.workspace = args.workspace
72
+ def _apply_global_flags(args: argparse.Namespace) -> None:
73
+ from .config import cfg
74
+ if args.theme:
75
+ cfg.theme = args.theme
74
76
 
75
77
 
76
78
  def _cmd_rc(args: argparse.Namespace) -> None:
@@ -83,36 +85,36 @@ def _cmd_rc(args: argparse.Namespace) -> None:
83
85
  sub = args.rc_command
84
86
 
85
87
  if sub == "status":
86
- projects = rc.scan()
88
+ from .data.sessions import scan as scan_sessions
89
+
90
+ # Same ordering as the 项目 tab (rc.order_by_activity — single
91
+ # source); costs one transcript scan, like `csctl resume`.
92
+ projects = rc.order_by_activity(rc.scan(), scan_sessions())
87
93
  for p in projects:
88
94
  icon = {"running": "[running]", "dead": "[dead ]", "stopped": "[stopped]"}.get(p.status, p.status)
89
95
  auto = "auto" if p.auto_start else " "
90
96
  missing = "" if p.dir_exists else " (directory missing)"
91
- print(f" {icon} {auto} {p.name}{missing}")
97
+ print(f" {icon} {auto} {p.name} {p.directory}{missing}")
92
98
 
93
99
  elif sub == "add":
94
- proj = args.project
95
- if proj == ".":
96
- ws = str(rc.cfg.workspace)
97
- cwd = os.getcwd()
98
- if cwd.startswith(ws + "/"):
99
- proj = cwd[len(ws) + 1:].split("/")[0]
100
- else:
101
- print(f"Current directory is not under {ws}. Specify project name explicitly.")
102
- sys.exit(1)
103
- if not rc.is_trusted(proj):
104
- print(f"Not trusted: {proj} — run 'claude' in that directory first to accept the trust dialog")
100
+ path = os.path.abspath(args.project)
101
+ if not os.path.isdir(path):
102
+ print(f"No such directory: {path}")
103
+ sys.exit(1)
104
+ if not rc.is_trusted(path):
105
+ print(f"Not trusted: {path} run 'claude' in that directory first to accept the trust dialog")
105
106
  sys.exit(1)
106
- rc.list_add(proj)
107
- print(f"Added to list: {proj}")
108
- ok = rc.start_one(proj)
107
+ rc.list_add(path)
108
+ print(f"Added to list: {path}")
109
+ ok = rc.start_one(path)
109
110
  if ok:
110
- print(f"Started: ws/{proj}")
111
+ print(f"Started RC server for {path}")
111
112
 
112
113
  elif sub == "rm":
113
- rc.list_rm(args.project)
114
- rc.stop_one(args.project)
115
- print(f"Removed and stopped: {args.project}")
114
+ path = os.path.abspath(args.project)
115
+ rc.list_rm(path)
116
+ rc.stop_one(path)
117
+ print(f"Removed and stopped: {path}")
116
118
 
117
119
  elif sub == "up":
118
120
  enabled = rc.list_enabled()
@@ -127,8 +129,9 @@ def _cmd_rc(args: argparse.Namespace) -> None:
127
129
  rc.stop_all()
128
130
  print("Stopped all")
129
131
  else:
130
- ok = rc.stop_one(args.target)
131
- print(f"Stopped {args.target}" if ok else f"Not running: {args.target}")
132
+ path = os.path.abspath(args.target)
133
+ ok = rc.stop_one(path)
134
+ print(f"Stopped {path}" if ok else f"Not running: {path}")
132
135
 
133
136
  elif sub == "list":
134
137
  for name in rc.list_enabled():
@@ -311,7 +314,7 @@ def _cmd_tui(args: argparse.Namespace) -> None:
311
314
  def main() -> None:
312
315
  parser = _build_parser()
313
316
  args = parser.parse_args()
314
- _apply_workspace(args)
317
+ _apply_global_flags(args)
315
318
 
316
319
  if args.command == "rc":
317
320
  _cmd_rc(args)
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import os
7
6
  from pathlib import Path
8
7
 
@@ -18,18 +17,8 @@ class Config:
18
17
  self.rc_stagger: int = int(os.environ.get("CSCTL_RC_STAGGER", "2"))
19
18
  # Age threshold (days) for the time/global-keyed cleanup strategy.
20
19
  self.cleanup_age_days: int = int(os.environ.get("CSCTL_CLEANUP_AGE_DAYS", "14"))
21
- self._workspace: Path | None = None
22
-
23
- @property
24
- def workspace(self) -> Path:
25
- if self._workspace is not None:
26
- return self._workspace
27
- self._workspace = _detect_workspace(self.claude_json)
28
- return self._workspace
29
-
30
- @workspace.setter
31
- def workspace(self, value: Path) -> None:
32
- self._workspace = value
20
+ # TUI palette: "auto" (detect the terminal background) | "dark" | "light".
21
+ self.theme: str = os.environ.get("CSCTL_THEME", "auto")
33
22
 
34
23
  @property
35
24
  def projects_root(self) -> Path:
@@ -106,28 +95,4 @@ class Config:
106
95
  return self.claude_home / "skills"
107
96
 
108
97
 
109
- def _detect_workspace(claude_json: Path) -> Path:
110
- env = os.environ.get("CSCTL_WORKSPACE")
111
- if env:
112
- return Path(env)
113
-
114
- default = Path.home() / "workspace"
115
- if default.is_dir():
116
- return default
117
-
118
- try:
119
- with open(claude_json) as f:
120
- data = json.load(f)
121
- dirs = [k for k in data.get("projects", {}) if "/" in k]
122
- if dirs:
123
- from os.path import commonpath
124
- common = Path(commonpath(dirs))
125
- if common.is_dir() and common != Path.home():
126
- return common
127
- except Exception:
128
- pass
129
-
130
- return Path.cwd()
131
-
132
-
133
98
  cfg = Config()