If you embed a pi agent in your own app (like WibWob-DOS does), you do NOT need a raw ANTHROPIC_API_KEY. The agent can piggyback on your existing pi subscription login — the same OAuth token pi itself uses — with zero extra configuration.
When you run pi login normally, pi does an OAuth flow (browser → Anthropic/Google/etc.)
and writes the resulting access token + refresh token to:
~/.pi/agent/auth.json
That file looks roughly like:
{
"anthropic": {
"type": "oauth",
"access_token": "...",
"refresh_token": "...",
"expires": 1712345678000
}
}Your embedded agent just reads the same file. No API key needed.
In your agent session initialisation:
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
const authStorage = AuthStorage.create(); // reads ~/.pi/agent/auth.json
const modelRegistry = new ModelRegistry(authStorage); // finds available models via those creds
const agent = new Agent({
// ...
getApiKey: (provider) => authStorage.getApiKey(provider), // called per-request
});AuthStorage.create() with no arguments defaults to ~/.pi/agent/auth.json.
That is the same file pi writes when you log in via the CLI.
authStorage.getApiKey(provider) handles the full priority chain:
- Runtime override (CLI --api-key flag)
api_keyentry in auth.json- OAuth token in auth.json — auto-refreshed if expired, with file locking so multiple concurrent instances do not race
- Environment variable (ANTHROPIC_API_KEY etc.)
- Custom fallback resolver (models.json custom providers)
For a standard claude-subscription login, path 3 fires and you get a fresh access token transparently whenever the previous one expires.
src/services/wibwob-agent-session.ts
The main wiring. Creates AuthStorage, ModelRegistry, AgentSession, and
Agent. Passes the getApiKey callback. Also handles jailed coding tools, TUI
tools, session persistence, and streaming transcript updates.
Key section is the initialize() method — search for AuthStorage.create().
These are compiled JS — no source available publicly, but the paths below are where they live on a typical macOS pi install:
@mariozechner/pi-coding-agent/dist/core/auth-storage.js
— AuthStorage class. Reads/writes auth.json with file locking. Handles OAuth
token refresh (with lock to prevent race conditions across multiple pi instances).
Priority chain for getApiKey() lives here.
@mariozechner/pi-coding-agent/dist/core/model-registry.js
— ModelRegistry. Enumerates configured providers, calls getApiKey to check
which are live, surfaces the list to resolveModel() in wibwob-agent-session.ts.
@mariozechner/pi-coding-agent/dist/core/agent-session.js
— AgentSession. Wraps Agent with session persistence, tool management,
compaction, and retry logic. The getApiKey callback set on Agent is called
here on every API request.
@mariozechner/pi-ai/dist/utils/oauth/anthropic.js
— claude-subscription PKCE OAuth flow (the one most pi users are logged in with)
@mariozechner/pi-ai/dist/utils/oauth/github-copilot.js
@mariozechner/pi-ai/dist/utils/oauth/google-gemini-cli.js
@mariozechner/pi-ai/dist/utils/oauth/openai-codex.js
— OAuth flows for other supported subscription providers
@mariozechner/pi-ai/dist/utils/oauth/index.js
— getOAuthProvider(id) registry that AuthStorage calls to look up a provider
by name (e.g. "anthropic") and get its refresh/getApiKey implementation
@mariozechner/pi-ai/dist/utils/oauth/pkce.js
— shared PKCE helpers used by all the above
@mariozechner/pi-ai/dist/env-api-keys.js
— getEnvApiKey(provider) — the env-var fallback (ANTHROPIC_API_KEY etc.)
~/.pi/agent/auth.json
— Written by pi login. Read by AuthStorage.create(). Contains type:"oauth"
entries per provider (or type:"api_key" if you stored a raw key instead).
This file is the entire reason no API key is needed in your app config.
authStorage.getApiKey() returns undefined when the OAuth token is invalid
(revoked, or auth.json missing — not just expired, which auto-refreshes fine).
ModelRegistry then finds no available models for that provider.
The first session.prompt() call throws:
No model available. Check provider auth.
In WibWob-DOS this surfaces in the agent window status bar.
Fix: run pi login again in your terminal. That rewrites auth.json and the
embedded agent picks it up on next app restart (or after calling authStorage.reload()).
This is not a WibWob-DOS invention. Pi's own CLI entry point (main.js) does
exactly the same thing — lines 507-508:
const authStorage = AuthStorage.create();
const modelRegistry = new ModelRegistry(authStorage, getModelsPath());The only extra thing pi's CLI adds is handling the --api-key flag by calling
authStorage.setRuntimeApiKey(provider, key), which inserts it at priority 1
above everything else. If you want to support that flag in your embedded agent
too, that's the call to make.
So: embedded agent, vanilla pi CLI, same file, same class, same auth.json.
You do not need to set ANTHROPIC_API_KEY. You do not need to pass credentials
into your app config. As long as the machine has a valid pi login (auth.json
exists and is not revoked), the agent just works.
This is the entire mechanism WibWob-DOS uses for its embedded Wib & Wob agent.