@mauricode/token-derby 2.11.0 → 2.12.1

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
@@ -702,10 +702,40 @@ function renderHatLabel(hats, hatChoice, initialEquipped) {
702
702
  ] });
703
703
  }
704
704
 
705
+ // src/env/env.ts
706
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
707
+ import * as os from "os";
708
+ import * as path from "path";
709
+ var ENV_NAMES = ["prod", "staging"];
710
+ function baseDir() {
711
+ return process.env.TOKEN_DERBY_BASE ?? os.homedir();
712
+ }
713
+ function configFile() {
714
+ return path.join(baseDir(), ".token-derby", "config.json");
715
+ }
716
+ function selectedEnv() {
717
+ try {
718
+ const raw = readFileSync(configFile(), "utf8");
719
+ const parsed = JSON.parse(raw);
720
+ if (parsed.env === "prod" || parsed.env === "staging") return parsed.env;
721
+ return "prod";
722
+ } catch {
723
+ return "prod";
724
+ }
725
+ }
726
+ function setSelectedEnv(env) {
727
+ const file = configFile();
728
+ mkdirSync(path.dirname(file), { recursive: true });
729
+ writeFileSync(file, JSON.stringify({ env }, null, 2) + "\n", "utf8");
730
+ }
731
+
705
732
  // src/config.ts
706
- var DEFAULT_API_BASE = "https://token-derby.mauricode.co.uk/api";
733
+ var ENVIRONMENTS = {
734
+ prod: { apiBase: "https://token-derby.mauricode.co.uk/api" },
735
+ staging: { apiBase: "https://token-derby-staging.mauricode.co.uk/api" }
736
+ };
707
737
  function apiBase() {
708
- return process.env.TOKEN_DERBY_API_BASE ?? DEFAULT_API_BASE;
738
+ return process.env.TOKEN_DERBY_API_BASE ?? ENVIRONMENTS[selectedEnv()].apiBase;
709
739
  }
710
740
  var HEARTBEAT_INTERVAL_MS = 6e4;
711
741
  var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
@@ -713,8 +743,8 @@ var HEARTBEAT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
713
743
  // src/version.ts
714
744
  import { createRequire } from "module";
715
745
  function readVersion() {
716
- if ("2.11.0".length > 0) {
717
- return "2.11.0";
746
+ if ("2.12.1".length > 0) {
747
+ return "2.12.1";
718
748
  }
719
749
  try {
720
750
  const req = createRequire(import.meta.url);
@@ -728,31 +758,34 @@ var CLI_VERSION = readVersion();
728
758
 
729
759
  // src/identity/identity.ts
730
760
  import { promises as fs } from "fs";
731
- import * as path2 from "path";
761
+ import * as path3 from "path";
732
762
 
733
763
  // src/paths.ts
734
- import * as os from "os";
735
- import * as path from "path";
764
+ import * as os2 from "os";
765
+ import * as path2 from "path";
736
766
  function homeDir() {
737
- return process.env.TOKEN_DERBY_HOME ?? path.join(os.homedir(), ".token-derby");
767
+ const override = process.env.TOKEN_DERBY_HOME;
768
+ if (override) return override;
769
+ const dir = selectedEnv() === "staging" ? ".token-derby-staging" : ".token-derby";
770
+ return path2.join(baseDir(), dir);
738
771
  }
739
772
  function identityFile() {
740
- return path.join(homeDir(), "identity.json");
773
+ return path2.join(homeDir(), "identity.json");
741
774
  }
742
775
  function activeRaceFile(joinCode) {
743
- return path.join(homeDir(), "active-races", `${joinCode}.json`);
776
+ return path2.join(homeDir(), "active-races", `${joinCode}.json`);
744
777
  }
745
778
  function activeRacesDir() {
746
- return path.join(homeDir(), "active-races");
779
+ return path2.join(homeDir(), "active-races");
747
780
  }
748
781
  function claudeProjectsDir() {
749
- return process.env.TOKEN_DERBY_CLAUDE_DIR ?? path.join(os.homedir(), ".claude", "projects");
782
+ return process.env.TOKEN_DERBY_CLAUDE_DIR ?? path2.join(os2.homedir(), ".claude", "projects");
750
783
  }
751
784
  function codexSessionsDir() {
752
- return process.env.TOKEN_DERBY_CODEX_DIR ?? path.join(os.homedir(), ".codex");
785
+ return process.env.TOKEN_DERBY_CODEX_DIR ?? path2.join(os2.homedir(), ".codex");
753
786
  }
754
787
  function geminiTmpDir() {
755
- return process.env.TOKEN_DERBY_GEMINI_DIR ?? path.join(os.homedir(), ".gemini", "tmp");
788
+ return process.env.TOKEN_DERBY_GEMINI_DIR ?? path2.join(os2.homedir(), ".gemini", "tmp");
756
789
  }
757
790
 
758
791
  // src/identity/identity.ts
@@ -808,8 +841,8 @@ function getIdentity() {
808
841
  function _resetIdentityCacheForTests() {
809
842
  identityCache = null;
810
843
  }
811
- async function request(method, path7, body, horseAuthToken, fetchImpl = fetch) {
812
- const url = path7.startsWith("http") ? path7 : `${apiBase()}${path7}`;
844
+ async function request(method, path8, body, horseAuthToken, fetchImpl = fetch) {
845
+ const url = path8.startsWith("http") ? path8 : `${apiBase()}${path8}`;
813
846
  const headers = {};
814
847
  headers[CLI_VERSION_HEADER] = CLI_VERSION;
815
848
  headers["user-agent"] = `token-derby/${CLI_VERSION}`;
@@ -1321,7 +1354,7 @@ function PrimaryPicker({ onPick }) {
1321
1354
 
1322
1355
  // src/stable/active-race.ts
1323
1356
  import * as fs2 from "fs/promises";
1324
- import * as path3 from "path";
1357
+ import * as path4 from "path";
1325
1358
  async function saveActiveRace(active) {
1326
1359
  await fs2.mkdir(activeRacesDir(), { recursive: true });
1327
1360
  await fs2.writeFile(
@@ -1512,11 +1545,11 @@ function runHeartbeatLoop(opts) {
1512
1545
 
1513
1546
  // src/tokens/transcripts.ts
1514
1547
  import * as fs3 from "fs/promises";
1515
- import * as path4 from "path";
1548
+ import * as path5 from "path";
1516
1549
  var MAX_PROJECT_DEPTH = 8;
1517
1550
  function conversationId(file, root) {
1518
- const rel = path4.relative(root, file);
1519
- const [project, session] = rel.split(path4.sep);
1551
+ const rel = path5.relative(root, file);
1552
+ const [project, session] = rel.split(path5.sep);
1520
1553
  if (project === void 0 || session === void 0) return rel.replace(/\.jsonl$/, "");
1521
1554
  return `${project}/${session.replace(/\.jsonl$/, "")}`;
1522
1555
  }
@@ -1548,7 +1581,7 @@ async function listJsonlFiles(root) {
1548
1581
  const projects = await fs3.readdir(root);
1549
1582
  const out = [];
1550
1583
  for (const project of projects) {
1551
- const projectDir = path4.join(root, project);
1584
+ const projectDir = path5.join(root, project);
1552
1585
  const stat3 = await fs3.stat(projectDir);
1553
1586
  if (!stat3.isDirectory()) continue;
1554
1587
  await collectJsonl(projectDir, MAX_PROJECT_DEPTH, out);
@@ -1560,9 +1593,9 @@ async function collectJsonl(dir, depth, out) {
1560
1593
  const entries = await fs3.readdir(dir);
1561
1594
  for (const entry of entries) {
1562
1595
  if (entry.endsWith(".jsonl")) {
1563
- out.push(path4.join(dir, entry));
1596
+ out.push(path5.join(dir, entry));
1564
1597
  } else if (depth > 1) {
1565
- const child = path4.join(dir, entry);
1598
+ const child = path5.join(dir, entry);
1566
1599
  const st = await fs3.stat(child);
1567
1600
  if (st.isDirectory()) await collectJsonl(child, depth - 1, out);
1568
1601
  }
@@ -1593,7 +1626,7 @@ async function sumFile(file) {
1593
1626
 
1594
1627
  // src/tokens/codex.ts
1595
1628
  import * as fs4 from "fs/promises";
1596
- import * as path5 from "path";
1629
+ import * as path6 from "path";
1597
1630
  function num(v) {
1598
1631
  return typeof v === "number" && Number.isFinite(v) ? v : 0;
1599
1632
  }
@@ -1601,8 +1634,8 @@ async function sumCodexByConversation() {
1601
1634
  const root = codexSessionsDir();
1602
1635
  await fs4.stat(root);
1603
1636
  const files = [
1604
- ...await collectRollouts(path5.join(root, "sessions")),
1605
- ...await collectRollouts(path5.join(root, "archived_sessions"))
1637
+ ...await collectRollouts(path6.join(root, "sessions")),
1638
+ ...await collectRollouts(path6.join(root, "archived_sessions"))
1606
1639
  ];
1607
1640
  const byConv = /* @__PURE__ */ new Map();
1608
1641
  for (const file of files) {
@@ -1630,7 +1663,7 @@ async function collectRollouts(dir) {
1630
1663
  }
1631
1664
  const out = [];
1632
1665
  for (const entry of entries) {
1633
- const full = path5.join(dir, entry.name);
1666
+ const full = path6.join(dir, entry.name);
1634
1667
  if (entry.isDirectory()) out.push(...await collectRollouts(full));
1635
1668
  else if (entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) out.push(full);
1636
1669
  }
@@ -1665,7 +1698,7 @@ async function lastTokenCount(file) {
1665
1698
 
1666
1699
  // src/tokens/gemini.ts
1667
1700
  import * as fs5 from "fs/promises";
1668
- import * as path6 from "path";
1701
+ import * as path7 from "path";
1669
1702
  function num2(v) {
1670
1703
  return typeof v === "number" && Number.isFinite(v) ? v : 0;
1671
1704
  }
@@ -1691,7 +1724,7 @@ async function listChatFiles(root) {
1691
1724
  const entries = await fs5.readdir(root);
1692
1725
  const out = [];
1693
1726
  for (const entry of entries) {
1694
- const chatsDir = path6.join(root, entry, "chats");
1727
+ const chatsDir = path7.join(root, entry, "chats");
1695
1728
  let files;
1696
1729
  try {
1697
1730
  files = await fs5.readdir(chatsDir);
@@ -1699,7 +1732,7 @@ async function listChatFiles(root) {
1699
1732
  continue;
1700
1733
  }
1701
1734
  for (const f of files) {
1702
- if (f.endsWith(".json") || f.endsWith(".jsonl")) out.push(path6.join(chatsDir, f));
1735
+ if (f.endsWith(".json") || f.endsWith(".jsonl")) out.push(path7.join(chatsDir, f));
1703
1736
  }
1704
1737
  }
1705
1738
  return out;
@@ -2910,6 +2943,36 @@ async function webCommand(deps = {}) {
2910
2943
  return 0;
2911
2944
  }
2912
2945
 
2946
+ // src/commands/env.ts
2947
+ function warnOverrides() {
2948
+ if (process.env.TOKEN_DERBY_HOME) {
2949
+ console.error("Warning: TOKEN_DERBY_HOME overrides the per-env data directory; `env` selection does not change where the identity is stored.");
2950
+ }
2951
+ if (process.env.TOKEN_DERBY_API_BASE) {
2952
+ console.error("Warning: TOKEN_DERBY_API_BASE overrides the API base; `env` selection does not change which API is used.");
2953
+ }
2954
+ }
2955
+ function envCommand(arg) {
2956
+ if (!arg) {
2957
+ console.log(`Environment: ${selectedEnv()}`);
2958
+ console.log(`API base: ${apiBase()}`);
2959
+ console.log(`Data dir: ${homeDir()}`);
2960
+ warnOverrides();
2961
+ return 0;
2962
+ }
2963
+ if (!ENV_NAMES.includes(arg)) {
2964
+ console.error(`Unknown environment: ${arg}`);
2965
+ console.error(`Valid environments: ${ENV_NAMES.join(", ")}`);
2966
+ return 2;
2967
+ }
2968
+ setSelectedEnv(arg);
2969
+ console.log(`Switched to ${arg}.`);
2970
+ console.log(`API base: ${apiBase()}`);
2971
+ console.log(`Data dir: ${homeDir()}`);
2972
+ warnOverrides();
2973
+ return 0;
2974
+ }
2975
+
2913
2976
  // src/bin.ts
2914
2977
  var HELP = `token-derby v${CLI_VERSION}
2915
2978
 
@@ -2946,8 +3009,12 @@ Cosmetics:
2946
3009
  Earn rolls by leveling up horses.
2947
3010
 
2948
3011
  Environment:
2949
- TOKEN_DERBY_API_BASE Override API base URL (default: production)
2950
- TOKEN_DERBY_HOME Override identity/stable directory
3012
+ token-derby env Show the active environment (prod|staging)
3013
+ token-derby env <prod|staging> Switch environment. Each env has its own
3014
+ identity/stable dir, so switching never
3015
+ touches the other env's account.
3016
+ TOKEN_DERBY_API_BASE Hard-override API base URL (wins over env)
3017
+ TOKEN_DERBY_HOME Hard-override identity/stable directory
2951
3018
  `;
2952
3019
  async function main() {
2953
3020
  const argv = process.argv.slice(2);
@@ -2965,6 +3032,7 @@ async function main() {
2965
3032
  return initCommand(reset);
2966
3033
  }
2967
3034
  if (cmd === "update") return updateCommand();
3035
+ if (cmd === "env") return envCommand(argv[1]);
2968
3036
  const identity = await loadIdentity();
2969
3037
  if (!identity) {
2970
3038
  console.error("Run `token-derby init` to set up your identity before using any other command.");