@mauricode/token-derby 2.13.1 → 3.0.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 +701 -134
- 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+)(?:[-+].*)?$/;
|
|
@@ -289,6 +291,22 @@ var STAMINA_PARAM_BOUNDS = {
|
|
|
289
291
|
tired_multiplier: { min: 0.2, max: 0.9, step: 0.05, default: STAMINA.TIRED_MULTIPLIER }
|
|
290
292
|
};
|
|
291
293
|
|
|
294
|
+
// ../shared/dist/devices.js
|
|
295
|
+
var DEVICE_CODE_LENGTH = Math.ceil(SECRET_TOKEN_BYTES * 4 / 3);
|
|
296
|
+
var UNSAFE_LABEL_CHARS = /[\p{Cc}\p{Cf}]/u;
|
|
297
|
+
function validateDeviceLabel(raw) {
|
|
298
|
+
if (typeof raw !== "string")
|
|
299
|
+
return { ok: false, message: "label is required" };
|
|
300
|
+
const label = raw.trim();
|
|
301
|
+
if (label.length < 1 || label.length > DEVICE_LABEL_MAX_LENGTH) {
|
|
302
|
+
return { ok: false, message: `label must be 1\u2013${DEVICE_LABEL_MAX_LENGTH} characters` };
|
|
303
|
+
}
|
|
304
|
+
if (UNSAFE_LABEL_CHARS.test(label)) {
|
|
305
|
+
return { ok: false, message: "label may not contain control or invisible characters" };
|
|
306
|
+
}
|
|
307
|
+
return { ok: true, label };
|
|
308
|
+
}
|
|
309
|
+
|
|
292
310
|
// src/ui/HorseSprite.tsx
|
|
293
311
|
import { Box, Text } from "ink";
|
|
294
312
|
|
|
@@ -825,8 +843,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
|
825
843
|
// src/version.ts
|
|
826
844
|
import { createRequire } from "module";
|
|
827
845
|
function readVersion() {
|
|
828
|
-
if ("
|
|
829
|
-
return "
|
|
846
|
+
if ("3.0.0".length > 0) {
|
|
847
|
+
return "3.0.0";
|
|
830
848
|
}
|
|
831
849
|
try {
|
|
832
850
|
const req = createRequire(import.meta.url);
|
|
@@ -871,18 +889,37 @@ function geminiTmpDir() {
|
|
|
871
889
|
}
|
|
872
890
|
|
|
873
891
|
// src/identity/identity.ts
|
|
874
|
-
async function
|
|
892
|
+
async function readIdentityFile() {
|
|
893
|
+
let raw;
|
|
875
894
|
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;
|
|
895
|
+
raw = await fs.readFile(identityFile(), "utf8");
|
|
882
896
|
} catch (e) {
|
|
883
|
-
if (e?.code === "ENOENT") return
|
|
884
|
-
return
|
|
897
|
+
if (e?.code === "ENOENT") return { kind: "missing" };
|
|
898
|
+
return { kind: "unreadable", reason: `could not be read (${e?.code ?? "read error"})` };
|
|
899
|
+
}
|
|
900
|
+
let parsed;
|
|
901
|
+
try {
|
|
902
|
+
parsed = JSON.parse(raw);
|
|
903
|
+
} catch {
|
|
904
|
+
return { kind: "unreadable", reason: "is not valid JSON" };
|
|
885
905
|
}
|
|
906
|
+
if (!isPlainObject(parsed)) {
|
|
907
|
+
return { kind: "unreadable", reason: "does not contain an identity object" };
|
|
908
|
+
}
|
|
909
|
+
if (typeof parsed.user_id === "string" && typeof parsed.display_name === "string" && typeof parsed.secret_token === "string" && typeof parsed.created_at === "string") {
|
|
910
|
+
return { kind: "ok", identity: parsed };
|
|
911
|
+
}
|
|
912
|
+
if (!("secret_token" in parsed)) {
|
|
913
|
+
return { kind: "no-credential", reason: "holds no credential this version can use" };
|
|
914
|
+
}
|
|
915
|
+
return { kind: "unreadable", reason: "is missing fields this version expects" };
|
|
916
|
+
}
|
|
917
|
+
function isPlainObject(value) {
|
|
918
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
919
|
+
}
|
|
920
|
+
async function loadIdentity() {
|
|
921
|
+
const state = await readIdentityFile();
|
|
922
|
+
return state.kind === "ok" ? state.identity : null;
|
|
886
923
|
}
|
|
887
924
|
async function saveIdentity(identity) {
|
|
888
925
|
await fs.mkdir(homeDir(), { recursive: true });
|
|
@@ -923,12 +960,12 @@ function getIdentity() {
|
|
|
923
960
|
function _resetIdentityCacheForTests() {
|
|
924
961
|
identityCache = null;
|
|
925
962
|
}
|
|
926
|
-
async function request(method, path9, body, horseAuthToken, fetchImpl = fetch) {
|
|
963
|
+
async function request(method, path9, body, horseAuthToken, fetchImpl = fetch, identityOverride) {
|
|
927
964
|
const url = path9.startsWith("http") ? path9 : `${apiBase()}${path9}`;
|
|
928
965
|
const headers = {};
|
|
929
966
|
headers[CLI_VERSION_HEADER] = CLI_VERSION;
|
|
930
967
|
headers["user-agent"] = `token-derby/${CLI_VERSION}`;
|
|
931
|
-
const identity = await getIdentity();
|
|
968
|
+
const identity = identityOverride ?? await getIdentity();
|
|
932
969
|
if (identity) {
|
|
933
970
|
headers[USER_ID_HEADER] = identity.user_id;
|
|
934
971
|
headers[USER_TOKEN_HEADER] = identity.secret_token;
|
|
@@ -994,6 +1031,9 @@ function listOrganisations() {
|
|
|
994
1031
|
function initJockey(body) {
|
|
995
1032
|
return request("POST", "/jockey/init", body, void 0);
|
|
996
1033
|
}
|
|
1034
|
+
function getJockey() {
|
|
1035
|
+
return request("GET", "/jockey/me", void 0, void 0);
|
|
1036
|
+
}
|
|
997
1037
|
function updateJockey(body) {
|
|
998
1038
|
return request("PUT", "/jockey/me", body, void 0);
|
|
999
1039
|
}
|
|
@@ -1034,6 +1074,28 @@ function probeClaim(code) {
|
|
|
1034
1074
|
function redeemClaim(code, body) {
|
|
1035
1075
|
return request("POST", `/claims/${encodeURIComponent(code)}/redeem`, body, void 0);
|
|
1036
1076
|
}
|
|
1077
|
+
function cliAuthStart(body) {
|
|
1078
|
+
return request("POST", "/auth/cli/start", body, void 0);
|
|
1079
|
+
}
|
|
1080
|
+
function cliAuthPoll(body) {
|
|
1081
|
+
return request("POST", "/auth/cli/poll", body, void 0);
|
|
1082
|
+
}
|
|
1083
|
+
function revokeDevice(deviceId, auth) {
|
|
1084
|
+
return request(
|
|
1085
|
+
"DELETE",
|
|
1086
|
+
`/devices/${encodeURIComponent(deviceId)}`,
|
|
1087
|
+
void 0,
|
|
1088
|
+
void 0,
|
|
1089
|
+
void 0,
|
|
1090
|
+
auth
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
function registerDevice(body) {
|
|
1094
|
+
return request("POST", "/devices", body, void 0);
|
|
1095
|
+
}
|
|
1096
|
+
function logoutDevice() {
|
|
1097
|
+
return request("DELETE", "/devices/me", void 0, void 0);
|
|
1098
|
+
}
|
|
1037
1099
|
|
|
1038
1100
|
// src/commands/stable-create.ts
|
|
1039
1101
|
async function stableCreateCommand() {
|
|
@@ -2461,7 +2523,7 @@ async function joinCommand(joinCode, argv = []) {
|
|
|
2461
2523
|
}
|
|
2462
2524
|
const identity = await loadIdentity();
|
|
2463
2525
|
if (!identity) {
|
|
2464
|
-
console.error("Run `token-derby
|
|
2526
|
+
console.error("Run `token-derby login` to set up your identity.");
|
|
2465
2527
|
return 1;
|
|
2466
2528
|
}
|
|
2467
2529
|
let race;
|
|
@@ -2634,72 +2696,346 @@ async function endCommand(adminCode) {
|
|
|
2634
2696
|
// src/commands/init.ts
|
|
2635
2697
|
import * as readline4 from "readline/promises";
|
|
2636
2698
|
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();
|
|
2699
|
+
async function defaultPromptText(question) {
|
|
2644
2700
|
const rl = readline4.createInterface({ input: stdin4, output: stdout4 });
|
|
2645
2701
|
try {
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2702
|
+
return await rl.question(question);
|
|
2703
|
+
} finally {
|
|
2704
|
+
rl.close();
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
async function initCommand(reset = false, deps = {}) {
|
|
2708
|
+
const loadIdentity2 = deps.loadIdentity ?? loadIdentity;
|
|
2709
|
+
const saveIdentity2 = deps.saveIdentity ?? saveIdentity;
|
|
2710
|
+
const deleteIdentity2 = deps.deleteIdentity ?? deleteIdentity;
|
|
2711
|
+
const readIdentityFile2 = deps.readIdentityFile ?? readIdentityFile;
|
|
2712
|
+
const initJockey2 = deps.initJockey ?? initJockey;
|
|
2713
|
+
const updateJockey2 = deps.updateJockey ?? updateJockey;
|
|
2714
|
+
const apiGetJockey = deps.apiGetJockey ?? getJockey;
|
|
2715
|
+
const promptText = deps.promptText ?? defaultPromptText;
|
|
2716
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
2717
|
+
console.log("");
|
|
2718
|
+
console.log(
|
|
2719
|
+
"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."
|
|
2720
|
+
);
|
|
2721
|
+
console.log("");
|
|
2722
|
+
if (reset) {
|
|
2723
|
+
const current = await readIdentityFile2();
|
|
2724
|
+
if (current.kind !== "missing") {
|
|
2725
|
+
if (current.kind === "ok") {
|
|
2726
|
+
console.log(`About to abandon jockey: ${current.identity.display_name}`);
|
|
2727
|
+
} else if (current.kind === "unreadable") {
|
|
2728
|
+
console.log(`About to delete identity.json, which ${current.reason}.`);
|
|
2729
|
+
console.log("The credential inside cannot be read, so there is no way to tell which jockey");
|
|
2730
|
+
console.log("this is, or whether anything else can still recover it.");
|
|
2731
|
+
} else {
|
|
2732
|
+
console.log(`About to delete identity.json, which ${current.reason}.`);
|
|
2733
|
+
}
|
|
2734
|
+
console.log("This deletes the local identity only \u2014 the account itself stays on the server.");
|
|
2735
|
+
if (current.kind === "ok") {
|
|
2736
|
+
try {
|
|
2737
|
+
const me = await apiGetJockey();
|
|
2738
|
+
if (me.device_label) {
|
|
2739
|
+
console.log(` This machine's credential, "${me.device_label}", will stay live on the`);
|
|
2740
|
+
console.log(" abandoned account and can only be revoked from the Account view in the org manager.");
|
|
2741
|
+
}
|
|
2742
|
+
if (me.email) {
|
|
2743
|
+
console.log(" This account has a Google account linked \u2014 `token-derby login` would recover");
|
|
2744
|
+
console.log(" this same jockey instead of abandoning it.");
|
|
2745
|
+
}
|
|
2746
|
+
} catch {
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
if (!isTTY) {
|
|
2750
|
+
console.error("");
|
|
2751
|
+
console.error("Refusing to reset without a terminal to confirm \u2014 this is irreversible.");
|
|
2752
|
+
console.error(
|
|
2753
|
+
"Run `token-derby init --reset` interactively, or use `token-derby login` to recover this jockey instead."
|
|
2754
|
+
);
|
|
2755
|
+
return 1;
|
|
2756
|
+
}
|
|
2757
|
+
const answer = (await promptText('Type "yes" to abandon this jockey [no]: ')).trim().toLowerCase();
|
|
2758
|
+
if (answer !== "yes") {
|
|
2759
|
+
console.log("Reset cancelled. Nothing was deleted.");
|
|
2651
2760
|
return 0;
|
|
2652
2761
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2762
|
+
}
|
|
2763
|
+
await deleteIdentity2();
|
|
2764
|
+
_resetIdentityCacheForTests();
|
|
2765
|
+
console.log("Removed local identity. Creating a new one\u2026");
|
|
2766
|
+
}
|
|
2767
|
+
if (!reset) {
|
|
2768
|
+
const state = await readIdentityFile2();
|
|
2769
|
+
if (state.kind === "unreadable") {
|
|
2770
|
+
console.error(`Your local identity file ${state.reason}.`);
|
|
2771
|
+
console.error("It may hold a credential that cannot be recovered, so this will not overwrite it.");
|
|
2772
|
+
console.error("Run `token-derby login` to sign in fresh, or `token-derby init --reset` to discard it.");
|
|
2773
|
+
return 1;
|
|
2774
|
+
}
|
|
2775
|
+
if (state.kind === "no-credential") {
|
|
2776
|
+
console.log(`Your local identity file ${state.reason}, so this will replace it.`);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
const existing = await loadIdentity2();
|
|
2780
|
+
if (existing) {
|
|
2781
|
+
console.log(`Current jockey name: ${existing.display_name}`);
|
|
2782
|
+
const raw2 = (await promptText("New jockey name (use your real name please) [keep]: ")).trim();
|
|
2783
|
+
if (!raw2) {
|
|
2784
|
+
console.log("Kept existing name.");
|
|
2785
|
+
return 0;
|
|
2786
|
+
}
|
|
2787
|
+
const v2 = validateDisplayName(raw2);
|
|
2788
|
+
if (!v2.ok) {
|
|
2789
|
+
console.error(v2.error);
|
|
2790
|
+
return 1;
|
|
2791
|
+
}
|
|
2792
|
+
try {
|
|
2793
|
+
const resp = await updateJockey2({ display_name: v2.name });
|
|
2794
|
+
const updated = { ...existing, display_name: resp.display_name };
|
|
2795
|
+
await saveIdentity2(updated);
|
|
2796
|
+
console.log(`Updated jockey name to: ${updated.display_name}`);
|
|
2797
|
+
return 0;
|
|
2798
|
+
} catch (e) {
|
|
2799
|
+
if (e instanceof ApiError) {
|
|
2800
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
2801
|
+
console.error(
|
|
2802
|
+
"Server does not recognise this identity. Your account may have been wiped. Run `token-derby init --reset` to start fresh."
|
|
2803
|
+
);
|
|
2804
|
+
} else {
|
|
2805
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2806
|
+
}
|
|
2656
2807
|
return 1;
|
|
2657
2808
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2809
|
+
throw e;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
const raw = (await promptText("Jockey Name (use your real name please): ")).trim();
|
|
2813
|
+
const v = validateDisplayName(raw);
|
|
2814
|
+
if (!v.ok) {
|
|
2815
|
+
console.error(v.error);
|
|
2816
|
+
return 1;
|
|
2817
|
+
}
|
|
2818
|
+
try {
|
|
2819
|
+
const resp = await initJockey2({ display_name: v.name });
|
|
2820
|
+
const identity = {
|
|
2821
|
+
user_id: resp.user_id,
|
|
2822
|
+
display_name: resp.display_name,
|
|
2823
|
+
secret_token: resp.secret_token,
|
|
2824
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2825
|
+
};
|
|
2826
|
+
await saveIdentity2(identity);
|
|
2827
|
+
_resetIdentityCacheForTests();
|
|
2828
|
+
console.log("");
|
|
2829
|
+
console.log(`Welcome, ${identity.display_name}!`);
|
|
2830
|
+
console.log("Your identity has been created on the server.");
|
|
2831
|
+
console.log("You can now create a stable and join races.");
|
|
2832
|
+
console.log("");
|
|
2833
|
+
console.log(" \u26A0 Your secret token is stored locally in identity.json.");
|
|
2834
|
+
console.log(" If you lose it, you cannot recover this account \u2014 you would");
|
|
2835
|
+
console.log(" need to run `token-derby init --reset` and rebuild your stable.");
|
|
2836
|
+
return 0;
|
|
2837
|
+
} catch (e) {
|
|
2838
|
+
if (e instanceof ApiError) {
|
|
2839
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2840
|
+
return 1;
|
|
2841
|
+
}
|
|
2842
|
+
throw e;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/commands/login.ts
|
|
2847
|
+
import { spawn as spawn2 } from "child_process";
|
|
2848
|
+
import * as os3 from "os";
|
|
2849
|
+
|
|
2850
|
+
// src/commands/web.ts
|
|
2851
|
+
import { spawn } from "child_process";
|
|
2852
|
+
function webOrigin() {
|
|
2853
|
+
return apiBase().replace(/\/api\/?$/, "");
|
|
2854
|
+
}
|
|
2855
|
+
function opener() {
|
|
2856
|
+
if (process.platform === "darwin") return "open";
|
|
2857
|
+
if (process.platform === "win32") return "start";
|
|
2858
|
+
if (process.platform === "linux") return "xdg-open";
|
|
2859
|
+
return null;
|
|
2860
|
+
}
|
|
2861
|
+
async function webCommand(deps = {}) {
|
|
2862
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
2863
|
+
let code;
|
|
2864
|
+
try {
|
|
2865
|
+
({ code } = await createWebSession());
|
|
2866
|
+
} catch (e) {
|
|
2867
|
+
if (e instanceof ApiError) {
|
|
2868
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2869
|
+
return 1;
|
|
2870
|
+
}
|
|
2871
|
+
throw e;
|
|
2872
|
+
}
|
|
2873
|
+
const url = `${webOrigin()}/org-manager#code=${code}`;
|
|
2874
|
+
console.log("");
|
|
2875
|
+
console.log(" Opening the Token Derby org manager in your browser...");
|
|
2876
|
+
console.log(` ${url}`);
|
|
2877
|
+
console.log("");
|
|
2878
|
+
console.log(" If it doesn't open, copy the link above. It expires in 60 seconds.");
|
|
2879
|
+
const cmd = opener();
|
|
2880
|
+
if (cmd) {
|
|
2881
|
+
try {
|
|
2882
|
+
const child = spawnImpl(cmd, [url], { stdio: "ignore", detached: true });
|
|
2883
|
+
child.on("error", () => {
|
|
2884
|
+
});
|
|
2885
|
+
child.unref();
|
|
2886
|
+
} catch {
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
return 0;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
// src/ui/prompt.ts
|
|
2893
|
+
async function promptYesNo(question) {
|
|
2894
|
+
resetStdinAfterInk();
|
|
2895
|
+
const readline6 = await import("readline/promises");
|
|
2896
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2897
|
+
const a = (await rl.question(question)).trim().toLowerCase();
|
|
2898
|
+
rl.close();
|
|
2899
|
+
return a === "" || a === "y" || a === "yes";
|
|
2900
|
+
}
|
|
2901
|
+
function resetStdinAfterInk() {
|
|
2902
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2903
|
+
process.stdin.setRawMode(false);
|
|
2904
|
+
}
|
|
2905
|
+
while (process.stdin.read() !== null) {
|
|
2906
|
+
}
|
|
2907
|
+
process.stdin.pause();
|
|
2908
|
+
process.stdin.ref();
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
// src/commands/login.ts
|
|
2912
|
+
function parseDeviceNameFlag(argv) {
|
|
2913
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2914
|
+
const a = argv[i];
|
|
2915
|
+
if (a === "--device-name") return argv[i + 1] ?? null;
|
|
2916
|
+
if (a.startsWith("--device-name=")) return a.slice("--device-name=".length);
|
|
2917
|
+
}
|
|
2918
|
+
return null;
|
|
2919
|
+
}
|
|
2920
|
+
async function defaultPromptText2(question) {
|
|
2921
|
+
const readline6 = await import("readline/promises");
|
|
2922
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2923
|
+
try {
|
|
2924
|
+
return await rl.question(question);
|
|
2925
|
+
} finally {
|
|
2926
|
+
rl.close();
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
function defaultSleep(ms) {
|
|
2930
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2931
|
+
}
|
|
2932
|
+
async function resolveDeviceName(flagName, isTTY, hostname3, promptText) {
|
|
2933
|
+
if (flagName) return flagName;
|
|
2934
|
+
if (!isTTY) return hostname3;
|
|
2935
|
+
const answer = (await promptText(`Device name [${hostname3}]: `)).trim();
|
|
2936
|
+
return answer || hostname3;
|
|
2937
|
+
}
|
|
2938
|
+
async function loginCommand(argv = [], deps = {}) {
|
|
2939
|
+
const apiStart = deps.apiStart ?? cliAuthStart;
|
|
2940
|
+
const apiPoll = deps.apiPoll ?? cliAuthPoll;
|
|
2941
|
+
const apiRevokeDevice = deps.apiRevokeDevice ?? revokeDevice;
|
|
2942
|
+
const apiCreateWebSession = deps.apiCreateWebSession ?? createWebSession;
|
|
2943
|
+
const saveIdentity2 = deps.saveIdentity ?? saveIdentity;
|
|
2944
|
+
const loadIdentity2 = deps.loadIdentity ?? loadIdentity;
|
|
2945
|
+
const promptText = deps.promptText ?? defaultPromptText2;
|
|
2946
|
+
const promptYesNo2 = deps.promptYesNo ?? promptYesNo;
|
|
2947
|
+
const sleepImpl = deps.sleepImpl ?? defaultSleep;
|
|
2948
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
2949
|
+
const spawnImpl = deps.spawnImpl ?? spawn2;
|
|
2950
|
+
const hostname3 = deps.hostname ?? (() => os3.hostname());
|
|
2951
|
+
const existing = await loadIdentity2();
|
|
2952
|
+
if (existing) {
|
|
2953
|
+
console.log("");
|
|
2954
|
+
console.log(` This machine is already signed in as ${existing.display_name}.`);
|
|
2955
|
+
console.log(" Signing in again adds a second credential; the current one stays active");
|
|
2956
|
+
console.log(" until you revoke it under Account in the org manager (`token-derby web`).");
|
|
2957
|
+
if (isTTY) {
|
|
2958
|
+
if (!await promptYesNo2(" Sign in again anyway? [Y/n] ")) {
|
|
2959
|
+
console.log("Login cancelled.");
|
|
2663
2960
|
return 0;
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2961
|
+
}
|
|
2962
|
+
} else {
|
|
2963
|
+
console.log(" No TTY to confirm \u2014 continuing (equivalent to answering Y).");
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
let label = await resolveDeviceName(parseDeviceNameFlag(argv), isTTY, hostname3(), promptText);
|
|
2967
|
+
let start;
|
|
2968
|
+
for (; ; ) {
|
|
2969
|
+
try {
|
|
2970
|
+
start = await apiStart({ label });
|
|
2971
|
+
break;
|
|
2972
|
+
} catch (e) {
|
|
2973
|
+
if (e instanceof ApiError && e.code === "BAD_REQUEST") {
|
|
2974
|
+
console.error(`Error: ${e.message}`);
|
|
2975
|
+
if (!isTTY) return 1;
|
|
2976
|
+
const retry = (await promptText("Enter a different device name: ")).trim();
|
|
2977
|
+
if (!retry) {
|
|
2978
|
+
console.error("Device name cannot be empty.");
|
|
2673
2979
|
return 1;
|
|
2674
2980
|
}
|
|
2981
|
+
label = retry;
|
|
2982
|
+
continue;
|
|
2983
|
+
}
|
|
2984
|
+
if (e instanceof ApiError) {
|
|
2985
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2986
|
+
return 1;
|
|
2987
|
+
}
|
|
2988
|
+
throw e;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
let verificationUrl = start.verification_uri;
|
|
2992
|
+
let hasGrant = false;
|
|
2993
|
+
if (existing) {
|
|
2994
|
+
try {
|
|
2995
|
+
const grant = await apiCreateWebSession();
|
|
2996
|
+
verificationUrl = `${start.verification_uri}#code=${grant.code}`;
|
|
2997
|
+
hasGrant = true;
|
|
2998
|
+
} catch (e) {
|
|
2999
|
+
if (e instanceof ApiError && e.code === "UNAUTHENTICATED") {
|
|
3000
|
+
console.log("");
|
|
3001
|
+
console.log(" The credential stored on this machine is no longer valid (it may have been");
|
|
3002
|
+
console.log(" revoked), so this will sign you in fresh in the browser.");
|
|
3003
|
+
} else if (e instanceof ApiError) {
|
|
3004
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3005
|
+
return 1;
|
|
3006
|
+
} else {
|
|
2675
3007
|
throw e;
|
|
2676
3008
|
}
|
|
2677
3009
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
3010
|
+
}
|
|
3011
|
+
console.log("");
|
|
3012
|
+
console.log(" To finish signing in, visit:");
|
|
3013
|
+
console.log(` ${verificationUrl}`);
|
|
3014
|
+
if (hasGrant) {
|
|
3015
|
+
console.log(" That link signs the browser in for you and expires in 60 seconds \u2014 if it");
|
|
3016
|
+
console.log(" does, run `token-derby login` again for a fresh one.");
|
|
3017
|
+
}
|
|
3018
|
+
console.log("");
|
|
3019
|
+
console.log(" And enter this code:");
|
|
3020
|
+
console.log(` ${start.user_code}`);
|
|
3021
|
+
console.log("");
|
|
3022
|
+
console.log(" Waiting for approval...");
|
|
3023
|
+
const cmd = opener();
|
|
3024
|
+
if (cmd) {
|
|
3025
|
+
try {
|
|
3026
|
+
const child = spawnImpl(cmd, [verificationUrl], { stdio: "ignore", detached: true });
|
|
3027
|
+
child.on("error", () => {
|
|
3028
|
+
});
|
|
3029
|
+
child.unref();
|
|
3030
|
+
} catch {
|
|
2683
3031
|
}
|
|
3032
|
+
}
|
|
3033
|
+
const deadline = Date.now() + start.expires_in * 1e3;
|
|
3034
|
+
let approved = null;
|
|
3035
|
+
while (!approved) {
|
|
3036
|
+
let poll;
|
|
2684
3037
|
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;
|
|
3038
|
+
poll = await apiPoll({ device_code: start.device_code });
|
|
2703
3039
|
} catch (e) {
|
|
2704
3040
|
if (e instanceof ApiError) {
|
|
2705
3041
|
console.error(`Error: ${e.code} ${e.message}`);
|
|
@@ -2707,21 +3043,292 @@ async function initCommand(reset = false) {
|
|
|
2707
3043
|
}
|
|
2708
3044
|
throw e;
|
|
2709
3045
|
}
|
|
3046
|
+
if (poll.status === "approved") {
|
|
3047
|
+
approved = poll;
|
|
3048
|
+
break;
|
|
3049
|
+
}
|
|
3050
|
+
if (Date.now() >= deadline) {
|
|
3051
|
+
console.error("Login timed out waiting for approval. Run `token-derby login` again.");
|
|
3052
|
+
return 1;
|
|
3053
|
+
}
|
|
3054
|
+
await sleepImpl(start.interval * 1e3);
|
|
3055
|
+
}
|
|
3056
|
+
console.log("");
|
|
3057
|
+
if (approved.email) console.log(` \u2713 Signed in as ${approved.email}`);
|
|
3058
|
+
console.log(
|
|
3059
|
+
` Linking to your existing jockey: ${approved.display_name} (${approved.horses} horses, ${approved.orgs} orgs)`
|
|
3060
|
+
);
|
|
3061
|
+
let proceed;
|
|
3062
|
+
if (isTTY) {
|
|
3063
|
+
proceed = await promptYesNo2(" Link this account? [Y/n] ");
|
|
3064
|
+
} else {
|
|
3065
|
+
console.log(" No TTY to confirm \u2014 proceeding automatically (equivalent to answering Y).");
|
|
3066
|
+
proceed = true;
|
|
3067
|
+
}
|
|
3068
|
+
if (!proceed) {
|
|
3069
|
+
try {
|
|
3070
|
+
await apiRevokeDevice(approved.device_id, { user_id: approved.user_id, secret_token: approved.secret_token });
|
|
3071
|
+
} catch (e) {
|
|
3072
|
+
console.error("Declined, but could not revoke the device credential on the server.");
|
|
3073
|
+
if (e instanceof ApiError) console.error(`Error: ${e.code} ${e.message}`);
|
|
3074
|
+
return 1;
|
|
3075
|
+
}
|
|
3076
|
+
console.log("Login cancelled.");
|
|
3077
|
+
return 0;
|
|
3078
|
+
}
|
|
3079
|
+
const identity = {
|
|
3080
|
+
user_id: approved.user_id,
|
|
3081
|
+
display_name: approved.display_name,
|
|
3082
|
+
secret_token: approved.secret_token,
|
|
3083
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3084
|
+
};
|
|
3085
|
+
await saveIdentity2(identity);
|
|
3086
|
+
console.log("");
|
|
3087
|
+
console.log(`Welcome back, ${identity.display_name}!`);
|
|
3088
|
+
return 0;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
// src/commands/logout.ts
|
|
3092
|
+
async function logoutCommand(deps = {}) {
|
|
3093
|
+
const apiLogoutDevice = deps.apiLogoutDevice ?? logoutDevice;
|
|
3094
|
+
const deleteIdentity2 = deps.deleteIdentity ?? deleteIdentity;
|
|
3095
|
+
try {
|
|
3096
|
+
const result = await apiLogoutDevice();
|
|
3097
|
+
if (result.revoked) {
|
|
3098
|
+
console.log("Revoked this device's credential on the server.");
|
|
3099
|
+
} else {
|
|
3100
|
+
console.log(
|
|
3101
|
+
"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."
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
} catch (e) {
|
|
3105
|
+
if (e instanceof ApiError && e.code === "UNAUTHENTICATED") {
|
|
3106
|
+
console.error("This device credential was already invalid on the server \u2014 clearing it locally too.");
|
|
3107
|
+
await deleteIdentity2();
|
|
3108
|
+
console.log("Removed local identity. You are signed out.");
|
|
3109
|
+
return 0;
|
|
3110
|
+
}
|
|
3111
|
+
if (e instanceof ApiError) {
|
|
3112
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3113
|
+
console.error("Local identity left in place \u2014 run `token-derby logout` again to retry.");
|
|
3114
|
+
return 1;
|
|
3115
|
+
}
|
|
3116
|
+
throw e;
|
|
3117
|
+
}
|
|
3118
|
+
await deleteIdentity2();
|
|
3119
|
+
console.log("Removed local identity. You are signed out.");
|
|
3120
|
+
return 0;
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
// src/commands/link.ts
|
|
3124
|
+
import { spawn as spawn3 } from "child_process";
|
|
3125
|
+
import * as os4 from "os";
|
|
3126
|
+
|
|
3127
|
+
// src/ui/messages.ts
|
|
3128
|
+
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.";
|
|
3129
|
+
|
|
3130
|
+
// src/commands/link.ts
|
|
3131
|
+
function defaultSleep2(ms) {
|
|
3132
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3133
|
+
}
|
|
3134
|
+
async function defaultPromptText3(question) {
|
|
3135
|
+
const readline6 = await import("readline/promises");
|
|
3136
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
3137
|
+
try {
|
|
3138
|
+
return await rl.question(question);
|
|
2710
3139
|
} finally {
|
|
2711
3140
|
rl.close();
|
|
2712
3141
|
}
|
|
2713
3142
|
}
|
|
3143
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
3144
|
+
var WAIT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3145
|
+
async function linkCommand(argv = [], deps = {}) {
|
|
3146
|
+
const apiGetJockey = deps.apiGetJockey ?? getJockey;
|
|
3147
|
+
const apiCreateWebSession = deps.apiCreateWebSession ?? createWebSession;
|
|
3148
|
+
const apiRegisterDevice = deps.apiRegisterDevice ?? registerDevice;
|
|
3149
|
+
const loadIdentity2 = deps.loadIdentity ?? loadIdentity;
|
|
3150
|
+
const saveIdentity2 = deps.saveIdentity ?? saveIdentity;
|
|
3151
|
+
const spawnImpl = deps.spawnImpl ?? spawn3;
|
|
3152
|
+
const sleepImpl = deps.sleepImpl ?? defaultSleep2;
|
|
3153
|
+
const promptText = deps.promptText ?? defaultPromptText3;
|
|
3154
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
3155
|
+
const hostname3 = deps.hostname ?? (() => os4.hostname());
|
|
3156
|
+
const flagName = parseDeviceNameFlag(argv);
|
|
3157
|
+
if (flagName !== null) {
|
|
3158
|
+
const checked = validateDeviceLabel(flagName);
|
|
3159
|
+
if (!checked.ok) {
|
|
3160
|
+
console.error(`Error: invalid --device-name: ${checked.message}`);
|
|
3161
|
+
return 1;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
const syncLocalName = async (serverName) => {
|
|
3165
|
+
const local = await loadIdentity2();
|
|
3166
|
+
if (!local || local.display_name === serverName) return;
|
|
3167
|
+
await saveIdentity2({ ...local, display_name: serverName });
|
|
3168
|
+
};
|
|
3169
|
+
const registerThisMachine = async (linked) => {
|
|
3170
|
+
const serverName = linked.display_name;
|
|
3171
|
+
if (linked.device_label) {
|
|
3172
|
+
await syncLocalName(serverName);
|
|
3173
|
+
console.log(` This machine already has its own credential, "${linked.device_label}", so it`);
|
|
3174
|
+
console.log(" was not registered again \u2014 a second one would leave the first live with");
|
|
3175
|
+
console.log(" nothing on this machine holding it.");
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
const local = await loadIdentity2();
|
|
3179
|
+
if (!local) {
|
|
3180
|
+
console.log(" This machine has no local identity file, so it was not registered as a");
|
|
3181
|
+
console.log(" device. Run `token-derby login` to give it its own credential.");
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
const named = local.display_name === serverName ? local : { ...local, display_name: serverName };
|
|
3185
|
+
let label;
|
|
3186
|
+
try {
|
|
3187
|
+
label = await resolveDeviceName(flagName, isTTY, hostname3(), promptText);
|
|
3188
|
+
for (; ; ) {
|
|
3189
|
+
try {
|
|
3190
|
+
const registered = await apiRegisterDevice({ label });
|
|
3191
|
+
await saveIdentity2({ ...named, secret_token: registered.secret_token });
|
|
3192
|
+
break;
|
|
3193
|
+
} catch (e) {
|
|
3194
|
+
if (!(e instanceof ApiError && e.code === "BAD_REQUEST" && isTTY)) throw e;
|
|
3195
|
+
console.error(` Error: ${e.message}`);
|
|
3196
|
+
const retry = (await promptText(" Enter a different device name: ")).trim();
|
|
3197
|
+
if (!retry) throw e;
|
|
3198
|
+
label = retry;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
} catch (e) {
|
|
3202
|
+
try {
|
|
3203
|
+
if (named !== local) await saveIdentity2(named);
|
|
3204
|
+
} catch {
|
|
3205
|
+
}
|
|
3206
|
+
console.log("");
|
|
3207
|
+
console.log(" Your account is linked \u2014 that part is done and does not need repeating.");
|
|
3208
|
+
console.log(" Registering this machine as a device did not complete, so it is still");
|
|
3209
|
+
console.log(" using the shared account credential. Run `token-derby login` to finish.");
|
|
3210
|
+
console.error(` Error: ${e instanceof ApiError ? `${e.code} ${e.message}` : String(e)}`);
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
console.log(` This machine is now registered as "${label}", with a credential of its own`);
|
|
3214
|
+
console.log(" that you can revoke separately under Account in the org manager.");
|
|
3215
|
+
};
|
|
3216
|
+
let me;
|
|
3217
|
+
try {
|
|
3218
|
+
me = await apiGetJockey();
|
|
3219
|
+
} catch (e) {
|
|
3220
|
+
if (e instanceof ApiError) {
|
|
3221
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
3222
|
+
console.error(CREDENTIAL_DEAD_MESSAGE);
|
|
3223
|
+
return 1;
|
|
3224
|
+
}
|
|
3225
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3226
|
+
return 1;
|
|
3227
|
+
}
|
|
3228
|
+
throw e;
|
|
3229
|
+
}
|
|
3230
|
+
if (me.email) {
|
|
3231
|
+
console.log(`Already linked to ${me.email}.`);
|
|
3232
|
+
await syncLocalName(me.display_name);
|
|
3233
|
+
return 0;
|
|
3234
|
+
}
|
|
3235
|
+
let code;
|
|
3236
|
+
try {
|
|
3237
|
+
({ code } = await apiCreateWebSession());
|
|
3238
|
+
} catch (e) {
|
|
3239
|
+
if (e instanceof ApiError) {
|
|
3240
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3241
|
+
return 1;
|
|
3242
|
+
}
|
|
3243
|
+
throw e;
|
|
3244
|
+
}
|
|
3245
|
+
const url = `${webOrigin()}/link#code=${code}`;
|
|
3246
|
+
console.log("");
|
|
3247
|
+
console.log(" Opening the Google link page in your browser...");
|
|
3248
|
+
console.log(` ${url}`);
|
|
3249
|
+
console.log("");
|
|
3250
|
+
console.log(" If it doesn't open, copy the link above. It expires in 60 seconds.");
|
|
3251
|
+
const cmd = opener();
|
|
3252
|
+
if (cmd) {
|
|
3253
|
+
try {
|
|
3254
|
+
const child = spawnImpl(cmd, [url], { stdio: "ignore", detached: true });
|
|
3255
|
+
child.on("error", () => {
|
|
3256
|
+
});
|
|
3257
|
+
child.unref();
|
|
3258
|
+
} catch {
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
console.log("");
|
|
3262
|
+
console.log(" Waiting for you to finish connecting with Google...");
|
|
3263
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
3264
|
+
for (; ; ) {
|
|
3265
|
+
let poll;
|
|
3266
|
+
try {
|
|
3267
|
+
poll = await apiGetJockey();
|
|
3268
|
+
} catch (e) {
|
|
3269
|
+
if (e instanceof ApiError) {
|
|
3270
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
3271
|
+
console.error(CREDENTIAL_DEAD_MESSAGE);
|
|
3272
|
+
return 1;
|
|
3273
|
+
}
|
|
3274
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3275
|
+
return 1;
|
|
3276
|
+
}
|
|
3277
|
+
throw e;
|
|
3278
|
+
}
|
|
3279
|
+
if (poll.email) {
|
|
3280
|
+
console.log(`\u2713 Linked to ${poll.email}.`);
|
|
3281
|
+
if (poll.display_name !== me.display_name) {
|
|
3282
|
+
console.log(` Your jockey is now named ${poll.display_name} \u2014 a first link renames it to`);
|
|
3283
|
+
console.log(" the first name on the Google account. `token-derby init` renames it again");
|
|
3284
|
+
console.log(" if you would rather it said something else.");
|
|
3285
|
+
}
|
|
3286
|
+
await registerThisMachine(poll);
|
|
3287
|
+
return 0;
|
|
3288
|
+
}
|
|
3289
|
+
if (Date.now() >= deadline) {
|
|
3290
|
+
console.error("Timed out waiting for the Google link to complete. Run `token-derby link` again.");
|
|
3291
|
+
return 1;
|
|
3292
|
+
}
|
|
3293
|
+
await sleepImpl(POLL_INTERVAL_MS);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
// src/commands/whoami.ts
|
|
3298
|
+
async function whoamiCommand(deps = {}) {
|
|
3299
|
+
const apiGetJockey = deps.apiGetJockey ?? getJockey;
|
|
3300
|
+
let me;
|
|
3301
|
+
try {
|
|
3302
|
+
me = await apiGetJockey();
|
|
3303
|
+
} catch (e) {
|
|
3304
|
+
if (e instanceof ApiError) {
|
|
3305
|
+
if (e.code === "UNAUTHENTICATED") {
|
|
3306
|
+
console.error(CREDENTIAL_DEAD_MESSAGE);
|
|
3307
|
+
return 1;
|
|
3308
|
+
}
|
|
3309
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3310
|
+
return 1;
|
|
3311
|
+
}
|
|
3312
|
+
throw e;
|
|
3313
|
+
}
|
|
3314
|
+
console.log("");
|
|
3315
|
+
console.log(` ${me.display_name}`);
|
|
3316
|
+
if (me.email) console.log(` ${me.email}`);
|
|
3317
|
+
if (me.device_label) console.log(` this machine: ${me.device_label}`);
|
|
3318
|
+
console.log("");
|
|
3319
|
+
return 0;
|
|
3320
|
+
}
|
|
2714
3321
|
|
|
2715
3322
|
// src/commands/update.ts
|
|
2716
3323
|
import * as readline5 from "readline/promises";
|
|
2717
3324
|
import { stdin as stdin5, stdout as stdout5 } from "process";
|
|
2718
|
-
import { spawn } from "child_process";
|
|
3325
|
+
import { spawn as spawn4 } from "child_process";
|
|
2719
3326
|
var REGISTRY_URL = "https://registry.npmjs.org/@mauricode/token-derby/latest";
|
|
2720
3327
|
var UPGRADE_CMD = "npm install -g @mauricode/token-derby@latest";
|
|
2721
3328
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
2722
3329
|
async function updateCommand(deps = {}) {
|
|
2723
3330
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
2724
|
-
const spawnImpl = deps.spawnImpl ??
|
|
3331
|
+
const spawnImpl = deps.spawnImpl ?? spawn4;
|
|
2725
3332
|
const promptYesNo2 = deps.promptYesNo ?? defaultPromptYesNo;
|
|
2726
3333
|
let latest;
|
|
2727
3334
|
try {
|
|
@@ -2857,25 +3464,6 @@ function RollHorsePicker({ horses, onPick, onCancel }) {
|
|
|
2857
3464
|
] });
|
|
2858
3465
|
}
|
|
2859
3466
|
|
|
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
3467
|
// src/ui/reveal.ts
|
|
2880
3468
|
import React13 from "react";
|
|
2881
3469
|
import { render as render5 } from "ink";
|
|
@@ -3310,48 +3898,6 @@ async function orgJoinCommand(token) {
|
|
|
3310
3898
|
}
|
|
3311
3899
|
}
|
|
3312
3900
|
|
|
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
3901
|
// src/commands/env.ts
|
|
3356
3902
|
function warnOverrides() {
|
|
3357
3903
|
if (process.env.TOKEN_DERBY_HOME) {
|
|
@@ -3386,10 +3932,27 @@ function envCommand(arg) {
|
|
|
3386
3932
|
var HELP = `token-derby v${CLI_VERSION}
|
|
3387
3933
|
|
|
3388
3934
|
Identity:
|
|
3389
|
-
token-derby init
|
|
3390
|
-
|
|
3391
|
-
token-derby init --reset
|
|
3392
|
-
|
|
3935
|
+
token-derby init Deprecated \u2014 creates or renames your jockey identity;
|
|
3936
|
+
prefer \`login\`, which also links your account.
|
|
3937
|
+
token-derby init --reset Deprecated \u2014 wipes local identity and starts a fresh
|
|
3938
|
+
account (previous stable abandoned); \`login\` recovers
|
|
3939
|
+
your existing account instead of abandoning it.
|
|
3940
|
+
token-derby login [--device-name <name>]
|
|
3941
|
+
About THIS MACHINE: sign in with Google to give
|
|
3942
|
+
it a credential of its own. The way in for a
|
|
3943
|
+
machine that has none yet. Adds no email to
|
|
3944
|
+
your account \u2014 see \`link\`.
|
|
3945
|
+
token-derby logout Retire this machine's credential and clear
|
|
3946
|
+
local identity.
|
|
3947
|
+
token-derby link [--device-name <name>]
|
|
3948
|
+
About YOUR JOCKEY: connect it to a Google
|
|
3949
|
+
account \u2014 adds your email on file, and
|
|
3950
|
+
renames your jockey to the first name on
|
|
3951
|
+
that account. Then registers this machine
|
|
3952
|
+
as a device too, if it is still using the
|
|
3953
|
+
shared account credential.
|
|
3954
|
+
token-derby whoami Show your jockey name, linked email (if any),
|
|
3955
|
+
and this machine's device name (if any).
|
|
3393
3956
|
|
|
3394
3957
|
Maintenance:
|
|
3395
3958
|
token-derby update Check for and install the latest CLI version
|
|
@@ -3442,11 +4005,12 @@ async function main() {
|
|
|
3442
4005
|
const reset = argv.slice(1).includes("--reset");
|
|
3443
4006
|
return initCommand(reset);
|
|
3444
4007
|
}
|
|
4008
|
+
if (cmd === "login") return loginCommand(argv.slice(1));
|
|
3445
4009
|
if (cmd === "update") return updateCommand();
|
|
3446
4010
|
if (cmd === "env") return envCommand(argv[1]);
|
|
3447
4011
|
const identity = await loadIdentity();
|
|
3448
4012
|
if (!identity) {
|
|
3449
|
-
console.error("Run `token-derby
|
|
4013
|
+
console.error("Run `token-derby login` to set up your identity before using any other command.");
|
|
3450
4014
|
return 1;
|
|
3451
4015
|
}
|
|
3452
4016
|
if (cmd === "stable") {
|
|
@@ -3470,6 +4034,9 @@ async function main() {
|
|
|
3470
4034
|
const orgName = parseFlag(argv.slice(1), "--organisation");
|
|
3471
4035
|
return createRaceCommand(orgName);
|
|
3472
4036
|
}
|
|
4037
|
+
if (cmd === "logout") return logoutCommand();
|
|
4038
|
+
if (cmd === "link") return linkCommand(argv.slice(1));
|
|
4039
|
+
if (cmd === "whoami") return whoamiCommand();
|
|
3473
4040
|
if (cmd === "join") return joinCommand(argv[1], argv.slice(2));
|
|
3474
4041
|
if (cmd === "end") return endCommand(argv[1]);
|
|
3475
4042
|
if (cmd === "roll") return rollCommand();
|