treebox 0.1.0__tar.gz → 0.2.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.
- treebox-0.2.0/.github/workflows/auto-release.yml +155 -0
- {treebox-0.1.0 → treebox-0.2.0}/.github/workflows/release.yml +16 -19
- {treebox-0.1.0 → treebox-0.2.0}/.gitignore +1 -0
- {treebox-0.1.0 → treebox-0.2.0}/CLAUDE.md +7 -5
- {treebox-0.1.0 → treebox-0.2.0}/PKG-INFO +19 -14
- {treebox-0.1.0 → treebox-0.2.0}/README.md +17 -13
- {treebox-0.1.0 → treebox-0.2.0}/docs/agents.md +3 -3
- {treebox-0.1.0 → treebox-0.2.0}/docs/configuration.md +7 -7
- {treebox-0.1.0 → treebox-0.2.0}/docs/how-it-works.md +8 -7
- {treebox-0.1.0 → treebox-0.2.0}/docs/index.md +10 -10
- {treebox-0.1.0 → treebox-0.2.0}/docs/install.md +8 -8
- {treebox-0.1.0 → treebox-0.2.0}/docs/usage.md +88 -21
- {treebox-0.1.0 → treebox-0.2.0}/pyproject.toml +1 -1
- {treebox-0.1.0 → treebox-0.2.0}/scripts/validate.sh +1 -1
- {treebox-0.1.0 → treebox-0.2.0}/skills/treebox/SKILL.md +18 -18
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/cli.py +542 -173
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/config.py +14 -13
- treebox-0.2.0/src/treebox/forge.py +195 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/git.py +93 -1
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/models.py +2 -2
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/output.py +5 -5
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/provision.py +37 -15
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/runners/__init__.py +3 -3
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/runners/base.py +3 -3
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/runners/docker.py +14 -14
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/runners/host.py +8 -8
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/state.py +12 -4
- treebox-0.2.0/src/treebox/status.py +86 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/system.py +4 -4
- {treebox-0.1.0 → treebox-0.2.0}/tests/test_integration.py +354 -20
- {treebox-0.1.0 → treebox-0.2.0}/tests/test_units.py +372 -47
- {treebox-0.1.0 → treebox-0.2.0}/uv.lock +35 -0
- treebox-0.1.0/.ai/docs/brainstorms/2026-07-02-branchless-create-brainstorm.md +0 -132
- {treebox-0.1.0 → treebox-0.2.0}/.agents/skills/no-mistakes/SKILL.md +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/.github/workflows/ci.yml +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/.github/workflows/claude.yml +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/.github/workflows/docs.yml +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/AGENTS.md +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/CONTRIBUTING.md +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/LICENSE +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/ROADMAP.md +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/assets/treebox-logo.png +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/docs/assets/treebox-logo.png +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/docs/javascripts/treebox.js +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/docs/stylesheets/extra.css +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/install.sh +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/mkdocs.yml +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/__init__.py +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets/container/Dockerfile +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets/container/allowed-domains.sh +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets/container/container.json +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets/container/firewall.json +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets/container/init-firewall.sh +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets/container/post-create.sh +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets/pre-push +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/assets.py +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/ecosystems.py +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/locking.py +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/names.py +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/py.typed +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/src/treebox/resolve.py +0 -0
- {treebox-0.1.0 → treebox-0.2.0}/tests/conftest.py +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
name: Auto release
|
|
2
|
+
|
|
3
|
+
# After CI succeeds on a push to main: read the Conventional Commit subjects
|
|
4
|
+
# since the last `v*` tag, derive the semver bump (`!`/BREAKING CHANGE ->
|
|
5
|
+
# major, feat -> minor, fix/perf -> patch), push the new `vX.Y.Z` tag, publish
|
|
6
|
+
# to PyPI, and create a GitHub Release. Merges with no release-worthy commits
|
|
7
|
+
# (docs, chore, refactor, ...) are a no-op — no tag, no upload. A red CI run
|
|
8
|
+
# never releases: the tag job only fires via workflow_run when CI concluded
|
|
9
|
+
# successfully, and it tags the exact commit CI validated (head_sha), not
|
|
10
|
+
# whatever main points at by then.
|
|
11
|
+
#
|
|
12
|
+
# While the major version is 0, breaking changes bump the minor instead
|
|
13
|
+
# (0.4.x -> 0.5.0), matching cargo/semantic-release conventions.
|
|
14
|
+
#
|
|
15
|
+
# Publishing uses PyPI Trusted Publishing (OIDC) like release.yml — no API
|
|
16
|
+
# tokens. One-time setup on https://pypi.org:
|
|
17
|
+
# Account -> Publishing -> add a "pending" trusted publisher with:
|
|
18
|
+
# owner = Seth-Peters, repo = treebox,
|
|
19
|
+
# workflow = auto-release.yml, environment = pypi
|
|
20
|
+
#
|
|
21
|
+
# The tag is pushed with the workflow's GITHUB_TOKEN, which GitHub
|
|
22
|
+
# deliberately does not let trigger other workflows — so release.yml does NOT
|
|
23
|
+
# fire and nothing publishes twice. release.yml stays as the manual /
|
|
24
|
+
# TestPyPI path.
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
workflow_run:
|
|
28
|
+
workflows: [CI]
|
|
29
|
+
types: [completed]
|
|
30
|
+
branches:
|
|
31
|
+
- main
|
|
32
|
+
|
|
33
|
+
# Serialize runs so back-to-back merges tag sequentially instead of racing.
|
|
34
|
+
concurrency:
|
|
35
|
+
group: auto-release
|
|
36
|
+
cancel-in-progress: false
|
|
37
|
+
|
|
38
|
+
jobs:
|
|
39
|
+
tag:
|
|
40
|
+
name: Compute version bump
|
|
41
|
+
# Only a green CI run on a push to main releases; the branches filter above
|
|
42
|
+
# matches head_branch, so the event guard keeps PR runs from a branch that
|
|
43
|
+
# happens to be named main from slipping through.
|
|
44
|
+
if: >-
|
|
45
|
+
github.event.workflow_run.conclusion == 'success' &&
|
|
46
|
+
github.event.workflow_run.event == 'push'
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
permissions:
|
|
49
|
+
contents: write # push the version tag
|
|
50
|
+
outputs:
|
|
51
|
+
released: ${{ steps.bump.outputs.released }}
|
|
52
|
+
tag: ${{ steps.bump.outputs.tag }}
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v7
|
|
55
|
+
with:
|
|
56
|
+
ref: ${{ github.event.workflow_run.head_sha }} # the commit CI validated
|
|
57
|
+
fetch-depth: 0 # full history + tags to find the last release and scan commits
|
|
58
|
+
|
|
59
|
+
- name: Derive next version from Conventional Commits
|
|
60
|
+
id: bump
|
|
61
|
+
run: |
|
|
62
|
+
last=$(git describe --tags --abbrev=0 --match 'v[0-9]*')
|
|
63
|
+
echo "last release: $last"
|
|
64
|
+
|
|
65
|
+
rank=0 # 0 none · 1 patch · 2 minor · 3 major
|
|
66
|
+
for sha in $(git rev-list "$last"..HEAD); do
|
|
67
|
+
subject=$(git log -1 --format=%s "$sha")
|
|
68
|
+
body=$(git log -1 --format=%b "$sha")
|
|
69
|
+
if [[ "$subject" =~ ^[a-z]+(\(.+\))?!: ]] || grep -q '^BREAKING CHANGE' <<<"$body"; then
|
|
70
|
+
rank=3
|
|
71
|
+
elif [[ "$subject" =~ ^feat(\(.+\))?: ]]; then
|
|
72
|
+
((rank < 2)) && rank=2
|
|
73
|
+
elif [[ "$subject" =~ ^(fix|perf)(\(.+\))?: ]]; then
|
|
74
|
+
((rank < 1)) && rank=1
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
ver=${last#v}
|
|
79
|
+
IFS=. read -r maj min pat <<<"$ver"
|
|
80
|
+
case $rank in
|
|
81
|
+
3) if ((maj == 0)); then new="0.$((min + 1)).0"; else new="$((maj + 1)).0.0"; fi ;;
|
|
82
|
+
2) new="$maj.$((min + 1)).0" ;;
|
|
83
|
+
1) new="$maj.$min.$((pat + 1))" ;;
|
|
84
|
+
0)
|
|
85
|
+
echo "no release-worthy commits since $last — skipping release"
|
|
86
|
+
echo "released=false" >>"$GITHUB_OUTPUT"
|
|
87
|
+
exit 0
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
|
|
91
|
+
echo "tagging v$new"
|
|
92
|
+
git tag "v$new"
|
|
93
|
+
git push origin "v$new"
|
|
94
|
+
echo "released=true" >>"$GITHUB_OUTPUT"
|
|
95
|
+
echo "tag=v$new" >>"$GITHUB_OUTPUT"
|
|
96
|
+
|
|
97
|
+
build:
|
|
98
|
+
name: Build distributions
|
|
99
|
+
needs: tag
|
|
100
|
+
if: needs.tag.outputs.released == 'true'
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/checkout@v7
|
|
104
|
+
with:
|
|
105
|
+
ref: ${{ needs.tag.outputs.tag }}
|
|
106
|
+
fetch-depth: 0 # full history + tags so hatch-vcs can derive the version
|
|
107
|
+
|
|
108
|
+
- name: Install uv
|
|
109
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
110
|
+
|
|
111
|
+
- name: Build sdist + wheel
|
|
112
|
+
run: uv build
|
|
113
|
+
|
|
114
|
+
- name: Show version being published
|
|
115
|
+
run: ls -1 dist/
|
|
116
|
+
|
|
117
|
+
- uses: actions/upload-artifact@v7
|
|
118
|
+
with:
|
|
119
|
+
name: dist
|
|
120
|
+
path: dist/
|
|
121
|
+
|
|
122
|
+
publish-pypi:
|
|
123
|
+
name: Publish to PyPI
|
|
124
|
+
needs: build
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
environment: pypi
|
|
127
|
+
permissions:
|
|
128
|
+
id-token: write # required for trusted publishing (OIDC)
|
|
129
|
+
steps:
|
|
130
|
+
- uses: actions/download-artifact@v8
|
|
131
|
+
with:
|
|
132
|
+
name: dist
|
|
133
|
+
path: dist/
|
|
134
|
+
|
|
135
|
+
- name: Publish
|
|
136
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
137
|
+
|
|
138
|
+
github-release:
|
|
139
|
+
name: GitHub Release
|
|
140
|
+
needs: [tag, publish-pypi]
|
|
141
|
+
runs-on: ubuntu-latest
|
|
142
|
+
permissions:
|
|
143
|
+
contents: write
|
|
144
|
+
steps:
|
|
145
|
+
- uses: actions/download-artifact@v8
|
|
146
|
+
with:
|
|
147
|
+
name: dist
|
|
148
|
+
path: dist/
|
|
149
|
+
|
|
150
|
+
- name: Create release
|
|
151
|
+
uses: softprops/action-gh-release@v3
|
|
152
|
+
with:
|
|
153
|
+
tag_name: ${{ needs.tag.outputs.tag }}
|
|
154
|
+
generate_release_notes: true
|
|
155
|
+
files: dist/*
|
|
@@ -2,17 +2,14 @@ name: Release
|
|
|
2
2
|
|
|
3
3
|
# Build and publish treebox.
|
|
4
4
|
#
|
|
5
|
-
# * Push a tag like `v0.4.0`
|
|
6
|
-
#
|
|
7
|
-
# * Manually run via "Run workflow" -> build current commit as a dev version and
|
|
8
|
-
# publish to TestPyPI only.
|
|
5
|
+
# * Push a tag like `v0.4.0` -> clean release version (0.4.0), publish to PyPI + GitHub Release.
|
|
6
|
+
# * Manually run via "Run workflow" -> builds the current commit as a dev version, publishes to TestPyPI.
|
|
9
7
|
#
|
|
10
8
|
# Publishing uses PyPI Trusted Publishing (OIDC) — there are NO API tokens or
|
|
11
|
-
# secrets to manage. One-time setup:
|
|
12
|
-
#
|
|
13
|
-
# owner = Seth-Peters, repo = treebox, workflow = release.yml, environment = testpypi
|
|
14
|
-
# pypi.org project -> Publishing -> GitHub publisher:
|
|
9
|
+
# secrets to manage. One-time setup, once per index:
|
|
10
|
+
# https://pypi.org -> Account -> Publishing -> add a "pending" trusted publisher:
|
|
15
11
|
# owner = Seth-Peters, repo = treebox, workflow = release.yml, environment = pypi
|
|
12
|
+
# https://test.pypi.org -> same, with environment = testpypi
|
|
16
13
|
|
|
17
14
|
on:
|
|
18
15
|
push:
|
|
@@ -43,11 +40,12 @@ jobs:
|
|
|
43
40
|
name: dist
|
|
44
41
|
path: dist/
|
|
45
42
|
|
|
46
|
-
publish-
|
|
47
|
-
name: Publish to
|
|
43
|
+
publish-pypi:
|
|
44
|
+
name: Publish to PyPI
|
|
48
45
|
needs: build
|
|
46
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
49
47
|
runs-on: ubuntu-latest
|
|
50
|
-
environment:
|
|
48
|
+
environment: pypi
|
|
51
49
|
permissions:
|
|
52
50
|
id-token: write # required for trusted publishing (OIDC)
|
|
53
51
|
steps:
|
|
@@ -58,15 +56,13 @@ jobs:
|
|
|
58
56
|
|
|
59
57
|
- name: Publish
|
|
60
58
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
61
|
-
with:
|
|
62
|
-
repository-url: https://test.pypi.org/legacy/
|
|
63
59
|
|
|
64
|
-
publish-
|
|
65
|
-
name: Publish to
|
|
60
|
+
publish-testpypi:
|
|
61
|
+
name: Publish dev build to TestPyPI
|
|
66
62
|
needs: build
|
|
67
|
-
if:
|
|
63
|
+
if: github.event_name == 'workflow_dispatch'
|
|
68
64
|
runs-on: ubuntu-latest
|
|
69
|
-
environment:
|
|
65
|
+
environment: testpypi
|
|
70
66
|
permissions:
|
|
71
67
|
id-token: write # required for trusted publishing (OIDC)
|
|
72
68
|
steps:
|
|
@@ -77,11 +73,12 @@ jobs:
|
|
|
77
73
|
|
|
78
74
|
- name: Publish
|
|
79
75
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
76
|
+
with:
|
|
77
|
+
repository-url: https://test.pypi.org/legacy/
|
|
80
78
|
|
|
81
79
|
github-release:
|
|
82
80
|
name: GitHub Release
|
|
83
|
-
needs:
|
|
84
|
-
if: startsWith(github.ref, 'refs/tags/v')
|
|
81
|
+
needs: publish-pypi
|
|
85
82
|
runs-on: ubuntu-latest
|
|
86
83
|
permissions:
|
|
87
84
|
contents: write
|
|
@@ -41,20 +41,22 @@ run (pluggable)**.
|
|
|
41
41
|
record lockfile hash → hand to runner`. Every branch treebox creates is a
|
|
42
42
|
`treebox/<name>` placeholder made un-pushable by a per-worktree pre-push
|
|
43
43
|
hook (`extensions.worktreeConfig` + `core.hooksPath` into the private git
|
|
44
|
-
dir); `create
|
|
44
|
+
dir); `create --checkout <existing-branch>` is the only path that skips it.
|
|
45
45
|
- **`runners/base.py`** defines the `Runner` protocol — the *only* thing that
|
|
46
46
|
differs between modes — implemented by **`runners/host.py`** (setup + agent in
|
|
47
47
|
the worktree shell) and **`runners/docker.py`** (plain `docker build/run`,
|
|
48
48
|
setup via a baked-in `post-create.sh`, agent via `docker exec`; the worktree
|
|
49
49
|
and its git common dir are bind-mounted at their host paths so in-container
|
|
50
|
-
git just works). `
|
|
50
|
+
git just works). `HARNESS_COMMANDS` maps `claude`/`codex` to their
|
|
51
51
|
fully-autonomous launch argv.
|
|
52
52
|
- **`cli.py`** (Typer) is the entry point: `create [NAME] / enter <ref> /
|
|
53
|
-
list / teardown <ref>... / doctor
|
|
53
|
+
list / teardown <ref>... / doctor` (`ls`/`rm` are hidden aliases of
|
|
54
|
+
list/teardown). `enter`/`teardown` resolve a ref as
|
|
54
55
|
name → live branch → unique substring (`resolve.py`); ambiguity exits 2.
|
|
55
56
|
It enforces **stable exit codes** (`0` ok · `2` usage · `3` not-found · `4`
|
|
56
57
|
auth/fetch · `5` conflict) and **`--json`** output carrying a
|
|
57
|
-
`schemaVersion` that only gains fields
|
|
58
|
+
`schemaVersion` that only gains fields within a version — a breaking reshape
|
|
59
|
+
bumps it (git-porcelain discipline). Agents
|
|
58
60
|
branch on these, so don't change their meanings casually.
|
|
59
61
|
- **`ecosystems.py`** detects package managers (uv, npm, pnpm, go, cargo),
|
|
60
62
|
drives their cache-backed setup, and defines which manifest files feed the
|
|
@@ -66,7 +68,7 @@ run (pluggable)**.
|
|
|
66
68
|
- **`models.py`** holds the `Worktree` value object and the name-as-identity
|
|
67
69
|
rule: the *name* is the directory leaf and lock key, never renamed; the
|
|
68
70
|
*branch* is a mutable attribute read live from git (the agent renames it
|
|
69
|
-
with `git branch -m`). `create
|
|
71
|
+
with `git branch -m`). `create --checkout feature/auth` derives the name by
|
|
70
72
|
flattening slashes to `--` (`feature--auth`); generated names come from
|
|
71
73
|
`names.py`.
|
|
72
74
|
- **`config.py`** is **user-level TOML only**, never read from the target repo —
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: treebox
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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/
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
23
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: questionary<3,>=2
|
|
25
26
|
Requires-Dist: typer<1.0,>=0.12
|
|
26
27
|
Provides-Extra: dev
|
|
27
28
|
Requires-Dist: mypy>=1.14; extra == 'dev'
|
|
@@ -69,10 +70,10 @@ on a laptop or over plain SSH.
|
|
|
69
70
|
|
|
70
71
|
Built by [Seth Peters](https://github.com/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.
|
|
71
72
|
|
|
72
|
-
Provisioning is identical everywhere; a pluggable **
|
|
73
|
-
agent runs:
|
|
73
|
+
Provisioning is identical everywhere; a pluggable **isolation mode** decides
|
|
74
|
+
where the agent runs:
|
|
74
75
|
|
|
75
|
-
|
|
|
76
|
+
| `--isolation` | Sandbox | Agent runs in |
|
|
76
77
|
| ---------------- | --------- | ----------------------------------------------------- |
|
|
77
78
|
| `host` (default) | none | the worktree shell |
|
|
78
79
|
| `docker` | sandboxed | a docker container, with your `.env` + caches mounted |
|
|
@@ -111,8 +112,8 @@ your back. Or install directly:
|
|
|
111
112
|
uv tool install git+https://github.com/Seth-Peters/treebox
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
`codex`);
|
|
115
|
+
Host isolation needs only `git` and a logged-in agent CLI (`claude` /
|
|
116
|
+
`codex`); docker isolation additionally needs just `docker` — no Node.js, no
|
|
116
117
|
extra CLIs. See the
|
|
117
118
|
[install guide](https://seth-peters.github.io/treebox/install/) for
|
|
118
119
|
requirements and installer overrides.
|
|
@@ -137,8 +138,8 @@ launches the agent:
|
|
|
137
138
|
```bash
|
|
138
139
|
treebox create # generated name (brave-otter), host-native
|
|
139
140
|
treebox create fix-auth # named up front
|
|
140
|
-
treebox create fix-auth --
|
|
141
|
-
treebox create
|
|
141
|
+
treebox create fix-auth --isolation docker # sandboxed
|
|
142
|
+
treebox create --checkout feature/auth # exact existing branch (resume, PR review)
|
|
142
143
|
treebox create auth-fixes --base feature/auth # stack on any base branch
|
|
143
144
|
```
|
|
144
145
|
|
|
@@ -161,8 +162,8 @@ unique substring of either. Dependencies re-sync only if the lockfile changed
|
|
|
161
162
|
since last time:
|
|
162
163
|
|
|
163
164
|
```bash
|
|
164
|
-
treebox enter fix-auth --
|
|
165
|
-
treebox enter fix-auth --
|
|
165
|
+
treebox enter fix-auth --harness claude
|
|
166
|
+
treebox enter fix-auth --harness codex -- --resume # args after -- go to the agent
|
|
166
167
|
```
|
|
167
168
|
|
|
168
169
|
**List.** See what exists, what each worktree was last doing, and what has
|
|
@@ -174,15 +175,19 @@ treebox list
|
|
|
174
175
|
```
|
|
175
176
|
|
|
176
177
|
**Tear down.** Remove one or more worktrees — and, when you're done, their
|
|
177
|
-
branches. Refuses to delete uncommitted work unless forced
|
|
178
|
+
branches. Refuses to delete uncommitted work unless forced. Run it with no
|
|
179
|
+
refs and treebox walks you through an arrow-key picker, each worktree
|
|
180
|
+
annotated with a "will I lose work?" badge (dirty/ahead/merged, plus PR state
|
|
181
|
+
when `gh`/`glab` is present):
|
|
178
182
|
|
|
179
183
|
```bash
|
|
180
184
|
treebox teardown fix-auth brave-otter --delete-branch
|
|
185
|
+
treebox teardown # pick interactively
|
|
181
186
|
```
|
|
182
187
|
|
|
183
188
|
treebox is built to be scripted, including by agents: every command takes
|
|
184
189
|
`--json` (data to stdout, diagnostics to stderr, a schema that only gains
|
|
185
|
-
fields), `--dry-run` prints the exact commands without running them, and exit
|
|
190
|
+
fields within a version), `--dry-run` prints the exact commands without running them, and exit
|
|
186
191
|
codes are stable (`0` ok · `2` usage · `3` not found · `4` auth · `5`
|
|
187
192
|
conflict). Full reference in the
|
|
188
193
|
[usage guide](https://seth-peters.github.io/treebox/usage/).
|
|
@@ -212,8 +217,8 @@ Optional, and user-level only (`~/.config/treebox/config.toml`) — treebox
|
|
|
212
217
|
never reads config from the target repo:
|
|
213
218
|
|
|
214
219
|
```toml
|
|
215
|
-
|
|
216
|
-
|
|
220
|
+
isolation = "docker" # host | docker
|
|
221
|
+
harness = "claude" # claude | codex
|
|
217
222
|
base = "main"
|
|
218
223
|
```
|
|
219
224
|
|
|
@@ -35,10 +35,10 @@ on a laptop or over plain SSH.
|
|
|
35
35
|
|
|
36
36
|
Built by [Seth Peters](https://github.com/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.
|
|
37
37
|
|
|
38
|
-
Provisioning is identical everywhere; a pluggable **
|
|
39
|
-
agent runs:
|
|
38
|
+
Provisioning is identical everywhere; a pluggable **isolation mode** decides
|
|
39
|
+
where the agent runs:
|
|
40
40
|
|
|
41
|
-
|
|
|
41
|
+
| `--isolation` | Sandbox | Agent runs in |
|
|
42
42
|
| ---------------- | --------- | ----------------------------------------------------- |
|
|
43
43
|
| `host` (default) | none | the worktree shell |
|
|
44
44
|
| `docker` | sandboxed | a docker container, with your `.env` + caches mounted |
|
|
@@ -77,8 +77,8 @@ your back. Or install directly:
|
|
|
77
77
|
uv tool install git+https://github.com/Seth-Peters/treebox
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
`codex`);
|
|
80
|
+
Host isolation needs only `git` and a logged-in agent CLI (`claude` /
|
|
81
|
+
`codex`); docker isolation additionally needs just `docker` — no Node.js, no
|
|
82
82
|
extra CLIs. See the
|
|
83
83
|
[install guide](https://seth-peters.github.io/treebox/install/) for
|
|
84
84
|
requirements and installer overrides.
|
|
@@ -103,8 +103,8 @@ launches the agent:
|
|
|
103
103
|
```bash
|
|
104
104
|
treebox create # generated name (brave-otter), host-native
|
|
105
105
|
treebox create fix-auth # named up front
|
|
106
|
-
treebox create fix-auth --
|
|
107
|
-
treebox create
|
|
106
|
+
treebox create fix-auth --isolation docker # sandboxed
|
|
107
|
+
treebox create --checkout feature/auth # exact existing branch (resume, PR review)
|
|
108
108
|
treebox create auth-fixes --base feature/auth # stack on any base branch
|
|
109
109
|
```
|
|
110
110
|
|
|
@@ -127,8 +127,8 @@ unique substring of either. Dependencies re-sync only if the lockfile changed
|
|
|
127
127
|
since last time:
|
|
128
128
|
|
|
129
129
|
```bash
|
|
130
|
-
treebox enter fix-auth --
|
|
131
|
-
treebox enter fix-auth --
|
|
130
|
+
treebox enter fix-auth --harness claude
|
|
131
|
+
treebox enter fix-auth --harness codex -- --resume # args after -- go to the agent
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
**List.** See what exists, what each worktree was last doing, and what has
|
|
@@ -140,15 +140,19 @@ treebox list
|
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
**Tear down.** Remove one or more worktrees — and, when you're done, their
|
|
143
|
-
branches. Refuses to delete uncommitted work unless forced
|
|
143
|
+
branches. Refuses to delete uncommitted work unless forced. Run it with no
|
|
144
|
+
refs and treebox walks you through an arrow-key picker, each worktree
|
|
145
|
+
annotated with a "will I lose work?" badge (dirty/ahead/merged, plus PR state
|
|
146
|
+
when `gh`/`glab` is present):
|
|
144
147
|
|
|
145
148
|
```bash
|
|
146
149
|
treebox teardown fix-auth brave-otter --delete-branch
|
|
150
|
+
treebox teardown # pick interactively
|
|
147
151
|
```
|
|
148
152
|
|
|
149
153
|
treebox is built to be scripted, including by agents: every command takes
|
|
150
154
|
`--json` (data to stdout, diagnostics to stderr, a schema that only gains
|
|
151
|
-
fields), `--dry-run` prints the exact commands without running them, and exit
|
|
155
|
+
fields within a version), `--dry-run` prints the exact commands without running them, and exit
|
|
152
156
|
codes are stable (`0` ok · `2` usage · `3` not found · `4` auth · `5`
|
|
153
157
|
conflict). Full reference in the
|
|
154
158
|
[usage guide](https://seth-peters.github.io/treebox/usage/).
|
|
@@ -178,8 +182,8 @@ Optional, and user-level only (`~/.config/treebox/config.toml`) — treebox
|
|
|
178
182
|
never reads config from the target repo:
|
|
179
183
|
|
|
180
184
|
```toml
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
isolation = "docker" # host | docker
|
|
186
|
+
harness = "claude" # claude | codex
|
|
183
187
|
base = "main"
|
|
184
188
|
```
|
|
185
189
|
|
|
@@ -54,7 +54,7 @@ whichever agent you launch:
|
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
57
|
-
`~/.config/treebox/config.toml` picks the
|
|
57
|
+
`~/.config/treebox/config.toml` picks the isolation mode, harness, base, caches, and
|
|
58
58
|
sandbox template. It's the single source of truth, and it's never read
|
|
59
59
|
from the target repo.
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ whichever agent you launch:
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
`treebox enter feature/auth --
|
|
65
|
+
`treebox enter feature/auth --harness claude` and `--harness codex` launch into
|
|
66
66
|
the *same* provisioned, isolated tree. The isolation doesn't change when
|
|
67
67
|
the agent does.
|
|
68
68
|
|
|
@@ -75,7 +75,7 @@ are added. You learn treebox once — not each agent's cage.
|
|
|
75
75
|
!!! note "treebox complements these systems, it doesn't replace them"
|
|
76
76
|
An agent's own permission rules still apply inside the box. treebox's job
|
|
77
77
|
is the layer they all leave to you: a consistent isolated worktree and,
|
|
78
|
-
with
|
|
78
|
+
with `docker` isolation, a consistent sandbox — defined by *your*
|
|
79
79
|
template, [rendered outside the box](how-it-works.md#the-sandbox-config-lives-outside-the-box)
|
|
80
80
|
so the agent can't edit its own cage.
|
|
81
81
|
|
|
@@ -10,12 +10,12 @@ Lives at `$TREEBOX_CONFIG` if set, else `$XDG_CONFIG_HOME/treebox/config.toml`
|
|
|
10
10
|
(defaulting to `~/.config/treebox/config.toml`). All keys optional:
|
|
11
11
|
|
|
12
12
|
```toml
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
isolation = "host" # host | docker
|
|
14
|
+
harness = "claude" # claude | codex
|
|
15
15
|
base = "main" # default base branch
|
|
16
16
|
root = ".treebox/worktrees"
|
|
17
17
|
env_file = ".env" # canonical secrets path, copied into each worktree
|
|
18
|
-
firewall = false # container firewall (docker
|
|
18
|
+
firewall = false # container firewall (docker isolation)
|
|
19
19
|
template = "default" # sandbox template name
|
|
20
20
|
|
|
21
21
|
# Replace auto-detected dependency setup with your own shell commands,
|
|
@@ -24,7 +24,7 @@ setup_hook = ["uv sync --frozen", "uv run python -m scripts.seed"]
|
|
|
24
24
|
|
|
25
25
|
# Override where shared package caches live, per ecosystem
|
|
26
26
|
# (uv, npm, pnpm, go, cargo). Defaults honor the standard env vars
|
|
27
|
-
# (UV_CACHE_DIR, GOMODCACHE, …);
|
|
27
|
+
# (UV_CACHE_DIR, GOMODCACHE, …); docker isolation bind-mounts
|
|
28
28
|
# these into the container.
|
|
29
29
|
[caches]
|
|
30
30
|
uv = "/mnt/fast/cache/uv"
|
|
@@ -39,12 +39,12 @@ command-line flag > config.toml > built-in default
|
|
|
39
39
|
|
|
40
40
|
| Key | Default | What it controls |
|
|
41
41
|
| ---------- | -------------------- | --------------------------------------------------------- |
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
42
|
+
| `isolation`| `host` | Where agents run: the worktree shell, or a docker sandbox. |
|
|
43
|
+
| `harness` | `claude` | Which agent `create`/`enter` launch by default. |
|
|
44
44
|
| `base` | `main` | Base branch for new branches (resolved as `origin/<base>`). |
|
|
45
45
|
| `root` | `.treebox/worktrees` | Where worktree directories are created, relative to the repo. |
|
|
46
46
|
| `env_file` | `.env` | The secrets file copied into every new worktree. |
|
|
47
|
-
| `firewall` | `false` | Restrict container egress (docker
|
|
47
|
+
| `firewall` | `false` | Restrict container egress (docker isolation). |
|
|
48
48
|
| `template` | `default` | Which operator template defines the sandbox. |
|
|
49
49
|
| `setup_hook` | *(auto-detect)* | Your own setup commands instead of the detected package manager's. |
|
|
50
50
|
| `caches` | *(standard env vars)* | Per-ecosystem shared cache locations treebox installs from and mounts. |
|
|
@@ -6,7 +6,7 @@ than reimplementing them. The whole tool is organized around one seam:
|
|
|
6
6
|
|
|
7
7
|
## The provisioning pipeline
|
|
8
8
|
|
|
9
|
-
Every `create` walks the same host-side pipeline, regardless of
|
|
9
|
+
Every `create` walks the same host-side pipeline, regardless of isolation mode:
|
|
10
10
|
|
|
11
11
|
```text
|
|
12
12
|
fetch origin # required — a failure exits 4, loudly
|
|
@@ -20,14 +20,15 @@ fetch origin # required — a failure exits 4, loudly
|
|
|
20
20
|
└─ hand off to the runner
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
The runner only decides **where** the last two
|
|
23
|
+
The runner — picked with `--isolation` — only decides **where** the last two
|
|
24
|
+
steps happen:
|
|
24
25
|
|
|
25
|
-
|
|
|
26
|
+
| `--isolation` | `setup` runs… | Agent runs… |
|
|
26
27
|
| -------- | ------------------------------- | --------------------------------------- |
|
|
27
28
|
| `host` | in the worktree shell | in the worktree shell |
|
|
28
29
|
| `docker` | inside the container, on create | via `docker exec`, inside the sandbox |
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Docker isolation builds and starts the sandbox with plain `docker build` /
|
|
31
32
|
`docker run`. The worktree and the repo's git dir are bind-mounted at their
|
|
32
33
|
host paths — mirrored 1:1 — so in-container `git` resolves the worktree's
|
|
33
34
|
pointers exactly as the host does, with no extra tooling.
|
|
@@ -35,7 +36,7 @@ pointers exactly as the host does, with no extra tooling.
|
|
|
35
36
|
## Names are identity; branches are mutable
|
|
36
37
|
|
|
37
38
|
The worktree **name** (the directory leaf) is the permanent identity: the
|
|
38
|
-
docker
|
|
39
|
+
docker sandbox bind-mounts the absolute path and a live agent's CWD sits in
|
|
39
40
|
it, so the directory is never renamed. The **branch** is just an attribute —
|
|
40
41
|
created as a `treebox/<name>` placeholder and expected to be renamed
|
|
41
42
|
(`git branch -m`) once the work has a shape. `list`, `enter`, and `teardown`
|
|
@@ -75,7 +76,7 @@ re-download. `--cold` bypasses the caches when you want a from-source build.
|
|
|
75
76
|
|
|
76
77
|
## The sandbox config lives outside the box
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
Docker isolation's threat model is simple: **an agent must not be able to
|
|
79
80
|
edit the config that defines its own sandbox.**
|
|
80
81
|
|
|
81
82
|
- The container template (Dockerfile, `container.json`, firewall setup) is
|
|
@@ -100,7 +101,7 @@ treebox assumes the caller is often another program:
|
|
|
100
101
|
automatically when stderr isn't a TTY.
|
|
101
102
|
- **Stable exit codes** — `0` ok · `2` usage · `3` not-found · `4` auth ·
|
|
102
103
|
`5` conflict — so callers can branch without parsing prose.
|
|
103
|
-
- **`--json` with a `schemaVersion`** that only
|
|
104
|
+
- **`--json` with a `schemaVersion`** that only gains fields within a version
|
|
104
105
|
(git-porcelain discipline), plus `--print` and `--dry-run` for scripts that
|
|
105
106
|
want the commands, not the side effects.
|
|
106
107
|
- **Per-worktree locking**, so two concurrent `create fix-auth` calls
|
|
@@ -34,7 +34,7 @@ sandbox.
|
|
|
34
34
|
<div class="t-line" style="--d:1.8s"> </div>
|
|
35
35
|
<div class="t-line" style="--d:1.8s"> <span class="c-dim">branch</span> treebox/brave-otter <span class="c-dim">· placeholder — rename before push</span></div>
|
|
36
36
|
<div class="t-line" style="--d:1.9s"> <span class="c-dim">base</span> main</div>
|
|
37
|
-
<div class="t-line" style="--d:2.0s"> <span class="c-dim">
|
|
37
|
+
<div class="t-line" style="--d:2.0s"> <span class="c-dim">isolation</span> host <span class="c-dim">→</span> claude</div>
|
|
38
38
|
<div class="t-line" style="--d:2.2s"> </div>
|
|
39
39
|
<div class="t-line" style="--d:2.2s;--run:.8s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> fetch <span class="c-dim">origin up to date</span></div>
|
|
40
40
|
<div class="t-line" style="--d:3.0s;--run:.5s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> worktree <span class="c-dim">.treebox/worktrees/brave-otter</span></div>
|
|
@@ -86,23 +86,23 @@ collisions — on a laptop or over plain SSH.
|
|
|
86
86
|
|
|
87
87
|
</div>
|
|
88
88
|
|
|
89
|
-
## Two
|
|
89
|
+
## Two isolation modes, one pipeline
|
|
90
90
|
|
|
91
|
-
Provisioning is identical either way; a pluggable **
|
|
92
|
-
agent runs:
|
|
91
|
+
Provisioning is identical either way; a pluggable **isolation mode** decides
|
|
92
|
+
where the agent runs:
|
|
93
93
|
|
|
94
|
-
|
|
|
94
|
+
| `--isolation` | Sandbox | Agent runs in |
|
|
95
95
|
| ---------------- | --------- | ----------------------------------------------------- |
|
|
96
96
|
| `host` (default) | none | the worktree shell |
|
|
97
97
|
| `docker` | sandboxed | a docker container, with your `.env` + caches mounted |
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
100
|
treebox create fix-auth # placeholder branch off fresh origin/main
|
|
101
|
-
treebox create fix-auth --
|
|
101
|
+
treebox create fix-auth --isolation docker # same provisioning, sandboxed
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
[what
|
|
104
|
+
Docker isolation needs exactly one extra thing: Docker — see
|
|
105
|
+
[what each isolation mode needs](install.md#what-each-isolation-mode-needs).
|
|
106
106
|
|
|
107
107
|
## What it is — and isn't
|
|
108
108
|
|
|
@@ -127,7 +127,7 @@ The docker runner needs exactly one extra thing: Docker — see
|
|
|
127
127
|
- :material-close:{ .tx-no } Manage API keys — it never uses `ANTHROPIC_API_KEY`
|
|
128
128
|
- :material-close:{ .tx-no } Review, merge, or push your branches
|
|
129
129
|
- :material-close:{ .tx-no } Trust the target repo's config — its container config and hooks are ignored
|
|
130
|
-
- :material-close:{ .tx-no } Isolate anything
|
|
130
|
+
- :material-close:{ .tx-no } Isolate anything in `host` isolation — that's what `docker` is for
|
|
131
131
|
- :material-close:{ .tx-no } Replace CI, or orchestrate fleets of agents
|
|
132
132
|
- :material-close:{ .tx-no } Install a package manager behind your back
|
|
133
133
|
|
|
@@ -139,7 +139,7 @@ The docker runner needs exactly one extra thing: Docker — see
|
|
|
139
139
|
```bash
|
|
140
140
|
treebox doctor # verify the host is ready
|
|
141
141
|
treebox create # provision + launch claude (name generated)
|
|
142
|
-
treebox enter brave-otter --
|
|
142
|
+
treebox enter brave-otter --harness codex # re-enter later, pick agent per entry
|
|
143
143
|
treebox list # what exists, what's stale
|
|
144
144
|
treebox teardown brave-otter --delete-branch
|
|
145
145
|
```
|