sigrank-mcp 0.7.0 → 0.8.0

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 (3) hide show
  1. package/README.md +183 -80
  2. package/cli.mjs +274 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,93 +1,196 @@
1
+ # sigrank-mcp
2
+
3
+ **SigRank terminal client + MCP server.** Check the leaderboard, score your token cascade, and publish your rank — directly from your terminal or any MCP-compatible agent (Claude Code, Cursor, Windsurf).
4
+
5
+ Zero external dependencies. Pure Node.js. No account required to read.
6
+
7
+ → **[signalaf.com](https://signalaf.com)**
8
+
1
9
  ---
2
- type: Reference
3
- title: SigRank MCP server
4
- description: The SigRank MCP — exposes the leaderboard as tools any agent can call (rank_paste, get_leaderboard, get_operator, submit_paste, tokenpull, tokenpull_submit). tokenpull is the zero-paste on-device reader (4-window cascade, verified vs token-dashboard). Token-only, read-only, no transcript content. Cascade math mirrors lib/ingest/bridge.ts; proprietary threshold cuts stay server-side.
5
- tags: [sigrank, mcp, tokenpull, agent, ingest, reference]
6
- timestamp: 2026-06-23
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npx sigrank-mcp board
15
+ ```
16
+
17
+ No install needed. `npx` runs it directly. Or install globally:
18
+
19
+ ```bash
20
+ npm install -g sigrank-mcp
21
+ sigrank-mcp board
22
+ ```
23
+
7
24
  ---
8
25
 
9
- # SigRank MCP server
26
+ ## Commands
10
27
 
11
- Exposes SigRank as MCP tools any agent (Claude Code, Cursor, …) can call turning the
12
- leaderboard into a tool every agent can invoke (distribution moat). Token-only, no auth,
13
- no transcript content.
28
+ ### `board`Live leaderboard
14
29
 
15
- ## Tools
16
- | tool | what |
17
- |---|---|
18
- | `rank_paste(text)` | paste ccusage token counts → **Υ Yield / SNR / Leverage / Velocity / 10xDEV + class + a deterministic prose `card`**. Accepts JSON `{input,output,cacheCreate,cacheRead}` or 4 whitespace numbers in that order. |
19
- | `get_leaderboard()` | the live public board (signalaf.com) |
20
- | `get_operator(codename)` | one operator's live profile |
21
- | `submit_paste(text, codename)` | **rank AND publish** in one call: local cascade + card, then POSTs the raw paste to the board's web-paste endpoint (server re-scores authoritatively). `codename` required to publish; omit for preview-only. |
22
- | `tokenpull(platform?)` | **in-house local reader** (no ccusage/tokscale): scans local logs → the 4 windows (7d/30d/90d/all) each cascaded. **Claude** (native, recursive incl. `subagents/`, dedup `(session,message)` keep-final, verified vs token-dashboard) + **Codex** (reads `~/.codex/sessions`, estimated via `io_ratio` — Beta from the operator's Claude ratio, else Alpha 2.0; verified vs `ccusage codex`). Zero paste, on-device, token-only. |
23
- | `tokenpull_submit(codename, window?)` | **the zero-paste flow**: `tokenpull` → publish each window's canonical pillars to the board (server re-scores), tagged with `platform`. `codename` required to publish; omit for preview. |
30
+ ```bash
31
+ npx sigrank-mcp board # live, refreshes every 30s
32
+ npx sigrank-mcp board --once # print once and exit
33
+ npx sigrank-mcp board --window 7d
34
+ ```
24
35
 
25
- The cascade math (`cascade.mjs`) mirrors `sigrank-app/lib/ingest/bridge.ts` Υ = (Cr·O)/I².
26
- Open by design; the proprietary threshold cuts / weights stay server-side.
36
+ **Windows:** `7d` · `30d` (default) · `90d` · `all`
27
37
 
28
- ## Privacy
29
- - **Token-only, always.** No message content is ever read, logged, or transmitted — only token counts (`input`, `output`, `cache_creation`, `cache_read`), message IDs, and timestamps.
30
- - **Local by default.** `tokenpull` and `tokenpull_submit` read only `~/.claude/projects` (Claude) or `~/.codex` (Codex) on your device. The numbers stay on your machine unless you explicitly call `_submit` with a codename.
31
- - **Background tooling excluded.** Memory plugins, observers, and summarizers (e.g. `claude-mem`, `mem0`, `observer-sessions`) are filtered out of both Claude and Codex reads. `subagents/` are kept — they represent real operator work. The filter list is in `EXCLUDE_TOOLING` in `tokenpull.mjs` and is extensible.
32
- - **No auth required.** All board reads and the web-paste submit path are anonymous. No credentials are stored or transmitted.
33
- - **Content hash per upload.** Every `_submit` call attaches a SHA-256 hash of the pillar payload + a `ddmmyy` datestamp. No personal identifiers beyond the operator-chosen codename.
38
+ ```
39
+ SigRank Leaderboard signalaf.com 02:00:49
40
+ window: 30d · 25 operators
41
+ ············································································
42
+ # Operator Class SIGNA SNR Depth Tokens 7d Δ
43
+ ············································································
44
+ #1 TransVaultOrigin TRANSMITTER 96.4 96.9% 26.1 18.4K —
45
+ #2 OrcaVanguard TRANSMITTER 88.0 88.0% 23.0 16.0K —
46
+ #3 IronLattice TRANSMITTER 84.0 84.0% 21.6 14.8K —
47
+ #4 PrismCartographer ARCH+ 79.3 79.2% 19.2 12.4K —
48
+ #5 MeridianScribe ARCH+ 76.1 76.4% 17.8 11.2K —
49
+ ············································································
50
+ ```
51
+
52
+ ---
53
+
54
+ ### `me` — Your cascade
55
+
56
+ Score your own token usage across all four measurement windows.
57
+
58
+ ```bash
59
+ npx sigrank-mcp me # auto-detects Claude Code
60
+ npx sigrank-mcp me --platform amp # specify platform
61
+ npx sigrank-mcp me --compare # side-by-side pillar comparison
62
+ ```
63
+
64
+ **Supported platforms:** `claude` · `codex` · `amp` · `gemini` · `qwen` · `goose` · `kimi` · `droid` · `hermes` · `kilo` · `copilot` · `codebuff`
65
+
66
+ ```
67
+ ⊙ SigRank · me signalaf.com
68
+ platform: claude · source: ~/.claude/projects
69
+
70
+ window Υ Yield SNR Leverage 10xDEV Velocity Class
71
+ ─────────────────────────────────────────────────────────────────────
72
+ 7d 8 231.4 90.1% 1 823.4x 3.26 8.8x TRANSMITTER
73
+ 30d 12 847.2 92.3% 2 041.1x 3.31 9.0x TRANSMITTER
74
+ 90d 11 204.6 89.7% 1 994.8x 3.30 8.9x TRANSMITTER
75
+ all 18 436.98 90.0% 2 042.2x 3.31 9.0x TRANSMITTER
76
+ ```
77
+
78
+ > Token counts are read locally from your platform's log files. No content is ever read or transmitted — only token counts.
79
+
80
+ ---
81
+
82
+ ### `watch` — Real-time tune meter
83
+
84
+ Live-updating cascade as you work. Useful for actively optimizing a session.
85
+
86
+ ```bash
87
+ npx sigrank-mcp watch # refreshes every 30s
88
+ npx sigrank-mcp watch --window 7d
89
+ ```
90
+
91
+ ---
34
92
 
35
- ## Verified
36
- `node test.mjs` → `rank_paste` reproduces canon: **MO§ES `1251211 11296121 128196310 2555179769`
37
- → Υ 18436.98 · lev 2042.2 · TRANSMITTER.** ✅ (math is dependency-free, runs without install.)
93
+ ### `--version`
38
94
 
39
- ## Run
40
95
  ```bash
41
- npm install # installs @modelcontextprotocol/sdk
42
- node index.mjs # stdio MCP server
96
+ npx sigrank-mcp --version
97
+ # 0.7.0
43
98
  ```
44
- Add to an MCP client (e.g. Claude Code `.mcp.json`):
99
+
100
+ ---
101
+
102
+ ## MCP server — use inside Claude Code / Cursor / Windsurf
103
+
104
+ Add to your MCP config (`.mcp.json` or equivalent):
105
+
45
106
  ```json
46
- { "mcpServers": { "sigrank": { "command": "node", "args": ["/Users/dericmchenry/Desktop/SigRank/sigrank-mcp/index.mjs"] } } }
47
- ```
48
- `SIGRANK_API_BASE` overrides the board host (default `https://signalaf.com`).
49
-
50
- ## Status (MVP)
51
- - ✅ cascade math verified (`rank_paste` → canon Υ). `cascade.mjs` is the testable core.
52
- - ✅ **Runtime smoke PASS** (2026-06-19): `npm install` (0 vuln) + live MCP-client `tools/list`
53
- + `rank_paste` round-trip + `get_leaderboard`/`get_operator` HTTP 200 against signalaf.com.
54
- - ✅ **3a insight card** (`narrate.mjs`): `rank_paste` returns a deterministic prose `card`
55
- ported from `moses-sigrank/narrate.py` `_template` (model path skipped on purpose).
56
- - **3b `submit_paste`** (first write op): ranks locally then POSTs the raw paste to the
57
- existing anonymous `/api/v1/ingest-paste` (web-paste path — `source='web_paste'`, no auth).
58
- Verified via injected fetch (no live write). ⚠️ The **first live submit writes production
59
- Supabase** — fire it once yourself: `SIGRANK_API_BASE=https://signalaf.com` + a real paste.
60
- - ✅ **tokenpull** (`tokenpull.mjs`): in-house local usage reader (Claude + Codex). Recursive scan
61
- (incl. `subagents/`), dedup by `(session_id, message_id)` keep-final, 4-window cascade.
62
- **Verified against token-dashboard (nateherkai): 7d input 3.44M EXACT match** (~1208 files).
63
- Bug fixed: a 2-level readdir was dropping sub-agent transcripts input under-count.
64
- - ✅ **Hardened** (2026-06-23): div-by-zero guards in cascade, `_parseWarnings` on suspicious input,
65
- AbortController fetch timeout (10s, env-overridable), symlink-safe `_walkJsonl` with MAX_JSONL_FILES
66
- cap, `EXCLUDE_TOOLING` applied to Codex, uncaughtException/unhandledRejection handlers.
67
-
68
- ## Multi-model adapter support
69
- All adapters are token-only (no message content, no cost fields, no credentials). Numbers are
70
- refined as data accumulates — SigRank is continuously improving methods as more operator data arrives.
71
-
72
- | Platform | Path | Notes |
107
+ {
108
+ "mcpServers": {
109
+ "sigrank": {
110
+ "command": "npx",
111
+ "args": ["sigrank-mcp"]
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ Once wired, your agent can call these tools directly:
118
+
119
+ | Tool | What it does |
120
+ |---|---|
121
+ | `rank_paste(text)` | Paste token counts Υ Yield + class + insight card |
122
+ | `get_leaderboard()` | Live public leaderboard |
123
+ | `get_operator(codename)` | One operator's live profile |
124
+ | `submit_paste(text, codename)` | Score locally + publish to the board |
125
+ | `tokenpull(platform?)` | Read local logs → 4-window cascade (no paste needed) |
126
+ | `tokenpull_submit(codename, window?)` | Zero-paste publish: read → sign → post |
127
+
128
+ **Example — score a paste inside Claude:**
129
+ ```
130
+ rank_paste("1251211 11296121 128196310 2555179769")
131
+
132
+ → Υ 18436.98 · SNR 90.0% · Leverage 2042.2x · Class: TRANSMITTER
133
+ "This operator holds both axes at once: 9.0x generation AND 2,042x memory leverage..."
134
+ ```
135
+
136
+ ---
137
+
138
+ ## How the math works
139
+
140
+ SigRank ranks operators on **Υ Yield** — a single number that captures how efficiently
141
+ you use your AI platform's token budget:
142
+
143
+ ```
144
+ Υ = (cache_read × output) / input²
145
+ ```
146
+
147
+ Four pillars, one score. Higher is better. The cascade collapses into nine class tiers
148
+ from BASE to TRANSMITTER.
149
+
150
+ | Metric | Formula | What it means |
73
151
  |---|---|---|
74
- | Claude Code | ✅ `~/.claude/projects` | native, verified; dedup by `(session_id, message_id)` |
75
- | Codex | `~/.codex/sessions` | estimated via `io_ratio`; verified vs `ccusage codex` |
76
- | Amp | `~/.local/share/amp/threads` | full 4-pillar; per-message |
77
- | Kimi | ✅ `~/.kimi/sessions` | full 4-pillar; `StatusUpdate` lines only |
78
- | pi-agent | `~/.pi/agent/sessions` | full 4-pillar; per-message JSONL |
79
- | OpenClaw | ✅ `~/.openclaw` (+ `.clawdbot`, `.moltbot`, `.moldbot`) | full 4-pillar; per-message JSONL |
80
- | Droid | ✅ `~/.factory/sessions/*.settings.json` | full 4-pillar; per-session JSON; thinking→output |
81
- | Codebuff | ✅ `~/.config/manicode` | full 4-pillar; `chat-messages.json` |
82
- | Hermes | ✅ `~/.hermes/state.db` | full 4-pillar; SQLite; reasoning→output |
83
- | Kilo | ✅ `~/.local/share/kilo/kilo.db` | full 4-pillar; SQLite |
84
- | Qwen | ✅ `~/.qwen/projects` | cacheCreate=0 (`estimated`); no create field in logs; thought→output |
85
- | Goose | `~/.local/share/goose/sessions/sessions.db` | cacheCreate=cacheRead=0 (`estimated`); SQLite |
86
- | Gemini CLI | `~/.gemini/tmp` | cacheCreate=0 (`estimated`); cache extracted from input field |
87
- | GitHub Copilot CLI | `~/.copilot/otel` | OTel JSONL; requires `COPILOT_OTEL_ENABLED=true` before session |
88
- | OpenCode | ⚠️ `~/.local/share/opencode` | `dataGap`: raw token counts not persisted in log format |
89
- | Cursor | 🔜 | chat log path TBD; token usage varies by plan |
90
- | Windsurf | 🔜 | session logs at `~/.codeium/windsurf/` |
91
-
92
- `estimated=true` means `cacheCreate` is unavailable the other 3 pillars are native. The server
93
- re-scores all submitted pillars authoritatively; local preview Υ is indicative only.
152
+ | **Υ Yield** | `(Cr × O) / I²` | Overall cascade efficiency |
153
+ | **SNR** | `Cr / (I + Cc)` | Signal-to-noise how much you're pulling vs pushing |
154
+ | **Leverage** | `Cr / I` | Memory amplification how hard your cache works |
155
+ | **10xDEV** | `log₁₀(Leverage)` | Orders of magnitude above baseline |
156
+ | **Velocity** | `O / I` | Generation rate — output per unit of input |
157
+
158
+ ---
159
+
160
+ ## Class tiers
161
+
162
+ ```
163
+ TRANSMITTER Υ 10,000 The closed kinetic loop. Both axes held simultaneously.
164
+ ARCH+ Υ 5,000 High leverage + strong generation.
165
+ ARCH Υ 2,500 Architectural thinkers. Deep cache, structured output.
166
+ POWER+ Υ 1,000 Power users with efficient patterns.
167
+ POWER Υ ≥ 500 Consistent, deliberate operators.
168
+ CORE+ Υ ≥ 200 Developing efficiency. Cache awareness emerging.
169
+ CORE Υ ≥ 50 Active operators. Signal accumulating.
170
+ SIGNAL Υ ≥ 10 Early signal. Patterns not yet locked.
171
+ BASE Υ < 10 Baseline. Every operator starts here.
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Privacy
177
+
178
+ - **Token counts only.** No message content is read, logged, or transmitted — ever.
179
+ - **Local by default.** `me` and `watch` read files on your device only. Numbers stay local unless you explicitly publish.
180
+ - **No account required.** Reading the leaderboard is fully anonymous.
181
+ - **Background tooling excluded.** Memory plugins and observers are filtered out automatically.
182
+
183
+ ---
184
+
185
+ ## Links
186
+
187
+ - **Leaderboard:** [signalaf.com](https://signalaf.com)
188
+ - **Agent (publish your own data):** [sigrank-agent on PyPI](https://pypi.org/project/sigrank-agent/)
189
+ - **npm:** [npmjs.com/package/sigrank-mcp](https://www.npmjs.com/package/sigrank-mcp)
190
+ - **Source:** [github.com/SunrisesIllNeverSee/sigrank-mcp](https://github.com/SunrisesIllNeverSee/sigrank-mcp)
191
+
192
+ ---
193
+
194
+ ## License
195
+
196
+ MIT — © 2026 Deric J. McHenry / Ello Cello LLC
package/cli.mjs CHANGED
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * cli.mjs — SigRank terminal UI.
3
3
  *
4
- * Commands (no external depspure Node.js ANSI escape codes):
4
+ * Default (no command): full unified view all platforms, all windows, token pillars,
5
+ * board position, [S] submit prompt.
5
6
  *
7
+ * Commands:
8
+ * npx sigrank-mcp full unified view (default)
6
9
  * npx sigrank-mcp board live leaderboard, refreshes every 30s
7
10
  * npx sigrank-mcp board --window 7d board for a specific window
8
11
  * npx sigrank-mcp board --once print once and exit (no live refresh)
@@ -738,6 +741,272 @@ async function runWatch({ platform = 'claude', window: win = '7d', refresh = 30
738
741
  }
739
742
  }
740
743
 
744
+ // ── UNIFIED DEFAULT VIEW ──────────────────────────────────────────────────────
745
+ // npx sigrank-mcp (no args) — pulls everything at once:
746
+ // - all platforms in parallel (only shows ones with data)
747
+ // - all 4 windows per platform
748
+ // - token pillars table (transparency layer)
749
+ // - comparison sources (ccusage, tokscale, token-dashboard) if available
750
+ // - live board position
751
+ // - [S] submit [B] board [Q] quit
752
+
753
+ const ALL_PLATFORMS = [
754
+ 'claude','codex','amp','gemini','kimi','qwen','goose','kilo',
755
+ 'hermes','droid','codebuff','copilot','openclaw','pi',
756
+ ]
757
+
758
+ async function runSigRank() {
759
+ write(HIDE_CURSOR)
760
+ const w = termWidth()
761
+
762
+ // ── 1. Pull everything in parallel ────────────────────────────────────────
763
+ writeln()
764
+ writeln(` ${gold('⊙ SigRank')} ${dim('reading all sources…')}`)
765
+
766
+ const { tokenpullAny } = await import('./tokenpull.mjs')
767
+
768
+ const [platformResults, boardData, ccPillars, tdPillars, tsPillars] = await Promise.all([
769
+ // all platforms
770
+ Promise.allSettled(ALL_PLATFORMS.map(p => tokenpullAny(p))),
771
+ // live board
772
+ callTool('get_leaderboard', {}).catch(() => null),
773
+ // ccusage
774
+ Promise.resolve(ccusagePillars('claude')),
775
+ // token-dashboard
776
+ Promise.resolve(tokenDashPillars()),
777
+ // tokscale
778
+ Promise.resolve(tokscalePillars()),
779
+ ])
780
+
781
+ // filter to platforms with actual data
782
+ const active = []
783
+ for (let i = 0; i < ALL_PLATFORMS.length; i++) {
784
+ const r = platformResults[i]
785
+ if (r.status !== 'fulfilled') continue
786
+ const d = r.value
787
+ const all = d.windows?.find(w => w.window === 'all')
788
+ if (!all) continue
789
+ const total = (all.pillars.input ?? 0) + (all.pillars.output ?? 0)
790
+ if (total === 0) continue
791
+ active.push(d)
792
+ }
793
+
794
+ // clear loading line
795
+ write(CURSOR_UP(2) + ERASE_LINE + CURSOR_UP(1) + ERASE_LINE)
796
+
797
+ // ── 2. Header ─────────────────────────────────────────────────────────────
798
+ const ts = new Date().toLocaleTimeString('en-US', { hour12: false })
799
+ const header = ` ${gold('⊙ SigRank')} ${bold('Operator Dashboard')}`
800
+ const right = dim(`signalaf.com ${ts}`)
801
+ const gap = Math.max(1, w - stripAnsi(header).length - stripAnsi(right).length)
802
+ writeln()
803
+ writeln(`${header}${' '.repeat(gap)}${right}`)
804
+
805
+ const platformSummary = active.map(d => {
806
+ const all = d.windows.find(w => w.window === 'all')
807
+ return `${cyan(d.platform)} ${dim(`${d.files ?? '?'} files · ${(all?.messages ?? 0).toLocaleString()} msgs`)}`
808
+ }).join(' ')
809
+ writeln(` ${dim('Detected:')} ${platformSummary || dim('no local data found')}`)
810
+ writeln(` ${dim('─'.repeat(w - 4))}`)
811
+
812
+ // ── 3. Cascade table — all platforms × all windows ─────────────────────────
813
+ writeln()
814
+ writeln(` ${bold('Your Cascade')}`)
815
+ const CH = [
816
+ padEnd(dim('Platform'), 10),
817
+ padEnd(dim('Window'), 7),
818
+ padStart(dim('Υ Yield'), 10),
819
+ padStart(dim('SNR'), 7),
820
+ padStart(dim('Leverage'), 10),
821
+ padStart(dim('Velocity'), 9),
822
+ padStart(dim('10xDEV'), 8),
823
+ padEnd(dim('Class'), 13),
824
+ ]
825
+ writeln(` ${CH.join(' ')}`)
826
+ writeln(` ${dim('·'.repeat(w - 4))}`)
827
+
828
+ const WINS = ['7d', '30d', '90d', 'all']
829
+ for (const d of active) {
830
+ for (const winKey of WINS) {
831
+ const wdata = d.windows?.find(ww => ww.window === winKey)
832
+ if (!wdata) continue
833
+ const p = wdata.pillars
834
+ const cas = cascadeFromPillars(p)
835
+ if (!cas) continue
836
+ const isTop = cas.yield > 10000
837
+ const clsFn = CLASS_COLOR[cas.class] ?? ((s) => s)
838
+ const cols = [
839
+ padEnd(winKey === '7d' ? cyan(d.platform) : dim(d.platform), 10),
840
+ padEnd(dim(winKey), 7),
841
+ padStart(isTop ? gold(fmtYield(cas.yield)) : fmtYield(cas.yield), 10),
842
+ padStart(fmtSNR(cas.snr), 7),
843
+ padStart(cas.leverage != null ? `${fmtLev(cas.leverage)}×` : '—', 10),
844
+ padStart(cas.velocity != null ? cas.velocity.toFixed(2) + 'x' : '—', 9),
845
+ padStart(cas.dev10x != null ? cas.dev10x.toFixed(2) : '—', 8),
846
+ padEnd(clsFn(cas.class), 13),
847
+ ]
848
+ writeln(` ${cols.join(' ')}`)
849
+ }
850
+ writeln()
851
+ }
852
+
853
+ // ── 4. Token Pillars transparency table ──────────────────────────────────
854
+ writeln(` ${dim('─'.repeat(w - 4))}`)
855
+ writeln()
856
+ writeln(` ${bold('Token Pillars')} ${dim('(all-time · transparency)')}`)
857
+
858
+ // rows: each active platform + comparison sources
859
+ const PCOLS = [
860
+ padEnd(dim('Source'), 14),
861
+ padStart(dim('Input'), 10),
862
+ padStart(dim('Output'), 10),
863
+ padStart(dim('Cache Write'), 12),
864
+ padStart(dim('Cache Read'), 12),
865
+ padStart(dim('Total'), 10),
866
+ ]
867
+ writeln(` ${PCOLS.join(' ')}`)
868
+ writeln(` ${dim('·'.repeat(Math.min(w - 4, 74)))}`)
869
+
870
+ const printPillarRow = (label, colorFn, p, note = '') => {
871
+ if (!p) return
872
+ const i = p.input ?? 0
873
+ const o = p.output ?? 0
874
+ const cw = p.cacheCreate ?? 0
875
+ const cr = p.cacheRead ?? 0
876
+ const total = i + o + cw + cr
877
+ const cols = [
878
+ padEnd(colorFn(label), 14),
879
+ padStart(fmtTokens(i), 10),
880
+ padStart(fmtTokens(o), 10),
881
+ padStart(cw > 0 ? fmtTokens(cw) : dim('—'), 12),
882
+ padStart(cr > 0 ? fmtTokens(cr) : dim('—'), 12),
883
+ padStart(fmtTokens(total), 10),
884
+ ]
885
+ writeln(` ${cols.join(' ')}${note ? ' ' + dim(note) : ''}`)
886
+ }
887
+
888
+ for (const d of active) {
889
+ const all = d.windows?.find(ww => ww.window === 'all')
890
+ if (all) printPillarRow(d.platform, cyan, all.pillars)
891
+ }
892
+
893
+ // comparison sources (dim, only shown if available)
894
+ if (ccPillars?.all) printPillarRow('ccusage', (s) => paint(c.green, s), ccPillars.all, 'ccusage CLI')
895
+ if (tdPillars?.all) printPillarRow('token-dash', (s) => paint(c.magenta, s), tdPillars.all, 'token-dashboard.db')
896
+ if (tsPillars?.all) printPillarRow('tokscale', (s) => paint(c.blue, s), tsPillars.all, 'tokscale_report.json')
897
+
898
+ // ── 5. Board position ─────────────────────────────────────────────────────
899
+ writeln()
900
+ writeln(` ${dim('─'.repeat(w - 4))}`)
901
+ writeln()
902
+ writeln(` ${bold('Board')} ${dim('30d window · signalaf.com')}`)
903
+
904
+ const entries = boardData?.operators ?? boardData?.entries ?? boardData ?? []
905
+ if (Array.isArray(entries) && entries.length > 0) {
906
+ const top5 = entries.slice(0, 5)
907
+ for (const e of top5) {
908
+ const rank = e.rank === 1 ? gold(`#${e.rank}`) : dim(`#${e.rank}`)
909
+ const name = padEnd(trunc(e.codename ?? '—', 20), 20)
910
+ const cls = padEnd(colorClass(e.class_tier ?? '—'), 13)
911
+ const signa = padStart(e.signa_rate != null ? e.signa_rate.toFixed(1) : '—', 7)
912
+ writeln(` ${rank} ${name} ${cls} ${signa}`)
913
+ }
914
+ if (entries.length > 5) writeln(` ${dim(` … ${entries.length - 5} more operators on signalaf.com`)}`)
915
+ } else {
916
+ writeln(` ${dim(' board unavailable')}`)
917
+ }
918
+
919
+ // ── 6. Footer / submit prompt ────────────────────────────────────────────
920
+ writeln()
921
+ writeln(` ${dim('─'.repeat(w - 4))}`)
922
+ writeln(` ${dim('[S]')} submit to board ${dim('[B]')} open board in browser ${dim('[Q]')} quit`)
923
+ writeln()
924
+
925
+ // ── 7. Keypress handler ───────────────────────────────────────────────────
926
+ if (process.stdin.isTTY) {
927
+ process.stdin.setRawMode(true)
928
+ process.stdin.resume()
929
+ process.stdin.setEncoding('utf8')
930
+
931
+ await new Promise((resolve) => {
932
+ process.stdin.on('data', async (key) => {
933
+ const k = key.toLowerCase()
934
+ if (k === 'q' || key === '\u0003') { // q or ctrl+c
935
+ resolve()
936
+ } else if (k === 'b') {
937
+ const { execSync: es } = await import('child_process')
938
+ try { es('open https://signalaf.com', { stdio: 'ignore' }) } catch { }
939
+ resolve()
940
+ } else if (k === 's') {
941
+ process.stdin.setRawMode(false)
942
+ write(SHOW_CURSOR)
943
+ process.stdout.write('\n Codename: ')
944
+ let codename = ''
945
+ process.stdin.on('data', async function onData(chunk) {
946
+ if (chunk === '\r' || chunk === '\n') {
947
+ process.stdin.removeListener('data', onData)
948
+ codename = codename.trim()
949
+ if (!codename) { writeln(red(' ✗ codename required')); resolve(); return }
950
+ writeln()
951
+ writeln(` ${dim('Submitting all windows for')} ${cyan(codename)}${dim('…')}`)
952
+ write(HIDE_CURSOR)
953
+ try {
954
+ for (const d of active) {
955
+ const apiBase = DEFAULT_API_BASE
956
+ const WINDOW_TYPE = { '7d': '7d', '30d': '30d', '90d': '90d', 'all': 'all_time' }
957
+ for (const ww of (d.windows ?? [])) {
958
+ const rawPaste = `${ww.pillars.input} ${ww.pillars.output} ${ww.pillars.cacheCreate} ${ww.pillars.cacheRead}`
959
+ const windowType = WINDOW_TYPE[ww.window] || ww.window
960
+ const now = new Date()
961
+ const ddmmyy = `${String(now.getDate()).padStart(2,'0')}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getFullYear()).slice(-2)}`
962
+ const content_hash = `sha256:${codename}|${windowType}|${rawPaste}|${ddmmyy}`
963
+ const res = await fetch(`${apiBase}/api/v1/ingest-paste`, {
964
+ method: 'POST',
965
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
966
+ body: JSON.stringify({
967
+ codename,
968
+ raw_paste: rawPaste,
969
+ window_type: windowType,
970
+ telemetry: { platform: { primary: d.platform } },
971
+ content_hash,
972
+ submitted_ddmmyy: ddmmyy,
973
+ submitted_at: now.toISOString(),
974
+ }),
975
+ }).catch(() => null)
976
+ const ok = res?.ok ?? false
977
+ const status = ok ? green('✓') : red('✗')
978
+ writeln(` ${status} ${d.platform} ${ww.window}${!ok ? dim(` HTTP ${res?.status ?? 'err'}`) : ''}`)
979
+ }
980
+ }
981
+ writeln()
982
+ writeln(` ${green('✓')} Submitted. Visit ${cyan(`signalaf.com/user/${codename}`)}`)
983
+ } catch (e) {
984
+ writeln(red(` ✗ ${e.message}`))
985
+ }
986
+ resolve()
987
+ } else if (chunk === '\u007f') { // backspace
988
+ if (codename.length > 0) {
989
+ codename = codename.slice(0, -1)
990
+ process.stdout.write('\b \b')
991
+ }
992
+ } else {
993
+ codename += chunk
994
+ process.stdout.write(chunk)
995
+ }
996
+ })
997
+ process.stdin.resume()
998
+ }
999
+ })
1000
+ })
1001
+ }
1002
+
1003
+ write(SHOW_CURSOR)
1004
+ if (process.stdin.isTTY) {
1005
+ process.stdin.setRawMode(false)
1006
+ process.stdin.pause()
1007
+ }
1008
+ }
1009
+
741
1010
  // ── HELP ─────────────────────────────────────────────────────────────────────
742
1011
 
743
1012
  function showHelp() {
@@ -805,7 +1074,10 @@ export async function runCli(argv) {
805
1074
  } else if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
806
1075
  showHelp()
807
1076
  } else if (cmd === '--version' || cmd === '-v') {
808
- writeln('0.7.0')
1077
+ writeln('0.8.0')
1078
+ } else if (!cmd || cmd === 'start' || cmd === 'run') {
1079
+ // default: full unified view
1080
+ await runSigRank()
809
1081
  } else {
810
1082
  // unknown command: show help
811
1083
  showHelp()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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",