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.
- package/README.md +8 -6
- package/package.json +1 -1
- package/src/cli/auth.ts +1 -1
- package/src/cli/doctor.ts +28 -3
- package/src/cli/rename.ts +1 -1
- package/src/cli/status.ts +18 -9
- package/src/entries/musesupervisor.ts +335 -0
- package/src/lib/claudebin.ts +1 -0
- package/src/lib/cursor.ts +185 -0
- package/src/lib/grok.ts +3 -1
- package/src/lib/install.ts +10 -1
- package/src/lib/muse.ts +235 -30
- package/src/lib/onboard.ts +1 -1
- package/src/lib/paths.ts +46 -4
- package/src/lib/provider.ts +1 -1
- package/src/lib/sessions.ts +6 -3
- package/src/lib/state.ts +2 -1
- package/src/lib/statusonly.ts +6 -5
- package/src/lib/supervise.ts +2 -1
- package/src/lib/types.ts +1 -0
- package/src/main.ts +21 -14
package/src/lib/sessions.ts
CHANGED
|
@@ -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())) : {};
|
package/src/lib/statusonly.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
};
|
package/src/lib/supervise.ts
CHANGED
|
@@ -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
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
|
|
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")}
|
|
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
|
|
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
|
|
143
|
-
emitError({ json, message: `${provider.flag.trim()} applies to ${[...
|
|
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":
|