tokenmaxxing 1.41.0 → 1.42.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.41.0",
3
+ "version": "1.42.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/sample.ts CHANGED
@@ -6,11 +6,12 @@ import { writeFileAtomic } from "./atomic.ts";
6
6
  import { readStore } from "./credstore.ts";
7
7
  import { withLock } from "./lock.ts";
8
8
  import { log } from "./log.ts";
9
- import { claudeTierLabel, isDeadCredential } from "./oauth.ts";
10
- import { claudePool, sampleDirFor, storeDirFor } from "./paths.ts";
9
+ import { claudeTierLabel, isAccessTokenExpiring, isDeadCredential } from "./oauth.ts";
10
+ import { claudePool, paths, sampleDirFor, storeDirFor } from "./paths.ts";
11
+ import { seatCounts } from "./presence.ts";
11
12
  import type { Observation } from "./provider.ts";
12
13
  import { loadAccounts, loadUsageSnapshot, saveAccounts } from "./state.ts";
13
- import { mergeWindows, probeUsage, windowsOf } from "./usage.ts";
14
+ import { fetchUsageDirect, mergeWindows, probeUsage, windowsOf } from "./usage.ts";
14
15
  import { UsageWindowsSchema, type Account, type Config } from "./types.ts";
15
16
 
16
17
  const SampleOutcomeSchema = z.discriminatedUnion("ok", [
@@ -93,11 +94,28 @@ export async function sampleOldest(cfg: Config): Promise<void> {
93
94
  log("sample.failed", { account: target.id.slice(0, 8), reason: prepared.reason.slice(0, 200) });
94
95
  return null;
95
96
  }
96
- return { account: target, dir: prepared.dir };
97
+ let token: string | null = null;
98
+ if (!seatCounts(paths.presenceDir).has(target.id)) {
99
+ const creds = await readStore(target.id).catch(() => null);
100
+ if (creds && !isDeadCredential(creds) && !isAccessTokenExpiring(creds)) token = creds.accessToken;
101
+ }
102
+ return { account: target, dir: prepared.dir, token };
97
103
  });
98
104
  if (!reserved) return;
99
105
  const startedAt = Date.now();
100
- const outcome = await runProbe(reserved.account, reserved.dir, { retries: 0 });
106
+ let via = "probe";
107
+ let outcome: SampleOutcome;
108
+ if (reserved.token) {
109
+ const usage = await fetchUsageDirect(reserved.token);
110
+ if (usage) {
111
+ via = "get";
112
+ outcome = { ok: true, usage };
113
+ } else {
114
+ outcome = await runProbe(reserved.account, reserved.dir, { retries: 0 });
115
+ }
116
+ } else {
117
+ outcome = await runProbe(reserved.account, reserved.dir, { retries: 0 });
118
+ }
101
119
  await withLock(claudePool.lockFile, () => {
102
120
  const idx = loadAccounts(claudePool);
103
121
  const stored = idx.accounts.find((a) => a.id === reserved.account.id);
@@ -108,7 +126,7 @@ export async function sampleOldest(cfg: Config): Promise<void> {
108
126
  }
109
127
  log(outcome.ok ? "sample.ok" : "sample.failed", {
110
128
  account: reserved.account.id.slice(0, 8),
111
- ...(outcome.ok ? {} : { reason: outcome.reason.slice(0, 200) }),
129
+ ...(outcome.ok ? { via } : { reason: outcome.reason.slice(0, 200) }),
112
130
  });
113
131
  });
114
132
  }
package/src/lib/usage.ts CHANGED
@@ -2,7 +2,9 @@ import { closeSync, fstatSync, openSync, readSync } from "node:fs";
2
2
  import { delay } from "es-toolkit";
3
3
  import { z } from "zod";
4
4
  import { MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealClaude } from "./claudebin.ts";
5
+ import { http } from "./http.ts";
5
6
  import { log } from "./log.ts";
7
+ import { env } from "./paths.ts";
6
8
  import { JsonTextSchema, RateLimitsStdinSchema, type ModelInfo, type UsageWindow, type UsageWindows, type Window } from "./types.ts";
7
9
 
8
10
  export function normalizeResetsAt(v: unknown): number | null {
@@ -341,10 +343,10 @@ function probeEnv(target: ProbeTarget): Record<string, string> {
341
343
  }
342
344
 
343
345
  export async function probeUsage(target: ProbeTarget, now = Date.now(), opts: { retries?: number } = {}): Promise<UsageWindows | null> {
344
- const env = probeEnv(target);
346
+ const probe = probeEnv(target);
345
347
 
346
348
  for (let attempt = 0; ; attempt++) {
347
- const full = await probeUsageOnce(env, now);
349
+ const full = await probeUsageOnce(probe, now);
348
350
  if (full) return full;
349
351
  if (attempt >= (opts.retries ?? PROBE_RETRY_DELAYS_MS.length)) {
350
352
  log("usage.probe_gave_up", { attempts: attempt + 1 });
@@ -353,3 +355,42 @@ export async function probeUsage(target: ProbeTarget, now = Date.now(), opts: {
353
355
  await delay(PROBE_RETRY_DELAYS_MS[Math.min(attempt, PROBE_RETRY_DELAYS_MS.length - 1)]!);
354
356
  }
355
357
  }
358
+
359
+ const USAGE_URL = env("TOKENMAXXING_OAUTH_USAGE_URL", "https://api.anthropic.com/api/oauth/usage");
360
+ const USAGE_DEADLINE_MS = 10_000;
361
+
362
+ const UsageWindowResponseSchema = z.object({ utilization: z.number(), resets_at: z.number().nullable().optional() });
363
+ const UsageResponseSchema = z.object({
364
+ rate_limits: z
365
+ .object({ five_hour: UsageWindowResponseSchema.nullish(), seven_day: UsageWindowResponseSchema.nullish() })
366
+ .nullish(),
367
+ });
368
+
369
+ export async function fetchUsageDirect(accessToken: string): Promise<UsageWindows | null> {
370
+ let res: Response;
371
+ try {
372
+ res = await http.get(`${USAGE_URL}?at_wall=1&skip_spend=1`, {
373
+ headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
374
+ signal: AbortSignal.timeout(USAGE_DEADLINE_MS),
375
+ });
376
+ } catch (e) {
377
+ log("usage.get_failed", { err: e instanceof Error ? e.message : String(e) });
378
+ return null;
379
+ }
380
+ if (!res.ok) {
381
+ log("usage.get_failed", { status: res.status });
382
+ return null;
383
+ }
384
+ const parsed = UsageResponseSchema.safeParse(JsonTextSchema.safeParse(await res.text()).data);
385
+ const five = parsed.success ? parsed.data.rate_limits?.five_hour : null;
386
+ const seven = parsed.success ? parsed.data.rate_limits?.seven_day : null;
387
+ if (!five || !seven) {
388
+ log("usage.get_incomplete", { ok: parsed.success });
389
+ return null;
390
+ }
391
+ const win = (w: z.infer<typeof UsageWindowResponseSchema>): UsageWindow => ({
392
+ usedPercentage: w.utilization * 100,
393
+ resetsAt: w.resets_at != null ? w.resets_at * 1000 : null,
394
+ });
395
+ return { fiveHour: win(five), sevenDay: win(seven), perModel: {} };
396
+ }