<div align="center">

# 🔇 pi-hide-providers

**Hide providers and models from the selector in [pi](https://github.com/earendil-works/pi-coding-agent)**

_Filter the model picker so you only see the models you care about._

[![pi extension](https://img.shields.io/badge/pi-extension-blueviolet)](https://github.com/earendil-works/pi-coding-agent)
[![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)

<img src="assets/demo.jpg" alt="pi-hide-providers interactive model selector" width="800">

</div>

---

## The Problem

Pi's model selector shows **every** available model from every configured provider. If you have Ollama running with 20 local models, or an OpenRouter account with hundreds of options, the model list becomes noisy and slow to navigate. There's no built-in way to say *"I never want to see these providers/models in the selector."*

Pi has `enabledModels` in `settings.json` as an allowlist, but maintaining it manually is tedious — you have to list every model you *do* want, and clobber `settings.json` with hundreds of entries. What you really want is a **blocklist**: *"hide everything from these providers, except the ones I explicitly use."*

## The Solution

`pi-hide-providers` gives you a blocklist that **completely removes** models from all lists — not an allowlist, not a scoped subset:

- Define hide rules in a config file (`~/.pi/agent/hide-providers.json` or `.pi/hide-providers.json`)
- On session start, the extension patches pi’s underlying `ModelRuntime` accessors (with a `ModelRegistry` fallback for older pi versions) to filter out hidden models
- The `/model` selector, `Ctrl+P` cycling, `--list-models`, and session restoration all see only visible models
- `/hide-models reset` removes the runtime/registry patch — all models return immediately
- Changes via `/hide-models add` and `/hide-models remove` take effect immediately (no reload needed)
- Interactive `/hide-models` TUI lists visible models first in user-toggle order, then hidden models in their original registry order, and keeps the cursor at the same index after each toggle — matching pi core's `/scoped-models` UX
- `/hide-models` subcommands for adding, removing, and inspecting rules

No `settings.json` is modified. No 250+ entry explosion. No allowlist semantics.

## Usage

### Interactive Commands

| Command | What it does |
|---------|-------------|
| `/hide-models` | Open interactive TUI to select providers/models to hide |
| `/hide-models add ollama` | Hide the entire `ollama` provider |
| `/hide-models add openrouter/cheap-model` | Hide a specific model from `openrouter` |
| `/hide-models add openrouter/*` | Hide the entire `openrouter` provider (explicit) |
| `/hide-models remove ollama` | Remove the hide rule for `ollama` |
| `/hide-models status` | Show current rules, patch status, and hidden model count |
| `/hide-models apply` | Show current hide state (changes are already active) |
| `/hide-models reset` | Unpatch registry — all models return immediately |
| `/hide-models help` | Show usage reference |

### Config File

Create `~/.pi/agent/hide-providers.json` (global) or `.pi/hide-providers.json` (project-local):

```json
{
  "hide": [
    { "provider": "ollama" },
    { "provider": "openrouter", "model": "cheap-model" },
    { "provider": "github-copilot", "model": "gpt-3.5-turbo" }
  ]
}
```

**Rule formats:**

| Rule | Effect |
|------|--------|
| `{ "provider": "ollama" }` | Hide all models from the `ollama` provider |
| `{ "provider": "ollama", "model": "*" }` | Same — explicit wildcard |
| `{ "provider": "openrouter", "model": "cheap-model" }` | Hide only `openrouter/cheap-model` |

Project config (`.pi/hide-providers.json`) takes priority over global config (`~/.pi/agent/hide-providers.json`).

## Installation

**With `pi install`** (recommended):

```bash
pi install npm:pi-hide-providers
```

Or install from GitHub:

```bash
pi install https://github.com/monotykamary/pi-hide-providers
```

**With npm**:

```bash
npm install pi-hide-providers
```

Or in `~/.pi/agent/settings.json`:

```json
{
  "packages": [
    "https://github.com/monotykamary/pi-hide-providers"
  ]
}
```

Then `/reload` or restart pi.

For quick one-off tests:

```bash
pi -e ./hide-providers.ts
```

## How It Works

```
Session starts
  → Extension reads hide-providers.json
  → Patches ModelRuntime (current pi) or ModelRegistry (older pi):
      available model reads → original result filtered by isHidden()
      all model reads       → original result filtered by isHidden()
      model lookup(p, m)    → returns undefined if isHidden(p, m)
  → All downstream consumers see only visible models:
      /model selector, Ctrl+P, --list-models, session restoration

/hide-models add or /hide-models remove:
  → Config updated on disk
  → currentRules updated in memory
  → Patched runtime/registry methods read latest rules via closure
  → Changes take effect immediately (no reload)

/hide-models reset:
  → Restores the original model accessors
  → All models return immediately
```

The SDK doesn't provide a mechanism to remove models from the registry — `registerProvider({ models: [] })` is treated as "no models to register" (override-only), not "remove all models." Monkey-patching the accessor methods is the only way to completely remove models from all lists without touching `settings.json`.

The patches survive model catalog refreshes because they wrap the runtime accessors rather than a model snapshot. On reload, the extension detects an existing patch and updates its rules source.

## Comparison with Alternatives

| Approach | Pros | Cons |
|----------|------|------|
| **pi-hide-providers** (this) | Blocklist — completely removes models from all lists; no `settings.json` writes; changes take effect immediately; survives catalog refresh | Monkey-patches internal model accessors (not an official SDK mechanism) |
| `enabledModels` in `settings.json` (manual) | Built-in, no extension needed | Allowlist — must list every model you want individually; no blocklist support; clobbers settings with hundreds of entries |
| `--models` CLI flag | Per-session scoping | Must pass every time; no persistence |
| `pi.unregisterProvider()` | Restores built-in models after override | Only works for providers registered via `pi.registerProvider()`; can't hide entire providers (empty models array is a no-op) |
| `pi-model-router` scope shim | Dynamic scoping with routing | Heavyweight — full routing system just to filter the model list |

## Development

```bash
npm install
npm test          # Vitest unit tests
npm run typecheck # TypeScript validation
npm run lint:dead # Dead code detection (knip)
```

### Structure

```
.
├── hide-providers.ts   # Main extension
├── src/
│   └── index.ts        # Constants, types, and utilities
├── __tests__/
│   └── unit/
│       └── hide-providers.test.ts
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── knip.json
```

## License

MIT
