treebox 0.3.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. treebox-0.5.0/.github/workflows/autofix.yml +69 -0
  2. {treebox-0.3.0 → treebox-0.5.0}/.github/workflows/ci.yml +3 -0
  3. treebox-0.5.0/.pre-commit-config.yaml +25 -0
  4. {treebox-0.3.0 → treebox-0.5.0}/CLAUDE.md +19 -14
  5. treebox-0.5.0/CONTRIBUTING.md +53 -0
  6. {treebox-0.3.0 → treebox-0.5.0}/PKG-INFO +96 -50
  7. {treebox-0.3.0 → treebox-0.5.0}/README.md +93 -49
  8. treebox-0.5.0/assets/treebox-demo.gif +0 -0
  9. treebox-0.5.0/assets/treebox-logo.png +0 -0
  10. {treebox-0.3.0 → treebox-0.5.0}/docs/agents.md +4 -3
  11. treebox-0.5.0/docs/configuration.md +160 -0
  12. {treebox-0.3.0 → treebox-0.5.0}/docs/how-it-works.md +18 -7
  13. {treebox-0.3.0 → treebox-0.5.0}/docs/index.md +64 -11
  14. {treebox-0.3.0 → treebox-0.5.0}/docs/install.md +6 -5
  15. {treebox-0.3.0 → treebox-0.5.0}/docs/javascripts/treebox.js +20 -13
  16. {treebox-0.3.0 → treebox-0.5.0}/docs/stylesheets/extra.css +59 -8
  17. {treebox-0.3.0 → treebox-0.5.0}/docs/usage.md +107 -17
  18. {treebox-0.3.0 → treebox-0.5.0}/install.sh +4 -12
  19. {treebox-0.3.0 → treebox-0.5.0}/pyproject.toml +5 -3
  20. {treebox-0.3.0 → treebox-0.5.0}/scripts/validate.sh +1 -0
  21. {treebox-0.3.0 → treebox-0.5.0}/skills/treebox/SKILL.md +32 -30
  22. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets.py +24 -5
  23. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/cli.py +292 -115
  24. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/config.py +79 -20
  25. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/forge.py +8 -2
  26. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/git.py +68 -25
  27. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/locking.py +2 -1
  28. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/models.py +26 -0
  29. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/output.py +32 -18
  30. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/provision.py +139 -26
  31. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/runners/base.py +10 -2
  32. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/runners/docker.py +118 -33
  33. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/runners/host.py +17 -4
  34. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/state.py +16 -0
  35. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/system.py +2 -1
  36. treebox-0.5.0/tests/test_contract.py +366 -0
  37. {treebox-0.3.0 → treebox-0.5.0}/tests/test_integration.py +747 -8
  38. {treebox-0.3.0 → treebox-0.5.0}/tests/test_units.py +1033 -21
  39. {treebox-0.3.0 → treebox-0.5.0}/uv.lock +93 -0
  40. treebox-0.3.0/CONTRIBUTING.md +0 -31
  41. treebox-0.3.0/docs/assets/treebox-logo.png +0 -0
  42. treebox-0.3.0/docs/configuration.md +0 -88
  43. {treebox-0.3.0 → treebox-0.5.0}/.agents/skills/no-mistakes/SKILL.md +0 -0
  44. {treebox-0.3.0 → treebox-0.5.0}/.github/workflows/claude.yml +0 -0
  45. {treebox-0.3.0 → treebox-0.5.0}/.github/workflows/docs.yml +0 -0
  46. {treebox-0.3.0 → treebox-0.5.0}/.github/workflows/release.yml +0 -0
  47. {treebox-0.3.0 → treebox-0.5.0}/.gitignore +0 -0
  48. {treebox-0.3.0 → treebox-0.5.0}/AGENTS.md +0 -0
  49. {treebox-0.3.0 → treebox-0.5.0}/LICENSE +0 -0
  50. {treebox-0.3.0 → treebox-0.5.0}/ROADMAP.md +0 -0
  51. {treebox-0.3.0 → treebox-0.5.0/docs}/assets/treebox-logo.png +0 -0
  52. {treebox-0.3.0 → treebox-0.5.0}/hooks/copy_page.py +0 -0
  53. {treebox-0.3.0 → treebox-0.5.0}/mkdocs.yml +0 -0
  54. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/__init__.py +0 -0
  55. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets/container/Dockerfile +0 -0
  56. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets/container/allowed-domains.sh +0 -0
  57. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets/container/container.json +0 -0
  58. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets/container/firewall.json +0 -0
  59. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets/container/init-firewall.sh +0 -0
  60. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets/container/post-create.sh +0 -0
  61. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/assets/pre-push +0 -0
  62. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/ecosystems.py +0 -0
  63. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/names.py +0 -0
  64. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/py.typed +0 -0
  65. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/resolve.py +0 -0
  66. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/runners/__init__.py +0 -0
  67. {treebox-0.3.0 → treebox-0.5.0}/src/treebox/status.py +0 -0
  68. {treebox-0.3.0 → treebox-0.5.0}/tests/conftest.py +0 -0
@@ -0,0 +1,69 @@
1
+ name: Autofix
2
+
3
+ # Applies ruff's auto-fixes (`ruff check --fix` + `ruff format`) to pull requests
4
+ # and commits the result back to the PR branch. This is the CI counterpart to the
5
+ # pre-commit hooks: if a change lands unformatted (hooks skipped), the branch is
6
+ # fixed in place instead of just failing the CI gate. Skips fork PRs, where the
7
+ # workflow token cannot push, and its own commits, to avoid loops.
8
+ #
9
+ # Gotcha: pushes made with the default GITHUB_TOKEN (from actions/checkout) do
10
+ # NOT trigger new workflow runs. So the auto-fixed commit this job pushes will
11
+ # not re-run ci.yml. Two implications:
12
+ # - If branch protection REQUIRES the CI status checks, the auto-fixed SHA will
13
+ # have no reported checks and the PR will stall. The remedy is to check out
14
+ # and push using a PAT or GitHub App token stored as a repo secret (e.g.
15
+ # AUTOFIX_TOKEN) instead of GITHUB_TOKEN, so the push re-triggers CI.
16
+ # - To close the "merged with a commit CI never validated" hole regardless,
17
+ # this job re-runs the ruff + mypy checks on the fixed tree below and fails
18
+ # rather than pushing if they don't pass, so the exact pushed commit is
19
+ # validated within this run.
20
+
21
+ on:
22
+ pull_request:
23
+
24
+ concurrency:
25
+ group: autofix-${{ github.ref }}
26
+ cancel-in-progress: true
27
+
28
+ permissions:
29
+ contents: write
30
+
31
+ jobs:
32
+ autofix:
33
+ name: ruff --fix
34
+ runs-on: ubuntu-latest
35
+ if: >-
36
+ github.event.pull_request.head.repo.full_name == github.repository &&
37
+ github.actor != 'github-actions[bot]'
38
+ steps:
39
+ - uses: actions/checkout@v7
40
+ with:
41
+ ref: ${{ github.head_ref }}
42
+
43
+ - name: Install uv
44
+ uses: astral-sh/setup-uv@v8.2.0
45
+ with:
46
+ enable-cache: true
47
+
48
+ - name: ruff check --fix
49
+ run: uv run --extra dev ruff check --fix src tests
50
+
51
+ - name: ruff format
52
+ run: uv run --extra dev ruff format src tests
53
+
54
+ - name: Validate fixed tree
55
+ run: |
56
+ uv run --extra dev ruff check src tests
57
+ uv run --extra dev ruff format --check src tests
58
+ uv run --extra dev mypy
59
+
60
+ - name: Commit fixes
61
+ run: |
62
+ if git diff --quiet; then
63
+ echo "No formatting or lint fixes needed."
64
+ exit 0
65
+ fi
66
+ git config user.name "github-actions[bot]"
67
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
68
+ git commit -am "style: apply ruff --fix and format"
69
+ git push
@@ -29,6 +29,9 @@ jobs:
29
29
  - name: Lint
30
30
  run: uv run -p ${{ matrix.python-version }} --extra dev ruff check src tests
31
31
 
32
+ - name: Format
33
+ run: uv run -p ${{ matrix.python-version }} --extra dev ruff format --check src tests
34
+
32
35
  - name: Type check
33
36
  run: uv run -p ${{ matrix.python-version }} --extra dev mypy
34
37
 
@@ -0,0 +1,25 @@
1
+ # Pre-commit hooks: keep every commit lint-clean, formatted, and type-checked.
2
+ # Install once with `uv run --extra dev pre-commit install` (or `pre-commit install`).
3
+ # Run against everything with `uv run --extra dev pre-commit run --all-files`.
4
+ #
5
+ # Keep the ruff rev in sync with the `ruff>=…` pin in pyproject.toml's [dev] extra.
6
+ repos:
7
+ - repo: https://github.com/astral-sh/ruff-pre-commit
8
+ rev: v0.15.20
9
+ hooks:
10
+ # Lint and auto-fix (import sorting, pyupgrade, simplify, …), then format.
11
+ - id: ruff-check
12
+ args: [--fix]
13
+ - id: ruff-format
14
+
15
+ # mypy runs from the project's own dev environment so it sees typer/questionary
16
+ # stubs and the [tool.mypy] config (files = ["src"]). pass_filenames is off
17
+ # because that config already selects the files to check.
18
+ - repo: local
19
+ hooks:
20
+ - id: mypy
21
+ name: mypy
22
+ entry: uv run --extra dev mypy
23
+ language: system
24
+ types: [python]
25
+ pass_filenames: false
@@ -19,9 +19,11 @@ template it provisions is separately pinned to CPython 3.14.6).
19
19
  uv run treebox ... # run the CLI from the working tree
20
20
  uv run --extra dev python -m pytest # full unit + integration suite
21
21
  uv run --extra dev python -m pytest tests/test_units.py::test_name # single test (or -k <pattern>)
22
- ruff check src tests # lint
23
- uv run --extra dev mypy # type check (config in pyproject.toml)
24
- ./scripts/validate.sh # lint + tests + live host-runner smoke (real uv, throwaway local remote)
22
+ uv run --extra dev ruff check src tests # lint
23
+ uv run --extra dev ruff format --check src tests # format check (drop --check to apply)
24
+ uv run --extra dev mypy # strict type check (config in pyproject.toml)
25
+ uv run --extra dev pre-commit install # install lint/format/strict type-check hooks (see CONTRIBUTING.md)
26
+ ./scripts/validate.sh # lint + format + tests + live host-runner smoke (real uv, throwaway local remote)
25
27
  uv pip install -e ".[dev]" # editable dev environment
26
28
  uv run --extra docs mkdocs serve # docs site (docs/ + mkdocs.yml), live-reloading
27
29
  uv run --extra docs mkdocs build --strict # build docs to site/ (gitignored)
@@ -50,13 +52,13 @@ run (pluggable)**.
50
52
  git just works). `HARNESS_COMMANDS` maps `claude`/`codex` to their
51
53
  fully-autonomous launch argv.
52
54
  - **`cli.py`** (Typer) is the entry point: `create [NAME] / enter <ref> /
53
- list / teardown <ref>... / doctor` (`ls`/`rm` are hidden aliases of
54
- list/teardown). `enter`/`teardown` resolve a ref as
55
+ list / teardown <ref>... / doctor / version` (`ls`/`rm` are hidden aliases
56
+ of list/teardown). `enter`/`teardown` resolve a ref as
55
57
  name → live branch → unique substring (`resolve.py`); ambiguity exits 2.
56
- It enforces **stable exit codes** (`0` ok · `2` usage · `3` not-found · `4`
57
- auth/fetch · `5` conflict) and **`--json`** output carrying a
58
- `schemaVersion` that only gains fields within a version — a breaking reshape
59
- bumps it (git-porcelain discipline). Agents
58
+ It enforces **stable exit codes** (`0` ok · `1` runtime/doctor hard-check ·
59
+ `2` usage · `3` not-found · `4` auth/fetch · `5` conflict) and **`--json`**
60
+ output carrying a `schemaVersion` that only gains fields within a version —
61
+ a breaking reshape bumps it (git-porcelain discipline). Agents
60
62
  branch on these, so don't change their meanings casually.
61
63
  - **`ecosystems.py`** detects package managers (uv, npm, pnpm, go, cargo),
62
64
  drives their cache-backed setup, and defines which manifest files feed the
@@ -71,11 +73,14 @@ run (pluggable)**.
71
73
  with `git branch -m`). `create --checkout feature/auth` derives the name by
72
74
  flattening slashes to `--` (`feature--auth`); generated names come from
73
75
  `names.py`.
74
- - **`config.py`** is **user-level TOML only**, never read from the target repo —
75
- a repo-level config could run arbitrary host commands. Same principle in
76
- **`assets.py`**: the sandbox template is operator-owned and rendered into
77
- a host-side dir *beside* the worktree, never inside the mount, so a boxed agent
78
- cannot edit the config that defines its own sandbox.
76
+ - **`config.py`** is **user-level TOML only** (`$TREEBOX_CONFIG`, else
77
+ `$TREEBOX_HOME/config.toml`, default `~/.treebox/config.toml`), never read
78
+ from the target repo a repo-level config could run arbitrary host commands.
79
+ Same principle in **`assets.py`**: templates resolve from
80
+ `$TREEBOX_TEMPLATE_DIR`, then `$TREEBOX_HOME/templates/<name>` (default
81
+ `~/.treebox/templates/<name>`), and the sandbox template is operator-owned
82
+ and rendered into a host-side dir *beside* the worktree, never inside the
83
+ mount, so a boxed agent cannot edit the config that defines its own sandbox.
79
84
 
80
85
  ## Invariants to preserve
81
86
 
@@ -0,0 +1,53 @@
1
+ # Contributing
2
+
3
+ treebox is a small, one-maintainer project. Contributions are welcome, but the scope is intentionally modest.
4
+
5
+ ## Good contributions
6
+
7
+ - Bug fixes with a clear reproduction.
8
+ - Documentation fixes that make install, usage, or runner behavior clearer.
9
+ - Small compatibility fixes that preserve the existing CLI and scripting behavior.
10
+
11
+ For larger feature ideas, open an issue first so we can decide whether they fit the project.
12
+
13
+ ## Local checks
14
+
15
+ Install the pre-commit hooks once so every commit is auto-formatted, lint-fixed,
16
+ and type-checked in strict mode (`ruff format`, `ruff check --fix`, and `mypy`):
17
+
18
+ ```bash
19
+ uv run --extra dev pre-commit install
20
+ ```
21
+
22
+ Note: `pre-commit install` has no effect inside a treebox worktree, because
23
+ treebox sets `core.hooksPath` for its own pre-push guard, so git ignores the
24
+ hook pre-commit writes to `.git/hooks`. Run the hooks manually there with
25
+ `uv run --extra dev pre-commit run --all-files`.
26
+
27
+ If the hooks are skipped, the Autofix CI workflow applies the same `ruff --fix`
28
+ and `ruff format` to same-repo PRs and pushes the result back. Note that pushes
29
+ made with the default `GITHUB_TOKEN` do not re-trigger CI, so if this repo ever
30
+ requires the CI status checks under branch protection, the auto-fixed commit
31
+ will have no reported checks and the PR will stall; the remedy is to push from
32
+ the workflow using a PAT or GitHub App token stored as a repo secret (e.g.
33
+ `AUTOFIX_TOKEN`) instead of `GITHUB_TOKEN`.
34
+
35
+ For code changes, run the relevant checks before opening a PR. Mypy runs in
36
+ strict mode (configured in `pyproject.toml`):
37
+
38
+ ```bash
39
+ uv run --extra dev python -m pytest
40
+ uv run --extra dev ruff check src tests
41
+ uv run --extra dev ruff format --check src tests
42
+ uv run --extra dev mypy
43
+ ```
44
+
45
+ For docs-only changes, this is enough:
46
+
47
+ ```bash
48
+ uv run --extra docs mkdocs build --strict
49
+ ```
50
+
51
+ ## Boundaries
52
+
53
+ Please preserve the basics: fresh refs by default, no trust in target-repo sandbox config, stable CLI output for scripts, and subscription-based agent auth.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: treebox
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Isolated, ready-to-run git worktrees for AI coding agents — host-native or docker-sandboxed.
5
5
  Project-URL: Homepage, https://github.com/Seth-Peters/treebox
6
6
  Project-URL: Documentation, https://seth-peters.github.io/treebox/
@@ -23,9 +23,11 @@ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Software Development :: Version Control :: Git
24
24
  Requires-Python: >=3.11
25
25
  Requires-Dist: questionary<3,>=2
26
+ Requires-Dist: rich<16,>=13
26
27
  Requires-Dist: typer<1.0,>=0.12
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: mypy>=1.14; extra == 'dev'
30
+ Requires-Dist: pre-commit>=4; extra == 'dev'
29
31
  Requires-Dist: pytest-cov>=6; extra == 'dev'
30
32
  Requires-Dist: pytest>=8; extra == 'dev'
31
33
  Requires-Dist: ruff>=0.15; extra == 'dev'
@@ -34,13 +36,13 @@ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
34
36
  Description-Content-Type: text/markdown
35
37
 
36
38
  <p align="center">
37
- <img src="assets/treebox-logo.png" alt="treebox" width="220">
39
+ <img src="https://raw.githubusercontent.com/Seth-Peters/treebox/main/assets/treebox-logo.png" alt="treebox logo: a small tree growing inside a glass box" width="300">
38
40
  </p>
39
41
 
40
- <h1 align="center">treebox</h1>
42
+ <h1 align="center">Spin up worktrees, put them in a box,<br>tear them down - treebox</h1>
41
43
 
42
44
  <p align="center">
43
- <strong>Isolated, ready-to-run git worktrees for AI coding agents.</strong>
45
+ <strong>Isolated, ready-to-run git worktrees for coding agents.</strong>
44
46
  </p>
45
47
 
46
48
  <p align="center">
@@ -59,17 +61,19 @@ Description-Content-Type: text/markdown
59
61
  <a href="https://www.linkedin.com/in/seth-peters/">Seth Peters</a>
60
62
  </p>
61
63
 
64
+ <p align="center">
65
+ <img src="https://raw.githubusercontent.com/Seth-Peters/treebox/main/assets/treebox-demo.gif" alt="treebox lifecycle demo: create, enter, ls, and an interactive teardown" width="900">
66
+ </p>
67
+
62
68
  ---
63
69
 
64
- Run `treebox create` and it fetches, cuts a worktree from a fresh
65
- `origin/<base>`, copies your `.env`, installs dependencies from a shared
70
+ `treebox` hands coding agents isolated, ready-to-run git worktrees one
71
+ directory per worktree name. `treebox create` fetches, cuts a worktree from a
72
+ fresh `origin/<base>`, copies your `.env`, installs dependencies from a shared
66
73
  cache, and launches `claude` or `codex` inside. No branch name needed up
67
- front: the worktree gets a stable name (yours, or a generated petname) and an
68
- un-pushable `treebox/<name>` placeholder branch the agent renames when the
69
- work takes shape. Agents work the same repo in parallel without collisions —
70
- on a laptop or over plain SSH.
71
-
72
- Built by [Seth Peters](https://www.linkedin.com/in/seth-peters/) as a small, operator-focused layer for AI agent infrastructure: git worktrees, sandbox boundaries, subscription auth, and repeatable developer environments. If that is the kind of problem you are solving, the [docs](https://seth-peters.github.io/treebox/) go deeper on the design tradeoffs — and I'm [on LinkedIn](https://www.linkedin.com/in/seth-peters/) if you want to compare notes.
74
+ front: the worktree gets a stable name and an un-pushable `treebox/<name>`
75
+ placeholder branch the agent renames when the work takes shape. Agents work the same repo
76
+ in parallel without collisions on a laptop or over plain SSH.
73
77
 
74
78
  Provisioning is identical everywhere; a pluggable **isolation mode** decides
75
79
  where the agent runs:
@@ -79,26 +83,6 @@ where the agent runs:
79
83
  | `host` (default) | none | the worktree shell |
80
84
  | `docker` | sandboxed | a docker container, with your `.env` + caches mounted |
81
85
 
82
- ## Every agent ships its own cage
83
-
84
- Every coding agent invents its own answer to "run me in parallel" and "don't
85
- let me touch the wrong thing" — a different config file, a different schema, a
86
- different word for the same idea:
87
-
88
- | Agent | Sandbox / permission config | Built-in worktrees | Config lives in |
89
- | ----- | --------------------------- | ------------------ | --------------- |
90
- | **Claude Code** | `permissions` allow/ask/deny **+** native OS sandbox (Seatbelt/bubblewrap) **+** dev container | Yes (`--worktree`) | `.claude/settings.json`, `.devcontainer/` |
91
- | **OpenAI Codex** | `sandbox_mode` × `approval_policy` (Seatbelt / Landlock+seccomp) | No in CLI (app only) | `~/.codex/config.toml`, `[profiles.*]` |
92
- | **opencode** | `permission` per-tool allow/ask/deny (no OS sandbox) | No (community plugins) | `opencode.json` |
93
- | **pi** | none built-in ("all permissions by default"); BYO Docker/VM + trust prompt | No in core (`pi-subagents`) | `~/.pi/agent/settings.json` |
94
-
95
- Learn one and it teaches you nothing about the next, and none of it ports
96
- across tools. treebox owns the *isolation* layer instead — **one
97
- worktree-per-branch layout, one operator-owned sandbox, one config file** — and
98
- launches your agent of choice inside it. Learn treebox once; swap the agent,
99
- keep the box. Full comparison with citations:
100
- [Agents & sandboxing](https://seth-peters.github.io/treebox/agents/).
101
-
102
86
  ## Install
103
87
 
104
88
  ```bash
@@ -110,7 +94,7 @@ instructions if uv is missing — it never installs a package manager behind
110
94
  your back. Or install directly:
111
95
 
112
96
  ```bash
113
- uv tool install git+https://github.com/Seth-Peters/treebox
97
+ uv tool install treebox
114
98
  ```
115
99
 
116
100
  Host isolation needs only `git` and a logged-in agent CLI (`claude` /
@@ -121,8 +105,7 @@ requirements and installer overrides.
121
105
 
122
106
  ## Usage
123
107
 
124
- The whole surface is five commands, and a worktree's life runs through all of
125
- them.
108
+ The worktree lifecycle is five commands.
126
109
 
127
110
  **Check the host.** `doctor` verifies exactly what `create` will need — git,
128
111
  agent logins, `.env`, credentials for the required fetch — and prints the fix
@@ -149,7 +132,7 @@ the branch starts as a `treebox/<name>` placeholder that a per-worktree
149
132
  pre-push guard keeps **un-pushable** — rename it conventionally
150
133
  (`git branch -m feature/user-auth`, `fix/login-race`, `chore/bump-deps`, …)
151
134
  when the work has a shape, then push. So a machine-generated name can never
152
- become a PR title. `-b` is the one path that skips the placeholder: it checks out an
135
+ become a PR title. `--checkout` is the one path that skips the placeholder: it checks out an
153
136
  existing branch exactly.
154
137
 
155
138
  `--base` takes any branch, not just `main` — branch off `dev`, or stack a new
@@ -157,10 +140,11 @@ worktree on top of an existing PR's branch, even while that branch is checked
157
140
  out in another worktree. It resolves as the freshly fetched `origin/<base>`,
158
141
  so push the base first if its latest commits only exist locally.
159
142
 
160
- **Enter.** Come back to an existing worktree, picking the agent per entry.
161
- The ref is the name, the *current* branch (renames are followed live), or a
162
- unique substring of either. Dependencies re-sync only if the lockfile changed
163
- since last time:
143
+ **Enter.** Come back to an existing worktree. By default it reuses the harness
144
+ the worktree was created with; an explicit `--harness` overrides it for that
145
+ session only, without changing what's recorded on disk. The ref is the name,
146
+ the *current* branch (renames are followed live), or a unique substring of
147
+ either. Dependencies re-sync only if the lockfile changed since last time:
164
148
 
165
149
  ```bash
166
150
  treebox enter fix-auth --harness claude
@@ -186,12 +170,32 @@ treebox teardown fix-auth brave-otter --delete-branch
186
170
  treebox teardown # pick interactively
187
171
  ```
188
172
 
189
- treebox is built to be scripted, including by agents: every command takes
190
- `--json` (data to stdout, diagnostics to stderr, a schema that only gains
191
- fields within a version), `--dry-run` prints the exact commands without running them, and exit
192
- codes are stable (`0` ok · `2` usage · `3` not found · `4` auth · `5`
193
- conflict). Full reference in the
194
- [usage guide](https://seth-peters.github.io/treebox/usage/).
173
+ treebox is built to be scripted, including by agents: the worktree commands and
174
+ `doctor` take `--json` (data to stdout, diagnostics to stderr, a schema that
175
+ only gains fields within a version), `--dry-run` prints the exact commands
176
+ without running them, and exit codes are stable (`0` ok · `1` runtime/doctor
177
+ blocked · `2` usage · `3` not found · `4` auth · `5` conflict). Full
178
+ reference in the [usage guide](https://seth-peters.github.io/treebox/usage/).
179
+
180
+ ## Every agent ships its own cage
181
+
182
+ Every coding agent invents its own answer to "run me in parallel" and "don't
183
+ let me touch the wrong thing" — a different config file, a different schema, a
184
+ different word for the same idea:
185
+
186
+ | Agent | Sandbox / permission config | Built-in worktrees | Config lives in |
187
+ | ----- | --------------------------- | ------------------ | --------------- |
188
+ | **Claude Code** | `permissions` allow/ask/deny **+** native OS sandbox (Seatbelt/bubblewrap) **+** dev container | Yes (`--worktree`) | `.claude/settings.json`, `.devcontainer/` |
189
+ | **OpenAI Codex** | `sandbox_mode` × `approval_policy` (Seatbelt / Landlock+seccomp) | No in CLI (app only) | `~/.codex/config.toml`, `[profiles.*]` |
190
+ | **opencode** | `permission` per-tool allow/ask/deny (no OS sandbox) | No (community plugins) | `opencode.json` |
191
+ | **pi** | none built-in ("all permissions by default"); BYO Docker/VM + trust prompt | No in core (`pi-subagents`) | `~/.pi/agent/settings.json` |
192
+
193
+ Learn one and it teaches you nothing about the next, and none of it ports
194
+ across tools. treebox owns the *isolation* layer instead — **one
195
+ named-worktree layout, one operator-owned sandbox, one config file** — and
196
+ launches your agent of choice inside it. Learn treebox once; swap the agent,
197
+ keep the box. Full comparison with citations:
198
+ [Agents & sandboxing](https://seth-peters.github.io/treebox/agents/).
195
199
 
196
200
  ## Design
197
201
 
@@ -207,14 +211,15 @@ conflict). Full reference in the
207
211
  hooks are ignored.
208
212
  - **Credentials go in as scoped copies.** Only the agents' login files are
209
213
  copied into a throwaway per-worktree dir — never the live `~/.claude` /
210
- `~/.codex` — and treebox uses your subscription login, never
211
- `ANTHROPIC_API_KEY`.
214
+ `~/.codex` — refreshed on every entry so a host logout or a fresh login
215
+ reaches the sandbox next time; treebox uses your subscription login.
212
216
 
213
217
  More in [how it works](https://seth-peters.github.io/treebox/how-it-works/).
214
218
 
215
219
  ## Configuration
216
220
 
217
- Optional, and user-level only (`~/.config/treebox/config.toml`) — treebox
221
+ Optional, and user-level only (`$TREEBOX_CONFIG`, else
222
+ `$TREEBOX_HOME/config.toml`, default `~/.treebox/config.toml`) — treebox
218
223
  never reads config from the target repo:
219
224
 
220
225
  ```toml
@@ -227,13 +232,54 @@ All keys, shared-cache overrides, setup hooks, and sandbox templates are
227
232
  covered in the
228
233
  [configuration guide](https://seth-peters.github.io/treebox/configuration/).
229
234
 
235
+ ## Customizing isolation
236
+
237
+ `docker` isolation builds from a **template you own** — a directory of files
238
+ treebox ships and you edit. treebox renders it beside the worktree so a boxed
239
+ agent can't touch its own cage. Three steps:
240
+
241
+ **1. Copy the template out** — you edit a copy, never the shipped one. Name it
242
+ (here `node`); a `node` box and a `python` box can coexist, one per stack:
243
+
244
+ ```bash
245
+ mkdir -p ~/.treebox/templates
246
+ cp -R "$(python -c 'import treebox.assets, pathlib; print(pathlib.Path(treebox.assets.__file__).parent)')/container" \
247
+ ~/.treebox/templates/node
248
+ ```
249
+
250
+ **2. Edit two files.** The shipped image already bundles Node 22, uv, `gh`,
251
+ ripgrep, and the agent CLIs, so most projects touch only these:
252
+
253
+ ```dockerfile
254
+ # ~/.treebox/templates/node/Dockerfile — global tooling
255
+ USER root
256
+ RUN npm install -g pnpm@9 typescript tsx
257
+ USER ${USERNAME}
258
+ ```
259
+
260
+ ```json
261
+ // ~/.treebox/templates/node/container.json — your install command
262
+ "postCreate": "if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; elif [ -f package-lock.json ]; then npm ci; else npm install; fi"
263
+ ```
264
+
265
+ (In docker, setup runs `postCreate`, not the host-mode ecosystem auto-detect,
266
+ so a non-Python project wires its install here.)
267
+
268
+ **3. Point treebox at it** — per run with `--isolation docker --template node`,
269
+ or set `template = "node"` in `~/.treebox/config.toml`.
270
+
271
+ Full walkthrough — every file in a template and the `container.json` schema — in
272
+ the [configuration guide](https://seth-peters.github.io/treebox/configuration/#customizing-the-sandbox).
273
+
230
274
  ## Development
231
275
 
232
276
  ```bash
233
277
  git clone https://github.com/Seth-Peters/treebox && cd treebox
234
278
  uv run treebox ... # run the CLI from the working tree
279
+ uv run --extra dev pre-commit install # lint/format/strict type-check hooks (see CONTRIBUTING.md)
235
280
  uv run --extra dev python -m pytest # unit + integration suite
236
- ./scripts/validate.sh # lint + tests + live host-runner smoke
281
+ uv run --extra dev mypy # strict type check
282
+ ./scripts/validate.sh # lint + format + tests + live host-runner smoke
237
283
  ```
238
284
 
239
285
  ## Contributing