@yusukeshib/pi-colored-model-status 0.1.1 → 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
@@ -4,7 +4,7 @@ A [pi](https://pi.dev) extension that color-codes the active model in the
4
4
  footer, so you can tell at a glance which model family is in use.
5
5
 
6
6
  It replaces the plain `claude-opus-4-8 • medium` text in the bottom-right of
7
- pi's footer with a background-colored badge. Everything else the default
7
+ pi's footer with independently background-colored model and thinking badges. Everything else the default
8
8
  footer shows (cwd, git branch, token stats, context %, and other extensions'
9
9
  status line) is faithfully reproduced.
10
10
 
@@ -17,15 +17,27 @@ strongly distinct hues:
17
17
 
18
18
  | Family | Color |
19
19
  | ------------------------- | ---------------- |
20
- | `opus` | deep purple |
21
- | `sonnet` | bright cyan-blue |
22
- | `fable` | vivid orange |
20
+ | `opus`, `terra` | deep purple |
21
+ | `sonnet`, `luna` | bright cyan-blue |
22
+ | `fable`, `sol` | vivid orange |
23
23
  | `haiku` | green |
24
24
  | `gpt`, `o1`, `o3`, `o4` | OpenAI teal |
25
25
  | `gemini` | Google blue |
26
26
  | `grok` | slate |
27
27
  | anything else | gray (fallback) |
28
28
 
29
+ Thinking effort has its own independent color scale:
30
+
31
+ | Level | Color |
32
+ | --------- | --------- |
33
+ | `off` | gray |
34
+ | `minimal` | blue-gray |
35
+ | `low` | green |
36
+ | `medium` | amber |
37
+ | `high` | orange |
38
+ | `xhigh` | red |
39
+ | `max` | magenta |
40
+
29
41
  The foreground color (black or white) is chosen automatically from the
30
42
  background's luminance for readability. The badge follows model changes made
31
43
  via `/model` or `Ctrl+P`.
@@ -50,18 +62,24 @@ pi -e npm:@yusukeshib/pi-colored-model-status
50
62
 
51
63
  ## Customize
52
64
 
53
- Edit the `BADGES` array at the top of
65
+ Edit the `MODEL_BADGES` and `THINKING_BADGES` arrays at the top of
54
66
  [`extensions/colored-model-status.ts`](extensions/colored-model-status.ts).
55
67
  Each entry maps substring keywords to an RGB background (and an optional `fg`):
56
68
 
57
69
  ```ts
58
- const BADGES = [
59
- { match: ["opus"], bg: [147, 51, 234] },
60
- { match: ["sonnet"], bg: [14, 165, 233] },
61
- { match: ["fable"], bg: [234, 88, 12] },
70
+ const MODEL_BADGES = [
71
+ { match: ["opus", "terra"], bg: [147, 51, 234] },
72
+ { match: ["sonnet", "luna"], bg: [14, 165, 233] },
73
+ { match: ["fable", "sol"], bg: [234, 88, 12] },
62
74
  // …add your own, e.g.:
63
75
  { match: ["deepseek"], bg: [30, 90, 200] },
64
76
  ];
77
+
78
+ const THINKING_BADGES = [
79
+ { match: ["low"], bg: [22, 163, 74] },
80
+ { match: ["high"], bg: [234, 88, 12] },
81
+ // …
82
+ ];
65
83
  ```
66
84
 
67
85
  Matching is a case-insensitive substring test against the model id, so `gpt`
@@ -26,22 +26,33 @@ interface Badge {
26
26
  fg?: Rgb;
27
27
  }
28
28
 
29
- // Matched top-to-bottom; first hit wins. Hues are spread far apart per family.
30
- const BADGES: readonly Badge[] = [
31
- { match: ["opus"], bg: [147, 51, 234] }, // deep purple
32
- { match: ["sonnet"], bg: [14, 165, 233] }, // bright cyan-blue
33
- { match: ["fable"], bg: [234, 88, 12] }, // vivid orange
29
+ // Model families and thinking levels are colored independently.
30
+ // Each list is matched top-to-bottom; first hit wins.
31
+ const MODEL_BADGES: readonly Badge[] = [
32
+ { match: ["opus", "terra"], bg: [147, 51, 234] }, // deep purple
33
+ { match: ["sonnet", "luna"], bg: [14, 165, 233] }, // bright cyan-blue
34
+ { match: ["fable", "sol"], bg: [234, 88, 12] }, // vivid orange
34
35
  { match: ["haiku"], bg: [22, 163, 74] }, // green
35
36
  { match: ["gpt", "o1", "o3", "o4"], bg: [16, 163, 127] }, // OpenAI teal
36
37
  { match: ["gemini"], bg: [66, 133, 244] }, // Google blue
37
38
  { match: ["grok"], bg: [30, 41, 59] }, // slate
38
39
  ];
39
40
 
41
+ const THINKING_BADGES: readonly Badge[] = [
42
+ { match: ["off"], bg: [82, 82, 91] }, // gray
43
+ { match: ["minimal"], bg: [71, 85, 105] }, // blue-gray
44
+ { match: ["low"], bg: [22, 163, 74] }, // green
45
+ { match: ["medium"], bg: [202, 138, 4] }, // amber
46
+ { match: ["xhigh"], bg: [220, 38, 38] }, // red
47
+ { match: ["high"], bg: [234, 88, 12] }, // orange
48
+ { match: ["max"], bg: [219, 39, 119] }, // magenta
49
+ ];
50
+
40
51
  const FALLBACK: Badge = { match: [], bg: [82, 82, 91] }; // gray
41
52
 
42
- function pickBadge(id: string): Badge {
43
- const lower = id.toLowerCase();
44
- return BADGES.find((b) => b.match.some((k) => lower.includes(k))) ?? FALLBACK;
53
+ function pickBadge(value: string, badges: readonly Badge[]): Badge {
54
+ const lower = value.toLowerCase();
55
+ return badges.find((badge) => badge.match.some((keyword) => lower.includes(keyword))) ?? FALLBACK;
45
56
  }
46
57
 
47
58
  /** Pick a contrasting foreground (black/white) from the background luminance. */
@@ -159,38 +170,44 @@ export default function (pi: ExtensionAPI) {
159
170
  statsLeftWidth = visibleWidth(statsLeft);
160
171
  }
161
172
 
162
- // --- Right side: model + thinking as a colored badge ---
173
+ // --- Right side: independently colored model and thinking badges ---
163
174
  const modelName = model?.id || "no-model";
164
- let rightText = modelName;
165
- if (model?.reasoning) {
166
- const level = pi.getThinkingLevel() || "off";
167
- rightText = level === "off" ? `${modelName} • thinking off` : `${modelName} • ${level}`;
168
- }
169
- // Prefix (provider) when more than one provider is available
175
+ let modelText = modelName;
170
176
  if (footerData.getAvailableProviderCount() > 1 && model) {
171
- rightText = `(${model.provider}) ${rightText}`;
177
+ modelText = `(${model.provider}) ${modelText}`;
172
178
  }
173
-
174
- const badge = pickBadge(modelName);
175
- // Badge adds a leading/trailing space, so width = text width + 2
176
- const rightWidth = visibleWidth(rightText) + 2;
179
+ const thinkingLevel = model?.reasoning ? pi.getThinkingLevel() || "off" : undefined;
180
+ const thinkingText = thinkingLevel === "off" ? "thinking off" : thinkingLevel;
181
+ const modelBadge = pickBadge(modelName, MODEL_BADGES);
182
+ const thinkingBadge = thinkingLevel
183
+ ? pickBadge(thinkingLevel, THINKING_BADGES)
184
+ : undefined;
185
+ const thinkingWidth = thinkingText && thinkingBadge ? visibleWidth(thinkingText) + 2 : 0;
186
+ const rightWidth = visibleWidth(modelText) + 2 + thinkingWidth;
177
187
 
178
188
  const minPadding = 2;
179
189
  const dimStatsLeft = theme.fg("dim", statsLeft);
180
190
  let statsLine: string;
181
191
  if (statsLeftWidth + minPadding + rightWidth <= width) {
182
192
  const pad = " ".repeat(width - statsLeftWidth - rightWidth);
183
- statsLine = dimStatsLeft + theme.fg("dim", pad) + paintBadge(rightText, badge);
193
+ const thinking =
194
+ thinkingText && thinkingBadge ? paintBadge(thinkingText, thinkingBadge) : "";
195
+ statsLine =
196
+ dimStatsLeft + theme.fg("dim", pad) + paintBadge(modelText, modelBadge) + thinking;
184
197
  } else {
185
- // Right side does not fit: truncate text, then paint
186
- const avail = width - statsLeftWidth - minPadding - 2; // -2 for the badge's padding spaces
187
- if (avail > 0) {
188
- const truncated = truncateToWidth(rightText, avail, "");
189
- const painted = paintBadge(truncated, badge);
190
- const pad = " ".repeat(
191
- Math.max(0, width - statsLeftWidth - (visibleWidth(truncated) + 2)),
192
- );
193
- statsLine = dimStatsLeft + theme.fg("dim", pad) + painted;
198
+ // Preserve the thinking badge when possible and truncate only the model badge.
199
+ const modelAvail = width - statsLeftWidth - minPadding - thinkingWidth - 2;
200
+ if (modelAvail > 0) {
201
+ const truncatedModel = truncateToWidth(modelText, modelAvail, "");
202
+ const thinking =
203
+ thinkingText && thinkingBadge ? paintBadge(thinkingText, thinkingBadge) : "";
204
+ const paintedWidth = visibleWidth(truncatedModel) + 2 + thinkingWidth;
205
+ const pad = " ".repeat(Math.max(0, width - statsLeftWidth - paintedWidth));
206
+ statsLine =
207
+ dimStatsLeft +
208
+ theme.fg("dim", pad) +
209
+ paintBadge(truncatedModel, modelBadge) +
210
+ thinking;
194
211
  } else {
195
212
  statsLine = dimStatsLeft;
196
213
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-colored-model-status",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Color-code the active model in pi's footer so you can tell at a glance which model family (opus, sonnet, fable, gpt, gemini, …) is in use.",
5
5
  "keywords": [
6
6
  "pi-package",