tokenmaxxing 1.79.0 → 1.80.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/entries/codexsupervisor.ts +3 -3
- package/src/entries/supervisor.ts +3 -3
- package/src/lib/claude.ts +2 -2
- package/src/lib/codexauth.ts +8 -7
- package/src/lib/install.ts +4 -4
- package/src/lib/presence.ts +4 -11
- package/src/lib/sessions.ts +3 -2
- package/src/lib/settings.ts +5 -9
- package/src/lib/state.ts +14 -38
- package/src/lib/update.ts +4 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.80.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",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { codexPaths, codexPool } from "../lib/paths.ts";
|
|
@@ -10,7 +10,7 @@ import { clearPresence, livingPresences } from "../lib/presence.ts";
|
|
|
10
10
|
import { isExhausted } from "../lib/picker.ts";
|
|
11
11
|
import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
|
|
12
12
|
import { saveTermios } from "../lib/tty.ts";
|
|
13
|
-
import { loadAccounts } from "../lib/state.ts";
|
|
13
|
+
import { loadAccounts, readJsonFile } from "../lib/state.ts";
|
|
14
14
|
import { CodexRespawnMarkerSchema, type Account } from "../lib/types.ts";
|
|
15
15
|
import { errorMessage, log } from "../lib/log.ts";
|
|
16
16
|
|
|
@@ -26,7 +26,7 @@ const PASSTHROUGH_FLAGS = new Set(["--version", "-V", "--help", "-h"]);
|
|
|
26
26
|
|
|
27
27
|
function readCodexMarker(marker: string): z.infer<typeof CodexRespawnMarkerSchema> {
|
|
28
28
|
try {
|
|
29
|
-
return
|
|
29
|
+
return readJsonFile(marker, CodexRespawnMarkerSchema);
|
|
30
30
|
} catch (e) {
|
|
31
31
|
log("codexsupervisor.marker_invalid", { err: errorMessage(e) });
|
|
32
32
|
throw new Error(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, join } from "node:path";
|
|
3
3
|
import type { FileSink, Subprocess } from "bun";
|
|
4
4
|
import { maxBy } from "es-toolkit";
|
|
@@ -16,7 +16,7 @@ import { teeObservation } from "../lib/sample.ts";
|
|
|
16
16
|
import { exitStatus, loopGuardTripped, raceMarkerOrExit, recordPresenceOrStop, runPassthrough } from "../lib/supervise.ts";
|
|
17
17
|
import { saveTermios } from "../lib/tty.ts";
|
|
18
18
|
import { liveSessionId, loadSessionFlags, pruneStaleSessions, saveSessionFlags, writeRespawnMarker } from "../lib/sessions.ts";
|
|
19
|
-
import { loadAccounts, loadConfig, releaseWaitClaim } from "../lib/state.ts";
|
|
19
|
+
import { loadAccounts, loadConfig, readJsonFile, releaseWaitClaim } from "../lib/state.ts";
|
|
20
20
|
import { gatedFamilies, modelFromFlag } from "../lib/usage.ts";
|
|
21
21
|
import { RespawnMarkerSchema, type Account, type Config, type ModelInfo } from "../lib/types.ts";
|
|
22
22
|
import { errorMessage, log } from "../lib/log.ts";
|
|
@@ -183,7 +183,7 @@ type MarkerGate = {
|
|
|
183
183
|
function consumableMarker(marker: string, gate: MarkerGate): z.infer<typeof RespawnMarkerSchema> | null {
|
|
184
184
|
let m: z.infer<typeof RespawnMarkerSchema>;
|
|
185
185
|
try {
|
|
186
|
-
m =
|
|
186
|
+
m = readJsonFile(marker, RespawnMarkerSchema);
|
|
187
187
|
} catch (e) {
|
|
188
188
|
log("supervisor.marker_invalid", { err: errorMessage(e) });
|
|
189
189
|
throw new Error(
|
package/src/lib/claude.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { pickBest, pickEarliestReset, thresholdBars, type PickCtx } from "./pick
|
|
|
12
12
|
import { seatCounts } from "./presence.ts";
|
|
13
13
|
import { StoreUnusableError, type Observation, type Provider, type SampleReport } from "./provider.ts";
|
|
14
14
|
import { foldTee, sampleAccountUsage, teeObservation, usageBlockedUntil } from "./sample.ts";
|
|
15
|
-
import { clearUsageSnapshot, loadAccounts, loadConfig, loadUsageSnapshot, pinBinOverride, saveAccounts, type Harvest } from "./state.ts";
|
|
15
|
+
import { clearUsageSnapshot, loadAccounts, loadConfig, loadUsageSnapshot, pinBinOverride, readJsonFile, saveAccounts, type Harvest } from "./state.ts";
|
|
16
16
|
import { saveTermios, restoreTermios } from "./tty.ts";
|
|
17
17
|
import { fetchUsageDirect, gatedFamilies, mergeWindows, modelFromFlag, scrubCredentialEnv, windowsOf } from "./usage.ts";
|
|
18
18
|
import { CredentialBlobSchema, JsonTextSchema, OAuthAccountSchema, type Account, type Config, type ModelInfo } from "./types.ts";
|
|
@@ -185,7 +185,7 @@ async function login(): Promise<Harvest | null> {
|
|
|
185
185
|
let blob, oauthAccount;
|
|
186
186
|
try {
|
|
187
187
|
blob = CredentialBlobSchema.parse(JSON.parse(blobRaw));
|
|
188
|
-
oauthAccount =
|
|
188
|
+
oauthAccount = readJsonFile(cjPath, z.object({ oauthAccount: OAuthAccountSchema })).oauthAccount;
|
|
189
189
|
} catch {
|
|
190
190
|
console.error(c.red("could not parse the onboarded account's credential/identity."));
|
|
191
191
|
return null;
|
package/src/lib/codexauth.ts
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, rmSync, symlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { writeFileAtomic } from "./atomic.ts";
|
|
5
5
|
import { codexAuthJsonFor, codexPaths, codexStoreDirFor } from "./paths.ts";
|
|
6
|
+
import { readJsonFile } from "./state.ts";
|
|
6
7
|
import { CodexAuthJsonSchema, ErrnoSchema, type CodexAuthJson } from "./types.ts";
|
|
7
8
|
|
|
9
|
+
const CodexAuthFileSchema = z
|
|
10
|
+
.looseObject({ tokens: z.unknown().optional() })
|
|
11
|
+
.transform((auth) => (auth.tokens === undefined || auth.tokens === null ? null : auth))
|
|
12
|
+
.pipe(CodexAuthJsonSchema.nullable());
|
|
13
|
+
|
|
8
14
|
export function readCodexAuthAt(input: { path: string }): CodexAuthJson | null {
|
|
9
|
-
let raw: string;
|
|
10
15
|
try {
|
|
11
|
-
|
|
16
|
+
return readJsonFile(input.path, CodexAuthFileSchema);
|
|
12
17
|
} catch (e) {
|
|
13
18
|
if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") return null;
|
|
14
19
|
throw e;
|
|
15
20
|
}
|
|
16
|
-
const parsed = JSON.parse(raw);
|
|
17
|
-
const probe = z.looseObject({ tokens: z.unknown().optional() }).parse(parsed);
|
|
18
|
-
if (probe.tokens === undefined || probe.tokens === null) return null;
|
|
19
|
-
return CodexAuthJsonSchema.parse(parsed);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function readCodexStoreAuth(accountId: string): CodexAuthJson | null {
|
package/src/lib/install.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { codexPaths, codexStoreDirFor, HOME, optionalEnv, paths } from "./paths.
|
|
|
7
7
|
import { writeFileAtomic } from "./atomic.ts";
|
|
8
8
|
import { installedBin, installSettings, isOurHookCommand, uninstallSettings } from "./settings.ts";
|
|
9
9
|
import { CLAUDE_BIN, resolveRealBin } from "./claudebin.ts";
|
|
10
|
-
import { loadConfig } from "./state.ts";
|
|
10
|
+
import { loadConfig, readJsonFile } from "./state.ts";
|
|
11
11
|
import { ErrnoSchema } from "./types.ts";
|
|
12
12
|
|
|
13
13
|
export type InstallOutcome = { claudeWrapper: string; installedBin: string; pathAhead: boolean; timerLoaded: boolean; hubLoaded: boolean; checkIntervalS: number };
|
|
@@ -134,7 +134,7 @@ function withoutOurCodexStopHooks(groups: { hooks: { type?: string; command?: st
|
|
|
134
134
|
|
|
135
135
|
export function installCodexStopHook(): void {
|
|
136
136
|
const current = existsSync(codexPaths.hooksJson)
|
|
137
|
-
?
|
|
137
|
+
? readJsonFile(codexPaths.hooksJson, CodexHooksFileSchema)
|
|
138
138
|
: CodexHooksFileSchema.parse({});
|
|
139
139
|
const next = {
|
|
140
140
|
...current,
|
|
@@ -151,7 +151,7 @@ export function installCodexStopHook(): void {
|
|
|
151
151
|
|
|
152
152
|
export function uninstallCodexStopHook(): void {
|
|
153
153
|
if (!existsSync(codexPaths.hooksJson)) return;
|
|
154
|
-
const current =
|
|
154
|
+
const current = readJsonFile(codexPaths.hooksJson, CodexHooksFileSchema);
|
|
155
155
|
const next = {
|
|
156
156
|
...current,
|
|
157
157
|
hooks: {
|
|
@@ -166,7 +166,7 @@ export function codexStopHookGroupIndex(): number | null {
|
|
|
166
166
|
if (!existsSync(codexPaths.hooksJson)) return null;
|
|
167
167
|
let parsed: z.infer<typeof CodexHooksFileSchema>;
|
|
168
168
|
try {
|
|
169
|
-
parsed =
|
|
169
|
+
parsed = readJsonFile(codexPaths.hooksJson, CodexHooksFileSchema);
|
|
170
170
|
} catch {
|
|
171
171
|
return null;
|
|
172
172
|
}
|
package/src/lib/presence.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { countBy } from "es-toolkit";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { writeFileAtomic } from "./atomic.ts";
|
|
6
6
|
import { pidExists, pidStartTime } from "./proc.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { readJsonFile } from "./state.ts";
|
|
8
|
+
import { ErrnoSchema, JsonTextSchema } from "./types.ts";
|
|
8
9
|
|
|
9
10
|
const PresenceSchema = z.object({
|
|
10
11
|
accountId: z.string(),
|
|
@@ -19,14 +20,12 @@ export function writePresence(input: { dir: string; id: string; accountId: strin
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function presencePid(input: { dir: string; id: string }): number | null {
|
|
22
|
-
let raw: string;
|
|
23
23
|
try {
|
|
24
|
-
|
|
24
|
+
return readJsonFile(join(input.dir, input.id), PresenceSchema).pid;
|
|
25
25
|
} catch (e) {
|
|
26
26
|
if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") return null;
|
|
27
27
|
throw e;
|
|
28
28
|
}
|
|
29
|
-
return PresenceSchema.parse(JSON.parse(raw)).pid;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export function clearPresence(input: { dir: string; id: string }): void {
|
|
@@ -47,13 +46,7 @@ export function livingPresences(dir: string): LivingPresence[] {
|
|
|
47
46
|
if (ErrnoSchema.safeParse(e).data?.code === "ENOENT") continue;
|
|
48
47
|
throw e;
|
|
49
48
|
}
|
|
50
|
-
const parsed = PresenceSchema.safeParse((
|
|
51
|
-
try {
|
|
52
|
-
return JSON.parse(raw);
|
|
53
|
-
} catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
})());
|
|
49
|
+
const parsed = PresenceSchema.safeParse(JsonTextSchema.safeParse(raw).data);
|
|
57
50
|
if (!parsed.success) {
|
|
58
51
|
throw new Error(`${file} is not a readable presence record - it may belong to a RUNNING session, refusing to treat it as absent; remove the file (or respawn that session) to proceed`);
|
|
59
52
|
}
|
package/src/lib/sessions.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync, readdirSync,
|
|
1
|
+
import { existsSync, readdirSync, realpathSync, rmSync, statSync, type Dirent } from "node:fs";
|
|
2
2
|
import { basename, join, sep } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { errorMessage, log } from "./log.ts";
|
|
5
5
|
import { codexPaths, grokPaths, opencodeGoPaths, paths } from "./paths.ts";
|
|
6
6
|
import { writeFileAtomic } from "./atomic.ts";
|
|
7
7
|
import { presencePid } from "./presence.ts";
|
|
8
|
+
import { readJsonFile } from "./state.ts";
|
|
8
9
|
import { spawnedThroughShellsBy } from "./proc.ts";
|
|
9
10
|
import type { RespawnMarkerSchema } from "./types.ts";
|
|
10
11
|
|
|
@@ -25,7 +26,7 @@ export function saveSessionFlags(sid: string, flags: string[], cwd: string): voi
|
|
|
25
26
|
function loadSession(sid: string): z.infer<typeof SessionSchema> | null {
|
|
26
27
|
const f = sessionFile(sid);
|
|
27
28
|
if (!existsSync(f)) return null;
|
|
28
|
-
return
|
|
29
|
+
return readJsonFile(f, SessionSchema);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export function loadSessionFlags(sid: string): string[] | null {
|
package/src/lib/settings.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { paths } from "./paths.ts";
|
|
5
5
|
import { writeFileAtomic } from "./atomic.ts";
|
|
6
|
+
import { readJsonFile } from "./state.ts";
|
|
7
|
+
import { JsonTextSchema } from "./types.ts";
|
|
6
8
|
|
|
7
9
|
export function installedBin(): string {
|
|
8
10
|
return join(paths.binDir, "tokenmaxxing");
|
|
@@ -31,7 +33,7 @@ const STOP_FAILURE_MATCHER = "rate_limit";
|
|
|
31
33
|
|
|
32
34
|
function readSettings(): Settings {
|
|
33
35
|
if (!existsSync(paths.claudeSettings)) return {};
|
|
34
|
-
return
|
|
36
|
+
return readJsonFile(paths.claudeSettings, SettingsSchema);
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
function writeSettings(s: Settings): void {
|
|
@@ -71,13 +73,7 @@ export function isOurHookCommand(cmd: string, sub: string): boolean {
|
|
|
71
73
|
if (!cmd.endsWith(` ${sub}`)) return false;
|
|
72
74
|
const quotedPath = cmd.slice(0, cmd.length - (sub.length + 1));
|
|
73
75
|
if (!quotedPath.startsWith('"') || !quotedPath.endsWith('"')) return false;
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
path = JSON.parse(quotedPath);
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
const parsed = z.string().safeParse(path);
|
|
76
|
+
const parsed = z.string().safeParse(JsonTextSchema.safeParse(quotedPath).data);
|
|
81
77
|
return parsed.success && parsed.data.endsWith("/tokenmaxxing");
|
|
82
78
|
}
|
|
83
79
|
|
package/src/lib/state.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
AccountsIndexSchema,
|
|
8
8
|
ConfigSchema,
|
|
9
9
|
ErrnoSchema,
|
|
10
|
+
JsonTextSchema,
|
|
10
11
|
UsageStateSchema,
|
|
11
12
|
WaitQueueSchema,
|
|
12
13
|
type Account,
|
|
@@ -17,21 +18,19 @@ import {
|
|
|
17
18
|
type Window,
|
|
18
19
|
} from "./types.ts";
|
|
19
20
|
|
|
20
|
-
export function
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
raw = JSON.parse(readFileSync(paths.configJson, "utf8"));
|
|
25
|
-
} catch {
|
|
26
|
-
throw new Error(`${paths.configJson} is corrupt (unparsable JSON) - fix or remove it`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
const parsed = ConfigSchema.safeParse(raw);
|
|
21
|
+
export function readJsonFile<T extends z.ZodType>(path: string, schema: T): z.output<T> {
|
|
22
|
+
const json = JsonTextSchema.safeParse(readFileSync(path, "utf8"));
|
|
23
|
+
if (!json.success) throw new Error(`${path} is corrupt (unparsable JSON) - repair or remove the file`);
|
|
24
|
+
const parsed = schema.safeParse(json.data);
|
|
30
25
|
if (!parsed.success) {
|
|
31
26
|
const fields = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ");
|
|
32
|
-
throw new Error(`${
|
|
27
|
+
throw new Error(`${path} does not match its schema (${fields}) - repair or remove the file`);
|
|
33
28
|
}
|
|
34
|
-
|
|
29
|
+
return parsed.data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadConfig(): Config {
|
|
33
|
+
const cfg = existsSync(paths.configJson) ? readJsonFile(paths.configJson, ConfigSchema) : ConfigSchema.parse({});
|
|
35
34
|
cfg.claudeBin = optionalEnv("TOKENMAXXING_CLAUDE_BIN") ?? cfg.claudeBin;
|
|
36
35
|
cfg.codexBin = optionalEnv("TOKENMAXXING_CODEX_BIN") ?? cfg.codexBin;
|
|
37
36
|
cfg.grokBin = optionalEnv("TOKENMAXXING_GROK_BIN") ?? cfg.grokBin;
|
|
@@ -42,10 +41,7 @@ export function loadConfig(): Config {
|
|
|
42
41
|
export type BinKey = "claudeBin" | "codexBin" | "grokBin" | "opencodeBin";
|
|
43
42
|
|
|
44
43
|
export function pinBinOverride(input: { key: BinKey; bin: string }): void {
|
|
45
|
-
|
|
46
|
-
if (existsSync(paths.configJson)) {
|
|
47
|
-
raw = z.record(z.string(), z.unknown()).parse(JSON.parse(readFileSync(paths.configJson, "utf8")));
|
|
48
|
-
}
|
|
44
|
+
const raw: Record<string, unknown> = existsSync(paths.configJson) ? readJsonFile(paths.configJson, z.record(z.string(), z.unknown())) : {};
|
|
49
45
|
raw[input.key] = input.bin;
|
|
50
46
|
writeFileAtomic(paths.configJson, JSON.stringify(raw, null, 2) + "\n");
|
|
51
47
|
}
|
|
@@ -54,17 +50,7 @@ const emptyIndex = (): AccountsIndex => ({ version: 2, accounts: [] });
|
|
|
54
50
|
|
|
55
51
|
export function loadAccounts(pool: PoolPaths): AccountsIndex {
|
|
56
52
|
if (!existsSync(pool.accountsJson)) return emptyIndex();
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
json = JSON.parse(readFileSync(pool.accountsJson, "utf8"));
|
|
60
|
-
} catch {
|
|
61
|
-
throw new Error(`${pool.accountsJson} is corrupt (unparsable JSON) - refusing to treat a damaged pool as empty; repair or remove the file`);
|
|
62
|
-
}
|
|
63
|
-
const parsed = AccountsIndexSchema.safeParse(json);
|
|
64
|
-
if (!parsed.success) {
|
|
65
|
-
throw new Error(`${pool.accountsJson} does not match the accounts schema - refusing to treat a damaged pool as empty; repair or remove the file`);
|
|
66
|
-
}
|
|
67
|
-
return parsed.data;
|
|
53
|
+
return readJsonFile(pool.accountsJson, AccountsIndexSchema);
|
|
68
54
|
}
|
|
69
55
|
|
|
70
56
|
export function saveAccounts(pool: PoolPaths, idx: AccountsIndex): void {
|
|
@@ -73,17 +59,7 @@ export function saveAccounts(pool: PoolPaths, idx: AccountsIndex): void {
|
|
|
73
59
|
|
|
74
60
|
function loadWaitQueue(): WaitClaim[] {
|
|
75
61
|
if (!existsSync(claudePool.waitQueueJson)) return [];
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
json = JSON.parse(readFileSync(claudePool.waitQueueJson, "utf8"));
|
|
79
|
-
} catch {
|
|
80
|
-
throw new Error(`${claudePool.waitQueueJson} is corrupt (unparsable JSON) - refusing to treat a damaged wait queue as empty; repair or remove the file`);
|
|
81
|
-
}
|
|
82
|
-
const parsed = WaitQueueSchema.safeParse(json);
|
|
83
|
-
if (!parsed.success) {
|
|
84
|
-
throw new Error(`${claudePool.waitQueueJson} does not match the wait-queue schema - refusing to treat a damaged wait queue as empty; repair or remove the file`);
|
|
85
|
-
}
|
|
86
|
-
return parsed.data.claims;
|
|
62
|
+
return readJsonFile(claudePool.waitQueueJson, WaitQueueSchema).claims;
|
|
87
63
|
}
|
|
88
64
|
|
|
89
65
|
function saveWaitQueue(claims: WaitClaim[]): void {
|
package/src/lib/update.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, lstatSync
|
|
1
|
+
import { existsSync, lstatSync } 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";
|
|
@@ -8,6 +8,7 @@ import { isNixPackaged } from "./install.ts";
|
|
|
8
8
|
import { withLock } from "./lock.ts";
|
|
9
9
|
import { errorMessage, log } from "./log.ts";
|
|
10
10
|
import { env, HOME, optionalEnv, paths } from "./paths.ts";
|
|
11
|
+
import { readJsonFile } from "./state.ts";
|
|
11
12
|
|
|
12
13
|
const UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
13
14
|
const INSTALL_DEADLINE_MS = 60_000;
|
|
@@ -67,18 +68,12 @@ function detectGlobalRoot(): string | null {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function installedVersion(root: string): string {
|
|
70
|
-
return
|
|
71
|
+
return readJsonFile(join(packageDir(root), "package.json"), VersionSchema).version;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
function isDue(now: number): boolean {
|
|
74
75
|
if (!existsSync(updateJson)) return true;
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
json = JSON.parse(readFileSync(updateJson, "utf8"));
|
|
78
|
-
} catch {
|
|
79
|
-
throw new Error(`${updateJson} is corrupt (unparsable JSON) - repair or remove the file`);
|
|
80
|
-
}
|
|
81
|
-
return now - AttemptSchema.parse(json).attemptedAt >= UPDATE_INTERVAL_MS;
|
|
76
|
+
return now - readJsonFile(updateJson, AttemptSchema).attemptedAt >= UPDATE_INTERVAL_MS;
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
async function updateToLatest(root: string): Promise<void> {
|