How my Pi coding agent is configured — extensions, instructions, skills, MCP servers, LSP, and model provider. Share this with a friend to copy my setup, or use it to rebuild on a new machine.
Pi keeps everything under ~/.pi/agent/. Secrets (auth.json) are not here —
run pi once and log in to regenerate them. Infra details (IPs, hostnames, my
vLLM endpoint) are redacted to placeholders like <PROXMOX_IP> / <VLLM_HOST>.
Pi extensions are npm packages. I run exactly these 8 (verify yours anytime
with pi list). To get the same setup, paste these:
pi install npm:@dreki-gg/pi-lsp # language-server (LSP) support
pi install npm:@d3ara1n/pi-ask-user # lets the agent ask me questions
pi install npm:pi-web-access # web search + fetch
pi install npm:pi-mcp-adapter # MCP server support
pi install npm:@narumitw/pi-chrome-devtools # Chrome DevTools browser automation
pi install npm:@narumitw/pi-subagents # subagents
pi install npm:@narumitw/pi-goal # goal tracking
pi install npm:@narumitw/pi-statusline # status linepi install adds each to the packages list in settings.json and pulls the npm
package. Exact pinned versions are in npm__package.json.
AGENTS.md is my global operating-rules file (Pi auto-loads AGENTS.md /
CLAUDE.md as context). It covers: search-don't-guess, ask-before-assuming,
read-only-first debugging for k8s/Proxmox/ZFS, GitOps PR-only rules, the Chrome
image budget, and browser-scraping patterns. Drop it at ~/.pi/agent/AGENTS.md.
homelab — read-first SSH diagnostics for Proxmox + TrueNAS (qm, zpool, zfs,
smartctl, midclt). Goes to ~/.pi/agent/skills/homelab/SKILL.md.
| Gist file | Restore to | What it is |
|---|---|---|
settings.json |
~/.pi/agent/settings.json |
extension list + theme |
npm__package.json |
~/.pi/agent/npm/package.json |
pinned extension versions |
AGENTS.md |
~/.pi/agent/AGENTS.md |
my instructions |
mcp.json |
~/.pi/agent/mcp.json |
k8s MCP server |
models.json |
~/.pi/agent/models.json |
my vLLM model provider |
extensions__lsp__config.json |
~/.pi/agent/extensions/lsp/config.json |
LSP servers |
skills__homelab__SKILL.md |
~/.pi/agent/skills/homelab/SKILL.md |
homelab skill |
Gist filenames can't contain
/, so__stands in for a path separator.
# 1. Install Pi (see pi.dev). Here it runs via a node install.
# 2. Pull this gist
gh gist clone <THIS_GIST_ID> /tmp/pi-backup
# 3. Lay files into ~/.pi/agent/
mkdir -p ~/.pi/agent/extensions/lsp ~/.pi/agent/skills/homelab
cp /tmp/pi-backup/settings.json ~/.pi/agent/settings.json
cp /tmp/pi-backup/mcp.json ~/.pi/agent/mcp.json
cp /tmp/pi-backup/models.json ~/.pi/agent/models.json
cp /tmp/pi-backup/AGENTS.md ~/.pi/agent/AGENTS.md
cp /tmp/pi-backup/extensions__lsp__config.json ~/.pi/agent/extensions/lsp/config.json
cp /tmp/pi-backup/skills__homelab__SKILL.md ~/.pi/agent/skills/homelab/SKILL.md
# 4. First run installs the extensions from settings.json + prompts login
pi
pi list # confirm all 8 extensions are present- MCP
k8sneedsbunx(Bun) + a valid~/.kube/config. - LSP servers on PATH:
typescript-language-server,pyright-langserver,ruff,gopls,yaml-language-server. - Model provider
vanillax-vllmpoints at<VLLM_HOST>/v1(must be reachable). - Chrome profile for Facebook Marketplace:
~/.pi-chrome-profileon CDP port 9222 (see AGENTS.md). Not backed up — holds a logged-in session.
Pi version at backup time: 0.80.2
{
"packages": [
"npm:@dreki-gg/pi-lsp",
"npm:@d3ara1n/pi-ask-user",
"npm:pi-web-access",
"npm:pi-mcp-adapter",
"npm:@narumitw/pi-chrome-devtools",
"npm:@narumitw/pi-subagents",
"npm:@narumitw/pi-goal",
"npm:@narumitw/pi-statusline"
],
"lastChangelogVersion": "0.80.2",
"theme": "dark"
}{
"name": "pi-extensions",
"private": true,
"dependencies": {
"@d3ara1n/pi-ask-user": "^1.0.0",
"@dreki-gg/pi-lsp": "^0.4.1",
"@narumitw/pi-chrome-devtools": "^0.6.2",
"@narumitw/pi-goal": "^0.6.2",
"@narumitw/pi-statusline": "^0.6.2",
"@narumitw/pi-subagents": "^0.6.2",
"pi-mcp-adapter": "^2.10.0",
"pi-web-access": "^0.10.7"
}
}
{
"mcpServers": {
"k8s": {
"command": "bunx",
"args": [
"-y",
"kubernetes-mcp-server@latest",
"--read-only",
"--kubeconfig",
"<HOME>/.kube/config"
],
"lifecycle": "lazy"
}
}
}
{
"providers": {
"vanillax-vllm": {
"baseUrl": "https://<VLLM_HOST>/v1",
"api": "openai-completions",
"apiKey": "vllm",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"supportsUsageInStreaming": false,
"maxTokensField": "max_tokens",
"thinkingFormat": "qwen-chat-template"
},
"models": [
{
"id": "qwen3.6-27b",
"name": "Qwen3.6 27B (vLLM)",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 262144,
"maxTokens": 32768,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
{
"lsp": {
"typescript": {
"command": ["typescript-language-server", "--stdio"],
"extensions": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]
},
"python": {
"command": ["pyright-langserver", "--stdio"],
"extensions": [".py"],
"initialization": {
"python": { "analysis": { "typeCheckingMode": "basic" } }
}
},
"ruff": {
"command": ["ruff", "server"],
"extensions": [".py"]
},
"go": {
"command": ["gopls"],
"extensions": [".go"]
},
"yaml": {
"command": ["yaml-language-server", "--stdio"],
"extensions": [".yaml", ".yml"]
}
}
}
# Operating rules
## Search, don't guess
If unsure about a current API, library version, CLI flag, CRD field, Helm value,
or anything that may have changed recently, you MUST use web search before
answering. Never state versions, flags, or config keys from memory when a search
can verify them. Prefer official docs and source repos over blogs.
## Ask, don't assume
If a task needs a value I haven't specified — port, namespace, hostname, secret
location, image tag, branch, or any decision with more than one reasonable answer
— ask me with explicit options instead of picking one. Always ask before
guessing on infrastructure changes.
## Debugging discipline
- Kubernetes: inspect READ-ONLY first. Pod status, events, logs before proposing
changes. Never delete/scale without asking.
- Infra (Proxmox/ZFS/TrueNAS): SSH commands hit production hardware. Show the exact
command and expected effect before anything destructive (zpool, qm, zfs destroy).
Read state first.
- React Native / web: reproduce and describe what you see before editing. Prefer
reading the DOM/text over screenshots (see Image budget). Screenshot only when a
visual/layout question genuinely can't be answered from text.
## GitOps
- GitOps repo: NEVER push to the deploy branch. Feature branch + PR only; ArgoCD
deploys. Check diagnostics on manifests before opening a PR.
## Image budget (HARD LIMIT — qwen3.6 via vLLM)
The model accepts **at most 4 images per request**, and Pi resends the whole
conversation every turn. `chrome_devtools_screenshot` embeds an inline image that
is NEVER freed, so the 5th screenshot in a session permanently breaks the chat
with `400 At most 4 image(s) may be provided in one prompt`.
- Treat screenshots as a scarce, non-renewable resource: **2 per session, max.**
- NEVER screenshot to read text, prices, listings, tables, or DOM data. Use
`chrome_devtools_evaluate` and return JSON/text instead — it costs zero images.
- If you ever see the "At most 4 images" error, the session is wedged: tell me to
`/clear` (or compact). Do not keep retrying — every retry fails identically.
## Browser scraping with Chrome DevTools
Workflow: `chrome_devtools_navigate` → wait → `chrome_devtools_evaluate` returning
JSON. Extract structured data in ONE evaluate call; never screenshot-and-read.
Pattern (adapt the selectors per site):
```js
(() => {
const out = [];
document.querySelectorAll('SELECTOR_FOR_EACH_ROW').forEach(c => {
out.push({
title: c.querySelector('TITLE_SEL')?.innerText?.trim() || '',
price: c.querySelector('PRICE_SEL')?.innerText?.trim() || '',
});
});
return JSON.stringify(out);
})()eBay note: real (non-headless) Chrome passes the "Pardon Our Interruption"
challenge automatically after wait --load networkidle; cards are li.s-card,
title .s-card__title, price .s-card__price. Headless Chrome gets blocked.
Marketplace shows nothing logged-out and is login-walled. There is a persistent,
already-logged-in Chromium for exactly this: profile ~/.pi-chrome-profile on CDP
port 9222 — chrome_devtools connects to it by default. DO NOT rely on pi's
auto-launched browser: it uses a throwaway temp profile (deleted each run), so it
is never logged in. If chrome_devtools_navigate to Marketplace shows a login
form, the 9222 browser isn't running — tell me to start it (systemctl --user start pi-chrome if installed, else launch chromium with
--remote-debugging-port=9222 --user-data-dir=~/.pi-chrome-profile). Never type my
FB credentials; I log in myself in that window.
Search URL (location slug + local pickup):
https://www.facebook.com/marketplace/<city-slug>/search?query=<q>&exact=false&deliveryMethod=local_pick_up
e.g. city-slug grand-rapids. After navigate, wait --load networkidle then
wait 2200 (lazy load), then extract. Cards are obfuscated — DON'T guess class
names; instead collect item anchors and parse their text:
(() => {
const seen=new Set(), out=[];
document.querySelectorAll('a[href*="/marketplace/item/"]').forEach(a=>{
const id=(a.href.match(/item\/(\d+)/)||[])[1]; if(!id||seen.has(id))return; seen.add(id);
const L=a.innerText.split('\n').map(s=>s.trim()).filter(Boolean);
const price=L.find(l=>/^\$[\d,]/.test(l))||'';
const loc=L.find(l=>/, [A-Z]{2}$/.test(l))||'';
const title=L.filter(l=>l!==price&&l!==loc&&!/^\$/.test(l)).sort((a,b)=>b.length-a.length)[0]||'';
out.push({price,title:title.slice(0,70),loc});
});
return JSON.stringify(out);
})()Results carry per-listing town; FB's radius is approximate, so filter by town distance yourself rather than trusting a radius param.
- Read neighboring files first. Minimal, reviewable diffs. I review every PR.
- I do all git commits and pushes myself. Never run
git add/commit/pushwithout an explicit instruction.
## `~/.pi/agent/skills/homelab/SKILL.md`
```markdown
---
name: homelab
description: Read-first diagnostics for the homelab Proxmox host and TrueNAS box over SSH (qm, zpool, zfs, smartctl, arcstat, midclt). Use when inspecting VM, ZFS pool, disk, or storage health. Destructive ops require explicit confirmation.
---
# homelab diagnostics
Hosts:
- proxmox = <PROXMOX_IP>
- truenas = <TRUENAS_IP>
## SAFE — read-only, run freely
```bash
ssh proxmox "qm list; pvesm status; cat /proc/loadavg"
ssh proxmox "zpool status; zpool list; zfs list -o name,used,avail,compressratio"
ssh truenas "zpool status; zpool iostat -v 1 3; arcstat 1 3"
ssh truenas "smartctl -a /dev/<disk>" # SMART for the SAS/SATA array
TrueNAS: prefer the middleware API over poking the host where possible — respects the appliance model:
ssh truenas "midclt call pool.query | jq '.[].name'"
ssh truenas "midclt call disk.query"zpool destroy/replace/offline, zfs destroy, qm stop/destroy, iLO/firmware,
reboot, dd, mkfs.
Read state first. Show the exact command and its expected effect. Ask before acting on anything that mutates pools, VMs, or disks.