@xynogen/pix-models 0.2.0 → 0.2.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 +8 -1
- package/package.json +1 -1
- package/src/patch-builtin.test.ts +11 -0
- package/src/patch-builtin.ts +96 -45
package/README.md
CHANGED
|
@@ -4,7 +4,14 @@ Pi extension — enhanced `/models` picker with coding score/rank.
|
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
Registers a `/models` slash command
|
|
7
|
+
Registers a `/models` slash command — a richer TUI picker replacing Pi's built-in `/model` selector.
|
|
8
|
+
|
|
9
|
+
- **Each row** — model id, context window, per-M-token cost, and a coding score/rank (star bar) when available.
|
|
10
|
+
- **Sorting** — by coding score (best first), then alphabetically for unscored models. Fuzzy search filters as you type.
|
|
11
|
+
- **Thinking level** — left/right cycles `off` → `minimal` → `low` → `medium` → `high` → `xhigh`, shown live in the header.
|
|
12
|
+
- **Select** — switches the active model for the session.
|
|
13
|
+
|
|
14
|
+
Model metadata comes from `~/.cache/pi/` via `pix-data`; the coding score/rank is computed locally from the modelgrep catalog (best = #1).
|
|
8
15
|
|
|
9
16
|
## Install
|
|
10
17
|
|
package/package.json
CHANGED
|
@@ -80,6 +80,17 @@ export const BUILTIN_SLASH_COMMANDS = [
|
|
|
80
80
|
expect(out).toContain('name: "login"');
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
it("removes /model from Pi's minified bundle chunk", () => {
|
|
84
|
+
// Real form from dist/bundle/chunks/*.js: no `export const`, no spaces,
|
|
85
|
+
// array followed immediately by other bundled statements.
|
|
86
|
+
const minified = `var x=1;BUILTIN_SLASH_COMMANDS=[{name:"settings",description:"Open settings menu"},{name:"model",description:"Select model (opens selector UI)",argumentHint:"<provider/model>"},{name:"scoped-models",description:"Enable/disable models for Ctrl+P cycling"}];import path11 from"node:path";`;
|
|
87
|
+
const out = stripBuiltinModelCommand(minified);
|
|
88
|
+
expect(out).not.toContain('name:"model"');
|
|
89
|
+
expect(out).toContain('name:"settings"');
|
|
90
|
+
expect(out).toContain('name:"scoped-models"');
|
|
91
|
+
expect(out).toContain('import path11 from"node:path"');
|
|
92
|
+
});
|
|
93
|
+
|
|
83
94
|
it("round-trips through disk", () => {
|
|
84
95
|
const dir = mkdtempSync(join(tmpdir(), "pix-patch-"));
|
|
85
96
|
const file = join(dir, "slash-commands.js");
|
package/src/patch-builtin.ts
CHANGED
|
@@ -2,79 +2,130 @@
|
|
|
2
2
|
* patch-builtin.ts — strip Pi's built-in /model slash command at load time.
|
|
3
3
|
*
|
|
4
4
|
* Built-in commands can't be removed via the extension API, so we edit Pi's
|
|
5
|
-
* compiled slash-
|
|
5
|
+
* compiled slash-command source directly. Done on every load: idempotent and
|
|
6
6
|
* self-healing across Pi upgrades, so no manual repatch is ever needed.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* Pi's build layout has moved over time. Two forms are in the wild:
|
|
9
|
+
* - legacy: <pkg>/dist/core/slash-commands.js (readable, one entry/line)
|
|
10
|
+
* - bundled: <pkg>/dist/bundle/chunks/chunk-*.js (minified, hash-named)
|
|
11
|
+
* The bundled chunk is what the runtime actually executes; the legacy
|
|
12
|
+
* dist/core/ file may still exist as dead output. We therefore prefer the
|
|
13
|
+
* chunk that really contains the `BUILTIN_SLASH_COMMANDS=[...]` assignment and
|
|
14
|
+
* only fall back to dist/core/ for older hosts that lack a bundle dir.
|
|
15
|
+
*
|
|
16
|
+
* Package-root resolution (in order):
|
|
17
|
+
* 1. `pi` binary via PATH → realpath → split at /dist/ to get the pkg root.
|
|
18
|
+
* 2. Well-known global install locations (bun, npm).
|
|
19
|
+
* 3. createRequire against the extension's own node_modules.
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
22
|
+
import {
|
|
23
|
+
accessSync,
|
|
24
|
+
constants,
|
|
25
|
+
existsSync,
|
|
26
|
+
readdirSync,
|
|
27
|
+
readFileSync,
|
|
28
|
+
realpathSync,
|
|
29
|
+
writeFileSync,
|
|
30
|
+
} from "node:fs";
|
|
18
31
|
import { createRequire } from "node:module";
|
|
19
32
|
import { homedir } from "node:os";
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
import { delimiter, join, sep } from "node:path";
|
|
34
|
+
|
|
35
|
+
/** Locate `pi` on PATH (a pure-JS `which`), returning its real (symlink-resolved) path. */
|
|
36
|
+
function resolvePiBinary(): string | undefined {
|
|
37
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
38
|
+
if (!dir) continue;
|
|
39
|
+
const candidate = join(dir, "pi");
|
|
40
|
+
try {
|
|
41
|
+
accessSync(candidate, constants.X_OK);
|
|
42
|
+
return realpathSync(candidate);
|
|
43
|
+
} catch {}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
// The assignment appears once per build. `\s*=\s*\[` avoids the `.map(...)`
|
|
49
|
+
// references and tolerates both `= [` (pretty) and `=[` (minified) spacing.
|
|
50
|
+
const BUILTIN_COMMANDS_ARRAY = /BUILTIN_SLASH_COMMANDS\s*=\s*\[/;
|
|
51
|
+
// Match one flat (non-nested) command object whose stable `name` is exactly
|
|
52
|
+
// "model", plus an optional trailing comma. No line anchors, so it works for
|
|
53
|
+
// both minified inline entries and pretty multi-line ones. The `["']model["']`
|
|
54
|
+
// requires a closing quote right after `model`, so `models`/`scoped-models`
|
|
55
|
+
// never match.
|
|
56
|
+
const BUILTIN_MODEL_COMMAND = /\{(?=[^{}]*\bname\s*:\s*["']model["'])[^{}]*\},?/g;
|
|
57
|
+
|
|
58
|
+
/** Infer pi-coding-agent package roots, most-specific first. */
|
|
59
|
+
function packageRoots(): string[] {
|
|
60
|
+
const roots: string[] = [];
|
|
61
|
+
const pushRoot = (r: string | undefined) => {
|
|
62
|
+
if (r && !roots.includes(r)) roots.push(r);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 1. Resolve via the `pi` binary on PATH → realpath → strip at /dist/.
|
|
66
|
+
const piReal = resolvePiBinary();
|
|
67
|
+
if (piReal) {
|
|
68
|
+
const idx = piReal.indexOf(`${sep}dist${sep}`);
|
|
69
|
+
if (idx >= 0) pushRoot(piReal.slice(0, idx));
|
|
46
70
|
}
|
|
47
71
|
|
|
48
72
|
// 2. Well-known global install locations.
|
|
49
73
|
const home = homedir();
|
|
50
|
-
const
|
|
74
|
+
for (const root of [
|
|
51
75
|
join(home, ".bun", "install", "global", "node_modules"),
|
|
52
76
|
join(home, ".npm-global", "lib", "node_modules"),
|
|
53
77
|
"/usr/local/lib/node_modules",
|
|
54
78
|
"/usr/lib/node_modules",
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
paths.push(
|
|
58
|
-
join(root, "@earendil-works", "pi-coding-agent", "dist", "core", "slash-commands.js"),
|
|
59
|
-
);
|
|
79
|
+
]) {
|
|
80
|
+
pushRoot(join(root, "@earendil-works", "pi-coding-agent"));
|
|
60
81
|
}
|
|
61
82
|
|
|
62
|
-
// 3. Fallback: createRequire from this file (
|
|
83
|
+
// 3. Fallback: createRequire from this file (co-installed extension).
|
|
63
84
|
try {
|
|
64
85
|
const require = createRequire(import.meta.url);
|
|
65
86
|
const entry = require.resolve("@earendil-works/pi-coding-agent");
|
|
66
|
-
|
|
87
|
+
const idx = entry.indexOf(`${sep}dist${sep}`);
|
|
88
|
+
if (idx >= 0) pushRoot(entry.slice(0, idx));
|
|
67
89
|
} catch {
|
|
68
90
|
// local resolution failed — skip
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
return
|
|
93
|
+
return roots;
|
|
72
94
|
}
|
|
73
95
|
|
|
74
|
-
/**
|
|
96
|
+
/** Candidate source files for a package root — live bundle chunks first. */
|
|
97
|
+
function candidateFiles(root: string): string[] {
|
|
98
|
+
const files: string[] = [];
|
|
99
|
+
const chunkDir = join(root, "dist", "bundle", "chunks");
|
|
100
|
+
try {
|
|
101
|
+
for (const name of readdirSync(chunkDir)) {
|
|
102
|
+
if (name.endsWith(".js")) files.push(join(chunkDir, name));
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// no bundle dir on this host — legacy layout below
|
|
106
|
+
}
|
|
107
|
+
// Legacy readable output, kept as a fallback for older Pi builds.
|
|
108
|
+
files.push(join(root, "dist", "core", "slash-commands.js"));
|
|
109
|
+
return files;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** All candidate paths across all roots (exported for tests). */
|
|
113
|
+
function candidatePaths(): string[] {
|
|
114
|
+
return packageRoots().flatMap(candidateFiles);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Locate the source file that actually declares BUILTIN_SLASH_COMMANDS.
|
|
119
|
+
* Chunk filenames are content hashes, so we confirm by contents, not by name.
|
|
120
|
+
*/
|
|
75
121
|
function findSlashCommandsFile(): string | null {
|
|
76
122
|
for (const p of candidatePaths()) {
|
|
77
|
-
if (existsSync(p))
|
|
123
|
+
if (!existsSync(p)) continue;
|
|
124
|
+
try {
|
|
125
|
+
if (BUILTIN_COMMANDS_ARRAY.test(readFileSync(p, "utf8"))) return p;
|
|
126
|
+
} catch {
|
|
127
|
+
// unreadable — skip
|
|
128
|
+
}
|
|
78
129
|
}
|
|
79
130
|
return null;
|
|
80
131
|
}
|