diff --git a/claude_bottle/backend/docker/bottle_plan.py b/claude_bottle/backend/docker/bottle_plan.py index 1805dd6..1efb14e 100644 --- a/claude_bottle/backend/docker/bottle_plan.py +++ b/claude_bottle/backend/docker/bottle_plan.py @@ -17,6 +17,7 @@ from ...log import info from ...pipelock import PipelockProxyPlan from ...supervise import SupervisePlan from .. import BottlePlan +from ..print_util import print_multi @dataclass(frozen=True) @@ -70,22 +71,10 @@ class DockerBottlePlan(BottlePlan): # from the agent to the proxy is needed. env_names = sorted(set(bottle.env.keys()) | set(self.forwarded_env.keys())) - def _multi(label: str, values: list[str]) -> None: - """Print a label with N continuation-indented values. Used - for env / skills / git-gate / egress where one item - per line keeps the summary scannable.""" - if not values: - info(f"{label}: (none)") - return - info(f"{label}: {values[0]}") - indent = " " * (len(label) + 2) - for v_ in values[1:]: - info(f"{indent}{v_}") - print(file=sys.stderr) info(f"agent : {spec.agent_name}") - _multi("env ", env_names) - _multi("skills ", list(agent.skills)) + print_multi("env ", env_names) + print_multi("skills ", list(agent.skills)) info(f"bottle : {agent.bottle}") git_lines = [ @@ -93,13 +82,13 @@ class DockerBottlePlan(BottlePlan): for u in self.git_gate_plan.upstreams ] if git_lines: - _multi(" git gate ", git_lines) + print_multi(" git gate ", git_lines) if self.egress_plan.routes: egress_lines = [] for r in self.egress_plan.routes: auth = f" [auth:{r.auth_scheme}]" if r.auth_scheme else "" egress_lines.append(f"{r.host}{auth}") - _multi(" egress ", egress_lines) + print_multi(" egress ", egress_lines) print(file=sys.stderr) diff --git a/claude_bottle/backend/print_util.py b/claude_bottle/backend/print_util.py new file mode 100644 index 0000000..f37e22c --- /dev/null +++ b/claude_bottle/backend/print_util.py @@ -0,0 +1,28 @@ +"""Shared print helpers for BottlePlan.print implementations. + +Lifts the multi-value label printer out of DockerBottlePlan so the +smolmachines backend (and any future backend) renders the same +two-column scannable preflight without duplicating the indent +math.""" + +from __future__ import annotations + +from typing import Sequence + +from ..log import info + + +def print_multi(label: str, values: Sequence[str]) -> None: + """Print `label: ` with continuation lines indented to + align under the first value. Empty `values` renders `(none)`. + + Used by every backend's `BottlePlan.print` for env / skills / + git / egress — one item per line keeps the preflight summary + scannable when an agent has many of any of these.""" + if not values: + info(f"{label}: (none)") + return + info(f"{label}: {values[0]}") + indent = " " * (len(label) + 2) + for v in values[1:]: + info(f"{indent}{v}") diff --git a/claude_bottle/backend/smolmachines/bottle_plan.py b/claude_bottle/backend/smolmachines/bottle_plan.py index 81c728d..166da28 100644 --- a/claude_bottle/backend/smolmachines/bottle_plan.py +++ b/claude_bottle/backend/smolmachines/bottle_plan.py @@ -8,11 +8,13 @@ launch flow grows.""" from __future__ import annotations +import sys from dataclasses import dataclass from pathlib import Path from ...log import info from .. import BottlePlan +from ..print_util import print_multi @dataclass(frozen=True) @@ -33,32 +35,27 @@ class SmolmachinesBottlePlan(BottlePlan): host_port_map: dict[str, int] def print(self, *, remote_control: bool) -> None: - """Compact y/N preflight for the smolmachines path. Mirrors - the docker preflight's layout so operators don't have to - learn two formats.""" + """Compact y/N preflight. Same shape as the Docker + backend's so operators see one format across backends.""" del remote_control # not surfaced in the compact summary spec = self.spec manifest = spec.manifest agent = manifest.agents[spec.agent_name] bottle = manifest.bottle_for(spec.agent_name) - info(f"backend: smolmachines") - info(f"agent: {spec.agent_name}") - info(f"bottle: {agent.bottle}") - info(f"slug: {self.slug}") - info(f"gvproxy: {self.gvproxy_gateway} on {self.gvproxy_subnet}") - env_names = sorted(bottle.env.keys()) - skills = list(agent.skills) - upstreams = [g.Name for g in bottle.git] + upstreams = [ + f"{g.Name} → {g.Upstream}" for g in bottle.git + ] routes = [r.host for r in bottle.egress.routes] - info(f"env: {', '.join(env_names) if env_names else '(none)'}") - info(f"skills: {', '.join(skills) if skills else '(none)'}") - info(f"git: {', '.join(upstreams) if upstreams else '(none)'}") - info(f"routes: {', '.join(routes) if routes else '(none)'}") - info(f"smolfile: {self.smolfile_path}") - info(f"gvproxy config: {self.gvproxy_config_path}") - info( - "(chunk 1 of PRD 0023: prepare-only — launch is " - "not yet implemented)" - ) + + print(file=sys.stderr) + info(f"agent : {spec.agent_name}") + print_multi("env ", env_names) + print_multi("skills ", list(agent.skills)) + info(f"bottle : {agent.bottle}") + if upstreams: + print_multi(" git gate ", upstreams) + if routes: + print_multi(" egress ", routes) + print(file=sys.stderr)