tokmon 0.28.2 → 0.28.4

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.
Files changed (56) hide show
  1. package/README.md +156 -234
  2. package/THIRD_PARTY_NOTICES.md +62 -0
  3. package/dist/{bootstrap-ink-IMT3S5HU.js → bootstrap-ink-WG4WG3R2.js} +1579 -1136
  4. package/dist/chunk-5B3XCEMC.js +294 -0
  5. package/dist/chunk-7CH6S7BR.js +447 -0
  6. package/dist/{chunk-LCAHTRGP.js → chunk-CFKUZCMB.js} +56 -29
  7. package/dist/{chunk-LKYOWBD4.js → chunk-FLANGRI6.js} +21 -42
  8. package/dist/chunk-HNWEJ6MS.js +1094 -0
  9. package/dist/{chunk-WUK3MQUB.js → chunk-JZSZEHVD.js} +167 -10
  10. package/dist/{chunk-6ZEW2M6P.js → chunk-P57DVFNH.js} +125 -25
  11. package/dist/chunk-TN6V7XSL.js +512 -0
  12. package/dist/chunk-UYPDMVW5.js +21 -0
  13. package/dist/{cli-command-DCENRCO3.js → cli-command-I4GRR3MF.js} +249 -15
  14. package/dist/cli.js +6 -6
  15. package/dist/{config-HXFJTNLM.js → config-3DGZ47F7.js} +23 -3
  16. package/dist/daemon-ZXV3V5CS.js +384 -0
  17. package/dist/daemon-handle-G4OGPFR3.js +11 -0
  18. package/dist/server-EPSH52DR.js +12 -0
  19. package/dist/web/assets/{Area-CMXvOw33.js → Area-stRnp8Km.js} +1 -1
  20. package/dist/web/assets/analytics-moZZt9L_.js +2 -0
  21. package/dist/web/assets/breakdown-km9w7myU.js +4 -0
  22. package/dist/web/assets/button-DrX04Cc2.js +1 -0
  23. package/dist/web/assets/{chart-cZoLgpmG.js → chart-D0CtuL2k.js} +1 -1
  24. package/dist/web/assets/explore-B11uNJV1.js +22 -0
  25. package/dist/web/assets/index-RDhXsCNG.js +83 -0
  26. package/dist/web/assets/index-ZmOS8p9O.css +1 -0
  27. package/dist/web/assets/models-DYbZnY34.js +2 -0
  28. package/dist/web/assets/overview-CruICbfJ.js +2 -0
  29. package/dist/web/assets/panel-xckM3_GC.js +1 -0
  30. package/dist/web/assets/settings-sheet-G-BTSIF6.js +1 -0
  31. package/dist/web/assets/share-sheet-weIdd4DW.js +2 -0
  32. package/dist/web/assets/timeline-BHgniliZ.js +1 -0
  33. package/dist/web/index.html +30 -4
  34. package/package.json +13 -4
  35. package/site/THIRD_PARTY_NOTICES.md +83 -0
  36. package/site/public/fonts/OFL.txt +93 -0
  37. package/dist/chunk-44ZSQEOS.js +0 -299
  38. package/dist/chunk-HP5UZCXP.js +0 -436
  39. package/dist/chunk-J3JW3RFP.js +0 -196
  40. package/dist/chunk-M7XMV36F.js +0 -275
  41. package/dist/daemon-6Y4O7NXS.js +0 -241
  42. package/dist/daemon-handle-GVCYVDPO.js +0 -10
  43. package/dist/server-GXXOWUVZ.js +0 -11
  44. package/dist/web/assets/analytics-DqdZXOJL.js +0 -2
  45. package/dist/web/assets/breakdown-kOSSJ3mI.js +0 -4
  46. package/dist/web/assets/explore-vcsM8K9I.js +0 -22
  47. package/dist/web/assets/index-BErCXT7f.css +0 -1
  48. package/dist/web/assets/index-DH3dOnbg.js +0 -83
  49. package/dist/web/assets/models-B5ikm83_.js +0 -2
  50. package/dist/web/assets/overview-DD1kP8CP.js +0 -2
  51. package/dist/web/assets/panel-DTQBwExW.js +0 -1
  52. package/dist/web/assets/primitives-C36qJlA2.js +0 -1
  53. package/dist/web/assets/settings-sheet-_i2zTQjf.js +0 -1
  54. package/dist/web/assets/share-sheet-BLHmrFbm.js +0 -2
  55. package/dist/web/assets/timeline-B4uG-Emo.js +0 -1
  56. package/dist/web/assets/use-dialog-trap-bfaXEMz9.js +0 -1
@@ -0,0 +1,512 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PROVIDERS,
4
+ PROVIDER_ORDER,
5
+ identityFromIdToken,
6
+ readClaudeIdentity,
7
+ readJson
8
+ } from "./chunk-CFKUZCMB.js";
9
+ import {
10
+ containsEmail,
11
+ expandHome,
12
+ redactEmail,
13
+ slugify
14
+ } from "./chunk-HNWEJ6MS.js";
15
+
16
+ // src/accounts.ts
17
+ import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
18
+ import { homedir } from "os";
19
+ import { basename, join as join2, resolve } from "path";
20
+
21
+ // src/providers/codex/identity.ts
22
+ import { readFileSync } from "fs";
23
+ import { join } from "path";
24
+ function codexAuthPaths(homeDir) {
25
+ return [join(homeDir, ".codex", "auth.json"), join(homeDir, "auth.json")];
26
+ }
27
+ function readCodexIdentity(homeDir) {
28
+ for (const path of codexAuthPaths(homeDir)) {
29
+ try {
30
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
31
+ const { email, displayName, payload } = identityFromIdToken(parsed?.tokens?.id_token);
32
+ if (!payload) continue;
33
+ return { email, displayName };
34
+ } catch {
35
+ }
36
+ }
37
+ return {};
38
+ }
39
+
40
+ // src/accounts.ts
41
+ function accountKey(providerId, homeDir) {
42
+ return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir()}`;
43
+ }
44
+ function uniqueId(base, used) {
45
+ let id = slugify(base) || "account";
46
+ if (!used.has(id)) {
47
+ used.add(id);
48
+ return id;
49
+ }
50
+ for (let i = 2; i < 1e3; i++) {
51
+ const next = `${id}_${i}`;
52
+ if (!used.has(next)) {
53
+ used.add(next);
54
+ return next;
55
+ }
56
+ }
57
+ id = `${id}_${Date.now()}`;
58
+ used.add(id);
59
+ return id;
60
+ }
61
+ function hasClaudeState(homeDir) {
62
+ return existsSync(join2(homeDir, ".claude.json")) || existsSync(join2(homeDir, ".claude", ".credentials.json")) || existsSync(join2(homeDir, ".claude", "projects")) || existsSync(join2(homeDir, ".config", "claude", ".credentials.json")) || existsSync(join2(homeDir, ".config", "claude", "projects"));
63
+ }
64
+ function candidateAlternateHomes(prefix) {
65
+ const home = homedir();
66
+ let entries;
67
+ try {
68
+ entries = readdirSync(home);
69
+ } catch {
70
+ return [];
71
+ }
72
+ const out = [];
73
+ const pattern = new RegExp(`^\\.${prefix}[_-]`);
74
+ for (const name of entries) {
75
+ if (!pattern.test(name)) continue;
76
+ const path = join2(home, name);
77
+ try {
78
+ if (!statSync(path).isDirectory()) continue;
79
+ out.push(path);
80
+ } catch {
81
+ }
82
+ }
83
+ return out.sort();
84
+ }
85
+ function labelForClaudeHome(homeDir) {
86
+ const identity = readClaudeIdentity(homeDir);
87
+ if (identity.email) return `Claude ${identity.email}`;
88
+ if (identity.displayName) return `Claude ${identity.displayName}`;
89
+ const raw = basename(homeDir).replace(/^\.claude[_-]?/, "").replace(/[_-]+/g, " ").trim();
90
+ return raw ? `Claude ${raw}` : "Claude";
91
+ }
92
+ function hasCodexAuth(homeDir) {
93
+ for (const path of codexAuthPaths(homeDir)) {
94
+ try {
95
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
96
+ const accessToken = parsed?.tokens?.access_token;
97
+ if (typeof accessToken === "string" && accessToken.trim()) return true;
98
+ } catch {
99
+ }
100
+ }
101
+ return false;
102
+ }
103
+ function labelForCodexHome(homeDir) {
104
+ const identity = readCodexIdentity(homeDir);
105
+ if (identity.email) return `Codex ${identity.email}`;
106
+ if (identity.displayName) return `Codex ${identity.displayName}`;
107
+ const raw = basename(homeDir).replace(/^\.codex[_-]?/, "").replace(/[_-]+/g, " ").trim();
108
+ return raw ? `Codex ${raw}` : "Codex";
109
+ }
110
+ function discoverClaudeAccounts(usedIds) {
111
+ const provider = PROVIDERS.claude;
112
+ const out = [];
113
+ for (const homeDir of candidateAlternateHomes("claude")) {
114
+ if (!hasClaudeState(homeDir)) continue;
115
+ const suffix = basename(homeDir).replace(/^\.claude[_-]?/, "") || basename(homeDir);
116
+ out.push({
117
+ id: uniqueId(`claude_${suffix}`, usedIds),
118
+ providerId: "claude",
119
+ name: labelForClaudeHome(homeDir),
120
+ color: provider.color,
121
+ homeDir
122
+ });
123
+ }
124
+ return out;
125
+ }
126
+ function discoverCodexAccounts(usedIds) {
127
+ const provider = PROVIDERS.codex;
128
+ const out = [];
129
+ for (const homeDir of candidateAlternateHomes("codex")) {
130
+ if (!hasCodexAuth(homeDir)) continue;
131
+ const suffix = basename(homeDir).replace(/^\.codex[_-]?/, "") || basename(homeDir);
132
+ out.push({
133
+ id: uniqueId(`codex_${suffix}`, usedIds),
134
+ providerId: "codex",
135
+ name: labelForCodexHome(homeDir),
136
+ color: provider.color,
137
+ homeDir
138
+ });
139
+ }
140
+ return out;
141
+ }
142
+ function discoverProviderAccounts(providerId, usedIds) {
143
+ if (providerId === "claude") return discoverClaudeAccounts(usedIds);
144
+ if (providerId === "codex") return discoverCodexAccounts(usedIds);
145
+ return [];
146
+ }
147
+ function buildAccounts(config, detected) {
148
+ const out = [];
149
+ const usedIds = new Set(config.accounts.map((a) => a.id));
150
+ const seenKeys = /* @__PURE__ */ new Set();
151
+ const excludedKeys = new Set(
152
+ config.accountDetection.excludedAccounts.map((ref) => accountKey(ref.providerId, ref.homeDir))
153
+ );
154
+ const add = (account) => {
155
+ const key = accountKey(account.providerId, account.homeDir);
156
+ if (seenKeys.has(key)) return;
157
+ seenKeys.add(key);
158
+ out.push(account);
159
+ };
160
+ for (const pid of PROVIDER_ORDER) {
161
+ if (config.disabledProviders.includes(pid)) continue;
162
+ const provider = PROVIDERS[pid];
163
+ const configured = config.accounts.filter((a) => a.providerId === pid);
164
+ for (const a of configured) {
165
+ add({
166
+ id: a.id,
167
+ providerId: pid,
168
+ name: a.name,
169
+ color: a.color || provider.color,
170
+ homeDir: a.homeDir && a.homeDir !== "~" ? expandHome(a.homeDir) : void 0
171
+ });
172
+ }
173
+ if (!config.accountDetection.enabled || config.accountDetection.disabledProviders.includes(pid)) continue;
174
+ const discovered = discoverProviderAccounts(pid, usedIds);
175
+ if (detected.includes(pid)) {
176
+ const account = { id: pid, providerId: pid, name: provider.name, color: provider.color, homeDir: void 0 };
177
+ if (!excludedKeys.has(accountKey(pid))) add(account);
178
+ }
179
+ for (const account of discovered) {
180
+ if (excludedKeys.has(accountKey(account.providerId, account.homeDir))) continue;
181
+ add(account);
182
+ }
183
+ }
184
+ return out;
185
+ }
186
+ function accountsByProvider(accounts) {
187
+ const groups = [];
188
+ for (const pid of PROVIDER_ORDER) {
189
+ const list = accounts.filter((a) => a.providerId === pid);
190
+ if (list.length > 0) groups.push({ provider: pid, accounts: list });
191
+ }
192
+ return groups;
193
+ }
194
+
195
+ // src/shared/colors.ts
196
+ var FALLBACK_HEX = "#8d9090";
197
+ var NAMED_HEX = {
198
+ green: "#6caa71",
199
+ greenBright: "#79be7e",
200
+ cyan: "#7ccbcd",
201
+ cyanBright: "#84dde0",
202
+ blue: "#6d96b4",
203
+ blueBright: "#67b5ed",
204
+ magenta: "#bd7bcd",
205
+ magentaBright: "#d389e5",
206
+ yellow: "#c4ac62",
207
+ yellowBright: "#d9c074",
208
+ red: "#b45648",
209
+ redBright: "#cf6a5a",
210
+ white: "#dee5eb",
211
+ whiteBright: "#f3f5f5",
212
+ gray: FALLBACK_HEX,
213
+ grey: FALLBACK_HEX
214
+ };
215
+ var PROVIDER_HEX = {
216
+ claude: NAMED_HEX.green,
217
+ codex: NAMED_HEX.cyan,
218
+ cursor: NAMED_HEX.magenta,
219
+ copilot: NAMED_HEX.white,
220
+ pi: NAMED_HEX.blue,
221
+ opencode: NAMED_HEX.yellow,
222
+ antigravity: NAMED_HEX.red,
223
+ gemini: NAMED_HEX.greenBright,
224
+ grok: NAMED_HEX.yellowBright
225
+ };
226
+ function namedHex(name) {
227
+ if (!name) return FALLBACK_HEX;
228
+ if (name.startsWith("#")) return name;
229
+ return NAMED_HEX[name] ?? FALLBACK_HEX;
230
+ }
231
+ function colorHex(accountColor, providerColorName) {
232
+ if (accountColor) {
233
+ if (accountColor.startsWith("#")) return accountColor;
234
+ const named = NAMED_HEX[accountColor];
235
+ if (named) return named;
236
+ }
237
+ return namedHex(providerColorName);
238
+ }
239
+ var MODEL_PALETTE = [
240
+ "#00d7ff",
241
+ "#00d787",
242
+ "#e6b450",
243
+ "#d75f87",
244
+ "#5f87ff",
245
+ "#af87ff",
246
+ "#5fd7a7",
247
+ "#ff8787",
248
+ "#d7af5f",
249
+ "#87d7ff",
250
+ "#d787d7",
251
+ "#9ee493",
252
+ "#ffb454",
253
+ "#7aa2f7",
254
+ "#bb9af7"
255
+ ];
256
+ var modelColorCache = /* @__PURE__ */ new Map();
257
+ function modelColor(name) {
258
+ const cached = modelColorCache.get(name);
259
+ if (cached) return cached;
260
+ let hash = 0;
261
+ for (let i = 0; i < name.length; i++) hash = hash * 31 + name.charCodeAt(i) >>> 0;
262
+ const color = MODEL_PALETTE[hash % MODEL_PALETTE.length];
263
+ modelColorCache.set(name, color);
264
+ return color;
265
+ }
266
+ var TOKEN_BUCKET = {
267
+ input: NAMED_HEX.blue,
268
+ output: NAMED_HEX.green,
269
+ cacheCreate: NAMED_HEX.yellow,
270
+ cacheRead: NAMED_HEX.cyan
271
+ };
272
+
273
+ // src/usage-semantics.ts
274
+ var clampPct = (value) => Math.max(0, Math.min(100, value));
275
+ function inferRole(metric) {
276
+ if (metric.role) return metric.role;
277
+ if (metric.format.kind !== "percent" && !(metric.limit != null && metric.limit > 0)) return "unbounded";
278
+ const label = metric.label.trim().toLowerCase();
279
+ if (label === "session" || label.includes("five hour")) return "session";
280
+ if (label === "weekly" || label.includes("seven day") || label.includes("weekly all")) return "weekly";
281
+ if (metric.modelId || !metric.primary && metric.format.kind === "percent" && !["usage", "api", "auto"].includes(label)) return "model";
282
+ return "other";
283
+ }
284
+ function valueText(metric, usedPct) {
285
+ if (usedPct !== null) return `${Math.round(usedPct)}% used`;
286
+ if (!Number.isFinite(metric.used)) return "\u2014";
287
+ if (metric.format.kind === "dollars") {
288
+ try {
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
+ }
293
+ }
294
+ if (metric.format.kind === "count") {
295
+ const suffix = metric.format.suffix?.trim();
296
+ return `${Math.round(metric.used).toLocaleString("en-US")}${suffix ? ` ${suffix}` : ""}`;
297
+ }
298
+ return `${Math.round(metric.used)}% used`;
299
+ }
300
+ var roleOrder = (role) => role === "session" ? 0 : role === "weekly" ? 1 : role === "model" ? 2 : role === "other" ? 3 : 4;
301
+ function deriveQuotaView(metric, sourceIndex = 0) {
302
+ const bounded = metric.format.kind === "percent" || metric.limit != null && Number.isFinite(metric.limit) && metric.limit > 0;
303
+ const usedPct = metric.format.kind === "percent" ? metric.used : metric.limit != null && metric.limit > 0 ? metric.used / metric.limit * 100 : null;
304
+ const finiteUsedPct = usedPct == null || !Number.isFinite(usedPct) ? null : clampPct(usedPct);
305
+ const resets = metric.resetsAt ? Date.parse(metric.resetsAt) : Number.NaN;
306
+ return {
307
+ key: metric.key ?? `${metric.label}:${sourceIndex}`,
308
+ label: metric.label.replace(/\s*(limit|usage)$/i, "").trim() || metric.label,
309
+ role: inferRole(metric),
310
+ modelId: metric.modelId ?? null,
311
+ usedPct: finiteUsedPct,
312
+ remainingPct: bounded && finiteUsedPct != null ? clampPct(100 - finiteUsedPct) : null,
313
+ resetsAt: Number.isFinite(resets) ? resets : null,
314
+ bounded,
315
+ primary: metric.primary === true,
316
+ active: metric.active === true,
317
+ displayOrder: sourceIndex,
318
+ valueText: valueText(metric, finiteUsedPct)
319
+ };
320
+ }
321
+ function deriveQuotaViews(metrics) {
322
+ return metrics.map(deriveQuotaView).sort((a, b) => roleOrder(a.role) - roleOrder(b.role) || (a.role === "model" ? a.label.localeCompare(b.label) : a.displayOrder - b.displayOrder) || a.key.localeCompare(b.key)).map((quota, displayOrder) => ({ ...quota, displayOrder }));
323
+ }
324
+ function resolveQuotaViews(input) {
325
+ return input.quotas == null ? deriveQuotaViews(input.metrics ?? []) : [...input.quotas];
326
+ }
327
+ function usageFromHeadroom(value) {
328
+ return value === null || !Number.isFinite(value) ? null : clampPct(100 - value);
329
+ }
330
+ function severity(remaining) {
331
+ if (remaining === null || !Number.isFinite(remaining)) return "unknown";
332
+ if (remaining <= 10) return "crit";
333
+ if (remaining <= 25) return "warn";
334
+ return "ok";
335
+ }
336
+ function deriveAccountIdentity(input) {
337
+ const registered = input.name.trim() || input.providerName;
338
+ const email = input.email?.trim() || null;
339
+ const displayName = input.displayName?.trim() || null;
340
+ if (input.privacyMode && (containsEmail(registered) || containsEmail(email))) {
341
+ const title2 = `${input.providerName} account ${input.ordinal}`;
342
+ return { title: title2, subtitle: null, accessibleLabel: title2, redacted: true };
343
+ }
344
+ const title = input.privacyMode ? redactEmail(registered) : registered;
345
+ const subtitleCandidate = !input.privacyMode ? email && !registered.toLowerCase().includes(email.toLowerCase()) ? email : displayName : displayName && !containsEmail(displayName) ? displayName : null;
346
+ const subtitle = subtitleCandidate && subtitleCandidate !== title ? subtitleCandidate : null;
347
+ return { title, subtitle, accessibleLabel: subtitle ? `${title}, ${subtitle}` : title, redacted: input.privacyMode && title !== registered };
348
+ }
349
+ function accountIdentityText(account, providerName) {
350
+ const identity = account.identity;
351
+ if (!identity) return account.name || providerName;
352
+ const parts = [identity.title, identity.subtitle].filter((value) => !!value?.trim()).filter((value, index, values) => values.indexOf(value) === index).filter((value) => value.toLocaleLowerCase() !== providerName.toLocaleLowerCase());
353
+ return parts.join(" \xB7 ") || identity.title || providerName;
354
+ }
355
+ function blendHeadroom(a, b) {
356
+ const low = Math.min(clampPct(a), clampPct(b));
357
+ const high = Math.max(clampPct(a), clampPct(b));
358
+ return low * (0.9 + 0.1 * high / 100);
359
+ }
360
+ function tightestQuotaView(quotas) {
361
+ return quotas.filter((q) => q.remainingPct !== null).sort((a, b) => a.remainingPct - b.remainingPct || (a.resetsAt ?? Infinity) - (b.resetsAt ?? Infinity) || a.key.localeCompare(b.key))[0] ?? null;
362
+ }
363
+ function accountHeadroom(account) {
364
+ const session = account.quotas.find((q) => q.role === "session" && q.remainingPct !== null);
365
+ const models = account.quotas.filter((q) => q.role === "model" && q.remainingPct !== null);
366
+ const activeModel = models.find((q) => q.active) ?? null;
367
+ const weekly = account.quotas.find((q) => q.role === "weekly" && q.remainingPct !== null);
368
+ const factors = [];
369
+ let value = null;
370
+ let mode = "unavailable";
371
+ if (session && activeModel) {
372
+ value = blendHeadroom(session.remainingPct, activeModel.remainingPct);
373
+ mode = "smart";
374
+ factors.push(
375
+ { key: session.key, label: session.label, role: session.role, remainingPct: session.remainingPct, included: true, reason: "session" },
376
+ { key: activeModel.key, label: activeModel.label, role: activeModel.role, remainingPct: activeModel.remainingPct, included: true, reason: "active-model" }
377
+ );
378
+ } else if (session || activeModel) {
379
+ const only = session ?? activeModel;
380
+ value = only.remainingPct;
381
+ mode = "single-window";
382
+ factors.push({ key: only.key, label: only.label, role: only.role, remainingPct: only.remainingPct, included: true, reason: session ? "session" : "active-model" });
383
+ } else {
384
+ const primary = account.quotas.find((q) => q.primary && q.remainingPct !== null) ?? null;
385
+ if (primary) {
386
+ value = primary.remainingPct;
387
+ mode = "single-window";
388
+ factors.push({ key: primary.key, label: primary.label, role: primary.role, remainingPct: primary.remainingPct, included: true, reason: "primary" });
389
+ } else {
390
+ const floor = tightestQuotaView(account.quotas);
391
+ if (floor) {
392
+ value = floor.remainingPct;
393
+ mode = "fallback-floor";
394
+ factors.push({ key: floor.key, label: floor.label, role: floor.role, remainingPct: floor.remainingPct, included: true, reason: "fallback-floor" });
395
+ }
396
+ }
397
+ }
398
+ if (value !== null && weekly) {
399
+ const close = weekly.remainingPct <= 30 || weekly.remainingPct <= value + 10;
400
+ value = Math.min(value, weekly.remainingPct);
401
+ factors.push({ key: weekly.key, label: weekly.label, role: weekly.role, remainingPct: weekly.remainingPct, included: close, reason: "weekly-cap" });
402
+ }
403
+ const included = factors.filter((f) => f.included);
404
+ return {
405
+ value,
406
+ unit: "index-100",
407
+ mode,
408
+ factors,
409
+ explanation: value === null ? "No bounded quota data" : `Based on ${included.map((f) => f.label).join(" + ") || "available quota data"}`
410
+ };
411
+ }
412
+ function cumulativeQuotaViews(accounts) {
413
+ const groups = /* @__PURE__ */ new Map();
414
+ for (const account of accounts) {
415
+ for (const quota of account.quotas) {
416
+ if (quota.remainingPct === null) continue;
417
+ const identity = quota.role === "session" || quota.role === "weekly" ? quota.role : quota.role === "model" ? `${quota.role}:${quota.modelId ?? quota.label.toLowerCase()}` : `${quota.role}:${quota.key}`;
418
+ const rows = groups.get(identity) ?? [];
419
+ rows.push(quota);
420
+ groups.set(identity, rows);
421
+ }
422
+ }
423
+ return [...groups.values()].filter((rows) => rows[0].role !== "model" || rows.length > 1 || accounts.length === 1).map((rows) => {
424
+ const first = rows[0];
425
+ const remainingPct = rows.reduce((sum, quota) => sum + quota.remainingPct, 0) / rows.length;
426
+ const usedPct = 100 - remainingPct;
427
+ const resets = rows.map((quota) => quota.resetsAt).filter((value) => value !== null);
428
+ return {
429
+ ...first,
430
+ usedPct,
431
+ remainingPct,
432
+ resetsAt: resets.length > 0 ? Math.min(...resets) : null,
433
+ primary: rows.some((quota) => quota.primary),
434
+ active: rows.some((quota) => quota.active),
435
+ displayOrder: Math.min(...rows.map((quota) => quota.displayOrder)),
436
+ valueText: `${Math.round(usedPct)}% used`
437
+ };
438
+ }).sort((a, b) => roleOrder(a.role) - roleOrder(b.role) || a.displayOrder - b.displayOrder || a.key.localeCompare(b.key));
439
+ }
440
+ function deriveProviderHeadroom(accounts, activeTimeoutMin, now, displayMetric = "smartHeadroom") {
441
+ const activeCutoff = now - activeTimeoutMin * 6e4;
442
+ const activeIds = accounts.filter((a) => a.lastActivityAt !== null && a.lastActivityAt >= activeCutoff).map((a) => a.id);
443
+ if (displayMetric === "smartHeadroom" && accounts.length > 1) {
444
+ const pooled = accountHeadroom({ id: "cumulative", lastActivityAt: null, quotas: cumulativeQuotaViews(accounts) });
445
+ if (pooled.value === null) return { ...pooled, basis: "unavailable", representativeAccountId: null, activeAccountIds: activeIds };
446
+ const dataCount = accounts.filter((account) => account.quotas.some((quota) => quota.remainingPct !== null)).length;
447
+ return {
448
+ ...pooled,
449
+ basis: activeIds.length > 0 ? "active" : "idle-runway",
450
+ representativeAccountId: null,
451
+ activeAccountIds: activeIds,
452
+ explanation: `${pooled.explanation}; ${dataCount} accounts combined`
453
+ };
454
+ }
455
+ const rows = accounts.map((account) => {
456
+ const smart = accountHeadroom(account);
457
+ if (displayMetric === "smartHeadroom") return { account, value: smart.value, view: smart };
458
+ const floor = tightestQuotaView(account.quotas);
459
+ const value = floor?.remainingPct ?? null;
460
+ return { account, value, view: { value, unit: "index-100", mode: floor ? "fallback-floor" : "unavailable", factors: floor ? [{ key: floor.key, label: floor.label, role: floor.role, remainingPct: value, included: true, reason: "fallback-floor" }] : [], explanation: floor ? `Based on ${floor.label}` : "No bounded quota data" } };
461
+ }).filter((row) => row.value !== null);
462
+ if (rows.length === 0) return { value: null, unit: "index-100", mode: "unavailable", basis: "unavailable", representativeAccountId: null, activeAccountIds: activeIds, factors: [], explanation: "No bounded quota data" };
463
+ const active = rows.filter((row) => activeIds.includes(row.account.id));
464
+ const candidates = active.length > 0 ? active : rows;
465
+ candidates.sort((a, b) => a.value !== b.value ? active.length > 0 ? a.value - b.value : b.value - a.value : active.length > 0 && a.account.lastActivityAt !== b.account.lastActivityAt ? (b.account.lastActivityAt ?? 0) - (a.account.lastActivityAt ?? 0) : a.account.id.localeCompare(b.account.id));
466
+ const selected = candidates[0];
467
+ const basis = active.length > 0 ? "active" : "idle-runway";
468
+ return { ...selected.view, basis, representativeAccountId: selected.account.id, activeAccountIds: activeIds, explanation: `${selected.view.explanation}; ${basis === "active" ? "active account" : "best available account"}` };
469
+ }
470
+
471
+ // src/peak.ts
472
+ async function fetchPeak() {
473
+ try {
474
+ const res = await fetch("https://promoclock.co/api/status", {
475
+ headers: { "Accept": "application/json", "User-Agent": "tokmon" },
476
+ signal: AbortSignal.timeout(3e3)
477
+ });
478
+ if (!res.ok) return null;
479
+ const data = await readJson(res);
480
+ if (!data) return null;
481
+ let state;
482
+ if (data.isPeak === true || data.status === "peak") state = "peak";
483
+ else if (data.isWeekend === true || data.status === "weekend") state = "weekend";
484
+ else if (data.isOffPeak === true || data.status === "off_peak" || data.status === "off-peak") state = "off-peak";
485
+ else return null;
486
+ const minutesUntilChange = typeof data.minutesUntilChange === "number" && Number.isFinite(data.minutesUntilChange) ? data.minutesUntilChange : null;
487
+ return {
488
+ state,
489
+ label: state === "peak" ? "Peak" : state === "weekend" ? "Weekend" : "Off-Peak",
490
+ minutesUntilChange,
491
+ changesAt: minutesUntilChange !== null ? new Date(Date.now() + minutesUntilChange * 6e4).toISOString() : null
492
+ };
493
+ } catch {
494
+ return null;
495
+ }
496
+ }
497
+
498
+ export {
499
+ buildAccounts,
500
+ accountsByProvider,
501
+ namedHex,
502
+ colorHex,
503
+ modelColor,
504
+ deriveQuotaViews,
505
+ resolveQuotaViews,
506
+ usageFromHeadroom,
507
+ severity,
508
+ deriveAccountIdentity,
509
+ accountIdentityText,
510
+ deriveProviderHeadroom,
511
+ fetchPeak
512
+ };
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/web/daemon-channel.ts
4
+ var RELEASE_DAEMON_CHANNEL = "release";
5
+ var DEV_DAEMON_CHANNEL = "dev";
6
+ function resolveDaemonChannel(explicit, env = process.env) {
7
+ if (explicit) return explicit;
8
+ const configured = env.TOKMON_CHANNEL ?? env.TOKMON_RUNTIME_CHANNEL ?? env.TOKMON_RUNTIME_TAG;
9
+ return configured === DEV_DAEMON_CHANNEL ? DEV_DAEMON_CHANNEL : RELEASE_DAEMON_CHANNEL;
10
+ }
11
+ function daemonChannelFromWire(value) {
12
+ if (value === void 0) return RELEASE_DAEMON_CHANNEL;
13
+ if (value === RELEASE_DAEMON_CHANNEL) return RELEASE_DAEMON_CHANNEL;
14
+ if (value === DEV_DAEMON_CHANNEL) return DEV_DAEMON_CHANNEL;
15
+ return null;
16
+ }
17
+
18
+ export {
19
+ resolveDaemonChannel,
20
+ daemonChannelFromWire
21
+ };