sigrank 0.13.0 → 0.14.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
@@ -1,16 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- ```
4
- ███████ ██ ██████ ██████ █████ ███ ██ ██ ██
5
- ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██
6
- ███████ ██ ██ ███ ██████ ███████ ██ ██ ██ █████
7
- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
8
- ███████ ██ ██████ ██ ██ ██ ██ ██ ████ ██ ██
9
- ```
10
-
11
- ◈ ─────────────────────────────────────────── ◈
12
-
13
- # SigRank MCP
3
+ <p><img src="./.github/assets/og-card.png" alt="SigRank — the new standard in AI evaluation &amp; benchmarks" width="800"></p>
14
4
 
15
5
  **The yield cascade + live leaderboard as MCP tools any agent can call.**
16
6
 
@@ -25,21 +15,35 @@ For all builders, burners and 10xers.
25
15
 
26
16
  </div>
27
17
 
28
- ---
18
+ | The board | Your operator profile |
19
+ |:---:|:---:|
20
+ | [![SigRank leaderboard](./.github/assets/board.png)](https://signalaf.com/board/all) | [![SigRank operator profile](./.github/assets/profile.png)](https://signalaf.com) |
21
+ | Every operator ranked by **Υ Yield** — the architecture of the cascade, not raw spend | Cascade layer, class, and fingerprint — derived from four token counts |
29
22
 
30
- Token-only. No auth. No transcript content. Privacy-preserving by design.
23
+ > **Run [`sigrank enroll`](#sign-in--submit) then [`sigrank submit`](#sign-in--submit) to get ranked and claim your public profile at [signalaf.com](https://signalaf.com).**
31
24
 
32
- Dual-mode package: **interactive CLI dashboard** for operators, **MCP stdio server** for AI clients.
25
+ ---
26
+
27
+ ## Quickstart — 3 steps to the board
33
28
 
34
29
  ```bash
30
+ # 1. Install (pulls ccusage + tokscale + tokendash automatically — no separate installs)
35
31
  npm install -g sigrank
36
- sigrank # launches the full tabbed TUI (TTY detected automatically)
32
+
33
+ # 2. Sign in (paste a connect code from signalaf.com → Settings → New key)
34
+ sigrank enroll
35
+
36
+ # 3. Submit your cascade to the board
37
+ sigrank submit
37
38
  ```
38
39
 
39
- Or one-shot without installing:
40
+ That's it. The agent reads your local AI session logs on-device, derives your token cascade (Υ Yield, Leverage, Velocity, 10xDEV), and publishes to [signalaf.com](https://signalaf.com). No paste, no transcript content — only the four token counts leave your machine.
41
+
42
+ Or just explore without signing in:
40
43
 
41
44
  ```bash
42
- npx sigrank board --once
45
+ sigrank # launches the full tabbed TUI (dashboard, compare, board, watch)
46
+ npx sigrank board --once # print the live leaderboard once
43
47
  ```
44
48
 
45
49
  ## Install from GitHub
@@ -67,7 +71,7 @@ sigrank
67
71
  ## Commands
68
72
 
69
73
  ```
70
- ⊙ SigRank CLI v0.13.0
74
+ ⊙ SigRank CLI v0.14.2
71
75
 
72
76
  Default (no args)
73
77
  sigrank unified dashboard: cascade + token pillars + board
@@ -78,8 +82,6 @@ Commands
78
82
  board live leaderboard (refreshes every 30s)
79
83
  board --window 7d board for a specific window (7d, 30d, 90d, all)
80
84
  board --once print once and exit
81
- me single-platform cascade (claude by default)
82
- me --platform codex use a different platform adapter
83
85
  compare raw pillar audit: tokenpull vs ccusage vs token-dash vs tokscale
84
86
  compare --platform codex compare for a specific platform
85
87
  tui full tabbed TUI: Dashboard / Trends / Compare / Board / Watch / Connect
@@ -103,7 +105,6 @@ Examples
103
105
  sigrank board # live leaderboard
104
106
  sigrank compare # pillar audit (claude)
105
107
  sigrank compare --platform codex
106
- sigrank me --platform codex
107
108
  sigrank watch --window 7d --refresh 60
108
109
  sigrank board --window all --once
109
110
  ```
@@ -202,9 +203,9 @@ The dashboard pulls from multiple sources and shows them side-by-side for verifi
202
203
  | Source | What | Platform |
203
204
  |---|---|---|
204
205
  | `tokenpull` | On-device JSONL scanner (canon source) | claude, codex, amp, … |
205
- | `ccusage` | `ccusage <platform> daily --json` CLI | claude, codex |
206
- | `token-dashboard` | `~/.claude/token-dashboard.db` SQLite | claude only |
207
- | `tokscale` | `~/tokscale_report.json` export | claude, codex |
206
+ | `ccusage` | `ccusage <platform> daily --json` CLI (bundled) | claude, codex |
207
+ | `token-dashboard` | `~/.claude/token-dashboard.db` SQLite (bundled) | claude only |
208
+ | `tokscale` | `tokscale models --json` CLI (bundled, falls back to `~/tokscale_report.json`) | claude, codex |
208
209
 
209
210
  **Codex input is estimated** — Codex logs don't expose true input tokens directly. The formula:
210
211
  ```
package/banner.svg ADDED
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="200" viewBox="0 0 800 200">
2
+ <rect width="800" height="200" rx="12" fill="#0a0805"/>
3
+ <rect x="0" y="0" width="800" height="3" rx="12" fill="#c4923a"/>
4
+ <text x="60" y="110" font-family="Georgia, serif" font-size="56" font-weight="700" fill="#e8b955">§ SigRank</text>
5
+ <text x="60" y="150" font-family="Helvetica, Arial, sans-serif" font-size="18" fill="#a89a7a">The AI operator leaderboard</text>
6
+ <text x="60" y="178" font-family="Helvetica, Arial, sans-serif" font-size="14" fill="#6b6354">signalaf.com</text>
7
+ </svg>
package/cli.mjs CHANGED
@@ -9,9 +9,7 @@
9
9
  * npx sigrank board live leaderboard, refreshes every 30s
10
10
  * npx sigrank board --window 7d board for a specific window
11
11
  * npx sigrank board --once print once and exit (no live refresh)
12
- * npx sigrank me your cascade across all 4 windows
13
- * npx sigrank me --platform amp use a different platform adapter
14
- * npx sigrank me --compare raw pillar comparison: ccusage vs tokenpull vs token-dashboard
12
+ * npx sigrank compare raw pillar comparison: ccusage vs tokenpull vs token-dashboard
15
13
  * npx sigrank watch RT tune meter — ALL active platforms × all windows
16
14
  * npx sigrank watch --platform X watch one platform only (optional filter)
17
15
  * npx sigrank watch --window 7d watch one window only (optional filter)
@@ -24,7 +22,7 @@
24
22
  * green = positive movement
25
23
  */
26
24
 
27
- import { callTool, DEFAULT_API_BASE } from './tools.mjs'
25
+ import { callTool, DEFAULT_API_BASE, pullActivePlatforms } from './tools.mjs'
28
26
  import { classify } from './cascade.mjs'
29
27
  import { ensureIdentity, keystorePath } from './keystore.mjs'
30
28
  import { submitSignedWindow } from './submit.mjs'
@@ -314,107 +312,6 @@ async function runBoard({ window = '30d', once = false, refresh = 30 } = {}) {
314
312
  }
315
313
  }
316
314
 
317
- // ── ME command ───────────────────────────────────────────────────────────────
318
-
319
- async function runMe({ platform = 'claude', compare = false } = {}) {
320
- if (compare) return runCompare({ platform })
321
-
322
- write(HIDE_CURSOR)
323
- writeln(` ${dim('reading local token logs…')}`)
324
-
325
- let pulled
326
- try {
327
- pulled = await callTool('tokenpull', { platform })
328
- } catch (e) {
329
- write(SHOW_CURSOR)
330
- write(CURSOR_UP(1) + ERASE_LINE)
331
- writeln(red(` ✗ ${e.message}`))
332
- process.exit(1)
333
- }
334
-
335
- // clear the "reading…" line
336
- write(CURSOR_UP(1) + ERASE_LINE)
337
-
338
- const w = termWidth()
339
- writeln()
340
- writeln(` ${gold('⊙ SigRank')} ${bold('Your Cascade')} ${dim(`platform: ${pulled.platform ?? platform}`)}`)
341
- if (pulled.estimated) writeln(` ${dim('⚠ estimated values (cache data unavailable for this platform)')}`)
342
- writeln(` ${dim('─'.repeat(w - 4))}`)
343
-
344
- // column headers
345
- const cols_h = [
346
- padEnd(dim('Window'), 8),
347
- padStart(dim('Υ Yield'), 10),
348
- padStart(dim('SNR'), 7),
349
- padStart(dim('Leverage'), 9),
350
- padStart(dim('Velocity'), 9),
351
- padStart(dim('10xDEV'), 8),
352
- padStart(dim('Class'), 13),
353
- padStart(dim('Tokens'), 8),
354
- ]
355
- writeln(` ${cols_h.join(' ')}`)
356
- writeln(` ${dim('·'.repeat(w - 4))}`)
357
-
358
- const windows = pulled.windows ?? []
359
- for (const win of windows) {
360
- const cas = win.cascade
361
- const isAll = win.window === 'all'
362
- const wLabel = isAll ? bold('all-time') : win.window
363
- const yVal = cas?.yield != null ? fmtYield(cas.yield) : '—'
364
- const snrVal = cas?.snr != null ? fmtSNR(cas.snr) : '—'
365
- const levVal = cas?.leverage != null ? `${fmtLev(cas.leverage)}×` : '—'
366
- const velVal = cas?.velocity != null ? cas.velocity.toFixed(2) : '—'
367
- const devVal = cas?.dev10x != null ? cas.dev10x.toFixed(2) : '—'
368
- const cls = colorClass(cas?.class ?? '—')
369
- const tok = fmtTokens(win.pillars?.total ?? (
370
- (win.pillars?.input ?? 0) + (win.pillars?.output ?? 0) +
371
- (win.pillars?.cacheCreate ?? 0) + (win.pillars?.cacheRead ?? 0)
372
- ))
373
-
374
- const row = [
375
- padEnd(wLabel, 8),
376
- padStart(isAll ? gold(yVal) : yVal, 10),
377
- padStart(snrVal, 7),
378
- padStart(levVal, 9),
379
- padStart(velVal, 9),
380
- padStart(devVal, 8),
381
- padEnd(cls, 13),
382
- padStart(tok, 8),
383
- ]
384
- writeln(` ${row.join(' ')}`)
385
- }
386
-
387
- writeln(` ${dim('─'.repeat(w - 4))}`)
388
-
389
- // card for the best window (all-time if present, else first)
390
- const best = windows.find(w => w.window === 'all') ?? windows[0]
391
- if (best?.card) {
392
- writeln()
393
- writeln(` ${dim('cascade read:')}`)
394
- // wrap card text at terminal width
395
- const cardText = best.card
396
- const maxW = w - 6
397
- const words = cardText.split(' ')
398
- let line = ''
399
- for (const word of words) {
400
- if (line.length + word.length + 1 > maxW) {
401
- writeln(` ${line}`)
402
- line = word
403
- } else {
404
- line = line ? `${line} ${word}` : word
405
- }
406
- }
407
- if (line) writeln(` ${line}`)
408
- }
409
-
410
- writeln()
411
-
412
- // submit hint
413
- writeln(` ${dim('to publish:')} ${cyan('npx sigrank submit')} ${dim('(sign in first:')} ${cyan('npx sigrank enroll')}${dim(' — get a code at signalaf.com → Settings)')}`)
414
- writeln()
415
- write(SHOW_CURSOR)
416
- }
417
-
418
315
  // ── COMPARE command ───────────────────────────────────────────────────────────
419
316
  // Side-by-side: ccusage (JSON) vs tokenpull vs token-dashboard (SQLite)
420
317
  //
@@ -756,16 +653,8 @@ const WATCH_WINDOWS = ['7d', '30d', '90d', 'all']
756
653
  // Detect which platforms have real local data (input+output > 0 in any window) via tokenpull.
757
654
  // Returns an array of pull results (the same shape tokenpullAny yields), one per active platform.
758
655
  async function detectActivePulls() {
759
- const { tokenpullAny } = await import('./tokenpull.mjs')
760
- const settled = await Promise.allSettled(ALL_PLATFORMS.map((p) => tokenpullAny(p)))
761
- const active = []
762
- for (const r of settled) {
763
- if (r.status !== 'fulfilled' || !r.value) continue
764
- const d = r.value
765
- const live = (d.windows || []).some((w) => (w.pillars.input + w.pillars.output) > 0)
766
- if (live) active.push(d)
767
- }
768
- return active
656
+ // Unified: one loader for me / watch / the TUI Dashboard (was a separate tokenpullAny loop).
657
+ return pullActivePlatforms()
769
658
  }
770
659
 
771
660
  async function runWatch({ platform, window: win, refresh = 30, submit = false } = {}) {
@@ -1320,8 +1209,6 @@ async function showHelp() {
1320
1209
  writeln(` ${cyan('board')} live leaderboard (refreshes every 30s)`)
1321
1210
  writeln(` ${cyan('board --window 7d')} board for a specific window (7d, 30d, 90d, all)`)
1322
1211
  writeln(` ${cyan('board --once')} print once and exit`)
1323
- writeln(` ${cyan('me')} single-platform cascade (claude by default)`)
1324
- writeln(` ${cyan('me --platform codex')} use a different platform adapter`)
1325
1212
  writeln(` ${cyan('compare')} raw pillar audit: tokenpull vs ccusage vs token-dash vs tokscale`)
1326
1213
  writeln(` ${cyan('compare --platform codex')} compare for a specific platform`)
1327
1214
  writeln(` ${cyan('tui')} full tabbed TUI: Dashboard / Compare / Board / Watch`)
@@ -1346,7 +1233,6 @@ async function showHelp() {
1346
1233
  writeln(` ${dim('sigrank board')} # live leaderboard`)
1347
1234
  writeln(` ${dim('sigrank compare')} # pillar audit (claude)`)
1348
1235
  writeln(` ${dim('sigrank compare --platform codex')}`)
1349
- writeln(` ${dim('sigrank me --platform codex')}`)
1350
1236
  writeln(` ${dim('sigrank watch --window 7d --refresh 60')}`)
1351
1237
  writeln(` ${dim('sigrank board --window all --once')}`)
1352
1238
  writeln()
@@ -1436,15 +1322,26 @@ async function runSubmit({ platform = 'claude', window } = {}) {
1436
1322
  }
1437
1323
 
1438
1324
  writeln()
1325
+ let anyRanked = false
1326
+ let anyReceivedNotRanked = false
1439
1327
  for (const w of out.windows || []) {
1440
1328
  const label = String(w.window).padEnd(8)
1441
- if (w.status === 'received') {
1442
- writeln(` ${green('✓')} ${bold(label)} ${dim(`tier=${w.verification_tier || '—'}${w.persisted ? ' · persisted' : ''}`)}`)
1329
+ if (w.ranked) {
1330
+ anyRanked = true
1331
+ writeln(` ${green('✓')} ${bold(label)} ${green('RANKED')} ${dim('· live on the board')}`)
1332
+ } else if (w.status === 'received') {
1333
+ anyReceivedNotRanked = true
1334
+ writeln(` ${gold('⚠')} ${bold(label)} ${gold('received · NOT ranked')} ${dim(`(tier=${w.verification_tier || '—'})`)}`)
1443
1335
  } else {
1444
1336
  writeln(` ${red('✗')} ${bold(label)} ${dim(w.reason || w.status || 'failed')}`)
1445
1337
  }
1446
1338
  }
1447
- writeln(` ${dim(`Reload your board row: signalaf.com/user/${id.codename}`)}`)
1339
+ writeln()
1340
+ if (anyRanked) {
1341
+ writeln(` ${dim('Your board row:')} ${cyan(`signalaf.com/user/${id.codename}`)}`)
1342
+ } else if (anyReceivedNotRanked) {
1343
+ writeln(` ${gold('Received, but NOT ranked')} ${dim('— this device is unenrolled or revoked. Re-enroll to rank:')} ${cyan('npx sigrank enroll')}`)
1344
+ }
1448
1345
  }
1449
1346
 
1450
1347
  export async function runCli(argv) {
@@ -1485,8 +1382,6 @@ export async function runCli(argv) {
1485
1382
  once: flags.once === true || flags.once === 'true',
1486
1383
  refresh: Number(flags.refresh) || 30,
1487
1384
  })
1488
- } else if (cmd === 'me') {
1489
- await runMe({ platform: flags.platform ?? 'claude', compare: flags.compare === true || flags.compare === 'true' })
1490
1385
  } else if (cmd === 'compare') {
1491
1386
  await runCompare({ platform: flags.platform ?? 'claude' })
1492
1387
  } else if (cmd === 'tui') {
package/index.mjs CHANGED
@@ -70,7 +70,7 @@ async function startMcpServer() {
70
70
  // no args + TTY stdout → full tabbed TUI (interactive use)
71
71
  // no args + piped → MCP stdio server (AI client use)
72
72
  const cliArgs = process.argv.slice(2)
73
- const CLI_COMMANDS = new Set(['board', 'me', 'compare', 'tui', 'watch', 'enroll', 'submit', 'help', '--help', '-h', '--version', '-v'])
73
+ const CLI_COMMANDS = new Set(['board', 'compare', 'tui', 'watch', 'enroll', 'submit', 'help', '--help', '-h', '--version', '-v'])
74
74
  if (cliArgs.length > 0 && (CLI_COMMANDS.has(cliArgs[0]) || cliArgs[0].startsWith('--'))) {
75
75
  runCli(process.argv)
76
76
  } else if (cliArgs.length === 0 && process.stdout.isTTY) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank",
3
- "version": "0.13.0",
3
+ "version": "0.14.2",
4
4
  "description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,9 +37,27 @@
37
37
  "keystore.mjs",
38
38
  "submit.mjs",
39
39
  "connect.mjs",
40
+ "banner.svg",
40
41
  "README.md",
41
42
  "LICENSE"
42
43
  ],
44
+ "keywords": [
45
+ "mcp",
46
+ "model-context-protocol",
47
+ "ai-agents",
48
+ "claude",
49
+ "anthropic",
50
+ "llm",
51
+ "token-telemetry",
52
+ "token-usage",
53
+ "leaderboard",
54
+ "cli",
55
+ "tui",
56
+ "yield-cascade",
57
+ "sigrank",
58
+ "agent-tools",
59
+ "on-device"
60
+ ],
43
61
  "dependencies": {
44
62
  "@modelcontextprotocol/sdk": "^1.0.0",
45
63
  "@zhangferry-dev/tokendash": "^1.7.5",
package/submit.mjs CHANGED
@@ -157,6 +157,9 @@ export async function submitSignedWindow(windowKey, pillars, messages, identity,
157
157
  window: WINDOW_TYPE[windowKey] || windowKey,
158
158
  verification_tier: ack.verification_tier ?? null,
159
159
  persisted: ack.persisted ?? null,
160
+ // ranked = actually on the board (verified + written), not just "received".
161
+ // An unenrolled/revoked device gets HTTP 202 received but is NEVER ranked.
162
+ ranked: !!(res.ok && ack.verification_tier === 'verified' && ack.persisted === true),
160
163
  snapshot_hash: payload.agent.snapshot_hash,
161
164
  reason: res.ok ? null : ack.reason || ack.status || `http_${res.status}`,
162
165
  detail: ack.detail ?? null,
package/tools.mjs CHANGED
@@ -299,6 +299,24 @@ const WINDOW_TYPE = { '7d': '7d', '30d': '30d', '90d': '90d', all: 'all_time' }
299
299
  const WATCH_SUBMIT_COOLDOWN_MS = 5 * 60 * 1000
300
300
  const _lastWatchSubmitAt = new Map()
301
301
 
302
+ // ── Shared active-platform loader ───────────────────────────────────────────
303
+ // THE single data path for "show my cascade across platforms" — used by `me`,
304
+ // `watch`, and the TUI Dashboard so they can't drift apart again. Pulls each
305
+ // target platform via the tokenpull tool (enriched: pillars + cascade + card),
306
+ // keeps only platforms with real local data, and sorts claude → codex → rest.
307
+ // Pass `platforms` to scope it (e.g. ['claude'] for a fast first paint).
308
+ export async function pullActivePlatforms({ platforms } = {}, opts = {}) {
309
+ const targets = platforms && platforms.length ? platforms : ALL_PLATFORMS
310
+ const settled = await Promise.allSettled(targets.map((p) => callTool('tokenpull', { platform: p }, opts)))
311
+ const active = settled
312
+ .filter((r) => r.status === 'fulfilled' && r.value)
313
+ .map((r) => r.value)
314
+ .filter((d) => (d.windows || []).some((w) => ((w.pillars?.input ?? 0) + (w.pillars?.output ?? 0)) > 0))
315
+ const rank = (p) => (p === 'claude' ? -2 : p === 'codex' ? -1 : ALL_PLATFORMS.indexOf(p))
316
+ active.sort((a, b) => rank(a.platform) - rank(b.platform))
317
+ return active
318
+ }
319
+
302
320
  export async function callTool(name, args, opts = {}) {
303
321
  const apiBase = opts.apiBase || DEFAULT_API_BASE
304
322
  const timeoutMs = opts.fetchTimeout ?? DEFAULT_FETCH_TIMEOUT
@@ -472,7 +490,8 @@ export async function callTool(name, args, opts = {}) {
472
490
  })
473
491
  let ack
474
492
  try { ack = await res.json() } catch { ack = { status: 'error', detail: `HTTP ${res.status} (non-JSON response)` } }
475
- return { ...c, card, submission: { ...stamp, httpStatus: res.status, ...ack } }
493
+ const ranked = !!(res.ok && ack.verification_tier === 'verified' && ack.persisted === true)
494
+ return { ...c, card, ranked, submission: { ...stamp, httpStatus: res.status, ranked, ...ack } }
476
495
  }
477
496
 
478
497
  if (name === 'tokenpull') {
@@ -512,7 +531,10 @@ export async function callTool(name, args, opts = {}) {
512
531
  })
513
532
  let ack
514
533
  try { ack = await res.json() } catch { ack = { status: 'error', detail: `HTTP ${res.status} (non-JSON)` } }
515
- out.push({ window: w.window, pillars: w.pillars, cascade: c, card, submission: { ...stamp, httpStatus: res.status, ...ack } })
534
+ // ranked = actually on the board (verified + persisted), not just received — parity
535
+ // with submit_verified (submit.mjs). An unenrolled/revoked device gets 202 but is NEVER ranked.
536
+ const ranked = !!(res.ok && ack.verification_tier === 'verified' && ack.persisted === true)
537
+ out.push({ window: w.window, pillars: w.pillars, cascade: c, card, ranked, submission: { ...stamp, httpStatus: res.status, ranked, ...ack } })
516
538
  }
517
539
  return { platform: pulled.platform, codename: codename || null, generatedAt: pulled.generatedAt, windows: out }
518
540
  }
package/tui.mjs CHANGED
@@ -12,7 +12,8 @@
12
12
  * - Dashboard + Compare load once, refresh on [R].
13
13
  */
14
14
 
15
- import { callTool, DEFAULT_API_BASE } from './tools.mjs'
15
+ import { callTool, DEFAULT_API_BASE, pullActivePlatforms } from './tools.mjs'
16
+ import { ALL_PLATFORMS } from './adapters.mjs'
16
17
  import { freshVerifierPillars } from './tokenpull.mjs'
17
18
  import { isSignedIn, isCodeChar } from './connect.mjs'
18
19
  import { loadIdentity, clearIdentity } from './keystore.mjs'
@@ -231,54 +232,33 @@ function sparkline(values) {
231
232
  // still used by the `tokenpull_compare` MCP tool.)
232
233
 
233
234
  async function loadDashboardData() {
234
- const { tokenpullAny } = await import('./tokenpull.mjs')
235
- const ALL_PLATFORMS = [
236
- 'claude','codex','amp','gemini','kimi','qwen','goose','kilo',
237
- 'hermes','droid','codebuff','copilot','openclaw','pi',
238
- ]
239
235
  const boardPromise = Promise.race([
240
236
  fetch(`${DEFAULT_API_BASE}/api/v1/leaderboard?window=30d&metric=yield_`, { headers: { accept: 'application/json' } })
241
237
  .then(r => r.ok ? r.json() : null).catch(() => null),
242
238
  new Promise(r => setTimeout(() => r(null), 5000)),
243
239
  ])
244
240
 
245
- // FIX 0: progressive load — render the primary platform (claude) FAST first,
246
- // then fill the other 13 as they resolve. The caller passes an onFirst callback
247
- // that fires when claude is ready so the user sees their cascade within ~1 read,
248
- // not after a 14-platform barrier.
249
- const claudeResult = await tokenpullAny('claude').catch(() => null)
241
+ // FIX 0 + unify (0.14): progressive load via the SHARED loader (pullActivePlatforms) — paint the
242
+ // primary platform (claude) FAST, then fill the rest in fillDashboardRest. This is the SAME loader
243
+ // `me` and `watch` use, so the three views can't drift apart. (Verifier comparison still runs
244
+ // FRESH + on-demand in the Compare tab — see loadCompareData — never on this Dashboard path.)
245
+ const active = await pullActivePlatforms({ platforms: ['claude'] }).catch(() => [])
250
246
 
251
- const active = []
252
- if (claudeResult) {
253
- const all = claudeResult.windows?.find(w => w.window === 'all')
254
- if (all && (all.pillars.input ?? 0) + (all.pillars.output ?? 0) > 0) active.push(claudeResult)
255
- }
256
-
257
- // FIX A1 (2026-06-27): removed the 14× SYNCHRONOUS ccusage `verifierMap` build (measured 39,816 ms)
258
- // that blocked the very first Dashboard paint — and the render never read `verifierMap` or `tdPillars`.
259
- // (Also dropped the discarded second `tokenDashPillars()` call above.) Verifier comparison
260
- // (ccusage / tokscale / token-dashboard) now runs FRESH + on-demand in the Compare tab, scoped to
261
- // active platforms — see loadCompareData. Dashboard paints from tokenpull in ~2s.
262
247
  const boardData = await boardPromise
263
- return { active, verifierMap: {}, tdPillars: null, boardData, _loading: true, _remaining: ALL_PLATFORMS.slice(1) }
248
+ return { active, verifierMap: {}, tdPillars: null, boardData, _loading: true, _remaining: ALL_PLATFORMS.filter(p => p !== 'claude') }
264
249
  }
265
250
 
266
251
  /** FIX 0: fill the remaining platforms after the primary render. Mutates dashData.active. */
267
252
  async function fillDashboardRest(dashData) {
268
253
  if (!dashData?._remaining) return false
269
- const { tokenpullAny } = await import('./tokenpull.mjs')
270
- const rest = await Promise.allSettled(dashData._remaining.map(p => tokenpullAny(p)))
271
- for (const r of rest) {
272
- if (r.status !== 'fulfilled') continue
273
- const d = r.value
274
- const all = d.windows?.find(w => w.window === 'all')
275
- if (!all) continue
276
- if ((all.pillars.input ?? 0) + (all.pillars.output ?? 0) === 0) continue
254
+ // Unify: fill the remaining platforms through the SAME shared loader.
255
+ const rest = await pullActivePlatforms({ platforms: dashData._remaining }).catch(() => [])
256
+ for (const d of rest) {
277
257
  if (!dashData.active.find(a => a.platform === d.platform)) dashData.active.push(d)
278
258
  }
279
- // sort active by ALL_PLATFORMS order for stable display
280
- const order = ['claude','codex','amp','gemini','kimi','qwen','goose','kilo','hermes','droid','codebuff','copilot','openclaw','pi']
281
- dashData.active.sort((a, b) => order.indexOf(a.platform) - order.indexOf(b.platform))
259
+ // stable display order: claude → codex → rest (matches the shared loader)
260
+ const rank = (p) => (p === 'claude' ? -2 : p === 'codex' ? -1 : ALL_PLATFORMS.indexOf(p))
261
+ dashData.active.sort((a, b) => rank(a.platform) - rank(b.platform))
282
262
  dashData._loading = false
283
263
  dashData._remaining = null
284
264
  return true