tokenmaxxing 1.34.0 → 1.35.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 +3 -5
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli/add.ts +23 -40
- package/src/cli/auth.ts +38 -52
- package/src/cli/check.ts +2 -1
- package/src/cli/doctor.ts +13 -13
- package/src/cli/init.ts +29 -126
- package/src/cli/rename.ts +11 -45
- package/src/cli/render.ts +0 -7
- package/src/cli/rm.ts +24 -39
- package/src/cli/setuptoken.ts +16 -16
- package/src/cli/status.ts +95 -238
- package/src/cli/switch.ts +54 -46
- package/src/entries/codexstophook.ts +22 -16
- package/src/entries/codexsupervisor.ts +3 -3
- package/src/entries/sessionstart.ts +3 -2
- package/src/entries/statusline.ts +15 -13
- package/src/entries/stopfailurehook.ts +7 -5
- package/src/entries/stophook.ts +3 -2
- package/src/lib/claude.ts +320 -0
- package/src/lib/codex.ts +353 -0
- package/src/lib/codexauth.ts +6 -0
- package/src/lib/codexpresence.ts +0 -10
- package/src/lib/codexusage.ts +14 -32
- package/src/lib/decide.ts +86 -174
- package/src/lib/oauth.ts +7 -0
- package/src/lib/paths.ts +14 -6
- package/src/lib/picker.ts +56 -37
- package/src/lib/provider.ts +34 -0
- package/src/lib/sample.ts +33 -33
- package/src/lib/state.ts +49 -16
- package/src/lib/swap.ts +41 -38
- package/src/lib/types.ts +17 -51
- package/src/lib/usage.ts +17 -4
- package/src/main.ts +20 -20
- package/src/cli/codexadd.ts +0 -89
- package/src/cli/codexinit.ts +0 -120
- package/src/cli/codexrm.ts +0 -36
- package/src/cli/codexswitch.ts +0 -113
- package/src/cli/onboard.ts +0 -92
- package/src/lib/codexdecide.ts +0 -157
- package/src/lib/codexpick.ts +0 -80
- package/src/lib/codexsample.ts +0 -48
- package/src/lib/codexstate.ts +0 -22
- package/src/lib/codexswap.ts +0 -70
package/src/cli/rename.ts
CHANGED
|
@@ -1,73 +1,39 @@
|
|
|
1
1
|
import { withLock } from "../lib/lock.ts";
|
|
2
|
-
import {
|
|
2
|
+
import type { Provider } from "../lib/provider.ts";
|
|
3
3
|
import { loadAccounts, saveAccounts } from "../lib/state.ts";
|
|
4
|
-
import { loadCodexAccounts, saveCodexAccounts } from "../lib/codexstate.ts";
|
|
5
4
|
import { c, emitError, plain } from "./render.ts";
|
|
6
|
-
import type { Account
|
|
5
|
+
import type { Account } from "../lib/types.ts";
|
|
7
6
|
|
|
8
7
|
export function findAccount(accounts: Account[], selector: string): Account | undefined {
|
|
9
|
-
const s = selector.toLowerCase();
|
|
10
|
-
return (
|
|
11
|
-
accounts.find((a) => a.email.toLowerCase() === s) ??
|
|
12
|
-
accounts.find((a) => a.label.toLowerCase() === s) ??
|
|
13
|
-
accounts.find((a) => a.accountUuid.toLowerCase().startsWith(s))
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function findCodexAccount(accounts: CodexAccount[], selector: string): CodexAccount | undefined {
|
|
18
8
|
const s = selector.toLowerCase();
|
|
19
9
|
return (
|
|
20
10
|
accounts.find((a) => a.email?.toLowerCase() === s) ??
|
|
21
11
|
accounts.find((a) => a.label.toLowerCase() === s) ??
|
|
22
|
-
accounts.find((a) => a.
|
|
12
|
+
accounts.find((a) => a.id.toLowerCase().startsWith(s))
|
|
23
13
|
);
|
|
24
14
|
}
|
|
25
15
|
|
|
26
|
-
async function
|
|
27
|
-
const
|
|
28
|
-
return withLock(codexPaths.lockFile, async () => {
|
|
29
|
-
const index = loadCodexAccounts();
|
|
30
|
-
const account = findCodexAccount(index.accounts, selector);
|
|
31
|
-
if (!account) {
|
|
32
|
-
emitError({ message: `no codex account matches "${selector}"` });
|
|
33
|
-
return 1;
|
|
34
|
-
}
|
|
35
|
-
const taken = index.accounts.find((x) => x.accountId !== account.accountId && x.label.toLowerCase() === newLabel.toLowerCase());
|
|
36
|
-
if (taken) {
|
|
37
|
-
emitError({ message: `label "${newLabel}" is already used by ${taken.accountId.slice(0, 8)} - labels must be unique within the pool` });
|
|
38
|
-
return 1;
|
|
39
|
-
}
|
|
40
|
-
const old = account.label;
|
|
41
|
-
account.label = newLabel;
|
|
42
|
-
saveCodexAccounts({ index });
|
|
43
|
-
console.log(`renamed codex account ${c.dim(old)} → ${c.bold(newLabel)}`);
|
|
44
|
-
return 0;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function cmdRename(argv: string[]): Promise<number> {
|
|
49
|
-
const codex = argv.includes("--codex");
|
|
50
|
-
const [selector, newLabel] = argv.filter((a) => a !== "--codex");
|
|
16
|
+
export async function cmdRename(p: Provider, argv: string[]): Promise<number> {
|
|
17
|
+
const [selector, newLabel] = argv;
|
|
51
18
|
if (!selector || !newLabel) {
|
|
52
19
|
emitError({ message: "usage: tokenmaxxing rename [--codex] <email|label|id> <new-label>", paint: plain });
|
|
53
20
|
return 2;
|
|
54
21
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const idx = loadAccounts();
|
|
22
|
+
return withLock(p.pool.lockFile, async () => {
|
|
23
|
+
const idx = loadAccounts(p.pool);
|
|
58
24
|
const a = findAccount(idx.accounts, selector);
|
|
59
25
|
if (!a) {
|
|
60
|
-
emitError({ message: `no
|
|
26
|
+
emitError({ message: `no ${p.name} account matches "${selector}"` });
|
|
61
27
|
return 1;
|
|
62
28
|
}
|
|
63
|
-
const taken = idx.accounts.find((x) => x.
|
|
29
|
+
const taken = idx.accounts.find((x) => x.id !== a.id && x.label.toLowerCase() === newLabel.toLowerCase());
|
|
64
30
|
if (taken) {
|
|
65
|
-
emitError({ message: `label "${newLabel}" is already used by ${taken.email} - labels must be unique within the pool` });
|
|
31
|
+
emitError({ message: `label "${newLabel}" is already used by ${taken.email ?? taken.id.slice(0, 8)} - labels must be unique within the pool` });
|
|
66
32
|
return 1;
|
|
67
33
|
}
|
|
68
34
|
const old = a.label;
|
|
69
35
|
a.label = newLabel;
|
|
70
|
-
saveAccounts(idx);
|
|
36
|
+
saveAccounts(p.pool, idx);
|
|
71
37
|
console.log(`renamed ${c.dim(old)} → ${c.bold(newLabel)}`);
|
|
72
38
|
return 0;
|
|
73
39
|
});
|
package/src/cli/render.ts
CHANGED
|
@@ -33,13 +33,6 @@ export function makeUsagePaint(input: { enabled: boolean; truecolor: boolean })
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export function claudeTierLabel(input: { subscriptionType?: string; rateLimitTier?: string }): string | null {
|
|
37
|
-
const segments = input.rateLimitTier?.split("_") ?? [];
|
|
38
|
-
const multiplier = segments.find((seg) => seg.length > 1 && seg.endsWith("x") && Number.isInteger(Number(seg.slice(0, -1))));
|
|
39
|
-
if (input.subscriptionType == null) return multiplier ?? null;
|
|
40
|
-
return multiplier ? `${input.subscriptionType} ${multiplier}` : input.subscriptionType;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
36
|
export function count(input: { n: number; noun: string }): string {
|
|
44
37
|
return `${input.n} ${input.noun}${input.n === 1 ? "" : "s"}`;
|
|
45
38
|
}
|
package/src/cli/rm.ts
CHANGED
|
@@ -1,60 +1,45 @@
|
|
|
1
|
-
import { rmSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { deleteItem, isolatedTarget, liveTarget, parkedTarget, readItem } from "../lib/credstore.ts";
|
|
4
|
-
import { fetchTokenIdentity } from "../lib/oauth.ts";
|
|
5
1
|
import { withLock } from "../lib/lock.ts";
|
|
6
|
-
import {
|
|
7
|
-
import { loadSetupTokens, saveSetupTokens } from "../lib/setuptokens.ts";
|
|
2
|
+
import type { Provider } from "../lib/provider.ts";
|
|
8
3
|
import { loadAccounts, saveAccounts } from "../lib/state.ts";
|
|
9
|
-
import { CredentialBlobSchema } from "../lib/types.ts";
|
|
10
4
|
import { findAccount } from "./rename.ts";
|
|
11
5
|
import { c, emitError, plain } from "./render.ts";
|
|
12
6
|
|
|
13
|
-
export async function cmdRm(selector?: string): Promise<number> {
|
|
7
|
+
export async function cmdRm(p: Provider, selector?: string): Promise<number> {
|
|
14
8
|
if (!selector) {
|
|
15
|
-
emitError({ message:
|
|
9
|
+
emitError({ message: `usage: tokenmaxxing rm${p.flag} <email|label|id>`, paint: plain });
|
|
16
10
|
return 2;
|
|
17
11
|
}
|
|
18
|
-
return withLock(
|
|
19
|
-
const idx = loadAccounts();
|
|
12
|
+
return withLock(p.pool.lockFile, async () => {
|
|
13
|
+
const idx = loadAccounts(p.pool);
|
|
20
14
|
const a = findAccount(idx.accounts, selector);
|
|
21
15
|
if (!a) {
|
|
22
16
|
emitError({ message: `no account matches "${selector}"` });
|
|
23
17
|
return 1;
|
|
24
18
|
}
|
|
25
|
-
if (a.
|
|
26
|
-
emitError({ message: `${a.
|
|
19
|
+
if (a.id === idx.activeId) {
|
|
20
|
+
emitError({ message: `${a.label} is the ACTIVE account - switch away before removing it.` });
|
|
27
21
|
return 1;
|
|
28
22
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
message: `cannot verify which account the LIVE credential belongs to (${e instanceof Error ? e.message : String(e)}) - refusing to remove while the live owner is unknown; repair the live credential or retry once the profile endpoint is reachable.`,
|
|
38
|
-
});
|
|
39
|
-
return 1;
|
|
40
|
-
}
|
|
41
|
-
if (liveAccount === a.accountUuid) {
|
|
42
|
-
emitError({ message: `${a.email}'s credential is currently LIVE (the active label is stale - a manual /login drifted it); run \`tokenmaxxing switch\` to move off it first.` });
|
|
43
|
-
return 1;
|
|
44
|
-
}
|
|
23
|
+
let liveOwner: string | null;
|
|
24
|
+
try {
|
|
25
|
+
liveOwner = await p.liveOwner();
|
|
26
|
+
} catch (e) {
|
|
27
|
+
emitError({
|
|
28
|
+
message: `cannot verify which account the LIVE credential belongs to (${e instanceof Error ? e.message : String(e)}) - refusing to remove while the live owner is unknown; repair the live credential or retry once the profile endpoint is reachable.`,
|
|
29
|
+
});
|
|
30
|
+
return 1;
|
|
45
31
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
for (const sampleDir of [join(paths.sampleDir, item), join(paths.sampleDir, `${item}-tick`)]) {
|
|
50
|
-
await deleteItem(isolatedTarget(sampleDir));
|
|
51
|
-
rmSync(sampleDir, { recursive: true, force: true });
|
|
32
|
+
if (liveOwner === a.id) {
|
|
33
|
+
emitError({ message: `${a.label}'s credential is currently LIVE (the active label is stale - a manual login drifted it); run \`tokenmaxxing switch${p.flag}\` to move off it first.` });
|
|
34
|
+
return 1;
|
|
52
35
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
saveSetupTokens({ ...setupTokens, tokens: setupTokens.tokens.filter((t) => t.accountUuid !== a.accountUuid) });
|
|
36
|
+
if (p.presentIds().has(a.id)) {
|
|
37
|
+
emitError({ message: `${a.label} is running in a live ${p.name} session - close that session before removing it.` });
|
|
38
|
+
return 1;
|
|
57
39
|
}
|
|
40
|
+
await p.removeCredentials(a);
|
|
41
|
+
idx.accounts = idx.accounts.filter((x) => x.id !== a.id);
|
|
42
|
+
saveAccounts(p.pool, idx);
|
|
58
43
|
console.log(`removed ${c.bold(a.label)} from the pool (${idx.accounts.length} left)`);
|
|
59
44
|
return 0;
|
|
60
45
|
});
|
package/src/cli/setuptoken.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdirSync, rmSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { resolveRealClaude } from "../lib/claudebin.ts";
|
|
4
4
|
import { withLock } from "../lib/lock.ts";
|
|
5
|
-
import { paths } from "../lib/paths.ts";
|
|
5
|
+
import { claudePool, credItemFor, paths } from "../lib/paths.ts";
|
|
6
6
|
import { loadAccounts } from "../lib/state.ts";
|
|
7
7
|
import { scrubCredentialEnv } from "../lib/usage.ts";
|
|
8
8
|
import { CURSOR_SECRET_VALUE_CAP_BYTES, cursorSecretValue, loadSetupTokens, saveSetupTokens, type SetupToken } from "../lib/setuptokens.ts";
|
|
@@ -53,19 +53,19 @@ async function mint(input: { real: string; item: string }): Promise<string | nul
|
|
|
53
53
|
|
|
54
54
|
function currentTokens(tokens: SetupToken[], accounts: Account[]): { label: string; token: string }[] {
|
|
55
55
|
return accounts.flatMap((a) => {
|
|
56
|
-
const stored = tokens.find((t) => t.accountUuid === a.
|
|
56
|
+
const stored = tokens.find((t) => t.accountUuid === a.id);
|
|
57
57
|
return stored ? [{ label: a.label, token: stored.token }] : [];
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async function rmToken(selector: string): Promise<number> {
|
|
62
|
-
const outcome = await withLock(
|
|
62
|
+
const outcome = await withLock(claudePool.lockFile, async (): Promise<{ error: string } | { kept: SetupToken[] }> => {
|
|
63
63
|
const store = loadSetupTokens();
|
|
64
|
-
const accounts = loadAccounts().accounts;
|
|
64
|
+
const accounts = loadAccounts(claudePool).accounts;
|
|
65
65
|
const pooled = accounts.find((a) => a.label === selector);
|
|
66
|
-
const orphan = (t: SetupToken) => !accounts.some((a) => a.
|
|
66
|
+
const orphan = (t: SetupToken) => !accounts.some((a) => a.id === t.accountUuid);
|
|
67
67
|
const targets = store.tokens.filter((t) =>
|
|
68
|
-
t.accountUuid === selector || (pooled ? t.accountUuid === pooled.
|
|
68
|
+
t.accountUuid === selector || (pooled ? t.accountUuid === pooled.id : t.label === selector && orphan(t)),
|
|
69
69
|
);
|
|
70
70
|
if (targets.length === 0) return { error: `no setup token stored for "${selector}"` };
|
|
71
71
|
if (targets.length > 1) return { error: `"${selector}" matches ${targets.length} stored tokens (${targets.map((t) => `${t.label} ${t.accountUuid}`).join(", ")}) - nothing removed` };
|
|
@@ -90,25 +90,25 @@ export async function cmdSetupToken(args: string[]): Promise<number> {
|
|
|
90
90
|
return 2;
|
|
91
91
|
}
|
|
92
92
|
let store = loadSetupTokens();
|
|
93
|
-
let idx = loadAccounts();
|
|
93
|
+
let idx = loadAccounts(claudePool);
|
|
94
94
|
if (!print) {
|
|
95
95
|
if (idx.accounts.length === 0) {
|
|
96
96
|
emitError({ message: "no accounts in the pool - run `tokenmaxxing init` first" });
|
|
97
97
|
return 1;
|
|
98
98
|
}
|
|
99
99
|
const real = resolveRealClaude();
|
|
100
|
-
const missing = idx.accounts.filter((a) => !store.tokens.some((t) => t.accountUuid === a.
|
|
100
|
+
const missing = idx.accounts.filter((a) => !store.tokens.some((t) => t.accountUuid === a.id));
|
|
101
101
|
if (missing.length === 0) console.log(c.dim("every pooled account already has a stored setup token (`setup-token rm <label>` drops one so it can be re-minted)"));
|
|
102
102
|
for (const a of missing) {
|
|
103
103
|
console.log();
|
|
104
|
-
console.log(`${c.bold(a.label)} - sign into this account in the browser (${a.email})`);
|
|
105
|
-
const token = await mint({ real, item: a.
|
|
104
|
+
console.log(`${c.bold(a.label)} - sign into this account in the browser (${a.email ?? a.label})`);
|
|
105
|
+
const token = await mint({ real, item: credItemFor(a.id) });
|
|
106
106
|
if (!token) return 1;
|
|
107
|
-
const stored = await withLock(
|
|
108
|
-
if (!loadAccounts().accounts.some((x) => x.
|
|
107
|
+
const stored = await withLock(claudePool.lockFile, async () => {
|
|
108
|
+
if (!loadAccounts(claudePool).accounts.some((x) => x.id === a.id)) return false;
|
|
109
109
|
const fresh = loadSetupTokens();
|
|
110
|
-
const tokens = fresh.tokens.filter((t) => t.accountUuid !== a.
|
|
111
|
-
saveSetupTokens({ ...fresh, tokens: [...tokens, { accountUuid: a.
|
|
110
|
+
const tokens = fresh.tokens.filter((t) => t.accountUuid !== a.id);
|
|
111
|
+
saveSetupTokens({ ...fresh, tokens: [...tokens, { accountUuid: a.id, label: a.label, token, mintedAt: Date.now() }] });
|
|
112
112
|
return true;
|
|
113
113
|
});
|
|
114
114
|
if (!stored) {
|
|
@@ -118,7 +118,7 @@ export async function cmdSetupToken(args: string[]): Promise<number> {
|
|
|
118
118
|
console.log(`${c.green("✓")} stored the setup token for ${c.bold(a.label)} ${c.dim("(ownership not verified: an inference-only token cannot read the profile endpoint, so the label is the sign-in you chose)")}`);
|
|
119
119
|
}
|
|
120
120
|
store = loadSetupTokens();
|
|
121
|
-
idx = loadAccounts();
|
|
121
|
+
idx = loadAccounts(claudePool);
|
|
122
122
|
}
|
|
123
123
|
if (store.tokens.length === 0) {
|
|
124
124
|
emitError({ message: "no setup tokens stored - run `tokenmaxxing setup-token` to mint them" });
|
|
@@ -126,7 +126,7 @@ export async function cmdSetupToken(args: string[]): Promise<number> {
|
|
|
126
126
|
}
|
|
127
127
|
const tokens = currentTokens(store.tokens, idx.accounts);
|
|
128
128
|
const orphaned = store.tokens
|
|
129
|
-
.filter((t) => !idx.accounts.some((a) => a.
|
|
129
|
+
.filter((t) => !idx.accounts.some((a) => a.id === t.accountUuid))
|
|
130
130
|
.map((t) => ({ label: t.label, accountUuid: t.accountUuid }));
|
|
131
131
|
if (tokens.length === 0) {
|
|
132
132
|
emitError({ message: "no stored setup token belongs to a pooled account - run `tokenmaxxing setup-token` to mint them" });
|