Standing PenguinHarness up to test by hand
Node >= 24. dev:* runs dev-prebuild.mjs first (keeps pnpm install current, prebuilds
workspace deps); pnpm desktop runs a full pnpm -r build, so it is slow to start.
Entry points
| Command | Open | Data root |
|---|---|---|
pnpm dev |
http://localhost:7365 | ~/.penguin/dev-data |
pnpm desktop |
its own window | ~/.penguin/dev-data |
pnpm dev:landing |
http://localhost:7366 | none (static) |
pnpm dev:docs |
http://localhost:7367 | none (static) |
Other fixed ports (packages/core/src/internal/ports.ts): 7364 installed server, 7368 dev backend,
7369 pnpm penguin web (data root ~/.penguin/dev-data-cli — its own, so it can serve while an
Agent it hosts runs pnpm dev; the lock is per root). On a shared box, ss -tln before assuming
one is free; PORT= inline moves it.
The user's installed app, server and CLI all use ~/.penguin/data — their real Agents, Sessions
and keys. Never point a dev run there. Both surfaces print the root they took (Data root: …,
[shell] dev instance … on data root …); read it rather than assume.
Four ways a working setup looks broken
7368 shows a stale app. The dev backend also serves packages/web/dist — the last
pnpm -r build, not what Vite is serving. Screenshot 7365, never 7368.
curl returns 502 but the server is fine. A shell http_proxy routes loopback through the
proxy. Use curl --noproxy '*'. Browsers and the server itself are unaffected.
/api on 127.0.0.1 returns 401. That address is reserved as the Workspace-preview host. Use
localhost.
The server exits 3 saying the data root is in use. Another instance — usually the user's own
desktop app — holds <root>/server.lock. The lock is per root, not per port, so PORT= will not
get past it. Use a different root; do not kill their process.
One instance at a time
Only one local dev instance runs at a time, counting every agent's. A separate root gets you past the lock but not past the browser: cookies and the admin claim are shared across instances, so bringing a second one up corrupts the first's session state. Stop the previous PR's instance before starting the next.
The slot belongs to the task that genuinely needs a running server — watching a live stream, driving a real tool call. The rest verify statically: for an icon-legibility or spacing question a throwaway HTML render shows the same pixels at a fraction of the cost.
PENGUIN_HOME
Never export it. scripts/run-with-env.mjs applies each VAR=value only when unset, so an
exported value silently redirects every dev script — an exported ~/.penguin/data puts pnpm dev
on the user's real data, where the lock is already held. It now names any default the environment
displaced before running, so read that line if it appears.
When you need your own root, prefix the one command:
PENGUIN_HOME=~/.penguin/dev-data-<topic> pnpm dev
Unset it rather than blanking it: run-with-env reads empty as unset, but resolveRoot() uses
?? and takes an empty string literally.
Signing in
A fresh root seeds admin with a random penguin-<4 digits> password, printed once at startup.
PENGUIN_SEED_ADMIN_PASSWORD pins it, which is how the e2e harness logs in.
The first-login link the server prints is the user's to open, not yours — no curl, no browser, no
headless fetch. It redeems into whichever cookie jar made the request, and the desktop shell's
variant is spent on first use, so following it can lock the user out of their own instance. Hand
them the URL and wait. A restart prints a fresh one, so it is recoverable, but only by interrupting
them. Being blocked here is an acceptable outcome: report how far you got and what stayed
unverified rather than guessing.
Under pnpm dev the printed link carries the wrong port. index.ts builds it as
http://<host>:<port>/api/auth/claim?token=… from the server's own listener, which in dev is the
backend on 7368 — but the App is Vite on 7365, and Vite is what serves the page the redirect lands
on. Swap the port before handing the link over: http://localhost:7365/api/auth/claim?token=….
The token is the same either way; only the origin that ends up holding the cookie changes.
Editing while it runs
Changes to core or skills do not reach a running dev server — web/server consume snapshot
copies that re-sync only when that package's build runs. Restart pnpm dev.
When you are done
Stop what you started. Leave anything the user was already running alone.