tokenmaxxing 1.107.0 → 1.109.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/package.json +1 -1
- package/src/cli/seat.ts +25 -4
- package/src/lib/claude.ts +35 -2
- package/src/lib/codex.ts +3 -5
- package/src/lib/grok.ts +1 -0
- package/src/lib/opencodego.ts +31 -2
- package/src/lib/paths.ts +1 -0
- package/src/lib/picker.ts +1 -0
- package/src/lib/provider.ts +2 -0
- package/src/lib/sessions.ts +1 -1
- package/src/lib/statusonly.ts +2 -1
- package/src/main.ts +11 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.109.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/cli/seat.ts
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
|
+
import { borrowClaudeSeat } from "../lib/claude.ts";
|
|
1
2
|
import { borrowCodexSeat } from "../lib/codex.ts";
|
|
3
|
+
import { borrowOpencodeGoSeat } from "../lib/opencodego.ts";
|
|
2
4
|
import { emitError } from "./render.ts";
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
const POOLS = {
|
|
7
|
+
claude: {
|
|
8
|
+
borrow: borrowClaudeSeat,
|
|
9
|
+
usage: "usage: tokenmaxxing seat <pid> - print the CLAUDE_SECURESTORAGE_CONFIG_DIR of one pooled claude account, lent to <pid> until it exits and shared with host sessions and other borrowers",
|
|
10
|
+
none: "no usable claude account (pool empty, or every account at a limit or flagged for reauthentication)",
|
|
11
|
+
},
|
|
12
|
+
codex: {
|
|
13
|
+
borrow: borrowCodexSeat,
|
|
14
|
+
usage: "usage: tokenmaxxing seat --codex <pid> - print the CODEX_HOME of one pooled account, reserved to <pid> alone until it exits, because one codex auth.json serves one machine or one serialized job stream (https://learn.chatgpt.com/docs/auth/ci-cd-auth)",
|
|
15
|
+
none: "no usable codex account (pool empty, every account live in another session, or every account at a limit) - use the ambient codex login",
|
|
16
|
+
},
|
|
17
|
+
"opencode-go": {
|
|
18
|
+
borrow: borrowOpencodeGoSeat,
|
|
19
|
+
usage: "usage: tokenmaxxing seat --opencode-go <pid> - print the store directory of one pooled opencode-go key, whose auth.json is an opencode auth file, lent to <pid> until it exits and shared with other borrowers",
|
|
20
|
+
none: "no usable opencode-go key (pool empty, or no store holds a usable key)",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export async function cmdSeat(pool: keyof typeof POOLS, pidRaw: string | undefined, extra: string[]): Promise<number> {
|
|
25
|
+
const seat = POOLS[pool];
|
|
5
26
|
const pid = pidRaw != null && /^(0|[1-9][0-9]*)$/.test(pidRaw) ? Number(pidRaw) : NaN;
|
|
6
27
|
if (!Number.isSafeInteger(pid) || pid < 2 || extra.length > 0) {
|
|
7
|
-
emitError({ message:
|
|
28
|
+
emitError({ message: seat.usage });
|
|
8
29
|
return 2;
|
|
9
30
|
}
|
|
10
|
-
const granted = await
|
|
31
|
+
const granted = await seat.borrow(pid);
|
|
11
32
|
if (!granted) {
|
|
12
|
-
emitError({ message:
|
|
33
|
+
emitError({ message: seat.none });
|
|
13
34
|
return 1;
|
|
14
35
|
}
|
|
15
36
|
if ("denied" in granted) {
|
package/src/lib/claude.ts
CHANGED
|
@@ -11,8 +11,8 @@ import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential
|
|
|
11
11
|
import { claudePool, env, paths, seatFromEnv, storeDirFor } from "./paths.ts";
|
|
12
12
|
import { landWindows, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
13
13
|
import { deletePiStore } from "./piauth.ts";
|
|
14
|
-
import { livingPresences } from "./presence.ts";
|
|
15
|
-
import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
|
|
14
|
+
import { livingPresences, writePresence } from "./presence.ts";
|
|
15
|
+
import { StoreUnusableError, type Observation, type Provider, type SampleReport, type SeatBorrow } from "./provider.ts";
|
|
16
16
|
import { foldTee, readyToSample, runSample, sampleAccountUsage, sampleDue, sampleIntervalMs, teeObservation, usageBlockedUntil } from "./sample.ts";
|
|
17
17
|
import { clearUsageSnapshot, liveWaitClaims, loadAccounts, loadConfig, loadUsageSnapshot, pinBinOverride, readJsonFile, saveAccounts, type Harvest } from "./state.ts";
|
|
18
18
|
import { saveTermios, restoreTermios } from "./tty.ts";
|
|
@@ -142,6 +142,39 @@ export function pickSeat(now: number, model: ModelInfo | null, eligible: (a: Acc
|
|
|
142
142
|
return pickBest(candidates, ctx) ?? pickEarliestReset(candidates, ctx)?.account ?? null;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
export async function borrowClaudeSeat(pid: number): Promise<SeatBorrow> {
|
|
146
|
+
const seatId = `seat-${pid}`;
|
|
147
|
+
const cfg = loadConfig();
|
|
148
|
+
const bars = thresholdBars(cfg);
|
|
149
|
+
const granted = await withLock(claudePool.lockFile, async (): Promise<SeatBorrow> => {
|
|
150
|
+
const idx = loadAccounts(claudePool);
|
|
151
|
+
let dirty = false;
|
|
152
|
+
for (const a of idx.accounts) dirty = foldTee(a, bars) || dirty;
|
|
153
|
+
if (dirty) saveAccounts(claudePool, idx);
|
|
154
|
+
const living = livingPresences(paths.presenceDir);
|
|
155
|
+
const held = living.find((p) => p.id === seatId);
|
|
156
|
+
const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
|
|
157
|
+
if (heldAccount) {
|
|
158
|
+
if (heldAccount.needsReauth === true) {
|
|
159
|
+
return { denied: "the account this pid holds needs reauthentication - run `tokenmaxxing auth` and borrow again" };
|
|
160
|
+
}
|
|
161
|
+
if (!(await storeUsable(heldAccount))) {
|
|
162
|
+
return { denied: "the account this pid holds has no usable credential in its store - refusing to hand back a credential-less seat" };
|
|
163
|
+
}
|
|
164
|
+
return { store: storeDirFor(heldAccount.id), id: heldAccount.id, reused: true };
|
|
165
|
+
}
|
|
166
|
+
const open = idx.accounts.filter((a) => a.needsReauth !== true);
|
|
167
|
+
const usable = await Promise.all(open.map(storeUsable));
|
|
168
|
+
const ctx: PickCtx = { now: Date.now(), thresholds: bars, currentId: null, families: gatedFamilies(null, cfg.policy.switchModels), seats: presence() };
|
|
169
|
+
const picked = pickBest(open.filter((_, i) => usable[i]), ctx);
|
|
170
|
+
if (!picked) return null;
|
|
171
|
+
writePresence({ dir: paths.presenceDir, id: seatId, accountId: picked.id, pid });
|
|
172
|
+
return { store: storeDirFor(picked.id), id: picked.id, reused: false };
|
|
173
|
+
});
|
|
174
|
+
if (granted && !("denied" in granted)) log("seat.grant", { account: granted.id.slice(0, 8), pid, reused: granted.reused });
|
|
175
|
+
return granted;
|
|
176
|
+
}
|
|
177
|
+
|
|
145
178
|
async function removeCredentials(a: Account): Promise<void> {
|
|
146
179
|
await deleteItem(storeTarget(a.id));
|
|
147
180
|
rmSync(storeDirFor(a.id), { recursive: true, force: true });
|
package/src/lib/codex.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { withLock } from "./lock.ts";
|
|
|
13
13
|
import { errorMessage, log } from "./log.ts";
|
|
14
14
|
import { codexPaths, codexPool, codexSeatFromEnv } from "./paths.ts";
|
|
15
15
|
import { isExhausted, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
16
|
-
import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
|
|
16
|
+
import { StoreUnusableError, type Observation, type Provider, type SampleReport, type SeatBorrow } from "./provider.ts";
|
|
17
17
|
import { loadAccounts, loadConfig, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
18
18
|
import { restoreTermios, saveTermios } from "./tty.ts";
|
|
19
19
|
import type { Account, CodexAuthJson, CodexUsage, Config } from "./types.ts";
|
|
@@ -239,9 +239,7 @@ export function pickCodexSeat(now: number, eligible: (a: Account) => boolean = (
|
|
|
239
239
|
return pickBest(open.filter((a) => !isExhausted(a, ctx)), ctx) ?? pickEarliestReset(open, ctx)?.account ?? null;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
export
|
|
243
|
-
|
|
244
|
-
export async function borrowCodexSeat(pid: number): Promise<CodexBorrow> {
|
|
242
|
+
export async function borrowCodexSeat(pid: number): Promise<SeatBorrow> {
|
|
245
243
|
const now = Date.now();
|
|
246
244
|
const seatId = `seat-${pid}`;
|
|
247
245
|
const cfg = loadConfig();
|
|
@@ -250,7 +248,7 @@ export async function borrowCodexSeat(pid: number): Promise<CodexBorrow> {
|
|
|
250
248
|
.accounts.filter((a) => a.needsReauth !== true && codexStoreUsable(a.id))
|
|
251
249
|
.map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false })),
|
|
252
250
|
);
|
|
253
|
-
const granted = await withLock(codexPool.lockFile, ():
|
|
251
|
+
const granted = await withLock(codexPool.lockFile, (): SeatBorrow => {
|
|
254
252
|
const ctx = codexPickCtx(now, null);
|
|
255
253
|
const idx = loadAccounts(codexPool);
|
|
256
254
|
const held = livingPresences(codexPaths.presenceDir).find((p) => p.id === seatId);
|
package/src/lib/grok.ts
CHANGED
|
@@ -131,6 +131,7 @@ const base = statusOnlyProvider({
|
|
|
131
131
|
liveAuthPath: () => join(grokPaths.home, "auth.json"),
|
|
132
132
|
readAuth,
|
|
133
133
|
liveId: () => grokSeatFromEnv(loadAccounts(grokPool).accounts.map((a) => a.id)),
|
|
134
|
+
presence: () => new Map(),
|
|
134
135
|
versionOk: (out) => out.toLowerCase().includes("grok"),
|
|
135
136
|
importIntro: "Opening an isolated grok login for your first pooled account - the login you already have stays as it is.",
|
|
136
137
|
foundLive: (count) => `found ${count} grok login(s) in ${grokPaths.home} - pooling the first; use \`tokenmaxxing add --grok\` for the rest.`,
|
package/src/lib/opencodego.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { countBy, minBy } from "es-toolkit";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
4
5
|
import { env, HOME, opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
6
|
+
import { withLock } from "./lock.ts";
|
|
7
|
+
import { log } from "./log.ts";
|
|
8
|
+
import { livingPresences, seatCounts, writePresence } from "./presence.ts";
|
|
9
|
+
import type { Provider, SeatBorrow } from "./provider.ts";
|
|
10
|
+
import { loadAccounts, readJsonFile, type Harvest } from "./state.ts";
|
|
7
11
|
import { statusOnlyProvider, type AuthEntry } from "./statusonly.ts";
|
|
8
12
|
import { ErrnoSchema } from "./types.ts";
|
|
9
13
|
import { c } from "../cli/render.ts";
|
|
@@ -47,6 +51,30 @@ function readAuth(path: string): AuthEntry[] {
|
|
|
47
51
|
return [{ id: idOfKey(entry.data.key), usable: true, harvest: harvestOf(entry.data.key) }];
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
export async function borrowOpencodeGoSeat(pid: number): Promise<SeatBorrow> {
|
|
55
|
+
const seatId = `seat-${pid}`;
|
|
56
|
+
const granted = await withLock(opencodeGoPool.lockFile, async (): Promise<SeatBorrow> => {
|
|
57
|
+
const accounts = loadAccounts(opencodeGoPool).accounts;
|
|
58
|
+
const living = livingPresences(opencodeGoPaths.presenceDir);
|
|
59
|
+
const held = living.find((p) => p.id === seatId);
|
|
60
|
+
const heldAccount = held ? (accounts.find((a) => a.id === held.accountId) ?? null) : null;
|
|
61
|
+
if (heldAccount) {
|
|
62
|
+
if (!(await opencodeGo.storeUsable(heldAccount))) {
|
|
63
|
+
return { denied: "the key this pid holds is no longer usable in its store - run `tokenmaxxing auth --opencode-go` and borrow again" };
|
|
64
|
+
}
|
|
65
|
+
return { store: opencodeGoStoreDirFor(heldAccount.id), id: heldAccount.id, reused: true };
|
|
66
|
+
}
|
|
67
|
+
const lent = countBy(living, (p) => p.accountId);
|
|
68
|
+
const usable = await Promise.all(accounts.map(opencodeGo.storeUsable));
|
|
69
|
+
const picked = minBy(accounts.filter((_, i) => usable[i]), (a) => lent[a.id] ?? 0);
|
|
70
|
+
if (!picked) return null;
|
|
71
|
+
writePresence({ dir: opencodeGoPaths.presenceDir, id: seatId, accountId: picked.id, pid });
|
|
72
|
+
return { store: opencodeGoStoreDirFor(picked.id), id: picked.id, reused: false };
|
|
73
|
+
});
|
|
74
|
+
if (granted && !("denied" in granted)) log("seat.grant", { account: granted.id.slice(0, 8), pid, reused: granted.reused });
|
|
75
|
+
return granted;
|
|
76
|
+
}
|
|
77
|
+
|
|
50
78
|
export const opencodeGo: Provider = statusOnlyProvider({
|
|
51
79
|
name: "opencode-go",
|
|
52
80
|
flag: " --opencode-go",
|
|
@@ -62,6 +90,7 @@ export const opencodeGo: Provider = statusOnlyProvider({
|
|
|
62
90
|
liveAuthPath,
|
|
63
91
|
readAuth,
|
|
64
92
|
liveId: () => null,
|
|
93
|
+
presence: () => seatCounts(opencodeGoPaths.presenceDir),
|
|
65
94
|
versionOk: (out) => out !== "",
|
|
66
95
|
importIntro: "Pooling your opencode-go API key - your existing opencode auth stays as it is.",
|
|
67
96
|
foundLive: () => `found an opencode-go credential in ${liveAuthPath()} - pooling it; use \`tokenmaxxing add --opencode-go\` for more keys.`,
|
package/src/lib/paths.ts
CHANGED
|
@@ -133,6 +133,7 @@ export function grokSeatFromEnv(accountIds: string[], env: Record<string, string
|
|
|
133
133
|
export const opencodeGoPaths = {
|
|
134
134
|
storesDir: join(TM_HOME, "opencode-go-stores"),
|
|
135
135
|
onboardDir: join(TM_HOME, "opencode-go-onboard"),
|
|
136
|
+
presenceDir: join(TM_HOME, "opencode-go-live"),
|
|
136
137
|
} as const;
|
|
137
138
|
|
|
138
139
|
export function opencodeGoStoreDirFor(accountId: string): string {
|
package/src/lib/picker.ts
CHANGED
|
@@ -122,6 +122,7 @@ const swapPreference = (ctx: PickCtx) => [
|
|
|
122
122
|
(a: Account) => -pacePressure(a, ctx),
|
|
123
123
|
(a: Account) => weeklyExpiry(a, ctx.now),
|
|
124
124
|
(a: Account) => weeklyWindow(a)?.usedPercentage ?? 101,
|
|
125
|
+
...(ctx.seats == null ? [] : [(a: Account) => ctx.seats?.get(a.id) ?? 0]),
|
|
125
126
|
];
|
|
126
127
|
|
|
127
128
|
export function pickBest(accounts: Account[], ctx: PickCtx): Account | null {
|
package/src/lib/provider.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type SampleReport = { ok: true; source: "statusline" | "probe" | "cached"
|
|
|
8
8
|
|
|
9
9
|
export type SwapFailure = "dead-grant" | "skip" | "fatal";
|
|
10
10
|
|
|
11
|
+
export type SeatBorrow = { store: string; id: string; reused: boolean } | { denied: string } | null;
|
|
12
|
+
|
|
11
13
|
export class StoreUnusableError extends Error {
|
|
12
14
|
constructor(message: string) {
|
|
13
15
|
super(message);
|
package/src/lib/sessions.ts
CHANGED
|
@@ -60,7 +60,7 @@ function listDir(dir: string, root: string): Dirent[] {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function tmpSweepDirs(root: string): string[] {
|
|
63
|
-
const dirs = [paths.home, paths.usageDir, paths.presenceDir, paths.respawnDir, sessionsDir(), paths.binDir, codexPaths.presenceDir, codexPaths.respawnDir, codexPaths.onboardDir];
|
|
63
|
+
const dirs = [paths.home, paths.usageDir, paths.presenceDir, paths.respawnDir, sessionsDir(), paths.binDir, codexPaths.presenceDir, codexPaths.respawnDir, codexPaths.onboardDir, opencodeGoPaths.presenceDir];
|
|
64
64
|
for (const parent of STORE_PARENTS) {
|
|
65
65
|
for (const child of listDir(parent, root)) {
|
|
66
66
|
if (child.isDirectory()) dirs.push(join(parent, child.name));
|
package/src/lib/statusonly.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type StatusOnlySpec = {
|
|
|
24
24
|
liveAuthPath: () => string;
|
|
25
25
|
readAuth: (path: string) => AuthEntry[];
|
|
26
26
|
liveId: () => string | null;
|
|
27
|
+
presence: () => Map<string, number>;
|
|
27
28
|
versionOk: (versionOutput: string) => boolean;
|
|
28
29
|
importIntro: string;
|
|
29
30
|
foundLive: (count: number) => string;
|
|
@@ -101,7 +102,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
|
|
|
101
102
|
waitsWhenDepleted: false,
|
|
102
103
|
statusOnly: true,
|
|
103
104
|
liveId: spec.liveId,
|
|
104
|
-
presence:
|
|
105
|
+
presence: spec.presence,
|
|
105
106
|
gatedFamilies: () => null,
|
|
106
107
|
observeLive: async (account) => (account.lastUsageAt != null ? { windows: account.windows, at: account.lastUsageAt } : null),
|
|
107
108
|
samplePool,
|
package/src/main.ts
CHANGED
|
@@ -39,6 +39,7 @@ const PI_FLAG = "--pi";
|
|
|
39
39
|
const JSON_COMMANDS = new Set(["status", "config", "check"]);
|
|
40
40
|
const CODEX_COMMANDS = new Set(["init", "add", "auth", "rm", "rename", "seat"]);
|
|
41
41
|
const STATUS_ONLY_COMMANDS = new Set(["init", "add", "auth", "rm", "rename"]);
|
|
42
|
+
const OPENCODE_GO_COMMANDS = new Set([...STATUS_ONLY_COMMANDS, "seat"]);
|
|
42
43
|
|
|
43
44
|
function printHelp(): void {
|
|
44
45
|
console.log(`${c.bold("tokenmaxxing")} - automatic Claude Code account switching
|
|
@@ -59,7 +60,9 @@ function printHelp(): void {
|
|
|
59
60
|
${c.cyan("tokenmaxxing doctor")} verify the install is intact
|
|
60
61
|
${c.cyan("tokenmaxxing rename")} [--codex | --grok | --opencode-go] <sel> <label>
|
|
61
62
|
${c.cyan("tokenmaxxing rm")} [--codex | --grok | --opencode-go] <sel>
|
|
62
|
-
${c.cyan("tokenmaxxing seat
|
|
63
|
+
${c.cyan("tokenmaxxing seat")} <pid> lend one pooled Claude account to an unattended consumer until <pid> exits: prints the store directory to set as CLAUDE_SECURESTORAGE_CONFIG_DIR (the directory itself, as its owner, never a copy of its credential); host sessions and other borrowers share the account; exit 1 = none usable
|
|
64
|
+
${c.cyan("tokenmaxxing seat --codex")} <pid> borrow one pooled codex account for an unattended consumer (plugin, script): prints the CODEX_HOME to set, reserved to <pid> alone until it exits because one auth.json serves one serialized job stream; exit 1 = none usable, fall back to the ambient login
|
|
65
|
+
${c.cyan("tokenmaxxing seat --opencode-go")} <pid> lend one pooled opencode-go key to an unattended consumer until <pid> exits: prints the store directory, whose auth.json is an opencode auth file (mount it at $XDG_DATA_HOME/opencode/auth.json, then run a model such as opencode-go/mimo-v2.6-pro); other borrowers share the key; exit 1 = none usable
|
|
63
66
|
${c.cyan("tokenmaxxing serve")} serve the CLIProxyAPI-compatible usage API on http://localhost:<hub.port> (default 8317) so a dashboard such as T3 Code's "Add a CLIProxyAPI hub" shows every pooled Claude and Codex account's quota; the management key is the contents of hub-key in the state directory
|
|
64
67
|
${c.cyan("tokenmaxxing uninstall")} [--yes] print the targets, then remove supervisor + settings entries (refused without ${c.cyan("--yes")} when HOME is the login home)
|
|
65
68
|
|
|
@@ -127,10 +130,13 @@ async function main(): Promise<number> {
|
|
|
127
130
|
return 2;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
if (
|
|
133
|
+
if (provider === grok || provider === opencodeGo) {
|
|
131
134
|
const flag = provider === grok ? GROK_FLAG : OPENCODE_GO_FLAG;
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
const commands = provider === grok ? STATUS_ONLY_COMMANDS : OPENCODE_GO_COMMANDS;
|
|
136
|
+
if (sub == null || !commands.has(sub)) {
|
|
137
|
+
emitError({ json, message: `${flag} applies to ${[...commands].join(", ")}, not ${sub ?? "status"}` });
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
if (json && sub != null && !JSON_COMMANDS.has(sub)) {
|
|
@@ -167,13 +173,7 @@ async function main(): Promise<number> {
|
|
|
167
173
|
case "doctor": return cmdDoctor();
|
|
168
174
|
case "rm": return cmdRm(provider, args[1]);
|
|
169
175
|
case "rename": return cmdRename(provider, args.slice(1));
|
|
170
|
-
case "seat":
|
|
171
|
-
if (provider !== codex) {
|
|
172
|
-
emitError({ message: `seat borrows a pooled codex account - pass ${CODEX_FLAG} (usage: tokenmaxxing seat ${CODEX_FLAG} <pid>)` });
|
|
173
|
-
return 2;
|
|
174
|
-
}
|
|
175
|
-
return cmdSeat(args[1], args.slice(2));
|
|
176
|
-
}
|
|
176
|
+
case "seat": return cmdSeat(provider === codex ? "codex" : provider === opencodeGo ? "opencode-go" : "claude", args[1], args.slice(2));
|
|
177
177
|
case "serve": return cmdServe(args.slice(1));
|
|
178
178
|
case "uninstall": {
|
|
179
179
|
if (args.length > 1) {
|