tokmon 0.29.3 → 0.29.5
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/README.md +2 -1
- package/dist/{bootstrap-ink-MPWICEIJ.js → bootstrap-ink-VORCPVK4.js} +38 -21
- package/dist/{chunk-AUUBRLBG.js → chunk-3SXVUTV2.js} +2 -2
- package/dist/{chunk-BLUQGJKM.js → chunk-6NJLM5IZ.js} +2 -2
- package/dist/{chunk-BYLWQ46U.js → chunk-6RPYSOBA.js} +6 -3
- package/dist/{chunk-WPZTPMBX.js → chunk-EZHV4R74.js} +20 -8
- package/dist/{chunk-G2A3DVPU.js → chunk-GQCFGAQ6.js} +40 -10
- package/dist/{chunk-5Q5HUOP4.js → chunk-LAJV5JMV.js} +160 -133
- package/dist/{chunk-3D6TO5TS.js → chunk-U2FQXNIU.js} +98 -66
- package/dist/{chunk-YWDMEU3L.js → chunk-UD2IPQK3.js} +1 -1
- package/dist/{cli-command-WL2K5WWR.js → cli-command-X75NGQON.js} +6 -6
- package/dist/cli.js +6 -6
- package/dist/{config-B7QJQTU2.js → config-4JC6X5QT.js} +3 -1
- package/dist/{daemon-A3WGDRFT.js → daemon-O7TOISBH.js} +6 -6
- package/dist/daemon-handle-NBGZUDKE.js +11 -0
- package/dist/server-VWBW53QU.js +12 -0
- package/dist/web/assets/{Area-CEpf7_6d.js → Area-B3FdWS80.js} +1 -1
- package/dist/web/assets/{analytics-CdjcYtmK.js → analytics-DSLBg5Lx.js} +2 -2
- package/dist/web/assets/{breakdown-DM6g0evr.js → breakdown-B1vg0Xn1.js} +1 -1
- package/dist/web/assets/{button-Cu4co7ZR.js → button-CDipMD0w.js} +1 -1
- package/dist/web/assets/{chart-YlnVH1If.js → chart-BRXIwVtE.js} +1 -1
- package/dist/web/assets/{explore-kLpZWR-_.js → explore-DyJiyG0m.js} +1 -1
- package/dist/web/assets/index-CEXpTvdh.js +83 -0
- package/dist/web/assets/{models-XKfoWTDv.js → models-_DeHctpR.js} +2 -2
- package/dist/web/assets/{overview-DC-WVKZ6.js → overview-CmPN-0w3.js} +2 -2
- package/dist/web/assets/{panel-B_U9oe1x.js → panel-DzILyYlc.js} +1 -1
- package/dist/web/assets/settings-sheet-AfoLwoCy.js +1 -0
- package/dist/web/assets/{share-sheet-BaEXe1m9.js → share-sheet-Cfl7PIrk.js} +1 -1
- package/dist/web/assets/{timeline-ar9mdhcb.js → timeline-D_JPTxAw.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/daemon-handle-4E7RSSDB.js +0 -11
- package/dist/server-P6BRXBZ5.js +0 -12
- package/dist/web/assets/index-Dd37pBKy.js +0 -83
- package/dist/web/assets/settings-sheet-B7BuJ3j8.js +0 -1
|
@@ -10,13 +10,14 @@ import {
|
|
|
10
10
|
deriveQuotaViews,
|
|
11
11
|
fetchPeak,
|
|
12
12
|
namedHex
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-U2FQXNIU.js";
|
|
14
14
|
import {
|
|
15
15
|
PROVIDERS,
|
|
16
|
-
|
|
16
|
+
detectAccountProviders,
|
|
17
|
+
detectInstalledProviders,
|
|
17
18
|
resolveTimezone,
|
|
18
19
|
withTimeout
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-EZHV4R74.js";
|
|
20
21
|
import {
|
|
21
22
|
ConfigPersistenceFailure,
|
|
22
23
|
ConfigUpdateConflictFailure,
|
|
@@ -26,137 +27,21 @@ import {
|
|
|
26
27
|
TOKMON_WS_METHODS,
|
|
27
28
|
TOKMON_WS_PATH,
|
|
28
29
|
TokmonRpcGroup
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-6RPYSOBA.js";
|
|
30
31
|
import {
|
|
31
32
|
cacheDir,
|
|
33
|
+
canonicalizeConfigHomeRefs,
|
|
32
34
|
expandHome,
|
|
33
35
|
loadConfig,
|
|
34
36
|
normalizeConfig,
|
|
35
37
|
saveConfig,
|
|
36
38
|
snapshotCacheFile
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-GQCFGAQ6.js";
|
|
38
40
|
|
|
39
41
|
// src/web/server.ts
|
|
40
42
|
import { createServer } from "http";
|
|
41
43
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
42
44
|
|
|
43
|
-
// src/web/data.ts
|
|
44
|
-
async function resolveAccounts(config) {
|
|
45
|
-
const detected = await detectProviders();
|
|
46
|
-
const accounts = buildAccounts(config, detected);
|
|
47
|
-
return accounts.map((a) => {
|
|
48
|
-
const p = PROVIDERS[a.providerId];
|
|
49
|
-
return {
|
|
50
|
-
account: a,
|
|
51
|
-
hasUsage: p.hasUsage || !!p.fetchTable,
|
|
52
|
-
hasBilling: p.hasBilling,
|
|
53
|
-
color: colorHex(a.color, PROVIDERS[a.providerId].color)
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
async function fetchAccountSummary(account, tz) {
|
|
58
|
-
const p = PROVIDERS[account.providerId];
|
|
59
|
-
if (!p.fetchSummary) return null;
|
|
60
|
-
return p.fetchSummary(account, tz);
|
|
61
|
-
}
|
|
62
|
-
async function fetchAccountTable(account, tz) {
|
|
63
|
-
const p = PROVIDERS[account.providerId];
|
|
64
|
-
if (!p.fetchTable) return null;
|
|
65
|
-
return p.fetchTable(account, tz);
|
|
66
|
-
}
|
|
67
|
-
async function fetchAccountBilling(account, tz) {
|
|
68
|
-
const p = PROVIDERS[account.providerId];
|
|
69
|
-
if (!p.fetchBilling) return null;
|
|
70
|
-
return p.fetchBilling(account, tz);
|
|
71
|
-
}
|
|
72
|
-
function assembleSnapshot(opts) {
|
|
73
|
-
const providerOrdinals = /* @__PURE__ */ new Map();
|
|
74
|
-
const accounts = opts.resolved.map((r) => {
|
|
75
|
-
const u = opts.usage.get(r.account.id);
|
|
76
|
-
const billing = opts.billing.get(r.account.id) ?? null;
|
|
77
|
-
const ordinal = (providerOrdinals.get(r.account.providerId) ?? 0) + 1;
|
|
78
|
-
providerOrdinals.set(r.account.providerId, ordinal);
|
|
79
|
-
const quotas = deriveQuotaViews(billing?.metrics ?? []);
|
|
80
|
-
return {
|
|
81
|
-
id: r.account.id,
|
|
82
|
-
providerId: r.account.providerId,
|
|
83
|
-
name: r.account.name,
|
|
84
|
-
color: r.color,
|
|
85
|
-
homeDir: r.account.homeDir ?? null,
|
|
86
|
-
hasUsage: r.hasUsage,
|
|
87
|
-
hasBilling: r.hasBilling,
|
|
88
|
-
email: billing?.email ?? null,
|
|
89
|
-
displayName: billing?.displayName ?? null,
|
|
90
|
-
identity: deriveAccountIdentity({
|
|
91
|
-
name: r.account.name,
|
|
92
|
-
email: billing?.email,
|
|
93
|
-
displayName: billing?.displayName,
|
|
94
|
-
providerName: PROVIDERS[r.account.providerId].name,
|
|
95
|
-
ordinal,
|
|
96
|
-
privacyMode: opts.config.privacyMode
|
|
97
|
-
}),
|
|
98
|
-
quotas,
|
|
99
|
-
headroom: deriveProviderHeadroom([{
|
|
100
|
-
id: r.account.id,
|
|
101
|
-
lastActivityAt: u?.dashboard?.lastActivityAt ?? null,
|
|
102
|
-
quotas
|
|
103
|
-
}], opts.config.tray.activeTimeoutMin, Date.now(), opts.config.tray.displayMetric),
|
|
104
|
-
plan: billing?.plan ?? null,
|
|
105
|
-
lastActivityAt: u?.dashboard?.lastActivityAt ?? null,
|
|
106
|
-
dashboard: u?.dashboard ?? null,
|
|
107
|
-
table: u?.table ?? null,
|
|
108
|
-
billing,
|
|
109
|
-
summaryState: opts.summaryState?.get(r.account.id) ?? "pending",
|
|
110
|
-
billingState: opts.billingState?.get(r.account.id) ?? "pending",
|
|
111
|
-
tableState: opts.tableState?.get(r.account.id) ?? "pending",
|
|
112
|
-
summaryUpdatedAt: opts.summaryUpdatedAt?.get(r.account.id) ?? null,
|
|
113
|
-
billingUpdatedAt: opts.billingUpdatedAt?.get(r.account.id) ?? null,
|
|
114
|
-
tableUpdatedAt: opts.tableUpdatedAt?.get(r.account.id) ?? null
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
const accountsByProvider = /* @__PURE__ */ new Map();
|
|
118
|
-
for (const account of accounts) {
|
|
119
|
-
const group = accountsByProvider.get(account.providerId) ?? [];
|
|
120
|
-
group.push(account);
|
|
121
|
-
accountsByProvider.set(account.providerId, group);
|
|
122
|
-
}
|
|
123
|
-
const seen = /* @__PURE__ */ new Set();
|
|
124
|
-
const providers = [];
|
|
125
|
-
for (const r of opts.resolved) {
|
|
126
|
-
if (seen.has(r.account.providerId)) continue;
|
|
127
|
-
seen.add(r.account.providerId);
|
|
128
|
-
providers.push({
|
|
129
|
-
id: r.account.providerId,
|
|
130
|
-
name: PROVIDERS[r.account.providerId].name,
|
|
131
|
-
color: namedHex(PROVIDERS[r.account.providerId].color),
|
|
132
|
-
headroom: deriveProviderHeadroom(
|
|
133
|
-
(accountsByProvider.get(r.account.providerId) ?? []).map((account) => ({
|
|
134
|
-
id: account.id,
|
|
135
|
-
lastActivityAt: account.lastActivityAt,
|
|
136
|
-
quotas: account.quotas ?? []
|
|
137
|
-
})),
|
|
138
|
-
opts.config.tray.activeTimeoutMin,
|
|
139
|
-
Date.now(),
|
|
140
|
-
opts.config.tray.displayMetric
|
|
141
|
-
)
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
version: opts.version,
|
|
146
|
-
generatedAt: Date.now(),
|
|
147
|
-
tz: opts.tz,
|
|
148
|
-
intervalMs: opts.intervalMs,
|
|
149
|
-
billingIntervalMs: opts.billingIntervalMs,
|
|
150
|
-
providers,
|
|
151
|
-
accounts,
|
|
152
|
-
seeded: opts.seeded ?? false,
|
|
153
|
-
peak: opts.peak ?? null
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
function tzFor(config) {
|
|
157
|
-
return resolveTimezone(config.timezone);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
45
|
// src/web/static.ts
|
|
161
46
|
import { createReadStream, existsSync, readFileSync } from "fs";
|
|
162
47
|
import { stat } from "fs/promises";
|
|
@@ -335,6 +220,125 @@ code{color:#e6b450}</style></head><body>
|
|
|
335
220
|
// src/web/data-engine.ts
|
|
336
221
|
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, renameSync } from "fs";
|
|
337
222
|
|
|
223
|
+
// src/web/data.ts
|
|
224
|
+
async function resolveAccounts(config) {
|
|
225
|
+
const detected = await detectAccountProviders();
|
|
226
|
+
const accounts = buildAccounts(config, detected);
|
|
227
|
+
return accounts.map((a) => {
|
|
228
|
+
const p = PROVIDERS[a.providerId];
|
|
229
|
+
return {
|
|
230
|
+
account: a,
|
|
231
|
+
hasUsage: p.hasUsage || !!p.fetchTable,
|
|
232
|
+
hasBilling: p.hasBilling,
|
|
233
|
+
color: colorHex(a.color, PROVIDERS[a.providerId].color)
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async function fetchAccountSummary(account, tz) {
|
|
238
|
+
const p = PROVIDERS[account.providerId];
|
|
239
|
+
if (!p.fetchSummary) return null;
|
|
240
|
+
return p.fetchSummary(account, tz);
|
|
241
|
+
}
|
|
242
|
+
async function fetchAccountTable(account, tz) {
|
|
243
|
+
const p = PROVIDERS[account.providerId];
|
|
244
|
+
if (!p.fetchTable) return null;
|
|
245
|
+
return p.fetchTable(account, tz);
|
|
246
|
+
}
|
|
247
|
+
async function fetchAccountBilling(account, tz) {
|
|
248
|
+
const p = PROVIDERS[account.providerId];
|
|
249
|
+
if (!p.fetchBilling) return null;
|
|
250
|
+
return p.fetchBilling(account, tz);
|
|
251
|
+
}
|
|
252
|
+
function assembleSnapshot(opts) {
|
|
253
|
+
const providerOrdinals = /* @__PURE__ */ new Map();
|
|
254
|
+
const accounts = opts.resolved.map((r) => {
|
|
255
|
+
const u = opts.usage.get(r.account.id);
|
|
256
|
+
const billing = opts.billing.get(r.account.id) ?? null;
|
|
257
|
+
const ordinal = (providerOrdinals.get(r.account.providerId) ?? 0) + 1;
|
|
258
|
+
providerOrdinals.set(r.account.providerId, ordinal);
|
|
259
|
+
const quotas = deriveQuotaViews(billing?.metrics ?? []);
|
|
260
|
+
return {
|
|
261
|
+
id: r.account.id,
|
|
262
|
+
providerId: r.account.providerId,
|
|
263
|
+
name: r.account.name,
|
|
264
|
+
color: r.color,
|
|
265
|
+
homeDir: r.account.homeDir ?? null,
|
|
266
|
+
source: r.account.source ?? "auto",
|
|
267
|
+
hasUsage: r.hasUsage,
|
|
268
|
+
hasBilling: r.hasBilling,
|
|
269
|
+
email: billing?.email ?? null,
|
|
270
|
+
displayName: billing?.displayName ?? null,
|
|
271
|
+
identity: deriveAccountIdentity({
|
|
272
|
+
name: r.account.name,
|
|
273
|
+
email: billing?.email,
|
|
274
|
+
displayName: billing?.displayName,
|
|
275
|
+
providerName: PROVIDERS[r.account.providerId].name,
|
|
276
|
+
ordinal,
|
|
277
|
+
privacyMode: opts.config.privacyMode
|
|
278
|
+
}),
|
|
279
|
+
quotas,
|
|
280
|
+
headroom: deriveProviderHeadroom([{
|
|
281
|
+
id: r.account.id,
|
|
282
|
+
lastActivityAt: u?.dashboard?.lastActivityAt ?? null,
|
|
283
|
+
quotas
|
|
284
|
+
}], opts.config.tray.activeTimeoutMin, Date.now(), opts.config.tray.displayMetric),
|
|
285
|
+
plan: billing?.plan ?? null,
|
|
286
|
+
lastActivityAt: u?.dashboard?.lastActivityAt ?? null,
|
|
287
|
+
dashboard: u?.dashboard ?? null,
|
|
288
|
+
table: u?.table ?? null,
|
|
289
|
+
billing,
|
|
290
|
+
summaryState: opts.summaryState?.get(r.account.id) ?? "pending",
|
|
291
|
+
billingState: opts.billingState?.get(r.account.id) ?? "pending",
|
|
292
|
+
tableState: opts.tableState?.get(r.account.id) ?? "pending",
|
|
293
|
+
summaryUpdatedAt: opts.summaryUpdatedAt?.get(r.account.id) ?? null,
|
|
294
|
+
billingUpdatedAt: opts.billingUpdatedAt?.get(r.account.id) ?? null,
|
|
295
|
+
tableUpdatedAt: opts.tableUpdatedAt?.get(r.account.id) ?? null
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
const accountsByProvider = /* @__PURE__ */ new Map();
|
|
299
|
+
for (const account of accounts) {
|
|
300
|
+
const group = accountsByProvider.get(account.providerId) ?? [];
|
|
301
|
+
group.push(account);
|
|
302
|
+
accountsByProvider.set(account.providerId, group);
|
|
303
|
+
}
|
|
304
|
+
const seen = /* @__PURE__ */ new Set();
|
|
305
|
+
const providers = [];
|
|
306
|
+
for (const r of opts.resolved) {
|
|
307
|
+
if (seen.has(r.account.providerId)) continue;
|
|
308
|
+
seen.add(r.account.providerId);
|
|
309
|
+
providers.push({
|
|
310
|
+
id: r.account.providerId,
|
|
311
|
+
name: PROVIDERS[r.account.providerId].name,
|
|
312
|
+
color: namedHex(PROVIDERS[r.account.providerId].color),
|
|
313
|
+
headroom: deriveProviderHeadroom(
|
|
314
|
+
(accountsByProvider.get(r.account.providerId) ?? []).map((account) => ({
|
|
315
|
+
id: account.id,
|
|
316
|
+
lastActivityAt: account.lastActivityAt,
|
|
317
|
+
quotas: account.quotas ?? []
|
|
318
|
+
})),
|
|
319
|
+
opts.config.tray.activeTimeoutMin,
|
|
320
|
+
Date.now(),
|
|
321
|
+
opts.config.tray.displayMetric
|
|
322
|
+
)
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
version: opts.version,
|
|
327
|
+
generatedAt: Date.now(),
|
|
328
|
+
tz: opts.tz,
|
|
329
|
+
intervalMs: opts.intervalMs,
|
|
330
|
+
billingIntervalMs: opts.billingIntervalMs,
|
|
331
|
+
installedProviders: opts.installedProviders,
|
|
332
|
+
providers,
|
|
333
|
+
accounts,
|
|
334
|
+
seeded: opts.seeded ?? false,
|
|
335
|
+
peak: opts.peak ?? null
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function tzFor(config) {
|
|
339
|
+
return resolveTimezone(config.timezone);
|
|
340
|
+
}
|
|
341
|
+
|
|
338
342
|
// src/web/refresh-queue.ts
|
|
339
343
|
function deferred() {
|
|
340
344
|
let resolve;
|
|
@@ -439,6 +443,7 @@ function createDataEngine(opts) {
|
|
|
439
443
|
let summaryIntervalMs = opts.summaryIntervalMs;
|
|
440
444
|
let billingIntervalMs = opts.billingIntervalMs;
|
|
441
445
|
let resolved = opts.resolved;
|
|
446
|
+
let installedProviders = opts.installedProviders ?? [];
|
|
442
447
|
let currentConfig = opts.config;
|
|
443
448
|
const usage = /* @__PURE__ */ new Map();
|
|
444
449
|
const billing = /* @__PURE__ */ new Map();
|
|
@@ -478,6 +483,7 @@ function createDataEngine(opts) {
|
|
|
478
483
|
intervalMs: summaryIntervalMs,
|
|
479
484
|
billingIntervalMs,
|
|
480
485
|
resolved,
|
|
486
|
+
installedProviders,
|
|
481
487
|
usage,
|
|
482
488
|
billing,
|
|
483
489
|
summaryState,
|
|
@@ -714,6 +720,7 @@ function createDataEngine(opts) {
|
|
|
714
720
|
const sourceKey = (r) => `${r.account.providerId}:${r.account.homeDir ?? ""}`;
|
|
715
721
|
const prevSources = new Map(resolved.map((r) => [r.account.id, sourceKey(r)]));
|
|
716
722
|
resolved = next.resolved;
|
|
723
|
+
installedProviders = next.installedProviders;
|
|
717
724
|
hasClaude = resolved.some((r) => r.account.providerId === "claude");
|
|
718
725
|
if (!hasClaude) peak = null;
|
|
719
726
|
usageAccounts = resolved.filter((r) => r.hasUsage);
|
|
@@ -820,13 +827,27 @@ var ConfigPersistenceError = class extends Error {
|
|
|
820
827
|
}
|
|
821
828
|
};
|
|
822
829
|
async function resolveEngineConfig(config) {
|
|
830
|
+
const [resolved, installedProviders] = await Promise.all([
|
|
831
|
+
resolveAccounts(config),
|
|
832
|
+
detectInstalledProviders()
|
|
833
|
+
]);
|
|
823
834
|
return {
|
|
824
|
-
resolved
|
|
835
|
+
resolved,
|
|
836
|
+
installedProviders,
|
|
825
837
|
tz: tzFor(config),
|
|
826
838
|
summaryIntervalMs: summaryIntervalFor(config),
|
|
827
839
|
billingIntervalMs: billingIntervalFor(config)
|
|
828
840
|
};
|
|
829
841
|
}
|
|
842
|
+
async function rediscoverEngineAccounts(engine, state, resolve = resolveEngineConfig) {
|
|
843
|
+
while (true) {
|
|
844
|
+
const expected = state.config;
|
|
845
|
+
const next = await resolve(expected);
|
|
846
|
+
if (state.config.revision !== expected.revision) continue;
|
|
847
|
+
engine.setConfig(next);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
830
851
|
function configAffectsEngine(previous, next) {
|
|
831
852
|
return previous.interval !== next.interval || previous.billingInterval !== next.billingInterval || previous.timezone !== next.timezone || JSON.stringify(previous.accounts) !== JSON.stringify(next.accounts) || JSON.stringify(previous.disabledProviders) !== JSON.stringify(next.disabledProviders) || JSON.stringify(previous.accountDetection) !== JSON.stringify(next.accountDetection);
|
|
832
853
|
}
|
|
@@ -838,8 +859,14 @@ function mergeCapabilityFields(incomingConfig, current) {
|
|
|
838
859
|
const incoming = incomingConfig;
|
|
839
860
|
const incomingTray = incoming.tray;
|
|
840
861
|
const incomingDesktop = incoming.desktop;
|
|
862
|
+
const currentAccounts = new Map(current.accounts.map((account) => [account.id, account]));
|
|
863
|
+
const accounts = hasOwn(incoming, "accounts") && Array.isArray(incoming.accounts) ? incoming.accounts.map((account) => {
|
|
864
|
+
const previous = currentAccounts.get(account.id);
|
|
865
|
+
return !hasOwn(account, "enabled") && previous?.enabled === false ? { ...account, enabled: false } : account;
|
|
866
|
+
}) : current.accounts;
|
|
841
867
|
return {
|
|
842
868
|
...incoming,
|
|
869
|
+
accounts,
|
|
843
870
|
appearance: hasOwn(incoming, "appearance") ? incoming.appearance : current.appearance,
|
|
844
871
|
accountDetection: hasOwn(incoming, "accountDetection") ? incoming.accountDetection : current.accountDetection,
|
|
845
872
|
tray: hasOwn(incoming, "tray") && incomingTray ? {
|
|
@@ -866,10 +893,10 @@ async function applyConfigUpdateUnlocked(engine, state, input) {
|
|
|
866
893
|
if (input.expectedRevision !== state.config.revision) {
|
|
867
894
|
throw new ConfigConflictError(toConfigState(state.config));
|
|
868
895
|
}
|
|
869
|
-
const normalized = normalizeConfig({
|
|
896
|
+
const normalized = canonicalizeConfigHomeRefs(normalizeConfig({
|
|
870
897
|
...mergeCapabilityFields(input.config, state.config),
|
|
871
898
|
revision: state.config.revision + 1
|
|
872
|
-
});
|
|
899
|
+
}));
|
|
873
900
|
const reconfigureEngine = configAffectsEngine(state.config, normalized);
|
|
874
901
|
const engineConfig = reconfigureEngine ? await resolveEngineConfig(normalized) : null;
|
|
875
902
|
try {
|
|
@@ -1065,9 +1092,12 @@ function configUpdateEffect(engine, state, input) {
|
|
|
1065
1092
|
function refreshFailureEffect(error) {
|
|
1066
1093
|
return error instanceof AggregateError ? Effect.fail(new RefreshFailure({ kind: "refresh", message: error.message })) : Effect.die(error);
|
|
1067
1094
|
}
|
|
1068
|
-
function refreshEffect(engine, scope) {
|
|
1095
|
+
function refreshEffect(engine, state, scope) {
|
|
1069
1096
|
return Effect.tryPromise({
|
|
1070
|
-
try: () =>
|
|
1097
|
+
try: async () => {
|
|
1098
|
+
if (scope === "all") await rediscoverEngineAccounts(engine, state);
|
|
1099
|
+
await engine.refresh(scope);
|
|
1100
|
+
},
|
|
1071
1101
|
catch: (error) => error
|
|
1072
1102
|
}).pipe(Effect.matchEffect({
|
|
1073
1103
|
onFailure: refreshFailureEffect,
|
|
@@ -1081,7 +1111,7 @@ async function mountWsRpc(server, deps) {
|
|
|
1081
1111
|
TokmonRpcGroup.of({
|
|
1082
1112
|
[TOKMON_WS_METHODS.getConfig]: () => Effect.promise(() => Promise.resolve(deps.state.config ?? loadConfig()).then(toConfigState)),
|
|
1083
1113
|
[TOKMON_WS_METHODS.setConfig]: (config) => configUpdateEffect(deps.engine, deps.state, config),
|
|
1084
|
-
[TOKMON_WS_METHODS.refresh]: ({ scope: scope2 }) => refreshEffect(deps.engine, scope2),
|
|
1114
|
+
[TOKMON_WS_METHODS.refresh]: ({ scope: scope2 }) => refreshEffect(deps.engine, deps.state, scope2),
|
|
1085
1115
|
[TOKMON_WS_METHODS.browseFs]: ({ path }) => Effect.promise(() => listHomeDirectory(path)),
|
|
1086
1116
|
[TOKMON_WS_METHODS.snapshot]: () => snapshotStream(deps.engine),
|
|
1087
1117
|
[TOKMON_WS_METHODS.config]: () => configStream(deps.engine).pipe(Stream.map(toConfigState))
|
|
@@ -1221,19 +1251,16 @@ function createRouter(engine, state, vite, webRoot, wsToken, version, protocolVe
|
|
|
1221
1251
|
}
|
|
1222
1252
|
async function startWebServer(opts) {
|
|
1223
1253
|
const state = { config: opts.config };
|
|
1224
|
-
const tz = tzFor(state.config);
|
|
1225
1254
|
const version = opts.version ?? appVersion();
|
|
1226
1255
|
const protocolVersion = opts.protocolVersion ?? TOKMON_PROTOCOL_VERSION;
|
|
1227
1256
|
const capabilities = opts.capabilities ?? TOKMON_CAPABILITIES;
|
|
1228
1257
|
const ownerKind = opts.ownerKind ?? "cli";
|
|
1229
1258
|
const channel = resolveDaemonChannel(opts.channel);
|
|
1230
|
-
const summaryIntervalMs = summaryIntervalFor(state.config);
|
|
1231
|
-
const billingIntervalMs = billingIntervalFor(state.config);
|
|
1232
1259
|
const wsToken = opts.wsToken ?? randomBytes(32).toString("base64url");
|
|
1233
1260
|
const log = (msg) => {
|
|
1234
1261
|
if (opts.log) process.stdout.write(msg + "\n");
|
|
1235
1262
|
};
|
|
1236
|
-
const
|
|
1263
|
+
const engineConfig = await resolveEngineConfig(state.config);
|
|
1237
1264
|
const server = createServer();
|
|
1238
1265
|
let vite = null;
|
|
1239
1266
|
let engine = null;
|
|
@@ -1242,7 +1269,7 @@ async function startWebServer(opts) {
|
|
|
1242
1269
|
if (isDevMode()) vite = await createViteDevServer(server, log);
|
|
1243
1270
|
const webRoot = vite ? null : opts.webRoot ?? findWebRoot();
|
|
1244
1271
|
if (!vite && !webRoot) log(" \u26A0 no dashboard available \u2014 see the page for build/dev instructions");
|
|
1245
|
-
engine = createDataEngine({ version, config: state.config,
|
|
1272
|
+
engine = createDataEngine({ version, config: state.config, ...engineConfig });
|
|
1246
1273
|
server.addListener("request", createRouter(
|
|
1247
1274
|
engine,
|
|
1248
1275
|
state,
|
|
@@ -5,13 +5,38 @@ import {
|
|
|
5
5
|
identityFromIdToken,
|
|
6
6
|
readClaudeIdentity,
|
|
7
7
|
readJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-EZHV4R74.js";
|
|
9
9
|
import {
|
|
10
|
-
containsEmail,
|
|
11
10
|
expandHome,
|
|
12
|
-
redactEmail,
|
|
13
11
|
slugify
|
|
14
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-GQCFGAQ6.js";
|
|
13
|
+
|
|
14
|
+
// src/peak.ts
|
|
15
|
+
async function fetchPeak() {
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch("https://promoclock.co/api/status", {
|
|
18
|
+
headers: { "Accept": "application/json", "User-Agent": "tokmon" },
|
|
19
|
+
signal: AbortSignal.timeout(3e3)
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) return null;
|
|
22
|
+
const data = await readJson(res);
|
|
23
|
+
if (!data) return null;
|
|
24
|
+
let state;
|
|
25
|
+
if (data.isPeak === true || data.status === "peak") state = "peak";
|
|
26
|
+
else if (data.isWeekend === true || data.status === "weekend") state = "weekend";
|
|
27
|
+
else if (data.isOffPeak === true || data.status === "off_peak" || data.status === "off-peak") state = "off-peak";
|
|
28
|
+
else return null;
|
|
29
|
+
const minutesUntilChange = typeof data.minutesUntilChange === "number" && Number.isFinite(data.minutesUntilChange) ? data.minutesUntilChange : null;
|
|
30
|
+
return {
|
|
31
|
+
state,
|
|
32
|
+
label: state === "peak" ? "Peak" : state === "weekend" ? "Weekend" : "Off-Peak",
|
|
33
|
+
minutesUntilChange,
|
|
34
|
+
changesAt: minutesUntilChange !== null ? new Date(Date.now() + minutesUntilChange * 6e4).toISOString() : null
|
|
35
|
+
};
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
15
40
|
|
|
16
41
|
// src/accounts.ts
|
|
17
42
|
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
@@ -41,25 +66,39 @@ function readCodexIdentity(homeDir) {
|
|
|
41
66
|
function accountKey(providerId, homeDir) {
|
|
42
67
|
return `${providerId}:${homeDir ? resolve(expandHome(homeDir)) : homedir()}`;
|
|
43
68
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
function runtimeHomeDir(homeDir) {
|
|
70
|
+
const expanded = resolve(expandHome(homeDir));
|
|
71
|
+
return expanded === resolve(homedir()) ? void 0 : expanded;
|
|
72
|
+
}
|
|
73
|
+
function stableHash(value) {
|
|
74
|
+
let hash = 2166136261;
|
|
75
|
+
for (let i = 0; i < value.length; i++) {
|
|
76
|
+
hash ^= value.charCodeAt(i);
|
|
77
|
+
hash = Math.imul(hash, 16777619);
|
|
49
78
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
79
|
+
return (hash >>> 0).toString(36);
|
|
80
|
+
}
|
|
81
|
+
function discoveredId(providerId, homeDir) {
|
|
82
|
+
const label = slugify(basename(homeDir).replace(new RegExp(`^\\.${providerId}[_-]?`), "")) || "account";
|
|
83
|
+
return `${providerId}_${label}_${stableHash(resolve(homeDir))}`;
|
|
84
|
+
}
|
|
85
|
+
function containsClaudeSession(root) {
|
|
86
|
+
if (!existsSync(root)) return false;
|
|
87
|
+
const pending = [root];
|
|
88
|
+
while (pending.length > 0) {
|
|
89
|
+
const current = pending.pop();
|
|
90
|
+
try {
|
|
91
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
92
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) return true;
|
|
93
|
+
if (entry.isDirectory()) pending.push(join2(current, entry.name));
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
55
96
|
}
|
|
56
97
|
}
|
|
57
|
-
|
|
58
|
-
used.add(id);
|
|
59
|
-
return id;
|
|
98
|
+
return false;
|
|
60
99
|
}
|
|
61
100
|
function hasClaudeState(homeDir) {
|
|
62
|
-
return existsSync(join2(homeDir, ".claude
|
|
101
|
+
return existsSync(join2(homeDir, ".claude", ".credentials.json")) || existsSync(join2(homeDir, ".config", "claude", ".credentials.json")) || containsClaudeSession(join2(homeDir, ".claude", "projects")) || containsClaudeSession(join2(homeDir, ".config", "claude", "projects"));
|
|
63
102
|
}
|
|
64
103
|
function candidateAlternateHomes(prefix) {
|
|
65
104
|
const home = homedir();
|
|
@@ -107,73 +146,93 @@ function labelForCodexHome(homeDir) {
|
|
|
107
146
|
const raw = basename(homeDir).replace(/^\.codex[_-]?/, "").replace(/[_-]+/g, " ").trim();
|
|
108
147
|
return raw ? `Codex ${raw}` : "Codex";
|
|
109
148
|
}
|
|
110
|
-
function discoverClaudeAccounts(
|
|
149
|
+
function discoverClaudeAccounts() {
|
|
111
150
|
const provider = PROVIDERS.claude;
|
|
112
151
|
const out = [];
|
|
113
152
|
for (const homeDir of candidateAlternateHomes("claude")) {
|
|
114
153
|
if (!hasClaudeState(homeDir)) continue;
|
|
115
|
-
const suffix = basename(homeDir).replace(/^\.claude[_-]?/, "") || basename(homeDir);
|
|
116
154
|
out.push({
|
|
117
|
-
id:
|
|
155
|
+
id: discoveredId("claude", homeDir),
|
|
118
156
|
providerId: "claude",
|
|
119
157
|
name: labelForClaudeHome(homeDir),
|
|
120
158
|
color: provider.color,
|
|
121
|
-
homeDir
|
|
159
|
+
homeDir,
|
|
160
|
+
source: "auto"
|
|
122
161
|
});
|
|
123
162
|
}
|
|
124
163
|
return out;
|
|
125
164
|
}
|
|
126
|
-
function discoverCodexAccounts(
|
|
165
|
+
function discoverCodexAccounts() {
|
|
127
166
|
const provider = PROVIDERS.codex;
|
|
128
167
|
const out = [];
|
|
129
168
|
for (const homeDir of candidateAlternateHomes("codex")) {
|
|
130
169
|
if (!hasCodexAuth(homeDir)) continue;
|
|
131
|
-
const suffix = basename(homeDir).replace(/^\.codex[_-]?/, "") || basename(homeDir);
|
|
132
170
|
out.push({
|
|
133
|
-
id:
|
|
171
|
+
id: discoveredId("codex", homeDir),
|
|
134
172
|
providerId: "codex",
|
|
135
173
|
name: labelForCodexHome(homeDir),
|
|
136
174
|
color: provider.color,
|
|
137
|
-
homeDir
|
|
175
|
+
homeDir,
|
|
176
|
+
source: "auto"
|
|
138
177
|
});
|
|
139
178
|
}
|
|
140
179
|
return out;
|
|
141
180
|
}
|
|
142
|
-
function discoverProviderAccounts(providerId
|
|
143
|
-
if (providerId === "claude") return discoverClaudeAccounts(
|
|
144
|
-
if (providerId === "codex") return discoverCodexAccounts(
|
|
181
|
+
function discoverProviderAccounts(providerId) {
|
|
182
|
+
if (providerId === "claude") return discoverClaudeAccounts();
|
|
183
|
+
if (providerId === "codex") return discoverCodexAccounts();
|
|
145
184
|
return [];
|
|
146
185
|
}
|
|
147
186
|
function buildAccounts(config, detected) {
|
|
148
187
|
const out = [];
|
|
149
|
-
const usedIds = new Set(config.accounts.map((a) => a.id));
|
|
150
188
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
189
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
151
190
|
const excludedKeys = new Set(
|
|
152
191
|
config.accountDetection.excludedAccounts.map((ref) => accountKey(ref.providerId, ref.homeDir))
|
|
153
192
|
);
|
|
154
193
|
const add = (account) => {
|
|
155
194
|
const key = accountKey(account.providerId, account.homeDir);
|
|
156
195
|
if (seenKeys.has(key)) return;
|
|
196
|
+
let id = account.id;
|
|
197
|
+
if (seenIds.has(id)) {
|
|
198
|
+
const base = `${id}_auto`;
|
|
199
|
+
id = base;
|
|
200
|
+
for (let suffix = 2; seenIds.has(id); suffix++) id = `${base}_${suffix}`;
|
|
201
|
+
}
|
|
157
202
|
seenKeys.add(key);
|
|
158
|
-
|
|
203
|
+
seenIds.add(id);
|
|
204
|
+
out.push(id === account.id ? account : { ...account, id });
|
|
159
205
|
};
|
|
160
206
|
for (const pid of PROVIDER_ORDER) {
|
|
161
207
|
if (config.disabledProviders.includes(pid)) continue;
|
|
162
208
|
const provider = PROVIDERS[pid];
|
|
163
209
|
const configured = config.accounts.filter((a) => a.providerId === pid);
|
|
164
210
|
for (const a of configured) {
|
|
211
|
+
if (a.enabled === false) {
|
|
212
|
+
seenKeys.add(accountKey(a.providerId, a.homeDir));
|
|
213
|
+
seenIds.add(a.id);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
165
216
|
add({
|
|
166
217
|
id: a.id,
|
|
167
218
|
providerId: pid,
|
|
168
219
|
name: a.name,
|
|
169
220
|
color: a.color || provider.color,
|
|
170
|
-
homeDir: a.homeDir
|
|
221
|
+
homeDir: runtimeHomeDir(a.homeDir || "~"),
|
|
222
|
+
source: "configured"
|
|
171
223
|
});
|
|
172
224
|
}
|
|
173
225
|
if (!config.accountDetection.enabled || config.accountDetection.disabledProviders.includes(pid)) continue;
|
|
174
|
-
const discovered = discoverProviderAccounts(pid
|
|
226
|
+
const discovered = discoverProviderAccounts(pid);
|
|
175
227
|
if (detected.includes(pid)) {
|
|
176
|
-
const account = {
|
|
228
|
+
const account = {
|
|
229
|
+
id: pid,
|
|
230
|
+
providerId: pid,
|
|
231
|
+
name: provider.name,
|
|
232
|
+
color: provider.color,
|
|
233
|
+
homeDir: void 0,
|
|
234
|
+
source: "auto"
|
|
235
|
+
};
|
|
177
236
|
if (!excludedKeys.has(accountKey(pid))) add(account);
|
|
178
237
|
}
|
|
179
238
|
for (const account of discovered) {
|
|
@@ -358,12 +417,12 @@ function deriveAccountIdentity(input) {
|
|
|
358
417
|
const registered = input.name.trim() || input.providerName;
|
|
359
418
|
const email = input.email?.trim() || null;
|
|
360
419
|
const displayName = input.displayName?.trim() || null;
|
|
361
|
-
if (input.privacyMode
|
|
420
|
+
if (input.privacyMode) {
|
|
362
421
|
const title2 = `${input.providerName} account ${input.ordinal}`;
|
|
363
422
|
return { title: title2, subtitle: null, accessibleLabel: title2, redacted: true };
|
|
364
423
|
}
|
|
365
|
-
const title =
|
|
366
|
-
const subtitleCandidate =
|
|
424
|
+
const title = registered;
|
|
425
|
+
const subtitleCandidate = email && !registered.toLowerCase().includes(email.toLowerCase()) ? email : displayName;
|
|
367
426
|
const subtitle = subtitleCandidate && subtitleCandidate !== title ? subtitleCandidate : null;
|
|
368
427
|
return { title, subtitle, accessibleLabel: subtitle ? `${title}, ${subtitle}` : title, redacted: input.privacyMode && title !== registered };
|
|
369
428
|
}
|
|
@@ -490,34 +549,8 @@ function deriveProviderHeadroom(accounts, activeTimeoutMin, now, displayMetric =
|
|
|
490
549
|
return { ...selected.view, basis, representativeAccountId: selected.account.id, activeAccountIds: activeIds, explanation: `${selected.view.explanation}; ${basis === "active" ? "active account" : "best available account"}` };
|
|
491
550
|
}
|
|
492
551
|
|
|
493
|
-
// src/peak.ts
|
|
494
|
-
async function fetchPeak() {
|
|
495
|
-
try {
|
|
496
|
-
const res = await fetch("https://promoclock.co/api/status", {
|
|
497
|
-
headers: { "Accept": "application/json", "User-Agent": "tokmon" },
|
|
498
|
-
signal: AbortSignal.timeout(3e3)
|
|
499
|
-
});
|
|
500
|
-
if (!res.ok) return null;
|
|
501
|
-
const data = await readJson(res);
|
|
502
|
-
if (!data) return null;
|
|
503
|
-
let state;
|
|
504
|
-
if (data.isPeak === true || data.status === "peak") state = "peak";
|
|
505
|
-
else if (data.isWeekend === true || data.status === "weekend") state = "weekend";
|
|
506
|
-
else if (data.isOffPeak === true || data.status === "off_peak" || data.status === "off-peak") state = "off-peak";
|
|
507
|
-
else return null;
|
|
508
|
-
const minutesUntilChange = typeof data.minutesUntilChange === "number" && Number.isFinite(data.minutesUntilChange) ? data.minutesUntilChange : null;
|
|
509
|
-
return {
|
|
510
|
-
state,
|
|
511
|
-
label: state === "peak" ? "Peak" : state === "weekend" ? "Weekend" : "Off-Peak",
|
|
512
|
-
minutesUntilChange,
|
|
513
|
-
changesAt: minutesUntilChange !== null ? new Date(Date.now() + minutesUntilChange * 6e4).toISOString() : null
|
|
514
|
-
};
|
|
515
|
-
} catch {
|
|
516
|
-
return null;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
552
|
export {
|
|
553
|
+
fetchPeak,
|
|
521
554
|
buildAccounts,
|
|
522
555
|
accountsByProvider,
|
|
523
556
|
namedHex,
|
|
@@ -529,6 +562,5 @@ export {
|
|
|
529
562
|
severity,
|
|
530
563
|
deriveAccountIdentity,
|
|
531
564
|
accountIdentityText,
|
|
532
|
-
deriveProviderHeadroom
|
|
533
|
-
fetchPeak
|
|
565
|
+
deriveProviderHeadroom
|
|
534
566
|
};
|