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.
- {ccmd-2.0.7/ccmd.egg-info → ccmd-2.0.9}/PKG-INFO +1 -1
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/__init__.py +1 -1
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/cli.py +163 -30
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/shell_cmds.py +82 -7
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/browser.py +18 -1
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/hub.py +35 -3
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/project_edit.py +6 -2
- {ccmd-2.0.7 → ccmd-2.0.9/ccmd.egg-info}/PKG-INFO +1 -1
- {ccmd-2.0.7 → ccmd-2.0.9}/pyproject.toml +1 -1
- {ccmd-2.0.7 → ccmd-2.0.9}/LICENSE +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/MANIFEST.in +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/README.md +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/__main__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/config/__init__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/config/paths.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/config/schema.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/__init__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/__init__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/aider.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/base.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/chatgpt.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/claude.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/codex.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/cursor.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/goose.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/agents/grok.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/fs.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/launcher.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/migrate.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/profiles.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/projects.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/scan.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/sessions.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/core/ssh.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/mcp/__init__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/mcp/server.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/__init__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/app.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/ascii.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/__init__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/agents.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/aliases.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/sessions.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/screens/ssh.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/theme.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/widgets/__init__.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd/tui/widgets/spinner.py +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/SOURCES.txt +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/dependency_links.txt +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/entry_points.txt +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/requires.txt +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/ccmd.egg-info/top_level.txt +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/commands.yaml +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/requirements.txt +0 -0
- {ccmd-2.0.7 → ccmd-2.0.9}/setup.cfg +0 -0
|
@@ -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 "
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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="
|
|
306
|
-
help="install
|
|
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
|
-
|
|
247
|
-
lines.
|
|
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(
|
|
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
|
-
|
|
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
|