tokenmaxxing 1.71.0 → 1.73.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/DESIGN.md CHANGED
@@ -42,7 +42,7 @@ It is a process manager only - spawn with inherited stdio (stdin through a relay
42
42
  - `sessions/<sid>.json`, `onboard/`, `hub-key`, `update.json`, `spawnrate.json`, `tokenmaxxing.log`, the codex `codex-onboard/`, `codex-respawn/`, and `codex-live/`, and the `grok-*` and `opencode-go-*` state of the status-only pools: the full table is in [State directory layout](docs/content/docs/state-directory.mdx).
43
43
  - Per-account **credential stores** follow the platform's Claude Code store: macOS = a login-keychain item keyed by the store path (never plaintext on disk); Linux = a 0600 `.credentials.json` inside the 0700 store directory (the same plaintext model claude itself uses - its Linux build has no keyring path at all, binary-verified 2.1.205). One `credstore` facade dispatches on a `{kind: keychain|file}` target; call sites never branch on platform. tokenmaxxing writes a store once, at onboarding; Claude Code's own refresh is the only writer afterwards.
44
44
 
45
- - A periodic check job (`com.tokenmaxxing.check` launchd agent on macOS, `tokenmaxxing-check.timer` systemd user timer on Linux) running `tokenmaxxing check` once per tick (`policy.checkIntervalMs`, 60s by default): it runs the decision with no seat (nothing moves), folds fresh tees into the index, samples up to three accounts whose last sample attempt is oldest (the direct no-spend usage GET), deletes leftover dead state files at the state root, and on a Bun global install checks npm for a newer version once a day, so the cached figures every placement reads stay fresh.
45
+ - A periodic check job (`com.tokenmaxxing.check` launchd agent on macOS, `tokenmaxxing-check.timer` systemd user timer on Linux) running `tokenmaxxing check` once per tick (`policy.checkIntervalMs`, 60s by default): it runs the decision with no seat (nothing moves), folds fresh tees into the index, samples up to three accounts whose last sample attempt is oldest (the direct no-spend usage GET), deletes leftover atomic-write temp files under the state root, and on a Bun global install checks npm for a newer version once a day, so the cached figures every placement reads stay fresh.
46
46
  - A usage hub service (`com.tokenmaxxing.hub` launchd agent on macOS, `tokenmaxxing-hub.service` systemd user service on Linux) running `tokenmaxxing serve`, so the [usage hub](docs/content/docs/hub.mdx) is up in the background beside the check timer.
47
47
 
48
48
  The switching path runs no long-lived daemon. The statusline pushes usage. Hooks and the supervisor react. The periodic check job above is the only recurring process in the switching path. The [usage hub](docs/content/docs/hub.mdx) that answers the CLIProxyAPI management subset for a dashboard from the cached figures runs outside the switching path, as a background service beside the check timer (`tokenmaxxing serve` runs the same server in the foreground). The `claude` binary, `~/.claude.json`, and Claude Code's default credential store are untouched; inside `~/.claude` only `settings.json` gets the five entries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenmaxxing",
3
- "version": "1.71.0",
3
+ "version": "1.73.0",
4
4
  "description": "Automatic Claude Code account switching: pool multiple accounts, start each session on its own account, and resume a session on a fresher account when quota fills.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  "DESIGN.md"
20
20
  ],
21
21
  "engines": {
22
- "bun": ">=1.3.6"
22
+ "bun": ">=1.3.7"
23
23
  },
24
24
  "os": [
25
25
  "darwin",
package/src/cli/status.ts CHANGED
@@ -138,35 +138,6 @@ const NOTE_INDENT = " ";
138
138
  type Note = { paint: (s: string) => string; text: string };
139
139
  type Card = { lines: string[]; notes: Note[] };
140
140
 
141
- function splitToWidth(token: string, width: number): string[] {
142
- const parts: string[] = [];
143
- let part = "";
144
- for (const ch of token) {
145
- if (part !== "" && Bun.stringWidth(part + ch) > width) {
146
- parts.push(part);
147
- part = "";
148
- }
149
- part += ch;
150
- }
151
- if (part !== "") parts.push(part);
152
- return parts;
153
- }
154
-
155
- function wrapWords(text: string, width: number): string[] {
156
- const out: string[] = [];
157
- let line = "";
158
- for (const word of text.split(" ").flatMap((token) => splitToWidth(token, width))) {
159
- if (line !== "" && Bun.stringWidth(`${line} ${word}`) > width) {
160
- out.push(line);
161
- line = word;
162
- } else {
163
- line = line === "" ? word : `${line} ${word}`;
164
- }
165
- }
166
- if (line !== "") out.push(line);
167
- return out;
168
- }
169
-
170
141
  function renderGrid(cards: Card[]): void {
171
142
  const bodyWidth = Math.max(...cards.flatMap((card) => card.lines.map((line) => Bun.stringWidth(line))));
172
143
  const termWidth = process.stdout.isTTY ? process.stdout.columns : Number.POSITIVE_INFINITY;
@@ -174,7 +145,7 @@ function renderGrid(cards: Card[]): void {
174
145
  const cellWidth = columns === 1 ? termWidth : Math.floor((termWidth + CARD_GAP) / columns) - CARD_GAP;
175
146
  const blocks = cards.map((card) => [
176
147
  ...card.lines,
177
- ...card.notes.flatMap((note) => wrapWords(note.text, cellWidth - NOTE_INDENT.length).map((line) => `${NOTE_INDENT}${note.paint(line)}`)),
148
+ ...card.notes.flatMap((note) => Bun.wrapAnsi(note.text, cellWidth - NOTE_INDENT.length, { hard: true }).split("\n").map((line) => `${NOTE_INDENT}${note.paint(line)}`)),
178
149
  ]);
179
150
  const width = Math.max(...blocks.flat().map((line) => Bun.stringWidth(line)));
180
151
  for (const rowBlocks of chunk(blocks, columns)) {
@@ -93,14 +93,14 @@ export function resolveRealClaude(): string {
93
93
 
94
94
  export function verifyRealClaude(bin: string): string | null {
95
95
  const env = { ...process.env, [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), TOKENMAXXING_PROBE: "1" };
96
- let p: ReturnType<typeof Bun.spawnSync>;
96
+ let p;
97
97
  try {
98
98
  p = Bun.spawnSync([bin, "--version"], { env, stdout: "pipe", stderr: "pipe", timeout: 15_000, killSignal: "SIGKILL" });
99
99
  } catch (e) {
100
100
  return errorMessage(e);
101
101
  }
102
- const outText = (p.stdout?.toString() ?? "").trim();
103
- const err = (p.stderr?.toString() ?? "").trim();
102
+ const outText = p.stdout.toString().trim();
103
+ const err = p.stderr.toString().trim();
104
104
  if (p.exitCode === 0) {
105
105
  if (/claude/i.test(outText)) return null;
106
106
  return `--version output does not identify claude: "${outText.slice(0, 80)}"`;
@@ -38,14 +38,14 @@ export function resolveRealCodex(): string {
38
38
 
39
39
  export function verifyRealCodex(input: { bin: string }): string | null {
40
40
  const env = { ...process.env, [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), TOKENMAXXING_PROBE: "1" };
41
- let p: ReturnType<typeof Bun.spawnSync>;
41
+ let p;
42
42
  try {
43
43
  p = Bun.spawnSync([input.bin, "--version"], { env, stdout: "pipe", stderr: "pipe", timeout: 15_000, killSignal: "SIGKILL" });
44
44
  } catch (e) {
45
45
  return errorMessage(e);
46
46
  }
47
- const outText = (p.stdout?.toString() ?? "").trim();
48
- const err = (p.stderr?.toString() ?? "").trim();
47
+ const outText = p.stdout.toString().trim();
48
+ const err = p.stderr.toString().trim();
49
49
  if (p.exitCode === 0) {
50
50
  if (outText.toLowerCase().includes("codex")) return null;
51
51
  return `--version output does not identify codex: "${outText.slice(0, 80)}"`;
@@ -1,4 +1,5 @@
1
1
  import { accessSync, appendFileSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
2
+ import { userInfo } from "node:os";
2
3
  import { basename, dirname, join, resolve } from "node:path";
3
4
  import { escape } from "es-toolkit";
4
5
  import { z } from "zod";
@@ -215,9 +216,8 @@ function launchdPlist(label: string): string {
215
216
  return join(paths.launchdAgentsDir, `${label}.plist`);
216
217
  }
217
218
 
218
- function launchdDomain(): string | null {
219
- const uid = process.getuid?.();
220
- return uid == null ? null : `gui/${uid}`;
219
+ function launchdDomain(): string {
220
+ return `gui/${userInfo().uid}`;
221
221
  }
222
222
 
223
223
  function run(cmd: string[]): boolean {
@@ -258,7 +258,6 @@ ${launchdEnv()} <key>StandardOutPath</key><string>/dev/null</string>
258
258
  0o644,
259
259
  );
260
260
  const domain = launchdDomain();
261
- if (domain == null) return false;
262
261
  run(["launchctl", "bootout", `${domain}/${LAUNCHD_LABEL}`]);
263
262
  return run(["launchctl", "bootstrap", domain, plist]) || checkTimerHealthy();
264
263
  }
@@ -313,8 +312,7 @@ export function hubActivationHint(): string {
313
312
  export function checkTimerHealthy(): boolean {
314
313
  if (skipImperativeTimer()) return true;
315
314
  if (process.platform === "darwin") {
316
- const domain = launchdDomain();
317
- return existsSync(launchdPlist(LAUNCHD_LABEL)) && domain != null && run(["launchctl", "print", `${domain}/${LAUNCHD_LABEL}`]);
315
+ return existsSync(launchdPlist(LAUNCHD_LABEL)) && run(["launchctl", "print", `${launchdDomain()}/${LAUNCHD_LABEL}`]);
318
316
  }
319
317
  return (
320
318
  existsSync(join(paths.systemdUserDir, "tokenmaxxing-check.timer")) &&
@@ -325,8 +323,7 @@ export function checkTimerHealthy(): boolean {
325
323
  export function hubServiceHealthy(): boolean {
326
324
  if (skipImperativeHub()) return true;
327
325
  if (process.platform === "darwin") {
328
- const domain = launchdDomain();
329
- return existsSync(launchdPlist(LAUNCHD_HUB_LABEL)) && domain != null && run(["launchctl", "print", `${domain}/${LAUNCHD_HUB_LABEL}`]);
326
+ return existsSync(launchdPlist(LAUNCHD_HUB_LABEL)) && run(["launchctl", "print", `${launchdDomain()}/${LAUNCHD_HUB_LABEL}`]);
330
327
  }
331
328
  return existsSync(join(paths.systemdUserDir, HUB_UNIT)) && run(["systemctl", "--user", "is-active", "--quiet", HUB_UNIT]);
332
329
  }
@@ -346,10 +343,8 @@ export function hubDeactivationHint(): string {
346
343
  }
347
344
 
348
345
  function launchdJobLoaded(label: string): "loaded" | "not-loaded" | "unavailable" {
349
- const domain = launchdDomain();
350
- if (domain == null) return "unavailable";
351
346
  try {
352
- const { exitCode } = Bun.spawnSync(["launchctl", "print", `${domain}/${label}`], { stdout: "ignore", stderr: "ignore", timeout: 10_000 });
347
+ const { exitCode } = Bun.spawnSync(["launchctl", "print", `${launchdDomain()}/${label}`], { stdout: "ignore", stderr: "ignore", timeout: 10_000 });
353
348
  if (exitCode === 0) return "loaded";
354
349
  return exitCode === 113 ? "not-loaded" : "unavailable";
355
350
  } catch {
@@ -387,9 +382,8 @@ function uninstallCheckTimer(live: boolean): UnitOutcome {
387
382
  return "removed";
388
383
  }
389
384
  if (process.platform === "darwin") {
390
- const domain = launchdDomain();
391
385
  const loaded = launchdJobLoaded(LAUNCHD_LABEL);
392
- const deactivated = loaded === "loaded" && domain != null ? run(["launchctl", "bootout", `${domain}/${LAUNCHD_LABEL}`]) : loaded === "not-loaded";
386
+ const deactivated = loaded === "loaded" ? run(["launchctl", "bootout", `${launchdDomain()}/${LAUNCHD_LABEL}`]) : loaded === "not-loaded";
393
387
  removeTimerUnits();
394
388
  return deactivated ? "removed" : "still-loaded";
395
389
  }
@@ -423,7 +417,6 @@ ${launchdEnv()} <key>StandardOutPath</key><string>/dev/null</string>
423
417
  0o644,
424
418
  );
425
419
  const domain = launchdDomain();
426
- if (domain == null) return false;
427
420
  run(["launchctl", "bootout", `${domain}/${LAUNCHD_HUB_LABEL}`]);
428
421
  return run(["launchctl", "bootstrap", domain, plist]) || hubServiceHealthy();
429
422
  }
@@ -463,9 +456,8 @@ function uninstallHubService(live: boolean): UnitOutcome {
463
456
  return "removed";
464
457
  }
465
458
  if (process.platform === "darwin") {
466
- const domain = launchdDomain();
467
459
  const loaded = launchdJobLoaded(LAUNCHD_HUB_LABEL);
468
- const deactivated = loaded === "loaded" && domain != null ? run(["launchctl", "bootout", `${domain}/${LAUNCHD_HUB_LABEL}`]) : loaded === "not-loaded";
460
+ const deactivated = loaded === "loaded" ? run(["launchctl", "bootout", `${launchdDomain()}/${LAUNCHD_HUB_LABEL}`]) : loaded === "not-loaded";
469
461
  removeHubUnits();
470
462
  return deactivated ? "removed" : "still-loaded";
471
463
  }
@@ -562,7 +554,7 @@ export function removePathFromRc(rc: string): boolean {
562
554
  export type UninstallOutcome = { timer: UnitOutcome; hub: UnitOutcome; pathLineRemoved: boolean };
563
555
 
564
556
  export function loginHome(): string {
565
- const cmd = process.platform === "darwin" ? ["id", "-P"] : ["getent", "passwd", String(process.getuid?.() ?? "")];
557
+ const cmd = process.platform === "darwin" ? ["id", "-P"] : ["getent", "passwd", String(userInfo().uid)];
566
558
  const proc = Bun.spawnSync(cmd, { stdout: "pipe", stderr: "ignore", timeout: 10_000 });
567
559
  const home = proc.stdout.toString().trim().split(":").at(-2);
568
560
  if (proc.exitCode !== 0 || home == null || home === "") throw new Error(`cannot read the login home: ${cmd.join(" ")} failed`);
@@ -576,11 +568,11 @@ export function onLoginHome(): boolean {
576
568
  export function uninstallTargets(live: boolean): string[] {
577
569
  const timer =
578
570
  process.platform === "darwin"
579
- ? `${launchdPlist(LAUNCHD_LABEL)}${live ? ` (and launchctl bootout gui/${process.getuid?.() ?? "?"}/${LAUNCHD_LABEL})` : ""}`
571
+ ? `${launchdPlist(LAUNCHD_LABEL)}${live ? ` (and launchctl bootout ${launchdDomain()}/${LAUNCHD_LABEL})` : ""}`
580
572
  : `${join(paths.systemdUserDir, "tokenmaxxing-check.timer")} and .service${live ? " (and systemctl --user disable --now tokenmaxxing-check.timer)" : ""}`;
581
573
  const hub =
582
574
  process.platform === "darwin"
583
- ? `${launchdPlist(LAUNCHD_HUB_LABEL)}${live ? ` (and launchctl bootout gui/${process.getuid?.() ?? "?"}/${LAUNCHD_HUB_LABEL})` : ""}`
575
+ ? `${launchdPlist(LAUNCHD_HUB_LABEL)}${live ? ` (and launchctl bootout ${launchdDomain()}/${LAUNCHD_HUB_LABEL})` : ""}`
584
576
  : `${join(paths.systemdUserDir, HUB_UNIT)}${live ? ` (and systemctl --user disable --now ${HUB_UNIT})` : ""}`;
585
577
  const rc = shellRcPath();
586
578
  return [
@@ -43,24 +43,6 @@ function recordLiveSession(sid: string, current: string): void {
43
43
  writeFileAtomic(sessionFile(sid), JSON.stringify({ flags: session?.flags ?? [], cwd: session?.cwd ?? process.cwd(), current }));
44
44
  }
45
45
 
46
- const DEAD_STATE_ENTRIES = [
47
- "model-usage.json",
48
- "accounts.json.v1-backup",
49
- "codex-accounts.json.v1-backup",
50
- "nextcheck.json",
51
- "usage.json",
52
- "lastswap.json",
53
- "depleted.json",
54
- "codex-lastswap.json",
55
- "creds",
56
- "codex-creds",
57
- "codex-reconcile",
58
- "sample",
59
- "setup-tokens.json",
60
- "setup-token",
61
- "cloud",
62
- ];
63
-
64
46
  const TMP_MARKER = ".tmp.";
65
47
  const TMP_GRACE_MS = 3600 * 1000;
66
48
 
@@ -88,14 +70,7 @@ function tmpSweepDirs(root: string): string[] {
88
70
  return dirs;
89
71
  }
90
72
 
91
- function pruneDeadState(now: number, root: string): void {
92
- for (const name of DEAD_STATE_ENTRIES) {
93
- try {
94
- rmSync(join(paths.home, name), { recursive: true, force: true });
95
- } catch (e) {
96
- log("state.dead_entry_failed", { name, err: errorMessage(e) });
97
- }
98
- }
73
+ function pruneTmpFiles(now: number, root: string): void {
99
74
  for (const dir of tmpSweepDirs(root)) {
100
75
  for (const f of listDir(dir, root)) {
101
76
  if (!f.isFile() || !f.name.includes(TMP_MARKER)) continue;
@@ -115,7 +90,7 @@ export function pruneStaleSessions(now: number): void {
115
90
  } catch {
116
91
  return;
117
92
  }
118
- pruneDeadState(now, root);
93
+ pruneTmpFiles(now, root);
119
94
  const dir = sessionsDir();
120
95
  for (const f of listDir(dir, root)) {
121
96
  const p = join(dir, f.name);
@@ -104,7 +104,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
104
104
  function preflight(): void {
105
105
  const real = resolveBin();
106
106
  const p = Bun.spawnSync([real, "--version"], { stdout: "pipe", stderr: "pipe", timeout: 15_000 });
107
- if (p.exitCode !== 0 || !spec.versionOk(p.stdout?.toString() ?? "")) {
107
+ if (p.exitCode !== 0 || !spec.versionOk(p.stdout.toString())) {
108
108
  throw new Error(`${spec.binName} binary failed verification: ${real}`);
109
109
  }
110
110
  pinBinOverride({ key: spec.binKey, bin: real });
package/src/lib/tty.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export function saveTermios(): string | null {
2
2
  const p = Bun.spawnSync(["/bin/sh", "-c", "stty -g </dev/tty"]);
3
- const s = p.stdout?.toString().trim();
3
+ const s = p.stdout.toString().trim();
4
4
  return s && p.exitCode === 0 ? s : null;
5
5
  }
6
6