diff --git a/AGENTS.md b/AGENTS.md index a427128..12c7742 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,7 @@ backend remains available with `BOT_BOTTLE_BACKEND=docker` or - `.bot-bottle/` — per-repo agent and bottle manifests (YAML markdown format). - `examples/` — example bottles and agents showing the manifest format. - `docs/README.md` — docs overview; when to write which document. +- `docs/glossary.md` — canonical term definitions (Agent Provider, Bottle, Sealed Bottle, etc.). - `docs/prds/` — product requirement docs (see `docs/prds/README.md` for format). - `docs/research/` — research notes (see `docs/research/README.md`). - `docs/decisions/` — decision records (ADR-lite). diff --git a/bot_bottle/backend/base.py b/bot_bottle/backend/base.py index a58ad4f..14d5ecf 100644 --- a/bot_bottle/backend/base.py +++ b/bot_bottle/backend/base.py @@ -177,7 +177,7 @@ class ExecResult: @dataclass(frozen=True) class ActiveAgent: - """One currently-running agent, as the CLI `list active` and + """One currently-running agent, as the CLI `active` and dashboard agents pane render it. ("Agent" is the project's consistent name for the thing running inside a bottle — the bottle is the container, the agent is what runs in it.) @@ -570,7 +570,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): Linux + KVM. Used by the cross-backend `enumerate_active_agents` / `cmd_cleanup` to skip backends the operator hasn't installed, so a docker-only host - doesn't fail when `cli.py list active` walks past + doesn't fail when `cli.py active` walks past firecracker.""" @classmethod diff --git a/bot_bottle/backend/docker/enumerate.py b/bot_bottle/backend/docker/enumerate.py index 0337d5a..8bfd91c 100644 --- a/bot_bottle/backend/docker/enumerate.py +++ b/bot_bottle/backend/docker/enumerate.py @@ -1,6 +1,6 @@ """Active-agent enumeration for the docker backend. -Returns `ActiveAgent` records the CLI `list active` command and the +Returns `ActiveAgent` records the CLI `active` command and the dashboard agents pane consume. Empty when docker isn't reachable — gated by `has_backend('docker')` at the cross-backend caller so this module trusts that docker is available when called. @@ -60,7 +60,7 @@ def _parse_services_by_project(stdout: str) -> dict[str, set[str]]: def _query_services_by_project() -> dict[str, set[str]]: """One `docker ps` call → `{project: {service, ...}}`. Used - by the CLI's `list active` and the dashboard's agents pane — + by the CLI's `active` and the dashboard's agents pane — one subprocess per refresh tick, not one per bottle.""" try: r = subprocess.run( diff --git a/bot_bottle/backend/selection.py b/bot_bottle/backend/selection.py index 60e2fa5..85ffb6b 100644 --- a/bot_bottle/backend/selection.py +++ b/bot_bottle/backend/selection.py @@ -168,7 +168,7 @@ def has_backend(name: str) -> bool: def enumerate_active_agents() -> list[ActiveAgent]: """All currently-running agents, across every available - backend. Used by CLI `list active` and the dashboard's agents + backend. Used by CLI `active` and the dashboard's agents pane so neither has to know which backends exist. Skips backends whose `is_available()` reports False. diff --git a/bot_bottle/cli/commands/__init__.py b/bot_bottle/cli/commands/__init__.py index 813524f..de42a33 100644 --- a/bot_bottle/cli/commands/__init__.py +++ b/bot_bottle/cli/commands/__init__.py @@ -17,12 +17,12 @@ from typing import Callable # command name -> ":". Kept as strings so building # the registry imports nothing; the module loads only when dispatched. _HANDLERS: dict[str, str] = { + "active": "active:cmd_active", "backend": "backend:cmd_backend", "cleanup": "cleanup:cmd_cleanup", "commit": "commit:cmd_commit", "edit": "edit:cmd_edit", "help": "help:cmd_help", - "info": "info:cmd_info", "init": "init:cmd_init", "list": "list:cmd_list", "login": "login:cmd_login", diff --git a/bot_bottle/cli/commands/active.py b/bot_bottle/cli/commands/active.py new file mode 100644 index 0000000..3c32122 --- /dev/null +++ b/bot_bottle/cli/commands/active.py @@ -0,0 +1,53 @@ +"""active: list currently-running bot-bottle bottles.""" + +from __future__ import annotations + +import os +import sys + +from ...backend import enumerate_active_agents +from ..constants import PROG + +_ANSI_COLOR_CODES: dict[str, str] = { + "red": "\033[91m", + "green": "\033[92m", + "yellow": "\033[93m", + "blue": "\033[94m", + "magenta": "\033[95m", +} +_ANSI_RESET = "\033[0m" + + +def _ansi_label(text: str, color: str) -> str: + if not color: + return text + if not sys.stdout.isatty(): + return text + term = os.environ.get("TERM", "") + if term in ("dumb", ""): + return text + code = _ANSI_COLOR_CODES.get(color) + if not code: + return text + return f"{code}{text}{_ANSI_RESET}" + + +def cmd_active(argv: list[str]) -> int: + if argv and argv[0] in ("-h", "--help"): + sys.stderr.write(f"usage: {PROG} active\n") + sys.stderr.write("\nList all currently-running bot-bottle bottles.\n") + sys.stderr.write("Output: \\t\\t