Compare commits

..

10 Commits

Author SHA1 Message Date
joakimp 45b6239777 test(smoke): don't hard-code a 'v' prefix on release_tag
Lint / hadolint (push) Successful in 8s
Lint / actionlint (push) Successful in 21s
Publish Docker Image / resolve-versions (push) Successful in 48s
Publish Docker Image / base-decide (push) Successful in 28s
Publish Docker Image / build-base (push) Has been skipped
Publish Docker Image / smoke (push) Successful in 4m44s
Publish Docker Image / smoke-studio (push) Failing after 5m4s
Publish Docker Image / build-variant-studio (push) Has been skipped
Publish Docker Image / build-variant (push) Successful in 29m5s
Publish Docker Image / update-description (push) Successful in 7s
Publish Docker Image / promote-base-latest (push) Successful in 15s
The smoke workflow deliberately passes RELEASE_TAG=smoke to the variant
build so smoke images don't collide with real vX.Y.Z tags. The variant
bakes that into /etc/pi-devbox/build-manifest.json, and pi-devbox-version
prints 'pi-devbox smoke' — correct behaviour. But the smoke assertion
required substring 'pi-devbox v', which only holds for real releases.

Assertion never fired before because pi-devbox-version was added after
v1.5.0 (fb49828, 2026-07-15) and every CI attempt since was blocked
before smoke ran (v1.6.0 network flake, v1.6.1 first successful base
build hit this). CI run 509 (v1.6.1) surfaced it.

Fix: require the substring 'pi-devbox ' (space, no v). The two
neighbouring assertions on --json and --quiet already cover the value
of release_tag; this one just verifies the human line renders.
Everything else in 55/56 checks passed on run 509 including
pi 0.81.1 reported and base-229f04e5d021 pushed OK.
2026-07-23 07:58:22 +02:00
joakimp d00eef2acb docs(changelog): v1.6.1 — pi 0.80.6→0.81.1 (skips 0.81.0)
Publish Docker Image / resolve-versions (push) Successful in 7s
Lint / actionlint (push) Successful in 55s
Lint / hadolint (push) Successful in 9s
Publish Docker Image / base-decide (push) Successful in 55s
Publish Docker Image / build-base (push) Successful in 58m42s
Publish Docker Image / smoke (push) Failing after 4m50s
Publish Docker Image / build-variant (push) Has been skipped
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
Publish Docker Image / smoke-studio (push) Failing after 13m22s
Publish Docker Image / build-variant-studio (push) Has been skipped
v1.6.0 was tagged 2026-07-17 but never reached Docker Hub (variant
publish blocked by a site-network SYN-drop fault, since fixed). Cut
v1.6.1 to land v1.6.0's content (agent-browser + pi-devbox-version)
alongside a first pi bump since v1.5.0.

0.81.0 is skipped deliberately: it removed the default stream fallback
for extensions using the pre-0.81 pi-agent-core API, which
pi-observational-memory relies on via agentLoop + stream.result().
0.81.1 restored the fallback (earendil-works/pi#6915), so 0.81.1 — but
not 0.81.0 — is a safe drop-in. pi-fork only imports types; unaffected.

Audit of 0.80.7–0.81.1 vs the two baked extensions and the base image:
no breaking changes affect pi-devbox. Node engine bumped to >=22.19.0
in 0.81.0 (nodesource 22.x currently 22.23.1, so no engine bump needed).

Base-affecting via the npm install line, so base-<hash> rebuilds.
2026-07-22 22:53:55 +02:00
joakimp fb35c549b5 fix(base): locate agent-browser's chrome arch-agnostically
Lint / actionlint (push) Failing after 12s
Lint / hadolint (push) Successful in 12s
Publish Docker Image / resolve-versions (push) Failing after 26s
Publish Docker Image / base-decide (push) Has been skipped
Publish Docker Image / build-base (push) Has been skipped
Publish Docker Image / smoke (push) Has been skipped
Publish Docker Image / build-variant (push) Has been skipped
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
Publish Docker Image / smoke-studio (push) Failing after 31m44s
Publish Docker Image / build-variant-studio (push) Has been skipped
v1.6.0's first build failed on amd64: playwright@latest fetches Chrome for
Testing, which extracts to chromium-<rev>/chrome-linux64/ on amd64 (vs
chrome-linux/ on arm64). The hardcoded chrome-linux/ glob matched only arm64,
so amd64 built no /usr/local/bin/agent-chrome symlink and 'test -x' failed
(agent-browser --version had already printed 0.32.1 — the tell).

Replace the glob with an arch-agnostic 'find -name chrome -path */chromium-*/*'
(skips the chrome-headless-shell binary and the chromium_headless_shell dir),
guarded by [ -n ] + the existing test -x. Verified locally: resolves the CfT
chrome and agent-browser drives it headless. hadolint clean.
2026-07-17 17:55:21 +02:00
joakimp 649fc44c5b chore(release): v1.6.0
Lint / hadolint (push) Failing after 22s
Lint / actionlint (push) Successful in 26s
Publish Docker Image / resolve-versions (push) Successful in 9s
Publish Docker Image / base-decide (push) Successful in 16s
Publish Docker Image / build-base (push) Failing after 25m26s
Publish Docker Image / smoke (push) Has been skipped
Publish Docker Image / smoke-studio (push) Has been skipped
Publish Docker Image / build-variant-studio (push) Has been skipped
Publish Docker Image / build-variant (push) Has been skipped
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
Roll the unreleased changes into v1.6.0 (minor — significant base addition:
agent-browser + headless Chromium baked into every variant for browser
automation/front-end verification). Also includes the pi-devbox-version command
and the bundled pi-toolkit sonnet-5 template bump. No pi bump — CI resolves
latest pi from npm at build time (verified: 0.80.6→0.80.10 has no breaking
changes affecting pi-devbox).
2026-07-17 17:19:56 +02:00
joakimp 89a8dc7fab perf(base): trim agent-browser footprint ~960MB→~625MB before release
Lint / hadolint (push) Failing after 14s
Lint / actionlint (push) Successful in 23s
Size concern ahead of the v1.6.0 base rebuild. agent-browser drives the full
chrome (verified headless: open/title/eval with the headless_shell removed), so
Playwright's chromium_headless_shell-* build is dead weight — drop it (~334MB),
and clean apt/npm caches in-layer. Net browser footprint ~625MB/arch.

CI disk is otherwise fine: build-base's 'Reclaim runner disk' step frees
~20-30GB (hostedtoolcache/dotnet/android/jvm) + docker prune before buildx.
hadolint clean.
2026-07-17 17:19:20 +02:00
joakimp 6625d66f3a docs(agents): point the global AGENTS.md managed block at agent-browser
Lint / hadolint (push) Failing after 7s
Lint / actionlint (push) Successful in 27s
Discoverability follow-up to baking agent-browser into the base: agents won't
reach for a browser they don't know they have (the exact gap that made this
capability easy to miss). Add a short pointer section to the pi-devbox managed
block — capability, the preset AGENT_BROWSER_EXECUTABLE_PATH, and
`agent-browser skills get core --full` for the command set. Pointer only; depth
stays in the skill. rootfs change → folds into the same base-<hash> rebuild.
CHANGELOG note updated.
2026-07-17 17:15:09 +02:00
joakimp 8caafc3f49 base: bake agent-browser + Playwright Chromium (headless browsing, all variants)
Lint / actionlint (push) Successful in 28s
Lint / hadolint (push) Successful in 11s
Gives the agent a real browser it can drive so front-end work involving live
DOM/WebGL can be VERIFIED, not guessed. The agent-browser skill (from the
skillset repo) was a no-op without the binary; it now works out of the box.

- agent-browser CLI (standalone Rust, ships no browser) via npm, prefixed
  NPM_CONFIG_PREFIX=/usr so it survives the ~/.pi/npm-global volume.
- Chromium fetched with 'playwright install --with-deps chromium' into
  PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/ms-playwright — a system path that
  the /home/developer volume can't shadow (unlike agent-browser's own
  ~/.agent-browser/browsers default, which WOULD vanish on recreate).
- Stable /usr/local/bin/agent-chrome symlink, exported as
  AGENT_BROWSER_EXECUTABLE_PATH, insulates the ENV from Playwright's
  per-version chromium-<rev> dir name.

Verified end-to-end (this session): agent-browser drives the baked Chromium
headless (open + title + screenshot + eval into a WebGL SPA); doctor launch
test passes in ~0.5s. Debian trixie --with-deps resolution verified (exit 0;
t64 lib renames handled). hadolint clean; check-base-hash OK (only *_VERSION
args added). Cost ~960 MB (Chromium + headless shell). Base-affecting →
rebuilds base-<hash> on next release.
2026-07-17 16:12:47 +02:00
joakimp 71b12a9ed4 docs(skill): note macOS NFD-filename gotcha for dscp/scp
Lint / hadolint (push) Failing after 7s
Lint / actionlint (push) Successful in 31s
Accented filenames on a macOS host are stored decomposed (NFD), so a precomposed
(NFC) remote path in scp/dscp silently fails with 'No such file or directory'.
Document the wildcard / list-first workaround in the pi-devbox-environment skill,
next to the dssh/dscp alias table. (Hit while copying a screenshot named
'Skärmavbild ….png' from the host.)
2026-07-17 13:06:22 +02:00
joakimp fb49828826 base: add pi-devbox-version command + startup banner
Lint / hadolint (push) Successful in 8s
Lint / actionlint (push) Successful in 1m11s
Wraps /etc/pi-devbox/build-manifest.json (already written at docker-build
time in Dockerfile.variant) into a human-readable summary instead of
requiring users to know the manifest path and pipe it through jq
themselves.

- rootfs/usr/local/bin/pi-devbox-version: human (default) / --json /
  --quiet output modes. Also flags live drift — compares the baked
  pi_version against the actually-running `pi --version` and warns
  on mismatch rather than trusting the manifest blindly (same
  ground-truth philosophy as the manifest generation itself). Exits 1
  with a short stderr notice on images built before the manifest
  existed, instead of failing silently.
- entrypoint-user.sh: calls it as the very first line. CMD is
  `bash -l` with tty:true in compose, so this banner lands directly
  above the first prompt on container start — no separate motd/bashrc
  hook needed (deliberately not wired into .bash_aliases, which would
  reprint on every docker exec -it).
- Dockerfile.base: COPY + chmod, same pattern as dot-watch/studio-expose.
- scripts/smoke-test.sh: 4 new checks (binary present+executable, human
  output has release tag, --json round-trips the manifest, --quiet is
  a single line).
- README.md / AGENTS.md / CHANGELOG.md updated.
2026-07-15 14:50:47 +02:00
pi 02be95ac1f docs(changelog): note bundled pi-toolkit sonnet-5 template bump (Unreleased)
Lint / hadolint (push) Successful in 10s
Lint / actionlint (push) Successful in 21s
2026-07-13 23:13:24 +02:00
9 changed files with 318 additions and 7 deletions
+9 -6
View File
@@ -22,13 +22,16 @@ re-brand of opencode-devbox's `pi-only` variant.
by copying `/opt/pi-extensions/skill/` over the committed `rootfs/` snapshot
(Option 1 over Option 2 — see `skills/VENDORED.md`).
- `entrypoint.sh` — UID/GID alignment as root, then drops to `developer`.
- `entrypoint-user.sh` — per-container start: SSH ControlMaster socket
dir, LAN-access setup, MemPalace init, pi-toolkit + pi-extensions
deploy, mempalace-bridge symlink, fork/recall + pi-studio pi-install,
optional `studio-expose` bridge (when `STUDIO_EXPOSE=1`), image-baked
skills symlink-in, skillset deploy.
- `entrypoint-user.sh` — per-container start: prints the `pi-devbox-version`
banner first (which build/commit is running, from the manifest below),
then SSH ControlMaster socket dir, LAN-access setup, MemPalace init,
pi-toolkit + pi-extensions deploy, mempalace-bridge symlink, fork/recall +
pi-studio pi-install, optional `studio-expose` bridge (when
`STUDIO_EXPOSE=1`), image-baked skills symlink-in, skillset deploy.
- `rootfs/` — files baked into the image (bash aliases, inputrc,
setup-lan-access.sh, `studio-expose` helper). Also
setup-lan-access.sh, `studio-expose` helper, `pi-devbox-version` — wraps
`/etc/pi-devbox/build-manifest.json` into a human-readable summary + live
drift check, see README “Build provenance”). Also
`usr/local/share/pi-devbox/skills/<name>/SKILL.md` — image-baked agent
skills (the repo-authored `pi-devbox-environment`, plus vendored fallback
copies of `pi-extensions` and `mempalace` — see `skills/VENDORED.md`)
+95
View File
@@ -15,6 +15,101 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
_Nothing yet — next release's changes will accrue here._
## v1.6.1 — 2026-07-22
Patch release. Headline: **pi `0.80.6` → `0.81.1`** (npm `latest`) — the first
pi bump since v1.5.0.
> **Note on v1.6.0.** The `v1.6.0` git tag was cut on 2026-07-17 (agent-browser +
> `pi-devbox-version`, see below) but never reached Docker Hub: the variant
> publish was blocked by an intermittent SYN-drop fault on the on-prem CI
> network (`ci-network-diagnosis.md`, since resolved). v1.6.1 lands v1.6.0's
> content **plus** the pi bump in one release; there is no `v1.6.0` image on
> Docker Hub. The `v1.6.0` git tag is left in place as an accurate record of
> what was intended on that day.
### Changed
- **pi `0.80.6``0.81.1`.** The CI resolves `pi@latest` at build time; latest
is now `0.81.1`. The intermediate `0.81.0` is deliberately skipped: 0.81.0
removed the default stream fallback for extensions using the pre-0.81
`@earendil-works/pi-agent-core` API, which `pi-observational-memory` relies
on (`agentLoop` + `stream.result()` in the observer/reflector/dropper
agents). 0.81.1 restored the fallback ([earendil-works/pi#6915][pi-6915]),
making 0.81.1 — but not 0.81.0 — a safe drop-in. `pi-fork` only imports
types from `pi-agent-core` and is unaffected. Everything since v1.5.0's
baked `0.80.6` (i.e. `0.80.7``0.80.10`, `0.81.0`, `0.81.1`) was audited for
breaking changes against the two baked extensions — none affect this image.
The Node engine requirement rose to `>=22.19.0` in `0.81.0`; the base still
ships `22.23.1` (nodesource 22.x), so no engine bump is needed. Highlights
users inherit from the upstream jump: **local llama.cpp router support**
(search + download Hugging Face models, explicit load/unload, live
progress); **full pi-ai provider extensions** (extensions can now register
complete providers with native auth, model refresh, filtering, and
streaming); **Qwen Token Plan** subscription providers; **resilient
compaction / branch-summary retries** on transient provider failures with
lifecycle events exposed to interactive, JSON, RPC, and SDK consumers;
expanded usage accounting for tools, compaction, and branch summaries.
Base-affecting (npm install line rebuilds), so `base-<hash>` rebuilds. See
the [pi changelog][pi-changelog] for the full list.
[pi-6915]: https://github.com/earendil-works/pi/issues/6915
[pi-changelog]: https://github.com/earendil-works/pi/blob/main/CHANGELOG.md
## v1.6.0 — 2026-07-13
> ⚠️ **Never published to Docker Hub.** Tagged in git on 2026-07-17 but the
> variant publish was blocked by a site-network fault before the image reached
> the registry. Superseded by v1.6.1, which carries this release's content
> forward alongside the `pi 0.81.1` bump.
### Added
- **`agent-browser` — headless browser automation, baked into every variant.**
The base now ships the [`agent-browser`](https://www.npmjs.com/package/agent-browser)
CLI plus a Playwright-fetched Chromium, so the agent can drive a real browser
(open/click/fill/`eval`/screenshot/snapshot) and *verify* front-end work
involving live DOM or WebGL instead of guessing. The `agent-browser` skill
(from the skillset repo) was previously a no-op because the binary was
absent; it now works out of the box. Two pieces: the standalone Rust CLI
(npm, `NPM_CONFIG_PREFIX=/usr` so it survives the `~/.pi/npm-global` volume),
and a Chromium fetched via `playwright install --with-deps chromium` into
`PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/ms-playwright` (a system path,
never shadowed by the `/home/developer` volume — unlike agent-browser's own
`~/.agent-browser/browsers` default). A stable `/usr/local/bin/agent-chrome`
symlink, exported as `AGENT_BROWSER_EXECUTABLE_PATH`, insulates the config
from Playwright's per-version `chromium-<rev>` directory name. Debian trixie
`--with-deps` dependency resolution verified (the t64 renames are handled).
The global AGENTS.md managed block
(`rootfs/usr/local/share/pi-devbox/pi-global-AGENTS.append.md`) gains a short
pointer so agents discover the capability. Adds ~625 MB (Chromium; Playwright's
unused headless-shell build is dropped and the apt/npm caches cleaned in-layer
to stay lean). Base-affecting, rebuilds `base-<hash>`.
- **`pi-devbox-version` command.** Wraps `/etc/pi-devbox/build-manifest.json`
into a human-readable summary (release tag, build date, source revision,
baked `pi_version`, and short SHAs for every `/opt` component) instead of
requiring users to know the manifest path and pipe it through `jq`
themselves. Also flags **live drift** — if `pi --version` no longer matches
what was baked at build time, the `pi:` line calls that out rather than
silently trusting the manifest. `--json` dumps the raw manifest for
scripting; `--quiet` gives a one-line `release_tag (source_revision)` form.
Printed automatically once at container start (`entrypoint-user.sh`, before
the rest of the setup output), and stays available on demand for the rest
of the session. Exits 1 with a short notice — rather than failing silently
— on images built before this file existed. Base-affecting (new
`rootfs/usr/local/bin/pi-devbox-version`), rebuilds `base-<hash>`.
### Changed
- **Bundled `pi-toolkit` settings template: `pi-fork` balanced tier bumped to
`eu.anthropic.claude-sonnet-5`** (was `claude-sonnet-4-6`), matching the model
now in use. The image clones `pi-toolkit@main` into `/opt/pi-toolkit` at build
time, so the next build bundles it automatically (pi-toolkit `0010417`); the
same commit also refreshes the template's `enabledModels` and the README
examples. Seed-only: existing containers keep their live `~/.pi/agent/settings.json`
(the entrypoint merge is live-wins), so only fresh `~/.pi` volumes are affected.
## v1.5.0 — 2026-07-13
### Added
+55 -1
View File
@@ -14,7 +14,7 @@
# content-addressed over this file, so any byte change invalidates the
# cache. Recommended cadence: once per release for security updates.
#
# BASE_REBUILD_DATE: 2026-07-11 (Unreleased — typst PDF engine + xz-utils + pandoc typst-template default-font patch)
# BASE_REBUILD_DATE: 2026-07-13 (Unreleased — agent-browser CLI + Playwright Chromium for headless browser automation; prior: typst PDF engine + xz-utils + pandoc typst-template default-font patch)
#
# ── Lineage note ─────────────────────────────────────────────────────
# Adapted from opencode-devbox/Dockerfile.base (commit before v1.16.2).
@@ -457,6 +457,58 @@ RUN curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors https://deb.nodesour
apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*
# ── agent-browser — headless browser automation for the agent ────────
# Gives the agent a real browser it can drive (open/click/fill/eval/
# screenshot) so front-end work involving live DOM or WebGL can be VERIFIED
# rather than guessed at. The `agent-browser` skill (shipped from the
# skillset repo, not this image) documents the CLI; without this block that
# skill is a no-op because the binary isn't present. Verified end-to-end
# 2026-07-13: drives the baked Chromium headless (open + screenshot + eval
# into a WebGL SPA) — doctor's launch test passes in ~0.5s.
#
# TWO pieces, because agent-browser is a standalone Rust CLI that ships NO
# browser of its own — it only drives one you provide:
# 1. the CLI itself (npm; ~70 MB of prebuilt native binaries), and
# 2. a Chromium, which we fetch via Playwright.
#
# Why Playwright fetches the browser (and NOT `agent-browser install`):
# agent-browser's own installer drops Chrome under ~/.agent-browser/browsers
# — inside /home/${USER_NAME}, which is a NAMED VOLUME at runtime, so a
# build-time download would be SHADOWED (invisible) once the volume mounts.
# Playwright honours PLAYWRIGHT_BROWSERS_PATH, so we place the browser under
# /usr/local/share (never shadowed) and hand agent-browser a STABLE symlink
# via AGENT_BROWSER_EXECUTABLE_PATH — the symlink insulates the ENV from
# Playwright's per-version, per-ARCH browser directory (`chrome-linux` on arm64,
# `chrome-linux64` on amd64 — Chrome-for-Testing), so we `find` the `chrome`
# binary rather than hardcode the path; the headless-shell binary is named
# `chrome-headless-shell`, so `-name chrome` skips it.
#
# `playwright install --with-deps chromium` also apt-installs Chromium's
# runtime libs; verified to resolve correctly on Debian trixie (exit 0 — the
# t64 library renames are handled by Playwright's dep list). Build runs as
# root, so the apt step works. NPM_CONFIG_PREFIX=/usr keeps both CLIs on /usr
# so they survive the ~/.pi/npm-global volume mount (same trick the variant
# uses for pi). After fetching, we DROP Playwright's `chromium_headless_shell-*`
# build — agent-browser drives the full chrome (verified, incl. headless), so the
# headless shell is dead weight — and clean the apt/npm caches, trimming the
# layer to ~625 MB (Chromium) from ~960 MB. Still the bulk of the base's size,
# and the one real tradeoff of shipping this to every variant.
ARG AGENT_BROWSER_VERSION=latest
ARG PLAYWRIGHT_VERSION=latest
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/ms-playwright
RUN NPM_CONFIG_PREFIX=/usr npm install -g \
"agent-browser@${AGENT_BROWSER_VERSION}" \
"playwright@${PLAYWRIGHT_VERSION}" && \
playwright install --with-deps chromium && \
CHROME="$(find "${PLAYWRIGHT_BROWSERS_PATH}" -type f -name chrome -path '*/chromium-*/*' | head -n1)" && \
[ -n "$CHROME" ] && ln -sf "$CHROME" /usr/local/bin/agent-chrome && \
agent-browser --version && \
test -x "$(readlink -f /usr/local/bin/agent-chrome)" && \
rm -rf "${PLAYWRIGHT_BROWSERS_PATH}"/chromium_headless_shell-* && \
npm cache clean --force && \
rm -rf /var/lib/apt/lists/* /root/.npm /tmp/*
ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/local/bin/agent-chrome
# ── tldr (tealdeer) — community-maintained command examples ──────────
# Tealdeer is a Rust port of the tldr-pages client; ~5 MB static binary,
# ~135 MB smaller than the Node tldr global. Same `tldr` command, same UX.
@@ -604,11 +656,13 @@ COPY rootfs/usr/local/lib/pi-devbox/ /usr/local/lib/pi-devbox/
COPY rootfs/usr/local/share/pi-devbox/ /usr/local/share/pi-devbox/
COPY rootfs/usr/local/bin/studio-expose /usr/local/bin/studio-expose
COPY rootfs/usr/local/bin/dot-watch /usr/local/bin/dot-watch
COPY rootfs/usr/local/bin/pi-devbox-version /usr/local/bin/pi-devbox-version
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \
/usr/local/bin/studio-expose \
/usr/local/bin/dot-watch \
/usr/local/bin/pi-devbox-version \
/usr/local/lib/pi-devbox/*.sh 2>/dev/null || true
# Start as root — entrypoint adjusts UID/GID then drops to developer
+28
View File
@@ -693,6 +693,34 @@ truth** — the actual checked-out commit of each `/opt` clone and the live
docker run --rm --entrypoint= joakimp/pi-devbox:latest cat /etc/pi-devbox/build-manifest.json
```
Inside a running container, `pi-devbox-version` wraps that manifest into a
human-readable summary — no need to remember the file path or pipe it
through `jq` yourself:
```console
$ pi-devbox-version
pi-devbox v1.5.0
built: 2026-07-13T17:53:16Z (source d68674d11e06)
pi: 0.80.6
components:
pi-toolkit: 9a8f6faeaa08
pi-extensions: 61c98e004e3d
pi-fork: 4a09af4ef527
pi-observational-memory: 27a5195eaf90
mempalace-toolkit: 96699f2a1781
pi-studio: 2ef38ef31cea
```
It also flags **live drift** — if `pi --version` no longer matches what was
baked at build time (e.g. something on a persisted volume shadowed the
image's binary), the `pi:` line calls that out instead of silently trusting
the manifest. `--json` dumps the raw manifest for scripting; `--quiet` gives
a one-line `release_tag (source_revision)` form. It also prints once,
automatically, at container start (from `entrypoint-user.sh`, before the
rest of the setup output) — so you see which build you're in without
asking. Exits 1 with a short notice on images built before this file
existed, rather than failing silently.
## Troubleshooting
### Image grew unexpectedly
+8
View File
@@ -1,6 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
# ── Startup banner: which pi-devbox build is this? ─────────────────
# Printed FIRST, before the setup noise below, so it's the first thing
# visible when the container starts (CMD is `bash -l`, tty:true in compose,
# so this reaches the same stream as the interactive shell the user lands
# in). Reads the ground-truth manifest baked in Dockerfile.variant; a no-op
# with a short stderr notice on images built before it existed.
command -v pi-devbox-version >/dev/null 2>&1 && pi-devbox-version || true
# ── SSH ControlMaster socket dir ────────────────────────────────
# Companion to /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf in the
# base image — that file declares ControlPath=/tmp/sshcm/%r@%h:%p; this
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# pi-devbox-version — show which pi-devbox image build is running.
#
# WHY THIS EXISTS
# The image bakes ground-truth build info into /etc/pi-devbox/build-manifest.json
# at `docker build` time (see Dockerfile.variant): the release tag, build date,
# source commit, live `pi --version` at build time, and the actual checked-out
# commit of every /opt component clone. That answers "what image am I running?"
# — but only if you know to go look for the file. This wraps it into one
# command, prints it human-first at container start (see entrypoint-user.sh),
# and stays available on demand for the rest of the session.
#
# USAGE
# pi-devbox-version human-readable summary (default)
# pi-devbox-version --json raw manifest JSON (for scripting)
# pi-devbox-version --quiet one-line "release_tag (source_revision)" form
#
# EXIT STATUS
# 0 on success. 1 if the manifest is missing (e.g. an image built before
# this file existed, or a non-pi-devbox base) — prints a short notice
# to stderr rather than failing silently.
set -euo pipefail
MANIFEST=/etc/pi-devbox/build-manifest.json
MODE="human"
case "${1:-}" in
--json) MODE="json" ;;
--quiet|-q) MODE="quiet" ;;
--help|-h)
sed -n '2,20p' "$0" | sed 's/^# \?//'
exit 0
;;
esac
if [ ! -f "$MANIFEST" ]; then
echo "pi-devbox-version: no build manifest at $MANIFEST" >&2
echo " (image predates the manifest, or this isn't a pi-devbox image)" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "pi-devbox-version: jq not found; dumping raw manifest instead" >&2
cat "$MANIFEST"
exit 0
fi
if [ "$MODE" = "json" ]; then
cat "$MANIFEST"
exit 0
fi
release_tag=$(jq -r '.release_tag' "$MANIFEST")
build_date=$(jq -r '.build_date' "$MANIFEST")
source_rev=$(jq -r '.source_revision' "$MANIFEST")
pi_version_baked=$(jq -r '.pi_version' "$MANIFEST")
if [ "$MODE" = "quiet" ]; then
printf '%s (%s)\n' "$release_tag" "${source_rev:0:7}"
exit 0
fi
# Live drift check: has `pi` been upgraded since this container was built?
# (image is immutable, but a volume-persisted ~/.pi could in theory shadow
# the baked binary — this stays honest rather than trusting the manifest
# blindly, same "ground truth over intent" spirit as how the manifest
# itself is generated in Dockerfile.variant.)
pi_version_live=""
if command -v pi >/dev/null 2>&1; then
pi_version_live=$(pi --version 2>/dev/null | head -n1 | tr -d '\r\n')
fi
printf 'pi-devbox %s\n' "$release_tag"
printf ' built: %s (source %s)\n' "$build_date" "${source_rev:0:12}"
if [ -n "$pi_version_live" ] && [ "$pi_version_live" != "$pi_version_baked" ]; then
printf ' pi: %s \033[33m(baked as %s — drift detected)\033[0m\n' "$pi_version_live" "$pi_version_baked"
else
printf ' pi: %s\n' "${pi_version_live:-$pi_version_baked}"
fi
printf ' components:\n'
jq -r '.components | to_entries[] | select(.value != null) | " \(.key): \(.value[0:12])"' "$MANIFEST"
@@ -19,6 +19,18 @@ be discovered at runtime, never assumed. And interactive shell aliases
tool, so spell out the underlying command (e.g.
`ssh -F "$HOME/.ssh-local/config" mac …`).
## Browser automation is available (agent-browser)
This image bakes the **`agent-browser`** CLI plus a headless Chromium, so you can
drive a real browser — open pages, click/fill/`eval`, snapshot the DOM, take
screenshots — to **verify** front-end work (live DOM, WebGL, layout, popup
positioning) instead of guessing. Reach for it whenever a task involves a web UI
or checking how a page actually renders. `AGENT_BROWSER_EXECUTABLE_PATH` is
preset to the baked browser, so `agent-browser open <url>` works out of the box
(headless). Run `agent-browser skills get core --full` for the command set and
workflow patterns (always version-matched to the CLI); the `agent-browser` skill
under `~/.agents/skills/` mirrors it when the skillset is mounted.
## Session start: load the mempalace skill
If MemPalace MCP tools (e.g. `mempalace_search`, `mempalace_diary_write`) are in
@@ -91,6 +91,22 @@ are "command not found" there — you must spell out the underlying command.
If a command "works in my terminal but not when the agent runs it," this alias
gap is the first thing to suspect.
**`dscp`/`scp` with accented filenames on a macOS host.** macOS stores filenames
in Unicode **NFD** (decomposed — e.g. `ä` is `a` + combining U+0308), while the
string you type or paste is usually **NFC** (precomposed `ä`, U+00E4). The bytes
differ, so a precomposed remote path *silently* fails to match on the host —
`scp … "mac:'~/Desktop/Skärmavbild ….png'"` returns *No such file or directory*
even though the file plainly exists. Sidestep the encoding entirely: let the
**remote shell expand a wildcard**, or list the directory first and copy the
exact name it prints.
```sh
# glob dodges the NFC/NFD mismatch (the remote shell matches the real bytes):
scp -F "$HOME/.ssh-local/config" "mac:~/Desktop/Sk*rmavbild*.png" ./
# or read the exact filename first, then copy that:
ssh -F "$HOME/.ssh-local/config" mac 'ls -1 ~/Desktop/*.png'
```
## 3. Reaching the Docker host and its LAN over SSH
When the host is VM-backed (e.g. OrbStack / Docker Desktop on macOS) the
+12
View File
@@ -19,6 +19,8 @@
# - mempalace bridge symlink present
# - settings.json bootstrapped
# - pi-fork + pi-observational-memory registered via `pi install`
# - pi-devbox-version command present + wraps the build manifest correctly
# (human, --json, --quiet)
# - (studio variant only, auto-detected) pi-studio cloned + prebuilt
# client bundle present + registered via `pi install`
# - image size within threshold
@@ -159,6 +161,16 @@ run_expect "manifest records pi_version" \
# non-studio variant) — 'unknown' means a clone silently failed to resolve.
run "manifest has no unresolved ('unknown') components" \
"! grep -q '\"unknown\"' /etc/pi-devbox/build-manifest.json"
# pi-devbox-version wraps the manifest into a human-first command (this
# PR); verify the binary is present, executable, and both output modes work.
run "pi-devbox-version binary present + executable" \
"test -x /usr/local/bin/pi-devbox-version"
run_expect "pi-devbox-version human output shows release tag" \
"pi-devbox-version" "pi-devbox "
run_expect "pi-devbox-version --json round-trips the manifest" \
"pi-devbox-version --json" '"release_tag"'
run_expect "pi-devbox-version --quiet is a compact one-liner" \
"pi-devbox-version --quiet | wc -l" "1"
# OCI labels live in the image config, not the container fs — inspect them
# from the host docker rather than via `docker run`.
LBL=$(docker inspect --format '{{ index .Config.Labels "se.jordbo.pi-devbox.pi-extensions-ref" }}' "$IMAGE" 2>/dev/null || true)