ccmd 2.0.6__tar.gz → 2.0.8__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.6/ccmd.egg-info → ccmd-2.0.8}/PKG-INFO +1 -1
  2. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/__init__.py +1 -1
  3. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/cli.py +86 -30
  4. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/shell_cmds.py +94 -19
  5. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/browser.py +18 -1
  6. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/hub.py +35 -3
  7. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/project_edit.py +6 -2
  8. {ccmd-2.0.6 → ccmd-2.0.8/ccmd.egg-info}/PKG-INFO +1 -1
  9. {ccmd-2.0.6 → ccmd-2.0.8}/pyproject.toml +1 -1
  10. {ccmd-2.0.6 → ccmd-2.0.8}/LICENSE +0 -0
  11. {ccmd-2.0.6 → ccmd-2.0.8}/MANIFEST.in +0 -0
  12. {ccmd-2.0.6 → ccmd-2.0.8}/README.md +0 -0
  13. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/__main__.py +0 -0
  14. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/config/__init__.py +0 -0
  15. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/config/paths.py +0 -0
  16. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/config/schema.py +0 -0
  17. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/__init__.py +0 -0
  18. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/__init__.py +0 -0
  19. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/aider.py +0 -0
  20. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/base.py +0 -0
  21. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/chatgpt.py +0 -0
  22. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/claude.py +0 -0
  23. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/codex.py +0 -0
  24. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/cursor.py +0 -0
  25. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/goose.py +0 -0
  26. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/grok.py +0 -0
  27. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/fs.py +0 -0
  28. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/launcher.py +0 -0
  29. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/migrate.py +0 -0
  30. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/profiles.py +0 -0
  31. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/projects.py +0 -0
  32. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/scan.py +0 -0
  33. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/sessions.py +0 -0
  34. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/ssh.py +0 -0
  35. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/mcp/__init__.py +0 -0
  36. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/mcp/server.py +0 -0
  37. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/__init__.py +0 -0
  38. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/app.py +0 -0
  39. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/ascii.py +0 -0
  40. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/__init__.py +0 -0
  41. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/agents.py +0 -0
  42. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/aliases.py +0 -0
  43. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/sessions.py +0 -0
  44. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/ssh.py +0 -0
  45. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/theme.py +0 -0
  46. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/widgets/__init__.py +0 -0
  47. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/widgets/spinner.py +0 -0
  48. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/SOURCES.txt +0 -0
  49. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/dependency_links.txt +0 -0
  50. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/entry_points.txt +0 -0
  51. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/requires.txt +0 -0
  52. {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/top_level.txt +0 -0
  53. {ccmd-2.0.6 → ccmd-2.0.8}/commands.yaml +0 -0
  54. {ccmd-2.0.6 → ccmd-2.0.8}/requirements.txt +0 -0
  55. {ccmd-2.0.6 → ccmd-2.0.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccmd
3
- Version: 2.0.6
3
+ Version: 2.0.8
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.6"
3
+ __version__ = "2.0.8"
4
4
  __all__ = ["__version__"]
@@ -20,55 +20,84 @@ from ccmd.core.sessions import SessionStore
20
20
  from ccmd.core.ssh import SSHStore
21
21
 
22
22
 
23
+ def _print_cmd_rows(rows: list, *, verbose: bool = False) -> None:
24
+ if not rows:
25
+ print("no matching commands")
26
+ return
27
+ for r in rows:
28
+ v = r["variants"]
29
+ print(
30
+ f"{r['short']:<10} {r['project']:<20} "
31
+ f"default={r['default_agent']:<8} "
32
+ f"{v.get('claude',''):<10} {v.get('grok',''):<10} "
33
+ f"{v.get('codex',''):<10} {v.get('cursor',''):<10}"
34
+ )
35
+ if verbose or len(rows) == 1:
36
+ print(f"{'':10} path {r['path']}")
37
+ print(
38
+ f"{'':10} all "
39
+ + " ".join(f"{aid}={fn}" for aid, fn in v.items())
40
+ )
41
+ print(f"\n{len(rows)} project(s) · helper: ccmds · file: use ccmd shell list")
42
+
43
+
23
44
  def _cmd_shell(args: argparse.Namespace) -> int:
24
45
  from ccmd.core.shell_cmds import (
46
+ format_cmds_block,
25
47
  install_shell_hook,
26
48
  list_cmd_rows,
27
49
  shell_cmds_path,
28
50
  write_shell_cmds,
29
51
  )
30
52
 
31
- sub = args.shell_cmd or "regen"
53
+ sub = args.shell_cmd or "list"
54
+ filt = getattr(args, "filter", None)
55
+ verbose = bool(getattr(args, "verbose", False))
56
+
32
57
  if sub == "install":
33
- path = write_shell_cmds()
58
+ path, shorts = write_shell_cmds()
34
59
  rcs = install_shell_hook()
35
60
  print(f"wrote {path}")
36
61
  for rc in rcs:
37
62
  print(f"hooked {rc}")
38
63
  print("restart shell or: source ~/.ccmd/shell_cmds.sh")
39
- print("then: ana / anac / anag …")
64
+ print("then: ccmds # list all")
65
+ print(" ana / anac / anag …")
66
+ if verbose:
67
+ _print_cmd_rows(list_cmd_rows(), verbose=False)
40
68
  return 0
41
69
  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})")
70
+ path, shorts = write_shell_cmds()
71
+ print(f"regenerated {path} ({len(shorts)} projects)")
72
+ rows = list_cmd_rows(filter_text=filt)
73
+ _print_cmd_rows(rows, verbose=verbose)
53
74
  return 0
54
75
  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()}")
76
+ rows = list_cmd_rows(filter_text=filt)
77
+ _print_cmd_rows(rows, verbose=verbose or bool(filt))
78
+ print(f"file: {shell_cmds_path()}")
79
+ return 0
80
+ if sub == "show" and filt:
81
+ rows = list_cmd_rows(filter_text=filt)
82
+ _print_cmd_rows(rows, verbose=True)
67
83
  return 0
68
- print("usage: ccmd shell install|regen|list", file=sys.stderr)
84
+ print("usage: ccmd shell install|regen|list [--filter NAME] [-v]", file=sys.stderr)
85
+ print(" ccmds # shell helper (after: source ~/.ccmd/shell_cmds.sh)", file=sys.stderr)
69
86
  return 1
70
87
 
71
88
 
89
+ def _cmd_cmds(args: argparse.Namespace) -> int:
90
+ """Top-level: ccmd cmds [--list] [filter] — same as shell list."""
91
+ from ccmd.core.shell_cmds import list_cmd_rows, shell_cmds_path
92
+
93
+ filt = args.filter or args.name
94
+ rows = list_cmd_rows(filter_text=filt)
95
+ _print_cmd_rows(rows, verbose=bool(filt) or args.verbose)
96
+ print(f"file: {shell_cmds_path()}")
97
+ print("tip: ccmds (shell function) after source ~/.ccmd/shell_cmds.sh")
98
+ return 0
99
+
100
+
72
101
  def _cmd_open(args: argparse.Namespace) -> int:
73
102
  reg = ProjectRegistry()
74
103
  project = reg.get(args.project)
@@ -301,9 +330,35 @@ def build_parser() -> argparse.ArgumentParser:
301
330
  sh.add_argument(
302
331
  "shell_cmd",
303
332
  nargs="?",
304
- choices=["install", "regen", "list"],
305
- default="regen",
306
- help="install=write cmds + hook bashrc; regen=rewrite cmds; list=show",
333
+ choices=["install", "regen", "list", "show"],
334
+ default="list",
335
+ help="install|regen|list|show",
336
+ )
337
+ sh.add_argument(
338
+ "--filter",
339
+ "-f",
340
+ dest="filter",
341
+ help="filter by short/project/path (e.g. ana)",
342
+ )
343
+ sh.add_argument(
344
+ "-v",
345
+ "--verbose",
346
+ action="store_true",
347
+ help="show full agent variant map",
348
+ )
349
+
350
+ cmds = sub.add_parser(
351
+ "cmds",
352
+ help="List short shell commands (same as: ccmd shell list / ccmds)",
353
+ )
354
+ cmds.add_argument("name", nargs="?", help="filter (e.g. ana or flow)")
355
+ cmds.add_argument("--filter", "-f", dest="filter", help="filter text")
356
+ cmds.add_argument("-v", "--verbose", action="store_true")
357
+ cmds.add_argument(
358
+ "--list",
359
+ "-l",
360
+ action="store_true",
361
+ help="list all (default)",
307
362
  )
308
363
 
309
364
  return p
@@ -336,6 +391,7 @@ def main(argv: list[str] | None = None) -> int:
336
391
  "doctor": _cmd_doctor,
337
392
  "mcp": _cmd_mcp,
338
393
  "shell": _cmd_shell,
394
+ "cmds": _cmd_cmds,
339
395
  }
340
396
  handler = handlers.get(args.cmd)
341
397
  if not handler:
@@ -128,26 +128,31 @@ def shell_cmds_path() -> Path:
128
128
 
129
129
 
130
130
  def short_candidates(project_id: str, name: str) -> list[str]:
131
- """Generate preferred short-name candidates for a project."""
131
+ """Generate preferred short-name candidates for a project.
132
+
133
+ Prefer snappy 3–4 letter names (ana, flo) before the full slug.
134
+ """
132
135
  raw = re.sub(r"[^a-z0-9]+", "", (project_id or name).lower())
133
136
  if not raw:
134
137
  raw = "proj"
135
138
  cands: list[str] = []
136
- # full slug if short enough
137
- if 2 <= len(raw) <= 12:
138
- cands.append(raw)
139
- # progressive prefixes: ana, anas, anast…
140
- for n in range(3, min(8, len(raw) + 1)):
141
- cands.append(raw[:n])
139
+ # progressive prefixes first: ana, anas, anast… (user wants ana not anastasis)
140
+ for n in (3, 4, 5, 6, 7, 8):
141
+ if n <= len(raw):
142
+ cands.append(raw[:n])
143
+ # first letters of hyphen/underscore parts (build-your-own byo)
144
+ parts = re.split(r"[-_\s]+", project_id or name)
145
+ initials = "".join(p[0] for p in parts if p)[:6].lower()
146
+ initials = re.sub(r"[^a-z0-9]", "", initials)
147
+ if len(initials) >= 2:
148
+ cands.append(initials)
142
149
  # consonants-only compact form
143
150
  cons = re.sub(r"[aeiou]", "", raw)
144
151
  if len(cons) >= 3:
145
152
  cands.append(cons[:4])
146
- # first letters of hyphen/underscore parts
147
- parts = re.split(r"[-_\s]+", project_id or name)
148
- initials = "".join(p[0] for p in parts if p)[:6].lower()
149
- if len(initials) >= 2:
150
- cands.append(re.sub(r"[^a-z0-9]", "", initials))
153
+ # full slug last (only if reasonably short)
154
+ if 2 <= len(raw) <= 10:
155
+ cands.append(raw)
151
156
  # dedupe preserve order
152
157
  seen: set[str] = set()
153
158
  out: list[str] = []
@@ -238,13 +243,66 @@ def render_shell_cmds(projects: list, shorts: dict[str, str]) -> str:
238
243
  )
239
244
  lines.append("")
240
245
 
241
- lines.append("# end ccmd shell cmds")
242
- lines.append("")
246
+ # helper: list all short commands from the shell
247
+ lines.extend(
248
+ [
249
+ "# list every project short command",
250
+ "ccmds() {",
251
+ ' if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then',
252
+ ' echo "ccmds list all short commands"',
253
+ ' echo "ccmds <name> show one project (e.g. ccmds ana)"',
254
+ ' echo "ccmds -l|--list same as bare ccmds"',
255
+ ' echo "ccmd shell list full CLI list"',
256
+ " return 0",
257
+ " fi",
258
+ ' if [ -z "$1" ] || [ "$1" = "-l" ] || [ "$1" = "--list" ]; then',
259
+ ' command ccmd shell list',
260
+ " return $?",
261
+ " fi",
262
+ ' command ccmd shell list --filter "$1"',
263
+ "}",
264
+ "",
265
+ "# end ccmd shell cmds",
266
+ "",
267
+ ]
268
+ )
269
+ return "\n".join(lines)
270
+
271
+
272
+ def cmds_notice(project, short: str | None = None) -> str:
273
+ """Human-readable one-liner of shortcuts for a project (for TUI/CLI notices)."""
274
+ s = short or getattr(project, "short", None) or ""
275
+ if not s:
276
+ shorts = allocate_shorts([project])
277
+ s = shorts.get(project.id, project.id[:4])
278
+ variants = " ".join(
279
+ f"{s}{suf}={aid}" for aid, suf in AGENT_SUFFIX.items()
280
+ )
281
+ return (
282
+ f"cmds: {s} (default={project.default_agent}) "
283
+ f"{s}c=claude {s}g=grok {s}x=codex {s}u=cursor "
284
+ f"| ccmds or ccmd shell list"
285
+ )
286
+
287
+
288
+ def format_cmds_block(project, short: str | None = None) -> str:
289
+ """Multi-line block shown after save."""
290
+ s = short or getattr(project, "short", None) or project.id[:4]
291
+ lines = [
292
+ f"shell commands for {project.id}:",
293
+ f" {s:<10} open with default agent ({project.default_agent})",
294
+ ]
295
+ for aid, suf in AGENT_SUFFIX.items():
296
+ lines.append(f" {s}{suf:<9} --agent {aid}")
297
+ lines.append("list all: ccmds or ccmd shell list")
243
298
  return "\n".join(lines)
244
299
 
245
300
 
246
- def write_shell_cmds(projects: list | None = None) -> Path:
247
- """Write ~/.ccmd/shell_cmds.sh and persist short names on projects."""
301
+ def write_shell_cmds(projects: list | None = None) -> tuple[Path, dict[str, str]]:
302
+ """Write ~/.ccmd/shell_cmds.sh and persist short names on projects.
303
+
304
+ Returns (path, {project_id: short}).
305
+ """
248
306
  from ccmd.core.projects import ProjectRegistry
249
307
 
250
308
  reg = ProjectRegistry()
@@ -273,7 +331,7 @@ def write_shell_cmds(projects: list | None = None) -> Path:
273
331
  )
274
332
  lines.append(f"{'':10} {p.path}\n")
275
333
  sheet.write_text("".join(lines), encoding="utf-8")
276
- return path
334
+ return path, shorts
277
335
 
278
336
 
279
337
  def install_shell_hook(rc_file: Path | None = None) -> list[Path]:
@@ -283,6 +341,7 @@ def install_shell_hook(rc_file: Path | None = None) -> list[Path]:
283
341
  block = (
284
342
  f"\n{MARKER_BEGIN}\n"
285
343
  f"# managed by ccmd — short project commands (ana/anac/anag…)\n"
344
+ f"# list them anytime: ccmds or ccmds ana\n"
286
345
  f'[ -f "$HOME/.ccmd/shell_cmds.sh" ] && . "$HOME/.ccmd/shell_cmds.sh"\n'
287
346
  f"{MARKER_END}\n"
288
347
  )
@@ -326,22 +385,38 @@ def install_shell_hook(rc_file: Path | None = None) -> list[Path]:
326
385
  return updated
327
386
 
328
387
 
329
- def list_cmd_rows(projects: list | None = None) -> list[dict]:
388
+ def list_cmd_rows(
389
+ projects: list | None = None,
390
+ *,
391
+ filter_text: str | None = None,
392
+ ) -> list[dict]:
330
393
  from ccmd.core.projects import ProjectRegistry
331
394
 
332
395
  projects = list(projects) if projects is not None else ProjectRegistry().list()
333
396
  shorts = allocate_shorts(projects)
334
397
  rows = []
335
398
  for p in projects:
336
- s = shorts.get(p.id, "")
399
+ s = shorts.get(p.id, "") or getattr(p, "short", "") or ""
337
400
  rows.append(
338
401
  {
339
402
  "short": s,
340
403
  "project": p.id,
404
+ "name": p.name,
341
405
  "default_agent": p.default_agent,
342
406
  "path": p.path,
343
407
  "variants": {a: f"{s}{suf}" for a, suf in AGENT_SUFFIX.items()},
344
408
  }
345
409
  )
346
410
  rows.sort(key=lambda r: r["short"])
411
+ if filter_text:
412
+ q = filter_text.strip().lower()
413
+ rows = [
414
+ r
415
+ for r in rows
416
+ if q in r["short"].lower()
417
+ or q in r["project"].lower()
418
+ or q in r["name"].lower()
419
+ or q in r["path"].lower()
420
+ or any(q in v.lower() for v in r["variants"].values())
421
+ ]
347
422
  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.6
3
+ Version: 2.0.8
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.6"
7
+ version = "2.0.8"
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