tokenmaxxing 1.154.0 → 1.156.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.154.0",
3
+ "version": "1.156.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",
@@ -14,7 +14,7 @@ import { thresholdBars, usableAt } from "../lib/picker.ts";
14
14
  import { clearPresence } from "../lib/presence.ts";
15
15
  import { readLines } from "../lib/proc.ts";
16
16
  import { teeObservation } from "../lib/sample.ts";
17
- import { countdownWait, exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, relayStdout, runPassthrough, SEAT_POLL_MS, SEAT_RETRY_MS, type Say } from "../lib/supervise.ts";
17
+ import { childStdout, countdownWait, exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, relayStdout, runPassthrough, SEAT_POLL_MS, SEAT_RETRY_MS, type Say } from "../lib/supervise.ts";
18
18
  import { saveTermios } from "../lib/tty.ts";
19
19
  import { liveSessionId, loadSessionFlags, pruneStaleSessions, saveSessionFlags, writeRespawnMarker } from "../lib/sessions.ts";
20
20
  import { loadAccounts, loadConfig, readJsonFile, releaseWaitClaim, writeUsage } from "../lib/state.ts";
@@ -584,7 +584,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
584
584
  let overriddenUntil = 0;
585
585
  let wanted: { accountId: string | null; apiKeyId: string | null } | null = null;
586
586
  let refused: string[] = [];
587
- let drainStdout: (() => Promise<void>) | null = null;
587
+ let drainStdout: ((abandoned: () => boolean) => Promise<void>) | null = null;
588
588
  try {
589
589
  while (true) {
590
590
  rmSync(marker, { force: true });
@@ -593,6 +593,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
593
593
  const launched = await withLock(claudePool.lockFile, async () => {
594
594
  if (terminating) return null;
595
595
  const target = resolveLaunch(wanted, gate.launchedAt, model, refused);
596
+ if (target?.account && target.depletedUntil !== undefined) gate.overriddenUntil = Math.max(gate.overriddenUntil, target.depletedUntil);
596
597
  const picked = target?.account ?? null;
597
598
  const key = target?.apiKey ?? null;
598
599
  log("supervisor.launch", { sid, respawns, seat: picked?.id.slice(0, 8) ?? null, key: key?.id, args: launchArgs.join(" "), injected: firstLine !== null });
@@ -602,7 +603,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
602
603
  const extra: "pipe"[] = secret == null ? [] : ["pipe"];
603
604
  const scrubbed = scrubCredEnv(childEnv);
604
605
  const spawned = Bun.spawn([real, ...launchArgs], {
605
- stdio: [relay === null ? "inherit" : "pipe", tee === null ? "inherit" : "pipe", "inherit", ...extra],
606
+ stdio: [relay === null ? "inherit" : "pipe", tee === null ? childStdout() : "pipe", "inherit", ...extra],
606
607
  env: {
607
608
  ...(picked ? { ...scrubbed, CLAUDE_SECURESTORAGE_CONFIG_DIR: storeDirFor(picked.id) } : key ? apiKeyEnv(scrubbed, key) : childEnv),
608
609
  TOKENMAXXING_SUPERVISED: "1",
@@ -615,7 +616,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
615
616
  });
616
617
  child = spawned;
617
618
  if (secret != null) sendApiKey(spawned, secret);
618
- const drain = tee !== null && spawned.stdout instanceof ReadableStream ? relayStdout(spawned.stdout, tee) : null;
619
+ drainStdout = spawned.stdout instanceof ReadableStream ? relayStdout(spawned.stdout, tee ?? undefined) : null;
619
620
  if (relay !== null) relay.attach(spawned.stdin!, firstLine, pendingTranscript);
620
621
  firstLine = null;
621
622
  if (picked || key) {
@@ -631,7 +632,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
631
632
  });
632
633
  }
633
634
  releaseClaimLocked(sid);
634
- return { child: spawned, seat: picked, key, drain, open };
635
+ return { child: spawned, seat: picked, key, drain: drainStdout, open };
635
636
  });
636
637
  if (launched == null) {
637
638
  await releaseClaim();
@@ -659,7 +660,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
659
660
  relay?.detach();
660
661
  },
661
662
  });
662
- await drainStdout?.();
663
+ await drainStdout?.(() => terminating);
663
664
  drainStdout = null;
664
665
  await foldCost(sid, key?.id ?? null, restored);
665
666
 
@@ -729,7 +730,7 @@ export async function runSupervisor(argv: string[]): Promise<number> {
729
730
  return exitStatus(proc);
730
731
  }
731
732
  } catch (e) {
732
- await drainStdout?.();
733
+ await drainStdout?.(() => terminating);
733
734
  const msg = errorMessage(e);
734
735
  const text = msg.startsWith("tokenmaxxing:") ? msg : `tokenmaxxing: ${msg}`;
735
736
  say(`\n\x1b[31m${text}\x1b[0m\n`, text);
package/src/lib/claude.ts CHANGED
@@ -10,7 +10,7 @@ import { withLock } from "./lock.ts";
10
10
  import { errorMessage, log } from "./log.ts";
11
11
  import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential, InvalidGrantError } from "./oauth.ts";
12
12
  import { claudePool, env, paths, seatFromEnv, shortId, storeDirFor } from "./paths.ts";
13
- import { MAX_WAITERS_PER_ACCOUNT, pickBest, pickEarliestReset, pickWaitTarget, thresholdBars, type PickCtx } from "./picker.ts";
13
+ import { MAX_WAITERS_PER_ACCOUNT, pickBest, pickEarliestReset, pickWaitTarget, thresholdBars, type EarliestReset, type PickCtx } from "./picker.ts";
14
14
  import { deletePiStore } from "./piauth.ts";
15
15
  import { livingPresences, writePresence } from "./presence.ts";
16
16
  import { StoreUnusableError, type Observation, type Provider, type ResetClaim, type SampleReport, type SeatBorrow } from "./provider.ts";
@@ -139,7 +139,7 @@ async function storeUsable(a: Account): Promise<boolean> {
139
139
  }
140
140
  }
141
141
 
142
- function seatContext(now: number, model: ModelInfo | null, eligible: (a: Account) => boolean): { candidates: Account[]; ctx: PickCtx; fallback: () => Account | null } {
142
+ function seatContext(now: number, model: ModelInfo | null, eligible: (a: Account) => boolean): { candidates: Account[]; ctx: PickCtx; fallback: () => EarliestReset | null } {
143
143
  const cfg = loadConfig();
144
144
  const bars = thresholdBars(cfg);
145
145
  const idx = loadAccounts(claudePool);
@@ -150,18 +150,17 @@ function seatContext(now: number, model: ModelInfo | null, eligible: (a: Account
150
150
  const ctx: PickCtx = { now, thresholds: bars, currentId: null, families: gatedFamilies(model, cfg.policy.switchModels), seats };
151
151
  const candidates = idx.accounts.filter(eligible);
152
152
  const fallback = () =>
153
- pickWaitTarget(candidates, ctx, seats, MAX_WAITERS_PER_ACCOUNT, now + cfg.policy.maxWaitMs, now + 2 * cfg.policy.maxWaitMs)?.account ??
154
- pickEarliestReset(candidates, ctx)?.account ??
155
- null;
153
+ pickWaitTarget(candidates, ctx, seats, MAX_WAITERS_PER_ACCOUNT, now + cfg.policy.maxWaitMs, now + 2 * cfg.policy.maxWaitMs) ??
154
+ pickEarliestReset(candidates, ctx);
156
155
  return { candidates, ctx, fallback };
157
156
  }
158
157
 
159
158
  export function pickSeat(now: number, model: ModelInfo | null, eligible: (a: Account) => boolean = () => true): Account | null {
160
159
  const { candidates, ctx, fallback } = seatContext(now, model, eligible);
161
- return pickBest(candidates, ctx) ?? fallback();
160
+ return pickBest(candidates, ctx) ?? fallback()?.account ?? null;
162
161
  }
163
162
 
164
- export type LaunchTarget = { account: Account; apiKey: null } | { account: null; apiKey: ApiKey };
163
+ export type LaunchTarget = { account: Account; apiKey: null; depletedUntil?: number } | { account: null; apiKey: ApiKey };
165
164
 
166
165
  export function pickLaunch(now: number, model: ModelInfo | null, refused: readonly string[] = []): LaunchTarget | null {
167
166
  const { candidates, ctx, fallback } = seatContext(now, model, (a) => !refused.includes(a.id));
@@ -169,8 +168,8 @@ export function pickLaunch(now: number, model: ModelInfo | null, refused: readon
169
168
  if (best) return { account: best, apiKey: null };
170
169
  const key = launchApiKey();
171
170
  if (key) return { account: null, apiKey: key };
172
- const account = fallback();
173
- return account ? { account, apiKey: null } : null;
171
+ const wait = fallback();
172
+ return wait ? { account: wait.account, apiKey: null, depletedUntil: wait.availableAt } : null;
174
173
  }
175
174
 
176
175
  export async function borrowClaudeSeat(pid: number, named?: string): Promise<SeatBorrow | { invalid: string }> {
@@ -117,7 +117,7 @@ export function relayStdout(source: ReadableStream<Uint8Array>, onLine?: (line:
117
117
  });
118
118
  return async (abandoned = () => false) => {
119
119
  const exitedAt = Date.now();
120
- while (!ended && (blocked ? !abandoned() : Date.now() - Math.max(readAt, exitedAt) < STDOUT_QUIET_MS)) await Bun.sleep(50);
120
+ while (!ended && !abandoned() && (blocked || Date.now() - Math.max(readAt, exitedAt) < STDOUT_QUIET_MS)) await Bun.sleep(50);
121
121
  };
122
122
  }
123
123