@timmyjl12/pi-model-picker
v0.1.0
Published
A stunning full-screen model picker overlay for the pi coding agent — fuzzy search, provider scopes, favorites, recents, and aligned per-model metrics.
Maintainers
Readme
@timmyjl12/pi-model-picker
A full-screen model picker overlay for the pi coding agent — section tabs, provider drill-down, a whole-catalogue Search tab, aligned metric columns, favorites and recents, and an Agents tab that can retarget the model an agent definition uses.
Built to emulate and improve on oh-my-pi's alt+p picker.
╭─ Model Picker — All ───────────────────────────────────────────────────────── 10 models · 5 providers · 2 scoped ╮
│ │
│ Session-only switch — the model changes for this session only · ctrl+D also saves it as the default │
│ ⌕ Search models by name, id, or provider… 10/10 · smart │
│ │
│ All 10 Search 10 Recent 2 Favorites 2 Scoped 2 Agents 4 │
├─ Providers ─────────────┬────────────────────────────────────────────────────────────────────────────────────────┤
│ ● All models 10 │ MODEL ◫ ↧ $ │
│ ○ Anthropic 3 │ ▾ Anthropic 3 │
│ ○ Google 2 │ claude-haiku-4-5 200k 64k $1/5 │
│ ○ Local Llama 1 │ claude-opus-4-5 200k 64k $15/75 │
│ ○ Openai 3 │ ● claude-sonnet-4-5 200k 64k $3/15 │
│ ○ OpenRouter 1 │ ▾ Google 2 │
│ │ gemini-2.5-pro 1M 64k $1.25/10 │
│ │ ▾ Openai 3 │
│ │ gpt-5.2 400k 64k $1.25/10 │
├─────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────┤
│ Claude Sonnet 4.5 · Anthropic · 200k ctx · 64k out · $3/15 per M · reasoning · vision · anthropic-messages │
│ ● current · ○ scoped · ◉ thinking high · ctrl+T to change │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ↑↓ move · → models · Tab section · ctrl+F ★ favorite · ctrl+T thinking · ctrl+S sort · esc close │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯That frame is real output — run
npm run previewto render every screen, including the Agents tab.
Install
pi install npm:@timmyjl12/pi-model-pickerTry it without installing:
pi -e npm:@timmyjl12/pi-model-pickerFrom a local checkout:
pi install /absolute/path/to/pi-model-pickerRestart pi (or run /reload) after installing.
Use
| Trigger | |
|---|---|
| /model-picker | open the picker |
| /models | alias, for muscle memory |
| ctrl+/ | keyboard shortcut, also Ctrl+Numpad-/ (needs a terminal with extended key reporting, e.g. Windows Terminal, Kitty, WezTerm, Ghostty); press again inside the picker to close it |
| ctrl+shift+m | keyboard shortcut that works in every terminal; also toggles |
/model itself is a reserved built-in pi command and cannot be overridden, which is why the primary
command is /model-picker.
Navigation
Three levels: tabs → providers → models.
All │ Search │ Recent │ ★ Favorites │ Scoped │ Agents ← Tab / shift+Tab
↓
● All models 10 ↑↓ walks the providers
○ Anthropic 3
○ Openai 9 → drills into that provider's models
↓
❯ ● claude-sonnet-4-5 ↑↓ walks models, Enter chooses, ← backs out| Key | Action |
|---|---|
| Tab / shift+Tab | next / previous section |
| ↑ ↓ | move within the focused pane |
| → | drill into the model list (or Enter on a provider) |
| ← | back out to the provider list |
| PageUp PageDown | move by a page |
| Home End | jump to the first / last row |
| Enter | use this model for the session |
| t / T | cycle the highlighted model's thinking level forward / back (model list, search box closed) |
| / | open the search box, where every letter types — use it for a query starting with t; Esc leaves it |
| ctrl+D | use it and save it as the startup default |
| ctrl+F | add / remove the highlighted model from favorites |
| ctrl+T | cycle the thinking level for the highlighted model (same as t) |
| ctrl+S | cycle sort: smart → name → cheapest → largest context |
| ctrl+G | toggle provider group headers |
| F5 | re-read the model catalogue and agent definitions |
| type | fuzzy search across name, id, provider, and provider/id, within the active section (the Search tab searches the whole catalogue) |
| ctrl+U | clear the search |
| esc | unwind one level: back out of the model list → clear the search → close |
| ctrl+C | close |
Mouse works too: wheel scrolls the model list, hover highlights, clicking a provider focuses it, and clicking a model selects it — click again to use it.
F5 re-pulls the catalogue and the agent definitions. A background refresh that lands while the
overlay is already open would otherwise only show up on the next open, so F5 gives you an explicit
way to pull; the status row reports while it is in flight, and a failed pull leaves the list exactly
as it was rather than emptying it.
The Agents tab
Reads agent definitions from <agent dir>/agents (i.e. ~/.pi/agent/agents) and the nearest
<cwd>/<config>/agents, with project agents shadowing user agents of the same name — the same
precedence pi itself uses. A .md file only counts as an agent when its frontmatter has string
name and description, which is what keeps shared rule files such as AGENTS.md out of the list.
The left pane lists agents (filled dot = the agent pins its own model, hollow = it inherits the session's), and the right pane inspects the highlighted one: model, thinking level, tools, source, file path, and prompt size.
Enter assigns a model. That opens the picker in assign mode — same tabs, same search — and on
selection shows you the exact frontmatter change and asks for confirmation before writing:
Update explore?
model: neuralwatt/deepseek-v4.1-flash-flex → openai/gpt-5.2
File: /home/u/.pi/agent/agents/explore.md
A timestamped backup is written alongside it.The write is deliberately surgical:
- Only the top-level
model:line (andthinking:, and only if you actually cycled the level in the picker) is rewritten. Assigning a model never silently resets an agent's own thinking level. - The frontmatter region is located with the same rule as pi's
parseFrontmatter, so the text edited is exactly the text pi reads. Bodies containing---rules are safe. - Commented-out alternates (
#model: ...), inline trailing comments, indentation, the entire body, every other key, CRLF endings, and a leading BOM are all preserved byte-for-byte. - Indented keys inside nested YAML (an
output:schema, for instance) are never matched. - A timestamped
.bak-model-picker-<stamp>copy is written first, then the edit goes in atomically. - The file is re-parsed before writing, and the edit is refused outright if the parsed name does not match the agent you selected.
What it does
Search that ranks like you'd expect. Four relevance tiers — exact id/name/selector, contiguous substring, all-tokens-present, then fuzzy subsequence. Tiers always win over ordering heuristics, so a perfect text match is never buried under your recent history. Inside a tier, favorites and recents break ties.
A Search tab for the whole catalogue. The search field filters within the active section — typing on Recent only ranks your recents, on Favorites your stars. The Search tab always searches the entire catalogue by model id or name, relevance-ranked, so a query finds a model wherever it lives. While a query is active its tab count shows live matches, and the provider facet pane still works on the results.
Sections as colored tabs. All · Search · Recent · Favorites · Scoped (--models /
enabledModels) ·
Agents — each with a live count, each with its own hue drawn from the active theme's palette. Recent
and Favorites are always visible, even at zero, so they're discoverable before you've used them.
Providers as a facet list. Whatever the active section contains is grouped by provider, with an "All …" row first. Search turns the counts into per-provider match counts and dims providers with no hits, so you can see where matches live before drilling in.
Aligned metrics with header icons. ◫ context, ↧ output limit, $ price — measured per
visible window and right-aligned so they line up exactly, including with their own header row.
Columns collapse independently at narrow widths rather than truncating.
Saved preferences, in their own file. Recents, favorites, sort mode, and the last section persist
to ~/.pi/agent/model-picker.json — write-through, atomic, and never mixed into pi's own
settings.json. (ctrl+D is the only thing that touches settings.json, and it goes through pi's
SettingsManager so every other key is preserved.)
Thinking level in the same flow. Pick the level you want before switching, instead of switching
and then cycling: t steps forward, T steps back, ctrl+T still works. Levels a model marks
unsupported via thinkingLevelMap are skipped, and the extended tiers (xhigh, max) are only
offered when the model entry explicitly maps them — matching pi-ai's capability rules — so the
picker never picks a level pi's runtime clamp would take away.
Responsive and theme-native. Everything paints through pi's Theme tokens (fg, bg, bold,
the per-level thinking colors, and the scrollbar tokens), so it follows whichever theme you have
active. At ≥ 64 columns you get the two-pane layout; below that the provider pane is dropped and the
list runs full width, with the tab track still on top. The overlay fills the terminal (100% width
and height, no inset margin) and hides entirely on terminals narrower than 48 columns or shorter
than 20 rows.
Over-context awareness. When the session transcript exceeds a model's context window, that
model's context cell turns to the warning color and the detail block says exactly what will happen:
400k of transcript exceeds this model's 32.8k window — switching may compact. The model stays
selectable.
Improvements over oh-my-pi's picker
oh-my-pi's picker is a fine piece of work and the direct inspiration for this one. Where this goes further:
| | oh-my-pi | this picker |
|---|---|---|
| Navigation | flat list + @role virtual rows | tabs → providers → models, with ←/→ drill-in |
| Overlay size | bottom-anchored, 40% of height | full-screen: 100% width and height |
| Sections | single ─── separator | colored, counted tabs that stay visible at zero |
| Columns | context, cost, intelligence, perf | context, output limit, cost — with header icons and measured alignment |
| Scope counts while searching | — | per-provider match counts, zero-match providers dimmed |
| Thinking level | separate control | t/T (or ctrl+T) in the picker, applied with the switch |
| Agents | @role rows bound to omp's role system | an Agents tab that inspects and rewrites agent definitions |
| Over-context | row grayed, context>Nk marker | warning-colored cell plus a detail line explaining the consequence |
| Mouse | reserved for fullscreen overlays | wheel, hover, and click in the picker overlay |
| Narrow terminals | fixed layout | provider pane dropped, tabs kept, bottom-pane scrolling |
| Glyph safety | Nerd Font icons with spacing workarounds | plain symbols by default, PI_MODEL_PICKER_GLYPHS=ascii for strict terminals |
Two omp features are not reproducible on pi, and the code is honest about it rather than faking
them: omp's ~118t/s and intelligence columns (pi's Model has no tps or int fields), and omp's
Nerd-Font icon vocabulary — omp's picker reaches for nine theme token groups (boxRound, icon,
nav.cursor, status.*, symbol, sep.powerline*, spinnerFrames, fg/bg, getFgAnsi) and
pi's Theme exposes only three of them (fg/bg/bold, plus getFgAnsi). So glyphs are hardcoded
here, with an ASCII fallback, and colors come from pi's semantic tokens instead.
Improvements over rilham97/pi-model-picker
That extension is a single-provider-tab list. Notable differences:
- Search is ranked and tiered, not a single
includes()filter. - Favorites, recents, and the configured scope are real counted sections, not just remembered search strings per tab.
- Provider facets show counts and search-match counts, and only providers with hits stay bright.
- Aligned metric columns with a header row, a detail block, and an agent inspector.
- Mouse support, responsive narrow layout, memoized rendering, and a real test suite.
- Imports the current
@earendil-works/*packages. (rilham97still imports the older@mariozechner/*scope, and itsFocusableimplementation does not set the embeddedInput'sfocusedflag, so IME cursor positioning doesn't work.)
Development
npm install
npm run preview # render every screen to your terminal
npm test # vitest suite
npm run typecheck # tsc --noEmit (includes tests)
npm run check # bothThe layout is a pure function of (width, terminal rows, catalogue, agents, state). The test suite's
highest-value assertions are the frame contracts: every state is rendered at nine widths and four
terminal heights, and every line must measure exactly the requested width and the frame must be
exactly the documented height, because the TUI rejects anything wider and the overlay host clips
anything taller. The agent editor is tested against a temp directory, including commented-out model
lines, ----heavy bodies, CRLF files, and nested YAML.
Layout
index.ts command/shortcut registration, overlay wiring, choice application, agent writeback
src/picker.ts the overlay component: tabs, drill-down, rendering, input, mouse
src/models.ts model entries, ordering, tiered search, metric formatting
src/agents.ts agent discovery + surgical frontmatter editing
src/chrome.ts frame chrome, tab track, alignment helpers, glyph set
src/state.ts MRU / favorites / sort persistence
test/ unit + integration tests, theme/TUI doubles, visual previewConfiguration
| Variable | Effect |
|---|---|
| PI_MODEL_PICKER_GLYPHS=ascii | use ASCII glyphs and word column headers instead of ❯ ● ○ ★ ▾ ⌕ ◫ ↧ ◉ |
| PI_MODEL_PICKER_STATE=/path/to.json | override the state file location |
| PI_CODING_AGENT_DIR | pi's own override; also relocates the agent definitions this read/writes |
License
MIT
