@mauricode/token-derby 2.10.1 → 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 CHANGED
@@ -41,6 +41,23 @@ token-derby rejoin <join-code>
41
41
  token-derby end <admin-code>
42
42
  ```
43
43
 
44
+ ## Organisations
45
+
46
+ ```bash
47
+ # Join an organisation with a join token
48
+ token-derby organisation join <token>
49
+ ```
50
+
51
+ Everything else — creating an organisation, managing members, and configuring
52
+ the schedule or webhook — is done on the web:
53
+
54
+ ```bash
55
+ token-derby web
56
+ ```
57
+
58
+ This opens `token-derby.mauricode.co.uk/org-manager` with a one-time login
59
+ link, signed in as your CLI identity.
60
+
44
61
  ## What's tracked
45
62
 
46
63
  The CLI sums `message.usage.output_tokens` across every `*.jsonl` under `~/.claude/projects/`. This includes **subagents and dynamic workflows** — their transcripts nest under `<project>/<session>/subagents/…` (and `…/subagents/workflows/wf_<id>/…`), and the scanner recurses into all of them, so a Plan/Workflow that fans out across many agents counts all of that real output. Your "race tokens" are everything generated since the moment you joined. Tokens generated while disconnected are skipped — that window is your crash penalty.
@@ -76,4 +93,4 @@ at usage you didn't produce.
76
93
  - `TOKEN_DERBY_API_BASE` — override the API base URL (default: `https://token-derby.mauricode.co.uk/api`)
77
94
  - `TOKEN_DERBY_HOME` — override the data directory (default: `~/.token-derby`)
78
95
  - `TOKEN_DERBY_CLAUDE_DIR` — override the transcripts directory (default: `~/.claude/projects`)
79
- - **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 on a schedule with `organisation schedule set --primary-top5`. Off by default (every conversation counts).
96
+ - **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
@@ -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/schedule.js
210
- var ORDER = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
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.10.1".length > 0) {
744
- return "2.10.1";
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() {
@@ -2802,8 +2724,8 @@ function pendingFor(horse) {
2802
2724
  }
2803
2725
  async function promptYesNo(question) {
2804
2726
  resetStdinAfterInk();
2805
- const readline7 = await import("readline/promises");
2806
- const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
2727
+ const readline6 = await import("readline/promises");
2728
+ const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
2807
2729
  const a = (await rl.question(question)).trim().toLowerCase();
2808
2730
  rl.close();
2809
2731
  return a === "" || a === "y" || a === "yes";
@@ -2927,39 +2849,6 @@ No hat this time. +${result.xp_awarded} XP toward your next level.
2927
2849
  }
2928
2850
  }
2929
2851
 
2930
- // src/commands/org-create.ts
2931
- import * as readline6 from "readline/promises";
2932
- import { stdin as stdin6, stdout as stdout6 } from "process";
2933
- async function orgCreateCommand() {
2934
- const rl = readline6.createInterface({ input: stdin6, output: stdout6 });
2935
- try {
2936
- const name = (await rl.question(`Organisation name (1\u2013${ORG_NAME_MAX_LENGTH} alphanumeric chars): `)).trim();
2937
- if (!ORG_NAME_PATTERN.test(name)) {
2938
- console.error(`Name must be 1\u2013${ORG_NAME_MAX_LENGTH} alphanumeric characters (no spaces or symbols).`);
2939
- return 1;
2940
- }
2941
- const resp = await createOrganisation({ name });
2942
- console.log("");
2943
- console.log(` Organisation created: ${resp.org_name}`);
2944
- 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");
2945
- console.log(` \u2551 JOIN TOKEN: ${resp.org_join_token.padEnd(43)}\u2551`);
2946
- 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");
2947
- console.log(" \u26A0 Share this token to invite members. Treat it as a secret.");
2948
- console.log("");
2949
- console.log(` Members join with: token-derby organisation join ${resp.org_join_token}`);
2950
- console.log(` Create org races: token-derby create --organisation ${resp.org_name}`);
2951
- return 0;
2952
- } catch (e) {
2953
- if (e instanceof ApiError) {
2954
- console.error(`Error: ${e.code} ${e.message}`);
2955
- return 1;
2956
- }
2957
- throw e;
2958
- } finally {
2959
- rl.close();
2960
- }
2961
- }
2962
-
2963
2852
  // src/commands/org-join.ts
2964
2853
  async function orgJoinCommand(token) {
2965
2854
  if (!token) {
@@ -2979,209 +2868,22 @@ async function orgJoinCommand(token) {
2979
2868
  }
2980
2869
  }
2981
2870
 
2982
- // src/commands/org-list.ts
2983
- async function orgListCommand() {
2984
- try {
2985
- const resp = await listOrganisations();
2986
- if (resp.organisations.length === 0) {
2987
- console.log("You are not in any organisations.");
2988
- console.log("Create one with: token-derby organisation create");
2989
- console.log("Or join one with: token-derby organisation join <token>");
2990
- return 0;
2991
- }
2992
- console.log(`Your organisations (${resp.organisations.length}):`);
2993
- for (const o of resp.organisations) {
2994
- console.log(` \u2022 ${o.org_name}`);
2995
- }
2996
- return 0;
2997
- } catch (e) {
2998
- if (e instanceof ApiError) {
2999
- console.error(`Error: ${e.code} ${e.message}`);
3000
- return 1;
3001
- }
3002
- throw e;
3003
- }
3004
- }
3005
-
3006
- // src/commands/org-info.ts
3007
- async function orgInfoCommand(name) {
3008
- if (!name) {
3009
- console.error("Usage: token-derby organisation info <name>");
3010
- return 2;
3011
- }
3012
- if (!ORG_NAME_PATTERN.test(name)) {
3013
- console.error("Organisation name must be 1\u201312 alphanumeric characters.");
3014
- return 2;
3015
- }
3016
- try {
3017
- const resp = await getOrganisation(name);
3018
- console.log(`Organisation: ${resp.org_name}`);
3019
- console.log(`Created: ${resp.created_at} by ${resp.creator_user_name}`);
3020
- console.log("");
3021
- 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");
3022
- console.log(` \u2551 JOIN TOKEN: ${resp.org_join_token.padEnd(43)}\u2551`);
3023
- 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");
3024
- console.log(" \u26A0 Treat the token as a secret \u2014 anyone with it can join.");
3025
- console.log("");
3026
- console.log(` Members join with: token-derby organisation join ${resp.org_join_token}`);
3027
- return 0;
3028
- } catch (e) {
3029
- if (e instanceof ApiError) {
3030
- console.error(`Error: ${e.code} ${e.message}`);
3031
- return 1;
3032
- }
3033
- throw e;
3034
- }
3035
- }
3036
-
3037
- // src/commands/org-webhook-set.ts
3038
- async function orgWebhookSetCommand(orgName, url) {
3039
- if (!orgName || !url) {
3040
- console.error("Usage: token-derby organisation webhook set <org-name> <https-url>");
3041
- return 2;
3042
- }
3043
- try {
3044
- const resp = await setOrgWebhook(orgName, { url });
3045
- console.log("");
3046
- console.log(` Webhook set for ${orgName}: ${resp.webhook_url}`);
3047
- 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");
3048
- console.log(` \u2551 SECRET: ${resp.webhook_secret.padEnd(47)}\u2551`);
3049
- 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");
3050
- console.log(" \u26A0 Save this secret now \u2014 it will not be shown again.");
3051
- console.log(" Your receiver verifies requests with: X-Token-Derby-Signature: sha256=<hmac(secret, raw body)>.");
3052
- return 0;
3053
- } catch (e) {
3054
- if (e instanceof ApiError) {
3055
- console.error(`Error: ${e.code} ${e.message}`);
3056
- return 1;
3057
- }
3058
- throw e;
3059
- }
3060
- }
3061
-
3062
- // src/commands/org-webhook-get.ts
3063
- async function orgWebhookGetCommand(orgName) {
3064
- if (!orgName) {
3065
- console.error("Usage: token-derby organisation webhook get <org-name>");
3066
- return 2;
3067
- }
3068
- try {
3069
- const resp = await getOrgWebhook(orgName);
3070
- if (resp.webhook_url) {
3071
- console.log(`Webhook for ${orgName}: ${resp.webhook_url}`);
3072
- } else {
3073
- console.log(`No webhook configured for ${orgName}.`);
3074
- }
3075
- return 0;
3076
- } catch (e) {
3077
- if (e instanceof ApiError) {
3078
- console.error(`Error: ${e.code} ${e.message}`);
3079
- return 1;
3080
- }
3081
- throw e;
3082
- }
3083
- }
3084
-
3085
- // src/commands/org-webhook-clear.ts
3086
- async function orgWebhookClearCommand(orgName) {
3087
- if (!orgName) {
3088
- console.error("Usage: token-derby organisation webhook clear <org-name>");
3089
- return 2;
3090
- }
3091
- try {
3092
- await deleteOrgWebhook(orgName);
3093
- console.log(`Webhook removed for ${orgName}.`);
3094
- return 0;
3095
- } catch (e) {
3096
- if (e instanceof ApiError) {
3097
- console.error(`Error: ${e.code} ${e.message}`);
3098
- return 1;
3099
- }
3100
- throw e;
3101
- }
3102
- }
3103
-
3104
- // src/commands/org-schedule-set.ts
3105
- function flag(args, name) {
3106
- for (let i = 0; i < args.length; i++) {
3107
- if (args[i] === name) return args[i + 1];
3108
- const eq = `${name}=`;
3109
- if (args[i]?.startsWith(eq)) return args[i].slice(eq.length);
3110
- }
3111
- return void 0;
2871
+ // src/commands/web.ts
2872
+ import { spawn as spawn2 } from "child_process";
2873
+ function webOrigin() {
2874
+ return apiBase().replace(/\/api\/?$/, "");
3112
2875
  }
3113
- 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]';
3114
- async function orgScheduleSetCommand(orgName, rest) {
3115
- if (!orgName) {
3116
- console.error(USAGE);
3117
- return 2;
3118
- }
3119
- const daysSpec = flag(rest, "--days");
3120
- const start = flag(rest, "--start");
3121
- const end = flag(rest, "--end");
3122
- const tz = flag(rest, "--tz");
3123
- const name = flag(rest, "--name");
3124
- const maxStr = flag(rest, "--max");
3125
- const counts_input = rest.includes("--counts-input");
3126
- const primary_top5 = rest.includes("--primary-top5");
3127
- if (!daysSpec || !start || !end || !tz) {
3128
- console.error("Required flags: --days, --start, --end, --tz");
3129
- console.error(USAGE);
3130
- return 2;
3131
- }
3132
- const weekdays = parseWeekdays(daysSpec);
3133
- if (!weekdays) {
3134
- console.error('Invalid --days. Use day names like "mon-fri" or "mon,wed,fri".');
3135
- return 2;
3136
- }
3137
- let max_participants;
3138
- if (maxStr !== void 0) {
3139
- max_participants = Number(maxStr);
3140
- if (!Number.isInteger(max_participants) || max_participants < 1) {
3141
- console.error("--max must be a positive integer");
3142
- return 2;
3143
- }
3144
- }
3145
- try {
3146
- const resp = await setOrgSchedule(orgName, {
3147
- weekdays,
3148
- start_local: start,
3149
- end_local: end,
3150
- tz,
3151
- ...name ? { race_name: name } : {},
3152
- ...max_participants !== void 0 ? { max_participants } : {},
3153
- ...counts_input ? { counts_input: true } : {},
3154
- ...primary_top5 ? { primary_top5: true } : {}
3155
- });
3156
- const s = resp.schedule;
3157
- console.log(`Schedule set for ${orgName}: days [${s.weekdays.join(",")}] ${s.start_local}\u2013${s.end_local} ${s.tz}`);
3158
- if (s.primary_top5) console.log(" Primary score counts only each racer's top 5 conversations per beat.");
3159
- return 0;
3160
- } catch (e) {
3161
- if (e instanceof ApiError) {
3162
- console.error(`Error: ${e.code} ${e.message}`);
3163
- return 1;
3164
- }
3165
- throw e;
3166
- }
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;
3167
2881
  }
3168
-
3169
- // src/commands/org-schedule-get.ts
3170
- async function orgScheduleGetCommand(orgName) {
3171
- if (!orgName) {
3172
- console.error("Usage: token-derby organisation schedule get <org-name>");
3173
- return 2;
3174
- }
2882
+ async function webCommand(deps = {}) {
2883
+ const spawnImpl = deps.spawnImpl ?? spawn2;
2884
+ let code;
3175
2885
  try {
3176
- const resp = await getOrgSchedule(orgName);
3177
- if (resp.schedule) {
3178
- const s = resp.schedule;
3179
- console.log(`Schedule for ${orgName}: days [${s.weekdays.join(",")}] ${s.start_local}\u2013${s.end_local} ${s.tz}`);
3180
- if (s.primary_top5) console.log(" Primary top-5 conversations cap: ON.");
3181
- } else {
3182
- console.log(`No schedule configured for ${orgName}.`);
3183
- }
3184
- return 0;
2886
+ ({ code } = await createWebSession());
3185
2887
  } catch (e) {
3186
2888
  if (e instanceof ApiError) {
3187
2889
  console.error(`Error: ${e.code} ${e.message}`);
@@ -3189,25 +2891,23 @@ async function orgScheduleGetCommand(orgName) {
3189
2891
  }
3190
2892
  throw e;
3191
2893
  }
3192
- }
3193
-
3194
- // src/commands/org-schedule-clear.ts
3195
- async function orgScheduleClearCommand(orgName) {
3196
- if (!orgName) {
3197
- console.error("Usage: token-derby organisation schedule clear <org-name>");
3198
- return 2;
3199
- }
3200
- try {
3201
- await clearOrgSchedule(orgName);
3202
- console.log(`Schedule removed for ${orgName}.`);
3203
- return 0;
3204
- } catch (e) {
3205
- if (e instanceof ApiError) {
3206
- console.error(`Error: ${e.code} ${e.message}`);
3207
- 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 {
3208
2908
  }
3209
- throw e;
3210
2909
  }
2910
+ return 0;
3211
2911
  }
3212
2912
 
3213
2913
  // src/bin.ts
@@ -3229,25 +2929,9 @@ Stable management:
3229
2929
  token-derby stable delete <name> Remove a horse from your stable
3230
2930
 
3231
2931
  Organisations:
3232
- token-derby organisation create Create a new organisation (interactive)
3233
2932
  token-derby organisation join <token> Join an organisation with a join token
3234
- token-derby organisation info <name> Show an org's join token (members only)
3235
- token-derby organisation list Show organisations you're a member of
3236
- token-derby organisation webhook set <name> <url>
3237
- Configure an https webhook for race events.
3238
- Prints a secret used to sign each request.
3239
- Only the org creator can run this.
3240
- token-derby organisation webhook get <name>
3241
- Show the org's configured webhook URL (or "no webhook").
3242
- token-derby organisation webhook clear <name>
3243
- Remove the webhook for this org.
3244
- token-derby organisation schedule set <name> --days mon-fri --start 09:00 --end 17:30 --tz Europe/London
3245
- Configure a repeating race schedule. Races auto-start
3246
- at the daily start time. Only the org creator can run this.
3247
- token-derby organisation schedule get <name>
3248
- Show the org's configured schedule (or "no schedule").
3249
- token-derby organisation schedule clear <name>
3250
- Remove the repeating schedule for this org.
2933
+ token-derby web Open the web org manager (create orgs,
2934
+ manage schedules, webhooks, members)
3251
2935
 
3252
2936
  Races:
3253
2937
  token-derby create [--organisation <name>]
@@ -3298,30 +2982,9 @@ async function main() {
3298
2982
  }
3299
2983
  if (cmd === "organisation" || cmd === "org") {
3300
2984
  const sub = argv[1];
3301
- if (sub === "create") return orgCreateCommand();
3302
2985
  if (sub === "join") return orgJoinCommand(argv[2]);
3303
- if (sub === "info") return orgInfoCommand(argv[2]);
3304
- if (sub === "list") return orgListCommand();
3305
- if (sub === "webhook") {
3306
- const action = argv[2];
3307
- if (action === "set") return orgWebhookSetCommand(argv[3], argv[4]);
3308
- if (action === "get") return orgWebhookGetCommand(argv[3]);
3309
- if (action === "clear") return orgWebhookClearCommand(argv[3]);
3310
- console.error(`Unknown webhook action: ${action ?? "(none)"}`);
3311
- console.error("Try: organisation webhook set <name> <url> | organisation webhook get <name> | organisation webhook clear <name>");
3312
- return 2;
3313
- }
3314
- if (sub === "schedule") {
3315
- const action = argv[2];
3316
- if (action === "set") return orgScheduleSetCommand(argv[3], argv.slice(4));
3317
- if (action === "get") return orgScheduleGetCommand(argv[3]);
3318
- if (action === "clear") return orgScheduleClearCommand(argv[3]);
3319
- console.error(`Unknown schedule action: ${action ?? "(none)"}`);
3320
- console.error("Try: organisation schedule set <name> ... | organisation schedule get <name> | organisation schedule clear <name>");
3321
- return 2;
3322
- }
3323
- console.error(`Unknown organisation subcommand: ${sub ?? "(none)"}`);
3324
- 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>`);
3325
2988
  return 2;
3326
2989
  }
3327
2990
  if (cmd === "create") {
@@ -3331,14 +2994,15 @@ async function main() {
3331
2994
  if (cmd === "join") return joinCommand(argv[1], argv.slice(2));
3332
2995
  if (cmd === "end") return endCommand(argv[1]);
3333
2996
  if (cmd === "roll") return rollCommand();
2997
+ if (cmd === "web") return webCommand();
3334
2998
  console.error(`Unknown command: ${cmd}`);
3335
2999
  console.error(HELP);
3336
3000
  return 2;
3337
3001
  }
3338
- function parseFlag(args, flag2) {
3002
+ function parseFlag(args, flag) {
3339
3003
  for (let i = 0; i < args.length; i++) {
3340
- if (args[i] === flag2) return args[i + 1];
3341
- const eq = `${flag2}=`;
3004
+ if (args[i] === flag) return args[i + 1];
3005
+ const eq = `${flag}=`;
3342
3006
  if (args[i]?.startsWith(eq)) return args[i].slice(eq.length);
3343
3007
  }
3344
3008
  return void 0;