tokenmaxxing 1.40.0 → 1.41.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/README.md +1 -1
- package/package.json +1 -1
- package/src/cli/auth.ts +14 -10
- package/src/lib/claude.ts +10 -0
- package/src/lib/codex.ts +7 -0
- package/src/lib/provider.ts +1 -0
- package/src/main.ts +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ claude # use claude as always; each session gets its own accoun
|
|
|
66
66
|
| `tokenmaxxing init --codex` | same for codex: import login, install codex supervisor + Stop hook |
|
|
67
67
|
| `tokenmaxxing add` | register an additional account (isolated login, harvested once into its own store) |
|
|
68
68
|
| `tokenmaxxing add --codex` | register an additional codex account (isolated login) |
|
|
69
|
-
| `tokenmaxxing auth [--codex] [sel \| --all]` | reauthenticate a pooled account in place: bare lists the pool (emails shown) and asks which; a selector targets one account and tells you the email to sign in with; `--all` walks every
|
|
69
|
+
| `tokenmaxxing auth [--codex] [sel \| --all]` | reauthenticate a pooled account in place: bare lists the pool (emails shown) and asks which; a selector targets one account and tells you the email to sign in with; `--all` walks every account that is flagged or has no usable credential in its store, one by one; `--codex` does the same for the codex pool |
|
|
70
70
|
| `tokenmaxxing switch --codex [sel]` | switch the codex pool (takes effect on the next codex start); the claude pool has no manual switch, each session is placed at launch |
|
|
71
71
|
| `tokenmaxxing status [--cached]` | accounts with 5h / weekly usage bars, live session counts, exhausted-until-reset; `--cached` renders the stored figures without sampling |
|
|
72
72
|
| `tokenmaxxing config` | the config path and the effective values; edit the file in an editor, a bad value fails the next load with the field name |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.41.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/auth.ts
CHANGED
|
@@ -18,14 +18,13 @@ const AuthPlanSchema = z.discriminatedUnion("kind", [
|
|
|
18
18
|
]);
|
|
19
19
|
export type AuthPlan = z.infer<typeof AuthPlanSchema>;
|
|
20
20
|
|
|
21
|
-
export function planAuth(input: { p: Provider; accounts: Account[]; argv: string[] }): AuthPlan {
|
|
21
|
+
export function planAuth(input: { p: Provider; accounts: Account[]; argv: string[]; needsAuth: Set<string> }): AuthPlan {
|
|
22
22
|
const all = input.argv.includes("--all");
|
|
23
23
|
const rest = input.argv.filter((a) => a !== "--all");
|
|
24
24
|
if ((all && rest.length > 0) || rest.length > 1) return { kind: "usage" };
|
|
25
25
|
if (input.accounts.length === 0) return { kind: "error", message: `no accounts in the pool - run \`tokenmaxxing init${input.p.flag}\` first` };
|
|
26
26
|
if (all) {
|
|
27
|
-
|
|
28
|
-
return { kind: "targets", ids: flagged.map((a) => a.id) };
|
|
27
|
+
return { kind: "targets", ids: input.accounts.filter((a) => input.needsAuth.has(a.id)).map((a) => a.id) };
|
|
29
28
|
}
|
|
30
29
|
const selector = rest[0];
|
|
31
30
|
if (selector !== undefined) {
|
|
@@ -36,18 +35,19 @@ export function planAuth(input: { p: Provider; accounts: Account[]; argv: string
|
|
|
36
35
|
return { kind: "pick" };
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
export function pickerOrder(accounts: Account[]): Account[] {
|
|
40
|
-
const [flagged, healthy] = partition(accounts, (a) => a.
|
|
38
|
+
export function pickerOrder(accounts: Account[], needsAuth: Set<string>): Account[] {
|
|
39
|
+
const [flagged, healthy] = partition(accounts, (a) => needsAuth.has(a.id));
|
|
41
40
|
return [...flagged, ...healthy];
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
function askWhichAccount(idx: AccountsIndex): Account | null {
|
|
45
|
-
const ordered = pickerOrder(idx.accounts);
|
|
43
|
+
function askWhichAccount(idx: AccountsIndex, needsAuth: Set<string>): Account | null {
|
|
44
|
+
const ordered = pickerOrder(idx.accounts, needsAuth);
|
|
46
45
|
console.log("which account do you want to reauthenticate?");
|
|
47
46
|
for (const [i, a] of ordered.entries()) {
|
|
48
47
|
const flags: string[] = [];
|
|
49
48
|
if (a.id === idx.activeId) flags.push(c.green("active"));
|
|
50
49
|
if (a.needsReauth) flags.push(c.red("needs-reauth"));
|
|
50
|
+
else if (needsAuth.has(a.id)) flags.push(c.red("no-credential"));
|
|
51
51
|
const labelNote = a.email != null && a.label !== a.email ? ` (${a.label})` : "";
|
|
52
52
|
const tag = flags.length ? ` ${flags.join(" ")}` : "";
|
|
53
53
|
console.log(` ${i + 1}. ${c.bold(a.email ?? a.label)}${labelNote}${tag}`);
|
|
@@ -107,7 +107,11 @@ async function reauthOne(p: Provider, target: Account): Promise<boolean> {
|
|
|
107
107
|
|
|
108
108
|
export async function cmdAuth(p: Provider, argv: string[]): Promise<number> {
|
|
109
109
|
const idx = loadAccounts(p.pool);
|
|
110
|
-
const
|
|
110
|
+
const needsAuth = new Set<string>();
|
|
111
|
+
for (const a of idx.accounts) {
|
|
112
|
+
if (a.needsReauth === true || !(await p.storeUsable(a))) needsAuth.add(a.id);
|
|
113
|
+
}
|
|
114
|
+
const plan = planAuth({ p, accounts: idx.accounts, argv, needsAuth });
|
|
111
115
|
if (plan.kind === "usage") {
|
|
112
116
|
console.error(AUTH_USAGE);
|
|
113
117
|
return 2;
|
|
@@ -119,7 +123,7 @@ export async function cmdAuth(p: Provider, argv: string[]): Promise<number> {
|
|
|
119
123
|
|
|
120
124
|
const targets: Account[] = [];
|
|
121
125
|
if (plan.kind === "pick") {
|
|
122
|
-
const picked = askWhichAccount(idx);
|
|
126
|
+
const picked = askWhichAccount(idx, needsAuth);
|
|
123
127
|
if (!picked) return 1;
|
|
124
128
|
targets.push(picked);
|
|
125
129
|
} else {
|
|
@@ -128,7 +132,7 @@ export async function cmdAuth(p: Provider, argv: string[]): Promise<number> {
|
|
|
128
132
|
if (account) targets.push(account);
|
|
129
133
|
}
|
|
130
134
|
if (targets.length === 0) {
|
|
131
|
-
console.log(`${c.green("✓")}
|
|
135
|
+
console.log(`${c.green("✓")} every account has a usable credential`);
|
|
132
136
|
return 0;
|
|
133
137
|
}
|
|
134
138
|
}
|
package/src/lib/claude.ts
CHANGED
|
@@ -108,6 +108,15 @@ async function prepareMove(target: Account): Promise<void> {
|
|
|
108
108
|
log("move.prepared", { account: target.id.slice(0, 8), label: target.label });
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
async function storeUsable(a: Account): Promise<boolean> {
|
|
112
|
+
try {
|
|
113
|
+
const creds = await readStore(a.id);
|
|
114
|
+
return creds != null && !isDeadCredential(creds);
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
111
120
|
export function pickSeat(now: number): Account | null {
|
|
112
121
|
const cfg = loadConfig();
|
|
113
122
|
const idx = loadAccounts(claudePool);
|
|
@@ -277,6 +286,7 @@ export const claude: Provider = {
|
|
|
277
286
|
swap: prepareMove,
|
|
278
287
|
classifySwapError: (e) => (e instanceof InvalidGrantError ? "dead-grant" : e instanceof StoreUnusableError ? "skip" : "fatal"),
|
|
279
288
|
removeCredentials,
|
|
289
|
+
storeUsable,
|
|
280
290
|
login,
|
|
281
291
|
importLive,
|
|
282
292
|
preflight: () => pinBinOverride({ key: "claudeBin", bin: resolveVerifiedClaude() }),
|
package/src/lib/codex.ts
CHANGED
|
@@ -334,6 +334,13 @@ export const codex: Provider = {
|
|
|
334
334
|
swap,
|
|
335
335
|
classifySwapError: (e) => (e instanceof CodexInvalidGrantError ? "dead-grant" : "fatal"),
|
|
336
336
|
removeCredentials: async (a) => deleteParkedCodexAuth({ credFile: codexCredItemFor(a.id) }),
|
|
337
|
+
storeUsable: async (a) => {
|
|
338
|
+
try {
|
|
339
|
+
return readParkedCodexAuth({ credFile: codexCredItemFor(a.id) }) != null;
|
|
340
|
+
} catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
},
|
|
337
344
|
login,
|
|
338
345
|
importLive,
|
|
339
346
|
preflight,
|
package/src/lib/provider.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type Provider = {
|
|
|
23
23
|
swap(target: Account): Promise<void>;
|
|
24
24
|
classifySwapError(e: unknown): SwapFailure;
|
|
25
25
|
removeCredentials(account: Account): Promise<void>;
|
|
26
|
+
storeUsable(account: Account): Promise<boolean>;
|
|
26
27
|
login(): Promise<Harvest | null>;
|
|
27
28
|
importLive(): Promise<Harvest | null>;
|
|
28
29
|
preflight(): void;
|
package/src/main.ts
CHANGED
|
@@ -42,7 +42,7 @@ function printHelp(): void {
|
|
|
42
42
|
${c.cyan("tokenmaxxing init --codex")} same for codex: import login, install codex supervisor + Stop hook (trust it via /hooks)
|
|
43
43
|
${c.cyan("tokenmaxxing add")} register an additional account (isolated login)
|
|
44
44
|
${c.cyan("tokenmaxxing add --codex")} register an additional codex account (isolated login)
|
|
45
|
-
${c.cyan("tokenmaxxing auth")} [--codex] [sel | --all] reauthenticate a pooled account in place (bare = pick from a list; --all = every
|
|
45
|
+
${c.cyan("tokenmaxxing auth")} [--codex] [sel | --all] reauthenticate a pooled account in place (bare = pick from a list; --all = every account that is flagged or has no usable credential in its store, one by one)
|
|
46
46
|
${c.cyan("tokenmaxxing switch --codex")} [sel] switch the codex pool (takes effect on next codex start)
|
|
47
47
|
${c.cyan("tokenmaxxing status")} [--cached] accounts with 5h / weekly / per-model usage bars (--cached: the stored figures, no sampling)
|
|
48
48
|
${c.cyan("tokenmaxxing config")} print the config path and the effective values (edit the file in an editor)
|