tokenmaxxing 1.112.0 → 1.113.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/lib/claude.ts +3 -3
- package/src/lib/presence.ts +14 -9
- package/src/lib/proc.ts +11 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.113.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
|
@@ -24,8 +24,8 @@ function liveId(): string | null {
|
|
|
24
24
|
return seatFromEnv(loadAccounts(claudePool).accounts.map((a) => a.id));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function presence(): Map<string, number> {
|
|
28
|
-
const seats = new Map(
|
|
27
|
+
function presence(living = livingPresences(paths.presenceDir)): Map<string, number> {
|
|
28
|
+
const seats = new Map(living.map((s) => [s.id, s.accountId]));
|
|
29
29
|
for (const claim of liveWaitClaims(Date.now())) seats.set(claim.sessionId, claim.accountId);
|
|
30
30
|
return new Map(Object.entries(countBy([...seats.values()], (id) => id)));
|
|
31
31
|
}
|
|
@@ -165,7 +165,7 @@ export async function borrowClaudeSeat(pid: number): Promise<SeatBorrow> {
|
|
|
165
165
|
}
|
|
166
166
|
const open = idx.accounts.filter((a) => a.needsReauth !== true);
|
|
167
167
|
const usable = await Promise.all(open.map(storeUsable));
|
|
168
|
-
const ctx: PickCtx = { now: Date.now(), thresholds: bars, currentId: null, families: gatedFamilies(null, cfg.policy.switchModels), seats: presence() };
|
|
168
|
+
const ctx: PickCtx = { now: Date.now(), thresholds: bars, currentId: null, families: gatedFamilies(null, cfg.policy.switchModels), seats: presence(living) };
|
|
169
169
|
const picked = pickBest(open.filter((_, i) => usable[i]), ctx);
|
|
170
170
|
if (!picked) return null;
|
|
171
171
|
writePresence({ dir: paths.presenceDir, id: seatId, accountId: picked.id, pid });
|
package/src/lib/presence.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { countBy } from "es-toolkit";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { writeFileAtomic } from "./atomic.ts";
|
|
6
|
-
import { pidExists,
|
|
6
|
+
import { pidExists, pidStartTimes } from "./proc.ts";
|
|
7
7
|
import { readJsonFile } from "./state.ts";
|
|
8
8
|
import { ErrnoSchema, JsonTextSchema } from "./types.ts";
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ const PresenceSchema = z.object({
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
export function writePresence(input: { dir: string; id: string; accountId: string; pid: number }): void {
|
|
19
|
-
const startedAt =
|
|
19
|
+
const startedAt = pidStartTimes([input.pid]).get(input.pid);
|
|
20
20
|
if (startedAt == null) throw new Error(`could not read pid ${input.pid}'s start time (ps lstart) - refusing to write an unverifiable presence file`);
|
|
21
21
|
writeFileAtomic(join(input.dir, input.id), JSON.stringify(PresenceSchema.parse({ accountId: input.accountId, pid: input.pid, startedAt })));
|
|
22
22
|
}
|
|
@@ -37,8 +37,8 @@ export function clearPresence(input: { dir: string; id: string }): void {
|
|
|
37
37
|
export type LivingPresence = { id: string; accountId: string };
|
|
38
38
|
|
|
39
39
|
export function livingPresences(dir: string): LivingPresence[] {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
if (!existsSync(dir)) return [];
|
|
41
|
+
const records: { name: string; file: string; record: z.infer<typeof PresenceSchema> }[] = [];
|
|
42
42
|
for (const name of readdirSync(dir)) {
|
|
43
43
|
const file = join(dir, name);
|
|
44
44
|
let raw: string;
|
|
@@ -52,15 +52,20 @@ export function livingPresences(dir: string): LivingPresence[] {
|
|
|
52
52
|
if (!parsed.success) {
|
|
53
53
|
throw new Error(`${file} is not a readable presence record - it may belong to a RUNNING session, refusing to treat it as absent; remove the file (or respawn that session) to proceed`);
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
records.push({ name, file, record: parsed.data });
|
|
56
|
+
}
|
|
57
|
+
const started = pidStartTimes(records.map((r) => r.record.pid));
|
|
58
|
+
const living: LivingPresence[] = [];
|
|
59
|
+
for (const { name, file, record } of records) {
|
|
60
|
+
const observed = started.get(record.pid);
|
|
61
|
+
if (observed !== record.startedAt) {
|
|
62
|
+
if (observed == null && pidExists(record.pid)) {
|
|
63
|
+
throw new Error(`ps could not read the start time of live pid ${record.pid} (${file}) - refusing to clear a presence file that may guard a RUNNING session`);
|
|
59
64
|
}
|
|
60
65
|
rmSync(file, { force: true });
|
|
61
66
|
continue;
|
|
62
67
|
}
|
|
63
|
-
living.push({ id: name, accountId:
|
|
68
|
+
living.push({ id: name, accountId: record.accountId });
|
|
64
69
|
}
|
|
65
70
|
return living;
|
|
66
71
|
}
|
package/src/lib/proc.ts
CHANGED
|
@@ -38,11 +38,17 @@ export function ancestorPids(): number[] {
|
|
|
38
38
|
return out;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
const
|
|
45
|
-
|
|
41
|
+
export function pidStartTimes(pids: number[]): Map<number, string> {
|
|
42
|
+
const started = new Map<number, string>();
|
|
43
|
+
if (pids.length === 0) return started;
|
|
44
|
+
const res = Bun.spawnSync(["ps", "-p", pids.join(","), "-o", "pid=", "-o", "lstart="], { env: { ...process.env, LC_ALL: "C" } });
|
|
45
|
+
if (res.exitCode !== 0) return started;
|
|
46
|
+
for (const row of res.stdout.toString().split("\n")) {
|
|
47
|
+
const line = row.trim();
|
|
48
|
+
const cut = line.indexOf(" ");
|
|
49
|
+
if (cut !== -1) started.set(Number(line.slice(0, cut)), line.slice(cut).trim());
|
|
50
|
+
}
|
|
51
|
+
return started;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export async function readStdin(): Promise<string> {
|