tokenmaxxing 1.72.0 → 1.74.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.72.0",
3
+ "version": "1.74.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",
@@ -1,5 +1,4 @@
1
1
  import { join } from "node:path";
2
- import { mkdirSync } from "node:fs";
3
2
  import type { z } from "zod";
4
3
  import { codexPaths, optionalEnv } from "../lib/paths.ts";
5
4
  import { writeFileAtomic } from "../lib/atomic.ts";
@@ -50,7 +49,6 @@ async function handleCodexStop(rawStdin: string): Promise<void> {
50
49
  await compactBeforeMove({ sessionId, now });
51
50
  const decision = await evaluateAndMaybeSwap(codex, now);
52
51
  if (decision.swapped && decision.account) {
53
- mkdirSync(codexPaths.respawnDir, { recursive: true });
54
52
  const payload: z.infer<typeof CodexRespawnMarkerSchema> = { accountId: decision.account.id, sessionId, ts: Date.now() };
55
53
  writeFileAtomic(join(codexPaths.respawnDir, supervisorId), JSON.stringify(payload));
56
54
  log("codexstop.marker", { supervisorId: supervisorId.slice(0, 8) });
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, realpathSync, statSync } from "node:fs";
1
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { uniq } from "es-toolkit";
4
4
  import { z } from "zod";
@@ -30,7 +30,6 @@ export function wrapperEntryRateTripped(now: number): boolean {
30
30
  entries = entries.filter((t) => now - t < WRAP_RATE_WINDOW_MS);
31
31
  entries.push(now);
32
32
  try {
33
- mkdirSync(paths.home, { recursive: true });
34
33
  writeFileAtomic(file, JSON.stringify({ entries }));
35
34
  } catch { }
36
35
  return entries.length > WRAP_RATE_MAX;
package/src/lib/codex.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
1
+ import { existsSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { writeFileAtomic } from "./atomic.ts";
4
4
  import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV } from "./claudebin.ts";
@@ -116,7 +116,6 @@ async function login(): Promise<Harvest | null> {
116
116
  const real = resolveRealCodex();
117
117
  const onboardDir = codexPaths.onboardDir;
118
118
  rmSync(onboardDir, { recursive: true, force: true });
119
- mkdirSync(onboardDir, { recursive: true });
120
119
  writeFileAtomic(join(onboardDir, "config.toml"), 'cli_auth_credentials_store = "file"\n');
121
120
 
122
121
  const savedTermios = saveTermios();
@@ -32,7 +32,6 @@ export function codexStoreUsable(accountId: string): boolean {
32
32
  }
33
33
 
34
34
  export function writeCodexStoreAuth(accountId: string, auth: CodexAuthJson): void {
35
- mkdirSync(codexStoreDirFor(accountId), { recursive: true });
36
35
  writeFileAtomic(codexAuthJsonFor(accountId), JSON.stringify(CodexAuthJsonSchema.parse(auth), null, 2), 0o600);
37
36
  }
38
37
 
package/src/lib/grok.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdirSync, readFileSync } from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { writeFileAtomic } from "./atomic.ts";
@@ -31,7 +31,6 @@ function harvestOf(id: string, key: string, entry: GrokAuthEntry): Harvest {
31
31
  tier: null,
32
32
  sample: null,
33
33
  park: async () => {
34
- mkdirSync(grokStoreDirFor(id), { recursive: true });
35
34
  writeFileAtomic(grokAuthJsonFor(id), JSON.stringify({ [key]: entry }, null, 2), 0o600);
36
35
  },
37
36
  };
@@ -1,4 +1,4 @@
1
- import { accessSync, appendFileSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
1
+ import { accessSync, appendFileSync, constants, existsSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
2
2
  import { userInfo } from "node:os";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
4
  import { escape } from "es-toolkit";
@@ -89,7 +89,6 @@ exec ${JSON.stringify(bun)} --no-env-file run ${JSON.stringify(entry)} "$@"
89
89
  }
90
90
 
91
91
  export function installSupervisor(): InstallOutcome {
92
- mkdirSync(paths.binDir, { recursive: true });
93
92
  const target = installedBin();
94
93
  const entry = realpathSync(Bun.main);
95
94
  if (isNixPackaged()) {
@@ -147,7 +146,6 @@ export function installCodexStopHook(): void {
147
146
  ],
148
147
  },
149
148
  };
150
- mkdirSync(codexPaths.home, { recursive: true });
151
149
  writeFileAtomic(codexPaths.hooksJson, JSON.stringify(next, null, 2) + "\n");
152
150
  }
153
151
 
@@ -198,7 +196,6 @@ export function codexSupervisorLink(): string {
198
196
  }
199
197
 
200
198
  export function installCodexSupervisor(): void {
201
- mkdirSync(paths.binDir, { recursive: true });
202
199
  writeFileAtomic(codexSupervisorLink(), `#!/bin/sh\nexec ${JSON.stringify(installedBin())} __supervise-codex "$@"\n`, 0o755);
203
200
  installCodexStopHook();
204
201
  }
@@ -1,4 +1,4 @@
1
- import { mkdirSync, readFileSync } from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { writeFileAtomic } from "./atomic.ts";
@@ -29,7 +29,6 @@ function harvestOf(key: string): Harvest {
29
29
  tier: "go",
30
30
  sample: null,
31
31
  park: async () => {
32
- mkdirSync(opencodeGoStoreDirFor(id), { recursive: true });
33
32
  writeFileAtomic(opencodeGoAuthJsonFor(id), JSON.stringify({ "opencode-go": { type: "api", key } }, null, 2), 0o600);
34
33
  },
35
34
  };
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { writeFileAtomic } from "./atomic.ts";
@@ -14,7 +14,6 @@ const PresenceSchema = z.object({
14
14
  export function writePresence(input: { dir: string; id: string; accountId: string; pid: number }): void {
15
15
  const startedAt = pidStartTime(input.pid);
16
16
  if (startedAt == null) throw new Error(`could not read pid ${input.pid}'s start time (ps lstart) - refusing to write an unverifiable presence file`);
17
- mkdirSync(input.dir, { recursive: true });
18
17
  writeFileAtomic(join(input.dir, input.id), JSON.stringify(PresenceSchema.parse({ accountId: input.accountId, pid: input.pid, startedAt })));
19
18
  }
20
19
 
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, type Dirent } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, type Dirent } from "node:fs";
2
2
  import { basename, join, sep } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { errorMessage, log } from "./log.ts";
@@ -19,7 +19,6 @@ function sessionFile(sid: string): string {
19
19
  }
20
20
 
21
21
  export function saveSessionFlags(sid: string, flags: string[], cwd: string): void {
22
- mkdirSync(sessionsDir(), { recursive: true });
23
22
  writeFileAtomic(sessionFile(sid), JSON.stringify({ flags, cwd }));
24
23
  }
25
24
 
@@ -39,28 +38,9 @@ export function liveSessionId(sid: string): string {
39
38
 
40
39
  function recordLiveSession(sid: string, current: string): void {
41
40
  const session = loadSession(sid);
42
- mkdirSync(sessionsDir(), { recursive: true });
43
41
  writeFileAtomic(sessionFile(sid), JSON.stringify({ flags: session?.flags ?? [], cwd: session?.cwd ?? process.cwd(), current }));
44
42
  }
45
43
 
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
44
  const TMP_MARKER = ".tmp.";
65
45
  const TMP_GRACE_MS = 3600 * 1000;
66
46
 
@@ -88,14 +68,7 @@ function tmpSweepDirs(root: string): string[] {
88
68
  return dirs;
89
69
  }
90
70
 
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
- }
71
+ function pruneTmpFiles(now: number, root: string): void {
99
72
  for (const dir of tmpSweepDirs(root)) {
100
73
  for (const f of listDir(dir, root)) {
101
74
  if (!f.isFile() || !f.name.includes(TMP_MARKER)) continue;
@@ -115,7 +88,7 @@ export function pruneStaleSessions(now: number): void {
115
88
  } catch {
116
89
  return;
117
90
  }
118
- pruneDeadState(now, root);
91
+ pruneTmpFiles(now, root);
119
92
  const dir = sessionsDir();
120
93
  for (const f of listDir(dir, root)) {
121
94
  const p = join(dir, f.name);
@@ -151,7 +124,6 @@ export function adoptLiveSession(session: SupervisedSession, stdinSid: string |
151
124
  }
152
125
 
153
126
  export function writeRespawnMarker(input: { session: SupervisedSession; accountId: string; waitUntil: number; compact: boolean }): void {
154
- mkdirSync(paths.respawnDir, { recursive: true });
155
127
  const payload: z.infer<typeof RespawnMarkerSchema> = {
156
128
  accountId: input.accountId,
157
129
  ts: Date.now(),