tokenmaxxing 1.165.0 → 1.166.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/DESIGN.md +1 -1
- package/package.json +1 -1
- package/src/lib/claude.ts +3 -5
- package/src/lib/codex.ts +2 -2
- package/src/lib/grok.ts +0 -1
- package/src/lib/muse.ts +0 -1
- package/src/lib/onboard.ts +22 -0
- package/src/lib/paths.ts +0 -4
- package/src/lib/pi.ts +3 -3
- package/src/lib/sessions.ts +1 -1
- package/src/lib/statusonly.ts +3 -5
package/DESIGN.md
CHANGED
|
@@ -40,7 +40,7 @@ It is a process manager only - spawn with inherited stdio (stdin through a relay
|
|
|
40
40
|
- `wait-queue.json` - the claims of supervised sessions that are moving or waiting (`{sessionId, accountId, at, waitUntil}`), naming the account each relaunches on: placement counts a claimed session there, and at most eight sessions wait on one account; a session that arrives when every reset within `policy.maxWaitMs` is full waits for the next reset with room up to twice `policy.maxWaitMs` out, joining the least-crowded reset when the horizon is full.
|
|
41
41
|
- `bin/claude` and `bin/codex` - the supervisors, beside the `tokenmaxxing` and `xx` entry points; `bin/pi` after `init --pi`.
|
|
42
42
|
- `pi-stores/claude/<uuid8>/` and `pi-stores/codex/<uuid8>/` - one pi store per pooled account that has a pi login: the account's own pi grant in `auth.json`, everything else symlinked to the shared `~/.pi/agent` ([pi support](docs/content/docs/pi.mdx)).
|
|
43
|
-
- `sessions/<sid>.json`, `onboard/`, `hub-key`, `update.json`, `spawnrate.json`, `tokenmaxxing.log`, the codex `codex-
|
|
43
|
+
- `sessions/<sid>.json`, `onboard/`, `hub-key`, `update.json`, `spawnrate.json`, `tokenmaxxing.log`, the codex `codex-respawn/` and `codex-live/`, and the `grok-*` and `muse-*` state of the status-only grok and Muse Code pools: the full table is in [State directory layout](docs/content/docs/state-directory.mdx).
|
|
44
44
|
- Per-account **credential stores** follow the platform's Claude Code store: macOS = a login-keychain item keyed by the store path (never plaintext on disk); Linux = a 0600 `.credentials.json` inside the 0700 store directory (the same plaintext model claude itself uses - its Linux build has no keyring path at all, binary-verified 2.1.205). One `credstore` facade dispatches on a `{kind: keychain|file}` target; call sites never branch on platform. tokenmaxxing writes a store once, at onboarding; Claude Code's own refresh is the only writer afterwards.
|
|
45
45
|
|
|
46
46
|
- A periodic check job (`com.tokenmaxxing.check` launchd agent on macOS, `tokenmaxxing-check.timer` systemd user timer on Linux) running `tokenmaxxing check` once per tick (`policy.checkIntervalMs`, 60s by default): it runs the decision with no seat (nothing moves), folds fresh tees into the index, samples up to three accounts whose last sample attempt is oldest (the direct no-spend usage GET), deletes leftover atomic-write temp files under the state root, and on a Bun global install checks npm for a newer version once a day, so the cached figures every placement reads stay fresh.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.166.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync, rmSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { countBy } from "es-toolkit";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -9,6 +9,7 @@ import { activationHint, CHECK_JOB, ensurePathInRc, HUB_JOB, installSupervisor,
|
|
|
9
9
|
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
|
+
import { onboardHome } from "./onboard.ts";
|
|
12
13
|
import { claudePool, env, paths, seatFromEnv, shortId, storeDirFor } from "./paths.ts";
|
|
13
14
|
import { MAX_WAITERS_PER_ACCOUNT, pickBest, pickEarliestReset, pickWaitTarget, thresholdBars, type PickCtx } from "./picker.ts";
|
|
14
15
|
import { deletePiStore } from "./piauth.ts";
|
|
@@ -232,11 +233,8 @@ function identityReady(cjPath: string): boolean {
|
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
async function login(): Promise<Harvest | null> {
|
|
235
|
-
const onboardDir =
|
|
236
|
-
rmSync(onboardDir, { recursive: true, force: true });
|
|
237
|
-
mkdirSync(onboardDir, { recursive: true });
|
|
236
|
+
const onboardDir = await onboardHome("claude", (dir) => deleteItem(isolatedTarget(dir)));
|
|
238
237
|
const iso = isolatedTarget(onboardDir);
|
|
239
|
-
await deleteItem(iso);
|
|
240
238
|
const cjPath = join(onboardDir, ".claude.json");
|
|
241
239
|
const real = resolveRealBin(CLAUDE_BIN);
|
|
242
240
|
|
package/src/lib/codex.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { writeFileAtomic } from "./atomic.ts";
|
|
|
6
6
|
import { CODEX_BIN, MAX_WRAP_DEPTH, WRAP_DEPTH_ENV, resolveRealBin, verifyRealBin } from "./claudebin.ts";
|
|
7
7
|
import { codexIdentityOf, codexStoreUsable, deleteCodexStoreAuth, ensureCodexStoreHome, isCodexAccessExpiring, readCodexAuthAt, readCodexStoreAuth, writeCodexStoreAuth } from "./codexauth.ts";
|
|
8
8
|
import { CodexInvalidGrantError, CodexRefreshFailedError, refreshCodexAuth } from "./codexoauth.ts";
|
|
9
|
+
import { onboardHome } from "./onboard.ts";
|
|
9
10
|
import { deletePiStore } from "./piauth.ts";
|
|
10
11
|
import { PI_PRESENCE_PREFIX, livingPresences, seatCounts, writePresence } from "./presence.ts";
|
|
11
12
|
import { CodexUsageReadError, codexLimitLabel, consumeCodexReset, fetchCodexUsage } from "./codexusage.ts";
|
|
@@ -139,8 +140,7 @@ async function prepareMove(target: Account): Promise<void> {
|
|
|
139
140
|
|
|
140
141
|
async function login(): Promise<Harvest | null> {
|
|
141
142
|
const real = resolveRealBin(CODEX_BIN);
|
|
142
|
-
const onboardDir =
|
|
143
|
-
rmSync(onboardDir, { recursive: true, force: true });
|
|
143
|
+
const onboardDir = await onboardHome("codex");
|
|
144
144
|
writeFileAtomic(join(onboardDir, "config.toml"), 'cli_auth_credentials_store = "file"\n');
|
|
145
145
|
|
|
146
146
|
const savedTermios = saveTermios();
|
package/src/lib/grok.ts
CHANGED
|
@@ -124,7 +124,6 @@ export const grok = statusOnlyProvider({
|
|
|
124
124
|
binKey: "grokBin",
|
|
125
125
|
storeDirFor: grokStoreDirFor,
|
|
126
126
|
authJsonFor: grokAuthJsonFor,
|
|
127
|
-
onboardDir: grokPaths.onboardDir,
|
|
128
127
|
homeEnv: "GROK_HOME",
|
|
129
128
|
loginArgs: ["login"],
|
|
130
129
|
onboardAuthRel: "auth.json",
|
package/src/lib/muse.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { paths } from "./paths.ts";
|
|
4
|
+
import { pidExists } from "./proc.ts";
|
|
5
|
+
|
|
6
|
+
const POOLS = ["claude", "codex", "pi", "grok", "muse"] as const;
|
|
7
|
+
|
|
8
|
+
export type OnboardPool = (typeof POOLS)[number];
|
|
9
|
+
|
|
10
|
+
const HOME_NAME = new RegExp(`^(${POOLS.join("|")})-([1-9]\\d*)-`);
|
|
11
|
+
|
|
12
|
+
export async function onboardHome(pool: OnboardPool, onStale?: (dir: string) => Promise<unknown>): Promise<string> {
|
|
13
|
+
mkdirSync(paths.onboardDir, { recursive: true, mode: 0o700 });
|
|
14
|
+
for (const name of readdirSync(paths.onboardDir)) {
|
|
15
|
+
const match = HOME_NAME.exec(name);
|
|
16
|
+
if (match && (match[1] !== pool || pidExists(Number(match[2])))) continue;
|
|
17
|
+
const dir = join(paths.onboardDir, name);
|
|
18
|
+
if (match) await onStale?.(dir);
|
|
19
|
+
rmSync(dir, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
return mkdtempSync(join(paths.onboardDir, `${pool}-${process.pid}-`));
|
|
22
|
+
}
|
package/src/lib/paths.ts
CHANGED
|
@@ -72,7 +72,6 @@ export const codexPaths = {
|
|
|
72
72
|
authJson: join(CODEX_HOME, "auth.json"),
|
|
73
73
|
hooksJson: join(CODEX_HOME, "hooks.json"),
|
|
74
74
|
storesDir: join(TM_HOME, "codex-stores"),
|
|
75
|
-
onboardDir: join(TM_HOME, "codex-onboard"),
|
|
76
75
|
respawnDir: join(TM_HOME, "codex-respawn"),
|
|
77
76
|
presenceDir: join(TM_HOME, "codex-live"),
|
|
78
77
|
} as const;
|
|
@@ -100,7 +99,6 @@ export function expandTilde(p: string): string {
|
|
|
100
99
|
export const piPaths = {
|
|
101
100
|
home: resolve(expandTilde(env("PI_CODING_AGENT_DIR", join(HOME, ".pi", "agent")))),
|
|
102
101
|
storesDir: join(TM_HOME, "pi-stores"),
|
|
103
|
-
onboardDir: join(TM_HOME, "pi-onboard"),
|
|
104
102
|
} as const;
|
|
105
103
|
|
|
106
104
|
export function piStoreDirFor(pool: PiPool, accountId: string): string {
|
|
@@ -116,7 +114,6 @@ const GROK_HOME_DEFAULT = join(HOME, ".grok");
|
|
|
116
114
|
export const grokPaths = {
|
|
117
115
|
home: env("GROK_HOME", GROK_HOME_DEFAULT),
|
|
118
116
|
storesDir: join(TM_HOME, "grok-stores"),
|
|
119
|
-
onboardDir: join(TM_HOME, "grok-onboard"),
|
|
120
117
|
} as const;
|
|
121
118
|
|
|
122
119
|
export function grokStoreDirFor(accountId: string): string {
|
|
@@ -130,7 +127,6 @@ export function grokAuthJsonFor(accountId: string): string {
|
|
|
130
127
|
export const musePaths = {
|
|
131
128
|
authJson: join(env("XDG_CONFIG_HOME", join(HOME, ".config")), "muse", "auth.json"),
|
|
132
129
|
storesDir: join(TM_HOME, "muse-stores"),
|
|
133
|
-
onboardDir: join(TM_HOME, "muse-onboard"),
|
|
134
130
|
} as const;
|
|
135
131
|
|
|
136
132
|
export function museStoreDirFor(accountId: string): string {
|
package/src/lib/pi.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { codexSeatIdOf } from "./codexauth.ts";
|
|
|
7
7
|
import { CHECK_JOB, installPiSupervisor, isBinDirAhead, jobHealthy, piSupervisorLink } from "./install.ts";
|
|
8
8
|
import { errorMessage, log } from "./log.ts";
|
|
9
9
|
import { fetchTokenIdentity } from "./oauth.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { onboardHome } from "./onboard.ts";
|
|
11
|
+
import { claudePool, type PiPool } from "./paths.ts";
|
|
11
12
|
import { linkSharedPiHome, piStoreUsable, readPiAuthAt, type PiOAuth } from "./piauth.ts";
|
|
12
13
|
import { StoreUnusableError, type Provider } from "./provider.ts";
|
|
13
14
|
import { loadAccounts, pinBinOverride } from "./state.ts";
|
|
@@ -51,8 +52,7 @@ async function identityOf(pool: PiPool, cred: PiOAuth): Promise<{ id: string; em
|
|
|
51
52
|
|
|
52
53
|
export async function piLogin(pool: PiPool): Promise<PiLogin | null> {
|
|
53
54
|
const real = resolveRealBin(PI_BIN);
|
|
54
|
-
const onboardDir =
|
|
55
|
-
rmSync(onboardDir, { recursive: true, force: true });
|
|
55
|
+
const onboardDir = await onboardHome("pi");
|
|
56
56
|
linkSharedPiHome(onboardDir);
|
|
57
57
|
const authPath = join(onboardDir, "auth.json");
|
|
58
58
|
|
package/src/lib/sessions.ts
CHANGED
|
@@ -59,7 +59,7 @@ function listDir(dir: string, root: string): Dirent[] {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function tmpSweepDirs(root: string): string[] {
|
|
62
|
-
const dirs = [paths.home, paths.usageDir, paths.presenceDir, paths.respawnDir, sessionsDir(), paths.binDir, paths.apiKeysDir, codexPaths.presenceDir, codexPaths.respawnDir
|
|
62
|
+
const dirs = [paths.home, paths.usageDir, paths.presenceDir, paths.respawnDir, sessionsDir(), paths.binDir, paths.apiKeysDir, codexPaths.presenceDir, codexPaths.respawnDir];
|
|
63
63
|
for (const parent of SWEEP_PARENTS) {
|
|
64
64
|
for (const child of listDir(parent, root)) {
|
|
65
65
|
if (child.isDirectory()) dirs.push(join(parent, child.name));
|
package/src/lib/statusonly.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { resolveRealBin, verifyRealBin } from "./claudebin.ts";
|
|
4
|
+
import { onboardHome } from "./onboard.ts";
|
|
4
5
|
import type { PoolPaths } from "./paths.ts";
|
|
5
6
|
import type { Provider } from "./provider.ts";
|
|
6
7
|
import { pinBinOverride, type Harvest } from "./state.ts";
|
|
@@ -17,7 +18,6 @@ export type StatusOnlySpec = {
|
|
|
17
18
|
binKey: "grokBin" | "museBin";
|
|
18
19
|
storeDirFor: (accountId: string) => string;
|
|
19
20
|
authJsonFor: (accountId: string) => string;
|
|
20
|
-
onboardDir: string;
|
|
21
21
|
homeEnv: string;
|
|
22
22
|
loginArgs: string[];
|
|
23
23
|
onboardAuthRel: string;
|
|
@@ -51,9 +51,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
|
|
|
51
51
|
|
|
52
52
|
async function login(): Promise<Harvest | null> {
|
|
53
53
|
const real = resolveRealBin(bin);
|
|
54
|
-
const onboardDir = spec.
|
|
55
|
-
rmSync(onboardDir, { recursive: true, force: true });
|
|
56
|
-
mkdirSync(onboardDir, { recursive: true });
|
|
54
|
+
const onboardDir = await onboardHome(spec.name);
|
|
57
55
|
const p = Bun.spawn([real, ...spec.loginArgs], {
|
|
58
56
|
stdin: "inherit",
|
|
59
57
|
stdout: "inherit",
|