ccmd 2.0.7__tar.gz → 2.0.9__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 (55) hide show
  1. {ccmd-2.0.7/ccmd.egg-info → ccmd-2.0.9}/PKG-INFO +1 -1
  2. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/__init__.py +1 -1
  3. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/cli.py +163 -30
  4. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/shell_cmds.py +82 -7
  5. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/browser.py +18 -1
  6. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/hub.py +35 -3
  7. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/project_edit.py +6 -2
  8. {ccmd-2.0.7 → ccmd-2.0.9/ccmd.egg-info}/PKG-INFO +1 -1
  9. {ccmd-2.0.7 → ccmd-2.0.9}/pyproject.toml +1 -1
  10. {ccmd-2.0.7 → ccmd-2.0.9}/LICENSE +0 -0
  11. {ccmd-2.0.7 → ccmd-2.0.9}/MANIFEST.in +0 -0
  12. {ccmd-2.0.7 → ccmd-2.0.9}/README.md +0 -0
  13. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/__main__.py +0 -0
  14. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/config/__init__.py +0 -0
  15. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/config/paths.py +0 -0
  16. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/config/schema.py +0 -0
  17. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/__init__.py +0 -0
  18. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/__init__.py +0 -0
  19. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/aider.py +0 -0
  20. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/base.py +0 -0
  21. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/chatgpt.py +0 -0
  22. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/claude.py +0 -0
  23. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/codex.py +0 -0
  24. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/cursor.py +0 -0
  25. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/goose.py +0 -0
  26. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/grok.py +0 -0
  27. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/fs.py +0 -0
  28. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/launcher.py +0 -0
  29. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/migrate.py +0 -0
  30. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/profiles.py +0 -0
  31. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/projects.py +0 -0
  32. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/scan.py +0 -0
  33. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/sessions.py +0 -0
  34. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/ssh.py +0 -0
  35. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/mcp/__init__.py +0 -0
  36. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/mcp/server.py +0 -0
  37. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/__init__.py +0 -0
  38. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/app.py +0 -0
  39. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/ascii.py +0 -0
  40. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/__init__.py +0 -0
  41. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/agents.py +0 -0
  42. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/aliases.py +0 -0
  43. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/sessions.py +0 -0
  44. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/ssh.py +0 -0
  45. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/theme.py +0 -0
  46. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/widgets/__init__.py +0 -0
  47. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/widgets/spinner.py +0 -0
  48. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/SOURCES.txt +0 -0
  49. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/dependency_links.txt +0 -0
  50. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/entry_points.txt +0 -0
  51. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/requires.txt +0 -0
  52. {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/top_level.txt +0 -0
  53. {ccmd-2.0.7 → ccmd-2.0.9}/commands.yaml +0 -0
  54. {ccmd-2.0.7 → ccmd-2.0.9}/requirements.txt +0 -0
  55. {ccmd-2.0.7 → ccmd-2.0.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccmd
3
- Version: 2.0.7
3
+ Version: 2.0.9
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.7"
3
+ __version__ = "2.0.9"
4
4
  __all__ = ["__version__"]
@@ -20,6 +20,103 @@ from ccmd.core.sessions import SessionStore
20
20
  from ccmd.core.ssh import SSHStore
21
21
 
22
22
 
23
+ # agent accent colors for -v lines
24
+ _AGENT_COLORS = {
25
+ "claude": "dark_orange3",
26
+ "grok": "bright_white",
27
+ "codex": "green",
28
+ "cursor": "medium_purple",
29
+ "goose": "yellow",
30
+ "aider": "cyan",
31
+ "chatgpt": "sea_green3",
32
+ }
33
+
34
+
35
+ def _print_cmd_rows(rows: list, *, verbose: bool = False) -> None:
36
+ """Pretty list: base command only. Agent variants only with -v or single match."""
37
+ try:
38
+ from rich.console import Console
39
+ from rich.table import Table
40
+ from rich.text import Text
41
+
42
+ console = Console(highlight=False)
43
+ use_rich = console.is_terminal
44
+ except Exception:
45
+ use_rich = False
46
+
47
+ if not rows:
48
+ if use_rich:
49
+ Console().print("[dim]no matching commands[/]")
50
+ else:
51
+ print("no matching commands")
52
+ return
53
+
54
+ show_variants = verbose or len(rows) == 1
55
+
56
+ if use_rich:
57
+ console = Console(highlight=False)
58
+ table = Table(
59
+ show_header=True,
60
+ header_style="bold cyan",
61
+ border_style="bright_black",
62
+ box=None,
63
+ pad_edge=False,
64
+ expand=False,
65
+ )
66
+ table.add_column("cmd", style="bold bright_cyan", no_wrap=True)
67
+ table.add_column("project", style="white")
68
+ table.add_column("agent", style="magenta")
69
+ table.add_column("path", style="dim", overflow="ellipsis", max_width=48)
70
+
71
+ for r in rows:
72
+ table.add_row(
73
+ r["short"],
74
+ r["project"],
75
+ r["default_agent"],
76
+ r["path"],
77
+ )
78
+ console.print(table)
79
+
80
+ if show_variants:
81
+ console.print()
82
+ for r in rows:
83
+ s = r["short"]
84
+ console.print(
85
+ f" [bold bright_cyan]{s}[/] "
86
+ f"[dim]→[/] [white]{r['project']}[/] "
87
+ f"[dim]({r['default_agent']})[/]"
88
+ )
89
+ parts = []
90
+ for aid, fn in r["variants"].items():
91
+ color = _AGENT_COLORS.get(aid, "white")
92
+ parts.append(f"[{color}]{fn}[/{color}]")
93
+ console.print(" " + " ".join(parts))
94
+ console.print(f" [dim]{r['path']}[/]")
95
+
96
+ console.print()
97
+ console.print(
98
+ f"[dim]{len(rows)} command(s)[/] "
99
+ f"[dim]·[/] [bright_cyan]ccmds <name>[/][dim] for agent variants[/] "
100
+ f"[dim]·[/] [bright_cyan]ccmds -v[/][dim] show all variants[/]"
101
+ )
102
+ return
103
+
104
+ # plain fallback
105
+ print(f"{'CMD':<10} {'PROJECT':<22} {'AGENT':<10} PATH")
106
+ print("-" * 72)
107
+ for r in rows:
108
+ print(
109
+ f"{r['short']:<10} {r['project']:<22} "
110
+ f"{r['default_agent']:<10} {r['path']}"
111
+ )
112
+ if show_variants:
113
+ print(
114
+ " agents: "
115
+ + " ".join(f"{fn}" for fn in r["variants"].values())
116
+ )
117
+ print(f"\n{len(rows)} command(s) · ccmds <name> for agent variants · ccmds -v")
118
+
119
+
23
120
  def _cmd_shell(args: argparse.Namespace) -> int:
24
121
  from ccmd.core.shell_cmds import (
25
122
  install_shell_hook,
@@ -28,47 +125,56 @@ def _cmd_shell(args: argparse.Namespace) -> int:
28
125
  write_shell_cmds,
29
126
  )
30
127
 
31
- sub = args.shell_cmd or "regen"
128
+ sub = args.shell_cmd or "list"
129
+ filt = getattr(args, "filter", None)
130
+ verbose = bool(getattr(args, "verbose", False))
131
+
32
132
  if sub == "install":
33
- path = write_shell_cmds()
133
+ path, shorts = write_shell_cmds()
34
134
  rcs = install_shell_hook()
35
135
  print(f"wrote {path}")
36
136
  for rc in rcs:
37
137
  print(f"hooked {rc}")
38
138
  print("restart shell or: source ~/.ccmd/shell_cmds.sh")
39
- print("then: ana / anac / anag …")
139
+ print("then: ccmds # list base commands (color)")
140
+ print(" ccmds ana # agent variants for one project")
141
+ print(" ana / anac / anag …")
142
+ if verbose:
143
+ _print_cmd_rows(list_cmd_rows(), verbose=False)
40
144
  return 0
41
145
  if sub == "regen":
42
- path = write_shell_cmds()
43
- print(f"regenerated {path}")
44
- rows = list_cmd_rows()
45
- for r in rows[:30]:
46
- print(
47
- f" {r['short']:<10} {r['project']:<18} "
48
- f"default={r['default_agent']:<8} "
49
- f"{r['short']}c/g/x/u/o/a/p"
50
- )
51
- if len(rows) > 30:
52
- print(f" … +{len(rows) - 30} more (see {path})")
146
+ path, shorts = write_shell_cmds()
147
+ print(f"regenerated {path} ({len(shorts)} projects)")
148
+ rows = list_cmd_rows(filter_text=filt)
149
+ _print_cmd_rows(rows, verbose=verbose)
53
150
  return 0
54
151
  if sub == "list":
55
- for r in list_cmd_rows():
56
- vars_ = " ".join(
57
- f"{k[0]}={v}" for k, v in r["variants"].items()
58
- )
59
- # show compact
60
- v = r["variants"]
61
- print(
62
- f"{r['short']:<10} {r['project']:<20} "
63
- f"{v.get('claude','')} {v.get('grok','')} {v.get('codex','')} "
64
- f"{v.get('cursor','')} → {r['path']}"
65
- )
66
- print(f"\nfile: {shell_cmds_path()}")
152
+ rows = list_cmd_rows(filter_text=filt)
153
+ # filter alone does NOT dump variants unless -v (cleaner list)
154
+ _print_cmd_rows(rows, verbose=verbose)
67
155
  return 0
68
- print("usage: ccmd shell install|regen|list", file=sys.stderr)
156
+ if sub == "show":
157
+ rows = list_cmd_rows(filter_text=filt)
158
+ _print_cmd_rows(rows, verbose=True)
159
+ return 0
160
+ print("usage: ccmd shell install|regen|list [--filter NAME] [-v]", file=sys.stderr)
161
+ print(" ccmds # list base cmds", file=sys.stderr)
162
+ print(" ccmds ana # one project + agent variants", file=sys.stderr)
69
163
  return 1
70
164
 
71
165
 
166
+ def _cmd_cmds(args: argparse.Namespace) -> int:
167
+ """Top-level: ccmd cmds [name] [-v] — same as ccmds."""
168
+ from ccmd.core.shell_cmds import list_cmd_rows
169
+
170
+ filt = args.filter or args.name
171
+ # single-name lookup → show agent variants; bare list → base only
172
+ verbose = bool(args.verbose) or bool(filt)
173
+ rows = list_cmd_rows(filter_text=filt)
174
+ _print_cmd_rows(rows, verbose=verbose)
175
+ return 0
176
+
177
+
72
178
  def _cmd_open(args: argparse.Namespace) -> int:
73
179
  reg = ProjectRegistry()
74
180
  project = reg.get(args.project)
@@ -301,9 +407,35 @@ def build_parser() -> argparse.ArgumentParser:
301
407
  sh.add_argument(
302
408
  "shell_cmd",
303
409
  nargs="?",
304
- choices=["install", "regen", "list"],
305
- default="regen",
306
- help="install=write cmds + hook bashrc; regen=rewrite cmds; list=show",
410
+ choices=["install", "regen", "list", "show"],
411
+ default="list",
412
+ help="install|regen|list|show",
413
+ )
414
+ sh.add_argument(
415
+ "--filter",
416
+ "-f",
417
+ dest="filter",
418
+ help="filter by short/project/path (e.g. ana)",
419
+ )
420
+ sh.add_argument(
421
+ "-v",
422
+ "--verbose",
423
+ action="store_true",
424
+ help="show full agent variant map",
425
+ )
426
+
427
+ cmds = sub.add_parser(
428
+ "cmds",
429
+ help="List short shell commands (same as: ccmd shell list / ccmds)",
430
+ )
431
+ cmds.add_argument("name", nargs="?", help="filter (e.g. ana or flow)")
432
+ cmds.add_argument("--filter", "-f", dest="filter", help="filter text")
433
+ cmds.add_argument("-v", "--verbose", action="store_true")
434
+ cmds.add_argument(
435
+ "--list",
436
+ "-l",
437
+ action="store_true",
438
+ help="list all (default)",
307
439
  )
308
440
 
309
441
  return p
@@ -336,6 +468,7 @@ def main(argv: list[str] | None = None) -> int:
336
468
  "doctor": _cmd_doctor,
337
469
  "mcp": _cmd_mcp,
338
470
  "shell": _cmd_shell,
471
+ "cmds": _cmd_cmds,
339
472
  }
340
473
  handler = handlers.get(args.cmd)
341
474
  if not handler:
@@ -243,13 +243,71 @@ def render_shell_cmds(projects: list, shorts: dict[str, str]) -> str:
243
243
  )
244
244
  lines.append("")
245
245
 
246
- lines.append("# end ccmd shell cmds")
247
- lines.append("")
246
+ # helper: list base commands (color); pass a name for agent variants
247
+ lines.extend(
248
+ [
249
+ "# list short commands — base only; pass a name for agent variants",
250
+ "ccmds() {",
251
+ ' if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then',
252
+ ' echo "ccmds list base commands (colored)"',
253
+ ' echo "ccmds -v list base + all agent variants"',
254
+ ' echo "ccmds <name> one project + agents (e.g. ccmds ana)"',
255
+ ' echo "ccmds -l|--list same as bare ccmds"',
256
+ " return 0",
257
+ " fi",
258
+ ' if [ "$1" = "-v" ] || [ "$1" = "--verbose" ]; then',
259
+ ' command ccmd shell list -v',
260
+ " return $?",
261
+ " fi",
262
+ ' if [ -z "$1" ] || [ "$1" = "-l" ] || [ "$1" = "--list" ]; then',
263
+ ' command ccmd shell list',
264
+ " return $?",
265
+ " fi",
266
+ ' # one project → show agent subcommands',
267
+ ' command ccmd shell show --filter "$1"',
268
+ "}",
269
+ "",
270
+ "# end ccmd shell cmds",
271
+ "",
272
+ ]
273
+ )
274
+ return "\n".join(lines)
275
+
276
+
277
+ def cmds_notice(project, short: str | None = None) -> str:
278
+ """Human-readable one-liner of shortcuts for a project (for TUI/CLI notices)."""
279
+ s = short or getattr(project, "short", None) or ""
280
+ if not s:
281
+ shorts = allocate_shorts([project])
282
+ s = shorts.get(project.id, project.id[:4])
283
+ variants = " ".join(
284
+ f"{s}{suf}={aid}" for aid, suf in AGENT_SUFFIX.items()
285
+ )
286
+ return (
287
+ f"cmds: {s} (default={project.default_agent}) "
288
+ f"{s}c=claude {s}g=grok {s}x=codex {s}u=cursor "
289
+ f"| ccmds or ccmd shell list"
290
+ )
291
+
292
+
293
+ def format_cmds_block(project, short: str | None = None) -> str:
294
+ """Multi-line block shown after save."""
295
+ s = short or getattr(project, "short", None) or project.id[:4]
296
+ lines = [
297
+ f"shell commands for {project.id}:",
298
+ f" {s:<10} open with default agent ({project.default_agent})",
299
+ ]
300
+ for aid, suf in AGENT_SUFFIX.items():
301
+ lines.append(f" {s}{suf:<9} --agent {aid}")
302
+ lines.append("list all: ccmds or ccmd shell list")
248
303
  return "\n".join(lines)
249
304
 
250
305
 
251
- def write_shell_cmds(projects: list | None = None) -> Path:
252
- """Write ~/.ccmd/shell_cmds.sh and persist short names on projects."""
306
+ def write_shell_cmds(projects: list | None = None) -> tuple[Path, dict[str, str]]:
307
+ """Write ~/.ccmd/shell_cmds.sh and persist short names on projects.
308
+
309
+ Returns (path, {project_id: short}).
310
+ """
253
311
  from ccmd.core.projects import ProjectRegistry
254
312
 
255
313
  reg = ProjectRegistry()
@@ -278,7 +336,7 @@ def write_shell_cmds(projects: list | None = None) -> Path:
278
336
  )
279
337
  lines.append(f"{'':10} {p.path}\n")
280
338
  sheet.write_text("".join(lines), encoding="utf-8")
281
- return path
339
+ return path, shorts
282
340
 
283
341
 
284
342
  def install_shell_hook(rc_file: Path | None = None) -> list[Path]:
@@ -288,6 +346,7 @@ def install_shell_hook(rc_file: Path | None = None) -> list[Path]:
288
346
  block = (
289
347
  f"\n{MARKER_BEGIN}\n"
290
348
  f"# managed by ccmd — short project commands (ana/anac/anag…)\n"
349
+ f"# list them anytime: ccmds or ccmds ana\n"
291
350
  f'[ -f "$HOME/.ccmd/shell_cmds.sh" ] && . "$HOME/.ccmd/shell_cmds.sh"\n'
292
351
  f"{MARKER_END}\n"
293
352
  )
@@ -331,22 +390,38 @@ def install_shell_hook(rc_file: Path | None = None) -> list[Path]:
331
390
  return updated
332
391
 
333
392
 
334
- def list_cmd_rows(projects: list | None = None) -> list[dict]:
393
+ def list_cmd_rows(
394
+ projects: list | None = None,
395
+ *,
396
+ filter_text: str | None = None,
397
+ ) -> list[dict]:
335
398
  from ccmd.core.projects import ProjectRegistry
336
399
 
337
400
  projects = list(projects) if projects is not None else ProjectRegistry().list()
338
401
  shorts = allocate_shorts(projects)
339
402
  rows = []
340
403
  for p in projects:
341
- s = shorts.get(p.id, "")
404
+ s = shorts.get(p.id, "") or getattr(p, "short", "") or ""
342
405
  rows.append(
343
406
  {
344
407
  "short": s,
345
408
  "project": p.id,
409
+ "name": p.name,
346
410
  "default_agent": p.default_agent,
347
411
  "path": p.path,
348
412
  "variants": {a: f"{s}{suf}" for a, suf in AGENT_SUFFIX.items()},
349
413
  }
350
414
  )
351
415
  rows.sort(key=lambda r: r["short"])
416
+ if filter_text:
417
+ q = filter_text.strip().lower()
418
+ rows = [
419
+ r
420
+ for r in rows
421
+ if q in r["short"].lower()
422
+ or q in r["project"].lower()
423
+ or q in r["name"].lower()
424
+ or q in r["path"].lower()
425
+ or any(q in v.lower() for v in r["variants"].values())
426
+ ]
352
427
  return rows
@@ -323,14 +323,24 @@ class DirBrowserScreen(Screen[bool | None]):
323
323
  self.notify(str(e), severity="error")
324
324
  return
325
325
 
326
+ from ccmd.core.shell_cmds import cmds_notice
327
+
326
328
  reg = ProjectRegistry()
327
329
  existing = reg.find_by_path(resolved)
328
330
  if existing:
329
331
  existing.default_agent = self._agent
330
332
  reg.save(existing)
333
+ # reload short after regen
334
+ existing = reg.get(existing.id) or existing
335
+ self.notify(
336
+ f"updated {existing.id} · agent {self._agent}",
337
+ severity="information",
338
+ timeout=4,
339
+ )
331
340
  self.notify(
332
- f"already had {existing.id} — default agent → {self._agent}",
341
+ cmds_notice(existing),
333
342
  severity="information",
343
+ timeout=8,
334
344
  )
335
345
  self.dismiss(True)
336
346
  return
@@ -342,9 +352,16 @@ class DirBrowserScreen(Screen[bool | None]):
342
352
  default_agent=self._agent,
343
353
  notes="added via browser",
344
354
  )
355
+ p = reg.get(p.id) or p
345
356
  self.notify(
346
357
  f"added {p.id} · agent {self._agent} → {resolved}",
347
358
  severity="information",
359
+ timeout=4,
360
+ )
361
+ self.notify(
362
+ cmds_notice(p),
363
+ severity="information",
364
+ timeout=10,
348
365
  )
349
366
  self.dismiss(True)
350
367
 
@@ -51,6 +51,7 @@ class HubScreen(Screen):
51
51
  Binding("slash", "focus_filter", "Filter", show=False),
52
52
  Binding("/", "focus_filter", "Filter", show=True),
53
53
  Binding("escape", "blur_filter", "Esc", show=False),
54
+ Binding("c", "show_cmds", "Cmds", show=True),
54
55
  Binding("1", "agent_n_1", show=False),
55
56
  Binding("2", "agent_n_2", show=False),
56
57
  Binding("3", "agent_n_3", show=False),
@@ -211,11 +212,34 @@ class HubScreen(Screen):
211
212
  "agents ]/[ cycle · 1-7 set · A set · a launch",
212
213
  *roster,
213
214
  "",
214
- "enter open · d detach · b/f add from disk",
215
- "shell: source ~/.ccmd/shell_cmds.sh (or: ccmd shell install)",
215
+ "enter open · d detach · b/f add from disk · c show cmds",
216
+ "shell: ccmds | ccmd shell list | source ~/.ccmd/shell_cmds.sh",
216
217
  ]
217
218
  detail.update("\n".join(lines))
218
219
 
220
+ def action_show_cmds(self) -> None:
221
+ """Flag-style view: toast the shell shortcuts for the selected project."""
222
+ from ccmd.core.shell_cmds import cmds_notice, format_cmds_block
223
+
224
+ p = self._selected()
225
+ if not p:
226
+ self.notify(
227
+ "all cmds: run ccmds or ccmd shell list",
228
+ severity="information",
229
+ timeout=6,
230
+ )
231
+ return
232
+ p = self.registry.get(p.id) or p
233
+ # multi-line block in status + toast summary
234
+ block = format_cmds_block(p)
235
+ self.query_one("#status", Static).update(block.replace("\n", " · "))
236
+ self.notify(cmds_notice(p), severity="information", timeout=10)
237
+ self.notify(
238
+ f"{p.short or p.id}: c={p.short}c g={p.short}g x={p.short}x u={p.short}u",
239
+ severity="information",
240
+ timeout=8,
241
+ )
242
+
219
243
  def _set_default_agent(self, agent_id: str, *, notify: bool = True) -> None:
220
244
  p = self._selected()
221
245
  if not p:
@@ -226,8 +250,16 @@ class HubScreen(Screen):
226
250
  return
227
251
  p.default_agent = agent_id
228
252
  self.registry.save(p)
253
+ p = self.registry.get(p.id) or p
229
254
  if notify:
230
- self.notify(f"{p.id} default agent {agent_id}", severity="information")
255
+ from ccmd.core.shell_cmds import cmds_notice
256
+
257
+ self.notify(
258
+ f"{p.id} → default agent {agent_id}",
259
+ severity="information",
260
+ timeout=3,
261
+ )
262
+ self.notify(cmds_notice(p), severity="information", timeout=8)
231
263
  self._load_projects(self.query_one("#filter", Input).value)
232
264
 
233
265
  def _cycle_agent(self, delta: int) -> None:
@@ -45,8 +45,12 @@ class ProjectEditScreen(Screen[bool | None]):
45
45
  if not p.is_dir():
46
46
  self.notify(f"not a directory: {p}", severity="error")
47
47
  return
48
- ProjectRegistry().add(name=name, path=p, default_agent=agent)
49
- self.notify(f"added {name}", severity="information")
48
+ from ccmd.core.shell_cmds import cmds_notice
49
+
50
+ proj = ProjectRegistry().add(name=name, path=p, default_agent=agent)
51
+ proj = ProjectRegistry().get(proj.id) or proj
52
+ self.notify(f"added {name}", severity="information", timeout=3)
53
+ self.notify(cmds_notice(proj), severity="information", timeout=10)
50
54
  self.dismiss(True)
51
55
 
52
56
  def action_cancel(self) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccmd
3
- Version: 2.0.7
3
+ Version: 2.0.9
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.7"
7
+ version = "2.0.9"
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