From f32057aa7b8c9060ce1c351160d87a7ba347d72e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Wed, 7 Jan 2026 15:59:30 +0100 Subject: [PATCH] cli: Add `list` command with `ls` alias (#158) --- CLAUDE.md | 3 ++- README.md | 37 ++++++++++++++++++++++++++++- docs/commands.md | 38 +++++++++++++++++++++++++++++- src/compose_farm/cli/monitoring.py | 37 +++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fd2755d..76e8ee9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ src/compose_farm/ │ ├── config.py # Config subcommand (init, show, path, validate, edit, symlink) │ ├── lifecycle.py # up, down, stop, pull, restart, update, apply, compose commands │ ├── management.py # refresh, check, init-network, traefik-file commands -│ ├── monitoring.py # logs, ps, stats commands +│ ├── monitoring.py # logs, ps, stats, list commands │ ├── ssh.py # SSH key management (setup, status, keygen) │ └── web.py # Web UI server command ├── compose.py # Compose file parsing (.env, ports, volumes, networks) @@ -157,6 +157,7 @@ CLI available as `cf` or `compose-farm`. | `logs` | Show stack logs | | `ps` | Show status of all stacks | | `stats` | Show overview (hosts, stacks, pending migrations; `--live` for container counts) | +| `list` | List stacks and hosts (`--simple` for scripting, `--host` to filter) | | `refresh` | Update state from reality: discover running stacks, capture image digests | | `check` | Validate config, traefik labels, mounts, networks; show host compatibility | | `init-network` | Create Docker network on hosts with consistent subnet/gateway | diff --git a/README.md b/README.md index ff536e3..0dd2c79 100644 --- a/README.md +++ b/README.md @@ -395,6 +395,7 @@ Multi-host orchestration that Docker Compose can't do: | `cf traefik-file` | Generate Traefik file-provider config | | `cf config` | Manage config files (init, show, validate, edit, symlink) | | `cf ssh` | Manage SSH keys (setup, status, keygen) | +| `cf list` | List all stacks and their assigned hosts | ### Aliases @@ -403,10 +404,11 @@ Short aliases for frequently used commands: | Alias | Command | Alias | Command | |-------|---------|-------|---------| | `cf a` | `apply` | `cf s` | `stats` | -| `cf l` | `logs` | `cf c` | `compose` | +| `cf l` | `logs` | `cf ls` | `list` | | `cf r` | `restart` | `cf rf` | `refresh` | | `cf u` | `update` | `cf ck` | `check` | | `cf p` | `pull` | `cf tf` | `traefik-file` | +| `cf c` | `compose` | | | Each command replaces: look up host → SSH → find compose file → run `ssh host "cd /opt/compose/plex && docker compose up -d"`. @@ -511,6 +513,7 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a │ service. │ │ ps Show status of stacks. │ │ stats Show overview statistics for hosts and stacks. │ +│ list List all stacks and their assigned hosts. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Server ─────────────────────────────────────────────────────────────────────╮ │ web Start the web UI server. │ @@ -1191,6 +1194,38 @@ Full `--help` output for each command. See the [Usage](#usage) table above for a +
+See the output of cf list --help + + + + + + + + + + + +```yaml + + Usage: cf list [OPTIONS] + + List all stacks and their assigned hosts. + +╭─ Options ────────────────────────────────────────────────────────────────────╮ +│ --host -H TEXT Filter to stacks on this host │ +│ --simple -s Plain output (one stack per line, for scripting) │ +│ --config -c PATH Path to config file │ +│ --help -h Show this message and exit. │ +╰──────────────────────────────────────────────────────────────────────────────╯ + +``` + + + +
+ **Server**
diff --git a/docs/commands.md b/docs/commands.md index 43ac1fd..44d59f9 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -23,6 +23,7 @@ Commands are either **Docker Compose wrappers** (`up`, `down`, `stop`, `restart` | **Monitoring** | `ps` | Show stack status | | | `logs` | Show stack logs | | | `stats` | Show overview statistics | +| | `list` | List stacks and hosts | | **Configuration** | `check` | Validate config and mounts | | | `refresh` | Sync state from reality | | | `init-network` | Create Docker network | @@ -45,10 +46,11 @@ Short aliases for frequently used commands: | Alias | Command | Alias | Command | |-------|---------|-------|---------| | `cf a` | `apply` | `cf s` | `stats` | -| `cf l` | `logs` | `cf c` | `compose` | +| `cf l` | `logs` | `cf ls` | `list` | | `cf r` | `restart` | `cf rf` | `refresh` | | `cf u` | `update` | `cf ck` | `check` | | `cf p` | `pull` | `cf tf` | `traefik-file` | +| `cf c` | `compose` | | | --- @@ -466,6 +468,40 @@ cf stats --live --- +### cf list + +List all stacks and their assigned hosts. + +```bash +cf list [OPTIONS] +``` + +**Options:** + +| Option | Description | +|--------|-------------| +| `--host, -H TEXT` | Filter to stacks on this host | +| `--simple, -s` | Plain output for scripting (one stack per line) | +| `--config, -c PATH` | Path to config file | + +**Examples:** + +```bash +# List all stacks +cf list + +# Filter by host +cf list --host nas + +# Plain output for scripting +cf list --simple + +# Combine: list stack names on a specific host +cf list --host nuc --simple +``` + +--- + ## Configuration Commands ### cf check diff --git a/src/compose_farm/cli/monitoring.py b/src/compose_farm/cli/monitoring.py index 0581014..4d930ee 100644 --- a/src/compose_farm/cli/monitoring.py +++ b/src/compose_farm/cli/monitoring.py @@ -203,6 +203,43 @@ def stats( console.print(_build_summary_table(cfg, state, pending)) +@app.command("list", rich_help_panel="Monitoring") +def list_( + host: HostOption = None, + simple: Annotated[ + bool, + typer.Option("--simple", "-s", help="Plain output (one stack per line, for scripting)"), + ] = False, + config: ConfigOption = None, +) -> None: + """List all stacks and their assigned hosts.""" + cfg = load_config_or_exit(config) + + stacks: list[tuple[str, str | list[str]]] = list(cfg.stacks.items()) + if host: + stacks = [(s, h) for s, h in stacks if str(h) == host or host in str(h).split(",")] + + if simple: + for stack, _ in sorted(stacks): + console.print(stack) + else: + # Assign colors to hosts for visual grouping + host_colors = ["magenta", "cyan", "green", "yellow", "blue", "red"] + unique_hosts = sorted({str(h) for _, h in stacks}) + host_color_map = {h: host_colors[i % len(host_colors)] for i, h in enumerate(unique_hosts)} + + table = Table(title="Stacks", show_header=True, header_style="bold cyan") + table.add_column("Stack") + table.add_column("Host") + + for stack, host_val in sorted(stacks): + color = host_color_map.get(str(host_val), "white") + table.add_row(f"[{color}]{stack}[/]", f"[{color}]{host_val}[/]") + + console.print(table) + + # Aliases (hidden from help) app.command("l", hidden=True)(logs) # cf l = cf logs +app.command("ls", hidden=True)(list_) # cf ls = cf list app.command("s", hidden=True)(stats) # cf s = cf stats