tokenmaxxing 1.10.0 → 1.12.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 +4 -4
- package/README.md +6 -5
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/pool-status/SKILL.md +2 -2
- package/agent-plugin/skills/pool-status/references/commands.md +1 -0
- package/agent-plugin/skills/switching-policy/SKILL.md +2 -2
- package/agent-plugin/skills/switching-policy/references/policy.md +1 -1
- package/package.json +1 -1
- package/src/cli/check.ts +17 -4
- package/src/cli/codexrm.ts +8 -7
- package/src/cli/codexswitch.ts +48 -18
- package/src/cli/config.ts +86 -42
- package/src/cli/doctor.ts +25 -9
- package/src/cli/ls.ts +37 -2
- package/src/cli/rename.ts +17 -14
- package/src/cli/render.ts +20 -0
- package/src/cli/rm.ts +12 -8
- package/src/cli/status.ts +239 -110
- package/src/cli/switch.ts +64 -28
- package/src/cli/watch.ts +13 -7
- package/src/entries/mcp.ts +1 -1
- package/src/lib/state.ts +2 -2
- package/src/main.ts +44 -22
package/src/cli/doctor.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { loadAccounts, loadConfig } from "../lib/state.ts";
|
|
|
7
7
|
import { readItem, liveTarget, parkedTarget } from "../lib/credstore.ts";
|
|
8
8
|
import { isAccessTokenExpiring, fetchTokenOrg } from "../lib/oauth.ts";
|
|
9
9
|
import { CredentialBlobSchema, type RolesResponse } from "../lib/types.ts";
|
|
10
|
-
import { c } from "./render.ts";
|
|
10
|
+
import { c, emitJson } from "./render.ts";
|
|
11
11
|
|
|
12
12
|
async function blobOrg(raw: string): Promise<RolesResponse | null> {
|
|
13
13
|
const creds = CredentialBlobSchema.parse(JSON.parse(raw)).claudeAiOauth;
|
|
@@ -15,11 +15,21 @@ async function blobOrg(raw: string): Promise<RolesResponse | null> {
|
|
|
15
15
|
return fetchTokenOrg(creds.accessToken);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export async function cmdDoctor(): Promise<number> {
|
|
19
|
-
|
|
18
|
+
export async function cmdDoctor(json = false): Promise<number> {
|
|
19
|
+
const checks: { ok: boolean; label: string; hint: string | null }[] = [];
|
|
20
|
+
const notes: string[] = [];
|
|
21
|
+
const warnings: string[] = [];
|
|
20
22
|
const check = (cond: boolean, label: string, hint?: string) => {
|
|
21
|
-
|
|
22
|
-
if (!cond)
|
|
23
|
+
checks.push({ ok: cond, label, hint: cond ? null : (hint ?? null) });
|
|
24
|
+
if (!json) console.log(`${cond ? c.green("✓") : c.red("✗")} ${label}${!cond && hint ? c.dim(` - ${hint}`) : ""}`);
|
|
25
|
+
};
|
|
26
|
+
const note = (text: string) => {
|
|
27
|
+
notes.push(text);
|
|
28
|
+
if (!json) console.log(c.dim(` - ${text}`));
|
|
29
|
+
};
|
|
30
|
+
const warn = (text: string) => {
|
|
31
|
+
warnings.push(text);
|
|
32
|
+
if (!json) console.log(c.yellow(`⚠ ${text}`));
|
|
23
33
|
};
|
|
24
34
|
|
|
25
35
|
check(existsSync(paths.supervisorLink), "claude supervisor wrapper present", "run `tokenmaxxing init`");
|
|
@@ -46,7 +56,7 @@ export async function cmdDoctor(): Promise<number> {
|
|
|
46
56
|
try {
|
|
47
57
|
const org = await blobOrg(live);
|
|
48
58
|
if (org) check(org.organization_uuid === active.organizationUuid, `live credential identity matches active (${active.email})`, `token belongs to ${org.organization_name} - run \`tokenmaxxing switch\``);
|
|
49
|
-
else
|
|
59
|
+
else note("live credential identity unverifiable (access token expired)");
|
|
50
60
|
} catch (e) {
|
|
51
61
|
check(false, `live credential identity matches active (${active.email})`, (e instanceof Error ? e.message : String(e)).slice(0, 100));
|
|
52
62
|
}
|
|
@@ -59,7 +69,7 @@ export async function cmdDoctor(): Promise<number> {
|
|
|
59
69
|
try {
|
|
60
70
|
const org = await blobOrg(parked);
|
|
61
71
|
if (org) check(org.organization_uuid === a.organizationUuid, `parked credential identity matches ${a.email}`, `token belongs to ${org.organization_name} - run \`tokenmaxxing auth ${a.label}\``);
|
|
62
|
-
else
|
|
72
|
+
else note(`${a.email} identity unverifiable (access token expired)`);
|
|
63
73
|
} catch (e) {
|
|
64
74
|
check(false, `parked credential identity matches ${a.email}`, (e instanceof Error ? e.message : String(e)).slice(0, 100));
|
|
65
75
|
}
|
|
@@ -77,11 +87,17 @@ export async function cmdDoctor(): Promise<number> {
|
|
|
77
87
|
const rc = shellRcPath();
|
|
78
88
|
if (rc && existsSync(rc)) {
|
|
79
89
|
for (const s of findClaudeShadowers(readFileSync(rc, "utf8"))) {
|
|
80
|
-
if (s.kind === "shadow")
|
|
81
|
-
else
|
|
90
|
+
if (s.kind === "shadow") warn(`${rc}: ${s.line.startsWith("alias ") ? "alias" : "function"} \`claude\` shadows the supervised claude wrapper - launches through it skip tokenmaxxing`);
|
|
91
|
+
else warn(`${rc}: alias \`${s.name}\` hardcodes a claude path and bypasses the supervisor - use plain \`claude\` in its body instead`);
|
|
82
92
|
}
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
const failed = checks.filter((entry) => !entry.ok).length;
|
|
96
|
+
const ok = failed === 0;
|
|
97
|
+
if (json) {
|
|
98
|
+
emitJson(ok ? { ok, checks, notes, warnings } : { ok, error: `issues found - ${failed} of ${checks.length} checks failed`, checks, notes, warnings });
|
|
99
|
+
return ok ? 0 : 1;
|
|
100
|
+
}
|
|
85
101
|
console.log();
|
|
86
102
|
console.log(ok ? c.green("all good ✓") : c.yellow("issues found - see above"));
|
|
87
103
|
return ok ? 0 : 1;
|
package/src/cli/ls.ts
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
import { loadAccounts } from "../lib/state.ts";
|
|
2
2
|
import { loadCodexAccounts } from "../lib/codexstate.ts";
|
|
3
3
|
import { liveCodexAccountId } from "../lib/codexsample.ts";
|
|
4
|
-
import { c, claudeTierLabel } from "./render.ts";
|
|
4
|
+
import { c, claudeTierLabel, emitJson } from "./render.ts";
|
|
5
5
|
|
|
6
|
-
export function cmdLs(): number {
|
|
6
|
+
export function cmdLs(json = false): number {
|
|
7
7
|
const idx = loadAccounts();
|
|
8
|
+
|
|
9
|
+
if (json) {
|
|
10
|
+
const codex = loadCodexAccounts();
|
|
11
|
+
const liveId = codex.accounts.length > 0 ? liveCodexAccountId() : null;
|
|
12
|
+
emitJson({
|
|
13
|
+
ok: true,
|
|
14
|
+
claude: {
|
|
15
|
+
activeAccountUuid: idx.activeAccountUuid,
|
|
16
|
+
accounts: idx.accounts.map((a) => ({
|
|
17
|
+
label: a.label,
|
|
18
|
+
email: a.email,
|
|
19
|
+
accountUuid: a.accountUuid,
|
|
20
|
+
organizationUuid: a.organizationUuid,
|
|
21
|
+
tier: claudeTierLabel(a),
|
|
22
|
+
active: a.accountUuid === idx.activeAccountUuid,
|
|
23
|
+
needsReauth: a.needsReauth === true,
|
|
24
|
+
addedAt: a.addedAt,
|
|
25
|
+
})),
|
|
26
|
+
},
|
|
27
|
+
codex: {
|
|
28
|
+
activeAccountId: liveId,
|
|
29
|
+
accounts: codex.accounts.map((account) => ({
|
|
30
|
+
label: account.label,
|
|
31
|
+
email: account.email,
|
|
32
|
+
accountId: account.accountId,
|
|
33
|
+
planType: account.planType,
|
|
34
|
+
active: account.accountId === liveId,
|
|
35
|
+
needsReauth: account.needsReauth === true,
|
|
36
|
+
addedAt: account.addedAt,
|
|
37
|
+
})),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
8
43
|
if (idx.accounts.length === 0) {
|
|
9
44
|
console.log(c.dim("no accounts yet - run `tokenmaxxing init` then `tokenmaxxing add`"));
|
|
10
45
|
}
|
package/src/cli/rename.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { withLock } from "../lib/lock.ts";
|
|
|
2
2
|
import { codexPaths, paths } from "../lib/paths.ts";
|
|
3
3
|
import { loadAccounts, saveAccounts } from "../lib/state.ts";
|
|
4
4
|
import { loadCodexAccounts, saveCodexAccounts } from "../lib/codexstate.ts";
|
|
5
|
-
import { c } from "./render.ts";
|
|
5
|
+
import { c, emitError, emitJson, plain } from "./render.ts";
|
|
6
6
|
import type { Account, CodexAccount } from "../lib/types.ts";
|
|
7
7
|
|
|
8
8
|
export function findAccount(accounts: Account[], selector: string): Account | undefined {
|
|
@@ -23,51 +23,54 @@ export function findCodexAccount(accounts: CodexAccount[], selector: string): Co
|
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
async function renameCodexAccount(input: { selector: string; newLabel: string }): Promise<number> {
|
|
26
|
+
async function renameCodexAccount(input: { selector: string; newLabel: string; json: boolean }): Promise<number> {
|
|
27
|
+
const { selector, newLabel, json } = input;
|
|
27
28
|
return withLock(codexPaths.lockFile, async () => {
|
|
28
29
|
const index = loadCodexAccounts();
|
|
29
|
-
const account = findCodexAccount(index.accounts,
|
|
30
|
+
const account = findCodexAccount(index.accounts, selector);
|
|
30
31
|
if (!account) {
|
|
31
|
-
|
|
32
|
+
emitError({ json, message: `no codex account matches "${selector}"` });
|
|
32
33
|
return 1;
|
|
33
34
|
}
|
|
34
|
-
const taken = index.accounts.find((x) => x.accountId !== account.accountId && x.label.toLowerCase() ===
|
|
35
|
+
const taken = index.accounts.find((x) => x.accountId !== account.accountId && x.label.toLowerCase() === newLabel.toLowerCase());
|
|
35
36
|
if (taken) {
|
|
36
|
-
|
|
37
|
+
emitError({ json, message: `label "${newLabel}" is already used by ${taken.accountId.slice(0, 8)} - labels must be unique within the pool` });
|
|
37
38
|
return 1;
|
|
38
39
|
}
|
|
39
40
|
const old = account.label;
|
|
40
|
-
account.label =
|
|
41
|
+
account.label = newLabel;
|
|
41
42
|
saveCodexAccounts({ index });
|
|
42
|
-
|
|
43
|
+
if (json) emitJson({ ok: true, pool: "codex", from: old, to: newLabel });
|
|
44
|
+
else console.log(`renamed codex account ${c.dim(old)} → ${c.bold(newLabel)}`);
|
|
43
45
|
return 0;
|
|
44
46
|
});
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
export async function cmdRename(argv: string[]): Promise<number> {
|
|
49
|
+
export async function cmdRename(argv: string[], json = false): Promise<number> {
|
|
48
50
|
const codex = argv.includes("--codex");
|
|
49
51
|
const [selector, newLabel] = argv.filter((a) => a !== "--codex");
|
|
50
52
|
if (!selector || !newLabel) {
|
|
51
|
-
|
|
53
|
+
emitError({ json, message: "usage: tokenmaxxing rename [--codex] <email|label|id> <new-label>", paint: plain });
|
|
52
54
|
return 2;
|
|
53
55
|
}
|
|
54
|
-
if (codex) return renameCodexAccount({ selector, newLabel });
|
|
56
|
+
if (codex) return renameCodexAccount({ selector, newLabel, json });
|
|
55
57
|
return withLock(paths.lockFile, async () => {
|
|
56
58
|
const idx = loadAccounts();
|
|
57
59
|
const a = findAccount(idx.accounts, selector);
|
|
58
60
|
if (!a) {
|
|
59
|
-
|
|
61
|
+
emitError({ json, message: `no claude account matches "${selector}" (codex accounts rename via --codex)` });
|
|
60
62
|
return 1;
|
|
61
63
|
}
|
|
62
64
|
const taken = idx.accounts.find((x) => x.accountUuid !== a.accountUuid && x.label.toLowerCase() === newLabel.toLowerCase());
|
|
63
65
|
if (taken) {
|
|
64
|
-
|
|
66
|
+
emitError({ json, message: `label "${newLabel}" is already used by ${taken.email} - labels must be unique within the pool` });
|
|
65
67
|
return 1;
|
|
66
68
|
}
|
|
67
69
|
const old = a.label;
|
|
68
70
|
a.label = newLabel;
|
|
69
71
|
saveAccounts(idx);
|
|
70
|
-
|
|
72
|
+
if (json) emitJson({ ok: true, pool: "claude", from: old, to: newLabel });
|
|
73
|
+
else console.log(`renamed ${c.dim(old)} → ${c.bold(newLabel)}`);
|
|
71
74
|
return 0;
|
|
72
75
|
});
|
|
73
76
|
}
|
package/src/cli/render.ts
CHANGED
|
@@ -75,3 +75,23 @@ export function fmtAgo(epochMs: number, now = Date.now()): string {
|
|
|
75
75
|
return `${m}m ago`;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export const plain = (s: string): string => s;
|
|
79
|
+
|
|
80
|
+
export function emitJson(value: unknown): void {
|
|
81
|
+
console.log(JSON.stringify(value, null, 2));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function emitError(input: {
|
|
85
|
+
json: boolean;
|
|
86
|
+
message: string;
|
|
87
|
+
notes?: string[];
|
|
88
|
+
extra?: Record<string, unknown>;
|
|
89
|
+
paint?: (s: string) => string;
|
|
90
|
+
}): void {
|
|
91
|
+
if (input.json) {
|
|
92
|
+
emitJson({ ok: false, error: input.message, ...input.extra });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.error((input.paint ?? c.red)(input.message));
|
|
96
|
+
for (const note of input.notes ?? []) console.error(c.dim(note));
|
|
97
|
+
}
|
package/src/cli/rm.ts
CHANGED
|
@@ -7,22 +7,22 @@ import { credItemFor, paths } from "../lib/paths.ts";
|
|
|
7
7
|
import { loadAccounts, saveAccounts } from "../lib/state.ts";
|
|
8
8
|
import { CredentialBlobSchema } from "../lib/types.ts";
|
|
9
9
|
import { findAccount } from "./rename.ts";
|
|
10
|
-
import { c } from "./render.ts";
|
|
10
|
+
import { c, emitError, emitJson, plain } from "./render.ts";
|
|
11
11
|
|
|
12
|
-
export async function cmdRm(selector?: string): Promise<number> {
|
|
12
|
+
export async function cmdRm(selector?: string, json = false): Promise<number> {
|
|
13
13
|
if (!selector) {
|
|
14
|
-
|
|
14
|
+
emitError({ json, message: "usage: tokenmaxxing rm <email|label|uuid>", paint: plain });
|
|
15
15
|
return 2;
|
|
16
16
|
}
|
|
17
17
|
return withLock(paths.lockFile, async () => {
|
|
18
18
|
const idx = loadAccounts();
|
|
19
19
|
const a = findAccount(idx.accounts, selector);
|
|
20
20
|
if (!a) {
|
|
21
|
-
|
|
21
|
+
emitError({ json, message: `no account matches "${selector}"` });
|
|
22
22
|
return 1;
|
|
23
23
|
}
|
|
24
24
|
if (a.accountUuid === idx.activeAccountUuid) {
|
|
25
|
-
|
|
25
|
+
emitError({ json, message: `${a.email} is the ACTIVE account - switch away before removing it.` });
|
|
26
26
|
return 1;
|
|
27
27
|
}
|
|
28
28
|
const live = await readItem(liveTarget());
|
|
@@ -32,11 +32,14 @@ export async function cmdRm(selector?: string): Promise<number> {
|
|
|
32
32
|
const liveCreds = CredentialBlobSchema.parse(JSON.parse(live)).claudeAiOauth;
|
|
33
33
|
liveOrg = (await fetchTokenOrg(liveCreds.accessToken)).organization_uuid;
|
|
34
34
|
} catch (e) {
|
|
35
|
-
|
|
35
|
+
emitError({
|
|
36
|
+
json,
|
|
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 roles endpoint is reachable.`,
|
|
38
|
+
});
|
|
36
39
|
return 1;
|
|
37
40
|
}
|
|
38
41
|
if (liveOrg === a.organizationUuid) {
|
|
39
|
-
|
|
42
|
+
emitError({ json, 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.` });
|
|
40
43
|
return 1;
|
|
41
44
|
}
|
|
42
45
|
}
|
|
@@ -46,7 +49,8 @@ export async function cmdRm(selector?: string): Promise<number> {
|
|
|
46
49
|
rmSync(sampleDir, { recursive: true, force: true });
|
|
47
50
|
idx.accounts = idx.accounts.filter((x) => x.accountUuid !== a.accountUuid);
|
|
48
51
|
saveAccounts(idx);
|
|
49
|
-
|
|
52
|
+
if (json) emitJson({ ok: true, pool: "claude", removed: a.label, remaining: idx.accounts.length });
|
|
53
|
+
else console.log(`removed ${c.bold(a.label)} from the pool (${idx.accounts.length} left)`);
|
|
50
54
|
return 0;
|
|
51
55
|
});
|
|
52
56
|
}
|