sigrank 0.0.211 → 0.0.212
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 +1 -1
- package/package.json +1 -1
- package/presentation/tui.mjs +142 -52
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.
|
|
5
|
+
"version": "0.0.211",
|
|
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.
|
|
3
|
+
"version": "0.0.212",
|
|
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",
|
package/presentation/tui.mjs
CHANGED
|
@@ -260,27 +260,12 @@ function cascadeFrom(p) {
|
|
|
260
260
|
// ── Unicode bar chart (no dep) ───────────────────────────────────────────────
|
|
261
261
|
const BLOCKS = " ▏▎▍▌▋▊▉█";
|
|
262
262
|
|
|
263
|
-
// Linear bar — use when all values are the same order of magnitude.
|
|
264
|
-
function barChart(values, labels, opts = {}) {
|
|
265
|
-
const { width = 30, colorFn = (s) => s, maxVal } = opts;
|
|
266
|
-
const max = maxVal ?? Math.max(...values.filter(Number.isFinite), 1);
|
|
267
|
-
const lines = [];
|
|
268
|
-
for (let i = 0; i < values.length; i++) {
|
|
269
|
-
const v = values[i] ?? 0;
|
|
270
|
-
const pct = Math.min(v / max, 1);
|
|
271
|
-
const full = Math.floor(pct * width);
|
|
272
|
-
const frac = Math.floor((pct * width - full) * 8);
|
|
273
|
-
const bar = colorFn("█".repeat(full) + (frac > 0 ? BLOCKS[frac] : ""));
|
|
274
|
-
const lbl = padEnd(dim(labels[i] ?? ""), 10);
|
|
275
|
-
const val = padStart(fmtTok(v), 8);
|
|
276
|
-
lines.push(` ${lbl} ${padEnd(bar, width)} ${val}`);
|
|
277
|
-
}
|
|
278
|
-
return lines;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
263
|
// Log-scale bar — use when values span multiple orders of magnitude (e.g. token pillars
|
|
282
264
|
// where cacheRead >> input). Maps log10(v) to bar width so each 10x = same visual step.
|
|
283
|
-
// minLog floor prevents zero/tiny values from going negative.
|
|
265
|
+
// minLog floor prevents zero/tiny values from going negative. WIRED into the Dashboard
|
|
266
|
+
// Token Composition section (logTokenBar) so cache-heavy operators like MOSES (1000×
|
|
267
|
+
// scale differences between pillars) render every pillar visibly instead of one pillar
|
|
268
|
+
// erasing the other three.
|
|
284
269
|
function logBar(v, maxLog, width = 40, colorCode = c.cyan) {
|
|
285
270
|
if (!v || v <= 0) return { bar: dim("·".repeat(width)), pct: 0 };
|
|
286
271
|
const log = Math.log10(v);
|
|
@@ -297,14 +282,23 @@ function logBar(v, maxLog, width = 40, colorCode = c.cyan) {
|
|
|
297
282
|
// ── Sparkline (no dep) ────────────────────────────────────────────────────────
|
|
298
283
|
const SPARK = "▁▂▃▄▅▆▇█";
|
|
299
284
|
function sparkline(values) {
|
|
300
|
-
const valid = values.filter(Number.isFinite);
|
|
285
|
+
const valid = values.filter((v) => v != null && Number.isFinite(v));
|
|
301
286
|
if (valid.length === 0) return dim("no data");
|
|
287
|
+
// With only 1 valid point there's no trend to show — render a flat marker
|
|
288
|
+
// at the mid-height so it doesn't look like a rising/falling edge.
|
|
289
|
+
if (valid.length === 1) {
|
|
290
|
+
return values
|
|
291
|
+
.map((v) =>
|
|
292
|
+
v != null && Number.isFinite(v) ? SPARK[4] : dim(" "),
|
|
293
|
+
)
|
|
294
|
+
.join("");
|
|
295
|
+
}
|
|
302
296
|
const min = Math.min(...valid),
|
|
303
297
|
max = Math.max(...valid);
|
|
304
298
|
return values
|
|
305
299
|
.map((v) => {
|
|
306
|
-
if (!Number.isFinite(v)) return dim("
|
|
307
|
-
const idx = max === min ?
|
|
300
|
+
if (v == null || !Number.isFinite(v)) return dim(" "); // gap, not a fake low point
|
|
301
|
+
const idx = max === min ? 4 : Math.round(((v - min) / (max - min)) * 7);
|
|
308
302
|
return SPARK[idx];
|
|
309
303
|
})
|
|
310
304
|
.join("");
|
|
@@ -524,11 +518,34 @@ function tokenBar(p, width = 40) {
|
|
|
524
518
|
return bar;
|
|
525
519
|
}
|
|
526
520
|
|
|
521
|
+
// Log-scale per-pillar breakdown — 4 separate log10 bars (input / cacheW / cacheR / output)
|
|
522
|
+
// so every pillar stays visible even when one dominates by 1000× (e.g. MOSES cacheRead
|
|
523
|
+
// 2.5B vs input 1.25M). The linear tokenBar() above erases small pillars in that case;
|
|
524
|
+
// this companion view shows the true shape. Returns an array of 4 lines:
|
|
525
|
+
// [" I ▏▎▍▌ 1.25M", " W ▏▎▍▌▋ 128M", " R ▏▎▍▌▋▊▉█ 2.5B", " O ▏▎▍ 11.3M"]
|
|
526
|
+
// maxLog is derived from the largest pillar so the biggest pillar fills the bar.
|
|
527
|
+
function logTokenBar(p, width = 36) {
|
|
528
|
+
if (!p) return [dim(" no data")];
|
|
529
|
+
const vals = [
|
|
530
|
+
{ lbl: "I", v: p.input ?? 0, color: c.cyan },
|
|
531
|
+
{ lbl: "W", v: p.cacheCreate ?? 0, color: c.blue },
|
|
532
|
+
{ lbl: "R", v: p.cacheRead ?? 0, color: c.boldGold },
|
|
533
|
+
{ lbl: "O", v: p.output ?? 0, color: c.green },
|
|
534
|
+
];
|
|
535
|
+
const maxV = Math.max(...vals.map((x) => x.v), 1);
|
|
536
|
+
const maxLog = Math.log10(maxV);
|
|
537
|
+
if (maxLog <= 0) return [dim(" no data")]; // all pillars ≤ 1 → nothing to show
|
|
538
|
+
return vals.map(({ lbl, v, color }) => {
|
|
539
|
+
const { bar } = logBar(v, maxLog, width, color);
|
|
540
|
+
return ` ${paint(color, lbl)} ${bar} ${dim(fmtTok(v))}`;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
527
544
|
// Generalized cascade-metric sparkline across windows (7d→30d→90d→all).
|
|
528
545
|
// pick: (cas) => number · fmt: (v) => string. Used by the Trends tab for every metric.
|
|
529
546
|
// FIX G1: window headers are rendered once at the top of each metric block (mirror
|
|
530
|
-
// renderCompare's column layout). metricSpark
|
|
531
|
-
//
|
|
547
|
+
// renderCompare's column layout). metricSpark returns sparkline + RIGHT-ALIGNED
|
|
548
|
+
// values in fixed COL_W columns — NO per-value `w:` prefix (the header row
|
|
532
549
|
// carries the window labels). Null windows render a dim `—` (columns stay aligned).
|
|
533
550
|
const TREND_COL_W = 10;
|
|
534
551
|
const TREND_WINS = ["7d", "30d", "90d", "all"];
|
|
@@ -550,23 +567,14 @@ function metricSpark(d, pick, fmt) {
|
|
|
550
567
|
const cols = vals.map((v) =>
|
|
551
568
|
v != null ? padStart(fmt(v), TREND_COL_W) : padStart(dim("—"), TREND_COL_W),
|
|
552
569
|
);
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
function yieldSpark(d) {
|
|
558
|
-
const vals = TREND_WINS.map((w) => {
|
|
559
|
-
const wd = d.windows?.find((x) => x.window === w);
|
|
560
|
-
if (!wd) return null;
|
|
561
|
-
const cas = cascadeFrom(wd.pillars);
|
|
562
|
-
return cas?.yield ?? null;
|
|
563
|
-
});
|
|
564
|
-
const cols = vals.map((v) =>
|
|
565
|
-
v != null
|
|
566
|
-
? padStart(fmtY(v), TREND_COL_W)
|
|
567
|
-
: padStart(dim("—"), TREND_COL_W),
|
|
570
|
+
// Non-numeric pick values (e.g. Op Ratio returns an object) can't sparkline —
|
|
571
|
+
// show just the formatted columns. Numeric values get the sparkline prefix.
|
|
572
|
+
const numericVals = vals.map((v) =>
|
|
573
|
+
v != null && Number.isFinite(v) ? v : null,
|
|
568
574
|
);
|
|
569
|
-
|
|
575
|
+
const hasNumeric = numericVals.some((v) => v != null);
|
|
576
|
+
if (!hasNumeric) return ` ${cols.join(" ")}`;
|
|
577
|
+
return sparkline(numericVals) + ` ${cols.join(" ")}`;
|
|
570
578
|
}
|
|
571
579
|
|
|
572
580
|
// ── TAB 1: DASHBOARD ─────────────────────────────────────────────────────────
|
|
@@ -828,6 +836,30 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
|
|
|
828
836
|
` ${padEnd(bold(active.map((d) => d.platform).join("+")), 10)} ${tokenBar(cp, 50)} ${dim(fmtTok(cp.input + cp.output + cp.cacheCreate + cp.cacheRead))}`,
|
|
829
837
|
);
|
|
830
838
|
}
|
|
839
|
+
// Log-scale per-pillar breakdown — companion to the linear stacked bar above.
|
|
840
|
+
// The linear bar erases small pillars when one dominates by 1000× (MOSES-class:
|
|
841
|
+
// cacheRead 2.5B vs input 1.25M → input gets 0 width). The log view shows every
|
|
842
|
+
// pillar's true shape so the operator can see the full composition at a glance.
|
|
843
|
+
// Only renders for the combined row (or the single platform when there's one) to
|
|
844
|
+
// avoid blowing the height budget on multi-platform setups.
|
|
845
|
+
if (used < budget - boardLines - 5) {
|
|
846
|
+
const cp = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
847
|
+
for (const d of active) {
|
|
848
|
+
const all = d.windows?.find((w) => w.window === "all");
|
|
849
|
+
if (!all) continue;
|
|
850
|
+
cp.input += all.pillars.input ?? 0;
|
|
851
|
+
cp.output += all.pillars.output ?? 0;
|
|
852
|
+
cp.cacheCreate += all.pillars.cacheCreate ?? 0;
|
|
853
|
+
cp.cacheRead += all.pillars.cacheRead ?? 0;
|
|
854
|
+
}
|
|
855
|
+
const total = cp.input + cp.output + cp.cacheCreate + cp.cacheRead;
|
|
856
|
+
if (total > 0) {
|
|
857
|
+
emit(
|
|
858
|
+
` ${dim("log-scale (each 10× = same step — every pillar visible)")}`,
|
|
859
|
+
);
|
|
860
|
+
for (const line of logTokenBar(cp, 36)) emit(line);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
831
863
|
}
|
|
832
864
|
|
|
833
865
|
// ── Your insights + the Three Degrees panel (replaces the old top-3 mini board)
|
|
@@ -1042,11 +1074,43 @@ function renderTrends(data, subIdx = 0) {
|
|
|
1042
1074
|
emit();
|
|
1043
1075
|
|
|
1044
1076
|
if (sub === "You" || sub === "Platform") {
|
|
1077
|
+
// FIX (2026-08-18): "You" previously took only active[0]?.windows (the first
|
|
1078
|
+
// platform by sort order — usually claude). If that platform had no timestamps
|
|
1079
|
+
// (NULL-TIMESTAMP WINDOW ASYMMETRY → 7d/30d/90d empty) or no local data, "You"
|
|
1080
|
+
// showed nothing even when other platforms had data. Now aggregate ALL active
|
|
1081
|
+
// platforms' windows into one combined "you" row, summing pillars per window
|
|
1082
|
+
// so every platform contributes to every window it has data for.
|
|
1045
1083
|
const rows =
|
|
1046
1084
|
sub === "You"
|
|
1047
1085
|
? active?.length
|
|
1048
|
-
? [
|
|
1049
|
-
|
|
1086
|
+
? [
|
|
1087
|
+
{
|
|
1088
|
+
platform: "you",
|
|
1089
|
+
windows: TREND_WINS.map((wk) => {
|
|
1090
|
+
let input = 0,
|
|
1091
|
+
output = 0,
|
|
1092
|
+
cacheCreate = 0,
|
|
1093
|
+
cacheRead = 0,
|
|
1094
|
+
messages = 0;
|
|
1095
|
+
for (const d of active) {
|
|
1096
|
+
const wd = d.windows?.find((x) => x.window === wk);
|
|
1097
|
+
if (wd) {
|
|
1098
|
+
input += wd.pillars.input ?? 0;
|
|
1099
|
+
output += wd.pillars.output ?? 0;
|
|
1100
|
+
cacheCreate += wd.pillars.cacheCreate ?? 0;
|
|
1101
|
+
cacheRead += wd.pillars.cacheRead ?? 0;
|
|
1102
|
+
messages += wd.messages ?? 0;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
window: wk,
|
|
1107
|
+
pillars: { input, output, cacheCreate, cacheRead },
|
|
1108
|
+
messages,
|
|
1109
|
+
};
|
|
1110
|
+
}),
|
|
1111
|
+
},
|
|
1112
|
+
]
|
|
1113
|
+
: []
|
|
1050
1114
|
: (active ?? []);
|
|
1051
1115
|
if (!rows.length) {
|
|
1052
1116
|
emit(` ${dim(" reading your cascade… (press [R] to refresh)")}`);
|
|
@@ -1071,7 +1135,7 @@ function renderTrends(data, subIdx = 0) {
|
|
|
1071
1135
|
if (used >= budget - 2) break;
|
|
1072
1136
|
if (m.stub) {
|
|
1073
1137
|
emit(
|
|
1074
|
-
` ${padEnd(dim(m.label), 10)} ${dim("
|
|
1138
|
+
` ${padEnd(dim(m.label), 10)} ${dim(" calculating… (" + m.stub + ")")}`,
|
|
1075
1139
|
);
|
|
1076
1140
|
} else {
|
|
1077
1141
|
emit(
|
|
@@ -1083,7 +1147,7 @@ function renderTrends(data, subIdx = 0) {
|
|
|
1083
1147
|
// rank trend — wired later; honest placeholder for now
|
|
1084
1148
|
if (used < budget - 1)
|
|
1085
1149
|
emit(
|
|
1086
|
-
` ${padEnd(dim("Rank"), 10)} ${dim("
|
|
1150
|
+
` ${padEnd(dim("Rank"), 10)} ${dim(" calculating… (rank history wires post-ingest)")}`,
|
|
1087
1151
|
);
|
|
1088
1152
|
else trendsDropped += 1;
|
|
1089
1153
|
emit();
|
|
@@ -1104,7 +1168,11 @@ function renderTrends(data, subIdx = 0) {
|
|
|
1104
1168
|
|
|
1105
1169
|
// ── TAB 3: COMPARE ───────────────────────────────────────────────────────────
|
|
1106
1170
|
function renderCompare(data) {
|
|
1107
|
-
|
|
1171
|
+
// FIX (2026-08-18): guard against null data — the debug renderOnce path
|
|
1172
|
+
// (node tui.mjs --render 1) passes null when loadCompareData fails, and
|
|
1173
|
+
// destructure of null throws. Coalesce to a safe empty shape.
|
|
1174
|
+
const { tpData, cc, ts, td } = data ?? {};
|
|
1175
|
+
const platform = data?.platform ?? "claude";
|
|
1108
1176
|
const WINS = ["7d", "30d", "90d", "all"];
|
|
1109
1177
|
const w = W();
|
|
1110
1178
|
const budget = H() - 4;
|
|
@@ -1163,8 +1231,27 @@ function renderCompare(data) {
|
|
|
1163
1231
|
);
|
|
1164
1232
|
}
|
|
1165
1233
|
}
|
|
1234
|
+
// FIX (2026-08-18): surface WHY external verifiers are absent so the user knows
|
|
1235
|
+
// it's a missing-tool issue, not a bug. Each verifier is null when its CLI/DB
|
|
1236
|
+
// isn't installed or returned no data for this platform.
|
|
1237
|
+
const missing = [];
|
|
1238
|
+
if (!cc) missing.push("ccusage (npm i -g ccusage)");
|
|
1239
|
+
if (!td) missing.push("token-dashboard (~/.claude/token-dashboard.db)");
|
|
1240
|
+
if (!ts) missing.push("tokscale (npm i -g tokscale)");
|
|
1241
|
+
if (missing.length)
|
|
1242
|
+
emit(` ${dim(` no data from: ${missing.join(" · ")}`)}`);
|
|
1166
1243
|
emit();
|
|
1167
1244
|
|
|
1245
|
+
// FIX (2026-08-18): if ALL sources are empty (tokenpull has no windows AND all
|
|
1246
|
+
// verifiers are null), show a clear empty-state message instead of bare headers
|
|
1247
|
+
// with zero data rows.
|
|
1248
|
+
if (SOURCES.length === 0) {
|
|
1249
|
+
emit(
|
|
1250
|
+
` ${dim(" no source data available for this platform — install a verifier or check ~/.claude/projects/")}`,
|
|
1251
|
+
);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1168
1255
|
const COL_W = 11;
|
|
1169
1256
|
const hcols = [
|
|
1170
1257
|
padEnd(dim("Source"), 12),
|
|
@@ -1789,20 +1876,23 @@ function renderSubmitPreview(dashData, id) {
|
|
|
1789
1876
|
}
|
|
1790
1877
|
|
|
1791
1878
|
// ── DEBUG: render a tab once (no TTY/alt-screen) + audit each line's visible
|
|
1792
|
-
// width vs terminal columns. Usage: `node tui.mjs --render [0
|
|
1793
|
-
//
|
|
1794
|
-
//
|
|
1879
|
+
// width vs terminal columns. Usage: `node tui.mjs --render [0-5]`. Tab indices
|
|
1880
|
+
// match the live TABS array: 0=Dashboard, 1=Trends, 2=Compare, 3=Board,
|
|
1881
|
+
// 4=Watch, 5=Connect. Prints a width report (>w = would overflow/wrap) then
|
|
1882
|
+
// the raw frame. For diagnosing layout overflow without an interactive session.
|
|
1795
1883
|
async function renderOnce(tabIdx = 0) {
|
|
1796
1884
|
const w = W();
|
|
1797
1885
|
const data = {
|
|
1798
1886
|
0: await loadDashboardData().catch((e) => ({ error: e.message })),
|
|
1799
|
-
1: await
|
|
1800
|
-
2: await
|
|
1887
|
+
1: await loadDashboardData().catch((e) => ({ error: e.message })),
|
|
1888
|
+
2: await loadCompareData("claude").catch(() => null),
|
|
1889
|
+
3: await loadBoardData("30d").catch(() => null),
|
|
1801
1890
|
}[tabIdx];
|
|
1802
1891
|
startBuffer();
|
|
1803
1892
|
if (tabIdx === 0) renderDashboard(data, "debug");
|
|
1804
|
-
else if (tabIdx === 1)
|
|
1805
|
-
else if (tabIdx === 2)
|
|
1893
|
+
else if (tabIdx === 1) renderTrends(data, 0);
|
|
1894
|
+
else if (tabIdx === 2) renderCompare(data);
|
|
1895
|
+
else if (tabIdx === 3) renderBoard(data, "30d");
|
|
1806
1896
|
else if (tabIdx === 4) await renderWatch("all", "all-windows");
|
|
1807
1897
|
else if (tabIdx === 5) renderConnect(loadIdentity(), "", "");
|
|
1808
1898
|
const lines = _screenBuf || [];
|