refactor!: rename project to bot-bottle

Assisted-by: Codex
This commit is contained in:
2026-05-28 17:56:14 -04:00
parent 8875d8cc17
commit c08b09dc9f
200 changed files with 1271 additions and 1271 deletions
+5 -5
View File
@@ -36,7 +36,7 @@ python -m unittest tests.unit.test_pipelock_yaml # one file
```
Discovery is invoked with `-t .` (top-level dir = repo root) so the
`claude_bottle` package on `sys.path` resolves correctly.
`bot_bottle` package on `sys.path` resolves correctly.
## What the integration tests cover
@@ -56,16 +56,16 @@ Discovery is invoked with `-t .` (top-level dir = repo root) so the
`tests/canaries/` holds upstream-regression checks (e.g. the pinned
pipelock digest's binary still runs). These are gated on
`CLAUDE_BOTTLE_RUN_CANARIES=1` and not part of the per-push suite.
`BOT_BOTTLE_RUN_CANARIES=1` and not part of the per-push suite.
They're invoked by the scheduled `canaries` workflow.
```bash
CLAUDE_BOTTLE_RUN_CANARIES=1 python -m unittest discover -t . -s tests/canaries -v
BOT_BOTTLE_RUN_CANARIES=1 python -m unittest discover -t . -s tests/canaries -v
```
## What's NOT covered
- `claude_bottle/ssh.py` end-to-end (would need a fake SSH host inside
- `bot_bottle/ssh.py` end-to-end (would need a fake SSH host inside
the container).
- A live SSH-through-pipelock tunnel against a real Tailscale-style IP.
- DLP false-positive measurements.
@@ -80,7 +80,7 @@ CLAUDE_BOTTLE_RUN_CANARIES=1 python -m unittest discover -t . -s tests/canaries
```python
import unittest
from claude_bottle.<module> import <symbol>
from bot_bottle.<module> import <symbol>
class TestThing(unittest.TestCase):
def test_x(self):
+4 -4
View File
@@ -3,7 +3,7 @@
This test exists to catch a broken upstream packaging at the pinned
digest. It is NOT part of the per-push suite — that would couple every
dev push to upstream registry availability. Set
CLAUDE_BOTTLE_RUN_CANARIES=1 to opt in (a scheduled CI workflow does
BOT_BOTTLE_RUN_CANARIES=1 to opt in (a scheduled CI workflow does
this; humans can run it ad-hoc the same way).
"""
@@ -11,13 +11,13 @@ import os
import subprocess
import unittest
from claude_bottle.backend.docker.pipelock import PIPELOCK_IMAGE
from bot_bottle.backend.docker.pipelock import PIPELOCK_IMAGE
from tests._docker import skip_unless_docker
@unittest.skipUnless(
os.environ.get("CLAUDE_BOTTLE_RUN_CANARIES") == "1",
"canary suite is opt-in; set CLAUDE_BOTTLE_RUN_CANARIES=1 to run",
os.environ.get("BOT_BOTTLE_RUN_CANARIES") == "1",
"canary suite is opt-in; set BOT_BOTTLE_RUN_CANARIES=1 to run",
)
@skip_unless_docker()
class TestPipelockImage(unittest.TestCase):
+3 -3
View File
@@ -10,7 +10,7 @@ import tempfile
from pathlib import Path
from typing import Any, Callable
from claude_bottle.manifest import Manifest
from bot_bottle.manifest import Manifest
def fixture_minimal_dict() -> dict[str, Any]:
@@ -45,8 +45,8 @@ def fixture_with_git_dict() -> dict[str, Any]:
"git": {
"remotes": {
"gitea.dideric.is": {
"Name": "claude-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git",
"Name": "bot-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
"IdentityFile": "/dev/null",
"KnownHostKey": "ssh-ed25519 AAAA...",
},
+12 -12
View File
@@ -7,7 +7,7 @@ an interactive claude session). Instead, this test stages the
minimum the orchestrator interacts with:
- A lightweight `alpine:latest sleep infinity` container named
`claude-bottle-<slug>` (matches the agent container name pattern)
`bot-bottle-<slug>` (matches the agent container name pattern)
on the per-bottle internal network.
- A marker file under `/home/node/.claude/` so we can assert the
transcript snapshot path actually transferred bytes.
@@ -31,15 +31,15 @@ import time
import unittest
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker import bottle_state, capability_apply
from claude_bottle.backend.docker.capability_apply import apply_capability_change
from claude_bottle.backend.docker.network import (
from bot_bottle import supervise
from bot_bottle.backend.docker import bottle_state, capability_apply
from bot_bottle.backend.docker.capability_apply import apply_capability_change
from bot_bottle.backend.docker.network import (
network_create_egress,
network_create_internal,
network_remove,
)
from claude_bottle.backend.docker.sidecar_bundle import (
from bot_bottle.backend.docker.sidecar_bundle import (
sidecar_bundle_container_name,
)
from tests._docker import skip_unless_docker
@@ -61,21 +61,21 @@ class TestCapabilityApply(unittest.TestCase):
def setUp(self):
self.slug = f"cb-test-cap-{os.getpid()}-{int(time.time())}"
self.agent_name = f"claude-bottle-{self.slug}"
self.agent_name = f"bot-bottle-{self.slug}"
self.sidecar_names: list[str] = []
self.internal_net = ""
self.egress_net = ""
# Fake home so tests don't touch ~/.claude-bottle/.
# Fake home so tests don't touch ~/.bot-bottle/.
self._tmp = tempfile.TemporaryDirectory(prefix="cap-apply-int.")
self._original_root = supervise.claude_bottle_root
self._original_root = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
def tearDown(self):
supervise.claude_bottle_root = self._original_root # type: ignore[assignment]
supervise.bot_bottle_root = self._original_root # type: ignore[assignment]
for name in [self.agent_name, *self.sidecar_names]:
subprocess.run(
["docker", "rm", "-f", name],
+2 -2
View File
@@ -13,7 +13,7 @@ import os
import subprocess
import unittest
from claude_bottle.backend.docker.network import (
from bot_bottle.backend.docker.network import (
network_create_egress,
network_create_internal,
network_remove,
@@ -40,7 +40,7 @@ class TestOrphanCleanup(unittest.TestCase):
def test_remove_missing_is_noop(self):
# Returning True == idempotent success.
self.assertTrue(network_remove(f"claude-bottle-net-{self.slug}-does-not-exist"))
self.assertTrue(network_remove(f"bot-bottle-net-{self.slug}-does-not-exist"))
@unittest.skipIf(
os.environ.get("GITEA_ACTIONS") == "true",
@@ -20,7 +20,7 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.backend import BottleSpec, get_bottle_backend
from tests._docker import skip_unless_docker
from tests.fixtures import fixture_minimal
@@ -17,7 +17,7 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.backend import BottleSpec, get_bottle_backend
from tests._docker import skip_unless_docker
from tests.fixtures import fixture_minimal
+9 -9
View File
@@ -26,29 +26,29 @@ import time
import unittest
from pathlib import Path
from claude_bottle.backend.docker.bottle_state import pipelock_state_dir
from claude_bottle.backend.docker.network import (
from bot_bottle.backend.docker.bottle_state import pipelock_state_dir
from bot_bottle.backend.docker.network import (
network_create_egress,
network_create_internal,
network_remove,
)
from claude_bottle.backend.docker.pipelock import (
from bot_bottle.backend.docker.pipelock import (
PIPELOCK_CA_CERT_IN_CONTAINER,
PIPELOCK_CA_KEY_IN_CONTAINER,
pipelock_tls_init,
)
from claude_bottle.pipelock import PipelockProxy
from claude_bottle.backend.docker.pipelock_apply import (
from bot_bottle.pipelock import PipelockProxy
from bot_bottle.backend.docker.pipelock_apply import (
PipelockApplyError,
apply_allowlist_change,
fetch_current_allowlist,
fetch_current_yaml,
)
from claude_bottle.backend.docker.sidecar_bundle import (
from bot_bottle.backend.docker.sidecar_bundle import (
SIDECAR_BUNDLE_IMAGE,
sidecar_bundle_container_name,
)
from claude_bottle.yaml_subset import parse_yaml_subset
from bot_bottle.yaml_subset import parse_yaml_subset
from tests._docker import skip_unless_docker
from tests.fixtures import fixture_minimal
@@ -77,7 +77,7 @@ class TestPipelockApply(unittest.TestCase):
if n:
network_remove(n)
shutil.rmtree(self.work_dir, ignore_errors=True)
# Clean up the per-slug state dir under ~/.claude-bottle/state/
# Clean up the per-slug state dir under ~/.bot-bottle/state/
# (apply_allowlist_change writes there; _bring_up calls
# proxy.prepare with the same path so the bind-mount and the
# hot-reload write target stay coherent).
@@ -123,7 +123,7 @@ class TestPipelockApply(unittest.TestCase):
["docker", "create",
"--name", self.sidecar_name,
"--network", self.internal_net,
"-e", "CLAUDE_BOTTLE_SIDECAR_DAEMONS=pipelock",
"-e", "BOT_BOTTLE_SIDECAR_DAEMONS=pipelock",
"-v", f"{prep.yaml_path}:/etc/pipelock.yaml:ro",
"-v", f"{ca_cert_host}:{PIPELOCK_CA_CERT_IN_CONTAINER}:ro",
"-v", f"{ca_key_host}:{PIPELOCK_CA_KEY_IN_CONTAINER}:ro",
@@ -17,7 +17,7 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.backend import BottleSpec, get_bottle_backend
from tests._docker import skip_unless_docker
from tests.fixtures import fixture_minimal
@@ -27,8 +27,8 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from claude_bottle.manifest import Manifest
from bot_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.manifest import Manifest
from tests._docker import skip_unless_docker
@@ -21,8 +21,8 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from claude_bottle.manifest import Manifest
from bot_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.manifest import Manifest
from tests._docker import skip_unless_docker
@@ -22,8 +22,8 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from claude_bottle.manifest import Manifest
from bot_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.manifest import Manifest
from tests._docker import skip_unless_docker
+7 -7
View File
@@ -12,7 +12,7 @@ asserts each one is blocked:
The suite is backend-agnostic — it goes through `get_bottle_backend()`
so a future smolmachines backend can be tested by setting
`CLAUDE_BOTTLE_BACKEND=smolmachines` without touching this file.
`BOT_BOTTLE_BACKEND=smolmachines` without touching this file.
PRD 0022 chunk 1 (this commit): fixture + setUpClass +
tearDownClass + preflight tool check. Attack tests land in
@@ -28,9 +28,9 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from claude_bottle.backend.docker.bottle_state import cleanup_state
from claude_bottle.manifest import Manifest
from bot_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.backend.docker.bottle_state import cleanup_state
from bot_bottle.manifest import Manifest
from tests._docker import skip_unless_docker
@@ -78,16 +78,16 @@ class TestSandboxEscape(unittest.TestCase):
# already covers that. Smolmachines additionally needs smolvm on
# PATH and is macOS-only in v1 (libkrun/TSI). Skip cleanly when
# those are missing rather than die-ing inside backend.prepare.
backend_name = os.environ.get("CLAUDE_BOTTLE_BACKEND", "docker")
backend_name = os.environ.get("BOT_BOTTLE_BACKEND", "docker")
if backend_name == "smolmachines":
if sys.platform != "darwin":
raise unittest.SkipTest(
"CLAUDE_BOTTLE_BACKEND=smolmachines is macOS-only in "
"BOT_BOTTLE_BACKEND=smolmachines is macOS-only in "
"v1 (libkrun TSI)"
)
if shutil.which("smolvm") is None:
raise unittest.SkipTest(
"CLAUDE_BOTTLE_BACKEND=smolmachines requires `smolvm` "
"BOT_BOTTLE_BACKEND=smolmachines requires `smolvm` "
"on PATH: curl -sSL https://smolmachines.com/install.sh | sh"
)
@@ -1,6 +1,6 @@
"""Integration: end-to-end smoke for the PRD 0024 bundle shape.
Verifies that flipping `CLAUDE_BOTTLE_SIDECAR_BUNDLE=1` produces a
Verifies that flipping `BOT_BOTTLE_SIDECAR_BUNDLE=1` produces a
working bottle: `docker compose up` brings the agent + bundle pair
online, the four daemons inside the bundle bind their ports, and
the agent can reach pipelock + supervise via the bundle's network
@@ -21,8 +21,8 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from claude_bottle.backend import BottleSpec, get_bottle_backend
from claude_bottle.manifest import Manifest
from bot_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.manifest import Manifest
from tests._docker import skip_unless_docker
@@ -57,7 +57,7 @@ class TestSidecarBundleCompose(unittest.TestCase):
def test_bottle_up_with_bundle_flag_on(self):
stage_dir = Path(tempfile.mkdtemp(prefix="cb-bundle-smoke."))
try:
with patch.dict(os.environ, {"CLAUDE_BOTTLE_SIDECAR_BUNDLE": "1"}):
with patch.dict(os.environ, {"BOT_BOTTLE_SIDECAR_BUNDLE": "1"}):
backend = get_bottle_backend()
spec = BottleSpec(
manifest=_manifest(),
@@ -28,7 +28,7 @@ import unittest
from tests._docker import skip_unless_docker
_IMAGE = "claude-bottle-sidecars-test:chunk1"
_IMAGE = "bot-bottle-sidecars-test:chunk1"
_DOCKERFILE = "Dockerfile.sidecars"
@@ -108,7 +108,7 @@ class TestSidecarBundleImage(unittest.TestCase):
# ENTRYPOINT wiring works.
proc = subprocess.run(
["docker", "run", "--rm",
"-e", "CLAUDE_BOTTLE_SIDECAR_DAEMONS=nothing",
"-e", "BOT_BOTTLE_SIDECAR_DAEMONS=nothing",
_IMAGE],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
timeout=10.0,
@@ -18,7 +18,7 @@ import subprocess
import time
import unittest
from claude_bottle.backend.smolmachines.sidecar_bundle import (
from bot_bottle.backend.smolmachines.sidecar_bundle import (
BundleLaunchSpec,
bundle_container_name,
bundle_network_name,
@@ -47,13 +47,13 @@ class TestBundleBringup(unittest.TestCase):
remove_bundle_network(self.network)
def _bundle_image_built(self) -> bool:
"""The bundle image (`claude-bottle-sidecars:latest`) is
"""The bundle image (`bot-bottle-sidecars:latest`) is
built lazily by the docker backend's compose. If a
smolmachines-only operator hasn't run the docker backend
first, the image won't exist locally. Skip rather than
fail."""
r = subprocess.run(
["docker", "image", "inspect", "claude-bottle-sidecars:latest"],
["docker", "image", "inspect", "bot-bottle-sidecars:latest"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=False,
)
@@ -62,7 +62,7 @@ class TestBundleBringup(unittest.TestCase):
def test_create_network_then_start_bundle_pins_ip(self):
if not self._bundle_image_built():
self.skipTest(
"claude-bottle-sidecars:latest not built; run a docker "
"bot-bottle-sidecars:latest not built; run a docker "
"bottle first or `docker build -f Dockerfile.sidecars .`"
)
@@ -85,7 +85,7 @@ class TestBundleBringup(unittest.TestCase):
# Only run the pipelock daemon for this smoke — it's
# the lightest of the four and doesn't need bind
# mounts beyond what we'd skip without
# CLAUDE_BOTTLE_SIDECAR_DAEMONS. (The init
# BOT_BOTTLE_SIDECAR_DAEMONS. (The init
# supervisor will exit if pipelock fails to find its
# yaml — that's expected here; we just need the
# container to land on the network at the right IP.)
@@ -32,9 +32,9 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec, get_bottle_backend
from claude_bottle.backend.smolmachines.smolvm import is_available as _smolvm_available
from claude_bottle.manifest import Manifest
from bot_bottle.backend import BottleSpec, get_bottle_backend
from bot_bottle.backend.smolmachines.smolvm import is_available as _smolvm_available
from bot_bottle.manifest import Manifest
from tests._docker import skip_unless_docker
@@ -76,7 +76,7 @@ class TestSmolmachinesLaunch(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.stage = Path(tempfile.mkdtemp(prefix="cb-smol-launch."))
os.environ["CLAUDE_BOTTLE_BACKEND"] = "smolmachines"
os.environ["BOT_BOTTLE_BACKEND"] = "smolmachines"
backend = get_bottle_backend()
spec = BottleSpec(
manifest=_minimal_manifest(),
@@ -94,7 +94,7 @@ class TestSmolmachinesLaunch(unittest.TestCase):
cls._launch.__exit__(None, None, None)
finally:
shutil.rmtree(cls.stage, ignore_errors=True)
os.environ.pop("CLAUDE_BOTTLE_BACKEND", None)
os.environ.pop("BOT_BOTTLE_BACKEND", None)
def test_smoke_exec_echo(self):
# The plumbing-verifies-end-to-end smoke: a shell command
@@ -152,10 +152,10 @@ class TestSmolmachinesLaunch(unittest.TestCase):
def test_prompt_file_lands_in_guest(self):
# provision_prompt copies the host-side prompt.txt into the
# guest at /root/.claude-bottle-prompt.txt. The content
# guest at /root/.bot-bottle-prompt.txt. The content
# must match what the manifest declared so claude-code's
# --append-system-prompt-file reads the right text.
r = self.bottle.exec("cat /root/.claude-bottle-prompt.txt")
r = self.bottle.exec("cat /root/.bot-bottle-prompt.txt")
self.assertEqual(0, r.returncode, msg=r.stderr)
self.assertEqual(_AGENT_PROMPT, r.stdout.rstrip("\n"))
@@ -15,7 +15,7 @@ import platform
import subprocess
import unittest
from claude_bottle.backend.smolmachines.smolvm import is_available
from bot_bottle.backend.smolmachines.smolvm import is_available
@unittest.skipIf(
+6 -6
View File
@@ -12,8 +12,8 @@ import os
import unittest
from unittest.mock import patch
from claude_bottle import backend as backend_mod
from claude_bottle.backend import (
from bot_bottle import backend as backend_mod
from bot_bottle.backend import (
ActiveAgent,
enumerate_active_agents,
get_bottle_backend,
@@ -23,12 +23,12 @@ from claude_bottle.backend import (
class TestGetBottleBackend(unittest.TestCase):
def test_explicit_name_wins_over_env(self):
with patch.dict(os.environ, {"CLAUDE_BOTTLE_BACKEND": "smolmachines"}):
with patch.dict(os.environ, {"BOT_BOTTLE_BACKEND": "smolmachines"}):
b = get_bottle_backend("docker")
self.assertEqual("docker", b.name)
def test_env_var_fallback(self):
with patch.dict(os.environ, {"CLAUDE_BOTTLE_BACKEND": "smolmachines"}):
with patch.dict(os.environ, {"BOT_BOTTLE_BACKEND": "smolmachines"}):
b = get_bottle_backend()
self.assertEqual("smolmachines", b.name)
@@ -139,11 +139,11 @@ class TestHasBackend(unittest.TestCase):
with patch.object(
backend_mod, "_BACKENDS", {"docker": _FakeBackend()},
):
from claude_bottle.backend import has_backend
from bot_bottle.backend import has_backend
self.assertFalse(has_backend("docker"))
def test_unknown_backend_returns_false(self):
from claude_bottle.backend import has_backend
from bot_bottle.backend import has_backend
self.assertFalse(has_backend("nonexistent"))
+12 -12
View File
@@ -6,9 +6,9 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker import bottle_state
from claude_bottle.backend.docker.bottle_state import (
from bot_bottle import supervise
from bot_bottle.backend.docker import bottle_state
from bot_bottle.backend.docker.bottle_state import (
BottleMetadata,
read_metadata,
write_metadata,
@@ -18,13 +18,13 @@ from claude_bottle.backend.docker.bottle_state import (
class _FakeHomeMixin:
def _setup_fake_home(self):
self._tmp = tempfile.TemporaryDirectory(prefix="bottle-state-test.")
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
self._restore = lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
self._restore = lambda: setattr(supervise, "bot_bottle_root", original)
def _teardown_fake_home(self):
self._restore()
@@ -58,11 +58,11 @@ class TestPerBottleDockerfile(_FakeHomeMixin, unittest.TestCase):
def test_dockerfile_path_under_state_dir(self):
path = bottle_state.per_bottle_dockerfile_path("dev")
self.assertTrue(str(path).endswith("/.claude-bottle/state/dev/Dockerfile"))
self.assertTrue(str(path).endswith("/.bot-bottle/state/dev/Dockerfile"))
def test_image_tag_unique_per_slug(self):
self.assertEqual(
"claude-bottle-rebuilt-dev:latest",
"bot-bottle-rebuilt-dev:latest",
bottle_state.per_bottle_image_tag("dev"),
)
self.assertNotEqual(
@@ -72,7 +72,7 @@ class TestPerBottleDockerfile(_FakeHomeMixin, unittest.TestCase):
def test_transcript_dir_under_state_dir(self):
path = bottle_state.transcript_snapshot_dir("dev")
self.assertTrue(str(path).endswith("/.claude-bottle/state/dev/transcript"))
self.assertTrue(str(path).endswith("/.bot-bottle/state/dev/transcript"))
class TestBottleIdentity(unittest.TestCase):
@@ -143,7 +143,7 @@ class TestPreserveMarker(_FakeHomeMixin, unittest.TestCase):
def test_marker_path_under_state_dir(self):
path = bottle_state.preserve_marker_path("dev-x")
self.assertTrue(str(path).endswith("/.claude-bottle/state/dev-x/.preserve"))
self.assertTrue(str(path).endswith("/.bot-bottle/state/dev-x/.preserve"))
class TestCleanupState(_FakeHomeMixin, unittest.TestCase):
@@ -197,7 +197,7 @@ class TestBottleMetadata(_FakeHomeMixin, unittest.TestCase):
)
path = write_metadata(meta)
self.assertTrue(
str(path).endswith("/.claude-bottle/state/dev-x/metadata.json"),
str(path).endswith("/.bot-bottle/state/dev-x/metadata.json"),
)
def test_overwriting_metadata_updates_timestamp(self):
+7 -7
View File
@@ -12,9 +12,9 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker import bottle_state, capability_apply
from claude_bottle.backend.docker.capability_apply import (
from bot_bottle import supervise
from bot_bottle.backend.docker import bottle_state, capability_apply
from bot_bottle.backend.docker.capability_apply import (
CapabilityApplyError,
apply_capability_change,
fetch_current_dockerfile,
@@ -24,13 +24,13 @@ from claude_bottle.backend.docker.capability_apply import (
class _FakeHomeMixin:
def _setup_fake_home(self):
self._tmp = tempfile.TemporaryDirectory(prefix="cap-apply-test.")
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
self._restore = lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
self._restore = lambda: setattr(supervise, "bot_bottle_root", original)
def _teardown_fake_home(self):
self._restore()
+1 -1
View File
@@ -10,7 +10,7 @@ import sys
import unittest
from unittest.mock import patch, MagicMock
from claude_bottle.cli import cleanup as cmd
from bot_bottle.cli import cleanup as cmd
def _make_backend(empty: bool = True):
+4 -4
View File
@@ -13,7 +13,7 @@ import unittest
from unittest.mock import patch
from claude_bottle.backend import known_backend_names
from bot_bottle.backend import known_backend_names
class TestStartBackendFlag(unittest.TestCase):
@@ -51,9 +51,9 @@ class TestStartBackendFlag(unittest.TestCase):
def test_resolution_priority_explicit_over_env(self):
# Independent assertion that get_bottle_backend (where
# `--backend` ultimately threads to) prefers the explicit
# name over CLAUDE_BOTTLE_BACKEND.
from claude_bottle.backend import get_bottle_backend
with patch.dict(os.environ, {"CLAUDE_BOTTLE_BACKEND": "smolmachines"}):
# name over BOT_BOTTLE_BACKEND.
from bot_bottle.backend import get_bottle_backend
with patch.dict(os.environ, {"BOT_BOTTLE_BACKEND": "smolmachines"}):
self.assertEqual("docker", get_bottle_backend("docker").name)
+7 -7
View File
@@ -8,23 +8,23 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker import bottle_state
from claude_bottle.cli import start as start_mod
from bot_bottle import supervise
from bot_bottle.backend.docker import bottle_state
from bot_bottle.cli import start as start_mod
class _FakeHomeMixin:
def _setup_fake_home(self):
self._tmp = tempfile.TemporaryDirectory(prefix="cli-start-settle.")
self._original = supervise.claude_bottle_root
self._original = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
def _teardown_fake_home(self):
supervise.claude_bottle_root = self._original # type: ignore[assignment]
supervise.bot_bottle_root = self._original # type: ignore[assignment]
self._tmp.cleanup()
+28 -28
View File
@@ -12,22 +12,22 @@ from __future__ import annotations
import unittest
from pathlib import Path
from claude_bottle.backend import BottleSpec
from claude_bottle.backend.docker.bottle_plan import DockerBottlePlan
from claude_bottle.backend.docker.compose import (
from bot_bottle.backend import BottleSpec
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
from bot_bottle.backend.docker.compose import (
COMPOSE_PROJECT_PREFIX,
bottle_plan_to_compose,
compose_project_name,
slug_from_compose_project,
)
from claude_bottle.egress import (
from bot_bottle.egress import (
EgressPlan,
EgressRoute,
)
from claude_bottle.git_gate import GitGatePlan, GitGateUpstream
from claude_bottle.manifest import Manifest
from claude_bottle.pipelock import PipelockProxyPlan
from claude_bottle.supervise import SupervisePlan
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
from bot_bottle.manifest import Manifest
from bot_bottle.pipelock import PipelockProxyPlan
from bot_bottle.supervise import SupervisePlan
SLUG = "demo-abc12"
@@ -78,9 +78,9 @@ def _proxy_plan() -> PipelockProxyPlan:
return PipelockProxyPlan(
yaml_path=STATE / "pipelock.yaml",
slug=SLUG,
internal_network=f"claude-bottle-net-{SLUG}",
internal_network=f"bot-bottle-net-{SLUG}",
internal_network_cidr="10.1.2.0/24",
egress_network=f"claude-bottle-egress-{SLUG}",
egress_network=f"bot-bottle-egress-{SLUG}",
ca_cert_host_path=STATE / "pipelock-ca" / "ca.pem",
ca_key_host_path=STATE / "pipelock-ca" / "ca-key.pem",
)
@@ -93,8 +93,8 @@ def _git_gate_plan(upstreams: tuple[GitGateUpstream, ...] = ()) -> GitGatePlan:
hook_script=STATE / "git-gate" / "pre-receive",
access_hook_script=STATE / "git-gate" / "access-hook",
upstreams=upstreams,
internal_network=f"claude-bottle-net-{SLUG}",
egress_network=f"claude-bottle-egress-{SLUG}",
internal_network=f"bot-bottle-net-{SLUG}",
egress_network=f"bot-bottle-egress-{SLUG}",
)
@@ -109,8 +109,8 @@ def _egress_plan(routes: tuple[EgressRoute, ...] = ()) -> EgressPlan:
routes_path=STATE / "egress" / "routes.yaml",
routes=routes,
token_env_map=token_env_map,
internal_network=f"claude-bottle-net-{SLUG}",
egress_network=f"claude-bottle-egress-{SLUG}",
internal_network=f"bot-bottle-net-{SLUG}",
egress_network=f"bot-bottle-egress-{SLUG}",
mitmproxy_ca_host_path=STATE / "egress-ca" / "mitmproxy-ca.pem",
mitmproxy_ca_cert_only_host_path=STATE / "egress-ca" / "ca.pem",
pipelock_ca_host_path=STATE / "pipelock-ca" / "ca.pem",
@@ -123,7 +123,7 @@ def _supervise_plan() -> SupervisePlan:
slug=SLUG,
queue_dir=STATE / "supervise" / "queue",
current_config_dir=STATE / "supervise" / "current-config",
internal_network=f"claude-bottle-net-{SLUG}",
internal_network=f"bot-bottle-net-{SLUG}",
)
@@ -161,11 +161,11 @@ def _plan(
spec=_spec(supervise=supervise, with_git=with_git, with_egress=with_egress),
stage_dir=STAGE,
slug=SLUG,
container_name=f"claude-bottle-{SLUG}",
container_name=f"bot-bottle-{SLUG}",
container_name_pinned=False,
image="claude-bottle:latest",
image="bot-bottle-claude:latest",
derived_image="",
runtime_image="claude-bottle:latest",
runtime_image="bot-bottle-claude:latest",
dockerfile_path="",
env_file=Path("/dev/null"), # exists, size 0 → renderer skips env_file
forwarded_env={"CLAUDE_CODE_OAUTH_TOKEN": "x"},
@@ -181,18 +181,18 @@ def _plan(
class TestProjectAndNetworks(unittest.TestCase):
def test_project_name(self):
spec = bottle_plan_to_compose(_plan())
self.assertEqual(f"claude-bottle-{SLUG}", spec["name"])
self.assertEqual(f"bot-bottle-{SLUG}", spec["name"])
def test_internal_network_is_internal(self):
spec = bottle_plan_to_compose(_plan())
net = spec["networks"]["internal"]
self.assertEqual(f"claude-bottle-net-{SLUG}", net["name"])
self.assertEqual(f"bot-bottle-net-{SLUG}", net["name"])
self.assertTrue(net["internal"])
def test_egress_network_is_external_bridge(self):
spec = bottle_plan_to_compose(_plan())
net = spec["networks"]["egress"]
self.assertEqual(f"claude-bottle-egress-{SLUG}", net["name"])
self.assertEqual(f"bot-bottle-egress-{SLUG}", net["name"])
# No `internal:` key on the egress network — defaults to a
# normal user-defined bridge.
self.assertNotIn("internal", net)
@@ -262,13 +262,13 @@ class TestAgentAlwaysPresent(unittest.TestCase):
def test_agent_current_config_mount_only_with_supervise(self):
with_sv = bottle_plan_to_compose(_plan(supervise=True))["services"]["agent"]
self.assertTrue(any(
v["target"] == "/etc/claude-bottle/current-config"
v["target"] == "/etc/bot-bottle/current-config"
for v in with_sv.get("volumes", [])
))
without_sv = bottle_plan_to_compose(_plan(supervise=False))["services"]["agent"]
# Either no volumes key at all, or no current-config target.
self.assertFalse(any(
v["target"] == "/etc/claude-bottle/current-config"
v["target"] == "/etc/bot-bottle/current-config"
for v in without_sv.get("volumes", [])
))
@@ -293,12 +293,12 @@ class TestSidecarBundleShape(unittest.TestCase):
def test_bundle_uses_bundle_image_and_dockerfile(self):
sc = self._render()["services"]["sidecars"]
self.assertEqual("claude-bottle-sidecars:latest", sc["image"])
self.assertEqual("bot-bottle-sidecars:latest", sc["image"])
self.assertEqual("Dockerfile.sidecars", sc["build"]["dockerfile"])
def test_bundle_container_name_uses_sidecars_prefix(self):
sc = self._render()["services"]["sidecars"]
self.assertEqual(f"claude-bottle-sidecars-{SLUG}", sc["container_name"])
self.assertEqual(f"bot-bottle-sidecars-{SLUG}", sc["container_name"])
def test_bundle_joins_both_networks(self):
sc = self._render()["services"]["sidecars"]
@@ -335,18 +335,18 @@ class TestSidecarBundleShape(unittest.TestCase):
daemons = {
line.split("=", 1)[1]
for line in sc["environment"]
if line.startswith("CLAUDE_BOTTLE_SIDECAR_DAEMONS=")
if line.startswith("BOT_BOTTLE_SIDECAR_DAEMONS=")
}
self.assertEqual({"egress,pipelock"}, daemons)
def test_daemons_csv_expands_with_optional_sidecars(self):
sc = self._render(with_git=True, supervise=True)["services"]["sidecars"]
for line in sc["environment"]:
if line.startswith("CLAUDE_BOTTLE_SIDECAR_DAEMONS="):
if line.startswith("BOT_BOTTLE_SIDECAR_DAEMONS="):
csv = line.split("=", 1)[1]
break
else:
self.fail("CLAUDE_BOTTLE_SIDECAR_DAEMONS not in env")
self.fail("BOT_BOTTLE_SIDECAR_DAEMONS not in env")
self.assertEqual(
["egress", "pipelock", "git-gate", "supervise"],
csv.split(","),
+11 -11
View File
@@ -15,12 +15,12 @@ import unittest
from datetime import datetime, timezone
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker.capability_apply import CapabilityApplyError
from claude_bottle.backend.docker.egress_apply import EgressApplyError
from claude_bottle.backend.docker.pipelock_apply import PipelockApplyError
from claude_bottle.cli import dashboard
from claude_bottle.supervise import (
from bot_bottle import supervise
from bot_bottle.backend.docker.capability_apply import CapabilityApplyError
from bot_bottle.backend.docker.egress_apply import EgressApplyError
from bot_bottle.backend.docker.pipelock_apply import PipelockApplyError
from bot_bottle.cli import dashboard
from bot_bottle.supervise import (
Proposal,
STATUS_APPROVED,
STATUS_MODIFIED,
@@ -58,17 +58,17 @@ def _proposal(slug: str = "dev", tool: str = TOOL_EGRESS_BLOCK) -> Proposal:
class _FakeHomeMixin:
"""Patch supervise.claude_bottle_root to a temp dir for the test."""
"""Patch supervise.bot_bottle_root to a temp dir for the test."""
def _setup_fake_home(self):
self._tmp = tempfile.TemporaryDirectory(prefix="dashboard-test.")
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
self._restore_home = lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
self._restore_home = lambda: setattr(supervise, "bot_bottle_root", original)
def _teardown_fake_home(self):
self._restore_home()
+13 -13
View File
@@ -6,20 +6,20 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.cli import dashboard
from bot_bottle import supervise
from bot_bottle.cli import dashboard
class _FakeHomeMixin:
def _setup_fake_home(self) -> None:
self._tmp = tempfile.TemporaryDirectory(prefix="dashboard-aa-test.")
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
self._restore_home = lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
self._restore_home = lambda: setattr(supervise, "bot_bottle_root", original)
def _teardown_fake_home(self) -> None:
self._restore_home()
@@ -230,7 +230,7 @@ class TestBottleForSlug(unittest.TestCase):
def test_unowned_synthesizes_docker_bottle(self):
bottle, _ = dashboard._bottle_for_slug("dev-xyz", {}, None)
# The synth wraps the slug-derived container name.
self.assertEqual("claude-bottle-dev-xyz", bottle.name)
self.assertEqual("bot-bottle-dev-xyz", bottle.name)
def test_unowned_without_manifest_omits_prompt_path(self):
bottle, hint = dashboard._bottle_for_slug("dev-xyz", {}, None)
@@ -295,7 +295,7 @@ class TestTmuxPaneArgvBuilders(unittest.TestCase):
DOCKER_ARGV = [
"docker", "exec", "-it",
"claude-bottle-dev-abc",
"bot-bottle-dev-abc",
"claude", "--dangerously-skip-permissions", "--continue",
]
@@ -329,9 +329,9 @@ class TestResumeArgvWithFallback(unittest.TestCase):
--continue has no session to resume."""
def _bottle(self, prompt_path: str | None = None):
from claude_bottle.backend.docker.bottle import DockerBottle
from bot_bottle.backend.docker.bottle import DockerBottle
return DockerBottle(
container="claude-bottle-dev-abc",
container="bot-bottle-dev-abc",
teardown=lambda: None,
prompt_path_in_container=prompt_path,
)
@@ -340,7 +340,7 @@ class TestResumeArgvWithFallback(unittest.TestCase):
argv = dashboard._build_resume_argv_with_fallback(self._bottle())
# Must end with `sh -c '<cmd> --continue || <cmd>'`.
self.assertEqual(
["docker", "exec", "-it", "claude-bottle-dev-abc", "sh", "-c"],
["docker", "exec", "-it", "bot-bottle-dev-abc", "sh", "-c"],
argv[:6],
)
inner = argv[6]
@@ -362,10 +362,10 @@ class TestResumeArgvWithFallback(unittest.TestCase):
self.assertIn("--dangerously-skip-permissions", argv[-1])
def test_includes_prompt_file_flag_when_set(self):
bottle = self._bottle("/home/node/.claude-bottle-prompt.txt")
bottle = self._bottle("/home/node/.bot-bottle-prompt.txt")
argv = dashboard._build_resume_argv_with_fallback(bottle)
self.assertIn("--append-system-prompt-file", argv[-1])
self.assertIn("/home/node/.claude-bottle-prompt.txt", argv[-1])
self.assertIn("/home/node/.bot-bottle-prompt.txt", argv[-1])
class TestClaudeRuntimeArgs(unittest.TestCase):
+3 -3
View File
@@ -7,9 +7,9 @@ which hostname will land in pipelock's allowlist on approval."""
import unittest
from claude_bottle import supervise
from claude_bottle.cli import dashboard
from claude_bottle.supervise import (
from bot_bottle import supervise
from bot_bottle.cli import dashboard
from bot_bottle.supervise import (
Proposal,
TOOL_CAPABILITY_BLOCK,
TOOL_EGRESS_BLOCK,
+1 -1
View File
@@ -6,7 +6,7 @@ highlight window?`"""
import unittest
from claude_bottle.cli import dashboard
from bot_bottle.cli import dashboard
class TestIsRecent(unittest.TestCase):
+15 -15
View File
@@ -11,12 +11,12 @@ from __future__ import annotations
import unittest
from claude_bottle.backend.docker.bottle import DockerBottle
from bot_bottle.backend.docker.bottle import DockerBottle
def _bottle(prompt_path: str | None = None) -> DockerBottle:
return DockerBottle(
container="claude-bottle-dev-abc",
container="bot-bottle-dev-abc",
teardown=lambda: None,
prompt_path_in_container=prompt_path,
)
@@ -24,7 +24,7 @@ def _bottle(prompt_path: str | None = None) -> DockerBottle:
def _codex_bottle(prompt_path: str | None = None) -> DockerBottle:
return DockerBottle(
container="claude-bottle-dev-abc",
container="bot-bottle-dev-abc",
teardown=lambda: None,
prompt_path_in_container=prompt_path,
agent_command="codex",
@@ -36,7 +36,7 @@ class TestClaudeArgv(unittest.TestCase):
def test_minimal_argv_no_prompt(self):
argv = _bottle().claude_argv([])
self.assertEqual(
["docker", "exec", "-it", "claude-bottle-dev-abc", "claude"],
["docker", "exec", "-it", "bot-bottle-dev-abc", "claude"],
argv,
)
@@ -45,20 +45,20 @@ class TestClaudeArgv(unittest.TestCase):
["--dangerously-skip-permissions", "--continue"],
)
self.assertEqual(
["docker", "exec", "-it", "claude-bottle-dev-abc", "claude",
["docker", "exec", "-it", "bot-bottle-dev-abc", "claude",
"--dangerously-skip-permissions", "--continue"],
argv,
)
def test_appends_prompt_file_flag_when_set(self):
argv = _bottle("/home/node/.claude-bottle-prompt.txt").claude_argv(
argv = _bottle("/home/node/.bot-bottle-prompt.txt").claude_argv(
["--dangerously-skip-permissions"],
)
self.assertEqual(
["docker", "exec", "-it", "claude-bottle-dev-abc", "claude",
["docker", "exec", "-it", "bot-bottle-dev-abc", "claude",
"--dangerously-skip-permissions",
"--append-system-prompt-file",
"/home/node/.claude-bottle-prompt.txt"],
"/home/node/.bot-bottle-prompt.txt"],
argv,
)
@@ -76,7 +76,7 @@ class TestClaudeArgv(unittest.TestCase):
def test_tty_false_drops_it_flag(self):
argv = _bottle().claude_argv([], tty=False)
self.assertEqual(
["docker", "exec", "claude-bottle-dev-abc", "claude"],
["docker", "exec", "bot-bottle-dev-abc", "claude"],
argv,
)
@@ -94,26 +94,26 @@ class TestClaudeArgv(unittest.TestCase):
["--dangerously-bypass-approvals-and-sandbox"],
)
self.assertEqual(
["docker", "exec", "-it", "claude-bottle-dev-abc", "codex",
["docker", "exec", "-it", "bot-bottle-dev-abc", "codex",
"--dangerously-bypass-approvals-and-sandbox"],
argv,
)
def test_codex_provider_passes_prompt_reference_as_initial_prompt(self):
argv = _codex_bottle("/home/node/.claude-bottle-prompt.txt").claude_argv([])
argv = _codex_bottle("/home/node/.bot-bottle-prompt.txt").claude_argv([])
self.assertEqual(
["docker", "exec", "-it", "claude-bottle-dev-abc", "codex",
["docker", "exec", "-it", "bot-bottle-dev-abc", "codex",
"Read and follow the instructions in "
"/home/node/.claude-bottle-prompt.txt."],
"/home/node/.bot-bottle-prompt.txt."],
argv,
)
def test_codex_resume_does_not_append_initial_prompt(self):
argv = _codex_bottle("/home/node/.claude-bottle-prompt.txt").claude_argv(
argv = _codex_bottle("/home/node/.bot-bottle-prompt.txt").claude_argv(
["--dangerously-bypass-approvals-and-sandbox", "resume", "--last"],
)
self.assertEqual(
["docker", "exec", "-it", "claude-bottle-dev-abc", "codex",
["docker", "exec", "-it", "bot-bottle-dev-abc", "codex",
"--dangerously-bypass-approvals-and-sandbox", "resume", "--last"],
argv,
)
+10 -10
View File
@@ -15,21 +15,21 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker import bottle_state
from claude_bottle.backend.docker.cleanup import _list_orphan_state_dirs
from bot_bottle import supervise
from bot_bottle.backend.docker import bottle_state
from bot_bottle.backend.docker.cleanup import _list_orphan_state_dirs
class _FakeHomeMixin:
def _setup_fake_home(self) -> None:
self._tmp = tempfile.TemporaryDirectory(prefix="docker-cleanup-test.")
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
self._restore = lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
self._restore = lambda: setattr(supervise, "bot_bottle_root", original)
def _teardown_fake_home(self) -> None:
self._restore()
@@ -61,7 +61,7 @@ class TestOrphanStateDirs(_FakeHomeMixin, unittest.TestCase):
bottle_state.write_per_bottle_dockerfile("live-bbb", "FROM x\n")
self.assertEqual(
[],
_list_orphan_state_dirs({"claude-bottle-live-bbb"}, set()),
_list_orphan_state_dirs({"bot-bottle-live-bbb"}, set()),
)
def test_preserve_marker_skips_dir(self):
@@ -86,7 +86,7 @@ class TestOrphanStateDirs(_FakeHomeMixin, unittest.TestCase):
bottle_state.write_per_bottle_dockerfile("kept-ggg", "FROM z\n")
bottle_state.mark_preserved("kept-ggg")
result = _list_orphan_state_dirs({"claude-bottle-live-fff"}, set())
result = _list_orphan_state_dirs({"bot-bottle-live-fff"}, set())
self.assertEqual(["orphan-eee"], result)
def test_sorted_output(self):
@@ -116,7 +116,7 @@ class TestOrphanStateDirs(_FakeHomeMixin, unittest.TestCase):
self.assertEqual(
[],
_list_orphan_state_dirs(
{"claude-bottle-something-else"}, # different project up
{"bot-bottle-something-else"}, # different project up
{"smol-iii"}, # but smol-iii is live
),
)
+19 -19
View File
@@ -23,8 +23,8 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.backend.docker import bottle_state, enumerate as _enumerate
from bot_bottle import supervise
from bot_bottle.backend.docker import bottle_state, enumerate as _enumerate
class TestParseServicesByProject(unittest.TestCase):
@@ -33,18 +33,18 @@ class TestParseServicesByProject(unittest.TestCase):
def test_one_container(self):
out = _enumerate._parse_services_by_project(
"claude-bottle-dev-abc\tegress\n"
"bot-bottle-dev-abc\tegress\n"
)
self.assertEqual({"claude-bottle-dev-abc": {"egress"}}, out)
self.assertEqual({"bot-bottle-dev-abc": {"egress"}}, out)
def test_multiple_services_per_project(self):
out = _enumerate._parse_services_by_project(
"claude-bottle-dev-abc\tegress\n"
"claude-bottle-dev-abc\tpipelock\n"
"claude-bottle-dev-abc\tsupervise\n"
"bot-bottle-dev-abc\tegress\n"
"bot-bottle-dev-abc\tpipelock\n"
"bot-bottle-dev-abc\tsupervise\n"
)
self.assertEqual(
{"claude-bottle-dev-abc": {"egress", "pipelock", "supervise"}},
{"bot-bottle-dev-abc": {"egress", "pipelock", "supervise"}},
out,
)
@@ -63,24 +63,24 @@ class TestParseServicesByProject(unittest.TestCase):
# Defends against unlabeled containers slipping into the
# output (the filter should prevent it, but be robust).
out = _enumerate._parse_services_by_project(
"claude-bottle-dev-abc\tegress\n"
"bot-bottle-dev-abc\tegress\n"
"no-tab-here\n"
"\tmissing-project\n"
"missing-service\t\n"
)
self.assertEqual({"claude-bottle-dev-abc": {"egress"}}, out)
self.assertEqual({"bot-bottle-dev-abc": {"egress"}}, out)
class _FakeHomeMixin:
def _setup_fake_home(self) -> None:
self._tmp = tempfile.TemporaryDirectory(prefix="enum-active.")
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return Path(self._tmp.name) / ".claude-bottle"
return Path(self._tmp.name) / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
self._restore_home = lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
self._restore_home = lambda: setattr(supervise, "bot_bottle_root", original)
def _teardown_fake_home(self) -> None:
self._restore_home()
@@ -113,11 +113,11 @@ class TestEnumerateActive(_FakeHomeMixin, unittest.TestCase):
cwd="",
copy_cwd=False,
started_at="2026-05-26T03:00:00+00:00",
compose_project="claude-bottle-dev-abc",
compose_project="bot-bottle-dev-abc",
))
self._stub(
["dev-abc"],
{"claude-bottle-dev-abc": {"pipelock", "egress", "supervise"}},
{"bot-bottle-dev-abc": {"pipelock", "egress", "supervise"}},
)
active = _enumerate.enumerate_active()
self.assertEqual(1, len(active))
@@ -131,7 +131,7 @@ class TestEnumerateActive(_FakeHomeMixin, unittest.TestCase):
def test_missing_metadata_renders_question_mark(self):
# State dir doesn't exist for this slug — agent_name falls
# back to "?" rather than dropping the row.
self._stub(["mystery-zzz"], {"claude-bottle-mystery-zzz": {"pipelock"}})
self._stub(["mystery-zzz"], {"bot-bottle-mystery-zzz": {"pipelock"}})
active = _enumerate.enumerate_active()
self.assertEqual(1, len(active))
self.assertEqual("?", active[0].agent_name)
@@ -148,7 +148,7 @@ class TestEnumerateActive(_FakeHomeMixin, unittest.TestCase):
cwd="",
copy_cwd=False,
started_at="2026-05-26T03:05:00+00:00",
compose_project="claude-bottle-warming-up",
compose_project="bot-bottle-warming-up",
))
self._stub(["warming-up"], {})
active = _enumerate.enumerate_active()
@@ -162,7 +162,7 @@ class TestEnumerateActive(_FakeHomeMixin, unittest.TestCase):
cwd="",
copy_cwd=False,
started_at="t",
compose_project=f"claude-bottle-{slug}",
compose_project=f"bot-bottle-{slug}",
))
# list_active_slugs returns sorted; preserve that order in
# the output.
+15 -15
View File
@@ -13,13 +13,13 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from claude_bottle.backend import BottleSpec
from claude_bottle.backend.docker.bottle_plan import DockerBottlePlan
from claude_bottle.backend.docker.provision import git as _git
from claude_bottle.egress import EgressPlan
from claude_bottle.git_gate import GitGatePlan
from claude_bottle.manifest import Manifest
from claude_bottle.pipelock import PipelockProxyPlan
from bot_bottle.backend import BottleSpec
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
from bot_bottle.backend.docker.provision import git as _git
from bot_bottle.egress import EgressPlan
from bot_bottle.git_gate import GitGatePlan
from bot_bottle.manifest import Manifest
from bot_bottle.pipelock import PipelockProxyPlan
def _plan(*, git_user: dict | None = None,
@@ -39,11 +39,11 @@ def _plan(*, git_user: dict | None = None,
spec=spec,
stage_dir=stage_dir or Path("/tmp/stage"),
slug="demo-abc12",
container_name="claude-bottle-demo-abc12",
container_name="bot-bottle-demo-abc12",
container_name_pinned=False,
image="claude-bottle:latest",
image="bot-bottle-claude:latest",
derived_image="",
runtime_image="claude-bottle:latest",
runtime_image="bot-bottle-claude:latest",
dockerfile_path="",
env_file=Path("/tmp/agent.env"),
forwarded_env={},
@@ -93,7 +93,7 @@ class TestProvisionGitUser(unittest.TestCase):
def test_noop_when_no_git_user(self):
with patch.object(_git.subprocess, "run") as run:
_git._provision_git_user(
_plan(stage_dir=self.stage), "claude-bottle-demo-abc12",
_plan(stage_dir=self.stage), "bot-bottle-demo-abc12",
)
self.assertEqual([], _git_config_calls(run))
@@ -103,14 +103,14 @@ class TestProvisionGitUser(unittest.TestCase):
stage_dir=self.stage,
)
with patch.object(_git.subprocess, "run") as run:
_git._provision_git_user(plan, "claude-bottle-demo-abc12")
_git._provision_git_user(plan, "bot-bottle-demo-abc12")
calls = _git_config_calls(run)
self.assertEqual(2, len(calls))
# All `docker exec` invocations run as `-u node` so the
# --global config lands in /home/node/.gitconfig.
for argv in calls:
self.assertEqual(
["docker", "exec", "-u", "node", "claude-bottle-demo-abc12",
["docker", "exec", "-u", "node", "bot-bottle-demo-abc12",
"git", "config", "--global"],
argv[:8],
)
@@ -120,7 +120,7 @@ class TestProvisionGitUser(unittest.TestCase):
def test_name_only_sets_only_name(self):
plan = _plan(git_user={"name": "Bot"}, stage_dir=self.stage)
with patch.object(_git.subprocess, "run") as run:
_git._provision_git_user(plan, "claude-bottle-demo-abc12")
_git._provision_git_user(plan, "bot-bottle-demo-abc12")
calls = _git_config_calls(run)
self.assertEqual(1, len(calls))
self.assertEqual(["user.name", "Bot"], calls[0][8:])
@@ -130,7 +130,7 @@ class TestProvisionGitUser(unittest.TestCase):
git_user={"email": "bot@example.com"}, stage_dir=self.stage,
)
with patch.object(_git.subprocess, "run") as run:
_git._provision_git_user(plan, "claude-bottle-demo-abc12")
_git._provision_git_user(plan, "bot-bottle-demo-abc12")
calls = _git_config_calls(run)
self.assertEqual(1, len(calls))
self.assertEqual(["user.email", "bot@example.com"], calls[0][8:])
+6 -6
View File
@@ -1,5 +1,5 @@
"""Unit: image_id / tag / push helpers in
claude_bottle.backend.docker.util (PRD 0023 chunk 4c additions).
bot_bottle.backend.docker.util (PRD 0023 chunk 4c additions).
Tests mock `subprocess.run` and assert on argv shape + parsing.
The actual docker round-trip is covered by the chunk 4c
@@ -11,7 +11,7 @@ import subprocess
import unittest
from unittest.mock import patch
from claude_bottle.backend.docker import util as docker_mod
from bot_bottle.backend.docker import util as docker_mod
def _ok(stdout: str = "", stderr: str = "") -> subprocess.CompletedProcess:
@@ -34,11 +34,11 @@ class TestImageId(unittest.TestCase):
return_value=_ok(stdout="sha256:abcdef\n"),
) as run:
self.assertEqual(
"sha256:abcdef", docker_mod.image_id("claude-bottle:latest")
"sha256:abcdef", docker_mod.image_id("bot-bottle-claude:latest")
)
argv = run.call_args.args[0]
self.assertEqual(
["docker", "image", "inspect", "--format", "{{.Id}}", "claude-bottle:latest"],
["docker", "image", "inspect", "--format", "{{.Id}}", "bot-bottle-claude:latest"],
argv,
)
@@ -59,10 +59,10 @@ class TestSave(unittest.TestCase):
with patch.object(
docker_mod.subprocess, "run", return_value=_ok(),
) as run:
docker_mod.save("claude-bottle:latest", "/tmp/img.tar")
docker_mod.save("bot-bottle-claude:latest", "/tmp/img.tar")
argv = run.call_args.args[0]
self.assertEqual(
["docker", "save", "claude-bottle:latest", "-o", "/tmp/img.tar"],
["docker", "save", "bot-bottle-claude:latest", "-o", "/tmp/img.tar"],
argv,
)
+5 -5
View File
@@ -3,7 +3,7 @@ resolution (PRD 0017)."""
import unittest
from claude_bottle.egress import (
from bot_bottle.egress import (
DEFAULT_ALLOWLIST,
egress_manifest_routes,
egress_render_routes,
@@ -11,9 +11,9 @@ from claude_bottle.egress import (
egress_routes_for_bottle,
egress_token_env_map,
)
from claude_bottle.log import Die
from claude_bottle.manifest import Manifest
from claude_bottle.yaml_subset import parse_yaml_subset
from bot_bottle.log import Die
from bot_bottle.manifest import Manifest
from bot_bottle.yaml_subset import parse_yaml_subset
def _bottle(routes):
@@ -188,7 +188,7 @@ class TestRenderRoutes(unittest.TestCase):
def test_round_trip_through_addon_core(self):
# Render here → parse in the addon must succeed for every
# combination the manifest can produce.
from claude_bottle.egress_addon_core import load_routes
from bot_bottle.egress_addon_core import load_routes
b = _bottle([
{"host": "api.github.com",
"auth": {"scheme": "Bearer", "token_ref": "GH_PAT"},
+1 -1
View File
@@ -6,7 +6,7 @@ half of the addon. The mitmproxy hook wrapper in
import unittest
from claude_bottle.egress_addon_core import (
from bot_bottle.egress_addon_core import (
Decision,
Route,
decide,
+2 -2
View File
@@ -4,14 +4,14 @@ integration test."""
import unittest
from claude_bottle.backend.docker.egress_apply import (
from bot_bottle.backend.docker.egress_apply import (
EgressApplyError,
_hosts_in_routes,
_merge_single_route,
_pipelock_safe_hosts,
validate_routes_content,
)
from claude_bottle.yaml_subset import parse_yaml_subset
from bot_bottle.yaml_subset import parse_yaml_subset
# YAML fixtures matching the hand-rolled `_render_routes_payload`
+1 -1
View File
@@ -21,7 +21,7 @@ from pathlib import Path
_SCRIPT = (
Path(__file__).resolve().parent.parent.parent
/ "claude_bottle" / "egress_entrypoint.sh"
/ "bot_bottle" / "egress_entrypoint.sh"
)
+10 -10
View File
@@ -5,7 +5,7 @@ import tempfile
import unittest
from pathlib import Path
from claude_bottle.git_gate import (
from bot_bottle.git_gate import (
GitGate,
GitGatePlan,
GitGateUpstream,
@@ -16,8 +16,8 @@ from claude_bottle.git_gate import (
git_gate_render_hook,
git_gate_upstreams_for_bottle,
)
from claude_bottle.log import Die
from claude_bottle.manifest import Manifest
from bot_bottle.log import Die
from bot_bottle.manifest import Manifest
from tests.fixtures import fixture_minimal, fixture_with_git
@@ -34,7 +34,7 @@ class TestUpstreamsForBottle(unittest.TestCase):
bottle = fixture_with_git().bottles["dev"]
ups = git_gate_upstreams_for_bottle(bottle)
self.assertEqual(2, len(ups))
self.assertEqual("claude-bottle", ups[0].name)
self.assertEqual("bot-bottle", ups[0].name)
self.assertEqual("gitea.dideric.is", ups[0].upstream_host)
self.assertEqual("30009", ups[0].upstream_port)
self.assertEqual("foo", ups[1].name)
@@ -53,8 +53,8 @@ class TestExtraHostsPlumbing(unittest.TestCase):
"dev": {
"git": {"remotes": {
"gitea.dideric.is": {
"Name": "claude-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git",
"Name": "bot-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
"IdentityFile": "/dev/null",
"ExtraHosts": {"gitea.dideric.is": "100.78.141.42"},
},
@@ -140,8 +140,8 @@ class TestEntrypointRender(unittest.TestCase):
def test_one_init_repo_call_per_upstream(self):
ups = (
GitGateUpstream(
name="claude-bottle",
upstream_url="ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git",
name="bot-bottle",
upstream_url="ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
upstream_host="gitea.dideric.is",
upstream_port="30009",
identity_file="/host/path/key",
@@ -159,8 +159,8 @@ class TestEntrypointRender(unittest.TestCase):
script = git_gate_render_entrypoint(ups)
self.assertIn("#!/bin/sh", script)
self.assertIn(
"init_repo 'claude-bottle' "
"'ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git'",
"init_repo 'bot-bottle' "
"'ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git'",
script,
)
self.assertIn(
+2 -2
View File
@@ -7,8 +7,8 @@ auth omission means unauthenticated."""
import unittest
from claude_bottle.log import Die
from claude_bottle.manifest import EgressRoute, Manifest
from bot_bottle.log import Die
from bot_bottle.manifest import EgressRoute, Manifest
def _bottle(routes):
+2 -2
View File
@@ -14,8 +14,8 @@ import contextlib
import io
import unittest
from claude_bottle.log import Die
from claude_bottle.manifest import Manifest
from bot_bottle.log import Die
from bot_bottle.manifest import Manifest
def _die_message(callable_, *args, **kwargs) -> str:
+8 -8
View File
@@ -2,8 +2,8 @@
import unittest
from claude_bottle.log import Die
from claude_bottle.manifest import Manifest
from bot_bottle.log import Die
from bot_bottle.manifest import Manifest
def _manifest(git_entries):
@@ -31,18 +31,18 @@ def _host_for(entry):
class TestGitEntryParsing(unittest.TestCase):
def test_parses_minimal_entry(self):
m = Manifest.from_json_obj(_manifest([{
"Name": "claude-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git",
"Name": "bot-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
"IdentityFile": "/dev/null",
}]))
entries = m.bottles["dev"].git
self.assertEqual(1, len(entries))
e = entries[0]
self.assertEqual("claude-bottle", e.Name)
self.assertEqual("bot-bottle", e.Name)
self.assertEqual("git", e.UpstreamUser)
self.assertEqual("gitea.dideric.is", e.UpstreamHost)
self.assertEqual("30009", e.UpstreamPort)
self.assertEqual("didericis/claude-bottle.git", e.UpstreamPath)
self.assertEqual("didericis/bot-bottle.git", e.UpstreamPath)
def test_default_port_is_22(self):
m = Manifest.from_json_obj(_manifest([{
@@ -137,8 +137,8 @@ class TestGitEntryExtraHosts(unittest.TestCase):
def test_extra_hosts_parses_host_to_ip_map(self):
m = Manifest.from_json_obj(_manifest([{
"Name": "claude-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git",
"Name": "bot-bottle",
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
"IdentityFile": "/dev/null",
"ExtraHosts": {"gitea.dideric.is": "100.78.141.42"},
}]))
+2 -2
View File
@@ -4,8 +4,8 @@ import contextlib
import io
import unittest
from claude_bottle.log import Die
from claude_bottle.manifest import GitUser, Manifest
from bot_bottle.log import Die
from bot_bottle.manifest import GitUser, Manifest
def _die_message(callable_, *args, **kwargs) -> str:
+11 -11
View File
@@ -11,8 +11,8 @@ import textwrap
import unittest
from pathlib import Path
from claude_bottle.log import Die
from claude_bottle.manifest import Manifest
from bot_bottle.log import Die
from bot_bottle.manifest import Manifest
def _write(p: Path, text: str) -> None:
@@ -63,21 +63,21 @@ class _ResolveCase(unittest.TestCase):
shutil.rmtree(self.home_root, ignore_errors=True)
shutil.rmtree(self.cwd_root, ignore_errors=True)
# Convenience: paths under home/cwd .claude-bottle dirs.
# Convenience: paths under home/cwd .bot-bottle dirs.
@property
def home_cb(self) -> Path:
return self.home_root / ".claude-bottle"
return self.home_root / ".bot-bottle"
@property
def cwd_cb(self) -> Path:
return self.cwd_root / ".claude-bottle"
return self.cwd_root / ".bot-bottle"
def resolve(self) -> Manifest:
return Manifest.resolve(str(self.cwd_root))
class TestBottleFileParses(_ResolveCase):
"""SC #1: a bottle file under $HOME/.claude-bottle/bottles/
"""SC #1: a bottle file under $HOME/.bot-bottle/bottles/
parses into the expected Bottle shape."""
def test_loads(self):
@@ -94,7 +94,7 @@ class TestBottleFileParses(_ResolveCase):
class TestAgentFileParses(_ResolveCase):
"""SC #2: an agent file under $HOME/.claude-bottle/agents/
"""SC #2: an agent file under $HOME/.bot-bottle/agents/
parses, the body becomes the prompt, the frontmatter fields
map to Agent fields."""
@@ -171,7 +171,7 @@ class TestStdlibOnly(unittest.TestCase):
existence of an `import yaml`-free file is the assertion."""
def test_no_pyyaml(self):
src = Path("claude_bottle/yaml_subset.py").read_text()
src = Path("bot_bottle/yaml_subset.py").read_text()
self.assertNotIn("import yaml", src)
self.assertNotIn("from yaml", src)
@@ -262,12 +262,12 @@ class TestUnknownBottleKeyDies(_ResolveCase):
class TestStaleJsonDies(_ResolveCase):
"""If `claude-bottle.json` exists in $HOME alongside no
`.claude-bottle/` dir, die with a clear pointer at the README's
"""If `bot-bottle.json` exists in $HOME alongside no
`.bot-bottle/` dir, die with a clear pointer at the README's
new manifest section. Don't silently ignore the JSON content."""
def test_dies(self):
(self.home_root / "claude-bottle.json").write_text('{"bottles": {}}')
(self.home_root / "bot-bottle.json").write_text('{"bottles": {}}')
with self.assertRaises(Die):
self.resolve()
+2 -2
View File
@@ -7,8 +7,8 @@ silently ignoring."""
import unittest
from typing import Any
from claude_bottle.log import Die
from claude_bottle.manifest import Bottle, Manifest
from bot_bottle.log import Die
from bot_bottle.manifest import Bottle, Manifest
def _manifest_with_runtime(value: object) -> dict[str, Any]:
+2 -2
View File
@@ -5,8 +5,8 @@ contribute; they flow through the per-agent git-gate (PRD 0008)."""
import unittest
from claude_bottle.manifest import Manifest
from claude_bottle.pipelock import (
from bot_bottle.manifest import Manifest
from bot_bottle.pipelock import (
pipelock_effective_allowlist,
pipelock_effective_tls_passthrough,
)
+3 -3
View File
@@ -6,13 +6,13 @@ test in Phase 4. Here we cover the host-side parsing + yaml roundtrip.
import unittest
from claude_bottle.backend.docker.pipelock_apply import (
from bot_bottle.backend.docker.pipelock_apply import (
PipelockApplyError,
parse_allowlist_content,
render_allowlist_content,
)
from claude_bottle.pipelock import pipelock_render_yaml
from claude_bottle.yaml_subset import parse_yaml_subset
from bot_bottle.pipelock import pipelock_render_yaml
from bot_bottle.yaml_subset import parse_yaml_subset
class TestParseAllowlistContent(unittest.TestCase):
+4 -4
View File
@@ -12,8 +12,8 @@ import unittest
from pathlib import Path
from typing import Any, cast
from claude_bottle.manifest import Manifest
from claude_bottle.pipelock import (
from bot_bottle.manifest import Manifest
from bot_bottle.pipelock import (
DEFAULT_TLS_PASSTHROUGH,
PipelockProxy,
pipelock_build_config,
@@ -114,7 +114,7 @@ class TestBuildConfig(unittest.TestCase):
# fires. The only knob that actually skips the block is the
# global on/off, so we flip it off whenever the bottle is set
# up to route claude through pipelock.
from claude_bottle.manifest import Manifest
from bot_bottle.manifest import Manifest
bottle = Manifest.from_json_obj({
"bottles": {"dev": {"egress": {"routes": [
{"host": "api.anthropic.com",
@@ -209,7 +209,7 @@ class TestRenderAndWrite(unittest.TestCase):
self.assertIn('- "172.20.0.0/16"', text)
def test_render_emits_seed_phrase_off_for_anthropic_route(self):
from claude_bottle.manifest import Manifest
from bot_bottle.manifest import Manifest
bottle = Manifest.from_json_obj({
"bottles": {"dev": {"egress": {"routes": [
{"host": "api.anthropic.com",
+5 -5
View File
@@ -1,12 +1,12 @@
"""Unit: render of ~/.gitconfig insteadOf rules (PRD 0008).
The render moved to `claude_bottle.git_gate` so both backends
The render moved to `bot_bottle.git_gate` so both backends
share it; tests live here because docker's provision_git is the
original consumer."""
import unittest
from claude_bottle.git_gate import (
from bot_bottle.git_gate import (
GIT_GATE_HOSTNAME,
git_gate_render_gitconfig,
)
@@ -26,12 +26,12 @@ class TestGitGateGitconfigRender(unittest.TestCase):
# Both entries map to a [url ...] block keyed on the gate's
# short network alias (`git-gate`) inside the sidecar bundle.
self.assertIn(
'[url "git://git-gate/claude-bottle.git"]',
'[url "git://git-gate/bot-bottle.git"]',
out,
)
self.assertIn(
"\tinsteadOf = "
"ssh://git@gitea.dideric.is:30009/didericis/claude-bottle.git",
"ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
out,
)
self.assertIn('[url "git://git-gate/foo.git"]', out)
@@ -56,7 +56,7 @@ class TestGitGateGitconfigRender(unittest.TestCase):
bottle = fixture_with_git().bottles["dev"]
out = git_gate_render_gitconfig(bottle.git, "192.168.20.2:9418")
self.assertIn(
'[url "git://192.168.20.2:9418/claude-bottle.git"]', out,
'[url "git://192.168.20.2:9418/bot-bottle.git"]', out,
)
+2 -2
View File
@@ -8,8 +8,8 @@ plumbing surfaces in unit CI."""
import unittest
from claude_bottle.backend.docker.provision.supervise import supervise_mcp_url
from claude_bottle.supervise import SUPERVISE_HOSTNAME, SUPERVISE_PORT
from bot_bottle.backend.docker.provision.supervise import supervise_mcp_url
from bot_bottle.supervise import SUPERVISE_HOSTNAME, SUPERVISE_PORT
class TestSuperviseMcpUrl(unittest.TestCase):
+11 -11
View File
@@ -1,6 +1,6 @@
"""Unit: sidecar bundle init supervisor (PRD 0024 chunk 1).
Tests both the helper functions in `claude_bottle.sidecar_init`
Tests both the helper functions in `bot_bottle.sidecar_init`
and the supervisor's end-to-end signal / exit-code behavior. The
end-to-end tests use real subprocesses (`/bin/sleep`,
`/bin/sh -c '...'`) short-lived, no docker required so they
@@ -17,7 +17,7 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from claude_bottle.sidecar_init import (
from bot_bottle.sidecar_init import (
_DaemonSpec,
_Supervisor,
_env_for_daemon,
@@ -88,18 +88,18 @@ class TestSelectedDaemons(unittest.TestCase):
["egress", "pipelock", "git-gate", "supervise"])
def test_empty_returns_all(self):
got = _selected_daemons({"CLAUDE_BOTTLE_SIDECAR_DAEMONS": ""},
got = _selected_daemons({"BOT_BOTTLE_SIDECAR_DAEMONS": ""},
all_daemons=self._DAEMONS)
self.assertEqual(4, len(got))
def test_whitespace_only_returns_all(self):
got = _selected_daemons({"CLAUDE_BOTTLE_SIDECAR_DAEMONS": " "},
got = _selected_daemons({"BOT_BOTTLE_SIDECAR_DAEMONS": " "},
all_daemons=self._DAEMONS)
self.assertEqual(4, len(got))
def test_explicit_subset(self):
got = _selected_daemons(
{"CLAUDE_BOTTLE_SIDECAR_DAEMONS": "egress,pipelock"},
{"BOT_BOTTLE_SIDECAR_DAEMONS": "egress,pipelock"},
all_daemons=self._DAEMONS,
)
self.assertEqual([d.name for d in got], ["egress", "pipelock"])
@@ -109,7 +109,7 @@ class TestSelectedDaemons(unittest.TestCase):
# the canonical _DAEMONS order so egress starts before
# pipelock (race-window reason).
got = _selected_daemons(
{"CLAUDE_BOTTLE_SIDECAR_DAEMONS": "supervise,pipelock,egress"},
{"BOT_BOTTLE_SIDECAR_DAEMONS": "supervise,pipelock,egress"},
all_daemons=self._DAEMONS,
)
self.assertEqual([d.name for d in got],
@@ -117,14 +117,14 @@ class TestSelectedDaemons(unittest.TestCase):
def test_unknown_names_ignored(self):
got = _selected_daemons(
{"CLAUDE_BOTTLE_SIDECAR_DAEMONS": "egress,bogus"},
{"BOT_BOTTLE_SIDECAR_DAEMONS": "egress,bogus"},
all_daemons=self._DAEMONS,
)
self.assertEqual([d.name for d in got], ["egress"])
def test_whitespace_in_names_stripped(self):
got = _selected_daemons(
{"CLAUDE_BOTTLE_SIDECAR_DAEMONS": " egress , pipelock "},
{"BOT_BOTTLE_SIDECAR_DAEMONS": " egress , pipelock "},
all_daemons=self._DAEMONS,
)
self.assertEqual([d.name for d in got], ["egress", "pipelock"])
@@ -355,7 +355,7 @@ class TestSupervisor(unittest.TestCase):
time.sleep(0.3) # let `trap` register
sup.request_shutdown(reason="test")
with patch("claude_bottle.sidecar_init._GRACE_SECONDS", 0.3):
with patch("bot_bottle.sidecar_init._GRACE_SECONDS", 0.3):
rc = self._drive(sup, max_wait_s=4.0)
# Process was SIGKILL'd → returncode -9 on POSIX.
@@ -397,14 +397,14 @@ class TestMainEndToEnd(unittest.TestCase):
helper = (
"import os, runpy, sys\n"
"from claude_bottle import sidecar_init as si\n"
"from bot_bottle import sidecar_init as si\n"
"si._DAEMONS = (\n"
" si._DaemonSpec('alpha', ('/bin/sleep','30')),\n"
" si._DaemonSpec('beta', ('/bin/sleep','30')),\n"
")\n"
"sys.exit(si.main([]))\n"
)
env = {**os.environ, "CLAUDE_BOTTLE_SIDECAR_DAEMONS": daemons_csv}
env = {**os.environ, "BOT_BOTTLE_SIDECAR_DAEMONS": daemons_csv}
proc = subprocess.Popen(
[sys.executable, "-c", helper],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+7 -7
View File
@@ -16,13 +16,13 @@ from __future__ import annotations
import sys
import unittest
from claude_bottle.backend.smolmachines import pty_resize as _pty_resize
from claude_bottle.backend.smolmachines.bottle import SmolmachinesBottle
from bot_bottle.backend.smolmachines import pty_resize as _pty_resize
from bot_bottle.backend.smolmachines.bottle import SmolmachinesBottle
def _bottle(prompt_path: str | None = None, **env: str) -> SmolmachinesBottle:
return SmolmachinesBottle(
"claude-bottle-dev-abc",
"bot-bottle-dev-abc",
prompt_path=prompt_path,
guest_env=env,
)
@@ -47,7 +47,7 @@ class TestClaudeArgvWrapped(unittest.TestCase):
self.assertEqual(
[
sys.executable, _pty_resize.__file__,
"claude-bottle-dev-abc", "--",
"bot-bottle-dev-abc", "--",
],
argv[:4],
)
@@ -57,7 +57,7 @@ class TestClaudeArgvWrapped(unittest.TestCase):
self.assertEqual(
[
"smolvm", "machine", "exec", "--name",
"claude-bottle-dev-abc",
"bot-bottle-dev-abc",
"-i", "-t",
"-e", "HOME=/home/node",
"-e", "USER=node",
@@ -79,7 +79,7 @@ class TestClaudeArgvWrapped(unittest.TestCase):
def test_appends_prompt_file_flag_when_set(self):
argv = _unwrap(
_bottle("/home/node/.claude-bottle-prompt.txt").claude_argv(
_bottle("/home/node/.bot-bottle-prompt.txt").claude_argv(
["--dangerously-skip-permissions"],
)
)
@@ -87,7 +87,7 @@ class TestClaudeArgvWrapped(unittest.TestCase):
[
"claude",
"--append-system-prompt-file",
"/home/node/.claude-bottle-prompt.txt",
"/home/node/.bot-bottle-prompt.txt",
"--dangerously-skip-permissions",
],
argv[argv.index("claude"):],
+20 -20
View File
@@ -11,9 +11,9 @@ import subprocess
import unittest
from unittest.mock import patch
from claude_bottle import backend as backend_mod
from claude_bottle.backend.smolmachines import cleanup
from claude_bottle.backend.smolmachines.bottle_cleanup_plan import (
from bot_bottle import backend as backend_mod
from bot_bottle.backend.smolmachines import cleanup
from bot_bottle.backend.smolmachines.bottle_cleanup_plan import (
SmolmachinesBottleCleanupPlan,
)
@@ -38,19 +38,19 @@ class TestPrepareCleanup(unittest.TestCase):
def fake_run(argv, *a, **kw):
if argv[:3] == ["smolvm", "machine", "ls"]:
return _ok(stdout=(
'[{"name":"claude-bottle-a-1","state":"running"},'
' {"name":"claude-bottle-b-2","state":"created"},'
'[{"name":"bot-bottle-a-1","state":"running"},'
' {"name":"bot-bottle-b-2","state":"created"},'
' {"name":"unrelated","state":"running"}]'
))
if argv[:2] == ["docker", "ps"]:
return _ok(stdout=(
"claude-bottle-sidecars-a-1\n"
"claude-bottle-sidecars-b-2\n"
"bot-bottle-sidecars-a-1\n"
"bot-bottle-sidecars-b-2\n"
))
if argv[:3] == ["docker", "network", "ls"]:
return _ok(stdout=(
"claude-bottle-bundle-a-1\n"
"claude-bottle-bundle-b-2\n"
"bot-bottle-bundle-a-1\n"
"bot-bottle-bundle-b-2\n"
))
return _ok()
@@ -60,17 +60,17 @@ class TestPrepareCleanup(unittest.TestCase):
smolvm.is_available.return_value = True
plan = cleanup.prepare_cleanup()
# `unrelated` filtered out (no claude-bottle- prefix).
# `unrelated` filtered out (no bot-bottle- prefix).
self.assertEqual(
("claude-bottle-a-1", "claude-bottle-b-2"),
("bot-bottle-a-1", "bot-bottle-b-2"),
plan.machines,
)
self.assertEqual(
("claude-bottle-sidecars-a-1", "claude-bottle-sidecars-b-2"),
("bot-bottle-sidecars-a-1", "bot-bottle-sidecars-b-2"),
plan.bundles,
)
self.assertEqual(
("claude-bottle-bundle-a-1", "claude-bottle-bundle-b-2"),
("bot-bottle-bundle-a-1", "bot-bottle-bundle-b-2"),
plan.networks,
)
@@ -86,9 +86,9 @@ class TestPrepareCleanup(unittest.TestCase):
class TestCleanup(unittest.TestCase):
def test_machines_stopped_then_deleted_then_bundles_then_networks(self):
plan = SmolmachinesBottleCleanupPlan(
machines=("claude-bottle-a-1",),
bundles=("claude-bottle-sidecars-a-1",),
networks=("claude-bottle-bundle-a-1",),
machines=("bot-bottle-a-1",),
bundles=("bot-bottle-sidecars-a-1",),
networks=("bot-bottle-bundle-a-1",),
)
calls: list[list[str]] = []
@@ -107,10 +107,10 @@ class TestCleanup(unittest.TestCase):
["smolvm", "machine", "delete", "-f"], calls[1],
)
self.assertEqual(
["docker", "rm", "-f", "claude-bottle-sidecars-a-1"], calls[2],
["docker", "rm", "-f", "bot-bottle-sidecars-a-1"], calls[2],
)
self.assertEqual(
["docker", "network", "rm", "claude-bottle-bundle-a-1"], calls[3],
["docker", "network", "rm", "bot-bottle-bundle-a-1"], calls[3],
)
def test_failures_are_warnings_not_fatal(self):
@@ -118,8 +118,8 @@ class TestCleanup(unittest.TestCase):
# but continue with bundles + networks. The cleanup is
# idempotent on success and tries to remove every resource.
plan = SmolmachinesBottleCleanupPlan(
machines=("claude-bottle-a-1",),
bundles=("claude-bottle-sidecars-a-1",),
machines=("bot-bottle-a-1",),
bundles=("bot-bottle-sidecars-a-1",),
networks=(),
)
results = iter([
+7 -7
View File
@@ -18,7 +18,7 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from claude_bottle.backend.smolmachines import launch as _launch_mod
from bot_bottle.backend.smolmachines import launch as _launch_mod
class TestEnsureSmolmachine(unittest.TestCase):
@@ -53,7 +53,7 @@ class TestEnsureSmolmachine(unittest.TestCase):
) as push, patch.object(
_launch_mod._smolvm, "pack_create",
) as pack:
result = _launch_mod._ensure_smolmachine("claude-bottle:latest")
result = _launch_mod._ensure_smolmachine("bot-bottle-claude:latest")
self.assertEqual(sidecar, result)
# build still runs (Dockerfile edits land without manual rmi).
@@ -71,7 +71,7 @@ class TestEnsureSmolmachine(unittest.TestCase):
# ephemeral_registry yields a RegistryHandle with the
# docker network + a push endpoint (container DNS) and
# pull endpoint (host port-forward).
from claude_bottle.backend.smolmachines.local_registry import (
from bot_bottle.backend.smolmachines.local_registry import (
RegistryHandle,
)
@@ -111,7 +111,7 @@ class TestEnsureSmolmachine(unittest.TestCase):
_launch_mod._smolvm, "pack_create",
side_effect=record("pack"),
) as pack:
_launch_mod._ensure_smolmachine("claude-bottle:latest")
_launch_mod._ensure_smolmachine("bot-bottle-claude:latest")
# Build → save → push → pack in that order. No `docker
# push` (the daemon's HTTPS-by-default path is what we're
@@ -121,14 +121,14 @@ class TestEnsureSmolmachine(unittest.TestCase):
# docker save targets a per-digest tarball alongside the
# cached sidecar.
save_args = save.call_args.args
self.assertEqual("claude-bottle:latest", save_args[0])
self.assertEqual("bot-bottle-claude:latest", save_args[0])
self.assertTrue(save_args[1].endswith(f"{digest}.image.tar"))
# crane push runs against the push_endpoint (container DNS
# on the registry network) with the digest as the tag.
push_args = push.call_args.args
self.assertEqual(
f"cb-registry-xyz:5000/claude-bottle:{digest}", push_args[2],
f"cb-registry-xyz:5000/bot-bottle:{digest}", push_args[2],
)
# pack_create reads from the pull_endpoint (host port-
@@ -136,7 +136,7 @@ class TestEnsureSmolmachine(unittest.TestCase):
# different routing hostname — the registry stores one blob.
pack_args = pack.call_args.args
self.assertEqual(
f"localhost:54321/claude-bottle:{digest}", pack_args[0],
f"localhost:54321/bot-bottle:{digest}", pack_args[0],
)
self.assertTrue(str(pack_args[1]).endswith(f"{digest}.smolmachine"))
@@ -12,7 +12,7 @@ import subprocess
import unittest
from unittest.mock import patch
from claude_bottle.backend.smolmachines import local_registry
from bot_bottle.backend.smolmachines import local_registry
def _ok(stdout: str = "", stderr: str = "") -> subprocess.CompletedProcess:
@@ -57,7 +57,7 @@ class TestEphemeralRegistry(unittest.TestCase):
# its docker-network name on its container port.
self.assertTrue(
handle.push_endpoint.startswith(
"claude-bottle-registry-"
"bot-bottle-registry-"
)
)
self.assertTrue(handle.push_endpoint.endswith(":5000"))
@@ -65,7 +65,7 @@ class TestEphemeralRegistry(unittest.TestCase):
self.assertEqual("localhost:54321", handle.pull_endpoint)
# network name is the per-session bridge crane joins.
self.assertTrue(
handle.network.startswith("claude-bottle-registry-net-")
handle.network.startswith("bot-bottle-registry-net-")
)
# docker network create + docker run + docker port + rm -f + network rm
self.assertEqual(5, run.call_count)
@@ -15,7 +15,7 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from claude_bottle.backend.smolmachines import loopback_alias
from bot_bottle.backend.smolmachines import loopback_alias
def _ok(stdout: str = "") -> subprocess.CompletedProcess:
@@ -161,7 +161,7 @@ class TestAliasInUseDetection(unittest.TestCase):
# First call: docker ps -> two bundle names.
# Then docker inspect each, returning a port-bindings JSON
# blob with a HostIp on the per-bottle alias.
ps_out = "claude-bottle-sidecars-a\nclaude-bottle-sidecars-b\n"
ps_out = "bot-bottle-sidecars-a\nbot-bottle-sidecars-b\n"
inspect_a = (
'{"8888/tcp":[{"HostIp":"127.0.0.16","HostPort":"54000"}]}'
)
@@ -183,7 +183,7 @@ class TestAliasInUseDetection(unittest.TestCase):
)
def test_inspect_failures_are_skipped(self):
ps_out = "claude-bottle-sidecars-c\n"
ps_out = "bot-bottle-sidecars-c\n"
with patch.object(
loopback_alias.subprocess, "run",
side_effect=[_ok(stdout=ps_out), _fail("inspect failed")],
+88 -88
View File
@@ -12,23 +12,23 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from claude_bottle.backend import BottleSpec
from claude_bottle.backend.smolmachines.bottle_plan import (
from bot_bottle.backend import BottleSpec
from bot_bottle.backend.smolmachines.bottle_plan import (
SmolmachinesBottlePlan,
)
from claude_bottle.backend.smolmachines.provision import (
from bot_bottle.backend.smolmachines.provision import (
ca as _ca,
git as _git,
prompt as _prompt,
skills as _skills,
supervise as _supervise,
)
from claude_bottle.backend.smolmachines.smolvm import SmolvmRunResult
from claude_bottle.egress import EgressPlan, EgressRoute
from claude_bottle.git_gate import GitGatePlan
from claude_bottle.manifest import GitEntry, Manifest
from claude_bottle.pipelock import PipelockProxyPlan
from claude_bottle.supervise import SupervisePlan
from bot_bottle.backend.smolmachines.smolvm import SmolvmRunResult
from bot_bottle.egress import EgressPlan, EgressRoute
from bot_bottle.git_gate import GitGatePlan
from bot_bottle.manifest import GitEntry, Manifest
from bot_bottle.pipelock import PipelockProxyPlan
from bot_bottle.supervise import SupervisePlan
def _remote_host(g: GitEntry) -> str:
@@ -101,8 +101,8 @@ def _plan(
bundle_subnet="192.168.50.0/24",
bundle_gateway="192.168.50.1",
bundle_ip=bundle_ip,
machine_name="claude-bottle-demo-abc12",
agent_image_ref="claude-bottle:latest",
machine_name="bot-bottle-demo-abc12",
agent_image_ref="bot-bottle-claude:latest",
guest_env={},
prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"),
proxy_plan=PipelockProxyPlan(
@@ -133,37 +133,37 @@ def _plan(
class TestProvisionPrompt(unittest.TestCase):
def test_cp_uses_smolvm_machine_cp_with_machine_path_syntax(self):
with patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
):
_prompt.provision_prompt(_plan(), "claude-bottle-demo-abc12")
_prompt.provision_prompt(_plan(), "bot-bottle-demo-abc12")
cp.assert_called_once_with(
"/tmp/state/demo-abc12/agent/prompt.txt",
"claude-bottle-demo-abc12:/home/node/.claude-bottle-prompt.txt",
"bot-bottle-demo-abc12:/home/node/.bot-bottle-prompt.txt",
)
def test_returns_path_when_agent_has_prompt(self):
with patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
), patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
):
r = _prompt.provision_prompt(
_plan(agent_prompt="You are a helpful assistant."),
"claude-bottle-demo-abc12",
"bot-bottle-demo-abc12",
)
self.assertEqual("/home/node/.claude-bottle-prompt.txt", r)
self.assertEqual("/home/node/.bot-bottle-prompt.txt", r)
def test_returns_none_when_agent_has_no_prompt(self):
# The file is still copied (path-must-exist contract);
# only the return value differs.
with patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
):
r = _prompt.provision_prompt(_plan(agent_prompt=""), "claude-bottle-demo-abc12")
r = _prompt.provision_prompt(_plan(agent_prompt=""), "bot-bottle-demo-abc12")
self.assertIsNone(r)
cp.assert_called_once()
@@ -171,18 +171,18 @@ class TestProvisionPrompt(unittest.TestCase):
# machine cp lands as root; without the chown, the node user
# can't read its own mode-600 prompt.
with patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_cp"
), patch(
"claude_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.prompt._smolvm.machine_exec"
) as ex:
_prompt.provision_prompt(_plan(), "claude-bottle-demo-abc12")
_prompt.provision_prompt(_plan(), "bot-bottle-demo-abc12")
argv_seen = [call.args[1] for call in ex.call_args_list]
self.assertIn(
["chown", "node:node", "/home/node/.claude-bottle-prompt.txt"],
["chown", "node:node", "/home/node/.bot-bottle-prompt.txt"],
argv_seen,
)
self.assertIn(
["chmod", "600", "/home/node/.claude-bottle-prompt.txt"],
["chmod", "600", "/home/node/.bot-bottle-prompt.txt"],
argv_seen,
)
@@ -190,17 +190,17 @@ class TestProvisionPrompt(unittest.TestCase):
class TestProvisionSkills(unittest.TestCase):
def _patch_host_skill_dir(self, returns: dict[str, str]):
return patch(
"claude_bottle.backend.smolmachines.provision.skills.host_skill_dir",
"bot_bottle.backend.smolmachines.provision.skills.host_skill_dir",
side_effect=lambda n: returns.get(n, f"/nope/{n}"),
)
def test_no_op_when_agent_has_no_skills(self):
with patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
) as ex:
_skills.provision_skills(_plan(skills=[]), "claude-bottle-demo-abc12")
_skills.provision_skills(_plan(skills=[]), "bot-bottle-demo-abc12")
self.assertEqual(0, cp.call_count)
self.assertEqual(0, ex.call_count)
@@ -209,23 +209,23 @@ class TestProvisionSkills(unittest.TestCase):
"init-prd": "/host/skills/init-prd",
"verify": "/host/skills/verify",
}), patch(
"claude_bottle.backend.smolmachines.provision.skills.os.path.isdir",
"bot_bottle.backend.smolmachines.provision.skills.os.path.isdir",
return_value=True,
), patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
) as ex:
_skills.provision_skills(
_plan(skills=["init-prd", "verify"]),
"claude-bottle-demo-abc12",
"bot-bottle-demo-abc12",
)
# mkdir -p once + (rm -rf + chown) per skill = 5 exec calls.
self.assertEqual(5, ex.call_count)
mkdir_call = ex.call_args_list[0]
self.assertEqual(
("claude-bottle-demo-abc12", ["mkdir", "-p", "/home/node/.claude/skills"]),
("bot-bottle-demo-abc12", ["mkdir", "-p", "/home/node/.claude/skills"]),
mkdir_call.args,
)
# Two cp calls, one per skill, into the per-skill subdir.
@@ -233,8 +233,8 @@ class TestProvisionSkills(unittest.TestCase):
cp_targets = {call.args[1] for call in cp.call_args_list}
self.assertEqual(
{
"claude-bottle-demo-abc12:/home/node/.claude/skills/init-prd",
"claude-bottle-demo-abc12:/home/node/.claude/skills/verify",
"bot-bottle-demo-abc12:/home/node/.claude/skills/init-prd",
"bot-bottle-demo-abc12:/home/node/.claude/skills/verify",
},
cp_targets,
)
@@ -257,36 +257,36 @@ class TestProvisionSkills(unittest.TestCase):
import os
with self._patch_host_skill_dir({"init-prd": "/host/skills/init-prd"}), \
patch(
"claude_bottle.backend.smolmachines.provision.skills.os.path.isdir",
"bot_bottle.backend.smolmachines.provision.skills.os.path.isdir",
return_value=True,
), \
patch.dict(os.environ, {"CLAUDE_BOTTLE_GUEST_SKILLS_DIR": "/home/node/.claude/skills"}), \
patch.dict(os.environ, {"BOT_BOTTLE_GUEST_SKILLS_DIR": "/home/node/.claude/skills"}), \
patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
) as cp, \
patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
):
_skills.provision_skills(_plan(skills=["init-prd"]), "claude-bottle-demo-abc12")
_skills.provision_skills(_plan(skills=["init-prd"]), "bot-bottle-demo-abc12")
self.assertEqual(
"claude-bottle-demo-abc12:/home/node/.claude/skills/init-prd",
"bot-bottle-demo-abc12:/home/node/.claude/skills/init-prd",
cp.call_args.args[1],
)
def test_missing_skill_dies(self):
with self._patch_host_skill_dir({"init-prd": "/host/skills/init-prd"}), \
patch(
"claude_bottle.backend.smolmachines.provision.skills.os.path.isdir",
"bot_bottle.backend.smolmachines.provision.skills.os.path.isdir",
return_value=False,
), \
patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_cp"
), \
patch(
"claude_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.skills._smolvm.machine_exec"
):
with self.assertRaises(SystemExit):
_skills.provision_skills(_plan(skills=["init-prd"]), "claude-bottle-demo-abc12")
_skills.provision_skills(_plan(skills=["init-prd"]), "bot-bottle-demo-abc12")
def _write_self_signed_cert(path: Path) -> None:
@@ -331,15 +331,15 @@ class TestProvisionCA(unittest.TestCase):
def test_pipelock_path_when_no_routes(self):
plan = _plan(pipelock_ca_path=self.pipelock_ca)
with patch(
"claude_bottle.backend.smolmachines.provision.ca._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.ca._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.ca._smolvm.machine_exec",
"bot_bottle.backend.smolmachines.provision.ca._smolvm.machine_exec",
return_value=self._UPDATE_OK,
) as ex:
_ca.provision_ca(plan, "claude-bottle-demo-abc12")
_ca.provision_ca(plan, "bot-bottle-demo-abc12")
cp.assert_called_once_with(
str(self.pipelock_ca),
"claude-bottle-demo-abc12:" + _ca.AGENT_CA_PATH,
"bot-bottle-demo-abc12:" + _ca.AGENT_CA_PATH,
)
# chmod + chown + update-ca-certificates are now folded
# into one `sh -c` invocation (working around a smolvm
@@ -359,17 +359,17 @@ class TestProvisionCA(unittest.TestCase):
pipelock_ca_path=self.pipelock_ca,
)
with patch(
"claude_bottle.backend.smolmachines.provision.ca._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.ca._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.ca._smolvm.machine_exec",
"bot_bottle.backend.smolmachines.provision.ca._smolvm.machine_exec",
return_value=self._UPDATE_OK,
):
_ca.provision_ca(plan, "claude-bottle-demo-abc12")
_ca.provision_ca(plan, "bot-bottle-demo-abc12")
# When routes are declared, egress is the agent's first hop,
# so egress's CA is the one that gets installed.
cp.assert_called_once_with(
str(self.egress_ca),
"claude-bottle-demo-abc12:" + _ca.AGENT_CA_PATH,
"bot-bottle-demo-abc12:" + _ca.AGENT_CA_PATH,
)
def test_dies_when_selected_cert_missing(self):
@@ -377,12 +377,12 @@ class TestProvisionCA(unittest.TestCase):
# something went wrong in launch's pipelock_tls_init.
plan = _plan(pipelock_ca_path=self.tmp / "does-not-exist.pem")
with patch(
"claude_bottle.backend.smolmachines.provision.ca._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.ca._smolvm.machine_cp"
), patch(
"claude_bottle.backend.smolmachines.provision.ca._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.ca._smolvm.machine_exec"
):
with self.assertRaises(SystemExit):
_ca.provision_ca(plan, "claude-bottle-demo-abc12")
_ca.provision_ca(plan, "bot-bottle-demo-abc12")
class TestProvisionGit(unittest.TestCase):
@@ -399,12 +399,12 @@ class TestProvisionGit(unittest.TestCase):
def test_noop_when_no_cwd_and_no_git_entries(self):
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
) as ex:
_git.provision_git(
_plan(stage_dir=self.stage), "claude-bottle-demo-abc12",
_plan(stage_dir=self.stage), "bot-bottle-demo-abc12",
)
cp.assert_not_called()
ex.assert_not_called()
@@ -418,14 +418,14 @@ class TestProvisionGit(unittest.TestCase):
copy_cwd=True, user_cwd=str(cwd), stage_dir=self.stage,
)
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
) as ex:
_git.provision_git(plan, "claude-bottle-demo-abc12")
_git.provision_git(plan, "bot-bottle-demo-abc12")
cp.assert_called_once_with(
f"{cwd}/.git",
"claude-bottle-demo-abc12:/home/node/workspace/.git",
"bot-bottle-demo-abc12:/home/node/workspace/.git",
)
argvs = [c.args[1] for c in ex.call_args_list]
self.assertIn(["mkdir", "-p", "/home/node/workspace"], argvs)
@@ -438,11 +438,11 @@ class TestProvisionGit(unittest.TestCase):
def test_skips_cwd_when_copy_cwd_false(self):
plan = _plan(copy_cwd=False, stage_dir=self.stage)
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
):
_git.provision_git(plan, "claude-bottle-demo-abc12")
_git.provision_git(plan, "bot-bottle-demo-abc12")
cp.assert_not_called()
def test_writes_gitconfig_with_ip_port_form_for_smolmachines(self):
@@ -452,7 +452,7 @@ class TestProvisionGit(unittest.TestCase):
# carries the discovered host port (here mocked to 9418).
plan = _plan(
git=[GitEntry(
Name="claude-bottle",
Name="bot-bottle",
Upstream="ssh://git@host/repo.git",
IdentityFile="~/.ssh/id_ed25519",
)],
@@ -460,11 +460,11 @@ class TestProvisionGit(unittest.TestCase):
agent_git_gate_host="127.0.0.1:9418",
)
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_cp"
) as cp, patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
):
_git.provision_git(plan, "claude-bottle-demo-abc12")
_git.provision_git(plan, "bot-bottle-demo-abc12")
# The staged gitconfig path is whatever NamedTemporaryFile
# picked; we read its contents.
cp_call = cp.call_args
@@ -472,7 +472,7 @@ class TestProvisionGit(unittest.TestCase):
self.assertEqual(self.stage, staged_path.parent)
content = staged_path.read_text()
self.assertIn(
'[url "git://127.0.0.1:9418/claude-bottle.git"]', content,
'[url "git://127.0.0.1:9418/bot-bottle.git"]', content,
)
self.assertIn(
"\tinsteadOf = ssh://git@host/repo.git", content,
@@ -497,9 +497,9 @@ class TestProvisionGitUser(unittest.TestCase):
def test_noop_when_no_git_user(self):
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
) as ex:
_git._provision_git_user(_plan(), "claude-bottle-demo-abc12")
_git._provision_git_user(_plan(), "bot-bottle-demo-abc12")
self.assertEqual([], self._git_config_calls(ex))
def test_sets_name_and_email_as_node(self):
@@ -508,9 +508,9 @@ class TestProvisionGitUser(unittest.TestCase):
"email": "eric@dideric.is",
})
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
) as ex:
_git._provision_git_user(plan, "claude-bottle-demo-abc12")
_git._provision_git_user(plan, "bot-bottle-demo-abc12")
calls = self._git_config_calls(ex)
self.assertEqual(2, len(calls))
# Both go through `runuser -u node --` so they run as node;
@@ -530,9 +530,9 @@ class TestProvisionGitUser(unittest.TestCase):
def test_name_only(self):
plan = _plan(git_user={"name": "Bot"})
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
) as ex:
_git._provision_git_user(plan, "claude-bottle-demo-abc12")
_git._provision_git_user(plan, "bot-bottle-demo-abc12")
calls = self._git_config_calls(ex)
self.assertEqual(1, len(calls))
self.assertEqual(["user.name", "Bot"], calls[0][0][7:])
@@ -540,9 +540,9 @@ class TestProvisionGitUser(unittest.TestCase):
def test_email_only(self):
plan = _plan(git_user={"email": "bot@example.com"})
with patch(
"claude_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.git._smolvm.machine_exec"
) as ex:
_git._provision_git_user(plan, "claude-bottle-demo-abc12")
_git._provision_git_user(plan, "bot-bottle-demo-abc12")
calls = self._git_config_calls(ex)
self.assertEqual(1, len(calls))
self.assertEqual(["user.email", "bot@example.com"], calls[0][0][7:])
@@ -551,9 +551,9 @@ class TestProvisionGitUser(unittest.TestCase):
class TestProvisionSupervise(unittest.TestCase):
def test_noop_when_supervise_not_enabled(self):
with patch(
"claude_bottle.backend.smolmachines.provision.supervise._smolvm.machine_exec"
"bot_bottle.backend.smolmachines.provision.supervise._smolvm.machine_exec"
) as ex:
_supervise.provision_supervise(_plan(), "claude-bottle-demo-abc12")
_supervise.provision_supervise(_plan(), "bot-bottle-demo-abc12")
ex.assert_not_called()
def test_calls_claude_mcp_add_when_supervise_enabled(self):
@@ -562,10 +562,10 @@ class TestProvisionSupervise(unittest.TestCase):
agent_supervise_url="http://127.0.0.1:9100/",
)
with patch(
"claude_bottle.backend.smolmachines.provision.supervise._smolvm.machine_exec",
"bot_bottle.backend.smolmachines.provision.supervise._smolvm.machine_exec",
return_value=SmolvmRunResult(returncode=0, stdout="", stderr=""),
) as ex:
_supervise.provision_supervise(plan, "claude-bottle-demo-abc12")
_supervise.provision_supervise(plan, "bot-bottle-demo-abc12")
ex.assert_called_once()
argv = ex.call_args.args[1]
# `claude mcp add --scope user` writes to ~/.claude.json,
@@ -589,14 +589,14 @@ class TestProvisionSupervise(unittest.TestCase):
def test_non_zero_exit_logs_warning_but_does_not_raise(self):
plan = _plan(supervise=True)
with patch(
"claude_bottle.backend.smolmachines.provision.supervise._smolvm.machine_exec",
"bot_bottle.backend.smolmachines.provision.supervise._smolvm.machine_exec",
return_value=SmolvmRunResult(
returncode=1, stdout="", stderr="boom",
),
):
# No raise — the bottle still works without the MCP
# entry, so we log and move on.
_supervise.provision_supervise(plan, "claude-bottle-demo-abc12")
_supervise.provision_supervise(plan, "bot-bottle-demo-abc12")
if __name__ == "__main__":
+5 -5
View File
@@ -13,7 +13,7 @@ import unittest
import unittest.mock
from unittest.mock import patch
from claude_bottle.backend.smolmachines import pty_resize
from bot_bottle.backend.smolmachines import pty_resize
class TestPushSize(unittest.TestCase):
@@ -23,11 +23,11 @@ class TestPushSize(unittest.TestCase):
# Per-PTY `stty -F ... 2>/dev/null` swallows EBADF when a
# session has already exited.
with patch.object(pty_resize.subprocess, "run") as run:
pty_resize._push_size("claude-bottle-m", 50, 200)
pty_resize._push_size("bot-bottle-m", 50, 200)
argv = run.call_args.args[0]
self.assertEqual(
["smolvm", "machine", "exec", "--name",
"claude-bottle-m", "--", "sh", "-c"],
"bot-bottle-m", "--", "sh", "-c"],
argv[:8],
)
# cols / rows land in the order stty wants them.
@@ -42,7 +42,7 @@ class TestPushSize(unittest.TestCase):
# PTY's FG-PG / input plumbing). DEVNULL stdin sidesteps
# the interaction.
with patch.object(pty_resize.subprocess, "run") as run:
pty_resize._push_size("claude-bottle-m", 24, 80)
pty_resize._push_size("bot-bottle-m", 24, 80)
self.assertEqual(
pty_resize.subprocess.DEVNULL,
run.call_args.kwargs.get("stdin"),
@@ -116,7 +116,7 @@ class TestMainArgvParsing(unittest.TestCase):
def test_missing_separator_returns_error_exit_code(self):
# No `--` between machine name and inner argv.
with patch.object(pty_resize.sys, "stderr", new=io.StringIO()) as err:
rc = pty_resize.main(["claude-bottle-m", "smolvm", "machine"])
rc = pty_resize.main(["bot-bottle-m", "smolvm", "machine"])
self.assertEqual(2, rc)
self.assertIn("usage:", err.getvalue())
+16 -16
View File
@@ -11,7 +11,7 @@ import subprocess
import unittest
from unittest.mock import patch
from claude_bottle.backend.smolmachines.sidecar_bundle import (
from bot_bottle.backend.smolmachines.sidecar_bundle import (
BundleLaunchSpec,
bundle_container_name,
bundle_network_name,
@@ -37,7 +37,7 @@ def _fail(stderr: str = "boom") -> subprocess.CompletedProcess:
def _spec(**kwargs) -> BundleLaunchSpec:
defaults = dict(
slug="demo-abc12",
network_name="claude-bottle-bundle-demo-abc12",
network_name="bot-bottle-bundle-demo-abc12",
subnet="192.168.50.0/24",
gateway="192.168.50.1",
bundle_ip="192.168.50.2",
@@ -49,10 +49,10 @@ def _spec(**kwargs) -> BundleLaunchSpec:
class TestNamingHelpers(unittest.TestCase):
def test_network_name_uses_bundle_prefix(self):
# Distinct from the docker backend's
# `claude-bottle-net-<slug>` so two backends running the
# `bot-bottle-net-<slug>` so two backends running the
# same agent slug don't collide.
self.assertEqual(
"claude-bottle-bundle-myagent-xyz",
"bot-bottle-bundle-myagent-xyz",
bundle_network_name("myagent-xyz"),
)
@@ -61,7 +61,7 @@ class TestNamingHelpers(unittest.TestCase):
# bundle container — dashboard prefix-discovery covers
# both backends with one filter.
self.assertEqual(
"claude-bottle-sidecars-myagent-xyz",
"bot-bottle-sidecars-myagent-xyz",
bundle_container_name("myagent-xyz"),
)
@@ -69,7 +69,7 @@ class TestNamingHelpers(unittest.TestCase):
class TestNetworkLifecycle(unittest.TestCase):
def _patch_run(self, **kwargs):
return patch(
"claude_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
**kwargs,
)
@@ -108,7 +108,7 @@ class TestNetworkLifecycle(unittest.TestCase):
class TestStartBundle(unittest.TestCase):
def _patch_run(self):
return patch(
"claude_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
return_value=_ok(),
)
@@ -118,7 +118,7 @@ class TestStartBundle(unittest.TestCase):
argv = m.call_args.args[0]
# --network NETNAME --ip <bundle-ip> on the docker run.
self.assertIn("--network", argv)
self.assertIn("claude-bottle-bundle-demo-abc12", argv)
self.assertIn("bot-bottle-bundle-demo-abc12", argv)
self.assertIn("--ip", argv)
self.assertIn("192.168.50.2", argv)
# Detached and auto-removed.
@@ -126,9 +126,9 @@ class TestStartBundle(unittest.TestCase):
self.assertIn("--rm", argv)
# Container name uses the per-slug bundle prefix.
i = argv.index("--name")
self.assertEqual("claude-bottle-sidecars-demo-abc12", argv[i + 1])
self.assertEqual("bot-bottle-sidecars-demo-abc12", argv[i + 1])
# Image at the end.
self.assertEqual("claude-bottle-sidecars:latest", argv[-1])
self.assertEqual("bot-bottle-sidecars:latest", argv[-1])
def test_daemons_env_passed_in(self):
with self._patch_run() as m:
@@ -136,7 +136,7 @@ class TestStartBundle(unittest.TestCase):
argv = m.call_args.args[0]
self.assertIn("-e", argv)
self.assertIn(
"CLAUDE_BOTTLE_SIDECAR_DAEMONS=egress,pipelock,supervise",
"BOT_BOTTLE_SIDECAR_DAEMONS=egress,pipelock,supervise",
argv,
)
@@ -164,7 +164,7 @@ class TestStartBundle(unittest.TestCase):
def test_failure_dies(self):
with patch(
"claude_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
return_value=_fail("invalid mount"),
):
with self.assertRaises(SystemExit):
@@ -175,7 +175,7 @@ class TestStartBundle(unittest.TestCase):
# subprocess being run with the host env. Confirm `env=`
# threads through.
with patch(
"claude_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
return_value=_ok(),
) as m:
start_bundle(_spec(), env={"FOO": "bar"})
@@ -185,7 +185,7 @@ class TestStartBundle(unittest.TestCase):
class TestStopBundle(unittest.TestCase):
def _patch_run(self, **kwargs):
return patch(
"claude_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
"bot_bottle.backend.smolmachines.sidecar_bundle.subprocess.run",
**kwargs,
)
@@ -193,13 +193,13 @@ class TestStopBundle(unittest.TestCase):
with self._patch_run(return_value=_ok()) as m:
stop_bundle("demo-abc12")
self.assertEqual(
["docker", "rm", "-f", "claude-bottle-sidecars-demo-abc12"],
["docker", "rm", "-f", "bot-bottle-sidecars-demo-abc12"],
m.call_args.args[0],
)
def test_missing_container_is_idempotent(self):
with self._patch_run(return_value=_fail(
"Error: No such container: claude-bottle-sidecars-demo-abc12"
"Error: No such container: bot-bottle-sidecars-demo-abc12"
)):
stop_bundle("demo-abc12") # no raise
+9 -9
View File
@@ -12,7 +12,7 @@ import unittest
from pathlib import Path
from unittest.mock import patch
from claude_bottle.backend.smolmachines.smolvm import (
from bot_bottle.backend.smolmachines.smolvm import (
SmolvmError,
SmolvmRunResult,
is_available,
@@ -46,17 +46,17 @@ class TestArgvShapes(unittest.TestCase):
def _patch_run(self):
return patch(
"claude_bottle.backend.smolmachines.smolvm.subprocess.run",
"bot_bottle.backend.smolmachines.smolvm.subprocess.run",
return_value=_ok(),
)
def test_pack_create_argv(self):
with self._patch_run() as m:
pack_create("claude-bottle:latest", Path("/tmp/agent.smolmachine"))
pack_create("bot-bottle-claude:latest", Path("/tmp/agent.smolmachine"))
argv = m.call_args.args[0]
self.assertEqual(
["smolvm", "pack", "create",
"--image", "claude-bottle:latest",
"--image", "bot-bottle-claude:latest",
"-o", "/tmp/agent.smolmachine"],
argv,
)
@@ -175,7 +175,7 @@ class TestErrorPath(unittest.TestCase):
def test_create_failure_raises(self):
with patch(
"claude_bottle.backend.smolmachines.smolvm.subprocess.run",
"bot_bottle.backend.smolmachines.smolvm.subprocess.run",
return_value=_fail("no such image"),
):
with self.assertRaises(SmolvmError) as cm:
@@ -185,7 +185,7 @@ class TestErrorPath(unittest.TestCase):
def test_pack_create_failure_raises(self):
with patch(
"claude_bottle.backend.smolmachines.smolvm.subprocess.run",
"bot_bottle.backend.smolmachines.smolvm.subprocess.run",
return_value=_fail("pack failed"),
):
with self.assertRaises(SmolvmError):
@@ -195,7 +195,7 @@ class TestErrorPath(unittest.TestCase):
# The in-VM command's exit code is what Bottle.exec sees;
# `false` exiting non-zero is not a smolvm failure.
with patch(
"claude_bottle.backend.smolmachines.smolvm.subprocess.run",
"bot_bottle.backend.smolmachines.smolvm.subprocess.run",
return_value=subprocess.CompletedProcess(
args=[], returncode=42, stdout="", stderr="nope",
),
@@ -207,14 +207,14 @@ class TestErrorPath(unittest.TestCase):
class TestIsAvailable(unittest.TestCase):
def test_true_when_on_path(self):
with patch(
"claude_bottle.backend.smolmachines.smolvm.shutil.which",
"bot_bottle.backend.smolmachines.smolvm.shutil.which",
return_value="/usr/local/bin/smolvm",
):
self.assertTrue(is_available())
def test_false_when_missing(self):
with patch(
"claude_bottle.backend.smolmachines.smolvm.shutil.which",
"bot_bottle.backend.smolmachines.smolvm.shutil.which",
return_value=None,
):
self.assertFalse(is_available())
+4 -4
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
import unittest
from unittest.mock import patch
from claude_bottle.backend.smolmachines.util import (
from bot_bottle.backend.smolmachines.util import (
smolmachines_bundle_subnet,
smolmachines_preflight,
)
@@ -57,14 +57,14 @@ class TestBundleSubnet(unittest.TestCase):
class TestPreflight(unittest.TestCase):
def test_smolvm_present_returns_none(self):
with patch(
"claude_bottle.backend.smolmachines.util.shutil.which",
"bot_bottle.backend.smolmachines.util.shutil.which",
return_value="/usr/local/bin/smolvm",
):
self.assertIsNone(smolmachines_preflight())
def test_missing_smolvm_dies(self):
with patch(
"claude_bottle.backend.smolmachines.util.shutil.which",
"bot_bottle.backend.smolmachines.util.shutil.which",
return_value=None,
):
with self.assertRaises(SystemExit) as cm:
@@ -75,7 +75,7 @@ class TestPreflight(unittest.TestCase):
import io
import sys
with patch(
"claude_bottle.backend.smolmachines.util.shutil.which",
"bot_bottle.backend.smolmachines.util.shutil.which",
return_value=None,
):
captured = io.StringIO()
+12 -12
View File
@@ -8,8 +8,8 @@ import unittest
from datetime import datetime, timezone
from pathlib import Path
from claude_bottle import supervise
from claude_bottle.supervise import (
from bot_bottle import supervise
from bot_bottle.supervise import (
AuditEntry,
Proposal,
Response,
@@ -108,7 +108,7 @@ class TestResponseRoundtrip(unittest.TestCase):
class TestQueueIO(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory(prefix="claude-bottle-supervise-test.")
self._tmp = tempfile.TemporaryDirectory(prefix="bot-bottle-supervise-test.")
self.queue_dir = Path(self._tmp.name)
def tearDown(self):
@@ -207,7 +207,7 @@ class TestQueueIO(unittest.TestCase):
class TestAuditLog(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory(prefix="claude-bottle-supervise-audit.")
self._tmp = tempfile.TemporaryDirectory(prefix="bot-bottle-supervise-audit.")
self._home_patch = self._patch_home(Path(self._tmp.name))
def tearDown(self):
@@ -215,13 +215,13 @@ class TestAuditLog(unittest.TestCase):
self._tmp.cleanup()
def _patch_home(self, fake_home: Path):
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return fake_home / ".claude-bottle"
return fake_home / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
return lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
return lambda: setattr(supervise, "bot_bottle_root", original)
def test_write_then_read_single_entry(self):
e = AuditEntry(
@@ -351,13 +351,13 @@ class TestSupervisePrepare(unittest.TestCase):
self._tmp.cleanup()
def _patch_home(self, fake_home: Path):
original = supervise.claude_bottle_root
original = supervise.bot_bottle_root
def fake_root() -> Path:
return fake_home / ".claude-bottle"
return fake_home / ".bot-bottle"
supervise.claude_bottle_root = fake_root # type: ignore[assignment]
return lambda: setattr(supervise, "claude_bottle_root", original)
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
return lambda: setattr(supervise, "bot_bottle_root", original)
def test_prepare_creates_queue_and_current_config(self):
plan = _StubSupervise().prepare(
+5 -5
View File
@@ -12,13 +12,13 @@ from pathlib import Path
# The server module loads `supervise` via same-directory import inside
# the container (Dockerfile.supervise WORKDIRs into /app). For tests
# we mirror that by injecting claude_bottle/ onto sys.path under the
# we mirror that by injecting bot_bottle/ onto sys.path under the
# bare name `supervise`.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "claude_bottle"))
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "bot_bottle"))
import supervise as _sv # noqa: E402
from claude_bottle import supervise_server # noqa: E402
from claude_bottle.supervise_server import (
from bot_bottle import supervise_server # noqa: E402
from bot_bottle.supervise_server import (
ERR_INVALID_PARAMS,
ERR_INVALID_REQUEST,
ERR_METHOD_NOT_FOUND,
@@ -152,7 +152,7 @@ class TestHandleInitialize(unittest.TestCase):
self.assertEqual("2024-11-05", result["protocolVersion"])
self.assertIn("tools", result["capabilities"]) # type: ignore[index]
self.assertEqual(
"claude-bottle-supervise",
"bot-bottle-supervise",
result["serverInfo"]["name"], # type: ignore[index]
)
+4 -4
View File
@@ -5,8 +5,8 @@ actually use, and every rejection case the PRD enumerates."""
import textwrap
import unittest
from claude_bottle.yaml_subset import YamlSubsetError
from claude_bottle.yaml_subset import parse_frontmatter, parse_yaml_subset
from bot_bottle.yaml_subset import YamlSubsetError
from bot_bottle.yaml_subset import parse_frontmatter, parse_yaml_subset
def _y(s: str):
@@ -174,7 +174,7 @@ class TestBlockList(unittest.TestCase):
def test_list_item_with_inline_list_value(self):
# role: [git-insteadof, tea-login] — the exact shape in the
# claude-bottle manifest.
# bot-bottle manifest.
out = _y("""
routes:
- path: /x/
@@ -267,7 +267,7 @@ class TestRealisticBottleFile(unittest.TestCase):
git:
remotes:
gitea.dideric.is:
Name: claude-bottle
Name: bot-bottle
Upstream: ssh://git@gitea.dideric.is:30009/x/y.git
IdentityFile: ~/.ssh/gitea.pem
ExtraHosts: