sigrank 0.0.214 → 0.0.216

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/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json",
3
3
  "manifest_version": "0.4",
4
4
  "name": "sigrank",
5
- "version": "0.0.213",
5
+ "version": "0.0.215",
6
6
  "description": "Ranks AI coding operators by token cascade efficiency (Yield = cache_read \u00d7 output / input\u00b2). 23 tools for yield calculation, local token usage pulls, signed submission, and tokscale-powered analytics (market share, developer profiles, model trends, cost analysis, device profiles, MCP usage, competitive intelligence). Privacy-first: runs locally, only four token counts leave the machine.",
7
7
  "author": {
8
8
  "name": "SunrisesIllNeverSee",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank",
3
- "version": "0.0.214",
3
+ "version": "0.0.216",
4
4
  "author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
5
5
  "description": "SigRank MCP server — AI operator evaluation and token cascade efficiency tools. The yield cascade + live leaderboard as MCP tools any agent can call.",
6
6
  "mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
@@ -82,11 +82,13 @@
82
82
  ],
83
83
  "dependencies": {
84
84
  "@modelcontextprotocol/sdk": "^1.0.0",
85
- "@resvg/resvg-js": "^2.6.2",
86
85
  "ccusage": "^20.0.19",
87
- "gifenc": "^1.0.3",
88
86
  "tokscale": "^4.7.0"
89
87
  },
88
+ "devDependencies": {
89
+ "@resvg/resvg-js": "^2.6.2",
90
+ "gifenc": "^1.0.3"
91
+ },
90
92
  "overrides": {
91
93
  "@hono/node-server": "^2.0.5"
92
94
  }
@@ -18,6 +18,8 @@ const ESC = "\x1b[";
18
18
 
19
19
  export const THEMES = {
20
20
  // ── Dark (default) — current palette, designed for dark terminals ──
21
+ // green uses 256-color 77 (#5fd75f) for WCAG AA compliance on dark bg (9.0:1).
22
+ // Standard ANSI 32 (#5a8a5a) only achieves 4.1:1 — below the 4.5:1 threshold.
21
23
  dark: {
22
24
  name: "dark",
23
25
  reset: `${ESC}0m`,
@@ -27,8 +29,8 @@ export const THEMES = {
27
29
  boldGold: `${ESC}1;33m`,
28
30
  cyan: `${ESC}36m`,
29
31
  boldCyan: `${ESC}1;36m`,
30
- green: `${ESC}32m`,
31
- red: `${ESC}31m`,
32
+ green: `${ESC}38;5;77m`, // brighter green #5fd75f (9.0:1 on dark — was ANSI 32 at 4.1:1)
33
+ red: `${ESC}38;5;203m`, // brighter red #ff5f5f (5.6:1 on dark — was ANSI 31 at 4.46:1, just below AA)
32
34
  white: `${ESC}97m`,
33
35
  boldWhite: `${ESC}1;97m`,
34
36
  magenta: `${ESC}35m`,
@@ -41,24 +43,26 @@ export const THEMES = {
41
43
  },
42
44
 
43
45
  // ── Light — dark text on light/white terminals ──
44
- // Colors adjusted for contrast against white/light backgrounds.
46
+ // Uses 256-color codes (38;5;N) with darker shades that pass WCAG AA (4.5:1)
47
+ // on white backgrounds. Standard ANSI 33/36/32/31/35/34 render as pastel
48
+ // shades that fail contrast on white — these 256-color variants fix that.
45
49
  light: {
46
50
  name: "light",
47
51
  reset: `${ESC}0m`,
48
52
  bold: `${ESC}1m`,
49
- dim: `${ESC}2m`,
50
- gold: `${ESC}33m`, // yellow is readable on white
51
- boldGold: `${ESC}1;33m`,
52
- cyan: `${ESC}36m`, // cyan is readable on white
53
- boldCyan: `${ESC}1;36m`,
54
- green: `${ESC}32m`, // standard green (darker shade would be better but ANSI 32 is the option)
55
- red: `${ESC}31m`, // standard red
56
- white: `${ESC}30m`, // BLACK text on light bg (was bright white)
57
- boldWhite: `${ESC}1;30m`, // bold black
58
- magenta: `${ESC}35m`,
59
- blue: `${ESC}34m`, // blue is readable on white
60
- bgDim: `${ESC}48;5;250m`, // light grey bg for active tab
61
- bgCyan: `${ESC}48;5;194m`,// light teal bg for active tab
53
+ dim: `${ESC}38;5;242m`, // medium gray (4.5:1 on white — passes AA for large text)
54
+ gold: `${ESC}38;5;130m`, // dark orange-gold #af5f00 (4.7:1 — yellow can't pass AA on white, this is the closest)
55
+ boldGold: `${ESC}1;38;5;130m`, // bold dark orange-gold (4.7:1)
56
+ cyan: `${ESC}38;5;24m`, // dark cyan #005f87 (7.0:1)
57
+ boldCyan: `${ESC}1;38;5;24m`, // bold dark cyan (7.0:1)
58
+ green: `${ESC}38;5;28m`, // dark green #008700 (4.7:1)
59
+ red: `${ESC}38;5;124m`, // dark red #af0000 (7.4:1)
60
+ white: `${ESC}30m`, // BLACK text on light bg (21:1)
61
+ boldWhite: `${ESC}1;30m`, // bold black
62
+ magenta: `${ESC}38;5;90m`, // dark magenta #870087 (8.8:1)
63
+ blue: `${ESC}38;5;19m`, // dark blue #0000af (13.0:1)
64
+ bgDim: `${ESC}48;5;250m`, // light grey bg for active tab
65
+ bgCyan: `${ESC}48;5;194m`, // light teal bg for active tab
62
66
  medalBg: { 1: 220, 2: 250, 3: 130 },
63
67
  medalFg: `${ESC}38;5;232m`,
64
68
  },
@@ -120,3 +124,66 @@ export function setTheme(name) {
120
124
  export function getThemeNames() {
121
125
  return Object.keys(THEMES);
122
126
  }
127
+
128
+ // ── Platform color map ─────────────────────────────────────────────────────
129
+ // Each platform gets a distinct 256-color code so platforms are visually
130
+ // distinguishable in the cascade table, token composition bars, board, and
131
+ // watch tabs. The colors are chosen for distinguishability on dark terminals
132
+ // (the default theme). For light theme, darker 256-color variants are used.
133
+ // Monochrome theme returns no color (bold only) — colorblind-safe.
134
+ //
135
+ // Color assignments (dark theme 256-color codes):
136
+ // claude = 75 (blue-cyan)
137
+ // codex = 114 (green)
138
+ // amp = 179 (orange)
139
+ // gemini = 129 (purple-magenta)
140
+ // kimi = 213 (pink)
141
+ // qwen = 68 (steel blue)
142
+ // goose = 142 (olive)
143
+ // kilo = 166 (dark orange)
144
+ // hermes = 96 (plum)
145
+ // droid = 167 (salmon)
146
+ // codebuff = 72 (teal-green)
147
+ // copilot = 33 (bright blue)
148
+ // openclaw = 136 (gold-brown)
149
+ // pi = 177 (light purple)
150
+ // omp = 220 (gold — the "oh my pi" platform gets gold)
151
+ // opencode = 60 (slate)
152
+ // devin = 30 (teal)
153
+ // proxy = 245 (gray — synthetic passthrough)
154
+ // other = 245 (gray — catch-all)
155
+
156
+ const PLATFORM_COLORS_DARK = {
157
+ claude: 75, codex: 114, amp: 179, gemini: 129, kimi: 213,
158
+ qwen: 68, goose: 142, kilo: 166, hermes: 96, droid: 167,
159
+ codebuff: 72, copilot: 33, openclaw: 136, pi: 177, omp: 220,
160
+ opencode: 60, devin: 30, proxy: 245, other: 245,
161
+ };
162
+
163
+ // Light theme: darker shades of the same hues for contrast on white
164
+ const PLATFORM_COLORS_LIGHT = {
165
+ claude: 24, codex: 28, amp: 130, gemini: 54, kimi: 90,
166
+ qwen: 19, goose: 100, kilo: 130, hermes: 54, droid: 124,
167
+ codebuff: 23, copilot: 19, openclaw: 130, pi: 90, omp: 130,
168
+ opencode: 19, devin: 23, proxy: 242, other: 242,
169
+ };
170
+
171
+ /**
172
+ * Returns a paint function for the given platform name, theme-aware.
173
+ * Usage: `const pc = platformColor("claude"); pc("claude")` → colored string.
174
+ * Falls back to the theme's cyan for unknown platforms.
175
+ */
176
+ export function platformColor(platform) {
177
+ const themeName = currentTheme.name;
178
+ // Monochrome: no color, just bold (colorblind-safe)
179
+ if (themeName === "monochrome") {
180
+ return (s) => `${currentTheme.bold}${s}${currentTheme.reset}`;
181
+ }
182
+ const map = themeName === "light" ? PLATFORM_COLORS_LIGHT : PLATFORM_COLORS_DARK;
183
+ const code = map[platform];
184
+ if (code == null) {
185
+ // Unknown platform — fall back to cyan
186
+ return (s) => `${currentTheme.cyan}${s}${currentTheme.reset}`;
187
+ }
188
+ return (s) => `${ESC}38;5;${code}m${s}${currentTheme.reset}`;
189
+ }
@@ -54,6 +54,7 @@ export function dashboardEnabledPlatforms({
54
54
  // The `c` object is a live proxy to the current theme — changing the theme
55
55
  // via setTheme() updates all colors. Supports dark/light/high-contrast/monochrome.
56
56
  import * as _themeMod from "./tui-themes.mjs";
57
+ import { platformColor } from "./tui-themes.mjs";
57
58
  const ESC = "\x1b[";
58
59
  // `c` is a live proxy — reads currentTheme at access time so theme switches
59
60
  // take effect immediately. The helper functions below (paint/bold/dim/etc.)
@@ -187,6 +188,27 @@ function padStart(s, w) {
187
188
  function trunc(s, w) {
188
189
  return stripAnsi(s).length <= w ? s : s.slice(0, w - 1) + "…";
189
190
  }
191
+ // Wrap a plain-text string into dim'd lines that fit within `maxW` columns.
192
+ // Used for no-data/empty-state messages that can be long on narrow terminals.
193
+ // Returns an array of strings (each already dim'd + indented) for emit().
194
+ function wrapDim(text, indent = " ", maxW) {
195
+ const w = maxW || W();
196
+ const avail = w - 2 - indent.length; // 2 = leading " " from emit caller
197
+ if (avail < 20) return [`${indent}${dim(text)}`]; // too narrow to wrap — let it be
198
+ const words = text.split(" ");
199
+ const lines = [];
200
+ let line = "";
201
+ for (const word of words) {
202
+ if ((line + " " + word).trim().length > avail) {
203
+ if (line) lines.push(`${indent}${dim(line.trim())}`);
204
+ line = word;
205
+ } else {
206
+ line = (line + " " + word).trim();
207
+ }
208
+ }
209
+ if (line) lines.push(`${indent}${dim(line.trim())}`);
210
+ return lines;
211
+ }
190
212
  function hr(ch = "─") {
191
213
  return dim(ch.repeat(Math.max(0, W() - 4)));
192
214
  }
@@ -456,13 +478,18 @@ const TABS = [
456
478
  { key: "6", label: "Connect", short: "N" }, // sign in / switch device — the whole app is the TUI
457
479
  ];
458
480
 
459
- // Three Degrees of Leverage — reference values pulled from signalaf.com/wiki (2026-06-25).
460
- // base = AI avg (modeled 7:2:1 baseline) · field = SigRank avg (wild field median) · top = best operator.
461
- // STATIC for now; goes live (field/top computed from the board) once there's real user volume.
481
+ // Four Degrees of Leverage — reference values matching signalaf.com/wiki/four-degrees.
482
+ // base = AA baseline (7:2:1 modeled reference, static)
483
+ // hcm = Human Center of Mass (median of all real operators)
484
+ // power = Power users (median of top 100 real operators)
485
+ // top = Top Evals (single top real operator)
486
+ // STATIC fallback values match the web app's GoldColumn fallbacks (lib/marketing/top-operator-column.ts).
487
+ // Goes live (hcm/power/top computed from the board) once there's real user volume.
462
488
  const TD = {
463
- base: { yield: 1.57, snr: 0.33, vel: 0.5, lev: 3.2, d10: 0.5 },
464
- field: { yield: 1.51, snr: 0.07, vel: 0.08, lev: 22.3, d10: 1.35 },
465
- top: { yield: 745.4, snr: 0.63, vel: 1.7, lev: 438.6, d10: 2.64 },
489
+ base: { yield: 1.75, snr: 0.33, vel: 0.50, lev: 3.5, d10: 0.54 },
490
+ hcm: { yield: 1.57, snr: 0.33, vel: 0.50, lev: 3.2, d10: 0.50 },
491
+ power: { yield: 1.51, snr: 0.07, vel: 0.08, lev: 22.3, d10: 1.35 },
492
+ top: { yield: 488.65, snr: 0.58, vel: 1.36, lev: 360.2, d10: 2.56 },
466
493
  };
467
494
 
468
495
  function renderTabBar(activeIdx) {
@@ -716,7 +743,7 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
716
743
  // which is what was starving codex's cascade rows after the Your-Read/Three-Degrees panels landed.)
717
744
  const platformCount = active.length;
718
745
  const barLines = 4 + platformCount + (platformCount > 1 ? 1 : 0); // Token Composition: hr + header + platforms + combined + note
719
- // Your Read (hr+header+~3 wins+1 gap = 6) + Three Degrees (blank+hr+header+col-header+5 rows+note = 10) + status
746
+ // Your Read (hr+header+~3 wins+1 gap = 6) + Four Degrees (blank+hr+header+col-header+5 rows+note = 10) + status
720
747
  const boardLines = 17;
721
748
  const sectionsBelow = barLines + boardLines;
722
749
  const maxCascadeRows = Math.max(8, budget - used - sectionsBelow);
@@ -768,7 +795,10 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
768
795
  if (lastPlatform !== null && row.platform !== lastPlatform) emit();
769
796
  renderRow(
770
797
  row.platform,
771
- (s) => (row.winKey === firstWin[row.platform] ? cyan(s) : dim(s)),
798
+ (s) => {
799
+ const pc = platformColor(row.platform);
800
+ return row.winKey === firstWin[row.platform] ? bold(pc(s)) : dim(s);
801
+ },
772
802
  row.winKey,
773
803
  row.pillars,
774
804
  row.estimated,
@@ -822,7 +852,7 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
822
852
  const all = d.windows?.find((w) => w.window === "all");
823
853
  if (!all) continue;
824
854
  emit(
825
- ` ${padEnd(cyan(d.platform), 10)} ${tokenBar(all.pillars, 50)} ${dim(fmtTok((all.pillars.input ?? 0) + (all.pillars.output ?? 0) + (all.pillars.cacheCreate ?? 0) + (all.pillars.cacheRead ?? 0)))}`,
855
+ ` ${padEnd(platformColor(d.platform)(d.platform), 10)} ${tokenBar(all.pillars, 50)} ${dim(fmtTok((all.pillars.input ?? 0) + (all.pillars.output ?? 0) + (all.pillars.cacheCreate ?? 0) + (all.pillars.cacheRead ?? 0)))}`,
826
856
  );
827
857
  }
828
858
  if (active.length > 1) {
@@ -865,9 +895,9 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
865
895
  }
866
896
  }
867
897
 
868
- // ── Your insights + the Three Degrees panel (replaces the old top-3 mini board)
898
+ // ── Your insights + the Four Degrees panel (replaces the old top-3 mini board)
869
899
  // The user's own cascade (the firstWin/combined row computed above) read against the
870
- // Three Degrees reference so the Dashboard says "here's you, here's the field, here's the ceiling."
900
+ // Four Degrees reference so the Dashboard says "here's you, here's the field, here's the ceiling."
871
901
  const you = (() => {
872
902
  // Best available cascade for the user: prefer claude's first-window, else any computed row.
873
903
  for (const d of active) {
@@ -910,7 +940,7 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
910
940
  chk(
911
941
  "Υ Yield",
912
942
  you.yield,
913
- TD.field.yield,
943
+ TD.hcm.yield,
914
944
  TD.top.yield,
915
945
  fmtY,
916
946
  "compound more — reuse cache, raise output",
@@ -918,7 +948,7 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
918
948
  chk(
919
949
  "SNR",
920
950
  you.snr,
921
- TD.field.snr,
951
+ TD.hcm.snr,
922
952
  TD.top.snr,
923
953
  fmtSNR,
924
954
  "tighten prompts — less input per unit output",
@@ -926,7 +956,7 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
926
956
  chk(
927
957
  "Leverage",
928
958
  you.leverage,
929
- TD.field.lev,
959
+ TD.hcm.lev,
930
960
  TD.top.lev,
931
961
  (v) => fmtLev(v) + "×",
932
962
  "lean on cache-read — amplify prior context",
@@ -934,7 +964,7 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
934
964
  chk(
935
965
  "Velocity",
936
966
  you.velocity,
937
- TD.field.vel,
967
+ TD.hcm.vel,
938
968
  TD.top.vel,
939
969
  (v) => v.toFixed(2),
940
970
  "more output per input token",
@@ -950,39 +980,42 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
950
980
  emit(` ${dim(" reading your cascade… (press [R] to refresh)")}`);
951
981
  }
952
982
 
953
- // ── Three Degrees — AI avg · SigRank avg · Top (live values from signalaf.com)
983
+ // ── Four Degrees — AA baseline · Human Center of Mass · Power users · Top Evals
984
+ // Matches signalaf.com/wiki/four-degrees (expanded from 3 → 4 degrees 2026-07-17).
954
985
  emit();
955
986
  emit(` ${hr()}`);
956
987
  emit(
957
- ` ${bold("Three Degrees")} ${dim("AI avg · SigRank avg · top — signalaf.com/wiki")}`,
988
+ ` ${bold("Four Degrees")} ${dim("AA baseline · Human Center of Mass · Power users · Top Evals — signalaf.com/wiki")}`,
958
989
  );
959
990
  const tdHead = [
960
991
  padEnd(hdr("Metric"), 10),
961
- padStart(hdr("AI avg"), 9),
962
- padStart(hdr("SigRank avg"), 12),
992
+ padStart(hdr("AA base"), 9),
993
+ padStart(hdr("HCM"), 9),
994
+ padStart(hdr("Power"), 9),
963
995
  padStart(hdr("Top"), 9),
964
996
  ];
965
997
  emit(` ${tdHead.join(" ")}`);
966
998
  const white = (s) => paint(c.white, s);
967
- const tdRow = (label, b, f, t, fmt) =>
999
+ const tdRow = (label, b, h, p, t, fmt) =>
968
1000
  emit(
969
- ` ${padEnd(label, 10)} ${padStart(white(fmt(b)), 9)} ${padStart(fmt(f), 12)} ${padStart(gold(fmt(t)), 9)}`,
1001
+ ` ${padEnd(label, 10)} ${padStart(dim(fmt(b)), 9)} ${padStart(white(fmt(h)), 9)} ${padStart(white(fmt(p)), 9)} ${padStart(gold(fmt(t)), 9)}`,
970
1002
  );
971
- tdRow("Υ Yield", TD.base.yield, TD.field.yield, TD.top.yield, fmtY);
972
- tdRow("SNR", TD.base.snr, TD.field.snr, TD.top.snr, fmtSNR);
973
- tdRow("Velocity", TD.base.vel, TD.field.vel, TD.top.vel, (v) =>
1003
+ tdRow("Υ Yield", TD.base.yield, TD.hcm.yield, TD.power.yield, TD.top.yield, fmtY);
1004
+ tdRow("SNR", TD.base.snr, TD.hcm.snr, TD.power.snr, TD.top.snr, fmtSNR);
1005
+ tdRow("Velocity", TD.base.vel, TD.hcm.vel, TD.power.vel, TD.top.vel, (v) =>
974
1006
  v.toFixed(2),
975
1007
  );
976
1008
  tdRow(
977
1009
  "Leverage",
978
1010
  TD.base.lev,
979
- TD.field.lev,
1011
+ TD.hcm.lev,
1012
+ TD.power.lev,
980
1013
  TD.top.lev,
981
1014
  (v) => fmtLev(v) + "×",
982
1015
  );
983
- tdRow("10xDEV", TD.base.d10, TD.field.d10, TD.top.d10, (v) => v.toFixed(2));
1016
+ tdRow("10xDEV", TD.base.d10, TD.hcm.d10, TD.power.d10, TD.top.d10, (v) => v.toFixed(2));
984
1017
  emit(
985
- ` ${dim("reference values until live user volume calibrates SigRank avg")}`,
1018
+ ` ${dim("reference values until live user volume calibrates HCM · Power · Top")}`,
986
1019
  );
987
1020
  }
988
1021
 
@@ -1246,16 +1279,17 @@ function renderCompare(data) {
1246
1279
  if (!td) missing.push("token-dashboard (~/.claude/token-dashboard.db)");
1247
1280
  if (!ts) missing.push("tokscale (npm i -g tokscale)");
1248
1281
  if (missing.length)
1249
- emit(` ${dim(` no data from: ${missing.join(" · ")}`)}`);
1282
+ for (const ln of wrapDim(`no data from: ${missing.join(" · ")}`)) emit(ln);
1250
1283
  emit();
1251
1284
 
1252
1285
  // FIX (2026-08-18): if ALL sources are empty (tokenpull has no windows AND all
1253
1286
  // verifiers are null), show a clear empty-state message instead of bare headers
1254
1287
  // with zero data rows.
1255
1288
  if (SOURCES.length === 0) {
1256
- emit(
1257
- ` ${dim(" no source data available for this platform — install a verifier or check ~/.claude/projects/")}`,
1258
- );
1289
+ for (const ln of wrapDim(
1290
+ "no source data available for this platform — install a verifier or check ~/.claude/projects/",
1291
+ ))
1292
+ emit(ln);
1259
1293
  return;
1260
1294
  }
1261
1295
 
@@ -1504,7 +1538,7 @@ function renderSubmissions(
1504
1538
  const cells = [
1505
1539
  padStart(rk, 4),
1506
1540
  nm,
1507
- padEnd(cyan(trunc(gPlat(e), 10)), 10),
1541
+ padEnd(platformColor(gPlat(e))(trunc(gPlat(e), 10)), 10),
1508
1542
  padEnd(dim(trunc(gWin(e), 6)), 6),
1509
1543
  yld,
1510
1544
  ...(showOpRatio ? [padEnd(dim(fmtOp(gOpRatio(e))), 18)] : []),
@@ -1695,6 +1729,7 @@ function renderWatchInfo(platform, win, refresh) {
1695
1729
  const winLabel = !win || win === "all-windows" ? "all windows" : win;
1696
1730
  const allDefault =
1697
1731
  platLabel === "all active platforms" && winLabel === "all windows";
1732
+ const platColorFn = (!platform || platform === "all") ? cyan : platformColor(platform);
1698
1733
  writeln();
1699
1734
  writeln(
1700
1735
  ` ${bold("Live Watch")} ${dim("the agent that keeps your rank current")}`,
@@ -1702,7 +1737,7 @@ function renderWatchInfo(platform, win, refresh) {
1702
1737
  writeln();
1703
1738
  writeln(` ${dim("What it does")}`);
1704
1739
  writeln(
1705
- ` Re-reads your local token logs for ${cyan(platLabel)} across ${cyan(winLabel)} every`,
1740
+ ` Re-reads your local token logs for ${platColorFn(platLabel)} across ${cyan(winLabel)} every`,
1706
1741
  );
1707
1742
  writeln(
1708
1743
  ` ${gold(refresh + "s")} and recomputes each cascade (Υ Yield · SNR · Leverage · class) live, on`,
@@ -1724,7 +1759,7 @@ function renderWatchInfo(platform, win, refresh) {
1724
1759
  writeln();
1725
1760
  writeln(` ${dim("Settings")}`);
1726
1761
  writeln(
1727
- ` ${dim("Platform")} ${cyan(platLabel)} ${dim("Window")} ${cyan(winLabel)} ${dim("Refresh")} ${gold(refresh + "s")}`,
1762
+ ` ${dim("Platform")} ${platColorFn(platLabel)} ${dim("Window")} ${cyan(winLabel)} ${dim("Refresh")} ${gold(refresh + "s")}`,
1728
1763
  );
1729
1764
  writeln(
1730
1765
  ` ${dim(allDefault ? "watching everything (default)" : "focused — press [P]/[W] to widen back to all")}`,
@@ -1870,9 +1905,10 @@ function renderWatchData(data) {
1870
1905
 
1871
1906
  if (active.length === 0) {
1872
1907
  emit();
1873
- emit(
1874
- ` ${dim(" no active platforms detected — run some sessions, this picks them up automatically")}`,
1875
- );
1908
+ for (const ln of wrapDim(
1909
+ "no active platforms detected — run some sessions, this picks them up automatically",
1910
+ ))
1911
+ emit(ln);
1876
1912
  return;
1877
1913
  }
1878
1914