sigrank 0.18.3 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tools.mjs +161 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
5
5
  "description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
6
6
  "mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
package/tools.mjs CHANGED
@@ -1362,6 +1362,36 @@ export const TOOLS = [
1362
1362
  },
1363
1363
  },
1364
1364
  },
1365
+ {
1366
+ name: "tokscale_breakdown",
1367
+ description:
1368
+ "Show a per-model breakdown of your token usage across all platforms detected by tokscale. Models under the threshold (default 1%) are lumped into 'other' to keep the display clean. Useful for seeing which models you actually use per platform (e.g. claude-opus-4-8 76%, claude-sonnet-4-6 11%, other 0.3%). Returns { platform: [{ model, input, output, cacheRead, cacheCreate, pct }] }.",
1369
+ annotations: {
1370
+ title: "Model breakdown",
1371
+ ...ANNOTATIONS.readOnlyHint,
1372
+ ...ANNOTATIONS.openWorldHint,
1373
+ },
1374
+ inputSchema: {
1375
+ type: "object",
1376
+ properties: {
1377
+ threshold: {
1378
+ type: "number",
1379
+ description:
1380
+ "Models below this fraction of their platform's total tokens are lumped into 'other'. Default 0.01 (1%). Set 0 to see every model.",
1381
+ default: 0.01,
1382
+ },
1383
+ },
1384
+ },
1385
+ outputSchema: {
1386
+ type: "object",
1387
+ properties: {
1388
+ platforms: {
1389
+ type: "object",
1390
+ description: "Map of platform name → array of { model, input, output, cacheRead, cacheCreate, pct }",
1391
+ },
1392
+ },
1393
+ },
1394
+ },
1365
1395
  ];
1366
1396
 
1367
1397
  // tokenpull window key → the board's window_type enum.
@@ -1412,26 +1442,54 @@ async function _tokscaleDetectClients() {
1412
1442
  ? data
1413
1443
  : [];
1414
1444
  if (!entries.length) return [];
1415
- // Map tokscale client names → our platform names
1445
+ // Complete tokscale client → our platform name mapping.
1446
+ // Tokscale supports 40 clients (as of v3.1.0). We have native adapters for 16;
1447
+ // the rest map to "other" (user-supplied JSON via SIGRANK_OTHER_PATH).
1448
+ // Source: https://github.com/junhoyeo/tokscale
1416
1449
  const TOKSCALE_CLIENT_MAP = {
1450
+ // Native adapters
1417
1451
  claude: "claude",
1418
- "devin-cli": "devin",
1419
1452
  codex: "codex",
1420
- copilot: "copilot",
1453
+ "devin-cli": "devin",
1454
+ "devin-desktop": "devin",
1421
1455
  gemini: "gemini",
1422
1456
  amp: "amp",
1423
1457
  kimi: "kimi",
1424
1458
  qwen: "qwen",
1425
1459
  goose: "goose",
1426
1460
  kilo: "kilo",
1461
+ kilocode: "kilo",
1427
1462
  hermes: "hermes",
1428
1463
  droid: "droid",
1429
1464
  codebuff: "codebuff",
1465
+ copilot: "copilot",
1430
1466
  opencode: "opencode",
1431
1467
  openclaw: "openclaw",
1432
1468
  pi: "pi",
1433
- cline: "other", // no native adapter yet
1434
- "antigravity-cli": "other", // no native adapter yet
1469
+ // No native adapter yet → "other"
1470
+ cursor: "other",
1471
+ roocode: "other",
1472
+ mux: "other",
1473
+ crush: "other",
1474
+ antigravity: "other",
1475
+ "antigravity-cli": "other",
1476
+ zed: "other",
1477
+ kiro: "other",
1478
+ trae: "other",
1479
+ warp: "other",
1480
+ cline: "other",
1481
+ gjc: "other",
1482
+ grok: "other",
1483
+ jcode: "other",
1484
+ commandcode: "other",
1485
+ micode: "other",
1486
+ junie: "other",
1487
+ zcode: "other",
1488
+ opencodereview: "other",
1489
+ codebuddy: "other",
1490
+ workbuddy: "other",
1491
+ // synthetic = re-attributed from other sources, skip
1492
+ synthetic: null,
1435
1493
  };
1436
1494
  const clients = new Set();
1437
1495
  for (const e of entries) {
@@ -1440,12 +1498,102 @@ async function _tokscaleDetectClients() {
1440
1498
  const input = Number(e.input) || 0;
1441
1499
  const output = Number(e.output) || 0;
1442
1500
  if (input + output === 0) continue;
1443
- const platform = TOKSCALE_CLIENT_MAP[e.client] || "other";
1444
- clients.add(platform);
1501
+ const platform = TOKSCALE_CLIENT_MAP[e.client];
1502
+ if (platform === null) continue; // synthetic → skip
1503
+ clients.add(platform || "other");
1445
1504
  }
1446
1505
  return [...clients];
1447
1506
  }
1448
1507
 
1508
+ /**
1509
+ * Run `tokscale models --json` and return a model-level breakdown per platform.
1510
+ * Models under `threshold` (default 1%) of their platform's total are lumped
1511
+ * into "other" to keep the display clean.
1512
+ *
1513
+ * Returns: { platform: [{ model, input, output, cacheRead, cacheCreate, pct }] }
1514
+ */
1515
+ export async function tokscaleModelBreakdown(threshold = 0.01) {
1516
+ const raw = await execFileAsync("tokscale", ["models", "--json"], 60000);
1517
+ const data = JSON.parse(raw);
1518
+ const entries = Array.isArray(data?.entries)
1519
+ ? data.entries
1520
+ : Array.isArray(data)
1521
+ ? data
1522
+ : [];
1523
+ if (!entries.length) return {};
1524
+
1525
+ const TOKSCALE_CLIENT_MAP = {
1526
+ claude: "claude", codex: "codex", "devin-cli": "devin",
1527
+ "devin-desktop": "devin", gemini: "gemini", amp: "amp",
1528
+ kimi: "kimi", qwen: "qwen", goose: "goose", kilo: "kilo",
1529
+ kilocode: "kilo", hermes: "hermes", droid: "droid",
1530
+ codebuff: "codebuff", copilot: "copilot", opencode: "opencode",
1531
+ openclaw: "openclaw", pi: "pi",
1532
+ cursor: "other", roocode: "other", mux: "other", crush: "other",
1533
+ antigravity: "other", "antigravity-cli": "other", zed: "other",
1534
+ kiro: "other", trae: "other", warp: "other", cline: "other",
1535
+ gjc: "other", grok: "other", jcode: "other", commandcode: "other",
1536
+ micode: "other", junie: "other", zcode: "other",
1537
+ opencodereview: "other", codebuddy: "other", workbuddy: "other",
1538
+ synthetic: null,
1539
+ };
1540
+
1541
+ // Group entries by our platform name
1542
+ const byPlatform = {};
1543
+ for (const e of entries) {
1544
+ if (!e || !e.client) continue;
1545
+ if (e.model === "<synthetic>" || e.model === "unknown") continue;
1546
+ const input = Number(e.input) || 0;
1547
+ const output = Number(e.output) || 0;
1548
+ if (input + output === 0) continue;
1549
+ const platform = TOKSCALE_CLIENT_MAP[e.client];
1550
+ if (platform === null) continue;
1551
+ const p = platform || "other";
1552
+ if (!byPlatform[p]) byPlatform[p] = [];
1553
+ byPlatform[p].push({
1554
+ model: e.model,
1555
+ input,
1556
+ output,
1557
+ cacheRead: Number(e.cacheRead) || 0,
1558
+ cacheCreate: Number(e.cacheWrite) || 0,
1559
+ });
1560
+ }
1561
+
1562
+ // For each platform: sort by total tokens desc, lump small models into "other"
1563
+ const result = {};
1564
+ for (const [platform, models] of Object.entries(byPlatform)) {
1565
+ const total = models.reduce(
1566
+ (s, m) => s + m.input + m.output + m.cacheRead + m.cacheCreate,
1567
+ 0,
1568
+ );
1569
+ if (total === 0) continue;
1570
+ const sorted = models.sort(
1571
+ (a, b) =>
1572
+ b.input + b.output + b.cacheRead + b.cacheCreate -
1573
+ (a.input + a.output + a.cacheRead + a.cacheCreate),
1574
+ );
1575
+ const big = [];
1576
+ const small = { model: "other", input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
1577
+ for (const m of sorted) {
1578
+ const t = m.input + m.output + m.cacheRead + m.cacheCreate;
1579
+ if (t / total < threshold) {
1580
+ small.input += m.input;
1581
+ small.output += m.output;
1582
+ small.cacheRead += m.cacheRead;
1583
+ small.cacheCreate += m.cacheCreate;
1584
+ } else {
1585
+ big.push({ ...m, pct: t / total });
1586
+ }
1587
+ }
1588
+ if (small.input + small.output + small.cacheRead + small.cacheCreate > 0) {
1589
+ const t = small.input + small.output + small.cacheRead + small.cacheCreate;
1590
+ big.push({ ...small, pct: t / total });
1591
+ }
1592
+ result[platform] = big;
1593
+ }
1594
+ return result;
1595
+ }
1596
+
1449
1597
  /** Pull a specific set of platforms in parallel, filter to active ones. */
1450
1598
  async function _pullExplicit(platforms, opts = {}) {
1451
1599
  const settled = await Promise.allSettled(
@@ -3354,6 +3502,12 @@ export async function callTool(name, args, opts = {}) {
3354
3502
  };
3355
3503
  }
3356
3504
 
3505
+ if (name === "tokscale_breakdown") {
3506
+ const threshold = Number(args?.threshold ?? 0.01);
3507
+ const platforms = await tokscaleModelBreakdown(threshold);
3508
+ return { platforms };
3509
+ }
3510
+
3357
3511
  throw new Error(`Unknown tool: ${name}`);
3358
3512
  }
3359
3513