tokenmaxxing 1.69.0 → 1.71.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 +1 -1
- package/src/cli/seat.ts +6 -8
- package/src/entries/supervisor.ts +1 -1
- package/src/lib/claude.ts +1 -8
- package/src/lib/claudebin.ts +1 -1
- package/src/lib/codex.ts +1 -8
- package/src/lib/codexauth.ts +3 -17
- package/src/lib/compact.ts +4 -8
- package/src/lib/install.ts +1 -1
- package/src/lib/keychain.ts +3 -3
- package/src/lib/opencodego.ts +2 -7
- package/src/lib/provider.ts +7 -0
- package/src/lib/sample.ts +3 -4
- package/src/lib/update.ts +4 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.71.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/cli/seat.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { observeCodex } from "../lib/codex.ts";
|
|
1
|
+
import { codexPickCtx, observeCodex } from "../lib/codex.ts";
|
|
2
2
|
import { codexStoreUsable, ensureCodexStoreHome } from "../lib/codexauth.ts";
|
|
3
3
|
import { withLock } from "../lib/lock.ts";
|
|
4
4
|
import { log } from "../lib/log.ts";
|
|
5
5
|
import { codexPaths, codexPool } from "../lib/paths.ts";
|
|
6
|
-
import { livingPresences, writePresence } from "../lib/presence.ts";
|
|
7
|
-
import { isExhausted, pickBest
|
|
6
|
+
import { livingPresences, seatCounts, writePresence } from "../lib/presence.ts";
|
|
7
|
+
import { isExhausted, pickBest } from "../lib/picker.ts";
|
|
8
8
|
import { loadAccounts, loadConfig } from "../lib/state.ts";
|
|
9
9
|
import { emitError } from "./render.ts";
|
|
10
10
|
|
|
@@ -23,10 +23,9 @@ export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Prom
|
|
|
23
23
|
.map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false })),
|
|
24
24
|
);
|
|
25
25
|
const granted = await withLock(codexPool.lockFile, (): { store: string; id: string; reused: boolean } | { denied: string } | null => {
|
|
26
|
-
const ctx
|
|
26
|
+
const ctx = codexPickCtx(now, null);
|
|
27
27
|
const idx = loadAccounts(codexPool);
|
|
28
|
-
const
|
|
29
|
-
const held = living.find((p) => p.id === seatId);
|
|
28
|
+
const held = livingPresences(codexPaths.presenceDir).find((p) => p.id === seatId);
|
|
30
29
|
const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
|
|
31
30
|
if (heldAccount) {
|
|
32
31
|
if (heldAccount.needsReauth === true) {
|
|
@@ -37,8 +36,7 @@ export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Prom
|
|
|
37
36
|
}
|
|
38
37
|
return { store: ensureCodexStoreHome(heldAccount.id), id: heldAccount.id, reused: true };
|
|
39
38
|
}
|
|
40
|
-
const present =
|
|
41
|
-
for (const p of living) present.set(p.accountId, (present.get(p.accountId) ?? 0) + 1);
|
|
39
|
+
const present = seatCounts(codexPaths.presenceDir);
|
|
42
40
|
const usable = idx.accounts.filter(
|
|
43
41
|
(a) => a.needsReauth !== true && !isExhausted(a, ctx) && !present.has(a.id) && codexStoreUsable(a.id)
|
|
44
42
|
);
|
|
@@ -169,7 +169,7 @@ function latestSessionForCwd(): string | null {
|
|
|
169
169
|
.filter((f) => f.endsWith(".jsonl"))
|
|
170
170
|
.map((f) => ({ f, m: statSync(join(projDir, f)).mtimeMs }));
|
|
171
171
|
const newest = maxBy(files, (x) => x.m);
|
|
172
|
-
return newest ? newest.f.
|
|
172
|
+
return newest ? basename(newest.f, ".jsonl") : null;
|
|
173
173
|
} catch {
|
|
174
174
|
return null;
|
|
175
175
|
}
|
package/src/lib/claude.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential
|
|
|
11
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
|
-
import type
|
|
14
|
+
import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
|
|
15
15
|
import { foldTee, sampleAccountUsage, teeObservation, usageBlockedUntil } from "./sample.ts";
|
|
16
16
|
import { clearUsageSnapshot, loadAccounts, loadConfig, loadUsageSnapshot, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
17
17
|
import { saveTermios, restoreTermios } from "./tty.ts";
|
|
@@ -19,13 +19,6 @@ import { fetchUsageDirect, gatedFamilies, mergeWindows, modelFromFlag, scrubCred
|
|
|
19
19
|
import { CredentialBlobSchema, JsonTextSchema, OAuthAccountSchema, type Account, type Config, type ModelInfo } from "./types.ts";
|
|
20
20
|
import { c } from "../cli/render.ts";
|
|
21
21
|
|
|
22
|
-
class StoreUnusableError extends Error {
|
|
23
|
-
constructor(message: string) {
|
|
24
|
-
super(message);
|
|
25
|
-
this.name = "StoreUnusableError";
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
22
|
function liveId(): string | null {
|
|
30
23
|
return seatFromEnv(loadAccounts(claudePool).accounts.map((a) => a.id));
|
|
31
24
|
}
|
package/src/lib/claudebin.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function wrapperEntryRateTripped(now: number): boolean {
|
|
|
36
36
|
return entries.length > WRAP_RATE_MAX;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function realpathOrNull(p: string): string | null {
|
|
39
|
+
export function realpathOrNull(p: string): string | null {
|
|
40
40
|
try {
|
|
41
41
|
return realpathSync(p);
|
|
42
42
|
} catch {
|
package/src/lib/codex.ts
CHANGED
|
@@ -12,19 +12,12 @@ import { withLock } from "./lock.ts";
|
|
|
12
12
|
import { errorMessage, log } from "./log.ts";
|
|
13
13
|
import { codexPaths, codexPool, codexSeatFromEnv } from "./paths.ts";
|
|
14
14
|
import { isExhausted, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
15
|
-
import type
|
|
15
|
+
import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
|
|
16
16
|
import { loadAccounts, loadConfig, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
17
17
|
import { restoreTermios, saveTermios } from "./tty.ts";
|
|
18
18
|
import type { Account, CodexAuthJson, CodexUsage, Config } from "./types.ts";
|
|
19
19
|
import { c } from "../cli/render.ts";
|
|
20
20
|
|
|
21
|
-
class StoreUnusableError extends Error {
|
|
22
|
-
constructor(message: string) {
|
|
23
|
-
super(message);
|
|
24
|
-
this.name = "StoreUnusableError";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
function liveId(): string | null {
|
|
29
22
|
return codexSeatFromEnv(loadAccounts(codexPool).accounts.map((a) => a.id));
|
|
30
23
|
}
|
package/src/lib/codexauth.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
@@ -45,25 +45,11 @@ export function ensureCodexStoreHome(accountId: string): string {
|
|
|
45
45
|
mkdirSync(store, { recursive: true });
|
|
46
46
|
mkdirSync(codexPaths.home, { recursive: true });
|
|
47
47
|
mkdirSync(join(codexPaths.home, "sessions"), { recursive: true });
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
names = readdirSync(codexPaths.home);
|
|
51
|
-
} catch (e) {
|
|
52
|
-
if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") return store;
|
|
53
|
-
throw e;
|
|
54
|
-
}
|
|
55
|
-
for (const name of names) {
|
|
48
|
+
for (const name of readdirSync(codexPaths.home)) {
|
|
56
49
|
if (name === "auth.json") continue;
|
|
57
50
|
const target = join(codexPaths.home, name);
|
|
58
51
|
const link = join(store, name);
|
|
59
|
-
if (existsSync(link))
|
|
60
|
-
try {
|
|
61
|
-
if (lstatSync(link).isSymbolicLink()) continue;
|
|
62
|
-
} catch {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
52
|
+
if (existsSync(link)) continue;
|
|
67
53
|
try {
|
|
68
54
|
symlinkSync(target, link);
|
|
69
55
|
} catch (e) {
|
package/src/lib/compact.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { delay } from "es-toolkit";
|
|
4
2
|
import { z } from "zod";
|
|
3
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
5
4
|
import { errorMessage, log } from "./log.ts";
|
|
6
5
|
import { readLines } from "./proc.ts";
|
|
7
6
|
import { JsonTextSchema } from "./types.ts";
|
|
@@ -20,7 +19,7 @@ export async function compactClaudeSession(input: { real: string; sid: string; e
|
|
|
20
19
|
timeout: CLAUDE_COMPACT_KILL_MS,
|
|
21
20
|
killSignal: "SIGKILL",
|
|
22
21
|
});
|
|
23
|
-
const reads = Promise.all([
|
|
22
|
+
const reads = Promise.all([p.stdout.text(), p.stderr.text()]);
|
|
24
23
|
const settled = await Promise.race([reads, p.exited.then(() => delay(PIPE_GRACE_MS)).then(() => null)]);
|
|
25
24
|
await p.exited;
|
|
26
25
|
if (settled === null) return { ok: false, reason: "output pipes still open after child exit (leaked descendant)" };
|
|
@@ -48,9 +47,6 @@ const INIT_ID = 1;
|
|
|
48
47
|
const RESUME_ID = 2;
|
|
49
48
|
const COMPACT_ID = 3;
|
|
50
49
|
|
|
51
|
-
const packageVersion = (): string =>
|
|
52
|
-
z.object({ version: z.string().min(1) }).parse(JSON.parse(readFileSync(join(import.meta.dir, "..", "..", "package.json"), "utf8"))).version;
|
|
53
|
-
|
|
54
50
|
export async function compactCodexThread(input: { real: string; threadId: string; env: Record<string, string | undefined> }): Promise<CompactOutcome> {
|
|
55
51
|
const p = Bun.spawn([input.real, "app-server"], {
|
|
56
52
|
env: input.env,
|
|
@@ -64,7 +60,7 @@ export async function compactCodexThread(input: { real: string; threadId: string
|
|
|
64
60
|
p.stdin.write(`${JSON.stringify(msg)}\n`);
|
|
65
61
|
p.stdin.flush();
|
|
66
62
|
};
|
|
67
|
-
const stderrText =
|
|
63
|
+
const stderrText = p.stderr.text();
|
|
68
64
|
|
|
69
65
|
const outcome = await new Promise<CompactOutcome>((resolve) => {
|
|
70
66
|
let finished = false;
|
|
@@ -109,7 +105,7 @@ export async function compactCodexThread(input: { real: string; threadId: string
|
|
|
109
105
|
const err = (await stderrText).trim().slice(0, 200);
|
|
110
106
|
finish({ ok: false, reason: p.exitCode === null ? `app-server killed after ${CODEX_COMPACT_KILL_MS / 1000}s` : `app-server exited ${p.exitCode}: ${err}` });
|
|
111
107
|
})().catch((e) => finish({ ok: false, reason: errorMessage(e) }));
|
|
112
|
-
send({ id: INIT_ID, method: "initialize", params: { clientInfo: { name: "tokenmaxxing", version:
|
|
108
|
+
send({ id: INIT_ID, method: "initialize", params: { clientInfo: { name: "tokenmaxxing", version: pkg.version } } });
|
|
113
109
|
});
|
|
114
110
|
|
|
115
111
|
try {
|
package/src/lib/install.ts
CHANGED
|
@@ -171,7 +171,7 @@ export function codexStopHookGroupIndex(): number | null {
|
|
|
171
171
|
} catch {
|
|
172
172
|
return null;
|
|
173
173
|
}
|
|
174
|
-
const idx = parsed.hooks.Stop.findIndex((group) => group.hooks.some((hook) => (hook.command ?? ""
|
|
174
|
+
const idx = parsed.hooks.Stop.findIndex((group) => group.hooks.some((hook) => isOurHookCommand(hook.command ?? "", CODEX_STOP_HOOK_SUBCOMMAND)));
|
|
175
175
|
return idx >= 0 ? idx : null;
|
|
176
176
|
}
|
|
177
177
|
|
package/src/lib/keychain.ts
CHANGED
|
@@ -16,7 +16,7 @@ export async function readItem(t: KeychainTarget): Promise<string | null> {
|
|
|
16
16
|
stdout: "pipe",
|
|
17
17
|
stderr: "pipe",
|
|
18
18
|
});
|
|
19
|
-
const [out, err] = await Promise.all([
|
|
19
|
+
const [out, err] = await Promise.all([p.stdout.text(), p.stderr.text()]);
|
|
20
20
|
await p.exited;
|
|
21
21
|
if (p.exitCode === 44) return null;
|
|
22
22
|
if (p.exitCode !== 0) {
|
|
@@ -38,7 +38,7 @@ async function writeViaInteractive(encodedLine: Uint8Array): Promise<void> {
|
|
|
38
38
|
stdout: "ignore",
|
|
39
39
|
stderr: "pipe",
|
|
40
40
|
});
|
|
41
|
-
const err = await
|
|
41
|
+
const err = await p.stderr.text();
|
|
42
42
|
await p.exited;
|
|
43
43
|
if (p.exitCode !== 0) throw new Error(`keychain write (interactive) failed (exit ${p.exitCode}): ${err.trim()}`);
|
|
44
44
|
}
|
|
@@ -48,7 +48,7 @@ async function writeViaArgv(t: KeychainTarget, secret: string): Promise<void> {
|
|
|
48
48
|
stdout: "ignore",
|
|
49
49
|
stderr: "pipe",
|
|
50
50
|
});
|
|
51
|
-
const err = await
|
|
51
|
+
const err = await p.stderr.text();
|
|
52
52
|
await p.exited;
|
|
53
53
|
if (p.exitCode !== 0) throw new Error(`keychain write (argv) failed (exit ${p.exitCode}): ${err.trim()}`);
|
|
54
54
|
}
|
package/src/lib/opencodego.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
6
|
-
import { opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
|
|
5
|
+
import { env, HOME, opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
|
|
7
6
|
import type { Provider } from "./provider.ts";
|
|
8
7
|
import { type Harvest } from "./state.ts";
|
|
9
8
|
import { statusOnlyProvider, type AuthEntry } from "./statusonly.ts";
|
|
@@ -12,12 +11,8 @@ import { c } from "../cli/render.ts";
|
|
|
12
11
|
const OpencodeAuthEntrySchema = z.looseObject({ type: z.string(), key: z.string().optional() });
|
|
13
12
|
const OpencodeAuthFileSchema = z.record(z.string(), z.unknown());
|
|
14
13
|
|
|
15
|
-
function dataHome(): string {
|
|
16
|
-
return process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME.length > 0 ? process.env.XDG_DATA_HOME : join(homedir(), ".local", "share");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
function liveAuthPath(): string {
|
|
20
|
-
return join(
|
|
15
|
+
return join(env("XDG_DATA_HOME", join(HOME, ".local", "share")), "opencode", "auth.json");
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
function idOfKey(key: string): string {
|
package/src/lib/provider.ts
CHANGED
|
@@ -8,6 +8,13 @@ export type SampleReport = { ok: true; source: "statusline" | "probe" } | { ok:
|
|
|
8
8
|
|
|
9
9
|
export type SwapFailure = "dead-grant" | "skip" | "fatal";
|
|
10
10
|
|
|
11
|
+
export class StoreUnusableError extends Error {
|
|
12
|
+
constructor(message: string) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "StoreUnusableError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export type Provider = {
|
|
12
19
|
name: "claude" | "codex" | "grok" | "opencode-go";
|
|
13
20
|
flag: string;
|
package/src/lib/sample.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Account, Config, UsageWindows } from "./types.ts";
|
|
|
11
11
|
|
|
12
12
|
export type SampleOutcome = { ok: true; usage: UsageWindows; via: "get" } | { ok: false; reason: string; retryAt?: number };
|
|
13
13
|
|
|
14
|
-
type PreparedSample = { ok: true; token: string
|
|
14
|
+
type PreparedSample = { ok: true; token: string } | { ok: false; reason: string };
|
|
15
15
|
|
|
16
16
|
export function teeObservation(account: Account): Observation | null {
|
|
17
17
|
const stored = account.lastUsageAt != null ? { windows: account.windows, at: account.lastUsageAt } : null;
|
|
@@ -56,8 +56,7 @@ function rateLimitedReason(retryAt: number, now: number): string {
|
|
|
56
56
|
return `the usage endpoint rate limited this account, next read in ${Math.ceil((retryAt - now) / 60_000)}m`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export async function runSample(account: Account, token: string
|
|
60
|
-
if (!token) return { ok: false, reason: "no usable access token in the store - run `tokenmaxxing auth`" };
|
|
59
|
+
export async function runSample(account: Account, token: string): Promise<SampleOutcome> {
|
|
61
60
|
const read = await fetchUsageDirect(token);
|
|
62
61
|
if (read.ok) return { ok: true, usage: read.usage, via: "get" };
|
|
63
62
|
if (read.retryAt == null) return { ok: false, reason: "usage read failed (see log)" };
|
|
@@ -91,7 +90,7 @@ export async function sampleOldest(cfg: Config): Promise<void> {
|
|
|
91
90
|
if (dirty) saveAccounts(claudePool, idx);
|
|
92
91
|
return [];
|
|
93
92
|
}
|
|
94
|
-
const batch: { account: Account; token: string
|
|
93
|
+
const batch: { account: Account; token: string }[] = [];
|
|
95
94
|
for (const target of stale) {
|
|
96
95
|
target.lastProbeAt = now;
|
|
97
96
|
const prepared = await prepareSample(target);
|
package/src/lib/update.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { existsSync, lstatSync, readFileSync
|
|
1
|
+
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
5
|
+
import { realpathOrNull } from "./claudebin.ts";
|
|
5
6
|
import { http } from "./http.ts";
|
|
6
7
|
import { isNixPackaged } from "./install.ts";
|
|
7
8
|
import { withLock } from "./lock.ts";
|
|
@@ -29,14 +30,6 @@ function registry(): { base: URL; authorization: string | null } {
|
|
|
29
30
|
return { base, authorization };
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function realOrNull(path: string): string | null {
|
|
33
|
-
try {
|
|
34
|
-
return realpathSync(path);
|
|
35
|
-
} catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
33
|
function globalRootCandidates(): string[] {
|
|
41
34
|
const globalDir = optionalEnv("BUN_INSTALL_GLOBAL_DIR");
|
|
42
35
|
const bunInstall = optionalEnv("BUN_INSTALL");
|
|
@@ -63,12 +56,12 @@ function registryPackageDir(root: string): string | null {
|
|
|
63
56
|
|
|
64
57
|
function detectGlobalRoot(): string | null {
|
|
65
58
|
if (isNixPackaged()) return null;
|
|
66
|
-
const entry =
|
|
59
|
+
const entry = realpathOrNull(Bun.main);
|
|
67
60
|
if (entry == null) return null;
|
|
68
61
|
return (
|
|
69
62
|
globalRootCandidates().find((root) => {
|
|
70
63
|
const dir = registryPackageDir(root);
|
|
71
|
-
return dir != null && entry ===
|
|
64
|
+
return dir != null && entry === realpathOrNull(join(dir, "src", "main.ts"));
|
|
72
65
|
}) ?? null
|
|
73
66
|
);
|
|
74
67
|
}
|