@subvalue/cli 0.1.3 → 0.1.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.
- package/PRE_RELEASE.md +6 -6
- package/README.md +5 -3
- package/dist/PRICING.md +15 -16
- package/dist/packages/cli/src/index.js +109 -20
- package/dist/packages/cli/src/protocol.js +13 -0
- package/dist/packages/cli/src/runtime.js +44 -14
- package/dist/packages/cli/src/server.js +306 -62
- package/dist/packages/core/src/calendar.js +41 -18
- package/dist/packages/core/src/coverage.js +12 -0
- package/dist/packages/core/src/demo.js +61 -9
- package/dist/packages/core/src/metadata.js +52 -9
- package/dist/packages/core/src/pricing/current.js +346 -297
- package/dist/packages/core/src/pricing/index.js +373 -73
- package/dist/packages/core/src/providers/claude/index.js +93 -21
- package/dist/packages/core/src/providers/codex/index.js +213 -46
- package/dist/packages/core/src/scanner.js +226 -46
- package/dist/packages/core/src/security.js +127 -34
- package/dist/packages/core/src/storage.js +215 -47
- package/dist/packages/core/src/subscriptions.js +146 -21
- package/dist/packages/core/src/summary.js +408 -59
- package/dist/packages/core/src/types.js +71 -17
- package/dist/packages/core/src/usage-json.js +166 -47
- package/dist/packages/receipt/src/index.js +386 -82
- package/dist/packages/ui/src/brand.js +42 -27
- package/dist/public/app.css +2280 -61
- package/dist/public/apps/web/src/landing.js +200 -8
- package/dist/public/apps/web/src/main.js +1143 -98
- package/dist/public/index.html +24 -1
- package/dist/public/landing.css +3235 -80
- package/dist/public/landing.html +21 -1
- package/dist/public/packages/core/src/calendar.js +41 -18
- package/dist/public/packages/core/src/coverage.js +12 -0
- package/dist/public/packages/core/src/subscriptions.js +146 -21
- package/dist/public/packages/receipt/src/index.js +386 -82
- package/dist/public/packages/ui/src/brand.js +42 -27
- package/dist/public/privacy.css +197 -9
- package/package.json +1 -1
|
@@ -1,64 +1,413 @@
|
|
|
1
|
-
|
|
2
|
-
import {priceRecord,catalog,
|
|
3
|
-
import {fullCalendarMonths,shiftCalendarDays} from './calendar.js';
|
|
4
|
-
export {fullCalendarMonths} from './calendar.js';
|
|
5
|
-
const dayFormatter=new Intl.DateTimeFormat('en-CA',{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
|
|
2
|
+
import { priceRecord, catalog, } from './pricing/index.js';
|
|
3
|
+
import { fullCalendarMonths, shiftCalendarDays } from './calendar.js';
|
|
4
|
+
export { fullCalendarMonths } from './calendar.js';
|
|
5
|
+
const dayFormatter = new Intl.DateTimeFormat('en-CA', {
|
|
6
|
+
year: 'numeric',
|
|
7
|
+
month: '2-digit',
|
|
8
|
+
day: '2-digit',
|
|
9
|
+
});
|
|
10
|
+
function localDay(timestamp ) {
|
|
11
|
+
const parts = dayFormatter.formatToParts(new Date(timestamp));
|
|
12
|
+
return ['year', 'month', 'day']
|
|
13
|
+
.map((type) => parts.find((part) => part.type === type) .value)
|
|
14
|
+
.join('-');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export function dateRange(
|
|
26
|
+
preset ,
|
|
27
|
+
now = new Date(),
|
|
28
|
+
from ,
|
|
29
|
+
to ,
|
|
30
|
+
month ,
|
|
31
|
+
anchor ,
|
|
32
|
+
) {
|
|
33
|
+
const today = localDay(now.toISOString());
|
|
34
|
+
if (anchor !== undefined) {
|
|
35
|
+
if (
|
|
36
|
+
(preset !== '7d' && preset !== '30d') ||
|
|
37
|
+
!/^(?:19|20|21)\d{2}-\d{2}-\d{2}$/.test(anchor) ||
|
|
38
|
+
shiftCalendarDays(anchor, 0) === null
|
|
39
|
+
)
|
|
40
|
+
throw new Error('Choose a valid period');
|
|
41
|
+
now = new Date(anchor + 'T12:00:00');
|
|
17
42
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
|
|
44
|
+
let start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 29);
|
|
45
|
+
let label = 'Last 30 days';
|
|
46
|
+
if (preset === '7d') {
|
|
47
|
+
start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
|
|
48
|
+
label = 'Last 7 days';
|
|
49
|
+
} else if (preset === 'month') {
|
|
50
|
+
if (month !== undefined && !/^(?:19|20|21)\d{2}-(?:0[1-9]|1[0-2])$/.test(month))
|
|
51
|
+
throw new Error('Choose a valid month');
|
|
52
|
+
const year = month ? Number(month.slice(0, 4)) : now.getFullYear(),
|
|
53
|
+
index = month ? Number(month.slice(5, 7)) - 1 : now.getMonth();
|
|
54
|
+
start = new Date(year, index, 1);
|
|
55
|
+
end.setTime(+new Date(year, index + 1, 1));
|
|
56
|
+
label = start.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
|
57
|
+
} else if (preset === 'custom') {
|
|
58
|
+
if (!from || !to || !/^\d{4}-\d{2}-\d{2}$/.test(from) || !/^\d{4}-\d{2}-\d{2}$/.test(to))
|
|
59
|
+
throw new Error('Choose a valid date range');
|
|
60
|
+
start = new Date(from + 'T00:00:00');
|
|
61
|
+
const last = new Date(to + 'T00:00:00');
|
|
62
|
+
const matches = (date , text ) =>
|
|
63
|
+
Number.isFinite(+date) &&
|
|
64
|
+
date.getFullYear() === Number(text.slice(0, 4)) &&
|
|
65
|
+
date.getMonth() + 1 === Number(text.slice(5, 7)) &&
|
|
66
|
+
date.getDate() === Number(text.slice(8, 10));
|
|
67
|
+
if (
|
|
68
|
+
!matches(start, from) ||
|
|
69
|
+
!matches(last, to) ||
|
|
70
|
+
start > last ||
|
|
71
|
+
+last - +start > 36600 * 864e5
|
|
72
|
+
)
|
|
73
|
+
throw new Error('Choose a valid date range');
|
|
74
|
+
end.setTime(+new Date(last.getFullYear(), last.getMonth(), last.getDate() + 1));
|
|
75
|
+
label = `${from} — ${to}`;
|
|
76
|
+
} else if (preset !== '30d') throw new Error('Invalid period');
|
|
77
|
+
const calendarFrom = localDay(start.toISOString()),
|
|
78
|
+
calendarTo = localDay(new Date(+end - 1).toISOString());
|
|
79
|
+
if (anchor !== undefined && anchor !== today) label = `${calendarFrom} — ${calendarTo}`;
|
|
80
|
+
const range = {
|
|
81
|
+
from: start.toISOString(),
|
|
82
|
+
until: end.toISOString(),
|
|
83
|
+
label,
|
|
84
|
+
preset,
|
|
85
|
+
calendarFrom,
|
|
86
|
+
calendarTo,
|
|
87
|
+
};
|
|
88
|
+
return { ...range, calendarMonths: fullCalendarMonths(range) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
export function subscriptionForRange(monthly , range ) {
|
|
99
|
+
const months = fullCalendarMonths(range);
|
|
100
|
+
return months === null ? null : monthly * months;
|
|
26
101
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
function neutral(reason , subscription = null) {
|
|
165
|
+
return { subscription, value: null, roi: null, breakEven: null, outcome: 'neutral', reason };
|
|
30
166
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
167
|
+
export function summarize(
|
|
168
|
+
records ,
|
|
169
|
+
statuses ,
|
|
170
|
+
settings ,
|
|
171
|
+
range ,
|
|
172
|
+
c = catalog,
|
|
173
|
+
demo = false,
|
|
174
|
+
) {
|
|
175
|
+
const selected = records.filter(
|
|
176
|
+
(r) => r.timestamp && r.timestamp >= range.from && r.timestamp < range.until,
|
|
177
|
+
);
|
|
178
|
+
const prices = new Map(selected.map((r) => [r.id, priceRecord(r, c)]));
|
|
179
|
+
const dailyMap = new Map ();
|
|
180
|
+
for (const r of selected) {
|
|
181
|
+
const date = localDay(r.timestamp );
|
|
182
|
+
const d = dailyMap.get(date) ?? { date, codex: null, claude: null, hasUsage: true };
|
|
183
|
+
const cost = prices.get(r.id) .usd;
|
|
184
|
+
if (cost !== null) d[r.provider] = (d[r.provider] ?? 0) + cost;
|
|
185
|
+
dailyMap.set(date, d);
|
|
186
|
+
}
|
|
187
|
+
const daily = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
188
|
+
const providers = (['codex', 'claude'] ).map((provider) => {
|
|
189
|
+
const rows = selected.filter((r) => r.provider === provider),
|
|
190
|
+
all = records.filter((r) => r.provider === provider),
|
|
191
|
+
source = statuses.find((s) => s.provider === provider);
|
|
192
|
+
const issues = {};
|
|
193
|
+
const costs = rows.map((r) => prices.get(r.id) );
|
|
194
|
+
for (const p of costs) if (p.reason) issues[p.reason] = (issues[p.reason] ?? 0) + 1;
|
|
195
|
+
const undated = all.filter((r) => !r.timestamp).length;
|
|
196
|
+
if (undated) issues['undated-events'] = undated;
|
|
197
|
+
const priced = costs.filter((p) => p.usd !== null),
|
|
198
|
+
subtotal = priced.length ? priced.reduce((n, p) => n + p.usd , 0) : null;
|
|
199
|
+
const historyPartial = !demo; // Local transcripts cannot prove that all historical requests were retained.
|
|
200
|
+
const completePricing = rows.length > 0 && priced.length === rows.length;
|
|
201
|
+
const apiEquivalent = completePricing ? subtotal : null;
|
|
202
|
+
const billing = settings.billing[provider];
|
|
203
|
+
const subscription =
|
|
204
|
+
billing.mode === 'SUBSCRIPTION' && billing.monthly !== null
|
|
205
|
+
? subscriptionForRange(billing.monthly, range)
|
|
206
|
+
: null;
|
|
207
|
+
let comparison = neutral(
|
|
208
|
+
billing.mode === 'API'
|
|
209
|
+
? 'API usage'
|
|
210
|
+
: billing.mode === 'MIXED'
|
|
211
|
+
? 'Mixed billing'
|
|
212
|
+
: billing.mode === 'UNKNOWN'
|
|
213
|
+
? 'Billing not set'
|
|
214
|
+
: fullCalendarMonths(range) === null
|
|
215
|
+
? 'Choose a full month'
|
|
216
|
+
: subscription === null
|
|
217
|
+
? 'Subscription price not set'
|
|
218
|
+
: !completePricing
|
|
219
|
+
? 'Pricing unavailable'
|
|
220
|
+
: 'Partial history',
|
|
221
|
+
subscription,
|
|
222
|
+
);
|
|
223
|
+
if (completePricing && subscription !== null) {
|
|
224
|
+
const value = apiEquivalent - subscription;
|
|
225
|
+
let accumulated = 0;
|
|
226
|
+
let breakEven = null;
|
|
227
|
+
for (const d of daily) {
|
|
228
|
+
accumulated += d[provider] ?? 0;
|
|
229
|
+
if (accumulated >= subscription) {
|
|
230
|
+
breakEven = d.date;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
comparison = {
|
|
235
|
+
subscription,
|
|
236
|
+
value,
|
|
237
|
+
roi: subscription > 0 ? apiEquivalent / subscription : null,
|
|
238
|
+
breakEven: historyPartial ? null : breakEven,
|
|
239
|
+
outcome: historyPartial
|
|
240
|
+
? 'neutral'
|
|
241
|
+
: value > 0
|
|
242
|
+
? 'positive'
|
|
243
|
+
: value < 0
|
|
244
|
+
? 'negative'
|
|
245
|
+
: 'neutral',
|
|
246
|
+
reason: historyPartial ? 'Observed usage · Partial history' : null,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const sumTokens = (
|
|
250
|
+
key ,
|
|
251
|
+
) =>
|
|
252
|
+
rows.length && rows.every((r) => r[key] !== null)
|
|
253
|
+
? rows.reduce((total, r) => total + r[key] , 0)
|
|
254
|
+
: null;
|
|
255
|
+
const tokenBreakdown = {
|
|
256
|
+
input: sumTokens('input_tokens'),
|
|
257
|
+
output: sumTokens('output_tokens'),
|
|
258
|
+
cacheRead: sumTokens('cached_input_tokens'),
|
|
259
|
+
};
|
|
260
|
+
const tokensKnown = rows.filter((r) => r.total_tokens !== null);
|
|
261
|
+
const timestamps = all
|
|
262
|
+
.map((r) => r.timestamp)
|
|
263
|
+
.filter((t) => !!t)
|
|
264
|
+
.sort();
|
|
265
|
+
const hasUsage = rows.some((r) => r.total_tokens !== 0);
|
|
266
|
+
const sourceIncomplete =
|
|
267
|
+
source?.errors ||
|
|
268
|
+
source?.missingFiles ||
|
|
269
|
+
source?.diagnostics.malformed ||
|
|
270
|
+
source?.diagnostics.partialTail ||
|
|
271
|
+
source?.diagnostics.oversize ||
|
|
272
|
+
source?.diagnostics.unsupported;
|
|
273
|
+
return {
|
|
274
|
+
provider,
|
|
275
|
+
detected: source?.detected ?? false,
|
|
276
|
+
visible: hasUsage || settings.include[provider],
|
|
277
|
+
status: rows.length ? (hasUsage ? 'USAGE_FOUND' : 'NO_USAGE') : 'NO_DATA',
|
|
278
|
+
records: rows.length,
|
|
279
|
+
importedRecords: all.length,
|
|
280
|
+
tokens: tokensKnown.length ? tokensKnown.reduce((n, r) => n + r.total_tokens , 0) : null,
|
|
281
|
+
tokensPartial: tokensKnown.length !== rows.length,
|
|
282
|
+
tokenBreakdown,
|
|
283
|
+
apiEquivalent,
|
|
284
|
+
knownSubtotal: subtotal,
|
|
285
|
+
pricedRecords: priced.length,
|
|
286
|
+
priceCoverage: rows.length ? priced.length / rows.length : null,
|
|
287
|
+
confidence: !rows.length
|
|
288
|
+
? 'NO DATA'
|
|
289
|
+
: rows.some((r) => r.quality === 'INCOMPLETE')
|
|
290
|
+
? 'INCOMPLETE'
|
|
291
|
+
: sourceIncomplete || undated || rows.some((r) => r.quality === 'LOW')
|
|
292
|
+
? 'LOW'
|
|
293
|
+
: rows.some((r) => r.quality === 'MEDIUM')
|
|
294
|
+
? 'MEDIUM'
|
|
295
|
+
: 'HIGH',
|
|
296
|
+
historyPartial,
|
|
297
|
+
comparison,
|
|
298
|
+
mode: billing.mode,
|
|
299
|
+
monthly: billing.monthly,
|
|
300
|
+
first: timestamps[0] ?? null,
|
|
301
|
+
last: timestamps.at(-1) ?? null,
|
|
302
|
+
days: new Set(timestamps.map((t) => t.slice(0, 10))).size,
|
|
303
|
+
issues,
|
|
304
|
+
} ;
|
|
305
|
+
});
|
|
306
|
+
const active = providers.filter((p) => p.records > 0),
|
|
307
|
+
pricedRecords = active.reduce((n, p) => n + p.pricedRecords, 0),
|
|
308
|
+
count = selected.length;
|
|
309
|
+
const known = active.filter((p) => p.knownSubtotal !== null);
|
|
310
|
+
const knownSubtotal = known.length ? known.reduce((n, p) => n + p.knownSubtotal , 0) : null;
|
|
311
|
+
const apiEquivalent = count > 0 && pricedRecords === count ? knownSubtotal : null;
|
|
312
|
+
const subs = active.filter((p) => p.mode === 'SUBSCRIPTION');
|
|
313
|
+
let comparison = neutral(
|
|
314
|
+
active.some((p) => p.mode === 'API' || p.mode === 'MIXED' || p.mode === 'UNKNOWN')
|
|
315
|
+
? 'Subscription comparison unavailable'
|
|
316
|
+
: 'Partial history',
|
|
317
|
+
);
|
|
318
|
+
if (
|
|
319
|
+
subs.length &&
|
|
320
|
+
subs.length === active.length &&
|
|
321
|
+
subs.every((p) => p.comparison.value !== null)
|
|
322
|
+
) {
|
|
323
|
+
const subscription = subs.reduce((n, p) => n + p.comparison.subscription , 0);
|
|
324
|
+
const value = apiEquivalent - subscription;
|
|
325
|
+
let accumulated = 0,
|
|
326
|
+
breakEven = null;
|
|
327
|
+
if (demo)
|
|
328
|
+
for (const d of daily) {
|
|
329
|
+
accumulated += (d.codex ?? 0) + (d.claude ?? 0);
|
|
330
|
+
if (accumulated >= subscription) {
|
|
331
|
+
breakEven = d.date;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
comparison = {
|
|
336
|
+
subscription,
|
|
337
|
+
value,
|
|
338
|
+
roi: subscription > 0 ? apiEquivalent / subscription : null,
|
|
339
|
+
breakEven,
|
|
340
|
+
outcome: demo ? (value > 0 ? 'positive' : value < 0 ? 'negative' : 'neutral') : 'neutral',
|
|
341
|
+
reason: demo ? null : 'Observed usage · Partial history',
|
|
342
|
+
};
|
|
343
|
+
} else if (
|
|
344
|
+
subs.length &&
|
|
345
|
+
subs.length === active.length &&
|
|
346
|
+
subs.every((p) => p.comparison.subscription !== null)
|
|
347
|
+
)
|
|
348
|
+
comparison.subscription = subs.reduce((n, p) => n + p.comparison.subscription , 0);
|
|
349
|
+
const confidence = active.length
|
|
350
|
+
? ['INCOMPLETE', 'LOW', 'MEDIUM', 'HIGH'].find((q) => active.some((p) => p.confidence === q))
|
|
351
|
+
: 'NO DATA';
|
|
352
|
+
const groups = new Map ();
|
|
353
|
+
for (const r of selected) {
|
|
354
|
+
const key = r.provider + ':' + (r.model_raw ?? 'Unknown model');
|
|
355
|
+
let rows = groups.get(key);
|
|
356
|
+
if (!rows) {
|
|
357
|
+
rows = [];
|
|
358
|
+
groups.set(key, rows);
|
|
359
|
+
}
|
|
360
|
+
rows.push(r);
|
|
361
|
+
}
|
|
362
|
+
const models = [...groups.values()].map((rows) => {
|
|
363
|
+
const first = rows[0],
|
|
364
|
+
p = prices.get(first.id) ;
|
|
365
|
+
const priced = rows.filter((r) => prices.get(r.id) .usd !== null),
|
|
366
|
+
known = rows.filter((r) => r.total_tokens !== null);
|
|
367
|
+
const issues = {};
|
|
368
|
+
for (const r of rows) {
|
|
369
|
+
const reason = prices.get(r.id) .reason;
|
|
370
|
+
if (reason) issues[reason] = (issues[reason] ?? 0) + 1;
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
provider: first.provider,
|
|
374
|
+
raw: first.model_raw ?? 'Unknown model',
|
|
375
|
+
canonical: p.canonical,
|
|
376
|
+
records: rows.length,
|
|
377
|
+
pricedRecords: priced.length,
|
|
378
|
+
tokens: known.length ? known.reduce((n, r) => n + r.total_tokens , 0) : null,
|
|
379
|
+
pricedTokens: priced.some((r) => r.total_tokens !== null)
|
|
380
|
+
? priced.reduce((n, r) => n + (r.total_tokens ?? 0), 0)
|
|
381
|
+
: null,
|
|
382
|
+
knownSubtotal: priced.length ? priced.reduce((n, r) => n + prices.get(r.id) .usd , 0) : null,
|
|
383
|
+
mappingConfidence: p.mapping?.confidence ?? null,
|
|
384
|
+
mappingSource: p.mapping?.source ?? null,
|
|
385
|
+
issues,
|
|
386
|
+
};
|
|
55
387
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
388
|
+
return {
|
|
389
|
+
range,
|
|
390
|
+
providers,
|
|
391
|
+
total: {
|
|
392
|
+
apiEquivalent,
|
|
393
|
+
knownSubtotal,
|
|
394
|
+
records: count,
|
|
395
|
+
pricedRecords,
|
|
396
|
+
priceCoverage: count ? pricedRecords / count : null,
|
|
397
|
+
comparison,
|
|
398
|
+
},
|
|
399
|
+
daily,
|
|
400
|
+
confidence,
|
|
401
|
+
historyPartial: !demo,
|
|
402
|
+
unresolvedModels: [
|
|
403
|
+
...new Set(
|
|
404
|
+
selected
|
|
405
|
+
.filter((r) => !prices.get(r.id) .canonical)
|
|
406
|
+
.map((r) => r.model_raw ?? 'Unknown model'),
|
|
407
|
+
),
|
|
408
|
+
],
|
|
409
|
+
models,
|
|
410
|
+
pricingVersion: c.version,
|
|
411
|
+
demo,
|
|
412
|
+
};
|
|
64
413
|
}
|
|
@@ -2,29 +2,83 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
14
32
|
|
|
15
33
|
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
19
45
|
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
23
56
|
|
|
24
57
|
|
|
25
|
-
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
26
61
|
|
|
27
|
-
|
|
62
|
+
|
|
28
63
|
|
|
29
|
-
export const defaultSettings = () => ({
|
|
30
|
-
|
|
64
|
+
export const defaultSettings = () => ({
|
|
65
|
+
onboarded: false,
|
|
66
|
+
currency: 'USD',
|
|
67
|
+
include: { codex: false, claude: false },
|
|
68
|
+
billing: {
|
|
69
|
+
codex: { mode: 'UNKNOWN', monthly: null },
|
|
70
|
+
claude: { mode: 'UNKNOWN', monthly: null },
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
export const emptyDiagnostics = () => ({
|
|
74
|
+
malformed: 0,
|
|
75
|
+
inherited: 0,
|
|
76
|
+
repeated: 0,
|
|
77
|
+
resets: 0,
|
|
78
|
+
oversize: 0,
|
|
79
|
+
unsupported: 0,
|
|
80
|
+
partialTail: false,
|
|
81
|
+
first: null,
|
|
82
|
+
last: null,
|
|
83
|
+
records: 0,
|
|
84
|
+
});
|