@mauricode/token-derby 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -199,9 +199,31 @@ var HATS = [
199
199
  // Claim-only: awarded by an admin, never from a roll. Only the A channel cycles,
200
200
  // so the inset motif shifts colour while the cap body stays a static rust brown.
201
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 }
202
+ { id: "contributor_cap", name: "Contributor Cap", rarity: "legendary", width: 11, anchor_x: 23, rows: [".....Q.....", "....QQQ....", "...QQQQQ...", "..QAAAQQQ..", "..QQAQQQQ..", ".QQQAQAAQQ.", ".QQQAQAQAQ.", ".QQQQQAQAQ.", ".QQQQQAAQQ.", ".QQQQQQQQQ."], colors: { A: "#21138b", Q: "#6f2801" }, animation: { type: "cycle", frames: ["#FF8C42", "#E0457B", "#8B5CF6", "#3B82F6", "#22D3EE", "#34D399", "#A3E635", "#FBBF24"], fps: 6 }, rollable: false },
203
+ // ── LIMITED (6) ────────────────────────────────────────────────────────
204
+ // London series. Claim-only by construction: limited is absent from
205
+ // TIER_WEIGHTS, so no roll can reach this tier.
206
+ { id: "bearskin", name: "Bearskin", rarity: "limited", width: 11, anchor_x: 23, rows: ["...CCCCC.Q.", "..CAAAAACQ.", "..AAAAAACQ.", "..AAAAAAAQ.", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..AAAAAAA..", "..DDDDDDD..", "..AAAAAAA.."], variants: [{ A: "#23232A", Q: "#C8102E", C: "#15151A", D: "#FFD300" }], rollable: false },
207
+ { id: "roundel_cap", name: "Roundel Cap", rarity: "limited", width: 11, anchor_x: 23, rows: ["...........", "..AAAAAA...", ".AAAAAAAA..", ".AAQQQQQAA.", ".AAQDDDQAA.", ".AFFFFFFFA.", ".AAQDDDQAA.", ".AAQQQQQAA.", ".AAAAAAAAA.", ".FFFFFFFFFF"], variants: [{ A: "#0B3B8C", Q: "#E32017", C: "#1050B8", D: "#F4F1E8", F: "#072A66" }], rollable: false },
208
+ { id: "union_topper", name: "Union Topper", rarity: "limited", width: 11, anchor_x: 23, rows: ["..CCCCCCC..", "..AQAAAQA..", "..AAQDQAA..", "..QQQDQQQ..", "..DDDDDDD..", "..QQQDQQQ..", "..AAQDQAA..", "..AQAAAQA..", ".CCCCCCCCC.", ".FFFFFFFFF."], variants: [{ A: "#0B2C6B", Q: "#F2F6FA", C: "#081F4D", D: "#C8102E", F: "#061838" }], rollable: false },
209
+ { id: "crown_jewels", name: "Crown Jewels", rarity: "limited", width: 11, anchor_x: 23, rows: ["...........", "....QQQ....", ".....Q.....", "..QQ.Q.QQ..", ".Q..QQQ..Q.", ".QCCCQCCCQ.", ".QCCCQCCCQ.", ".QQCCQCCQQ.", "..QQQQQQQ..", "..AAAAAAA.."], variants: [{ A: "#f5f5f5", Q: "#ffd700", C: "#5d0a8a" }], rollable: false },
210
+ { id: "black_cab", name: "Black Cab", rarity: "limited", width: 11, anchor_x: 22, rows: ["...........", "....CCC....", "..AAAAAA...", "..AQQAQA...", ".AAQQAQAAA.", ".AAAAAAAAAF", "AAAAAAAAAAF", "ADDAAAADDA.", ".DD....DD..", "..........."], variants: [{ A: "#141416", Q: "#8FB8D8", C: "#F5C518", D: "#3A3A40", F: "#D9DDE3" }], rollable: false },
211
+ { id: "london_bus", name: "London Bus", rarity: "limited", width: 11, anchor_x: 22, rows: ["...........", "...........", ".AAAAAAAAAA", "AAAAAAAAAAA", "ACCCACCCAAC", "AAAAAAAAACC", "ACCCACCCACC", "AAAAAAAAAAA", "AQQAAAAAQQA", ".QQ.....QQ."], variants: [{ A: "#e32402", Q: "#0d0c0c", C: "#dcaf09" }], rollable: false }
203
212
  ];
204
213
 
214
+ // ../shared/dist/hat-shape.js
215
+ function isAnimatedHat(hat) {
216
+ return "animation" in hat;
217
+ }
218
+ function hatColors(hat, variantIdx = 0) {
219
+ if (isAnimatedHat(hat))
220
+ return hat.colors;
221
+ return hat.variants[variantIdx] ?? hat.variants[0];
222
+ }
223
+ function channelColor(colors, ch) {
224
+ return colors[ch] ?? colors.A;
225
+ }
226
+
205
227
  // ../shared/dist/series-transform.js
206
228
  var PACE_WINDOW_MS = 15 * 6e4;
207
229
 
@@ -433,7 +455,7 @@ function composeHatGrid(baseSprite, hat, variantIdx, horseColors) {
433
455
  grid[y + ext][x + horseOffsetX] = tagToColor(tag, horseColors);
434
456
  }
435
457
  }
436
- const hatColors = hatColorsFor(hat, variantIdx);
458
+ const colors = hatColors(hat, variantIdx);
437
459
  for (let y = 0; y < hatH; y++) {
438
460
  const row = hat.rows[y];
439
461
  for (let x = 0; x < hatW; x++) {
@@ -441,7 +463,7 @@ function composeHatGrid(baseSprite, hat, variantIdx, horseColors) {
441
463
  if (ch === "." || ch === void 0) continue;
442
464
  const gx = hat.anchor_x + x + horseOffsetX;
443
465
  if (gx < 0 || gx >= canvasW) continue;
444
- const color = ch === "A" ? hatColors.A : hatColors.Q ?? hatColors.A;
466
+ const color = channelColor(colors, ch);
445
467
  grid[y][gx] = color;
446
468
  }
447
469
  }
@@ -463,10 +485,6 @@ function tagToColor(tag, c) {
463
485
  return FIXED_COLORS.H;
464
486
  }
465
487
  }
466
- function hatColorsFor(hat, variantIdx) {
467
- if (hat.rarity === "legendary") return hat.colors;
468
- return hat.variants[variantIdx] ?? hat.variants[0];
469
- }
470
488
 
471
489
  // src/ui/half-blocks.ts
472
490
  var RESET = "\x1B[0m";
@@ -538,19 +556,19 @@ import { useEffect, useState } from "react";
538
556
  import { Box as Box2, Text as Text2 } from "ink";
539
557
  import { jsx as jsx2 } from "react/jsx-runtime";
540
558
  function AnimatedHorseSprite({ sprite, colors, hat }) {
541
- const isLegendary = hat.rarity === "legendary";
542
- const frames = isLegendary ? hat.animation.frames : [];
543
- const fps = isLegendary ? hat.animation.fps : 1;
559
+ const animated = isAnimatedHat(hat);
560
+ const frames = animated ? hat.animation.frames : [];
561
+ const fps = animated ? hat.animation.fps : 1;
544
562
  const [idx, setIdx] = useState(0);
545
563
  useEffect(() => {
546
- if (!isLegendary || frames.length <= 1) return;
564
+ if (!animated || frames.length <= 1) return;
547
565
  const interval = setInterval(
548
566
  () => setIdx((i) => (i + 1) % frames.length),
549
567
  Math.max(1, Math.round(1e3 / fps))
550
568
  );
551
569
  return () => clearInterval(interval);
552
- }, [isLegendary, frames.length, fps]);
553
- const renderedHat = isLegendary && frames[idx] ? { ...hat, colors: { ...hat.colors, A: frames[idx] } } : hat;
570
+ }, [animated, frames.length, fps]);
571
+ const renderedHat = animated && frames[idx] ? { ...hat, colors: { ...hat.colors, A: frames[idx] } } : hat;
554
572
  const { grid } = composeHatGrid(sprite, renderedHat, 0, colors);
555
573
  const lines = hexGridToHalfBlocks(grid);
556
574
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx2(Text2, { children: line }, i)) });
@@ -739,7 +757,7 @@ function HorseCreator({
739
757
  "]"
740
758
  ] })
741
759
  ] }),
742
- /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: previewHatDef ? previewHatDef.rarity === "legendary" ? /* @__PURE__ */ jsx3(AnimatedHorseSprite, { sprite: MAIN_SPRITE, colors, hat: previewHatDef }) : /* @__PURE__ */ jsx3(HorseSprite, { sprite: MAIN_SPRITE, colors, hat: { hat: previewHatDef, variant: previewHat?.variant ?? 0 } }) : /* @__PURE__ */ jsx3(HorseSprite, { sprite: MAIN_SPRITE, colors }) }),
760
+ /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: previewHatDef ? isAnimatedHat(previewHatDef) ? /* @__PURE__ */ jsx3(AnimatedHorseSprite, { sprite: MAIN_SPRITE, colors, hat: previewHatDef }) : /* @__PURE__ */ jsx3(HorseSprite, { sprite: MAIN_SPRITE, colors, hat: { hat: previewHatDef, variant: previewHat?.variant ?? 0 } }) : /* @__PURE__ */ jsx3(HorseSprite, { sprite: MAIN_SPRITE, colors }) }),
743
761
  /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: rows.map((r, i) => {
744
762
  const cursor = i === rowIdx ? "\u25BA" : " ";
745
763
  if (r.kind === "color") {
@@ -785,8 +803,8 @@ function renderHatLabel(hats, hatChoice, initialEquipped) {
785
803
  if (!collected) return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "?" });
786
804
  const hat = hatById(collected.id);
787
805
  const name = hat?.name ?? collected.id;
788
- const variantSuffix = hat && hat.rarity !== "legendary" && collected.variant !== void 0 ? ` #${collected.variant + 1}` : "";
789
- const rarityColor = hat ? hat.rarity === "legendary" ? "yellow" : hat.rarity === "epic" ? "magenta" : hat.rarity === "rare" ? "blue" : "gray" : "gray";
806
+ const variantSuffix = hat && !isAnimatedHat(hat) && collected.variant !== void 0 ? ` #${collected.variant + 1}` : "";
807
+ const rarityColor = hat ? hat.rarity === "limited" ? "magentaBright" : hat.rarity === "legendary" ? "yellow" : hat.rarity === "epic" ? "magenta" : hat.rarity === "rare" ? "blue" : "gray" : "gray";
790
808
  const equippedMark = initialEquipped === hatChoice ? " \u2713" : "";
791
809
  return /* @__PURE__ */ jsxs(Fragment, { children: [
792
810
  name,
@@ -843,8 +861,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
843
861
  // src/version.ts
844
862
  import { createRequire } from "module";
845
863
  function readVersion() {
846
- if ("3.0.0".length > 0) {
847
- return "3.0.0";
864
+ if ("3.1.0".length > 0) {
865
+ return "3.1.0";
848
866
  }
849
867
  try {
850
868
  const req = createRequire(import.meta.url);
@@ -3477,13 +3495,13 @@ import { useEffect as useEffect3, useState as useState7 } from "react";
3477
3495
  import { Box as Box10, Text as Text10 } from "ink";
3478
3496
  import { jsx as jsx10 } from "react/jsx-runtime";
3479
3497
  function HatSprite({ hat, variant, centerIn }) {
3480
- const colors = hatColorsFor2(hat, variant ?? 0);
3498
+ const colors = hatColors(hat, variant ?? 0);
3481
3499
  const grid = makeHatGrid(hat, colors, centerIn);
3482
3500
  const lines = hexGridToHalfBlocks(grid);
3483
3501
  return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx10(Text10, { children: line }, i)) });
3484
3502
  }
3485
3503
  function AnimatedHatSprite({ hat, variant, centerIn }) {
3486
- if (hat.rarity !== "legendary") {
3504
+ if (!isAnimatedHat(hat)) {
3487
3505
  return /* @__PURE__ */ jsx10(HatSprite, { hat, variant, centerIn });
3488
3506
  }
3489
3507
  const frames = hat.animation.frames;
@@ -3500,10 +3518,6 @@ function AnimatedHatSprite({ hat, variant, centerIn }) {
3500
3518
  const framed = { ...hat, colors: { ...hat.colors, A: frames[idx] } };
3501
3519
  return /* @__PURE__ */ jsx10(HatSprite, { hat: framed, variant, centerIn });
3502
3520
  }
3503
- function hatColorsFor2(hat, variantIdx) {
3504
- if (hat.rarity === "legendary") return hat.colors;
3505
- return hat.variants[variantIdx] ?? hat.variants[0];
3506
- }
3507
3521
  function makeHatGrid(hat, colors, centerIn) {
3508
3522
  const w = centerIn?.w ?? hat.width;
3509
3523
  const h = centerIn?.h ?? hat.rows.length;
@@ -3518,7 +3532,7 @@ function makeHatGrid(hat, colors, centerIn) {
3518
3532
  const gx = x + offX;
3519
3533
  const gy = y + offY;
3520
3534
  if (gx < 0 || gx >= w || gy < 0 || gy >= h) continue;
3521
- grid[gy][gx] = ch === "A" ? colors.A : colors.Q ?? colors.A;
3535
+ grid[gy][gx] = channelColor(colors, ch);
3522
3536
  }
3523
3537
  }
3524
3538
  return grid;
@@ -3532,7 +3546,8 @@ var TIER_PALETTE = {
3532
3546
  common: ["#FFFFFF", "#DDDDDD", "#AAAAAA", "#9E9E9E"],
3533
3547
  rare: ["#42A5F5", "#1E88E5", "#90CAF9", "#0277BD"],
3534
3548
  epic: ["#AB47BC", "#8E24AA", "#CE93D8", "#6A1B9A"],
3535
- legendary: ["#FFD700", "#FF7F00", "#FF0000", "#FF00FF", "#00BFFF", "#7CFC00", "#8B00FF"]
3549
+ legendary: ["#FFD700", "#FF7F00", "#FF0000", "#FF00FF", "#00BFFF", "#7CFC00", "#8B00FF"],
3550
+ limited: ["#F472B6", "#EC4899", "#FF4FA3", "#FBCFE8", "#FFFFFF"]
3536
3551
  };
3537
3552
  var CONFETTI_CHARS = ["\u2726", "\u2727", "\u22C6", "\u2605", "\u2606", "\u2728", "*", "\u2022", "\u25C6", "\u25C7"];
3538
3553
  var SCENE_W = 32;
@@ -3635,7 +3650,7 @@ function ConfettiBurst({ tier }) {
3635
3650
  var CLOSED_HOLD_MS = 3e3;
3636
3651
  function RollReveal({ outcome, onDone }) {
3637
3652
  const isNoHat = outcome.kind === "no_hat";
3638
- const isLegendary = outcome.kind !== "no_hat" && outcome.hat.rarity === "legendary";
3653
+ const isShowpiece = outcome.kind !== "no_hat" && (outcome.hat.rarity === "legendary" || outcome.hat.rarity === "limited");
3639
3654
  const [phase, setPhase] = useState8("closed");
3640
3655
  useEffect4(() => {
3641
3656
  const timers = [];
@@ -3647,10 +3662,10 @@ function RollReveal({ outcome, onDone }) {
3647
3662
  } else {
3648
3663
  timers.push(setTimeout(() => setPhase("burst"), CLOSED_HOLD_MS + 700));
3649
3664
  timers.push(setTimeout(() => setPhase("reveal"), CLOSED_HOLD_MS + 1650));
3650
- timers.push(setTimeout(onDone, CLOSED_HOLD_MS + (isLegendary ? 4650 : 2650)));
3665
+ timers.push(setTimeout(onDone, CLOSED_HOLD_MS + (isShowpiece ? 4650 : 2650)));
3651
3666
  }
3652
3667
  return () => timers.forEach(clearTimeout);
3653
- }, [isNoHat, isLegendary, onDone]);
3668
+ }, [isNoHat, isShowpiece, onDone]);
3654
3669
  if (phase === "closed") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_CLOSED, color: BOX_COLOR });
3655
3670
  if (phase === "open1") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_1, color: BOX_COLOR });
3656
3671
  if (phase === "open2") return /* @__PURE__ */ jsx11(GiftBox, { frame: BOX_OPENING_2, color: BOX_COLOR });
@@ -3659,7 +3674,7 @@ function RollReveal({ outcome, onDone }) {
3659
3674
  return /* @__PURE__ */ jsx11(ConfettiBurst, { tier: outcome.hat.rarity });
3660
3675
  }
3661
3676
  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 } });
3677
+ return isAnimatedHat(outcome.hat) ? /* @__PURE__ */ jsx11(AnimatedHatSprite, { hat: outcome.hat, centerIn: { w: SCENE_W, h: SCENE_H } }) : /* @__PURE__ */ jsx11(HatSprite, { hat: outcome.hat, variant: outcome.variant, centerIn: { w: SCENE_W, h: SCENE_H } });
3663
3678
  }
3664
3679
 
3665
3680
  // src/ui/reveal.ts
@@ -3750,7 +3765,7 @@ async function rollCommand() {
3750
3765
  await runReveal(outcome);
3751
3766
  if (result.result === "hat") {
3752
3767
  const hat = hatById(result.collected.id);
3753
- const variantSuffix = hat.rarity !== "legendary" && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
3768
+ const variantSuffix = !isAnimatedHat(hat) && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
3754
3769
  console.log(`
3755
3770
  \u2728 ${hat.name}${variantSuffix} [${hat.rarity.toUpperCase()}]
3756
3771
  `);
@@ -3788,8 +3803,9 @@ async function claimCommand(token) {
3788
3803
  console.error("Usage: token-derby claim <token>");
3789
3804
  return 2;
3790
3805
  }
3806
+ let probe;
3791
3807
  try {
3792
- await probeClaim(token);
3808
+ probe = await probeClaim(token);
3793
3809
  } catch (e) {
3794
3810
  if (e instanceof ApiError) {
3795
3811
  console.error(`Error: ${e.code} ${e.message}`);
@@ -3811,7 +3827,9 @@ async function claimCommand(token) {
3811
3827
  console.error("No horses in your stable. Run `token-derby stable create` first.");
3812
3828
  return 1;
3813
3829
  }
3814
- console.log("\n\u{1F381} A cosmetic has been awarded to you.\n");
3830
+ console.log(probe.entry_count > 1 ? `
3831
+ \u{1F381} A pack of ${probe.entry_count} cosmetics \u2014 one of them will be yours.
3832
+ ` : "\n\u{1F381} A cosmetic has been awarded to you.\n");
3815
3833
  const picked = await new Promise((resolve) => {
3816
3834
  const app = render7(React15.createElement(HorsePicker, {
3817
3835
  horses: stable.horses,
@@ -3855,7 +3873,7 @@ async function claimCommand(token) {
3855
3873
  await runReveal(outcome);
3856
3874
  if (result.result === "hat") {
3857
3875
  const hat2 = hatById(result.collected.id);
3858
- const variantSuffix2 = hat2.rarity !== "legendary" && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
3876
+ const variantSuffix2 = !isAnimatedHat(hat2) && result.collected.variant !== void 0 ? ` #${result.collected.variant + 1}` : "";
3859
3877
  console.log(`
3860
3878
  \u2728 ${hat2.name}${variantSuffix2} [${hat2.rarity.toUpperCase()}]
3861
3879
  `);
@@ -3881,12 +3899,9 @@ ${picked.name} already has ${hat?.name ?? result.hat_id}${variantSuffix}. +${res
3881
3899
 
3882
3900
  // src/commands/org-join.ts
3883
3901
  async function orgJoinCommand(token) {
3884
- if (!token) {
3885
- console.error("Usage: token-derby organisation join <join-token>");
3886
- return 2;
3887
- }
3902
+ const join_token = token?.trim();
3888
3903
  try {
3889
- const resp = await joinOrganisation({ join_token: token });
3904
+ const resp = await joinOrganisation(join_token ? { join_token } : {});
3890
3905
  console.log(`Joined organisation: ${resp.org_name}`);
3891
3906
  return 0;
3892
3907
  } catch (e) {
@@ -3964,7 +3979,9 @@ Stable management:
3964
3979
  token-derby stable delete <name> Remove a horse from your stable
3965
3980
 
3966
3981
  Organisations:
3967
- token-derby organisation join <token> Join an organisation with a join token
3982
+ token-derby organisation join [token] Join an organisation with a join token,
3983
+ or leave the token out to join by your
3984
+ verified email domain
3968
3985
  token-derby web Open the web org manager (create orgs,
3969
3986
  manage schedules, webhooks, members)
3970
3987
 
@@ -4027,7 +4044,7 @@ async function main() {
4027
4044
  const sub = argv[1];
4028
4045
  if (sub === "join") return orgJoinCommand(argv[2]);
4029
4046
  console.error(`Organisation management has moved to the web: token-derby web`);
4030
- console.error(`The only CLI organisation command is: organisation join <token>`);
4047
+ console.error(`The only CLI organisation command is: organisation join [token]`);
4031
4048
  return 2;
4032
4049
  }
4033
4050
  if (cmd === "create") {