pi-mtplx 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,166 +1,69 @@
1
1
  # pi-mtplx
2
2
 
3
- Zero-config [MTPLX](https://github.com/youssofal/MTPLX) integration for the [Pi](https://pi.dev) coding agent. Install it, switch to a `mtplx` model, and pi-mtplx handles the rest: discovering installed MTPLX models, booting a local MTPLX OpenAI-compatible server on demand, switching models transparently, live fan-curve control, and clean shutdown when Pi exits — without ever touching MTPLX processes that Pi doesn't own.
3
+ Run local MLX models with Pi zero config, automatic model discovery, and live token-speed monitoring.
4
4
 
5
- ```
6
- pi install npm:pi-mtplx
7
- ```
8
-
9
- Or from GitHub:
10
-
11
- ```
12
- pi install git:github.com/KrossKinetic/pi-mtplx
13
- ```
14
-
15
- ## Features
16
-
17
- - **Zero-config model discovery** — `/mtplx → Models` lists every model in your MTPLX cache and registers the one you pick into Pi's catalog (`~/.pi/agent/models.json`), no manual provider setup.
18
- - **On-demand autostart** — the first time you run an agent with a `mtplx` model, the local MTPLX server boots itself and waits until `/health` confirms it's serving exactly that model.
19
- - **Transparent model switching** — switch from model A to model B with `/model`; pi-mtplx stops A and starts B before the next request proceeds.
20
- - **Process ownership** — pi-mtplx only ever stops servers it can positively identify as its own (see [Automatic MTPLX Lifecycle](#automatic-mtplx-lifecycle)). Manually started MTPLX servers are left alone.
21
- - **Fan-curve controls** — pick `default`, `smart`, or `max` fan modes; applied at boot and live-updated over the MTPLX thermal endpoint while the server runs.
22
- - **Clean shutdown** — `/quit` stops the MTPLX server this Pi session owns; no orphaned processes.
23
- - **tk/s footer** — a companion extension reports the token generation rate of the last assistant turn (`⚡NN.N tk/s`) in the footer.
24
-
25
- ## Requirements
26
-
27
- - macOS on Apple Silicon (MTPLX requirement)
28
- - The `mtplx` CLI on your `PATH` (pi-mtplx shells out to `mtplx quickstart/stop/list` and reads `GET /health` / `POST /v1/mtplx/thermal/fan_mode`)
29
- - Node.js ≥ 20 (Pi requirement)
30
- - At least one MTPLX model installed (`mtplx install <repo>` or `mtplx models --update`)
31
-
32
- ## Installation
5
+ ## Install
33
6
 
34
- ```
7
+ ```bash
35
8
  pi install npm:pi-mtplx
36
9
  ```
37
10
 
38
- or:
39
-
40
- ```
41
- pi install git:github.com/KrossKinetic/pi-mtplx
42
- ```
43
-
44
- After installing, make sure `mtplx` is on the PATH that Pi runs under, then restart Pi (or `/reload`).
45
-
46
- ## Quick Start
47
-
48
- 1. Ensure a model is installed: `mtplx models` (or `mtplx install <repo-id>`).
49
- 2. In Pi, run `/mtplx` → **Models (register)** and pick your model. This maps your Pi model id to the MTPLX artifact and adds the `mtplx` provider to `~/.pi/agent/models.json`.
50
- 3. `/reload` (or restart Pi), then switch to the model with `/model`.
51
- 4. Send a message. On the first request pi-mtplx boots the MTPLX server and waits for it to be ready.
52
- 5. `/mtplx` → **Toggle (off)** to stop it, or just `/quit` — the server shuts down with Pi.
53
-
54
- ## How It Works
55
-
56
- pi-mtplx runs an OpenAI-compatible MTPLX server at `http://127.0.0.1:8000` and registers a `mtplx` provider in Pi's model catalog pointing at it. Every time an agent turn starts on a `mtplx` model, the extension checks the server's `/health` endpoint; if the right model isn't being served, it fixes that before the request is admitted (see [Automatic MTPLX Lifecycle](#automatic-mtplx-lifecycle)). Model ids are stable: the server is started with `mtplx quickstart --model <ref> --model-id <pi-model-id>`, so `/health` and `/v1/models` report Pi's id and the extension can always tell which model a healthy server is serving.
57
-
58
- ## `/mtplx` Commands
11
+ Restart Pi after installation.
59
12
 
60
- `/mtplx` opens a menu:
13
+ ## What happens on first run
61
14
 
62
- | Choice | What it does |
63
- | --- | --- |
64
- | **Toggle (on/off)** | Starts or stops the MTPLX server. Stopping only affects servers pi-mtplx owns (see below). Starting with a `mtplx` model active boots that model. |
65
- | **Fan Curves** | Choose `default`, `smart`, or `max`. Persisted to `~/.pi/agent/mtplx-fanmode.json`, applied at next boot, and pushed live to a running server via `POST /v1/mtplx/thermal/fan_mode`. |
66
- | **Models (register)** | Lists models from your MTPLX cache (`mtplx list --json`), marks which are already registered in Pi, and registers your pick: saved to `~/.pi/agent/mtplx-models.json` and added to the `mtplx` provider in `~/.pi/agent/models.json`. |
67
- | **Uninstall** | Removes the `mtplx` provider (and only that provider) from `~/.pi/agent/models.json` after a confirmation. The registry file and installed MTPLX models are untouched. |
15
+ Once installed, Pi automatically manages your MTPLX workflow:
68
16
 
69
- ## Model Discovery
17
+ - **Model discovery** — A built-in model (`mtplx-qwen38-27b-optimized-quality`) is pre-registered. More are discoverable via `/mtplx`.
18
+ - **Auto-start** — The MTPLX server starts when you switch to an `mtplx` model and shuts down cleanly when Pi exits.
19
+ - **Token speed** — A `⚡N.N tk/s` indicator appears in the footer showing the generation speed of the last assistant turn.
70
20
 
71
- `/mtplx → Models` shells out to `mtplx list --json`, which reports every artifact in the local MTPLX model cache (`~/.mtplx/models`). Each entry's `repo_id` (or path) is mapped to a Pi model id with the scheme `mtplx-<slug>`, e.g. `Youssofal/Qwen3.8-27B-MTPLX-Optimized-Quality` → `mtplx-qwen38-27b-optimized-quality`.
21
+ ## Commands
72
22
 
73
- Two files back this:
23
+ Run `/mtplx` to open an interactive menu:
74
24
 
75
- - `~/.pi/agent/mtplx-models.json` Pi model id → MTPLX artifact ref. This is pi-mtplx's own registry; new registrations are appended here. (A built-in fallback map ships in the package for `mtplx-qwen38-27b-optimized-quality`.)
76
- - `~/.pi/agent/models.json` Pi's model catalog. pi-mtplx only ever adds to or removes its own `mtplx` provider entry there; it never rewrites other providers.
77
-
78
- Registered models are OpenAI-completions models with a 262144 context window, 65536 max output tokens, zero cost (local), and reasoning enabled.
79
-
80
- ## Automatic MTPLX Lifecycle
81
-
82
- - **Boot**: on the first `before_agent_start` for a `mtplx` model, if nothing healthy is on port 8000, pi-mtplx runs `mtplx quickstart --model <ref> --model-id <id> --profile sustained --fan-mode <mode> --host 127.0.0.1 --port 8000 --ssd-session-cache off`, detached and unref'd, then polls `/health` (500 ms) until it reports the requested model id, up to 3 minutes.
83
- - **Switching**: if `/health` reports a *different* model, pi-mtplx stops it first, then starts the requested one. Requests are serialized: a model is never swapped out while an agent already admitted on the current model is still running.
84
- - **Stopping**: goes through `mtplx stop --host 127.0.0.1 --port 8000 --json` (MTPLX's own graceful stop: SIGTERM → grace period → SIGKILL), then polls `/health` until the port stops answering.
85
- - **Shutdown**: on `/quit` (`session_shutdown` with reason `quit`), pi-mtplx stops the server it can identify as its own. If nothing is running, this is a no-op.
86
-
87
- ### Process ownership
88
-
89
- pi-mtplx distinguishes *its* MTPLX process from one you started manually, and cleanup only touches the former:
90
-
91
- 1. **Session handle** — when pi-mtplx spawns the server, it keeps the child handle. A server it spawned in this session is always owned.
92
- 2. **Health fingerprint** — pi-mtplx always starts its server with `--model-id <pi-model-id>`, and only model ids from its own registry qualify. If `/health` reports a model id that is in pi-mtplx's registry, the server is treated as Pi-owned even across Pi restarts (the process handle doesn't survive a restart, the fingerprint does).
93
-
94
- What this means in practice:
95
-
96
- - A **manually started** MTPLX server (e.g. `mtplx quickstart` with its own flags, or no `--model-id` matching a pi-mtplx registry id) is **never killed** by pi-mtplx — neither on model switch, `/mtplx → Toggle` stop, nor `/quit`. You'll get a console note telling you to use `/mtplx → Toggle` if you want it stopped. If you rely on a manual server, keep its `--model-id` out of pi-mtplx's registry ids, or just run `/mtplx → Toggle` manually.
97
- - A **non-MTPLX** service on port 8000 is never touched; pi-mtplx refuses to start and tells you the port is occupied.
98
- - If MTPLX ever changes what `/health` reports, ownership may degrade to the session handle only — in that case pi-mtplx is conservative and will not stop cross-session servers.
99
-
100
- ## Model Switching
101
-
102
- Switch with `/model` as usual. Behind the scenes, on the next agent turn:
103
-
104
- 1. `before_agent_start` acquires the model lease.
105
- 2. `/health` is checked. If the running server serves a different `mtplx` model, pi-mtplx stops it (ownership rules above) and starts the new one.
106
- 3. Only once `/health` confirms the requested model is served does the request proceed.
107
- 4. On `agent_end` the lease is released; a different model can then be started for the next turn.
108
-
109
- Concurrent same-model requests share the in-flight start; different-model requests queue behind running agents instead of swapping the model out from under them.
25
+ | Option | What it does |
26
+ | -------- | ------------- |
27
+ | **Toggle (on/off)** | Start or stop the MTPLX server |
28
+ | **Fan Curves** | Set the thermal profile (`default`, `smart`, `max`) |
29
+ | **Models (register)** | Scan installed MTPLX models and register one with Pi |
30
+ | **Remove Model** | Unregister a model from Pi |
31
+ | **Uninstall** | Remove the `mtplx` provider from Pi's config |
110
32
 
111
33
  ## Configuration
112
34
 
113
- There is no config file to write. The extension uses fixed, documented defaults:
35
+ ### Model registry
114
36
 
115
- | Setting | Value |
116
- | --- | --- |
117
- | Server host:port | `127.0.0.1:8000` (MTPLX default) |
118
- | Quickstart profile | `sustained` |
119
- | SSD session cache | `off` |
120
- | Boot readiness timeout | 180 s |
121
- | Health poll interval | 500 ms |
122
- | Fan mode | `smart` (persisted choice wins; set via `/mtplx → Fan Curves`) |
37
+ Models are registered in `~/.pi/agent/mtplx-models.json`. Each entry maps a Pi model ID to an MTPLX artifact ref:
123
38
 
124
- Persisted state (all under `~/.pi/agent/`):
125
-
126
- - `mtplx-fanmode.json` — chosen fan mode.
127
- - `mtplx-models.json` — model id → artifact ref registry.
128
- - `models.json` — Pi's catalog; pi-mtplx manages only the `mtplx` provider entry.
129
-
130
- To change the port or profile, edit `src/utils.ts` / `src/mtplx-process.ts` — these are the only two constants the whole package depends on.
39
+ ```json
40
+ {
41
+ "mtplx-qwen38-27b-optimized-quality": {
42
+ "ref": "Youssofal/Qwen3.8-27B-MTPLX-Optimized-Quality"
43
+ }
44
+ }
45
+ ```
131
46
 
132
- ## Troubleshooting
47
+ Register new models via the `/mtplx` → **Models** menu, or add entries manually to this file (then run `/reload`).
133
48
 
134
- - **"MTPLX cannot use 127.0.0.1:8000: another, non-MTPLX service is listening there."** — something else owns the port. Stop it, or change `PORT` in `src/utils.ts`.
135
- - **"MTPLX startup failed … timed out after 180s"** — check `mtplx status --deep` for MTPLX-side diagnostics (model validation, memory, thermal).
136
- - **"MTPLX model … is not mapped to an installed MTPLX artifact"** — the model id isn't in `~/.pi/agent/mtplx-models.json`; register it via `/mtplx → Models`.
137
- - **Model registered but `/model` doesn't list it** — run `/reload` (or restart Pi) after registration; `models.json` is read at load time.
138
- - **Fan mode change didn't apply to a running server** — the MTPLX thermal endpoint requires the server to be healthy; if `/health` fails, the new fan mode applies at next boot.
139
- - **`mtplx: command not found`** — make sure the PATH Pi runs under includes the directory with `mtplx` (`which mtplx`).
140
- - **Uninstalling** — `/mtplx → Uninstall` removes the `mtplx` provider from `models.json`; the registry file and installed models stay. Then remove the package from Pi.
49
+ ### Fan mode
141
50
 
142
- ## Development
51
+ Controls the thermal profile of your MTPLX server. Saved to `~/.pi/agent/mtplx-fanmode.json` and persists across restarts.
143
52
 
144
- ```bash
145
- git clone https://github.com/KrossKinetic/pi-mtplx
146
- cd pi-mtplx
147
- npm install
148
- npm run typecheck # tsc --noEmit
149
- npm test # node:test via tsx (tests for the pure helpers)
150
- ```
53
+ | Mode | Behavior |
54
+ | ------ | ---------- |
55
+ | `default` | System default fan curve |
56
+ | `smart` | Default — adaptive thermal management |
57
+ | `max` | Maximum fan, fastest inference |
151
58
 
152
- To load the extension locally without publishing, point Pi at it as a package in a test directory, or symlink `extensions/mtplx.ts` and `extensions/mtplx-tkps-footer.ts` into `~/.pi/agent/extensions/` and `/reload`.
153
-
154
- ## Publishing
155
-
156
- ```bash
157
- npm version <next> # or edit package.json
158
- npm login
159
- npm publish # name is unscoped, so no --access flag needed
160
- ```
59
+ ## Troubleshooting
161
60
 
162
- For GitHub, push the `main` branch and users can `pi install git:github.com/KrossKinetic/pi-mtplx`. The package is tagged `pi-package` in `keywords`, so it shows up in the [package gallery](https://pi.dev/packages).
61
+ | Problem | Fix |
62
+ | --------- | ----- |
63
+ | **"MTPLX not started"** — Pi warns when you ask an MTPLX model to respond | Run `/mtplx` → **Toggle** to start the server |
64
+ | **"No MTPLX models registered"** | Run `/mtplx` → **Models** to discover and register one |
65
+ | **MTPLX startup timed out after 180s** | Run `mtplx status --deep` for MTPLX-side diagnostics (model validation, memory, thermal) |
163
66
 
164
67
  ## License
165
68
 
166
- MIT — see [LICENSE](./LICENSE).
69
+ MIT — see [LICENSE](./LICENSE).
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { acquire, release, stopServer } from "../src/mtplx-process.ts";
10
10
  import { getFanMode, health, setFanMode, setFanModeValue } from "../src/mtplx-client.ts";
11
- import { MTPLX_PROVIDER, listModels, removePiMtplxProvider } from "../src/model-discovery.ts";
11
+ import { MTPLX_PROVIDER, listModels, removeModel, removePiMtplxProvider, MTPLX_MODELS } from "../src/model-discovery.ts";
12
12
  import { FAN_MODES, isMtplxModel, saveFanMode, type FanMode } from "../src/utils.ts";
13
13
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
14
 
@@ -19,7 +19,7 @@ export default function mtplxAutostart(pi: ExtensionAPI): void {
19
19
  const current = await health();
20
20
  const status = current ? "on" : "off";
21
21
  await ctx.ui.setStatus("mtplx", `MTPLX: ${status}`);
22
- const topChoices = [`Toggle (${status})`, `Fan Curves (current: ${getFanMode()})`, `Models (register)`, "Uninstall (remove pi models.json entry)"];
22
+ const topChoices = [`Toggle (${status})`, `Fan Curves (current: ${getFanMode()})`, `Models (register)`, `Remove Model`, "Uninstall (remove provider)"];
23
23
  const top = await ctx.ui.select("MTPLX", topChoices, undefined);
24
24
  if (!top) return;
25
25
  if (top.startsWith("Toggle")) {
@@ -50,15 +50,31 @@ export default function mtplxAutostart(pi: ExtensionAPI): void {
50
50
  if (top.startsWith("Models")) {
51
51
  await listModels(ctx);
52
52
  }
53
+ if (top.startsWith("Remove Model")) {
54
+ const registered = Object.keys(MTPLX_MODELS);
55
+ if (registered.length === 0) {
56
+ ctx.ui.notify("No MTPLX models registered. Register one first via Models (register).", "warning");
57
+ return;
58
+ }
59
+ const choices = registered.map((id) => `${id} (registered)`);
60
+ choices.push("Cancel");
61
+ const picked = await ctx.ui.select("Remove a registered MTPLX model", choices, undefined);
62
+ if (!picked || picked === "Cancel") return;
63
+ if (removeModel(picked)) {
64
+ ctx.ui.notify(`Removed ${picked} from models.json and enabledModels. Run /reload.`, "info");
65
+ } else {
66
+ ctx.ui.notify(`${picked} not found in registered models.`, "warning");
67
+ }
68
+ }
53
69
  if (top.startsWith("Uninstall")) {
54
70
  const ok = await ctx.ui.confirm(
55
71
  "pi-mtplx uninstall",
56
- `Remove the ${MTPLX_PROVIDER} provider (and its models) from ~/.pi/agent/models.json? The model registry file (~/.pi/agent/mtplx-models.json) and installed MTPLX models are left in place.`,
72
+ `Remove the ${MTPLX_PROVIDER} provider (and all its models) from models.json and enabledModels? The model registry file (~/.pi/agent/mtplx-models.json) and installed MTPLX models are left in place.`,
57
73
  undefined,
58
74
  );
59
75
  if (!ok) return;
60
76
  if (removePiMtplxProvider()) {
61
- ctx.ui.notify(`Removed ${MTPLX_PROVIDER} provider from models.json. Run /reload (or restart Pi), then remove the pi-mtplx package.`, "info");
77
+ ctx.ui.notify(`Removed ${MTPLX_PROVIDER} provider from models.json and enabledModels. Run /reload (or restart Pi), then remove the pi-mtplx package.`, "info");
62
78
  } else {
63
79
  ctx.ui.notify(`Nothing to remove — the ${MTPLX_PROVIDER} provider is not in models.json.`, "info");
64
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mtplx",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Zero-config MTPLX integration for Pi coding agent",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -50,4 +50,4 @@
50
50
  "typecheck": "tsc --noEmit",
51
51
  "test": "tsx --test test/*.test.ts"
52
52
  }
53
- }
53
+ }
@@ -27,6 +27,7 @@ const execFileAsync = promisify(execFile);
27
27
  // the exact provider block that `mtplx start pi` creates.
28
28
  export const MTPLX_PROVIDER = "mtplx";
29
29
  const MODELS_FILE = join(homedir(), ".pi", "agent", "mtplx-models.json");
30
+ const SETTINGS_FILE = join(homedir(), ".pi", "agent", "settings.json");
30
31
 
31
32
  const BUILTIN_MODELS: Record<string, { ref: string }> = {
32
33
  "mtplx-qwen38-27b-optimized-quality": {
@@ -59,6 +60,49 @@ export function saveRegisteredModels(): void {
59
60
  }
60
61
  }
61
62
 
63
+ // --- enabledModels management (settings.json) ---
64
+
65
+ function loadEnabledModels(): string[] {
66
+ try {
67
+ const parsed = JSON.parse(readFileSync(SETTINGS_FILE, "utf8")) as { enabledModels?: unknown };
68
+ return Array.isArray(parsed.enabledModels) ? (parsed.enabledModels as string[]) : [];
69
+ } catch {
70
+ return [];
71
+ }
72
+ }
73
+
74
+ export function enableModelInSettings(modelId: string): boolean {
75
+ try {
76
+ const catalog = JSON.parse(readFileSync(SETTINGS_FILE, "utf8")) as { enabledModels?: unknown[] };
77
+ const enabled = Array.isArray(catalog.enabledModels) ? catalog.enabledModels : [];
78
+ const entry = `mtplx/${modelId}`;
79
+ if (enabled.includes(entry)) return true;
80
+ enabled.push(entry);
81
+ catalog.enabledModels = enabled;
82
+ writeFileSync(SETTINGS_FILE, JSON.stringify(catalog, null, 2) + "\n");
83
+ return true;
84
+ } catch (error) {
85
+ console.error(`pi-mtplx could not enable ${modelId} in settings.json: ${error instanceof Error ? error.message : String(error)}`);
86
+ return false;
87
+ }
88
+ }
89
+
90
+ export function disableModelInSettings(modelId: string): boolean {
91
+ try {
92
+ const catalog = JSON.parse(readFileSync(SETTINGS_FILE, "utf8")) as { enabledModels?: unknown[] };
93
+ const enabled = Array.isArray(catalog.enabledModels) ? catalog.enabledModels : [];
94
+ const entry = `mtplx/${modelId}`;
95
+ const idx = enabled.indexOf(entry);
96
+ if (idx === -1) return true;
97
+ enabled.splice(idx, 1);
98
+ catalog.enabledModels = enabled;
99
+ writeFileSync(SETTINGS_FILE, JSON.stringify(catalog, null, 2) + "\n");
100
+ return true;
101
+ } catch (error) {
102
+ console.error(`pi-mtplx could not disable ${modelId} in settings.json: ${error instanceof Error ? error.message : String(error)}`);
103
+ return false;
104
+ }
105
+ }
62
106
  type MtplxListedModel = { repo_id?: unknown; path?: unknown; name?: unknown };
63
107
 
64
108
  export async function listMtplxModels(): Promise<MtplxListedModel[]> {
@@ -158,19 +202,48 @@ export async function listModels(ctx: ExtensionContext): Promise<void> {
158
202
  ctx.ui.notify(`MTPLX model mapping saved, but models.json update failed: ${error instanceof Error ? error.message : String(error)}`, "error");
159
203
  return;
160
204
  }
161
- ctx.ui.notify(`Registered ${modelId} ${ref}. Restart Pi, then switch to it with /model.`, "info");
205
+ // 3) Also enable the model in settings.json so it shows on first boot.
206
+ enableModelInSettings(modelId);
207
+ ctx.ui.notify(`Registered ${modelId} → ${ref}. Switch to it with /model.`, "info");
162
208
  }
163
209
 
164
210
  /**
165
- * Remove the `mtplx` provider from the user's models.json. Never touches any
166
- * other provider. Silently succeeds when the file or provider is absent.
211
+ * Remove a single registered model from the provider and from enabledModels.
167
212
  */
168
- export function removePiMtplxProvider(): boolean {
213
+ export function removeModel(modelId: string): boolean {
214
+ // 1) Remove from the mtplx provider in models.json
169
215
  const modelsJsonPath = join(homedir(), ".pi", "agent", "models.json");
170
216
  if (!existsSync(modelsJsonPath)) return false;
171
217
  try {
172
218
  const catalog = JSON.parse(readFileSync(modelsJsonPath, "utf8")) as { providers?: Record<string, unknown> };
219
+ const provider = (catalog.providers?.[MTPLX_PROVIDER] as { models?: unknown[] }) ?? {};
220
+ const models = Array.isArray(provider.models) ? provider.models : [];
221
+ const filtered = (models as { id?: string }[]).filter((m) => m.id !== modelId);
222
+ if (filtered.length === models.length) return false; // not found
223
+ (provider as { models?: unknown[] }).models = filtered;
224
+ writeFileSync(modelsJsonPath, JSON.stringify(catalog, null, 2) + "\n");
225
+ } catch (error) {
226
+ console.error(`pi-mtplx could not remove ${modelId} from models.json: ${error instanceof Error ? error.message : String(error)}`);
227
+ return false;
228
+ }
229
+ // 2) Also remove from enabledModels in settings.json
230
+ disableModelInSettings(modelId);
231
+ return true;
232
+ }
233
+
234
+ /**
235
+ * Remove the entire `mtplx` provider from models.json and clean enabledModels.
236
+ */
237
+ export function removePiMtplxProvider(): boolean {
238
+ const modelsJsonPath = join(homedir(), ".pi", "agent", "models.json");
239
+ if (!existsSync(modelsJsonPath)) return false;
240
+ try {
241
+ const catalog = JSON.parse(readFileSync(modelsJsonPath, "utf8")) as { providers?: Record<string, unknown>; enabledModels?: string[] };
173
242
  if (!catalog.providers || !(MTPLX_PROVIDER in catalog.providers)) return false;
243
+ // Remove all mtplx models from enabledModels
244
+ if (catalog.enabledModels) {
245
+ catalog.enabledModels = catalog.enabledModels.filter((m: string) => !m.startsWith("mtplx/"));
246
+ }
174
247
  delete catalog.providers[MTPLX_PROVIDER];
175
248
  writeFileSync(modelsJsonPath, JSON.stringify(catalog, null, 2) + "\n");
176
249
  return true;