tokenmaxxing 1.70.0 → 1.72.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 +2 -2
- package/src/cli/seat.ts +6 -8
- package/src/cli/status.ts +1 -30
- package/src/lib/claude.ts +1 -8
- package/src/lib/claudebin.ts +4 -4
- package/src/lib/codex.ts +1 -8
- package/src/lib/codexbin.ts +3 -3
- package/src/lib/compact.ts +4 -8
- package/src/lib/install.ts +12 -20
- package/src/lib/keychain.ts +3 -3
- package/src/lib/opencodego.ts +2 -7
- package/src/lib/provider.ts +7 -0
- package/src/lib/statusonly.ts +1 -1
- package/src/lib/tty.ts +1 -1
- package/src/lib/update.ts +4 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.72.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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"DESIGN.md"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"bun": ">=1.3.
|
|
22
|
+
"bun": ">=1.3.7"
|
|
23
23
|
},
|
|
24
24
|
"os": [
|
|
25
25
|
"darwin",
|
package/src/cli/seat.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { observeCodex } from "../lib/codex.ts";
|
|
1
|
+
import { codexPickCtx, observeCodex } from "../lib/codex.ts";
|
|
2
2
|
import { codexStoreUsable, ensureCodexStoreHome } from "../lib/codexauth.ts";
|
|
3
3
|
import { withLock } from "../lib/lock.ts";
|
|
4
4
|
import { log } from "../lib/log.ts";
|
|
5
5
|
import { codexPaths, codexPool } from "../lib/paths.ts";
|
|
6
|
-
import { livingPresences, writePresence } from "../lib/presence.ts";
|
|
7
|
-
import { isExhausted, pickBest
|
|
6
|
+
import { livingPresences, seatCounts, writePresence } from "../lib/presence.ts";
|
|
7
|
+
import { isExhausted, pickBest } from "../lib/picker.ts";
|
|
8
8
|
import { loadAccounts, loadConfig } from "../lib/state.ts";
|
|
9
9
|
import { emitError } from "./render.ts";
|
|
10
10
|
|
|
@@ -23,10 +23,9 @@ export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Prom
|
|
|
23
23
|
.map((a) => observeCodex(a, cfg, now, { probe: true, refresh: false })),
|
|
24
24
|
);
|
|
25
25
|
const granted = await withLock(codexPool.lockFile, (): { store: string; id: string; reused: boolean } | { denied: string } | null => {
|
|
26
|
-
const ctx
|
|
26
|
+
const ctx = codexPickCtx(now, null);
|
|
27
27
|
const idx = loadAccounts(codexPool);
|
|
28
|
-
const
|
|
29
|
-
const held = living.find((p) => p.id === seatId);
|
|
28
|
+
const held = livingPresences(codexPaths.presenceDir).find((p) => p.id === seatId);
|
|
30
29
|
const heldAccount = held ? (idx.accounts.find((x) => x.id === held.accountId) ?? null) : null;
|
|
31
30
|
if (heldAccount) {
|
|
32
31
|
if (heldAccount.needsReauth === true) {
|
|
@@ -37,8 +36,7 @@ export async function cmdSeat(pidRaw: string | undefined, extra: string[]): Prom
|
|
|
37
36
|
}
|
|
38
37
|
return { store: ensureCodexStoreHome(heldAccount.id), id: heldAccount.id, reused: true };
|
|
39
38
|
}
|
|
40
|
-
const present =
|
|
41
|
-
for (const p of living) present.set(p.accountId, (present.get(p.accountId) ?? 0) + 1);
|
|
39
|
+
const present = seatCounts(codexPaths.presenceDir);
|
|
42
40
|
const usable = idx.accounts.filter(
|
|
43
41
|
(a) => a.needsReauth !== true && !isExhausted(a, ctx) && !present.has(a.id) && codexStoreUsable(a.id)
|
|
44
42
|
);
|
package/src/cli/status.ts
CHANGED
|
@@ -138,35 +138,6 @@ const NOTE_INDENT = " ";
|
|
|
138
138
|
type Note = { paint: (s: string) => string; text: string };
|
|
139
139
|
type Card = { lines: string[]; notes: Note[] };
|
|
140
140
|
|
|
141
|
-
function splitToWidth(token: string, width: number): string[] {
|
|
142
|
-
const parts: string[] = [];
|
|
143
|
-
let part = "";
|
|
144
|
-
for (const ch of token) {
|
|
145
|
-
if (part !== "" && Bun.stringWidth(part + ch) > width) {
|
|
146
|
-
parts.push(part);
|
|
147
|
-
part = "";
|
|
148
|
-
}
|
|
149
|
-
part += ch;
|
|
150
|
-
}
|
|
151
|
-
if (part !== "") parts.push(part);
|
|
152
|
-
return parts;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function wrapWords(text: string, width: number): string[] {
|
|
156
|
-
const out: string[] = [];
|
|
157
|
-
let line = "";
|
|
158
|
-
for (const word of text.split(" ").flatMap((token) => splitToWidth(token, width))) {
|
|
159
|
-
if (line !== "" && Bun.stringWidth(`${line} ${word}`) > width) {
|
|
160
|
-
out.push(line);
|
|
161
|
-
line = word;
|
|
162
|
-
} else {
|
|
163
|
-
line = line === "" ? word : `${line} ${word}`;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (line !== "") out.push(line);
|
|
167
|
-
return out;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
141
|
function renderGrid(cards: Card[]): void {
|
|
171
142
|
const bodyWidth = Math.max(...cards.flatMap((card) => card.lines.map((line) => Bun.stringWidth(line))));
|
|
172
143
|
const termWidth = process.stdout.isTTY ? process.stdout.columns : Number.POSITIVE_INFINITY;
|
|
@@ -174,7 +145,7 @@ function renderGrid(cards: Card[]): void {
|
|
|
174
145
|
const cellWidth = columns === 1 ? termWidth : Math.floor((termWidth + CARD_GAP) / columns) - CARD_GAP;
|
|
175
146
|
const blocks = cards.map((card) => [
|
|
176
147
|
...card.lines,
|
|
177
|
-
...card.notes.flatMap((note) =>
|
|
148
|
+
...card.notes.flatMap((note) => Bun.wrapAnsi(note.text, cellWidth - NOTE_INDENT.length, { hard: true }).split("\n").map((line) => `${NOTE_INDENT}${note.paint(line)}`)),
|
|
178
149
|
]);
|
|
179
150
|
const width = Math.max(...blocks.flat().map((line) => Bun.stringWidth(line)));
|
|
180
151
|
for (const rowBlocks of chunk(blocks, columns)) {
|
package/src/lib/claude.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { claudeTierLabel, describeIdentity, fetchTokenIdentity, isDeadCredential
|
|
|
11
11
|
import { claudePool, env, paths, seatFromEnv, storeDirFor } from "./paths.ts";
|
|
12
12
|
import { pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
13
13
|
import { seatCounts } from "./presence.ts";
|
|
14
|
-
import type
|
|
14
|
+
import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
|
|
15
15
|
import { foldTee, sampleAccountUsage, teeObservation, usageBlockedUntil } from "./sample.ts";
|
|
16
16
|
import { clearUsageSnapshot, loadAccounts, loadConfig, loadUsageSnapshot, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
17
17
|
import { saveTermios, restoreTermios } from "./tty.ts";
|
|
@@ -19,13 +19,6 @@ import { fetchUsageDirect, gatedFamilies, mergeWindows, modelFromFlag, scrubCred
|
|
|
19
19
|
import { CredentialBlobSchema, JsonTextSchema, OAuthAccountSchema, type Account, type Config, type ModelInfo } from "./types.ts";
|
|
20
20
|
import { c } from "../cli/render.ts";
|
|
21
21
|
|
|
22
|
-
class StoreUnusableError extends Error {
|
|
23
|
-
constructor(message: string) {
|
|
24
|
-
super(message);
|
|
25
|
-
this.name = "StoreUnusableError";
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
22
|
function liveId(): string | null {
|
|
30
23
|
return seatFromEnv(loadAccounts(claudePool).accounts.map((a) => a.id));
|
|
31
24
|
}
|
package/src/lib/claudebin.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function wrapperEntryRateTripped(now: number): boolean {
|
|
|
36
36
|
return entries.length > WRAP_RATE_MAX;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function realpathOrNull(p: string): string | null {
|
|
39
|
+
export function realpathOrNull(p: string): string | null {
|
|
40
40
|
try {
|
|
41
41
|
return realpathSync(p);
|
|
42
42
|
} catch {
|
|
@@ -93,14 +93,14 @@ export function resolveRealClaude(): string {
|
|
|
93
93
|
|
|
94
94
|
export function verifyRealClaude(bin: string): string | null {
|
|
95
95
|
const env = { ...process.env, [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), TOKENMAXXING_PROBE: "1" };
|
|
96
|
-
let p
|
|
96
|
+
let p;
|
|
97
97
|
try {
|
|
98
98
|
p = Bun.spawnSync([bin, "--version"], { env, stdout: "pipe", stderr: "pipe", timeout: 15_000, killSignal: "SIGKILL" });
|
|
99
99
|
} catch (e) {
|
|
100
100
|
return errorMessage(e);
|
|
101
101
|
}
|
|
102
|
-
const outText =
|
|
103
|
-
const err =
|
|
102
|
+
const outText = p.stdout.toString().trim();
|
|
103
|
+
const err = p.stderr.toString().trim();
|
|
104
104
|
if (p.exitCode === 0) {
|
|
105
105
|
if (/claude/i.test(outText)) return null;
|
|
106
106
|
return `--version output does not identify claude: "${outText.slice(0, 80)}"`;
|
package/src/lib/codex.ts
CHANGED
|
@@ -12,19 +12,12 @@ import { withLock } from "./lock.ts";
|
|
|
12
12
|
import { errorMessage, log } from "./log.ts";
|
|
13
13
|
import { codexPaths, codexPool, codexSeatFromEnv } from "./paths.ts";
|
|
14
14
|
import { isExhausted, pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./picker.ts";
|
|
15
|
-
import type
|
|
15
|
+
import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
|
|
16
16
|
import { loadAccounts, loadConfig, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
17
17
|
import { restoreTermios, saveTermios } from "./tty.ts";
|
|
18
18
|
import type { Account, CodexAuthJson, CodexUsage, Config } from "./types.ts";
|
|
19
19
|
import { c } from "../cli/render.ts";
|
|
20
20
|
|
|
21
|
-
class StoreUnusableError extends Error {
|
|
22
|
-
constructor(message: string) {
|
|
23
|
-
super(message);
|
|
24
|
-
this.name = "StoreUnusableError";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
function liveId(): string | null {
|
|
29
22
|
return codexSeatFromEnv(loadAccounts(codexPool).accounts.map((a) => a.id));
|
|
30
23
|
}
|
package/src/lib/codexbin.ts
CHANGED
|
@@ -38,14 +38,14 @@ export function resolveRealCodex(): string {
|
|
|
38
38
|
|
|
39
39
|
export function verifyRealCodex(input: { bin: string }): string | null {
|
|
40
40
|
const env = { ...process.env, [WRAP_DEPTH_ENV]: String(MAX_WRAP_DEPTH), TOKENMAXXING_PROBE: "1" };
|
|
41
|
-
let p
|
|
41
|
+
let p;
|
|
42
42
|
try {
|
|
43
43
|
p = Bun.spawnSync([input.bin, "--version"], { env, stdout: "pipe", stderr: "pipe", timeout: 15_000, killSignal: "SIGKILL" });
|
|
44
44
|
} catch (e) {
|
|
45
45
|
return errorMessage(e);
|
|
46
46
|
}
|
|
47
|
-
const outText =
|
|
48
|
-
const err =
|
|
47
|
+
const outText = p.stdout.toString().trim();
|
|
48
|
+
const err = p.stderr.toString().trim();
|
|
49
49
|
if (p.exitCode === 0) {
|
|
50
50
|
if (outText.toLowerCase().includes("codex")) return null;
|
|
51
51
|
return `--version output does not identify codex: "${outText.slice(0, 80)}"`;
|
package/src/lib/compact.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { delay } from "es-toolkit";
|
|
4
2
|
import { z } from "zod";
|
|
3
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
5
4
|
import { errorMessage, log } from "./log.ts";
|
|
6
5
|
import { readLines } from "./proc.ts";
|
|
7
6
|
import { JsonTextSchema } from "./types.ts";
|
|
@@ -20,7 +19,7 @@ export async function compactClaudeSession(input: { real: string; sid: string; e
|
|
|
20
19
|
timeout: CLAUDE_COMPACT_KILL_MS,
|
|
21
20
|
killSignal: "SIGKILL",
|
|
22
21
|
});
|
|
23
|
-
const reads = Promise.all([
|
|
22
|
+
const reads = Promise.all([p.stdout.text(), p.stderr.text()]);
|
|
24
23
|
const settled = await Promise.race([reads, p.exited.then(() => delay(PIPE_GRACE_MS)).then(() => null)]);
|
|
25
24
|
await p.exited;
|
|
26
25
|
if (settled === null) return { ok: false, reason: "output pipes still open after child exit (leaked descendant)" };
|
|
@@ -48,9 +47,6 @@ const INIT_ID = 1;
|
|
|
48
47
|
const RESUME_ID = 2;
|
|
49
48
|
const COMPACT_ID = 3;
|
|
50
49
|
|
|
51
|
-
const packageVersion = (): string =>
|
|
52
|
-
z.object({ version: z.string().min(1) }).parse(JSON.parse(readFileSync(join(import.meta.dir, "..", "..", "package.json"), "utf8"))).version;
|
|
53
|
-
|
|
54
50
|
export async function compactCodexThread(input: { real: string; threadId: string; env: Record<string, string | undefined> }): Promise<CompactOutcome> {
|
|
55
51
|
const p = Bun.spawn([input.real, "app-server"], {
|
|
56
52
|
env: input.env,
|
|
@@ -64,7 +60,7 @@ export async function compactCodexThread(input: { real: string; threadId: string
|
|
|
64
60
|
p.stdin.write(`${JSON.stringify(msg)}\n`);
|
|
65
61
|
p.stdin.flush();
|
|
66
62
|
};
|
|
67
|
-
const stderrText =
|
|
63
|
+
const stderrText = p.stderr.text();
|
|
68
64
|
|
|
69
65
|
const outcome = await new Promise<CompactOutcome>((resolve) => {
|
|
70
66
|
let finished = false;
|
|
@@ -109,7 +105,7 @@ export async function compactCodexThread(input: { real: string; threadId: string
|
|
|
109
105
|
const err = (await stderrText).trim().slice(0, 200);
|
|
110
106
|
finish({ ok: false, reason: p.exitCode === null ? `app-server killed after ${CODEX_COMPACT_KILL_MS / 1000}s` : `app-server exited ${p.exitCode}: ${err}` });
|
|
111
107
|
})().catch((e) => finish({ ok: false, reason: errorMessage(e) }));
|
|
112
|
-
send({ id: INIT_ID, method: "initialize", params: { clientInfo: { name: "tokenmaxxing", version:
|
|
108
|
+
send({ id: INIT_ID, method: "initialize", params: { clientInfo: { name: "tokenmaxxing", version: pkg.version } } });
|
|
113
109
|
});
|
|
114
110
|
|
|
115
111
|
try {
|
package/src/lib/install.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { accessSync, appendFileSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { userInfo } from "node:os";
|
|
2
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
4
|
import { escape } from "es-toolkit";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -171,7 +172,7 @@ export function codexStopHookGroupIndex(): number | null {
|
|
|
171
172
|
} catch {
|
|
172
173
|
return null;
|
|
173
174
|
}
|
|
174
|
-
const idx = parsed.hooks.Stop.findIndex((group) => group.hooks.some((hook) => (hook.command ?? ""
|
|
175
|
+
const idx = parsed.hooks.Stop.findIndex((group) => group.hooks.some((hook) => isOurHookCommand(hook.command ?? "", CODEX_STOP_HOOK_SUBCOMMAND)));
|
|
175
176
|
return idx >= 0 ? idx : null;
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -215,9 +216,8 @@ function launchdPlist(label: string): string {
|
|
|
215
216
|
return join(paths.launchdAgentsDir, `${label}.plist`);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
function launchdDomain(): string
|
|
219
|
-
|
|
220
|
-
return uid == null ? null : `gui/${uid}`;
|
|
219
|
+
function launchdDomain(): string {
|
|
220
|
+
return `gui/${userInfo().uid}`;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
function run(cmd: string[]): boolean {
|
|
@@ -258,7 +258,6 @@ ${launchdEnv()} <key>StandardOutPath</key><string>/dev/null</string>
|
|
|
258
258
|
0o644,
|
|
259
259
|
);
|
|
260
260
|
const domain = launchdDomain();
|
|
261
|
-
if (domain == null) return false;
|
|
262
261
|
run(["launchctl", "bootout", `${domain}/${LAUNCHD_LABEL}`]);
|
|
263
262
|
return run(["launchctl", "bootstrap", domain, plist]) || checkTimerHealthy();
|
|
264
263
|
}
|
|
@@ -313,8 +312,7 @@ export function hubActivationHint(): string {
|
|
|
313
312
|
export function checkTimerHealthy(): boolean {
|
|
314
313
|
if (skipImperativeTimer()) return true;
|
|
315
314
|
if (process.platform === "darwin") {
|
|
316
|
-
|
|
317
|
-
return existsSync(launchdPlist(LAUNCHD_LABEL)) && domain != null && run(["launchctl", "print", `${domain}/${LAUNCHD_LABEL}`]);
|
|
315
|
+
return existsSync(launchdPlist(LAUNCHD_LABEL)) && run(["launchctl", "print", `${launchdDomain()}/${LAUNCHD_LABEL}`]);
|
|
318
316
|
}
|
|
319
317
|
return (
|
|
320
318
|
existsSync(join(paths.systemdUserDir, "tokenmaxxing-check.timer")) &&
|
|
@@ -325,8 +323,7 @@ export function checkTimerHealthy(): boolean {
|
|
|
325
323
|
export function hubServiceHealthy(): boolean {
|
|
326
324
|
if (skipImperativeHub()) return true;
|
|
327
325
|
if (process.platform === "darwin") {
|
|
328
|
-
|
|
329
|
-
return existsSync(launchdPlist(LAUNCHD_HUB_LABEL)) && domain != null && run(["launchctl", "print", `${domain}/${LAUNCHD_HUB_LABEL}`]);
|
|
326
|
+
return existsSync(launchdPlist(LAUNCHD_HUB_LABEL)) && run(["launchctl", "print", `${launchdDomain()}/${LAUNCHD_HUB_LABEL}`]);
|
|
330
327
|
}
|
|
331
328
|
return existsSync(join(paths.systemdUserDir, HUB_UNIT)) && run(["systemctl", "--user", "is-active", "--quiet", HUB_UNIT]);
|
|
332
329
|
}
|
|
@@ -346,10 +343,8 @@ export function hubDeactivationHint(): string {
|
|
|
346
343
|
}
|
|
347
344
|
|
|
348
345
|
function launchdJobLoaded(label: string): "loaded" | "not-loaded" | "unavailable" {
|
|
349
|
-
const domain = launchdDomain();
|
|
350
|
-
if (domain == null) return "unavailable";
|
|
351
346
|
try {
|
|
352
|
-
const { exitCode } = Bun.spawnSync(["launchctl", "print", `${
|
|
347
|
+
const { exitCode } = Bun.spawnSync(["launchctl", "print", `${launchdDomain()}/${label}`], { stdout: "ignore", stderr: "ignore", timeout: 10_000 });
|
|
353
348
|
if (exitCode === 0) return "loaded";
|
|
354
349
|
return exitCode === 113 ? "not-loaded" : "unavailable";
|
|
355
350
|
} catch {
|
|
@@ -387,9 +382,8 @@ function uninstallCheckTimer(live: boolean): UnitOutcome {
|
|
|
387
382
|
return "removed";
|
|
388
383
|
}
|
|
389
384
|
if (process.platform === "darwin") {
|
|
390
|
-
const domain = launchdDomain();
|
|
391
385
|
const loaded = launchdJobLoaded(LAUNCHD_LABEL);
|
|
392
|
-
const deactivated = loaded === "loaded"
|
|
386
|
+
const deactivated = loaded === "loaded" ? run(["launchctl", "bootout", `${launchdDomain()}/${LAUNCHD_LABEL}`]) : loaded === "not-loaded";
|
|
393
387
|
removeTimerUnits();
|
|
394
388
|
return deactivated ? "removed" : "still-loaded";
|
|
395
389
|
}
|
|
@@ -423,7 +417,6 @@ ${launchdEnv()} <key>StandardOutPath</key><string>/dev/null</string>
|
|
|
423
417
|
0o644,
|
|
424
418
|
);
|
|
425
419
|
const domain = launchdDomain();
|
|
426
|
-
if (domain == null) return false;
|
|
427
420
|
run(["launchctl", "bootout", `${domain}/${LAUNCHD_HUB_LABEL}`]);
|
|
428
421
|
return run(["launchctl", "bootstrap", domain, plist]) || hubServiceHealthy();
|
|
429
422
|
}
|
|
@@ -463,9 +456,8 @@ function uninstallHubService(live: boolean): UnitOutcome {
|
|
|
463
456
|
return "removed";
|
|
464
457
|
}
|
|
465
458
|
if (process.platform === "darwin") {
|
|
466
|
-
const domain = launchdDomain();
|
|
467
459
|
const loaded = launchdJobLoaded(LAUNCHD_HUB_LABEL);
|
|
468
|
-
const deactivated = loaded === "loaded"
|
|
460
|
+
const deactivated = loaded === "loaded" ? run(["launchctl", "bootout", `${launchdDomain()}/${LAUNCHD_HUB_LABEL}`]) : loaded === "not-loaded";
|
|
469
461
|
removeHubUnits();
|
|
470
462
|
return deactivated ? "removed" : "still-loaded";
|
|
471
463
|
}
|
|
@@ -562,7 +554,7 @@ export function removePathFromRc(rc: string): boolean {
|
|
|
562
554
|
export type UninstallOutcome = { timer: UnitOutcome; hub: UnitOutcome; pathLineRemoved: boolean };
|
|
563
555
|
|
|
564
556
|
export function loginHome(): string {
|
|
565
|
-
const cmd = process.platform === "darwin" ? ["id", "-P"] : ["getent", "passwd", String(
|
|
557
|
+
const cmd = process.platform === "darwin" ? ["id", "-P"] : ["getent", "passwd", String(userInfo().uid)];
|
|
566
558
|
const proc = Bun.spawnSync(cmd, { stdout: "pipe", stderr: "ignore", timeout: 10_000 });
|
|
567
559
|
const home = proc.stdout.toString().trim().split(":").at(-2);
|
|
568
560
|
if (proc.exitCode !== 0 || home == null || home === "") throw new Error(`cannot read the login home: ${cmd.join(" ")} failed`);
|
|
@@ -576,11 +568,11 @@ export function onLoginHome(): boolean {
|
|
|
576
568
|
export function uninstallTargets(live: boolean): string[] {
|
|
577
569
|
const timer =
|
|
578
570
|
process.platform === "darwin"
|
|
579
|
-
? `${launchdPlist(LAUNCHD_LABEL)}${live ? ` (and launchctl bootout
|
|
571
|
+
? `${launchdPlist(LAUNCHD_LABEL)}${live ? ` (and launchctl bootout ${launchdDomain()}/${LAUNCHD_LABEL})` : ""}`
|
|
580
572
|
: `${join(paths.systemdUserDir, "tokenmaxxing-check.timer")} and .service${live ? " (and systemctl --user disable --now tokenmaxxing-check.timer)" : ""}`;
|
|
581
573
|
const hub =
|
|
582
574
|
process.platform === "darwin"
|
|
583
|
-
? `${launchdPlist(LAUNCHD_HUB_LABEL)}${live ? ` (and launchctl bootout
|
|
575
|
+
? `${launchdPlist(LAUNCHD_HUB_LABEL)}${live ? ` (and launchctl bootout ${launchdDomain()}/${LAUNCHD_HUB_LABEL})` : ""}`
|
|
584
576
|
: `${join(paths.systemdUserDir, HUB_UNIT)}${live ? ` (and systemctl --user disable --now ${HUB_UNIT})` : ""}`;
|
|
585
577
|
const rc = shellRcPath();
|
|
586
578
|
return [
|
package/src/lib/keychain.ts
CHANGED
|
@@ -16,7 +16,7 @@ export async function readItem(t: KeychainTarget): Promise<string | null> {
|
|
|
16
16
|
stdout: "pipe",
|
|
17
17
|
stderr: "pipe",
|
|
18
18
|
});
|
|
19
|
-
const [out, err] = await Promise.all([
|
|
19
|
+
const [out, err] = await Promise.all([p.stdout.text(), p.stderr.text()]);
|
|
20
20
|
await p.exited;
|
|
21
21
|
if (p.exitCode === 44) return null;
|
|
22
22
|
if (p.exitCode !== 0) {
|
|
@@ -38,7 +38,7 @@ async function writeViaInteractive(encodedLine: Uint8Array): Promise<void> {
|
|
|
38
38
|
stdout: "ignore",
|
|
39
39
|
stderr: "pipe",
|
|
40
40
|
});
|
|
41
|
-
const err = await
|
|
41
|
+
const err = await p.stderr.text();
|
|
42
42
|
await p.exited;
|
|
43
43
|
if (p.exitCode !== 0) throw new Error(`keychain write (interactive) failed (exit ${p.exitCode}): ${err.trim()}`);
|
|
44
44
|
}
|
|
@@ -48,7 +48,7 @@ async function writeViaArgv(t: KeychainTarget, secret: string): Promise<void> {
|
|
|
48
48
|
stdout: "ignore",
|
|
49
49
|
stderr: "pipe",
|
|
50
50
|
});
|
|
51
|
-
const err = await
|
|
51
|
+
const err = await p.stderr.text();
|
|
52
52
|
await p.exited;
|
|
53
53
|
if (p.exitCode !== 0) throw new Error(`keychain write (argv) failed (exit ${p.exitCode}): ${err.trim()}`);
|
|
54
54
|
}
|
package/src/lib/opencodego.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
6
|
-
import { opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
|
|
5
|
+
import { env, HOME, opencodeGoAuthJsonFor, opencodeGoPaths, opencodeGoPool, opencodeGoStoreDirFor } from "./paths.ts";
|
|
7
6
|
import type { Provider } from "./provider.ts";
|
|
8
7
|
import { type Harvest } from "./state.ts";
|
|
9
8
|
import { statusOnlyProvider, type AuthEntry } from "./statusonly.ts";
|
|
@@ -12,12 +11,8 @@ import { c } from "../cli/render.ts";
|
|
|
12
11
|
const OpencodeAuthEntrySchema = z.looseObject({ type: z.string(), key: z.string().optional() });
|
|
13
12
|
const OpencodeAuthFileSchema = z.record(z.string(), z.unknown());
|
|
14
13
|
|
|
15
|
-
function dataHome(): string {
|
|
16
|
-
return process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME.length > 0 ? process.env.XDG_DATA_HOME : join(homedir(), ".local", "share");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
function liveAuthPath(): string {
|
|
20
|
-
return join(
|
|
15
|
+
return join(env("XDG_DATA_HOME", join(HOME, ".local", "share")), "opencode", "auth.json");
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
function idOfKey(key: string): string {
|
package/src/lib/provider.ts
CHANGED
|
@@ -8,6 +8,13 @@ export type SampleReport = { ok: true; source: "statusline" | "probe" } | { ok:
|
|
|
8
8
|
|
|
9
9
|
export type SwapFailure = "dead-grant" | "skip" | "fatal";
|
|
10
10
|
|
|
11
|
+
export class StoreUnusableError extends Error {
|
|
12
|
+
constructor(message: string) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "StoreUnusableError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export type Provider = {
|
|
12
19
|
name: "claude" | "codex" | "grok" | "opencode-go";
|
|
13
20
|
flag: string;
|
package/src/lib/statusonly.ts
CHANGED
|
@@ -104,7 +104,7 @@ export function statusOnlyProvider(spec: StatusOnlySpec): Provider {
|
|
|
104
104
|
function preflight(): void {
|
|
105
105
|
const real = resolveBin();
|
|
106
106
|
const p = Bun.spawnSync([real, "--version"], { stdout: "pipe", stderr: "pipe", timeout: 15_000 });
|
|
107
|
-
if (p.exitCode !== 0 || !spec.versionOk(p.stdout
|
|
107
|
+
if (p.exitCode !== 0 || !spec.versionOk(p.stdout.toString())) {
|
|
108
108
|
throw new Error(`${spec.binName} binary failed verification: ${real}`);
|
|
109
109
|
}
|
|
110
110
|
pinBinOverride({ key: spec.binKey, bin: real });
|
package/src/lib/tty.ts
CHANGED
package/src/lib/update.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { existsSync, lstatSync, readFileSync
|
|
1
|
+
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
5
|
+
import { realpathOrNull } from "./claudebin.ts";
|
|
5
6
|
import { http } from "./http.ts";
|
|
6
7
|
import { isNixPackaged } from "./install.ts";
|
|
7
8
|
import { withLock } from "./lock.ts";
|
|
@@ -29,14 +30,6 @@ function registry(): { base: URL; authorization: string | null } {
|
|
|
29
30
|
return { base, authorization };
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function realOrNull(path: string): string | null {
|
|
33
|
-
try {
|
|
34
|
-
return realpathSync(path);
|
|
35
|
-
} catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
33
|
function globalRootCandidates(): string[] {
|
|
41
34
|
const globalDir = optionalEnv("BUN_INSTALL_GLOBAL_DIR");
|
|
42
35
|
const bunInstall = optionalEnv("BUN_INSTALL");
|
|
@@ -63,12 +56,12 @@ function registryPackageDir(root: string): string | null {
|
|
|
63
56
|
|
|
64
57
|
function detectGlobalRoot(): string | null {
|
|
65
58
|
if (isNixPackaged()) return null;
|
|
66
|
-
const entry =
|
|
59
|
+
const entry = realpathOrNull(Bun.main);
|
|
67
60
|
if (entry == null) return null;
|
|
68
61
|
return (
|
|
69
62
|
globalRootCandidates().find((root) => {
|
|
70
63
|
const dir = registryPackageDir(root);
|
|
71
|
-
return dir != null && entry ===
|
|
64
|
+
return dir != null && entry === realpathOrNull(join(dir, "src", "main.ts"));
|
|
72
65
|
}) ?? null
|
|
73
66
|
);
|
|
74
67
|
}
|