Skip to content

Repository files navigation

@vessup/pi-kit

Reusable extensions for the Pi coding agent.

PR footer

extensions/pr-footer.ts contributes the current GitHub pull request to the shared footer as a clickable  #123 link after the session name on the directory/branch line. A colored circle beside it shows the aggregate check status:

  • Green: checks passed
  • Yellow: checks are pending or in progress
  • Red: at least one check failed or was cancelled

It uses the GitHub CLI to resolve the pull request and check status for the checked-out branch, refreshing every 30 seconds. If gh is unavailable, unauthenticated, or the branch has no pull request, the link is hidden. Run /pr-refresh to refresh immediately.

Requirements

  • Pi 0.84.1
  • Git 2.36.0 or newer
  • lsof for exclusive source-session verification during worktree replacement
  • GitHub CLI (gh), authenticated with gh auth login
  • A terminal that supports OSC 8 hyperlinks for clickable links
  • A Nerd Font for the branch icon

Subagents

extensions/subagents.ts lets the main agent run up to eight isolated subagents in the background. Each subagent can use its own model, reasoning effort, prompt, and working directory. The extension provides tools to:

  • Create and monitor subagents
  • Read activity, output, and retained transcripts
  • Send urgent steering or queued follow-up messages
  • Change model and reasoning effort
  • Terminate subagents and release their resources

The subagent extension independently contributes its token use and status to extensions/session-footer.ts, the package's generic composable footer. When subagents are involved, the right side of the footer's second row shows their aggregate status with the same in-progress, completed, failed, or stopped icon used by the manager. With an empty editor, press Option+Down (Alt+Down) to select that summary and Enter to open the manager; /subagents opens it directly. The manager shows individual status and transcripts and supports model, effort, messaging, and termination controls. Run /subagents-cleanup to stop and remove every retained subagent.

Auto model routing

extensions/auto-router.ts adds an "Auto" entry to /model. Selecting it routes each turn to a model/reasoning-effort pair chosen from your own configured lists, based on the turn's classified complexity, and fails over to other configured models or tiers when one is unhealthy or out of usage.

Configure it under a new autoRouter key in ~/.pi/agent/settings.json:

{
  "autoRouter": {
    "efforts": {
      "medium": {
        "models": [
          { "provider": "anthropic", "id": "claude-sonnet-4-5" },
          { "provider": "openai", "id": "gpt-5.3-codex" }
        ]
      },
      "high": {
        "models": [{ "provider": "anthropic", "id": "claude-opus-4-7" }]
      },
      "xhigh": {
        "models": [{ "provider": "openai", "id": "gpt-5.6-sol" }]
      }
    }
  }
}

Each tier key is a Pi thinking level (off, minimal, low, medium, high, xhigh, max); medium is the default/anchor tier. Each tier holds an ordered list of { provider, id } model references — the first is preferred, later entries are failover within that tier.

Which tier a model is listed under only decides when it's used (which classified-complexity bucket routes to it, and where it sits in the escalation order) — it doesn't have to be the reasoning effort that model is actually dispatched at. Add "effort" to a model reference to pin its own thinking level independent of its tier, e.g. a model that only performs well at its own maximum setting can still live under high (so moderately-hard tasks reach it and it takes part in escalation normally) while always running at max:

"high": {
  "models": [{ "provider": "opencode-go", "id": "kimi-k3", "effort": "max" }]
}

Omit effort and a model just uses its tier's own name, as before.

On the first meaningful turn of a new session, Auto runs the medium tier's first healthy model (the "default model") as an isolated, in-memory classifier agent. It receives the expanded prompt and bounded copies of matching project/global skill instructions as untrusted classifier context. When the raw request explicitly identifies Linear through a Linear URL, Linear issue phrase, or Linear skill invocation, it can run a strictly restricted linear issue view command for only the exact issue IDs the user supplied. That lets a matching skill resolve an issue such as ENG-123 before Auto judges the implementation complexity without mistaking unrelated tokens such as HTTP-404 for issues. The classifier receives no file or mutation tools, cannot edit files or mutate Linear, never loads project context as system-level instructions, and its transcript never enters the main session. It is bounded to 60 seconds, 12 tool calls, and 4,096 output tokens per model request. The linear CLI must already be installed and authenticated for Linear issue lookup; missing or failed required lookup falls back conservatively to high.

On subsequent turns, Auto uses the faster isolated one-request evaluator with the submitted prompt and no tool loop. Both classifiers return minimal, low, medium, high, xhigh, or max, then Auto routes to the resolved tier: if the classified level has no configured models, it steps toward medium until it finds one (a classified low with nothing configured there falls back to medium). Within that tier it picks the first model that isn't in a failure/rate-limit cooldown; if every model in the tier is unhealthy, it escalates to the next higher configured tier; if nothing anywhere is healthy, it uses the first configured model anyway rather than blocking the turn, with a warning.

/model shows a separate entry per configured tier — "Auto (auto)" for the classify-every-turn behavior above, plus "Auto (medium)", "Auto (high)", and so on for each tier that has at least one model configured (tiers with nothing configured don't get an entry). Picking a specific one pins Auto to that tier: every turn skips classification and routes directly within it — still with the same failover, escalation, and health tracking as the adaptive mode, just without asking a model to judge complexity first. This list is fixed at startup from whatever's configured then, so adding a new tier to autoRouter needs a Pi restart before its "Auto (<tier>)" entry shows up.

Health is tracked from two sources. Router-observed traffic (HTTP status codes, rate-limit headers, and message-level provider errors that never surface as a bad HTTP status) sets an immediate cooldown the moment any turn against a configured model fails — whether Auto routed there itself or you picked it manually from /model; a model configured in autoRouter is tracked the same way either way. Separately, best-effort real quota reconciliation runs at session start and on /usage, for providers with a known quota source: Anthropic, OpenAI Codex, Z.ai, Kimi Coding, and OpenCode Go via their HTTP APIs (using the same credentials Pi already has for each), plus Minimax via its mmx CLI (mmx auth login) since MiniMax has no HTTP quota endpoint of its own. This is what lets the router self-correct for usage consumed truly outside its view (a different session or machine, another tool, or before Auto was set up) instead of only reacting to its own observations. Codex specifically reports quota per-model for models it meters individually (its own additional_rate_limits entries) — those are independent of its account-wide limit in both directions, so a model with its own entry is neither blocked by, nor shielded by, the account-wide state; only models without one fall back to it. Providers without a known quota source simply stay on router-observed data.

Run /usage to see health and usage for every configured model, grouped by tier. Each row shows its cooldown status if any, the real "verified usage" reported by the provider's own quota API when available — always normalized to "X% used" regardless of how the provider itself reports it, with each window labeled by its real duration rather than a vague placeholder where the provider's response makes that derivable (e.g. "7d 5% used", "5h 16% used, weekly 11% used") — and separately the request/token/cost totals this Pi installation has observed for that model, whether Auto routed there or it was picked manually. The latter still won't reflect usage from other sessions/machines/tools or from before Auto started tracking, which is exactly what verified usage is for. Shown as a bordered dashboard in the TUI, or a compact summary elsewhere (including Pi Web).

/usage also shows the last several routing decisions under "Recent classifications" — whether the agent or fast classifier was used, what its raw reply actually was, the level it parsed to, and the tier/model it routed to. The classification call itself is otherwise a throwaway completion whose result would normally vanish the moment it's parsed, so if a turn ever looks under- or over-routed, this is what to check first rather than guessing from the code.

The /model picker's effort/thinking control is inert while any Auto entry is selected, since effort is chosen per turn (or fixed to the pinned tier) internally. /model keeps showing whichever Auto entry you picked selected even after routing: the real model is only swapped in for the duration of each turn and swapped back to that same inert Auto placeholder as soon as it settles, so reopening /model between turns still shows "Auto (auto)" or "Auto (high)" (whichever you picked), not whichever model last handled a turn. The right side of the TUI footer's first row mirrors that selection before the runtime details—Auto (auto) • (provider) model • effort for the adaptive entry, or Auto (<tier>) • (provider) model • effort for a pinned one—without a separate Auto Router icon. The Auto label reflects the selection, not whatever a given turn happened to classify or dispatch to (check /usage for that; a model's own effort override in particular can differ from its tier, so the two aren't the same thing). Pi Web follows the same distinction: its model control keeps Auto checked while a turn is running, and appends the concrete routed model and effort used for that turn. Manually picking a real (non-Auto) model from /model turns Auto off; reselecting any Auto entry turns it back on.

If you've scoped /model with enabledModels (or --models), Pi's picker defaults to showing only that scoped list, hiding everything else — including every Auto entry — behind a manual Tab to "all". At session start, Auto best-effort appends an auto/* pattern to enabledModels (only when scoping is already configured, and only if it isn't already present) so every Auto entry shows up in the default scoped view too, without changing anything else about what's scoped.

Requirements

  • Pi 0.84.1
  • Network access from the machine running Pi, for the optional quota reconciliation calls (never required — routing and /usage work fully offline from router-observed data alone)
  • For Minimax quota reconciliation specifically: MiniMax's own mmx CLI on PATH, logged in via mmx auth login. Without it, Minimax models just stay on router-observed data like any other unsupported provider.
  • For first-turn Linear issue context: the linear CLI on PATH, authenticated for the relevant workspace, plus a discovered Linear skill. Only linear issue view ISSUE-ID is permitted to the classifier.

Worktrees

Run /worktree <name> to create <repo-root>/.pi/worktrees/<name>, run the optional .pi/worktrees/setup.sh, and move the active conversation into a replacement session rooted in the managed checkout. The backward-compatible default creates or reuses local branch <name>; a missing branch starts at the repository's default branch — origin/HEAD when set (for example origin/main), otherwise local main or master — and only falls back to the primary checkout's HEAD when none of those resolve.

The managed directory name, local branch, and new-branch start point can be selected independently:

/worktree pr-30 --repo /path/to/repo \
  --branch tembo/cancel-builds \
  --start-point origin/tembo/cancel-builds

That creates .pi/worktrees/pr-30 without creating a pr-30 branch. If tembo/cancel-builds does not exist locally, it is created at origin/tembo/cancel-builds and tracks that remote branch. If the local branch already exists, omit --start-point; Pi reuses it without moving it or taking ownership of it. If it is already checked out in another registered worktree (or the primary checkout), Pi moves the conversation into that checkout instead of creating a duplicate; an entered checkout is never modified, rolled back, or removed. To enter an already registered worktree without modifying its checkout or branch, run /worktree --existing <worktree-path>.

Managed ownership records the directory name and whether Pi created the local branch. Final-session cleanup removes the managed checkout, while automatic rollback removes it only when clean so unrelated uncommitted files are never discarded. Both paths delete only a branch created by Pi; reused branches are preserved. Switching completes only after the replacement CWD and actual branch or detached HEAD are verified; the replacement is then made self-contained and the source session is deleted automatically.

The LLM-callable worktree tool provides the same name, repository, branch, startPoint, and existing flows. For a pull request URL, agents must resolve the PR's real head branch and fetched remote-tracking ref and pass them explicitly rather than deriving a branch from a directory such as pr-30. The tool queues a correlated /worktree follow-up, ends the old run, verifies the replacement, and resumes its continuation there. Create-only requests that should not enter the checkout remain ordinary Git operations.

Web sessions

extensions/web-sessions.ts connects every running Pi session to a local Bun server. The first Pi process starts the server on 127.0.0.1:31415; later processes discover it through ~/.pi/agent/web/server.json and attach their own live event streams.

The bundled Vite/React app provides a shadcn/ui-style session shell with:

  • Sessions with filtering, creation-time sorting, and persistent drag-and-drop ordering
  • Collapsible repository groups by default, keeping linked Git worktrees together
  • Responsive collapsible sidebar and per-session menus for resume, delete, clone, fork, rename, and compact controls
  • Semantic conversation rendering for both browser-managed and native Pi sessions
  • Streaming assistant text, thinking, tool activity, queued follow-ups, model selection, and image prompts
  • Fork-point selection from the session's real user-message entries
  • Optional Tailscale Serve publishing for HTTPS access from authorized tailnet identities

A linked appears at the far left of Pi's first footer line, immediately before the directory. Click it to open that session directly, or run /web to display its URL.

The server is intentionally tokenless so installed iOS home-screen links remain stable. It binds only to localhost unless explicitly published through Tailscale Serve. Local machine users are therefore inside the trust boundary; remote access relies on Tailscale Service grants, which must be limited to trusted identities. Browser WebSockets also require an exact same-host Origin, preventing unrelated websites from driving shell-capable sessions. Do not expose the localhost port with a generic reverse proxy or Tailscale Funnel.

The daemon is detached from the Pi process and remains available after every TUI closes. On normal TUI exit, the extension verifies that daemon and republishes the configured Tailscale route to its live port before disconnecting. Run /web-background to perform the same handoff explicitly and show the resulting URL.

Only one daemon owns machine-wide discovery (the shared state file and the Tailscale Serve route) at a time. A newly started daemon defers to a healthy one instead of racing it for the port, and /api/health reports the daemon's checkout root and whether its client assets are servable. A later spawn replaces a daemon whose checkout no longer exists (so a deleted install can never keep serving 404s while still passing health checks), and Pi sessions only adopt daemons that can actually serve the web app. Isolated daemons using a custom PI_WEB_STATE_FILE cannot modify the machine-wide Tailscale route.

Browser-created sessions use Pi's RPC mode, while native Pi processes keep their physical TUI and publish semantic session events. The browser never requests an isolated TUI repaint or resizes the native terminal, avoiding the CPU starvation that full viewport rendering can cause on long sessions. Bun must be installed on the machine running the web server.

Tailscale

If Tailscale is installed and connected, opt into tailnet-only publishing with /web-tailscale on. The running server immediately configures Tailscale Serve to proxy its HTTPS MagicDNS address to the localhost-only backend, and future starts restore it automatically. /web, the footer link, and /web-tailscale status then use the tailnet URL. Node-level publishing defaults to HTTPS port 8443 to avoid macOS port-443 conflicts.

The equivalent global Pi setting in ~/.pi/agent/settings.json is:

{
  "web": {
    "tailscale": {
      "enabled": true,
      "httpsPort": 8443
    }
  }
}

Use /web-tailscale off to remove Pi's active Serve route immediately and disable publishing on future starts. This integration uses Tailscale Serve, not Funnel, so it remains tailnet-only. Access is controlled by your Tailscale Service grant.

To publish as a named entry on the Tailscale Services page, use /web-tailscale on pi-web or set serviceName: "pi-web". Named Services require this machine to be a tagged node, an admin-defined svc:pi-web resource, approval (or auto-approval), and an access grant. Pi reports Tailscale's actionable error until those requirements are met.

For frontend development, run bun run webDev. Production assets in web/dist are not checked in. Source and Git checkouts rebuild them whenever the web server starts, while registry releases build and bundle them during npm pack/npm publish; run bun run webBuild manually only to preview a production build without starting the server.

Prompt templates

  • /address-pr gets the current pull request ready to merge by addressing review comments, conflicts, and CI failures.

Install

GitHub Packages

GitHub's npm registry requires authentication. Create a classic personal access token with read:packages, then either log npm in or configure the scope in ~/.npmrc:

npm login --scope=@vessup --auth-type=legacy --registry=https://npm.pkg.github.com
# ~/.npmrc
@vessup:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}

With the npm scope authenticated, install the package through Pi:

pi install npm:@vessup/pi-kit
# Pin CI and other reproducible installs to a release:
pi install npm:@vessup/pi-kit@0.1.2

In GitHub Actions, grant the job packages: read, expose its token as NODE_AUTH_TOKEN, and configure npm before Pi starts:

permissions:
  contents: read
  packages: read

steps:
  - name: Configure GitHub Packages
    shell: bash
    run: |
      echo '@vessup:registry=https://npm.pkg.github.com' >> ~/.npmrc
      echo '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}' >> ~/.npmrc
  - run: pi install npm:@vessup/pi-kit@0.1.2
    env:
      NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

If a consuming repository cannot read the package with its GITHUB_TOKEN, grant that repository access in the package settings or use a read:packages token stored as a repository secret.

Use /reload in a running Pi session after installing. Update an unpinned registry installation later with:

pi update --extensions

Source checkout

For local development, or as an alternative to the registry, install this checkout or Git repository directly:

pi install ~/vessup/pi-kit
pi install git:git@github.com:Vessup/pi-kit.git
# or
pi install git:github.com/Vessup/pi-kit

Development

This repository uses Bun for dependency management and scripts. Pi still loads the package normally from a local path or Git source; the web-session extension starts Bun only when a Pi session begins. Runtime dependencies are declared in package.json, and bun.lock is the checked-in source of truth. Pi may invoke npm install internally when reconciling a Git package, but that does not require a separate npm lockfile or change this repository's Bun workflow.

bun install --frozen-lockfile
bun run check
bun test
bun run webBuild
pi -e ./extensions/session-footer.ts -e ./extensions/pr-footer.ts -e ./extensions/subagents.ts -e ./extensions/worktree.ts -e ./extensions/web-sessions.ts -e ./extensions/auto-router.ts

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages