token-usage-dashboard 1.0.6

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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/bin/ai-usage-dashboard.js +92 -0
  4. package/build/client/_app/immutable/assets/0.fjR0gTLF.css +1 -0
  5. package/build/client/_app/immutable/assets/0.fjR0gTLF.css.br +0 -0
  6. package/build/client/_app/immutable/assets/0.fjR0gTLF.css.gz +0 -0
  7. package/build/client/_app/immutable/assets/2.BABRsmGA.css +1 -0
  8. package/build/client/_app/immutable/assets/2.BABRsmGA.css.br +0 -0
  9. package/build/client/_app/immutable/assets/2.BABRsmGA.css.gz +0 -0
  10. package/build/client/_app/immutable/chunks/Bjy-W4x2.js +81 -0
  11. package/build/client/_app/immutable/chunks/Bjy-W4x2.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/Bjy-W4x2.js.gz +0 -0
  13. package/build/client/_app/immutable/chunks/DC6tN2WI.js +3 -0
  14. package/build/client/_app/immutable/chunks/DC6tN2WI.js.br +0 -0
  15. package/build/client/_app/immutable/chunks/DC6tN2WI.js.gz +0 -0
  16. package/build/client/_app/immutable/chunks/DRLSLo2t.js +2 -0
  17. package/build/client/_app/immutable/chunks/DRLSLo2t.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/DRLSLo2t.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/xihTtKlq.js +1 -0
  20. package/build/client/_app/immutable/chunks/xihTtKlq.js.br +1 -0
  21. package/build/client/_app/immutable/chunks/xihTtKlq.js.gz +0 -0
  22. package/build/client/_app/immutable/entry/app.C7Gg5PnI.js +2 -0
  23. package/build/client/_app/immutable/entry/app.C7Gg5PnI.js.br +0 -0
  24. package/build/client/_app/immutable/entry/app.C7Gg5PnI.js.gz +0 -0
  25. package/build/client/_app/immutable/entry/start.fbJ-_JRS.js +1 -0
  26. package/build/client/_app/immutable/entry/start.fbJ-_JRS.js.br +0 -0
  27. package/build/client/_app/immutable/entry/start.fbJ-_JRS.js.gz +0 -0
  28. package/build/client/_app/immutable/nodes/0.BNwlucmP.js +1 -0
  29. package/build/client/_app/immutable/nodes/0.BNwlucmP.js.br +0 -0
  30. package/build/client/_app/immutable/nodes/0.BNwlucmP.js.gz +0 -0
  31. package/build/client/_app/immutable/nodes/1.Dj3Fzim2.js +1 -0
  32. package/build/client/_app/immutable/nodes/1.Dj3Fzim2.js.br +0 -0
  33. package/build/client/_app/immutable/nodes/1.Dj3Fzim2.js.gz +0 -0
  34. package/build/client/_app/immutable/nodes/2.Csi9nKxw.js +2 -0
  35. package/build/client/_app/immutable/nodes/2.Csi9nKxw.js.br +0 -0
  36. package/build/client/_app/immutable/nodes/2.Csi9nKxw.js.gz +0 -0
  37. package/build/client/_app/version.json +1 -0
  38. package/build/client/_app/version.json.br +0 -0
  39. package/build/client/_app/version.json.gz +0 -0
  40. package/build/client/favicon.svg +1 -0
  41. package/build/client/favicon.svg.br +0 -0
  42. package/build/client/favicon.svg.gz +0 -0
  43. package/build/client/robots.txt +2 -0
  44. package/build/client/robots.txt.br +3 -0
  45. package/build/client/robots.txt.gz +0 -0
  46. package/build/env.js +70 -0
  47. package/build/handler.js +22 -0
  48. package/build/index.js +145 -0
  49. package/build/server/chunks/chunks/client.js-C1_n_a2s.js +28 -0
  50. package/build/server/chunks/chunks/env.js-l0sNRNKZ.js +2 -0
  51. package/build/server/chunks/chunks/exports.js-Dd48p7Ih.js +263 -0
  52. package/build/server/chunks/chunks/internal.js-b-1wrnfA.js +1201 -0
  53. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js +22 -0
  54. package/build/server/chunks/chunks/queries.js-aTxz7ubg.js +676 -0
  55. package/build/server/chunks/chunks/range.js-CLYFtcRE.js +117 -0
  56. package/build/server/chunks/chunks/refresh.js-DM1hnB2Y.js +107 -0
  57. package/build/server/chunks/chunks/request.js-B3j1_Icg.js +20 -0
  58. package/build/server/chunks/chunks/server.js-mTMwhapa.js +5374 -0
  59. package/build/server/chunks/chunks/shared.js-7DyfRqo3.js +255 -0
  60. package/build/server/chunks/chunks/utils.js-DzwqnkBT.js +1049 -0
  61. package/build/server/chunks/entries/endpoints/api/refresh/_server.ts.js-CJa6pKKD.js +21 -0
  62. package/build/server/chunks/entries/endpoints/api/settings/_server.ts.js-DQt7Bc2q.js +24 -0
  63. package/build/server/chunks/entries/endpoints/api/usage/_server.ts.js-kInjrhtE.js +17 -0
  64. package/build/server/chunks/entries/fallbacks/error.svelte.js-CVKDebWa.js +68 -0
  65. package/build/server/chunks/entries/hooks.server.js-BbojNy1X.js +16 -0
  66. package/build/server/chunks/entries/pages/_layout.svelte.js-C9kkUOBf.js +9 -0
  67. package/build/server/chunks/entries/pages/_page.server.ts.js-Oa3Sfml2.js +17 -0
  68. package/build/server/chunks/entries/pages/_page.svelte.js-D91hy9R4.js +1255 -0
  69. package/build/server/chunks/handler-br42Ds2q.js +1475 -0
  70. package/build/server/chunks/index.js-CBAFVV7O.js +3972 -0
  71. package/build/server/chunks/manifest.js-s70ajt69.js +63 -0
  72. package/build/server/chunks/nodes/0.js-Bzngkdxl.js +9 -0
  73. package/build/server/chunks/nodes/1.js-Cvu2_eGw.js +9 -0
  74. package/build/server/chunks/nodes/2.js-7xvomkGu.js +20 -0
  75. package/build/shims.js +33 -0
  76. package/package.json +64 -0
@@ -0,0 +1,1255 @@
1
+ import '../../chunks/internal.js-b-1wrnfA.js';
2
+ import { a0 as run, aj as head, ak as attr_class, ai as escape_html, al as attr, am as ensure_array_like, an as bind_props, ao as stringify, P as derived, ap as attr_style, aq as element } from '../../chunks/server.js-mTMwhapa.js';
3
+ import { R as RANGES, a as rangeLabel, b as RATES } from '../../chunks/range.js-CLYFtcRE.js';
4
+ import '../../chunks/client.js-C1_n_a2s.js';
5
+ import '../../chunks/shared.js-7DyfRqo3.js';
6
+ import '../../chunks/exports.js-Dd48p7Ih.js';
7
+ import '../../chunks/utils.js-DzwqnkBT.js';
8
+
9
+ //#region src/lib/components/Panel.svelte
10
+ function Panel($$renderer, $$props) {
11
+ let { title, hint, span = 12, actions, children } = $$props;
12
+ $$renderer.push(`<section class="panel svelte-hxsa5u"${attr_style(`--span: ${stringify(span)}`)}><header class="svelte-hxsa5u"><h2 class="svelte-hxsa5u">${escape_html(title)}</h2> `);
13
+ if (actions) {
14
+ $$renderer.push(`<!--[0--><div class="actions svelte-hxsa5u">`);
15
+ actions($$renderer);
16
+ $$renderer.push(`<!----></div>`);
17
+ } else $$renderer.push("<!--[-1-->");
18
+ $$renderer.push(`<!--]--></header> <div class="body svelte-hxsa5u">`);
19
+ children($$renderer);
20
+ $$renderer.push(`<!----></div></section>`);
21
+ }
22
+ //#endregion
23
+ //#region src/lib/components/Segmented.svelte
24
+ function Segmented($$renderer, $$props) {
25
+ $$renderer.component(($$renderer) => {
26
+ let { options, value = void 0, label, size = "md" } = $$props;
27
+ $$renderer.push(`<div${attr_class("segmented svelte-1dkbyok", void 0, { "sm": size === "sm" })} role="group"${attr("aria-label", label)}><!--[-->`);
28
+ const each_array = ensure_array_like(options);
29
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
30
+ let option = each_array[$$index];
31
+ $$renderer.push(`<button type="button"${attr("aria-pressed", value === option.id)}${attr_class("svelte-1dkbyok", void 0, { "on": value === option.id })}>${escape_html(option.label)}</button>`);
32
+ }
33
+ $$renderer.push(`<!--]--></div>`);
34
+ bind_props($$props, { value });
35
+ });
36
+ }
37
+ //#endregion
38
+ //#region src/lib/components/MultiSelect.svelte
39
+ function MultiSelect($$renderer, $$props) {
40
+ $$renderer.component(($$renderer) => {
41
+ let { label, options, selected = void 0, format = (value) => value } = $$props;
42
+ let open = false;
43
+ $$renderer.push(`<div class="select svelte-vbwhwd"><button type="button"${attr_class("trigger svelte-vbwhwd", void 0, { "on": selected.length > 0 })}${attr("aria-expanded", open)}>${escape_html(label)} `);
44
+ if (selected.length) $$renderer.push(`<!--[0--><b class="num svelte-vbwhwd">${escape_html(selected.length)}</b>`);
45
+ else $$renderer.push("<!--[-1-->");
46
+ $$renderer.push(`<!--]--> <svg viewBox="0 0 12 12" width="10" height="10" aria-hidden="true"><path d="M2 4.5 6 8.5 10 4.5" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg></button> `);
47
+ $$renderer.push("<!--[-1-->");
48
+ $$renderer.push(`<!--]--></div>`);
49
+ bind_props($$props, { selected });
50
+ });
51
+ }
52
+ //#endregion
53
+ //#region src/lib/format.ts
54
+ var UNITS = [
55
+ {
56
+ at: 0xe8d4a51000,
57
+ suffix: "T"
58
+ },
59
+ {
60
+ at: 1e9,
61
+ suffix: "B"
62
+ },
63
+ {
64
+ at: 1e6,
65
+ suffix: "M"
66
+ },
67
+ {
68
+ at: 1e3,
69
+ suffix: "k"
70
+ }
71
+ ];
72
+ function compact(value) {
73
+ const size = UNITS.find((unit) => Math.abs(value) >= unit.at);
74
+ if (!size) return String(Math.round(value));
75
+ const scaled = value / size.at;
76
+ return `${scaled.toFixed(scaled < 10 ? 1 : 0)}${size.suffix}`;
77
+ }
78
+ var full = (value) => Math.round(value).toLocaleString("en-GB");
79
+ function money(value) {
80
+ if (value === null) return "--";
81
+ if (value === 0) return "$0";
82
+ if (Math.abs(value) < .01) return "<$0.01";
83
+ return `$${value.toLocaleString("en-GB", {
84
+ minimumFractionDigits: 2,
85
+ maximumFractionDigits: 2
86
+ })}`;
87
+ }
88
+ function moneyCompact(value) {
89
+ if (value === null) return "--";
90
+ if (Math.abs(value) >= 1e3) return `$${compact(value)}`;
91
+ return money(value);
92
+ }
93
+ var RELATIVE = new Intl.RelativeTimeFormat("en-GB", { numeric: "auto" });
94
+ var STEPS = [
95
+ ["second", 60],
96
+ ["minute", 60],
97
+ ["hour", 24],
98
+ ["day", 7],
99
+ ["week", 4.35],
100
+ ["month", 12]
101
+ ];
102
+ function relative(at, now = Date.now()) {
103
+ if (!at) return "never";
104
+ let gap = (at - now) / 1e3;
105
+ if (Math.abs(gap) < 45) return gap < 0 ? "just now" : "in a moment";
106
+ for (const [unit, span] of STEPS) {
107
+ if (Math.abs(gap) < span) return RELATIVE.format(Math.round(gap), unit);
108
+ gap /= span;
109
+ }
110
+ return RELATIVE.format(Math.round(gap), "year");
111
+ }
112
+ var dayLabel = (day) => (/* @__PURE__ */ new Date(`${day}T00:00:00`)).toLocaleDateString("en-GB", {
113
+ weekday: "short",
114
+ day: "numeric",
115
+ month: "short"
116
+ });
117
+ var clockLabel = (at) => new Date(at).toLocaleString("en-GB", {
118
+ day: "numeric",
119
+ month: "short",
120
+ hour: "2-digit",
121
+ minute: "2-digit"
122
+ });
123
+ /** Directories are shown by their last segment, with the full path in the tooltip. */
124
+ var projectName = (path) => path ? path.replace(/\/+$/, "").split("/").pop() || path : "unknown";
125
+ /** Drops the vendor prefix and the release stamp vendors tack on the end. */
126
+ var modelName = (model) => (model.includes("/") ? model.slice(model.indexOf("/") + 1) : model).replace(/-\d{8}$/, "");
127
+ function delta(now, before) {
128
+ if (!before) return null;
129
+ const change = (now - before) / before * 100;
130
+ if (!Number.isFinite(change) || Math.abs(change) < 1) return {
131
+ text: "flat",
132
+ tone: "even"
133
+ };
134
+ return {
135
+ text: `${change > 0 ? "+" : ""}${change.toFixed(0)}%`,
136
+ tone: change > 0 ? "up" : "down"
137
+ };
138
+ }
139
+ //#endregion
140
+ //#region src/lib/components/Figures.svelte
141
+ function Figures($$renderer, $$props) {
142
+ $$renderer.component(($$renderer) => {
143
+ let { totals, previous, activeDays, rangeLabel } = $$props;
144
+ const change = derived(() => delta(totals.total, previous.total));
145
+ const costChange = derived(() => delta(totals.cost ?? 0, previous.cost ?? 0));
146
+ const perDay = derived(() => activeDays ? totals.total / activeDays : 0);
147
+ $$renderer.push(`<div class="figures svelte-l3stpt"><div class="lead svelte-l3stpt"><span class="cap svelte-l3stpt">Tokens, ${escape_html(rangeLabel.toLowerCase())}</span> <strong class="num svelte-l3stpt">${escape_html(compact(totals.total))}</strong> `);
148
+ if (change()) $$renderer.push(`<!--[0--><span class="delta svelte-l3stpt">${escape_html(change().text)}</span>`);
149
+ else $$renderer.push("<!--[-1-->");
150
+ $$renderer.push(`<!--]--></div> <dl class="svelte-l3stpt"><div class="svelte-l3stpt"><dt class="svelte-l3stpt">Est. cost</dt> <dd class="num svelte-l3stpt">${escape_html(money(totals.cost))} `);
151
+ if (costChange()) $$renderer.push(`<!--[0--><i${attr_class(`delta cost ${stringify(costChange().tone)}`, "svelte-l3stpt")}>${escape_html(costChange().text)}</i>`);
152
+ else $$renderer.push("<!--[-1-->");
153
+ $$renderer.push(`<!--]--></dd></div> <div class="svelte-l3stpt"><dt class="svelte-l3stpt">Sessions</dt> <dd class="num svelte-l3stpt">${escape_html(full(totals.sessions))}</dd></div> <div class="svelte-l3stpt"><dt class="svelte-l3stpt">Active days</dt> <dd class="num svelte-l3stpt">${escape_html(full(activeDays))}</dd></div> <div class="svelte-l3stpt"><dt class="svelte-l3stpt">Daily pace</dt> <dd class="num svelte-l3stpt">${escape_html(compact(perDay()))}</dd></div></dl></div>`);
154
+ });
155
+ }
156
+ //#endregion
157
+ //#region src/lib/components/Calendar.svelte
158
+ function Calendar($$renderer, $$props) {
159
+ $$renderer.component(($$renderer) => {
160
+ let { days, metric, oldest } = $$props;
161
+ const GAP = 3;
162
+ const LABELS = 32;
163
+ const iso = (date) => date.toLocaleDateString("en-CA");
164
+ const value = (day) => !day ? 0 : metric === "cost" ? day.cost ?? 0 : day.total;
165
+ const byDay = derived(() => new Map(days.map((day) => [day.day, day])));
166
+ const start = derived(() => oldest ? iso(new Date(oldest)) : days[0]?.day ?? "");
167
+ /** Shows at least a quarter, so a fresh install still reads as a calendar. */
168
+ const grid = derived(() => {
169
+ const today = /* @__PURE__ */ new Date();
170
+ today.setHours(0, 0, 0, 0);
171
+ const first = new Date(days.length ? `${days[0].day}T00:00:00` : today);
172
+ const floor = new Date(today);
173
+ floor.setDate(floor.getDate() - 370);
174
+ const shown = new Date(today);
175
+ shown.setDate(shown.getDate() - 125);
176
+ const from = new Date(Math.max(floor.getTime(), Math.min(first.getTime(), shown.getTime())));
177
+ from.setDate(from.getDate() - (from.getDay() + 6) % 7);
178
+ const cells = [];
179
+ const cursor = new Date(from);
180
+ let week = 0;
181
+ while (cursor <= today) {
182
+ const weekday = (cursor.getDay() + 6) % 7;
183
+ cells.push({
184
+ day: iso(cursor),
185
+ date: new Date(cursor),
186
+ week,
187
+ weekday
188
+ });
189
+ if (weekday === 6) week += 1;
190
+ cursor.setDate(cursor.getDate() + 1);
191
+ }
192
+ return {
193
+ cells,
194
+ weeks: week + 1
195
+ };
196
+ });
197
+ const cell = derived(() => Math.max(9, Math.min(30, Math.floor(648 / grid().weeks) - GAP)));
198
+ const step = derived(() => cell() + GAP);
199
+ /** Quantile buckets, or one loud day flattens every other square to nothing. */
200
+ const cuts = derived(() => {
201
+ const values = days.map((day) => value(day)).filter((amount) => amount > 0).sort((a, b) => a - b);
202
+ if (!values.length) return [];
203
+ return [
204
+ .2,
205
+ .4,
206
+ .6,
207
+ .8
208
+ ].map((at) => values[Math.floor(values.length * at)]);
209
+ });
210
+ const level = (amount) => amount <= 0 ? 0 : cuts().filter((cut) => amount >= cut).length + 1;
211
+ const months = derived(() => {
212
+ const seen = [];
213
+ for (const entry of grid().cells) {
214
+ if (entry.weekday !== 0) continue;
215
+ const label = entry.date.toLocaleDateString("en-GB", { month: "short" });
216
+ if (!seen.length || entry.date.getDate() <= 7 && seen[seen.length - 1].label !== label) seen.push({
217
+ label,
218
+ week: entry.week
219
+ });
220
+ }
221
+ return seen;
222
+ });
223
+ const dayCaption = (day) => {
224
+ if (start() && day < start()) return `${dayLabel(day)} - before your records start`;
225
+ const amount = value(byDay().get(day));
226
+ const shown = metric === "cost" ? money(amount) : `${compact(amount)} tokens`;
227
+ return `${dayLabel(day)} - ${amount ? shown : "nothing"}`;
228
+ };
229
+ const WEEKDAYS = [
230
+ "Mon",
231
+ "",
232
+ "Wed",
233
+ "",
234
+ "Fri",
235
+ "",
236
+ "Sun"
237
+ ];
238
+ const busy = derived(() => days.filter((day) => value(day) > 0).length);
239
+ const caption = derived(() => start() ? `Since ${dayLabel(start())}, ${busy()} ${busy() === 1 ? "day" : "days"} with usage` : "");
240
+ $$renderer.push(`<div class="calendar svelte-1iu5iby"><svg${attr("width", grid().weeks * step() + LABELS)}${attr("height", 7 * step() + 18)} role="img" aria-label="Daily usage calendar" class="svelte-1iu5iby"><!--[-->`);
241
+ const each_array = ensure_array_like(months());
242
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
243
+ let month = each_array[$$index];
244
+ $$renderer.push(`<text class="axis svelte-1iu5iby"${attr("x", LABELS + month.week * step())} y="10">${escape_html(month.label)}</text>`);
245
+ }
246
+ $$renderer.push(`<!--]--><!--[-->`);
247
+ const each_array_1 = ensure_array_like(WEEKDAYS);
248
+ for (let row = 0, $$length = each_array_1.length; row < $$length; row++) {
249
+ let name = each_array_1[row];
250
+ if (name) $$renderer.push(`<!--[0--><text class="axis svelte-1iu5iby" x="0"${attr("y", 18 + row * step() + cell() / 2 + 3)}>${escape_html(name)}</text>`);
251
+ else $$renderer.push("<!--[-1-->");
252
+ $$renderer.push(`<!--]-->`);
253
+ }
254
+ $$renderer.push(`<!--]--><!--[-->`);
255
+ const each_array_2 = ensure_array_like(grid().cells);
256
+ for (let $$index_2 = 0, $$length = each_array_2.length; $$index_2 < $$length; $$index_2++) {
257
+ let entry = each_array_2[$$index_2];
258
+ const blank = !!start() && entry.day < start();
259
+ $$renderer.push(`<rect${attr_class("cell svelte-1iu5iby", void 0, { "blank": blank })}${attr("x", LABELS + entry.week * step())}${attr("y", 18 + entry.weekday * step())}${attr("width", cell())}${attr("height", cell())}${attr("rx", Math.max(2, cell() / 4))}${attr("fill", `var(--ramp-${stringify(blank ? 0 : level(value(byDay().get(entry.day))))})`)} role="img"${attr("aria-label", dayCaption(entry.day))}></rect>`);
260
+ }
261
+ $$renderer.push(`<!--]--></svg> <div class="legend svelte-1iu5iby"><span class="span svelte-1iu5iby">${escape_html(caption())}</span> <span>No records</span> <i class="blank svelte-1iu5iby"></i> <span class="spread svelte-1iu5iby">Quieter</span> <!--[-->`);
262
+ const each_array_3 = ensure_array_like([
263
+ 0,
264
+ 1,
265
+ 2,
266
+ 3,
267
+ 4,
268
+ 5
269
+ ]);
270
+ for (let $$index_3 = 0, $$length = each_array_3.length; $$index_3 < $$length; $$index_3++) {
271
+ let tone = each_array_3[$$index_3];
272
+ $$renderer.push(`<i${attr_style(`background: var(--ramp-${stringify(tone)})`)} class="svelte-1iu5iby"></i>`);
273
+ }
274
+ $$renderer.push(`<!--]--> <span>Busier</span></div></div>`);
275
+ });
276
+ }
277
+ //#endregion
278
+ //#region src/lib/components/Trend.svelte
279
+ function Trend($$renderer, $$props) {
280
+ $$renderer.component(($$renderer) => {
281
+ let { series, keys, metric } = $$props;
282
+ const amount = (point) => metric === "cost" ? point.cost ?? 0 : point.total;
283
+ const columns = derived(() => {
284
+ const days = /* @__PURE__ */ new Map();
285
+ for (const point of series) {
286
+ const entry = days.get(point.day) ?? {
287
+ day: point.day,
288
+ total: 0,
289
+ parts: /* @__PURE__ */ new Map()
290
+ };
291
+ entry.total += amount(point);
292
+ entry.parts.set(point.key, (entry.parts.get(point.key) ?? 0) + amount(point));
293
+ days.set(point.day, entry);
294
+ }
295
+ return [...days.values()].sort((a, b) => a.day.localeCompare(b.day));
296
+ });
297
+ const peak = derived(() => Math.max(1, ...columns().map((column) => column.total)));
298
+ const tickLabel = (value) => metric === "cost" ? moneyCompact(value) : compact(value);
299
+ const ticks = derived(() => [
300
+ 1,
301
+ .5,
302
+ 0
303
+ ].map((at) => ({
304
+ at,
305
+ label: tickLabel(peak() * at)
306
+ })));
307
+ $$renderer.push(`<div class="trend svelte-118k1un"><div class="plot svelte-118k1un"><!--[-->`);
308
+ const each_array = ensure_array_like(ticks());
309
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
310
+ let tick = each_array[$$index];
311
+ $$renderer.push(`<div class="tick svelte-118k1un"${attr_style(`bottom: ${stringify(tick.at * 100)}%`)}><span class="svelte-118k1un">${escape_html(tick.label)}</span></div>`);
312
+ }
313
+ $$renderer.push(`<!--]--> <div class="bars svelte-118k1un"><!--[-->`);
314
+ const each_array_1 = ensure_array_like(columns());
315
+ for (let $$index_2 = 0, $$length = each_array_1.length; $$index_2 < $$length; $$index_2++) {
316
+ let column = each_array_1[$$index_2];
317
+ $$renderer.push(`<div class="column svelte-118k1un"><div class="stack svelte-118k1un"${attr_style(`height: ${stringify(column.total / peak() * 100)}%`)}><!--[-->`);
318
+ const each_array_2 = ensure_array_like(keys);
319
+ for (let $$index_1 = 0, $$length = each_array_2.length; $$index_1 < $$length; $$index_1++) {
320
+ let key = each_array_2[$$index_1];
321
+ const part = column.parts.get(key.id) ?? 0;
322
+ if (part > 0) $$renderer.push(`<!--[0--><span${attr_style(`flex: ${stringify(part)}; background: ${stringify(key.colour)}`)} aria-hidden="true" class="svelte-118k1un"></span>`);
323
+ else $$renderer.push("<!--[-1-->");
324
+ $$renderer.push(`<!--]-->`);
325
+ }
326
+ $$renderer.push(`<!--]--></div></div>`);
327
+ }
328
+ $$renderer.push(`<!--]--></div></div> <div class="axis svelte-118k1un"><span>${escape_html(columns().length ? dayLabel(columns()[0].day) : "")}</span> <span>${escape_html(columns().length ? dayLabel(columns()[columns().length - 1].day) : "")}</span></div></div>`);
329
+ });
330
+ }
331
+ //#endregion
332
+ //#region src/lib/logos/marks.ts
333
+ /** Gemini's spark stands in for Google as a whole, since that's all we ever count. */
334
+ var GEMINI = "M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81";
335
+ /** 24x24 marks, drawn in currentColor. From Simple Icons (CC0) and LobeHub icons (MIT). */
336
+ var MARKS = {
337
+ anthropic: "M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z",
338
+ openai: "M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z",
339
+ google: GEMINI,
340
+ xai: "M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815",
341
+ meta: "M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z",
342
+ mistral: "M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z",
343
+ deepseek: "M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45",
344
+ qwen: "M23.919 14.545 20.817 9.17l1.47-2.544a.56.56 0 0 0 0-.566l-1.633-2.83a.57.57 0 0 0-.49-.283h-6.207L12.487.402a.57.57 0 0 0-.49-.284H8.732a.56.56 0 0 0-.49.284L5.139 5.775h-2.94a.56.56 0 0 0-.49.284L.077 8.887a.56.56 0 0 0 0 .567L3.18 14.83l-1.47 2.545a.56.56 0 0 0 0 .566l1.634 2.83a.57.57 0 0 0 .49.283h6.205l1.47 2.545a.57.57 0 0 0 .49.284h3.266a.57.57 0 0 0 .49-.284l3.104-5.375h2.94a.57.57 0 0 0 .49-.283l1.634-2.828a.55.55 0 0 0-.004-.568M8.733.686l1.634 2.828-1.634 2.828H21.8L20.164 9.17H7.425L5.63 6.06Zm1.306 19.801-6.205-.002 1.634-2.83h3.265L2.201 6.344h3.267q3.182 5.517 6.367 11.032zm10.124-5.66L18.53 12l-6.532 11.315-1.634-2.83c2.129-3.673 4.25-7.351 6.373-11.028h3.592l3.102 5.374z",
345
+ "claude-code": "M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z",
346
+ codex: "M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z",
347
+ opencode: "M22 24H2V0h20zM17 4.8H7v14.4h10z",
348
+ "gemini-cli": GEMINI
349
+ };
350
+ //#endregion
351
+ //#region src/lib/logos/Mark.svelte
352
+ function Mark($$renderer, $$props) {
353
+ $$renderer.component(($$renderer) => {
354
+ let { id, label, colour, size = 16 } = $$props;
355
+ const path = derived(() => MARKS[id]);
356
+ const initials = derived(() => label.replace(/[^a-z ]/gi, "").split(" ").map((word) => word[0]).join("").slice(0, 2).toUpperCase());
357
+ $$renderer.push(`<svg class="mark svelte-jlywqn" viewBox="0 0 24 24"${attr("width", size)}${attr("height", size)} role="img"${attr("aria-label", label)}${attr_style("", { color: colour })}>`);
358
+ if (path()) $$renderer.push(`<!--[0--><path${attr("d", path())} fill="currentColor"></path>`);
359
+ else $$renderer.push(`<!--[-1--><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="none" stroke="currentColor" stroke-width="1.8"></rect><text x="12" y="12.5" text-anchor="middle" dominant-baseline="central" fill="currentColor"${attr("font-size", initials().length > 1 ? 9 : 12)} font-weight="600" font-family="var(--sans)">${escape_html(initials())}</text>`);
360
+ $$renderer.push(`<!--]--></svg>`);
361
+ });
362
+ }
363
+ //#endregion
364
+ //#region src/lib/components/BarList.svelte
365
+ function BarList($$renderer, $$props) {
366
+ $$renderer.component(($$renderer) => {
367
+ let { rows, active = [], onpick, empty = "Nothing in this range" } = $$props;
368
+ const peak = derived(() => Math.max(1, ...rows.map((row) => row.value)));
369
+ if (rows.length) {
370
+ $$renderer.push(`<!--[0--><ul class="bars svelte-eh60s9"><!--[-->`);
371
+ const each_array = ensure_array_like(rows);
372
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
373
+ let row = each_array[$$index];
374
+ $$renderer.push(`<li>`);
375
+ element($$renderer, onpick ? "button" : "div", () => {
376
+ $$renderer.push(`${attr_class("row svelte-eh60s9", void 0, { "on": active.includes(row.id) })}${attr("role", onpick ? "button" : void 0)}${attr("tabindex", onpick ? 0 : void 0)}`);
377
+ }, () => {
378
+ $$renderer.push(`<span class="fill svelte-eh60s9"${attr_style(`width: ${stringify(row.value / peak() * 100)}%; background: ${stringify(row.colour)}`)}></span> <span class="label svelte-eh60s9">`);
379
+ if (row.mark) {
380
+ $$renderer.push("<!--[0-->");
381
+ Mark($$renderer, {
382
+ id: row.mark,
383
+ label: row.label,
384
+ colour: row.colour,
385
+ size: 15
386
+ });
387
+ } else $$renderer.push(`<!--[-1--><i class="dot svelte-eh60s9"${attr_style(`background: ${stringify(row.colour)}`)}></i>`);
388
+ $$renderer.push(`<!--]--> <b class="svelte-eh60s9">${escape_html(row.label)}</b> `);
389
+ if (row.detail) $$renderer.push(`<!--[0--><em class="svelte-eh60s9">${escape_html(row.detail)}</em>`);
390
+ else $$renderer.push("<!--[-1-->");
391
+ $$renderer.push(`<!--]--></span> <span class="value num svelte-eh60s9">${escape_html(row.display)}</span>`);
392
+ });
393
+ $$renderer.push(`</li>`);
394
+ }
395
+ $$renderer.push(`<!--]--></ul>`);
396
+ } else $$renderer.push(`<!--[-1--><p class="empty svelte-eh60s9">${escape_html(empty)}</p>`);
397
+ $$renderer.push(`<!--]-->`);
398
+ });
399
+ }
400
+ //#endregion
401
+ //#region src/lib/components/Donut.svelte
402
+ function Donut($$renderer, $$props) {
403
+ $$renderer.component(($$renderer) => {
404
+ let { slices, centre, caption } = $$props;
405
+ const RADIUS = 52;
406
+ const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
407
+ const arcs = derived(() => {
408
+ const total = slices.reduce((sum, slice) => sum + slice.value, 0) || 1;
409
+ let offset = 0;
410
+ return slices.map((slice) => {
411
+ const length = slice.value / total * CIRCUMFERENCE;
412
+ const arc = {
413
+ ...slice,
414
+ length,
415
+ offset,
416
+ share: slice.value / total * 100
417
+ };
418
+ offset += length;
419
+ return arc;
420
+ });
421
+ });
422
+ $$renderer.push(`<div class="donut svelte-1o711x0"><svg viewBox="0 0 140 140" role="img"${attr("aria-label", caption)} class="svelte-1o711x0"><circle cx="70" cy="70"${attr("r", RADIUS)} fill="none" stroke="var(--surface-3)" stroke-width="18" class="svelte-1o711x0"></circle><!--[-->`);
423
+ const each_array = ensure_array_like(arcs());
424
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
425
+ let arc = each_array[$$index];
426
+ $$renderer.push(`<circle cx="70" cy="70"${attr("r", RADIUS)} fill="none"${attr("stroke", arc.colour)} stroke-width="18"${attr("stroke-dasharray", `${stringify(Math.max(0, arc.length - 2))} 326.7256359733385`)}${attr("stroke-dashoffset", -arc.offset)} transform="rotate(-90 70 70)" class="svelte-1o711x0"></circle>`);
427
+ }
428
+ $$renderer.push(`<!--]--><text class="centre num svelte-1o711x0" x="70" y="66" text-anchor="middle">${escape_html(centre)}</text><text class="sub svelte-1o711x0" x="70" y="84" text-anchor="middle">${escape_html(caption)}</text></svg></div>`);
429
+ });
430
+ }
431
+ //#endregion
432
+ //#region src/lib/components/Split.svelte
433
+ function Split($$renderer, $$props) {
434
+ $$renderer.component(($$renderer) => {
435
+ let { parts } = $$props;
436
+ const total = derived(() => parts.reduce((sum, part) => sum + part.value, 0) || 1);
437
+ $$renderer.push(`<div class="split svelte-1qwkpf0"><div class="bar svelte-1qwkpf0"><!--[-->`);
438
+ const each_array = ensure_array_like(parts);
439
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
440
+ let part = each_array[$$index];
441
+ if (part.value > 0) $$renderer.push(`<!--[0--><span${attr_style(`flex: ${stringify(part.value)}; background: ${stringify(part.colour)}`)} class="svelte-1qwkpf0"></span>`);
442
+ else $$renderer.push("<!--[-1-->");
443
+ $$renderer.push(`<!--]-->`);
444
+ }
445
+ $$renderer.push(`<!--]--></div> <ul class="svelte-1qwkpf0"><!--[-->`);
446
+ const each_array_1 = ensure_array_like(parts);
447
+ for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
448
+ let part = each_array_1[$$index_1];
449
+ $$renderer.push(`<li class="svelte-1qwkpf0"><i${attr_style(`background: ${stringify(part.colour)}`)} class="svelte-1qwkpf0"></i> <span class="name svelte-1qwkpf0">${escape_html(part.label)}</span> <span class="value num svelte-1qwkpf0">${escape_html(part.display)}</span> <span class="share num svelte-1qwkpf0">${escape_html((part.value / total() * 100).toFixed(1))}%</span></li>`);
450
+ }
451
+ $$renderer.push(`<!--]--></ul></div>`);
452
+ });
453
+ }
454
+ //#endregion
455
+ //#region src/lib/components/Clock.svelte
456
+ function Clock($$renderer, $$props) {
457
+ $$renderer.component(($$renderer) => {
458
+ let { points } = $$props;
459
+ const DAYS = [
460
+ "Mon",
461
+ "Tue",
462
+ "Wed",
463
+ "Thu",
464
+ "Fri",
465
+ "Sat",
466
+ "Sun"
467
+ ];
468
+ const grid = derived(() => {
469
+ const cells = /* @__PURE__ */ new Map();
470
+ for (const point of points) {
471
+ const row = (point.day + 6) % 7;
472
+ cells.set(`${row}-${point.hour}`, (cells.get(`${row}-${point.hour}`) ?? 0) + point.total);
473
+ }
474
+ return cells;
475
+ });
476
+ const peak = derived(() => Math.max(1, ...grid().values()));
477
+ /** Punch card: radius goes as the square root, off a floor so a quiet hour still shows. */
478
+ const dot = (value) => value ? .32 + .68 * Math.sqrt(value / peak()) : 0;
479
+ $$renderer.push(`<div class="clock svelte-13d36y"><div class="rows svelte-13d36y"><!--[-->`);
480
+ const each_array = ensure_array_like(DAYS);
481
+ for (let row = 0, $$length = each_array.length; row < $$length; row++) {
482
+ let day = each_array[row];
483
+ $$renderer.push(`<span class="day svelte-13d36y">${escape_html(day)}</span> <div class="hours svelte-13d36y"><!--[-->`);
484
+ const each_array_1 = ensure_array_like(Array(24));
485
+ for (let hour = 0, $$length = each_array_1.length; hour < $$length; hour++) {
486
+ each_array_1[hour];
487
+ const value = grid().get(`${row}-${hour}`) ?? 0;
488
+ $$renderer.push(`<i${attr_style(`--dot: ${stringify(dot(value))}`)} class="svelte-13d36y"></i>`);
489
+ }
490
+ $$renderer.push(`<!--]--></div>`);
491
+ }
492
+ $$renderer.push(`<!--]--></div> <div class="scale svelte-13d36y"><!--[-->`);
493
+ const each_array_2 = ensure_array_like([
494
+ 0,
495
+ 6,
496
+ 12,
497
+ 18
498
+ ]);
499
+ for (let $$index_2 = 0, $$length = each_array_2.length; $$index_2 < $$length; $$index_2++) {
500
+ let hour = each_array_2[$$index_2];
501
+ $$renderer.push(`<span>${escape_html(String(hour).padStart(2, "0"))}</span>`);
502
+ }
503
+ $$renderer.push(`<!--]--> <span>23</span></div></div>`);
504
+ });
505
+ }
506
+ //#endregion
507
+ //#region src/lib/components/Meter.svelte
508
+ function Meter($$renderer, $$props) {
509
+ $$renderer.component(($$renderer) => {
510
+ let { window: meter } = $$props;
511
+ const stale = derived(() => !!meter.resets_at && meter.resets_at < Date.now());
512
+ const tone = derived(() => stale() ? "text-faint" : meter.used_percent >= 90 ? "bad" : meter.used_percent >= 70 ? "warn" : "good");
513
+ $$renderer.push(`<div${attr_class("meter svelte-tlhxud", void 0, { "stale": stale() })}><div class="top svelte-tlhxud"><span class="name svelte-tlhxud">${escape_html(meter.label)}</span> <span class="num pct svelte-tlhxud"${attr_style(`color: var(--${tone()})`)}>${escape_html(Math.round(meter.used_percent))}%</span></div> <div class="track svelte-tlhxud" role="meter"${attr("aria-valuenow", Math.round(meter.used_percent))} aria-valuemin="0" aria-valuemax="100"${attr("aria-label", meter.label)}><span${attr_style(`width: ${stringify(Math.min(100, meter.used_percent))}%; background: var(--${tone()})`)} class="svelte-tlhxud"></span></div> <span class="reset svelte-tlhxud">`);
514
+ if (stale()) $$renderer.push(`<!--[0-->window has turned over, last read ${escape_html(relative(meter.seen_at))}`);
515
+ else if (meter.resets_at) $$renderer.push(`<!--[1-->resets ${escape_html(relative(meter.resets_at))}`);
516
+ else $$renderer.push("<!--[-1-->");
517
+ $$renderer.push(`<!--]--></span></div>`);
518
+ });
519
+ }
520
+ //#endregion
521
+ //#region src/lib/providers.ts
522
+ var PROVIDERS = [
523
+ {
524
+ id: "anthropic",
525
+ label: "Anthropic",
526
+ colour: "#d97757"
527
+ },
528
+ {
529
+ id: "openai",
530
+ label: "OpenAI",
531
+ colour: "#10a37f"
532
+ },
533
+ {
534
+ id: "google",
535
+ label: "Google",
536
+ colour: "#4285f4"
537
+ },
538
+ {
539
+ id: "meta",
540
+ label: "Meta",
541
+ colour: "#0668e1"
542
+ },
543
+ {
544
+ id: "mistral",
545
+ label: "Mistral",
546
+ colour: "#fa520f"
547
+ },
548
+ {
549
+ id: "xai",
550
+ label: "xAI",
551
+ colour: "#7c7c8a"
552
+ },
553
+ {
554
+ id: "deepseek",
555
+ label: "DeepSeek",
556
+ colour: "#4d6bfe"
557
+ },
558
+ {
559
+ id: "qwen",
560
+ label: "Qwen",
561
+ colour: "#a855f7"
562
+ },
563
+ {
564
+ id: "unknown",
565
+ label: "Other",
566
+ colour: "#8b93a7"
567
+ }
568
+ ];
569
+ var MATCHES = [
570
+ [/^claude/, "anthropic"],
571
+ [/^(gpt|codex|o[1-4]-|chatgpt|text-embedding)/, "openai"],
572
+ [/^(gemini|gemma|palm)/, "google"],
573
+ [/^(llama|code-?llama)/, "meta"],
574
+ [/^(mistral|mixtral|codestral|magistral|devstral)/, "mistral"],
575
+ [/^grok/, "xai"],
576
+ [/^deepseek/, "deepseek"],
577
+ [/^(qwen|qwq)/, "qwen"]
578
+ ];
579
+ /** Vendor-prefixed ids like `anthropic/claude-sonnet-5` come back from OpenRouter. */
580
+ function providerFor(model) {
581
+ const name = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
582
+ const vendor = model.includes("/") ? model.slice(0, model.indexOf("/")).toLowerCase() : "";
583
+ if (PROVIDERS.some((provider) => provider.id === vendor)) return vendor;
584
+ return MATCHES.find(([pattern]) => pattern.test(name.toLowerCase()))?.[1] ?? "unknown";
585
+ }
586
+ var providerMeta = (id) => PROVIDERS.find((provider) => provider.id === id) ?? PROVIDERS[PROVIDERS.length - 1];
587
+ var TOOLS = [
588
+ {
589
+ id: "claude-code",
590
+ label: "Claude Code",
591
+ colour: "#d97757",
592
+ home: "https://claude.com/claude-code"
593
+ },
594
+ {
595
+ id: "codex",
596
+ label: "Codex",
597
+ colour: "#10a37f",
598
+ home: "https://developers.openai.com/codex"
599
+ },
600
+ {
601
+ id: "opencode",
602
+ label: "OpenCode",
603
+ colour: "#f5a524",
604
+ home: "https://opencode.ai"
605
+ },
606
+ {
607
+ id: "gemini-cli",
608
+ label: "Gemini CLI",
609
+ colour: "#4285f4",
610
+ home: "https://github.com/google-gemini/gemini-cli"
611
+ }
612
+ ];
613
+ var toolMeta = (id) => TOOLS.find((tool) => tool.id === id) ?? {
614
+ id,
615
+ label: id,
616
+ colour: "#8b93a7",
617
+ home: ""
618
+ };
619
+ //#endregion
620
+ //#region src/lib/components/Sessions.svelte
621
+ function Sessions($$renderer, $$props) {
622
+ $$renderer.component(($$renderer) => {
623
+ let { rows, metric} = $$props;
624
+ if (rows.length) {
625
+ $$renderer.push(`<!--[0--><div class="scroll svelte-1uqtl83"><table class="svelte-1uqtl83"><thead><tr><th scope="col" class="svelte-1uqtl83">Session</th><th scope="col" class="svelte-1uqtl83">Project</th><th scope="col" class="svelte-1uqtl83">Models</th><th scope="col" class="svelte-1uqtl83">Ran</th><th scope="col" class="right svelte-1uqtl83">${escape_html(metric === "cost" ? "Est. cost" : "Tokens")}</th></tr></thead><tbody class="svelte-1uqtl83"><!--[-->`);
626
+ const each_array = ensure_array_like(rows);
627
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
628
+ let row = each_array[$$index];
629
+ const tool = toolMeta(row.tool);
630
+ $$renderer.push(`<tr class="svelte-1uqtl83"><td class="svelte-1uqtl83"><span class="tool svelte-1uqtl83">`);
631
+ Mark($$renderer, {
632
+ id: row.tool,
633
+ label: tool.label,
634
+ colour: tool.colour,
635
+ size: 14
636
+ });
637
+ $$renderer.push(`<!----> <span class="id mono svelte-1uqtl83">${escape_html(row.id.slice(0, 8))}</span></span></td><td class="svelte-1uqtl83"><button type="button" class="link svelte-1uqtl83">${escape_html(projectName(row.project))}</button></td><td class="models svelte-1uqtl83">${escape_html(row.models.split(", ").map(modelName).join(", "))}</td><td class="svelte-1uqtl83">${escape_html(clockLabel(row.ended))}</td><td class="right num svelte-1uqtl83">${escape_html(metric === "cost" ? money(row.cost) : compact(row.total))}</td></tr>`);
638
+ }
639
+ $$renderer.push(`<!--]--></tbody></table></div>`);
640
+ } else $$renderer.push(`<!--[-1--><p class="empty svelte-1uqtl83">No sessions in this range</p>`);
641
+ $$renderer.push(`<!--]-->`);
642
+ });
643
+ }
644
+ //#endregion
645
+ //#region src/lib/components/Problems.svelte
646
+ function Problems($$renderer, $$props) {
647
+ $$renderer.component(($$renderer) => {
648
+ let { problems } = $$props;
649
+ let dismissed = [];
650
+ let open = false;
651
+ const shown = derived(() => problems.filter((problem) => !dismissed.includes(problem.id)));
652
+ const worst = derived(() => shown().some((problem) => problem.level === "error") ? "error" : shown().some((problem) => problem.level === "warn") ? "warn" : "info");
653
+ if (shown().length) {
654
+ $$renderer.push(`<!--[0--><aside${attr_class(`problems ${worst()}`, "svelte-1vaf4tg")} aria-live="polite"><button type="button" class="head svelte-1vaf4tg"${attr("aria-expanded", open)}><svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><circle cx="8" cy="8" r="6.6" fill="none" stroke="currentColor" stroke-width="1.5"></circle><path d="M8 4.6v4.2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path><circle cx="8" cy="11.4" r="0.9" fill="currentColor"></circle></svg> <span class="svelte-1vaf4tg">${escape_html(shown().length === 1 ? shown()[0].message : `${shown().length} things need a look`)}</span> <em class="svelte-1vaf4tg">${escape_html("Details")}</em></button> `);
655
+ $$renderer.push("<!--[-1-->");
656
+ $$renderer.push(`<!--]--></aside>`);
657
+ } else $$renderer.push("<!--[-1-->");
658
+ $$renderer.push(`<!--]-->`);
659
+ });
660
+ }
661
+ //#endregion
662
+ //#region src/lib/components/Settings.svelte
663
+ function Settings($$renderer, $$props) {
664
+ $$renderer.component(($$renderer) => {
665
+ let { open = void 0, saved, sources, dbPath, scannedAt, onsave, onrescan } = $$props;
666
+ let priceModel = "";
667
+ let priceIn = "";
668
+ let priceOut = "";
669
+ const overrides = derived(() => Object.entries(saved.prices));
670
+ const ENV = {
671
+ "claude-code": "AIUSAGE_CLAUDE_DIR",
672
+ codex: "AIUSAGE_CODEX_DIR",
673
+ opencode: "AIUSAGE_OPENCODE_DIR"
674
+ };
675
+ $$renderer.push(`<dialog class="svelte-anx9w7"><header class="svelte-anx9w7"><h2 class="svelte-anx9w7">Settings</h2> <button type="button" class="close svelte-anx9w7" aria-label="Close settings"><svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></button></header> <div class="body svelte-anx9w7"><section class="svelte-anx9w7"><h3 class="svelte-anx9w7">Appearance</h3> <label class="field svelte-anx9w7"><span>Theme</span> `);
676
+ $$renderer.select({
677
+ value: saved.theme,
678
+ onchange: (event) => onsave({ theme: event.currentTarget.value }),
679
+ class: ""
680
+ }, ($$renderer) => {
681
+ $$renderer.option({ value: "auto" }, ($$renderer) => {
682
+ $$renderer.push(`Match the system`);
683
+ });
684
+ $$renderer.option({ value: "light" }, ($$renderer) => {
685
+ $$renderer.push(`Light`);
686
+ });
687
+ $$renderer.option({ value: "dark" }, ($$renderer) => {
688
+ $$renderer.push(`Dark`);
689
+ });
690
+ }, "svelte-anx9w7");
691
+ $$renderer.push(`</label> <label class="field svelte-anx9w7"><span>Range on open</span> `);
692
+ $$renderer.select({
693
+ value: saved.range,
694
+ onchange: (event) => onsave({ range: event.currentTarget.value }),
695
+ class: ""
696
+ }, ($$renderer) => {
697
+ $$renderer.push(`<!--[-->`);
698
+ const each_array = ensure_array_like(RANGES);
699
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
700
+ let range = each_array[$$index];
701
+ $$renderer.option({ value: range.id }, ($$renderer) => {
702
+ $$renderer.push(`${escape_html(range.label)}`);
703
+ });
704
+ }
705
+ $$renderer.push(`<!--]-->`);
706
+ }, "svelte-anx9w7");
707
+ $$renderer.push(`</label> <label class="field svelte-anx9w7"><span>Headline measure</span> `);
708
+ $$renderer.select({
709
+ value: saved.metric,
710
+ onchange: (event) => onsave({ metric: event.currentTarget.value }),
711
+ class: ""
712
+ }, ($$renderer) => {
713
+ $$renderer.option({ value: "tokens" }, ($$renderer) => {
714
+ $$renderer.push(`Tokens`);
715
+ });
716
+ $$renderer.option({ value: "cost" }, ($$renderer) => {
717
+ $$renderer.push(`Estimated cost`);
718
+ });
719
+ }, "svelte-anx9w7");
720
+ $$renderer.push(`</label></section> <section class="svelte-anx9w7"><h3 class="svelte-anx9w7">Data</h3> <ul class="sources svelte-anx9w7"><!--[-->`);
721
+ const each_array_1 = ensure_array_like(sources);
722
+ for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
723
+ let source = each_array_1[$$index_1];
724
+ $$renderer.push(`<li class="svelte-anx9w7"><div><b class="svelte-anx9w7">${escape_html(source.label)}</b> <code class="svelte-anx9w7">${escape_html(source.root)}</code></div> <span${attr_class("badge svelte-anx9w7", void 0, { "on": source.present })}>${escape_html(source.present ? `${source.files} files` : "not found")}</span></li>`);
725
+ }
726
+ $$renderer.push(`<!--]--></ul> <p class="note svelte-anx9w7">Point these somewhere else with <!--[-->`);
727
+ const each_array_2 = ensure_array_like(sources);
728
+ for (let index = 0, $$length = each_array_2.length; index < $$length; index++) {
729
+ let source = each_array_2[index];
730
+ $$renderer.push(`<code class="svelte-anx9w7">${escape_html(ENV[source.id] ?? source.id)}</code>${escape_html(index < sources.length - 1 ? ", " : "")}`);
731
+ }
732
+ $$renderer.push(`<!--]-->. History lives in <code class="svelte-anx9w7">${escape_html(dbPath)}</code> and outlives the transcripts, which most tools prune.</p> <label class="field svelte-anx9w7"><span>Rescan every</span> `);
733
+ $$renderer.select({
734
+ value: String(saved.scanIntervalSec),
735
+ onchange: (event) => onsave({ scanIntervalSec: Number(event.currentTarget.value) }),
736
+ class: ""
737
+ }, ($$renderer) => {
738
+ $$renderer.option({ value: "30" }, ($$renderer) => {
739
+ $$renderer.push(`30 seconds`);
740
+ });
741
+ $$renderer.option({ value: "120" }, ($$renderer) => {
742
+ $$renderer.push(`2 minutes`);
743
+ });
744
+ $$renderer.option({ value: "600" }, ($$renderer) => {
745
+ $$renderer.push(`10 minutes`);
746
+ });
747
+ $$renderer.option({ value: "3600" }, ($$renderer) => {
748
+ $$renderer.push(`an hour`);
749
+ });
750
+ }, "svelte-anx9w7");
751
+ $$renderer.push(`</label> <label class="check svelte-anx9w7"><input type="checkbox"${attr("checked", saved.claudeWindows, true)} class="svelte-anx9w7"/> <span>Ask Anthropic for your 5-hour and 7-day windows <em class="svelte-anx9w7">Reads the Claude Code credentials on this machine. Nothing else leaves it.</em></span></label> <div class="row svelte-anx9w7"><button type="button" class="action svelte-anx9w7">Rescan now</button> <span class="note svelte-anx9w7">Last scan ${escape_html(relative(scannedAt))}</span></div></section> <section class="svelte-anx9w7"><h3 class="svelte-anx9w7">Prices</h3> <p class="note svelte-anx9w7">Dollars per million tokens. ${escape_html(Object.keys(RATES).length)} models are priced out of the box; anything
752
+ you add here wins, and an unknown model is reported rather than counted as free.</p> `);
753
+ if (overrides().length) {
754
+ $$renderer.push(`<!--[0--><ul class="prices svelte-anx9w7"><!--[-->`);
755
+ const each_array_3 = ensure_array_like(overrides());
756
+ for (let $$index_3 = 0, $$length = each_array_3.length; $$index_3 < $$length; $$index_3++) {
757
+ let [model, rate] = each_array_3[$$index_3];
758
+ $$renderer.push(`<li class="svelte-anx9w7"><code class="svelte-anx9w7">${escape_html(model)}</code> <span class="num">in ${escape_html(rate.input)} / out ${escape_html(rate.output)}</span> <button type="button" class="svelte-anx9w7">Remove</button></li>`);
759
+ }
760
+ $$renderer.push(`<!--]--></ul>`);
761
+ } else $$renderer.push("<!--[-1-->");
762
+ $$renderer.push(`<!--]--> <div class="add svelte-anx9w7"><input placeholder="model id"${attr("value", priceModel)} class="svelte-anx9w7"/> <input placeholder="in" inputmode="decimal"${attr("value", priceIn)} class="svelte-anx9w7"/> <input placeholder="out" inputmode="decimal"${attr("value", priceOut)} class="svelte-anx9w7"/> <button type="button" class="action svelte-anx9w7">Add</button></div></section></div></dialog>`);
763
+ bind_props($$props, { open });
764
+ });
765
+ }
766
+ //#endregion
767
+ //#region src/lib/logos/Logo.svelte
768
+ function Logo($$renderer, $$props) {
769
+ $$renderer.component(($$renderer) => {
770
+ let { size = 28 } = $$props;
771
+ const cells = [
772
+ 0,
773
+ 1,
774
+ 2,
775
+ 3
776
+ ].flatMap((row) => [
777
+ 0,
778
+ 1,
779
+ 2,
780
+ 3
781
+ ].map((column) => ({
782
+ row,
783
+ column,
784
+ heat: (column + (3 - row)) / 6
785
+ })));
786
+ $$renderer.push(`<svg class="logo svelte-nck4wt" viewBox="0 0 32 32"${attr("width", size)}${attr("height", size)} role="img" aria-label="AI usage dashboard"><rect width="32" height="32" rx="8" fill="var(--accent)"></rect><!--[-->`);
787
+ const each_array = ensure_array_like(cells);
788
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
789
+ let cell = each_array[$$index];
790
+ $$renderer.push(`<rect${attr("x", 5 + cell.column * 6)}${attr("y", 5 + cell.row * 6)} width="4" height="4" rx="1.2" fill="var(--accent-text)"${attr("opacity", (.22 + cell.heat * .78).toFixed(2))}></rect>`);
791
+ }
792
+ $$renderer.push(`<!--]--></svg>`);
793
+ });
794
+ }
795
+ //#endregion
796
+ //#region src/routes/+page.svelte
797
+ function _page($$renderer, $$props) {
798
+ $$renderer.component(($$renderer) => {
799
+ let { data } = $$props;
800
+ const first = run(() => data);
801
+ let usage = first.usage;
802
+ let saved = first.settings;
803
+ let busy = false;
804
+ let settingsOpen = false;
805
+ let range = first.usage.filters.range;
806
+ let tools = first.usage.filters.tools;
807
+ let models = first.usage.filters.models;
808
+ let projects = first.usage.filters.projects;
809
+ let metric = first.settings.metric;
810
+ let trendBy = "tool";
811
+ let modelView = "share";
812
+ let projectBy = "measure";
813
+ const query = derived(() => new URLSearchParams({
814
+ range,
815
+ tools: tools.join(","),
816
+ models: models.join(","),
817
+ projects: projects.join(",")
818
+ }).toString());
819
+ let showing = run(() => query());
820
+ async function save(patch) {
821
+ const response = await fetch("/api/settings", {
822
+ method: "POST",
823
+ headers: { "content-type": "application/json" },
824
+ body: JSON.stringify(patch)
825
+ });
826
+ if (response.ok) saved = await response.json();
827
+ }
828
+ async function rescan() {
829
+ busy = true;
830
+ try {
831
+ const response = await fetch(`/api/refresh?${showing}`, { method: "POST" });
832
+ if (response.ok) usage = await response.json();
833
+ } finally {
834
+ busy = false;
835
+ }
836
+ }
837
+ const toggle = (list, value) => list.includes(value) ? list.filter((item) => item !== value) : [...list, value];
838
+ const measure = (slice) => metric === "cost" ? slice.cost ?? 0 : slice.total;
839
+ const show = (value) => metric === "cost" ? money(value) : compact(value);
840
+ const activeDays = derived(() => new Set(usage.series.filter((point) => point.total > 0).map((point) => point.day)).size);
841
+ const toolKeys = derived(() => usage.byTool.map((slice) => ({
842
+ id: slice.key,
843
+ label: toolMeta(slice.key).label,
844
+ colour: toolMeta(slice.key).colour
845
+ })));
846
+ const modelKeys = derived(() => usage.byModel.map((slice) => ({
847
+ id: slice.key,
848
+ label: modelName(slice.key),
849
+ colour: providerMeta(providerFor(slice.key)).colour
850
+ })));
851
+ const trendKeys = derived(() => trendBy === "tool" ? toolKeys() : modelKeys());
852
+ const modelRows = derived(() => usage.byModel.map((slice) => {
853
+ const provider = providerMeta(providerFor(slice.key));
854
+ return {
855
+ id: slice.key,
856
+ label: modelName(slice.key),
857
+ detail: provider.label,
858
+ value: measure(slice),
859
+ display: show(measure(slice)),
860
+ colour: provider.colour,
861
+ mark: provider.id,
862
+ hint: `${slice.key} - ${full(slice.total)} tokens over ${full(slice.events)} requests, ${money(slice.cost)} estimated`
863
+ };
864
+ }));
865
+ const projectRows = derived(() => usage.byProject.map((slice) => ({
866
+ id: slice.key,
867
+ label: projectName(slice.key),
868
+ detail: "",
869
+ value: projectBy === "sessions" ? slice.sessions : measure(slice),
870
+ display: projectBy === "sessions" ? full(slice.sessions) : show(measure(slice)),
871
+ colour: "var(--accent)",
872
+ hint: `${slice.key || "unknown"} - ${full(slice.total)} tokens, ${full(slice.sessions)} sessions`
873
+ })));
874
+ /** Tools with nothing to show still get a row, so it is obvious what else is supported. */
875
+ const toolRows = derived(() => usage.sources.map((source) => {
876
+ const slice = usage.byTool.find((entry) => entry.key === source.id);
877
+ const tool = toolMeta(source.id);
878
+ return {
879
+ id: source.id,
880
+ label: tool.label,
881
+ detail: slice ? `${full(slice.sessions)} sessions` : "not found",
882
+ value: slice ? measure(slice) : 0,
883
+ display: slice ? show(measure(slice)) : "--",
884
+ colour: tool.colour,
885
+ mark: source.id,
886
+ hint: source.present ? `${full(source.files)} files under ${source.root}` : `Nothing at ${source.root}`
887
+ };
888
+ }));
889
+ const mix = derived(() => [
890
+ {
891
+ id: "input",
892
+ label: "Input",
893
+ value: usage.totals.tokens.input,
894
+ display: compact(usage.totals.tokens.input),
895
+ colour: "var(--ramp-4)",
896
+ hint: "Fresh prompt tokens, billed at full rate"
897
+ },
898
+ {
899
+ id: "output",
900
+ label: "Output",
901
+ value: usage.totals.tokens.output,
902
+ display: compact(usage.totals.tokens.output),
903
+ colour: "var(--ramp-2)",
904
+ hint: `Everything the model wrote back, ${compact(usage.totals.reasoning)} of it reasoning`
905
+ },
906
+ {
907
+ id: "cache_read",
908
+ label: "Cache read",
909
+ value: usage.totals.tokens.cache_read,
910
+ display: compact(usage.totals.tokens.cache_read),
911
+ colour: "var(--ramp-1)",
912
+ hint: "Context served from cache, usually a tenth of the input price"
913
+ },
914
+ {
915
+ id: "cache_write",
916
+ label: "Cache write",
917
+ value: usage.totals.tokens.cache_write,
918
+ display: compact(usage.totals.tokens.cache_write),
919
+ colour: "var(--ramp-3)",
920
+ hint: "Putting context into the cache, a premium on the input price"
921
+ }
922
+ ]);
923
+ const cacheShare = derived(() => usage.totals.total ? usage.totals.tokens.cache_read / usage.totals.total * 100 : 0);
924
+ let $$settled = true;
925
+ let $$inner_renderer;
926
+ function $$render_inner($$renderer) {
927
+ head("1uha8ag", $$renderer, ($$renderer) => {
928
+ $$renderer.title(($$renderer) => {
929
+ $$renderer.push(`<title>AI usage dashboard</title>`);
930
+ });
931
+ });
932
+ $$renderer.push(`<header class="bar svelte-1uha8ag"><a class="brand svelte-1uha8ag" href="/">`);
933
+ Logo($$renderer, {});
934
+ $$renderer.push(`<!----> <span>AI usage</span></a> <div class="filters svelte-1uha8ag">`);
935
+ Segmented($$renderer, {
936
+ options: RANGES.map((entry) => ({
937
+ id: entry.id,
938
+ label: entry.label
939
+ })),
940
+ label: "Date range",
941
+ size: "sm",
942
+ get value() {
943
+ return range;
944
+ },
945
+ set value($$value) {
946
+ range = $$value;
947
+ $$settled = false;
948
+ }
949
+ });
950
+ $$renderer.push(`<!----> `);
951
+ Segmented($$renderer, {
952
+ options: [{
953
+ id: "tokens",
954
+ label: "Tokens"
955
+ }, {
956
+ id: "cost",
957
+ label: "Est. cost",
958
+ hint: "At published API rates. Your subscription is billed separately."
959
+ }],
960
+ label: "Measure",
961
+ size: "sm",
962
+ get value() {
963
+ return metric;
964
+ },
965
+ set value($$value) {
966
+ metric = $$value;
967
+ $$settled = false;
968
+ }
969
+ });
970
+ $$renderer.push(`<!----> `);
971
+ MultiSelect($$renderer, {
972
+ label: "Tools",
973
+ options: usage.options.tools,
974
+ format: (id) => toolMeta(id).label,
975
+ get selected() {
976
+ return tools;
977
+ },
978
+ set selected($$value) {
979
+ tools = $$value;
980
+ $$settled = false;
981
+ }
982
+ });
983
+ $$renderer.push(`<!----> `);
984
+ MultiSelect($$renderer, {
985
+ label: "Models",
986
+ options: usage.options.models,
987
+ format: modelName,
988
+ get selected() {
989
+ return models;
990
+ },
991
+ set selected($$value) {
992
+ models = $$value;
993
+ $$settled = false;
994
+ }
995
+ });
996
+ $$renderer.push(`<!----> `);
997
+ MultiSelect($$renderer, {
998
+ label: "Projects",
999
+ options: usage.options.projects,
1000
+ format: projectName,
1001
+ get selected() {
1002
+ return projects;
1003
+ },
1004
+ set selected($$value) {
1005
+ projects = $$value;
1006
+ $$settled = false;
1007
+ }
1008
+ });
1009
+ $$renderer.push(`<!----> `);
1010
+ if (tools.length || models.length || projects.length) $$renderer.push(`<!--[0--><button type="button" class="reset svelte-1uha8ag">Clear filters</button>`);
1011
+ else $$renderer.push("<!--[-1-->");
1012
+ $$renderer.push(`<!--]--></div> <div class="tools svelte-1uha8ag"><button type="button"${attr_class("icon svelte-1uha8ag", void 0, { "busy": busy })} aria-label="Rescan now"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" class="svelte-1uha8ag"><path d="M13.4 7A5.5 5.5 0 1 0 12.6 11" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M13.8 3.4v3.8h-3.8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></button> <button type="button" class="icon svelte-1uha8ag" aria-label="Settings"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="2.3" fill="none" stroke="currentColor" stroke-width="1.5"></circle><path d="M8 1.6v1.8M8 12.6v1.8M14.4 8h-1.8M3.4 8H1.6M12.5 3.5l-1.3 1.3M4.8 11.2l-1.3 1.3M12.5 12.5l-1.3-1.3M4.8 4.8 3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></svg></button></div></header> <main class="svelte-1uha8ag">`);
1013
+ Figures($$renderer, {
1014
+ totals: usage.totals,
1015
+ previous: usage.previous,
1016
+ activeDays: activeDays(),
1017
+ rangeLabel: rangeLabel(usage.filters.range)
1018
+ });
1019
+ $$renderer.push(`<!----> <div class="grid svelte-1uha8ag">`);
1020
+ Panel($$renderer, {
1021
+ title: "Every day so far",
1022
+ span: 8,
1023
+ hint: "One square per day, whatever range is picked above. Outlined days are before your records start.",
1024
+ children: ($$renderer) => {
1025
+ Calendar($$renderer, {
1026
+ days: usage.calendar,
1027
+ metric,
1028
+ oldest: usage.oldest
1029
+ });
1030
+ }});
1031
+ $$renderer.push(`<!----> `);
1032
+ Panel($$renderer, {
1033
+ title: "Plan windows",
1034
+ span: 4,
1035
+ hint: "Straight from each account, not worked out from tokens",
1036
+ children: ($$renderer) => {
1037
+ if (usage.windows.length) {
1038
+ $$renderer.push(`<!--[0--><div class="meters svelte-1uha8ag"><!--[-->`);
1039
+ const each_array = ensure_array_like(usage.windows);
1040
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
1041
+ let window = each_array[$$index];
1042
+ Meter($$renderer, { window });
1043
+ }
1044
+ $$renderer.push(`<!--]--></div>`);
1045
+ } else $$renderer.push(`<!--[-1--><p class="empty svelte-1uha8ag">No plan windows yet. Sign in with Claude Code or run Codex once and they turn up here.</p>`);
1046
+ $$renderer.push(`<!--]-->`);
1047
+ }});
1048
+ $$renderer.push(`<!----> `);
1049
+ {
1050
+ function actions($$renderer) {
1051
+ Segmented($$renderer, {
1052
+ options: [{
1053
+ id: "tool",
1054
+ label: "By tool"
1055
+ }, {
1056
+ id: "model",
1057
+ label: "By model"
1058
+ }],
1059
+ label: "Trend grouping",
1060
+ size: "sm",
1061
+ get value() {
1062
+ return trendBy;
1063
+ },
1064
+ set value($$value) {
1065
+ trendBy = $$value;
1066
+ $$settled = false;
1067
+ }
1068
+ });
1069
+ }
1070
+ Panel($$renderer, {
1071
+ title: "Day by day",
1072
+ span: 12,
1073
+ hint: "Stacked over the range you picked",
1074
+ actions,
1075
+ children: ($$renderer) => {
1076
+ Trend($$renderer, {
1077
+ series: trendBy === "tool" ? usage.series : usage.seriesByModel,
1078
+ keys: trendKeys(),
1079
+ metric
1080
+ });
1081
+ }});
1082
+ }
1083
+ $$renderer.push(`<!----> `);
1084
+ {
1085
+ function actions($$renderer) {
1086
+ Segmented($$renderer, {
1087
+ options: [{
1088
+ id: "share",
1089
+ label: "Share"
1090
+ }, {
1091
+ id: "list",
1092
+ label: "Ranked"
1093
+ }],
1094
+ label: "Model view",
1095
+ size: "sm",
1096
+ get value() {
1097
+ return modelView;
1098
+ },
1099
+ set value($$value) {
1100
+ modelView = $$value;
1101
+ $$settled = false;
1102
+ }
1103
+ });
1104
+ }
1105
+ Panel($$renderer, {
1106
+ title: "Models",
1107
+ span: 5,
1108
+ hint: "Click one to filter the whole page by it",
1109
+ actions,
1110
+ children: ($$renderer) => {
1111
+ if (modelView === "share") {
1112
+ $$renderer.push(`<!--[0--><div class="share svelte-1uha8ag">`);
1113
+ Donut($$renderer, {
1114
+ slices: modelRows().map((row) => ({
1115
+ id: row.id,
1116
+ label: row.label,
1117
+ value: row.value,
1118
+ colour: row.colour,
1119
+ display: row.display
1120
+ })),
1121
+ centre: show(measure(usage.totals)),
1122
+ caption: metric === "cost" ? "estimated" : "tokens"
1123
+ });
1124
+ $$renderer.push(`<!----> <ul class="key svelte-1uha8ag"><!--[-->`);
1125
+ const each_array_1 = ensure_array_like(modelRows());
1126
+ for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
1127
+ let row = each_array_1[$$index_1];
1128
+ $$renderer.push(`<li class="svelte-1uha8ag">`);
1129
+ Mark($$renderer, {
1130
+ id: row.mark,
1131
+ label: row.detail,
1132
+ colour: row.colour,
1133
+ size: 14
1134
+ });
1135
+ $$renderer.push(`<!----> <span class="svelte-1uha8ag">${escape_html(row.label)}</span> <b class="num svelte-1uha8ag">${escape_html(row.display)}</b></li>`);
1136
+ }
1137
+ $$renderer.push(`<!--]--></ul></div>`);
1138
+ } else {
1139
+ $$renderer.push("<!--[-1-->");
1140
+ BarList($$renderer, {
1141
+ rows: modelRows(),
1142
+ active: models,
1143
+ onpick: (id) => models = toggle(models, id)
1144
+ });
1145
+ }
1146
+ $$renderer.push(`<!--]-->`);
1147
+ }});
1148
+ }
1149
+ $$renderer.push(`<!----> `);
1150
+ {
1151
+ function actions($$renderer) {
1152
+ Segmented($$renderer, {
1153
+ options: [{
1154
+ id: "measure",
1155
+ label: metric === "cost" ? "Est. cost" : "Tokens"
1156
+ }, {
1157
+ id: "sessions",
1158
+ label: "Sessions"
1159
+ }],
1160
+ label: "Project measure",
1161
+ size: "sm",
1162
+ get value() {
1163
+ return projectBy;
1164
+ },
1165
+ set value($$value) {
1166
+ projectBy = $$value;
1167
+ $$settled = false;
1168
+ }
1169
+ });
1170
+ }
1171
+ Panel($$renderer, {
1172
+ title: "Projects",
1173
+ span: 7,
1174
+ hint: "Where the work happened. Click one to filter.",
1175
+ actions,
1176
+ children: ($$renderer) => {
1177
+ BarList($$renderer, {
1178
+ rows: projectRows().slice(0, 8),
1179
+ active: projects,
1180
+ onpick: (id) => projects = toggle(projects, id)
1181
+ });
1182
+ }});
1183
+ }
1184
+ $$renderer.push(`<!----> `);
1185
+ Panel($$renderer, {
1186
+ title: "Where the tokens go",
1187
+ span: 4,
1188
+ hint: "Cache reads are cheap, cache writes are not. This is the split that decides the bill.",
1189
+ children: ($$renderer) => {
1190
+ Split($$renderer, { parts: mix() });
1191
+ $$renderer.push(`<!----> <p class="aside svelte-1uha8ag">${escape_html(cacheShare().toFixed(0))}% of everything counted was served from cache.</p>`);
1192
+ }});
1193
+ $$renderer.push(`<!----> `);
1194
+ Panel($$renderer, {
1195
+ title: "When you work",
1196
+ span: 5,
1197
+ hint: "Local time, over the range above",
1198
+ children: ($$renderer) => {
1199
+ Clock($$renderer, { points: usage.clock });
1200
+ }});
1201
+ $$renderer.push(`<!----> `);
1202
+ Panel($$renderer, {
1203
+ title: "Tools",
1204
+ span: 3,
1205
+ hint: "Click one to filter the whole page by it",
1206
+ children: ($$renderer) => {
1207
+ BarList($$renderer, {
1208
+ rows: toolRows(),
1209
+ active: tools,
1210
+ onpick: (id) => tools = toggle(tools, id)
1211
+ });
1212
+ }});
1213
+ $$renderer.push(`<!----> `);
1214
+ Panel($$renderer, {
1215
+ title: "Recent sessions",
1216
+ span: 12,
1217
+ children: ($$renderer) => {
1218
+ Sessions($$renderer, {
1219
+ rows: usage.sessions,
1220
+ metric});
1221
+ }});
1222
+ $$renderer.push(`<!----></div> <footer class="svelte-1uha8ag"><span>Reads the transcripts already on this machine. Nothing is sent anywhere.</span> <span>`);
1223
+ if (usage.oldest) $$renderer.push(`<!--[0-->Records from ${escape_html(new Date(usage.oldest).toLocaleDateString("en-GB"))} ·`);
1224
+ else $$renderer.push("<!--[-1-->");
1225
+ $$renderer.push(`<!--]--> Scanned ${escape_html(relative(usage.scannedAt))}</span></footer></main> `);
1226
+ Problems($$renderer, { problems: usage.problems });
1227
+ $$renderer.push(`<!----> `);
1228
+ Settings($$renderer, {
1229
+ saved,
1230
+ sources: usage.sources,
1231
+ dbPath: data.paths.db,
1232
+ scannedAt: usage.scannedAt,
1233
+ onsave: save,
1234
+ onrescan: rescan,
1235
+ get open() {
1236
+ return settingsOpen;
1237
+ },
1238
+ set open($$value) {
1239
+ settingsOpen = $$value;
1240
+ $$settled = false;
1241
+ }
1242
+ });
1243
+ $$renderer.push(`<!---->`);
1244
+ }
1245
+ do {
1246
+ $$settled = true;
1247
+ $$inner_renderer = $$renderer.copy();
1248
+ $$render_inner($$inner_renderer);
1249
+ } while (!$$settled);
1250
+ $$renderer.subsume($$inner_renderer);
1251
+ });
1252
+ }
1253
+
1254
+ export { _page as default };
1255
+ //# sourceMappingURL=_page.svelte.js-D91hy9R4.js.map