Commit Graph

63 Commits

Author SHA1 Message Date
didericis 7b5a798186 refactor(backend): introduce BottleProvisioner ABC + DockerBottleProvisioner
test / run tests/run_tests.py (pull_request) Successful in 17s
Lift the file-copying-into-the-running-container step out of
DockerBottleBackend._provision_container into its own class. The
backend now holds a DockerBottleProvisioner instance and delegates
the post-launch provisioning to it.

  - BottleProvisioner (abstract) in backend/__init__.py with a
    `provision(plan, target) -> str | None` method.
  - DockerBottleProvisioner (concrete) in backend/docker/provisioner.py
    inheriting from the base, narrowing plan to DockerBottlePlan via
    isinstance, and carrying the prompt/skills/SSH/.git copy logic
    unchanged.
  - DockerBottleBackend keeps a class-level DockerBottleProvisioner()
    and calls self._provisioner.provision(plan, container) from launch.
    _provision_container method removed.

No behavior change.
2026-05-11 00:04:12 -04:00
didericis 70a22fa210 refactor: rename platform abstraction to backend
test / run tests/run_tests.py (pull_request) Successful in 21s
Across the package:
  - claude_bottle/platform/         -> claude_bottle/backend/
  - platform/docker/platform.py     -> backend/docker/backend.py
  - class BottlePlatform            -> BottleBackend
  - class DockerBottlePlatform      -> DockerBottleBackend
  - get_bottle_platform()           -> get_bottle_backend()
  - env var CLAUDE_BOTTLE_PLATFORM  -> CLAUDE_BOTTLE_BACKEND
  - dict _PLATFORMS                 -> _BACKENDS

"Backend" is shorter and more established as the term for a
pluggable strategy-pattern implementation. "Platform" was vague
(could mean OS, hardware, cloud) and mildly redundant — Docker is
itself a platform.

The previous PRD section claiming "the Backend protocol was
rejected" referred to a low-level run/exec/cp/network_connect
protocol; the name was never the reason. The PRD is updated to
describe that rejected design by shape rather than by name.

The bottle/agent concepts and the manifest schema are unchanged.
2026-05-10 23:59:38 -04:00
didericis c79966731c refactor(docker): move network.py into platform/docker/
test / run tests/run_tests.py (pull_request) Successful in 14s
The Docker bridge / internal network primitives are Docker-specific;
they belong inside the Docker platform package alongside util.py and
the rest. Same logic the earlier top-level docker.py move followed.

Imports:
  - platform.py: `from ... import network as network_mod`
                 -> `from . import network as network_mod`
  - network.py: `from .log import ...` -> `from ...log import ...`
  - tests/test_orphan_cleanup.py: from claude_bottle.network
                 -> from claude_bottle.platform.docker.network
2026-05-10 23:40:58 -04:00
didericis 1d2c18eaae refactor(platform): rename claude_bottle/bottles -> claude_bottle/platform
test / run tests/run_tests.py (pull_request) Successful in 13s
'bottles' was the package name when it held a single Bottle Protocol;
since we added BottlePlatform / BottlePlan / BottleCleanupPlan and
made it the home of platform dispatch, 'platform' describes the
package better. The 'bottle' concept (and the manifest field) stays.

CLI imports update from ..bottles to ..platform; internal relative
imports inside the package survive the rename unchanged. Git
detected all 7 file renames.
2026-05-10 23:37:28 -04:00
didericis aaed390953 refactor(bottles): Bottle becomes an ABC; DockerBottle inherits
test / run tests/run_tests.py (pull_request) Successful in 14s
Bottle was the only Protocol in an otherwise-ABC family
(BottlePlan, BottleCleanupPlan, BottlePlatform are all ABCs).
Convert to an ABC with abstract exec_claude / cp_in / close,
matching the rest of the hierarchy.

Rename _DockerBottle -> DockerBottle: the underscore was a
default-Python-private instinct that doesn't match the sibling
plan classes (DockerBottlePlan, DockerBottleCleanupPlan), all of
which are equally "only constructed by the platform" and yet
public-by-name.

Re-export DockerBottle from claude_bottle.bottles.docker.
2026-05-10 23:32:33 -04:00
didericis d28f0e6d9b refactor(docker): split bottles/docker/__init__.py into sibling modules
test / run tests/run_tests.py (pull_request) Successful in 14s
The single docker/__init__.py grew to ~555 lines holding the platform,
its plan classes, the bottle handle, and the runsc probe. Split into:

  - util.py                : Docker subprocess primitives + runsc_available
  - bottle_plan.py         : DockerBottlePlan (+ its print method)
  - bottle_cleanup_plan.py : DockerBottleCleanupPlan
  - bottle.py              : _DockerBottle handle class
  - platform.py            : DockerBottlePlatform (the bulk)

docker/__init__.py becomes a thin re-export shim so existing imports
(claude_bottle.bottles.docker.DockerBottlePlatform, etc.) keep working.
2026-05-10 23:29:38 -04:00
didericis e20f8af05a refactor(bottles): make docker a package; absorb top-level docker.py
test / run tests/run_tests.py (pull_request) Successful in 14s
- claude_bottle/bottles/docker.py -> claude_bottle/bottles/docker/__init__.py
- claude_bottle/docker.py        -> claude_bottle/bottles/docker/util.py

The host-side Docker primitives (require_docker, slugify, image_exists,
container_exists, build_image, build_image_with_cwd) lived at the top
of the package as if cross-cutting, but they are Docker-specific.
Moving them into bottles/docker/util.py colocates them with the
platform that uses them and clears the top-level namespace.

Imports in bottles/docker/__init__.py shift to `from . import util as
docker_mod` so the existing call sites (`docker_mod.require_docker()`,
etc.) keep working unchanged.
2026-05-10 23:25:42 -04:00
didericis 47b882f634 refactor(bottles): move 'list active' onto DockerBottlePlatform
test / run tests/run_tests.py (pull_request) Successful in 14s
Add list_active() abstract method on BottlePlatform; DockerBottlePlatform
implements it with the existing docker ps logic. cli/list.py no longer
calls docker directly — it just dispatches the active branch to the
platform.
2026-05-10 23:19:22 -04:00
didericis 18d29fc23f refactor(bottles): two-phase cleanup parallel to prepare/launch
test / run tests/run_tests.py (pull_request) Successful in 13s
cmd_cleanup used to only sweep running containers via `docker ps`,
missing stopped pipelock sidecars and orphaned networks entirely. On
my host the new version surfaced ~10 stranded networks left behind by
SIGKILLed sessions — the kind of thing the old command implied it was
handling.

New shape, symmetric with start:
- BottleCleanupPlan (abstract, in bottles/__init__.py) with `print` +
  `empty` abstract members.
- DockerBottleCleanupPlan (concrete, in bottles/docker.py) carrying
  the resolved tuples of containers and networks.
- BottlePlatform gains abstract prepare_cleanup() + cleanup(plan).
  DockerBottlePlatform implements both:
    - prepare_cleanup: docker ps -a + docker network ls, both
      filtered to ^claude-bottle-, sorted for stable output.
    - cleanup: docker rm -f containers first (they hold the network
      attachment), then docker network rm.
- cmd_cleanup is now ~25 lines: prepare → print → y/N → cleanup.
2026-05-10 23:14:54 -04:00
didericis 4a45c267f3 refactor(cli): remove redundant 'build' command
test / run tests/run_tests.py (pull_request) Successful in 13s
DockerBottlePlatform.launch already runs 'docker build' on every
start, and the layer cache makes no-change rebuilds nearly free.
Anything 'cli.py build' did, 'cli.py start' does for you.
2026-05-10 23:05:24 -04:00
didericis 5f82044403 refactor(bottles): move _run_agent_container and _provision_container onto the platform class
test / run tests/run_tests.py (pull_request) Successful in 14s
They were the only remaining module-level helpers in docker.py besides
runsc_available(); promote them to private methods on DockerBottlePlatform
so all the Docker orchestration logic lives in one place.
2026-05-10 23:02:21 -04:00
didericis 7ab35a5e2a refactor(bottles): absorb prepare/launch fns into DockerBottlePlatform
test / run tests/run_tests.py (pull_request) Successful in 14s
The module-level prepare_docker_bottle and launch_docker_bottle were
trivial delegations from the platform class. Move their bodies onto
DockerBottlePlatform.prepare and .launch directly. The launch method
keeps the @contextmanager decorator; isinstance narrows BottlePlan
to DockerBottlePlan at the entry point.

Internal helpers (_run_agent_container, _provision_container,
runsc_available) stay at module scope — they're stateless and don't
benefit from self.

No behavior change. Tests pass; dry-run output unchanged.
2026-05-10 23:00:07 -04:00
didericis e22a96e511 refactor(bottles): BottlePlatform becomes ABC; DockerBottlePlatform in docker.py
test / run tests/run_tests.py (pull_request) Successful in 18s
Mirror the BottlePlan -> DockerBottlePlan hierarchy at the platform
layer. BottlePlatform is now an abstract base with abstract `prepare`
and `launch` methods; DockerBottlePlatform lives alongside the rest of
the Docker code in bottles/docker.py and supplies the concrete impls.

The registry in bottles/__init__.py now holds an instance of each
concrete platform class. Future per-platform state (region, api
token, cleanup primitives) has a natural home on the subclass rather
than being stitched onto a dataclass struct.

No behavior change. Tests pass; dry-run output unchanged.
2026-05-10 22:56:47 -04:00
didericis 2827d9b899 refactor(bottles): introduce BottlePlan base + move print onto plan
test / run tests/run_tests.py (pull_request) Successful in 19s
- Add BottlePlan (frozen dataclass + ABC) with spec, stage_dir, and an
  abstract `print(*, remote_control)` method.
- DockerBottlePlan now inherits from BottlePlan; spec/stage_dir come
  from the base, Docker-specific fields stay on the subclass.
- Move BottleSpec from bottles/docker.py to bottles/__init__.py so the
  cross-platform types live together. docker.py pulls them via
  `from . import ...`.
- Move show_plan from cli/start.py to `DockerBottlePlan.print`. Caller
  becomes `plan.print(remote_control=...)`. The CLI no longer reads
  any Docker-specific fields.
- BottlePlatform.prepare is now typed `Callable[..., BottlePlan]`.

cmd_start drops ~46 more lines.
2026-05-10 22:49:57 -04:00
didericis 236c4fa50c refactor(bottles): rename DockerBottleSpec to BottleSpec
test / run tests/run_tests.py (pull_request) Successful in 13s
The spec is intent-only and platform-agnostic — only the plan carries
Docker-specific fields. Drop the 'Docker' prefix and re-export from
claude_bottle.bottles so callers see it as cross-platform.
2026-05-10 22:40:19 -04:00
didericis 4f16b3a9e1 refactor(bottles): split factory into prepare + launch phases
test / run tests/run_tests.py (pull_request) Successful in 15s
The Docker factory had absorbed live container ops but left the
host-side prep (image-name resolution, container-name collision
retry, pipelock yaml generation, env_resolve writes, host
validation) in cli/start.py. That kept ~half the Docker-specific
logic outside the abstraction.

Split the factory into two phases:

  prepare_docker_bottle(spec, stage_dir=...) -> DockerBottlePlan
      Resolves names, validates skills/SSH, writes scratch files.
      No Docker resources created yet.

  launch_docker_bottle(plan) -> ContextManager[Bottle]
      Builds image, creates networks, boots pipelock, runs the
      agent container, provisions files. Teardown on exit.

DockerBottleSpec shrinks to intent-only inputs (manifest, agent
name, --cwd flag, user_cwd, forward_oauth_token). The CLI no longer
references docker_mod, pipelock, skills, ssh, or env_resolve.

get_bottle_factory becomes get_bottle_platform returning a
BottlePlatform with .prepare and .launch — one selectable thing per
platform.

The Bottle handle now remembers the in-container prompt path and
adds --append-system-prompt-file to claude's argv when present, so
the CLI no longer needs to know the path.

cmd_start: ~148 lines down from 229. Tests pass; dry-run output
byte-identical.
2026-05-10 22:36:26 -04:00
didericis a284d85296 refactor(start): show_plan now takes DockerBottleSpec
test / run tests/run_tests.py (pull_request) Successful in 15s
2026-05-10 22:23:40 -04:00
didericis 7500ba230c refactor(start): extract show_plan from cmd_start
test / run tests/run_tests.py (pull_request) Successful in 15s
2026-05-10 22:20:33 -04:00
didericis d75cc9325f feat(bottles): implement bottle factory abstraction per PRD 0003
test / run tests/run_tests.py (pull_request) Successful in 16s
Introduce claude_bottle/bottles/ with a Bottle Protocol and a
get_bottle_factory() that dispatches on CLAUDE_BOTTLE_PLATFORM
(default "docker"). Move every Docker-specific subprocess.run call
from cli/start.py, plus the orchestration of build, networks, the
pipelock sidecar, container launch, and per-container provisioning
(prompt, skills, ssh, .git), into create_docker_bottle.

Drop bottles[].runtime from the manifest schema. Auto-detect whether
gVisor is registered with the daemon and pass --runtime=runsc when it
is; the preflight shows the resolved runtime so the choice is visible.
Manifests still carrying 'runtime' get a clear error pointing at the
auto-detect behavior, rather than silent ignore.

Out of scope: cli/cleanup.py and cli/list.py still call docker
directly. They enumerate active bottles across the host, which is a
separate concern from "create a bottle" and is left for a follow-up
that introduces a list_active/cleanup primitive on the factory.
2026-05-10 22:15:05 -04:00
didericis d5c056f36e docs(prd): add 0003 bottle factory abstraction
test / run tests/run_tests.py (pull_request) Successful in 17s
2026-05-10 21:56:10 -04:00
didericis a39c7b1b7b Merge pull request 'refactor(manifest): convert to frozen dataclasses' (#4) from convert-manifest-to-dataclass into main
test / run tests/run_tests.py (push) Successful in 16s
2026-05-10 21:42:34 -04:00
didericis 9343f6f21d refactor(manifest): drop _json_type, use type(x).__name__ in error messages
test / run tests/run_tests.py (pull_request) Successful in 14s
The jq-style mapping (bool→"boolean", list→"array", None→"null", etc.)
existed only to match the original bash error wording. Not worth the
extra function; Python's native type names are clear enough.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 21:36:38 -04:00
didericis e9a3de49af fix(types): make manifest.py clean under pyright strict
test / run tests/run_tests.py (pull_request) Successful in 14s
- log.die() typed NoReturn so pyright knows it terminates control flow
  (was returning the unreachable Die instance type).
- manifest.py: raw inputs typed object (not Any) and narrowed via a new
  _as_json_object helper that validates str keys and returns
  dict[str, object]. Eliminates the Unknown cascade through .get()
  calls under strict.
- _from_dict classmethods renamed to from_dict so cross-class
  construction (Bottle.from_dict from Manifest.from_json_obj, etc.)
  doesn't trip reportPrivateUsage.
- _SUPPORTED_RUNTIMES typed tuple[Runtime, ...] so the membership
  check narrows runtime_raw to Literal["runc", "runsc"] and the
  # type: ignore[assignment] is no longer needed.
- Bottle.env uses a typed _empty_str_dict factory; bare dict resolves
  to dict[Unknown, Unknown] under strict.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 21:34:03 -04:00
didericis 1f36d53f7b refactor(manifest): convert TypedDict to frozen dataclasses
test / run tests/run_tests.py (pull_request) Successful in 14s
Replace the TypedDict + 14 manifest_* free functions with frozen
dataclasses (SshEntry, BottleEgress, Bottle, Agent, Manifest) carrying
their own validators and constructors. Call sites import Manifest and
chain attribute access; the manifest_* helpers and manifest_validate
are gone.

Behavior changes worth flagging:
- Agent.bottle is now required (was optional with a "(none)" fallback).
  Manifest.from_json_obj dies if any agent lacks a 'bottle' field or
  references an undefined bottle, where previously start.py raised the
  error lazily for the specific agent being launched.
- ssh.py now takes SshEntry instances; Host/IdentityFile shape checks
  moved upstream into Manifest construction, leaving only the IdentityFile
  filesystem-existence check in ssh_validate_entries.
- pipelock_bottle_allowlist's per-element string check is dropped — the
  Manifest validator enforces it at load.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 21:20:15 -04:00
didericis 36cb0c53bf refactor(manifest): add TypedDict schema and eager validation
test / run tests/run_tests.py (pull_request) Successful in 20s
Move schema checks out of per-access getters into a single
manifest_validate pass invoked by manifest_resolve. Getters can now
assume bottles/agents are well-typed dicts and every agent has a
defined bottle, so the .get(...) or {} chains collapse. Behavior
change: a bad runtime / shape error anywhere in the manifest now
fails at load instead of on the N-th read.

Intermediate step toward replacing TypedDict with a dataclass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 21:08:54 -04:00
didericis 7e0e256370 docs: add research note on polish priorities to close the maturity gap
test / run tests/run_tests.py (push) Successful in 21s
Captures the ranked list of changes that would move the project from
"works for me" toward the perceived maturity of comparable tools —
onboarding friction, error messages, distribution, versioning, schema
validation, starter library, docs site, cross-platform CI. Includes
effort estimates and an explicit "what polish is not" section so the
roadmap doesn't drift into feature work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 20:38:44 -04:00
didericis 79604fded7 docs: reposition README around scoped-agent wedge and note DoH
test / run tests/run_tests.py (push) Successful in 21s
Lead with what the project does for the user — scoped Claude Code
agents on self-hosted infrastructure with per-agent secret and egress
limits — instead of the 2024-coded "isolated container" framing.
Tagline, "Why claude-bottle?" intro, and goals list now name secret
minimization, egress allowlisting, and self-hosted operation as the
load-bearing properties.

Also adds a sentence to the security model noting that DoH is
blocked structurally by the existing egress allowlist, since the
bottle has no L3 path off-box except through pipelock's hostname-
allowlisted CONNECT proxy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 03:00:19 -04:00
didericis fe232744a6 docs: reframe security model around secret exposure and exfiltration
test / run tests/run_tests.py (push) Successful in 20s
Soften the "container is the boundary against reaching the host"
framing in favor of what the design actually leans on: granting each
bottle only the secrets it needs, and constraining where those
secrets can travel via pipelock. The container is one layer rather
than the load-bearing one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:42:12 -04:00
didericis e1efc64862 docs: add research note on Apple container as an alternative backend
test / run tests/run_tests.py (push) Successful in 14s
Captures the surface area of the current Docker integration, how it
maps to Apple's `container` framework, the dominant networking risk
(pipelock multi-network attach), and the cost difference between a
faithful port and a simplified VM-firewall variant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:36:11 -04:00
didericis 1e6f254db5 docs: add research note comparing bash, Python, and Go for the CLI
test / run tests/run_tests.py (push) Successful in 14s
Captures the reasoning for staying on Python, the conditions under which
a Go rewrite would pay for itself, and why bash isn't viable at the
project's current size.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:34:40 -04:00
didericis 65d2ab9d5f docs: fill in copyright in LICENSE appendix
test / run tests/run_tests.py (push) Successful in 14s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:03:50 -04:00
didericis acbaffb98e docs: add Apache 2.0 LICENSE and link it from the README
test / run tests/run_tests.py (push) Successful in 15s
Drops the canonical Apache 2.0 text at repo root and adds a License
section to the README with the copyright line. Apache 2.0 picked over
MIT for the patent grant and contributor language, both of which
matter for a security-adjacent tool aiming at corporate-evaluable
adoption. Also tidies the manifest example's GIT_AUTHOR_NAME to use
the project's online handle.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:02:42 -04:00
didericis e7cfc91ca5 docs: consolidate egress + gVisor docs into a worked Manifest section
test / run tests/run_tests.py (push) Successful in 17s
Replaces the standalone Egress section with a Manifest section that
shows a complete bottle + agent example, with the egress and gVisor
explanations folded into JSONC comments above the relevant keys. The
gVisor paragraph in Security model is trimmed to a one-line pointer
at the manifest example.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 01:47:52 -04:00
didericis c8a35beb12 docs: add project logo and trademark disclaimer to README
test / run tests/run_tests.py (push) Successful in 15s
A short apothecary-bottle SVG with a cream cartoon robot inside —
sized roughly to the robot so it works as a favicon-shaped icon.
README gains a centered logo above the title and a Trademarks
section disclaiming affiliation with Anthropic and framing the
"claude" in the project name as descriptive use.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 01:40:05 -04:00
didericis ec6261cd77 docs: add Fly Machines case study to remote-docker-vm-isolation note
test / run tests/run_tests.py (push) Successful in 13s
Concrete worked example covering image strategy (with the bake-the-
claude-bottle-image-in optimization that elides 30-90s of in-VM
build), cold/warm/hot boot-to-prompt timing, standby vs ephemeral
cost breakdown, three workflow patterns, and Fly-specific gotchas
(DinD kernel requirements, the y/N preflight blocking automated
launch, pricing-may-have-moved hedge).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 01:18:08 -04:00
didericis 43453c66ea docs: add research note on remote Docker VM as an isolation upgrade
test / run tests/run_tests.py (push) Successful in 15s
Argues that running claude-bottle unchanged on a remote Linux VM with
dockerd is the cheapest practical path to stronger isolation than
local Docker — preserves the v1 pipelock topology, requires zero code
changes, and shrinks the agent's blast radius from the developer
laptop to a disposable VM. Cross-references the existing
stronger-isolation-alternatives and local-vs-remote-agent-execution
notes so the research set composes cleanly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 01:07:17 -04:00
didericis e3f5a5907a feat(bottle): opt-in gVisor runtime per bottle
test / run tests/run_tests.py (push) Successful in 19s
Bottles can now set "runtime": "runsc" to launch the agent container
under gVisor instead of runc, adding a userspace syscall barrier
between the agent and the host kernel. Default is runc (Docker
default). Pipelock stays on the default runtime per the research doc's
minimum-diff prescription.

The launcher verifies runsc is registered with the daemon before
launch, surfaces the runtime in the preflight plan, and dies with an
install pointer (and a macOS-not-supported note) when runsc is
requested but unavailable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:48:11 -04:00
didericis 3eff1e0b6e docs: replace non-goals with a security model section in README
test / run tests/run_tests.py (push) Successful in 18s
Frames the per-agent isolation story (each bottle gets only the env,
skills, ssh, and egress hosts its manifest grants) and is honest about
the limits of the container boundary, pointing at the new research doc
for the stronger-isolation v2 question.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:41:14 -04:00
didericis 7986f2bd23 docs: add research note on stronger isolation alternatives
test / run tests/run_tests.py (push) Successful in 19s
Surveys gVisor, Kata, Firecracker, and Apple Container as replacements
or complements to Docker+runc, with concrete file-level migration notes
for this codebase and a recommended rung-by-rung path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:38:46 -04:00
didericis cc5e772519 docs: replace stale .sh paths with claude_bottle/*.py equivalents
test / run tests/run_tests.py (push) Successful in 13s
Cleans up references to the pre-refactor bash layout (cli.sh,
lib/*.sh, scripts/*.sh) across README, Dockerfile, the pipelock PRD,
and research notes. Refreshes line numbers in the oauth-token note
against the current cli/start.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:27:25 -04:00
didericis 4ebfcec2f7 fix(cli): make 'build --help' actually print help
test / run tests/run_tests.py (push) Successful in 15s
cmd_build was ignoring its argv, so 'cli.py build --help' fell through
and started a docker build instead of printing the subcommand's
argparse help. Wire up an empty parser so --help and unknown args are
handled the same way the other subcommands handle them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:16:17 -04:00
didericis f817847dff refactor(cli): split claude_bottle/cli.py into a package
test / run tests/run_tests.py (push) Successful in 20s
One file per subcommand under claude_bottle/cli/, with shared constants
and the tty helper in _common.py and dispatch in __init__.py. The
public import (from claude_bottle.cli import main) is unchanged, so
the root cli.py entrypoint and the test suite see no surface change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:15:16 -04:00
didericis 0c1740ca99 chore: remove journal and project-local init-entry skill
test / run tests/run_tests.py (push) Successful in 23s
Drop docs/JOURNAL.md and .claude/skills/init-entry/, and update
CLAUDE.md, docs/INDEX.md, and claude-bottle.example.json so nothing
points at them anymore.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 23:55:39 -04:00
didericis b88fbbaedd docs: add claude-bottle.example.json
test / run tests/run_tests.py (push) Successful in 17s
Worked example covering two bottles (one minimal, one with all three
env-entry modes, an SSH entry, and a wider egress allowlist) and three
agents that share them. Named .example.json so manifest_resolve does
not auto-load it when running cli.py from the repo root.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 23:53:28 -04:00
didericis 91b1683306 docs: fix post-Python-refactor drift in README and Dockerfile
test / run tests/run_tests.py (push) Successful in 25s
Update the quickstart command to ./cli.py and drop a stale Dockerfile
comment that referenced scripts/lib/auth.sh, which no longer exists
after the bash->Python refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 23:51:11 -04:00
didericis 4694db1201 PRD 0002: Test pipeline on Gitea Actions (#3)
test / run tests/run_tests.py (push) Successful in 20s
2026-05-09 02:48:03 -04:00
didericis 97aabd3d75 docs: trim CLAUDE.md to minimal orientation
Drop the Intended design section and PRD references; keep only
What this is, Goals, Non-goals, Repository layout, Conventions,
and When you're unsure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 14:58:46 -04:00
didericis 3f03d65e73 chore: tidy gitignore, Dockerfile indent, unused import
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 14:51:59 -04:00
didericis 399ed93dc8 refactor: convert project from bash to Python
Replaces cli.sh + lib/*.sh with a claude_bottle/ Python package and a
cli.py entry point. No external dependencies — uses only Python's
stdlib (json, subprocess, getpass, tempfile, argparse, re, etc.).

- claude_bottle/{log,docker,manifest,env_resolve,network,pipelock,
  skills,ssh,cli}.py mirror the previous lib/*.sh modules.
- Tests converted to unittest under tests/test_*.py with a stdlib
  runner at tests/run_tests.py (unit | integration | path).
- .githooks/commit-msg ported to Python; same Conventional Commits rules.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:26:58 +00:00
didericis b94b6904ae feat(cli): make --remote-control on start opt-in
Previously cmd_start unconditionally passed --remote-control to claude.
Make it a parsed flag so callers can choose. Behavior change: the
default is now disabled — pass --remote-control to opt in.

Surfaced in usage, the launch plan, and the assembled CLAUDE_ARGS.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 02:15:25 -04:00