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,447 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ConfigUpdateConflictFailure,
4
+ TOKMON_WS_METHODS,
5
+ TOKMON_WS_PATH,
6
+ TokmonRpcGroup
7
+ } from "./chunk-JZSZEHVD.js";
8
+ import {
9
+ normalizeConfig
10
+ } from "./chunk-HNWEJ6MS.js";
11
+
12
+ // src/client/daemon-rpc-client.ts
13
+ import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Schema, Stream } from "effect";
14
+ import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
15
+ import { RpcClientError } from "effect/unstable/rpc/RpcClientError";
16
+ import * as Socket from "effect/unstable/socket/Socket";
17
+ var DaemonRpcConnectionError = class extends Error {
18
+ constructor(message = "daemon RPC connection closed") {
19
+ super(message);
20
+ this.name = "DaemonRpcConnectionError";
21
+ }
22
+ };
23
+ var DaemonRpcRequestTimeoutError = class extends Error {
24
+ constructor(method, timeoutMs) {
25
+ super(`${method} timed out after ${timeoutMs}ms`);
26
+ this.method = method;
27
+ this.timeoutMs = timeoutMs;
28
+ this.name = "DaemonRpcRequestTimeoutError";
29
+ }
30
+ method;
31
+ timeoutMs;
32
+ };
33
+ var TokmonRpcClient = class extends Context.Service()(
34
+ "tokmon/client/DaemonRpcClient/TokmonRpcClient"
35
+ ) {
36
+ };
37
+ var dynamicImport = new Function("specifier", "return import(specifier)");
38
+ function makeSignal() {
39
+ let settled = false;
40
+ let resolvePromise;
41
+ const promise = new Promise((resolve) => {
42
+ resolvePromise = resolve;
43
+ });
44
+ return {
45
+ promise,
46
+ resolve: (value) => {
47
+ if (settled) return;
48
+ settled = true;
49
+ resolvePromise(value);
50
+ }
51
+ };
52
+ }
53
+ function toWsUrl(baseUrl) {
54
+ const base = typeof window === "undefined" ? void 0 : window.location.origin;
55
+ const url = new URL(baseUrl, base);
56
+ if (url.protocol === "http:") url.protocol = "ws:";
57
+ else if (url.protocol === "https:") url.protocol = "wss:";
58
+ if (url.protocol !== "ws:" && url.protocol !== "wss:") {
59
+ throw new Error(`unsupported daemon RPC protocol: ${url.protocol}`);
60
+ }
61
+ url.hash = "";
62
+ url.searchParams.delete("tokmonToken");
63
+ url.searchParams.delete("wsToken");
64
+ url.pathname = TOKMON_WS_PATH;
65
+ return url.toString();
66
+ }
67
+ function shouldUseNodeTransport(transport) {
68
+ if (transport === "node") return true;
69
+ if (transport === "browser") return false;
70
+ return typeof window === "undefined";
71
+ }
72
+ async function socketLayerFor(url, transport, openTimeoutMs) {
73
+ const socketOptions = { openTimeout: Duration.millis(openTimeoutMs) };
74
+ if (shouldUseNodeTransport(transport)) {
75
+ const NodeSocket = await dynamicImport("@effect/platform-node/NodeSocket");
76
+ return NodeSocket.layerWebSocket(url, socketOptions);
77
+ }
78
+ return Socket.layerWebSocket(url, socketOptions).pipe(
79
+ Layer.provide(Socket.layerWebSocketConstructorGlobal)
80
+ );
81
+ }
82
+ function normalizeConfigState(state) {
83
+ return {
84
+ protocol: {
85
+ version: state.protocol.version,
86
+ capabilities: [...state.protocol.capabilities]
87
+ },
88
+ config: normalizeConfig(state.config)
89
+ };
90
+ }
91
+ function normalizeSnapshot(snapshot) {
92
+ const copyHeadroom = (headroom) => headroom ? {
93
+ ...headroom,
94
+ activeAccountIds: [...headroom.activeAccountIds],
95
+ factors: headroom.factors.map((factor) => ({ ...factor }))
96
+ } : void 0;
97
+ const copyRows = (rows) => rows.map((row) => ({
98
+ ...row,
99
+ models: [...row.models],
100
+ breakdown: row.breakdown.map((detail) => ({ ...detail }))
101
+ }));
102
+ return {
103
+ ...snapshot,
104
+ providers: snapshot.providers.map((provider) => ({
105
+ ...provider,
106
+ headroom: copyHeadroom(provider.headroom)
107
+ })),
108
+ accounts: snapshot.accounts.map((account) => ({
109
+ ...account,
110
+ identity: account.identity ? { ...account.identity } : void 0,
111
+ quotas: account.quotas?.map((quota) => ({ ...quota })),
112
+ headroom: copyHeadroom(account.headroom),
113
+ dashboard: account.dashboard ? {
114
+ ...account.dashboard,
115
+ today: { ...account.dashboard.today },
116
+ week: { ...account.dashboard.week },
117
+ month: { ...account.dashboard.month },
118
+ series: [...account.dashboard.series]
119
+ } : null,
120
+ table: account.table ? {
121
+ daily: copyRows(account.table.daily),
122
+ weekly: copyRows(account.table.weekly),
123
+ monthly: copyRows(account.table.monthly)
124
+ } : null,
125
+ billing: account.billing ? {
126
+ ...account.billing,
127
+ metrics: account.billing.metrics.map((metric) => ({
128
+ ...metric,
129
+ format: { ...metric.format }
130
+ })),
131
+ activity: account.billing.activity ? { ...account.billing.activity, series: [...account.billing.activity.series] } : account.billing.activity,
132
+ modelSpend: account.billing.modelSpend == null ? account.billing.modelSpend : account.billing.modelSpend.map((spend) => ({ ...spend }))
133
+ } : null,
134
+ summaryUpdatedAt: account.summaryUpdatedAt ?? null,
135
+ billingUpdatedAt: account.billingUpdatedAt ?? null,
136
+ tableUpdatedAt: account.tableUpdatedAt ?? null
137
+ })),
138
+ peak: snapshot.peak ? { ...snapshot.peak } : null
139
+ };
140
+ }
141
+ function normalizeRequestFailure(error) {
142
+ if (!error || typeof error !== "object" || error.kind !== "conflict") return error;
143
+ const state = error.state;
144
+ if (!state) return error;
145
+ const normalized = normalizeConfigState(state);
146
+ if (error instanceof ConfigUpdateConflictFailure) {
147
+ return new ConfigUpdateConflictFailure({ kind: "conflict", state: normalized });
148
+ }
149
+ return { ...error, state: normalized };
150
+ }
151
+ function isSessionFailure(error) {
152
+ return error instanceof RpcClientError || Schema.isSchemaError(error);
153
+ }
154
+ async function waitForReady(ready, disconnected, timeoutMs) {
155
+ let timer = null;
156
+ try {
157
+ await Promise.race([
158
+ ready.promise,
159
+ disconnected.promise.then((error) => Promise.reject(error)),
160
+ new Promise((_resolve, reject) => {
161
+ timer = setTimeout(() => reject(new DaemonRpcConnectionError(`daemon RPC connection timed out after ${timeoutMs}ms`)), timeoutMs);
162
+ timer.unref?.();
163
+ })
164
+ ]);
165
+ } finally {
166
+ if (timer) clearTimeout(timer);
167
+ }
168
+ }
169
+ function createDaemonRpcClient(baseUrl, options = {}) {
170
+ const url = toWsUrl(baseUrl);
171
+ const requestTimeoutMs = Math.max(1, options.requestTimeoutMs ?? 3e4);
172
+ const reconnectBaseDelayMs = Math.max(1, options.reconnectBaseDelayMs ?? 250);
173
+ const subscriptions = /* @__PURE__ */ new Set();
174
+ let session = null;
175
+ let sessionPromise = null;
176
+ let pendingRuntime = null;
177
+ let supervisorPromise = null;
178
+ let reconnectTimer = null;
179
+ let reconnectAttemptsUsed = 0;
180
+ let hasConnected = false;
181
+ let closed = false;
182
+ const reportSubscriberError = (error) => {
183
+ if (options.onSubscriberError) {
184
+ try {
185
+ options.onSubscriberError(error);
186
+ } catch (reportError) {
187
+ console.error("[tokmon] daemon RPC subscriber error reporter failed", reportError);
188
+ }
189
+ return;
190
+ }
191
+ console.error("[tokmon] daemon RPC subscriber failed", error);
192
+ };
193
+ const setConn = (state, error) => {
194
+ try {
195
+ options.onConn?.(state, error);
196
+ } catch (callbackError) {
197
+ reportSubscriberError(callbackError);
198
+ }
199
+ };
200
+ const clearReconnectTimer = () => {
201
+ if (!reconnectTimer) return;
202
+ clearTimeout(reconnectTimer);
203
+ reconnectTimer = null;
204
+ };
205
+ const stopSubscriptionFiber = (subscription) => {
206
+ const fiber = subscription.fiber;
207
+ subscription.fiber = null;
208
+ subscription.owner = null;
209
+ return fiber ? Effect.runPromise(Fiber.interrupt(fiber)).catch(() => {
210
+ }) : Promise.resolve();
211
+ };
212
+ const disposeSubscription = (subscription) => {
213
+ subscription.active = false;
214
+ subscriptions.delete(subscription);
215
+ if (subscription.watchdogTimer) {
216
+ clearInterval(subscription.watchdogTimer);
217
+ subscription.watchdogTimer = null;
218
+ }
219
+ return stopSubscriptionFiber(subscription);
220
+ };
221
+ const scheduleReconnect = () => {
222
+ if (closed || subscriptions.size === 0 || reconnectTimer || session || sessionPromise) return;
223
+ if (typeof options.reconnectAttempts === "number" && reconnectAttemptsUsed >= options.reconnectAttempts) return;
224
+ const delayMs = Math.min(2500, reconnectBaseDelayMs * 1.5 ** reconnectAttemptsUsed);
225
+ reconnectTimer = setTimeout(() => {
226
+ reconnectTimer = null;
227
+ reconnectAttemptsUsed++;
228
+ connectSubscriptions();
229
+ }, delayMs);
230
+ reconnectTimer.unref?.();
231
+ };
232
+ const invalidateSession = (active, state, error) => {
233
+ if (active.invalidated) return;
234
+ active.invalidated = true;
235
+ if (session === active) session = null;
236
+ void active.runtime.dispose().catch(() => {
237
+ });
238
+ if (!closed) setConn(state, error);
239
+ scheduleReconnect();
240
+ };
241
+ const makeProtocolLayer = async (ready, disconnected) => {
242
+ const socketLayer = await socketLayerFor(url, options.transport, requestTimeoutMs);
243
+ const connectionHooksLayer = Layer.succeed(
244
+ RpcClient.ConnectionHooks,
245
+ RpcClient.ConnectionHooks.of({
246
+ onConnect: Effect.sync(() => {
247
+ ready.resolve();
248
+ }),
249
+ onDisconnect: Effect.sync(() => {
250
+ disconnected.resolve(new DaemonRpcConnectionError());
251
+ })
252
+ })
253
+ );
254
+ return Layer.effect(
255
+ RpcClient.Protocol,
256
+ RpcClient.makeProtocolSocket({
257
+ retryPolicy: Schedule.recurs(0),
258
+ retryTransientErrors: false
259
+ })
260
+ ).pipe(
261
+ Layer.provide(Layer.mergeAll(socketLayer, RpcSerialization.layerJson, connectionHooksLayer))
262
+ );
263
+ };
264
+ const ensureSession = async () => {
265
+ if (closed) throw new Error("daemon RPC client is closed");
266
+ if (session) return session;
267
+ if (sessionPromise) return sessionPromise;
268
+ setConn(hasConnected ? "reconnecting" : "connecting");
269
+ const pending = (async () => {
270
+ let runtime = null;
271
+ try {
272
+ const ready = makeSignal();
273
+ const disconnected = makeSignal();
274
+ const protocolLayer = await makeProtocolLayer(ready, disconnected);
275
+ const clientLayer = Layer.effect(
276
+ TokmonRpcClient,
277
+ RpcClient.make(TokmonRpcGroup)
278
+ ).pipe(Layer.provide(protocolLayer));
279
+ runtime = ManagedRuntime.make(clientLayer);
280
+ pendingRuntime = runtime;
281
+ await runtime.runPromise(TokmonRpcClient.asEffect());
282
+ await waitForReady(ready, disconnected, requestTimeoutMs);
283
+ if (closed) throw new Error("daemon RPC client is closed");
284
+ const active = {
285
+ runtime,
286
+ disconnected: disconnected.promise,
287
+ invalidated: false
288
+ };
289
+ session = active;
290
+ pendingRuntime = null;
291
+ hasConnected = true;
292
+ reconnectAttemptsUsed = 0;
293
+ setConn("live");
294
+ void active.disconnected.then((error) => {
295
+ invalidateSession(active, "reconnecting", error);
296
+ });
297
+ startAllSubscriptions(active);
298
+ return active;
299
+ } catch (error) {
300
+ if (pendingRuntime === runtime) pendingRuntime = null;
301
+ await runtime?.dispose().catch(() => {
302
+ });
303
+ if (!closed) setConn("error", error);
304
+ throw error;
305
+ }
306
+ })();
307
+ sessionPromise = pending;
308
+ try {
309
+ return await pending;
310
+ } finally {
311
+ if (sessionPromise === pending) sessionPromise = null;
312
+ }
313
+ };
314
+ const run = async (method, effectFor) => {
315
+ const active = await ensureSession();
316
+ try {
317
+ return await active.runtime.runPromise(
318
+ TokmonRpcClient.use((client) => effectFor(client)).pipe(
319
+ Effect.timeoutOrElse({
320
+ duration: Duration.millis(requestTimeoutMs),
321
+ orElse: () => Effect.fail(new DaemonRpcRequestTimeoutError(method, requestTimeoutMs))
322
+ })
323
+ )
324
+ );
325
+ } catch (rawError) {
326
+ const error = normalizeRequestFailure(rawError);
327
+ if (!closed && isSessionFailure(error)) invalidateSession(active, "error", error);
328
+ throw error;
329
+ }
330
+ };
331
+ const startSubscription = (subscription, active) => {
332
+ if (closed || !subscription.active || subscription.fiber || active.invalidated) return;
333
+ subscription.lastValueAt = Date.now();
334
+ subscription.staleAfterMs = options.snapshotStaleFloorMs ?? 9e4;
335
+ subscription.owner = active;
336
+ const fiber = active.runtime.runFork(
337
+ TokmonRpcClient.use(
338
+ (client) => subscription.streamFor(client).pipe(
339
+ Stream.runForEach(
340
+ (value) => Effect.sync(() => {
341
+ subscription.lastValueAt = Date.now();
342
+ if (subscription.staleAfterFor) subscription.staleAfterMs = subscription.staleAfterFor(value);
343
+ try {
344
+ subscription.onValue(value);
345
+ } catch (error) {
346
+ reportSubscriberError(error);
347
+ }
348
+ })
349
+ )
350
+ )
351
+ )
352
+ );
353
+ subscription.fiber = fiber;
354
+ fiber.addObserver((exit) => {
355
+ if (subscription.fiber === fiber) subscription.fiber = null;
356
+ if (subscription.owner === active) subscription.owner = null;
357
+ if (closed || !subscription.active || active.invalidated) return;
358
+ if (Exit.isSuccess(exit)) {
359
+ invalidateSession(active, "reconnecting");
360
+ } else {
361
+ invalidateSession(active, "error", Cause.squash(exit.cause));
362
+ }
363
+ });
364
+ };
365
+ const startAllSubscriptions = (active) => {
366
+ for (const subscription of subscriptions) startSubscription(subscription, active);
367
+ };
368
+ function connectSubscriptions() {
369
+ if (closed || subscriptions.size === 0 || supervisorPromise) return;
370
+ supervisorPromise = ensureSession().then(() => void 0).catch(() => {
371
+ scheduleReconnect();
372
+ }).finally(() => {
373
+ supervisorPromise = null;
374
+ });
375
+ }
376
+ const subscribe = (streamFor, onValue, staleAfterFor) => {
377
+ if (closed) return () => {
378
+ };
379
+ const subscription = {
380
+ streamFor,
381
+ onValue,
382
+ staleAfterFor,
383
+ active: true,
384
+ fiber: null,
385
+ owner: null,
386
+ watchdogTimer: null,
387
+ lastValueAt: 0,
388
+ staleAfterMs: Number.POSITIVE_INFINITY
389
+ };
390
+ subscriptions.add(subscription);
391
+ if (staleAfterFor) {
392
+ const checkEveryMs = Math.min(5e3, Math.max(10, (options.snapshotStaleFloorMs ?? 9e4) / 2));
393
+ subscription.watchdogTimer = setInterval(() => {
394
+ if (!subscription.fiber || !subscription.owner || subscription.lastValueAt === 0) return;
395
+ if (Date.now() - subscription.lastValueAt <= subscription.staleAfterMs) return;
396
+ subscription.lastValueAt = Date.now();
397
+ invalidateSession(subscription.owner, "reconnecting");
398
+ }, checkEveryMs);
399
+ subscription.watchdogTimer.unref?.();
400
+ }
401
+ if (session) startSubscription(subscription, session);
402
+ else connectSubscriptions();
403
+ return () => {
404
+ void disposeSubscription(subscription);
405
+ if (subscriptions.size === 0) clearReconnectTimer();
406
+ };
407
+ };
408
+ return {
409
+ getConfig: () => run(TOKMON_WS_METHODS.getConfig, (client) => client[TOKMON_WS_METHODS.getConfig]({})).then(normalizeConfigState),
410
+ setConfig: (update) => run(TOKMON_WS_METHODS.setConfig, (client) => client[TOKMON_WS_METHODS.setConfig](update)).then(normalizeConfigState),
411
+ refresh: (scope = "all") => run(TOKMON_WS_METHODS.refresh, (client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
412
+ browseFs: (path) => run(TOKMON_WS_METHODS.browseFs, (client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
413
+ subscribeSnapshot: (onSnapshot) => subscribe(
414
+ (client) => client[TOKMON_WS_METHODS.snapshot]({}).pipe(Stream.map(normalizeSnapshot)),
415
+ onSnapshot,
416
+ (snapshot) => Math.max(options.snapshotStaleFloorMs ?? 9e4, snapshot.intervalMs * 3)
417
+ ),
418
+ subscribeConfig: (onConfig) => subscribe(
419
+ (client) => client[TOKMON_WS_METHODS.config]({}).pipe(Stream.map(normalizeConfigState)),
420
+ onConfig
421
+ ),
422
+ async close() {
423
+ if (closed) return;
424
+ closed = true;
425
+ setConn("closed");
426
+ clearReconnectTimer();
427
+ const cleanup = [...subscriptions].map(disposeSubscription);
428
+ subscriptions.clear();
429
+ const activeSession = session;
430
+ session = null;
431
+ await pendingRuntime?.dispose().catch(() => {
432
+ });
433
+ await Promise.all(cleanup);
434
+ await activeSession?.runtime.dispose().catch(() => {
435
+ });
436
+ await sessionPromise?.catch(() => {
437
+ });
438
+ pendingRuntime = null;
439
+ sessionPromise = null;
440
+ supervisorPromise = null;
441
+ }
442
+ };
443
+ }
444
+
445
+ export {
446
+ createDaemonRpcClient
447
+ };
@@ -5,7 +5,7 @@ import {
5
5
  envDir,
6
6
  expandHome,
7
7
  isValidTimezone
8
- } from "./chunk-HP5UZCXP.js";
8
+ } from "./chunk-HNWEJ6MS.js";
9
9
 
10
10
  // src/async.ts
11
11
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -20,13 +20,6 @@ function withTimeout(promise, ms = DEFAULT_TIMEOUT_MS) {
20
20
  });
21
21
  }
22
22
 
23
- // src/providers/usage-core.ts
24
- import { readFileSync, readdirSync } from "fs";
25
- import { readdir as readdir2 } from "fs/promises";
26
- import { dirname, join as join2, relative } from "path";
27
- import { createHash as createHash2 } from "crypto";
28
- import { fileURLToPath } from "url";
29
-
30
23
  // src/tz.ts
31
24
  function systemTimezone() {
32
25
  try {
@@ -120,14 +113,17 @@ function startOfWeek(ts, tz) {
120
113
  const offset = p.weekday === 0 ? 6 : p.weekday - 1;
121
114
  return instantFromTz(p.y, p.m, p.d - offset, 0, 0, 0, tz);
122
115
  }
123
- function monthsAgoStart(ts, months, tz) {
124
- const p = tzParts(ts, tz);
125
- return instantFromTz(p.y, p.m - months, 1, 0, 0, 0, tz);
126
- }
127
116
  function weekKey(ts, tz) {
128
117
  return dayKey(startOfWeek(ts, tz), tz);
129
118
  }
130
119
 
120
+ // src/providers/usage-core.ts
121
+ import { readFileSync, readdirSync } from "fs";
122
+ import { readdir as readdir2 } from "fs/promises";
123
+ import { dirname, join as join2, relative } from "path";
124
+ import { createHash as createHash2 } from "crypto";
125
+ import { fileURLToPath } from "url";
126
+
131
127
  // src/providers/_shared/metric.ts
132
128
  var finite = (value, fallback = 0) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
133
129
  var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value);
@@ -395,7 +391,7 @@ async function mapLimit(items, limit, fn) {
395
391
  }
396
392
 
397
393
  // src/providers/usage-core.ts
398
- var SPARK_DAYS = 14;
394
+ var SPARK_DAYS = 30;
399
395
  var DAY_MS = 864e5;
400
396
  var PRUNE_AGE_MS = 230 * DAY_MS;
401
397
  var stores = /* @__PURE__ */ new Map();
@@ -560,8 +556,8 @@ function dashboardSince(tz) {
560
556
  const now = Date.now();
561
557
  return Math.min(startOfMonth(now, tz), startOfWeek(now, tz), now - SPARK_DAYS * DAY_MS);
562
558
  }
563
- function tableSince(tz) {
564
- return monthsAgoStart(Date.now(), 6, tz);
559
+ function tableSince(_tz) {
560
+ return 0;
565
561
  }
566
562
  function cleanEntry(e) {
567
563
  return {
@@ -596,6 +592,7 @@ function summarize(entries, tz) {
596
592
  const byDay = /* @__PURE__ */ new Map();
597
593
  let oldestToday = now;
598
594
  let hadToday = false;
595
+ let lastActivityAt = null;
599
596
  const add = (s, e) => {
600
597
  s.cost += e.cost;
601
598
  s.tokens += e.input + e.output + e.cacheCreate + e.cacheRead;
@@ -605,6 +602,9 @@ function summarize(entries, tz) {
605
602
  };
606
603
  for (const raw of entries) {
607
604
  const e = cleanEntry(raw);
605
+ if (e.ts > 0 && (lastActivityAt === null || e.ts > lastActivityAt)) {
606
+ lastActivityAt = Math.floor(e.ts);
607
+ }
608
608
  if (e.ts >= monthStart) add(month, e);
609
609
  if (e.ts >= weekStart) add(week, e);
610
610
  if (e.ts >= todayStart) {
@@ -619,7 +619,7 @@ function summarize(entries, tz) {
619
619
  const rawBurnRate = hadToday ? today.cost / hrs : 0;
620
620
  const burnRate = Number.isFinite(rawBurnRate) ? rawBurnRate : 0;
621
621
  const series = lastDayKeys(now, tz, SPARK_DAYS).map((k) => byDay.get(k) ?? 0);
622
- return { today, week, month, burnRate, series };
622
+ return { today, week, month, burnRate, series, lastActivityAt };
623
623
  }
624
624
  function groupBy(entries, keyFn) {
625
625
  const groups = /* @__PURE__ */ new Map();
@@ -747,7 +747,10 @@ function formatCurrency(value, opts = {}) {
747
747
  const abs = Math.abs(value);
748
748
  if (abs >= 1e5) return `${sign}$${(value / 1e3).toFixed(0)}k`;
749
749
  if (abs >= 1e4) return `${sign}$${(value / 1e3).toFixed(1)}k`;
750
- if (abs >= 1) return `${sign}$${value.toFixed(2)}`;
750
+ if (abs >= 1) {
751
+ const polarity = value < 0 ? "-" : "";
752
+ return `${sign}${polarity}$${abs.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
753
+ }
751
754
  if (abs >= 0.01) return `${sign}$${value.toFixed(3)}`;
752
755
  if (abs === 0) return "$0.00";
753
756
  return `${sign}$${value.toFixed(4)}`;
@@ -784,17 +787,22 @@ function formatShortDate(label, opts = {}) {
784
787
  const day = Number(match[3]);
785
788
  return `${MONTHS[Number(match[2]) - 1]} ${opts.padDay ? day.toString().padStart(2, " ") : day}`;
786
789
  }
790
+ function resetParts(diffMs) {
791
+ if (!Number.isFinite(diffMs) || diffMs <= 0) return null;
792
+ const totalMinutes = Math.max(1, Math.ceil(diffMs / 6e4));
793
+ return {
794
+ days: Math.floor(totalMinutes / (24 * 60)),
795
+ hours: Math.floor(totalMinutes % (24 * 60) / 60),
796
+ minutes: totalMinutes % 60,
797
+ totalMinutes
798
+ };
799
+ }
787
800
  function formatResetIn(iso, now = Date.now()) {
788
- const diff = new Date(iso).getTime() - now;
789
- if (!Number.isFinite(diff) || diff <= 0) return "now";
790
- const minutes = Math.round(diff / 6e4);
791
- if (minutes < 60) return `${minutes}m`;
792
- const hours = Math.floor(minutes / 60);
793
- const mins = minutes % 60;
794
- if (hours < 24) return `${hours}h ${mins}m`;
795
- const days = Math.floor(hours / 24);
796
- const hrs = hours % 24;
797
- return `${days}d ${hrs}h`;
801
+ const parts = resetParts(new Date(iso).getTime() - now);
802
+ if (!parts) return "now";
803
+ if (parts.days > 0) return `${parts.days}d ${parts.hours}h`;
804
+ if (parts.hours > 0) return `${parts.hours}h ${parts.minutes}m`;
805
+ return `${parts.minutes}m`;
798
806
  }
799
807
  function formatResetAt(iso, display, now = Date.now(), tz) {
800
808
  const timestamp = Date.parse(iso);
@@ -1269,7 +1277,19 @@ function limitMetric(entry, primary) {
1269
1277
  if (used === void 0) return null;
1270
1278
  const label = limitLabel(o);
1271
1279
  if (!label) return null;
1272
- return percentMetric(label, used, resetFrom(o.resets_at), primary);
1280
+ const scope = recordValue(o.scope);
1281
+ const model = recordValue(scope?.model);
1282
+ const modelId = nonEmptyString(model?.id);
1283
+ const kind = nonEmptyString(o.kind)?.toLowerCase() ?? "";
1284
+ const scopedModel = kind === "weekly_scoped" || kind.startsWith("weekly_model");
1285
+ const role = kind === "session" || nonEmptyString(o.group)?.toLowerCase() === "session" ? "session" : kind === "weekly_all" ? "weekly" : modelId || scopedModel ? "model" : "other";
1286
+ return {
1287
+ ...percentMetric(label, used, resetFrom(o.resets_at), primary),
1288
+ key: modelId ? `model:${modelId}` : kind || label.toLowerCase(),
1289
+ role,
1290
+ modelId: modelId ?? (scopedModel ? label.toLowerCase().replace(/\s+/g, "-") : null),
1291
+ active: boolValue(o.is_active)
1292
+ };
1273
1293
  }
1274
1294
  function limitMetrics(limits) {
1275
1295
  if (!Array.isArray(limits)) return [];
@@ -1296,7 +1316,12 @@ function topLevelUsageMetrics(data) {
1296
1316
  const window = recordValue(value);
1297
1317
  if (!window || numberValue(window.utilization) === void 0) continue;
1298
1318
  const metric = usageMetric(usageLabelFromKey(key), window, key === "five_hour" ? true : void 0);
1299
- if (metric) metrics.push(metric);
1319
+ if (metric) metrics.push({
1320
+ ...metric,
1321
+ key,
1322
+ role: key === "five_hour" ? "session" : key === "seven_day" ? "weekly" : key.startsWith("seven_day_") ? "model" : "other",
1323
+ modelId: key.startsWith("seven_day_") ? key.slice("seven_day_".length) : null
1324
+ });
1300
1325
  }
1301
1326
  return metrics;
1302
1327
  }
@@ -1339,6 +1364,8 @@ async function claudeBilling(account) {
1339
1364
  if (usedCredits !== void 0 && (usedCredits > 0 || monthlyLimit !== void 0 && monthlyLimit > 0)) {
1340
1365
  const scale = decimalScale(data.extra_usage?.decimal_places);
1341
1366
  metrics.push({
1367
+ key: "extra_usage",
1368
+ role: "unbounded",
1342
1369
  label: "Extra",
1343
1370
  used: finite(usedCredits) / scale,
1344
1371
  limit: monthlyLimit !== void 0 && monthlyLimit > 0 ? monthlyLimit / scale : null,