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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenmaxxing",
3
- "version": "1.70.0",
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.6"
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, thresholdBars, type PickCtx } from "../lib/picker.ts";
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: PickCtx = { now, thresholds: thresholdBars(loadConfig()), currentId: null, families: null, seats: null };
26
+ const ctx = codexPickCtx(now, null);
27
27
  const idx = loadAccounts(codexPool);
28
- const living = livingPresences(codexPaths.presenceDir);
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 = new Map<string, number>();
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) => wrapWords(note.text, cellWidth - NOTE_INDENT.length).map((line) => `${NOTE_INDENT}${note.paint(line)}`)),
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 { Observation, Provider, SampleReport } from "./provider.ts";
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
  }
@@ -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: ReturnType<typeof Bun.spawnSync>;
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 = (p.stdout?.toString() ?? "").trim();
103
- const err = (p.stderr?.toString() ?? "").trim();
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 { Observation, Provider, SampleReport } from "./provider.ts";
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
  }
@@ -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: ReturnType<typeof Bun.spawnSync>;
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 = (p.stdout?.toString() ?? "").trim();
48
- const err = (p.stderr?.toString() ?? "").trim();
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)}"`;
@@ -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([new Response(p.stdout).text(), new Response(p.stderr).text()]);
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 = new Response(p.stderr).text();
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: packageVersion() } } });
108
+ send({ id: INIT_ID, method: "initialize", params: { clientInfo: { name: "tokenmaxxing", version: pkg.version } } });
113
109
  });
114
110
 
115
111
  try {
@@ -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 ?? "").includes(CODEX_STOP_HOOK_SUBCOMMAND)));
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 | null {
219
- const uid = process.getuid?.();
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
- const domain = launchdDomain();
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
- const domain = launchdDomain();
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", `${domain}/${label}`], { stdout: "ignore", stderr: "ignore", timeout: 10_000 });
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" && domain != null ? run(["launchctl", "bootout", `${domain}/${LAUNCHD_LABEL}`]) : loaded === "not-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" && domain != null ? run(["launchctl", "bootout", `${domain}/${LAUNCHD_HUB_LABEL}`]) : loaded === "not-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(process.getuid?.() ?? "")];
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 gui/${process.getuid?.() ?? "?"}/${LAUNCHD_LABEL})` : ""}`
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 gui/${process.getuid?.() ?? "?"}/${LAUNCHD_HUB_LABEL})` : ""}`
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 [
@@ -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([new Response(p.stdout).text(), new Response(p.stderr).text()]);
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 new Response(p.stderr).text();
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 new Response(p.stderr).text();
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
  }
@@ -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(dataHome(), "opencode", "auth.json");
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 {
@@ -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;
@@ -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?.toString() ?? "")) {
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
@@ -1,6 +1,6 @@
1
1
  export function saveTermios(): string | null {
2
2
  const p = Bun.spawnSync(["/bin/sh", "-c", "stty -g </dev/tty"]);
3
- const s = p.stdout?.toString().trim();
3
+ const s = p.stdout.toString().trim();
4
4
  return s && p.exitCode === 0 ? s : null;
5
5
  }
6
6
 
package/src/lib/update.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
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 = realOrNull(Bun.main);
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 === realOrNull(join(dir, "src", "main.ts"));
64
+ return dir != null && entry === realpathOrNull(join(dir, "src", "main.ts"));
72
65
  }) ?? null
73
66
  );
74
67
  }