@subvalue/cli 0.1.5 → 0.1.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.
- package/README.md +6 -2
- package/dist/packages/cli/src/server.js +74 -27
- package/dist/packages/core/src/billing.js +112 -0
- package/dist/packages/core/src/summary.js +69 -70
- package/dist/packages/core/src/types.js +10 -2
- package/dist/packages/receipt/src/index.js +158 -16
- package/dist/public/app.css +185 -0
- package/dist/public/apps/web/src/landing.js +8 -2
- package/dist/public/apps/web/src/main.js +243 -44
- package/dist/public/landing.css +185 -0
- package/dist/public/packages/core/src/billing.js +112 -0
- package/dist/public/packages/receipt/src/index.js +158 -16
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
|
|
2
|
+
|
|
2
3
|
import { fullCalendarMonths } from '../../core/src/calendar.js';
|
|
3
4
|
import { formatPricingCoverage } from '../../core/src/coverage.js';
|
|
4
|
-
import { product } from '../../ui/src/brand.js';
|
|
5
|
+
import { product, wordmarkSvg } from '../../ui/src/brand.js';
|
|
5
6
|
/** Only public, aggregated fields needed by the receipt and its example. */
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
|
|
@@ -14,7 +15,8 @@ import { product } from '../../ui/src/brand.js';
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
|
|
@@ -37,23 +39,67 @@ export const money = (n , signed = false) =>
|
|
|
37
39
|
? 'Unavailable'
|
|
38
40
|
: `${signed && n >= 0 ? '+' : ''}${new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(n)}`;
|
|
39
41
|
/** Compare only the displayed priced amount; preserve uncertainty and billing eligibility. */
|
|
40
|
-
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
export function comparisonForDisplay(s ) {
|
|
41
48
|
const comparison = s.total.comparison,
|
|
42
49
|
amount = s.total.apiEquivalent ?? s.total.knownSubtotal;
|
|
43
50
|
if (fullCalendarMonths(s.range) === null)
|
|
44
51
|
return {
|
|
45
52
|
...comparison,
|
|
46
53
|
subscription: null,
|
|
54
|
+
apiSpend: null,
|
|
55
|
+
paid: null,
|
|
47
56
|
value: null,
|
|
48
57
|
roi: null,
|
|
49
58
|
breakEven: null,
|
|
50
59
|
outcome: 'neutral',
|
|
60
|
+
scope: [],
|
|
61
|
+
apiEquivalent: null,
|
|
51
62
|
};
|
|
52
|
-
|
|
63
|
+
const paidFor = (p ) =>
|
|
64
|
+
p.comparison?.paid !== undefined
|
|
65
|
+
? p.comparison.paid
|
|
66
|
+
: p.mode === 'SUBSCRIPTION'
|
|
67
|
+
? (p.comparison?.subscription ?? null)
|
|
68
|
+
: null;
|
|
69
|
+
const active = s.providers.filter((p) => p.records > 0),
|
|
70
|
+
included = active.filter((p) => paidFor(p) !== null);
|
|
71
|
+
if (included.length > 0 && included.length < active.length) {
|
|
72
|
+
const subscription = included.reduce((n, p) => n + (p.comparison?.subscription ?? 0), 0),
|
|
73
|
+
apiSpend = included.reduce((n, p) => n + (p.comparison?.apiSpend ?? 0), 0),
|
|
74
|
+
paid = included.reduce((n, p) => n + paidFor(p) , 0),
|
|
75
|
+
priced = included.map((p) => p.apiEquivalent ?? p.knownSubtotal),
|
|
76
|
+
scopedAmount = priced.every((cost) => cost !== null)
|
|
77
|
+
? priced.reduce ((n, cost) => n + cost , 0)
|
|
78
|
+
: null,
|
|
79
|
+
certain = !s.historyPartial && included.every((p) => p.apiEquivalent !== null);
|
|
80
|
+
return {
|
|
81
|
+
subscription,
|
|
82
|
+
apiSpend,
|
|
83
|
+
paid,
|
|
84
|
+
value: scopedAmount === null ? null : scopedAmount - paid,
|
|
85
|
+
roi: scopedAmount === null || paid <= 0 ? null : scopedAmount / paid,
|
|
86
|
+
outcome: certain && included.length === 1 ? included[0].comparison .outcome : 'neutral',
|
|
87
|
+
breakEven: certain && included.length === 1 ? included[0].comparison .breakEven : null,
|
|
88
|
+
reason: 'Providers with declared expenses only',
|
|
89
|
+
scope: included.map((p) => p.provider),
|
|
90
|
+
apiEquivalent: scopedAmount,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const paid = comparison.paid === undefined ? comparison.subscription : comparison.paid;
|
|
94
|
+
if (amount === null || paid === null)
|
|
95
|
+
return { ...comparison, paid, scope: [], apiEquivalent: amount };
|
|
53
96
|
return {
|
|
54
97
|
...comparison,
|
|
55
|
-
|
|
56
|
-
|
|
98
|
+
paid,
|
|
99
|
+
value: comparison.value ?? amount - paid,
|
|
100
|
+
roi: comparison.roi ?? (paid > 0 ? amount / paid : null),
|
|
101
|
+
scope: [],
|
|
102
|
+
apiEquivalent: amount,
|
|
57
103
|
};
|
|
58
104
|
}
|
|
59
105
|
export function receiptModel(s , generatedAt = new Date()) {
|
|
@@ -86,12 +132,33 @@ export function receiptModel(s , generatedAt = new Date())
|
|
|
86
132
|
strong: true,
|
|
87
133
|
});
|
|
88
134
|
const comp = comparisonForDisplay(s);
|
|
89
|
-
if (comp.
|
|
135
|
+
if (comp.scope.length)
|
|
136
|
+
rows.push(
|
|
137
|
+
{ left: '', right: '' },
|
|
138
|
+
{
|
|
139
|
+
left:
|
|
140
|
+
comp.scope
|
|
141
|
+
.map((provider) => (provider === 'codex' ? 'CODEX' : 'CLAUDE CODE'))
|
|
142
|
+
.join(' + ') + ' COMPARISON',
|
|
143
|
+
right: '',
|
|
144
|
+
strong: true,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
left: 'API Equivalent',
|
|
148
|
+
right: comp.apiEquivalent === null ? '—' : money(comp.apiEquivalent),
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
if (comp.subscription != null && comp.subscription > 0)
|
|
90
152
|
rows.push({ left: 'Subscription Cost', right: money(comp.subscription) });
|
|
153
|
+
if (comp.apiSpend != null && (comp.apiSpend > 0 || comp.subscription === 0)) {
|
|
154
|
+
rows.push({ left: 'API Paid', right: money(comp.apiSpend) });
|
|
155
|
+
if (comp.subscription != null && comp.subscription > 0)
|
|
156
|
+
rows.push({ left: 'Total Paid', right: money(comp.paid ?? null), strong: true });
|
|
157
|
+
}
|
|
91
158
|
const value = comp.value;
|
|
92
159
|
if (value !== null)
|
|
93
160
|
rows.push({
|
|
94
|
-
left: '
|
|
161
|
+
left: 'Total Saved',
|
|
95
162
|
right: money(value, true),
|
|
96
163
|
accent: value >= 0 ? 'positive' : 'negative',
|
|
97
164
|
strong: true,
|
|
@@ -179,8 +246,53 @@ export async function receiptFonts() {
|
|
|
179
246
|
await Promise.all([
|
|
180
247
|
document.fonts.load('14px "Plex Mono"'),
|
|
181
248
|
document.fonts.load('600 14px "Plex Mono"'),
|
|
249
|
+
receiptBrandAssets(document),
|
|
182
250
|
]);
|
|
183
251
|
}
|
|
252
|
+
// Decode the same bundled product marks used by the dashboard before drawing.
|
|
253
|
+
// Canvas previews and exported PNGs therefore include identical local artwork.
|
|
254
|
+
const brandAssets = new WeakMap ();
|
|
255
|
+
const decodedBrands = new WeakMap ();
|
|
256
|
+
const decodedWordmarks = new WeakMap ();
|
|
257
|
+
function receiptBrandAssets(document ) {
|
|
258
|
+
let assets = brandAssets.get(document);
|
|
259
|
+
if (!assets) {
|
|
260
|
+
assets = Promise.all(
|
|
261
|
+
['codex-outline.svg', 'claude-code-clawd.svg'].map(async (file) => {
|
|
262
|
+
const image = document.createElement('img');
|
|
263
|
+
image.src = `/brands/${file}`;
|
|
264
|
+
await image.decode();
|
|
265
|
+
return image;
|
|
266
|
+
}),
|
|
267
|
+
).then(async ([codex, claude]) => {
|
|
268
|
+
const svg = wordmarkSvg();
|
|
269
|
+
if (svg) {
|
|
270
|
+
const dimensions = /viewBox="0 0 (\d+) (\d+)"/.exec(svg) ;
|
|
271
|
+
const images = await Promise.all(
|
|
272
|
+
(['dark', 'light'] ).map(async (theme) => {
|
|
273
|
+
const image = document.createElement('img');
|
|
274
|
+
const artwork = svg
|
|
275
|
+
.replace('<svg ', `<svg width="${dimensions[1]}" height="${dimensions[2]}" `)
|
|
276
|
+
.replace('fill="currentColor"', `fill="${theme === 'dark' ? '#eef2f4' : '#22272d'}"`)
|
|
277
|
+
.replaceAll(
|
|
278
|
+
'class="brand-name-accent"',
|
|
279
|
+
`fill="${theme === 'dark' ? '#37ec85' : '#137744'}"`,
|
|
280
|
+
);
|
|
281
|
+
image.src = 'data:image/svg+xml,' + encodeURIComponent(artwork);
|
|
282
|
+
await image.decode();
|
|
283
|
+
return image;
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
decodedWordmarks.set(document, { dark: images[0], light: images[1] });
|
|
287
|
+
}
|
|
288
|
+
const marks = { codex, claude };
|
|
289
|
+
decodedBrands.set(document, marks);
|
|
290
|
+
return marks;
|
|
291
|
+
});
|
|
292
|
+
brandAssets.set(document, assets);
|
|
293
|
+
}
|
|
294
|
+
return assets;
|
|
295
|
+
}
|
|
184
296
|
const paperTextures = new WeakMap ();
|
|
185
297
|
function paperTexture(canvas ) {
|
|
186
298
|
const document = canvas.ownerDocument;
|
|
@@ -202,6 +314,8 @@ function paperTexture(canvas ) {
|
|
|
202
314
|
paperTextures.set(document, texture);
|
|
203
315
|
return texture;
|
|
204
316
|
}
|
|
317
|
+
const isTokenRow = (row ) =>
|
|
318
|
+
['Input Tokens', 'Output Tokens', 'Cache Reads', 'Tokens', 'Known Tokens'].includes(row.left);
|
|
205
319
|
const rowHeight = (row ) =>
|
|
206
320
|
!row.left && !row.right
|
|
207
321
|
? 34
|
|
@@ -211,7 +325,9 @@ const rowHeight = (row ) =>
|
|
|
211
325
|
? 46
|
|
212
326
|
: row.accent
|
|
213
327
|
? 32
|
|
214
|
-
:
|
|
328
|
+
: isTokenRow(row)
|
|
329
|
+
? 24
|
|
330
|
+
: 28;
|
|
215
331
|
export function drawReceipt(
|
|
216
332
|
canvas ,
|
|
217
333
|
model ,
|
|
@@ -313,7 +429,13 @@ export function drawReceipt(
|
|
|
313
429
|
c.stroke();
|
|
314
430
|
c.setLineDash([]);
|
|
315
431
|
};
|
|
316
|
-
|
|
432
|
+
const wordmark = decodedWordmarks.get(canvas.ownerDocument)?.[theme];
|
|
433
|
+
if (wordmark) {
|
|
434
|
+
// Compact, centered lettering only; no symbol or terminal cursor on the paper.
|
|
435
|
+
const logoWidth = 156,
|
|
436
|
+
logoHeight = logoWidth * (wordmark.naturalHeight / wordmark.naturalWidth);
|
|
437
|
+
c.drawImage(wordmark, (width - logoWidth) / 2, 64 - logoHeight, logoWidth, logoHeight);
|
|
438
|
+
} else text(product.name, width / 2, 64, 29, '#f0f0ef', 'center');
|
|
317
439
|
text('USAGE RECEIPT', width / 2, 92, 13, '#b6b8bd', 'center');
|
|
318
440
|
if (model.demo) text('EXAMPLE DATA', width / 2, 112, 10, '#a3a5a9', 'center');
|
|
319
441
|
text('Period', left, 137, 12.5, '#b4b5b9');
|
|
@@ -331,7 +453,8 @@ export function drawReceipt(
|
|
|
331
453
|
}
|
|
332
454
|
const provider = !row.right,
|
|
333
455
|
multiple = row.left === 'VALUE MULTIPLE';
|
|
334
|
-
const label =
|
|
456
|
+
const label =
|
|
457
|
+
row.left === 'CODEX' ? 'Codex' : row.left === 'CLAUDE CODE' ? 'Claude Code' : row.left;
|
|
335
458
|
const color =
|
|
336
459
|
row.accent === 'positive'
|
|
337
460
|
? '#37ec85'
|
|
@@ -340,18 +463,37 @@ export function drawReceipt(
|
|
|
340
463
|
: row.strong
|
|
341
464
|
? '#e1e2e4'
|
|
342
465
|
: '#bdc0c6';
|
|
343
|
-
const
|
|
466
|
+
const tokenRow = isTokenRow(row);
|
|
467
|
+
const valueSize = multiple ? 25 : row.accent ? 22 : row.strong ? 18 : tokenRow ? 13 : 16;
|
|
344
468
|
c.font = `${printSize(valueSize)}px ${mono}`;
|
|
345
469
|
const valueWidth = c.measureText(row.right).width;
|
|
470
|
+
const brand = row.left === 'CODEX' ? 'codex' : row.left === 'CLAUDE CODE' ? 'claude' : null;
|
|
471
|
+
const mark = brand && decodedBrands.get(canvas.ownerDocument)?.[brand];
|
|
472
|
+
let labelLeft = left;
|
|
473
|
+
if (mark) {
|
|
474
|
+
const size = 22;
|
|
475
|
+
const markHeight = size * (mark.naturalHeight / mark.naturalWidth);
|
|
476
|
+
if (brand === 'codex') {
|
|
477
|
+
const tinted = canvas.ownerDocument.createElement('canvas');
|
|
478
|
+
tinted.width = tinted.height = size * scale;
|
|
479
|
+
const context = tinted.getContext('2d') ;
|
|
480
|
+
context.drawImage(mark, 0, 0, tinted.width, tinted.height);
|
|
481
|
+
context.globalCompositeOperation = 'source-in';
|
|
482
|
+
context.fillStyle = ink('#e1e2e4');
|
|
483
|
+
context.fillRect(0, 0, tinted.width, tinted.height);
|
|
484
|
+
c.drawImage(tinted, left, y - 17, size, size);
|
|
485
|
+
} else c.drawImage(mark, left, y - 14, size, markHeight);
|
|
486
|
+
labelLeft += size + 8;
|
|
487
|
+
}
|
|
346
488
|
text(
|
|
347
489
|
label,
|
|
348
|
-
|
|
490
|
+
labelLeft,
|
|
349
491
|
y,
|
|
350
|
-
multiple ? 17 : provider ? 17 :
|
|
492
|
+
multiple ? 17 : provider ? 17 : tokenRow ? 12 : 14,
|
|
351
493
|
multiple ? '#e1e2e4' : color,
|
|
352
494
|
'left',
|
|
353
495
|
provider,
|
|
354
|
-
provider ?
|
|
496
|
+
provider ? right - labelLeft : width - 62 - valueWidth,
|
|
355
497
|
);
|
|
356
498
|
text(row.right, right, y, valueSize, row.accent ? color : '#e3e4e7', 'right', row.strong, 160);
|
|
357
499
|
y += step;
|
package/dist/public/app.css
CHANGED
|
@@ -308,6 +308,12 @@ p {
|
|
|
308
308
|
font-weight: 550;
|
|
309
309
|
letter-spacing: -0.3px;
|
|
310
310
|
}
|
|
311
|
+
.empty-history {
|
|
312
|
+
color: var(--muted);
|
|
313
|
+
font-size: 12px;
|
|
314
|
+
line-height: 1.6;
|
|
315
|
+
margin: 10px 0 16px;
|
|
316
|
+
}
|
|
311
317
|
@media (max-width: 760px) {
|
|
312
318
|
.overview-title {
|
|
313
319
|
flex-direction: column;
|
|
@@ -2313,3 +2319,182 @@ dialog {
|
|
|
2313
2319
|
max-height: calc(100dvh - 32px);
|
|
2314
2320
|
overflow-y: auto;
|
|
2315
2321
|
}
|
|
2322
|
+
#expenses-dialog {
|
|
2323
|
+
width: min(440px, calc(100vw - 32px));
|
|
2324
|
+
max-height: calc(100dvh - 32px);
|
|
2325
|
+
overflow: auto;
|
|
2326
|
+
}
|
|
2327
|
+
.expenses-heading {
|
|
2328
|
+
display: flex;
|
|
2329
|
+
align-items: center;
|
|
2330
|
+
justify-content: space-between;
|
|
2331
|
+
gap: 12px;
|
|
2332
|
+
}
|
|
2333
|
+
.expenses-heading h2 {
|
|
2334
|
+
margin: 0;
|
|
2335
|
+
font-size: 18px;
|
|
2336
|
+
}
|
|
2337
|
+
.expenses-heading button {
|
|
2338
|
+
font-size: 24px;
|
|
2339
|
+
}
|
|
2340
|
+
.expense-month {
|
|
2341
|
+
display: flex;
|
|
2342
|
+
align-items: center;
|
|
2343
|
+
justify-content: space-between;
|
|
2344
|
+
gap: 16px;
|
|
2345
|
+
margin: 24px 0;
|
|
2346
|
+
color: var(--muted);
|
|
2347
|
+
font-size: 13px;
|
|
2348
|
+
}
|
|
2349
|
+
#expenses-dialog .billing-row {
|
|
2350
|
+
display: block;
|
|
2351
|
+
}
|
|
2352
|
+
#expenses-dialog .provider-setting-name {
|
|
2353
|
+
display: none;
|
|
2354
|
+
}
|
|
2355
|
+
#expenses-dialog .subscription-fields {
|
|
2356
|
+
display: flex;
|
|
2357
|
+
flex-direction: column;
|
|
2358
|
+
align-items: stretch;
|
|
2359
|
+
gap: 14px;
|
|
2360
|
+
}
|
|
2361
|
+
#expenses-dialog select {
|
|
2362
|
+
width: 100%;
|
|
2363
|
+
}
|
|
2364
|
+
.mixed-field {
|
|
2365
|
+
display: flex;
|
|
2366
|
+
align-items: center;
|
|
2367
|
+
gap: 8px;
|
|
2368
|
+
color: var(--muted);
|
|
2369
|
+
font-size: 12px;
|
|
2370
|
+
}
|
|
2371
|
+
.api-spend-field {
|
|
2372
|
+
display: flex;
|
|
2373
|
+
align-items: center;
|
|
2374
|
+
justify-content: space-between;
|
|
2375
|
+
gap: 16px;
|
|
2376
|
+
font-size: 13px;
|
|
2377
|
+
}
|
|
2378
|
+
.expense-note {
|
|
2379
|
+
color: var(--muted);
|
|
2380
|
+
font-size: 12px;
|
|
2381
|
+
margin: 20px 0;
|
|
2382
|
+
}
|
|
2383
|
+
.expense-actions {
|
|
2384
|
+
display: flex;
|
|
2385
|
+
align-items: center;
|
|
2386
|
+
justify-content: space-between;
|
|
2387
|
+
gap: 12px;
|
|
2388
|
+
}
|
|
2389
|
+
.expense-actions .button {
|
|
2390
|
+
width: auto;
|
|
2391
|
+
}
|
|
2392
|
+
#expense-error {
|
|
2393
|
+
color: var(--red);
|
|
2394
|
+
font-size: 12px;
|
|
2395
|
+
}
|
|
2396
|
+
.subscription-fields {
|
|
2397
|
+
flex-wrap: wrap;
|
|
2398
|
+
}
|
|
2399
|
+
.subscription-fields .mixed-field {
|
|
2400
|
+
flex-basis: 100%;
|
|
2401
|
+
}
|
|
2402
|
+
.hidden {
|
|
2403
|
+
display: none !important;
|
|
2404
|
+
}
|
|
2405
|
+
.provider-expenses-form {
|
|
2406
|
+
border-top: 1px solid var(--border);
|
|
2407
|
+
margin-top: 18px;
|
|
2408
|
+
padding-top: 16px;
|
|
2409
|
+
}
|
|
2410
|
+
.provider-expense-toggle {
|
|
2411
|
+
display: flex;
|
|
2412
|
+
align-items: center;
|
|
2413
|
+
justify-content: space-between;
|
|
2414
|
+
gap: 12px;
|
|
2415
|
+
width: 100%;
|
|
2416
|
+
min-height: 40px;
|
|
2417
|
+
margin: 14px 0 0;
|
|
2418
|
+
padding: 10px 12px;
|
|
2419
|
+
color: var(--green);
|
|
2420
|
+
background: #37ec850c;
|
|
2421
|
+
border: 1px solid #37ec8540;
|
|
2422
|
+
border-radius: 4px;
|
|
2423
|
+
font-size: 12px;
|
|
2424
|
+
text-align: left;
|
|
2425
|
+
line-height: 1.4;
|
|
2426
|
+
}
|
|
2427
|
+
.provider-expense-toggle:hover {
|
|
2428
|
+
background: #37ec8518;
|
|
2429
|
+
border-color: #37ec8580;
|
|
2430
|
+
}
|
|
2431
|
+
.provider-expense-toggle svg {
|
|
2432
|
+
width: 13px;
|
|
2433
|
+
height: 13px;
|
|
2434
|
+
flex-shrink: 0;
|
|
2435
|
+
}
|
|
2436
|
+
.provider-expense-toggle[aria-expanded='true'] {
|
|
2437
|
+
border-color: var(--green);
|
|
2438
|
+
}
|
|
2439
|
+
.provider-expenses-popover {
|
|
2440
|
+
position: fixed;
|
|
2441
|
+
inset: auto;
|
|
2442
|
+
margin: 0;
|
|
2443
|
+
width: min(350px, calc(100vw - 32px));
|
|
2444
|
+
max-height: calc(100dvh - 32px);
|
|
2445
|
+
overflow-y: auto;
|
|
2446
|
+
padding: 20px;
|
|
2447
|
+
color: var(--text);
|
|
2448
|
+
background: #14191e;
|
|
2449
|
+
border: 1px solid #3a4249;
|
|
2450
|
+
border-radius: 6px;
|
|
2451
|
+
box-shadow: 0 18px 48px #0009;
|
|
2452
|
+
}
|
|
2453
|
+
.provider-expenses-popover .expenses-heading h2 {
|
|
2454
|
+
font-size: 14px;
|
|
2455
|
+
}
|
|
2456
|
+
.provider-expenses-form .billing-row {
|
|
2457
|
+
display: block;
|
|
2458
|
+
}
|
|
2459
|
+
.provider-expenses-form .provider-setting-name {
|
|
2460
|
+
display: none;
|
|
2461
|
+
}
|
|
2462
|
+
.provider-expenses-form .subscription-fields {
|
|
2463
|
+
display: flex;
|
|
2464
|
+
flex-direction: column;
|
|
2465
|
+
align-items: stretch;
|
|
2466
|
+
gap: 12px;
|
|
2467
|
+
}
|
|
2468
|
+
.provider-expenses-form select {
|
|
2469
|
+
width: 100%;
|
|
2470
|
+
min-width: 0;
|
|
2471
|
+
}
|
|
2472
|
+
.provider-expenses-form .mixed-field {
|
|
2473
|
+
flex-basis: auto;
|
|
2474
|
+
}
|
|
2475
|
+
.provider-expenses-form .amount-field {
|
|
2476
|
+
width: fit-content;
|
|
2477
|
+
max-width: 100%;
|
|
2478
|
+
}
|
|
2479
|
+
.provider-expenses-form .api-spend-field .amount-field {
|
|
2480
|
+
width: 130px;
|
|
2481
|
+
}
|
|
2482
|
+
.provider-expenses-form .expense-actions {
|
|
2483
|
+
margin-top: 14px;
|
|
2484
|
+
}
|
|
2485
|
+
.provider-expenses-form small,
|
|
2486
|
+
.expense-period {
|
|
2487
|
+
color: var(--muted);
|
|
2488
|
+
font-size: 12px;
|
|
2489
|
+
}
|
|
2490
|
+
.billing-error {
|
|
2491
|
+
color: var(--red);
|
|
2492
|
+
font-size: 12px;
|
|
2493
|
+
}
|
|
2494
|
+
.settings-expenses {
|
|
2495
|
+
margin: 12px 0 22px;
|
|
2496
|
+
font-size: 12px;
|
|
2497
|
+
}
|
|
2498
|
+
.provider-grid {
|
|
2499
|
+
align-items: stretch;
|
|
2500
|
+
}
|
|
@@ -29,6 +29,7 @@ const example = {
|
|
|
29
29
|
records: 1,
|
|
30
30
|
tokens: 20530000,
|
|
31
31
|
tokensPartial: false,
|
|
32
|
+
tokenBreakdown: { input: 19230000, output: 1300000, cacheRead: 15000000 },
|
|
32
33
|
apiEquivalent: 201.36,
|
|
33
34
|
knownSubtotal: 201.36,
|
|
34
35
|
},
|
|
@@ -38,6 +39,7 @@ const example = {
|
|
|
38
39
|
records: 1,
|
|
39
40
|
tokens: 7840000,
|
|
40
41
|
tokensPartial: false,
|
|
42
|
+
tokenBreakdown: { input: 440000, output: 400000, cacheRead: 7000000 },
|
|
41
43
|
apiEquivalent: 86.06,
|
|
42
44
|
knownSubtotal: 86.06,
|
|
43
45
|
},
|
|
@@ -59,6 +61,7 @@ const example = {
|
|
|
59
61
|
confidence: 'HIGH',
|
|
60
62
|
demo: true,
|
|
61
63
|
};
|
|
64
|
+
const exampleReceipt = receiptModel(example, new Date(2026, 10, 1, 9, 30));
|
|
62
65
|
document.querySelector('#landing') .innerHTML = /* HTML */ `<header class="landing-nav">
|
|
63
66
|
<a class="brand" href="#"><span class="brand-mark">${productMark}</span>${productNameMarkup}</a>
|
|
64
67
|
<nav class="landing-links" aria-label="Site navigation">
|
|
@@ -150,7 +153,10 @@ document.querySelector('#landing') .innerHTML = /* HTML */ `<header class="landi
|
|
|
150
153
|
<canvas
|
|
151
154
|
id="example-receipt"
|
|
152
155
|
role="img"
|
|
153
|
-
aria-label="Illustrative ${product.name} receipt
|
|
156
|
+
aria-label="Illustrative ${product.name} receipt. Codex and Claude Code example data. ${exampleReceipt.rows
|
|
157
|
+
.filter((row) => row.left)
|
|
158
|
+
.map((row) => row.left + ' ' + row.right)
|
|
159
|
+
.join('. ')}"
|
|
154
160
|
></canvas
|
|
155
161
|
><span>Export PNG ↓</span>
|
|
156
162
|
</div>
|
|
@@ -188,7 +194,7 @@ document.querySelector('#landing') .innerHTML = /* HTML */ `<header class="landi
|
|
|
188
194
|
await receiptFonts();
|
|
189
195
|
drawReceipt(
|
|
190
196
|
document.querySelector ('#example-receipt') ,
|
|
191
|
-
|
|
197
|
+
exampleReceipt,
|
|
192
198
|
'dark',
|
|
193
199
|
'preview',
|
|
194
200
|
);
|