@mauricode/token-derby 2.10.0 → 2.11.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 +18 -1
- package/dist/bin.js +88 -402
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -45,7 +45,6 @@ var CLI_VERSION_HEADER = "x-cli-version";
|
|
|
45
45
|
var USER_ID_HEADER = "x-user-id";
|
|
46
46
|
var USER_TOKEN_HEADER = "x-user-token";
|
|
47
47
|
var USER_NAME_MAX_LENGTH = 40;
|
|
48
|
-
var ORG_NAME_MAX_LENGTH = 12;
|
|
49
48
|
var ORG_NAME_PATTERN = /^[A-Za-z0-9]{1,12}$/;
|
|
50
49
|
|
|
51
50
|
// ../shared/dist/version-match.js
|
|
@@ -206,34 +205,8 @@ var HATS = [
|
|
|
206
205
|
{ id: "aurora_helm", name: "Aurora Helm", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...QQQQQ...", "...QQQQQ...", "..AAAAAAA..", "..AAAAAAA..", "..AQAAAQA..", "..AQAAAQA.."], colors: { A: "#00CED1", Q: "#00FF7F" }, animation: { type: "cycle", frames: ["#0000FF", "#0066FF", "#00BFFF", "#00CED1", "#00FF7F", "#7CFC00", "#00FF7F", "#00CED1"], fps: 4 } }
|
|
207
206
|
];
|
|
208
207
|
|
|
209
|
-
// ../shared/dist/
|
|
210
|
-
var
|
|
211
|
-
function parseWeekdays(spec) {
|
|
212
|
-
const tokens = spec.toLowerCase().split(",").map((s) => s.trim()).filter(Boolean);
|
|
213
|
-
if (tokens.length === 0)
|
|
214
|
-
return null;
|
|
215
|
-
const set = /* @__PURE__ */ new Set();
|
|
216
|
-
for (const tok of tokens) {
|
|
217
|
-
if (tok.includes("-")) {
|
|
218
|
-
const parts = tok.split("-");
|
|
219
|
-
if (parts.length !== 2)
|
|
220
|
-
return null;
|
|
221
|
-
const [a, b] = parts;
|
|
222
|
-
const ai = ORDER.indexOf(a);
|
|
223
|
-
const bi = ORDER.indexOf(b);
|
|
224
|
-
if (ai < 0 || bi < 0 || ai > bi)
|
|
225
|
-
return null;
|
|
226
|
-
for (let i = ai; i <= bi; i++)
|
|
227
|
-
set.add(i + 1);
|
|
228
|
-
} else {
|
|
229
|
-
const i = ORDER.indexOf(tok);
|
|
230
|
-
if (i < 0)
|
|
231
|
-
return null;
|
|
232
|
-
set.add(i + 1);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return [...set].sort((x, y) => x - y);
|
|
236
|
-
}
|
|
208
|
+
// ../shared/dist/series-transform.js
|
|
209
|
+
var PACE_WINDOW_MS = 15 * 6e4;
|
|
237
210
|
|
|
238
211
|
// src/ui/HorseSprite.tsx
|
|
239
212
|
import { Box, Text } from "ink";
|
|
@@ -740,8 +713,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
|
740
713
|
// src/version.ts
|
|
741
714
|
import { createRequire } from "module";
|
|
742
715
|
function readVersion() {
|
|
743
|
-
if ("2.
|
|
744
|
-
return "2.
|
|
716
|
+
if ("2.11.0".length > 0) {
|
|
717
|
+
return "2.11.0";
|
|
745
718
|
}
|
|
746
719
|
try {
|
|
747
720
|
const req = createRequire(import.meta.url);
|
|
@@ -897,18 +870,12 @@ function heartbeat(joinCode, horseId, token, body) {
|
|
|
897
870
|
function endRace(adminCode) {
|
|
898
871
|
return request("DELETE", `/races/admin/${encodeURIComponent(adminCode)}`, void 0, void 0);
|
|
899
872
|
}
|
|
900
|
-
function createOrganisation(body) {
|
|
901
|
-
return request("POST", "/organisations", body, void 0);
|
|
902
|
-
}
|
|
903
873
|
function joinOrganisation(body) {
|
|
904
874
|
return request("POST", "/organisations/join", body, void 0);
|
|
905
875
|
}
|
|
906
876
|
function listOrganisations() {
|
|
907
877
|
return request("GET", "/organisations", void 0, void 0);
|
|
908
878
|
}
|
|
909
|
-
function getOrganisation(name) {
|
|
910
|
-
return request("GET", `/organisations/${encodeURIComponent(name)}`, void 0, void 0);
|
|
911
|
-
}
|
|
912
879
|
function initJockey(body) {
|
|
913
880
|
return request("POST", "/jockey/init", body, void 0);
|
|
914
881
|
}
|
|
@@ -937,60 +904,15 @@ function deleteStableHorse(stableHorseId) {
|
|
|
937
904
|
void 0
|
|
938
905
|
);
|
|
939
906
|
}
|
|
940
|
-
function setOrgWebhook(orgName, body) {
|
|
941
|
-
return request(
|
|
942
|
-
"PUT",
|
|
943
|
-
`/organisations/${encodeURIComponent(orgName)}/webhook`,
|
|
944
|
-
body,
|
|
945
|
-
void 0
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
function getOrgWebhook(orgName) {
|
|
949
|
-
return request(
|
|
950
|
-
"GET",
|
|
951
|
-
`/organisations/${encodeURIComponent(orgName)}/webhook`,
|
|
952
|
-
void 0,
|
|
953
|
-
void 0
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
function deleteOrgWebhook(orgName) {
|
|
957
|
-
return request(
|
|
958
|
-
"DELETE",
|
|
959
|
-
`/organisations/${encodeURIComponent(orgName)}/webhook`,
|
|
960
|
-
void 0,
|
|
961
|
-
void 0
|
|
962
|
-
);
|
|
963
|
-
}
|
|
964
|
-
function setOrgSchedule(orgName, body) {
|
|
965
|
-
return request(
|
|
966
|
-
"PUT",
|
|
967
|
-
`/organisations/${encodeURIComponent(orgName)}/schedule`,
|
|
968
|
-
body,
|
|
969
|
-
void 0
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
function getOrgSchedule(orgName) {
|
|
973
|
-
return request(
|
|
974
|
-
"GET",
|
|
975
|
-
`/organisations/${encodeURIComponent(orgName)}/schedule`,
|
|
976
|
-
void 0,
|
|
977
|
-
void 0
|
|
978
|
-
);
|
|
979
|
-
}
|
|
980
|
-
function clearOrgSchedule(orgName) {
|
|
981
|
-
return request(
|
|
982
|
-
"DELETE",
|
|
983
|
-
`/organisations/${encodeURIComponent(orgName)}/schedule`,
|
|
984
|
-
void 0,
|
|
985
|
-
void 0
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
907
|
function rollHat(stableHorseId) {
|
|
989
908
|
return request("POST", `/jockey/me/horses/${encodeURIComponent(stableHorseId)}/roll`, void 0, void 0);
|
|
990
909
|
}
|
|
991
910
|
function equipHat(stableHorseId, body) {
|
|
992
911
|
return request("POST", `/jockey/me/horses/${encodeURIComponent(stableHorseId)}/equip`, body, void 0);
|
|
993
912
|
}
|
|
913
|
+
function createWebSession() {
|
|
914
|
+
return request("POST", "/web-sessions", void 0, void 0);
|
|
915
|
+
}
|
|
994
916
|
|
|
995
917
|
// src/commands/stable-create.ts
|
|
996
918
|
async function stableCreateCommand() {
|
|
@@ -1430,7 +1352,7 @@ function TokenBreakdown(props) {
|
|
|
1430
1352
|
] });
|
|
1431
1353
|
}
|
|
1432
1354
|
function StatusScreen(props) {
|
|
1433
|
-
const { race, ownHorseId, ownHorseName, ownColors, ownUserName, lastHeartbeatAgoSec, lastHeartbeatOk, stalled, primaryModel, perSource, primaryCapped } = props;
|
|
1355
|
+
const { race, ownHorseId, ownHorseName, ownColors, ownUserName, lastHeartbeatAgoSec, lastHeartbeatOk, stalled, stallReason, primaryModel, perSource, primaryCapped } = props;
|
|
1434
1356
|
if (!race) {
|
|
1435
1357
|
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text7, { children: "Joining race\u2026" }) });
|
|
1436
1358
|
}
|
|
@@ -1504,7 +1426,11 @@ function StatusScreen(props) {
|
|
|
1504
1426
|
" ",
|
|
1505
1427
|
/* @__PURE__ */ jsx7(Text7, { color: lastHeartbeatOk ? "green" : "yellow", children: lastHeartbeatOk ? "\u2713" : "\u26A0" })
|
|
1506
1428
|
] }),
|
|
1507
|
-
stalled && /* @__PURE__ */
|
|
1429
|
+
stalled && /* @__PURE__ */ jsxs5(Text7, { color: "yellow", children: [
|
|
1430
|
+
"\u26A0 ",
|
|
1431
|
+
stallReason ?? "Can't read token usage",
|
|
1432
|
+
". Your race continues."
|
|
1433
|
+
] })
|
|
1508
1434
|
] }),
|
|
1509
1435
|
primaryModel && perSource && /* @__PURE__ */ jsx7(
|
|
1510
1436
|
TokenBreakdown,
|
|
@@ -1817,6 +1743,9 @@ function parseJsonl(raw) {
|
|
|
1817
1743
|
}
|
|
1818
1744
|
|
|
1819
1745
|
// src/tokens/race-tokens.ts
|
|
1746
|
+
function isStall(r) {
|
|
1747
|
+
return "stall" in r;
|
|
1748
|
+
}
|
|
1820
1749
|
var SCALAR_READERS = {
|
|
1821
1750
|
claude: sumTokens,
|
|
1822
1751
|
codex: sumCodexTokens,
|
|
@@ -1831,20 +1760,22 @@ function scoreFor(race, t) {
|
|
|
1831
1760
|
return race.counts_input ? t.input + t.output : t.output;
|
|
1832
1761
|
}
|
|
1833
1762
|
async function readAllSources(race, primary) {
|
|
1763
|
+
const primaryByConv = /* @__PURE__ */ new Map();
|
|
1834
1764
|
try {
|
|
1835
1765
|
const primaryMap = await BY_CONVERSATION_READERS[primary]();
|
|
1836
|
-
const primaryByConv = /* @__PURE__ */ new Map();
|
|
1837
1766
|
for (const [id, totals] of primaryMap) primaryByConv.set(id, scoreFor(race, totals));
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
})
|
|
1843
|
-
);
|
|
1844
|
-
return { secondary, primaryByConv };
|
|
1845
|
-
} catch {
|
|
1846
|
-
return null;
|
|
1767
|
+
} catch (e) {
|
|
1768
|
+
if (e?.code !== "ENOENT") {
|
|
1769
|
+
return { stall: `Can't read ${primary} token usage: ${e?.message ?? String(e)}` };
|
|
1770
|
+
}
|
|
1847
1771
|
}
|
|
1772
|
+
const secondary = { claude: 0, codex: 0, gemini: 0 };
|
|
1773
|
+
await Promise.all(
|
|
1774
|
+
MODEL_KEYS.filter((k) => k !== primary).map(async (k) => {
|
|
1775
|
+
secondary[k] = await SCALAR_READERS[k]().then((t) => scoreFor(race, t)).catch(() => 0);
|
|
1776
|
+
})
|
|
1777
|
+
);
|
|
1778
|
+
return { secondary, primaryByConv };
|
|
1848
1779
|
}
|
|
1849
1780
|
|
|
1850
1781
|
// src/tokens/primary-cap.ts
|
|
@@ -1866,6 +1797,7 @@ var RaceScoreTracker = class {
|
|
|
1866
1797
|
counted;
|
|
1867
1798
|
seq;
|
|
1868
1799
|
stalls = 0;
|
|
1800
|
+
lastStall = null;
|
|
1869
1801
|
primary;
|
|
1870
1802
|
primaryTop5;
|
|
1871
1803
|
constructor(init, primary, primaryTop5) {
|
|
@@ -1880,16 +1812,19 @@ var RaceScoreTracker = class {
|
|
|
1880
1812
|
}
|
|
1881
1813
|
/**
|
|
1882
1814
|
* Record a scan result.
|
|
1883
|
-
* - `null` → stall (warning), anchors untouched.
|
|
1815
|
+
* - `null` or a `{ stall }` reading → stall (warning), anchors untouched. A
|
|
1816
|
+
* stall reading also captures its cause for the UI.
|
|
1884
1817
|
* - otherwise → secondaries advance scalar lastGood (never down to 0); the
|
|
1885
1818
|
* primary's per-conversation latest readings are updated (monotonic).
|
|
1886
1819
|
*/
|
|
1887
1820
|
recordReading(reading) {
|
|
1888
|
-
if (reading === null) {
|
|
1821
|
+
if (reading === null || isStall(reading)) {
|
|
1889
1822
|
this.stalls += 1;
|
|
1823
|
+
this.lastStall = reading?.stall ?? null;
|
|
1890
1824
|
return;
|
|
1891
1825
|
}
|
|
1892
1826
|
this.stalls = 0;
|
|
1827
|
+
this.lastStall = null;
|
|
1893
1828
|
for (const key of MODEL_KEYS) {
|
|
1894
1829
|
if (key === this.primary) continue;
|
|
1895
1830
|
const v = reading.secondary[key];
|
|
@@ -1946,6 +1881,10 @@ var RaceScoreTracker = class {
|
|
|
1946
1881
|
get stalled() {
|
|
1947
1882
|
return this.stalls >= STALL_THRESHOLD;
|
|
1948
1883
|
}
|
|
1884
|
+
/** Human-readable cause of the most recent stall (null once a good read recovers). */
|
|
1885
|
+
get stallReason() {
|
|
1886
|
+
return this.lastStall;
|
|
1887
|
+
}
|
|
1949
1888
|
/** Cumulative primary tokens credited so far (for the UI's primary "since join" row). */
|
|
1950
1889
|
primaryCounted() {
|
|
1951
1890
|
return this.counted;
|
|
@@ -1985,6 +1924,7 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
1985
1924
|
const pendingRef = useRef(pendingMode);
|
|
1986
1925
|
const ctrl = useRef(new AbortController());
|
|
1987
1926
|
const [stalled, setStalled] = useState5(false);
|
|
1927
|
+
const [stallReason, setStallReason] = useState5(null);
|
|
1988
1928
|
const baselineRef = useRef(initialState.acked);
|
|
1989
1929
|
const [perSource, setPerSource] = useState5({ claude: 0, codex: 0, gemini: 0 });
|
|
1990
1930
|
useEffect2(() => {
|
|
@@ -2006,15 +1946,16 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2006
1946
|
new Promise((_, reject) => setTimeout(() => reject(new Error("scan timeout")), 1e4))
|
|
2007
1947
|
]);
|
|
2008
1948
|
} catch {
|
|
2009
|
-
return
|
|
1949
|
+
return { stall: "Token scan timed out" };
|
|
2010
1950
|
}
|
|
2011
1951
|
};
|
|
2012
1952
|
runHeartbeatLoop({
|
|
2013
1953
|
prepareBeat: async () => {
|
|
2014
1954
|
const reading = await scanWithTimeout();
|
|
2015
1955
|
tracker.recordReading(reading);
|
|
2016
|
-
if (pendingRef.current) tracker.reprime();
|
|
1956
|
+
if (pendingRef.current && !isStall(reading)) tracker.reprime();
|
|
2017
1957
|
setStalled(tracker.stalled);
|
|
1958
|
+
setStallReason(tracker.stalled ? tracker.stallReason : null);
|
|
2018
1959
|
const since = tracker.secondarySinceJoin(baselineRef.current);
|
|
2019
1960
|
const ps = { claude: 0, codex: 0, gemini: 0 };
|
|
2020
1961
|
for (const k of MODEL_KEYS) {
|
|
@@ -2087,6 +2028,7 @@ function RunRace({ active, initialState, pendingMode, ownUserName }) {
|
|
|
2087
2028
|
lastHeartbeatAgoSec,
|
|
2088
2029
|
lastHeartbeatOk: lastHbOk,
|
|
2089
2030
|
stalled,
|
|
2031
|
+
stallReason,
|
|
2090
2032
|
primaryModel: active.primary_model,
|
|
2091
2033
|
perSource,
|
|
2092
2034
|
primaryCapped: primaryConversationCap(active.primary_top5 ?? false) !== Infinity
|
|
@@ -2134,14 +2076,16 @@ function raceViewFrom(resp) {
|
|
|
2134
2076
|
};
|
|
2135
2077
|
}
|
|
2136
2078
|
async function buildInitialState(args) {
|
|
2137
|
-
let
|
|
2079
|
+
let secondary = { claude: 0, codex: 0, gemini: 0 };
|
|
2080
|
+
const primaryConvAcked = {};
|
|
2138
2081
|
try {
|
|
2139
|
-
now = await readAllSources(args.active, args.active.primary_model);
|
|
2082
|
+
const now = await readAllSources(args.active, args.active.primary_model);
|
|
2083
|
+
if (!isStall(now)) {
|
|
2084
|
+
secondary = now.secondary;
|
|
2085
|
+
for (const [id, v] of now.primaryByConv) primaryConvAcked[id] = v;
|
|
2086
|
+
}
|
|
2140
2087
|
} catch {
|
|
2141
2088
|
}
|
|
2142
|
-
const secondary = now?.secondary ?? { claude: 0, codex: 0, gemini: 0 };
|
|
2143
|
-
const primaryConvAcked = {};
|
|
2144
|
-
if (now) for (const [id, v] of now.primaryByConv) primaryConvAcked[id] = v;
|
|
2145
2089
|
return {
|
|
2146
2090
|
initialState: {
|
|
2147
2091
|
acked: { ...secondary },
|
|
@@ -2780,8 +2724,8 @@ function pendingFor(horse) {
|
|
|
2780
2724
|
}
|
|
2781
2725
|
async function promptYesNo(question) {
|
|
2782
2726
|
resetStdinAfterInk();
|
|
2783
|
-
const
|
|
2784
|
-
const rl =
|
|
2727
|
+
const readline6 = await import("readline/promises");
|
|
2728
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2785
2729
|
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2786
2730
|
rl.close();
|
|
2787
2731
|
return a === "" || a === "y" || a === "yes";
|
|
@@ -2905,39 +2849,6 @@ No hat this time. +${result.xp_awarded} XP toward your next level.
|
|
|
2905
2849
|
}
|
|
2906
2850
|
}
|
|
2907
2851
|
|
|
2908
|
-
// src/commands/org-create.ts
|
|
2909
|
-
import * as readline6 from "readline/promises";
|
|
2910
|
-
import { stdin as stdin6, stdout as stdout6 } from "process";
|
|
2911
|
-
async function orgCreateCommand() {
|
|
2912
|
-
const rl = readline6.createInterface({ input: stdin6, output: stdout6 });
|
|
2913
|
-
try {
|
|
2914
|
-
const name = (await rl.question(`Organisation name (1\u2013${ORG_NAME_MAX_LENGTH} alphanumeric chars): `)).trim();
|
|
2915
|
-
if (!ORG_NAME_PATTERN.test(name)) {
|
|
2916
|
-
console.error(`Name must be 1\u2013${ORG_NAME_MAX_LENGTH} alphanumeric characters (no spaces or symbols).`);
|
|
2917
|
-
return 1;
|
|
2918
|
-
}
|
|
2919
|
-
const resp = await createOrganisation({ name });
|
|
2920
|
-
console.log("");
|
|
2921
|
-
console.log(` Organisation created: ${resp.org_name}`);
|
|
2922
|
-
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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
2923
|
-
console.log(` \u2551 JOIN TOKEN: ${resp.org_join_token.padEnd(43)}\u2551`);
|
|
2924
|
-
console.log(" \u255A\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
2925
|
-
console.log(" \u26A0 Share this token to invite members. Treat it as a secret.");
|
|
2926
|
-
console.log("");
|
|
2927
|
-
console.log(` Members join with: token-derby organisation join ${resp.org_join_token}`);
|
|
2928
|
-
console.log(` Create org races: token-derby create --organisation ${resp.org_name}`);
|
|
2929
|
-
return 0;
|
|
2930
|
-
} catch (e) {
|
|
2931
|
-
if (e instanceof ApiError) {
|
|
2932
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
2933
|
-
return 1;
|
|
2934
|
-
}
|
|
2935
|
-
throw e;
|
|
2936
|
-
} finally {
|
|
2937
|
-
rl.close();
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
2852
|
// src/commands/org-join.ts
|
|
2942
2853
|
async function orgJoinCommand(token) {
|
|
2943
2854
|
if (!token) {
|
|
@@ -2957,209 +2868,22 @@ async function orgJoinCommand(token) {
|
|
|
2957
2868
|
}
|
|
2958
2869
|
}
|
|
2959
2870
|
|
|
2960
|
-
// src/commands/
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
if (resp.organisations.length === 0) {
|
|
2965
|
-
console.log("You are not in any organisations.");
|
|
2966
|
-
console.log("Create one with: token-derby organisation create");
|
|
2967
|
-
console.log("Or join one with: token-derby organisation join <token>");
|
|
2968
|
-
return 0;
|
|
2969
|
-
}
|
|
2970
|
-
console.log(`Your organisations (${resp.organisations.length}):`);
|
|
2971
|
-
for (const o of resp.organisations) {
|
|
2972
|
-
console.log(` \u2022 ${o.org_name}`);
|
|
2973
|
-
}
|
|
2974
|
-
return 0;
|
|
2975
|
-
} catch (e) {
|
|
2976
|
-
if (e instanceof ApiError) {
|
|
2977
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
2978
|
-
return 1;
|
|
2979
|
-
}
|
|
2980
|
-
throw e;
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
|
|
2984
|
-
// src/commands/org-info.ts
|
|
2985
|
-
async function orgInfoCommand(name) {
|
|
2986
|
-
if (!name) {
|
|
2987
|
-
console.error("Usage: token-derby organisation info <name>");
|
|
2988
|
-
return 2;
|
|
2989
|
-
}
|
|
2990
|
-
if (!ORG_NAME_PATTERN.test(name)) {
|
|
2991
|
-
console.error("Organisation name must be 1\u201312 alphanumeric characters.");
|
|
2992
|
-
return 2;
|
|
2993
|
-
}
|
|
2994
|
-
try {
|
|
2995
|
-
const resp = await getOrganisation(name);
|
|
2996
|
-
console.log(`Organisation: ${resp.org_name}`);
|
|
2997
|
-
console.log(`Created: ${resp.created_at} by ${resp.creator_user_name}`);
|
|
2998
|
-
console.log("");
|
|
2999
|
-
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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
3000
|
-
console.log(` \u2551 JOIN TOKEN: ${resp.org_join_token.padEnd(43)}\u2551`);
|
|
3001
|
-
console.log(" \u255A\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
3002
|
-
console.log(" \u26A0 Treat the token as a secret \u2014 anyone with it can join.");
|
|
3003
|
-
console.log("");
|
|
3004
|
-
console.log(` Members join with: token-derby organisation join ${resp.org_join_token}`);
|
|
3005
|
-
return 0;
|
|
3006
|
-
} catch (e) {
|
|
3007
|
-
if (e instanceof ApiError) {
|
|
3008
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
3009
|
-
return 1;
|
|
3010
|
-
}
|
|
3011
|
-
throw e;
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
|
|
3015
|
-
// src/commands/org-webhook-set.ts
|
|
3016
|
-
async function orgWebhookSetCommand(orgName, url) {
|
|
3017
|
-
if (!orgName || !url) {
|
|
3018
|
-
console.error("Usage: token-derby organisation webhook set <org-name> <https-url>");
|
|
3019
|
-
return 2;
|
|
3020
|
-
}
|
|
3021
|
-
try {
|
|
3022
|
-
const resp = await setOrgWebhook(orgName, { url });
|
|
3023
|
-
console.log("");
|
|
3024
|
-
console.log(` Webhook set for ${orgName}: ${resp.webhook_url}`);
|
|
3025
|
-
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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
3026
|
-
console.log(` \u2551 SECRET: ${resp.webhook_secret.padEnd(47)}\u2551`);
|
|
3027
|
-
console.log(" \u255A\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
3028
|
-
console.log(" \u26A0 Save this secret now \u2014 it will not be shown again.");
|
|
3029
|
-
console.log(" Your receiver verifies requests with: X-Token-Derby-Signature: sha256=<hmac(secret, raw body)>.");
|
|
3030
|
-
return 0;
|
|
3031
|
-
} catch (e) {
|
|
3032
|
-
if (e instanceof ApiError) {
|
|
3033
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
3034
|
-
return 1;
|
|
3035
|
-
}
|
|
3036
|
-
throw e;
|
|
3037
|
-
}
|
|
2871
|
+
// src/commands/web.ts
|
|
2872
|
+
import { spawn as spawn2 } from "child_process";
|
|
2873
|
+
function webOrigin() {
|
|
2874
|
+
return apiBase().replace(/\/api\/?$/, "");
|
|
3038
2875
|
}
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
if (
|
|
3043
|
-
|
|
3044
|
-
return 2;
|
|
3045
|
-
}
|
|
3046
|
-
try {
|
|
3047
|
-
const resp = await getOrgWebhook(orgName);
|
|
3048
|
-
if (resp.webhook_url) {
|
|
3049
|
-
console.log(`Webhook for ${orgName}: ${resp.webhook_url}`);
|
|
3050
|
-
} else {
|
|
3051
|
-
console.log(`No webhook configured for ${orgName}.`);
|
|
3052
|
-
}
|
|
3053
|
-
return 0;
|
|
3054
|
-
} catch (e) {
|
|
3055
|
-
if (e instanceof ApiError) {
|
|
3056
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
3057
|
-
return 1;
|
|
3058
|
-
}
|
|
3059
|
-
throw e;
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
// src/commands/org-webhook-clear.ts
|
|
3064
|
-
async function orgWebhookClearCommand(orgName) {
|
|
3065
|
-
if (!orgName) {
|
|
3066
|
-
console.error("Usage: token-derby organisation webhook clear <org-name>");
|
|
3067
|
-
return 2;
|
|
3068
|
-
}
|
|
3069
|
-
try {
|
|
3070
|
-
await deleteOrgWebhook(orgName);
|
|
3071
|
-
console.log(`Webhook removed for ${orgName}.`);
|
|
3072
|
-
return 0;
|
|
3073
|
-
} catch (e) {
|
|
3074
|
-
if (e instanceof ApiError) {
|
|
3075
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
3076
|
-
return 1;
|
|
3077
|
-
}
|
|
3078
|
-
throw e;
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
|
|
3082
|
-
// src/commands/org-schedule-set.ts
|
|
3083
|
-
function flag(args, name) {
|
|
3084
|
-
for (let i = 0; i < args.length; i++) {
|
|
3085
|
-
if (args[i] === name) return args[i + 1];
|
|
3086
|
-
const eq = `${name}=`;
|
|
3087
|
-
if (args[i]?.startsWith(eq)) return args[i].slice(eq.length);
|
|
3088
|
-
}
|
|
3089
|
-
return void 0;
|
|
3090
|
-
}
|
|
3091
|
-
var USAGE = 'Usage: token-derby organisation schedule set <org-name> --days mon-fri --start 09:00 --end 17:30 --tz Europe/London [--name "Daily"] [--max 30] [--counts-input] [--primary-top5]';
|
|
3092
|
-
async function orgScheduleSetCommand(orgName, rest) {
|
|
3093
|
-
if (!orgName) {
|
|
3094
|
-
console.error(USAGE);
|
|
3095
|
-
return 2;
|
|
3096
|
-
}
|
|
3097
|
-
const daysSpec = flag(rest, "--days");
|
|
3098
|
-
const start = flag(rest, "--start");
|
|
3099
|
-
const end = flag(rest, "--end");
|
|
3100
|
-
const tz = flag(rest, "--tz");
|
|
3101
|
-
const name = flag(rest, "--name");
|
|
3102
|
-
const maxStr = flag(rest, "--max");
|
|
3103
|
-
const counts_input = rest.includes("--counts-input");
|
|
3104
|
-
const primary_top5 = rest.includes("--primary-top5");
|
|
3105
|
-
if (!daysSpec || !start || !end || !tz) {
|
|
3106
|
-
console.error("Required flags: --days, --start, --end, --tz");
|
|
3107
|
-
console.error(USAGE);
|
|
3108
|
-
return 2;
|
|
3109
|
-
}
|
|
3110
|
-
const weekdays = parseWeekdays(daysSpec);
|
|
3111
|
-
if (!weekdays) {
|
|
3112
|
-
console.error('Invalid --days. Use day names like "mon-fri" or "mon,wed,fri".');
|
|
3113
|
-
return 2;
|
|
3114
|
-
}
|
|
3115
|
-
let max_participants;
|
|
3116
|
-
if (maxStr !== void 0) {
|
|
3117
|
-
max_participants = Number(maxStr);
|
|
3118
|
-
if (!Number.isInteger(max_participants) || max_participants < 1) {
|
|
3119
|
-
console.error("--max must be a positive integer");
|
|
3120
|
-
return 2;
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
try {
|
|
3124
|
-
const resp = await setOrgSchedule(orgName, {
|
|
3125
|
-
weekdays,
|
|
3126
|
-
start_local: start,
|
|
3127
|
-
end_local: end,
|
|
3128
|
-
tz,
|
|
3129
|
-
...name ? { race_name: name } : {},
|
|
3130
|
-
...max_participants !== void 0 ? { max_participants } : {},
|
|
3131
|
-
...counts_input ? { counts_input: true } : {},
|
|
3132
|
-
...primary_top5 ? { primary_top5: true } : {}
|
|
3133
|
-
});
|
|
3134
|
-
const s = resp.schedule;
|
|
3135
|
-
console.log(`Schedule set for ${orgName}: days [${s.weekdays.join(",")}] ${s.start_local}\u2013${s.end_local} ${s.tz}`);
|
|
3136
|
-
if (s.primary_top5) console.log(" Primary score counts only each racer's top 5 conversations per beat.");
|
|
3137
|
-
return 0;
|
|
3138
|
-
} catch (e) {
|
|
3139
|
-
if (e instanceof ApiError) {
|
|
3140
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
3141
|
-
return 1;
|
|
3142
|
-
}
|
|
3143
|
-
throw e;
|
|
3144
|
-
}
|
|
2876
|
+
function opener() {
|
|
2877
|
+
if (process.platform === "darwin") return "open";
|
|
2878
|
+
if (process.platform === "win32") return "start";
|
|
2879
|
+
if (process.platform === "linux") return "xdg-open";
|
|
2880
|
+
return null;
|
|
3145
2881
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
if (!orgName) {
|
|
3150
|
-
console.error("Usage: token-derby organisation schedule get <org-name>");
|
|
3151
|
-
return 2;
|
|
3152
|
-
}
|
|
2882
|
+
async function webCommand(deps = {}) {
|
|
2883
|
+
const spawnImpl = deps.spawnImpl ?? spawn2;
|
|
2884
|
+
let code;
|
|
3153
2885
|
try {
|
|
3154
|
-
|
|
3155
|
-
if (resp.schedule) {
|
|
3156
|
-
const s = resp.schedule;
|
|
3157
|
-
console.log(`Schedule for ${orgName}: days [${s.weekdays.join(",")}] ${s.start_local}\u2013${s.end_local} ${s.tz}`);
|
|
3158
|
-
if (s.primary_top5) console.log(" Primary top-5 conversations cap: ON.");
|
|
3159
|
-
} else {
|
|
3160
|
-
console.log(`No schedule configured for ${orgName}.`);
|
|
3161
|
-
}
|
|
3162
|
-
return 0;
|
|
2886
|
+
({ code } = await createWebSession());
|
|
3163
2887
|
} catch (e) {
|
|
3164
2888
|
if (e instanceof ApiError) {
|
|
3165
2889
|
console.error(`Error: ${e.code} ${e.message}`);
|
|
@@ -3167,25 +2891,23 @@ async function orgScheduleGetCommand(orgName) {
|
|
|
3167
2891
|
}
|
|
3168
2892
|
throw e;
|
|
3169
2893
|
}
|
|
3170
|
-
}
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
3185
|
-
return 1;
|
|
2894
|
+
const url = `${webOrigin()}/org-manager#code=${code}`;
|
|
2895
|
+
console.log("");
|
|
2896
|
+
console.log(" Opening the Token Derby org manager in your browser...");
|
|
2897
|
+
console.log(` ${url}`);
|
|
2898
|
+
console.log("");
|
|
2899
|
+
console.log(" If it doesn't open, copy the link above. It expires in 60 seconds.");
|
|
2900
|
+
const cmd = opener();
|
|
2901
|
+
if (cmd) {
|
|
2902
|
+
try {
|
|
2903
|
+
const child = spawnImpl(cmd, [url], { stdio: "ignore", detached: true });
|
|
2904
|
+
child.on("error", () => {
|
|
2905
|
+
});
|
|
2906
|
+
child.unref();
|
|
2907
|
+
} catch {
|
|
3186
2908
|
}
|
|
3187
|
-
throw e;
|
|
3188
2909
|
}
|
|
2910
|
+
return 0;
|
|
3189
2911
|
}
|
|
3190
2912
|
|
|
3191
2913
|
// src/bin.ts
|
|
@@ -3207,25 +2929,9 @@ Stable management:
|
|
|
3207
2929
|
token-derby stable delete <name> Remove a horse from your stable
|
|
3208
2930
|
|
|
3209
2931
|
Organisations:
|
|
3210
|
-
token-derby organisation create Create a new organisation (interactive)
|
|
3211
2932
|
token-derby organisation join <token> Join an organisation with a join token
|
|
3212
|
-
token-derby
|
|
3213
|
-
|
|
3214
|
-
token-derby organisation webhook set <name> <url>
|
|
3215
|
-
Configure an https webhook for race events.
|
|
3216
|
-
Prints a secret used to sign each request.
|
|
3217
|
-
Only the org creator can run this.
|
|
3218
|
-
token-derby organisation webhook get <name>
|
|
3219
|
-
Show the org's configured webhook URL (or "no webhook").
|
|
3220
|
-
token-derby organisation webhook clear <name>
|
|
3221
|
-
Remove the webhook for this org.
|
|
3222
|
-
token-derby organisation schedule set <name> --days mon-fri --start 09:00 --end 17:30 --tz Europe/London
|
|
3223
|
-
Configure a repeating race schedule. Races auto-start
|
|
3224
|
-
at the daily start time. Only the org creator can run this.
|
|
3225
|
-
token-derby organisation schedule get <name>
|
|
3226
|
-
Show the org's configured schedule (or "no schedule").
|
|
3227
|
-
token-derby organisation schedule clear <name>
|
|
3228
|
-
Remove the repeating schedule for this org.
|
|
2933
|
+
token-derby web Open the web org manager (create orgs,
|
|
2934
|
+
manage schedules, webhooks, members)
|
|
3229
2935
|
|
|
3230
2936
|
Races:
|
|
3231
2937
|
token-derby create [--organisation <name>]
|
|
@@ -3276,30 +2982,9 @@ async function main() {
|
|
|
3276
2982
|
}
|
|
3277
2983
|
if (cmd === "organisation" || cmd === "org") {
|
|
3278
2984
|
const sub = argv[1];
|
|
3279
|
-
if (sub === "create") return orgCreateCommand();
|
|
3280
2985
|
if (sub === "join") return orgJoinCommand(argv[2]);
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
if (sub === "webhook") {
|
|
3284
|
-
const action = argv[2];
|
|
3285
|
-
if (action === "set") return orgWebhookSetCommand(argv[3], argv[4]);
|
|
3286
|
-
if (action === "get") return orgWebhookGetCommand(argv[3]);
|
|
3287
|
-
if (action === "clear") return orgWebhookClearCommand(argv[3]);
|
|
3288
|
-
console.error(`Unknown webhook action: ${action ?? "(none)"}`);
|
|
3289
|
-
console.error("Try: organisation webhook set <name> <url> | organisation webhook get <name> | organisation webhook clear <name>");
|
|
3290
|
-
return 2;
|
|
3291
|
-
}
|
|
3292
|
-
if (sub === "schedule") {
|
|
3293
|
-
const action = argv[2];
|
|
3294
|
-
if (action === "set") return orgScheduleSetCommand(argv[3], argv.slice(4));
|
|
3295
|
-
if (action === "get") return orgScheduleGetCommand(argv[3]);
|
|
3296
|
-
if (action === "clear") return orgScheduleClearCommand(argv[3]);
|
|
3297
|
-
console.error(`Unknown schedule action: ${action ?? "(none)"}`);
|
|
3298
|
-
console.error("Try: organisation schedule set <name> ... | organisation schedule get <name> | organisation schedule clear <name>");
|
|
3299
|
-
return 2;
|
|
3300
|
-
}
|
|
3301
|
-
console.error(`Unknown organisation subcommand: ${sub ?? "(none)"}`);
|
|
3302
|
-
console.error("Try: organisation create | organisation join <token> | organisation info <name> | organisation list | organisation webhook <set|get|clear> ... | organisation schedule <set|get|clear> ...");
|
|
2986
|
+
console.error(`Organisation management has moved to the web: token-derby web`);
|
|
2987
|
+
console.error(`The only CLI organisation command is: organisation join <token>`);
|
|
3303
2988
|
return 2;
|
|
3304
2989
|
}
|
|
3305
2990
|
if (cmd === "create") {
|
|
@@ -3309,14 +2994,15 @@ async function main() {
|
|
|
3309
2994
|
if (cmd === "join") return joinCommand(argv[1], argv.slice(2));
|
|
3310
2995
|
if (cmd === "end") return endCommand(argv[1]);
|
|
3311
2996
|
if (cmd === "roll") return rollCommand();
|
|
2997
|
+
if (cmd === "web") return webCommand();
|
|
3312
2998
|
console.error(`Unknown command: ${cmd}`);
|
|
3313
2999
|
console.error(HELP);
|
|
3314
3000
|
return 2;
|
|
3315
3001
|
}
|
|
3316
|
-
function parseFlag(args,
|
|
3002
|
+
function parseFlag(args, flag) {
|
|
3317
3003
|
for (let i = 0; i < args.length; i++) {
|
|
3318
|
-
if (args[i] ===
|
|
3319
|
-
const eq = `${
|
|
3004
|
+
if (args[i] === flag) return args[i + 1];
|
|
3005
|
+
const eq = `${flag}=`;
|
|
3320
3006
|
if (args[i]?.startsWith(eq)) return args[i].slice(eq.length);
|
|
3321
3007
|
}
|
|
3322
3008
|
return void 0;
|