@mauricode/token-derby 2.13.1 → 3.1.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/dist/bin.js +757 -173
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -36,7 +36,9 @@ var CLI_VERSION_HEADER = "x-cli-version";
|
|
|
36
36
|
var USER_ID_HEADER = "x-user-id";
|
|
37
37
|
var USER_TOKEN_HEADER = "x-user-token";
|
|
38
38
|
var USER_NAME_MAX_LENGTH = 40;
|
|
39
|
+
var SECRET_TOKEN_BYTES = 32;
|
|
39
40
|
var ORG_NAME_PATTERN = /^[A-Za-z0-9]{1,12}$/;
|
|
41
|
+
var DEVICE_LABEL_MAX_LENGTH = 40;
|
|
40
42
|
|
|
41
43
|
// ../shared/dist/version-match.js
|
|
42
44
|
var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/;
|
|
@@ -197,9 +199,31 @@ var HATS = [
|
|
|
197
199
|
// Claim-only: awarded by an admin, never from a roll. Only the A channel cycles,
|
|
198
200
|
// so the inset motif shifts colour while the cap body stays a static rust brown.
|
|
199
201
|
// The body colour is kept out of the frame list so the motif never merges into it.
|
|
200
|
-
{ 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 }
|
|
201
212
|
];
|
|
202
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
|
+
|
|
203
227
|
// ../shared/dist/series-transform.js
|
|
204
228
|
var PACE_WINDOW_MS = 15 * 6e4;
|
|
205
229
|
|
|
@@ -289,6 +313,22 @@ var STAMINA_PARAM_BOUNDS = {
|
|
|
289
313
|
tired_multiplier: { min: 0.2, max: 0.9, step: 0.05, default: STAMINA.TIRED_MULTIPLIER }
|
|
290
314
|
};
|
|
291
315
|
|
|
316
|
+
// ../shared/dist/devices.js
|
|
317
|
+
var DEVICE_CODE_LENGTH = Math.ceil(SECRET_TOKEN_BYTES * 4 / 3);
|
|
318
|
+
var UNSAFE_LABEL_CHARS = /[\p{Cc}\p{Cf}]/u;
|
|
319
|
+
function validateDeviceLabel(raw) {
|
|
320
|
+
if (typeof raw !== "string")
|
|
321
|
+
return { ok: false, message: "label is required" };
|
|
322
|
+
const label = raw.trim();
|
|
323
|
+
if (label.length < 1 || label.length > DEVICE_LABEL_MAX_LENGTH) {
|
|
324
|
+
return { ok: false, message: `label must be 1\u2013${DEVICE_LABEL_MAX_LENGTH} characters` };
|
|
325
|
+
}
|
|
326
|
+
if (UNSAFE_LABEL_CHARS.test(label)) {
|
|
327
|
+
return { ok: false, message: "label may not contain control or invisible characters" };
|
|
328
|
+
}
|
|
329
|
+
return { ok: true, label };
|
|
330
|
+
}
|
|
331
|
+
|
|
292
332
|
// src/ui/HorseSprite.tsx
|
|
293
333
|
import { Box, Text } from "ink";
|
|
294
334
|
|
|
@@ -415,7 +455,7 @@ function composeHatGrid(baseSprite, hat, variantIdx, horseColors) {
|
|
|
415
455
|
grid[y + ext][x + horseOffsetX] = tagToColor(tag, horseColors);
|
|
416
456
|
}
|
|
417
457
|
}
|
|
418
|
-
const
|
|
458
|
+
const colors = hatColors(hat, variantIdx);
|
|
419
459
|
for (let y = 0; y < hatH; y++) {
|
|
420
460
|
const row = hat.rows[y];
|
|
421
461
|
for (let x = 0; x < hatW; x++) {
|
|
@@ -423,7 +463,7 @@ function composeHatGrid(baseSprite, hat, variantIdx, horseColors) {
|
|
|
423
463
|
if (ch === "." || ch === void 0) continue;
|
|
424
464
|
const gx = hat.anchor_x + x + horseOffsetX;
|
|
425
465
|
if (gx < 0 || gx >= canvasW) continue;
|
|
426
|
-
const color = ch
|
|
466
|
+
const color = channelColor(colors, ch);
|
|
427
467
|
grid[y][gx] = color;
|
|
428
468
|
}
|
|
429
469
|
}
|
|
@@ -445,10 +485,6 @@ function tagToColor(tag, c) {
|
|
|
445
485
|
return FIXED_COLORS.H;
|
|
446
486
|
}
|
|
447
487
|
}
|
|
448
|
-
function hatColorsFor(hat, variantIdx) {
|
|
449
|
-
if (hat.rarity === "legendary") return hat.colors;
|
|
450
|
-
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
451
|
-
}
|
|
452
488
|
|
|
453
489
|
// src/ui/half-blocks.ts
|
|
454
490
|
var RESET = "\x1B[0m";
|
|
@@ -520,19 +556,19 @@ import { useEffect, useState } from "react";
|
|
|
520
556
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
521
557
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
522
558
|
function AnimatedHorseSprite({ sprite, colors, hat }) {
|
|
523
|
-
const
|
|
524
|
-
const frames =
|
|
525
|
-
const fps =
|
|
559
|
+
const animated = isAnimatedHat(hat);
|
|
560
|
+
const frames = animated ? hat.animation.frames : [];
|
|
561
|
+
const fps = animated ? hat.animation.fps : 1;
|
|
526
562
|
const [idx, setIdx] = useState(0);
|
|
527
563
|
useEffect(() => {
|
|
528
|
-
if (!
|
|
564
|
+
if (!animated || frames.length <= 1) return;
|
|
529
565
|
const interval = setInterval(
|
|
530
566
|
() => setIdx((i) => (i + 1) % frames.length),
|
|
531
567
|
Math.max(1, Math.round(1e3 / fps))
|
|
532
568
|
);
|
|
533
569
|
return () => clearInterval(interval);
|
|
534
|
-
}, [
|
|
535
|
-
const renderedHat =
|
|
570
|
+
}, [animated, frames.length, fps]);
|
|
571
|
+
const renderedHat = animated && frames[idx] ? { ...hat, colors: { ...hat.colors, A: frames[idx] } } : hat;
|
|
536
572
|
const { grid } = composeHatGrid(sprite, renderedHat, 0, colors);
|
|
537
573
|
const lines = hexGridToHalfBlocks(grid);
|
|
538
574
|
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx2(Text2, { children: line }, i)) });
|
|
@@ -721,7 +757,7 @@ function HorseCreator({
|
|
|
721
757
|
"]"
|
|
722
758
|
] })
|
|
723
759
|
] }),
|
|
724
|
-
/* @__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 }) }),
|
|
725
761
|
/* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: rows.map((r, i) => {
|
|
726
762
|
const cursor = i === rowIdx ? "\u25BA" : " ";
|
|
727
763
|
if (r.kind === "color") {
|
|
@@ -767,8 +803,8 @@ function renderHatLabel(hats, hatChoice, initialEquipped) {
|
|
|
767
803
|
if (!collected) return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "?" });
|
|
768
804
|
const hat = hatById(collected.id);
|
|
769
805
|
const name = hat?.name ?? collected.id;
|
|
770
|
-
const variantSuffix = hat && hat
|
|
771
|
-
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";
|
|
772
808
|
const equippedMark = initialEquipped === hatChoice ? " \u2713" : "";
|
|
773
809
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
774
810
|
name,
|
|
@@ -825,8 +861,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
|
825
861
|
// src/version.ts
|
|
826
862
|
import { createRequire } from "module";
|
|
827
863
|
function readVersion() {
|
|
828
|
-
if ("
|
|
829
|
-
return "
|
|
864
|
+
if ("3.1.0".length > 0) {
|
|
865
|
+
return "3.1.0";
|
|
830
866
|
}
|
|
831
867
|
try {
|
|
832
868
|
const req = createRequire(import.meta.url);
|
|
@@ -871,18 +907,37 @@ function geminiTmpDir() {
|
|
|
871
907
|
}
|
|
872
908
|
|
|
873
909
|
// src/identity/identity.ts
|
|
874
|
-
async function
|
|
910
|
+
async function readIdentityFile() {
|
|
911
|
+
let raw;
|
|
875
912
|
try {
|
|
876
|
-
|
|
877
|
-
const parsed = JSON.parse(raw);
|
|
878
|
-
if (typeof parsed.user_id === "string" && typeof parsed.display_name === "string" && typeof parsed.secret_token === "string" && typeof parsed.created_at === "string") {
|
|
879
|
-
return parsed;
|
|
880
|
-
}
|
|
881
|
-
return null;
|
|
913
|
+
raw = await fs.readFile(identityFile(), "utf8");
|
|
882
914
|
} catch (e) {
|
|
883
|
-
if (e?.code === "ENOENT") return
|
|
884
|
-
return
|
|
915
|
+
if (e?.code === "ENOENT") return { kind: "missing" };
|
|
916
|
+
return { kind: "unreadable", reason: `could not be read (${e?.code ?? "read error"})` };
|
|
917
|
+
}
|
|
918
|
+
let parsed;
|
|
919
|
+
try {
|
|
920
|
+
parsed = JSON.parse(raw);
|
|
921
|
+
} catch {
|
|
922
|
+
return { kind: "unreadable", reason: "is not valid JSON" };
|
|
885
923
|
}
|
|
924
|
+
if (!isPlainObject(parsed)) {
|
|
925
|
+
return { kind: "unreadable", reason: "does not contain an identity object" };
|
|
926
|
+
}
|
|
927
|
+
if (typeof parsed.user_id === "string" && typeof parsed.display_name === "string" && typeof parsed.secret_token === "string" && typeof parsed.created_at === "string") {
|
|
928
|
+
return { kind: "ok", identity: parsed };
|
|
929
|
+
}
|
|
930
|
+
if (!("secret_token" in parsed)) {
|
|
931
|
+
return { kind: "no-credential", reason: "holds no credential this version can use" };
|
|
932
|
+
}
|
|
933
|
+
return { kind: "unreadable", reason: "is missing fields this version expects" };
|
|
934
|
+
}
|
|
935
|
+
function isPlainObject(value) {
|
|
936
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
937
|
+
}
|
|
938
|
+
async function loadIdentity() {
|
|
939
|
+
const state = await readIdentityFile();
|
|
940
|
+
return state.kind === "ok" ? state.identity : null;
|
|
886
941
|
}
|
|
887
942
|
async function saveIdentity(identity) {
|
|
888
943
|
await fs.mkdir(homeDir(), { recursive: true });
|
|
@@ -923,12 +978,12 @@ function getIdentity() {
|
|
|
923
978
|
function _resetIdentityCacheForTests() {
|
|
924
979
|
identityCache = null;
|
|
925
980
|
}
|
|
926
|
-
async function request(method, path9, body, horseAuthToken, fetchImpl = fetch) {
|
|
981
|
+
async function request(method, path9, body, horseAuthToken, fetchImpl = fetch, identityOverride) {
|
|
927
982
|
const url = path9.startsWith("http") ? path9 : `${apiBase()}${path9}`;
|
|
928
983
|
const headers = {};
|
|
929
984
|
headers[CLI_VERSION_HEADER] = CLI_VERSION;
|
|
930
985
|
headers["user-agent"] = `token-derby/${CLI_VERSION}`;
|
|
931
|
-
const identity = await getIdentity();
|
|
986
|
+
const identity = identityOverride ?? await getIdentity();
|
|
932
987
|
if (identity) {
|
|
933
988
|
headers[USER_ID_HEADER] = identity.user_id;
|
|
934
989
|
headers[USER_TOKEN_HEADER] = identity.secret_token;
|
|
@@ -994,6 +1049,9 @@ function listOrganisations() {
|
|
|
994
1049
|
function initJockey(body) {
|
|
995
1050
|
return request("POST", "/jockey/init", body, void 0);
|
|
996
1051
|
}
|
|
1052
|
+
function getJockey() {
|
|
1053
|
+
return request("GET", "/jockey/me", void 0, void 0);
|
|
1054
|
+
}
|
|
997
1055
|
function updateJockey(body) {
|
|
998
1056
|
return request("PUT", "/jockey/me", body, void 0);
|
|
999
1057
|
}
|
|
@@ -1034,6 +1092,28 @@ function probeClaim(code) {
|
|
|
1034
1092
|
function redeemClaim(code, body) {
|
|
1035
1093
|
return request("POST", `/claims/${encodeURIComponent(code)}/redeem`, body, void 0);
|
|
1036
1094
|
}
|
|
1095
|
+
function cliAuthStart(body) {
|
|
1096
|
+
return request("POST", "/auth/cli/start", body, void 0);
|
|
1097
|
+
}
|
|
1098
|
+
function cliAuthPoll(body) {
|
|
1099
|
+
return request("POST", "/auth/cli/poll", body, void 0);
|
|
1100
|
+
}
|
|
1101
|
+
function revokeDevice(deviceId, auth) {
|
|
1102
|
+
return request(
|
|
1103
|
+
"DELETE",
|
|
1104
|
+
`/devices/${encodeURIComponent(deviceId)}`,
|
|
1105
|
+
void 0,
|
|
1106
|
+
void 0,
|
|
1107
|
+
void 0,
|
|
1108
|
+
auth
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
function registerDevice(body) {
|
|
1112
|
+
return request("POST", "/devices", body, void 0);
|
|
1113
|
+
}
|
|
1114
|
+
function logoutDevice() {
|
|
1115
|
+
return request("DELETE", "/devices/me", void 0, void 0);
|
|
1116
|
+
}
|
|
1037
1117
|
|
|
1038
1118
|
// src/commands/stable-create.ts
|
|
1039
1119
|
async function stableCreateCommand() {
|
|
@@ -2461,7 +2541,7 @@ async function joinCommand(joinCode, argv = []) {
|
|
|
2461
2541
|
}
|
|
2462
2542
|
const identity = await loadIdentity();
|
|
2463
2543
|
if (!identity) {
|
|
2464
|
-
console.error("Run `token-derby
|
|
2544
|
+
console.error("Run `token-derby login` to set up your identity.");
|
|
2465
2545
|
return 1;
|
|
2466
2546
|
}
|
|
2467
2547
|
let race;
|
|
@@ -2634,72 +2714,346 @@ async function endCommand(adminCode) {
|
|
|
2634
2714
|
// src/commands/init.ts
|
|
2635
2715
|
import * as readline4 from "readline/promises";
|
|
2636
2716
|
import { stdin as stdin4, stdout as stdout4 } from "process";
|
|
2637
|
-
async function
|
|
2638
|
-
if (reset) {
|
|
2639
|
-
await deleteIdentity();
|
|
2640
|
-
_resetIdentityCacheForTests();
|
|
2641
|
-
console.log("Removed local identity. Creating a new one\u2026");
|
|
2642
|
-
}
|
|
2643
|
-
const existing = await loadIdentity();
|
|
2717
|
+
async function defaultPromptText(question) {
|
|
2644
2718
|
const rl = readline4.createInterface({ input: stdin4, output: stdout4 });
|
|
2645
2719
|
try {
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2720
|
+
return await rl.question(question);
|
|
2721
|
+
} finally {
|
|
2722
|
+
rl.close();
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
async function initCommand(reset = false, deps = {}) {
|
|
2726
|
+
const loadIdentity2 = deps.loadIdentity ?? loadIdentity;
|
|
2727
|
+
const saveIdentity2 = deps.saveIdentity ?? saveIdentity;
|
|
2728
|
+
const deleteIdentity2 = deps.deleteIdentity ?? deleteIdentity;
|
|
2729
|
+
const readIdentityFile2 = deps.readIdentityFile ?? readIdentityFile;
|
|
2730
|
+
const initJockey2 = deps.initJockey ?? initJockey;
|
|
2731
|
+
const updateJockey2 = deps.updateJockey ?? updateJockey;
|
|
2732
|
+
const apiGetJockey = deps.apiGetJockey ?? getJockey;
|
|
2733
|
+
const promptText = deps.promptText ?? defaultPromptText;
|
|
2734
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
2735
|
+
console.log("");
|
|
2736
|
+
console.log(
|
|
2737
|
+
"Heads up: `token-derby login` is becoming the only way to manage accounts on this machine. `init` still works today, but plan to move over."
|
|
2738
|
+
);
|
|
2739
|
+
console.log("");
|
|
2740
|
+
if (reset) {
|
|
2741
|
+
const current = await readIdentityFile2();
|
|
2742
|
+
if (current.kind !== "missing") {
|
|
2743
|
+
if (current.kind === "ok") {
|
|
2744
|
+
console.log(`About to abandon jockey: ${current.identity.display_name}`);
|
|
2745
|
+
} else if (current.kind === "unreadable") {
|
|
2746
|
+
console.log(`About to delete identity.json, which ${current.reason}.`);
|
|
2747
|
+
console.log("The credential inside cannot be read, so there is no way to tell which jockey");
|
|
2748
|
+
console.log("this is, or whether anything else can still recover it.");
|
|
2749
|
+
} else {
|
|
2750
|
+
console.log(`About to delete identity.json, which ${current.reason}.`);
|
|
2751
|
+
}
|
|
2752
|
+
console.log("This deletes the local identity only \u2014 the account itself stays on the server.");
|
|
2753
|
+
if (current.kind === "ok") {
|
|
2754
|
+
try {
|
|
2755
|
+
const me = await apiGetJockey();
|
|
2756
|
+
if (me.device_label) {
|
|
2757
|
+
console.log(` This machine's credential, "${me.device_label}", will stay live on the`);
|
|
2758
|
+
console.log(" abandoned account and can only be revoked from the Account view in the org manager.");
|
|
2759
|
+
}
|
|
2760
|
+
if (me.email) {
|
|
2761
|
+
console.log(" This account has a Google account linked \u2014 `token-derby login` would recover");
|
|
2762
|
+
console.log(" this same jockey instead of abandoning it.");
|
|
2763
|
+
}
|
|
2764
|
+
} catch {
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
if (!isTTY) {
|
|
2768
|
+
console.error("");
|
|
2769
|
+
console.error("Refusing to reset without a terminal to confirm \u2014 this is irreversible.");
|
|
2770
|
+
console.error(
|
|
2771
|
+
"Run `token-derby init --reset` interactively, or use `token-derby login` to recover this jockey instead."
|
|
2772
|
+
);
|
|
2773
|
+
return 1;
|
|
2774
|
+
}
|
|
2775
|
+
const answer = (await promptText('Type "yes" to abandon this jockey [no]: ')).trim().toLowerCase();
|
|
2776
|
+
if (answer !== "yes") {
|
|
2777
|
+
console.log("Reset cancelled. Nothing was deleted.");
|
|
2651
2778
|
return 0;
|
|
2652
2779
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2780
|
+
}
|
|
2781
|
+
await deleteIdentity2();
|
|
2782
|
+
_resetIdentityCacheForTests();
|
|
2783
|
+
console.log("Removed local identity. Creating a new one\u2026");
|
|
2784
|
+
}
|
|
2785
|
+
if (!reset) {
|
|
2786
|
+
const state = await readIdentityFile2();
|
|
2787
|
+
if (state.kind === "unreadable") {
|
|
2788
|
+
console.error(`Your local identity file ${state.reason}.`);
|
|
2789
|
+
console.error("It may hold a credential that cannot be recovered, so this will not overwrite it.");
|
|
2790
|
+
console.error("Run `token-derby login` to sign in fresh, or `token-derby init --reset` to discard it.");
|
|
2791
|
+
return 1;
|
|
2792
|
+
}
|
|
2793
|
+
if (state.kind === "no-credential") {
|
|
2794
|
+
console.log(`Your local identity file ${state.reason}, so this will replace it.`);
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
const existing = await loadIdentity2();
|
|
2798
|
+
if (existing) {
|
|
2799
|
+
console.log(`Current jockey name: ${existing.display_name}`);
|
|
2800
|
+
const raw2 = (await promptText("New jockey name (use your real name please) [keep]: ")).trim();
|
|
2801
|
+
if (!raw2) {
|
|
2802
|
+
console.log("Kept existing name.");
|
|
2803
|
+
return 0;
|
|
2804
|
+
}
|
|
2805
|
+
const v2 = validateDisplayName(raw2);
|
|
2806
|
+
if (!v2.ok) {
|
|
2807
|
+
console.error(v2.error);
|
|
2808
|
+
return 1;
|
|
2809
|
+
}
|
|
2810
|
+
try {
|
|
2811
|
+
const resp = await updateJockey2({ display_name: v2.name });
|
|
2812
|
+
const updated = { ...existing, display_name: resp.display_name };
|
|
2813
|
+
await saveIdentity2(updated);
|
|
2814
|
+
console.log(`Updated jockey name to: ${updated.display_name}`);
|
|
2815
|
+
return 0;
|
|
2816
|
+
} catch (e) {
|
|
2817
|
+
if (e instanceof ApiError) {
|
|
2818
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
2819
|
+
console.error(
|
|
2820
|
+
"Server does not recognise this identity. Your account may have been wiped. Run `token-derby init --reset` to start fresh."
|
|
2821
|
+
);
|
|
2822
|
+
} else {
|
|
2823
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2824
|
+
}
|
|
2656
2825
|
return 1;
|
|
2657
2826
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2827
|
+
throw e;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
const raw = (await promptText("Jockey Name (use your real name please): ")).trim();
|
|
2831
|
+
const v = validateDisplayName(raw);
|
|
2832
|
+
if (!v.ok) {
|
|
2833
|
+
console.error(v.error);
|
|
2834
|
+
return 1;
|
|
2835
|
+
}
|
|
2836
|
+
try {
|
|
2837
|
+
const resp = await initJockey2({ display_name: v.name });
|
|
2838
|
+
const identity = {
|
|
2839
|
+
user_id: resp.user_id,
|
|
2840
|
+
display_name: resp.display_name,
|
|
2841
|
+
secret_token: resp.secret_token,
|
|
2842
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2843
|
+
};
|
|
2844
|
+
await saveIdentity2(identity);
|
|
2845
|
+
_resetIdentityCacheForTests();
|
|
2846
|
+
console.log("");
|
|
2847
|
+
console.log(`Welcome, ${identity.display_name}!`);
|
|
2848
|
+
console.log("Your identity has been created on the server.");
|
|
2849
|
+
console.log("You can now create a stable and join races.");
|
|
2850
|
+
console.log("");
|
|
2851
|
+
console.log(" \u26A0 Your secret token is stored locally in identity.json.");
|
|
2852
|
+
console.log(" If you lose it, you cannot recover this account \u2014 you would");
|
|
2853
|
+
console.log(" need to run `token-derby init --reset` and rebuild your stable.");
|
|
2854
|
+
return 0;
|
|
2855
|
+
} catch (e) {
|
|
2856
|
+
if (e instanceof ApiError) {
|
|
2857
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2858
|
+
return 1;
|
|
2859
|
+
}
|
|
2860
|
+
throw e;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
// src/commands/login.ts
|
|
2865
|
+
import { spawn as spawn2 } from "child_process";
|
|
2866
|
+
import * as os3 from "os";
|
|
2867
|
+
|
|
2868
|
+
// src/commands/web.ts
|
|
2869
|
+
import { spawn } from "child_process";
|
|
2870
|
+
function webOrigin() {
|
|
2871
|
+
return apiBase().replace(/\/api\/?$/, "");
|
|
2872
|
+
}
|
|
2873
|
+
function opener() {
|
|
2874
|
+
if (process.platform === "darwin") return "open";
|
|
2875
|
+
if (process.platform === "win32") return "start";
|
|
2876
|
+
if (process.platform === "linux") return "xdg-open";
|
|
2877
|
+
return null;
|
|
2878
|
+
}
|
|
2879
|
+
async function webCommand(deps = {}) {
|
|
2880
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
2881
|
+
let code;
|
|
2882
|
+
try {
|
|
2883
|
+
({ code } = await createWebSession());
|
|
2884
|
+
} catch (e) {
|
|
2885
|
+
if (e instanceof ApiError) {
|
|
2886
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2887
|
+
return 1;
|
|
2888
|
+
}
|
|
2889
|
+
throw e;
|
|
2890
|
+
}
|
|
2891
|
+
const url = `${webOrigin()}/org-manager#code=${code}`;
|
|
2892
|
+
console.log("");
|
|
2893
|
+
console.log(" Opening the Token Derby org manager in your browser...");
|
|
2894
|
+
console.log(` ${url}`);
|
|
2895
|
+
console.log("");
|
|
2896
|
+
console.log(" If it doesn't open, copy the link above. It expires in 60 seconds.");
|
|
2897
|
+
const cmd = opener();
|
|
2898
|
+
if (cmd) {
|
|
2899
|
+
try {
|
|
2900
|
+
const child = spawnImpl(cmd, [url], { stdio: "ignore", detached: true });
|
|
2901
|
+
child.on("error", () => {
|
|
2902
|
+
});
|
|
2903
|
+
child.unref();
|
|
2904
|
+
} catch {
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
return 0;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
// src/ui/prompt.ts
|
|
2911
|
+
async function promptYesNo(question) {
|
|
2912
|
+
resetStdinAfterInk();
|
|
2913
|
+
const readline6 = await import("readline/promises");
|
|
2914
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2915
|
+
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2916
|
+
rl.close();
|
|
2917
|
+
return a === "" || a === "y" || a === "yes";
|
|
2918
|
+
}
|
|
2919
|
+
function resetStdinAfterInk() {
|
|
2920
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2921
|
+
process.stdin.setRawMode(false);
|
|
2922
|
+
}
|
|
2923
|
+
while (process.stdin.read() !== null) {
|
|
2924
|
+
}
|
|
2925
|
+
process.stdin.pause();
|
|
2926
|
+
process.stdin.ref();
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
// src/commands/login.ts
|
|
2930
|
+
function parseDeviceNameFlag(argv) {
|
|
2931
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2932
|
+
const a = argv[i];
|
|
2933
|
+
if (a === "--device-name") return argv[i + 1] ?? null;
|
|
2934
|
+
if (a.startsWith("--device-name=")) return a.slice("--device-name=".length);
|
|
2935
|
+
}
|
|
2936
|
+
return null;
|
|
2937
|
+
}
|
|
2938
|
+
async function defaultPromptText2(question) {
|
|
2939
|
+
const readline6 = await import("readline/promises");
|
|
2940
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2941
|
+
try {
|
|
2942
|
+
return await rl.question(question);
|
|
2943
|
+
} finally {
|
|
2944
|
+
rl.close();
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
function defaultSleep(ms) {
|
|
2948
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2949
|
+
}
|
|
2950
|
+
async function resolveDeviceName(flagName, isTTY, hostname3, promptText) {
|
|
2951
|
+
if (flagName) return flagName;
|
|
2952
|
+
if (!isTTY) return hostname3;
|
|
2953
|
+
const answer = (await promptText(`Device name [${hostname3}]: `)).trim();
|
|
2954
|
+
return answer || hostname3;
|
|
2955
|
+
}
|
|
2956
|
+
async function loginCommand(argv = [], deps = {}) {
|
|
2957
|
+
const apiStart = deps.apiStart ?? cliAuthStart;
|
|
2958
|
+
const apiPoll = deps.apiPoll ?? cliAuthPoll;
|
|
2959
|
+
const apiRevokeDevice = deps.apiRevokeDevice ?? revokeDevice;
|
|
2960
|
+
const apiCreateWebSession = deps.apiCreateWebSession ?? createWebSession;
|
|
2961
|
+
const saveIdentity2 = deps.saveIdentity ?? saveIdentity;
|
|
2962
|
+
const loadIdentity2 = deps.loadIdentity ?? loadIdentity;
|
|
2963
|
+
const promptText = deps.promptText ?? defaultPromptText2;
|
|
2964
|
+
const promptYesNo2 = deps.promptYesNo ?? promptYesNo;
|
|
2965
|
+
const sleepImpl = deps.sleepImpl ?? defaultSleep;
|
|
2966
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
2967
|
+
const spawnImpl = deps.spawnImpl ?? spawn2;
|
|
2968
|
+
const hostname3 = deps.hostname ?? (() => os3.hostname());
|
|
2969
|
+
const existing = await loadIdentity2();
|
|
2970
|
+
if (existing) {
|
|
2971
|
+
console.log("");
|
|
2972
|
+
console.log(` This machine is already signed in as ${existing.display_name}.`);
|
|
2973
|
+
console.log(" Signing in again adds a second credential; the current one stays active");
|
|
2974
|
+
console.log(" until you revoke it under Account in the org manager (`token-derby web`).");
|
|
2975
|
+
if (isTTY) {
|
|
2976
|
+
if (!await promptYesNo2(" Sign in again anyway? [Y/n] ")) {
|
|
2977
|
+
console.log("Login cancelled.");
|
|
2663
2978
|
return 0;
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2979
|
+
}
|
|
2980
|
+
} else {
|
|
2981
|
+
console.log(" No TTY to confirm \u2014 continuing (equivalent to answering Y).");
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
let label = await resolveDeviceName(parseDeviceNameFlag(argv), isTTY, hostname3(), promptText);
|
|
2985
|
+
let start;
|
|
2986
|
+
for (; ; ) {
|
|
2987
|
+
try {
|
|
2988
|
+
start = await apiStart({ label });
|
|
2989
|
+
break;
|
|
2990
|
+
} catch (e) {
|
|
2991
|
+
if (e instanceof ApiError && e.code === "BAD_REQUEST") {
|
|
2992
|
+
console.error(`Error: ${e.message}`);
|
|
2993
|
+
if (!isTTY) return 1;
|
|
2994
|
+
const retry = (await promptText("Enter a different device name: ")).trim();
|
|
2995
|
+
if (!retry) {
|
|
2996
|
+
console.error("Device name cannot be empty.");
|
|
2673
2997
|
return 1;
|
|
2674
2998
|
}
|
|
2999
|
+
label = retry;
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
if (e instanceof ApiError) {
|
|
3003
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3004
|
+
return 1;
|
|
3005
|
+
}
|
|
3006
|
+
throw e;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
let verificationUrl = start.verification_uri;
|
|
3010
|
+
let hasGrant = false;
|
|
3011
|
+
if (existing) {
|
|
3012
|
+
try {
|
|
3013
|
+
const grant = await apiCreateWebSession();
|
|
3014
|
+
verificationUrl = `${start.verification_uri}#code=${grant.code}`;
|
|
3015
|
+
hasGrant = true;
|
|
3016
|
+
} catch (e) {
|
|
3017
|
+
if (e instanceof ApiError && e.code === "UNAUTHENTICATED") {
|
|
3018
|
+
console.log("");
|
|
3019
|
+
console.log(" The credential stored on this machine is no longer valid (it may have been");
|
|
3020
|
+
console.log(" revoked), so this will sign you in fresh in the browser.");
|
|
3021
|
+
} else if (e instanceof ApiError) {
|
|
3022
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3023
|
+
return 1;
|
|
3024
|
+
} else {
|
|
2675
3025
|
throw e;
|
|
2676
3026
|
}
|
|
2677
3027
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
3028
|
+
}
|
|
3029
|
+
console.log("");
|
|
3030
|
+
console.log(" To finish signing in, visit:");
|
|
3031
|
+
console.log(` ${verificationUrl}`);
|
|
3032
|
+
if (hasGrant) {
|
|
3033
|
+
console.log(" That link signs the browser in for you and expires in 60 seconds \u2014 if it");
|
|
3034
|
+
console.log(" does, run `token-derby login` again for a fresh one.");
|
|
3035
|
+
}
|
|
3036
|
+
console.log("");
|
|
3037
|
+
console.log(" And enter this code:");
|
|
3038
|
+
console.log(` ${start.user_code}`);
|
|
3039
|
+
console.log("");
|
|
3040
|
+
console.log(" Waiting for approval...");
|
|
3041
|
+
const cmd = opener();
|
|
3042
|
+
if (cmd) {
|
|
3043
|
+
try {
|
|
3044
|
+
const child = spawnImpl(cmd, [verificationUrl], { stdio: "ignore", detached: true });
|
|
3045
|
+
child.on("error", () => {
|
|
3046
|
+
});
|
|
3047
|
+
child.unref();
|
|
3048
|
+
} catch {
|
|
2683
3049
|
}
|
|
3050
|
+
}
|
|
3051
|
+
const deadline = Date.now() + start.expires_in * 1e3;
|
|
3052
|
+
let approved = null;
|
|
3053
|
+
while (!approved) {
|
|
3054
|
+
let poll;
|
|
2684
3055
|
try {
|
|
2685
|
-
|
|
2686
|
-
const identity = {
|
|
2687
|
-
user_id: resp.user_id,
|
|
2688
|
-
display_name: resp.display_name,
|
|
2689
|
-
secret_token: resp.secret_token,
|
|
2690
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2691
|
-
};
|
|
2692
|
-
await saveIdentity(identity);
|
|
2693
|
-
_resetIdentityCacheForTests();
|
|
2694
|
-
console.log("");
|
|
2695
|
-
console.log(`Welcome, ${identity.display_name}!`);
|
|
2696
|
-
console.log("Your identity has been created on the server.");
|
|
2697
|
-
console.log("You can now create a stable and join races.");
|
|
2698
|
-
console.log("");
|
|
2699
|
-
console.log(" \u26A0 Your secret token is stored locally in identity.json.");
|
|
2700
|
-
console.log(" If you lose it, you cannot recover this account \u2014 you would");
|
|
2701
|
-
console.log(" need to run `token-derby init --reset` and rebuild your stable.");
|
|
2702
|
-
return 0;
|
|
3056
|
+
poll = await apiPoll({ device_code: start.device_code });
|
|
2703
3057
|
} catch (e) {
|
|
2704
3058
|
if (e instanceof ApiError) {
|
|
2705
3059
|
console.error(`Error: ${e.code} ${e.message}`);
|
|
@@ -2707,21 +3061,292 @@ async function initCommand(reset = false) {
|
|
|
2707
3061
|
}
|
|
2708
3062
|
throw e;
|
|
2709
3063
|
}
|
|
3064
|
+
if (poll.status === "approved") {
|
|
3065
|
+
approved = poll;
|
|
3066
|
+
break;
|
|
3067
|
+
}
|
|
3068
|
+
if (Date.now() >= deadline) {
|
|
3069
|
+
console.error("Login timed out waiting for approval. Run `token-derby login` again.");
|
|
3070
|
+
return 1;
|
|
3071
|
+
}
|
|
3072
|
+
await sleepImpl(start.interval * 1e3);
|
|
3073
|
+
}
|
|
3074
|
+
console.log("");
|
|
3075
|
+
if (approved.email) console.log(` \u2713 Signed in as ${approved.email}`);
|
|
3076
|
+
console.log(
|
|
3077
|
+
` Linking to your existing jockey: ${approved.display_name} (${approved.horses} horses, ${approved.orgs} orgs)`
|
|
3078
|
+
);
|
|
3079
|
+
let proceed;
|
|
3080
|
+
if (isTTY) {
|
|
3081
|
+
proceed = await promptYesNo2(" Link this account? [Y/n] ");
|
|
3082
|
+
} else {
|
|
3083
|
+
console.log(" No TTY to confirm \u2014 proceeding automatically (equivalent to answering Y).");
|
|
3084
|
+
proceed = true;
|
|
3085
|
+
}
|
|
3086
|
+
if (!proceed) {
|
|
3087
|
+
try {
|
|
3088
|
+
await apiRevokeDevice(approved.device_id, { user_id: approved.user_id, secret_token: approved.secret_token });
|
|
3089
|
+
} catch (e) {
|
|
3090
|
+
console.error("Declined, but could not revoke the device credential on the server.");
|
|
3091
|
+
if (e instanceof ApiError) console.error(`Error: ${e.code} ${e.message}`);
|
|
3092
|
+
return 1;
|
|
3093
|
+
}
|
|
3094
|
+
console.log("Login cancelled.");
|
|
3095
|
+
return 0;
|
|
3096
|
+
}
|
|
3097
|
+
const identity = {
|
|
3098
|
+
user_id: approved.user_id,
|
|
3099
|
+
display_name: approved.display_name,
|
|
3100
|
+
secret_token: approved.secret_token,
|
|
3101
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3102
|
+
};
|
|
3103
|
+
await saveIdentity2(identity);
|
|
3104
|
+
console.log("");
|
|
3105
|
+
console.log(`Welcome back, ${identity.display_name}!`);
|
|
3106
|
+
return 0;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// src/commands/logout.ts
|
|
3110
|
+
async function logoutCommand(deps = {}) {
|
|
3111
|
+
const apiLogoutDevice = deps.apiLogoutDevice ?? logoutDevice;
|
|
3112
|
+
const deleteIdentity2 = deps.deleteIdentity ?? deleteIdentity;
|
|
3113
|
+
try {
|
|
3114
|
+
const result = await apiLogoutDevice();
|
|
3115
|
+
if (result.revoked) {
|
|
3116
|
+
console.log("Revoked this device's credential on the server.");
|
|
3117
|
+
} else {
|
|
3118
|
+
console.log(
|
|
3119
|
+
"This machine is signed in with a legacy account credential (never linked via `token-derby login`), so there is no separate device credential to revoke."
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
} catch (e) {
|
|
3123
|
+
if (e instanceof ApiError && e.code === "UNAUTHENTICATED") {
|
|
3124
|
+
console.error("This device credential was already invalid on the server \u2014 clearing it locally too.");
|
|
3125
|
+
await deleteIdentity2();
|
|
3126
|
+
console.log("Removed local identity. You are signed out.");
|
|
3127
|
+
return 0;
|
|
3128
|
+
}
|
|
3129
|
+
if (e instanceof ApiError) {
|
|
3130
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3131
|
+
console.error("Local identity left in place \u2014 run `token-derby logout` again to retry.");
|
|
3132
|
+
return 1;
|
|
3133
|
+
}
|
|
3134
|
+
throw e;
|
|
3135
|
+
}
|
|
3136
|
+
await deleteIdentity2();
|
|
3137
|
+
console.log("Removed local identity. You are signed out.");
|
|
3138
|
+
return 0;
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// src/commands/link.ts
|
|
3142
|
+
import { spawn as spawn3 } from "child_process";
|
|
3143
|
+
import * as os4 from "os";
|
|
3144
|
+
|
|
3145
|
+
// src/ui/messages.ts
|
|
3146
|
+
var CREDENTIAL_DEAD_MESSAGE = "This machine's credential is no longer valid on the server \u2014 it may have been revoked.\nRun `token-derby login` to sign this machine in again.";
|
|
3147
|
+
|
|
3148
|
+
// src/commands/link.ts
|
|
3149
|
+
function defaultSleep2(ms) {
|
|
3150
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3151
|
+
}
|
|
3152
|
+
async function defaultPromptText3(question) {
|
|
3153
|
+
const readline6 = await import("readline/promises");
|
|
3154
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
3155
|
+
try {
|
|
3156
|
+
return await rl.question(question);
|
|
2710
3157
|
} finally {
|
|
2711
3158
|
rl.close();
|
|
2712
3159
|
}
|
|
2713
3160
|
}
|
|
3161
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
3162
|
+
var WAIT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3163
|
+
async function linkCommand(argv = [], deps = {}) {
|
|
3164
|
+
const apiGetJockey = deps.apiGetJockey ?? getJockey;
|
|
3165
|
+
const apiCreateWebSession = deps.apiCreateWebSession ?? createWebSession;
|
|
3166
|
+
const apiRegisterDevice = deps.apiRegisterDevice ?? registerDevice;
|
|
3167
|
+
const loadIdentity2 = deps.loadIdentity ?? loadIdentity;
|
|
3168
|
+
const saveIdentity2 = deps.saveIdentity ?? saveIdentity;
|
|
3169
|
+
const spawnImpl = deps.spawnImpl ?? spawn3;
|
|
3170
|
+
const sleepImpl = deps.sleepImpl ?? defaultSleep2;
|
|
3171
|
+
const promptText = deps.promptText ?? defaultPromptText3;
|
|
3172
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
3173
|
+
const hostname3 = deps.hostname ?? (() => os4.hostname());
|
|
3174
|
+
const flagName = parseDeviceNameFlag(argv);
|
|
3175
|
+
if (flagName !== null) {
|
|
3176
|
+
const checked = validateDeviceLabel(flagName);
|
|
3177
|
+
if (!checked.ok) {
|
|
3178
|
+
console.error(`Error: invalid --device-name: ${checked.message}`);
|
|
3179
|
+
return 1;
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
const syncLocalName = async (serverName) => {
|
|
3183
|
+
const local = await loadIdentity2();
|
|
3184
|
+
if (!local || local.display_name === serverName) return;
|
|
3185
|
+
await saveIdentity2({ ...local, display_name: serverName });
|
|
3186
|
+
};
|
|
3187
|
+
const registerThisMachine = async (linked) => {
|
|
3188
|
+
const serverName = linked.display_name;
|
|
3189
|
+
if (linked.device_label) {
|
|
3190
|
+
await syncLocalName(serverName);
|
|
3191
|
+
console.log(` This machine already has its own credential, "${linked.device_label}", so it`);
|
|
3192
|
+
console.log(" was not registered again \u2014 a second one would leave the first live with");
|
|
3193
|
+
console.log(" nothing on this machine holding it.");
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
const local = await loadIdentity2();
|
|
3197
|
+
if (!local) {
|
|
3198
|
+
console.log(" This machine has no local identity file, so it was not registered as a");
|
|
3199
|
+
console.log(" device. Run `token-derby login` to give it its own credential.");
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
const named = local.display_name === serverName ? local : { ...local, display_name: serverName };
|
|
3203
|
+
let label;
|
|
3204
|
+
try {
|
|
3205
|
+
label = await resolveDeviceName(flagName, isTTY, hostname3(), promptText);
|
|
3206
|
+
for (; ; ) {
|
|
3207
|
+
try {
|
|
3208
|
+
const registered = await apiRegisterDevice({ label });
|
|
3209
|
+
await saveIdentity2({ ...named, secret_token: registered.secret_token });
|
|
3210
|
+
break;
|
|
3211
|
+
} catch (e) {
|
|
3212
|
+
if (!(e instanceof ApiError && e.code === "BAD_REQUEST" && isTTY)) throw e;
|
|
3213
|
+
console.error(` Error: ${e.message}`);
|
|
3214
|
+
const retry = (await promptText(" Enter a different device name: ")).trim();
|
|
3215
|
+
if (!retry) throw e;
|
|
3216
|
+
label = retry;
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
} catch (e) {
|
|
3220
|
+
try {
|
|
3221
|
+
if (named !== local) await saveIdentity2(named);
|
|
3222
|
+
} catch {
|
|
3223
|
+
}
|
|
3224
|
+
console.log("");
|
|
3225
|
+
console.log(" Your account is linked \u2014 that part is done and does not need repeating.");
|
|
3226
|
+
console.log(" Registering this machine as a device did not complete, so it is still");
|
|
3227
|
+
console.log(" using the shared account credential. Run `token-derby login` to finish.");
|
|
3228
|
+
console.error(` Error: ${e instanceof ApiError ? `${e.code} ${e.message}` : String(e)}`);
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
console.log(` This machine is now registered as "${label}", with a credential of its own`);
|
|
3232
|
+
console.log(" that you can revoke separately under Account in the org manager.");
|
|
3233
|
+
};
|
|
3234
|
+
let me;
|
|
3235
|
+
try {
|
|
3236
|
+
me = await apiGetJockey();
|
|
3237
|
+
} catch (e) {
|
|
3238
|
+
if (e instanceof ApiError) {
|
|
3239
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
3240
|
+
console.error(CREDENTIAL_DEAD_MESSAGE);
|
|
3241
|
+
return 1;
|
|
3242
|
+
}
|
|
3243
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3244
|
+
return 1;
|
|
3245
|
+
}
|
|
3246
|
+
throw e;
|
|
3247
|
+
}
|
|
3248
|
+
if (me.email) {
|
|
3249
|
+
console.log(`Already linked to ${me.email}.`);
|
|
3250
|
+
await syncLocalName(me.display_name);
|
|
3251
|
+
return 0;
|
|
3252
|
+
}
|
|
3253
|
+
let code;
|
|
3254
|
+
try {
|
|
3255
|
+
({ code } = await apiCreateWebSession());
|
|
3256
|
+
} catch (e) {
|
|
3257
|
+
if (e instanceof ApiError) {
|
|
3258
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3259
|
+
return 1;
|
|
3260
|
+
}
|
|
3261
|
+
throw e;
|
|
3262
|
+
}
|
|
3263
|
+
const url = `${webOrigin()}/link#code=${code}`;
|
|
3264
|
+
console.log("");
|
|
3265
|
+
console.log(" Opening the Google link page in your browser...");
|
|
3266
|
+
console.log(` ${url}`);
|
|
3267
|
+
console.log("");
|
|
3268
|
+
console.log(" If it doesn't open, copy the link above. It expires in 60 seconds.");
|
|
3269
|
+
const cmd = opener();
|
|
3270
|
+
if (cmd) {
|
|
3271
|
+
try {
|
|
3272
|
+
const child = spawnImpl(cmd, [url], { stdio: "ignore", detached: true });
|
|
3273
|
+
child.on("error", () => {
|
|
3274
|
+
});
|
|
3275
|
+
child.unref();
|
|
3276
|
+
} catch {
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
console.log("");
|
|
3280
|
+
console.log(" Waiting for you to finish connecting with Google...");
|
|
3281
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
3282
|
+
for (; ; ) {
|
|
3283
|
+
let poll;
|
|
3284
|
+
try {
|
|
3285
|
+
poll = await apiGetJockey();
|
|
3286
|
+
} catch (e) {
|
|
3287
|
+
if (e instanceof ApiError) {
|
|
3288
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
3289
|
+
console.error(CREDENTIAL_DEAD_MESSAGE);
|
|
3290
|
+
return 1;
|
|
3291
|
+
}
|
|
3292
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3293
|
+
return 1;
|
|
3294
|
+
}
|
|
3295
|
+
throw e;
|
|
3296
|
+
}
|
|
3297
|
+
if (poll.email) {
|
|
3298
|
+
console.log(`\u2713 Linked to ${poll.email}.`);
|
|
3299
|
+
if (poll.display_name !== me.display_name) {
|
|
3300
|
+
console.log(` Your jockey is now named ${poll.display_name} \u2014 a first link renames it to`);
|
|
3301
|
+
console.log(" the first name on the Google account. `token-derby init` renames it again");
|
|
3302
|
+
console.log(" if you would rather it said something else.");
|
|
3303
|
+
}
|
|
3304
|
+
await registerThisMachine(poll);
|
|
3305
|
+
return 0;
|
|
3306
|
+
}
|
|
3307
|
+
if (Date.now() >= deadline) {
|
|
3308
|
+
console.error("Timed out waiting for the Google link to complete. Run `token-derby link` again.");
|
|
3309
|
+
return 1;
|
|
3310
|
+
}
|
|
3311
|
+
await sleepImpl(POLL_INTERVAL_MS);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
// src/commands/whoami.ts
|
|
3316
|
+
async function whoamiCommand(deps = {}) {
|
|
3317
|
+
const apiGetJockey = deps.apiGetJockey ?? getJockey;
|
|
3318
|
+
let me;
|
|
3319
|
+
try {
|
|
3320
|
+
me = await apiGetJockey();
|
|
3321
|
+
} catch (e) {
|
|
3322
|
+
if (e instanceof ApiError) {
|
|
3323
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
3324
|
+
console.error(CREDENTIAL_DEAD_MESSAGE);
|
|
3325
|
+
return 1;
|
|
3326
|
+
}
|
|
3327
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3328
|
+
return 1;
|
|
3329
|
+
}
|
|
3330
|
+
throw e;
|
|
3331
|
+
}
|
|
3332
|
+
console.log("");
|
|
3333
|
+
console.log(` ${me.display_name}`);
|
|
3334
|
+
if (me.email) console.log(` ${me.email}`);
|
|
3335
|
+
if (me.device_label) console.log(` this machine: ${me.device_label}`);
|
|
3336
|
+
console.log("");
|
|
3337
|
+
return 0;
|
|
3338
|
+
}
|
|
2714
3339
|
|
|
2715
3340
|
// src/commands/update.ts
|
|
2716
3341
|
import * as readline5 from "readline/promises";
|
|
2717
3342
|
import { stdin as stdin5, stdout as stdout5 } from "process";
|
|
2718
|
-
import { spawn } from "child_process";
|
|
3343
|
+
import { spawn as spawn4 } from "child_process";
|
|
2719
3344
|
var REGISTRY_URL = "https://registry.npmjs.org/@mauricode/token-derby/latest";
|
|
2720
3345
|
var UPGRADE_CMD = "npm install -g @mauricode/token-derby@latest";
|
|
2721
3346
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
2722
3347
|
async function updateCommand(deps = {}) {
|
|
2723
3348
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
2724
|
-
const spawnImpl = deps.spawnImpl ??
|
|
3349
|
+
const spawnImpl = deps.spawnImpl ?? spawn4;
|
|
2725
3350
|
const promptYesNo2 = deps.promptYesNo ?? defaultPromptYesNo;
|
|
2726
3351
|
let latest;
|
|
2727
3352
|
try {
|
|
@@ -2857,25 +3482,6 @@ function RollHorsePicker({ horses, onPick, onCancel }) {
|
|
|
2857
3482
|
] });
|
|
2858
3483
|
}
|
|
2859
3484
|
|
|
2860
|
-
// src/ui/prompt.ts
|
|
2861
|
-
async function promptYesNo(question) {
|
|
2862
|
-
resetStdinAfterInk();
|
|
2863
|
-
const readline6 = await import("readline/promises");
|
|
2864
|
-
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2865
|
-
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2866
|
-
rl.close();
|
|
2867
|
-
return a === "" || a === "y" || a === "yes";
|
|
2868
|
-
}
|
|
2869
|
-
function resetStdinAfterInk() {
|
|
2870
|
-
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2871
|
-
process.stdin.setRawMode(false);
|
|
2872
|
-
}
|
|
2873
|
-
while (process.stdin.read() !== null) {
|
|
2874
|
-
}
|
|
2875
|
-
process.stdin.pause();
|
|
2876
|
-
process.stdin.ref();
|
|
2877
|
-
}
|
|
2878
|
-
|
|
2879
3485
|
// src/ui/reveal.ts
|
|
2880
3486
|
import React13 from "react";
|
|
2881
3487
|
import { render as render5 } from "ink";
|
|
@@ -2889,13 +3495,13 @@ import { useEffect as useEffect3, useState as useState7 } from "react";
|
|
|
2889
3495
|
import { Box as Box10, Text as Text10 } from "ink";
|
|
2890
3496
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2891
3497
|
function HatSprite({ hat, variant, centerIn }) {
|
|
2892
|
-
const colors =
|
|
3498
|
+
const colors = hatColors(hat, variant ?? 0);
|
|
2893
3499
|
const grid = makeHatGrid(hat, colors, centerIn);
|
|
2894
3500
|
const lines = hexGridToHalfBlocks(grid);
|
|
2895
3501
|
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx10(Text10, { children: line }, i)) });
|
|
2896
3502
|
}
|
|
2897
3503
|
function AnimatedHatSprite({ hat, variant, centerIn }) {
|
|
2898
|
-
if (hat
|
|
3504
|
+
if (!isAnimatedHat(hat)) {
|
|
2899
3505
|
return /* @__PURE__ */ jsx10(HatSprite, { hat, variant, centerIn });
|
|
2900
3506
|
}
|
|
2901
3507
|
const frames = hat.animation.frames;
|
|
@@ -2912,10 +3518,6 @@ function AnimatedHatSprite({ hat, variant, centerIn }) {
|
|
|
2912
3518
|
const framed = { ...hat, colors: { ...hat.colors, A: frames[idx] } };
|
|
2913
3519
|
return /* @__PURE__ */ jsx10(HatSprite, { hat: framed, variant, centerIn });
|
|
2914
3520
|
}
|
|
2915
|
-
function hatColorsFor2(hat, variantIdx) {
|
|
2916
|
-
if (hat.rarity === "legendary") return hat.colors;
|
|
2917
|
-
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
2918
|
-
}
|
|
2919
3521
|
function makeHatGrid(hat, colors, centerIn) {
|
|
2920
3522
|
const w = centerIn?.w ?? hat.width;
|
|
2921
3523
|
const h = centerIn?.h ?? hat.rows.length;
|
|
@@ -2930,7 +3532,7 @@ function makeHatGrid(hat, colors, centerIn) {
|
|
|
2930
3532
|
const gx = x + offX;
|
|
2931
3533
|
const gy = y + offY;
|
|
2932
3534
|
if (gx < 0 || gx >= w || gy < 0 || gy >= h) continue;
|
|
2933
|
-
grid[gy][gx] =
|
|
3535
|
+
grid[gy][gx] = channelColor(colors, ch);
|
|
2934
3536
|
}
|
|
2935
3537
|
}
|
|
2936
3538
|
return grid;
|
|
@@ -2944,7 +3546,8 @@ var TIER_PALETTE = {
|
|
|
2944
3546
|
common: ["#FFFFFF", "#DDDDDD", "#AAAAAA", "#9E9E9E"],
|
|
2945
3547
|
rare: ["#42A5F5", "#1E88E5", "#90CAF9", "#0277BD"],
|
|
2946
3548
|
epic: ["#AB47BC", "#8E24AA", "#CE93D8", "#6A1B9A"],
|
|
2947
|
-
legendary: ["#FFD700", "#FF7F00", "#FF0000", "#FF00FF", "#00BFFF", "#7CFC00", "#8B00FF"]
|
|
3549
|
+
legendary: ["#FFD700", "#FF7F00", "#FF0000", "#FF00FF", "#00BFFF", "#7CFC00", "#8B00FF"],
|
|
3550
|
+
limited: ["#F472B6", "#EC4899", "#FF4FA3", "#FBCFE8", "#FFFFFF"]
|
|
2948
3551
|
};
|
|
2949
3552
|
var CONFETTI_CHARS = ["\u2726", "\u2727", "\u22C6", "\u2605", "\u2606", "\u2728", "*", "\u2022", "\u25C6", "\u25C7"];
|
|
2950
3553
|
var SCENE_W = 32;
|
|
@@ -3047,7 +3650,7 @@ function ConfettiBurst({ tier }) {
|
|
|
3047
3650
|
var CLOSED_HOLD_MS = 3e3;
|
|
3048
3651
|
function RollReveal({ outcome, onDone }) {
|
|
3049
3652
|
const isNoHat = outcome.kind === "no_hat";
|
|
3050
|
-
const
|
|
3653
|
+
const isShowpiece = outcome.kind !== "no_hat" && (outcome.hat.rarity === "legendary" || outcome.hat.rarity === "limited");
|
|
3051
3654
|
const [phase, setPhase] = useState8("closed");
|
|
3052
3655
|
useEffect4(() => {
|
|
3053
3656
|
const timers = [];
|
|
@@ -3059,10 +3662,10 @@ function RollReveal({ outcome, onDone }) {
|
|
|
3059
3662
|
} else {
|
|
3060
3663
|
timers.push(setTimeout(() => setPhase("burst"), CLOSED_HOLD_MS + 700));
|
|
3061
3664
|
timers.push(setTimeout(() => setPhase("reveal"), CLOSED_HOLD_MS + 1650));
|
|
3062
|
-
timers.push(setTimeout(onDone, CLOSED_HOLD_MS + (
|
|
3665
|
+
timers.push(setTimeout(onDone, CLOSED_HOLD_MS + (isShowpiece ? 4650 : 2650)));
|
|
3063
3666
|
}
|
|
3064
3667
|
return () => timers.forEach(clearTimeout);
|
|
3065
|
-
}, [isNoHat,
|
|
3668
|
+
}, [isNoHat, isShowpiece, onDone]);
|
|
3066
3669
|
if (phase === "closed") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_CLOSED, color: BOX_COLOR });
|
|
3067
3670
|
if (phase === "open1") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_1, color: BOX_COLOR });
|
|
3068
3671
|
if (phase === "open2") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_2, color: BOX_COLOR });
|
|
@@ -3071,7 +3674,7 @@ function RollReveal({ outcome, onDone }) {
|
|
|
3071
3674
|
return /* @__PURE__ */ jsx11(ConfettiBurst, { tier: outcome.hat.rarity });
|
|
3072
3675
|
}
|
|
3073
3676
|
if (outcome.kind === "no_hat") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
|
|
3074
|
-
return outcome.hat
|
|
3677
|
+
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 } });
|
|
3075
3678
|
}
|
|
3076
3679
|
|
|
3077
3680
|
// src/ui/reveal.ts
|
|
@@ -3162,7 +3765,7 @@ async function rollCommand() {
|
|
|
3162
3765
|
await runReveal(outcome);
|
|
3163
3766
|
if (result.result === "hat") {
|
|
3164
3767
|
const hat = hatById(result.collected.id);
|
|
3165
|
-
const variantSuffix = hat
|
|
3768
|
+
const variantSuffix = !isAnimatedHat(hat) && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
|
|
3166
3769
|
console.log(`
|
|
3167
3770
|
\u2728 ${hat.name}${variantSuffix} [${hat.rarity.toUpperCase()}]
|
|
3168
3771
|
`);
|
|
@@ -3200,8 +3803,9 @@ async function claimCommand(token) {
|
|
|
3200
3803
|
console.error("Usage: token-derby claim <token>");
|
|
3201
3804
|
return 2;
|
|
3202
3805
|
}
|
|
3806
|
+
let probe;
|
|
3203
3807
|
try {
|
|
3204
|
-
await probeClaim(token);
|
|
3808
|
+
probe = await probeClaim(token);
|
|
3205
3809
|
} catch (e) {
|
|
3206
3810
|
if (e instanceof ApiError) {
|
|
3207
3811
|
console.error(`Error: ${e.code} ${e.message}`);
|
|
@@ -3223,7 +3827,9 @@ async function claimCommand(token) {
|
|
|
3223
3827
|
console.error("No horses in your stable. Run `token-derby stable create` first.");
|
|
3224
3828
|
return 1;
|
|
3225
3829
|
}
|
|
3226
|
-
console.log(
|
|
3830
|
+
console.log(probe.entry_count > 1 ? `
|
|
3831
|
+
\u{1F381} A pack of ${probe.entry_count} cosmetics \u2014 one of them will be yours.
|
|
3832
|
+
` : "\n\u{1F381} A cosmetic has been awarded to you.\n");
|
|
3227
3833
|
const picked = await new Promise((resolve) => {
|
|
3228
3834
|
const app = render7(React15.createElement(HorsePicker, {
|
|
3229
3835
|
horses: stable.horses,
|
|
@@ -3267,7 +3873,7 @@ async function claimCommand(token) {
|
|
|
3267
3873
|
await runReveal(outcome);
|
|
3268
3874
|
if (result.result === "hat") {
|
|
3269
3875
|
const hat2 = hatById(result.collected.id);
|
|
3270
|
-
const variantSuffix2 = hat2
|
|
3876
|
+
const variantSuffix2 = !isAnimatedHat(hat2) && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
|
|
3271
3877
|
console.log(`
|
|
3272
3878
|
\u2728 ${hat2.name}${variantSuffix2} [${hat2.rarity.toUpperCase()}]
|
|
3273
3879
|
`);
|
|
@@ -3293,12 +3899,9 @@ ${picked.name} already has ${hat?.name ?? result.hat_id}${variantSuffix}. +${res
|
|
|
3293
3899
|
|
|
3294
3900
|
// src/commands/org-join.ts
|
|
3295
3901
|
async function orgJoinCommand(token) {
|
|
3296
|
-
|
|
3297
|
-
console.error("Usage: token-derby organisation join <join-token>");
|
|
3298
|
-
return 2;
|
|
3299
|
-
}
|
|
3902
|
+
const join_token = token?.trim();
|
|
3300
3903
|
try {
|
|
3301
|
-
const resp = await joinOrganisation({ join_token:
|
|
3904
|
+
const resp = await joinOrganisation(join_token ? { join_token } : {});
|
|
3302
3905
|
console.log(`Joined organisation: ${resp.org_name}`);
|
|
3303
3906
|
return 0;
|
|
3304
3907
|
} catch (e) {
|
|
@@ -3310,48 +3913,6 @@ async function orgJoinCommand(token) {
|
|
|
3310
3913
|
}
|
|
3311
3914
|
}
|
|
3312
3915
|
|
|
3313
|
-
// src/commands/web.ts
|
|
3314
|
-
import { spawn as spawn2 } from "child_process";
|
|
3315
|
-
function webOrigin() {
|
|
3316
|
-
return apiBase().replace(/\/api\/?$/, "");
|
|
3317
|
-
}
|
|
3318
|
-
function opener() {
|
|
3319
|
-
if (process.platform === "darwin") return "open";
|
|
3320
|
-
if (process.platform === "win32") return "start";
|
|
3321
|
-
if (process.platform === "linux") return "xdg-open";
|
|
3322
|
-
return null;
|
|
3323
|
-
}
|
|
3324
|
-
async function webCommand(deps = {}) {
|
|
3325
|
-
const spawnImpl = deps.spawnImpl ?? spawn2;
|
|
3326
|
-
let code;
|
|
3327
|
-
try {
|
|
3328
|
-
({ code } = await createWebSession());
|
|
3329
|
-
} catch (e) {
|
|
3330
|
-
if (e instanceof ApiError) {
|
|
3331
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
3332
|
-
return 1;
|
|
3333
|
-
}
|
|
3334
|
-
throw e;
|
|
3335
|
-
}
|
|
3336
|
-
const url = `${webOrigin()}/org-manager#code=${code}`;
|
|
3337
|
-
console.log("");
|
|
3338
|
-
console.log(" Opening the Token Derby org manager in your browser...");
|
|
3339
|
-
console.log(` ${url}`);
|
|
3340
|
-
console.log("");
|
|
3341
|
-
console.log(" If it doesn't open, copy the link above. It expires in 60 seconds.");
|
|
3342
|
-
const cmd = opener();
|
|
3343
|
-
if (cmd) {
|
|
3344
|
-
try {
|
|
3345
|
-
const child = spawnImpl(cmd, [url], { stdio: "ignore", detached: true });
|
|
3346
|
-
child.on("error", () => {
|
|
3347
|
-
});
|
|
3348
|
-
child.unref();
|
|
3349
|
-
} catch {
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
return 0;
|
|
3353
|
-
}
|
|
3354
|
-
|
|
3355
3916
|
// src/commands/env.ts
|
|
3356
3917
|
function warnOverrides() {
|
|
3357
3918
|
if (process.env.TOKEN_DERBY_HOME) {
|
|
@@ -3386,10 +3947,27 @@ function envCommand(arg) {
|
|
|
3386
3947
|
var HELP = `token-derby v${CLI_VERSION}
|
|
3387
3948
|
|
|
3388
3949
|
Identity:
|
|
3389
|
-
token-derby init
|
|
3390
|
-
|
|
3391
|
-
token-derby init --reset
|
|
3392
|
-
|
|
3950
|
+
token-derby init Deprecated \u2014 creates or renames your jockey identity;
|
|
3951
|
+
prefer \`login\`, which also links your account.
|
|
3952
|
+
token-derby init --reset Deprecated \u2014 wipes local identity and starts a fresh
|
|
3953
|
+
account (previous stable abandoned); \`login\` recovers
|
|
3954
|
+
your existing account instead of abandoning it.
|
|
3955
|
+
token-derby login [--device-name <name>]
|
|
3956
|
+
About THIS MACHINE: sign in with Google to give
|
|
3957
|
+
it a credential of its own. The way in for a
|
|
3958
|
+
machine that has none yet. Adds no email to
|
|
3959
|
+
your account \u2014 see \`link\`.
|
|
3960
|
+
token-derby logout Retire this machine's credential and clear
|
|
3961
|
+
local identity.
|
|
3962
|
+
token-derby link [--device-name <name>]
|
|
3963
|
+
About YOUR JOCKEY: connect it to a Google
|
|
3964
|
+
account \u2014 adds your email on file, and
|
|
3965
|
+
renames your jockey to the first name on
|
|
3966
|
+
that account. Then registers this machine
|
|
3967
|
+
as a device too, if it is still using the
|
|
3968
|
+
shared account credential.
|
|
3969
|
+
token-derby whoami Show your jockey name, linked email (if any),
|
|
3970
|
+
and this machine's device name (if any).
|
|
3393
3971
|
|
|
3394
3972
|
Maintenance:
|
|
3395
3973
|
token-derby update Check for and install the latest CLI version
|
|
@@ -3401,7 +3979,9 @@ Stable management:
|
|
|
3401
3979
|
token-derby stable delete <name> Remove a horse from your stable
|
|
3402
3980
|
|
|
3403
3981
|
Organisations:
|
|
3404
|
-
token-derby organisation join
|
|
3982
|
+
token-derby organisation join [token] Join an organisation with a join token,
|
|
3983
|
+
or leave the token out to join by your
|
|
3984
|
+
verified email domain
|
|
3405
3985
|
token-derby web Open the web org manager (create orgs,
|
|
3406
3986
|
manage schedules, webhooks, members)
|
|
3407
3987
|
|
|
@@ -3442,11 +4022,12 @@ async function main() {
|
|
|
3442
4022
|
const reset = argv.slice(1).includes("--reset");
|
|
3443
4023
|
return initCommand(reset);
|
|
3444
4024
|
}
|
|
4025
|
+
if (cmd === "login") return loginCommand(argv.slice(1));
|
|
3445
4026
|
if (cmd === "update") return updateCommand();
|
|
3446
4027
|
if (cmd === "env") return envCommand(argv[1]);
|
|
3447
4028
|
const identity = await loadIdentity();
|
|
3448
4029
|
if (!identity) {
|
|
3449
|
-
console.error("Run `token-derby
|
|
4030
|
+
console.error("Run `token-derby login` to set up your identity before using any other command.");
|
|
3450
4031
|
return 1;
|
|
3451
4032
|
}
|
|
3452
4033
|
if (cmd === "stable") {
|
|
@@ -3463,13 +4044,16 @@ async function main() {
|
|
|
3463
4044
|
const sub = argv[1];
|
|
3464
4045
|
if (sub === "join") return orgJoinCommand(argv[2]);
|
|
3465
4046
|
console.error(`Organisation management has moved to the web: token-derby web`);
|
|
3466
|
-
console.error(`The only CLI organisation command is: organisation join
|
|
4047
|
+
console.error(`The only CLI organisation command is: organisation join [token]`);
|
|
3467
4048
|
return 2;
|
|
3468
4049
|
}
|
|
3469
4050
|
if (cmd === "create") {
|
|
3470
4051
|
const orgName = parseFlag(argv.slice(1), "--organisation");
|
|
3471
4052
|
return createRaceCommand(orgName);
|
|
3472
4053
|
}
|
|
4054
|
+
if (cmd === "logout") return logoutCommand();
|
|
4055
|
+
if (cmd === "link") return linkCommand(argv.slice(1));
|
|
4056
|
+
if (cmd === "whoami") return whoamiCommand();
|
|
3473
4057
|
if (cmd === "join") return joinCommand(argv[1], argv.slice(2));
|
|
3474
4058
|
if (cmd === "end") return endCommand(argv[1]);
|
|
3475
4059
|
if (cmd === "roll") return rollCommand();
|