TL;DR: Installed Pi Agent (v0.66.0) via NVM with Node.js 25, configured it with Google Antigravity (free Claude + Gemini access), set up custom extensions for TPS monitoring, permission gating, and K8s shortcuts, added prompt templates for commit workflows, and connected it to shared skills that work across multiple coding agents.
What is Pi Agent?
Pi Agent (yes, that’s the real URL) is an open-source terminal-based AI coding agent by Mario Zechner (libGDX creator). It’s like Claude Code or Codex CLI, but with a key difference: no vendor lock-in. It supports dozens of LLM providers out of the box — Anthropic, OpenAI, Google, Groq, Cerebras, OpenRouter, Mistral, HuggingFace, ZAI, and more — and you can add custom providers via extensions.
With 33k+ GitHub stars, it’s one of the most popular open-source coding agents. The codebase lives in a monorepo at badlogic/pi-mono.
Why Pi Agent?
I already use Hermes Agent as my daily driver and Claude Code / Codex as subagents. So why add another one?
- Free model access via Google Antigravity (Cloud Code Assist) — Claude Opus 4, Claude Sonnet 4, Gemini 3 Flash, all at no cost
- Shared skill ecosystem — Pi skills from badlogic/pi-skills also work with Claude Code and Codex
- Extensions system — write TypeScript extensions for custom tools, providers, and UI widgets
- Zero runtime cost — free providers (Groq, Cerebras, Antigravity) mean I can run agents all day without watching a billing meter
Installation
I use NVM to manage Node.js versions. Gitnexus requires Node 20, so I keep multiple versions around:
# Gitnexus uses Node 20nvm use 20
# Pi Agent needs Node 25nvm use 25npm i -g @mariozechner/pi-coding-agentRather than manually switching Node versions every time, I created a wrapper script at ~/.local/bin/pi:
#!/usr/bin/env bashset -euo pipefail
export NVM_DIR="${NVM_DIR:-$HOME/.config/nvm}"[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
NODE_VERSION="25"
if ! nvm ls "$NODE_VERSION" | grep -q "$NODE_VERSION"; then echo "Node $NODE_VERSION not found. Installing..." >&2 nvm install "$NODE_VERSION"fi
nvm exec "$NODE_VERSION" pi "$@"This is the same pattern I use for Gitnexus (which wraps npx gitnexus@latest with Node 22). Each tool gets its own Node version, no conflicts.
Configuration
Pi stores its config in ~/.pi/agent/:
~/.pi/agent/├── settings.json # Default provider, model, theme├── auth.json # API keys and OAuth tokens├── models.json # Custom provider endpoints├── extensions/ # TypeScript extensions├── prompts/ # Prompt templates├── skills/ # Symlinked shared skills└── git/ # Installed packagesProvider Setup
The default provider is Google Antigravity with Gemini 3 Flash. Antigravity gives free access to Claude and Gemini models through Google’s Cloud Code Assist — you authenticate once via OAuth (/login google-antigravity inside Pi) and it just works.
For ZAI (GLM models), I’m on the coding plan which uses a different endpoint than the standard API:
{ "providers": { "zai": { "baseUrl": "https://api.z.ai/api/coding/paas/v4" } }}This goes in ~/.pi/agent/models.json — a simple override that tells Pi to use the coding plan endpoint instead of the default.
API keys for other providers (ZAI, OpenRouter, Groq, etc.) live in ~/.config/{bashrc,zshrc}/99-env-jp and get picked up automatically by Pi.
Free vs Paid Models
One thing to watch: Pi lists all models for every provider that has an API key configured. It doesn’t know about plan tiers. For example, on ZAI’s coding plan, glm-5-turbo works but glm-5v-turbo doesn’t — yet both appear in the model list. You need to manually verify which models your plan supports.
Here are the providers I use that are genuinely free:
| Provider | Cost | Notable Models |
|---|---|---|
| Google Antigravity | Free (OAuth) | Claude Opus 4.6, Claude Sonnet 4.6, Gemini 3 Flash |
| Groq | Free tier | Kimi K2, Qwen3-32B, Llama 4, GPT-OSS |
| Cerebras | Free | Qwen-3-235B, GPT-OSS-120B, GLM-4.7 |
Extensions
Pi’s extension system is where it really shines. Extensions are TypeScript files that can register tools, hook into events, and add UI widgets. I created four:
TPS Monitor
Shows tokens-per-second after each agent run — useful for benchmarking free providers:
export default function (pi: any) { let agentStartMs: number | null = null;
pi.on("agent_start", () => { agentStartMs = Date.now(); });
pi.on("agent_end", (event: any, ctx: any) => { if (!ctx.hasUI || agentStartMs === null) return; const elapsedMs = Date.now() - agentStartMs; agentStartMs = null; if (elapsedMs <= 0) return;
let output = 0; for (const message of event.messages) { if (message.role !== "assistant") continue; output += message.usage?.output || 0; } if (output <= 0) return;
const tps = output / (elapsedMs / 1000); ctx.ui.notify(`TPS ${tps.toFixed(1)} tok/s`, "info"); });}Permission Gate
Blocks destructive bash commands (rm -rf, kubectl delete, git push —force, DROP TABLE) until confirmed:
export default function (pi: any) { const DANGEROUS = [ /\brm\s+(-[rfRF]+\s+|.*--force\b)/, /\bkubectl\s+delete\b/, /\bgit\s+push\s+.*--force\b/, /\bDROP\s+(TABLE|DATABASE|SCHEMA)\b/i, // ...more patterns ];
pi.on("tool_call", async (event: any, ctx: any) => { if (event.toolName !== "bash") return; for (const p of DANGEROUS) { if (p.test(event.input?.command)) { const ok = await ctx.ui.confirm("Dangerous command", event.input.command); if (!ok) return { block: true, reason: "Blocked by permission-gate" }; break; } } });}Deploy Dashboard & K8s Shortcuts
Two domain-specific extensions for my XPASS project — one handles the full deploy pipeline (podman build → push to Aliyun registry → k8s rollout), the other provides quick kubectl commands for the xpass-dev namespace.
Prompt Templates
Prompt templates are markdown files in ~/.pi/agent/prompts/ that you invoke with /command. I set up two:
/wr— Wraps up a task: stages only changed files, writes a conventional commit message, optionally posts a GitHub comment, and pushes/is— Analyzes GitHub issues: reads the full issue + comments, checks bd (beads) for matching tasks, traces code to find the real root cause
These mirror the workflow patterns I already use in Claude Code via Hermes.
Shared Skills
Pi reads skills from ~/.agents/skills/ — a shared directory that Claude Code and Codex also use. I have 34 skills symlinked there, covering everything from Astro blog conventions to Go Fiber patterns to K8s deployment workflows.
On top of that, I installed the pi-skills package which adds:
- brave-search — Web search via Brave API
- browser-tools — Chrome DevTools Protocol automation
- transcribe — Speech-to-text via Groq Whisper
- youtube-transcript — Fetch video transcripts
- Google CLIs — Drive, Gmail, and Calendar CLI tools
pi install git:github.com/badlogic/pi-skillsWhat I Learned
-
NVM
--delete-prefixis a thing — If you set a custom npm prefix (e.g.,~/.local/share/npm-global), NVM will complain every time you open a terminal. Pi spawnsnpm install -ginternally, so it needs a prefix it can write to. The wrapper script approach avoids this entirely sincenvm exechandles the Node context. -
Credentials may be redacted — When AI agents read files containing API keys, the values can be truncated by content safety filters. Always verify credential values manually after copying them between files.
-
Pi can’t distinguish plan tiers — It lists every model a provider offers, regardless of what your subscription actually covers. Check your provider’s plan page and note which models work.
-
The extension API is clean — Registering tools, hooking into lifecycle events, and adding UI widgets all follow a consistent pattern via the
ExtensionAPIobject. The author’s own extensions in the pi-mono repo are great reference material. -
Pi skills are cross-agent — Skills written for Pi also work with Claude Code and Codex CLI. This means investment in skill development pays off across your entire agent toolkit.
-
Pi doesn’t know which model it’s running on — Pi has no runtime awareness of which provider or model is actually serving its requests. It knows its own agent name (“Pi”) but nothing about the LLM behind it. For accurate attribution in generated content, inject the model name via system prompt or settings.
-
Pi can do deep autonomous debugging — I asked Pi to figure out why the first request after
bun run devtook 13 seconds. It systematically eliminated suspects (fonts, content loading, image processing, Vite pre-bundling, Astro SSR), traced the bottleneck to@tailwindcss/vitecompiling Tailwind CSS 4 + DaisyUI 5 on first request, wrote a full blog post about it, and even created an SVG hero image — all in a single session using ~159k input / 39k output tokens. The entire process was autonomous: Pi read source files, ran curl benchmarks, analyzed Vite’s transform pipeline, and produced publication-ready output with no hand-holding.
This article was written by Hermes Agent (GLM-5-Turbo | ZAI).

