@mauricode/token-derby 3.0.0 → 3.1.2
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 +5 -0
- package/dist/bin.js +234 -83
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,4 +103,9 @@ at usage you didn't produce.
|
|
|
103
103
|
- `TOKEN_DERBY_API_BASE` — override the API base URL (default: `https://token-derby.mauricode.co.uk/api`)
|
|
104
104
|
- `TOKEN_DERBY_HOME` — override the data directory (default: `~/.token-derby`)
|
|
105
105
|
- `TOKEN_DERBY_CLAUDE_DIR` — override the transcripts directory (default: `~/.claude/projects`)
|
|
106
|
+
- `CLAUDE_CONFIG_DIR` — Claude Code's own config override. When set, transcripts are read from `$CLAUDE_CONFIG_DIR/projects`. `TOKEN_DERBY_CLAUDE_DIR` still wins.
|
|
107
|
+
|
|
108
|
+
Token Derby counts usage from this machine's filesystem only. If Claude Code runs
|
|
109
|
+
in a container, over SSH, or on another machine, join the race from there — `join`
|
|
110
|
+
warns before entering a race whose primary model has no transcripts to read.
|
|
106
111
|
- **Top-5 conversations (primary):** a race can be created so that only each racer's **5 most-active conversations per heartbeat** count toward their **primary** model's score (secondaries unaffected). The race creator opts in at `token-derby create` (prompt) or, for organisation-scheduled races, via the "Primary top-5 cap" option on the schedule tab of `token-derby web`. Off by default (every conversation counts).
|
package/dist/bin.js
CHANGED
|
@@ -199,9 +199,31 @@ var HATS = [
|
|
|
199
199
|
// Claim-only: awarded by an admin, never from a roll. Only the A channel cycles,
|
|
200
200
|
// so the inset motif shifts colour while the cap body stays a static rust brown.
|
|
201
201
|
// The body colour is kept out of the frame list so the motif never merges into it.
|
|
202
|
-
{ id: "contributor_cap", name: "Contributor Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: [".....Q.....", "....QQQ....", "...QQQQQ...", "..QAAAQQQ..", "..QQAQQQQ..", ".QQQAQAAQQ.", ".QQQAQAQAQ.", ".QQQQQAQAQ.", ".QQQQQAAQQ.", ".QQQQQQQQQ."], colors: { A: "#21138b", Q: "#6f2801" }, animation: { type: "cycle", frames: ["#FF8C42", "#E0457B", "#8B5CF6", "#3B82F6", "#22D3EE", "#34D399", "#A3E635", "#FBBF24"], fps: 6 }, rollable: false }
|
|
202
|
+
{ id: "contributor_cap", name: "Contributor Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: [".....Q.....", "....QQQ....", "...QQQQQ...", "..QAAAQQQ..", "..QQAQQQQ..", ".QQQAQAAQQ.", ".QQQAQAQAQ.", ".QQQQQAQAQ.", ".QQQQQAAQQ.", ".QQQQQQQQQ."], colors: { A: "#21138b", Q: "#6f2801" }, animation: { type: "cycle", frames: ["#FF8C42", "#E0457B", "#8B5CF6", "#3B82F6", "#22D3EE", "#34D399", "#A3E635", "#FBBF24"], fps: 6 }, rollable: false },
|
|
203
|
+
// ── LIMITED (6) ────────────────────────────────────────────────────────
|
|
204
|
+
// London series. Claim-only by construction: limited is absent from
|
|
205
|
+
// TIER_WEIGHTS, so no roll can reach this tier.
|
|
206
|
+
{ id: "bearskin", name: "Bearskin", rarity: "limited", width: 11, anchor_x: 23, rows: ["...CCCCC.Q.", "..CAAAAACQ.", "..AAAAAACQ.", "..AAAAAAAQ.", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..DDDDDDD..", "..AAAAAAA.."], variants: [{ A: "#23232A", Q: "#C8102E", C: "#15151A", D: "#FFD300" }], rollable: false },
|
|
207
|
+
{ id: "roundel_cap", name: "Roundel Cap", rarity: "limited", width: 11, anchor_x: 23, rows: ["...........", "..AAAAAA...", ".AAAAAAAA..", ".AAQQQQQAA.", ".AAQDDDQAA.", ".AFFFFFFFA.", ".AAQDDDQAA.", ".AAQQQQQAA.", ".AAAAAAAAA.", ".FFFFFFFFFF"], variants: [{ A: "#0B3B8C", Q: "#E32017", C: "#1050B8", D: "#F4F1E8", F: "#072A66" }], rollable: false },
|
|
208
|
+
{ id: "union_topper", name: "Union Topper", rarity: "limited", width: 11, anchor_x: 23, rows: ["..CCCCCCC..", "..AQAAAQA..", "..AAQDQAA..", "..QQQDQQQ..", "..DDDDDDD..", "..QQQDQQQ..", "..AAQDQAA..", "..AQAAAQA..", ".CCCCCCCCC.", ".FFFFFFFFF."], variants: [{ A: "#0B2C6B", Q: "#F2F6FA", C: "#081F4D", D: "#C8102E", F: "#061838" }], rollable: false },
|
|
209
|
+
{ id: "crown_jewels", name: "Crown Jewels", rarity: "limited", width: 11, anchor_x: 23, rows: ["...........", "....QQQ....", ".....Q.....", "..QQ.Q.QQ..", ".Q..QQQ..Q.", ".QCCCQCCCQ.", ".QCCCQCCCQ.", ".QQCCQCCQQ.", "..QQQQQQQ..", "..AAAAAAA.."], variants: [{ A: "#f5f5f5", Q: "#ffd700", C: "#5d0a8a" }], rollable: false },
|
|
210
|
+
{ id: "black_cab", name: "Black Cab", rarity: "limited", width: 11, anchor_x: 22, rows: ["...........", "....CCC....", "..AAAAAA...", "..AQQAQA...", ".AAQQAQAAA.", ".AAAAAAAAAF", "AAAAAAAAAAF", "ADDAAAADDA.", ".DD....DD..", "..........."], variants: [{ A: "#141416", Q: "#8FB8D8", C: "#F5C518", D: "#3A3A40", F: "#D9DDE3" }], rollable: false },
|
|
211
|
+
{ id: "london_bus", name: "London Bus", rarity: "limited", width: 11, anchor_x: 22, rows: ["...........", "...........", ".AAAAAAAAAA", "AAAAAAAAAAA", "ACCCACCCAAC", "AAAAAAAAACC", "ACCCACCCACC", "AAAAAAAAAAA", "AQQAAAAAQQA", ".QQ.....QQ."], variants: [{ A: "#e32402", Q: "#0d0c0c", C: "#dcaf09" }], rollable: false }
|
|
203
212
|
];
|
|
204
213
|
|
|
214
|
+
// ../shared/dist/hat-shape.js
|
|
215
|
+
function isAnimatedHat(hat) {
|
|
216
|
+
return "animation" in hat;
|
|
217
|
+
}
|
|
218
|
+
function hatColors(hat, variantIdx = 0) {
|
|
219
|
+
if (isAnimatedHat(hat))
|
|
220
|
+
return hat.colors;
|
|
221
|
+
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
222
|
+
}
|
|
223
|
+
function channelColor(colors, ch) {
|
|
224
|
+
return colors[ch] ?? colors.A;
|
|
225
|
+
}
|
|
226
|
+
|
|
205
227
|
// ../shared/dist/series-transform.js
|
|
206
228
|
var PACE_WINDOW_MS = 15 * 6e4;
|
|
207
229
|
|
|
@@ -433,7 +455,7 @@ function composeHatGrid(baseSprite, hat, variantIdx, horseColors) {
|
|
|
433
455
|
grid[y + ext][x + horseOffsetX] = tagToColor(tag, horseColors);
|
|
434
456
|
}
|
|
435
457
|
}
|
|
436
|
-
const
|
|
458
|
+
const colors = hatColors(hat, variantIdx);
|
|
437
459
|
for (let y = 0; y < hatH; y++) {
|
|
438
460
|
const row = hat.rows[y];
|
|
439
461
|
for (let x = 0; x < hatW; x++) {
|
|
@@ -441,7 +463,7 @@ function composeHatGrid(baseSprite, hat, variantIdx, horseColors) {
|
|
|
441
463
|
if (ch === "." || ch === void 0) continue;
|
|
442
464
|
const gx = hat.anchor_x + x + horseOffsetX;
|
|
443
465
|
if (gx < 0 || gx >= canvasW) continue;
|
|
444
|
-
const color = ch
|
|
466
|
+
const color = channelColor(colors, ch);
|
|
445
467
|
grid[y][gx] = color;
|
|
446
468
|
}
|
|
447
469
|
}
|
|
@@ -463,10 +485,6 @@ function tagToColor(tag, c) {
|
|
|
463
485
|
return FIXED_COLORS.H;
|
|
464
486
|
}
|
|
465
487
|
}
|
|
466
|
-
function hatColorsFor(hat, variantIdx) {
|
|
467
|
-
if (hat.rarity === "legendary") return hat.colors;
|
|
468
|
-
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
469
|
-
}
|
|
470
488
|
|
|
471
489
|
// src/ui/half-blocks.ts
|
|
472
490
|
var RESET = "\x1B[0m";
|
|
@@ -538,19 +556,19 @@ import { useEffect, useState } from "react";
|
|
|
538
556
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
539
557
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
540
558
|
function AnimatedHorseSprite({ sprite, colors, hat }) {
|
|
541
|
-
const
|
|
542
|
-
const frames =
|
|
543
|
-
const fps =
|
|
559
|
+
const animated = isAnimatedHat(hat);
|
|
560
|
+
const frames = animated ? hat.animation.frames : [];
|
|
561
|
+
const fps = animated ? hat.animation.fps : 1;
|
|
544
562
|
const [idx, setIdx] = useState(0);
|
|
545
563
|
useEffect(() => {
|
|
546
|
-
if (!
|
|
564
|
+
if (!animated || frames.length <= 1) return;
|
|
547
565
|
const interval = setInterval(
|
|
548
566
|
() => setIdx((i) => (i + 1) % frames.length),
|
|
549
567
|
Math.max(1, Math.round(1e3 / fps))
|
|
550
568
|
);
|
|
551
569
|
return () => clearInterval(interval);
|
|
552
|
-
}, [
|
|
553
|
-
const renderedHat =
|
|
570
|
+
}, [animated, frames.length, fps]);
|
|
571
|
+
const renderedHat = animated && frames[idx] ? { ...hat, colors: { ...hat.colors, A: frames[idx] } } : hat;
|
|
554
572
|
const { grid } = composeHatGrid(sprite, renderedHat, 0, colors);
|
|
555
573
|
const lines = hexGridToHalfBlocks(grid);
|
|
556
574
|
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx2(Text2, { children: line }, i)) });
|
|
@@ -739,7 +757,7 @@ function HorseCreator({
|
|
|
739
757
|
"]"
|
|
740
758
|
] })
|
|
741
759
|
] }),
|
|
742
|
-
/* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: previewHatDef ? previewHatDef
|
|
760
|
+
/* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: previewHatDef ? isAnimatedHat(previewHatDef) ? /* @__PURE__ */ jsx3(AnimatedHorseSprite, { sprite: MAIN_SPRITE, colors, hat: previewHatDef }) : /* @__PURE__ */ jsx3(HorseSprite, { sprite: MAIN_SPRITE, colors, hat: { hat: previewHatDef, variant: previewHat?.variant ?? 0 } }) : /* @__PURE__ */ jsx3(HorseSprite, { sprite: MAIN_SPRITE, colors }) }),
|
|
743
761
|
/* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: rows.map((r, i) => {
|
|
744
762
|
const cursor = i === rowIdx ? "\u25BA" : " ";
|
|
745
763
|
if (r.kind === "color") {
|
|
@@ -785,8 +803,8 @@ function renderHatLabel(hats, hatChoice, initialEquipped) {
|
|
|
785
803
|
if (!collected) return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "?" });
|
|
786
804
|
const hat = hatById(collected.id);
|
|
787
805
|
const name = hat?.name ?? collected.id;
|
|
788
|
-
const variantSuffix = hat && hat
|
|
789
|
-
const rarityColor = hat ? hat.rarity === "legendary" ? "yellow" : hat.rarity === "epic" ? "magenta" : hat.rarity === "rare" ? "blue" : "gray" : "gray";
|
|
806
|
+
const variantSuffix = hat && !isAnimatedHat(hat) && collected.variant !== void 0 ? ` #${collected.variant + 1}` : "";
|
|
807
|
+
const rarityColor = hat ? hat.rarity === "limited" ? "magentaBright" : hat.rarity === "legendary" ? "yellow" : hat.rarity === "epic" ? "magenta" : hat.rarity === "rare" ? "blue" : "gray" : "gray";
|
|
790
808
|
const equippedMark = initialEquipped === hatChoice ? " \u2713" : "";
|
|
791
809
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
792
810
|
name,
|
|
@@ -839,12 +857,13 @@ function apiBase() {
|
|
|
839
857
|
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
840
858
|
var SCAN_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * 0.75;
|
|
841
859
|
var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
860
|
+
var PRIMARY_SILENT_THRESHOLD = 10;
|
|
842
861
|
|
|
843
862
|
// src/version.ts
|
|
844
863
|
import { createRequire } from "module";
|
|
845
864
|
function readVersion() {
|
|
846
|
-
if ("3.
|
|
847
|
-
return "3.
|
|
865
|
+
if ("3.1.2".length > 0) {
|
|
866
|
+
return "3.1.2";
|
|
848
867
|
}
|
|
849
868
|
try {
|
|
850
869
|
const req = createRequire(import.meta.url);
|
|
@@ -879,7 +898,11 @@ function activeRacesDir() {
|
|
|
879
898
|
return path2.join(homeDir(), "active-races");
|
|
880
899
|
}
|
|
881
900
|
function claudeProjectsDir() {
|
|
882
|
-
|
|
901
|
+
const override = process.env.TOKEN_DERBY_CLAUDE_DIR;
|
|
902
|
+
if (override) return override;
|
|
903
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
904
|
+
if (configDir) return path2.join(configDir, "projects");
|
|
905
|
+
return path2.join(os2.homedir(), ".claude", "projects");
|
|
883
906
|
}
|
|
884
907
|
function codexSessionsDir() {
|
|
885
908
|
return process.env.TOKEN_DERBY_CODEX_DIR ?? path2.join(os2.homedir(), ".codex");
|
|
@@ -1541,7 +1564,7 @@ function ModelList(props) {
|
|
|
1541
1564
|
] }) });
|
|
1542
1565
|
}
|
|
1543
1566
|
function StatusScreen(props) {
|
|
1544
|
-
const { race, ownHorseId, ownHorseName, ownColors, ownUserName, lastHeartbeatAgoSec, lastHeartbeatOk, stalled, stallReason, primaryModel } = props;
|
|
1567
|
+
const { race, ownHorseId, ownHorseName, ownColors, ownUserName, lastHeartbeatAgoSec, lastHeartbeatOk, stalled, stallReason, primarySilent, primarySourceDir, primaryModel } = props;
|
|
1545
1568
|
if (!race) {
|
|
1546
1569
|
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text7, { children: "Joining race\u2026" }) });
|
|
1547
1570
|
}
|
|
@@ -1612,6 +1635,15 @@ function StatusScreen(props) {
|
|
|
1612
1635
|
"\u26A0 ",
|
|
1613
1636
|
stallReason ?? "Can't read token usage",
|
|
1614
1637
|
". Your race continues."
|
|
1638
|
+
] }),
|
|
1639
|
+
!stalled && primarySilent && /* @__PURE__ */ jsxs5(Text7, { color: "yellow", children: [
|
|
1640
|
+
"\u26A0 No ",
|
|
1641
|
+
MODEL_LABELS[primaryModel ?? "claude"],
|
|
1642
|
+
" transcripts in ",
|
|
1643
|
+
PRIMARY_SILENT_THRESHOLD,
|
|
1644
|
+
" beats",
|
|
1645
|
+
primarySourceDir ? ` \u2014 nothing under ${primarySourceDir}` : "",
|
|
1646
|
+
". Your race continues, but your horse cannot move until they can be read."
|
|
1615
1647
|
] })
|
|
1616
1648
|
] }),
|
|
1617
1649
|
primaryModel && /* @__PURE__ */ jsx7(ModelList, { primaryModel }),
|
|
@@ -1851,6 +1883,24 @@ async function readCompleteLines(file, start, end) {
|
|
|
1851
1883
|
}
|
|
1852
1884
|
}
|
|
1853
1885
|
|
|
1886
|
+
// src/tokens/source-root.ts
|
|
1887
|
+
var SourceRootMissing = class extends Error {
|
|
1888
|
+
constructor(dir) {
|
|
1889
|
+
super(`No history directory at ${dir}`);
|
|
1890
|
+
this.dir = dir;
|
|
1891
|
+
this.name = "SourceRootMissing";
|
|
1892
|
+
}
|
|
1893
|
+
dir;
|
|
1894
|
+
};
|
|
1895
|
+
async function readRoot(dir, read) {
|
|
1896
|
+
try {
|
|
1897
|
+
return await read();
|
|
1898
|
+
} catch (e) {
|
|
1899
|
+
if (e?.code === "ENOENT") throw new SourceRootMissing(dir);
|
|
1900
|
+
throw e;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1854
1904
|
// src/tokens/transcripts.ts
|
|
1855
1905
|
var MAX_PROJECT_DEPTH = 8;
|
|
1856
1906
|
function conversationId(file, root) {
|
|
@@ -1887,29 +1937,33 @@ async function sumTokens() {
|
|
|
1887
1937
|
return { input, output };
|
|
1888
1938
|
}
|
|
1889
1939
|
async function listJsonlFiles(root) {
|
|
1890
|
-
const
|
|
1940
|
+
const entries = await readEntries(root, true);
|
|
1891
1941
|
const out = [];
|
|
1892
|
-
for (const
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
if (!stat4.isDirectory()) continue;
|
|
1896
|
-
await collectJsonl(projectDir, MAX_PROJECT_DEPTH, out);
|
|
1942
|
+
for (const entry of entries) {
|
|
1943
|
+
if (!await isDirectory(entry, root)) continue;
|
|
1944
|
+
await collectJsonl(path6.join(root, entry.name), MAX_PROJECT_DEPTH, out);
|
|
1897
1945
|
}
|
|
1898
1946
|
return out;
|
|
1899
1947
|
}
|
|
1900
1948
|
async function collectJsonl(dir, depth, out) {
|
|
1901
1949
|
if (depth <= 0) return;
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
const child = path6.join(dir, entry);
|
|
1908
|
-
const st = await fs4.stat(child);
|
|
1909
|
-
if (st.isDirectory()) await collectJsonl(child, depth - 1, out);
|
|
1950
|
+
for (const entry of await readEntries(dir, false)) {
|
|
1951
|
+
if (entry.name.endsWith(".jsonl")) {
|
|
1952
|
+
out.push(path6.join(dir, entry.name));
|
|
1953
|
+
} else if (depth > 1 && await isDirectory(entry, dir)) {
|
|
1954
|
+
await collectJsonl(path6.join(dir, entry.name), depth - 1, out);
|
|
1910
1955
|
}
|
|
1911
1956
|
}
|
|
1912
1957
|
}
|
|
1958
|
+
async function readEntries(dir, failLoud) {
|
|
1959
|
+
if (failLoud) return readRoot(dir, () => fs4.readdir(dir, { withFileTypes: true }));
|
|
1960
|
+
return fs4.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1961
|
+
}
|
|
1962
|
+
async function isDirectory(entry, parent) {
|
|
1963
|
+
if (entry.isDirectory()) return true;
|
|
1964
|
+
if (!entry.isSymbolicLink()) return false;
|
|
1965
|
+
return fs4.stat(path6.join(parent, entry.name)).then((st) => st.isDirectory()).catch(() => false);
|
|
1966
|
+
}
|
|
1913
1967
|
function addNum(value) {
|
|
1914
1968
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1915
1969
|
}
|
|
@@ -1942,11 +1996,8 @@ function num(v) {
|
|
|
1942
1996
|
}
|
|
1943
1997
|
async function sumCodexByConversation() {
|
|
1944
1998
|
const root = codexSessionsDir();
|
|
1945
|
-
await fs5.stat(root);
|
|
1946
|
-
const files =
|
|
1947
|
-
...await collectRollouts(path7.join(root, "sessions")),
|
|
1948
|
-
...await collectRollouts(path7.join(root, "archived_sessions"))
|
|
1949
|
-
];
|
|
1999
|
+
await readRoot(root, () => fs5.stat(root));
|
|
2000
|
+
const files = await listCodexRollouts(root);
|
|
1950
2001
|
const cache = await ScanCache.open("codex");
|
|
1951
2002
|
const totals = await mapWithConcurrency(
|
|
1952
2003
|
files,
|
|
@@ -1968,6 +2019,12 @@ async function sumCodexTokens() {
|
|
|
1968
2019
|
}
|
|
1969
2020
|
return { input, output };
|
|
1970
2021
|
}
|
|
2022
|
+
async function listCodexRollouts(root) {
|
|
2023
|
+
return [
|
|
2024
|
+
...await collectRollouts(path7.join(root, "sessions")),
|
|
2025
|
+
...await collectRollouts(path7.join(root, "archived_sessions"))
|
|
2026
|
+
];
|
|
2027
|
+
}
|
|
1971
2028
|
async function collectRollouts(dir) {
|
|
1972
2029
|
let entries;
|
|
1973
2030
|
try {
|
|
@@ -2038,7 +2095,7 @@ async function sumGeminiTokens() {
|
|
|
2038
2095
|
return { input, output };
|
|
2039
2096
|
}
|
|
2040
2097
|
async function listChatFiles(root) {
|
|
2041
|
-
const entries = await fs6.readdir(root);
|
|
2098
|
+
const entries = await readRoot(root, () => fs6.readdir(root));
|
|
2042
2099
|
const out = [];
|
|
2043
2100
|
for (const entry of entries) {
|
|
2044
2101
|
const chatsDir = path8.join(root, entry, "chats");
|
|
@@ -2136,7 +2193,7 @@ async function readAllSources(race, primary, progress) {
|
|
|
2136
2193
|
const primaryByConv = /* @__PURE__ */ new Map();
|
|
2137
2194
|
if (primaryResult.ok) {
|
|
2138
2195
|
for (const [id, totals] of primaryResult.map) primaryByConv.set(id, scoreFor(race, totals));
|
|
2139
|
-
} else if (primaryResult.err
|
|
2196
|
+
} else if (!(primaryResult.err instanceof SourceRootMissing)) {
|
|
2140
2197
|
const err = primaryResult.err;
|
|
2141
2198
|
return { stall: `Can't read ${primary} token usage: ${err?.message ?? String(err)}` };
|
|
2142
2199
|
}
|
|
@@ -2203,6 +2260,7 @@ var RaceScoreTracker = class {
|
|
|
2203
2260
|
seq;
|
|
2204
2261
|
stalls = 0;
|
|
2205
2262
|
lastStall = null;
|
|
2263
|
+
primaryEmptyBeats = 0;
|
|
2206
2264
|
primary;
|
|
2207
2265
|
primaryTop5;
|
|
2208
2266
|
constructor(init, primary, primaryTop5) {
|
|
@@ -2235,6 +2293,7 @@ var RaceScoreTracker = class {
|
|
|
2235
2293
|
const v = reading.secondary[key];
|
|
2236
2294
|
if (v > 0) this.lastGood[key] = v;
|
|
2237
2295
|
}
|
|
2296
|
+
this.primaryEmptyBeats = reading.primaryByConv.size === 0 ? this.primaryEmptyBeats + 1 : 0;
|
|
2238
2297
|
for (const [id, v] of reading.primaryByConv) {
|
|
2239
2298
|
const prev = this.primaryConvLast[id] ?? 0;
|
|
2240
2299
|
if (v > prev) this.primaryConvLast[id] = v;
|
|
@@ -2286,6 +2345,10 @@ var RaceScoreTracker = class {
|
|
|
2286
2345
|
get stalled() {
|
|
2287
2346
|
return this.stalls >= STALL_THRESHOLD;
|
|
2288
2347
|
}
|
|
2348
|
+
/** The primary source has produced no conversations for long enough to be worth saying. */
|
|
2349
|
+
get primarySilent() {
|
|
2350
|
+
return this.primaryEmptyBeats >= PRIMARY_SILENT_THRESHOLD;
|
|
2351
|
+
}
|
|
2289
2352
|
/** Human-readable cause of the most recent stall (null once a good read recovers). */
|
|
2290
2353
|
get stallReason() {
|
|
2291
2354
|
return this.lastStall;
|
|
@@ -2314,6 +2377,75 @@ var RaceScoreTracker = class {
|
|
|
2314
2377
|
}
|
|
2315
2378
|
};
|
|
2316
2379
|
|
|
2380
|
+
// src/tokens/source-probe.ts
|
|
2381
|
+
import * as fs7 from "fs/promises";
|
|
2382
|
+
var ROOTS = {
|
|
2383
|
+
claude: claudeProjectsDir,
|
|
2384
|
+
codex: codexSessionsDir,
|
|
2385
|
+
gemini: geminiTmpDir
|
|
2386
|
+
};
|
|
2387
|
+
var LISTERS = {
|
|
2388
|
+
claude: listJsonlFiles,
|
|
2389
|
+
codex: listCodexRollouts,
|
|
2390
|
+
gemini: listChatFiles
|
|
2391
|
+
};
|
|
2392
|
+
var LABELS2 = { claude: "Claude", codex: "Codex", gemini: "Gemini" };
|
|
2393
|
+
function sourceDir(key) {
|
|
2394
|
+
return ROOTS[key]();
|
|
2395
|
+
}
|
|
2396
|
+
async function probeSource(key) {
|
|
2397
|
+
const dir = ROOTS[key]();
|
|
2398
|
+
const exists = await fs7.stat(dir).then((st) => st.isDirectory()).catch(() => false);
|
|
2399
|
+
if (!exists) return { key, dir, exists: false, projects: 0, transcripts: 0 };
|
|
2400
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2401
|
+
const projects = entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).length;
|
|
2402
|
+
const files = await LISTERS[key](dir).catch(() => []);
|
|
2403
|
+
return { key, dir, exists: true, projects, transcripts: files.length };
|
|
2404
|
+
}
|
|
2405
|
+
function overrideVar(key) {
|
|
2406
|
+
return `TOKEN_DERBY_${key.toUpperCase()}_DIR`;
|
|
2407
|
+
}
|
|
2408
|
+
async function confirmEmptySource(opts) {
|
|
2409
|
+
if (opts.probe.transcripts > 0) return true;
|
|
2410
|
+
opts.warn(describeEmptySource(opts.probe));
|
|
2411
|
+
if (!opts.interactive) return true;
|
|
2412
|
+
return opts.ask();
|
|
2413
|
+
}
|
|
2414
|
+
function describeEmptySource(probe) {
|
|
2415
|
+
const label = LABELS2[probe.key];
|
|
2416
|
+
const populated = probe.exists && probe.projects > 0;
|
|
2417
|
+
const reason = !probe.exists ? "does not exist" : populated ? `holds ${probe.projects} project ${probe.projects === 1 ? "directory" : "directories"}, none of which could be read` : "exists, but holds no transcripts";
|
|
2418
|
+
const lines = [
|
|
2419
|
+
`\u26A0 No ${label} transcripts found \u2014 your horse will not move.`,
|
|
2420
|
+
``,
|
|
2421
|
+
` Looked in: ${probe.dir}`,
|
|
2422
|
+
` (${reason})`,
|
|
2423
|
+
``
|
|
2424
|
+
];
|
|
2425
|
+
if (populated) {
|
|
2426
|
+
lines.push(
|
|
2427
|
+
` The directory is there and has history in it, so this is usually a`,
|
|
2428
|
+
` dangling symlink or a permissions problem on one of those projects.`,
|
|
2429
|
+
` To find dangling links:`,
|
|
2430
|
+
` find ${probe.dir} -type l ! -exec test -e {} \\; -print`,
|
|
2431
|
+
``
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
lines.push(
|
|
2435
|
+
` Token Derby counts ${label} usage from this machine's own filesystem.`,
|
|
2436
|
+
` If ${label} runs in a container, over SSH, or on another machine, join`,
|
|
2437
|
+
` the race from there instead.`
|
|
2438
|
+
);
|
|
2439
|
+
if (probe.key === "claude") {
|
|
2440
|
+
lines.push(
|
|
2441
|
+
` If CLAUDE_CONFIG_DIR relocated your config, Token Derby follows it \u2014`,
|
|
2442
|
+
` check it points at the config root, not the projects directory.`
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
lines.push(``, ` To read them from somewhere else: export ${overrideVar(probe.key)}=<dir>`);
|
|
2446
|
+
return lines.join("\n");
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2317
2449
|
// src/runtime/run-race.tsx
|
|
2318
2450
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2319
2451
|
function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
@@ -2330,6 +2462,7 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2330
2462
|
const ctrl = useRef(new AbortController());
|
|
2331
2463
|
const [stalled, setStalled] = useState5(false);
|
|
2332
2464
|
const [stallReason, setStallReason] = useState5(null);
|
|
2465
|
+
const [primarySilent, setPrimarySilent] = useState5(false);
|
|
2333
2466
|
useEffect2(() => {
|
|
2334
2467
|
const t = setInterval(() => setTickNow(/* @__PURE__ */ new Date()), 1e3);
|
|
2335
2468
|
return () => clearInterval(t);
|
|
@@ -2361,6 +2494,7 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2361
2494
|
if (pendingRef.current && !isStall(reading)) tracker.reprime();
|
|
2362
2495
|
setStalled(tracker.stalled);
|
|
2363
2496
|
setStallReason(tracker.stalled ? tracker.stallReason : null);
|
|
2497
|
+
setPrimarySilent(tracker.primarySilent);
|
|
2364
2498
|
return tracker.nextBeat();
|
|
2365
2499
|
},
|
|
2366
2500
|
sendBeat: async (snapshot) => {
|
|
@@ -2428,6 +2562,8 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2428
2562
|
lastHeartbeatOk: lastHbOk,
|
|
2429
2563
|
stalled,
|
|
2430
2564
|
stallReason,
|
|
2565
|
+
primarySilent,
|
|
2566
|
+
primarySourceDir: sourceDir(active.primary_model),
|
|
2431
2567
|
primaryModel: active.primary_model
|
|
2432
2568
|
}
|
|
2433
2569
|
),
|
|
@@ -2495,6 +2631,28 @@ async function buildInitialState(args) {
|
|
|
2495
2631
|
};
|
|
2496
2632
|
}
|
|
2497
2633
|
|
|
2634
|
+
// src/ui/prompt.ts
|
|
2635
|
+
async function promptYesNo(question, opts = {}) {
|
|
2636
|
+
const input = opts.input ?? process.stdin;
|
|
2637
|
+
const output = opts.output ?? process.stdout;
|
|
2638
|
+
if (input === process.stdin) resetStdinAfterInk();
|
|
2639
|
+
const readline6 = await import("readline/promises");
|
|
2640
|
+
const rl = readline6.createInterface({ input, output });
|
|
2641
|
+
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2642
|
+
rl.close();
|
|
2643
|
+
if (a === "") return opts.defaultYes !== false;
|
|
2644
|
+
return a === "y" || a === "yes";
|
|
2645
|
+
}
|
|
2646
|
+
function resetStdinAfterInk() {
|
|
2647
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2648
|
+
process.stdin.setRawMode(false);
|
|
2649
|
+
}
|
|
2650
|
+
while (process.stdin.read() !== null) {
|
|
2651
|
+
}
|
|
2652
|
+
process.stdin.pause();
|
|
2653
|
+
process.stdin.ref();
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2498
2656
|
// src/commands/join.ts
|
|
2499
2657
|
function parsePrimaryFlag(argv) {
|
|
2500
2658
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -2589,6 +2747,19 @@ async function joinCommand(joinCode, argv = []) {
|
|
|
2589
2747
|
if (primaryFlag) chosenPrimary = primaryFlag;
|
|
2590
2748
|
else if (process.stdout.isTTY) chosenPrimary = await pickPrimary();
|
|
2591
2749
|
}
|
|
2750
|
+
const effectivePrimary = ownHorse?.primary_model ?? chosenPrimary;
|
|
2751
|
+
const proceed = await confirmEmptySource({
|
|
2752
|
+
probe: await probeSource(effectivePrimary),
|
|
2753
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
2754
|
+
warn: (text) => console.error(`
|
|
2755
|
+
${text}
|
|
2756
|
+
`),
|
|
2757
|
+
ask: () => promptYesNo("Join anyway? [y/N] ", { defaultYes: false })
|
|
2758
|
+
});
|
|
2759
|
+
if (!proceed) {
|
|
2760
|
+
console.log("Cancelled.");
|
|
2761
|
+
return 1;
|
|
2762
|
+
}
|
|
2592
2763
|
let joinResp;
|
|
2593
2764
|
try {
|
|
2594
2765
|
joinResp = await joinRace(code, { stable_horse_id: chosenStableHorseId, primary_model: chosenPrimary });
|
|
@@ -2889,25 +3060,6 @@ async function webCommand(deps = {}) {
|
|
|
2889
3060
|
return 0;
|
|
2890
3061
|
}
|
|
2891
3062
|
|
|
2892
|
-
// src/ui/prompt.ts
|
|
2893
|
-
async function promptYesNo(question) {
|
|
2894
|
-
resetStdinAfterInk();
|
|
2895
|
-
const readline6 = await import("readline/promises");
|
|
2896
|
-
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2897
|
-
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2898
|
-
rl.close();
|
|
2899
|
-
return a === "" || a === "y" || a === "yes";
|
|
2900
|
-
}
|
|
2901
|
-
function resetStdinAfterInk() {
|
|
2902
|
-
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2903
|
-
process.stdin.setRawMode(false);
|
|
2904
|
-
}
|
|
2905
|
-
while (process.stdin.read() !== null) {
|
|
2906
|
-
}
|
|
2907
|
-
process.stdin.pause();
|
|
2908
|
-
process.stdin.ref();
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
3063
|
// src/commands/login.ts
|
|
2912
3064
|
function parseDeviceNameFlag(argv) {
|
|
2913
3065
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -3477,13 +3629,13 @@ import { useEffect as useEffect3, useState as useState7 } from "react";
|
|
|
3477
3629
|
import { Box as Box10, Text as Text10 } from "ink";
|
|
3478
3630
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
3479
3631
|
function HatSprite({ hat, variant, centerIn }) {
|
|
3480
|
-
const colors =
|
|
3632
|
+
const colors = hatColors(hat, variant ?? 0);
|
|
3481
3633
|
const grid = makeHatGrid(hat, colors, centerIn);
|
|
3482
3634
|
const lines = hexGridToHalfBlocks(grid);
|
|
3483
3635
|
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx10(Text10, { children: line }, i)) });
|
|
3484
3636
|
}
|
|
3485
3637
|
function AnimatedHatSprite({ hat, variant, centerIn }) {
|
|
3486
|
-
if (hat
|
|
3638
|
+
if (!isAnimatedHat(hat)) {
|
|
3487
3639
|
return /* @__PURE__ */ jsx10(HatSprite, { hat, variant, centerIn });
|
|
3488
3640
|
}
|
|
3489
3641
|
const frames = hat.animation.frames;
|
|
@@ -3500,10 +3652,6 @@ function AnimatedHatSprite({ hat, variant, centerIn }) {
|
|
|
3500
3652
|
const framed = { ...hat, colors: { ...hat.colors, A: frames[idx] } };
|
|
3501
3653
|
return /* @__PURE__ */ jsx10(HatSprite, { hat: framed, variant, centerIn });
|
|
3502
3654
|
}
|
|
3503
|
-
function hatColorsFor2(hat, variantIdx) {
|
|
3504
|
-
if (hat.rarity === "legendary") return hat.colors;
|
|
3505
|
-
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
3506
|
-
}
|
|
3507
3655
|
function makeHatGrid(hat, colors, centerIn) {
|
|
3508
3656
|
const w = centerIn?.w ?? hat.width;
|
|
3509
3657
|
const h = centerIn?.h ?? hat.rows.length;
|
|
@@ -3518,7 +3666,7 @@ function makeHatGrid(hat, colors, centerIn) {
|
|
|
3518
3666
|
const gx = x + offX;
|
|
3519
3667
|
const gy = y + offY;
|
|
3520
3668
|
if (gx < 0 || gx >= w || gy < 0 || gy >= h) continue;
|
|
3521
|
-
grid[gy][gx] =
|
|
3669
|
+
grid[gy][gx] = channelColor(colors, ch);
|
|
3522
3670
|
}
|
|
3523
3671
|
}
|
|
3524
3672
|
return grid;
|
|
@@ -3532,7 +3680,8 @@ var TIER_PALETTE = {
|
|
|
3532
3680
|
common: ["#FFFFFF", "#DDDDDD", "#AAAAAA", "#9E9E9E"],
|
|
3533
3681
|
rare: ["#42A5F5", "#1E88E5", "#90CAF9", "#0277BD"],
|
|
3534
3682
|
epic: ["#AB47BC", "#8E24AA", "#CE93D8", "#6A1B9A"],
|
|
3535
|
-
legendary: ["#FFD700", "#FF7F00", "#FF0000", "#FF00FF", "#00BFFF", "#7CFC00", "#8B00FF"]
|
|
3683
|
+
legendary: ["#FFD700", "#FF7F00", "#FF0000", "#FF00FF", "#00BFFF", "#7CFC00", "#8B00FF"],
|
|
3684
|
+
limited: ["#F472B6", "#EC4899", "#FF4FA3", "#FBCFE8", "#FFFFFF"]
|
|
3536
3685
|
};
|
|
3537
3686
|
var CONFETTI_CHARS = ["\u2726", "\u2727", "\u22C6", "\u2605", "\u2606", "\u2728", "*", "\u2022", "\u25C6", "\u25C7"];
|
|
3538
3687
|
var SCENE_W = 32;
|
|
@@ -3635,7 +3784,7 @@ function ConfettiBurst({ tier }) {
|
|
|
3635
3784
|
var CLOSED_HOLD_MS = 3e3;
|
|
3636
3785
|
function RollReveal({ outcome, onDone }) {
|
|
3637
3786
|
const isNoHat = outcome.kind === "no_hat";
|
|
3638
|
-
const
|
|
3787
|
+
const isShowpiece = outcome.kind !== "no_hat" && (outcome.hat.rarity === "legendary" || outcome.hat.rarity === "limited");
|
|
3639
3788
|
const [phase, setPhase] = useState8("closed");
|
|
3640
3789
|
useEffect4(() => {
|
|
3641
3790
|
const timers = [];
|
|
@@ -3647,10 +3796,10 @@ function RollReveal({ outcome, onDone }) {
|
|
|
3647
3796
|
} else {
|
|
3648
3797
|
timers.push(setTimeout(() => setPhase("burst"), CLOSED_HOLD_MS + 700));
|
|
3649
3798
|
timers.push(setTimeout(() => setPhase("reveal"), CLOSED_HOLD_MS + 1650));
|
|
3650
|
-
timers.push(setTimeout(onDone, CLOSED_HOLD_MS + (
|
|
3799
|
+
timers.push(setTimeout(onDone, CLOSED_HOLD_MS + (isShowpiece ? 4650 : 2650)));
|
|
3651
3800
|
}
|
|
3652
3801
|
return () => timers.forEach(clearTimeout);
|
|
3653
|
-
}, [isNoHat,
|
|
3802
|
+
}, [isNoHat, isShowpiece, onDone]);
|
|
3654
3803
|
if (phase === "closed") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_CLOSED, color: BOX_COLOR });
|
|
3655
3804
|
if (phase === "open1") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_1, color: BOX_COLOR });
|
|
3656
3805
|
if (phase === "open2") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_2, color: BOX_COLOR });
|
|
@@ -3659,7 +3808,7 @@ function RollReveal({ outcome, onDone }) {
|
|
|
3659
3808
|
return /* @__PURE__ */ jsx11(ConfettiBurst, { tier: outcome.hat.rarity });
|
|
3660
3809
|
}
|
|
3661
3810
|
if (outcome.kind === "no_hat") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
|
|
3662
|
-
return outcome.hat
|
|
3811
|
+
return isAnimatedHat(outcome.hat) ? /* @__PURE__ */ jsx11(AnimatedHatSprite, { hat: outcome.hat, centerIn: { w: SCENE_W, h: SCENE_H } }) : /* @__PURE__ */ jsx11(HatSprite, { hat: outcome.hat, variant: outcome.variant, centerIn: { w: SCENE_W, h: SCENE_H } });
|
|
3663
3812
|
}
|
|
3664
3813
|
|
|
3665
3814
|
// src/ui/reveal.ts
|
|
@@ -3750,7 +3899,7 @@ async function rollCommand() {
|
|
|
3750
3899
|
await runReveal(outcome);
|
|
3751
3900
|
if (result.result === "hat") {
|
|
3752
3901
|
const hat = hatById(result.collected.id);
|
|
3753
|
-
const variantSuffix = hat
|
|
3902
|
+
const variantSuffix = !isAnimatedHat(hat) && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
|
|
3754
3903
|
console.log(`
|
|
3755
3904
|
\u2728 ${hat.name}${variantSuffix} [${hat.rarity.toUpperCase()}]
|
|
3756
3905
|
`);
|
|
@@ -3788,8 +3937,9 @@ async function claimCommand(token) {
|
|
|
3788
3937
|
console.error("Usage: token-derby claim <token>");
|
|
3789
3938
|
return 2;
|
|
3790
3939
|
}
|
|
3940
|
+
let probe;
|
|
3791
3941
|
try {
|
|
3792
|
-
await probeClaim(token);
|
|
3942
|
+
probe = await probeClaim(token);
|
|
3793
3943
|
} catch (e) {
|
|
3794
3944
|
if (e instanceof ApiError) {
|
|
3795
3945
|
console.error(`Error: ${e.code} ${e.message}`);
|
|
@@ -3811,7 +3961,9 @@ async function claimCommand(token) {
|
|
|
3811
3961
|
console.error("No horses in your stable. Run `token-derby stable create` first.");
|
|
3812
3962
|
return 1;
|
|
3813
3963
|
}
|
|
3814
|
-
console.log(
|
|
3964
|
+
console.log(probe.entry_count > 1 ? `
|
|
3965
|
+
\u{1F381} A pack of ${probe.entry_count} cosmetics \u2014 one of them will be yours.
|
|
3966
|
+
` : "\n\u{1F381} A cosmetic has been awarded to you.\n");
|
|
3815
3967
|
const picked = await new Promise((resolve) => {
|
|
3816
3968
|
const app = render7(React15.createElement(HorsePicker, {
|
|
3817
3969
|
horses: stable.horses,
|
|
@@ -3855,7 +4007,7 @@ async function claimCommand(token) {
|
|
|
3855
4007
|
await runReveal(outcome);
|
|
3856
4008
|
if (result.result === "hat") {
|
|
3857
4009
|
const hat2 = hatById(result.collected.id);
|
|
3858
|
-
const variantSuffix2 = hat2
|
|
4010
|
+
const variantSuffix2 = !isAnimatedHat(hat2) && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
|
|
3859
4011
|
console.log(`
|
|
3860
4012
|
\u2728 ${hat2.name}${variantSuffix2} [${hat2.rarity.toUpperCase()}]
|
|
3861
4013
|
`);
|
|
@@ -3881,12 +4033,9 @@ ${picked.name} already has ${hat?.name ?? result.hat_id}${variantSuffix}. +${res
|
|
|
3881
4033
|
|
|
3882
4034
|
// src/commands/org-join.ts
|
|
3883
4035
|
async function orgJoinCommand(token) {
|
|
3884
|
-
|
|
3885
|
-
console.error("Usage: token-derby organisation join <join-token>");
|
|
3886
|
-
return 2;
|
|
3887
|
-
}
|
|
4036
|
+
const join_token = token?.trim();
|
|
3888
4037
|
try {
|
|
3889
|
-
const resp = await joinOrganisation({ join_token:
|
|
4038
|
+
const resp = await joinOrganisation(join_token ? { join_token } : {});
|
|
3890
4039
|
console.log(`Joined organisation: ${resp.org_name}`);
|
|
3891
4040
|
return 0;
|
|
3892
4041
|
} catch (e) {
|
|
@@ -3964,7 +4113,9 @@ Stable management:
|
|
|
3964
4113
|
token-derby stable delete <name> Remove a horse from your stable
|
|
3965
4114
|
|
|
3966
4115
|
Organisations:
|
|
3967
|
-
token-derby organisation join
|
|
4116
|
+
token-derby organisation join [token] Join an organisation with a join token,
|
|
4117
|
+
or leave the token out to join by your
|
|
4118
|
+
verified email domain
|
|
3968
4119
|
token-derby web Open the web org manager (create orgs,
|
|
3969
4120
|
manage schedules, webhooks, members)
|
|
3970
4121
|
|
|
@@ -4027,7 +4178,7 @@ async function main() {
|
|
|
4027
4178
|
const sub = argv[1];
|
|
4028
4179
|
if (sub === "join") return orgJoinCommand(argv[2]);
|
|
4029
4180
|
console.error(`Organisation management has moved to the web: token-derby web`);
|
|
4030
|
-
console.error(`The only CLI organisation command is: organisation join
|
|
4181
|
+
console.error(`The only CLI organisation command is: organisation join [token]`);
|
|
4031
4182
|
return 2;
|
|
4032
4183
|
}
|
|
4033
4184
|
if (cmd === "create") {
|