tokmon 0.28.5 → 0.28.7
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/dist/{bootstrap-ink-RXSGXK7H.js → bootstrap-ink-PYYVQAYP.js} +25 -9
- package/dist/{chunk-KLRDULRC.js → chunk-6E2OJCIQ.js} +4 -4
- package/dist/{chunk-TN6V7XSL.js → chunk-BZFDTIFV.js} +32 -10
- package/dist/{chunk-CFKUZCMB.js → chunk-DESEB73E.js} +7 -6
- package/dist/{chunk-NNQGZNX4.js → chunk-MIXBF6QU.js} +96 -12
- package/dist/{chunk-HNWEJ6MS.js → chunk-O7ORALKA.js} +2 -0
- package/dist/{chunk-6YJ5H3JT.js → chunk-RY24UHMK.js} +2 -2
- package/dist/{chunk-EEMD2PXA.js → chunk-UON47FYF.js} +11 -3
- package/dist/{chunk-5B3XCEMC.js → chunk-WHOIUSJM.js} +53 -4
- package/dist/{cli-command-4XXKT32M.js → cli-command-XPFM6LUP.js} +17 -8
- package/dist/cli.js +6 -6
- package/dist/{config-3DGZ47F7.js → config-JEDXAKPZ.js} +1 -1
- package/dist/{daemon-D42FZQC5.js → daemon-KFMXYG7V.js} +55 -53
- package/dist/daemon-handle-6RYUJPC2.js +11 -0
- package/dist/server-LNFK5MRX.js +12 -0
- package/dist/web/assets/{Area-DSOnmPHW.js → Area-BqyvRn2x.js} +1 -1
- package/dist/web/assets/{analytics-CQcXEq3U.js → analytics-B6QwqU9g.js} +2 -2
- package/dist/web/assets/{breakdown-DLwDPWvA.js → breakdown-DYnVKScr.js} +1 -1
- package/dist/web/assets/{button-D767RAip.js → button-CnT5g-Jl.js} +1 -1
- package/dist/web/assets/{chart-CylR-9UK.js → chart-Duz9D-nH.js} +1 -1
- package/dist/web/assets/{explore-jBA0FMUo.js → explore-P2EolJrt.js} +1 -1
- package/dist/web/assets/{index-DMNfKjk7.js → index-9hkGpa7j.js} +12 -12
- package/dist/web/assets/{models-B4X_VrrZ.js → models-8B4FBxr7.js} +2 -2
- package/dist/web/assets/{overview-E1xtkiJT.js → overview-DXA_HAsw.js} +2 -2
- package/dist/web/assets/{panel-Cna_MX7C.js → panel-gxLFBb8y.js} +1 -1
- package/dist/web/assets/settings-sheet-CJJl-3Gw.js +1 -0
- package/dist/web/assets/{share-sheet-BBs3U9QV.js → share-sheet-CXdyA1ga.js} +1 -1
- package/dist/web/assets/{timeline-DAOfy1yc.js → timeline-CzC85trW.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/daemon-handle-RHIX5OYY.js +0 -11
- package/dist/server-BNZCKS2A.js +0 -12
- package/dist/web/assets/settings-sheet-Uba9f8CS.js +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createDaemonRpcClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-RY24UHMK.js";
|
|
5
5
|
import {
|
|
6
6
|
accountIdentityText,
|
|
7
7
|
accountsByProvider,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveQuotaViews,
|
|
12
12
|
severity,
|
|
13
13
|
usageFromHeadroom
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-BZFDTIFV.js";
|
|
15
15
|
import {
|
|
16
16
|
PROVIDERS,
|
|
17
17
|
PROVIDER_ORDER,
|
|
@@ -28,8 +28,8 @@ import {
|
|
|
28
28
|
time,
|
|
29
29
|
tokens,
|
|
30
30
|
withTimeout
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-DESEB73E.js";
|
|
32
|
+
import "./chunk-UON47FYF.js";
|
|
33
33
|
import {
|
|
34
34
|
COLOR_PALETTE,
|
|
35
35
|
DEFAULTS,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
snapshotCacheFile,
|
|
55
55
|
themePresetOption,
|
|
56
56
|
toggleProviderSelection
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-O7ORALKA.js";
|
|
58
58
|
import {
|
|
59
59
|
glyphs
|
|
60
60
|
} from "./chunk-RF4GGQGM.js";
|
|
@@ -655,6 +655,12 @@ function quotaViews(stats) {
|
|
|
655
655
|
}
|
|
656
656
|
function QuotaRow({ quota, color, barW, labelW, resetW, resetDisplay, tz }) {
|
|
657
657
|
const theme = useTuiTheme();
|
|
658
|
+
if (quota.value?.kind === "money") {
|
|
659
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
660
|
+
/* @__PURE__ */ jsx3(Box2, { width: labelW, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate", children: quota.label }) }),
|
|
661
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, color: theme.cost, wrap: "truncate-end", children: quota.valueText })
|
|
662
|
+
] });
|
|
663
|
+
}
|
|
658
664
|
if (quota.bounded && quota.usedPct !== null) {
|
|
659
665
|
const used = quota.usedPct;
|
|
660
666
|
const remaining = quota.remainingPct ?? 0;
|
|
@@ -1293,6 +1299,14 @@ var DESKTOP_FIXED_SETTINGS = [
|
|
|
1293
1299
|
if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, showMenuBarText: !c.tray.showMenuBarText } }));
|
|
1294
1300
|
}
|
|
1295
1301
|
},
|
|
1302
|
+
{
|
|
1303
|
+
key: "menuBarContent",
|
|
1304
|
+
label: "Menu bar content",
|
|
1305
|
+
render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.accent, children: rc.config.tray.menuBarValue === "todayTokens" ? "tokens today" : "usage" })),
|
|
1306
|
+
onAdjust: (input, key, ctx) => {
|
|
1307
|
+
if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, menuBarValue: c.tray.menuBarValue === "usage" ? "todayTokens" : "usage" } }));
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1296
1310
|
{
|
|
1297
1311
|
key: "summary",
|
|
1298
1312
|
label: "Summary",
|
|
@@ -3622,7 +3636,7 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
3622
3636
|
|
|
3623
3637
|
// src/app.tsx
|
|
3624
3638
|
import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3625
|
-
function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "degraded" }) {
|
|
3639
|
+
function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "degraded", degradedMessage }) {
|
|
3626
3640
|
const connected = mode === "connected" && baseUrl !== null;
|
|
3627
3641
|
const degraded = !connected;
|
|
3628
3642
|
const daemon = useDaemon(connected ? baseUrl : null);
|
|
@@ -3996,7 +4010,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3996
4010
|
if (webStartingRef.current) return;
|
|
3997
4011
|
webStartingRef.current = true;
|
|
3998
4012
|
try {
|
|
3999
|
-
const { startWebServer } = await import("./server-
|
|
4013
|
+
const { startWebServer } = await import("./server-LNFK5MRX.js");
|
|
4000
4014
|
const ctrl = await startWebServer({ config: cfg, log: false });
|
|
4001
4015
|
webRef.current = ctrl;
|
|
4002
4016
|
openUrl(ctrl.url);
|
|
@@ -4185,7 +4199,8 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
4185
4199
|
glyphs().warn,
|
|
4186
4200
|
" degraded ",
|
|
4187
4201
|
glyphs().middot,
|
|
4188
|
-
"
|
|
4202
|
+
" ",
|
|
4203
|
+
degradedMessage ?? "background service unavailable, running in-process"
|
|
4189
4204
|
] }),
|
|
4190
4205
|
connected && daemon.conn !== "live" && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
4191
4206
|
glyphs().warn,
|
|
@@ -4333,7 +4348,8 @@ async function bootstrapInk({ interval, config, daemon, mode }) {
|
|
|
4333
4348
|
interval,
|
|
4334
4349
|
initialConfig: config,
|
|
4335
4350
|
baseUrl: daemon.baseUrl,
|
|
4336
|
-
mode
|
|
4351
|
+
mode,
|
|
4352
|
+
degradedMessage: daemon.issue?.message
|
|
4337
4353
|
}
|
|
4338
4354
|
) })
|
|
4339
4355
|
);
|
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
deriveQuotaViews,
|
|
11
11
|
fetchPeak,
|
|
12
12
|
namedHex
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-BZFDTIFV.js";
|
|
14
14
|
import {
|
|
15
15
|
PROVIDERS,
|
|
16
16
|
detectProviders,
|
|
17
17
|
resolveTimezone,
|
|
18
18
|
withTimeout
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-DESEB73E.js";
|
|
20
20
|
import {
|
|
21
21
|
ConfigPersistenceFailure,
|
|
22
22
|
ConfigUpdateConflictFailure,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
TOKMON_WS_METHODS,
|
|
27
27
|
TOKMON_WS_PATH,
|
|
28
28
|
TokmonRpcGroup
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-UON47FYF.js";
|
|
30
30
|
import {
|
|
31
31
|
cacheDir,
|
|
32
32
|
expandHome,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
normalizeConfig,
|
|
35
35
|
saveConfig,
|
|
36
36
|
snapshotCacheFile
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-O7ORALKA.js";
|
|
38
38
|
|
|
39
39
|
// src/web/server.ts
|
|
40
40
|
import { createServer } from "http";
|
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
identityFromIdToken,
|
|
6
6
|
readClaudeIdentity,
|
|
7
7
|
readJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DESEB73E.js";
|
|
9
9
|
import {
|
|
10
10
|
containsEmail,
|
|
11
11
|
expandHome,
|
|
12
12
|
redactEmail,
|
|
13
13
|
slugify
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-O7ORALKA.js";
|
|
15
15
|
|
|
16
16
|
// src/accounts.ts
|
|
17
17
|
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
@@ -281,15 +281,34 @@ function inferRole(metric) {
|
|
|
281
281
|
if (metric.modelId || !metric.primary && metric.format.kind === "percent" && !["usage", "api", "auto"].includes(label)) return "model";
|
|
282
282
|
return "other";
|
|
283
283
|
}
|
|
284
|
-
function
|
|
284
|
+
function formatMoney(value, currency) {
|
|
285
|
+
try {
|
|
286
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(value);
|
|
287
|
+
} catch {
|
|
288
|
+
return `$${value.toFixed(2)}`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function moneyValue(metric) {
|
|
292
|
+
if (metric.format.kind !== "dollars" || !Number.isFinite(metric.used)) return void 0;
|
|
293
|
+
const limit = metric.limit != null && Number.isFinite(metric.limit) ? metric.limit : null;
|
|
294
|
+
return {
|
|
295
|
+
kind: "money",
|
|
296
|
+
used: metric.used,
|
|
297
|
+
limit,
|
|
298
|
+
remaining: limit === null ? null : limit - metric.used,
|
|
299
|
+
currency: metric.format.currency?.trim().toUpperCase() || "USD"
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function valueText(metric, usedPct, value) {
|
|
303
|
+
if (value) {
|
|
304
|
+
if (value.remaining === null) return formatMoney(value.used, value.currency);
|
|
305
|
+
const used = `${formatMoney(value.used, value.currency)} used`;
|
|
306
|
+
return value.remaining >= 0 ? `${used} \xB7 ${formatMoney(value.remaining, value.currency)} left` : `${used} \xB7 ${formatMoney(-value.remaining, value.currency)} over`;
|
|
307
|
+
}
|
|
285
308
|
if (usedPct !== null) return `${Math.round(usedPct)}% used`;
|
|
286
309
|
if (!Number.isFinite(metric.used)) return "\u2014";
|
|
287
310
|
if (metric.format.kind === "dollars") {
|
|
288
|
-
|
|
289
|
-
return new Intl.NumberFormat("en-US", { style: "currency", currency: metric.format.currency ?? "USD", maximumFractionDigits: 2 }).format(metric.used);
|
|
290
|
-
} catch {
|
|
291
|
-
return `$${metric.used.toFixed(2)}`;
|
|
292
|
-
}
|
|
311
|
+
return formatMoney(metric.used, metric.format.currency?.trim().toUpperCase() || "USD");
|
|
293
312
|
}
|
|
294
313
|
if (metric.format.kind === "count") {
|
|
295
314
|
const suffix = metric.format.suffix?.trim();
|
|
@@ -303,6 +322,7 @@ function deriveQuotaView(metric, sourceIndex = 0) {
|
|
|
303
322
|
const usedPct = metric.format.kind === "percent" ? metric.used : metric.limit != null && metric.limit > 0 ? metric.used / metric.limit * 100 : null;
|
|
304
323
|
const finiteUsedPct = usedPct == null || !Number.isFinite(usedPct) ? null : clampPct(usedPct);
|
|
305
324
|
const resets = metric.resetsAt ? Date.parse(metric.resetsAt) : Number.NaN;
|
|
325
|
+
const value = moneyValue(metric);
|
|
306
326
|
return {
|
|
307
327
|
key: metric.key ?? `${metric.label}:${sourceIndex}`,
|
|
308
328
|
label: metric.label.replace(/\s*(limit|usage)$/i, "").trim() || metric.label,
|
|
@@ -315,7 +335,8 @@ function deriveQuotaView(metric, sourceIndex = 0) {
|
|
|
315
335
|
primary: metric.primary === true,
|
|
316
336
|
active: metric.active === true,
|
|
317
337
|
displayOrder: sourceIndex,
|
|
318
|
-
valueText: valueText(metric, finiteUsedPct)
|
|
338
|
+
valueText: valueText(metric, finiteUsedPct, value),
|
|
339
|
+
...value ? { value } : {}
|
|
319
340
|
};
|
|
320
341
|
}
|
|
321
342
|
function deriveQuotaViews(metrics) {
|
|
@@ -425,8 +446,9 @@ function cumulativeQuotaViews(accounts) {
|
|
|
425
446
|
const remainingPct = rows.reduce((sum, quota) => sum + quota.remainingPct, 0) / rows.length;
|
|
426
447
|
const usedPct = 100 - remainingPct;
|
|
427
448
|
const resets = rows.map((quota) => quota.resetsAt).filter((value) => value !== null);
|
|
449
|
+
const { value: _value, ...firstWithoutValue } = first;
|
|
428
450
|
return {
|
|
429
|
-
...
|
|
451
|
+
...firstWithoutValue,
|
|
430
452
|
usedPct,
|
|
431
453
|
remainingPct,
|
|
432
454
|
resetsAt: resets.length > 0 ? Math.min(...resets) : null,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
envDir,
|
|
6
6
|
expandHome,
|
|
7
7
|
isValidTimezone
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-O7ORALKA.js";
|
|
9
9
|
|
|
10
10
|
// src/async.ts
|
|
11
11
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -1706,7 +1706,6 @@ function identityFromIdToken(idToken) {
|
|
|
1706
1706
|
// src/providers/codex/billing.ts
|
|
1707
1707
|
var USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
1708
1708
|
var RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
1709
|
-
var CREDIT_USD_RATE = 0.04;
|
|
1710
1709
|
function chatGptPlanLabel(planType) {
|
|
1711
1710
|
if (typeof planType !== "string" || !planType.trim()) return null;
|
|
1712
1711
|
const p = planType.trim().toLowerCase();
|
|
@@ -1830,11 +1829,13 @@ function appendWindowMetrics(metrics, rl, headerPct) {
|
|
|
1830
1829
|
metrics.push(percentMetric(label, used, resetFrom2(item)));
|
|
1831
1830
|
}
|
|
1832
1831
|
}
|
|
1833
|
-
function
|
|
1832
|
+
function creditBalanceMetric(source) {
|
|
1834
1833
|
const balance = numberValue(source?.credits?.balance ?? source?.credit_balance);
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1834
|
+
return balance !== void 0 && balance >= 0 ? { key: "credits", role: "unbounded", label: "Credits", used: balance, limit: null, format: { kind: "count", suffix: "available" } } : null;
|
|
1835
|
+
}
|
|
1836
|
+
function appendCredits(metrics, source) {
|
|
1837
|
+
const metric = creditBalanceMetric(source);
|
|
1838
|
+
if (metric) metrics.push(metric);
|
|
1838
1839
|
}
|
|
1839
1840
|
async function fetchResetCredits(headers) {
|
|
1840
1841
|
try {
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
classifyDaemonCompatibility,
|
|
4
|
+
daemonConflictMessage,
|
|
5
|
+
isAlive,
|
|
6
|
+
probeHealth,
|
|
7
|
+
readForeignLock,
|
|
3
8
|
readLock,
|
|
4
9
|
retireIncompatibleCliOwner,
|
|
5
10
|
verifyLock
|
|
6
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-WHOIUSJM.js";
|
|
7
12
|
import {
|
|
8
13
|
daemonChannelFromWire,
|
|
9
14
|
resolveDaemonChannel
|
|
10
15
|
} from "./chunk-UYPDMVW5.js";
|
|
11
16
|
import {
|
|
12
17
|
TOKMON_PROTOCOL_VERSION
|
|
13
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-UON47FYF.js";
|
|
14
19
|
|
|
15
20
|
// src/client/daemon-handle.ts
|
|
16
21
|
import { spawn } from "child_process";
|
|
@@ -51,6 +56,45 @@ async function attach(opts, protocolVersion) {
|
|
|
51
56
|
const lock = await verifyLock(readLock(opts), protocolVersion);
|
|
52
57
|
return lock ? connected(lock.url) : null;
|
|
53
58
|
}
|
|
59
|
+
function incompatibleOwnerIssue(owner, protocolVersion, options = {}) {
|
|
60
|
+
return {
|
|
61
|
+
kind: owner.ownerKind === "desktop" ? "incompatible-desktop" : owner.ownerKind === "cli" ? "incompatible-cli" : "incompatible-owner",
|
|
62
|
+
message: daemonConflictMessage(owner, {
|
|
63
|
+
clientKind: "cli",
|
|
64
|
+
clientProtocolVersion: protocolVersion,
|
|
65
|
+
...options
|
|
66
|
+
}),
|
|
67
|
+
ownerKind: owner.ownerKind ?? null,
|
|
68
|
+
ownerVersion: owner.version ?? null,
|
|
69
|
+
ownerProtocolVersion: owner.protocolVersion ?? null,
|
|
70
|
+
clientProtocolVersion: protocolVersion
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function arbitrateIncompatibleOwner(opts, protocolVersion, timeoutMs) {
|
|
74
|
+
const owner = readForeignLock(opts);
|
|
75
|
+
if (!owner || !isAlive(owner.pid)) return { retired: false, issue: null };
|
|
76
|
+
const decision = classifyDaemonCompatibility(owner, protocolVersion);
|
|
77
|
+
const verified = await probeHealth(owner.url, owner.wsToken, {
|
|
78
|
+
channel: owner.channel,
|
|
79
|
+
...owner.ownerKind ? { ownerKind: owner.ownerKind } : {},
|
|
80
|
+
...owner.protocolVersion === void 0 ? {} : { protocolVersion: owner.protocolVersion },
|
|
81
|
+
...owner.version ? { version: owner.version } : {}
|
|
82
|
+
}, Math.min(500, timeoutMs));
|
|
83
|
+
if (!verified) {
|
|
84
|
+
return {
|
|
85
|
+
retired: false,
|
|
86
|
+
issue: incompatibleOwnerIssue(owner, protocolVersion, { verificationFailed: true })
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (decision.action !== "retire") {
|
|
90
|
+
return { retired: false, issue: incompatibleOwnerIssue(owner, protocolVersion) };
|
|
91
|
+
}
|
|
92
|
+
const retired = await retireIncompatibleCliOwner(opts, protocolVersion, timeoutMs);
|
|
93
|
+
return retired ? { retired: true, issue: null } : {
|
|
94
|
+
retired: false,
|
|
95
|
+
issue: incompatibleOwnerIssue(owner, protocolVersion, { retirementFailed: true })
|
|
96
|
+
};
|
|
97
|
+
}
|
|
54
98
|
async function attachOrSpawn(opts = {}) {
|
|
55
99
|
const protocolVersion = TOKMON_PROTOCOL_VERSION;
|
|
56
100
|
const timeoutMs = opts.timeoutMs ?? HANDSHAKE_TIMEOUT_MS;
|
|
@@ -59,9 +103,21 @@ async function attachOrSpawn(opts = {}) {
|
|
|
59
103
|
const lockOpts = { cachePath: opts.cachePath, channel };
|
|
60
104
|
const existing = await attach(lockOpts, protocolVersion);
|
|
61
105
|
if (existing) return existing;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
106
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
107
|
+
const arbitration = await arbitrateIncompatibleOwner(lockOpts, protocolVersion, timeoutMs);
|
|
108
|
+
if (arbitration.issue) return degraded(arbitration.issue);
|
|
109
|
+
if (!arbitration.retired) break;
|
|
110
|
+
const upgraded = await attach(lockOpts, protocolVersion);
|
|
111
|
+
if (upgraded) return upgraded;
|
|
112
|
+
}
|
|
113
|
+
const raced = await attach(lockOpts, protocolVersion);
|
|
114
|
+
if (raced) return raced;
|
|
115
|
+
const raceArbitration = await arbitrateIncompatibleOwner(lockOpts, protocolVersion, timeoutMs);
|
|
116
|
+
if (raceArbitration.issue) return degraded(raceArbitration.issue);
|
|
117
|
+
if (raceArbitration.retired) {
|
|
118
|
+
const replacement = await attach(lockOpts, protocolVersion);
|
|
119
|
+
if (replacement) return replacement;
|
|
120
|
+
}
|
|
65
121
|
const entry = opts.entry ?? process.argv[1];
|
|
66
122
|
if (!entry) return degraded();
|
|
67
123
|
const execPath = opts.execPath ?? process.execPath;
|
|
@@ -79,13 +135,19 @@ async function attachOrSpawn(opts = {}) {
|
|
|
79
135
|
detached: process.platform !== "win32",
|
|
80
136
|
env
|
|
81
137
|
});
|
|
82
|
-
} catch {
|
|
83
|
-
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const detail = error instanceof Error && error.message ? `: ${error.message}` : "";
|
|
140
|
+
resolve(degraded({
|
|
141
|
+
kind: "spawn-failed",
|
|
142
|
+
message: `Could not start the Tokmon background service${detail}. Check Node permissions and retry; local-only mode is active.`
|
|
143
|
+
}));
|
|
84
144
|
return;
|
|
85
145
|
}
|
|
86
146
|
let settled = false;
|
|
87
147
|
let stdout = "";
|
|
88
148
|
let pollTimer = null;
|
|
149
|
+
let startupError = null;
|
|
150
|
+
let startupExit = null;
|
|
89
151
|
const finish = (handle) => {
|
|
90
152
|
if (settled) return;
|
|
91
153
|
settled = true;
|
|
@@ -102,7 +164,23 @@ async function attachOrSpawn(opts = {}) {
|
|
|
102
164
|
return;
|
|
103
165
|
}
|
|
104
166
|
if (final) {
|
|
105
|
-
|
|
167
|
+
if (startupError) {
|
|
168
|
+
finish(degraded({
|
|
169
|
+
kind: "spawn-failed",
|
|
170
|
+
message: `Could not start the Tokmon background service: ${startupError}. Check Node permissions and retry; local-only mode is active.`
|
|
171
|
+
}));
|
|
172
|
+
} else if (startupExit) {
|
|
173
|
+
const result = startupExit.signal ? `signal ${startupExit.signal}` : `exit code ${startupExit.code ?? "unknown"}`;
|
|
174
|
+
finish(degraded({
|
|
175
|
+
kind: "startup-exit",
|
|
176
|
+
message: `The Tokmon background service exited before it was ready (${result}). Update or reinstall Tokmon, then retry; local-only mode is active.`
|
|
177
|
+
}));
|
|
178
|
+
} else {
|
|
179
|
+
finish(degraded({
|
|
180
|
+
kind: "startup-timeout",
|
|
181
|
+
message: `The Tokmon background service did not become ready within ${Math.ceil(timeoutMs / 1e3)}s. Quit any stuck Tokmon process and retry; local-only mode is active.`
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
106
184
|
return;
|
|
107
185
|
}
|
|
108
186
|
if (!settled && !pollTimer) {
|
|
@@ -127,12 +205,18 @@ async function attachOrSpawn(opts = {}) {
|
|
|
127
205
|
return;
|
|
128
206
|
}
|
|
129
207
|
});
|
|
130
|
-
child.once("error", () =>
|
|
131
|
-
|
|
208
|
+
child.once("error", (error) => {
|
|
209
|
+
startupError = error.message || error.name;
|
|
210
|
+
tryAttach();
|
|
211
|
+
});
|
|
212
|
+
child.once("exit", (code, signal) => {
|
|
213
|
+
startupExit = { code, signal };
|
|
214
|
+
tryAttach();
|
|
215
|
+
});
|
|
132
216
|
});
|
|
133
217
|
}
|
|
134
|
-
function degraded() {
|
|
135
|
-
return { kind: "degraded", baseUrl: null, stop: () => {
|
|
218
|
+
function degraded(issue) {
|
|
219
|
+
return { kind: "degraded", baseUrl: null, ...issue ? { issue } : {}, stop: () => {
|
|
136
220
|
} };
|
|
137
221
|
}
|
|
138
222
|
|
|
@@ -503,6 +503,7 @@ function moveProviderSelection(values, providerId, direction) {
|
|
|
503
503
|
var DEFAULT_TRAY_CONFIG = {
|
|
504
504
|
enabled: true,
|
|
505
505
|
showMenuBarText: true,
|
|
506
|
+
menuBarValue: "usage",
|
|
506
507
|
displayMetric: "smartHeadroom",
|
|
507
508
|
pollIntervalSec: 30,
|
|
508
509
|
activeTimeoutMin: 10,
|
|
@@ -821,6 +822,7 @@ function repairConfig(input) {
|
|
|
821
822
|
const tray = {
|
|
822
823
|
enabled: typeof rawTray.enabled === "boolean" ? rawTray.enabled : DEFAULT_TRAY_CONFIG.enabled,
|
|
823
824
|
showMenuBarText: typeof rawTray.showMenuBarText === "boolean" ? rawTray.showMenuBarText : DEFAULT_TRAY_CONFIG.showMenuBarText,
|
|
825
|
+
menuBarValue: rawTray.menuBarValue === "todayTokens" || rawTray.menuBarValue === "usage" ? rawTray.menuBarValue : DEFAULT_TRAY_CONFIG.menuBarValue,
|
|
824
826
|
displayMetric: rawTray.displayMetric === "tightestRemaining" || rawTray.displayMetric === "smartHeadroom" ? rawTray.displayMetric : DEFAULT_TRAY_CONFIG.displayMetric,
|
|
825
827
|
pollIntervalSec: finiteInRange(rawTray.pollIntervalSec, DEFAULT_TRAY_CONFIG.pollIntervalSec, 1, 86400),
|
|
826
828
|
activeTimeoutMin: finiteInRange(rawTray.activeTimeoutMin, DEFAULT_TRAY_CONFIG.activeTimeoutMin, 1, 1440),
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
TOKMON_WS_METHODS,
|
|
5
5
|
TOKMON_WS_PATH,
|
|
6
6
|
TokmonRpcGroup
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UON47FYF.js";
|
|
8
8
|
import {
|
|
9
9
|
normalizeConfig
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-O7ORALKA.js";
|
|
11
11
|
|
|
12
12
|
// src/client/daemon-rpc-client.ts
|
|
13
13
|
import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Schema, Stream } from "effect";
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
DESKTOP_GRAPH_RANGES,
|
|
5
5
|
PROVIDER_IDS,
|
|
6
6
|
THEME_PRESET_IDS
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-O7ORALKA.js";
|
|
8
8
|
|
|
9
9
|
// src/rpc/contract.ts
|
|
10
10
|
import { Schema } from "effect";
|
|
@@ -12,7 +12,7 @@ import * as Rpc from "effect/unstable/rpc/Rpc";
|
|
|
12
12
|
import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
|
13
13
|
var TOKMON_WS_PATH = "/ws";
|
|
14
14
|
var TOKMON_PROTOCOL_VERSION = 4;
|
|
15
|
-
var TOKMON_CAPABILITIES = ["config-cas", "config-revision", "allowed-hosts", "tray-config", "usage-activity", "tray-pins", "provider-pins", "desktop-disclosure", "desktop-graph-range", "provider-headroom", "canonical-identity", "appearance-v1", "theme-engine", "account-detection-v1"];
|
|
15
|
+
var TOKMON_CAPABILITIES = ["config-cas", "config-revision", "allowed-hosts", "tray-config", "usage-activity", "tray-pins", "provider-pins", "desktop-disclosure", "desktop-graph-range", "provider-headroom", "canonical-identity", "appearance-v1", "theme-engine", "account-detection-v1", "menu-bar-today-tokens"];
|
|
16
16
|
var TOKMON_WS_METHODS = {
|
|
17
17
|
getConfig: "tokmon.getConfig",
|
|
18
18
|
setConfig: "tokmon.setConfig",
|
|
@@ -41,6 +41,7 @@ var AccountSchema = Schema.Struct({
|
|
|
41
41
|
var TrayConfigSchema = Schema.Struct({
|
|
42
42
|
enabled: Schema.Boolean,
|
|
43
43
|
showMenuBarText: Schema.Boolean,
|
|
44
|
+
menuBarValue: Schema.optionalKey(Schema.Literals(["usage", "todayTokens"])),
|
|
44
45
|
displayMetric: Schema.Literals(["smartHeadroom", "tightestRemaining"]),
|
|
45
46
|
pollIntervalSec: Schema.Finite.check(Schema.isBetween({ minimum: 1, maximum: 86400 })),
|
|
46
47
|
activeTimeoutMin: Schema.Finite.check(Schema.isBetween({ minimum: 1, maximum: 1440 })),
|
|
@@ -271,7 +272,14 @@ var QuotaViewSchema = Schema.Struct({
|
|
|
271
272
|
primary: Schema.Boolean,
|
|
272
273
|
active: Schema.Boolean,
|
|
273
274
|
displayOrder: NonNegativeIntegerSchema,
|
|
274
|
-
valueText: Schema.String
|
|
275
|
+
valueText: Schema.String,
|
|
276
|
+
value: Schema.optionalKey(Schema.Struct({
|
|
277
|
+
kind: Schema.Literal("money"),
|
|
278
|
+
used: Schema.Finite,
|
|
279
|
+
limit: Schema.NullOr(Schema.Finite),
|
|
280
|
+
remaining: Schema.NullOr(Schema.Finite),
|
|
281
|
+
currency: Schema.String
|
|
282
|
+
}))
|
|
275
283
|
});
|
|
276
284
|
var HeadroomFactorSchema = Schema.Struct({
|
|
277
285
|
key: Schema.String,
|
|
@@ -5,7 +5,53 @@ import {
|
|
|
5
5
|
} from "./chunk-UYPDMVW5.js";
|
|
6
6
|
import {
|
|
7
7
|
cacheDir
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-O7ORALKA.js";
|
|
9
|
+
|
|
10
|
+
// src/web/daemon-compatibility.ts
|
|
11
|
+
function classifyDaemonCompatibility(owner, clientProtocolVersion) {
|
|
12
|
+
if (owner.protocolVersion === clientProtocolVersion) {
|
|
13
|
+
return { action: "attach", reason: "same-protocol" };
|
|
14
|
+
}
|
|
15
|
+
if (owner.ownerKind === "desktop") {
|
|
16
|
+
return { action: "refuse", reason: "desktop-owner" };
|
|
17
|
+
}
|
|
18
|
+
if (owner.ownerKind === "cli" && owner.protocolVersion !== void 0) {
|
|
19
|
+
return owner.protocolVersion < clientProtocolVersion ? { action: "retire", reason: "older-cli" } : { action: "refuse", reason: "newer-cli" };
|
|
20
|
+
}
|
|
21
|
+
if (owner.ownerKind === void 0 && owner.protocolVersion === void 0) {
|
|
22
|
+
return { action: "retire", reason: "legacy-cli" };
|
|
23
|
+
}
|
|
24
|
+
return { action: "refuse", reason: "ambiguous-owner" };
|
|
25
|
+
}
|
|
26
|
+
function daemonConflictMessage(owner, context) {
|
|
27
|
+
const kind = owner.ownerKind === "desktop" ? "Tokmon Desktop" : owner.ownerKind === "cli" ? "Tokmon CLI background service" : "Legacy Tokmon background service";
|
|
28
|
+
const name = owner.version ? `${kind} ${owner.version}` : kind;
|
|
29
|
+
const ownerProtocol = owner.protocolVersion === void 0 ? "an implicit legacy protocol" : `protocol ${owner.protocolVersion}`;
|
|
30
|
+
const client = context.clientKind === "desktop" ? "this desktop app" : "this CLI";
|
|
31
|
+
if (context.verificationFailed) {
|
|
32
|
+
return `A live lock claims ${name} using ${ownerProtocol}, but its owner proof could not be verified. It was not signalled. Quit the existing Tokmon process, remove a stale lock only after confirming it has stopped, then retry.`;
|
|
33
|
+
}
|
|
34
|
+
if (owner.protocolVersion === context.clientProtocolVersion) {
|
|
35
|
+
return `${name} claims compatible protocol ${context.clientProtocolVersion}, but its owner proof could not be verified. It was not signalled. Quit the existing Tokmon process, then retry.`;
|
|
36
|
+
}
|
|
37
|
+
const summary = `${name} owns the background service using ${ownerProtocol}; ${client} needs protocol ${context.clientProtocolVersion}.`;
|
|
38
|
+
if (context.retirementFailed) {
|
|
39
|
+
return `${summary} Tokmon could not stop that older CLI service safely, so it was left running. Quit the existing Tokmon CLI/background service, then retry.`;
|
|
40
|
+
}
|
|
41
|
+
if (owner.ownerKind === "desktop") {
|
|
42
|
+
if (context.clientKind === "desktop") {
|
|
43
|
+
return owner.protocolVersion !== void 0 && owner.protocolVersion < context.clientProtocolVersion ? `${summary} Fully quit the older Tokmon Desktop instance, install the current release, then reopen Tokmon.` : `${summary} This app is older than the running Tokmon Desktop instance. Reopen the newer app, or replace this installation with the current release.`;
|
|
44
|
+
}
|
|
45
|
+
if (owner.protocolVersion !== void 0 && owner.protocolVersion < context.clientProtocolVersion) {
|
|
46
|
+
return `${summary} In Tokmon Desktop, open Settings \u2192 Desktop App \u2192 Check for Updates, install the update, then restart Tokmon. Alternatively, quit Tokmon Desktop before retrying the CLI.`;
|
|
47
|
+
}
|
|
48
|
+
return `${summary} Update the CLI with \`pnpm --config.minimum-release-age=0 dlx tokmon@latest\`, or quit Tokmon Desktop before retrying this CLI.`;
|
|
49
|
+
}
|
|
50
|
+
if (owner.ownerKind === "cli" && owner.protocolVersion !== void 0 && owner.protocolVersion > context.clientProtocolVersion) {
|
|
51
|
+
return context.clientKind === "desktop" ? `${summary} Update Tokmon Desktop and restart it, or stop the newer CLI background service before reopening the app.` : `${summary} Update the CLI with \`pnpm --config.minimum-release-age=0 dlx tokmon@latest\`, or stop the newer background service before retrying.`;
|
|
52
|
+
}
|
|
53
|
+
return `${summary} The owner could not be identified safely and was not signalled. Quit the existing Tokmon process, then retry.`;
|
|
54
|
+
}
|
|
9
55
|
|
|
10
56
|
// src/web/lockfile.ts
|
|
11
57
|
import { closeSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -247,11 +293,11 @@ async function retireIncompatibleCliOwner(opts, protocolVersion, timeoutMs = TAK
|
|
|
247
293
|
const compatible = readLock(opts);
|
|
248
294
|
if (compatible?.protocolVersion === protocolVersion) return false;
|
|
249
295
|
const foreign = readForeignLock(opts);
|
|
250
|
-
const
|
|
251
|
-
if (!foreign || foreign.state !== "ready" ||
|
|
296
|
+
const decision = foreign && classifyDaemonCompatibility(foreign, protocolVersion);
|
|
297
|
+
if (!foreign || foreign.state !== "ready" || decision?.action !== "retire" || foreign.pid === process.pid || !isAlive(foreign.pid)) return false;
|
|
252
298
|
const health = await ownerHealth(foreign.url, foreign.wsToken, Math.min(1e3, timeoutMs));
|
|
253
299
|
const healthChannel = daemonChannelFromWire(health?.channel);
|
|
254
|
-
if (health?.ok !== true || health.owner !== true || health.ownerKind !== void 0
|
|
300
|
+
if (health?.ok !== true || health.owner !== true || (decision.reason === "legacy-cli" ? health.ownerKind !== void 0 || health.protocolVersion !== void 0 : health.ownerKind !== "cli" || health.protocolVersion !== foreign.protocolVersion) || healthChannel !== foreign.channel) return false;
|
|
255
301
|
try {
|
|
256
302
|
process.kill(foreign.pid, "SIGTERM");
|
|
257
303
|
} catch {
|
|
@@ -279,6 +325,8 @@ async function retireIncompatibleCliOwner(opts, protocolVersion, timeoutMs = TAK
|
|
|
279
325
|
}
|
|
280
326
|
|
|
281
327
|
export {
|
|
328
|
+
classifyDaemonCompatibility,
|
|
329
|
+
daemonConflictMessage,
|
|
282
330
|
lockfilePath,
|
|
283
331
|
readLock,
|
|
284
332
|
readForeignLock,
|
|
@@ -289,6 +337,7 @@ export {
|
|
|
289
337
|
reclaimUnhealthyLock,
|
|
290
338
|
reclaimAbandonedLock,
|
|
291
339
|
isAlive,
|
|
340
|
+
probeHealth,
|
|
292
341
|
verifyLock,
|
|
293
342
|
retireIncompatibleCliOwner
|
|
294
343
|
};
|