tokenmaxxing 1.33.0 → 1.34.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 CHANGED
@@ -76,7 +76,7 @@ claude # use claude as always
76
76
  | `tokenmaxxing setup-token [--print \| rm <label\|uuid>]` | Cursor Cloud only: mint one `claude setup-token` per pooled account (a browser sign-in each) and print the `TOKENMAXXING_TOKENS` secret value; `--print` prints the stored set, `rm` drops one |
77
77
  | `tokenmaxxing cursor init [dir]` | write the Claude relay subagent and `.cursor/environment.json` into a repo |
78
78
  | `tokenmaxxing cloud run [--session <id>] [--max-turns <n>] "<prompt>"` | on a Cursor Cloud VM: run `claude -p` on a setup token, rotate to the next token on a usage limit |
79
- | `--json` | machine-readable output: one JSON document on stdout for `status`, `config`, `doctor`, `check`, `switch`, `rename`, `rm`, `uninstall`, `setup-token --print`, `cursor init`, and `cloud run` (`ok` mirrors the exit code, failures add `error`) |
79
+ | `--json` | machine-readable output: one JSON document on stdout for `status`, `config`, `check`, and `switch` (`ok` mirrors the exit code, failures add `error`) |
80
80
 
81
81
  ## How switching decides
82
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenmaxxing",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "Automatic Claude Code account switching: pool multiple accounts and hot-swap when quota fills, resuming your session on the fresh account.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,7 +12,7 @@ import {
12
12
  spawnCloudClaude,
13
13
  transcriptBytes,
14
14
  } from "../lib/cloud.ts";
15
- import { c, emitError, emitJson, fmtReset } from "./render.ts";
15
+ import { c, emitError, fmtReset } from "./render.ts";
16
16
 
17
17
  const USAGE = "usage: tokenmaxxing cloud run [--session <id>] [--max-turns <n>] \"<prompt>\" (the prompt is also read from stdin)";
18
18
 
@@ -39,15 +39,15 @@ function parseFlags(args: string[]): { session: string | null; maxTurns: number
39
39
  return { session, maxTurns, prompt: positional.length > 0 ? positional.join(" ") : null };
40
40
  }
41
41
 
42
- export async function cmdCloudRun(args: string[], json = false): Promise<number> {
42
+ export async function cmdCloudRun(args: string[]): Promise<number> {
43
43
  const flags = parseFlags(args);
44
44
  if ("error" in flags) {
45
- emitError({ json, message: `${flags.error} - ${USAGE}` });
45
+ emitError({ message: `${flags.error} - ${USAGE}` });
46
46
  return 2;
47
47
  }
48
48
  const prompt = flags.prompt ?? (process.stdin.isTTY ? null : await Bun.stdin.text());
49
49
  if (prompt == null || prompt.trim() === "") {
50
- emitError({ json, message: USAGE });
50
+ emitError({ message: USAGE });
51
51
  return 2;
52
52
  }
53
53
  const tokens = readCloudTokens();
@@ -61,11 +61,7 @@ export async function cmdCloudRun(args: string[], json = false): Promise<number>
61
61
  return picked;
62
62
  });
63
63
  if (!pick.ok) {
64
- emitError({
65
- json,
66
- message: `every token is at its limit - earliest ${fmtReset(pick.earliestReset)}`,
67
- extra: { earliestReset: pick.earliestReset, session_id: sessionId },
68
- });
64
+ emitError({ message: `every token is at its limit - earliest ${fmtReset(pick.earliestReset)}` });
69
65
  return 1;
70
66
  }
71
67
  const label = pick.token.label;
@@ -99,17 +95,11 @@ export async function cmdCloudRun(args: string[], json = false): Promise<number>
99
95
  }
100
96
  const errors = result.subtype === "success" ? [result.result] : result.errors;
101
97
  emitError({
102
- json,
103
98
  message: `claude exited ${exitCode ?? "on signal"} (${result.subtype}): ${errors.join("; ")}`,
104
99
  notes: [`session ${result.session_id}`],
105
- extra: { session_id: result.session_id, subtype: result.subtype, errors },
106
100
  });
107
101
  return 1;
108
102
  }
109
- if (json) {
110
- emitJson({ ok: true, session_id: result.session_id, result: result.result, num_turns: result.num_turns, total_cost_usd: result.total_cost_usd });
111
- return 0;
112
- }
113
103
  console.log(result.result);
114
104
  console.log(`session ${result.session_id}`);
115
105
  return 0;
@@ -5,33 +5,32 @@ import { deleteParkedCodexAuth } from "../lib/codexauth.ts";
5
5
  import { liveCodexAccountId } from "../lib/codexsample.ts";
6
6
  import { presentCodexAccountIds } from "../lib/codexpresence.ts";
7
7
  import { findCodexAccount } from "./rename.ts";
8
- import { c, emitError, emitJson, plain } from "./render.ts";
8
+ import { c, emitError, plain } from "./render.ts";
9
9
 
10
- export async function cmdCodexRm(selector?: string, json = false): Promise<number> {
10
+ export async function cmdCodexRm(selector?: string): Promise<number> {
11
11
  if (!selector) {
12
- emitError({ json, message: "usage: tokenmaxxing rm --codex <email|label|id>", paint: plain });
12
+ emitError({ message: "usage: tokenmaxxing rm --codex <email|label|id>", paint: plain });
13
13
  return 2;
14
14
  }
15
15
  return withLock(codexPaths.lockFile, async () => {
16
16
  const index = loadCodexAccounts();
17
17
  const account = findCodexAccount(index.accounts, selector);
18
18
  if (!account) {
19
- emitError({ json, message: `no codex account matches "${selector}"` });
19
+ emitError({ message: `no codex account matches "${selector}"` });
20
20
  return 1;
21
21
  }
22
22
  if (liveCodexAccountId() === account.accountId) {
23
- emitError({ json, message: `${account.label} is the LIVE codex account - run \`tokenmaxxing switch --codex\` to move off it first.` });
23
+ emitError({ message: `${account.label} is the LIVE codex account - run \`tokenmaxxing switch --codex\` to move off it first.` });
24
24
  return 1;
25
25
  }
26
26
  if (presentCodexAccountIds().has(account.accountId)) {
27
- emitError({ json, message: `${account.label} is running in a live codex session - close that session before removing it.` });
27
+ emitError({ message: `${account.label} is running in a live codex session - close that session before removing it.` });
28
28
  return 1;
29
29
  }
30
30
  deleteParkedCodexAuth({ credFile: account.credFile });
31
31
  index.accounts = index.accounts.filter((x) => x.accountId !== account.accountId);
32
32
  saveCodexAccounts({ index });
33
- if (json) emitJson({ ok: true, pool: "codex", removed: account.label, remaining: index.accounts.length });
34
- else console.log(`removed codex account ${c.bold(account.label)} from the pool (${index.accounts.length} left)`);
33
+ console.log(`removed codex account ${c.bold(account.label)} from the pool (${index.accounts.length} left)`);
35
34
  return 0;
36
35
  });
37
36
  }
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { writeFileAtomic } from "../lib/atomic.ts";
5
- import { c, emitError, emitJson } from "./render.ts";
5
+ import { c, emitError } from "./render.ts";
6
6
 
7
7
  export const CLOUD_INSTALL_LINE =
8
8
  'curl -fsSL https://bun.sh/install | bash && curl -fsSL https://claude.ai/install.sh | bash && "$HOME/.bun/bin/bun" add -g tokenmaxxing && test -x "$HOME/.bun/bin/bun" && test -x "$HOME/.local/bin/claude" && test -e "$HOME/.bun/bin/tokenmaxxing"';
@@ -42,14 +42,14 @@ const ExistingEnvironmentSchema = z.looseObject({ user: z.string().optional() })
42
42
  const REPO_FILE_MODE = 0o644;
43
43
  const USAGE = "usage: tokenmaxxing cursor init [dir]";
44
44
 
45
- export function cmdCursorInit(args: string[], json = false): number {
45
+ export function cmdCursorInit(args: string[]): number {
46
46
  if (args.length > 1) {
47
- emitError({ json, message: USAGE });
47
+ emitError({ message: USAGE });
48
48
  return 2;
49
49
  }
50
50
  const dir = resolve(args[0] ?? ".");
51
51
  if (!existsSync(dir)) {
52
- emitError({ json, message: `no such directory: ${dir}` });
52
+ emitError({ message: `no such directory: ${dir}` });
53
53
  return 1;
54
54
  }
55
55
  const agentFile = join(dir, ".cursor", "agents", "claude.md");
@@ -64,10 +64,6 @@ export function cmdCursorInit(args: string[], json = false): number {
64
64
  } else if (existing.user === "root" || existing.user === "0") {
65
65
  warnings.push(".cursor/environment.json sets user to root; Claude Code refuses --dangerously-skip-permissions under root, so every relay call fails until user is a non-root account");
66
66
  }
67
- if (json) {
68
- emitJson({ ok: true, dir, agent, environment, install: CLOUD_INSTALL_LINE, warnings });
69
- return 0;
70
- }
71
67
  console.log(`${c.green("✓")} .cursor/agents/claude.md ${agent}`);
72
68
  if (environment === "written") {
73
69
  console.log(`${c.green("✓")} .cursor/environment.json written`);
package/src/cli/doctor.ts CHANGED
@@ -8,7 +8,7 @@ import { readItem, liveTarget, parkedTarget } from "../lib/credstore.ts";
8
8
  import { isAccessTokenExpiring, fetchTokenIdentity, describeIdentity } from "../lib/oauth.ts";
9
9
  import { CredentialBlobSchema, type TokenIdentity } from "../lib/types.ts";
10
10
  import { SETUP_TOKEN_STALE_MS, loadSetupTokens } from "../lib/setuptokens.ts";
11
- import { c, emitJson, fmtAgo } from "./render.ts";
11
+ import { c, fmtAgo } from "./render.ts";
12
12
 
13
13
  async function blobIdentity(raw: string): Promise<TokenIdentity | null> {
14
14
  const creds = CredentialBlobSchema.parse(JSON.parse(raw)).claudeAiOauth;
@@ -16,22 +16,14 @@ async function blobIdentity(raw: string): Promise<TokenIdentity | null> {
16
16
  return fetchTokenIdentity(creds.accessToken);
17
17
  }
18
18
 
19
- export async function cmdDoctor(json = false): Promise<number> {
20
- const checks: { ok: boolean; label: string; hint: string | null }[] = [];
21
- const notes: string[] = [];
22
- const warnings: string[] = [];
19
+ export async function cmdDoctor(): Promise<number> {
20
+ let failed = 0;
23
21
  const check = (cond: boolean, label: string, hint?: string) => {
24
- checks.push({ ok: cond, label, hint: cond ? null : (hint ?? null) });
25
- if (!json) console.log(`${cond ? c.green("✓") : c.red("✗")} ${label}${!cond && hint ? c.dim(` - ${hint}`) : ""}`);
26
- };
27
- const note = (text: string) => {
28
- notes.push(text);
29
- if (!json) console.log(c.dim(` - ${text}`));
30
- };
31
- const warn = (text: string) => {
32
- warnings.push(text);
33
- if (!json) console.log(c.yellow(`⚠ ${text}`));
22
+ if (!cond) failed++;
23
+ console.log(`${cond ? c.green("✓") : c.red("✗")} ${label}${!cond && hint ? c.dim(` - ${hint}`) : ""}`);
34
24
  };
25
+ const note = (text: string) => console.log(c.dim(` - ${text}`));
26
+ const warn = (text: string) => console.log(c.yellow(`⚠ ${text}`));
35
27
 
36
28
  check(existsSync(paths.supervisorLink), "claude supervisor wrapper present", "run `tokenmaxxing init`");
37
29
  check(existsSync(installedBin()), "tokenmaxxing binary installed", "run `tokenmaxxing init`");
@@ -102,13 +94,7 @@ export async function cmdDoctor(json = false): Promise<number> {
102
94
  }
103
95
  }
104
96
 
105
- const failed = checks.filter((entry) => !entry.ok).length;
106
- const ok = failed === 0;
107
- if (json) {
108
- emitJson(ok ? { ok, checks, notes, warnings } : { ok, error: `issues found - ${failed} of ${checks.length} checks failed`, checks, notes, warnings });
109
- return ok ? 0 : 1;
110
- }
111
97
  console.log();
112
- console.log(ok ? c.green("all good ✓") : c.yellow("issues found - see above"));
113
- return ok ? 0 : 1;
98
+ console.log(failed === 0 ? c.green("all good ✓") : c.yellow("issues found - see above"));
99
+ return failed === 0 ? 0 : 1;
114
100
  }
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, emitError, emitJson, plain } from "./render.ts";
5
+ import { c, emitError, 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,54 +23,52 @@ export function findCodexAccount(accounts: CodexAccount[], selector: string): Co
23
23
  );
24
24
  }
25
25
 
26
- async function renameCodexAccount(input: { selector: string; newLabel: string; json: boolean }): Promise<number> {
27
- const { selector, newLabel, json } = input;
26
+ async function renameCodexAccount(input: { selector: string; newLabel: string }): Promise<number> {
27
+ const { selector, newLabel } = input;
28
28
  return withLock(codexPaths.lockFile, async () => {
29
29
  const index = loadCodexAccounts();
30
30
  const account = findCodexAccount(index.accounts, selector);
31
31
  if (!account) {
32
- emitError({ json, message: `no codex account matches "${selector}"` });
32
+ emitError({ message: `no codex account matches "${selector}"` });
33
33
  return 1;
34
34
  }
35
35
  const taken = index.accounts.find((x) => x.accountId !== account.accountId && x.label.toLowerCase() === newLabel.toLowerCase());
36
36
  if (taken) {
37
- emitError({ json, message: `label "${newLabel}" is already used by ${taken.accountId.slice(0, 8)} - labels must be unique within the pool` });
37
+ emitError({ message: `label "${newLabel}" is already used by ${taken.accountId.slice(0, 8)} - labels must be unique within the pool` });
38
38
  return 1;
39
39
  }
40
40
  const old = account.label;
41
41
  account.label = newLabel;
42
42
  saveCodexAccounts({ index });
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
+ console.log(`renamed codex account ${c.dim(old)} → ${c.bold(newLabel)}`);
45
44
  return 0;
46
45
  });
47
46
  }
48
47
 
49
- export async function cmdRename(argv: string[], json = false): Promise<number> {
48
+ export async function cmdRename(argv: string[]): Promise<number> {
50
49
  const codex = argv.includes("--codex");
51
50
  const [selector, newLabel] = argv.filter((a) => a !== "--codex");
52
51
  if (!selector || !newLabel) {
53
- emitError({ json, message: "usage: tokenmaxxing rename [--codex] <email|label|id> <new-label>", paint: plain });
52
+ emitError({ message: "usage: tokenmaxxing rename [--codex] <email|label|id> <new-label>", paint: plain });
54
53
  return 2;
55
54
  }
56
- if (codex) return renameCodexAccount({ selector, newLabel, json });
55
+ if (codex) return renameCodexAccount({ selector, newLabel });
57
56
  return withLock(paths.lockFile, async () => {
58
57
  const idx = loadAccounts();
59
58
  const a = findAccount(idx.accounts, selector);
60
59
  if (!a) {
61
- emitError({ json, message: `no claude account matches "${selector}" (codex accounts rename via --codex)` });
60
+ emitError({ message: `no claude account matches "${selector}" (codex accounts rename via --codex)` });
62
61
  return 1;
63
62
  }
64
63
  const taken = idx.accounts.find((x) => x.accountUuid !== a.accountUuid && x.label.toLowerCase() === newLabel.toLowerCase());
65
64
  if (taken) {
66
- emitError({ json, message: `label "${newLabel}" is already used by ${taken.email} - labels must be unique within the pool` });
65
+ emitError({ message: `label "${newLabel}" is already used by ${taken.email} - labels must be unique within the pool` });
67
66
  return 1;
68
67
  }
69
68
  const old = a.label;
70
69
  a.label = newLabel;
71
70
  saveAccounts(idx);
72
- if (json) emitJson({ ok: true, pool: "claude", from: old, to: newLabel });
73
- else console.log(`renamed ${c.dim(old)} → ${c.bold(newLabel)}`);
71
+ console.log(`renamed ${c.dim(old)} → ${c.bold(newLabel)}`);
74
72
  return 0;
75
73
  });
76
74
  }
package/src/cli/render.ts CHANGED
@@ -82,7 +82,7 @@ export function emitJson(value: unknown): void {
82
82
  }
83
83
 
84
84
  export function emitError(input: {
85
- json: boolean;
85
+ json?: boolean;
86
86
  message: string;
87
87
  notes?: string[];
88
88
  extra?: Record<string, unknown>;
package/src/cli/rm.ts CHANGED
@@ -8,22 +8,22 @@ import { loadSetupTokens, saveSetupTokens } from "../lib/setuptokens.ts";
8
8
  import { loadAccounts, saveAccounts } from "../lib/state.ts";
9
9
  import { CredentialBlobSchema } from "../lib/types.ts";
10
10
  import { findAccount } from "./rename.ts";
11
- import { c, emitError, emitJson, plain } from "./render.ts";
11
+ import { c, emitError, plain } from "./render.ts";
12
12
 
13
- export async function cmdRm(selector?: string, json = false): Promise<number> {
13
+ export async function cmdRm(selector?: string): Promise<number> {
14
14
  if (!selector) {
15
- emitError({ json, message: "usage: tokenmaxxing rm <email|label|uuid>", paint: plain });
15
+ emitError({ message: "usage: tokenmaxxing rm <email|label|uuid>", paint: plain });
16
16
  return 2;
17
17
  }
18
18
  return withLock(paths.lockFile, async () => {
19
19
  const idx = loadAccounts();
20
20
  const a = findAccount(idx.accounts, selector);
21
21
  if (!a) {
22
- emitError({ json, message: `no account matches "${selector}"` });
22
+ emitError({ message: `no account matches "${selector}"` });
23
23
  return 1;
24
24
  }
25
25
  if (a.accountUuid === idx.activeAccountUuid) {
26
- emitError({ json, message: `${a.email} is the ACTIVE account - switch away before removing it.` });
26
+ emitError({ message: `${a.email} is the ACTIVE account - switch away before removing it.` });
27
27
  return 1;
28
28
  }
29
29
  const live = await readItem(liveTarget());
@@ -34,13 +34,12 @@ export async function cmdRm(selector?: string, json = false): Promise<number> {
34
34
  liveAccount = (await fetchTokenIdentity(liveCreds.accessToken)).accountUuid;
35
35
  } catch (e) {
36
36
  emitError({
37
- json,
38
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.`,
39
38
  });
40
39
  return 1;
41
40
  }
42
41
  if (liveAccount === a.accountUuid) {
43
- 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.` });
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.` });
44
43
  return 1;
45
44
  }
46
45
  }
@@ -56,8 +55,7 @@ export async function cmdRm(selector?: string, json = false): Promise<number> {
56
55
  if (setupTokens.tokens.some((t) => t.accountUuid === a.accountUuid)) {
57
56
  saveSetupTokens({ ...setupTokens, tokens: setupTokens.tokens.filter((t) => t.accountUuid !== a.accountUuid) });
58
57
  }
59
- if (json) emitJson({ ok: true, pool: "claude", removed: a.label, remaining: idx.accounts.length });
60
- else console.log(`removed ${c.bold(a.label)} from the pool (${idx.accounts.length} left)`);
58
+ console.log(`removed ${c.bold(a.label)} from the pool (${idx.accounts.length} left)`);
61
59
  return 0;
62
60
  });
63
61
  }
@@ -7,7 +7,7 @@ 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";
9
9
  import type { Account } from "../lib/types.ts";
10
- import { c, emitError, emitJson } from "./render.ts";
10
+ import { c, emitError } from "./render.ts";
11
11
 
12
12
  const TOKEN_BEGIN_MARKER = "Your OAuth token (valid for";
13
13
  const TOKEN_END_MARKER = "Store this token securely";
@@ -58,7 +58,7 @@ function currentTokens(tokens: SetupToken[], accounts: Account[]): { label: stri
58
58
  });
59
59
  }
60
60
 
61
- async function rmToken(selector: string, json: boolean): Promise<number> {
61
+ async function rmToken(selector: string): Promise<number> {
62
62
  const outcome = await withLock(paths.lockFile, async (): Promise<{ error: string } | { kept: SetupToken[] }> => {
63
63
  const store = loadSetupTokens();
64
64
  const accounts = loadAccounts().accounts;
@@ -74,35 +74,26 @@ async function rmToken(selector: string, json: boolean): Promise<number> {
74
74
  return { kept };
75
75
  });
76
76
  if ("error" in outcome) {
77
- emitError({ json, message: outcome.error });
77
+ emitError({ message: outcome.error });
78
78
  return 1;
79
79
  }
80
- const remaining = outcome.kept;
81
- if (json) {
82
- emitJson({ ok: true, removed: selector, remaining: remaining.map((t) => t.label) });
83
- return 0;
84
- }
85
80
  console.log(`removed the setup token for ${selector} (local only: Claude Code documents no revocation for setup tokens)`);
86
81
  return 0;
87
82
  }
88
83
 
89
- export async function cmdSetupToken(args: string[], json = false): Promise<number> {
84
+ export async function cmdSetupToken(args: string[]): Promise<number> {
90
85
  const [sub, label] = args;
91
- if (sub === "rm" && label !== undefined && args.length === 2) return rmToken(label, json);
86
+ if (sub === "rm" && label !== undefined && args.length === 2) return rmToken(label);
92
87
  const print = sub === "--print" && args.length === 1;
93
88
  if (args.length > 0 && !print) {
94
- emitError({ json, message: USAGE });
95
- return 2;
96
- }
97
- if (json && !print) {
98
- emitError({ json, message: "setup-token mints through a browser login flow and has no --json form; `setup-token --print --json` prints the stored set" });
89
+ emitError({ message: USAGE });
99
90
  return 2;
100
91
  }
101
92
  let store = loadSetupTokens();
102
93
  let idx = loadAccounts();
103
94
  if (!print) {
104
95
  if (idx.accounts.length === 0) {
105
- emitError({ json, message: "no accounts in the pool - run `tokenmaxxing init` first" });
96
+ emitError({ message: "no accounts in the pool - run `tokenmaxxing init` first" });
106
97
  return 1;
107
98
  }
108
99
  const real = resolveRealClaude();
@@ -130,7 +121,7 @@ export async function cmdSetupToken(args: string[], json = false): Promise<numbe
130
121
  idx = loadAccounts();
131
122
  }
132
123
  if (store.tokens.length === 0) {
133
- emitError({ json, message: "no setup tokens stored - run `tokenmaxxing setup-token` to mint them" });
124
+ emitError({ message: "no setup tokens stored - run `tokenmaxxing setup-token` to mint them" });
134
125
  return 1;
135
126
  }
136
127
  const tokens = currentTokens(store.tokens, idx.accounts);
@@ -138,15 +129,11 @@ export async function cmdSetupToken(args: string[], json = false): Promise<numbe
138
129
  .filter((t) => !idx.accounts.some((a) => a.accountUuid === t.accountUuid))
139
130
  .map((t) => ({ label: t.label, accountUuid: t.accountUuid }));
140
131
  if (tokens.length === 0) {
141
- emitError({ json, message: "no stored setup token belongs to a pooled account - run `tokenmaxxing setup-token` to mint them" });
132
+ emitError({ message: "no stored setup token belongs to a pooled account - run `tokenmaxxing setup-token` to mint them" });
142
133
  return 1;
143
134
  }
144
135
  const secret = cursorSecretValue(tokens);
145
136
  const bytes = Buffer.byteLength(secret);
146
- if (json) {
147
- emitJson({ ok: true, tokens, orphaned, bytes, cap: CURSOR_SECRET_VALUE_CAP_BYTES });
148
- return 0;
149
- }
150
137
  console.log();
151
138
  if (orphaned.length > 0) {
152
139
  console.log(c.dim(`left out of the secret (no longer in the pool): ${orphaned.map((t) => `${t.label} (${t.accountUuid})`).join(", ")}; \`setup-token rm <label|uuid>\` drops one`));
package/src/main.ts CHANGED
@@ -27,11 +27,11 @@ import { cmdSetupToken } from "./cli/setuptoken.ts";
27
27
  import { cmdCloudRun } from "./cli/cloudrun.ts";
28
28
  import { cmdCursorInit } from "./cli/cursorinit.ts";
29
29
  import { timerDeactivationHint, uninstallSupervisor } from "./lib/install.ts";
30
- import { c, emitError, emitJson } from "./cli/render.ts";
30
+ import { c, emitError } from "./cli/render.ts";
31
31
 
32
32
  const JSON_FLAG = "--json";
33
33
  const CACHED_FLAG = "--cached";
34
- const INTERACTIVE_COMMANDS = new Set(["init", "add", "auth"]);
34
+ const JSON_COMMANDS = new Set(["status", "config", "check", "switch"]);
35
35
 
36
36
  function printHelp(): void {
37
37
  console.log(`${c.bold("tokenmaxxing")} - automatic Claude Code account switching
@@ -55,7 +55,7 @@ function printHelp(): void {
55
55
  ${c.cyan("tokenmaxxing cursor init")} [dir] write the Claude relay subagent (.cursor/agents/claude.md) and .cursor/environment.json into a repo
56
56
  ${c.cyan("tokenmaxxing cloud run")} [--session <id>] [--max-turns <n>] "<prompt>" on a Cursor Cloud VM: run claude -p on a setup token from TOKENMAXXING_TOKENS, rotate to the next token on a usage limit
57
57
 
58
- ${c.cyan("--json")} print one JSON document on stdout instead of text (status, config, doctor, check, switch, rename, rm, uninstall, setup-token --print, cursor init, cloud run); every document carries ${c.bold("ok")}, failures add ${c.bold("error")}
58
+ ${c.cyan("--json")} print one JSON document on stdout instead of text (status, config, check, switch); every document carries ${c.bold("ok")}, failures add ${c.bold("error")}
59
59
 
60
60
  ${c.dim("(aliased as")} ${c.cyan("xx")}${c.dim(")")} - then just run ${c.bold("claude")} as always; it switches accounts near quota automatically.`);
61
61
  }
@@ -88,8 +88,8 @@ async function main(): Promise<number> {
88
88
  return 2;
89
89
  }
90
90
 
91
- if (json && sub != null && INTERACTIVE_COMMANDS.has(sub)) {
92
- emitError({ json, message: `${sub} is interactive (it runs a login flow) and has no --json form` });
91
+ if (json && sub != null && !JSON_COMMANDS.has(sub)) {
92
+ emitError({ json, message: `${sub} has no ${JSON_FLAG} form (${JSON_FLAG} applies to ${[...JSON_COMMANDS].join(", ")})` });
93
93
  return 2;
94
94
  }
95
95
  if (!(sub != null && sub.startsWith("__")) && !process.env.TOKENMAXXING_PROBE) {
@@ -135,21 +135,21 @@ async function main(): Promise<number> {
135
135
  case "init": return args.includes("--codex") ? cmdCodexInit() : cmdInit();
136
136
  case "add": return args.includes("--codex") ? cmdCodexAdd() : cmdAdd();
137
137
  case "auth": return cmdAuth(args.slice(1));
138
- case "doctor": return cmdDoctor(json);
138
+ case "doctor": return cmdDoctor();
139
139
  case "rm": {
140
140
  const rest = args.slice(1).filter((a) => a !== "--codex");
141
- return args.includes("--codex") ? cmdCodexRm(rest[0], json) : cmdRm(rest[0], json);
141
+ return args.includes("--codex") ? cmdCodexRm(rest[0]) : cmdRm(rest[0]);
142
142
  }
143
- case "rename": return cmdRename(args.slice(1), json);
144
- case "setup-token": return cmdSetupToken(args.slice(1), json);
143
+ case "rename": return cmdRename(args.slice(1));
144
+ case "setup-token": return cmdSetupToken(args.slice(1));
145
145
  case "cursor": {
146
- if (args[1] === "init") return cmdCursorInit(args.slice(2), json);
147
- emitError({ json, message: "usage: tokenmaxxing cursor init [dir]" });
146
+ if (args[1] === "init") return cmdCursorInit(args.slice(2));
147
+ emitError({ message: "usage: tokenmaxxing cursor init [dir]" });
148
148
  return 2;
149
149
  }
150
150
  case "cloud": {
151
- if (args[1] === "run") return cmdCloudRun(args.slice(2), json);
152
- emitError({ json, message: 'usage: tokenmaxxing cloud run [--session <id>] [--max-turns <n>] "<prompt>"' });
151
+ if (args[1] === "run") return cmdCloudRun(args.slice(2));
152
+ emitError({ message: 'usage: tokenmaxxing cloud run [--session <id>] [--max-turns <n>] "<prompt>"' });
153
153
  return 2;
154
154
  }
155
155
  case "uninstall": {
@@ -160,10 +160,6 @@ async function main(): Promise<number> {
160
160
  ...(out.timerDeactivated ? ["check timer"] : []),
161
161
  ...(out.pathLineRemoved ? ["rc PATH line"] : []),
162
162
  ];
163
- if (json) {
164
- emitJson({ ok: true, removed, timerDeactivated: out.timerDeactivated, pathLineRemoved: out.pathLineRemoved });
165
- return 0;
166
- }
167
163
  console.log(`removed ${removed.join(", ")}`);
168
164
  if (!out.timerDeactivated) console.log(c.yellow(`⚠ the check job may still be loaded - run: ${timerDeactivationHint()}`));
169
165
  if (!out.pathLineRemoved) console.log(c.dim("(no tokenmaxxing PATH line found in the shell rc)"));
@@ -176,8 +172,8 @@ async function main(): Promise<number> {
176
172
  printHelp();
177
173
  return 0;
178
174
  default:
179
- emitError({ json, message: `unknown command: ${sub}` });
180
- if (!json) printHelp();
175
+ emitError({ message: `unknown command: ${sub}` });
176
+ printHelp();
181
177
  return 2;
182
178
  }
183
179
  }