tokenmaxxing 1.62.0 → 1.63.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenmaxxing",
3
- "version": "1.62.0",
3
+ "version": "1.63.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",
package/src/lib/claude.ts CHANGED
@@ -8,7 +8,7 @@ import { ensurePathInRc, installSupervisor, managedShellRcSkipLines, shellRcPath
8
8
  import { withLock } from "./lock.ts";
9
9
  import { errorMessage, log } from "./log.ts";
10
10
  import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential, InvalidGrantError } from "./oauth.ts";
11
- import { claudePool, env, paths, sampleDirFor, seatFromEnv, storeDirFor } from "./paths.ts";
11
+ import { claudePool, env, paths, seatFromEnv, storeDirFor } from "./paths.ts";
12
12
  import { pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
13
13
  import { seatCounts } from "./presence.ts";
14
14
  import type { Observation, Provider, SampleReport } from "./provider.ts";
@@ -138,7 +138,6 @@ async function removeCredentials(a: Account): Promise<void> {
138
138
  await deleteItem(storeTarget(a.id));
139
139
  rmSync(storeDirFor(a.id), { recursive: true, force: true });
140
140
  rmSync(`${storeDirFor(a.id)}.lock`, { recursive: true, force: true });
141
- for (const dir of [sampleDirFor(a.id), sampleDirFor(a.id, "-tick")]) rmSync(dir, { recursive: true, force: true });
142
141
  clearUsageSnapshot(a.id);
143
142
  const setupTokens = loadSetupTokens();
144
143
  if (setupTokens.tokens.some((t) => t.accountUuid === a.id)) {
package/src/lib/paths.ts CHANGED
@@ -26,10 +26,10 @@ export const paths = {
26
26
  supervisorLink: join(TM_HOME, "bin", "claude"),
27
27
  logFile: join(TM_HOME, "tokenmaxxing.log"),
28
28
  onboardDir: join(TM_HOME, "onboard"),
29
- sampleDir: join(TM_HOME, "sample"),
30
29
  storesDir: join(TM_HOME, "stores"),
31
30
  setupTokensJson: join(TM_HOME, "setup-tokens.json"),
32
31
  setupTokenDir: join(TM_HOME, "setup-token"),
32
+ cloudDir: join(TM_HOME, "cloud"),
33
33
  cloudSessionsJson: join(TM_HOME, "cloud", "sessions.json"),
34
34
  cloudWalledJson: join(TM_HOME, "cloud", "walled.json"),
35
35
  cloudLockFile: join(TM_HOME, "cloud", "lock"),
@@ -139,10 +139,6 @@ export function storeDirFor(accountId: string): string {
139
139
  return join(paths.storesDir, shortId(accountId));
140
140
  }
141
141
 
142
- export function sampleDirFor(accountId: string, suffix = ""): string {
143
- return join(paths.sampleDir, `${shortId(accountId)}${suffix}`);
144
- }
145
-
146
142
  export function usageJsonFor(accountId: string): string {
147
143
  return join(paths.usageDir, `${shortId(accountId)}.json`);
148
144
  }
@@ -1,7 +1,8 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
2
- import { join } from "node:path";
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, type Dirent } from "node:fs";
2
+ import { join, sep } from "node:path";
3
3
  import { z } from "zod";
4
- import { paths } from "./paths.ts";
4
+ import { errorMessage, log } from "./log.ts";
5
+ import { codexPaths, grokPaths, opencodeGoPaths, paths } from "./paths.ts";
5
6
  import { writeFileAtomic } from "./atomic.ts";
6
7
  import type { RespawnMarkerSchema } from "./types.ts";
7
8
 
@@ -9,12 +10,14 @@ const SessionSchema = z.object({ flags: z.array(z.string()), cwd: z.string() });
9
10
 
10
11
  const SESSION_RETENTION_MS = 30 * 24 * 3600 * 1000;
11
12
 
13
+ const sessionsDir = (): string => join(paths.home, "sessions");
14
+
12
15
  function sessionFile(sid: string): string {
13
- return join(paths.home, "sessions", `${sid}.json`);
16
+ return join(sessionsDir(), `${sid}.json`);
14
17
  }
15
18
 
16
19
  export function saveSessionFlags(sid: string, flags: string[], cwd: string): void {
17
- mkdirSync(join(paths.home, "sessions"), { recursive: true });
20
+ mkdirSync(sessionsDir(), { recursive: true });
18
21
  writeFileAtomic(sessionFile(sid), JSON.stringify({ flags, cwd }));
19
22
  }
20
23
 
@@ -24,23 +27,79 @@ export function loadSessionFlags(sid: string): string[] | null {
24
27
  return SessionSchema.parse(JSON.parse(readFileSync(f, "utf8"))).flags;
25
28
  }
26
29
 
27
- const DEAD_STATE_FILES = ["model-usage.json", "accounts.json.v1-backup", "codex-accounts.json.v1-backup"];
30
+ const DEAD_STATE_ENTRIES = [
31
+ "model-usage.json",
32
+ "accounts.json.v1-backup",
33
+ "codex-accounts.json.v1-backup",
34
+ "nextcheck.json",
35
+ "usage.json",
36
+ "lastswap.json",
37
+ "depleted.json",
38
+ "codex-lastswap.json",
39
+ "creds",
40
+ "codex-creds",
41
+ "codex-reconcile",
42
+ "sample",
43
+ ];
44
+
45
+ const TMP_MARKER = ".tmp.";
46
+ const TMP_GRACE_MS = 3600 * 1000;
47
+
48
+ const STORE_PARENTS = [paths.storesDir, codexPaths.storesDir, grokPaths.storesDir, opencodeGoPaths.storesDir];
49
+
50
+ function listDir(dir: string, root: string): Dirent[] {
51
+ if (!existsSync(dir)) return [];
52
+ try {
53
+ const resolved = realpathSync(dir);
54
+ if (resolved !== root && !resolved.startsWith(root + sep)) return [];
55
+ return readdirSync(dir, { withFileTypes: true });
56
+ } catch (e) {
57
+ log("state.sweep_unreadable", { dir, err: errorMessage(e) });
58
+ return [];
59
+ }
60
+ }
61
+
62
+ function tmpSweepDirs(root: string): string[] {
63
+ const dirs = [paths.home, paths.usageDir, paths.presenceDir, paths.respawnDir, sessionsDir(), paths.binDir, paths.cloudDir, codexPaths.presenceDir, codexPaths.respawnDir, codexPaths.onboardDir];
64
+ for (const parent of STORE_PARENTS) {
65
+ for (const child of listDir(parent, root)) {
66
+ if (child.isDirectory()) dirs.push(join(parent, child.name));
67
+ }
68
+ }
69
+ return dirs;
70
+ }
28
71
 
29
- function pruneDeadState(): void {
30
- for (const name of DEAD_STATE_FILES) {
72
+ function pruneDeadState(now: number, root: string): void {
73
+ for (const name of DEAD_STATE_ENTRIES) {
31
74
  try {
32
- rmSync(join(paths.home, name), { force: true });
33
- } catch {
75
+ rmSync(join(paths.home, name), { recursive: true, force: true });
76
+ } catch (e) {
77
+ log("state.dead_entry_failed", { name, err: errorMessage(e) });
78
+ }
79
+ }
80
+ for (const dir of tmpSweepDirs(root)) {
81
+ for (const f of listDir(dir, root)) {
82
+ if (!f.isFile() || !f.name.includes(TMP_MARKER)) continue;
83
+ const p = join(dir, f.name);
84
+ try {
85
+ if (now - statSync(p).mtimeMs > TMP_GRACE_MS) rmSync(p, { force: true });
86
+ } catch {
87
+ }
34
88
  }
35
89
  }
36
90
  }
37
91
 
38
92
  export function pruneStaleSessions(now: number): void {
39
- pruneDeadState();
40
- const dir = join(paths.home, "sessions");
41
- if (!existsSync(dir)) return;
42
- for (const f of readdirSync(dir)) {
43
- const p = join(dir, f);
93
+ let root: string;
94
+ try {
95
+ root = realpathSync(paths.home);
96
+ } catch {
97
+ return;
98
+ }
99
+ pruneDeadState(now, root);
100
+ const dir = sessionsDir();
101
+ for (const f of listDir(dir, root)) {
102
+ const p = join(dir, f.name);
44
103
  try {
45
104
  if (now - statSync(p).mtimeMs > SESSION_RETENTION_MS) rmSync(p, { force: true });
46
105
  } catch {