---
slug: "pi-codexbar"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/harrisiirak/pi-codexbar@main/README.md"
repo: "https://github.com/harrisiirak/pi-codexbar"
source_file: "README.md"
branch: "main"
---
# pi-codexbar

A [pi](https://github.com/badlogic/pi-mono) coding-agent extension that surfaces [CodexBar](https://github.com/steipete/CodexBar) provider usage — session, weekly and monthly quota windows, remaining credits, and plan/login info — directly inside the pi TUI. It renders a live footer widget below the input editor, exposes `/codexbar-status` and `/codexbar-switch` slash commands, and refreshes automatically when the active provider/model changes.

It is intentionally a thin wrapper: all provider knowledge lives in the CodexBar CLI. This extension only maps pi's provider identifiers to CodexBar's, runs `codexbar usage …`, caches the JSON, and paints it.

## Background

pi-coding-agent talks to many AI providers (Anthropic, OpenAI, Copilot, OpenRouter, Gemini, …). Each one has its own billing / quota model, and none of them is visible from inside the agent. [CodexBar](https://github.com/steipete/CodexBar) is a local menubar app + CLI that already aggregates this state for you from its bundled provider adapters, without requiring explicit logins.

This extension stitches the two together:

- pi emits `session_start`, `agent_end`, `model_select` events → the extension asks CodexBar for the current provider's usage and rewrites the footer.
- The user runs `/codexbar-status [provider]` → the extension fetches fresh state and renders it as both a widget and a notification.
- The user runs `/codexbar-switch <query>` → the extension ranks matching models by remaining usage budget and switches to the best candidate.
- Built-in keys and user aliases are registered as virtual models under the `codexbar` provider. Selecting one from the Pi model picker resolves it to the best-ranked real model automatically.
- Results are cached on disk with a short TTL so footer redraws don't hammer the CLI.

## Installation

```bash
pi install npm:pi-codexbar
```

Or, for local development, symlink the checkout into pi's extension directory and run `/reload`:

```bash
git clone <repo-url> ~/src/pi-codexbar
ln -s ~/src/pi-codexbar ~/.pi/agent/extensions/pi-codexbar
```

The extension ships TypeScript sources directly (no build step) and is loaded by pi through `tsx`.

## Requirements

| Requirement | Purpose |
|-------------|---------|
| **Node.js ≥ 24** | pi-coding-agent runtime; native TS via `tsx`. |
| **[pi-coding-agent](https://github.com/mariozechner/pi-coding-agent)** | Peer dependency. The extension attaches to `session_start`, `agent_end`, and `model_select` events and registers a slash command. |
| **[CodexBar](https://github.com/steipete/CodexBar) CLI** | Required on `$PATH` (or at one of the known locations below). pi-codexbar never talks to provider APIs directly — it spawns `codexbar usage …` and parses the JSON. |

**CodexBar binary discovery order:**

1. `/usr/local/bin/codexbar` (macOS default)
2. `/usr/bin/codexbar`
3. `/opt/homebrew/bin/codexbar`
4. Whatever `which codexbar` resolves to in the current shell

If nothing is found the footer falls back to `codexbar: unavailable` and `/codexbar-status` reports the error — the extension never crashes pi.

> **Note:** Authentication is handled entirely by CodexBar and its provider adapters. pi-codexbar never stores tokens, never prompts for credentials, and has no opinion about how you logged in (cookies, OAuth, `sessionKey`, …).

## Quick Commands

| Command | Description |
|---------|-------------|
| `/codexbar-status` | Fetch and render usage for the current session's provider. |
| `/codexbar-status <provider>` | Force a specific provider — accepts either a CodexBar id (`claude`, `codex`, `copilot`, `gemini`, `openrouter`) or a pi-native id (it's mapped automatically). |
| `/codexbar-switch list [query]` | List candidate models for a query (shows resolve tier and matching models). |
| `/codexbar-switch <query>` | Switch to the model with the highest remaining usage budget matching the query. Accepts a provider/id (e.g. `anthropic/claude-sonnet-4-20250514`), a provider name (e.g. `openai`), a built-in key (e.g. `cheap`, `vision`, `reasoning`, `long-context`), or a user-defined alias. |
| **Model picker** | Built-in keys and user aliases also appear as `codexbar/cheap`, `codexbar/vision`, etc. in the Pi model picker. Selecting one resolves it to the best-ranked real model — no slash command needed. |
| `/codexbar-switch <query> --dry-run` | Preview the ranked candidates without actually switching. Shows each model’s remaining budget percentage. |
| `/codexbar-switch <query> --exclude=<provider>` | Exclude one or more providers from consideration. Repeat the flag to exclude multiple: `--exclude=openai --exclude=anthropic`. |
| `/codexbar-toggle` | Turn the footer widget on/off in real time. When off, the widget is cleared and `session_start` / `agent_end` / `model_select` no longer refresh it. State is persisted to user-scope `settings.json` under the root `enabled` flag (project-scope override still applies as usual). |

The status command prints a notification with the formatted usage line **and** refreshes the footer widget.

### /codexbar-switch

The switch command resolves a query against the model registry, user aliases, and built-in classifications, then ranks matching models by remaining CodexBar usage budget. It can also be invoked as a tool (`codexbar_switch_model`) from within an agent session.

**Query resolution tiers** (first non-empty tier wins):

| Tier | Match |
|------|-------|
| `exact` | `provider/id` literal, e.g. `anthropic/claude-sonnet-4-20250514`. |
| `registry` | Token-subsequence match against model ids in pi's registry — `opus-4-7` matches `claude-opus-4-7-20251022`, `gemini-3.1` matches `gemini-3.1-pro`. |
| `alias` | User-defined key in `aliases.json` (see below). |
| `builtin` | One of the built-in classifications listed below. |

**Built-in classifications:**

| Key | Selects |
|-----|---------|
| `cheap` | Top 5 models by ascending total cost (input + output per 1M tokens) |
| `vision` | Models that accept image input |
| `reasoning` | Models with reasoning enabled |
| `long-context` | Models with context window ≥ 200K tokens |

**Notification conventions:**

| Notification | Meaning |
|-------------|---------|
| `⏳` | Resolving candidates… |
| `📊` | Preview / list / dry-run result |
| `✅` | Successfully switched |
| `⚠️` | Validation error (bad flags, unknown query) |
| `❌` | Runtime error (usage unavailable, switch failed) |

User-defined aliases can be placed in `~/.pi/agent/extensions/pi-codexbar/aliases.json` or `~/.pi/agent/extensions/model-switch/aliases.json`. Each key maps to a model string (`provider/id`) or an array of model strings. The extension merges both files, with `pi-codexbar` taking precedence on collisions.

#### Model picker aliases

Built-in keys (`cheap`, `vision`, `reasoning`, `long-context`) and all user-defined aliases are registered as virtual models under the `codexbar` provider. They show up in the Pi model picker and can be selected like any other model.

When you pick e.g. `codexbar/cheap`:

1. `model_select` fires with `{ provider: 'codexbar', id: 'cheap' }`.
2. The extension calls `runSwitch({ action: 'switch', query: 'cheap', excludeProviders: ['codexbar'] })` against the full model registry.
3. The winner (best-ranked real model) is activated via `pi.setModel()`.
4. A notification confirms the switch: `✅ cheap → anthropic/claude-haiku-4-5`.

The virtual model is never used to stream requests — it's a one-click shortcut that always resolves to a real provider.

> **Note:** The virtual `codexbar` provider has placeholder request config (no real API endpoint). If alias resolution fails, the extension notifies you with the specific reason and leaves the virtual entry active so you can pick another.

#### `codexbar_switch_model` tool

The same functionality is exposed as a tool so an agent can switch itself mid-session. Parameters:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `action` | `'switch'` \| `'list'` | yes | `switch` picks the best-ranked model and activates it; `list` returns resolved candidates without changing state. |
| `query` | `string` | optional | Model selector — see *Query resolution tiers* above. Empty reuses the current provider's models. |
| `excludeProviders` | `string[]` | optional | Pi-native provider ids to exclude from ranking (e.g. `['openai', 'anthropic']`). |
| `dryRun` | `boolean` | optional | With `action: 'switch'`, return the ranked budget breakdown without activating any model. |

Returns a single text content block. On success: `✅ Switched to <provider>/<id>` (for `switch`), or the formatted candidate table (for `list` / `dryRun`). On failure: `❌ <reason>`. The tool never throws — errors are always returned as text so the agent can react.

#### Usage examples

All examples assume pi is running and the CodexBar CLI is on `$PATH`. Slash commands and tool calls are interchangeable — the tool is what the agent calls autonomously, the slash command is what you type.

**1. Switch to any Claude model with the most remaining budget**

```
/codexbar-switch anthropic
```

Resolves via the `registry` tier (provider name), ranks the matching Anthropic models by remaining session/weekly/monthly window, activates the winner.

**2. Pick the cheapest model right now (by cost × budget)**

```
/codexbar-switch cheap
```

`cheap` is a built-in that shortlists the five cheapest models by total token cost, then ranks them by remaining budget so you get the cheapest model that still has headroom.

**3. Switch by token subsequence (partial model id)**

```
/codexbar-switch opus-4-7
```

Matches `anthropic/claude-opus-4-7-20251022` (and any other id where the tokens `opus`, `4`, `7` appear in order). Useful when you can't remember the exact date stamp.

**4. Switch by exact `provider/id`**

```
/codexbar-switch anthropic/claude-sonnet-4-20250514
```

Top-priority `exact` tier — never ambiguous, skips ranking (only one candidate).

**5. Preview without switching**

```
/codexbar-switch reasoning --dry-run
```

Ranks the `reasoning` built-in's candidates and prints the budget breakdown, but leaves the active model unchanged. Good for "what would happen if I switched right now?".

**6. Exclude providers**

```
/codexbar-switch coding --exclude=openai --exclude=anthropic
```

Resolves the `coding` alias, drops any OpenAI/Anthropic candidates, ranks the rest. Repeat `--exclude` to stack, or use `--exclude=openai,anthropic` with a comma.

**7. List candidates without ranking**

```
/codexbar-switch list vision
```

Shows the tier that matched and the list of candidate `provider/id`s for the `vision` built-in. No CodexBar calls, no budget fetch — pure resolution preview.

**8. Agent tool call: inspect before switching**

```json
{
  "name": "codexbar_switch_model",
  "input": { "action": "list", "query": "opus" }
}
```

Returns a text block with the resolved candidates so the agent can reason about which to pick.

**9. Agent tool call: budget-aware switch with exclusions**

```json
{
  "name": "codexbar_switch_model",
  "input": {
    "action": "switch",
    "query": "cheap",
    "excludeProviders": ["openai"],
    "dryRun": false
  }
}
```

Picks the cheapest non-OpenAI model that still has budget and activates it.

#### `aliases.json`

User aliases let you define your own keys for `/codexbar-switch <key>`. Each value is either a single `provider/id` string or an array of them (first-match wins inside an array, but pi-codexbar ranks the whole array by remaining budget before picking).

**File locations and precedence:**

1. `~/.pi/agent/extensions/pi-codexbar/aliases.json` (pi-codexbar-specific)
2. `~/.pi/agent/extensions/model-switch/aliases.json` (shared with the [pi-model-switch](#interop-with-pi-model-switch) extension)

Both files are loaded and merged; pi-codexbar's file wins on key collisions.

**Example `aliases.json`:**

```json
{
  "default": "openai/gpt-5.4",
  "coding": [
    "openai/gpt-5.4",
    "anthropic/claude-sonnet-4-20250514",
    "github-copilot/claude-sonnet-4.6"
  ],
  "reasoning": [
    "anthropic/claude-opus-4-7-20251022",
    "openai/gpt-5.4"
  ],
  "sonnet": [
    "anthropic/claude-sonnet-4-20250514",
    "github-copilot/claude-sonnet-4.6"
  ],
  "cheap-local": [
    "ollama/llama-3.3-70b",
    "ollama/qwen2.5-coder-32b"
  ]
}
```

With that file in place, `/codexbar-switch coding` evaluates all three coding models, fetches their CodexBar usage, and activates whichever has the most budget left. `/codexbar-switch sonnet` does the same for the two Sonnet routes — handy when you want the Sonnet quality tier and don't care which backend serves it.

#### Interop with pi-model-switch

pi-codexbar intentionally reads `~/.pi/agent/extensions/model-switch/aliases.json` so aliases you've already defined for the [pi-model-switch](https://github.com/badlogic/pi-mono) extension (slash command + tool for direct model switching) are available in pi-codexbar for free. You don't need to duplicate them.

Suggested arrangement:

- Put shared aliases (`coding`, `sonnet`, `cheap`, …) in `model-switch/aliases.json` — both extensions see them.
- Put pi-codexbar-only overrides in `pi-codexbar/aliases.json` — e.g. a `budget` alias that only includes providers with CodexBar support.
- Use pi-model-switch when you want a direct, deterministic switch to a known model.
- Use pi-codexbar (`/codexbar-switch`) when you want the same alias resolved to whichever candidate has the most remaining budget right now.

## Footer Widget

On every `session_start`, `agent_end`, and `model_select`, the extension resolves the active model's provider, maps it to a CodexBar id, and repaints a widget called `codexbar-usage` above or below the input editor.

A typical line looks like:

```
claude (max) │ S(5h): 42% │ W(7d): 18%M(1mo): 6% │ $23.45 │ ⏱ Apr 20, 3:00 PM
```

**Tokens available in the format string:**

| Token              | Meaning                                                |
|--------------------|--------------------------------------------------------|
| `{provider}`       | CodexBar provider id (e.g., `claude`, `codex`).        |
| `{plan}`           | Login method / plan label (`max`, `api`, …).           |
| `{session}`        | Primary window usage — e.g., `S(5h): 42%`.             |
| `{weekly}`         | Secondary window usage.                                 |
| `{monthly}`        | Tertiary window usage (if the provider reports one).   |
| `{credits}`        | Remaining credit balance, formatted as `$X.XX`.        |
| `{session_reset}`  | Human-readable reset time for the primary window.      |
| `{weekly_reset}`   | Reset time for the secondary window.                   |
| `{monthly_reset}`  | Reset time for the tertiary window.                    |

Empty tokens and the `⏱` marker are stripped from the rendered line, so you can leave tokens you don't care about in the format string without collecting stray `│` separators.

Windows exceeding `colors.highThreshold` percent (default 80) switch from their normal color to the matching `*High` color (e.g., `session` → `sessionHigh`). Override the threshold or individual colors in settings.

## Configuration

pi-codexbar merges configuration from **three layers**, in increasing precedence (last wins):

| Scope | Path | Priority |
|-------|------|----------|
| Builtin | `<package root>/settings.json` & `<package root>/provider-mappings.json` | Lowest |
| User | `~/.pi/agent/extensions/pi-codexbar/{settings,provider-mappings}.json` | Medium |
| Project | `<cwd>/.pi/extensions/pi-codexbar/{settings,provider-mappings}.json` | Highest |

Each layer is optional. Missing keys fall back to the previous layer and, ultimately, to in-code defaults. Merging is shallow per top-level key (`footer`, `colors`, provider mapping entries) — provide only what you want to override.

### User-level overrides

Drop a file at `~/.pi/agent/extensions/pi-codexbar/settings.json`:

```json
{
  "footer": {
    "format": "{provider} │ {session} │ {credits}",
    "placement": "aboveEditor"
  },
  "colors": {
    "provider": "#ffffff",
    "session": "#00ff88",
    "sessionHigh": "#ff0055",
    "highThreshold": 75
  }
}
```

### Project-level overrides

Place a file at `<repo>/.pi/extensions/pi-codexbar/settings.json`. Use this when a given repo should always show a specific layout — e.g., a team monorepo where everyone is on a flat-rate plan and the `{credits}` token should be hidden.

### Settings schema

| Key                          | Type   | Default                                                              |
|------------------------------|--------|----------------------------------------------------------------------|
| `enabled`                    | bool   | `true` — toggled by `/codexbar-toggle`; persisted at user scope.     |
| `footer.format`              | string | `{provider} {plan} │ {session} │ {weekly}{monthly} │ {credits} │ ⏱ {session_reset}` |
| `footer.placement`           | enum   | `belowEditor` (also accepts `aboveEditor`)                           |
| `colors.provider`            | hex    | `#d787af`                                                            |
| `colors.plan`                | hex    | `#808080`                                                            |
| `colors.session` / `…High`   | hex    | `#5faf5f` / `#ff5f5f`                                                |
| `colors.weekly` / `…High`    | hex    | `#00afaf` / `#ff8700`                                                |
| `colors.monthly` / `…High`   | hex    | `#af87d7` / `#ff5f5f`                                                |
| `colors.reset`               | hex    | `#808080`                                                            |
| `colors.separator`           | hex    | `#4e4e4e`                                                            |
| `colors.credits`             | hex    | `#febc38`                                                            |
| `colors.error`               | hex    | `#ff5f5f`                                                            |
| `colors.highThreshold`       | number | `80` (percent; switches `session`/`weekly`/`monthly` to `*High` colors above this value) |

See the bundled [`settings.json`](https://github.com/harrisiirak/pi-codexbar/blob/HEAD/settings.json) for the complete default payload.

### Provider mappings

pi and CodexBar use different provider identifiers. The bundled [`provider-mappings.json`](https://github.com/harrisiirak/pi-codexbar/blob/HEAD/provider-mappings.json) translates pi ids (and provider family names) into the canonical CodexBar id:

```json
{
  "anthropic":      "claude",
  "openai-codex":   "codex",
  "github-copilot": "copilot",
  "google":         "gemini",
  "openrouter":     "openrouter"
}
```

**Lookup rules:**

1. Exact match on the lower-cased pi provider id.
2. Prefix match — `openai-codex-preview` → `codex`.
3. Otherwise the id is passed through untouched.

Override or extend on either scope:

```jsonc
// ~/.pi/agent/extensions/pi-codexbar/provider-mappings.json
{
  "my-internal-proxy": "openrouter",
  "anthropic":         "claude-experimental"
}
```

Only the entries you list are merged in; everything else still comes from the bundled mapping (or the user layer if you're overriding at the project scope).

**Resolution priority:** project mapping > user mapping > bundled mapping > identity passthrough.

### Common Recipes

| Goal | Config change |
|------|---------------|
| Minimal footer — provider + credits only | `footer.format = "{provider} │ {credits}"` |
| Warn earlier when approaching quota | `colors.highThreshold = 50` |
| Show the widget above the editor | `footer.placement = "aboveEditor"` |
| Route a custom proxy provider to CodexBar | Add a row to `provider-mappings.json` (user or project scope) |
| Disable the widget in a flat-rate repo | Project-scope settings with `footer.format = "{provider}"` |

## Caching

Provider usage is cached under `~/.pi/agent/extensions/pi-codexbar/.cache/usage-<provider>.json` with a **60-second TTL**. This keeps footer refreshes on every `agent_end` from spawning one CodexBar process per turn.

- Stale files are overwritten on the next successful fetch.
- Errors are not cached — a failing run always re-hits the CLI.
- To force a refresh, either wait out the TTL or delete the cache file.

## Events & Runtime Behavior

| Pi event | Behavior |
|----------|----------|
| `session_start` | Detect provider from `ctx.model.provider`, fetch usage, render widget. Also re-registers the virtual `codexbar` provider so alias edits pick up new entries. |
| `agent_end` | Refresh the widget after every agent turn. |
| `model_select` | When the selection targets `codexbar/<alias>`, resolve the alias through `runSwitch` and activate the winner. Otherwise re-fetch usage for the new provider's footer. |

All three handlers swallow errors internally — a failed CodexBar call never blocks the event loop or interrupts pi.

### Alias resolution notifications

| Notification | Meaning |
|-------------|---------|
| `✅ <alias> → <provider>/<id>` | Alias resolved; winner activated. |
| `⚠️ No candidates matched "<alias>".` | Alias resolution produced zero candidates. |
| `⚠️ Alias resolution did not return a switch candidate.` | Resolver returned `list`/`preview` instead of `switch`. |
| `❌ Failed to switch to <provider>/<id> — API key may not be configured.` | `pi.setModel()` returned `false` (missing auth for the resolved model). |

## Development

```bash
npm install
npm test          # node --test, via tsx loader
npm run run       # smoke-run the extension entry point
```

- No bundler / transpile step — the extension ships `src/*.ts` and pi loads it through `tsx`.
- CI runs `npm ci && npm test` on push and pull requests (see `.github/workflows/`).

## License

MIT.
