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.
- {ccmd-2.0.6/ccmd.egg-info → ccmd-2.0.8}/PKG-INFO +1 -1
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/__init__.py +1 -1
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/cli.py +86 -30
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/shell_cmds.py +94 -19
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/browser.py +18 -1
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/hub.py +35 -3
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/project_edit.py +6 -2
- {ccmd-2.0.6 → ccmd-2.0.8/ccmd.egg-info}/PKG-INFO +1 -1
- {ccmd-2.0.6 → ccmd-2.0.8}/pyproject.toml +1 -1
- {ccmd-2.0.6 → ccmd-2.0.8}/LICENSE +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/MANIFEST.in +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/README.md +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/__main__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/config/__init__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/config/paths.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/config/schema.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/__init__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/__init__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/aider.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/base.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/chatgpt.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/claude.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/codex.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/cursor.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/goose.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/agents/grok.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/fs.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/launcher.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/migrate.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/profiles.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/projects.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/scan.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/sessions.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/core/ssh.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/mcp/__init__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/mcp/server.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/__init__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/app.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/ascii.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/__init__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/agents.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/aliases.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/sessions.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/screens/ssh.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/theme.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/widgets/__init__.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd/tui/widgets/spinner.py +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/SOURCES.txt +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/dependency_links.txt +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/entry_points.txt +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/requires.txt +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/ccmd.egg-info/top_level.txt +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/commands.yaml +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/requirements.txt +0 -0
- {ccmd-2.0.6 → ccmd-2.0.8}/setup.cfg +0 -0
|
@@ -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 "
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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="
|
|
306
|
-
help="install
|
|
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
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
242
|
-
lines.
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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:
|
|
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
|