sigrank 0.18.5 → 1.0.1
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/CHANGELOG.md +158 -0
- package/MCP_SERVER_DESCRIPTION.md +172 -0
- package/README.md +94 -41
- package/adapters/index.mjs +1100 -0
- package/adapters/tokenpull.mjs +568 -0
- package/adapters.mjs +1 -857
- package/analytics/cascade.mjs +338 -0
- package/cascade.mjs +1 -274
- package/cli.mjs +1 -1853
- package/connect.mjs +1 -13
- package/identity/connect.mjs +13 -0
- package/identity/keystore.mjs +231 -0
- package/identity/sign.mjs +106 -0
- package/index.mjs +47 -162
- package/keystore.mjs +1 -242
- package/lib/constants.mjs +137 -0
- package/lib/pkg-version.mjs +59 -0
- package/manifest.json +46 -0
- package/narrate.mjs +1 -77
- package/package.json +41 -16
- package/preflight.mjs +1 -191
- package/presentation/cli.mjs +1926 -0
- package/presentation/narrate.mjs +219 -0
- package/presentation/tui-themes.mjs +189 -0
- package/presentation/tui.mjs +3316 -0
- package/prompts/check-my-efficiency.md +1 -0
- package/prompts/compare-with-leader.md +1 -0
- package/prompts/simulate-improvement.md +1 -0
- package/proxy.mjs +492 -0
- package/resources/class-tiers.md +81 -0
- package/resources/data-policy.md +31 -0
- package/resources/install-guide.md +59 -0
- package/resources/privacy-model.md +44 -0
- package/resources/scoring-formula.md +24 -0
- package/review.mjs +263 -0
- package/sign.mjs +1 -106
- package/submit/index.mjs +297 -0
- package/submit/preflight.mjs +191 -0
- package/submit.mjs +1 -303
- package/tokenpull.mjs +1 -576
- package/tokscale_analytics.mjs +864 -0
- package/tools/_framing.mjs +217 -0
- package/tools/_helpers.mjs +253 -0
- package/tools/_peer-matching.mjs +361 -0
- package/tools/_report.mjs +262 -0
- package/tools/_schemas.mjs +492 -0
- package/tools/_verifiers.mjs +148 -0
- package/tools/compare-operators.mjs +80 -0
- package/tools/compare-self.mjs +124 -0
- package/tools/describe-power-user.mjs +78 -0
- package/tools/diagnose-cascade.mjs +192 -0
- package/tools/discover-peers.mjs +198 -0
- package/tools/enroll.mjs +119 -0
- package/tools/get-best-operator.mjs +59 -0
- package/tools/get-leaderboard.mjs +27 -0
- package/tools/get-operator.mjs +33 -0
- package/tools/index.mjs +229 -0
- package/tools/optimize-efficiency.mjs +170 -0
- package/tools/rank-paste.mjs +51 -0
- package/tools/rank-windows.mjs +120 -0
- package/tools/self-improve.mjs +423 -0
- package/tools/simulate-change.mjs +207 -0
- package/tools/submit-paste.mjs +121 -0
- package/tools/submit-verified.mjs +153 -0
- package/tools/suggest-improvements.mjs +168 -0
- package/tools/tokenpull-compare.mjs +123 -0
- package/tools/tokenpull-submit.mjs +235 -0
- package/tools/tokenpull.mjs +113 -0
- package/tools/tokscale-breakdown.mjs +119 -0
- package/tools/tokscale-competitive-intel.mjs +45 -0
- package/tools/tokscale-cost-analysis.mjs +31 -0
- package/tools/tokscale-developer-profile.mjs +36 -0
- package/tools/tokscale-device-profile.mjs +35 -0
- package/tools/tokscale-market-share.mjs +38 -0
- package/tools/tokscale-mcp-usage.mjs +33 -0
- package/tools/tokscale-model-trends.mjs +32 -0
- package/tools/watch-tokenpull.mjs +134 -0
- package/tools.mjs +1 -3897
- package/tui.mjs +4 -2650
- package/banner.svg +0 -7
|
@@ -0,0 +1,1926 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli.mjs — SigRank terminal UI.
|
|
3
|
+
*
|
|
4
|
+
* Default (no command): full unified view — all platforms, all windows, token pillars,
|
|
5
|
+
* board position, [S] submit prompt.
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* npx sigrank full unified view (default)
|
|
9
|
+
* npx sigrank board live leaderboard, refreshes every 30s
|
|
10
|
+
* npx sigrank board --window 7d board for a specific window
|
|
11
|
+
* npx sigrank board --once print once and exit (no live refresh)
|
|
12
|
+
* npx sigrank compare raw pillar comparison: ccusage vs tokenpull vs token-dashboard
|
|
13
|
+
* npx sigrank watch RT tune meter — ALL active platforms × all windows
|
|
14
|
+
* npx sigrank watch --platform X watch one platform only (optional filter)
|
|
15
|
+
* npx sigrank watch --window 7d watch one window only (optional filter)
|
|
16
|
+
* npx sigrank proxy opt-in Anthropic/OpenAI usage proxy
|
|
17
|
+
* npx sigrank proxy --port 9000 proxy on a custom loopback port
|
|
18
|
+
*
|
|
19
|
+
* Color palette mirrors the SigRank web dark theme:
|
|
20
|
+
* gold = rank #1 headline
|
|
21
|
+
* cyan = active metrics / your row highlight
|
|
22
|
+
* dim = secondary data, separators
|
|
23
|
+
* red = negative movement / delta
|
|
24
|
+
* green = positive movement
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { callTool, DEFAULT_API_BASE, pullActivePlatforms } from "../tools.mjs";
|
|
28
|
+
import { cascade, classify, CLASS_TIERS, UNCLASSED, tierOf } from "../cascade.mjs";
|
|
29
|
+
import { ALL_PLATFORMS } from "../adapters.mjs";
|
|
30
|
+
import { ensureIdentity, keystorePath } from "../keystore.mjs";
|
|
31
|
+
import { submitSignedWindow } from "../submit.mjs";
|
|
32
|
+
import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
|
|
33
|
+
import { execFile } from "child_process";
|
|
34
|
+
import { existsSync, readFileSync } from "fs";
|
|
35
|
+
import os from "os";
|
|
36
|
+
import path from "path";
|
|
37
|
+
import { fileURLToPath } from "url";
|
|
38
|
+
|
|
39
|
+
// Resolve local node_modules/.bin for bundled deps (ccusage, tokscale)
|
|
40
|
+
const _pkgRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const _localBin = path.join(_pkgRoot, "node_modules", ".bin");
|
|
42
|
+
const _envPath = `${_localBin}${process.env.PATH ? ":" + process.env.PATH : ""}`;
|
|
43
|
+
|
|
44
|
+
// ASYNC FIX (2026-06-27): execFile wrapped in a Promise — replaces execSync for
|
|
45
|
+
// defense-in-depth (shell injection prevention). execFile passes args as an
|
|
46
|
+
// array, so no shell parsing occurs — even if platform contained special chars,
|
|
47
|
+
// they'd be treated as literal arguments, not shell commands.
|
|
48
|
+
// BIN FIX (2026-06-27): PATH includes local node_modules/.bin so bundled deps
|
|
49
|
+
// are found even when sigrank isn't globally installed.
|
|
50
|
+
function execFileAsync(cmd, args, timeoutMs) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
execFile(
|
|
53
|
+
cmd,
|
|
54
|
+
args,
|
|
55
|
+
{
|
|
56
|
+
timeout: timeoutMs,
|
|
57
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
58
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
59
|
+
env: { ...process.env, PATH: _envPath },
|
|
60
|
+
},
|
|
61
|
+
(err, stdout) => {
|
|
62
|
+
if (err) reject(err);
|
|
63
|
+
else resolve(stdout.toString());
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── ANSI helpers (no chalk dep) ────────────────────────────────────────────
|
|
70
|
+
const ESC = "\x1b[";
|
|
71
|
+
const c = {
|
|
72
|
+
reset: `${ESC}0m`,
|
|
73
|
+
bold: `${ESC}1m`,
|
|
74
|
+
dim: `${ESC}2m`,
|
|
75
|
+
gold: `${ESC}33m`,
|
|
76
|
+
boldGold: `${ESC}1;33m`,
|
|
77
|
+
cyan: `${ESC}36m`,
|
|
78
|
+
boldCyan: `${ESC}1;36m`,
|
|
79
|
+
green: `${ESC}32m`,
|
|
80
|
+
red: `${ESC}31m`,
|
|
81
|
+
white: `${ESC}97m`,
|
|
82
|
+
boldWhite: `${ESC}1;97m`,
|
|
83
|
+
magenta: `${ESC}35m`,
|
|
84
|
+
blue: `${ESC}34m`,
|
|
85
|
+
};
|
|
86
|
+
const paint = (color, str) => `${color}${str}${c.reset}`;
|
|
87
|
+
const bold = (str) => paint(c.bold, str);
|
|
88
|
+
const dim = (str) => paint(c.dim, str);
|
|
89
|
+
const gold = (str) => paint(c.boldGold, str);
|
|
90
|
+
const cyan = (str) => paint(c.boldCyan, str);
|
|
91
|
+
const green = (str) => paint(c.green, str);
|
|
92
|
+
const red = (str) => paint(c.red, str);
|
|
93
|
+
|
|
94
|
+
// ── Class tier → color ─────────────────────────────────────────────────────
|
|
95
|
+
// Color is keyed on the BASE TIER (8 entries in CLASS_TIERS). classify() returns
|
|
96
|
+
// a full sub-stage string (e.g. "REFINER II") — tierOf() extracts the base tier
|
|
97
|
+
// for color lookup. TRANSMITTER is not a class. UNCLASSED for no-data.
|
|
98
|
+
const CLASS_COLOR = {
|
|
99
|
+
"ARCH+": (s) => paint(c.boldCyan, s),
|
|
100
|
+
ARCH: (s) => paint(c.cyan, s),
|
|
101
|
+
POWER: (s) => paint(c.boldWhite, s),
|
|
102
|
+
BASE: (s) => paint(c.white, s),
|
|
103
|
+
SEEKER: (s) => paint(c.magenta, s),
|
|
104
|
+
REFINER: (s) => paint(c.blue, s),
|
|
105
|
+
BEARER: (s) => paint(c.dim, s),
|
|
106
|
+
IGNITER: (s) => paint(c.dim, s),
|
|
107
|
+
[UNCLASSED]: (s) => paint(c.dim, s),
|
|
108
|
+
};
|
|
109
|
+
const colorClass = (cls) => (CLASS_COLOR[tierOf(cls)] ?? CLASS_COLOR[UNCLASSED] ?? ((s) => s))(cls);
|
|
110
|
+
// Guard: every canonical tier must have a color here.
|
|
111
|
+
for (const t of CLASS_TIERS) {
|
|
112
|
+
if (!CLASS_COLOR[t]) {
|
|
113
|
+
// eslint-disable-next-line no-console
|
|
114
|
+
console.warn(`[cli] CLASS_COLOR map missing color for tier "${t}"`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Terminal utils ──────────────────────────────────────────────────────────
|
|
119
|
+
const CLEAR_SCREEN = `${ESC}2J${ESC}H`;
|
|
120
|
+
const CURSOR_UP = (n) => `${ESC}${n}A`;
|
|
121
|
+
const ERASE_LINE = `${ESC}2K`;
|
|
122
|
+
const HIDE_CURSOR = `${ESC}?25l`;
|
|
123
|
+
const SHOW_CURSOR = `${ESC}?25h`;
|
|
124
|
+
const termWidth = () => process.stdout.columns || 80;
|
|
125
|
+
const write = (s) => process.stdout.write(s);
|
|
126
|
+
const writeln = (s = "") => process.stdout.write(s + "\n");
|
|
127
|
+
|
|
128
|
+
// Right-pad or truncate to exact width (ANSI-escape-aware via strip helper)
|
|
129
|
+
function stripAnsi(s) {
|
|
130
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
131
|
+
}
|
|
132
|
+
function padEnd(s, w) {
|
|
133
|
+
const vis = stripAnsi(s).length;
|
|
134
|
+
return vis >= w ? s : s + " ".repeat(w - vis);
|
|
135
|
+
}
|
|
136
|
+
function padStart(s, w) {
|
|
137
|
+
const vis = stripAnsi(s).length;
|
|
138
|
+
return vis >= w ? s : " ".repeat(w - vis) + s;
|
|
139
|
+
}
|
|
140
|
+
function trunc(s, w) {
|
|
141
|
+
const stripped = stripAnsi(s);
|
|
142
|
+
if (stripped.length <= w) return s;
|
|
143
|
+
// truncate the raw string, not the escape-aware one — safe for plain strings
|
|
144
|
+
return s.slice(0, w - 1) + "…";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Number formatters ───────────────────────────────────────────────────────
|
|
148
|
+
const fmtYield = (y) => {
|
|
149
|
+
if (y == null) return "—";
|
|
150
|
+
if (y >= 10000) return `${(y / 1000).toFixed(1)}K`;
|
|
151
|
+
if (y >= 1000) return `${(y / 1000).toFixed(2)}K`;
|
|
152
|
+
return y.toFixed(1);
|
|
153
|
+
};
|
|
154
|
+
const fmtLev = (l) => {
|
|
155
|
+
if (l == null) return "—";
|
|
156
|
+
if (l >= 1000) return `${(l / 1000).toFixed(1)}K`;
|
|
157
|
+
return l.toFixed(0);
|
|
158
|
+
};
|
|
159
|
+
const fmtPct = (n) => (n != null ? `${(n * 100).toFixed(0)}%` : "—");
|
|
160
|
+
const fmtSNR = (n) => (n != null ? `${(n * 100).toFixed(1)}%` : "—");
|
|
161
|
+
const fmtMove = (n) => {
|
|
162
|
+
if (n == null || n === 0) return dim(" —");
|
|
163
|
+
return n > 0 ? green(`+${n}`) : red(`${n}`);
|
|
164
|
+
};
|
|
165
|
+
const fmtTokens = (n) => {
|
|
166
|
+
if (n == null) return "—";
|
|
167
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
168
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
169
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
170
|
+
return String(n);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// ── Header / footer ─────────────────────────────────────────────────────────
|
|
174
|
+
function renderHeader(title, subtitle = "") {
|
|
175
|
+
const w = termWidth();
|
|
176
|
+
const ts = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
177
|
+
const right = dim(`signalaf.com ${ts}`);
|
|
178
|
+
const rightVis = stripAnsi(right).length;
|
|
179
|
+
const leftVis = stripAnsi(title).length;
|
|
180
|
+
const gap = Math.max(1, w - leftVis - rightVis);
|
|
181
|
+
writeln();
|
|
182
|
+
writeln(` ${title}${" ".repeat(gap)}${right}`);
|
|
183
|
+
if (subtitle) writeln(` ${dim(subtitle)}`);
|
|
184
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function renderFooter(hint = "") {
|
|
188
|
+
const w = termWidth();
|
|
189
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
190
|
+
if (hint) writeln(` ${dim(hint)}`);
|
|
191
|
+
writeln();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── BOARD command ────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
const BOARD_COLS = [
|
|
197
|
+
{ key: "rank", label: "#", w: 4, align: "r" },
|
|
198
|
+
{ key: "codename", label: "Operator", w: 20, align: "l" },
|
|
199
|
+
{ key: "class_tier", label: "Class", w: 13, align: "l" },
|
|
200
|
+
{ key: "yield_", label: "Υ Yield", w: 9, align: "r" },
|
|
201
|
+
{ key: "compression_ratio", label: "SNR", w: 6, align: "r" },
|
|
202
|
+
{ key: "session_depth", label: "Depth", w: 6, align: "r" },
|
|
203
|
+
{ key: "token_throughput", label: "Tokens", w: 8, align: "r" },
|
|
204
|
+
{ key: "movement_7d", label: "7d Δ", w: 6, align: "r" },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
function renderBoardRow(entry, highlight = false) {
|
|
208
|
+
const rank = entry.rank === 1 ? gold(`#${entry.rank}`) : `#${entry.rank}`;
|
|
209
|
+
const name = highlight
|
|
210
|
+
? cyan(trunc(entry.codename, 19))
|
|
211
|
+
: trunc(entry.codename, 19);
|
|
212
|
+
const cls = colorClass(entry.class_tier ?? "—");
|
|
213
|
+
const yld = entry.yield_ != null ? fmtYield(entry.yield_) : "—";
|
|
214
|
+
const snr = fmtSNR(entry.compression_ratio);
|
|
215
|
+
const dep =
|
|
216
|
+
entry.session_depth != null ? entry.session_depth.toFixed(1) : "—";
|
|
217
|
+
const tok = fmtTokens(entry.token_throughput);
|
|
218
|
+
const mv = fmtMove(entry.movement_7d);
|
|
219
|
+
|
|
220
|
+
const cols = [
|
|
221
|
+
padStart(rank, 4),
|
|
222
|
+
padEnd(name, 20),
|
|
223
|
+
padEnd(cls, 13),
|
|
224
|
+
padStart(yld, 9),
|
|
225
|
+
padStart(snr, 6),
|
|
226
|
+
padStart(dep, 6),
|
|
227
|
+
padStart(tok, 8),
|
|
228
|
+
padStart(mv, 6),
|
|
229
|
+
];
|
|
230
|
+
const prefix = highlight ? `${c.boldCyan}▶${c.reset} ` : " ";
|
|
231
|
+
writeln(prefix + cols.join(" "));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function renderBoardHeader(window = "30d") {
|
|
235
|
+
renderHeader(
|
|
236
|
+
`${gold("⊙ SigRank")} ${bold("Leaderboard")}`,
|
|
237
|
+
`window: ${window} · sorted by Υ Yield · top 25 operators`,
|
|
238
|
+
);
|
|
239
|
+
// column headers
|
|
240
|
+
const headers = BOARD_COLS.map((col) =>
|
|
241
|
+
col.align === "r"
|
|
242
|
+
? padStart(dim(col.label), col.w)
|
|
243
|
+
: padEnd(dim(col.label), col.w),
|
|
244
|
+
);
|
|
245
|
+
writeln(` ${headers.join(" ")}`);
|
|
246
|
+
writeln(` ${dim("·".repeat(termWidth() - 4))}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function fetchBoard(window = "30d") {
|
|
250
|
+
const res = await fetch(
|
|
251
|
+
`${DEFAULT_API_BASE}/api/v1/leaderboard?window=${window}&metric=${LEADERBOARD_METRIC}`,
|
|
252
|
+
{
|
|
253
|
+
headers: {
|
|
254
|
+
accept: "application/json",
|
|
255
|
+
"user-agent": `node/${process.version} sigrank-mcp/cli`,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
if (!res.ok) throw new Error(`Board API → HTTP ${res.status}`);
|
|
260
|
+
return res.json();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function runBoard({ window = "30d", once = false, refresh = 30 } = {}) {
|
|
264
|
+
let lines = 0;
|
|
265
|
+
|
|
266
|
+
const draw = async () => {
|
|
267
|
+
let data;
|
|
268
|
+
try {
|
|
269
|
+
data = await fetchBoard(window);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
writeln(red(` ✗ Could not reach signalaf.com: ${e.message}`));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!once && lines > 0) {
|
|
276
|
+
// move cursor up and redraw in-place
|
|
277
|
+
write(CURSOR_UP(lines));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const out = [];
|
|
281
|
+
const push = (s = "") => out.push(s);
|
|
282
|
+
|
|
283
|
+
push();
|
|
284
|
+
const ts = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
285
|
+
const right = `signalaf.com ${ts}`;
|
|
286
|
+
const title = `⊙ SigRank Leaderboard`;
|
|
287
|
+
const w = termWidth();
|
|
288
|
+
const gap = Math.max(1, w - 2 - title.length - right.length);
|
|
289
|
+
push(
|
|
290
|
+
` ${gold("⊙ SigRank")} ${bold("Leaderboard")}${" ".repeat(gap)}${dim(right)}`,
|
|
291
|
+
);
|
|
292
|
+
push(
|
|
293
|
+
` ${dim(`window: ${data.window ?? window} · ${data.total_operators ?? data.entries?.length ?? 0} operators`)}`,
|
|
294
|
+
);
|
|
295
|
+
push(` ${dim("─".repeat(w - 4))}`);
|
|
296
|
+
|
|
297
|
+
// column header row
|
|
298
|
+
const headers = BOARD_COLS.map((col) =>
|
|
299
|
+
col.align === "r"
|
|
300
|
+
? padStart(dim(col.label), col.w)
|
|
301
|
+
: padEnd(dim(col.label), col.w),
|
|
302
|
+
).join(" ");
|
|
303
|
+
push(` ${headers}`);
|
|
304
|
+
push(` ${dim("·".repeat(w - 4))}`);
|
|
305
|
+
|
|
306
|
+
const entries = data.entries ?? [];
|
|
307
|
+
for (const entry of entries) {
|
|
308
|
+
const rank = entry.rank === 1 ? gold(`#${entry.rank}`) : `#${entry.rank}`;
|
|
309
|
+
const name = trunc(entry.codename ?? "—", 19);
|
|
310
|
+
const cls = colorClass(entry.class_tier ?? "—");
|
|
311
|
+
const yld = entry.yield_ != null ? fmtYield(entry.yield_) : "—";
|
|
312
|
+
const snr = fmtSNR(entry.compression_ratio);
|
|
313
|
+
const dep =
|
|
314
|
+
entry.session_depth != null ? entry.session_depth.toFixed(1) : "—";
|
|
315
|
+
const tok = fmtTokens(entry.token_throughput);
|
|
316
|
+
const mv = fmtMove(entry.movement_7d);
|
|
317
|
+
const cols = [
|
|
318
|
+
padStart(rank, 4),
|
|
319
|
+
padEnd(name, 20),
|
|
320
|
+
padEnd(cls, 13),
|
|
321
|
+
padStart(yld, 9),
|
|
322
|
+
padStart(snr, 6),
|
|
323
|
+
padStart(dep, 6),
|
|
324
|
+
padStart(tok, 8),
|
|
325
|
+
padStart(mv, 6),
|
|
326
|
+
];
|
|
327
|
+
push(` ${cols.join(" ")}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
push(` ${dim("─".repeat(w - 4))}`);
|
|
331
|
+
if (!once)
|
|
332
|
+
push(` ${dim(`auto-refresh every ${refresh}s · ctrl+c to exit`)}`);
|
|
333
|
+
push();
|
|
334
|
+
|
|
335
|
+
// write all at once to minimize flicker
|
|
336
|
+
const rendered = out.join("\n");
|
|
337
|
+
write(rendered);
|
|
338
|
+
lines = out.length;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
if (!once) write(HIDE_CURSOR);
|
|
342
|
+
try {
|
|
343
|
+
await draw();
|
|
344
|
+
if (!once) {
|
|
345
|
+
const iv = setInterval(draw, refresh * 1000);
|
|
346
|
+
await new Promise((resolve) => {
|
|
347
|
+
process.on("SIGINT", () => {
|
|
348
|
+
clearInterval(iv);
|
|
349
|
+
resolve();
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
} finally {
|
|
354
|
+
if (!once) write(SHOW_CURSOR + "\n");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ── COMPARE command ───────────────────────────────────────────────────────────
|
|
359
|
+
// Side-by-side: ccusage (JSON) vs tokenpull vs token-dashboard (SQLite)
|
|
360
|
+
//
|
|
361
|
+
// NOTE (P3 2026-06-27): These sync verifier readers (ccusagePillars / tokscalePillars /
|
|
362
|
+
// tokenDashPillars / appPillars) are INTENTIONALLY separate from tokenpull.mjs
|
|
363
|
+
// `freshVerifierPillars()`. The compare command uses cached/file-based data sources
|
|
364
|
+
// (tokscale_report.json, direct db read with windowed+model-filtered queries) for a
|
|
365
|
+
// quick side-by-side, while freshVerifierPillars runs all three live (bunx tokscale,
|
|
366
|
+
// scan+read tokendash) for the dashboard. Merging them would change compare's data
|
|
367
|
+
// source and lose the windowed tokendash breakdown. ccusagePillars IS functionally
|
|
368
|
+
// identical to _freshCcusage and could be consolidated in a future pass if compare
|
|
369
|
+
// switches to live data — but that's a behavior change, not a refactor.
|
|
370
|
+
|
|
371
|
+
async function ccusagePillars(platform = "claude") {
|
|
372
|
+
// ccusage <platform> daily --json → sum by window
|
|
373
|
+
try {
|
|
374
|
+
const raw = await execFileAsync(
|
|
375
|
+
"ccusage",
|
|
376
|
+
[platform, "daily", "--json"],
|
|
377
|
+
15000,
|
|
378
|
+
);
|
|
379
|
+
const data = JSON.parse(raw);
|
|
380
|
+
const rows = data.daily ?? data; // ccusage may return {daily:[...]} or [...]
|
|
381
|
+
|
|
382
|
+
const now = Date.now();
|
|
383
|
+
const cutoff = { "7d": 7, "30d": 30, "90d": 90 };
|
|
384
|
+
const result = {};
|
|
385
|
+
|
|
386
|
+
for (const [win, days] of Object.entries(cutoff)) {
|
|
387
|
+
const since = new Date(now - days * 86400000);
|
|
388
|
+
let input = 0,
|
|
389
|
+
output = 0,
|
|
390
|
+
cacheCreate = 0,
|
|
391
|
+
cacheRead = 0;
|
|
392
|
+
for (const row of rows) {
|
|
393
|
+
const d = new Date(row.date ?? row.day ?? row.week ?? "1970-01-01");
|
|
394
|
+
if (d >= since) {
|
|
395
|
+
input += row.inputTokens ?? row.input_tokens ?? 0;
|
|
396
|
+
output += row.outputTokens ?? row.output_tokens ?? 0;
|
|
397
|
+
cacheCreate +=
|
|
398
|
+
row.cacheCreationTokens ?? row.cache_create_tokens ?? 0;
|
|
399
|
+
cacheRead += row.cacheReadTokens ?? row.cache_read_tokens ?? 0;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
result[win] = { input, output, cacheCreate, cacheRead };
|
|
403
|
+
}
|
|
404
|
+
// all-time = sum everything
|
|
405
|
+
let input = 0,
|
|
406
|
+
output = 0,
|
|
407
|
+
cacheCreate = 0,
|
|
408
|
+
cacheRead = 0;
|
|
409
|
+
for (const row of rows) {
|
|
410
|
+
input += row.inputTokens ?? row.input_tokens ?? 0;
|
|
411
|
+
output += row.outputTokens ?? row.output_tokens ?? 0;
|
|
412
|
+
cacheCreate += row.cacheCreationTokens ?? row.cache_create_tokens ?? 0;
|
|
413
|
+
cacheRead += row.cacheReadTokens ?? row.cache_read_tokens ?? 0;
|
|
414
|
+
}
|
|
415
|
+
result["all"] = { input, output, cacheCreate, cacheRead };
|
|
416
|
+
return result;
|
|
417
|
+
} catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function tokscalePillars(platform = "claude") {
|
|
423
|
+
// Try the bundled tokscale CLI first (fresh data), fall back to saved report file.
|
|
424
|
+
try {
|
|
425
|
+
const raw = await execFileAsync("tokscale", ["models", "--json"], 60000);
|
|
426
|
+
const data = JSON.parse(raw);
|
|
427
|
+
const entries = Array.isArray(data?.entries)
|
|
428
|
+
? data.entries
|
|
429
|
+
: Array.isArray(data)
|
|
430
|
+
? data
|
|
431
|
+
: [];
|
|
432
|
+
const rows = entries.filter(
|
|
433
|
+
(e) =>
|
|
434
|
+
e &&
|
|
435
|
+
e.client === platform &&
|
|
436
|
+
e.model !== "<synthetic>" &&
|
|
437
|
+
e.model !== "unknown" &&
|
|
438
|
+
((Number(e.input) || 0) > 0 || (Number(e.output) || 0) > 0),
|
|
439
|
+
);
|
|
440
|
+
if (rows.length) {
|
|
441
|
+
const p = rows.reduce(
|
|
442
|
+
(acc, e) => ({
|
|
443
|
+
input: acc.input + (Number(e.input) || 0),
|
|
444
|
+
output: acc.output + (Number(e.output) || 0),
|
|
445
|
+
cacheCreate: acc.cacheCreate + (Number(e.cacheWrite) || 0),
|
|
446
|
+
cacheRead: acc.cacheRead + (Number(e.cacheRead) || 0),
|
|
447
|
+
}),
|
|
448
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
|
|
449
|
+
);
|
|
450
|
+
return { all: p };
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
/* fall through to file-based read */
|
|
454
|
+
}
|
|
455
|
+
// Fallback: read saved tokscale_report.json — all-time only (no timestamps in export)
|
|
456
|
+
const reportPath = path.join(os.homedir(), "tokscale_report.json");
|
|
457
|
+
if (!existsSync(reportPath)) return null;
|
|
458
|
+
try {
|
|
459
|
+
const data = JSON.parse(readFileSync(reportPath, "utf8"));
|
|
460
|
+
const entries = data.entries ?? [];
|
|
461
|
+
const rows = entries.filter(
|
|
462
|
+
(e) =>
|
|
463
|
+
e.client === platform &&
|
|
464
|
+
e.model !== "<synthetic>" &&
|
|
465
|
+
e.model !== "unknown" &&
|
|
466
|
+
(e.input > 0 || e.output > 0),
|
|
467
|
+
);
|
|
468
|
+
if (rows.length === 0) return null;
|
|
469
|
+
const p = rows.reduce(
|
|
470
|
+
(acc, e) => ({
|
|
471
|
+
input: acc.input + (e.input ?? 0),
|
|
472
|
+
output: acc.output + (e.output ?? 0),
|
|
473
|
+
cacheCreate: acc.cacheCreate + (e.cacheWrite ?? 0),
|
|
474
|
+
cacheRead: acc.cacheRead + (e.cacheRead ?? 0),
|
|
475
|
+
}),
|
|
476
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
|
|
477
|
+
);
|
|
478
|
+
// tokscale export has no timestamps → only all-time available
|
|
479
|
+
return { all: p };
|
|
480
|
+
} catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function appPillars() {
|
|
486
|
+
// ⚠ STALE HARDCODED DATA — App numbers from screenshots, all-time, per model
|
|
487
|
+
// (no cache fields). Captured 2026-06-23. NOT refreshed automatically. The
|
|
488
|
+
// compare view uses this only as a static reference point; it does NOT feed
|
|
489
|
+
// the ranked submit path. Update manually when a fresh screenshot is taken.
|
|
490
|
+
return {
|
|
491
|
+
all: {
|
|
492
|
+
input: 6_378_000, // sum of all models: 5.6M + 102.1K + 92.9K + 130.3K + 418.9K + 33.5K
|
|
493
|
+
output: 38_682_400, // sum: 19.6M + 6.5M + 5.4M + 6.6M + 292.4K + 290.4K
|
|
494
|
+
cacheCreate: null, // not shown in App UI
|
|
495
|
+
cacheRead: null, // not shown in App UI
|
|
496
|
+
},
|
|
497
|
+
_note:
|
|
498
|
+
"App UI — all-time, per-model sum from screenshots 2026-06-23. No cache fields. Update when re-screenshotted.",
|
|
499
|
+
_perModel: [
|
|
500
|
+
{ model: "claude-opus-4-8", input: 5_600_000, output: 19_600_000 },
|
|
501
|
+
{ model: "claude-sonnet-4-5", input: 102_100, output: 6_500_000 },
|
|
502
|
+
{ model: "claude-sonnet-4-6", input: 92_900, output: 5_400_000 },
|
|
503
|
+
{ model: "claude-opus-4-7", input: 130_300, output: 6_600_000 },
|
|
504
|
+
{ model: "claude-fable-5", input: 418_900, output: 292_400 },
|
|
505
|
+
{ model: "claude-haiku-4-5", input: 33_500, output: 290_400 },
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function tokenDashPillars() {
|
|
511
|
+
const dbPath = path.join(os.homedir(), ".claude", "token-dashboard.db");
|
|
512
|
+
if (!existsSync(dbPath)) return null;
|
|
513
|
+
// Read directly with sqlite3 — DB created by Nate's token-dashboard (github.com/nateherkai/token-dashboard).
|
|
514
|
+
// Windowed queries using sqlite3's datetime() function.
|
|
515
|
+
const cf =
|
|
516
|
+
"(model LIKE '%claude%' OR model LIKE '%fable%' OR model LIKE '%sonnet%' OR model LIKE '%opus%' OR model LIKE '%haiku%')";
|
|
517
|
+
try {
|
|
518
|
+
const wins = [
|
|
519
|
+
["7d", "datetime('now','-7 days')"],
|
|
520
|
+
["30d", "datetime('now','-30 days')"],
|
|
521
|
+
["90d", "datetime('now','-90 days')"],
|
|
522
|
+
];
|
|
523
|
+
const sql = (cutoff) =>
|
|
524
|
+
`SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens+cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages WHERE timestamp>=${cutoff} AND ${cf}`;
|
|
525
|
+
const allSql = `SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens+cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages WHERE ${cf}`;
|
|
526
|
+
const [d7, d30, d90, dAll] = await Promise.all([
|
|
527
|
+
execFileAsync("sqlite3", [dbPath, sql(wins[0][1])], 5000),
|
|
528
|
+
execFileAsync("sqlite3", [dbPath, sql(wins[1][1])], 5000),
|
|
529
|
+
execFileAsync("sqlite3", [dbPath, sql(wins[2][1])], 5000),
|
|
530
|
+
execFileAsync("sqlite3", [dbPath, allSql], 5000),
|
|
531
|
+
]);
|
|
532
|
+
const parse = (raw) => {
|
|
533
|
+
const [i, o, cw, cr] = raw.trim().split("|").map(Number);
|
|
534
|
+
return {
|
|
535
|
+
input: i || 0,
|
|
536
|
+
output: o || 0,
|
|
537
|
+
cacheCreate: cw || 0,
|
|
538
|
+
cacheRead: cr || 0,
|
|
539
|
+
};
|
|
540
|
+
};
|
|
541
|
+
return {
|
|
542
|
+
"7d": parse(d7),
|
|
543
|
+
"30d": parse(d30),
|
|
544
|
+
"90d": parse(d90),
|
|
545
|
+
all: parse(dAll),
|
|
546
|
+
};
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function fmtDelta(a, b) {
|
|
553
|
+
if (a == null || b == null) return dim(" —");
|
|
554
|
+
const d = b - a;
|
|
555
|
+
if (d === 0) return dim(" =");
|
|
556
|
+
const pct =
|
|
557
|
+
a !== 0 ? `${d > 0 ? "+" : ""}${((d / a) * 100).toFixed(1)}%` : "";
|
|
558
|
+
const abs = `${d > 0 ? "+" : ""}${fmtTokens(Math.abs(d))}`;
|
|
559
|
+
const label = `${abs} ${pct}`;
|
|
560
|
+
return d > 0 ? green(label) : red(label);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Compute cascade metrics from raw pillars. Fix 3: this was a third copy of
|
|
564
|
+
// the cascade math with its own divide-by-zero policy (safeI = max(i,1) clamp),
|
|
565
|
+
// which let the CLI's Υ drift from the MCP tools' Υ for the same tokens. It now
|
|
566
|
+
// forwards to the single shared cascade() from analytics/cascade.mjs (null-guard
|
|
567
|
+
// policy, identical to rank_paste / submit_verified) and only adds the
|
|
568
|
+
// CLI-display-only `efficiency` + `total` fields the compare view renders.
|
|
569
|
+
function cascadeFromPillars(p) {
|
|
570
|
+
if (!p) return null;
|
|
571
|
+
const c = cascade(p);
|
|
572
|
+
const i = p.input ?? 0;
|
|
573
|
+
const o = p.output ?? 0;
|
|
574
|
+
const cw = p.cacheCreate ?? 0;
|
|
575
|
+
const cr = p.cacheRead ?? 0;
|
|
576
|
+
const total = i + o + cw + cr;
|
|
577
|
+
// efficiency = ((cr+cw+o)/i)/4.0 — display-only derived metric (not in the
|
|
578
|
+
// canonical cascade). Null when input is 0 (matches the old safeI→0 behavior
|
|
579
|
+
// would have been div-by-zero; the shared cascade already nulls yield there).
|
|
580
|
+
const efficiency = i > 0 ? (cr + cw + o) / i / 4.0 : null;
|
|
581
|
+
return {
|
|
582
|
+
yield: c.yield,
|
|
583
|
+
velocity: c.velocity,
|
|
584
|
+
leverage: c.leverage,
|
|
585
|
+
snr: c.snr,
|
|
586
|
+
dev10x: c.dev10x,
|
|
587
|
+
efficiency,
|
|
588
|
+
class: c.class,
|
|
589
|
+
total,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function runCompare({ platform = "claude" } = {}) {
|
|
594
|
+
write(HIDE_CURSOR);
|
|
595
|
+
|
|
596
|
+
// Pull all five sources in parallel (verifiers now async via execFile)
|
|
597
|
+
writeln(` ${dim("reading all 5 sources…")}`);
|
|
598
|
+
const [ccPillars, tpData, tdPillars, tsPillars, apPillars] =
|
|
599
|
+
await Promise.all([
|
|
600
|
+
ccusagePillars(platform).catch(() => null),
|
|
601
|
+
callTool("tokenpull", { platform }).catch(() => null),
|
|
602
|
+
(platform === "claude"
|
|
603
|
+
? tokenDashPillars()
|
|
604
|
+
: Promise.resolve(null)
|
|
605
|
+
).catch(() => null),
|
|
606
|
+
tokscalePillars(platform).catch(() => null),
|
|
607
|
+
Promise.resolve(platform === "claude" ? appPillars() : null),
|
|
608
|
+
]);
|
|
609
|
+
write(CURSOR_UP(1) + ERASE_LINE);
|
|
610
|
+
|
|
611
|
+
const w = termWidth();
|
|
612
|
+
const WINS = ["7d", "30d", "90d", "all"];
|
|
613
|
+
const WIN_LABEL = { "7d": "7d", "30d": "30d", "90d": "90d", all: "all-time" };
|
|
614
|
+
|
|
615
|
+
// build tokenpull pillar lookup
|
|
616
|
+
const tpPillars = {};
|
|
617
|
+
for (const win of tpData?.windows ?? []) {
|
|
618
|
+
tpPillars[win.window] = win.pillars;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// sources: name, color, pillars-by-window, note
|
|
622
|
+
const SOURCES = [
|
|
623
|
+
{
|
|
624
|
+
name: "tokenpull",
|
|
625
|
+
color: cyan,
|
|
626
|
+
pillars: tpPillars,
|
|
627
|
+
note: "JSONL deduped by msg id · canon source",
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: "ccusage",
|
|
631
|
+
color: (s) => paint(c.green, s),
|
|
632
|
+
pillars: ccPillars ?? {},
|
|
633
|
+
note: "ccusage claude subcommand · monthly only",
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
name: "token-dash",
|
|
637
|
+
color: (s) => paint(c.magenta, s),
|
|
638
|
+
pillars: tdPillars ?? {},
|
|
639
|
+
note: "SQLite — double-counts sessions · use with caution",
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "tokscale",
|
|
643
|
+
color: (s) => paint(c.blue, s),
|
|
644
|
+
pillars: tsPillars ?? {},
|
|
645
|
+
note: "all-time only · partial export (~5% of opus-4-8)",
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: "App",
|
|
649
|
+
color: gold,
|
|
650
|
+
pillars: apPillars ?? {},
|
|
651
|
+
note: "screenshots 2026-06-23 · no cache fields · update manually",
|
|
652
|
+
},
|
|
653
|
+
];
|
|
654
|
+
|
|
655
|
+
writeln();
|
|
656
|
+
writeln(
|
|
657
|
+
` ${gold("⊙ SigRank")} ${bold("Source Comparison")} ${dim(`platform: ${platform}`)} ${dim("tokenpull · ccusage · token-dash · tokscale")}`,
|
|
658
|
+
);
|
|
659
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
660
|
+
|
|
661
|
+
// ── PILLARS TABLE ──────────────────────────────────────────────────────────
|
|
662
|
+
const PILLARS = [
|
|
663
|
+
{ key: "input", label: "Input" },
|
|
664
|
+
{ key: "output", label: "Output" },
|
|
665
|
+
{ key: "cacheCreate", label: "Cache Write" },
|
|
666
|
+
{ key: "cacheRead", label: "Cache Read" },
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
for (const { key, label } of PILLARS) {
|
|
670
|
+
writeln();
|
|
671
|
+
writeln(` ${bold(label)}`);
|
|
672
|
+
const hcols = [
|
|
673
|
+
padEnd(dim("Source"), 14),
|
|
674
|
+
...WINS.map((win) => padStart(dim(WIN_LABEL[win]), 13)),
|
|
675
|
+
];
|
|
676
|
+
writeln(` ${hcols.join(" ")}`);
|
|
677
|
+
writeln(` ${dim("·".repeat(Math.min(w - 4, 14 + WINS.length * 15)))}`);
|
|
678
|
+
|
|
679
|
+
for (const src of SOURCES) {
|
|
680
|
+
const vals = WINS.map((win) => {
|
|
681
|
+
const p = src.pillars[win];
|
|
682
|
+
const v = p?.[key];
|
|
683
|
+
if (v == null) return padStart(dim("—"), 13);
|
|
684
|
+
return padStart(fmtTokens(v), 13);
|
|
685
|
+
});
|
|
686
|
+
writeln(` ${padEnd(src.color(src.name), 14)} ${vals.join(" ")}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// ── SIGNATURE TABLE ────────────────────────────────────────────────────────
|
|
691
|
+
writeln();
|
|
692
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
693
|
+
writeln(
|
|
694
|
+
` ${bold("Cascade Signature")} ${dim("per source · all windows where data available")}`,
|
|
695
|
+
);
|
|
696
|
+
writeln();
|
|
697
|
+
|
|
698
|
+
const SIG_METRICS = [
|
|
699
|
+
{ key: "yield", label: "Υ Yield", fmt: (v) => fmtYield(v), w: 9 },
|
|
700
|
+
{ key: "velocity", label: "Vel", fmt: (v) => v.toFixed(2), w: 6 },
|
|
701
|
+
{ key: "leverage", label: "Lev", fmt: (v) => `${fmtLev(v)}×`, w: 7 },
|
|
702
|
+
{ key: "snr", label: "SNR", fmt: (v) => fmtSNR(v), w: 6 },
|
|
703
|
+
{ key: "dev10x", label: "10x", fmt: (v) => v.toFixed(2), w: 5 },
|
|
704
|
+
{ key: "efficiency", label: "Eff", fmt: (v) => v.toFixed(1), w: 6 },
|
|
705
|
+
{ key: "class", label: "Class", fmt: (v) => colorClass(v), w: 12 },
|
|
706
|
+
];
|
|
707
|
+
|
|
708
|
+
// header
|
|
709
|
+
const sigHdr = [
|
|
710
|
+
padEnd(dim("Source"), 14),
|
|
711
|
+
padEnd(dim("Window"), 8),
|
|
712
|
+
...SIG_METRICS.map((m) => padStart(dim(m.label), m.w)),
|
|
713
|
+
];
|
|
714
|
+
writeln(` ${sigHdr.join(" ")}`);
|
|
715
|
+
writeln(` ${dim("·".repeat(Math.min(w - 4, 80)))}`);
|
|
716
|
+
|
|
717
|
+
for (const src of SOURCES) {
|
|
718
|
+
const availWins = WINS.filter((win) => src.pillars[win] != null);
|
|
719
|
+
if (availWins.length === 0) {
|
|
720
|
+
writeln(` ${padEnd(src.color(src.name), 14)} ${dim("no data")}`);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
let first = true;
|
|
724
|
+
for (const win of availWins) {
|
|
725
|
+
const p = src.pillars[win];
|
|
726
|
+
// For tokenpull, use the pre-computed cascade from the tool if available
|
|
727
|
+
let cas;
|
|
728
|
+
if (src.name === "tokenpull") {
|
|
729
|
+
const tpWin = tpData?.windows?.find((ww) => ww.window === win);
|
|
730
|
+
cas = tpWin?.cascade
|
|
731
|
+
? {
|
|
732
|
+
yield: tpWin.cascade.yield,
|
|
733
|
+
velocity: tpWin.cascade.velocity,
|
|
734
|
+
leverage: tpWin.cascade.leverage,
|
|
735
|
+
snr: tpWin.cascade.snr,
|
|
736
|
+
dev10x: tpWin.cascade.dev10x,
|
|
737
|
+
efficiency: null,
|
|
738
|
+
class: tpWin.cascade.class,
|
|
739
|
+
}
|
|
740
|
+
: cascadeFromPillars(p);
|
|
741
|
+
} else {
|
|
742
|
+
cas = cascadeFromPillars(p);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const srcLabel = first
|
|
746
|
+
? src.color(src.name)
|
|
747
|
+
: " ".repeat(stripAnsi(src.name).length);
|
|
748
|
+
const winLabel = win === "all" ? bold("all-time") : win;
|
|
749
|
+
const sigCols = SIG_METRICS.map((m) => {
|
|
750
|
+
const v = cas?.[m.key];
|
|
751
|
+
return padStart(v != null ? m.fmt(v) : dim("—"), m.w);
|
|
752
|
+
});
|
|
753
|
+
writeln(
|
|
754
|
+
` ${padEnd(srcLabel, 14)} ${padEnd(winLabel, 8)} ${sigCols.join(" ")}`,
|
|
755
|
+
);
|
|
756
|
+
first = false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ── NOTES ─────────────────────────────────────────────────────────────────
|
|
761
|
+
writeln();
|
|
762
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
763
|
+
for (const src of SOURCES) {
|
|
764
|
+
writeln(` ${src.color(src.name.padEnd(12))} ${dim(src.note)}`);
|
|
765
|
+
}
|
|
766
|
+
writeln(
|
|
767
|
+
` ${dim("Eff = ((cacheRead+cacheWrite+output)/input)/4.0 vs AA baseline")}`,
|
|
768
|
+
);
|
|
769
|
+
writeln(
|
|
770
|
+
` ${dim("App has no cache fields → Υ/Lev/Eff/10x unavailable from App source")}`,
|
|
771
|
+
);
|
|
772
|
+
writeln();
|
|
773
|
+
write(SHOW_CURSOR);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// ── WATCH command ─────────────────────────────────────────────────────────────
|
|
777
|
+
//
|
|
778
|
+
// A5: by default (no --platform / --window) the watcher auto-loads EVERYTHING — every
|
|
779
|
+
// active platform (input+output > 0) × every window (7d/30d/90d/all), refreshed each tick.
|
|
780
|
+
// --platform / --window remain OPTIONAL filters for back-compat (focus one cell). The
|
|
781
|
+
// refresh loop + --submit behavior are preserved; submit publishes whichever windows are
|
|
782
|
+
// shown whenever their Υ changes.
|
|
783
|
+
|
|
784
|
+
const WATCH_WINDOWS = ["7d", "30d", "90d", "all"];
|
|
785
|
+
|
|
786
|
+
// Detect which platforms have real local data (input+output > 0 in any window) via tokenpull.
|
|
787
|
+
// Returns an array of pull results (the same shape tokenpullAny yields), one per active
|
|
788
|
+
// platform, through the shared loader used by `me` / `watch` / the TUI Dashboard.
|
|
789
|
+
//
|
|
790
|
+
// `platform` scopes the PULL, not just the render: the watcher re-pulls every `refresh`
|
|
791
|
+
// seconds (default 30) and an unscoped auto-detect also probes the tokscale-blind ~10 GiB
|
|
792
|
+
// oh-my-pi tree (~46s), so `watch --platform claude` used to scan everything and then
|
|
793
|
+
// display one row — scan longer than refresh interval, permanently saturated.
|
|
794
|
+
// null / '' / 'all' still mean "everything", keeping withTokscaleBlind's omp row.
|
|
795
|
+
export async function detectActivePulls(platform, opts = {}) {
|
|
796
|
+
const scoped = platform && platform !== "all" ? [platform] : undefined;
|
|
797
|
+
return pullActivePlatforms({ platforms: scoped }, opts).catch(() => []);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
async function runWatch({
|
|
801
|
+
platform,
|
|
802
|
+
window: win,
|
|
803
|
+
refresh = 30,
|
|
804
|
+
submit = false,
|
|
805
|
+
} = {}) {
|
|
806
|
+
let lines = 0;
|
|
807
|
+
const id = submit ? ensureIdentity() : null;
|
|
808
|
+
const enrolled = !!(
|
|
809
|
+
id &&
|
|
810
|
+
id.codename &&
|
|
811
|
+
id.operator_id &&
|
|
812
|
+
id.private_key_pkcs8_b64
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
// Filters: when provided, narrow the grid; when absent, watch all active × all windows.
|
|
816
|
+
const winFilter = typeof win === "string" && win ? win : null;
|
|
817
|
+
const platFilter = typeof platform === "string" && platform ? platform : null;
|
|
818
|
+
const windows = winFilter ? [winFilter] : WATCH_WINDOWS;
|
|
819
|
+
|
|
820
|
+
// Per-cell Υ memory (key = `${platform}|${window}`) so each cell tracks its own change.
|
|
821
|
+
const prevY = new Map();
|
|
822
|
+
const lastSubmitMsg = new Map();
|
|
823
|
+
|
|
824
|
+
write(HIDE_CURSOR);
|
|
825
|
+
|
|
826
|
+
const draw = async () => {
|
|
827
|
+
// Re-pull each tick so a platform that just started writing logs appears automatically.
|
|
828
|
+
// Scoped by platFilter, so a focused watch never scans platforms it cannot display.
|
|
829
|
+
const pulls = await detectActivePulls(platFilter);
|
|
830
|
+
|
|
831
|
+
if (lines > 0) write(CURSOR_UP(lines));
|
|
832
|
+
|
|
833
|
+
const out = [];
|
|
834
|
+
const push = (s = "") => out.push(s);
|
|
835
|
+
const w = termWidth();
|
|
836
|
+
const ts = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
837
|
+
|
|
838
|
+
const scopeLabel =
|
|
839
|
+
(platFilter || "all active platforms") +
|
|
840
|
+
" · " +
|
|
841
|
+
(winFilter ? `window: ${winFilter}` : "all windows");
|
|
842
|
+
|
|
843
|
+
push();
|
|
844
|
+
push(
|
|
845
|
+
` ${gold("⊙ SigRank")} ${bold("Watch")} ${dim(`${scopeLabel} · ${ts}`)}`,
|
|
846
|
+
);
|
|
847
|
+
push(` ${dim("─".repeat(w - 4))}`);
|
|
848
|
+
|
|
849
|
+
if (pulls.length === 0) {
|
|
850
|
+
push();
|
|
851
|
+
push(
|
|
852
|
+
` ${dim("no active platforms detected — run some sessions, this will pick them up automatically")}`,
|
|
853
|
+
);
|
|
854
|
+
push();
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
for (const d of pulls) {
|
|
858
|
+
const plat = d.platform;
|
|
859
|
+
const byWin = {};
|
|
860
|
+
for (const ww of d.windows || []) byWin[ww.window] = ww;
|
|
861
|
+
push();
|
|
862
|
+
push(` ${cyan(plat)}${d.estimated ? dim(" (est)") : ""}`);
|
|
863
|
+
for (const wk of windows) {
|
|
864
|
+
const ww = byWin[wk];
|
|
865
|
+
const cas = ww ? cascadeFromPillars(ww.pillars) : null;
|
|
866
|
+
const key = `${plat}|${wk}`;
|
|
867
|
+
const prev = prevY.get(key);
|
|
868
|
+
const yNow = cas?.yield ?? null;
|
|
869
|
+
const changed = prev !== undefined && prev !== yNow;
|
|
870
|
+
|
|
871
|
+
// --submit: publish this cell on first observation + whenever its Υ changes.
|
|
872
|
+
if (
|
|
873
|
+
submit &&
|
|
874
|
+
enrolled &&
|
|
875
|
+
ww &&
|
|
876
|
+
yNow != null &&
|
|
877
|
+
(prev === undefined || changed)
|
|
878
|
+
) {
|
|
879
|
+
try {
|
|
880
|
+
const r = await submitSignedWindow(
|
|
881
|
+
wk,
|
|
882
|
+
ww.pillars,
|
|
883
|
+
ww.messages,
|
|
884
|
+
id,
|
|
885
|
+
{ platform: plat },
|
|
886
|
+
);
|
|
887
|
+
// Pass 6: gate the ✓ on r.ranked (verified + persisted), NOT on
|
|
888
|
+
// r.status === "received". An unenrolled/revoked device gets HTTP
|
|
889
|
+
// 202 "received" but is NEVER ranked — gating on "received" would
|
|
890
|
+
// show a green ✓ to an operator who didn't actually make the board.
|
|
891
|
+
// Mirrors the isRankedAck() predicate used by every submit path.
|
|
892
|
+
lastSubmitMsg.set(
|
|
893
|
+
key,
|
|
894
|
+
r.ranked
|
|
895
|
+
? green(`✓ tier=${r.verification_tier || "—"} (ranked)`)
|
|
896
|
+
: red(
|
|
897
|
+
`✗ ${r.reason || r.status || "not ranked"}${r.verification_tier ? ` (tier=${r.verification_tier})` : ""}`,
|
|
898
|
+
),
|
|
899
|
+
);
|
|
900
|
+
} catch (e) {
|
|
901
|
+
lastSubmitMsg.set(key, red(`✗ ${e.message}`));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
prevY.set(key, yNow);
|
|
905
|
+
|
|
906
|
+
const winLabel = wk === "all" ? "all-time" : wk;
|
|
907
|
+
const yDisplay =
|
|
908
|
+
cas?.yield != null ? gold(fmtYield(cas.yield)) : dim("—");
|
|
909
|
+
const indicator = changed ? green(" ▲") : dim(" ·");
|
|
910
|
+
const metrics = cas
|
|
911
|
+
? `${dim("SNR")} ${fmtSNR(cas.snr)} ${dim("Lev")} ${cas.leverage != null ? fmtLev(cas.leverage) + "×" : "—"} ${dim("Vel")} ${cas.velocity != null ? cas.velocity.toFixed(2) : "—"} ${colorClass(cas.class ?? "—")}`
|
|
912
|
+
: dim("no data");
|
|
913
|
+
const submitNote =
|
|
914
|
+
submit && enrolled ? " " + (lastSubmitMsg.get(key) || dim("…")) : "";
|
|
915
|
+
push(
|
|
916
|
+
` ${padEnd(winLabel, 8)} ${bold("Υ")} ${yDisplay}${indicator} ${metrics}${submitNote}`,
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
push();
|
|
922
|
+
push(` ${dim("─".repeat(w - 4))}`);
|
|
923
|
+
if (submit && !enrolled) {
|
|
924
|
+
push(
|
|
925
|
+
` ${red("not enrolled — run `npx sigrank enroll` to auto-submit")}`,
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
push(
|
|
929
|
+
` ${dim(`polling every ${refresh}s · ${submit ? "auto-submit ON · " : ""}tokens stay on your machine · ctrl+c to exit`)}`,
|
|
930
|
+
);
|
|
931
|
+
push();
|
|
932
|
+
|
|
933
|
+
write(out.join("\n"));
|
|
934
|
+
lines = out.length;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
try {
|
|
938
|
+
await draw();
|
|
939
|
+
const iv = setInterval(draw, refresh * 1000);
|
|
940
|
+
await new Promise((resolve) => {
|
|
941
|
+
process.on("SIGINT", () => {
|
|
942
|
+
clearInterval(iv);
|
|
943
|
+
resolve();
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
} finally {
|
|
947
|
+
write(SHOW_CURSOR + "\n");
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// ── UNIFIED DEFAULT VIEW ──────────────────────────────────────────────────────
|
|
952
|
+
// npx sigrank (no args) — pulls everything at once:
|
|
953
|
+
// - all platforms in parallel (only shows ones with data)
|
|
954
|
+
// - all 4 windows per platform
|
|
955
|
+
// - token pillars table (transparency layer)
|
|
956
|
+
// - comparison sources (ccusage, tokscale, token-dashboard) if available
|
|
957
|
+
// - live board position
|
|
958
|
+
// - [S] submit [B] board [Q] quit
|
|
959
|
+
|
|
960
|
+
// ALL_PLATFORMS is imported from ../adapters.mjs (single source of truth —
|
|
961
|
+
// was redefined inline here, which drifted from the adapter registry whenever
|
|
962
|
+
// a platform was added there but not in this list).
|
|
963
|
+
|
|
964
|
+
async function runSigRank() {
|
|
965
|
+
write(HIDE_CURSOR);
|
|
966
|
+
const w = termWidth();
|
|
967
|
+
|
|
968
|
+
// ── 1. Show immediate header so screen isn't blank ────────────────────────
|
|
969
|
+
writeln();
|
|
970
|
+
writeln(` ${gold("⊙ SigRank")} ${bold("Operator Dashboard")}`);
|
|
971
|
+
writeln(` ${dim("reading local data…")}`);
|
|
972
|
+
|
|
973
|
+
// ../tokenpull.mjs — cli.mjs lives in presentation/, so "./tokenpull.mjs" resolved to
|
|
974
|
+
// presentation/tokenpull.mjs (nonexistent) and threw ERR_MODULE_NOT_FOUND, taking the
|
|
975
|
+
// whole dashboard read down. Matches the `../adapters.mjs` static import above.
|
|
976
|
+
const { tokenpullAny, freshVerifierPillars } =
|
|
977
|
+
await import("../tokenpull.mjs");
|
|
978
|
+
|
|
979
|
+
// Local sources first (fast) — board fetch with 5s timeout runs in parallel
|
|
980
|
+
const boardPromise = Promise.race([
|
|
981
|
+
callTool("get_leaderboard", {}).catch(() => null),
|
|
982
|
+
new Promise((r) => setTimeout(() => r(null), 5000)),
|
|
983
|
+
]);
|
|
984
|
+
|
|
985
|
+
// FIX A1-CLI (2026-06-27): verifiers (ccusage/tokscale/tokendash) are fetched
|
|
986
|
+
// ON-DEMAND for ACTIVE platforms only — not synchronously for every ALL_PLATFORMS entry
|
|
987
|
+
// upfront (which blocked Dashboard paint up to ~40s, same class of bug as TUI
|
|
988
|
+
// FIX A1). The sync ccusagePillars()/tokscalePillars()/tokenDashPillars() loop
|
|
989
|
+
// over ALL_PLATFORMS is replaced by parallel freshVerifierPillars() calls after
|
|
990
|
+
// we know which platforms actually have local data.
|
|
991
|
+
const [platformResults] = await Promise.all([
|
|
992
|
+
Promise.allSettled(ALL_PLATFORMS.map((p) => tokenpullAny(p))),
|
|
993
|
+
]);
|
|
994
|
+
|
|
995
|
+
const boardData = await boardPromise;
|
|
996
|
+
|
|
997
|
+
// filter to platforms with actual data
|
|
998
|
+
const active = [];
|
|
999
|
+
for (let i = 0; i < ALL_PLATFORMS.length; i++) {
|
|
1000
|
+
const r = platformResults[i];
|
|
1001
|
+
if (r.status !== "fulfilled") continue;
|
|
1002
|
+
const d = r.value;
|
|
1003
|
+
const all = d.windows?.find((w) => w.window === "all");
|
|
1004
|
+
if (!all) continue;
|
|
1005
|
+
const total = (all.pillars.input ?? 0) + (all.pillars.output ?? 0);
|
|
1006
|
+
if (total === 0) continue;
|
|
1007
|
+
active.push(d);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Fetch verifiers on-demand for active platforms only (parallel, never throws).
|
|
1011
|
+
// tdPillars (token-dashboard) is claude-only and pulled from the claude fresh result.
|
|
1012
|
+
const verifierMap = {};
|
|
1013
|
+
let tdPillars = null;
|
|
1014
|
+
if (active.length > 0) {
|
|
1015
|
+
const fresh = await Promise.all(
|
|
1016
|
+
active.map((d) =>
|
|
1017
|
+
freshVerifierPillars(d.platform).catch(() => ({
|
|
1018
|
+
ccusage: null,
|
|
1019
|
+
tokscale: null,
|
|
1020
|
+
tokendash: null,
|
|
1021
|
+
})),
|
|
1022
|
+
),
|
|
1023
|
+
);
|
|
1024
|
+
for (let i = 0; i < active.length; i++) {
|
|
1025
|
+
const f = fresh[i];
|
|
1026
|
+
verifierMap[active[i].platform] = { cc: f.ccusage, ts: f.tokscale };
|
|
1027
|
+
if (active[i].platform === "claude" && f.tokendash)
|
|
1028
|
+
tdPillars = f.tokendash;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// clear the 3 loading lines (blank + header + "reading…")
|
|
1033
|
+
write(
|
|
1034
|
+
CURSOR_UP(3) +
|
|
1035
|
+
ERASE_LINE +
|
|
1036
|
+
CURSOR_UP(1) +
|
|
1037
|
+
ERASE_LINE +
|
|
1038
|
+
CURSOR_UP(1) +
|
|
1039
|
+
ERASE_LINE,
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
// ── 2. Header ─────────────────────────────────────────────────────────────
|
|
1043
|
+
const ts = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
1044
|
+
const header = ` ${gold("⊙ SigRank")} ${bold("Operator Dashboard")}`;
|
|
1045
|
+
const right = dim(`signalaf.com ${ts}`);
|
|
1046
|
+
const gap = Math.max(
|
|
1047
|
+
1,
|
|
1048
|
+
w - stripAnsi(header).length - stripAnsi(right).length,
|
|
1049
|
+
);
|
|
1050
|
+
writeln();
|
|
1051
|
+
writeln(`${header}${" ".repeat(gap)}${right}`);
|
|
1052
|
+
|
|
1053
|
+
const platformSummary = active
|
|
1054
|
+
.map((d) => {
|
|
1055
|
+
const all = d.windows.find((w) => w.window === "all");
|
|
1056
|
+
return `${cyan(d.platform)} ${dim(`${d.files ?? "?"} files · ${(all?.messages ?? 0).toLocaleString()} msgs`)}`;
|
|
1057
|
+
})
|
|
1058
|
+
.join(" ");
|
|
1059
|
+
writeln(
|
|
1060
|
+
` ${dim("Detected:")} ${platformSummary || dim("no local data found")}`,
|
|
1061
|
+
);
|
|
1062
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
1063
|
+
|
|
1064
|
+
// ── 3. Cascade table — all platforms × all windows ─────────────────────────
|
|
1065
|
+
writeln();
|
|
1066
|
+
writeln(` ${bold("Your Cascade")}`);
|
|
1067
|
+
const CH = [
|
|
1068
|
+
padEnd(dim("Platform"), 10),
|
|
1069
|
+
padEnd(dim("Win"), 5),
|
|
1070
|
+
padStart(dim("Input"), 8),
|
|
1071
|
+
padStart(dim("Output"), 8),
|
|
1072
|
+
padStart(dim("CacheW"), 8),
|
|
1073
|
+
padStart(dim("CacheR"), 9),
|
|
1074
|
+
padStart(dim("Υ Yield"), 9),
|
|
1075
|
+
padStart(dim("SNR"), 7),
|
|
1076
|
+
padStart(dim("Leverage"), 10),
|
|
1077
|
+
padStart(dim("Vel"), 6),
|
|
1078
|
+
padStart(dim("10x"), 6),
|
|
1079
|
+
padEnd(dim("Class"), 13),
|
|
1080
|
+
];
|
|
1081
|
+
writeln(` ${CH.join(" ")}`);
|
|
1082
|
+
writeln(` ${dim("·".repeat(Math.min(w - 4, 110)))}`);
|
|
1083
|
+
|
|
1084
|
+
const WINS = ["7d", "30d", "90d", "all"];
|
|
1085
|
+
|
|
1086
|
+
// helper to render one platform/combined row
|
|
1087
|
+
const renderCascadeRow = (label, labelColorFn, winKey, p, est = false) => {
|
|
1088
|
+
const cas = cascadeFromPillars(p);
|
|
1089
|
+
if (!cas) return;
|
|
1090
|
+
const clsFn = CLASS_COLOR[cas.class] ?? ((s) => s);
|
|
1091
|
+
const cols = [
|
|
1092
|
+
padEnd(labelColorFn(label), 10),
|
|
1093
|
+
padEnd(dim(winKey), 5),
|
|
1094
|
+
padStart(est ? dim("~") + fmtTokens(p.input) : fmtTokens(p.input), 8),
|
|
1095
|
+
padStart(fmtTokens(p.output), 8),
|
|
1096
|
+
padStart(
|
|
1097
|
+
p.cacheCreate > 0
|
|
1098
|
+
? est
|
|
1099
|
+
? dim("~") + fmtTokens(p.cacheCreate)
|
|
1100
|
+
: fmtTokens(p.cacheCreate)
|
|
1101
|
+
: dim("—"),
|
|
1102
|
+
8,
|
|
1103
|
+
),
|
|
1104
|
+
padStart(p.cacheRead > 0 ? fmtTokens(p.cacheRead) : dim("—"), 9),
|
|
1105
|
+
padStart(
|
|
1106
|
+
cas.yield != null
|
|
1107
|
+
? cas.yield > 10000
|
|
1108
|
+
? gold(fmtYield(cas.yield))
|
|
1109
|
+
: fmtYield(cas.yield)
|
|
1110
|
+
: "—",
|
|
1111
|
+
9,
|
|
1112
|
+
),
|
|
1113
|
+
padStart(fmtSNR(cas.snr), 7),
|
|
1114
|
+
padStart(cas.leverage != null ? `${fmtLev(cas.leverage)}×` : "—", 10),
|
|
1115
|
+
padStart(cas.velocity != null ? cas.velocity.toFixed(2) : "—", 6),
|
|
1116
|
+
padStart(cas.dev10x != null ? cas.dev10x.toFixed(2) : "—", 6),
|
|
1117
|
+
padEnd(clsFn(cas.class), 13),
|
|
1118
|
+
];
|
|
1119
|
+
writeln(` ${cols.join(" ")}`);
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
// per-platform rows
|
|
1123
|
+
for (const d of active) {
|
|
1124
|
+
const isFirst = (winKey) =>
|
|
1125
|
+
winKey === "7d" ||
|
|
1126
|
+
(winKey === "30d" &&
|
|
1127
|
+
!d.windows?.find(
|
|
1128
|
+
(w) => w.window === "7d" && w.pillars.input + w.pillars.output > 0,
|
|
1129
|
+
));
|
|
1130
|
+
for (const winKey of WINS) {
|
|
1131
|
+
const wdata = d.windows?.find((ww) => ww.window === winKey);
|
|
1132
|
+
if (!wdata) continue;
|
|
1133
|
+
renderCascadeRow(
|
|
1134
|
+
d.platform,
|
|
1135
|
+
(s) => (isFirst(winKey) ? cyan(s) : dim(s)),
|
|
1136
|
+
winKey,
|
|
1137
|
+
wdata.pillars,
|
|
1138
|
+
d.estimated === true,
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
writeln();
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// combined row (only when 2+ platforms active)
|
|
1145
|
+
if (active.length > 1) {
|
|
1146
|
+
const combinedLabel = active.map((d) => d.platform).join("+");
|
|
1147
|
+
const hasEstimated = active.some((d) => d.estimated === true);
|
|
1148
|
+
let isFirstWin = true;
|
|
1149
|
+
for (const winKey of WINS) {
|
|
1150
|
+
const combinedP = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
1151
|
+
let hasData = false;
|
|
1152
|
+
for (const d of active) {
|
|
1153
|
+
const wdata = d.windows?.find((ww) => ww.window === winKey);
|
|
1154
|
+
if (!wdata) continue;
|
|
1155
|
+
const p = wdata.pillars;
|
|
1156
|
+
if (p.input + p.output === 0) continue;
|
|
1157
|
+
combinedP.input += p.input ?? 0;
|
|
1158
|
+
combinedP.output += p.output ?? 0;
|
|
1159
|
+
combinedP.cacheCreate += p.cacheCreate ?? 0;
|
|
1160
|
+
combinedP.cacheRead += p.cacheRead ?? 0;
|
|
1161
|
+
hasData = true;
|
|
1162
|
+
}
|
|
1163
|
+
if (!hasData) continue;
|
|
1164
|
+
renderCascadeRow(
|
|
1165
|
+
combinedLabel,
|
|
1166
|
+
(s) => (isFirstWin ? bold(cyan(s)) : dim(s)),
|
|
1167
|
+
winKey,
|
|
1168
|
+
combinedP,
|
|
1169
|
+
hasEstimated,
|
|
1170
|
+
);
|
|
1171
|
+
isFirstWin = false;
|
|
1172
|
+
}
|
|
1173
|
+
writeln();
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// ── 4. Token Pillars — per-platform verification + combined ──────────────
|
|
1177
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
1178
|
+
writeln();
|
|
1179
|
+
writeln(
|
|
1180
|
+
` ${bold("Token Pillars")} ${dim("(all-time · per-platform verification)")}`,
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
const PCOLS = [
|
|
1184
|
+
padEnd(dim("Source"), 14),
|
|
1185
|
+
padStart(dim("Input"), 10),
|
|
1186
|
+
padStart(dim("Output"), 10),
|
|
1187
|
+
padStart(dim("Cache Write"), 12),
|
|
1188
|
+
padStart(dim("Cache Read"), 12),
|
|
1189
|
+
padStart(dim("Total"), 10),
|
|
1190
|
+
];
|
|
1191
|
+
writeln(` ${PCOLS.join(" ")}`);
|
|
1192
|
+
writeln(` ${dim("·".repeat(Math.min(w - 4, 74)))}`);
|
|
1193
|
+
|
|
1194
|
+
const printPillarRow = (label, colorFn, p, note = "") => {
|
|
1195
|
+
if (!p) return;
|
|
1196
|
+
const i = p.input ?? 0;
|
|
1197
|
+
const o = p.output ?? 0;
|
|
1198
|
+
const cw = p.cacheCreate ?? 0;
|
|
1199
|
+
const cr = p.cacheRead ?? 0;
|
|
1200
|
+
const total = i + o + cw + cr;
|
|
1201
|
+
const cols = [
|
|
1202
|
+
padEnd(colorFn(label), 14),
|
|
1203
|
+
padStart(fmtTokens(i), 10),
|
|
1204
|
+
padStart(fmtTokens(o), 10),
|
|
1205
|
+
padStart(cw > 0 ? fmtTokens(cw) : dim("—"), 12),
|
|
1206
|
+
padStart(cr > 0 ? fmtTokens(cr) : dim("—"), 12),
|
|
1207
|
+
padStart(fmtTokens(total), 10),
|
|
1208
|
+
];
|
|
1209
|
+
writeln(` ${cols.join(" ")}${note ? " " + dim(note) : ""}`);
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
// ── Per-platform blocks ─────────────────────────────────────────────────
|
|
1213
|
+
for (const d of active) {
|
|
1214
|
+
const all = d.windows?.find((ww) => ww.window === "all");
|
|
1215
|
+
if (!all) continue;
|
|
1216
|
+
const v = verifierMap[d.platform] ?? {};
|
|
1217
|
+
const estMarker = d.estimated ? dim(" ~ estimated") : "";
|
|
1218
|
+
printPillarRow(d.platform, cyan, all.pillars, `tokenpull${estMarker}`);
|
|
1219
|
+
if (d.estimated) {
|
|
1220
|
+
// show the estimation formula inline
|
|
1221
|
+
const ratio = d.ioRatio != null ? d.ioRatio.toFixed(3) : "?";
|
|
1222
|
+
writeln(
|
|
1223
|
+
` ${dim(` ~ input = output × ioRatio(${ratio}) cacheCreate = uncached − input cacheRead = exact`)}`,
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
if (v.cc?.all)
|
|
1227
|
+
printPillarRow(
|
|
1228
|
+
" ccusage",
|
|
1229
|
+
(s) => paint(c.green, s),
|
|
1230
|
+
v.cc.all,
|
|
1231
|
+
"ccusage CLI",
|
|
1232
|
+
);
|
|
1233
|
+
if (d.platform === "claude" && tdPillars?.all)
|
|
1234
|
+
printPillarRow(
|
|
1235
|
+
" token-dash",
|
|
1236
|
+
(s) => paint(c.magenta, s),
|
|
1237
|
+
tdPillars.all,
|
|
1238
|
+
"token-dashboard.db",
|
|
1239
|
+
);
|
|
1240
|
+
if (v.ts?.all)
|
|
1241
|
+
printPillarRow(
|
|
1242
|
+
" tokscale",
|
|
1243
|
+
(s) => paint(c.blue, s),
|
|
1244
|
+
v.ts.all,
|
|
1245
|
+
"tokscale_report.json",
|
|
1246
|
+
);
|
|
1247
|
+
if (d.estimated && (v.cc?.all || v.ts?.all)) {
|
|
1248
|
+
writeln(
|
|
1249
|
+
` ${dim(" ^ verifiers show raw uncached input (input_tokens − cached) — different field than estimated input above")}`,
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
writeln();
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// ── Combined (if more than one platform) ───────────────────────────────
|
|
1256
|
+
if (active.length > 1) {
|
|
1257
|
+
const combined = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
1258
|
+
for (const d of active) {
|
|
1259
|
+
const all = d.windows?.find((ww) => ww.window === "all");
|
|
1260
|
+
if (!all) continue;
|
|
1261
|
+
combined.input += all.pillars.input ?? 0;
|
|
1262
|
+
combined.output += all.pillars.output ?? 0;
|
|
1263
|
+
combined.cacheCreate += all.pillars.cacheCreate ?? 0;
|
|
1264
|
+
combined.cacheRead += all.pillars.cacheRead ?? 0;
|
|
1265
|
+
}
|
|
1266
|
+
// sum verifiers across all active platforms
|
|
1267
|
+
const ccCombined = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
1268
|
+
const tsCombined = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
1269
|
+
let hasCc = false,
|
|
1270
|
+
hasTs = false;
|
|
1271
|
+
for (const d of active) {
|
|
1272
|
+
const v = verifierMap[d.platform] ?? {};
|
|
1273
|
+
if (v.cc?.all) {
|
|
1274
|
+
ccCombined.input += v.cc.all.input ?? 0;
|
|
1275
|
+
ccCombined.output += v.cc.all.output ?? 0;
|
|
1276
|
+
ccCombined.cacheCreate += v.cc.all.cacheCreate ?? 0;
|
|
1277
|
+
ccCombined.cacheRead += v.cc.all.cacheRead ?? 0;
|
|
1278
|
+
hasCc = true;
|
|
1279
|
+
}
|
|
1280
|
+
if (v.ts?.all) {
|
|
1281
|
+
tsCombined.input += v.ts.all.input ?? 0;
|
|
1282
|
+
tsCombined.output += v.ts.all.output ?? 0;
|
|
1283
|
+
tsCombined.cacheCreate += v.ts.all.cacheCreate ?? 0;
|
|
1284
|
+
tsCombined.cacheRead += v.ts.all.cacheRead ?? 0;
|
|
1285
|
+
hasTs = true;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
printPillarRow(
|
|
1289
|
+
"combined",
|
|
1290
|
+
(s) => bold(s),
|
|
1291
|
+
combined,
|
|
1292
|
+
active.map((d) => d.platform).join("+"),
|
|
1293
|
+
);
|
|
1294
|
+
if (hasCc)
|
|
1295
|
+
printPillarRow(
|
|
1296
|
+
" ccusage",
|
|
1297
|
+
(s) => paint(c.green, s),
|
|
1298
|
+
ccCombined,
|
|
1299
|
+
"ccusage CLI",
|
|
1300
|
+
);
|
|
1301
|
+
if (tdPillars?.all)
|
|
1302
|
+
printPillarRow(
|
|
1303
|
+
" token-dash",
|
|
1304
|
+
(s) => paint(c.magenta, s),
|
|
1305
|
+
tdPillars.all,
|
|
1306
|
+
"token-dashboard.db",
|
|
1307
|
+
);
|
|
1308
|
+
if (hasTs)
|
|
1309
|
+
printPillarRow(
|
|
1310
|
+
" tokscale",
|
|
1311
|
+
(s) => paint(c.blue, s),
|
|
1312
|
+
tsCombined,
|
|
1313
|
+
"tokscale_report.json",
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// ── 5. Board position ─────────────────────────────────────────────────────
|
|
1318
|
+
writeln();
|
|
1319
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
1320
|
+
writeln();
|
|
1321
|
+
writeln(
|
|
1322
|
+
` ${bold("Board")} ${dim("30d window · signalaf.com · sorted by Υ Yield")}`,
|
|
1323
|
+
);
|
|
1324
|
+
|
|
1325
|
+
const entries = boardData?.operators ?? boardData?.entries ?? boardData ?? [];
|
|
1326
|
+
if (Array.isArray(entries) && entries.length > 0) {
|
|
1327
|
+
const top5 = entries.slice(0, 5);
|
|
1328
|
+
// header
|
|
1329
|
+
const BH = [
|
|
1330
|
+
padStart(dim("#"), 4),
|
|
1331
|
+
padEnd(dim("Codename"), 20),
|
|
1332
|
+
padEnd(dim("Class"), 13),
|
|
1333
|
+
padStart(dim("Υ Yield"), 9),
|
|
1334
|
+
padStart(dim("SNR"), 6),
|
|
1335
|
+
padStart(dim("Depth"), 6),
|
|
1336
|
+
padStart(dim("Tokens"), 8),
|
|
1337
|
+
padStart(dim("Force"), 6),
|
|
1338
|
+
padStart(dim("Pct"), 5),
|
|
1339
|
+
padStart(dim("7d↕"), 5),
|
|
1340
|
+
];
|
|
1341
|
+
writeln(` ${BH.join(" ")}`);
|
|
1342
|
+
writeln(` ${dim("·".repeat(Math.min(w - 4, 92)))}`);
|
|
1343
|
+
for (const e of top5) {
|
|
1344
|
+
const rank = e.rank === 1 ? gold(`#${e.rank}`) : `#${e.rank}`;
|
|
1345
|
+
const name = padEnd(trunc(e.codename ?? "—", 20), 20);
|
|
1346
|
+
const cls = padEnd(colorClass(e.class_tier ?? "—"), 13);
|
|
1347
|
+
const yld = padStart(e.yield_ != null ? fmtYield(e.yield_) : "—", 9);
|
|
1348
|
+
const snr = padStart(
|
|
1349
|
+
e.compression_ratio != null ? fmtSNR(e.compression_ratio) : "—",
|
|
1350
|
+
6,
|
|
1351
|
+
);
|
|
1352
|
+
const depth = padStart(
|
|
1353
|
+
e.session_depth != null ? e.session_depth.toFixed(1) : "—",
|
|
1354
|
+
6,
|
|
1355
|
+
);
|
|
1356
|
+
const tok = padStart(
|
|
1357
|
+
e.token_throughput != null ? fmtTokens(e.token_throughput) : "—",
|
|
1358
|
+
8,
|
|
1359
|
+
);
|
|
1360
|
+
const force = padStart(
|
|
1361
|
+
e.signal_force != null ? e.signal_force.toFixed(1) : "—",
|
|
1362
|
+
6,
|
|
1363
|
+
);
|
|
1364
|
+
const pct = padStart(e.percentile != null ? `${e.percentile}%` : "—", 5);
|
|
1365
|
+
const mv7 = padStart(fmtMove(e.movement_7d), 5);
|
|
1366
|
+
writeln(
|
|
1367
|
+
` ${padStart(rank, 4)} ${name} ${cls} ${yld} ${snr} ${depth} ${tok} ${force} ${pct} ${mv7}`,
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
if (entries.length > 5)
|
|
1371
|
+
writeln(
|
|
1372
|
+
` ${dim(` … ${entries.length - 5} more operators on signalaf.com`)}`,
|
|
1373
|
+
);
|
|
1374
|
+
} else {
|
|
1375
|
+
writeln(` ${dim(" board unavailable")}`);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// ── 6. Footer / submit prompt ────────────────────────────────────────────
|
|
1379
|
+
writeln();
|
|
1380
|
+
writeln(` ${dim("─".repeat(w - 4))}`);
|
|
1381
|
+
writeln(
|
|
1382
|
+
` ${dim("[S]")} submit to board ${dim("[B]")} open board in browser ${dim("[Q]")} quit`,
|
|
1383
|
+
);
|
|
1384
|
+
writeln();
|
|
1385
|
+
|
|
1386
|
+
// ── 7. Keypress handler ───────────────────────────────────────────────────
|
|
1387
|
+
if (process.stdin.isTTY) {
|
|
1388
|
+
process.stdin.setRawMode(true);
|
|
1389
|
+
process.stdin.resume();
|
|
1390
|
+
process.stdin.setEncoding("utf8");
|
|
1391
|
+
|
|
1392
|
+
await new Promise((resolve) => {
|
|
1393
|
+
process.stdin.on("data", async (key) => {
|
|
1394
|
+
const k = key.toLowerCase();
|
|
1395
|
+
if (k === "q" || key === "\u0003") {
|
|
1396
|
+
// q or ctrl+c
|
|
1397
|
+
resolve();
|
|
1398
|
+
} else if (k === "b") {
|
|
1399
|
+
// ASYNC FIX (2026-06-27): execFile instead of dynamic execSync import
|
|
1400
|
+
execFile(
|
|
1401
|
+
"open",
|
|
1402
|
+
["https://signalaf.com"],
|
|
1403
|
+
{ stdio: "ignore" },
|
|
1404
|
+
() => {},
|
|
1405
|
+
);
|
|
1406
|
+
resolve();
|
|
1407
|
+
} else if (k === "s") {
|
|
1408
|
+
const id = ensureIdentity();
|
|
1409
|
+
const enrolled = !!(
|
|
1410
|
+
id.codename &&
|
|
1411
|
+
id.operator_id &&
|
|
1412
|
+
id.private_key_pkcs8_b64
|
|
1413
|
+
);
|
|
1414
|
+
process.stdin.setRawMode(false);
|
|
1415
|
+
write(SHOW_CURSOR);
|
|
1416
|
+
if (enrolled) {
|
|
1417
|
+
// VERIFIED path (D7 §7): signed submit from the enrolled device. No codename
|
|
1418
|
+
// prompt — it comes from the keystore. Only signed submissions rank on the board.
|
|
1419
|
+
writeln();
|
|
1420
|
+
writeln(
|
|
1421
|
+
` ${dim("Publishing verified runs for")} ${cyan(id.codename)}${dim("…")}`,
|
|
1422
|
+
);
|
|
1423
|
+
write(HIDE_CURSOR);
|
|
1424
|
+
try {
|
|
1425
|
+
for (const d of active) {
|
|
1426
|
+
for (const ww of d.windows ?? []) {
|
|
1427
|
+
const r = await submitSignedWindow(
|
|
1428
|
+
ww.window,
|
|
1429
|
+
ww.pillars,
|
|
1430
|
+
ww.messages,
|
|
1431
|
+
id,
|
|
1432
|
+
{ platform: d.platform },
|
|
1433
|
+
);
|
|
1434
|
+
const ok = r.status === "received";
|
|
1435
|
+
writeln(
|
|
1436
|
+
` ${ok ? green("✓") : red("✗")} ${d.platform} ${ww.window}${ok ? dim(` tier=${r.verification_tier || "—"}`) : dim(` ${r.reason || r.status}`)}`,
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
writeln();
|
|
1441
|
+
writeln(
|
|
1442
|
+
` ${green("✓")} Published. Reload ${cyan(`signalaf.com/user/${id.codename}`)}`,
|
|
1443
|
+
);
|
|
1444
|
+
} catch (e) {
|
|
1445
|
+
writeln(red(` ✗ ${e.message}`));
|
|
1446
|
+
}
|
|
1447
|
+
resolve();
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
// NOT enrolled → the anonymous paste path (preview-only; does NOT rank) + a hint.
|
|
1451
|
+
writeln();
|
|
1452
|
+
writeln(
|
|
1453
|
+
` ${dim("Not enrolled — run")} ${bold("npx sigrank enroll")} ${dim("to rank as a verified operator. Anonymous paste below:")}`,
|
|
1454
|
+
);
|
|
1455
|
+
process.stdout.write(" Codename: ");
|
|
1456
|
+
let codename = "";
|
|
1457
|
+
process.stdin.on("data", async function onData(chunk) {
|
|
1458
|
+
if (chunk === "\r" || chunk === "\n") {
|
|
1459
|
+
process.stdin.removeListener("data", onData);
|
|
1460
|
+
codename = codename.trim();
|
|
1461
|
+
if (!codename) {
|
|
1462
|
+
writeln(red(" ✗ codename required"));
|
|
1463
|
+
resolve();
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
writeln();
|
|
1467
|
+
writeln(
|
|
1468
|
+
` ${dim("Submitting all windows for")} ${cyan(codename)}${dim("…")}`,
|
|
1469
|
+
);
|
|
1470
|
+
write(HIDE_CURSOR);
|
|
1471
|
+
try {
|
|
1472
|
+
for (const d of active) {
|
|
1473
|
+
const apiBase = DEFAULT_API_BASE;
|
|
1474
|
+
const WINDOW_TYPE = {
|
|
1475
|
+
"7d": "7d",
|
|
1476
|
+
"30d": "30d",
|
|
1477
|
+
"90d": "90d",
|
|
1478
|
+
all: "all_time",
|
|
1479
|
+
};
|
|
1480
|
+
for (const ww of d.windows ?? []) {
|
|
1481
|
+
const rawPaste = `${ww.pillars.input} ${ww.pillars.output} ${ww.pillars.cacheCreate} ${ww.pillars.cacheRead}`;
|
|
1482
|
+
const windowType = WINDOW_TYPE[ww.window] || ww.window;
|
|
1483
|
+
const now = new Date();
|
|
1484
|
+
const ddmmyy = `${String(now.getDate()).padStart(2, "0")}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getFullYear()).slice(-2)}`;
|
|
1485
|
+
const content_hash = `sha256:${codename}|${windowType}|${rawPaste}|${ddmmyy}`;
|
|
1486
|
+
const res = await fetch(`${apiBase}/api/v1/ingest-paste`, {
|
|
1487
|
+
method: "POST",
|
|
1488
|
+
headers: {
|
|
1489
|
+
"content-type": "application/json",
|
|
1490
|
+
accept: "application/json",
|
|
1491
|
+
"user-agent": `node/${process.version} sigrank-mcp/cli`,
|
|
1492
|
+
},
|
|
1493
|
+
body: JSON.stringify({
|
|
1494
|
+
codename,
|
|
1495
|
+
raw_paste: rawPaste,
|
|
1496
|
+
window_type: windowType,
|
|
1497
|
+
telemetry: { platform: { primary: d.platform } },
|
|
1498
|
+
content_hash,
|
|
1499
|
+
submitted_ddmmyy: ddmmyy,
|
|
1500
|
+
submitted_at: now.toISOString(),
|
|
1501
|
+
}),
|
|
1502
|
+
}).catch(() => null);
|
|
1503
|
+
const ok = res?.ok ?? false;
|
|
1504
|
+
const status = ok ? green("✓") : red("✗");
|
|
1505
|
+
writeln(
|
|
1506
|
+
` ${status} ${d.platform} ${ww.window}${!ok ? dim(` HTTP ${res?.status ?? "err"}`) : ""}`,
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
writeln();
|
|
1511
|
+
writeln(
|
|
1512
|
+
` ${green("✓")} Submitted. Visit ${cyan(`signalaf.com/user/${codename}`)}`,
|
|
1513
|
+
);
|
|
1514
|
+
} catch (e) {
|
|
1515
|
+
writeln(red(` ✗ ${e.message}`));
|
|
1516
|
+
}
|
|
1517
|
+
resolve();
|
|
1518
|
+
} else if (chunk === "\u007f") {
|
|
1519
|
+
// backspace
|
|
1520
|
+
if (codename.length > 0) {
|
|
1521
|
+
codename = codename.slice(0, -1);
|
|
1522
|
+
process.stdout.write("\b \b");
|
|
1523
|
+
}
|
|
1524
|
+
} else {
|
|
1525
|
+
codename += chunk;
|
|
1526
|
+
process.stdout.write(chunk);
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
process.stdin.resume();
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
write(SHOW_CURSOR);
|
|
1536
|
+
if (process.stdin.isTTY) {
|
|
1537
|
+
process.stdin.setRawMode(false);
|
|
1538
|
+
process.stdin.pause();
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// ── HELP ─────────────────────────────────────────────────────────────────────
|
|
1543
|
+
|
|
1544
|
+
async function showHelp() {
|
|
1545
|
+
const { createRequire } = await import("module");
|
|
1546
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
1547
|
+
writeln();
|
|
1548
|
+
writeln(` ${gold("⊙ SigRank")} ${bold("CLI")} ${dim("v" + pkg.version)}`);
|
|
1549
|
+
writeln();
|
|
1550
|
+
writeln(` ${bold("Default (no args)")}`);
|
|
1551
|
+
writeln(
|
|
1552
|
+
` ${cyan("sigrank")} unified dashboard: cascade + token pillars + board`,
|
|
1553
|
+
);
|
|
1554
|
+
writeln();
|
|
1555
|
+
writeln(` ${bold("Commands")}`);
|
|
1556
|
+
writeln(
|
|
1557
|
+
` ${cyan("enroll")} sign in: paste a connect code (get one at signalaf.com → Settings)`,
|
|
1558
|
+
);
|
|
1559
|
+
writeln(
|
|
1560
|
+
` ${cyan("submit")} publish your verified runs to the board (sign in first)`,
|
|
1561
|
+
);
|
|
1562
|
+
writeln(
|
|
1563
|
+
` ${cyan("board")} live leaderboard (refreshes every 30s)`,
|
|
1564
|
+
);
|
|
1565
|
+
writeln(
|
|
1566
|
+
` ${cyan("board --window 7d")} board for a specific window (7d, 30d, 90d, all)`,
|
|
1567
|
+
);
|
|
1568
|
+
writeln(` ${cyan("board --once")} print once and exit`);
|
|
1569
|
+
writeln(
|
|
1570
|
+
` ${cyan("compare")} raw pillar audit: tokenpull vs ccusage vs token-dash vs tokscale`,
|
|
1571
|
+
);
|
|
1572
|
+
writeln(
|
|
1573
|
+
` ${cyan("compare --platform codex")} compare for a specific platform`,
|
|
1574
|
+
);
|
|
1575
|
+
writeln(
|
|
1576
|
+
` ${cyan("tui")} full tabbed TUI: Dashboard / Compare / Board / Watch`,
|
|
1577
|
+
);
|
|
1578
|
+
writeln(
|
|
1579
|
+
` ${cyan("tui --platform codex")} TUI with a different default platform`,
|
|
1580
|
+
);
|
|
1581
|
+
writeln(
|
|
1582
|
+
` ${cyan("watch")} live tune meter — ALL active platforms × all windows, every 30s`,
|
|
1583
|
+
);
|
|
1584
|
+
writeln(
|
|
1585
|
+
` ${cyan("watch --platform codex")} watch only one platform (optional filter)`,
|
|
1586
|
+
);
|
|
1587
|
+
writeln(
|
|
1588
|
+
` ${cyan("watch --window 7d")} watch only one window (optional filter)`,
|
|
1589
|
+
);
|
|
1590
|
+
writeln(
|
|
1591
|
+
` ${cyan("proxy")} opt-in local Anthropic/OpenAI usage proxy`,
|
|
1592
|
+
);
|
|
1593
|
+
writeln(
|
|
1594
|
+
` ${cyan("proxy --port 9000")} run the proxy on a custom loopback port`,
|
|
1595
|
+
);
|
|
1596
|
+
writeln();
|
|
1597
|
+
writeln(` ${bold("Options")}`);
|
|
1598
|
+
writeln(
|
|
1599
|
+
` ${dim("--window")} 7d · 30d · 90d · all (default: 30d for board; all windows for watch)`,
|
|
1600
|
+
);
|
|
1601
|
+
writeln(
|
|
1602
|
+
` ${dim("--platform")} claude · codex · amp · gemini · opencode · goose · …`,
|
|
1603
|
+
);
|
|
1604
|
+
writeln(` ${dim("--refresh")} poll interval in seconds (default: 30)`);
|
|
1605
|
+
writeln(` ${dim("--once")} print once and exit (board only)`);
|
|
1606
|
+
writeln(` ${dim("--port")} proxy port (default: 8787)`);
|
|
1607
|
+
writeln();
|
|
1608
|
+
writeln(` ${bold("For AI clients (not typeable)")}`);
|
|
1609
|
+
writeln(
|
|
1610
|
+
` ${dim("In a piped/non-TTY context, sigrank is an MCP stdio server.")}`,
|
|
1611
|
+
);
|
|
1612
|
+
writeln(
|
|
1613
|
+
` ${dim("AI clients (Claude, Cursor, …) call its tools automatically — these are")}`,
|
|
1614
|
+
);
|
|
1615
|
+
writeln(` ${dim("NOT shell commands. Humans use the commands above.")}`);
|
|
1616
|
+
writeln();
|
|
1617
|
+
writeln(` ${bold("Examples")}`);
|
|
1618
|
+
writeln(` ${dim("sigrank")} # unified dashboard`);
|
|
1619
|
+
writeln(` ${dim("sigrank board")} # live leaderboard`);
|
|
1620
|
+
writeln(
|
|
1621
|
+
` ${dim("sigrank compare")} # pillar audit (claude)`,
|
|
1622
|
+
);
|
|
1623
|
+
writeln(` ${dim("sigrank compare --platform codex")}`);
|
|
1624
|
+
writeln(` ${dim("sigrank watch --window 7d --refresh 60")}`);
|
|
1625
|
+
writeln(` ${dim("sigrank board --window all --once")}`);
|
|
1626
|
+
writeln(` ${dim("sigrank proxy --port 9000")}`);
|
|
1627
|
+
writeln();
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
async function runProxy({ port = 8787 } = {}) {
|
|
1631
|
+
const { startProxy, defaultProxyLogPath, parseProxyPort } = await import(
|
|
1632
|
+
"../proxy.mjs"
|
|
1633
|
+
);
|
|
1634
|
+
const proxy = await startProxy({ port: parseProxyPort(port) });
|
|
1635
|
+
writeln();
|
|
1636
|
+
writeln(`SigRank proxy running on http://localhost:${proxy.port}`);
|
|
1637
|
+
writeln();
|
|
1638
|
+
writeln("Set your tool's API base URL to:");
|
|
1639
|
+
writeln(` Anthropic: http://localhost:${proxy.port}`);
|
|
1640
|
+
writeln(` OpenAI: http://localhost:${proxy.port}`);
|
|
1641
|
+
writeln();
|
|
1642
|
+
writeln(`Usage data will be captured to ${defaultProxyLogPath()}`);
|
|
1643
|
+
writeln();
|
|
1644
|
+
writeln(
|
|
1645
|
+
dim("The proxy is active only while this command is running. Press Ctrl-C to stop."),
|
|
1646
|
+
);
|
|
1647
|
+
|
|
1648
|
+
let stopping = false;
|
|
1649
|
+
const stop = async () => {
|
|
1650
|
+
if (stopping) return;
|
|
1651
|
+
stopping = true;
|
|
1652
|
+
await proxy.close();
|
|
1653
|
+
writeln("\nSigRank proxy stopped.");
|
|
1654
|
+
process.exit(0);
|
|
1655
|
+
};
|
|
1656
|
+
process.once("SIGINT", stop);
|
|
1657
|
+
process.once("SIGTERM", stop);
|
|
1658
|
+
return proxy;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// ── ENTRY POINT ───────────────────────────────────────────────────────────────
|
|
1662
|
+
|
|
1663
|
+
// ── enroll: bind this device to your operator via a web connect code (D7 §4.5) ──
|
|
1664
|
+
async function runEnroll({ label } = {}) {
|
|
1665
|
+
const id = ensureIdentity();
|
|
1666
|
+
writeln();
|
|
1667
|
+
writeln(` ${gold("⊙ SigRank")} ${bold("Connect this device")}`);
|
|
1668
|
+
writeln(` ${dim(`device ${id.device_id}`)}`);
|
|
1669
|
+
if (id.codename && id.operator_id) {
|
|
1670
|
+
writeln(
|
|
1671
|
+
` ${dim(`already enrolled as ${id.codename} — re-enrolling a bound device is rejected this slice.`)}`,
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
writeln(
|
|
1675
|
+
` ${dim("Get a code at signalaf.com → Settings → Connect a device.")}`,
|
|
1676
|
+
);
|
|
1677
|
+
writeln();
|
|
1678
|
+
|
|
1679
|
+
const { createInterface } = await import("node:readline/promises");
|
|
1680
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1681
|
+
let code;
|
|
1682
|
+
try {
|
|
1683
|
+
// D1: explicit consent prompt in TTY mode before collecting a connect code.
|
|
1684
|
+
const ok = (
|
|
1685
|
+
await rl.question(
|
|
1686
|
+
" By enrolling, you agree to the SignalAF Terms (signalaf.com/terms) and Privacy Policy (signalaf.com/privacy). Continue? [y/N] ",
|
|
1687
|
+
)
|
|
1688
|
+
)
|
|
1689
|
+
.trim()
|
|
1690
|
+
.toLowerCase();
|
|
1691
|
+
if (ok !== "y" && ok !== "yes") {
|
|
1692
|
+
writeln(dim(" Enrollment cancelled."));
|
|
1693
|
+
process.exitCode = 1;
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
code = (await rl.question(" Paste your connect code: ")).trim();
|
|
1697
|
+
} finally {
|
|
1698
|
+
rl.close();
|
|
1699
|
+
}
|
|
1700
|
+
if (!code) {
|
|
1701
|
+
writeln(red(" ✗ No code entered."));
|
|
1702
|
+
process.exitCode = 1;
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
let out;
|
|
1707
|
+
try {
|
|
1708
|
+
out = await callTool("enroll", {
|
|
1709
|
+
code,
|
|
1710
|
+
device_label: typeof label === "string" ? label : undefined,
|
|
1711
|
+
});
|
|
1712
|
+
} catch (e) {
|
|
1713
|
+
writeln(red(` ✗ ${e.message}`));
|
|
1714
|
+
process.exitCode = 1;
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
if (out.status === "enrolled") {
|
|
1719
|
+
writeln();
|
|
1720
|
+
writeln(
|
|
1721
|
+
` ${green("✓")} Signed in as ${cyan(out.codename || "(operator)")}.`,
|
|
1722
|
+
);
|
|
1723
|
+
writeln(
|
|
1724
|
+
` ${dim("Your runs now cascade to the board. Publish with ")}${bold("npx sigrank submit")}${dim(" (one-shot) or ")}${bold("npx sigrank watch --submit")}${dim(" (continuous).")}`,
|
|
1725
|
+
);
|
|
1726
|
+
writeln(` ${dim(`identity: ${keystorePath()}`)}`);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
const msg =
|
|
1731
|
+
{
|
|
1732
|
+
code_invalid:
|
|
1733
|
+
"That code is invalid, expired, or already used — generate a fresh one.",
|
|
1734
|
+
device_already_enrolled:
|
|
1735
|
+
'This device is already enrolled. Need a new key? Click "New key" at signalaf.com → Settings, then paste it here.',
|
|
1736
|
+
bad_request: "The code or device key was malformed.",
|
|
1737
|
+
rate_limited: "Too many attempts — wait a few minutes and retry.",
|
|
1738
|
+
persistence_unavailable:
|
|
1739
|
+
"Enrollment is temporarily unavailable — try again shortly.",
|
|
1740
|
+
}[out.reason] || `Enrollment failed (${out.reason || "unknown"}).`;
|
|
1741
|
+
writeln(red(` ✗ ${msg}`));
|
|
1742
|
+
process.exitCode = 1;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// ── submit: publish your verified (signed) runs to the board (D7 §5/§8) ─────────
|
|
1746
|
+
async function runSubmit({ platform = "claude", window } = {}) {
|
|
1747
|
+
const id = ensureIdentity();
|
|
1748
|
+
if (!id.codename || !id.operator_id) {
|
|
1749
|
+
writeln(
|
|
1750
|
+
red(" ✗ This device is not enrolled. Run `npx sigrank enroll` first."),
|
|
1751
|
+
);
|
|
1752
|
+
process.exitCode = 1;
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
writeln();
|
|
1756
|
+
writeln(
|
|
1757
|
+
` ${gold("⊙ SigRank")} ${bold("Publishing verified runs")} ${dim(`as ${id.codename}`)}`,
|
|
1758
|
+
);
|
|
1759
|
+
writeln(` ${dim("reading local token logs + signing…")}`);
|
|
1760
|
+
|
|
1761
|
+
let out;
|
|
1762
|
+
try {
|
|
1763
|
+
out = await callTool("submit_verified", {
|
|
1764
|
+
platform,
|
|
1765
|
+
window: typeof window === "string" ? window : undefined,
|
|
1766
|
+
dry_run: process.argv.includes("--dry-run"),
|
|
1767
|
+
});
|
|
1768
|
+
} catch (e) {
|
|
1769
|
+
writeln(red(` ✗ ${e.message}`));
|
|
1770
|
+
process.exitCode = 1;
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
if (out.status === "not_enrolled") {
|
|
1774
|
+
writeln(red(" ✗ Not enrolled — run `npx sigrank enroll`."));
|
|
1775
|
+
process.exitCode = 1;
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
writeln();
|
|
1780
|
+
let anyRanked = false;
|
|
1781
|
+
let anyReceivedNotRanked = false;
|
|
1782
|
+
for (const w of out.windows || []) {
|
|
1783
|
+
const label = String(w.window).padEnd(8);
|
|
1784
|
+
if (w.status === "dry_run") {
|
|
1785
|
+
writeln(
|
|
1786
|
+
` ${gold("◇")} ${bold(label)} ${gold("DRY RUN")} ${dim("· nothing sent")}`,
|
|
1787
|
+
);
|
|
1788
|
+
} else if (w.ranked) {
|
|
1789
|
+
anyRanked = true;
|
|
1790
|
+
writeln(
|
|
1791
|
+
` ${green("✓")} ${bold(label)} ${green("RANKED")} ${dim("· live on the board")}`,
|
|
1792
|
+
);
|
|
1793
|
+
} else if (w.status === "received") {
|
|
1794
|
+
anyReceivedNotRanked = true;
|
|
1795
|
+
writeln(
|
|
1796
|
+
` ${gold("⚠")} ${bold(label)} ${gold("received · NOT ranked")} ${dim(`(tier=${w.verification_tier || "—"})`)}`,
|
|
1797
|
+
);
|
|
1798
|
+
} else {
|
|
1799
|
+
writeln(
|
|
1800
|
+
` ${red("✗")} ${bold(label)} ${dim(w.reason || w.status || "failed")}`,
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
writeln();
|
|
1805
|
+
if (anyRanked) {
|
|
1806
|
+
writeln(
|
|
1807
|
+
` ${dim("Your board row:")} ${cyan(`signalaf.com/user/${id.codename}`)}`,
|
|
1808
|
+
);
|
|
1809
|
+
} else if (anyReceivedNotRanked) {
|
|
1810
|
+
writeln(
|
|
1811
|
+
` ${gold("Received, but NOT ranked")} ${dim("— submission flagged by preflight checks (extreme cache ratio, implausible cadence, etc.). Your data is stored but not ranked. Check the flags above for details.")}`,
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
export async function runCli(argv) {
|
|
1817
|
+
const args = argv.slice(2); // strip 'node' + script path
|
|
1818
|
+
const cmd = args[0];
|
|
1819
|
+
|
|
1820
|
+
// parse --key value AND --key=value flags. E4: validate against the known set and warn
|
|
1821
|
+
// (don't crash) on anything unknown, so a typo'd flag is visible instead of silently ignored.
|
|
1822
|
+
const KNOWN_FLAGS = new Set([
|
|
1823
|
+
"window",
|
|
1824
|
+
"platform",
|
|
1825
|
+
"refresh",
|
|
1826
|
+
"once",
|
|
1827
|
+
"submit",
|
|
1828
|
+
"compare",
|
|
1829
|
+
"label",
|
|
1830
|
+
"dry-run",
|
|
1831
|
+
"output",
|
|
1832
|
+
"cache-read",
|
|
1833
|
+
"combined-input",
|
|
1834
|
+
"port",
|
|
1835
|
+
]);
|
|
1836
|
+
const flags = {};
|
|
1837
|
+
for (let i = 1; i < args.length; i++) {
|
|
1838
|
+
const a = args[i];
|
|
1839
|
+
if (!a.startsWith("--")) continue;
|
|
1840
|
+
const body = a.slice(2);
|
|
1841
|
+
let key, val;
|
|
1842
|
+
const eq = body.indexOf("=");
|
|
1843
|
+
if (eq !== -1) {
|
|
1844
|
+
// --flag=value form
|
|
1845
|
+
key = body.slice(0, eq);
|
|
1846
|
+
val = body.slice(eq + 1);
|
|
1847
|
+
} else {
|
|
1848
|
+
// --flag [value] form: consume the next token as the value unless it's another flag
|
|
1849
|
+
key = body;
|
|
1850
|
+
val = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : true;
|
|
1851
|
+
}
|
|
1852
|
+
if (!key) continue;
|
|
1853
|
+
if (!KNOWN_FLAGS.has(key)) {
|
|
1854
|
+
writeln(
|
|
1855
|
+
dim(
|
|
1856
|
+
` (ignoring unknown flag --${key}; known: ${[...KNOWN_FLAGS].join(", ")})`,
|
|
1857
|
+
),
|
|
1858
|
+
);
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
flags[key] = val;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
try {
|
|
1865
|
+
if (cmd === "board") {
|
|
1866
|
+
await runBoard({
|
|
1867
|
+
window: flags.window ?? "30d",
|
|
1868
|
+
once: flags.once === true || flags.once === "true",
|
|
1869
|
+
refresh: Number(flags.refresh) || 30,
|
|
1870
|
+
});
|
|
1871
|
+
} else if (cmd === "compare") {
|
|
1872
|
+
await runCompare({ platform: flags.platform ?? "claude" });
|
|
1873
|
+
} else if (cmd === "tui") {
|
|
1874
|
+
const { runTui } = await import("./tui.mjs");
|
|
1875
|
+
await runTui({
|
|
1876
|
+
platform: flags.platform ?? "claude",
|
|
1877
|
+
window: flags.window ?? "7d",
|
|
1878
|
+
});
|
|
1879
|
+
} else if (cmd === "watch") {
|
|
1880
|
+
// A5: no --platform/--window → watch ALL active platforms × ALL windows. Pass the raw
|
|
1881
|
+
// flags through (undefined = no filter) so runWatch's auto-all mode engages by default;
|
|
1882
|
+
// a provided flag narrows the grid (back-compat).
|
|
1883
|
+
await runWatch({
|
|
1884
|
+
platform:
|
|
1885
|
+
typeof flags.platform === "string" ? flags.platform : undefined,
|
|
1886
|
+
window: typeof flags.window === "string" ? flags.window : undefined,
|
|
1887
|
+
refresh: Number(flags.refresh) || 30,
|
|
1888
|
+
submit: flags.submit === true || flags.submit === "true",
|
|
1889
|
+
});
|
|
1890
|
+
} else if (cmd === "proxy") {
|
|
1891
|
+
await runProxy({ port: flags.port ?? 8787 });
|
|
1892
|
+
} else if (cmd === "enroll") {
|
|
1893
|
+
await runEnroll({ label: flags.label });
|
|
1894
|
+
// `review` was removed from the public CLI (owner decision 2026-07-16):
|
|
1895
|
+
// the ratio review + apply tool modifies operator profiles, so it doesn't
|
|
1896
|
+
// belong in `npx sigrank`. It now lives in the private `sigadmin` MCP
|
|
1897
|
+
// server (~/Desktop/SigRank-repos/sigadmin/) as ops-review.mjs, alongside
|
|
1898
|
+
// the retire tool and the CRM/Calculate tools. See sigadmin/README.md.
|
|
1899
|
+
} else if (cmd === "submit") {
|
|
1900
|
+
await runSubmit({
|
|
1901
|
+
platform: flags.platform ?? "claude",
|
|
1902
|
+
window: flags.window,
|
|
1903
|
+
});
|
|
1904
|
+
} else if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
1905
|
+
await showHelp();
|
|
1906
|
+
} else if (cmd === "--version" || cmd === "-v") {
|
|
1907
|
+
const { createRequire } = await import("module");
|
|
1908
|
+
const req = createRequire(import.meta.url);
|
|
1909
|
+
const pkg = req("../package.json");
|
|
1910
|
+
writeln(pkg.version);
|
|
1911
|
+
} else if (!cmd || cmd === "start" || cmd === "run") {
|
|
1912
|
+
// default: full unified view
|
|
1913
|
+
await runSigRank();
|
|
1914
|
+
} else {
|
|
1915
|
+
// unknown command: show help + exit non-zero so typos fail loudly (index.mjs now
|
|
1916
|
+
// routes ALL arg'd invocations here instead of falling through to the MCP server)
|
|
1917
|
+
writeln(red(`\n ✗ unknown command: ${cmd}`));
|
|
1918
|
+
await showHelp();
|
|
1919
|
+
process.exit(2);
|
|
1920
|
+
}
|
|
1921
|
+
} catch (e) {
|
|
1922
|
+
write(SHOW_CURSOR);
|
|
1923
|
+
writeln(red(`\n ✗ ${e.message}`));
|
|
1924
|
+
process.exit(1);
|
|
1925
|
+
}
|
|
1926
|
+
}
|