sigrank-mcp 0.6.5 → 0.6.7

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 (2) hide show
  1. package/cli.mjs +181 -106
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  import { callTool, DEFAULT_API_BASE } from './tools.mjs'
24
24
  import { execSync } from 'child_process'
25
- import { existsSync } from 'fs'
25
+ import { existsSync, readFileSync, writeFileSync } from 'fs'
26
26
  import os from 'os'
27
27
  import path from 'path'
28
28
 
@@ -431,35 +431,73 @@ function ccusagePillars(platform = 'claude') {
431
431
  }
432
432
  }
433
433
 
434
+ function tokscalePillars() {
435
+ // Read tokscale_report.json — claude client only, all-time only (no timestamps in export)
436
+ const reportPath = path.join(os.homedir(), 'tokscale_report.json')
437
+ if (!existsSync(reportPath)) return null
438
+ try {
439
+ const data = JSON.parse(readFileSync(reportPath, 'utf8'))
440
+ const entries = data.entries ?? []
441
+ const claude = entries.filter(e =>
442
+ e.client === 'claude' &&
443
+ e.model !== '<synthetic>' && e.model !== 'unknown' &&
444
+ e.provider !== 'unknown'
445
+ )
446
+ const p = claude.reduce((acc, e) => ({
447
+ input: acc.input + (e.input ?? 0),
448
+ output: acc.output + (e.output ?? 0),
449
+ cacheCreate: acc.cacheCreate + (e.cacheWrite ?? 0),
450
+ cacheRead: acc.cacheRead + (e.cacheRead ?? 0),
451
+ }), { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 })
452
+ // tokscale export has no timestamps → only all-time available
453
+ return { all: p }
454
+ } catch { return null }
455
+ }
456
+
457
+ function appPillars() {
458
+ // App numbers from screenshots — all-time, per model (no cache fields)
459
+ // Hard-coded from 2026-06-23 screenshot capture (update when re-screenshotted)
460
+ return {
461
+ all: {
462
+ input: 6_378_000, // sum of all models: 5.6M + 102.1K + 92.9K + 130.3K + 418.9K + 33.5K
463
+ output: 38_682_400, // sum: 19.6M + 6.5M + 5.4M + 6.6M + 292.4K + 290.4K
464
+ cacheCreate: null, // not shown in App UI
465
+ cacheRead: null, // not shown in App UI
466
+ },
467
+ _note: 'App UI — all-time, per-model sum from screenshots 2026-06-23. No cache fields. Update when re-screenshotted.',
468
+ _perModel: [
469
+ { model: 'claude-opus-4-8', input: 5_600_000, output: 19_600_000 },
470
+ { model: 'claude-sonnet-4-5',input: 102_100, output: 6_500_000 },
471
+ { model: 'claude-sonnet-4-6',input: 92_900, output: 5_400_000 },
472
+ { model: 'claude-opus-4-7', input: 130_300, output: 6_600_000 },
473
+ { model: 'claude-fable-5', input: 418_900, output: 292_400 },
474
+ { model: 'claude-haiku-4-5', input: 33_500, output: 290_400 },
475
+ ],
476
+ }
477
+ }
478
+
434
479
  function tokenDashPillars() {
435
- // query token-dashboard SQLite directly
436
480
  const dbPath = path.join(os.homedir(), '.claude', 'token-dashboard.db')
437
481
  if (!existsSync(dbPath)) return null
438
482
  try {
439
- const claudeFilter = `(model LIKE '%claude%' OR model LIKE '%fable%' OR model LIKE '%sonnet%' OR model LIKE '%opus%' OR model LIKE '%haiku%')`
440
- const now = new Date()
441
- const result = {}
442
- for (const [win, days] of [['7d', 7], ['30d', 30], ['90d', 90]]) {
443
- const since = new Date(now - days * 86400000).toISOString()
444
- const cmd = `python3 -c "
445
- import sqlite3, json
446
- db = sqlite3.connect('${dbPath}')
447
- r = db.execute('''SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens+cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages WHERE timestamp>=? AND ${claudeFilter}''',(('${since}',))).fetchone()
448
- print(json.dumps({'input':r[0] or 0,'output':r[1] or 0,'cacheCreate':r[2] or 0,'cacheRead':r[3] or 0}))
449
- "`
450
- const raw = execSync(cmd, { timeout: 10000, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
451
- result[win] = JSON.parse(raw)
452
- }
453
- // all-time
454
- const cmd = `python3 -c "
455
- import sqlite3, json
456
- db = sqlite3.connect('${dbPath}')
457
- r = db.execute('SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens+cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages WHERE ${claudeFilter}').fetchone()
458
- print(json.dumps({'input':r[0] or 0,'output':r[1] or 0,'cacheCreate':r[2] or 0,'cacheRead':r[3] or 0}))
459
- "`
460
- const raw = execSync(cmd, { timeout: 10000, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
461
- result['all'] = JSON.parse(raw)
462
- return result
483
+ const tmpScript = path.join(os.tmpdir(), 'sigrank_td_query.py')
484
+ writeFileSync(tmpScript, `
485
+ import sqlite3, json, sys
486
+ from datetime import datetime, timezone, timedelta
487
+ db = sqlite3.connect(sys.argv[1])
488
+ cf = "(model LIKE '%claude%' OR model LIKE '%fable%' OR model LIKE '%sonnet%' OR model LIKE '%opus%' OR model LIKE '%haiku%')"
489
+ out = {}
490
+ for win, days in [('7d',7),('30d',30),('90d',90)]:
491
+ since = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
492
+ r = db.execute(f"SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens+cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages WHERE timestamp>=? AND {cf}",(since,)).fetchone()
493
+ out[win] = {'input':r[0] or 0,'output':r[1] or 0,'cacheCreate':r[2] or 0,'cacheRead':r[3] or 0}
494
+ r = db.execute(f"SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens+cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages WHERE {cf}").fetchone()
495
+ out['all'] = {'input':r[0] or 0,'output':r[1] or 0,'cacheCreate':r[2] or 0,'cacheRead':r[3] or 0}
496
+ print(json.dumps(out))
497
+ `)
498
+ const raw = execSync(`python3 "${tmpScript}" "${dbPath}"`,
499
+ { timeout: 15000, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
500
+ return JSON.parse(raw)
463
501
  } catch {
464
502
  return null
465
503
  }
@@ -475,19 +513,44 @@ function fmtDelta(a, b) {
475
513
  return d > 0 ? green(label) : red(label)
476
514
  }
477
515
 
516
+ // Compute cascade metrics from raw pillars (mirrors bridge.ts computeCascadeMetrics)
517
+ function cascadeFromPillars(p) {
518
+ if (!p) return null
519
+ const i = p.input ?? 0
520
+ const o = p.output ?? 0
521
+ const cw = p.cacheCreate ?? 0
522
+ const cr = p.cacheRead ?? 0
523
+ if (i === 0 && o === 0) return null
524
+ const safeI = Math.max(i, 1)
525
+ const total = i + o + cw + cr
526
+ const velocity = o / safeI
527
+ const leverage = cr / safeI
528
+ const yield_ = leverage * velocity
529
+ const snr = (i + o) > 0 ? o / (i + o) : 0
530
+ // dev10x = log10(T × C × R) — only when all four pillars present
531
+ let dev10x = null
532
+ if (cw > 0 && o > 0 && i > 0 && cr > 0) {
533
+ const T = o / i, C = cw / o, R = cr / cw
534
+ dev10x = Math.log10(T * C * R)
535
+ }
536
+ // efficiency = ((cr+cw+o)/i) / 4.0
537
+ const efficiency = ((cr + cw + o) / safeI) / 4.0
538
+ const cls = yield_ > 500 ? 'TRANSMITTER' : yield_ > 400 ? 'ARCH+' : yield_ > 300 ? 'ARCH' : yield_ > 150 ? 'POWER' : 'BASE'
539
+ return { yield: yield_, velocity, leverage, snr, dev10x, efficiency, class: cls, total }
540
+ }
541
+
478
542
  async function runCompare({ platform = 'claude' } = {}) {
479
543
  write(HIDE_CURSOR)
480
- writeln(` ${dim('reading ccusage…')}`)
481
- const ccPillars = ccusagePillars(platform)
482
- write(CURSOR_UP(1) + ERASE_LINE)
483
-
484
- writeln(` ${dim('reading tokenpull…')}`)
485
- let tpData
486
- try { tpData = await callTool('tokenpull', { platform }) } catch { tpData = null }
487
- write(CURSOR_UP(1) + ERASE_LINE)
488
544
 
489
- writeln(` ${dim('reading token-dashboard…')}`)
490
- const tdPillars = tokenDashPillars()
545
+ // Pull all five sources in parallel
546
+ writeln(` ${dim('reading all 5 sources…')}`)
547
+ const [ccPillars, tpData, tdPillars, tsPillars, apPillars] = await Promise.all([
548
+ Promise.resolve(ccusagePillars(platform)),
549
+ callTool('tokenpull', { platform }).catch(() => null),
550
+ Promise.resolve(tokenDashPillars()),
551
+ Promise.resolve(tokscalePillars()),
552
+ Promise.resolve(appPillars()),
553
+ ])
491
554
  write(CURSOR_UP(1) + ERASE_LINE)
492
555
 
493
556
  const w = termWidth()
@@ -500,103 +563,115 @@ async function runCompare({ platform = 'claude' } = {}) {
500
563
  tpPillars[win.window] = win.pillars
501
564
  }
502
565
 
566
+ // sources: name, color, pillars-by-window, note
567
+ const SOURCES = [
568
+ { name: 'tokenpull', color: cyan, pillars: tpPillars, note: 'JSONL deduped by msg id · canon source' },
569
+ { name: 'ccusage', color: (s) => paint(c.green, s), pillars: ccPillars ?? {}, note: 'ccusage claude subcommand · monthly only' },
570
+ { name: 'token-dash', color: (s) => paint(c.magenta,s), pillars: tdPillars ?? {}, note: 'SQLite — double-counts sessions · use with caution' },
571
+ { name: 'tokscale', color: (s) => paint(c.blue, s), pillars: tsPillars ?? {}, note: 'all-time only · partial export (~5% of opus-4-8)' },
572
+ { name: 'App', color: gold, pillars: apPillars ?? {}, note: 'screenshots 2026-06-23 · no cache fields · update manually' },
573
+ ]
574
+
503
575
  writeln()
504
- writeln(` ${gold('⊙ SigRank')} ${bold('Source Comparison')} ${dim(`platform: ${platform} · claude tokens only`)}`)
576
+ writeln(` ${gold('⊙ SigRank')} ${bold('5-Source Comparison')} ${dim(`platform: ${platform} · claude only`)}`)
505
577
  writeln(` ${dim('─'.repeat(w - 4))}`)
506
578
 
579
+ // ── PILLARS TABLE ──────────────────────────────────────────────────────────
507
580
  const PILLARS = [
508
581
  { key: 'input', label: 'Input' },
509
582
  { key: 'output', label: 'Output' },
510
- { key: 'cacheCreate', label: 'Cache Create' },
583
+ { key: 'cacheCreate', label: 'Cache Write' },
511
584
  { key: 'cacheRead', label: 'Cache Read' },
512
585
  ]
513
586
 
514
587
  for (const { key, label } of PILLARS) {
515
588
  writeln()
516
589
  writeln(` ${bold(label)}`)
517
-
518
- // header row
519
- const hcols = [
520
- padEnd(dim('Source'), 16),
521
- ...WINS.map(win => padStart(dim(WIN_LABEL[win]), 14))
522
- ]
590
+ const hcols = [padEnd(dim('Source'), 14), ...WINS.map(win => padStart(dim(WIN_LABEL[win]), 13))]
523
591
  writeln(` ${hcols.join(' ')}`)
524
- writeln(` ${dim('·'.repeat(Math.min(w - 4, 16 + WINS.length * 16)))}`)
525
-
526
- // ccusage row
527
- if (ccPillars) {
528
- const cols = [padEnd(cyan('ccusage'), 16), ...WINS.map(win => padStart(fmtTokens(ccPillars[win]?.[key] ?? 0), 14))]
529
- writeln(` ${cols.join(' ')}`)
530
- } else {
531
- writeln(` ${padEnd(dim('ccusage'), 16)} ${dim('not found')}`)
532
- }
533
-
534
- // tokenpull row
535
- if (tpData) {
536
- const cols = [padEnd(cyan('tokenpull'), 16), ...WINS.map(win => padStart(fmtTokens(tpPillars[win]?.[key] ?? 0), 14))]
537
- writeln(` ${cols.join(' ')}`)
538
- } else {
539
- writeln(` ${padEnd(dim('tokenpull'), 16)} ${dim('not found')}`)
540
- }
541
-
542
- // token-dashboard row
543
- if (tdPillars) {
544
- const cols = [padEnd(cyan('token-dash'), 16), ...WINS.map(win => padStart(fmtTokens(tdPillars[win]?.[key] ?? 0), 14))]
545
- writeln(` ${cols.join(' ')}`)
546
- } else {
547
- writeln(` ${padEnd(dim('token-dash'), 16)} ${dim('not found — run: python3 ~/token-dashboard/cli.py scan')}`)
548
- }
549
-
550
- // delta row: tokenpull vs token-dash (the two most comparable)
551
- if (tpData && tdPillars) {
552
- const cols = [
553
- padEnd(dim('Δ tp→td'), 16),
554
- ...WINS.map(win => {
555
- const a = tpPillars[win]?.[key] ?? 0
556
- const b = tdPillars[win]?.[key] ?? 0
557
- return padStart(fmtDelta(a, b), 14)
558
- })
559
- ]
560
- writeln(` ${cols.join(' ')}`)
592
+ writeln(` ${dim('·'.repeat(Math.min(w - 4, 14 + WINS.length * 15)))}`)
593
+
594
+ for (const src of SOURCES) {
595
+ const vals = WINS.map(win => {
596
+ const p = src.pillars[win]
597
+ const v = p?.[key]
598
+ if (v == null) return padStart(dim('—'), 13)
599
+ return padStart(fmtTokens(v), 13)
600
+ })
601
+ writeln(` ${padEnd(src.color(src.name), 14)} ${vals.join(' ')}`)
561
602
  }
562
603
  }
563
604
 
564
- // SigRank cascade section
605
+ // ── SIGNATURE TABLE ────────────────────────────────────────────────────────
565
606
  writeln()
566
607
  writeln(` ${dim('─'.repeat(w - 4))}`)
567
- writeln(` ${bold('SigRank Cascade')} ${dim('(tokenpull cascade math)')}`)
608
+ writeln(` ${bold('Cascade Signature')} ${dim('per source · all windows where data available')}`)
568
609
  writeln()
569
610
 
570
- const CASCADE_ROWS = [
571
- { key: 'yield', label: 'Υ Yield', fmt: v => fmtYield(v) },
572
- { key: 'snr', label: 'SNR', fmt: v => fmtSNR(v) },
573
- { key: 'leverage', label: 'Leverage', fmt: v => `${fmtLev(v)}×` },
574
- { key: 'velocity', label: 'Velocity', fmt: v => v.toFixed(2) },
575
- { key: 'dev10x', label: '10xDEV', fmt: v => v.toFixed(2) },
576
- { key: 'class', label: 'Class', fmt: v => colorClass(v) },
611
+ const SIG_METRICS = [
612
+ { key: 'yield', label: 'Υ Yield', fmt: v => fmtYield(v), w: 9 },
613
+ { key: 'velocity', label: 'Vel', fmt: v => v.toFixed(2), w: 6 },
614
+ { key: 'leverage', label: 'Lev', fmt: v => `${fmtLev(v)}×`, w: 7 },
615
+ { key: 'snr', label: 'SNR', fmt: v => fmtSNR(v), w: 6 },
616
+ { key: 'dev10x', label: '10x', fmt: v => v.toFixed(2), w: 5 },
617
+ { key: 'efficiency', label: 'Eff', fmt: v => v.toFixed(1), w: 6 },
618
+ { key: 'class', label: 'Class', fmt: v => colorClass(v), w: 12 },
577
619
  ]
578
620
 
579
- const hcols = [padEnd(dim('Metric'), 12), ...WINS.map(win => padStart(dim(WIN_LABEL[win]), 12))]
580
- writeln(` ${hcols.join(' ')}`)
581
- writeln(` ${dim('·'.repeat(Math.min(w - 4, 12 + WINS.length * 14)))}`)
582
-
583
- for (const { key, label, fmt } of CASCADE_ROWS) {
584
- const cols = [
585
- padEnd(dim(label), 12),
586
- ...WINS.map(win => {
587
- const cas = tpPillars[win] ? (tpData?.windows?.find(w => w.window === win)?.cascade) : null
588
- const v = cas?.[key]
589
- return padStart(v != null ? fmt(v) : dim('—'), 12)
621
+ // header
622
+ const sigHdr = [
623
+ padEnd(dim('Source'), 14),
624
+ padEnd(dim('Window'), 8),
625
+ ...SIG_METRICS.map(m => padStart(dim(m.label), m.w)),
626
+ ]
627
+ writeln(` ${sigHdr.join(' ')}`)
628
+ writeln(` ${dim('·'.repeat(Math.min(w - 4, 80)))}`)
629
+
630
+ for (const src of SOURCES) {
631
+ const availWins = WINS.filter(win => src.pillars[win] != null)
632
+ if (availWins.length === 0) {
633
+ writeln(` ${padEnd(src.color(src.name), 14)} ${dim('no data')}`)
634
+ continue
635
+ }
636
+ let first = true
637
+ for (const win of availWins) {
638
+ const p = src.pillars[win]
639
+ // For tokenpull, use the pre-computed cascade from the tool if available
640
+ let cas
641
+ if (src.name === 'tokenpull') {
642
+ const tpWin = tpData?.windows?.find(ww => ww.window === win)
643
+ cas = tpWin?.cascade ? {
644
+ yield: tpWin.cascade.yield,
645
+ velocity: tpWin.cascade.velocity,
646
+ leverage: tpWin.cascade.leverage,
647
+ snr: tpWin.cascade.snr,
648
+ dev10x: tpWin.cascade.dev10x,
649
+ efficiency: null,
650
+ class: tpWin.cascade.class,
651
+ } : cascadeFromPillars(p)
652
+ } else {
653
+ cas = cascadeFromPillars(p)
654
+ }
655
+
656
+ const srcLabel = first ? src.color(src.name) : ' '.repeat(stripAnsi(src.name).length)
657
+ const winLabel = win === 'all' ? bold('all-time') : win
658
+ const sigCols = SIG_METRICS.map(m => {
659
+ const v = cas?.[m.key]
660
+ return padStart(v != null ? m.fmt(v) : dim('—'), m.w)
590
661
  })
591
- ]
592
- writeln(` ${cols.join(' ')}`)
662
+ writeln(` ${padEnd(srcLabel, 14)} ${padEnd(winLabel, 8)} ${sigCols.join(' ')}`)
663
+ first = false
664
+ }
593
665
  }
594
666
 
595
- // estimated rank hint
667
+ // ── NOTES ─────────────────────────────────────────────────────────────────
596
668
  writeln()
597
669
  writeln(` ${dim('─'.repeat(w - 4))}`)
598
- writeln(` ${dim('note: token-dash has longer history (SQLite); tokenpull reads live JSONL only')}`)
599
- writeln(` ${dim('note: ccusage = primary pillar source (all agents); tokenpull = claude-only cascade input')}`)
670
+ for (const src of SOURCES) {
671
+ writeln(` ${src.color(src.name.padEnd(12))} ${dim(src.note)}`)
672
+ }
673
+ writeln(` ${dim('Eff = ((cacheRead+cacheWrite+output)/input)/4.0 vs AA baseline')}`)
674
+ writeln(` ${dim('App has no cache fields → Υ/Lev/Eff/10x unavailable from App source')}`)
600
675
  writeln()
601
676
  write(SHOW_CURSOR)
602
677
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank-mcp",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
5
5
  "type": "module",
6
6
  "bin": {