@mauricode/token-derby 2.13.0 → 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 +1040 -353
- 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+)(?:[-+].*)?$/;
|
|
@@ -152,48 +154,52 @@ function hatById(id) {
|
|
|
152
154
|
}
|
|
153
155
|
var HATS = [
|
|
154
156
|
// ── COMMON (18) ────────────────────────────────────────────────────────
|
|
155
|
-
{ id: "flat_cap", name: "Flat Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...........", "....AAAA...", "...AAAAAA..", "..AAAAAAA.."], variants: [{ A: "#8B6914" }, { A: "#12301b" }, { A: "#aab5cb" }, { A: "#bdcbaa" }] },
|
|
156
|
-
{ id: "bucket_hat", name: "Bucket Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "...AAAAA...", "...AAAAA...", ".AAAAAAAAA."], variants: [{ A: "#CC2200", Q: "#FFFFFF" }, { A: "#0007cc", Q: "#FFFFFF" }, { A: "#becc00", Q: "#4d3dc7" }, { A: "#cc00c5", Q: "#d6d6dc" }] },
|
|
157
|
-
{ id: "beanie", name: "Beanie", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#4A7C59" }, { A: "#b21f35" }, { A: "#ec9418" }, { A: "#3a17e6" }] },
|
|
158
|
-
{ id: "stetson", name: "Stetson", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AA.AA...", "...AAAAA...", "...AAAAA...", "A..AAAAA..A", "AAAAAAAAAAA", "AAAAAAAAAAA"], variants: [{ A: "#C49A00" }, { A: "#272004" }, { A: "#040404" }, { A: "#373606" }] },
|
|
159
|
-
{ id: "party_hat", name: "Party Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "....AQA....", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FF69B4", Q: "#FFD700" }, { A: "#c46bff", Q: "#ff000d" }, { A: "#fdff6b", Q: "#0400ff" }, { A: "#6bff97", Q: "#05050a" }] },
|
|
160
|
-
{ id: "fez", name: "Fez", rarity: "common", width: 11, anchor_x: 22, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....Q......", "....AAA....", "....AAA....", "....AAA...."], variants: [{ A: "#CC0000", Q: "#8B0000" }, { A: "#CC0000", Q: "#f5e5e5" }, { A: "#CC0000", Q: "#f1e209" }, { A: "#CC0000", Q: "#0956f1" }] },
|
|
161
|
-
{ id: "sailor_hat", name: "Sailor Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "..AAAAAAA..", "..QQQQQQQ..", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#000080" }, { A: "#FFFFFF", Q: "#c0c0ed" }, { A: "#FFFFFF", Q: "#00000a" }, { A: "#FFFFFF", Q: "#0505ef" }] },
|
|
162
|
-
{ id: "newsboy_cap", name: "Newsboy Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA..", "..AAAAAA..."], variants: [{ A: "#5C4033", Q: "#3D2B1F" }, { A: "#e96020", Q: "#131211" }, { A: "#342c28", Q: "#131211" }, { A: "#0a0300", Q: "#ddab78" }] },
|
|
163
|
-
{ id: "tam_o_shanter", name: "Tam O'Shanter", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "....AAA....", "..AAAAAAA..", "..AAAAAAA..", "...AAAAA..."], variants: [{ A: "#006400", Q: "#FF0000" }, { A: "#ff0000", Q: "#016400" }] },
|
|
164
|
-
{ id: "trucker_cap", name: "Trucker Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAQQ....", "...AAQQ....", "..AAQQQAA..", "..AAQQQAA.."], variants: [{ A: "#2196F3", Q: "#FFFFFF" }, { A: "#f41fe3", Q: "#FFFFFF" }, { A: "#1ff45f", Q: "#FFFFFF" }] },
|
|
165
|
-
{ id: "hard_hat", name: "Hard Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#FFD600" }, { A: "#d74c1d" }] },
|
|
166
|
-
{ id: "chef_toque", name: "Chef's Toque", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AAAAA...", "..AAQAQAA..", "...AQAQA...", "...AQAQA...", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FFFFFF", Q: "#f9f9f9" }] },
|
|
167
|
-
{ id: "bobble_hat", name: "Bobble Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....Q.....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#C62828", Q: "#FFFFFF" }, { A: "#293bc7", Q: "#FFFFFF" }, { A: "#29c775", Q: "#231f1f" }, { A: "#a529c7", Q: "#d50707" }] },
|
|
168
|
-
{ id: "cowboy_hat", name: "Cowboy Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "A.AAAAAAA.A", ".AAAAAAAAA."], variants: [{ A: "#8B4513" }, { A: "#0a0400" }, { A: "#956c50" }] },
|
|
169
|
-
{ id: "baseball_cap", name: "Baseball Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#1565C0" }, { A: "#8515c1" }, { A: "#15c157" }, { A: "#ef2d0b" }] },
|
|
170
|
-
{ id: "tinfoil_hat", name: "Tinfoil Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#B0BEC5" }, { A: "#6a6c6c" }] },
|
|
171
|
-
{ id: "dunce_cap", name: "Dunce Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....Q.....", "....QAQ....", "...QAAAQ...", "...QAAAQ...", "..QAAAAAQ..", "..AAAAAAA.."], variants: [{ A: "#F8F8F8", Q: "#F44336" }] },
|
|
172
|
-
{ id: "mini_top_hat", name: "Mini Top Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#212121" }, { A: "#626060" }] },
|
|
157
|
+
{ id: "flat_cap", name: "Flat Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...........", "....AAAA...", "...AAAAAA..", "..AAAAAAA.."], variants: [{ A: "#8B6914" }, { A: "#12301b" }, { A: "#aab5cb" }, { A: "#bdcbaa" }], rollable: true },
|
|
158
|
+
{ id: "bucket_hat", name: "Bucket Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "...AAAAA...", "...AAAAA...", ".AAAAAAAAA."], variants: [{ A: "#CC2200", Q: "#FFFFFF" }, { A: "#0007cc", Q: "#FFFFFF" }, { A: "#becc00", Q: "#4d3dc7" }, { A: "#cc00c5", Q: "#d6d6dc" }], rollable: true },
|
|
159
|
+
{ id: "beanie", name: "Beanie", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#4A7C59" }, { A: "#b21f35" }, { A: "#ec9418" }, { A: "#3a17e6" }], rollable: true },
|
|
160
|
+
{ id: "stetson", name: "Stetson", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AA.AA...", "...AAAAA...", "...AAAAA...", "A..AAAAA..A", "AAAAAAAAAAA", "AAAAAAAAAAA"], variants: [{ A: "#C49A00" }, { A: "#272004" }, { A: "#040404" }, { A: "#373606" }], rollable: true },
|
|
161
|
+
{ id: "party_hat", name: "Party Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "....AQA....", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FF69B4", Q: "#FFD700" }, { A: "#c46bff", Q: "#ff000d" }, { A: "#fdff6b", Q: "#0400ff" }, { A: "#6bff97", Q: "#05050a" }], rollable: true },
|
|
162
|
+
{ id: "fez", name: "Fez", rarity: "common", width: 11, anchor_x: 22, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....Q......", "....AAA....", "....AAA....", "....AAA...."], variants: [{ A: "#CC0000", Q: "#8B0000" }, { A: "#CC0000", Q: "#f5e5e5" }, { A: "#CC0000", Q: "#f1e209" }, { A: "#CC0000", Q: "#0956f1" }], rollable: true },
|
|
163
|
+
{ id: "sailor_hat", name: "Sailor Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...QQQQQ...", "..AAAAAAA..", "..QQQQQQQ..", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#000080" }, { A: "#FFFFFF", Q: "#c0c0ed" }, { A: "#FFFFFF", Q: "#00000a" }, { A: "#FFFFFF", Q: "#0505ef" }], rollable: true },
|
|
164
|
+
{ id: "newsboy_cap", name: "Newsboy Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA..", "..AAAAAA..."], variants: [{ A: "#5C4033", Q: "#3D2B1F" }, { A: "#e96020", Q: "#131211" }, { A: "#342c28", Q: "#131211" }, { A: "#0a0300", Q: "#ddab78" }], rollable: true },
|
|
165
|
+
{ id: "tam_o_shanter", name: "Tam O'Shanter", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "....AAA....", "..AAAAAAA..", "..AAAAAAA..", "...AAAAA..."], variants: [{ A: "#006400", Q: "#FF0000" }, { A: "#ff0000", Q: "#016400" }], rollable: true },
|
|
166
|
+
{ id: "trucker_cap", name: "Trucker Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAQQ....", "...AAQQ....", "..AAQQQAA..", "..AAQQQAA.."], variants: [{ A: "#2196F3", Q: "#FFFFFF" }, { A: "#f41fe3", Q: "#FFFFFF" }, { A: "#1ff45f", Q: "#FFFFFF" }], rollable: true },
|
|
167
|
+
{ id: "hard_hat", name: "Hard Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#FFD600" }, { A: "#d74c1d" }], rollable: true },
|
|
168
|
+
{ id: "chef_toque", name: "Chef's Toque", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...AAAAA...", "..AAQAQAA..", "...AQAQA...", "...AQAQA...", "...AQAQA...", "...AAAAA..."], variants: [{ A: "#FFFFFF", Q: "#f9f9f9" }], rollable: true },
|
|
169
|
+
{ id: "bobble_hat", name: "Bobble Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....Q.....", "...AAAAA...", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#C62828", Q: "#FFFFFF" }, { A: "#293bc7", Q: "#FFFFFF" }, { A: "#29c775", Q: "#231f1f" }, { A: "#a529c7", Q: "#d50707" }], rollable: true },
|
|
170
|
+
{ id: "cowboy_hat", name: "Cowboy Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "A.AAAAAAA.A", ".AAAAAAAAA."], variants: [{ A: "#8B4513" }, { A: "#0a0400" }, { A: "#956c50" }], rollable: true },
|
|
171
|
+
{ id: "baseball_cap", name: "Baseball Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "...AAAAA...", "..AAAAAA...", "..AAAAAAA..", "..AAAAAAAAA"], variants: [{ A: "#1565C0" }, { A: "#8515c1" }, { A: "#15c157" }, { A: "#ef2d0b" }], rollable: true },
|
|
172
|
+
{ id: "tinfoil_hat", name: "Tinfoil Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA.."], variants: [{ A: "#B0BEC5" }, { A: "#6a6c6c" }], rollable: true },
|
|
173
|
+
{ id: "dunce_cap", name: "Dunce Cap", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....Q.....", "....QAQ....", "...QAAAQ...", "...QAAAQ...", "..QAAAAAQ..", "..AAAAAAA.."], variants: [{ A: "#F8F8F8", Q: "#F44336" }], rollable: true },
|
|
174
|
+
{ id: "mini_top_hat", name: "Mini Top Hat", rarity: "common", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...AAAAA...", "...AAAAA...", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#212121" }, { A: "#626060" }], rollable: true },
|
|
173
175
|
// ── RARE (10) ──────────────────────────────────────────────────────────
|
|
174
|
-
{ id: "bicorne", name: "Bicorne", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AAAQQQQ..", "..AAAAAAA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#1A237E", Q: "#FFD700" }, { A: "#01052d", Q: "#544803" }] },
|
|
175
|
-
{ id: "viking_helmet", name: "Viking Helmet", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..A.AAA.A..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#9E9E9E", Q: "#8D6E63" }, { A: "#dcdbdb", Q: "#bf3908" }] },
|
|
176
|
-
{ id: "jesters_cap", name: "Jester's Cap", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "...AAAAA...", "...AAAAA..."], variants: [{ A: "#E53935", Q: "#FFD600" }, { A: "#e5a434", Q: "#0040ff" }, { A: "#34e537", Q: "#eeff00" }, { A: "#e234e5", Q: "#00ff40" }] },
|
|
177
|
-
{ id: "plague_doctor", name: "Plague Doctor Beak", rarity: "rare", width: 11, anchor_x: 24, rows: ["...........", "...........", "...........", "...........", "....QQQ....", "...QAAAQQ..", "...QQQQQQQ.", "..QQQQQ.QQQ", "..QQQQ....Q", "..QQQQ....."], variants: [{ A: "#F5F5F5", Q: "#795548" }, { A: "#e1dbdb", Q: "#080707" }] },
|
|
178
|
-
{ id: "morion", name: "Conquistador Morion", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "...QQQQQ...", "...QAAAQ...", ".A..AAA..A.", "..AAQQQAA.."], variants: [{ A: "#B0BEC5", Q: "#FFD600" }, { A: "#0a4768", Q: "#e6c10a" }] },
|
|
179
|
-
{ id: "shako", name: "Shako", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "........Q..", "........Q..", "........Q..", "........Q..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#45464f", Q: "#00ff2a" }, { A: "#45464f", Q: "#ff4d00" }, { A: "#45464f", Q: "#ff0019" }, { A: "#45464f", Q: "#fff700" }] },
|
|
180
|
-
{ id: "centurion_helm", name: "Centurion Helm", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "....QQQ....", "...QQQQQQ..", "..QQQQQQQQ.", ".QQ..A...Q.", ".Q..AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#2f3131", Q: "#C62828" }] },
|
|
181
|
-
{ id: "papal_mitre", name: "Papal Mitre", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....QQQ....", "...QQQQQ...", "...QAAAQ...", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }] },
|
|
182
|
-
{ id: "headdress", name: "Headdress", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#FF8F00", Q: "#1565C0" }] },
|
|
183
|
-
{ id: "sombrero", name: "Sombrero", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "...AQAQA...", "AAAAAAAAAAA"], variants: [{ A: "#F57F17", Q: "#BF360C" }, { A: "#f8b377", Q: "#3e1204" }, { A: "#d6f877", Q: "#04133e" }, { A: "#d6f877", Q: "#04133e" }] },
|
|
176
|
+
{ id: "bicorne", name: "Bicorne", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AAAQQQQ..", "..AAAAAAA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#1A237E", Q: "#FFD700" }, { A: "#01052d", Q: "#544803" }], rollable: true },
|
|
177
|
+
{ id: "viking_helmet", name: "Viking Helmet", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..A.AAA.A..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#9E9E9E", Q: "#8D6E63" }, { A: "#dcdbdb", Q: "#bf3908" }], rollable: true },
|
|
178
|
+
{ id: "jesters_cap", name: "Jester's Cap", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "...AAAAA...", "...AAAAA..."], variants: [{ A: "#E53935", Q: "#FFD600" }, { A: "#e5a434", Q: "#0040ff" }, { A: "#34e537", Q: "#eeff00" }, { A: "#e234e5", Q: "#00ff40" }], rollable: true },
|
|
179
|
+
{ id: "plague_doctor", name: "Plague Doctor Beak", rarity: "rare", width: 11, anchor_x: 24, rows: ["...........", "...........", "...........", "...........", "....QQQ....", "...QAAAQQ..", "...QQQQQQQ.", "..QQQQQ.QQQ", "..QQQQ....Q", "..QQQQ....."], variants: [{ A: "#F5F5F5", Q: "#795548" }, { A: "#e1dbdb", Q: "#080707" }], rollable: true },
|
|
180
|
+
{ id: "morion", name: "Conquistador Morion", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....Q.....", "...QQQQQ...", "...QAAAQ...", ".A..AAA..A.", "..AAQQQAA.."], variants: [{ A: "#B0BEC5", Q: "#FFD600" }, { A: "#0a4768", Q: "#e6c10a" }], rollable: true },
|
|
181
|
+
{ id: "shako", name: "Shako", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "........Q..", "........Q..", "........Q..", "........Q..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#45464f", Q: "#00ff2a" }, { A: "#45464f", Q: "#ff4d00" }, { A: "#45464f", Q: "#ff0019" }, { A: "#45464f", Q: "#fff700" }], rollable: true },
|
|
182
|
+
{ id: "centurion_helm", name: "Centurion Helm", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "....QQQ....", "...QQQQQQ..", "..QQQQQQQQ.", ".QQ..A...Q.", ".Q..AAA....", "...AAAAA...", "..AAAAAAA..", "..AAAAAAA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#2f3131", Q: "#C62828" }], rollable: true },
|
|
183
|
+
{ id: "papal_mitre", name: "Papal Mitre", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "....QQQ....", "...QQQQQ...", "...QAAAQ...", "..AAAAAAA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }], rollable: true },
|
|
184
|
+
{ id: "headdress", name: "Headdress", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", "...........", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "...QQQQQ..."], variants: [{ A: "#FF8F00", Q: "#1565C0" }], rollable: true },
|
|
185
|
+
{ id: "sombrero", name: "Sombrero", rarity: "rare", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "...AQAQA...", "AAAAAAAAAAA"], variants: [{ A: "#F57F17", Q: "#BF360C" }, { A: "#f8b377", Q: "#3e1204" }, { A: "#d6f877", Q: "#04133e" }, { A: "#d6f877", Q: "#04133e" }], rollable: true },
|
|
184
186
|
// ── EPIC (6) ───────────────────────────────────────────────────────────
|
|
185
|
-
{ id: "papal_tiara", name: "Papal Tiara", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "....QQ.....", "...QQQQQ...", "...QAAQQ...", "...QQQQQ...", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }, { A: "#b18e10", Q: "#f3f3f1" }] },
|
|
186
|
-
{ id: "samurai_kabuto", name: "Samurai Kabuto", rarity: "epic", width: 11, anchor_x: 23, rows: ["....Q...Q..", "....QQ.QQ..", ".....QQQ...", ".......Q...", "...AAAAQ...", "..AAAAAQQ..", "..AAAAAQQ..", ".AAAAAAAQ..", "AAAAAAAAQ..", "AAAAAAAAQ.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#050505", Q: "#C62828" }, { A: "#675f5f", Q: "#c4c729" }] },
|
|
187
|
-
{ id: "gladiator_galea", name: "Gladiator Galea", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "....QQQQQQ.", "...QQQQQ...", "...QQQQQ...", "...QAAA....", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#ffe907", Q: "#C62828" }] },
|
|
188
|
-
{ id: "pharaoh_nemes", name: "Pharaoh Nemes", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "..AAAAAAA..", "..AAQAQAA..", "..QAQAQAQ..", "..QAQAQAQ..", "..QQAAAQQ..", "..QQAAAAA..", "...QAQA...."], variants: [{ A: "#FFD700", Q: "#1565C0" }, { A: "#8d342a", Q: "#63686e" }] },
|
|
189
|
-
{ id: "spartan_helmet", name: "Spartan Helmet", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "........Q..", ".......QQ..", ".....QQQQ..", "....QQQQQ..", "..QQQQQQ...", ".QQQAA.....", "QQAAAAAAA..", "Q.AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#B71C1C" }, { A: "#B0BEC5", Q: "#b2b51c" }, { A: "#B0BEC5", Q: "#b5611c" }] },
|
|
190
|
-
{ id: "conquistador_full", name: "Conquistador Helm", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA..", "...AQQQA...", "...AQQQA..."], variants: [{ A: "#B0BEC5", Q: "#FFD700" }, { A: "#B0BEC5", Q: "#0040ff" }, { A: "#B0BEC5", Q: "#36123b" }] },
|
|
187
|
+
{ id: "papal_tiara", name: "Papal Tiara", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "....QQ.....", "...QQQQQ...", "...QAAQQ...", "...QQQQQ...", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#FFFFFF", Q: "#FFD700" }, { A: "#b18e10", Q: "#f3f3f1" }], rollable: true },
|
|
188
|
+
{ id: "samurai_kabuto", name: "Samurai Kabuto", rarity: "epic", width: 11, anchor_x: 23, rows: ["....Q...Q..", "....QQ.QQ..", ".....QQQ...", ".......Q...", "...AAAAQ...", "..AAAAAQQ..", "..AAAAAQQ..", ".AAAAAAAQ..", "AAAAAAAAQ..", "AAAAAAAAQ.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#050505", Q: "#C62828" }, { A: "#675f5f", Q: "#c4c729" }], rollable: true },
|
|
189
|
+
{ id: "gladiator_galea", name: "Gladiator Galea", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "....QQQQQQ.", "...QQQQQ...", "...QQQQQ...", "...QAAA....", "..AAAAAAA..", "..AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#C62828" }, { A: "#ffe907", Q: "#C62828" }], rollable: true },
|
|
190
|
+
{ id: "pharaoh_nemes", name: "Pharaoh Nemes", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "..AAAAAAA..", "..AAQAQAA..", "..QAQAQAQ..", "..QAQAQAQ..", "..QQAAAQQ..", "..QQAAAAA..", "...QAQA...."], variants: [{ A: "#FFD700", Q: "#1565C0" }, { A: "#8d342a", Q: "#63686e" }], rollable: true },
|
|
191
|
+
{ id: "spartan_helmet", name: "Spartan Helmet", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "........Q..", ".......QQ..", ".....QQQQ..", "....QQQQQ..", "..QQQQQQ...", ".QQQAA.....", "QQAAAAAAA..", "Q.AAAAAAA..", "..AQQQQQA.."], variants: [{ A: "#B0BEC5", Q: "#B71C1C" }, { A: "#B0BEC5", Q: "#b2b51c" }, { A: "#B0BEC5", Q: "#b5611c" }], rollable: true },
|
|
192
|
+
{ id: "conquistador_full", name: "Conquistador Helm", rarity: "epic", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", ".....A.....", "....AAA....", "...AAAAA...", "..AAAAAAA..", "...AQQQA...", "...AQQQA..."], variants: [{ A: "#B0BEC5", Q: "#FFD700" }, { A: "#B0BEC5", Q: "#0040ff" }, { A: "#B0BEC5", Q: "#36123b" }], rollable: true },
|
|
191
193
|
// ── LEGENDARY (5) ──────────────────────────────────────────────────────
|
|
192
|
-
{ id: "rainbow_crown", name: "Rainbow Crown", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", ".....A.....", "....AAA....", "....AQA....", "....AAA....", "...AAQAA...", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA.."], colors: { A: "#FFD700", Q: "#553f3f" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF"], fps: 8 } },
|
|
193
|
-
{ id: "inferno_cap", name: "Inferno Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "....AAA....", "...AAAAA...", "....AAA....", ".....Q.....", "A....Q....A", "A...QQQ...A", ".A..QQQ..A.", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF4500", Q: "#FFD700" }, animation: { type: "cycle", frames: ["#FF0000", "#FF2200", "#FF4500", "#FF6600", "#FF8C00", "#FFA500"], fps: 12 } },
|
|
194
|
-
{ id: "void_hood", name: "Void Hood", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...AAA.....", "..AAAAAA...", ".AAAAAAA...", ".AAAAAAAA..", "AAAAAAAAA..", "AAAAQQQAA..", "AAAAQQQAA.."], colors: { A: "#1A0033", Q: "#d9cfe3" }, animation: { type: "cycle", frames: ["#0D0019", "#1A0033", "#2D004D", "#3D0066", "#2D004D", "#1A0033"], fps: 3 } },
|
|
195
|
-
{ id: "prismatic_jester", name: "Prismatic Jester", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "..Q.Q.Q.Q..", "Q..A.A.A..Q", ".Q.A.A.A.Q.", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF0000", Q: "#0000FF" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF", "#FF00FF", "#00FFFF"], fps: 15 } },
|
|
196
|
-
{ id: "aurora_helm", name: "Aurora Helm", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...QQQQQ...", "...QQQQQ...", "..AAAAAAA..", "..AAAAAAA..", "..AQAAAQA..", "..AQAAAQA.."], colors: { A: "#00CED1", Q: "#00FF7F" }, animation: { type: "cycle", frames: ["#0000FF", "#0066FF", "#00BFFF", "#00CED1", "#00FF7F", "#7CFC00", "#00FF7F", "#00CED1"], fps: 4 } }
|
|
194
|
+
{ id: "rainbow_crown", name: "Rainbow Crown", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", ".....A.....", "....AAA....", "....AQA....", "....AAA....", "...AAQAA...", "...AAAAA...", "..AAAQAAA..", "..AAAAAAA.."], colors: { A: "#FFD700", Q: "#553f3f" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF"], fps: 8 }, rollable: true },
|
|
195
|
+
{ id: "inferno_cap", name: "Inferno Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "....AAA....", "...AAAAA...", "....AAA....", ".....Q.....", "A....Q....A", "A...QQQ...A", ".A..QQQ..A.", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF4500", Q: "#FFD700" }, animation: { type: "cycle", frames: ["#FF0000", "#FF2200", "#FF4500", "#FF6600", "#FF8C00", "#FFA500"], fps: 12 }, rollable: true },
|
|
196
|
+
{ id: "void_hood", name: "Void Hood", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...AAA.....", "..AAAAAA...", ".AAAAAAA...", ".AAAAAAAA..", "AAAAAAAAA..", "AAAAQQQAA..", "AAAAQQQAA.."], colors: { A: "#1A0033", Q: "#d9cfe3" }, animation: { type: "cycle", frames: ["#0D0019", "#1A0033", "#2D004D", "#3D0066", "#2D004D", "#1A0033"], fps: 3 }, rollable: true },
|
|
197
|
+
{ id: "prismatic_jester", name: "Prismatic Jester", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "..Q.Q.Q.Q..", "Q..A.A.A..Q", ".Q.A.A.A.Q.", "..AQAQAQA..", "..AQAQAQA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA.."], colors: { A: "#FF0000", Q: "#0000FF" }, animation: { type: "cycle", frames: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#8B00FF", "#FF00FF", "#00FFFF"], fps: 15 }, rollable: true },
|
|
198
|
+
{ id: "aurora_helm", name: "Aurora Helm", rarity: "legendary", width: 11, anchor_x: 23, rows: ["...........", "...........", "...........", "...........", "...QQQQQ...", "...QQQQQ...", "..AAAAAAA..", "..AAAAAAA..", "..AQAAAQA..", "..AQAAAQA.."], colors: { A: "#00CED1", Q: "#00FF7F" }, animation: { type: "cycle", frames: ["#0000FF", "#0066FF", "#00BFFF", "#00CED1", "#00FF7F", "#7CFC00", "#00FF7F", "#00CED1"], fps: 4 }, rollable: true },
|
|
199
|
+
// Claim-only: awarded by an admin, never from a roll. Only the A channel cycles,
|
|
200
|
+
// so the inset motif shifts colour while the cap body stays a static rust brown.
|
|
201
|
+
// The body colour is kept out of the frame list so the motif never merges into it.
|
|
202
|
+
{ id: "contributor_cap", name: "Contributor Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: [".....Q.....", "....QQQ....", "...QQQQQ...", "..QAAAQQQ..", "..QQAQQQQ..", ".QQQAQAAQQ.", ".QQQAQAQAQ.", ".QQQQQAQAQ.", ".QQQQQAAQQ.", ".QQQQQQQQQ."], colors: { A: "#21138b", Q: "#6f2801" }, animation: { type: "cycle", frames: ["#FF8C42", "#E0457B", "#8B5CF6", "#3B82F6", "#22D3EE", "#34D399", "#A3E635", "#FBBF24"], fps: 6 }, rollable: false }
|
|
197
203
|
];
|
|
198
204
|
|
|
199
205
|
// ../shared/dist/series-transform.js
|
|
@@ -285,6 +291,22 @@ var STAMINA_PARAM_BOUNDS = {
|
|
|
285
291
|
tired_multiplier: { min: 0.2, max: 0.9, step: 0.05, default: STAMINA.TIRED_MULTIPLIER }
|
|
286
292
|
};
|
|
287
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
|
+
|
|
288
310
|
// src/ui/HorseSprite.tsx
|
|
289
311
|
import { Box, Text } from "ink";
|
|
290
312
|
|
|
@@ -821,8 +843,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
|
821
843
|
// src/version.ts
|
|
822
844
|
import { createRequire } from "module";
|
|
823
845
|
function readVersion() {
|
|
824
|
-
if ("
|
|
825
|
-
return "
|
|
846
|
+
if ("3.0.0".length > 0) {
|
|
847
|
+
return "3.0.0";
|
|
826
848
|
}
|
|
827
849
|
try {
|
|
828
850
|
const req = createRequire(import.meta.url);
|
|
@@ -867,18 +889,37 @@ function geminiTmpDir() {
|
|
|
867
889
|
}
|
|
868
890
|
|
|
869
891
|
// src/identity/identity.ts
|
|
870
|
-
async function
|
|
892
|
+
async function readIdentityFile() {
|
|
893
|
+
let raw;
|
|
871
894
|
try {
|
|
872
|
-
|
|
873
|
-
const parsed = JSON.parse(raw);
|
|
874
|
-
if (typeof parsed.user_id === "string" && typeof parsed.display_name === "string" && typeof parsed.secret_token === "string" && typeof parsed.created_at === "string") {
|
|
875
|
-
return parsed;
|
|
876
|
-
}
|
|
877
|
-
return null;
|
|
895
|
+
raw = await fs.readFile(identityFile(), "utf8");
|
|
878
896
|
} catch (e) {
|
|
879
|
-
if (e?.code === "ENOENT") return
|
|
880
|
-
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" };
|
|
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" };
|
|
881
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;
|
|
882
923
|
}
|
|
883
924
|
async function saveIdentity(identity) {
|
|
884
925
|
await fs.mkdir(homeDir(), { recursive: true });
|
|
@@ -919,12 +960,12 @@ function getIdentity() {
|
|
|
919
960
|
function _resetIdentityCacheForTests() {
|
|
920
961
|
identityCache = null;
|
|
921
962
|
}
|
|
922
|
-
async function request(method, path9, body, horseAuthToken, fetchImpl = fetch) {
|
|
963
|
+
async function request(method, path9, body, horseAuthToken, fetchImpl = fetch, identityOverride) {
|
|
923
964
|
const url = path9.startsWith("http") ? path9 : `${apiBase()}${path9}`;
|
|
924
965
|
const headers = {};
|
|
925
966
|
headers[CLI_VERSION_HEADER] = CLI_VERSION;
|
|
926
967
|
headers["user-agent"] = `token-derby/${CLI_VERSION}`;
|
|
927
|
-
const identity = await getIdentity();
|
|
968
|
+
const identity = identityOverride ?? await getIdentity();
|
|
928
969
|
if (identity) {
|
|
929
970
|
headers[USER_ID_HEADER] = identity.user_id;
|
|
930
971
|
headers[USER_TOKEN_HEADER] = identity.secret_token;
|
|
@@ -990,6 +1031,9 @@ function listOrganisations() {
|
|
|
990
1031
|
function initJockey(body) {
|
|
991
1032
|
return request("POST", "/jockey/init", body, void 0);
|
|
992
1033
|
}
|
|
1034
|
+
function getJockey() {
|
|
1035
|
+
return request("GET", "/jockey/me", void 0, void 0);
|
|
1036
|
+
}
|
|
993
1037
|
function updateJockey(body) {
|
|
994
1038
|
return request("PUT", "/jockey/me", body, void 0);
|
|
995
1039
|
}
|
|
@@ -1024,6 +1068,34 @@ function equipHat(stableHorseId, body) {
|
|
|
1024
1068
|
function createWebSession() {
|
|
1025
1069
|
return request("POST", "/web-sessions", void 0, void 0);
|
|
1026
1070
|
}
|
|
1071
|
+
function probeClaim(code) {
|
|
1072
|
+
return request("GET", `/claims/${encodeURIComponent(code)}`, void 0, void 0);
|
|
1073
|
+
}
|
|
1074
|
+
function redeemClaim(code, body) {
|
|
1075
|
+
return request("POST", `/claims/${encodeURIComponent(code)}/redeem`, body, void 0);
|
|
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
|
+
}
|
|
1027
1099
|
|
|
1028
1100
|
// src/commands/stable-create.ts
|
|
1029
1101
|
async function stableCreateCommand() {
|
|
@@ -1163,7 +1235,7 @@ import { render as render3 } from "ink";
|
|
|
1163
1235
|
import { useState as useState3 } from "react";
|
|
1164
1236
|
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
1165
1237
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1166
|
-
function HorsePicker({ horses, onPick, onCancel }) {
|
|
1238
|
+
function HorsePicker({ horses, onPick, onCancel, prompt = "Pick a horse to race:" }) {
|
|
1167
1239
|
const [idx, setIdx] = useState3(0);
|
|
1168
1240
|
useInput2((input, key) => {
|
|
1169
1241
|
if (key.escape) {
|
|
@@ -1191,7 +1263,7 @@ function HorsePicker({ horses, onPick, onCancel }) {
|
|
|
1191
1263
|
] });
|
|
1192
1264
|
}
|
|
1193
1265
|
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
|
|
1194
|
-
/* @__PURE__ */ jsx5(Text5, { children:
|
|
1266
|
+
/* @__PURE__ */ jsx5(Text5, { children: prompt }),
|
|
1195
1267
|
horses.map((h, i) => /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
|
|
1196
1268
|
/* @__PURE__ */ jsx5(Box5, { flexDirection: "row", children: /* @__PURE__ */ jsxs3(Text5, { children: [
|
|
1197
1269
|
i === idx ? "\u25BA" : " ",
|
|
@@ -2451,7 +2523,7 @@ async function joinCommand(joinCode, argv = []) {
|
|
|
2451
2523
|
}
|
|
2452
2524
|
const identity = await loadIdentity();
|
|
2453
2525
|
if (!identity) {
|
|
2454
|
-
console.error("Run `token-derby
|
|
2526
|
+
console.error("Run `token-derby login` to set up your identity.");
|
|
2455
2527
|
return 1;
|
|
2456
2528
|
}
|
|
2457
2529
|
let race;
|
|
@@ -2624,210 +2696,814 @@ async function endCommand(adminCode) {
|
|
|
2624
2696
|
// src/commands/init.ts
|
|
2625
2697
|
import * as readline4 from "readline/promises";
|
|
2626
2698
|
import { stdin as stdin4, stdout as stdout4 } from "process";
|
|
2627
|
-
async function
|
|
2628
|
-
if (reset) {
|
|
2629
|
-
await deleteIdentity();
|
|
2630
|
-
_resetIdentityCacheForTests();
|
|
2631
|
-
console.log("Removed local identity. Creating a new one\u2026");
|
|
2632
|
-
}
|
|
2633
|
-
const existing = await loadIdentity();
|
|
2699
|
+
async function defaultPromptText(question) {
|
|
2634
2700
|
const rl = readline4.createInterface({ input: stdin4, output: stdout4 });
|
|
2635
2701
|
try {
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
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
|
+
}
|
|
2642
2748
|
}
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
console.error(
|
|
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
|
+
);
|
|
2646
2755
|
return 1;
|
|
2647
2756
|
}
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
await saveIdentity(updated);
|
|
2652
|
-
console.log(`Updated jockey name to: ${updated.display_name}`);
|
|
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.");
|
|
2653
2760
|
return 0;
|
|
2654
|
-
} catch (e) {
|
|
2655
|
-
if (e instanceof ApiError) {
|
|
2656
|
-
if (e.code === "UNAUTHENTICATED") {
|
|
2657
|
-
console.error(
|
|
2658
|
-
"Server does not recognise this identity. Your account may have been wiped. Run `token-derby init --reset` to start fresh."
|
|
2659
|
-
);
|
|
2660
|
-
} else {
|
|
2661
|
-
console.error(`Error: ${e.code} ${e.message}`);
|
|
2662
|
-
}
|
|
2663
|
-
return 1;
|
|
2664
|
-
}
|
|
2665
|
-
throw e;
|
|
2666
2761
|
}
|
|
2667
2762
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
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);
|
|
2672
2790
|
return 1;
|
|
2673
2791
|
}
|
|
2674
2792
|
try {
|
|
2675
|
-
const resp = await
|
|
2676
|
-
const
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
secret_token: resp.secret_token,
|
|
2680
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2681
|
-
};
|
|
2682
|
-
await saveIdentity(identity);
|
|
2683
|
-
_resetIdentityCacheForTests();
|
|
2684
|
-
console.log("");
|
|
2685
|
-
console.log(`Welcome, ${identity.display_name}!`);
|
|
2686
|
-
console.log("Your identity has been created on the server.");
|
|
2687
|
-
console.log("You can now create a stable and join races.");
|
|
2688
|
-
console.log("");
|
|
2689
|
-
console.log(" \u26A0 Your secret token is stored locally in identity.json.");
|
|
2690
|
-
console.log(" If you lose it, you cannot recover this account \u2014 you would");
|
|
2691
|
-
console.log(" need to run `token-derby init --reset` and rebuild your stable.");
|
|
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}`);
|
|
2692
2797
|
return 0;
|
|
2693
2798
|
} catch (e) {
|
|
2694
2799
|
if (e instanceof ApiError) {
|
|
2695
|
-
|
|
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
|
+
}
|
|
2696
2807
|
return 1;
|
|
2697
2808
|
}
|
|
2698
2809
|
throw e;
|
|
2699
2810
|
}
|
|
2700
|
-
}
|
|
2701
|
-
|
|
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;
|
|
2702
2843
|
}
|
|
2703
2844
|
}
|
|
2704
2845
|
|
|
2705
|
-
// src/commands/
|
|
2706
|
-
import
|
|
2707
|
-
import
|
|
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
|
|
2708
2851
|
import { spawn } from "child_process";
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
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 = {}) {
|
|
2714
2862
|
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
2715
|
-
|
|
2716
|
-
let latest;
|
|
2863
|
+
let code;
|
|
2717
2864
|
try {
|
|
2718
|
-
|
|
2865
|
+
({ code } = await createWebSession());
|
|
2719
2866
|
} catch (e) {
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2867
|
+
if (e instanceof ApiError) {
|
|
2868
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
2869
|
+
return 1;
|
|
2870
|
+
}
|
|
2871
|
+
throw e;
|
|
2723
2872
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
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
|
+
}
|
|
2727
2888
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
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);
|
|
2733
2904
|
}
|
|
2734
|
-
|
|
2735
|
-
if (code === 0) {
|
|
2736
|
-
console.log(`
|
|
2737
|
-
Upgraded to ${latest}.`);
|
|
2738
|
-
console.log(`If \`token-derby --version\` still shows ${CLI_VERSION}, your shell has`);
|
|
2739
|
-
console.log(`the old path cached. Drop the cache by running ONE of:`);
|
|
2740
|
-
console.log(` exec $SHELL (any shell \u2014 replaces this shell with a fresh one)`);
|
|
2741
|
-
console.log(` rehash (zsh)`);
|
|
2742
|
-
console.log(` hash -r (bash)`);
|
|
2743
|
-
console.log(`Or just open a new terminal tab.`);
|
|
2744
|
-
console.log(`If --version still doesn't match after that, you may have multiple`);
|
|
2745
|
-
console.log(`installs \u2014 run \`which -a token-derby\` to compare against \`npm bin -g\`.`);
|
|
2905
|
+
while (process.stdin.read() !== null) {
|
|
2746
2906
|
}
|
|
2747
|
-
|
|
2907
|
+
process.stdin.pause();
|
|
2908
|
+
process.stdin.ref();
|
|
2748
2909
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
const
|
|
2754
|
-
if (
|
|
2755
|
-
|
|
2756
|
-
if (typeof body.version !== "string" || !/^\d+\.\d+\.\d+/.test(body.version)) {
|
|
2757
|
-
throw new Error("unexpected registry response");
|
|
2758
|
-
}
|
|
2759
|
-
return body.version;
|
|
2760
|
-
} finally {
|
|
2761
|
-
clearTimeout(t);
|
|
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);
|
|
2762
2917
|
}
|
|
2918
|
+
return null;
|
|
2763
2919
|
}
|
|
2764
|
-
async function
|
|
2765
|
-
const
|
|
2920
|
+
async function defaultPromptText2(question) {
|
|
2921
|
+
const readline6 = await import("readline/promises");
|
|
2922
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
2766
2923
|
try {
|
|
2767
|
-
|
|
2768
|
-
return answer === "y" || answer === "yes";
|
|
2924
|
+
return await rl.question(question);
|
|
2769
2925
|
} finally {
|
|
2770
2926
|
rl.close();
|
|
2771
2927
|
}
|
|
2772
2928
|
}
|
|
2773
|
-
function
|
|
2774
|
-
return new Promise((resolve) =>
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
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.");
|
|
2960
|
+
return 0;
|
|
2784
2961
|
}
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
});
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
|
-
// src/commands/roll.ts
|
|
2792
|
-
import React13 from "react";
|
|
2793
|
-
import { render as render5 } from "ink";
|
|
2794
|
-
|
|
2795
|
-
// src/ui/RollReveal.tsx
|
|
2796
|
-
import { useState as useState7, useEffect as useEffect4, useMemo } from "react";
|
|
2797
|
-
import { Box as Box10, Text as Text10 } from "ink";
|
|
2798
|
-
|
|
2799
|
-
// src/ui/HatSprite.tsx
|
|
2800
|
-
import { useEffect as useEffect3, useState as useState6 } from "react";
|
|
2801
|
-
import { Box as Box9, Text as Text9 } from "ink";
|
|
2802
|
-
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2803
|
-
function HatSprite({ hat, variant, centerIn }) {
|
|
2804
|
-
const colors = hatColorsFor2(hat, variant ?? 0);
|
|
2805
|
-
const grid = makeHatGrid(hat, colors, centerIn);
|
|
2806
|
-
const lines = hexGridToHalfBlocks(grid);
|
|
2807
|
-
return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx9(Text9, { children: line }, i)) });
|
|
2808
|
-
}
|
|
2809
|
-
function AnimatedHatSprite({ hat, variant, centerIn }) {
|
|
2810
|
-
if (hat.rarity !== "legendary") {
|
|
2811
|
-
return /* @__PURE__ */ jsx9(HatSprite, { hat, variant, centerIn });
|
|
2962
|
+
} else {
|
|
2963
|
+
console.log(" No TTY to confirm \u2014 continuing (equivalent to answering Y).");
|
|
2964
|
+
}
|
|
2812
2965
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
}
|
|
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.");
|
|
2979
|
+
return 1;
|
|
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 {
|
|
3007
|
+
throw e;
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
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 {
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
const deadline = Date.now() + start.expires_in * 1e3;
|
|
3034
|
+
let approved = null;
|
|
3035
|
+
while (!approved) {
|
|
3036
|
+
let poll;
|
|
3037
|
+
try {
|
|
3038
|
+
poll = await apiPoll({ device_code: start.device_code });
|
|
3039
|
+
} catch (e) {
|
|
3040
|
+
if (e instanceof ApiError) {
|
|
3041
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3042
|
+
return 1;
|
|
3043
|
+
}
|
|
3044
|
+
throw e;
|
|
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);
|
|
3139
|
+
} finally {
|
|
3140
|
+
rl.close();
|
|
3141
|
+
}
|
|
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
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// src/commands/update.ts
|
|
3323
|
+
import * as readline5 from "readline/promises";
|
|
3324
|
+
import { stdin as stdin5, stdout as stdout5 } from "process";
|
|
3325
|
+
import { spawn as spawn4 } from "child_process";
|
|
3326
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@mauricode/token-derby/latest";
|
|
3327
|
+
var UPGRADE_CMD = "npm install -g @mauricode/token-derby@latest";
|
|
3328
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
3329
|
+
async function updateCommand(deps = {}) {
|
|
3330
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3331
|
+
const spawnImpl = deps.spawnImpl ?? spawn4;
|
|
3332
|
+
const promptYesNo2 = deps.promptYesNo ?? defaultPromptYesNo;
|
|
3333
|
+
let latest;
|
|
3334
|
+
try {
|
|
3335
|
+
latest = await fetchLatestVersion(fetchImpl);
|
|
3336
|
+
} catch (e) {
|
|
3337
|
+
console.error(`Could not reach the npm registry${e?.message ? ` (${e.message})` : ""}.`);
|
|
3338
|
+
console.error(`To upgrade manually: ${UPGRADE_CMD}`);
|
|
3339
|
+
return 1;
|
|
3340
|
+
}
|
|
3341
|
+
if (gteSemver(CLI_VERSION, latest)) {
|
|
3342
|
+
console.log(`You're on the latest version (${CLI_VERSION}).`);
|
|
3343
|
+
return 0;
|
|
3344
|
+
}
|
|
3345
|
+
console.log(`Current: ${CLI_VERSION} Latest: ${latest}`);
|
|
3346
|
+
const yes = await promptYesNo2("Run upgrade now? [y/N]: ");
|
|
3347
|
+
if (!yes) {
|
|
3348
|
+
console.log(`To upgrade manually: ${UPGRADE_CMD}`);
|
|
3349
|
+
return 0;
|
|
3350
|
+
}
|
|
3351
|
+
const code = await runNpmUpgrade(spawnImpl);
|
|
3352
|
+
if (code === 0) {
|
|
3353
|
+
console.log(`
|
|
3354
|
+
Upgraded to ${latest}.`);
|
|
3355
|
+
console.log(`If \`token-derby --version\` still shows ${CLI_VERSION}, your shell has`);
|
|
3356
|
+
console.log(`the old path cached. Drop the cache by running ONE of:`);
|
|
3357
|
+
console.log(` exec $SHELL (any shell \u2014 replaces this shell with a fresh one)`);
|
|
3358
|
+
console.log(` rehash (zsh)`);
|
|
3359
|
+
console.log(` hash -r (bash)`);
|
|
3360
|
+
console.log(`Or just open a new terminal tab.`);
|
|
3361
|
+
console.log(`If --version still doesn't match after that, you may have multiple`);
|
|
3362
|
+
console.log(`installs \u2014 run \`which -a token-derby\` to compare against \`npm bin -g\`.`);
|
|
3363
|
+
}
|
|
3364
|
+
return code;
|
|
3365
|
+
}
|
|
3366
|
+
async function fetchLatestVersion(fetchImpl) {
|
|
3367
|
+
const controller = new AbortController();
|
|
3368
|
+
const t = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
3369
|
+
try {
|
|
3370
|
+
const res = await fetchImpl(REGISTRY_URL, { signal: controller.signal });
|
|
3371
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
3372
|
+
const body = await res.json();
|
|
3373
|
+
if (typeof body.version !== "string" || !/^\d+\.\d+\.\d+/.test(body.version)) {
|
|
3374
|
+
throw new Error("unexpected registry response");
|
|
3375
|
+
}
|
|
3376
|
+
return body.version;
|
|
3377
|
+
} finally {
|
|
3378
|
+
clearTimeout(t);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
async function defaultPromptYesNo(question) {
|
|
3382
|
+
const rl = readline5.createInterface({ input: stdin5, output: stdout5 });
|
|
3383
|
+
try {
|
|
3384
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
3385
|
+
return answer === "y" || answer === "yes";
|
|
3386
|
+
} finally {
|
|
3387
|
+
rl.close();
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
function runNpmUpgrade(spawnImpl) {
|
|
3391
|
+
return new Promise((resolve) => {
|
|
3392
|
+
const child = spawnImpl("npm", ["install", "-g", "@mauricode/token-derby@latest"], {
|
|
3393
|
+
stdio: "inherit"
|
|
3394
|
+
});
|
|
3395
|
+
child.on("error", (e) => {
|
|
3396
|
+
if (e.code === "ENOENT") {
|
|
3397
|
+
console.error("Could not find `npm` on PATH.");
|
|
3398
|
+
console.error(`To upgrade manually: ${UPGRADE_CMD}`);
|
|
3399
|
+
} else {
|
|
3400
|
+
console.error(`npm failed to start: ${e.message}`);
|
|
3401
|
+
}
|
|
3402
|
+
resolve(1);
|
|
3403
|
+
});
|
|
3404
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
// src/commands/roll.ts
|
|
3409
|
+
import React14 from "react";
|
|
3410
|
+
import { render as render6 } from "ink";
|
|
3411
|
+
|
|
3412
|
+
// src/ui/RollHorsePicker.tsx
|
|
3413
|
+
import { useState as useState6 } from "react";
|
|
3414
|
+
import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
|
|
3415
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3416
|
+
function RollHorsePicker({ horses, onPick, onCancel }) {
|
|
3417
|
+
const [idx, setIdx] = useState6(0);
|
|
3418
|
+
useInput4((input, key) => {
|
|
3419
|
+
if (key.escape) {
|
|
3420
|
+
onCancel();
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
if (horses.length === 0) return;
|
|
3424
|
+
if (key.upArrow) {
|
|
3425
|
+
setIdx((idx - 1 + horses.length) % horses.length);
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
if (key.downArrow) {
|
|
3429
|
+
setIdx((idx + 1) % horses.length);
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
if (key.return) {
|
|
3433
|
+
onPick(horses[idx]);
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
});
|
|
3437
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", children: [
|
|
3438
|
+
/* @__PURE__ */ jsx9(Text9, { children: "Pick a horse to roll for:" }),
|
|
3439
|
+
horses.map((h, i) => /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", children: [
|
|
3440
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "row", children: /* @__PURE__ */ jsxs7(Text9, { children: [
|
|
3441
|
+
i === idx ? "\u25BA" : " ",
|
|
3442
|
+
" ",
|
|
3443
|
+
h.name,
|
|
3444
|
+
" ",
|
|
3445
|
+
/* @__PURE__ */ jsxs7(Text9, { color: "cyan", children: [
|
|
3446
|
+
"[Lvl. ",
|
|
3447
|
+
levelFromXp(h.xp),
|
|
3448
|
+
"]"
|
|
3449
|
+
] }),
|
|
3450
|
+
" ",
|
|
3451
|
+
/* @__PURE__ */ jsxs7(Text9, { color: "yellow", children: [
|
|
3452
|
+
"\u2014 ",
|
|
3453
|
+
h.pending,
|
|
3454
|
+
" roll",
|
|
3455
|
+
h.pending === 1 ? "" : "s"
|
|
3456
|
+
] })
|
|
3457
|
+
] }) }),
|
|
3458
|
+
/* @__PURE__ */ jsxs7(Box9, { flexDirection: "row", children: [
|
|
3459
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
3460
|
+
/* @__PURE__ */ jsx9(HorseSprite, { sprite: MINI_SPRITE, colors: h.colors })
|
|
3461
|
+
] })
|
|
3462
|
+
] }, h.stable_horse_id)),
|
|
3463
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2191/\u2193 choose \xB7 Enter pick \xB7 Esc cancel" }) })
|
|
3464
|
+
] });
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
// src/ui/reveal.ts
|
|
3468
|
+
import React13 from "react";
|
|
3469
|
+
import { render as render5 } from "ink";
|
|
3470
|
+
|
|
3471
|
+
// src/ui/RollReveal.tsx
|
|
3472
|
+
import { useState as useState8, useEffect as useEffect4, useMemo } from "react";
|
|
3473
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
3474
|
+
|
|
3475
|
+
// src/ui/HatSprite.tsx
|
|
3476
|
+
import { useEffect as useEffect3, useState as useState7 } from "react";
|
|
3477
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
3478
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
3479
|
+
function HatSprite({ hat, variant, centerIn }) {
|
|
3480
|
+
const colors = hatColorsFor2(hat, variant ?? 0);
|
|
3481
|
+
const grid = makeHatGrid(hat, colors, centerIn);
|
|
3482
|
+
const lines = hexGridToHalfBlocks(grid);
|
|
3483
|
+
return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx10(Text10, { children: line }, i)) });
|
|
3484
|
+
}
|
|
3485
|
+
function AnimatedHatSprite({ hat, variant, centerIn }) {
|
|
3486
|
+
if (hat.rarity !== "legendary") {
|
|
3487
|
+
return /* @__PURE__ */ jsx10(HatSprite, { hat, variant, centerIn });
|
|
3488
|
+
}
|
|
3489
|
+
const frames = hat.animation.frames;
|
|
3490
|
+
const fps = hat.animation.fps;
|
|
3491
|
+
const [idx, setIdx] = useState7(0);
|
|
3492
|
+
useEffect3(() => {
|
|
3493
|
+
if (frames.length <= 1) return;
|
|
3494
|
+
const interval = setInterval(
|
|
3495
|
+
() => setIdx((i) => (i + 1) % frames.length),
|
|
3496
|
+
Math.max(1, Math.round(1e3 / fps))
|
|
3497
|
+
);
|
|
3498
|
+
return () => clearInterval(interval);
|
|
3499
|
+
}, [frames.length, fps]);
|
|
3500
|
+
const framed = { ...hat, colors: { ...hat.colors, A: frames[idx] } };
|
|
3501
|
+
return /* @__PURE__ */ jsx10(HatSprite, { hat: framed, variant, centerIn });
|
|
3502
|
+
}
|
|
3503
|
+
function hatColorsFor2(hat, variantIdx) {
|
|
3504
|
+
if (hat.rarity === "legendary") return hat.colors;
|
|
3505
|
+
return hat.variants[variantIdx] ?? hat.variants[0];
|
|
3506
|
+
}
|
|
2831
3507
|
function makeHatGrid(hat, colors, centerIn) {
|
|
2832
3508
|
const w = centerIn?.w ?? hat.width;
|
|
2833
3509
|
const h = centerIn?.h ?? hat.rows.length;
|
|
@@ -2849,7 +3525,7 @@ function makeHatGrid(hat, colors, centerIn) {
|
|
|
2849
3525
|
}
|
|
2850
3526
|
|
|
2851
3527
|
// src/ui/RollReveal.tsx
|
|
2852
|
-
import { jsx as
|
|
3528
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2853
3529
|
var RESET3 = "\x1B[0m";
|
|
2854
3530
|
var BOX_COLOR = "#E5C76B";
|
|
2855
3531
|
var TIER_PALETTE = {
|
|
@@ -2918,7 +3594,7 @@ var BOX_EMPTY = [
|
|
|
2918
3594
|
""
|
|
2919
3595
|
].map(pad);
|
|
2920
3596
|
function GiftBox({ frame, color }) {
|
|
2921
|
-
return /* @__PURE__ */
|
|
3597
|
+
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: frame.map((line, i) => /* @__PURE__ */ jsx11(Text11, { children: line ? ansiFg(color) + line + RESET3 : line }, i)) });
|
|
2922
3598
|
}
|
|
2923
3599
|
function spawnParticles(tier, count, cx, cy) {
|
|
2924
3600
|
const palette = TIER_PALETTE[tier];
|
|
@@ -2941,7 +3617,7 @@ function ConfettiBurst({ tier }) {
|
|
|
2941
3617
|
const cx = Math.floor(SCENE_W / 2);
|
|
2942
3618
|
const cy = Math.floor(SCENE_H / 2);
|
|
2943
3619
|
const particles = useMemo(() => spawnParticles(tier, 36, cx, cy), [tier, cx, cy]);
|
|
2944
|
-
const [tick, setTick] =
|
|
3620
|
+
const [tick, setTick] = useState8(0);
|
|
2945
3621
|
useEffect4(() => {
|
|
2946
3622
|
const i = setInterval(() => setTick((t) => t + 1), 70);
|
|
2947
3623
|
return () => clearInterval(i);
|
|
@@ -2954,13 +3630,13 @@ function ConfettiBurst({ tier }) {
|
|
|
2954
3630
|
grid[y][x] = ansiFg(p.color) + p.char + RESET3;
|
|
2955
3631
|
}
|
|
2956
3632
|
}
|
|
2957
|
-
return /* @__PURE__ */
|
|
3633
|
+
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx11(Text11, { children: row.join("") }, y)) });
|
|
2958
3634
|
}
|
|
2959
3635
|
var CLOSED_HOLD_MS = 3e3;
|
|
2960
3636
|
function RollReveal({ outcome, onDone }) {
|
|
2961
3637
|
const isNoHat = outcome.kind === "no_hat";
|
|
2962
3638
|
const isLegendary = outcome.kind !== "no_hat" && outcome.hat.rarity === "legendary";
|
|
2963
|
-
const [phase, setPhase] =
|
|
3639
|
+
const [phase, setPhase] = useState8("closed");
|
|
2964
3640
|
useEffect4(() => {
|
|
2965
3641
|
const timers = [];
|
|
2966
3642
|
timers.push(setTimeout(() => setPhase("open1"), CLOSED_HOLD_MS));
|
|
@@ -2975,95 +3651,18 @@ function RollReveal({ outcome, onDone }) {
|
|
|
2975
3651
|
}
|
|
2976
3652
|
return () => timers.forEach(clearTimeout);
|
|
2977
3653
|
}, [isNoHat, isLegendary, onDone]);
|
|
2978
|
-
if (phase === "closed") return /* @__PURE__ */
|
|
2979
|
-
if (phase === "open1") return /* @__PURE__ */
|
|
2980
|
-
if (phase === "open2") return /* @__PURE__ */
|
|
2981
|
-
if (phase === "empty") return /* @__PURE__ */
|
|
3654
|
+
if (phase === "closed") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_CLOSED, color: BOX_COLOR });
|
|
3655
|
+
if (phase === "open1") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_1, color: BOX_COLOR });
|
|
3656
|
+
if (phase === "open2") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_2, color: BOX_COLOR });
|
|
3657
|
+
if (phase === "empty") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
|
|
2982
3658
|
if (phase === "burst" && outcome.kind !== "no_hat") {
|
|
2983
|
-
return /* @__PURE__ */
|
|
3659
|
+
return /* @__PURE__ */ jsx11(ConfettiBurst, { tier: outcome.hat.rarity });
|
|
2984
3660
|
}
|
|
2985
|
-
if (outcome.kind === "no_hat") return /* @__PURE__ */
|
|
2986
|
-
return outcome.hat.rarity === "legendary" ? /* @__PURE__ */
|
|
3661
|
+
if (outcome.kind === "no_hat") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_EMPTY, color: BOX_COLOR });
|
|
3662
|
+
return outcome.hat.rarity === "legendary" ? /* @__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 } });
|
|
2987
3663
|
}
|
|
2988
3664
|
|
|
2989
|
-
// src/ui/
|
|
2990
|
-
import { useState as useState8 } from "react";
|
|
2991
|
-
import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
|
|
2992
|
-
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2993
|
-
function RollHorsePicker({ horses, onPick, onCancel }) {
|
|
2994
|
-
const [idx, setIdx] = useState8(0);
|
|
2995
|
-
useInput4((input, key) => {
|
|
2996
|
-
if (key.escape) {
|
|
2997
|
-
onCancel();
|
|
2998
|
-
return;
|
|
2999
|
-
}
|
|
3000
|
-
if (horses.length === 0) return;
|
|
3001
|
-
if (key.upArrow) {
|
|
3002
|
-
setIdx((idx - 1 + horses.length) % horses.length);
|
|
3003
|
-
return;
|
|
3004
|
-
}
|
|
3005
|
-
if (key.downArrow) {
|
|
3006
|
-
setIdx((idx + 1) % horses.length);
|
|
3007
|
-
return;
|
|
3008
|
-
}
|
|
3009
|
-
if (key.return) {
|
|
3010
|
-
onPick(horses[idx]);
|
|
3011
|
-
return;
|
|
3012
|
-
}
|
|
3013
|
-
});
|
|
3014
|
-
return /* @__PURE__ */ jsxs7(Box11, { flexDirection: "column", children: [
|
|
3015
|
-
/* @__PURE__ */ jsx11(Text11, { children: "Pick a horse to roll for:" }),
|
|
3016
|
-
horses.map((h, i) => /* @__PURE__ */ jsxs7(Box11, { flexDirection: "column", children: [
|
|
3017
|
-
/* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: /* @__PURE__ */ jsxs7(Text11, { children: [
|
|
3018
|
-
i === idx ? "\u25BA" : " ",
|
|
3019
|
-
" ",
|
|
3020
|
-
h.name,
|
|
3021
|
-
" ",
|
|
3022
|
-
/* @__PURE__ */ jsxs7(Text11, { color: "cyan", children: [
|
|
3023
|
-
"[Lvl. ",
|
|
3024
|
-
levelFromXp(h.xp),
|
|
3025
|
-
"]"
|
|
3026
|
-
] }),
|
|
3027
|
-
" ",
|
|
3028
|
-
/* @__PURE__ */ jsxs7(Text11, { color: "yellow", children: [
|
|
3029
|
-
"\u2014 ",
|
|
3030
|
-
h.pending,
|
|
3031
|
-
" roll",
|
|
3032
|
-
h.pending === 1 ? "" : "s"
|
|
3033
|
-
] })
|
|
3034
|
-
] }) }),
|
|
3035
|
-
/* @__PURE__ */ jsxs7(Box11, { flexDirection: "row", children: [
|
|
3036
|
-
/* @__PURE__ */ jsx11(Text11, { children: " " }),
|
|
3037
|
-
/* @__PURE__ */ jsx11(HorseSprite, { sprite: MINI_SPRITE, colors: h.colors })
|
|
3038
|
-
] })
|
|
3039
|
-
] }, h.stable_horse_id)),
|
|
3040
|
-
/* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\u2191/\u2193 choose \xB7 Enter pick \xB7 Esc cancel" }) })
|
|
3041
|
-
] });
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
// src/commands/roll.ts
|
|
3045
|
-
function pendingFor(horse) {
|
|
3046
|
-
const level = levelFromXp(horse.xp);
|
|
3047
|
-
const lastRolled = horse.last_rolled_level ?? Math.max(1, level - 1);
|
|
3048
|
-
return level - lastRolled;
|
|
3049
|
-
}
|
|
3050
|
-
async function promptYesNo(question) {
|
|
3051
|
-
resetStdinAfterInk();
|
|
3052
|
-
const readline6 = await import("readline/promises");
|
|
3053
|
-
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
3054
|
-
const a = (await rl.question(question)).trim().toLowerCase();
|
|
3055
|
-
rl.close();
|
|
3056
|
-
return a === "" || a === "y" || a === "yes";
|
|
3057
|
-
}
|
|
3058
|
-
function resetStdinAfterInk() {
|
|
3059
|
-
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
3060
|
-
process.stdin.setRawMode(false);
|
|
3061
|
-
}
|
|
3062
|
-
while (process.stdin.read() !== null) {
|
|
3063
|
-
}
|
|
3064
|
-
process.stdin.pause();
|
|
3065
|
-
process.stdin.ref();
|
|
3066
|
-
}
|
|
3665
|
+
// src/ui/reveal.ts
|
|
3067
3666
|
async function runReveal(outcome) {
|
|
3068
3667
|
await new Promise((resolve) => {
|
|
3069
3668
|
const app = render5(React13.createElement(RollReveal, {
|
|
@@ -3075,6 +3674,13 @@ async function runReveal(outcome) {
|
|
|
3075
3674
|
}));
|
|
3076
3675
|
});
|
|
3077
3676
|
}
|
|
3677
|
+
|
|
3678
|
+
// src/commands/roll.ts
|
|
3679
|
+
function pendingFor(horse) {
|
|
3680
|
+
const level = levelFromXp(horse.xp);
|
|
3681
|
+
const lastRolled = horse.last_rolled_level ?? Math.max(1, level - 1);
|
|
3682
|
+
return level - lastRolled;
|
|
3683
|
+
}
|
|
3078
3684
|
async function rollCommand() {
|
|
3079
3685
|
let stable;
|
|
3080
3686
|
try {
|
|
@@ -3092,7 +3698,7 @@ async function rollCommand() {
|
|
|
3092
3698
|
return 0;
|
|
3093
3699
|
}
|
|
3094
3700
|
const picked = await new Promise((resolve) => {
|
|
3095
|
-
const app =
|
|
3701
|
+
const app = render6(React14.createElement(RollHorsePicker, {
|
|
3096
3702
|
horses: eligible,
|
|
3097
3703
|
onPick: (h) => {
|
|
3098
3704
|
app.unmount();
|
|
@@ -3174,16 +3780,16 @@ No hat this time. +${result.xp_awarded} XP toward your next level.
|
|
|
3174
3780
|
}
|
|
3175
3781
|
}
|
|
3176
3782
|
|
|
3177
|
-
// src/commands/
|
|
3178
|
-
|
|
3783
|
+
// src/commands/claim.ts
|
|
3784
|
+
import React15 from "react";
|
|
3785
|
+
import { render as render7 } from "ink";
|
|
3786
|
+
async function claimCommand(token) {
|
|
3179
3787
|
if (!token) {
|
|
3180
|
-
console.error("Usage: token-derby
|
|
3788
|
+
console.error("Usage: token-derby claim <token>");
|
|
3181
3789
|
return 2;
|
|
3182
3790
|
}
|
|
3183
3791
|
try {
|
|
3184
|
-
|
|
3185
|
-
console.log(`Joined organisation: ${resp.org_name}`);
|
|
3186
|
-
return 0;
|
|
3792
|
+
await probeClaim(token);
|
|
3187
3793
|
} catch (e) {
|
|
3188
3794
|
if (e instanceof ApiError) {
|
|
3189
3795
|
console.error(`Error: ${e.code} ${e.message}`);
|
|
@@ -3191,24 +3797,9 @@ async function orgJoinCommand(token) {
|
|
|
3191
3797
|
}
|
|
3192
3798
|
throw e;
|
|
3193
3799
|
}
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
// src/commands/web.ts
|
|
3197
|
-
import { spawn as spawn2 } from "child_process";
|
|
3198
|
-
function webOrigin() {
|
|
3199
|
-
return apiBase().replace(/\/api\/?$/, "");
|
|
3200
|
-
}
|
|
3201
|
-
function opener() {
|
|
3202
|
-
if (process.platform === "darwin") return "open";
|
|
3203
|
-
if (process.platform === "win32") return "start";
|
|
3204
|
-
if (process.platform === "linux") return "xdg-open";
|
|
3205
|
-
return null;
|
|
3206
|
-
}
|
|
3207
|
-
async function webCommand(deps = {}) {
|
|
3208
|
-
const spawnImpl = deps.spawnImpl ?? spawn2;
|
|
3209
|
-
let code;
|
|
3800
|
+
let stable;
|
|
3210
3801
|
try {
|
|
3211
|
-
|
|
3802
|
+
stable = await listStable();
|
|
3212
3803
|
} catch (e) {
|
|
3213
3804
|
if (e instanceof ApiError) {
|
|
3214
3805
|
console.error(`Error: ${e.code} ${e.message}`);
|
|
@@ -3216,25 +3807,97 @@ async function webCommand(deps = {}) {
|
|
|
3216
3807
|
}
|
|
3217
3808
|
throw e;
|
|
3218
3809
|
}
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
console.log("");
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3810
|
+
if (stable.horses.length === 0) {
|
|
3811
|
+
console.error("No horses in your stable. Run `token-derby stable create` first.");
|
|
3812
|
+
return 1;
|
|
3813
|
+
}
|
|
3814
|
+
console.log("\n\u{1F381} A cosmetic has been awarded to you.\n");
|
|
3815
|
+
const picked = await new Promise((resolve) => {
|
|
3816
|
+
const app = render7(React15.createElement(HorsePicker, {
|
|
3817
|
+
horses: stable.horses,
|
|
3818
|
+
prompt: "Which horse should receive it?",
|
|
3819
|
+
onPick: (h) => {
|
|
3820
|
+
app.unmount();
|
|
3821
|
+
resolve(h);
|
|
3822
|
+
},
|
|
3823
|
+
onCancel: () => {
|
|
3824
|
+
app.unmount();
|
|
3825
|
+
resolve(null);
|
|
3826
|
+
}
|
|
3827
|
+
}));
|
|
3828
|
+
});
|
|
3829
|
+
if (!picked) {
|
|
3830
|
+
console.log("Cancelled. Your token is unspent.");
|
|
3831
|
+
return 0;
|
|
3832
|
+
}
|
|
3833
|
+
let result;
|
|
3834
|
+
try {
|
|
3835
|
+
result = await redeemClaim(token, { stable_horse_id: picked.stable_horse_id });
|
|
3836
|
+
} catch (e) {
|
|
3837
|
+
if (e instanceof ApiError) {
|
|
3838
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3839
|
+
return 1;
|
|
3840
|
+
}
|
|
3841
|
+
throw e;
|
|
3842
|
+
}
|
|
3843
|
+
const outcome = (() => {
|
|
3844
|
+
if (result.result === "hat") {
|
|
3845
|
+
const hat3 = hatById(result.collected.id);
|
|
3846
|
+
return hat3 ? { kind: "hat", hat: hat3, variant: result.collected.variant } : null;
|
|
3847
|
+
}
|
|
3848
|
+
const hat2 = hatById(result.hat_id);
|
|
3849
|
+
return hat2 ? { kind: "duplicate", hat: hat2, variant: result.variant } : null;
|
|
3850
|
+
})();
|
|
3851
|
+
if (!outcome) {
|
|
3852
|
+
console.error("Server returned an unknown hat id \u2014 catalog mismatch.");
|
|
3853
|
+
return 1;
|
|
3854
|
+
}
|
|
3855
|
+
await runReveal(outcome);
|
|
3856
|
+
if (result.result === "hat") {
|
|
3857
|
+
const hat2 = hatById(result.collected.id);
|
|
3858
|
+
const variantSuffix2 = hat2.rarity !== "legendary" && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
|
|
3859
|
+
console.log(`
|
|
3860
|
+
\u2728 ${hat2.name}${variantSuffix2} [${hat2.rarity.toUpperCase()}]
|
|
3861
|
+
`);
|
|
3862
|
+
if (await promptYesNo("Equip now? [Y/n] ")) {
|
|
3863
|
+
try {
|
|
3864
|
+
await equipHat(picked.stable_horse_id, { hat_index: result.hat_index });
|
|
3865
|
+
console.log(`Equipped on ${picked.name}.`);
|
|
3866
|
+
} catch (e) {
|
|
3867
|
+
if (e instanceof ApiError) {
|
|
3868
|
+
console.error(`Equip failed: ${e.code} ${e.message}`);
|
|
3869
|
+
} else throw e;
|
|
3870
|
+
}
|
|
3233
3871
|
}
|
|
3872
|
+
return 0;
|
|
3234
3873
|
}
|
|
3874
|
+
const hat = hatById(result.hat_id);
|
|
3875
|
+
const variantSuffix = result.variant !== void 0 ? ` #${result.variant + 1}` : "";
|
|
3876
|
+
console.log(`
|
|
3877
|
+
${picked.name} already has ${hat?.name ?? result.hat_id}${variantSuffix}. +${result.xp_awarded} XP.
|
|
3878
|
+
`);
|
|
3235
3879
|
return 0;
|
|
3236
3880
|
}
|
|
3237
3881
|
|
|
3882
|
+
// src/commands/org-join.ts
|
|
3883
|
+
async function orgJoinCommand(token) {
|
|
3884
|
+
if (!token) {
|
|
3885
|
+
console.error("Usage: token-derby organisation join <join-token>");
|
|
3886
|
+
return 2;
|
|
3887
|
+
}
|
|
3888
|
+
try {
|
|
3889
|
+
const resp = await joinOrganisation({ join_token: token });
|
|
3890
|
+
console.log(`Joined organisation: ${resp.org_name}`);
|
|
3891
|
+
return 0;
|
|
3892
|
+
} catch (e) {
|
|
3893
|
+
if (e instanceof ApiError) {
|
|
3894
|
+
console.error(`Error: ${e.code} ${e.message}`);
|
|
3895
|
+
return 1;
|
|
3896
|
+
}
|
|
3897
|
+
throw e;
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3238
3901
|
// src/commands/env.ts
|
|
3239
3902
|
function warnOverrides() {
|
|
3240
3903
|
if (process.env.TOKEN_DERBY_HOME) {
|
|
@@ -3269,10 +3932,27 @@ function envCommand(arg) {
|
|
|
3269
3932
|
var HELP = `token-derby v${CLI_VERSION}
|
|
3270
3933
|
|
|
3271
3934
|
Identity:
|
|
3272
|
-
token-derby init
|
|
3273
|
-
|
|
3274
|
-
token-derby init --reset
|
|
3275
|
-
|
|
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).
|
|
3276
3956
|
|
|
3277
3957
|
Maintenance:
|
|
3278
3958
|
token-derby update Check for and install the latest CLI version
|
|
@@ -3299,6 +3979,8 @@ Races:
|
|
|
3299
3979
|
Cosmetics:
|
|
3300
3980
|
token-derby roll Spend a pending roll to try for a hat.
|
|
3301
3981
|
Earn rolls by leveling up horses.
|
|
3982
|
+
token-derby claim <token> Redeem a claim token for a cosmetic
|
|
3983
|
+
awarded to you by an admin.
|
|
3302
3984
|
|
|
3303
3985
|
Environment:
|
|
3304
3986
|
token-derby env Show the active environment (prod|staging)
|
|
@@ -3323,11 +4005,12 @@ async function main() {
|
|
|
3323
4005
|
const reset = argv.slice(1).includes("--reset");
|
|
3324
4006
|
return initCommand(reset);
|
|
3325
4007
|
}
|
|
4008
|
+
if (cmd === "login") return loginCommand(argv.slice(1));
|
|
3326
4009
|
if (cmd === "update") return updateCommand();
|
|
3327
4010
|
if (cmd === "env") return envCommand(argv[1]);
|
|
3328
4011
|
const identity = await loadIdentity();
|
|
3329
4012
|
if (!identity) {
|
|
3330
|
-
console.error("Run `token-derby
|
|
4013
|
+
console.error("Run `token-derby login` to set up your identity before using any other command.");
|
|
3331
4014
|
return 1;
|
|
3332
4015
|
}
|
|
3333
4016
|
if (cmd === "stable") {
|
|
@@ -3351,9 +4034,13 @@ async function main() {
|
|
|
3351
4034
|
const orgName = parseFlag(argv.slice(1), "--organisation");
|
|
3352
4035
|
return createRaceCommand(orgName);
|
|
3353
4036
|
}
|
|
4037
|
+
if (cmd === "logout") return logoutCommand();
|
|
4038
|
+
if (cmd === "link") return linkCommand(argv.slice(1));
|
|
4039
|
+
if (cmd === "whoami") return whoamiCommand();
|
|
3354
4040
|
if (cmd === "join") return joinCommand(argv[1], argv.slice(2));
|
|
3355
4041
|
if (cmd === "end") return endCommand(argv[1]);
|
|
3356
4042
|
if (cmd === "roll") return rollCommand();
|
|
4043
|
+
if (cmd === "claim") return claimCommand(argv[1]);
|
|
3357
4044
|
if (cmd === "web") return webCommand();
|
|
3358
4045
|
console.error(`Unknown command: ${cmd}`);
|
|
3359
4046
|
console.error(HELP);
|