tokenmaxxing 1.173.0 → 1.175.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.
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, realpathSync, rmSync, statSync, type Dirent }
2
2
  import { basename, join, sep } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { errorMessage, log } from "./log.ts";
5
- import { codexPaths, grokPaths, musePaths, paths, piPaths } from "./paths.ts";
5
+ import { codexPaths, cursorPaths, grokPaths, musePaths, paths, piPaths } from "./paths.ts";
6
6
  import { stagingTarget, writeFileAtomic } from "./atomic.ts";
7
7
  import { presencePid } from "./presence.ts";
8
8
  import { readJsonFile } from "./state.ts";
@@ -44,7 +44,7 @@ function recordLiveSession(sid: string, current: string): void {
44
44
 
45
45
  const TMP_GRACE_MS = 3600 * 1000;
46
46
 
47
- const SWEEP_PARENTS = [paths.storesDir, codexPaths.storesDir, grokPaths.storesDir, musePaths.storesDir, join(piPaths.storesDir, "claude"), join(piPaths.storesDir, "codex"), paths.costDir];
47
+ const SWEEP_PARENTS = [paths.storesDir, codexPaths.storesDir, grokPaths.storesDir, musePaths.storesDir, cursorPaths.storesDir, join(piPaths.storesDir, "claude"), join(piPaths.storesDir, "codex"), paths.costDir];
48
48
 
49
49
  function listDir(dir: string, root: string): Dirent[] {
50
50
  if (!existsSync(dir)) return [];
@@ -59,12 +59,15 @@ 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, musePaths.presenceDir];
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));
66
66
  }
67
67
  }
68
+ for (const child of listDir(musePaths.storesDir, root)) {
69
+ if (child.isDirectory()) dirs.push(join(musePaths.storesDir, child.name, "muse"));
70
+ }
68
71
  return dirs;
69
72
  }
70
73
 
package/src/lib/state.ts CHANGED
@@ -35,11 +35,12 @@ export function loadConfig(): Config {
35
35
  cfg.codexBin = optionalEnv("TOKENMAXXING_CODEX_BIN") ?? cfg.codexBin;
36
36
  cfg.grokBin = optionalEnv("TOKENMAXXING_GROK_BIN") ?? cfg.grokBin;
37
37
  cfg.museBin = optionalEnv("TOKENMAXXING_MUSE_BIN") ?? cfg.museBin;
38
+ cfg.cursorBin = optionalEnv("TOKENMAXXING_CURSOR_BIN") ?? cfg.cursorBin;
38
39
  cfg.piBin = optionalEnv("TOKENMAXXING_PI_BIN") ?? cfg.piBin;
39
40
  return cfg;
40
41
  }
41
42
 
42
- export type BinKey = "claudeBin" | "codexBin" | "grokBin" | "museBin" | "piBin";
43
+ export type BinKey = "claudeBin" | "codexBin" | "grokBin" | "museBin" | "cursorBin" | "piBin";
43
44
 
44
45
  export function pinBinOverride(input: { key: BinKey; bin: string }): void {
45
46
  const raw: Record<string, unknown> = existsSync(paths.configJson) ? readJsonFile(paths.configJson, z.record(z.string(), z.unknown())) : {};
@@ -15,7 +15,7 @@ export type StatusOnlySpec = {
15
15
  flag: string;
16
16
  pool: PoolPaths;
17
17
  binName: string;
18
- binKey: "grokBin" | "museBin";
18
+ binKey: "grokBin" | "museBin" | "cursorBin";
19
19
  storeDirFor: (accountId: string) => string;
20
20
  authJsonFor: (accountId: string) => string;
21
21
  homeEnv: string;
@@ -28,9 +28,10 @@ export type StatusOnlySpec = {
28
28
  versionOk: (versionOutput: string) => boolean;
29
29
  importIntro: string;
30
30
  foundLive: (count: number) => string;
31
- installNotice: string;
31
+ install: () => void;
32
32
  loginStep: () => string;
33
33
  samplePool: Provider["samplePool"];
34
+ identify?: (harvest: Harvest) => Promise<void>;
34
35
  };
35
36
 
36
37
  // An entry with no identity (a Muse Code provider without an email) has no harvest; the first one that has one is pooled.
@@ -62,6 +63,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
62
63
  try {
63
64
  const harvest = p.exitCode === 0 ? firstHarvest(spec.readAuth(join(onboardDir, spec.onboardAuthRel))) : null;
64
65
  if (!harvest) console.error(c.red(`no ${spec.name} login landed in the isolated home - nothing added.`));
66
+ else await spec.identify?.(harvest);
65
67
  return harvest;
66
68
  } finally {
67
69
  rmSync(onboardDir, { recursive: true, force: true });
@@ -75,6 +77,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
75
77
  const harvest = live[0]?.harvest;
76
78
  if (!harvest) return login();
77
79
  console.log(c.dim(spec.foundLive(live.length)));
80
+ await spec.identify?.(harvest);
78
81
  return harvest;
79
82
  }
80
83
 
@@ -110,9 +113,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
110
113
  login,
111
114
  importLive,
112
115
  preflight,
113
- install: () => {
114
- console.log(c.dim(spec.installNotice));
115
- },
116
+ install: spec.install,
116
117
  loginStep: spec.loginStep,
117
118
  windowLabel: (name) => name.toLowerCase(),
118
119
  };
@@ -8,7 +8,7 @@ import { readLines } from "./proc.ts";
8
8
  import { writePresence } from "./presence.ts";
9
9
  import { restoreTermios } from "./tty.ts";
10
10
 
11
- export type WrappedProduct = "claude" | "codex" | "pi";
11
+ export type WrappedProduct = "claude" | "codex" | "pi" | "muse";
12
12
 
13
13
  export const SEAT_POLL_MS = 10_000;
14
14
  export const SEAT_RETRY_MS = 60_000;
@@ -29,6 +29,7 @@ const WRAPPED: Record<WrappedProduct, { setting: string; binary: string; events:
29
29
  claude: { setting: "claudeBin", binary: "Claude", events: "supervisor" },
30
30
  codex: { setting: "codexBin", binary: "codex", events: "codexsupervisor" },
31
31
  pi: { setting: "piBin", binary: "pi", events: "pisupervisor" },
32
+ muse: { setting: "museBin", binary: "muse", events: "musesupervisor" },
32
33
  };
33
34
 
34
35
  export function exitStatus(child: { exitCode: number | null; signalCode: string | null }): number {
package/src/lib/types.ts CHANGED
@@ -117,6 +117,7 @@ export const ConfigSchema = z
117
117
  codexBin: z.string().default(""),
118
118
  grokBin: z.string().default(""),
119
119
  museBin: z.string().default(""),
120
+ cursorBin: z.string().default(""),
120
121
  piBin: z.string().default(""),
121
122
  policy: z
122
123
  .object({
package/src/main.ts CHANGED
@@ -10,9 +10,11 @@ import { runStopFailureHook } from "./entries/stopfailurehook.ts";
10
10
  import { runCodexSupervisor } from "./entries/codexsupervisor.ts";
11
11
  import { runCodexStopHook } from "./entries/codexstophook.ts";
12
12
  import { runPiSupervisor } from "./entries/pisupervisor.ts";
13
+ import { runMuseSupervisor } from "./entries/musesupervisor.ts";
13
14
  import { claude } from "./lib/claude.ts";
14
15
  import { codex } from "./lib/codex.ts";
15
16
  import { grok } from "./lib/grok.ts";
17
+ import { cursor } from "./lib/cursor.ts";
16
18
  import { muse } from "./lib/muse.ts";
17
19
  import { cmdInit, cmdInitPi } from "./cli/init.ts";
18
20
  import { cmdAdd } from "./cli/add.ts";
@@ -38,10 +40,11 @@ const YES_FLAG = "--yes";
38
40
  const CODEX_FLAG = "--codex";
39
41
  const GROK_FLAG = "--grok";
40
42
  const MUSE_FLAG = "--muse";
43
+ const CURSOR_FLAG = "--cursor";
41
44
  const PI_FLAG = "--pi";
42
45
  const JSON_COMMANDS = new Set(["status", "config", "check"]);
43
46
  const CODEX_COMMANDS = new Set(["init", "add", "auth", "emails", "rm", "rename", "seat"]);
44
- const STATUS_ONLY_COMMANDS = new Set(["init", "add", "auth", "emails", "rm", "rename"]);
47
+ const POOL_COMMANDS = new Set(["init", "add", "auth", "emails", "rm", "rename"]);
45
48
  const MAX_OPERANDS = new Map([["init", 0], ["add", 0], ["emails", 0], ["rm", 1], ["rename", 2], ["key", 4]]);
46
49
 
47
50
  function printHelp(): void {
@@ -53,18 +56,19 @@ function printHelp(): void {
53
56
  ${c.cyan("tokenmaxxing init --codex")} same for codex: log in the first account, isolated, install codex supervisor + Stop hook
54
57
  ${c.cyan("tokenmaxxing init --pi")} install the pi supervisor: pi sessions run on pooled Claude or ChatGPT accounts that are logged into pi
55
58
  ${c.cyan("tokenmaxxing init --grok")} pool grok Build logins (status-only: no supervisor yet)
56
- ${c.cyan("tokenmaxxing init --muse")} pool Muse Code logins (status-only: no supervisor and no usage read)
59
+ ${c.cyan("tokenmaxxing init --muse")} same for Muse Code: log in the first account, isolated, install the muse supervisor
60
+ ${c.cyan("tokenmaxxing init --cursor")} pool Cursor CLI logins (status-only: no supervisor yet)
57
61
  ${c.cyan("tokenmaxxing add")} register an additional account (isolated login)
58
- ${c.cyan("tokenmaxxing add")} --codex | --grok | --muse same for the codex, grok, or muse pool
59
- ${c.cyan("tokenmaxxing auth")} [--codex | --grok | --muse] [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)
60
- ${c.cyan("tokenmaxxing auth")} [--codex | --grok | --muse] --emails <email>... log in and pool each listed email, skipping the ones already pooled (for moving to a new host: paste the output of ${c.cyan("emails")} from the old one)
61
- ${c.cyan("tokenmaxxing emails")} [--codex | --grok | --muse] print every pooled account's email, one per line, to copy into ${c.cyan("auth --emails")} on another host
62
+ ${c.cyan("tokenmaxxing add")} --codex | --grok | --muse | --cursor same for the codex, grok, muse, or cursor pool
63
+ ${c.cyan("tokenmaxxing auth")} [--codex | --grok | --muse | --cursor] [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)
64
+ ${c.cyan("tokenmaxxing auth")} [--codex | --grok | --muse | --cursor] --emails <email>... log in and pool each listed email, skipping the ones already pooled (for moving to a new host: paste the output of ${c.cyan("emails")} from the old one)
65
+ ${c.cyan("tokenmaxxing emails")} [--codex | --grok | --muse | --cursor] print every pooled account's email, one per line, to copy into ${c.cyan("auth --emails")} on another host
62
66
  ${c.cyan("tokenmaxxing auth --pi")} [--codex] [sel | --all] log a pooled Claude account (or a codex account with --codex) into pi, isolated (bare = pick from a list; --all = every account without a pi login)
63
67
  ${c.cyan("tokenmaxxing status")} [--cached] accounts with 5h / weekly / per-model usage bars (--cached: the stored figures, no sampling)
64
68
  ${c.cyan("tokenmaxxing config")} print the config path and the effective values (edit the file in an editor)
65
69
  ${c.cyan("tokenmaxxing doctor")} verify the install is intact
66
- ${c.cyan("tokenmaxxing rename")} [--codex | --grok | --muse] <sel> <label>
67
- ${c.cyan("tokenmaxxing rm")} [--codex | --grok | --muse] <sel>
70
+ ${c.cyan("tokenmaxxing rename")} [--codex | --grok | --muse | --cursor] <sel> <label>
71
+ ${c.cyan("tokenmaxxing rm")} [--codex | --grok | --muse | --cursor] <sel>
68
72
  ${c.cyan("tokenmaxxing key add")} <label> [credit-usd] [workspace-id] store an Anthropic or OpenRouter (sk-or-...) API key read from stdin or the prompt; Claude sessions run on it only while every pooled Claude account is at its limit (workspace-id: for a key that spans several workspaces; an OpenRouter key takes neither argument and reads its credit from OpenRouter)
69
73
  ${c.cyan("tokenmaxxing key credit")} <label> <usd> set the key's balance as the Console shows it and restart its spend count (an OpenRouter key takes no amount: key credit <label> clears its refusal)
70
74
  ${c.cyan("tokenmaxxing key rm")} <label> delete the key and its stored secret (refused while a session runs on it)
@@ -99,19 +103,22 @@ async function main(): Promise<number> {
99
103
  if (argv0 === "pi" || argv[0] === "__supervise-pi") {
100
104
  return runPiSupervisor(argv[0] === "__supervise-pi" ? argv.slice(1) : argv);
101
105
  }
106
+ if (argv0 === "muse" || argv[0] === "__supervise-muse") {
107
+ return runMuseSupervisor(argv[0] === "__supervise-muse" ? argv.slice(1) : argv);
108
+ }
102
109
 
103
110
  jsonMode = argv.includes(JSON_FLAG);
104
111
  const json = jsonMode;
105
112
  const cached = argv.includes(CACHED_FLAG);
106
113
  const yes = argv.includes(YES_FLAG);
107
- const providerFlags = [CODEX_FLAG, GROK_FLAG, MUSE_FLAG].filter((f) => argv.includes(f));
114
+ const providerFlags = [CODEX_FLAG, GROK_FLAG, MUSE_FLAG, CURSOR_FLAG].filter((f) => argv.includes(f));
108
115
  if (providerFlags.length > 1) {
109
116
  emitError({ json, message: `${providerFlags.join(" and ")} are mutually exclusive - pick one pool` });
110
117
  return 2;
111
118
  }
112
- const provider = argv.includes(CODEX_FLAG) ? codex : argv.includes(GROK_FLAG) ? grok : argv.includes(MUSE_FLAG) ? muse : claude;
119
+ const provider = argv.includes(CODEX_FLAG) ? codex : argv.includes(GROK_FLAG) ? grok : argv.includes(MUSE_FLAG) ? muse : argv.includes(CURSOR_FLAG) ? cursor : claude;
113
120
  const pi = argv.includes(PI_FLAG);
114
- const args = argv.filter((a) => a !== JSON_FLAG && a !== CACHED_FLAG && a !== YES_FLAG && a !== CODEX_FLAG && a !== GROK_FLAG && a !== MUSE_FLAG && a !== PI_FLAG);
121
+ const args = argv.filter((a) => a !== JSON_FLAG && a !== CACHED_FLAG && a !== YES_FLAG && a !== CODEX_FLAG && a !== GROK_FLAG && a !== MUSE_FLAG && a !== CURSOR_FLAG && a !== PI_FLAG);
115
122
  const sub = args[0];
116
123
 
117
124
  if (pi && sub !== "init" && sub !== "auth") {
@@ -139,8 +146,8 @@ async function main(): Promise<number> {
139
146
  return 2;
140
147
  }
141
148
 
142
- if (provider.statusOnly && (sub == null || !STATUS_ONLY_COMMANDS.has(sub))) {
143
- emitError({ json, message: `${provider.flag.trim()} applies to ${[...STATUS_ONLY_COMMANDS].join(", ")}, not ${sub ?? "status"}` });
149
+ if ((provider === grok || provider === muse || provider === cursor) && (sub == null || !POOL_COMMANDS.has(sub))) {
150
+ emitError({ json, message: `${provider.flag.trim()} applies to ${[...POOL_COMMANDS].join(", ")}, not ${sub ?? "status"}` });
144
151
  return 2;
145
152
  }
146
153
 
@@ -211,7 +218,7 @@ async function main(): Promise<number> {
211
218
  if (out.timer === "still-loaded") console.log(c.yellow(`⚠ the check job may still be loaded - run: ${deactivationHint(CHECK_JOB)}`));
212
219
  if (out.hub === "still-loaded") console.log(c.yellow(`⚠ the usage hub job may still be loaded - run: ${deactivationHint(HUB_JOB)}`));
213
220
  if (!out.pathLineRemoved) console.log(c.dim("(no tokenmaxxing PATH line found in the shell rc)"));
214
- console.log(`kept: accounts.json, config.json, and every account credential store (claude: stores/ and its keychain items on macOS; codex: codex-stores/; pi: pi-stores/; grok: grok-stores/; muse: muse-stores/), plus api-keys.json and the API keys in api-keys/ - remove accounts with \`xx rm\` and keys with \`xx key rm\` to delete their credentials`);
221
+ console.log(`kept: accounts.json, config.json, and every account credential store (claude: stores/ and its keychain items on macOS; codex: codex-stores/; pi: pi-stores/; grok: grok-stores/; muse: muse-stores/; cursor: cursor-stores/), plus api-keys.json and the API keys in api-keys/ - remove accounts with \`xx rm\` and keys with \`xx key rm\` to delete their credentials`);
215
222
  return 0;
216
223
  }
217
224
  case "help":