@geut/pi-cowork-mode
v0.1.0
Published
Pi extension for operating a long-lived Docker Compose cowork environment.
Readme
pi-cowork-mode
A pi extension for operating a long-lived Docker Compose "cowork" environment for local development and testing.
The extension does not author Dockerfiles or compose files. It expects a cowork service that was scaffolded by the separate cowork-mode skill (or created manually). Install that skill too if you want the consent-gated scaffolding flow.
What it gives you
- Eight tools the agent can call:
cowork_up,cowork_status,cowork_exec,cowork_test,cowork_run,cowork_logs,cowork_down,cowork_reset. /coworkcommand namespace for humans:/cowork up|status|test|run|logs|down|shell.- A footer one-liner (always on, added to pi's default footer via
setStatus— pi's model/branch/tokens/provider info is preserved) and an expanded status widget (shown as a non-capturing overlay by/cowork status, closed with Escape) with container state, published ports with URLs, and the last test result. - Persisted runtime state (
.pi/cowork-state.json) so the last test result and container state survive restarts. - Lazy + event-driven refresh via a
docker eventsstream (auto-restart with backoff). No polling loop. - Consent-gated scaffolding handoff to the separate
cowork-modeskill.
Prerequisites
Install the companion skill so the agent can scaffold a cowork service when one does not exist:
npx skills add geut/cowork-mode-skillThe skill is standalone and works without pi; the extension is pi-only.
Install
pi install npm:@geut/pi-cowork-modeDiscovery rules
The extension is convention-driven:
- Find
docker-compose.yml/docker-compose.yaml/compose.yml/compose.yamlin the project root (and ancestors up to the git repo root). - Look for a service named
cowork(or theserviceoverride in.pi/cowork.json). - If a cowork service already exists, use it as-is. The extension never modifies your compose file or service.
- If none exists, the footer shows
cowork: not configured. Callingcowork_up(or/cowork up) asks for consent; on consent, the extension writes a minimal.pi/cowork.jsonand hands off to the separatecowork-modeskill (/skill:cowork-mode) to scaffolddocker-compose.yml,Dockerfile.cowork, andscripts/cowork-entrypoint.sh. Install the skill vianpx skills add geut/cowork-mode-skillif it is not already available. The extension itself never authors Dockerfiles.
Configuration: .pi/cowork.json
Optional. Created automatically on consent. Fields:
{
// Override the service name (default "cowork").
"service": "cowork",
// Override the compose file path (default: auto-discovered).
"composeFile": "docker-compose.yml",
// Test command(s) the extension recognizes as "test runs". String or array. Overrides package.json derivation.
"testCommand": ["npx vitest run", "npm test"],
}Test-command cascade:
.pi/cowork.jsontestCommand→package.jsonscripts.test:cowork→package.jsonscripts.test→- omit (no test signal).
Tools
| Tool | Description |
|------|-------------|
| cowork_up | docker compose up -d [--build] cowork. Rebuilds only when Dockerfile.cowork, the compose file, or the lockfile changed since the last successful up. Pass forceBuild: true to force a rebuild. Returns service, container name, ports, state. |
| cowork_status | Reports container lifecycle, health (if a HEALTHCHECK is defined), published ports with URLs, and last test result. Does not start or stop anything. |
| cowork_exec | Runs a command inside the container, always at the configured workdir (/workspace by default). Streams stdout/stderr. Recognizes test runs (via the test-command cascade) and updates the last-test-result signal. Errors if the env is down — does not auto-start. Don't run watch mode / REPLs. |
| cowork_test | Runs the recognized test command(s) (from .pi/cowork.json testCommand or package.json scripts) and updates the last-test-result signal. Streams output. Stops on first failure. Use this instead of cowork_exec when you intend to run tests. |
| cowork_run | Runs an arbitrary package command string in the container (e.g. npm run lint, npm run test:e2e, npm run dev). Streams output. Does not update the last-test-result signal — use cowork_test for tracked test runs. Errors if the env is down. Avoid watch mode / long-running servers; use /cowork shell for those. |
| cowork_logs | One-shot fetch of the last N lines (default 200). No follow mode. |
| cowork_down | Stops the service, keeps named volumes. |
| cowork_reset | Stops the service and removes named volumes (full reinstall). Always confirms with the user first. |
/cowork subcommands
/cowork up bring the environment up (smart build)
/cowork status show expanded status in the widget (Escape to close)
/cowork test run the recognized test command(s) and track the result
/cowork run <cmd> run a command string in the container (e.g. npm run lint)
/cowork logs [N] show last N log lines (default 200)
/cowork down stop the environment
/cowork shell interactive shell inside the container (TUI only)/cowork run captures output and notifies when the command finishes. For long-running commands (e.g. a dev server) it will block until the command exits or you abort; use /cowork shell for an interactive session instead.
State model
Two independent signals:
- Container lifecycle:
up/paused/restarting/exited/dead/ … fromdocker compose ps. - Last test result:
passing/failing/unknown.cowork_testalways sets it;cowork_execsets it when the command matches the test-command cascade;cowork_rundoes not. Timeout/abort counts asfailing.
Health is whatever Docker reports on docker compose ps (healthy / unhealthy / starting). If the image or compose service has no HEALTHCHECK, the extension shows none and does not probe ports. For an HTTP app, add a HEALTHCHECK (in the Dockerfile or a compose healthcheck:) so the footer can show reachability. CLI/module projects can skip it.
Customizing the container workdir
cowork_exec runs commands at /workspace (the conventional bind-mount root) by default. If your project mounts the repo elsewhere, edit DEFAULT_WORKDIR in src/tools.ts.
Refresh model
- One
docker compose psonsession_startand after everycowork_*tool call. - A
docker eventsstream (filtered to the compose project) flips state between tool calls. - The events stream auto-restarts on exit with exponential backoff (1s → 2s → 4s → … capped at 30s). Torn down on
session_shutdown. - Docker-unavailable: probed on
session_start; footer showscowork: docker unavailable; tools return a clear daemon-unreachable error. Re-probed on eachcowork_*call (self-heals when docker comes back, without a polling timer).
Development
git clone [email protected]:geut/pi-cowork-mode.git
cd pi-cowork-mode
npm install
npm run check # tsc --noEmit
npm run lint # oxlint .
npm test # vitest runTests use fakes/fixtures (no real docker). See __tests__/.
Packaging notes
peerDependencies(@earendil-works/pi-coding-agent,@earendil-works/pi-ai,@earendil-works/pi-tui,typebox) are provided by the host pi at runtime — never bundled.devDependenciesare for typecheck/lint/test inside the package.pi.extensionsinpackage.jsonregisters only the extension. Thecowork-modeskill is shipped separately as@geut/cowork-mode-skill.
