treebox 0.2.0__tar.gz → 0.3.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 (62) hide show
  1. {treebox-0.2.0 → treebox-0.3.0}/PKG-INFO +4 -3
  2. {treebox-0.2.0 → treebox-0.3.0}/README.md +3 -2
  3. {treebox-0.2.0 → treebox-0.3.0}/docs/index.md +22 -14
  4. {treebox-0.2.0 → treebox-0.3.0}/docs/javascripts/treebox.js +70 -2
  5. {treebox-0.2.0 → treebox-0.3.0}/docs/stylesheets/extra.css +57 -0
  6. treebox-0.3.0/hooks/copy_page.py +32 -0
  7. {treebox-0.2.0 → treebox-0.3.0}/mkdocs.yml +8 -1
  8. treebox-0.2.0/.github/workflows/auto-release.yml +0 -155
  9. {treebox-0.2.0 → treebox-0.3.0}/.agents/skills/no-mistakes/SKILL.md +0 -0
  10. {treebox-0.2.0 → treebox-0.3.0}/.github/workflows/ci.yml +0 -0
  11. {treebox-0.2.0 → treebox-0.3.0}/.github/workflows/claude.yml +0 -0
  12. {treebox-0.2.0 → treebox-0.3.0}/.github/workflows/docs.yml +0 -0
  13. {treebox-0.2.0 → treebox-0.3.0}/.github/workflows/release.yml +0 -0
  14. {treebox-0.2.0 → treebox-0.3.0}/.gitignore +0 -0
  15. {treebox-0.2.0 → treebox-0.3.0}/AGENTS.md +0 -0
  16. {treebox-0.2.0 → treebox-0.3.0}/CLAUDE.md +0 -0
  17. {treebox-0.2.0 → treebox-0.3.0}/CONTRIBUTING.md +0 -0
  18. {treebox-0.2.0 → treebox-0.3.0}/LICENSE +0 -0
  19. {treebox-0.2.0 → treebox-0.3.0}/ROADMAP.md +0 -0
  20. {treebox-0.2.0 → treebox-0.3.0}/assets/treebox-logo.png +0 -0
  21. {treebox-0.2.0 → treebox-0.3.0}/docs/agents.md +0 -0
  22. {treebox-0.2.0 → treebox-0.3.0}/docs/assets/treebox-logo.png +0 -0
  23. {treebox-0.2.0 → treebox-0.3.0}/docs/configuration.md +0 -0
  24. {treebox-0.2.0 → treebox-0.3.0}/docs/how-it-works.md +0 -0
  25. {treebox-0.2.0 → treebox-0.3.0}/docs/install.md +0 -0
  26. {treebox-0.2.0 → treebox-0.3.0}/docs/usage.md +0 -0
  27. {treebox-0.2.0 → treebox-0.3.0}/install.sh +0 -0
  28. {treebox-0.2.0 → treebox-0.3.0}/pyproject.toml +0 -0
  29. {treebox-0.2.0 → treebox-0.3.0}/scripts/validate.sh +0 -0
  30. {treebox-0.2.0 → treebox-0.3.0}/skills/treebox/SKILL.md +0 -0
  31. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/__init__.py +0 -0
  32. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets/container/Dockerfile +0 -0
  33. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets/container/allowed-domains.sh +0 -0
  34. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets/container/container.json +0 -0
  35. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets/container/firewall.json +0 -0
  36. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets/container/init-firewall.sh +0 -0
  37. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets/container/post-create.sh +0 -0
  38. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets/pre-push +0 -0
  39. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/assets.py +0 -0
  40. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/cli.py +0 -0
  41. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/config.py +0 -0
  42. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/ecosystems.py +0 -0
  43. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/forge.py +0 -0
  44. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/git.py +0 -0
  45. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/locking.py +0 -0
  46. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/models.py +0 -0
  47. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/names.py +0 -0
  48. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/output.py +0 -0
  49. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/provision.py +0 -0
  50. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/py.typed +0 -0
  51. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/resolve.py +0 -0
  52. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/runners/__init__.py +0 -0
  53. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/runners/base.py +0 -0
  54. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/runners/docker.py +0 -0
  55. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/runners/host.py +0 -0
  56. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/state.py +0 -0
  57. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/status.py +0 -0
  58. {treebox-0.2.0 → treebox-0.3.0}/src/treebox/system.py +0 -0
  59. {treebox-0.2.0 → treebox-0.3.0}/tests/conftest.py +0 -0
  60. {treebox-0.2.0 → treebox-0.3.0}/tests/test_integration.py +0 -0
  61. {treebox-0.2.0 → treebox-0.3.0}/tests/test_units.py +0 -0
  62. {treebox-0.2.0 → treebox-0.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: treebox
3
- Version: 0.2.0
3
+ Version: 0.3.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/
@@ -48,6 +48,7 @@ Description-Content-Type: text/markdown
48
48
  <a href="https://seth-peters.github.io/treebox/"><img src="https://img.shields.io/badge/docs-seth--peters.github.io%2Ftreebox-2f6f4f" alt="Documentation"></a>
49
49
  <img src="https://img.shields.io/badge/python-3.11%2B-3776ab" alt="Python 3.11+">
50
50
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-PolyForm%20Noncommercial-2f6f4f" alt="PolyForm Noncommercial License"></a>
51
+ <a href="https://www.linkedin.com/in/seth-peters/"><img src="https://img.shields.io/badge/LinkedIn-Seth%20Peters-0a66c2?logo=linkedin&logoColor=white" alt="Seth Peters on LinkedIn"></a>
51
52
  </p>
52
53
 
53
54
  <p align="center">
@@ -55,7 +56,7 @@ Description-Content-Type: text/markdown
55
56
  <a href="https://seth-peters.github.io/treebox/install/">Install</a> ·
56
57
  <a href="https://seth-peters.github.io/treebox/usage/">Usage</a> ·
57
58
  <a href="https://seth-peters.github.io/treebox/how-it-works/">How it works</a> ·
58
- <a href="https://github.com/Seth-Peters">Seth Peters</a>
59
+ <a href="https://www.linkedin.com/in/seth-peters/">Seth Peters</a>
59
60
  </p>
60
61
 
61
62
  ---
@@ -68,7 +69,7 @@ un-pushable `treebox/<name>` placeholder branch the agent renames when the
68
69
  work takes shape. Agents work the same repo in parallel without collisions —
69
70
  on a laptop or over plain SSH.
70
71
 
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.
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.
72
73
 
73
74
  Provisioning is identical everywhere; a pluggable **isolation mode** decides
74
75
  where the agent runs:
@@ -13,6 +13,7 @@
13
13
  <a href="https://seth-peters.github.io/treebox/"><img src="https://img.shields.io/badge/docs-seth--peters.github.io%2Ftreebox-2f6f4f" alt="Documentation"></a>
14
14
  <img src="https://img.shields.io/badge/python-3.11%2B-3776ab" alt="Python 3.11+">
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-PolyForm%20Noncommercial-2f6f4f" alt="PolyForm Noncommercial License"></a>
16
+ <a href="https://www.linkedin.com/in/seth-peters/"><img src="https://img.shields.io/badge/LinkedIn-Seth%20Peters-0a66c2?logo=linkedin&logoColor=white" alt="Seth Peters on LinkedIn"></a>
16
17
  </p>
17
18
 
18
19
  <p align="center">
@@ -20,7 +21,7 @@
20
21
  <a href="https://seth-peters.github.io/treebox/install/">Install</a> ·
21
22
  <a href="https://seth-peters.github.io/treebox/usage/">Usage</a> ·
22
23
  <a href="https://seth-peters.github.io/treebox/how-it-works/">How it works</a> ·
23
- <a href="https://github.com/Seth-Peters">Seth Peters</a>
24
+ <a href="https://www.linkedin.com/in/seth-peters/">Seth Peters</a>
24
25
  </p>
25
26
 
26
27
  ---
@@ -33,7 +34,7 @@ un-pushable `treebox/<name>` placeholder branch the agent renames when the
33
34
  work takes shape. Agents work the same repo in parallel without collisions —
34
35
  on a laptop or over plain SSH.
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
+ 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.
37
38
 
38
39
  Provisioning is identical everywhere; a pluggable **isolation mode** decides
39
40
  where the agent runs:
@@ -26,23 +26,24 @@ sandbox.
26
26
  </div>
27
27
 
28
28
  <div class="tx-terminal">
29
- <div class="tx-terminal__bar"><span class="tx-dot tx-dot--r"></span><span class="tx-dot tx-dot--y"></span><span class="tx-dot tx-dot--g"></span><button class="tx-copy" type="button" data-copy="treebox create" aria-label="Copy command"><svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path fill="currentColor" d="M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12z"/></svg><span class="tx-copy__done">✓</span></button></div>
29
+ <div class="tx-terminal__bar"><span class="tx-dot tx-dot--r"></span><span class="tx-dot tx-dot--y"></span><span class="tx-dot tx-dot--g"></span><button class="tx-copy" type="button" data-copy="treebox create --isolation docker" aria-label="Copy command"><svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path fill="currentColor" d="M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12z"/></svg><span class="tx-copy__done">✓</span></button></div>
30
30
  <div class="tx-screen">
31
- <div class="t-line" style="--d:0s"><span class="c-ok">$ </span><span class="t-type">treebox create</span></div>
32
- <div class="t-line" style="--d:1.6s"> </div>
33
- <div class="t-line" style="--d:1.6s"> <span class="c-acc">●</span> create <b>brave-otter</b></div>
31
+ <div class="t-line" style="--d:0s"><span class="c-ok">$ </span><span class="t-type">treebox create --isolation docker</span></div>
34
32
  <div class="t-line" style="--d:1.8s"> </div>
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
- <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">isolation</span> host <span class="c-dim">→</span> claude</div>
38
- <div class="t-line" style="--d:2.2s"> </div>
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
- <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>
41
- <div class="t-line" style="--d:3.5s;--run:.2s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> push guard <span class="c-dim">un-pushable until renamed</span></div>
33
+ <div class="t-line" style="--d:1.8s"> <span class="c-acc">●</span> create <b>brave-otter</b></div>
34
+ <div class="t-line" style="--d:2.0s"> </div>
35
+ <div class="t-line" style="--d:2.0s"> <span class="c-dim">branch</span> treebox/brave-otter <span class="c-dim" placeholder — rename before push</span></div>
36
+ <div class="t-line" style="--d:2.1s"> <span class="c-dim">base</span> main</div>
37
+ <div class="t-line" style="--d:2.2s"> <span class="c-dim">isolation</span> docker <span class="c-dim">→</span> claude</div>
38
+ <div class="t-line" style="--d:2.4s"> </div>
39
+ <div class="t-line" style="--d:2.4s;--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
+ <div class="t-line" style="--d:3.2s;--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>
42
41
  <div class="t-line" style="--d:3.7s;--run:.3s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> secrets <span class="c-dim">copied</span></div>
43
- <div class="t-line" style="--d:4.0s;--run:.9s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> deps <span class="c-dim">synced from shared cache</span></div>
44
- <div class="t-line" style="--d:5.1s"> </div>
45
- <div class="t-line" style="--d:5.1s"> <span class="c-ok">Ready</span> <span class="c-dim">in 4.2s launching</span> claude</div>
42
+ <div class="t-line" style="--d:4.0s;--run:.4s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> sandbox <span class="c-dim">templates written (outside worktree)</span></div>
43
+ <div class="t-line" style="--d:4.4s;--run:1.9s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> image <span class="c-dim">building container image…</span></div>
44
+ <div class="t-line" style="--d:6.3s;--run:.5s"> <span class="t-g"><span class="t-spin"></span><span class="t-check">✓</span></span> container <span class="c-dim">started · agent execs via docker exec</span></div>
45
+ <div class="t-line" style="--d:6.9s"> </div>
46
+ <div class="t-line" style="--d:6.9s"> <span class="c-ok">Ready</span> <span class="c-dim">in 22.6s — launching</span> claude</div>
46
47
  </div>
47
48
  </div>
48
49
 
@@ -146,3 +147,10 @@ treebox teardown brave-otter --delete-branch
146
147
 
147
148
  Five commands — the whole surface. [Install it](install.md), or read
148
149
  [how it works](how-it-works.md).
150
+
151
+ ---
152
+
153
+ Built by **[Seth Peters](https://www.linkedin.com/in/seth-peters/)** — an
154
+ operator-focused layer for AI-agent infrastructure: git worktrees, sandbox
155
+ boundaries, subscription auth, and repeatable dev environments. Building in
156
+ this space? [Connect on LinkedIn](https://www.linkedin.com/in/seth-peters/).
@@ -4,12 +4,15 @@
4
4
  * 2. Console code blocks: the Material copy button copies only the
5
5
  * `$ `-prefixed commands, never the output printed under them.
6
6
  * 3. ✓ / ✗ marks in console output get their CLI colors.
7
- * 4. The hero terminal replays its `treebox create` run in a loop,
7
+ * 4. The hero terminal replays its `treebox create --isolation docker` run in a loop,
8
8
  * starting when it scrolls into view. Without JS or with reduced
9
9
  * motion, the static final frame shows instead.
10
+ * 5. A "Copy page" button at the top of every page copies that page's raw
11
+ * Markdown source (embedded by hooks/copy_page.py) — an agent-ready copy,
12
+ * entirely offline.
10
13
  */
11
14
  (function () {
12
- var PLAY_MS = 5200; // last line lands at ~5.1s
15
+ var PLAY_MS = 7000; // last line lands at ~6.9s
13
16
  var HOLD_MS = 3800; // linger on the final frame before replaying
14
17
 
15
18
  function animateTerminals() {
@@ -42,8 +45,73 @@
42
45
  });
43
46
  }
44
47
 
48
+ // Copy icon (default) and check icon (shown green after a successful copy).
49
+ var COPY_SVG =
50
+ '<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">' +
51
+ '<path fill="currentColor" d="M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12z"/></svg>';
52
+ var CHECK_SVG =
53
+ '<svg class="tx-copy-page__check" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">' +
54
+ '<path fill="currentColor" d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>';
55
+
56
+ // base64 (ASCII) → original UTF-8 string.
57
+ function decodeUtf8Base64(b64) {
58
+ return decodeURIComponent(
59
+ Array.prototype.map
60
+ .call(atob(b64), function (c) {
61
+ return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
62
+ })
63
+ .join("")
64
+ );
65
+ }
66
+
67
+ function copyPageButton() {
68
+ var carrier = document.querySelector("script.tx-page-md");
69
+ var article = document.querySelector(".md-content__inner");
70
+ if (!carrier || !article || article.querySelector(".tx-copy-page")) return;
71
+
72
+ var markdown;
73
+ try {
74
+ markdown = decodeUtf8Base64(carrier.textContent.trim());
75
+ } catch (e) {
76
+ return; // malformed payload — leave the page untouched
77
+ }
78
+ var url = carrier.getAttribute("data-url") || "";
79
+ var payload = url ? "> Source: " + url + "\n\n" + markdown : markdown;
80
+
81
+ var bar = document.createElement("div");
82
+ bar.className = "tx-copy-page-bar";
83
+ var btn = document.createElement("button");
84
+ btn.type = "button";
85
+ btn.className = "tx-copy-page";
86
+ btn.setAttribute("aria-label", "Copy this page as Markdown");
87
+ // Both labels are always in the DOM, overlapping in one grid cell, so the
88
+ // pill is permanently sized to the wider ("Copy page") — the copied state
89
+ // only flips visibility, never reflows. The icon swaps the same way.
90
+ btn.innerHTML =
91
+ COPY_SVG +
92
+ CHECK_SVG +
93
+ '<span class="tx-copy-page__label">' +
94
+ '<span class="tx-copy-page__word tx-copy-page__word--copy">Copy page</span>' +
95
+ '<span class="tx-copy-page__word tx-copy-page__word--done">Copied</span>' +
96
+ "</span>";
97
+
98
+ btn.addEventListener("click", function () {
99
+ navigator.clipboard.writeText(payload).then(function () {
100
+ btn.classList.add("is-copied");
101
+ clearTimeout(btn._reset);
102
+ btn._reset = setTimeout(function () {
103
+ btn.classList.remove("is-copied");
104
+ }, 2000);
105
+ });
106
+ });
107
+
108
+ bar.appendChild(btn);
109
+ article.insertBefore(bar, article.firstChild);
110
+ }
111
+
45
112
  function enhance() {
46
113
  animateTerminals();
114
+ copyPageButton();
47
115
  document.querySelectorAll(".tx-copy").forEach(function (btn) {
48
116
  if (btn.dataset.bound) return;
49
117
  btn.dataset.bound = "1";
@@ -310,6 +310,63 @@
310
310
  .tx-copy.is-copied svg { display: none; }
311
311
  .tx-copy.is-copied .tx-copy__done { display: inline; }
312
312
 
313
+ /* "Copy page" button — an agent-ready Markdown copy of the whole page.
314
+ Injected at the top-right of every article by javascripts/treebox.js,
315
+ above the H1. Degrades to nothing without JS. */
316
+ .tx-copy-page-bar {
317
+ display: flex;
318
+ justify-content: flex-end;
319
+ margin: 0 0 0.35rem;
320
+ }
321
+
322
+ .tx-copy-page {
323
+ display: inline-flex;
324
+ align-items: center;
325
+ gap: 0.4em;
326
+ padding: 0.28rem 0.62rem;
327
+ font-size: 0.68rem;
328
+ font-weight: 600;
329
+ line-height: 1;
330
+ color: var(--md-default-fg-color--light);
331
+ background: var(--md-code-bg-color);
332
+ border: 1px solid var(--md-default-fg-color--lightest);
333
+ border-radius: 999px;
334
+ cursor: pointer;
335
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
336
+ }
337
+
338
+ .tx-copy-page:hover {
339
+ color: var(--md-accent-fg-color);
340
+ border-color: var(--md-accent-fg-color);
341
+ }
342
+
343
+ /* After a copy: the whole pill goes green and the copy icon swaps to a
344
+ check — currentColor makes the checkmark green to match. */
345
+ .tx-copy-page.is-copied {
346
+ color: var(--tx-ok);
347
+ border-color: currentColor;
348
+ }
349
+
350
+ /* Icon swap. Each selector carries the same class+element specificity so the
351
+ toggle is decided by state, not by rule ordering: copy icon shows by
352
+ default, check icon shows only when copied. */
353
+ .tx-copy-page svg { display: block; }
354
+ .tx-copy-page svg.tx-copy-page__check { display: none; }
355
+ .tx-copy-page.is-copied svg:not(.tx-copy-page__check) { display: none; }
356
+ .tx-copy-page.is-copied svg.tx-copy-page__check { display: block; }
357
+
358
+ /* Label with zero layout shift: both words share one grid cell, so the pill is
359
+ always as wide as "Copy page"; copying only toggles which word is visible. */
360
+ .tx-copy-page__label { display: inline-grid; }
361
+ .tx-copy-page__word {
362
+ grid-area: 1 / 1;
363
+ text-align: left;
364
+ white-space: nowrap;
365
+ }
366
+ .tx-copy-page__word--done { visibility: hidden; }
367
+ .tx-copy-page.is-copied .tx-copy-page__word--copy { visibility: hidden; }
368
+ .tx-copy-page.is-copied .tx-copy-page__word--done { visibility: visible; }
369
+
313
370
  /* Animated screen: the static final frame is the default (no-JS,
314
371
  reduced-motion). javascripts/treebox.js arms `.tx-anim` and toggles
315
372
  `.is-playing` when the terminal scrolls into view, looping the run:
@@ -0,0 +1,32 @@
1
+ """MkDocs hook: embed each page's raw Markdown source so the docs can offer a
2
+ "Copy page" button that hands an agent-ready copy of the page to the clipboard.
3
+
4
+ The whole site ships self-contained (no external requests), so rather than
5
+ convert rendered HTML back to Markdown in the browser, we carry the *original*
6
+ `.md` source into the page at build time — base64-encoded inside a hidden
7
+ ``<script type="text/markdown">`` element. ``javascripts/treebox.js`` decodes
8
+ it and wires up the button. Base64 keeps the payload in the safe [A-Za-z0-9+/=]
9
+ alphabet, so it can never break out of the script tag or trip UTF-8 escaping.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ from html import escape
16
+ from typing import Any
17
+
18
+
19
+ def on_page_content(html: str, *, page: Any, config: Any, files: Any) -> str:
20
+ """Append the raw-Markdown carrier to each rendered page's content."""
21
+ markdown: str | None = getattr(page, "markdown", None)
22
+ if not markdown:
23
+ return html
24
+
25
+ payload = base64.b64encode(markdown.encode("utf-8")).decode("ascii")
26
+ title = escape(page.title or "", quote=True)
27
+ url = escape(page.canonical_url or "", quote=True)
28
+ carrier = (
29
+ '<script type="text/markdown" class="tx-page-md" '
30
+ f'data-title="{title}" data-url="{url}">{payload}</script>'
31
+ )
32
+ return html + carrier
@@ -3,7 +3,14 @@ site_description: >-
3
3
  Isolated, ready-to-run git worktrees for AI coding agents —
4
4
  host-native or docker-sandboxed.
5
5
  site_url: https://seth-peters.github.io/treebox/
6
- copyright: PolyForm Noncommercial 1.0.0
6
+ copyright: >-
7
+ Built by <a href="https://www.linkedin.com/in/seth-peters/">Seth Peters</a>
8
+ · PolyForm Noncommercial 1.0.0
9
+
10
+ # Carry each page's raw Markdown source into the build so the "Copy page"
11
+ # button (javascripts/treebox.js) can offer an agent-ready copy, offline.
12
+ hooks:
13
+ - hooks/copy_page.py
7
14
 
8
15
  nav:
9
16
  - Overview: index.md
@@ -1,155 +0,0 @@
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/*
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes