@mauricode/token-derby 2.12.3 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/bin.js +97 -38
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Token Derby runs on the **real** output tokens your Claude Code produces — tha
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm i -g @mauricode/token-derby
|
|
14
|
+
npm i -g @mauricode/token-derby@latest
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Requires Node 20+.
|
|
@@ -64,6 +64,16 @@ The CLI sums `message.usage.output_tokens` across every `*.jsonl` under `~/.clau
|
|
|
64
64
|
|
|
65
65
|
Races can optionally also count *fresh input tokens* — i.e. `input_tokens + cache_creation_input_tokens` (your new context this turn) in addition to output. `cache_read_input_tokens` is never counted, since those reflect passive context size rather than work. The race creator opts in at `token-derby create` time; thresholds for Stampede!, Pulled Away!, and the heartbeat rate cap scale 10× in these races so the achievement cadence stays comparable.
|
|
66
66
|
|
|
67
|
+
## Stamina
|
|
68
|
+
|
|
69
|
+
Some races turn on stamina. Push a horse far above a sustainable pace and it tires — its tokens still count, but at a fraction of face value until it recovers. A normal working pace never triggers this; stamina only bites once you're running well past what the race considers sustainable.
|
|
70
|
+
|
|
71
|
+
Staying connected and easing off is how you recover. Disconnecting does not — a long gap since your last heartbeat earns the same recovery as one ordinary tick, not a bonus for the time you were away, so crashing is not a rest strategy. That's on top of the crash penalty above: you lose the tokens from the gap *and* you don't recover any faster for it.
|
|
72
|
+
|
|
73
|
+
Nothing here asks you to hold work back. A flat-out day still beats a lazy one — it just doesn't beat it by as much as the raw token count would suggest once your horse tires.
|
|
74
|
+
|
|
75
|
+
Stamina is off by default; org owners turn it on and tune it from the Race Settings tab of `token-derby web`. When it's on, the live view shows your horse's stamina as a percentage and bar, plus a multiplier once you're actually losing score to fatigue.
|
|
76
|
+
|
|
67
77
|
## Other models (Codex, Gemini)
|
|
68
78
|
|
|
69
79
|
At join you pick one **primary** model — Claude, Codex, or Gemini — counted 1:1.
|
package/dist/bin.js
CHANGED
|
@@ -252,6 +252,39 @@ function toTag(c, y) {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
// ../shared/dist/scoring.js
|
|
256
|
+
function scoredOf(horse) {
|
|
257
|
+
return horse.scored_tokens ?? horse.current_tokens;
|
|
258
|
+
}
|
|
259
|
+
var STAMINA = {
|
|
260
|
+
SUSTAINABLE_PACE: 4e3,
|
|
261
|
+
DRAIN_PER_MIN: 4,
|
|
262
|
+
MAX_DRAIN_PER_MIN: 6,
|
|
263
|
+
RECOVER_PER_MIN: 2,
|
|
264
|
+
RECOVER_TICK_CAP_MS: 9e4,
|
|
265
|
+
TAPER_FLOOR: 25,
|
|
266
|
+
TIRED_MULTIPLIER: 0.5
|
|
267
|
+
};
|
|
268
|
+
function resolveStaminaConfig(race) {
|
|
269
|
+
const c = race.stamina_config ?? {};
|
|
270
|
+
return {
|
|
271
|
+
sustainable_pace: c.sustainable_pace ?? STAMINA.SUSTAINABLE_PACE,
|
|
272
|
+
drain_per_min: c.drain_per_min ?? STAMINA.DRAIN_PER_MIN,
|
|
273
|
+
max_drain_per_min: c.max_drain_per_min ?? STAMINA.MAX_DRAIN_PER_MIN,
|
|
274
|
+
recover_per_min: c.recover_per_min ?? STAMINA.RECOVER_PER_MIN,
|
|
275
|
+
taper_floor: c.taper_floor ?? STAMINA.TAPER_FLOOR,
|
|
276
|
+
tired_multiplier: c.tired_multiplier ?? STAMINA.TIRED_MULTIPLIER
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
var STAMINA_PARAM_BOUNDS = {
|
|
280
|
+
sustainable_pace: { min: 1e3, max: 2e4, step: 250, default: STAMINA.SUSTAINABLE_PACE },
|
|
281
|
+
drain_per_min: { min: 1, max: 12, step: 1, default: STAMINA.DRAIN_PER_MIN },
|
|
282
|
+
max_drain_per_min: { min: 2, max: 20, step: 1, default: STAMINA.MAX_DRAIN_PER_MIN },
|
|
283
|
+
recover_per_min: { min: 1, max: 8, step: 1, default: STAMINA.RECOVER_PER_MIN },
|
|
284
|
+
taper_floor: { min: 10, max: 60, step: 5, default: STAMINA.TAPER_FLOOR },
|
|
285
|
+
tired_multiplier: { min: 0.2, max: 0.9, step: 0.05, default: STAMINA.TIRED_MULTIPLIER }
|
|
286
|
+
};
|
|
287
|
+
|
|
255
288
|
// src/ui/HorseSprite.tsx
|
|
256
289
|
import { Box, Text } from "ink";
|
|
257
290
|
|
|
@@ -788,8 +821,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
|
788
821
|
// src/version.ts
|
|
789
822
|
import { createRequire } from "module";
|
|
790
823
|
function readVersion() {
|
|
791
|
-
if ("2.
|
|
792
|
-
return "2.
|
|
824
|
+
if ("2.13.0".length > 0) {
|
|
825
|
+
return "2.13.0";
|
|
793
826
|
}
|
|
794
827
|
try {
|
|
795
828
|
const req = createRequire(import.meta.url);
|
|
@@ -1326,6 +1359,8 @@ async function createRaceCommand(organisationName) {
|
|
|
1326
1359
|
const counts_input = countInputRaw === "y" || countInputRaw === "yes";
|
|
1327
1360
|
const top5Raw = (await rl.question("Count only each racer's 5 most-active conversations toward their primary model's score? [y/N]: ")).trim().toLowerCase();
|
|
1328
1361
|
const primary_top5 = top5Raw === "y" || top5Raw === "yes";
|
|
1362
|
+
const staminaRaw = (await rl.question("Stamina \u2014 horses that run flat out tire and score less until they recover? [y/N]: ")).trim().toLowerCase();
|
|
1363
|
+
const stamina = staminaRaw === "y" || staminaRaw === "yes";
|
|
1329
1364
|
const resp = await createRace({
|
|
1330
1365
|
name,
|
|
1331
1366
|
start_time: start,
|
|
@@ -1334,7 +1369,8 @@ async function createRaceCommand(organisationName) {
|
|
|
1334
1369
|
...max !== void 0 ? { max_participants: max } : {},
|
|
1335
1370
|
...org ? { organisation_name: org } : {},
|
|
1336
1371
|
...counts_input ? { counts_input: true } : {},
|
|
1337
|
-
...primary_top5 ? { primary_top5: true } : {}
|
|
1372
|
+
...primary_top5 ? { primary_top5: true } : {},
|
|
1373
|
+
...stamina ? { stamina: true } : {}
|
|
1338
1374
|
});
|
|
1339
1375
|
console.log("");
|
|
1340
1376
|
console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
@@ -1353,6 +1389,9 @@ async function createRaceCommand(organisationName) {
|
|
|
1353
1389
|
if (primary_top5) {
|
|
1354
1390
|
console.log(" Primary score counts only each racer's top 5 conversations per beat.");
|
|
1355
1391
|
}
|
|
1392
|
+
if (stamina) {
|
|
1393
|
+
console.log(" Stamina on \u2014 horses running above a sustainable pace will tire.");
|
|
1394
|
+
}
|
|
1356
1395
|
console.log(` Share with participants: token-derby join ${resp.join_code}`);
|
|
1357
1396
|
return 0;
|
|
1358
1397
|
} catch (e) {
|
|
@@ -1415,7 +1454,7 @@ import { Box as Box8, Text as Text8, useApp } from "ink";
|
|
|
1415
1454
|
|
|
1416
1455
|
// src/ui/StatusScreen.tsx
|
|
1417
1456
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
1418
|
-
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1457
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1419
1458
|
var MODEL_LABELS = { claude: "Claude", codex: "Codex", gemini: "Gemini" };
|
|
1420
1459
|
function ModelList(props) {
|
|
1421
1460
|
const { primaryModel } = props;
|
|
@@ -1439,6 +1478,32 @@ function StatusScreen(props) {
|
|
|
1439
1478
|
const elapsedPct = elapsed(race);
|
|
1440
1479
|
const timeLeft = formatDuration(race.time_left_seconds);
|
|
1441
1480
|
const lvl = levelInfo((own?.xp ?? 0) + (own?.live_xp ?? 0));
|
|
1481
|
+
const divisionField = own?.division === void 0 ? [] : race.horses.filter((h) => h.division === own.division);
|
|
1482
|
+
const divisionRank = own ? divisionField.indexOf(own) + 1 : 0;
|
|
1483
|
+
const showDivision = (race.league_division_names?.length ?? 0) > 0 && divisionRank > 0;
|
|
1484
|
+
const staminaRow = race.stamina === true ? staminaLine(own, race) : null;
|
|
1485
|
+
const rows = [
|
|
1486
|
+
{ label: "Tokens (race):", value: String(own?.final_scored_tokens ?? (own ? scoredOf(own) : 0)) },
|
|
1487
|
+
...staminaRow ? [staminaRow] : [],
|
|
1488
|
+
{ label: "Position:", value: `${own?.rank ?? "\u2014"} of ${race.horses.length}` },
|
|
1489
|
+
...showDivision ? [{ label: "Position (Division):", value: `${divisionRank} of ${divisionField.length}` }] : [],
|
|
1490
|
+
{ label: "Leader:", value: leaderText(leader) },
|
|
1491
|
+
...showDivision ? [{ label: "Leader (Division):", value: leaderText(divisionField[0]) }] : [],
|
|
1492
|
+
{ label: "Race elapsed:", value: `${(elapsedPct * 100).toFixed(0)}% ${bar(elapsedPct, 20)}` },
|
|
1493
|
+
{ label: "Time left:", value: timeLeft },
|
|
1494
|
+
{
|
|
1495
|
+
label: "XP:",
|
|
1496
|
+
value: lvl.next_level_xp === null ? `${lvl.xp} (max level) ${bar(1, 20)}` : `${lvl.xp_into_level}/${lvl.xp_for_level} \u2192 Lvl. ${lvl.level + 1} ${bar(lvl.progress, 20)}`
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
label: "Last heartbeat:",
|
|
1500
|
+
value: /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1501
|
+
lastHeartbeatAgoSec === null ? "\u2014" : `${lastHeartbeatAgoSec}s ago`,
|
|
1502
|
+
" ",
|
|
1503
|
+
/* @__PURE__ */ jsx7(Text7, { color: lastHeartbeatOk ? "green" : "yellow", children: lastHeartbeatOk ? "\u2713" : "\u26A0" })
|
|
1504
|
+
] })
|
|
1505
|
+
}
|
|
1506
|
+
];
|
|
1442
1507
|
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [
|
|
1443
1508
|
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1444
1509
|
"\u{1F3C7} TOKEN DERBY \u2500\u2500\u2500 ",
|
|
@@ -1470,40 +1535,7 @@ function StatusScreen(props) {
|
|
|
1470
1535
|
] })
|
|
1471
1536
|
] }),
|
|
1472
1537
|
/* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1473
|
-
/* @__PURE__ */
|
|
1474
|
-
"Tokens (race): ",
|
|
1475
|
-
own?.current_tokens ?? 0
|
|
1476
|
-
] }),
|
|
1477
|
-
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1478
|
-
"Position: ",
|
|
1479
|
-
own?.rank ?? "\u2014",
|
|
1480
|
-
" of ",
|
|
1481
|
-
race.horses.length
|
|
1482
|
-
] }),
|
|
1483
|
-
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1484
|
-
"Leader: ",
|
|
1485
|
-
leader ? `${leader.name}${leader.user_name ? ` (${leader.user_name})` : ""} \u2014 ${leader.current_tokens}` : "\u2014"
|
|
1486
|
-
] }),
|
|
1487
|
-
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1488
|
-
"Race elapsed: ",
|
|
1489
|
-
(elapsedPct * 100).toFixed(0),
|
|
1490
|
-
"% ",
|
|
1491
|
-
bar(elapsedPct, 20)
|
|
1492
|
-
] }),
|
|
1493
|
-
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1494
|
-
"Time left: ",
|
|
1495
|
-
timeLeft
|
|
1496
|
-
] }),
|
|
1497
|
-
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1498
|
-
"XP: ",
|
|
1499
|
-
lvl.next_level_xp === null ? `${lvl.xp} (max level) ${bar(1, 20)}` : `${lvl.xp_into_level}/${lvl.xp_for_level} \u2192 Lvl. ${lvl.level + 1} ${bar(lvl.progress, 20)}`
|
|
1500
|
-
] }),
|
|
1501
|
-
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1502
|
-
"Last heartbeat: ",
|
|
1503
|
-
lastHeartbeatAgoSec === null ? "\u2014" : `${lastHeartbeatAgoSec}s ago`,
|
|
1504
|
-
" ",
|
|
1505
|
-
/* @__PURE__ */ jsx7(Text7, { color: lastHeartbeatOk ? "green" : "yellow", children: lastHeartbeatOk ? "\u2713" : "\u26A0" })
|
|
1506
|
-
] }),
|
|
1538
|
+
/* @__PURE__ */ jsx7(StatLines, { rows }),
|
|
1507
1539
|
stalled && /* @__PURE__ */ jsxs5(Text7, { color: "yellow", children: [
|
|
1508
1540
|
"\u26A0 ",
|
|
1509
1541
|
stallReason ?? "Can't read token usage",
|
|
@@ -1514,6 +1546,17 @@ function StatusScreen(props) {
|
|
|
1514
1546
|
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Press Ctrl+C to crash out of the race." }) })
|
|
1515
1547
|
] });
|
|
1516
1548
|
}
|
|
1549
|
+
function StatLines(props) {
|
|
1550
|
+
const width = Math.max(...props.rows.map((r) => r.label.length)) + 1;
|
|
1551
|
+
return /* @__PURE__ */ jsx7(Fragment2, { children: props.rows.map((r) => /* @__PURE__ */ jsxs5(Text7, { children: [
|
|
1552
|
+
r.label.padEnd(width),
|
|
1553
|
+
r.value
|
|
1554
|
+
] }, r.label)) });
|
|
1555
|
+
}
|
|
1556
|
+
function leaderText(h) {
|
|
1557
|
+
if (!h) return "\u2014";
|
|
1558
|
+
return `${h.name}${h.user_name ? ` (${h.user_name})` : ""} \u2014 ${h.final_scored_tokens ?? scoredOf(h)}`;
|
|
1559
|
+
}
|
|
1517
1560
|
function elapsed(race) {
|
|
1518
1561
|
const start = new Date(race.start_time).getTime();
|
|
1519
1562
|
const end = new Date(race.end_time).getTime();
|
|
@@ -1526,6 +1569,22 @@ function bar(pct, width) {
|
|
|
1526
1569
|
const filled = Math.round(pct * width);
|
|
1527
1570
|
return "\u2593".repeat(filled) + "\u2591".repeat(width - filled);
|
|
1528
1571
|
}
|
|
1572
|
+
function staminaLine(own, race) {
|
|
1573
|
+
const stamina = own?.stamina ?? 100;
|
|
1574
|
+
const cfg = resolveStaminaConfig(race);
|
|
1575
|
+
const floor = cfg.taper_floor;
|
|
1576
|
+
const band = stamina > 50 ? "green" : stamina >= floor ? "amber" : "red";
|
|
1577
|
+
const color = band === "green" ? "green" : band === "amber" ? "yellow" : "red";
|
|
1578
|
+
const pct = Math.max(0, Math.min(1, stamina / 100));
|
|
1579
|
+
const multiplier = band === "red" ? cfg.tired_multiplier + (1 - cfg.tired_multiplier) * (stamina / floor) : null;
|
|
1580
|
+
return {
|
|
1581
|
+
label: "Stamina:",
|
|
1582
|
+
value: /* @__PURE__ */ jsxs5(Text7, { color, children: [
|
|
1583
|
+
`${Math.round(stamina)}% ${bar(pct, 20)}`,
|
|
1584
|
+
multiplier !== null ? ` \xD7${multiplier.toFixed(2)}` : ""
|
|
1585
|
+
] })
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1529
1588
|
function statusColor(status) {
|
|
1530
1589
|
if (status === "live") return "green";
|
|
1531
1590
|
if (status === "pending") return "yellow";
|