refactor(bottles): introduce BottlePlan base + move print onto plan
test / run tests/run_tests.py (pull_request) Successful in 19s
test / run tests/run_tests.py (pull_request) Successful in 19s
- Add BottlePlan (frozen dataclass + ABC) with spec, stage_dir, and an abstract `print(*, remote_control)` method. - DockerBottlePlan now inherits from BottlePlan; spec/stage_dir come from the base, Docker-specific fields stay on the subclass. - Move BottleSpec from bottles/docker.py to bottles/__init__.py so the cross-platform types live together. docker.py pulls them via `from . import ...`. - Move show_plan from cli/start.py to `DockerBottlePlan.print`. Caller becomes `plan.print(remote_control=...)`. The CLI no longer reads any Docker-specific fields. - BottlePlatform.prepare is now typed `Callable[..., BottlePlan]`. cmd_start drops ~46 more lines.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
A bottle is a running, isolated environment with claude inside. Each
|
||||
platform exposes two functions:
|
||||
|
||||
prepare(spec, stage_dir=...) -> Plan
|
||||
prepare(spec, stage_dir=...) -> BottlePlan
|
||||
Resolves names, validates host-side prerequisites, and writes
|
||||
scratch files. No remote/runtime resources are created yet.
|
||||
Safe to call before the y/N preflight.
|
||||
@@ -20,14 +20,48 @@ environment picks.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import AbstractContextManager
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Callable, Protocol
|
||||
|
||||
from ..log import die
|
||||
from .docker import BottleSpec, launch_docker_bottle, prepare_docker_bottle
|
||||
from ..manifest import Manifest
|
||||
|
||||
__all__ = ["Bottle", "BottlePlatform", "BottleSpec", "get_bottle_platform"]
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BottleSpec:
|
||||
"""CLI-supplied intent. Platform-agnostic — each platform's prepare
|
||||
step consumes it and produces its own platform-specific plan.
|
||||
Resolved values (image names, container name, scratch paths, runsc
|
||||
availability) live on the plan, not the spec."""
|
||||
|
||||
manifest: Manifest
|
||||
agent_name: str
|
||||
copy_cwd: bool
|
||||
user_cwd: str
|
||||
forward_oauth_token: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BottlePlan(ABC):
|
||||
"""Base output of a platform's prepare step. Concrete subclasses
|
||||
(e.g. DockerBottlePlan) add platform-specific resolved fields and
|
||||
implement `print`."""
|
||||
|
||||
spec: BottleSpec
|
||||
stage_dir: Path
|
||||
|
||||
@abstractmethod
|
||||
def print(self, *, remote_control: bool) -> None:
|
||||
"""Render the y/N preflight summary to stderr."""
|
||||
|
||||
|
||||
# Import concrete platform factories AFTER the base types are defined,
|
||||
# so each platform module can pull BottleSpec / BottlePlan via
|
||||
# `from . import ...` without hitting a partially-initialized module.
|
||||
from .docker import launch_docker_bottle, prepare_docker_bottle # noqa: E402
|
||||
|
||||
|
||||
class Bottle(Protocol):
|
||||
@@ -50,7 +84,7 @@ class BottlePlatform:
|
||||
"""Bundles a platform's two-phase factory under one selectable name."""
|
||||
|
||||
name: str
|
||||
prepare: Callable[..., object]
|
||||
prepare: Callable[..., BottlePlan]
|
||||
launch: Callable[..., AbstractContextManager[Bottle]]
|
||||
|
||||
|
||||
@@ -72,3 +106,12 @@ def get_bottle_platform() -> BottlePlatform:
|
||||
known = ", ".join(sorted(_PLATFORMS))
|
||||
die(f"unknown CLAUDE_BOTTLE_PLATFORM={name!r}; known platforms: {known}")
|
||||
return _PLATFORMS[name]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Bottle",
|
||||
"BottlePlan",
|
||||
"BottlePlatform",
|
||||
"BottleSpec",
|
||||
"get_bottle_platform",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user