Skip to content

Commit 63ec333

Browse files
authored
Merge pull request #23 from VibePod/list-cmd
Adapt list to show multiple workspaces, and separate available agent list
2 parents 3f80014 + 0302fa8 commit 63ec333

2 files changed

Lines changed: 102 additions & 34 deletions

File tree

src/vibepod/commands/list_cmd.py

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,35 @@
1313
from vibepod.utils.console import console, error
1414

1515

16-
def _running_map(containers: list[Any]) -> dict[str, Any]:
17-
by_agent: dict[str, Any] = {}
16+
def _configured_agent_rows() -> list[dict[str, str]]:
17+
rows: list[dict[str, str]] = []
18+
for agent in SUPPORTED_AGENTS:
19+
rows.append(
20+
{
21+
"short": get_agent_shortcut(agent) or "-",
22+
"agent": agent,
23+
"image": DEFAULT_IMAGES[agent],
24+
}
25+
)
26+
return rows
27+
28+
29+
def _running_rows(containers: list[Any]) -> list[dict[str, str]]:
30+
rows: list[dict[str, str]] = []
1831
for container in containers:
19-
agent = container.labels.get("vibepod.agent")
20-
if agent and agent not in by_agent:
21-
by_agent[agent] = container
22-
return by_agent
32+
labels = getattr(container, "labels", {}) or {}
33+
agent = labels.get("vibepod.agent")
34+
status = getattr(container, "status", "-")
35+
if not agent or status != "running":
36+
continue
37+
rows.append(
38+
{
39+
"agent": agent,
40+
"container": getattr(container, "name", "-"),
41+
"context": labels.get("vibepod.workspace", "-"),
42+
}
43+
)
44+
return sorted(rows, key=lambda row: (row["agent"], row["container"]))
2345

2446

2547
def list_agents(
@@ -38,36 +60,38 @@ def list_agents(
3860
raise typer.Exit(EXIT_DOCKER_NOT_RUNNING) from exc
3961
containers = []
4062

41-
mapped = _running_map(containers)
42-
rows: list[dict[str, str]] = []
43-
for agent in SUPPORTED_AGENTS:
44-
container = mapped.get(agent)
45-
shortcut = get_agent_shortcut(agent) or "-"
46-
rows.append(
47-
{
48-
"short": shortcut,
49-
"agent": agent,
50-
"image": DEFAULT_IMAGES[agent],
51-
"status": container.status if container else "stopped",
52-
"workspace": container.labels.get("vibepod.workspace", "-") if container else "-",
53-
}
54-
)
55-
56-
if running:
57-
rows = [r for r in rows if r["status"] == "running"]
63+
running_rows = _running_rows(containers)
64+
configured_rows = _configured_agent_rows()
5865

5966
if as_json:
6067
import json
6168

62-
print(json.dumps(rows, indent=2))
69+
payload: dict[str, Any] = {"running": running_rows}
70+
if not running:
71+
payload["agents"] = configured_rows
72+
print(json.dumps(payload, indent=2))
73+
return
74+
75+
running_table = Table(title="Running Agents", title_justify="left")
76+
running_table.add_column("AGENT", style="cyan")
77+
running_table.add_column("CONTAINER", style="magenta")
78+
running_table.add_column("CONTEXT")
79+
80+
if running_rows:
81+
for row in running_rows:
82+
running_table.add_row(row["agent"], row["container"], row["context"])
83+
console.print(running_table)
84+
else:
85+
console.print("No running agents.")
86+
87+
if running:
6388
return
6489

65-
table = Table(title="VibePod Agents")
66-
table.add_column("SHORT", style="green")
67-
table.add_column("AGENT", style="cyan")
68-
table.add_column("IMAGE", style="magenta")
69-
table.add_column("STATUS")
70-
table.add_column("WORKSPACE")
71-
for row in rows:
72-
table.add_row(row["short"], row["agent"], row["image"], row["status"], row["workspace"])
73-
console.print(table)
90+
console.print()
91+
reference_table = Table(title="Configured Agents", title_justify="left")
92+
reference_table.add_column("SHORT", style="green")
93+
reference_table.add_column("AGENT", style="cyan")
94+
reference_table.add_column("BASE IMAGE", style="magenta")
95+
for row in configured_rows:
96+
reference_table.add_row(row["short"], row["agent"], row["image"])
97+
console.print(reference_table)

tests/test_list.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,53 @@ def list_managed(self, all_containers: bool = True): # noqa: ARG002
2323
result = runner.invoke(app, ["list", "--json"])
2424
assert result.exit_code == 0
2525

26-
rows = json.loads(result.stdout)
26+
payload = json.loads(result.stdout)
27+
assert payload["running"] == []
28+
29+
rows = payload["agents"]
2730
by_agent = {row["agent"]: row for row in rows}
2831
assert set(by_agent.keys()) == set(SUPPORTED_AGENTS)
2932

3033
for shortcut, agent in AGENT_SHORTCUTS.items():
3134
assert by_agent[agent]["short"] == shortcut
35+
36+
37+
def test_list_running_json_preserves_multiple_instances(monkeypatch) -> None:
38+
class _FakeContainer:
39+
def __init__(self, name: str, status: str, labels: dict[str, str]) -> None:
40+
self.name = name
41+
self.status = status
42+
self.labels = labels
43+
44+
class _FakeDockerManager:
45+
def list_managed(self, all_containers: bool = True): # noqa: ARG002
46+
return [
47+
_FakeContainer(
48+
"vibepod-claude-1",
49+
"running",
50+
{"vibepod.agent": "claude", "vibepod.workspace": "/workspace/a"},
51+
),
52+
_FakeContainer(
53+
"vibepod-claude-2",
54+
"running",
55+
{"vibepod.agent": "claude", "vibepod.workspace": "/workspace/b"},
56+
),
57+
_FakeContainer(
58+
"vibepod-codex-1",
59+
"exited",
60+
{"vibepod.agent": "codex", "vibepod.workspace": "/workspace/c"},
61+
),
62+
]
63+
64+
monkeypatch.setattr(list_cmd, "DockerManager", _FakeDockerManager)
65+
66+
result = runner.invoke(app, ["list", "--running", "--json"])
67+
assert result.exit_code == 0
68+
69+
payload = json.loads(result.stdout)
70+
assert "agents" not in payload
71+
rows = payload["running"]
72+
assert len(rows) == 2
73+
assert [row["container"] for row in rows] == ["vibepod-claude-1", "vibepod-claude-2"]
74+
assert {row["context"] for row in rows} == {"/workspace/a", "/workspace/b"}
75+
assert all(set(row) == {"agent", "container", "context"} for row in rows)

0 commit comments

Comments
 (0)