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.
Files changed (54) hide show
  1. {ccmd-2.0.3/ccmd.egg-info → ccmd-2.0.4}/PKG-INFO +1 -1
  2. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/__init__.py +1 -1
  3. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/browser.py +9 -7
  4. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/hub.py +29 -22
  5. {ccmd-2.0.3 → ccmd-2.0.4/ccmd.egg-info}/PKG-INFO +1 -1
  6. {ccmd-2.0.3 → ccmd-2.0.4}/pyproject.toml +1 -1
  7. {ccmd-2.0.3 → ccmd-2.0.4}/LICENSE +0 -0
  8. {ccmd-2.0.3 → ccmd-2.0.4}/MANIFEST.in +0 -0
  9. {ccmd-2.0.3 → ccmd-2.0.4}/README.md +0 -0
  10. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/__main__.py +0 -0
  11. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/cli.py +0 -0
  12. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/config/__init__.py +0 -0
  13. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/config/paths.py +0 -0
  14. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/config/schema.py +0 -0
  15. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/__init__.py +0 -0
  16. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/__init__.py +0 -0
  17. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/aider.py +0 -0
  18. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/base.py +0 -0
  19. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/chatgpt.py +0 -0
  20. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/claude.py +0 -0
  21. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/codex.py +0 -0
  22. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/cursor.py +0 -0
  23. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/goose.py +0 -0
  24. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/agents/grok.py +0 -0
  25. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/fs.py +0 -0
  26. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/launcher.py +0 -0
  27. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/migrate.py +0 -0
  28. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/profiles.py +0 -0
  29. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/projects.py +0 -0
  30. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/scan.py +0 -0
  31. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/sessions.py +0 -0
  32. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/core/ssh.py +0 -0
  33. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/mcp/__init__.py +0 -0
  34. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/mcp/server.py +0 -0
  35. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/__init__.py +0 -0
  36. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/app.py +0 -0
  37. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/ascii.py +0 -0
  38. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/__init__.py +0 -0
  39. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/agents.py +0 -0
  40. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/aliases.py +0 -0
  41. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/project_edit.py +0 -0
  42. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/sessions.py +0 -0
  43. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/screens/ssh.py +0 -0
  44. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/theme.py +0 -0
  45. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/widgets/__init__.py +0 -0
  46. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd/tui/widgets/spinner.py +0 -0
  47. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/SOURCES.txt +0 -0
  48. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/dependency_links.txt +0 -0
  49. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/entry_points.txt +0 -0
  50. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/requires.txt +0 -0
  51. {ccmd-2.0.3 → ccmd-2.0.4}/ccmd.egg-info/top_level.txt +0 -0
  52. {ccmd-2.0.3 → ccmd-2.0.4}/commands.yaml +0 -0
  53. {ccmd-2.0.3 → ccmd-2.0.4}/requirements.txt +0 -0
  54. {ccmd-2.0.3 → ccmd-2.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccmd
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: ccmd — Agentic Project Hub. Pick a project, pick an agent, launch.
5
5
  Author-email: De Catalyst <Robert5560newton@gmail.com>
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
1
  """ccmd 2.0 — Agentic Project Hub."""
2
2
 
3
- __version__ = "2.0.3"
3
+ __version__ = "2.0.4"
4
4
  __all__ = ["__version__"]
@@ -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] open folder · s select as project[/]",
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
- "[bold]][/] cycles default agent on selection.[/]"
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(f" {mark} {i} {a['logo']} {a['id']:<8} {ready}")
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[/] ]/[ cycle · 1-7 set · A set only · a launch",
212
+ "[bold #c84bff]agents[/] keys: ] [ cycle · 1-7 set · A set · a launch",
209
213
  *roster,
210
214
  "",
211
- "[#6a6a80] open · d detach · b/f add from disk[/]",
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 "[vylth]"
468
+ return "<vylth>"
462
469
  if p.startswith("/mnt/c"):
463
- return "[win-c]"
470
+ return "<win-c>"
464
471
  if p.startswith("/mnt/d"):
465
- return "[win-d]"
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"[mnt/{part}]"
475
+ return f"<mnt/{part}>"
469
476
  if p.startswith(str(Path.home())):
470
- return "[home]"
477
+ return "<home>"
471
478
  if p.startswith("/opt"):
472
- return "[opt]"
473
- return "[disk]"
479
+ return "<opt>"
480
+ return "<disk>"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccmd
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: ccmd — Agentic Project Hub. Pick a project, pick an agent, launch.
5
5
  Author-email: De Catalyst <Robert5560newton@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ccmd"
7
- version = "2.0.3"
7
+ version = "2.0.4"
8
8
  description = "ccmd — Agentic Project Hub. Pick a project, pick an agent, launch."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
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