5 Commits

Author SHA1 Message Date
pi e996b01542 docs: promote CHANGELOG Unreleased -> v0.78.0b
Publish Docker Image / smoke (push) Successful in 2m57s
Publish Docker Image / publish (push) Successful in 10m40s
Publish Docker Image / update-description (push) Successful in 9s
2026-06-03 22:59:37 +02:00
pi 03629cdac7 refactor: build FROM joakimp/pi-devbox:base-pi-only (Option B)
The pi-only building block now lives in this repo as the internal
base-pi-only tag (produced by opencode-devbox CI from Dockerfile.variant,
INSTALL_OPENCODE=false) instead of opencode-devbox:latest-pi-only — so an
'opencode-devbox' tag never ships without opencode.

- Dockerfile: BASE_IMAGE default joakimp/opencode-devbox:latest-pi-only
  -> joakimp/pi-devbox:base-pi-only.
- Updated README, AGENTS, DOCKER_HUB, docker-compose, CHANGELOG.
- Single source of truth unchanged (opencode-devbox/Dockerfile.variant);
  publish ordering + EXPECTED_PI_VERSION smoke guard unchanged.
2026-06-03 17:04:21 +02:00
pi 1d1283f942 refactor: FROM opencode-devbox:latest-pi-only (lean, no opencode)
Re-point the re-brand at the new pi-only variant instead of with-pi, so
pi-devbox stays a lean pi-focused image (no opencode) while the pi install
logic still lives in one place upstream. This keeps pi-devbox meaningfully
distinct from opencode-devbox:latest-with-pi.

- Dockerfile: BASE_IMAGE default -> joakimp/opencode-devbox:latest-pi-only.
- smoke-test.sh: size threshold 2900 -> 2750 MB (pi-only = with-pi minus
  opencode's ~145 MB binary).
- Docs (README/AGENTS/DOCKER_HUB/CHANGELOG/docker-compose): drop the
  'also contains opencode' notes; describe pi-only basis and the distinction
  from with-pi.

Publish ordering unchanged: release opencode-devbox first so latest-pi-only
carries the target pi version, then tag here (smoke asserts pi --version).
2026-06-03 16:14:05 +02:00
pi c139be326f refactor: re-brand the opencode-devbox with-pi variant (single source of truth)
pi-devbox no longer installs pi itself. The Dockerfile is now a thin
FROM joakimp/opencode-devbox:latest-with-pi (overridable via BASE_IMAGE),
inheriting pi + pi-toolkit + pi-extensions + pi-fork (fork) +
pi-observational-memory (recall) + the LAN-access helper + all base tooling
from the single source of truth. Eliminates the install-logic duplication
that drifted against opencode-devbox/Dockerfile.variant (decision #3).

Consequences (documented in CHANGELOG/AGENTS):
- The image now ALSO contains opencode (with-pi has INSTALL_OPENCODE=true).
  A leaner pi-only image would need a dedicated pi-only variant upstream.
- Publish ordering: release opencode-devbox first so latest-with-pi carries
  the target pi version, THEN tag this repo. The smoke test asserts
  pi --version matches the tag (EXPECTED_PI_VERSION) and fails loudly if the
  base is stale — turning the version coupling into an enforced ordering guard.

CI: drop PI_VERSION build-arg (Dockerfile installs nothing); keep tag->version
resolution to feed the smoke base-freshness guard. Smoke adds fork/recall
clone + node_modules + settings.json registration checks; size threshold
2200 -> 2900 MB (now tracks with-pi). Docs updated across README, AGENTS,
DOCKER_HUB, .env.example, docker-compose.
2026-06-03 15:51:41 +02:00
pi 1587a84579 Cut v0.78.0 — pi 0.77.0→0.78.0
Publish Docker Image / smoke (push) Successful in 2m24s
Publish Docker Image / publish (push) Successful in 13m13s
Publish Docker Image / update-description (push) Successful in 7s
2026-05-31 22:26:21 +02:00
9 changed files with 218 additions and 121 deletions
+14
View File
@@ -9,6 +9,20 @@ WORKSPACE_PATH=~/projects
# Path to SSH keys on host # Path to SSH keys on host
SSH_KEY_PATH=~/.ssh SSH_KEY_PATH=~/.ssh
# ── LAN access from the container (host-OS-agnostic) ─────────────────
# On VM-backed hosts (macOS OrbStack / Docker Desktop) the container can't
# reach the host's directly-attached LAN peers by default. The entrypoint
# then sets up the host as an SSH jump (use the `dssh` alias, or add
# `ProxyJump host` to targets in your bind-mounted ~/.ssh/config). On native
# Linux Docker the LAN is reachable directly and this is a no-op.
# See the opencode-devbox README for the full walkthrough.
#
# DEVBOX_LAN_ACCESS: auto (default) | jump | off
# DEVBOX_LAN_ACCESS=auto
# HOST_SSH_USER: your username on the host (required for the jump). On first
# start the entrypoint prints the public key to authorize on the host.
# HOST_SSH_USER=
# ── Git Configuration ──────────────────────────────────────────────── # ── Git Configuration ────────────────────────────────────────────────
GIT_USER_NAME= GIT_USER_NAME=
GIT_USER_EMAIL= GIT_USER_EMAIL=
+14 -25
View File
@@ -33,14 +33,13 @@ jobs:
- uses: docker/setup-buildx-action@v4 - uses: docker/setup-buildx-action@v4
with: {driver-opts: network=host} with: {driver-opts: network=host}
# Derive PI_VERSION from the tag (e.g. v0.75.5 -> 0.75.5; v0.75.5b -> 0.75.5). # Derive PI_VERSION from the tag (e.g. v0.78.0 -> 0.78.0; v0.78.0b -> 0.78.0).
# MUST be passed as a build-arg so Docker's layer cache invalidates when pi # Since the refactor to FROM opencode-devbox:latest-with-pi, this repo no
# is bumped. Without this, the bare `npm install -g <pkg>` in the Dockerfile # longer installs pi itself — pi comes from the base image. We still resolve
# produces an identical layer-hash across builds and the registry buildcache # the tag version and feed it to the smoke test as EXPECTED_PI_VERSION: the
# silently reuses the layer from whatever pi version was current when the # smoke asserts the inherited base actually carries this pi version, which
# cache was first populated. Discovered 2026-05-23 — every pi-devbox release # turns the version coupling into an enforced publish-ordering guard (it
# since v0.74.0 had been shipping the same image bytes (manifest digests # fails loudly if latest-with-pi is stale relative to this tag).
# identical across v0.74.0..v0.75.5 on both arches).
- name: Resolve PI_VERSION from tag - name: Resolve PI_VERSION from tag
id: resolve id: resolve
run: | run: |
@@ -58,8 +57,6 @@ jobs:
push: false push: false
load: true load: true
tags: pi-devbox:smoke tags: pi-devbox:smoke
build-args: |
PI_VERSION=${{ steps.resolve.outputs.pi_version }}
- name: Smoke test - name: Smoke test
env: env:
@@ -102,7 +99,8 @@ jobs:
echo "EOF" echo "EOF"
} >> "$GITHUB_OUTPUT" } >> "$GITHUB_OUTPUT"
# See the smoke job for why this is required (cache-hit silent regression). # See the smoke job for why the tag version is resolved (now used only for
# the base-freshness smoke guard; pi is no longer installed in this repo).
- name: Resolve PI_VERSION from tag - name: Resolve PI_VERSION from tag
id: resolve id: resolve
run: | run: |
@@ -115,7 +113,6 @@ jobs:
- name: Build and push (amd64 + arm64) — with retry - name: Build and push (amd64 + arm64) — with retry
shell: bash shell: bash
env: env:
PI_VERSION: ${{ steps.resolve.outputs.pi_version }}
TAGS: ${{ steps.tags.outputs.tags }} TAGS: ${{ steps.tags.outputs.tags }}
run: | run: |
set -euo pipefail set -euo pipefail
@@ -125,24 +122,16 @@ jobs:
while IFS= read -r t; do [[ -n "$t" ]] && TAG_FLAGS+=( -t "$t" ); done <<< "${TAGS}" while IFS= read -r t; do [[ -n "$t" ]] && TAG_FLAGS+=( -t "$t" ); done <<< "${TAGS}"
# 3-attempt retry around `docker buildx build --push` for transient # 3-attempt retry around `docker buildx build --push` for transient
# registry-1.docker.io blips (rate limits, CDN flap, brief 5xx). # registry-1.docker.io blips (rate limits, CDN flap, brief 5xx).
# Does NOT mask deterministic failures: a true regression (e.g. the # The build itself is now trivial (FROM opencode-devbox:latest-with-pi
# cache-export 400 hit 2026-05-23..28) will fail all 3 attempts # + an empty layer) so it is fast even without registry cache.
# identically and the job still fails — by design. # Registry cache stays disabled (buildkit mode=max cache-export hits a
# Registry cache disabled: buildkit's mode=max cache-export to # reproducible HTTP 400 from Hub CDN since ~2026-05-23; image push is
# registry-1.docker.io reproducibly returns HTTP 400 on resumable- # unaffected). See opencode-devbox CHANGELOG v1.15.12.
# upload PUT (Hub-CDN protocol mismatch with buildx 0.34.x, surfaced
# ~2026-05-23). Diagnosed during opencode-devbox v1.15.12 manual
# publish: image push works fine, only --cache-to fails. See
# opencode-devbox CHANGELOG v1.15.12 Unreleased section for full
# root-cause analysis. Re-enable when buildkit upstream resolves.
# Single-stage Dockerfile + tiny diff (npm install pi only) means
# build is fast even without cache (~30-60s).
for attempt in 1 2 3; do for attempt in 1 2 3; do
echo "==> Build+push attempt ${attempt}/3" echo "==> Build+push attempt ${attempt}/3"
if docker buildx build \ if docker buildx build \
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
--push \ --push \
--build-arg "PI_VERSION=${PI_VERSION}" \
"${TAG_FLAGS[@]}" \ "${TAG_FLAGS[@]}" \
.; then .; then
echo "==> Attempt ${attempt} succeeded" echo "==> Attempt ${attempt} succeeded"
+17 -16
View File
@@ -1,10 +1,11 @@
# AGENTS.md — pi-devbox # AGENTS.md — pi-devbox
Container image that adds pi coding-agent on top of the opencode-devbox base image. Container image that re-brands the opencode-devbox **pi-only** variant as a
pi-focused image. As of 2026-06-03 it no longer installs pi itself.
## Repository layout ## Repository layout
- `Dockerfile`single-stage build, `FROM opencode-devbox:base-latest`, installs pi + companion repos - `Dockerfile`thin re-brand: `FROM joakimp/pi-devbox:base-pi-only` (overridable via `BASE_IMAGE` arg). No install logic of its own — pi + companions are inherited from the pi-only build (built `INSTALL_OPENCODE=false`, so **no opencode** — that's the distinction from `opencode-devbox:latest-with-pi`). The `base-pi-only` tag is produced by opencode-devbox CI (from `opencode-devbox/Dockerfile.variant`) but published into THIS repo as an internal building-block tag. This refactor removed the install-logic duplication that used to drift against `opencode-devbox/Dockerfile.variant`.
- `docker-compose.yml` — compose file for local use - `docker-compose.yml` — compose file for local use
- `.env.example` — environment variable template - `.env.example` — environment variable template
- `scripts/smoke-test.sh` — sanity checks run by CI before pushing to Docker Hub - `scripts/smoke-test.sh` — sanity checks run by CI before pushing to Docker Hub
@@ -13,12 +14,12 @@ Container image that adds pi coding-agent on top of the opencode-devbox base ima
## Versioning scheme ## Versioning scheme
- Tags follow the pi npm version: `v{pi_version}[letter]` - Tags follow the pi npm version: `v{pi_version}[letter]`
- Bump `PI_VERSION` build-arg default in `Dockerfile` when cutting a new release - The image inherits pi from `base-pi-only`, so the **publish ordering matters**: rebuild opencode-devbox first so `joakimp/pi-devbox:base-pi-only` carries the target pi version, *then* tag this repo. The smoke test asserts `pi --version` matches the tag (`EXPECTED_PI_VERSION`) and fails loudly if the base is stale.
- Docker Hub: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest` - Docker Hub: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest`
## Release-day checklist ## Release-day checklist
1. Bump `PI_VERSION` in `Dockerfile` (or leave as `latest` to pick up current) 1. Ensure opencode-devbox has been released so `joakimp/pi-devbox:base-pi-only` carries the target pi version (and the fork/recall extensions). This is the hard prerequisite — the smoke guard enforces it.
2. Update `CHANGELOG.md`: promote `Unreleased``vX.Y.Z — YYYY-MM-DD` 2. Update `CHANGELOG.md`: promote `Unreleased``vX.Y.Z — YYYY-MM-DD`
3. Add fresh `## Unreleased` section 3. Add fresh `## Unreleased` section
4. Commit, tag `vX.Y.Z`, push tag → CI fires automatically 4. Commit, tag `vX.Y.Z`, push tag → CI fires automatically
@@ -38,27 +39,27 @@ per version. Don't try to derive notes from the npm registry metadata
## Key facts ## Key facts
- **Base image**: `joakimp/opencode-devbox:base-latest` — rebuilt whenever opencode-devbox cuts a new base - **Base image**: `joakimp/pi-devbox:base-pi-only` — an internal building-block tag (produced by opencode-devbox CI from `Dockerfile.variant`, the single source of truth for the pi install + companions; published into this repo, not under opencode-devbox). Rebuilt whenever opencode-devbox releases. Not for end users — they pull `joakimp/pi-devbox:latest` or a `vX.Y.Z` tag.
- **pi binary**: baked at `/usr/bin/pi` (system npm prefix); `NPM_CONFIG_PREFIX=/home/developer/.pi/npm-global` at runtime so user-installed pi/packages land on the named volume - **Inherited content**: pi (`/usr/bin/pi`), pi-toolkit, pi-extensions, pi-fork (`fork`), pi-observational-memory (`recall`), the mempalace bridge, the LAN-access helper, entrypoints, and all base dev tooling. The pi-only variant is built `INSTALL_OPENCODE=false`, so the image does **not** contain opencode.
- **Companion repos**: pi-toolkit and pi-extensions cloned to `/opt/` at build time; `entrypoint-user.sh` (inherited from base) deploys symlinks to `~/.pi/agent/` on container start - **Companion repos**: cloned to `/opt/` by the pi-only build; `entrypoint-user.sh` (inherited) deploys/registers them on container start.
- **MemPalace**: fully operational — inherited from base image; bridge extension deployed by entrypoint - **MemPalace**: fully operational — inherited from base; bridge extension deployed by entrypoint.
## Conventions ## Conventions
- Do NOT call `mempalace-toolkit/install.sh` in the Dockerfile — the base entrypoint handles it - This repo no longer installs pi or clones companion repos — do **not** re-add that logic here. Change it in `opencode-devbox/Dockerfile.variant` (the single source of truth) instead.
- `NPM_CONFIG_PREFIX=/usr` must be set per-RUN for any build-time `npm install -g` to keep baked binaries off the volume-shadowed path - The smoke test threshold is 2750 MB (tracks the pi-only variant) — update if the image legitimately grows past it.
- The smoke test threshold is 2200 MB — update if the image legitimately grows past it - The CI still resolves the tag's pi version, but only to feed `EXPECTED_PI_VERSION` to the smoke base-freshness guard — it is no longer passed as a build-arg (nothing in the Dockerfile consumes it).
- **PI_VERSION must be passed explicitly by CI as a concrete version** (derived from the git tag), not left as the `latest` default. The Dockerfile's bare `npm install -g @earendil-works/pi-coding-agent` (without `@${PI_VERSION}`) produces an identical layer-hash across builds; combined with registry buildcache (`cache-from`/`cache-to`) the layer gets reused even when `latest` would have resolved to a newer pi version. **All releases v0.74.0 → v0.75.5 silently shipped the same image bytes** because of this (verified via `docker manifest inspect` — identical digests across both arches and all four tags). Fixed in v0.75.5b: workflow now derives `PI_VERSION` from `${{ github.ref_name }}` and passes it as a build-arg; smoke-test asserts the resulting `pi --version` matches via `EXPECTED_PI_VERSION` env var. Same latent bug exists in opencode-devbox's `with-pi` variants but is masked there because `OPENCODE_VERSION` bumps invalidate downstream layers — will only manifest when cutting a `vN.N.Nb`-style opencode-version-unchanged release that only bumps pi. - To pin a specific base build instead of tracking `base-pi-only`, override the `BASE_IMAGE` arg (a `base-pi-only-vX.Y.Z` tag or a digest).
## Documentation drift sweep ## Documentation drift sweep
Before committing any non-trivial change, check that prose still matches code. Drift hotspots in this repo: Before committing any non-trivial change, check that prose still matches code. Drift hotspots in this repo:
- `README.md` — quick-start examples, env-var table, base-image reference (must match `FROM` in `Dockerfile`). - `README.md` — quick-start examples, env-var table, base-image reference (must match `FROM` in `Dockerfile`), "what's inside" (fork/recall; no opencode).
- `AGENTS.md` (this file) — `Key facts` block (pi binary path, `NPM_CONFIG_PREFIX`, base-image tag), smoke-test threshold number. - `AGENTS.md` (this file) — `Key facts` block (base-image tag, inherited content), smoke-test threshold number.
- `CHANGELOG.md` — promote `Unreleased` only on tag, but record post-release fixes in a fresh `Unreleased` block. - `CHANGELOG.md` — promote `Unreleased` only on tag, but record post-release fixes in a fresh `Unreleased` block.
- `DOCKER_HUB.md` — hand-maintained slim Hub description; sync anything user-facing that changes (env vars, run command, base image). - `DOCKER_HUB.md` — hand-maintained slim Hub description; sync anything user-facing that changes (env vars, run command, base image).
- `.env.example` — hand-updated, must match Dockerfile/entrypoint env vars. - `.env.example` — hand-updated, must match Dockerfile/entrypoint env vars (including the inherited LAN-access knobs).
- `Dockerfile` `PI_VERSION` ARG default — if you intend to pin (rather than `latest`), bump it on release. - `Dockerfile` `BASE_IMAGE` ARG default — the pi-only tag this image tracks.
Quick triage: `git diff --name-only HEAD | xargs -I{} grep -l 'thing-you-changed' README.md AGENTS.md DOCKER_HUB.md CHANGELOG.md .env.example`. Quick triage: `git diff --name-only HEAD | xargs -I{} grep -l 'thing-you-changed' README.md AGENTS.md DOCKER_HUB.md CHANGELOG.md .env.example`.
+71 -1
View File
@@ -8,7 +8,77 @@ Tags follow the pi npm version: `v{pi_version}[letter]` — bare tag for the fir
## Unreleased ## Unreleased
_(no changes since v0.77.0)_ _(no changes since v0.78.0b)_
## v0.78.0b — 2026-06-03
Container-level rebuild on pi `0.78.0` (unchanged): re-brands the pi-only build
as a thin `FROM joakimp/pi-devbox:base-pi-only`, inheriting fork/recall and
host-OS-agnostic LAN access. Letter-suffix release (pi version unchanged).
### Changed: refactored to re-brand the opencode-devbox `pi-only` variant
pi-devbox no longer installs pi itself. The `Dockerfile` is now a thin
`FROM joakimp/pi-devbox:base-pi-only` (overridable via the `BASE_IMAGE`
arg), inheriting pi + pi-toolkit + pi-extensions and all base tooling from the
single source of truth. This eliminates the install-logic duplication that
used to drift against `opencode-devbox/Dockerfile.variant`.
The pi-only artifact is **built** by opencode-devbox's CI (from
`opencode-devbox/Dockerfile.variant` with `INSTALL_OPENCODE=false`) but is
**published into this repo** as the internal building-block tag
`joakimp/pi-devbox:base-pi-only` (+ `base-pi-only-vX.Y.Z`, where `vX.Y.Z` is
the opencode-devbox release version). This supersedes the brief approach of
publishing it as `opencode-devbox:latest-pi-only` — an "opencode-devbox" tag
with no opencode in it confused users. `base-pi-only` is internal; end users
pull `joakimp/pi-devbox:latest` or a `vX.Y.Z` tag.
The pi-only build uses `INSTALL_OPENCODE=false`, so this image
stays lean and pi-focused — it does **not** carry opencode, and remains
distinct from `opencode-devbox:latest-with-pi` (which has both).
### Added (inherited from the pi-only variant)
- **`fork` tool** (pi-fork) and **`recall` tool** (pi-observational-memory),
baked into `/opt` with `node_modules` and registered at runtime.
- **Host-OS-agnostic LAN access**: on VM-backed hosts (macOS OrbStack /
Docker Desktop) the entrypoint sets up the host as an SSH jump to reach LAN
peers (`dssh` alias; `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` env). No-op on
native Linux. See the opencode-devbox README for details.
### Consequences / notes
- **Publish ordering**: release opencode-devbox first so `base-pi-only`
carries the target pi version, *then* tag this repo. The smoke test asserts
`pi --version` matches the tag and fails loudly if the base is stale.
- CI no longer passes `PI_VERSION` as a build-arg (the Dockerfile installs
nothing); it still resolves the tag version to feed the smoke base-freshness
guard. Smoke size threshold 2200 → 2750 MB (now tracks the pi-only variant).
_pi version unchanged at `0.78.0` (still latest)._
## v0.78.0 — 2026-05-29
pi `0.77.0``0.78.0` bump (first container build on the pi 0.78 line, published upstream 2026-05-29). Built against `joakimp/opencode-devbox:base-latest` (unchanged from the v0.77.0 build).
### Bumped: pi 0.77.0 → 0.78.0
**New Features**
- **Named startup sessions** — `--name` / `-n` sets the session display name before startup across interactive, print, JSON, and RPC modes.
- **Clickable file tool paths** — built-in file tool titles render OSC 8 `file://` hyperlinks when the terminal supports them, including supported tmux clients.
**Added**
- Exported `convertToPng` for extension authors.
- Exported `parseArgs` and type `Args` for extension authors.
- Added a resume command hint when exiting interactive sessions.
- Added custom Amazon Bedrock request header support.
**Fixed**
- Fixed early interactive input typed before the prompt loop starts so it is buffered instead of dropped.
- Fixed OpenRouter Moonshot Kimi K2.6 requests to use `system` instead of unsupported `developer` messages.
- Fixed OSC 8 hyperlinks to pass through tmux when the client supports them.
- Fixed ANSI text wrapping to avoid stack overflows on very long wrapped lines.
- Fixed OpenAI Codex Responses SSE streams to abort response body reads after terminal events.
## v0.77.0 — 2026-05-29 ## v0.77.0 — 2026-05-29
+19 -7
View File
@@ -38,7 +38,17 @@ Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini
## What's inside ## What's inside
Inherited from [opencode-devbox base](https://hub.docker.com/r/joakimp/opencode-devbox): pi-devbox is a re-brand of the **pi-only build** — it builds
`FROM joakimp/pi-devbox:base-pi-only` and adds no layers of its own. That
building-block tag is produced by opencode-devbox's CI (from
`Dockerfile.variant` with `INSTALL_OPENCODE=false`) but published here, in the
pi-devbox repo, so an opencode-devbox tag never ships without opencode.
The pi-only build is lean
and pi-focused (no opencode — use `opencode-devbox:latest-with-pi` if you want
both).
Everything below is inherited from that single source of truth.
Base tooling:
- **Debian trixie** (latest stable) - **Debian trixie** (latest stable)
- **Node.js** (LTS), **uv** (Python tooling), **rustup** (Rust on-demand) - **Node.js** (LTS), **uv** (Python tooling), **rustup** (Rust on-demand)
@@ -47,19 +57,21 @@ Inherited from [opencode-devbox base](https://hub.docker.com/r/joakimp/opencode-
- **Gitea MCP** server - **Gitea MCP** server
- **Dev tools**: neovim (LazyVim defaults), tmux, bat, eza, fzf, zoxide, ripgrep, git-lfs, make - **Dev tools**: neovim (LazyVim defaults), tmux, bat, eza, fzf, zoxide, ripgrep, git-lfs, make
- **Shell**: bash with history tuning, prefix-search bindings, fzf/zoxide integration - **Shell**: bash with history tuning, prefix-search bindings, fzf/zoxide integration
- **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the host is set up as an SSH jump to reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS`/`HOST_SSH_USER`). No-op on native Linux.
Added by pi-devbox: pi and companions:
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned at build time via the `PI_VERSION` build-arg - **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version set by the pi-only base build
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template - **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle` (manage extensions interactively), `mcp-loader` (load MCP servers via settings.json), `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive` - **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
- **mempalace bridge** — MCP extension auto-symlinked from `/opt/mempalace-toolkit` so pi can read/write the same palace as opencode - **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools
- **mempalace bridge** — MCP extension auto-symlinked so pi can read/write the same palace as opencode-devbox
The entrypoint deploys all of these on first container start. Re-running is idempotent and preserves user edits. The entrypoint deploys/registers all of these on first container start. Re-running is idempotent and preserves user edits.
## Versioning ## Versioning
Tags follow the pi npm version: `v0.74.0`, `v0.75.0`, etc. `latest` always points at the most recent release. When pi cuts a new upstream version, this image is rebuilt and re-tagged to match. Tags follow the pi npm version: `v0.74.0`, `v0.75.0`, etc. `latest` always points at the most recent release. The pi binary is inherited from `joakimp/pi-devbox:base-pi-only`, so each release follows an opencode-devbox release that bakes the target pi version. (`base-pi-only` is an internal building-block tag — pull `latest` or a `vX.Y.Z` tag instead.)
For container-level rebuilds on the same pi version (security updates, base bumps, fixes) the tag gets a letter suffix: `v0.74.0b`, `v0.74.0c`, … For container-level rebuilds on the same pi version (security updates, base bumps, fixes) the tag gets a letter suffix: `v0.74.0b`, `v0.74.0c`, …
+29 -56
View File
@@ -1,62 +1,35 @@
# pi-devbox — pi coding-agent container # pi-devbox — pi coding-agent container
# #
# Builds on top of the opencode-devbox base image, which provides: # As of 2026-06-03 this image is a thin re-brand of the "pi-only" build, which
# Debian trixie, Node.js, AWS CLI, mempalace + MCP server, gitea-mcp, # is the SINGLE SOURCE OF TRUTH for the pi install and its companion repos
# dev tools (neovim, tmux, bat, eza, fzf, zoxide, ripgrep, uv, rustup, # (pi-toolkit, pi-extensions, pi-fork, pi-observational-memory). That build is
# git-crypt, gitleaks), # produced by opencode-devbox's CI (from opencode-devbox/Dockerfile.variant
# user setup (developer/gosu), entrypoints, chromadb prewarm. # with INSTALL_OPENCODE=false), but is published as an INTERNAL building-block
# tag in THIS repo — joakimp/pi-devbox:base-pi-only — NOT under opencode-devbox.
# Rationale: an "opencode-devbox" tag containing no opencode confuses
# opencode-devbox users, so the pi-only artifact lives here instead.
# Previously pi-devbox/Dockerfile duplicated the install logic, which drifted
# from opencode-devbox/Dockerfile.variant; this refactor eliminates the dup.
# #
# This image adds only pi itself and its companion repos. # The pi-only build uses INSTALL_OPENCODE=false, so this image does NOT contain
# opencode — it stays a lean, pi-focused image, distinct from
# opencode-devbox:latest-with-pi (which carries both).
# #
# Build args: # Everything is inherited from the pi-only build:
# BASE_IMAGE — base image to build from (default: base-latest) # pi + pi-toolkit + pi-extensions + pi-fork (fork) + pi-observational-memory
# PI_VERSION — pi npm version: "latest" or a pinned version e.g. "0.74.0" # (recall), the mempalace bridge, the LAN-access helper, entrypoints, and
# PI_TOOLKIT_REF — git ref for pi-toolkit (default: main) # all base dev tooling.
# PI_EXTENSIONS_REF — git ref for pi-extensions (default: main) #
# NOTE on PUBLISH ORDERING: rebuild opencode-devbox (so `base-pi-only` carries
ARG BASE_IMAGE=joakimp/opencode-devbox:base-latest # the target pi version) BEFORE tagging this repo. The smoke test asserts
# `pi --version` matches this repo's tag and fails loudly if the base is stale
# — turning the version coupling into an enforced ordering check.
#
# base-pi-only is an internal building-block alias (existence-only, not for
# end users — pull joakimp/pi-devbox:latest or a vX.Y.Z tag instead). Override
# BASE_IMAGE to pin a specific pi-only build (a version tag or a digest).
ARG BASE_IMAGE=joakimp/pi-devbox:base-pi-only
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
# PI_VERSION should be passed explicitly by CI as a concrete version # WORKDIR / ENTRYPOINT / CMD and all tooling inherited from the base.
# (e.g. PI_VERSION=0.75.5, derived from the git tag). The default `latest` # No additional layers — the value here is the single-source-of-truth refactor.
# is for local dev convenience only — it has a known cache-hit footgun
# when used in registry-cached CI builds. See .gitea/workflows/docker-
# publish.yml § "Resolve PI_VERSION from tag" and AGENTS.md gotcha for
# the full story (silent same-bytes-across-releases regression discovered
# 2026-05-23 affecting all builds v0.74.0..v0.75.5).
ARG PI_VERSION=latest
ARG PI_TOOLKIT_REF=main
ARG PI_EXTENSIONS_REF=main
# Install pi and clone companion repos.
# NPM_CONFIG_PREFIX is overridden to /usr so the baked binary lands at the
# system prefix — same pattern as opencode-devbox's variant Dockerfile.
# At runtime, NPM_CONFIG_PREFIX is reset to /home/developer/.pi/npm-global
# (inherited from base ENV) so user-installed packages land on the named
# volume and survive container recreate.
#
# git clone is wrapped in a retry loop because gitea.jordbo.se occasionally
# returns transient HTTP 500s on the first request after idle.
RUN set -e && \
git_clone_retry() { \
url="$1"; ref="$2"; dest="$3"; \
for i in 1 2 3 4 5; do \
if git clone --depth 1 --branch "$ref" "$url" "$dest"; then return 0; fi; \
rm -rf "$dest"; \
echo "git clone $url failed (attempt $i/5), retrying in $((i*5))s..."; \
sleep $((i*5)); \
done; \
return 1; \
} && \
if [ "${PI_VERSION}" = "latest" ]; then \
NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent ; \
else \
NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent@${PI_VERSION} ; \
fi && \
pi --version && \
git_clone_retry https://gitea.jordbo.se/joakimp/pi-toolkit.git "${PI_TOOLKIT_REF}" /opt/pi-toolkit && \
git_clone_retry https://gitea.jordbo.se/joakimp/pi-extensions.git "${PI_EXTENSIONS_REF}" /opt/pi-extensions && \
echo "pi-toolkit at $(cd /opt/pi-toolkit && git rev-parse --short HEAD)" && \
echo "pi-extensions at $(cd /opt/pi-extensions && git rev-parse --short HEAD)"
# WORKDIR / ENTRYPOINT / CMD inherited from base.
+30 -14
View File
@@ -9,7 +9,16 @@ A Docker container with [pi coding-agent](https://github.com/earendil-works/pi)
## What's inside ## What's inside
Inherited from `opencode-devbox:base-latest`: pi-devbox is a thin re-brand of the **`pi-only` build** — it `FROM`s
`joakimp/pi-devbox:base-pi-only` and adds no layers of its own. That base build
is produced by opencode-devbox's CI (from `opencode-devbox/Dockerfile.variant`
with `INSTALL_OPENCODE=false`, the single source of truth for the pi install +
companions) but is published **into this repo** as the internal building-block
tag `base-pi-only`*not* under opencode-devbox, so an "opencode-devbox" tag
never ships without opencode. Everything below is inherited from that build,
which is lean and pi-focused — no opencode.
Base tooling:
- **Debian trixie** (current stable) - **Debian trixie** (current stable)
- **Node.js** (LTS), **uv** (Python), **rustup** (Rust on-demand) - **Node.js** (LTS), **uv** (Python), **rustup** (Rust on-demand)
@@ -18,15 +27,19 @@ Inherited from `opencode-devbox:base-latest`:
- **Gitea MCP** server - **Gitea MCP** server
- **Dev tools**: neovim (LazyVim), tmux, bat, eza, fzf, zoxide, ripgrep, jq, git-lfs, make - **Dev tools**: neovim (LazyVim), tmux, bat, eza, fzf, zoxide, ripgrep, jq, git-lfs, make
- **Shell**: bash with history tuning, prefix-search, fzf/zoxide integration - **Shell**: bash with history tuning, prefix-search, fzf/zoxide integration
- **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the entrypoint sets up the host as an SSH jump so you can reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS`/`HOST_SSH_USER` env). No-op on native Linux.
Added by pi-devbox: pi and companions:
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned at build time - **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned by the pi-only base build
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — mosh/tmux-friendly keybindings (Shift+Enter, Ctrl+J, Alt+J newline), AWS env loader, settings template - **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — mosh/tmux-friendly keybindings (Shift+Enter, Ctrl+J, Alt+J newline), AWS env loader, settings template
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive` - **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
- **mempalace bridge** — auto-symlinked MCP extension so pi reads/writes the same palace as opencode - **`fork` tool** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall` tool** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) — baked into `/opt` and registered at runtime
- **mempalace bridge** — auto-symlinked MCP extension so pi reads/writes the same palace as opencode-devbox's palace
The entrypoint deploys all of these on first container start. Idempotent and preserves user edits. (opencode itself is **not** included — that's the difference from `opencode-devbox:latest-with-pi`. If you want both opencode and pi in one image, use that variant instead.)
The entrypoint deploys/registers all of these on first container start. Idempotent and preserves user edits.
--- ---
@@ -187,6 +200,8 @@ All config flows through `.env`. The full list (with annotations) is in [`.env.e
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | (unset) | AWS Bedrock static creds | | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | (unset) | AWS Bedrock static creds |
| `GITEA_ACCESS_TOKEN` / `GITEA_HOST` | (unset) | Gitea MCP server (optional) | | `GITEA_ACCESS_TOKEN` / `GITEA_HOST` | (unset) | Gitea MCP server (optional) |
| `GITHUB_PERSONAL_ACCESS_TOKEN` | (unset) | GitHub MCP server / git ops over HTTPS | | `GITHUB_PERSONAL_ACCESS_TOKEN` | (unset) | GitHub MCP server / git ops over HTTPS |
| `DEVBOX_LAN_ACCESS` | `auto` | LAN-access mode: `auto` (jump only on VM-backed hosts), `jump`, `off` |
| `HOST_SSH_USER` | (unset) | Host username for the LAN SSH jump (see opencode-devbox README) |
| `LANG` / `LANGUAGE` / `LC_ALL` | `en_US.UTF-8` | Locale override | | `LANG` / `LANGUAGE` / `LC_ALL` | `en_US.UTF-8` | Locale override |
--- ---
@@ -197,34 +212,35 @@ Tags follow the pi npm package version: `v0.74.0`, `v0.75.0`, … `latest` alway
Container-level rebuilds on the same pi version (security updates, base bumps, fixes) get a letter suffix: `v0.74.0b`, `v0.74.0c`, … Container-level rebuilds on the same pi version (security updates, base bumps, fixes) get a letter suffix: `v0.74.0b`, `v0.74.0c`, …
When the upstream [pi npm package](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) cuts a new version, this image is rebuilt and re-tagged to match. The pi binary is inherited from `joakimp/pi-devbox:base-pi-only`, so a release of this image must be preceded by an opencode-devbox release that bakes the target pi version into `base-pi-only`. The smoke test enforces this (it asserts `pi --version` matches the tag).
--- ---
## Building from source ## Building from source
If you want to pin a specific pi version, change the base image, or hack on the Dockerfile: This image is a thin re-brand of the pi-only variant, so building it just pulls
the base. To pin a specific pi-only build or hack on it:
```bash ```bash
git clone https://gitea.jordbo.se/joakimp/pi-devbox git clone https://gitea.jordbo.se/joakimp/pi-devbox
cd pi-devbox cd pi-devbox
# Edit Dockerfile or override via build args: # Default tracks base-pi-only; override BASE_IMAGE to pin a build:
docker compose build \ docker compose build \
--build-arg PI_VERSION=0.73.0 \ --build-arg BASE_IMAGE=joakimp/pi-devbox:base-pi-only-v1.15.13c
--build-arg BASE_IMAGE=joakimp/opencode-devbox:base-latest
docker compose up -d docker compose up -d
``` ```
To change the pi version, the pi extensions, or the install logic, edit
`opencode-devbox/Dockerfile.variant` (the single source of truth) and release
opencode-devbox — not this repo.
Build args supported: Build args supported:
| Arg | Default | Effect | | Arg | Default | Effect |
|---|---|---| |---|---|---|
| `BASE_IMAGE` | `joakimp/opencode-devbox:base-latest` | Parent image — set to `joakimp/opencode-devbox:base-<sha>` for reproducible builds | | `BASE_IMAGE` | `joakimp/pi-devbox:base-pi-only` | Parent image (internal building-block tag) — set to a `:base-pi-only-vX.Y.Z` tag or a digest for reproducible builds |
| `PI_VERSION` | `latest` | npm version of `@earendil-works/pi-coding-agent` |
| `PI_TOOLKIT_REF` | `main` | Git ref for [pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit) |
| `PI_EXTENSIONS_REF` | `main` | Git ref for [pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions) |
--- ---
+2 -1
View File
@@ -17,7 +17,8 @@ services:
# build: # build:
# context: . # context: .
# args: # args:
# PI_VERSION: "latest" # # Pin a specific pi-only build instead of tracking base-pi-only:
# BASE_IMAGE: "joakimp/pi-devbox:base-pi-only-v1.15.13c"
container_name: pi-devbox container_name: pi-devbox
stdin_open: true stdin_open: true
tty: true tty: true
+22 -1
View File
@@ -17,7 +17,10 @@ set -euo pipefail
IMAGE="${1:?usage: $0 <image>}" IMAGE="${1:?usage: $0 <image>}"
PASS=0; FAIL=0 PASS=0; FAIL=0
SIZE_THRESHOLD_MB=2200 # Since the refactor to FROM opencode-devbox:latest-pi-only, this image equals
# the pi-only variant (pi + companions + fork/recall node_modules, NO opencode),
# so the threshold tracks pi-only's (2750 MB), not the old standalone 2200 MB.
SIZE_THRESHOLD_MB=2750
run() { run() {
local label="$1"; local cmd="$2" local label="$1"; local cmd="$2"
@@ -67,6 +70,12 @@ echo ""
echo "── Repo clones ──" echo "── Repo clones ──"
run "pi-toolkit clone" "test -d /opt/pi-toolkit && git -C /opt/pi-toolkit rev-parse --short HEAD" run "pi-toolkit clone" "test -d /opt/pi-toolkit && git -C /opt/pi-toolkit rev-parse --short HEAD"
run "pi-extensions clone" "test -d /opt/pi-extensions && git -C /opt/pi-extensions rev-parse --short HEAD" run "pi-extensions clone" "test -d /opt/pi-extensions && git -C /opt/pi-extensions rev-parse --short HEAD"
# pi-fork (fork tool) + pi-observational-memory (recall tool) — inherited from
# the pi-only base, cloned to /opt with node_modules baked at build time.
run "pi-fork clone + node_modules" \
"test -f /opt/pi-fork/package.json && test -d /opt/pi-fork/node_modules"
run "pi-observational-memory clone + node_modules" \
"test -f /opt/pi-observational-memory/package.json && test -d /opt/pi-observational-memory/node_modules"
# ── Runtime deployment (needs entrypoint to run) ────────────────────── # ── Runtime deployment (needs entrypoint to run) ──────────────────────
echo "" echo ""
@@ -101,6 +110,18 @@ exec_test "extensions ≥ 4 (pi-extensions)" 'count=$(ls -1 $HOME/.pi/age
exec_test "mempalace.ts bridge" 'test -L $HOME/.pi/agent/extensions/mempalace.ts && echo ok' exec_test "mempalace.ts bridge" 'test -L $HOME/.pi/agent/extensions/mempalace.ts && echo ok'
exec_test "settings.json bootstrapped" 'test -f $HOME/.pi/agent/settings.json && echo ok' exec_test "settings.json bootstrapped" 'test -f $HOME/.pi/agent/settings.json && echo ok'
# pi-fork + pi-observational-memory are registered by entrypoint-user.sh via
# `pi install /opt/<pkg>`, which runs slightly after the keybindings marker.
for i in $(seq 1 15); do
if docker exec "$CID" grep -q pi-observational-memory \
/home/developer/.pi/agent/settings.json 2>/dev/null; then
break
fi
sleep 1
done
exec_test "pi-fork registered (fork tool)" 'grep -q pi-fork $HOME/.pi/agent/settings.json && echo ok'
exec_test "pi-observational-memory registered (recall tool)" 'grep -q pi-observational-memory $HOME/.pi/agent/settings.json && echo ok'
# ── Image size ──────────────────────────────────────────────────────── # ── Image size ────────────────────────────────────────────────────────
echo "" echo ""
echo "── Image size ──" echo "── Image size ──"