ccmd 2.0.3__tar.gz → 2.0.4__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.
- {ccmd-2.0.3/ccmd.egg-info → ccmd-2.0.4}/PKG-INFO +1 -1
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/__init__.py +1 -1
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/browser.py +9 -7
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/hub.py +29 -22
- {ccmd-2.0.3 → ccmd-2.0.4/ccmd.egg-info}/PKG-INFO +1 -1
- {ccmd-2.0.3 → ccmd-2.0.4}/pyproject.toml +1 -1
- {ccmd-2.0.3 → ccmd-2.0.4}/LICENSE +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/MANIFEST.in +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/README.md +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/__main__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/cli.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/config/__init__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/config/paths.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/config/schema.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/__init__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/__init__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/aider.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/base.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/chatgpt.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/claude.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/codex.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/cursor.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/goose.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/grok.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/fs.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/launcher.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/migrate.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/profiles.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/projects.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/scan.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/sessions.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/ssh.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/mcp/__init__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/mcp/server.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/__init__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/app.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/ascii.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/__init__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/agents.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/aliases.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/project_edit.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/sessions.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/ssh.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/theme.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/widgets/__init__.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/widgets/spinner.py +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/SOURCES.txt +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/dependency_links.txt +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/entry_points.txt +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/requires.txt +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/top_level.txt +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/commands.yaml +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/requirements.txt +0 -0
- {ccmd-2.0.3 → ccmd-2.0.4}/setup.cfg +0 -0
|
@@ -12,6 +12,8 @@ from textual.screen import Screen
|
|
|
12
12
|
from textual.widgets import Footer, Input, OptionList, Static
|
|
13
13
|
from textual.widgets.option_list import Option
|
|
14
14
|
|
|
15
|
+
from rich.markup import escape
|
|
16
|
+
|
|
15
17
|
from ccmd.config.schema import load_config
|
|
16
18
|
from ccmd.core.agents import all_agents, get_agent
|
|
17
19
|
from ccmd.core.fs import default_browse_roots, is_projectish, list_dir_entries
|
|
@@ -98,9 +100,9 @@ class DirBrowserScreen(Screen[bool | None]):
|
|
|
98
100
|
ag = get_agent(self._agent)
|
|
99
101
|
ag_s = f"{ag.ascii_logo} {self._agent}" if ag else self._agent
|
|
100
102
|
bar.update(
|
|
101
|
-
f"[bold #00ffc8]{self._cwd}[/]{mark}{hidden} · "
|
|
102
|
-
f"agent [bold #c84bff]{ag_s}[/]"
|
|
103
|
-
+ (f" [#6a6a80]{extra}[/]" if extra else "")
|
|
103
|
+
f"[bold #00ffc8]{escape(str(self._cwd))}[/]{mark}{hidden} · "
|
|
104
|
+
f"agent [bold #c84bff]{escape(ag_s)}[/]"
|
|
105
|
+
+ (f" [#6a6a80]{escape(extra)}[/]" if extra else "")
|
|
104
106
|
)
|
|
105
107
|
|
|
106
108
|
def _render_list(self) -> None:
|
|
@@ -184,21 +186,21 @@ class DirBrowserScreen(Screen[bool | None]):
|
|
|
184
186
|
kids = list_dir_entries(resolved, show_hidden=self._show_hidden, dirs_only=True)
|
|
185
187
|
project = is_projectish(resolved)
|
|
186
188
|
lines = [
|
|
187
|
-
f"[bold #00ffc8]{resolved.name or resolved}[/]",
|
|
188
|
-
f"path {resolved}",
|
|
189
|
+
f"[bold #00ffc8]{escape(str(resolved.name or resolved))}[/]",
|
|
190
|
+
f"path {escape(str(resolved))}",
|
|
189
191
|
f"project {'yes ◆' if project else 'no (still selectable)'}",
|
|
190
192
|
f"subdirs {len(kids)}",
|
|
191
193
|
"",
|
|
192
194
|
"[#6a6a80]children[/]",
|
|
193
195
|
]
|
|
194
196
|
for name, _, _ in kids[:12]:
|
|
195
|
-
lines.append(f" · {name}/")
|
|
197
|
+
lines.append(f" · {escape(name)}/")
|
|
196
198
|
if len(kids) > 12:
|
|
197
199
|
lines.append(f" … +{len(kids) - 12} more")
|
|
198
200
|
lines.extend(
|
|
199
201
|
[
|
|
200
202
|
"",
|
|
201
|
-
"[#6a6a80]
|
|
203
|
+
"[#6a6a80]enter open folder · s select as project[/]",
|
|
202
204
|
]
|
|
203
205
|
)
|
|
204
206
|
prev.update("\n".join(lines))
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from rich.markup import escape
|
|
7
8
|
from textual import on, work
|
|
8
9
|
from textual.app import ComposeResult
|
|
9
10
|
from textual.binding import Binding
|
|
@@ -176,7 +177,7 @@ class HubScreen(Screen):
|
|
|
176
177
|
"[#6a6a80]This list is your saved registry.\n"
|
|
177
178
|
"Press [bold]f[/] to browse /mnt and subdirs,\n"
|
|
178
179
|
"or [bold]b[/] for home + all roots.\n"
|
|
179
|
-
"
|
|
180
|
+
"Press ] to cycle default agent.[/]"
|
|
180
181
|
)
|
|
181
182
|
return
|
|
182
183
|
agent = get_agent(p.default_agent)
|
|
@@ -186,29 +187,32 @@ class HubScreen(Screen):
|
|
|
186
187
|
else p.default_agent
|
|
187
188
|
)
|
|
188
189
|
exists = "yes" if p.exists() else "MISSING"
|
|
189
|
-
# agent roster
|
|
190
190
|
roster: list[str] = []
|
|
191
191
|
for i, a in enumerate(self._agent_info, start=1):
|
|
192
192
|
mark = "●" if a["id"] == p.default_agent else "·"
|
|
193
193
|
ready = "✓" if a["installed"] else "✗"
|
|
194
|
-
roster.append(
|
|
194
|
+
roster.append(
|
|
195
|
+
f" {mark} {i} {escape(str(a['logo']))} "
|
|
196
|
+
f"{escape(a['id']):<8} {ready}"
|
|
197
|
+
)
|
|
195
198
|
|
|
199
|
+
# Escape anything that can contain [ ] so Rich markup doesn't explode
|
|
196
200
|
lines = [
|
|
197
|
-
f"[bold #00ffc8]{p.name}[/]",
|
|
198
|
-
f"id {p.id}",
|
|
199
|
-
f"path {p.path}",
|
|
200
|
-
f"root {_root_label(p.path)}",
|
|
201
|
+
f"[bold #00ffc8]{escape(p.name)}[/]",
|
|
202
|
+
f"id {escape(p.id)}",
|
|
203
|
+
f"path {escape(p.path)}",
|
|
204
|
+
f"root {escape(_root_label(p.path))}",
|
|
201
205
|
f"exists {exists}",
|
|
202
|
-
f"agent [bold]{agent_line}[/] (default)",
|
|
203
|
-
f"ssh {p.ssh_host or '—'}",
|
|
204
|
-
f"env {', '.join(p.env_files) or '—'}",
|
|
205
|
-
f"tags {', '.join(p.tags) or '—'}",
|
|
206
|
-
f"opened {p.last_opened_at or 'never'}",
|
|
206
|
+
f"agent [bold]{escape(agent_line)}[/] (default)",
|
|
207
|
+
f"ssh {escape(p.ssh_host or '—')}",
|
|
208
|
+
f"env {escape(', '.join(p.env_files) or '—')}",
|
|
209
|
+
f"tags {escape(', '.join(p.tags) or '—')}",
|
|
210
|
+
f"opened {escape(p.last_opened_at or 'never')}",
|
|
207
211
|
"",
|
|
208
|
-
"[bold #c84bff]agents[/] ]
|
|
212
|
+
"[bold #c84bff]agents[/] keys: ] [ cycle · 1-7 set · A set · a launch",
|
|
209
213
|
*roster,
|
|
210
214
|
"",
|
|
211
|
-
"[#6a6a80]
|
|
215
|
+
"[#6a6a80]enter open · d detach · b/f add from disk[/]",
|
|
212
216
|
]
|
|
213
217
|
detail.update("\n".join(lines))
|
|
214
218
|
|
|
@@ -455,19 +459,22 @@ class HubScreen(Screen):
|
|
|
455
459
|
|
|
456
460
|
|
|
457
461
|
def _root_label(path: str) -> str:
|
|
458
|
-
"""Short badge for where the project lives.
|
|
462
|
+
"""Short badge for where the project lives.
|
|
463
|
+
|
|
464
|
+
Uses angle brackets, not square ones — square brackets break Rich markup.
|
|
465
|
+
"""
|
|
459
466
|
p = path.replace("\\", "/")
|
|
460
467
|
if p.startswith("/mnt/vylth"):
|
|
461
|
-
return "
|
|
468
|
+
return "<vylth>"
|
|
462
469
|
if p.startswith("/mnt/c"):
|
|
463
|
-
return "
|
|
470
|
+
return "<win-c>"
|
|
464
471
|
if p.startswith("/mnt/d"):
|
|
465
|
-
return "
|
|
472
|
+
return "<win-d>"
|
|
466
473
|
if p.startswith("/mnt/"):
|
|
467
474
|
part = p.split("/")[2] if len(p.split("/")) > 2 else "mnt"
|
|
468
|
-
return f"
|
|
475
|
+
return f"<mnt/{part}>"
|
|
469
476
|
if p.startswith(str(Path.home())):
|
|
470
|
-
return "
|
|
477
|
+
return "<home>"
|
|
471
478
|
if p.startswith("/opt"):
|
|
472
|
-
return "
|
|
473
|
-
return "
|
|
479
|
+
return "<opt>"
|
|
480
|
+
return "<disk>"
|
|
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
|
|
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
|
|
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
|