tokenmaxxing 1.55.0 → 1.56.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 +1 -1
- package/package.json +1 -1
- package/src/lib/install.ts +56 -12
- package/src/main.ts +24 -6
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ claude # use claude as always; each session gets its own accoun
|
|
|
73
73
|
| `tokenmaxxing check` | fold fresh tees, sample up to three stale accounts, and self-update a Bun global install once a day; the periodic timer runs this every tick |
|
|
74
74
|
| `tokenmaxxing doctor` | verify the install: PATH order, `claudeBin`, the five settings entries, the timer, the credential identity of every Codex store and of every Claude store whose access token is still fresh (an expiring Claude token is reported as unverifiable, not as a failure), codex hook trust, setup-token age, and shell aliases that shadow `claude` |
|
|
75
75
|
| `tokenmaxxing rename [--codex \| --grok \| --opencode-go] <sel> <label>` / `rm [...] <sel>` | manage a pool (one email can hold both a claude and a codex account); `rm` is refused for an account with a running supervised session |
|
|
76
|
-
| `tokenmaxxing uninstall` | remove the shims, settings entries, codex Stop hook, check timer, and rc PATH line (accounts/stores kept) |
|
|
76
|
+
| `tokenmaxxing uninstall [--yes]` | print the targets, then remove the shims, settings entries, codex Stop hook, check timer, and rc PATH line (accounts/stores kept); refused without `--yes` when `HOME` is the login home |
|
|
77
77
|
| `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 |
|
|
78
78
|
| `tokenmaxxing cursor init [dir]` | write the Claude relay subagent and `.cursor/environment.json` into a repo |
|
|
79
79
|
| `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 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.0",
|
|
4
4
|
"description": "Automatic Claude Code account switching: pool multiple accounts, start each session on its own account, and resume a session on a fresher account when quota fills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/lib/install.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { accessSync, appendFileSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { escape } from "es-toolkit";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { codexPaths, codexStoreDirFor, HOME, paths } from "./paths.ts";
|
|
@@ -343,21 +343,35 @@ function systemdTimerActive(): "active" | "not-active" | "unavailable" {
|
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
function
|
|
347
|
-
if (
|
|
346
|
+
function removeTimerUnits(): void {
|
|
347
|
+
if (process.platform === "darwin") {
|
|
348
|
+
rmSync(launchdPlist(), { force: true });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
rmSync(join(paths.systemdUserDir, "tokenmaxxing-check.timer"), { force: true });
|
|
352
|
+
rmSync(join(paths.systemdUserDir, "tokenmaxxing-check.service"), { force: true });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
type TimerOutcome = "removed" | "skipped" | "still-loaded";
|
|
356
|
+
|
|
357
|
+
function uninstallCheckTimer(live: boolean): TimerOutcome {
|
|
358
|
+
if (skipImperativeTimer()) return "skipped";
|
|
359
|
+
if (!live) {
|
|
360
|
+
removeTimerUnits();
|
|
361
|
+
return "removed";
|
|
362
|
+
}
|
|
348
363
|
if (process.platform === "darwin") {
|
|
349
364
|
const domain = launchdDomain();
|
|
350
365
|
const loaded = launchdJobLoaded();
|
|
351
366
|
const deactivated = loaded === "loaded" && domain != null ? run(["launchctl", "bootout", `${domain}/${LAUNCHD_LABEL}`]) : loaded === "not-loaded";
|
|
352
|
-
|
|
353
|
-
return deactivated;
|
|
367
|
+
removeTimerUnits();
|
|
368
|
+
return deactivated ? "removed" : "still-loaded";
|
|
354
369
|
}
|
|
355
370
|
const active = systemdTimerActive();
|
|
356
371
|
const deactivated = active === "active" ? run(["systemctl", "--user", "disable", "--now", "tokenmaxxing-check.timer"]) : active === "not-active";
|
|
357
|
-
|
|
358
|
-
rmSync(join(paths.systemdUserDir, "tokenmaxxing-check.service"), { force: true });
|
|
372
|
+
removeTimerUnits();
|
|
359
373
|
run(["systemctl", "--user", "daemon-reload"]);
|
|
360
|
-
return deactivated;
|
|
374
|
+
return deactivated ? "removed" : "still-loaded";
|
|
361
375
|
}
|
|
362
376
|
|
|
363
377
|
export function shellRcPath(): string | null {
|
|
@@ -448,17 +462,47 @@ export function removePathFromRc(rc: string): boolean {
|
|
|
448
462
|
return true;
|
|
449
463
|
}
|
|
450
464
|
|
|
451
|
-
const UninstallOutcomeSchema = z.object({
|
|
465
|
+
const UninstallOutcomeSchema = z.object({ timer: z.enum(["removed", "skipped", "still-loaded"]), pathLineRemoved: z.boolean() });
|
|
452
466
|
export type UninstallOutcome = z.infer<typeof UninstallOutcomeSchema>;
|
|
453
467
|
|
|
454
|
-
export function
|
|
468
|
+
export function loginHome(): string {
|
|
469
|
+
const cmd = process.platform === "darwin" ? ["id", "-P"] : ["getent", "passwd", String(process.getuid?.() ?? "")];
|
|
470
|
+
const proc = Bun.spawnSync(cmd, { stdout: "pipe", stderr: "ignore", timeout: 10_000 });
|
|
471
|
+
const home = proc.stdout.toString().trim().split(":").at(-2);
|
|
472
|
+
if (proc.exitCode !== 0 || home == null || home === "") throw new Error(`cannot read the login home: ${cmd.join(" ")} failed`);
|
|
473
|
+
return home;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function onLoginHome(): boolean {
|
|
477
|
+
return resolve(HOME) === resolve(loginHome());
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function uninstallTargets(live: boolean): string[] {
|
|
481
|
+
const timer =
|
|
482
|
+
process.platform === "darwin"
|
|
483
|
+
? `${launchdPlist()}${live ? ` (and launchctl bootout gui/${process.getuid?.() ?? "?"}/${LAUNCHD_LABEL})` : ""}`
|
|
484
|
+
: `${join(paths.systemdUserDir, "tokenmaxxing-check.timer")} and .service${live ? " (and systemctl --user disable --now tokenmaxxing-check.timer)" : ""}`;
|
|
485
|
+
const rc = shellRcPath();
|
|
486
|
+
return [
|
|
487
|
+
`${paths.claudeSettings}: the hook and statusline entries`,
|
|
488
|
+
`${codexPaths.hooksJson}: the codex Stop hook entry`,
|
|
489
|
+
...(skipImperativeTimer() ? [] : [timer]),
|
|
490
|
+
paths.supervisorLink,
|
|
491
|
+
codexSupervisorLink(),
|
|
492
|
+
join(paths.binDir, "xx"),
|
|
493
|
+
installedBin(),
|
|
494
|
+
...(rc == null ? [] : [`${rc}: the ${PATH_LINE_MARK} line`]),
|
|
495
|
+
];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function uninstallSupervisor(input: { liveTimer: boolean }): UninstallOutcome {
|
|
455
499
|
uninstallSettings();
|
|
456
|
-
const
|
|
500
|
+
const timer = uninstallCheckTimer(input.liveTimer);
|
|
457
501
|
uninstallCodexSupervisor();
|
|
458
502
|
for (const f of [paths.supervisorLink, join(paths.binDir, "xx"), installedBin()]) {
|
|
459
503
|
if (existsSync(f)) rmSync(f, { force: true });
|
|
460
504
|
}
|
|
461
505
|
const rc = shellRcPath();
|
|
462
506
|
const pathLineRemoved = rc != null && removePathFromRc(rc);
|
|
463
|
-
return {
|
|
507
|
+
return { timer, pathLineRemoved };
|
|
464
508
|
}
|
package/src/main.ts
CHANGED
|
@@ -25,11 +25,13 @@ import { cmdConfig } from "./cli/config.ts";
|
|
|
25
25
|
import { cmdSetupToken } from "./cli/setuptoken.ts";
|
|
26
26
|
import { cmdCloudRun } from "./cli/cloudrun.ts";
|
|
27
27
|
import { cmdCursorInit } from "./cli/cursorinit.ts";
|
|
28
|
-
import { timerDeactivationHint, uninstallSupervisor } from "./lib/install.ts";
|
|
28
|
+
import { onLoginHome, timerDeactivationHint, uninstallSupervisor, uninstallTargets } from "./lib/install.ts";
|
|
29
|
+
import { HOME } from "./lib/paths.ts";
|
|
29
30
|
import { c, emitError } from "./cli/render.ts";
|
|
30
31
|
|
|
31
32
|
const JSON_FLAG = "--json";
|
|
32
33
|
const CACHED_FLAG = "--cached";
|
|
34
|
+
const YES_FLAG = "--yes";
|
|
33
35
|
const CODEX_FLAG = "--codex";
|
|
34
36
|
const GROK_FLAG = "--grok";
|
|
35
37
|
const OPENCODE_GO_FLAG = "--opencode-go";
|
|
@@ -54,7 +56,7 @@ function printHelp(): void {
|
|
|
54
56
|
${c.cyan("tokenmaxxing doctor")} verify the install is intact
|
|
55
57
|
${c.cyan("tokenmaxxing rename")} [--codex | --grok | --opencode-go] <sel> <label>
|
|
56
58
|
${c.cyan("tokenmaxxing rm")} [--codex | --grok | --opencode-go] <sel>
|
|
57
|
-
${c.cyan("tokenmaxxing uninstall")} remove supervisor + settings entries
|
|
59
|
+
${c.cyan("tokenmaxxing uninstall")} [--yes] print the targets, then remove supervisor + settings entries (refused without ${c.cyan("--yes")} when HOME is the login home)
|
|
58
60
|
${c.cyan("tokenmaxxing setup-token")} [--print | rm <label|uuid>] Cursor Cloud only: mint one \`claude setup-token\` per pooled account (browser sign-in each) and print the TOKENMAXXING_TOKENS secret value; ${c.cyan("--print")} prints the stored set, ${c.cyan("rm")} drops one
|
|
59
61
|
${c.cyan("tokenmaxxing cursor init")} [dir] write the Claude relay subagent (.cursor/agents/claude.md) and .cursor/environment.json into a repo
|
|
60
62
|
${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
|
|
@@ -84,13 +86,14 @@ async function main(): Promise<number> {
|
|
|
84
86
|
jsonMode = argv.includes(JSON_FLAG);
|
|
85
87
|
const json = jsonMode;
|
|
86
88
|
const cached = argv.includes(CACHED_FLAG);
|
|
89
|
+
const yes = argv.includes(YES_FLAG);
|
|
87
90
|
const providerFlags = [CODEX_FLAG, GROK_FLAG, OPENCODE_GO_FLAG].filter((f) => argv.includes(f));
|
|
88
91
|
if (providerFlags.length > 1) {
|
|
89
92
|
emitError({ json, message: `${providerFlags.join(" and ")} are mutually exclusive - pick one pool` });
|
|
90
93
|
return 2;
|
|
91
94
|
}
|
|
92
95
|
const provider = argv.includes(CODEX_FLAG) ? codex : argv.includes(GROK_FLAG) ? grok : argv.includes(OPENCODE_GO_FLAG) ? opencodeGo : claude;
|
|
93
|
-
const args = argv.filter((a) => a !== JSON_FLAG && a !== CACHED_FLAG && a !== CODEX_FLAG && a !== GROK_FLAG && a !== OPENCODE_GO_FLAG);
|
|
96
|
+
const args = argv.filter((a) => a !== JSON_FLAG && a !== CACHED_FLAG && a !== YES_FLAG && a !== CODEX_FLAG && a !== GROK_FLAG && a !== OPENCODE_GO_FLAG);
|
|
94
97
|
const sub = args[0];
|
|
95
98
|
|
|
96
99
|
if (cached && sub != null && sub !== "status") {
|
|
@@ -98,6 +101,11 @@ async function main(): Promise<number> {
|
|
|
98
101
|
return 2;
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
if (yes && sub !== "uninstall") {
|
|
105
|
+
emitError({ json, message: `${YES_FLAG} applies to uninstall only, not ${sub ?? "status"}` });
|
|
106
|
+
return 2;
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
if (provider === codex && (sub == null || !CODEX_COMMANDS.has(sub))) {
|
|
102
110
|
emitError({ json, message: `${CODEX_FLAG} applies to ${[...CODEX_COMMANDS].join(", ")}, not ${sub ?? "status"}` });
|
|
103
111
|
return 2;
|
|
@@ -155,15 +163,25 @@ async function main(): Promise<number> {
|
|
|
155
163
|
return 2;
|
|
156
164
|
}
|
|
157
165
|
case "uninstall": {
|
|
158
|
-
|
|
166
|
+
if (args.length > 1) {
|
|
167
|
+
emitError({ message: `unknown uninstall option: ${args[1]} (uninstall takes only ${YES_FLAG})` });
|
|
168
|
+
return 2;
|
|
169
|
+
}
|
|
170
|
+
const live = onLoginHome();
|
|
171
|
+
console.log(`uninstall removes:\n${uninstallTargets(live).map((t) => ` ${t}`).join("\n")}`);
|
|
172
|
+
if (live && !yes) {
|
|
173
|
+
emitError({ message: `refused: HOME is the login home (${HOME}) - rerun with ${YES_FLAG} to remove these from the live install` });
|
|
174
|
+
return 2;
|
|
175
|
+
}
|
|
176
|
+
const out = uninstallSupervisor({ liveTimer: live });
|
|
159
177
|
const removed = [
|
|
160
178
|
"supervisor wrapper",
|
|
161
179
|
"settings entries",
|
|
162
|
-
...(out.
|
|
180
|
+
...(out.timer === "removed" ? ["check timer"] : []),
|
|
163
181
|
...(out.pathLineRemoved ? ["rc PATH line"] : []),
|
|
164
182
|
];
|
|
165
183
|
console.log(`removed ${removed.join(", ")}`);
|
|
166
|
-
if (
|
|
184
|
+
if (out.timer === "still-loaded") console.log(c.yellow(`⚠ the check job may still be loaded - run: ${timerDeactivationHint()}`));
|
|
167
185
|
if (!out.pathLineRemoved) console.log(c.dim("(no tokenmaxxing PATH line found in the shell rc)"));
|
|
168
186
|
console.log(`kept: accounts.json, config.json, and every account credential store (claude: stores/ and its keychain items on macOS; codex: codex-stores/; grok: grok-stores/; opencode-go: opencode-go-stores/) - remove accounts with \`xx rm\` to delete their credentials`);
|
|
169
187
|
return 0;
|