@xynogen/pix-models 0.1.24 → 0.1.27

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/models.ts +36 -22
package/README.md CHANGED
@@ -4,7 +4,7 @@ Pi extension — enhanced `/models` picker with coding score/rank.
4
4
 
5
5
  ## What it does
6
6
 
7
- Registers a `/models` slash command that replaces Pi's built-in `/model` selector with a richer TUI picker. Each row shows the model id, context window, per-million-token cost, and a coding-focused score/rank (with star bar) when available. The list is sorted by coding score (best first), then alphabetically for unscored models. Fuzzy search filters the list as you type. Selecting a model switches the active model for the session. Model metadata is sourced from `~/.cache/pi/` via `pix-data`; the coding score/rank is computed locally from the modelgrep catalog (best = #1). Requires `@xynogen/pix-data` as a dependency.
7
+ Registers a `/models` slash command that replaces Pi's built-in `/model` selector with a richer TUI picker. Each row shows the model id, context window, per-million-token cost, and a coding-focused score/rank (with star bar) when available. The list is sorted by coding score (best first), then alphabetically for unscored models. Fuzzy search filters the list as you type. Left/right changes the thinking level (`off` → `minimal` → `low` → `medium` → `high` → `xhigh`), with the effective level shown live in the picker header. Selecting a model switches the active model for the session. Model metadata is sourced from `~/.cache/pi/` via `pix-data`; the coding score/rank is computed locally from the modelgrep catalog (best = #1). Requires `@xynogen/pix-data` as a dependency.
8
8
 
9
9
  ## Install
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-models",
3
- "version": "0.1.24",
3
+ "version": "0.1.27",
4
4
  "description": "Pi extension — enhanced /models picker with BenchLM ranks",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/models.ts CHANGED
@@ -102,8 +102,8 @@ export function normalizeModelText(s: string): string {
102
102
  // ─── Thinking level control ──────────────────────────────────────────────────
103
103
 
104
104
  /**
105
- * Canonical thinking levels, ascending. Shift+←/→ in the picker steps through this
106
- * list; pi.setThinkingLevel() clamps to what the active model actually supports,
105
+ * Canonical thinking levels, ascending. ←/→ in the picker steps through this list;
106
+ * pi.setThinkingLevel() clamps to what the active model actually supports,
107
107
  * so visiting an unsupported rung is harmless (it lands on the nearest allowed).
108
108
  */
109
109
  export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
@@ -277,7 +277,7 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
277
277
  // items built inside the custom() factory so we have theme access for colors
278
278
 
279
279
  const result = await ctx.ui.custom<string | null>(
280
- (tui, theme, kb, done) => {
280
+ (tui, theme, _kb, done) => {
281
281
  const accent = "accent";
282
282
 
283
283
  // Find max rank width across all benchmarked rows for # padding
@@ -286,9 +286,20 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
286
286
  1,
287
287
  );
288
288
 
289
+ // Widest cost string so the cost column pads to a common width and the
290
+ // following ⚡score/stars stay column-aligned (e.g. "10.00/50.00" is 11
291
+ // chars — a fixed pad of 10 shifted those rows right by one).
292
+ const maxCostWidth = Math.max(
293
+ ...dedupedRows.map((r) => fmtCost(r.dev).length),
294
+ "free".length,
295
+ );
296
+
289
297
  // Mute low-info parts (separators, padding, #, ☆) so the actual values pop.
290
298
  const mute = (s: string) => theme.fg("muted", s);
291
299
  const sep = mute(" · ");
300
+ const guide = (key: string, action: string) =>
301
+ theme.fg("text", key) + theme.fg("dim", ` ${action}`);
302
+ const guideSep = theme.fg("dim", " · ");
292
303
 
293
304
  // Track rank per item value so fuzzy results can prioritize ranked models.
294
305
  const rankByValue = new Map<string, number>();
@@ -340,11 +351,11 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
340
351
  const rawCost = fmtCost(dev);
341
352
  let costSeg: string;
342
353
  if (rawCost === "—") {
343
- costSeg = theme.fg("dim", "—".padEnd(10));
354
+ costSeg = theme.fg("dim", "—".padEnd(maxCostWidth));
344
355
  } else if (rawCost === "free") {
345
- costSeg = mute("free".padEnd(10));
356
+ costSeg = mute("free".padEnd(maxCostWidth));
346
357
  } else {
347
- costSeg = theme.fg("success", rawCost.padEnd(10));
358
+ costSeg = theme.fg("success", rawCost.padEnd(maxCostWidth));
348
359
  }
349
360
  let benchSeg = "";
350
361
  if (bench) {
@@ -417,7 +428,7 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
417
428
  internal.invalidate();
418
429
  };
419
430
 
420
- // Live thinking-level readout. Shift+←/→ mutates the session immediately via
431
+ // Live thinking-level readout. ←/→ mutates the session immediately via
421
432
  // pi.setThinkingLevel(); we mirror pi.getThinkingLevel() so the header
422
433
  // reflects the clamped result (model may not support every rung).
423
434
  //
@@ -435,7 +446,11 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
435
446
  ? theme.getThinkingBorderColor(resolved)(label)
436
447
  : theme.fg("dim", label);
437
448
  return (
438
- theme.fg("muted", "Thinking: ") + coloredLabel + theme.fg("dim", " (shift+←/→ adjust)")
449
+ theme.fg("muted", "Thinking: ") +
450
+ coloredLabel +
451
+ theme.fg("dim", " (") +
452
+ guide("←/→", "adjust") +
453
+ theme.fg("dim", ")")
439
454
  );
440
455
  };
441
456
 
@@ -451,8 +466,8 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
451
466
  theme.fg(accent, theme.bold(`${icon("picker.model")} Select model`)),
452
467
  theme.fg("dim", "context · pricing · coding rank & score from modelgrep.com"),
453
468
  thinkLine(),
454
- theme.fg("muted", "Search:"),
455
469
  ...search.render(inner),
470
+ "",
456
471
  ],
457
472
  body: list.render(inner),
458
473
  selectedBodyRange: (() => {
@@ -463,10 +478,14 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
463
478
  });
464
479
  })(),
465
480
  footer: [
466
- theme.fg(
467
- "dim",
468
- "fuzzy search · ↑↓ navigate · ←→/PgUp/PgDn inspect · shift+←/→ thinking · enter select · esc cancel",
469
- ),
481
+ "",
482
+ guide("↑↓", "navigate") +
483
+ guideSep +
484
+ guide("←/→", "thinking") +
485
+ guideSep +
486
+ guide("enter", "select") +
487
+ guideSep +
488
+ guide("esc", "cancel"),
470
489
  ],
471
490
  bodyOffset: pager.bodyOffset,
472
491
  color: (s) => theme.fg(accent, s),
@@ -481,20 +500,15 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
481
500
  search.invalidate();
482
501
  },
483
502
  handleInput(data: string) {
484
- if (pager.handleInput(data, kb, true)) {
485
- tui.requestRender();
486
- return;
487
- }
488
503
  // Detect keys via pi-tui's own parser — the same recognition
489
504
  // SelectList uses. Arrows arrive as named keys ("up"/"down"),
490
505
  // not raw escape sequences, so string-equality checks fail.
491
506
  const isNav = matchesKey(data, "up") || matchesKey(data, "down");
492
- // Shift+←/→ tunes the ACTIVE session model's thinking level without
493
- // stealing plain ←/→ cursor movement from search. setThinkingLevel clamps
494
- // to model capability, so unsupported rungs land on the nearest allowed.
507
+ // ←/→ tunes the ACTIVE session model's thinking level. setThinkingLevel
508
+ // clamps to model capability, so unsupported rungs land on the nearest allowed.
495
509
  let dir: -1 | 1 | 0 = 0;
496
- if (matchesKey(data, Key.shift(Key.left))) dir = -1;
497
- else if (matchesKey(data, Key.shift(Key.right))) dir = 1;
510
+ if (matchesKey(data, Key.left)) dir = -1;
511
+ else if (matchesKey(data, Key.right)) dir = 1;
498
512
  if (dir !== 0) {
499
513
  const cur = pi.getThinkingLevel?.() || localLevel || "medium";
500
514
  localLevel = stepEffectiveThinkingLevel(cur, dir, (candidate) => {