@zhangferry-dev/tokendash 1.0.1 → 1.1.2

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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Codex token pricing configuration.
3
+ *
4
+ * Pricing formula (confirmed by reverse-engineering @ccusage/codex):
5
+ * cost = (inputTokens - cachedInputTokens) * input_rate
6
+ * + cachedInputTokens * cached_rate
7
+ * + outputTokens * output_rate
8
+ *
9
+ * Reasoning tokens are NOT billed separately (included in outputTokens).
10
+ *
11
+ * Update rates from https://openai.com/api/pricing/ when models change.
12
+ * All prices are USD per 1M tokens.
13
+ */
14
+ const MODEL_PRICING = {
15
+ 'gpt-5.4': {
16
+ inputPer1M: 2.50,
17
+ cachedInputPer1M: 0.25,
18
+ outputPer1M: 15.00,
19
+ },
20
+ };
21
+ const DEFAULT_PRICING = {
22
+ inputPer1M: 2.50,
23
+ cachedInputPer1M: 0.25,
24
+ outputPer1M: 15.00,
25
+ };
26
+ /**
27
+ * Calculate cost in USD from token counts and model pricing.
28
+ * Matches the @ccusage/codex calculateCostUSD function exactly.
29
+ */
30
+ export function calculateCost(tokens, models) {
31
+ const model = [...models][0] ?? '';
32
+ const pricing = MODEL_PRICING[model] ?? DEFAULT_PRICING;
33
+ const nonCachedInput = Math.max(tokens.inputTokens - tokens.cachedInputTokens, 0);
34
+ const cachedInput = Math.min(tokens.cachedInputTokens, tokens.inputTokens);
35
+ const outputTokens = tokens.outputTokens;
36
+ const inputCost = (nonCachedInput / 1_000_000) * pricing.inputPer1M;
37
+ const cachedCost = (cachedInput / 1_000_000) * pricing.cachedInputPer1M;
38
+ const outputCost = (outputTokens / 1_000_000) * pricing.outputPer1M;
39
+ return inputCost + cachedCost + outputCost;
40
+ }
41
+ export function getModelPricing(model) {
42
+ return MODEL_PRICING[model] ?? DEFAULT_PRICING;
43
+ }
@@ -1,19 +1,52 @@
1
1
  import express from 'express';
2
+ import { readFileSync } from 'node:fs';
2
3
  import { registerApiRoutes } from './routes/api.js';
3
4
  import { ensureUsageToolsReady } from './ccusage.js';
4
5
  import open from 'open';
6
+ const CLI_USAGE = [
7
+ 'Usage:',
8
+ ' tokendash',
9
+ ' tokendash --version',
10
+ ' tokendash --port <number> [--no-open]',
11
+ ].join('\n');
12
+ function getPackageVersion() {
13
+ const packageJson = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
14
+ return packageJson.version ?? 'unknown';
15
+ }
16
+ function exitWithCliError(message) {
17
+ console.error(message);
18
+ console.error(`\n${CLI_USAGE}`);
19
+ process.exit(1);
20
+ }
5
21
  function parseCliArgs() {
6
22
  const args = process.argv.slice(2);
7
23
  const result = {};
24
+ if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
25
+ result.showVersion = true;
26
+ return result;
27
+ }
8
28
  for (let i = 0; i < args.length; i++) {
9
29
  const arg = args[i];
10
- if (arg === '--port' && i + 1 < args.length) {
11
- result.port = parseInt(args[i + 1], 10);
30
+ if (arg === '--version' || arg === '-v') {
31
+ exitWithCliError('The --version flag must be used by itself.');
32
+ }
33
+ if (arg === '--port') {
34
+ if (i + 1 >= args.length) {
35
+ exitWithCliError('Missing value for --port.');
36
+ }
37
+ const value = parseInt(args[i + 1], 10);
38
+ if (!Number.isInteger(value) || value <= 0) {
39
+ exitWithCliError(`Invalid port value: ${args[i + 1]}`);
40
+ }
41
+ result.port = value;
12
42
  i++;
13
43
  }
14
44
  else if (arg === '--no-open') {
15
45
  result.noOpen = true;
16
46
  }
47
+ else {
48
+ exitWithCliError(`Unsupported argument: ${arg}`);
49
+ }
17
50
  }
18
51
  return result;
19
52
  }
@@ -28,10 +61,55 @@ async function ensureUsageSupportAvailable() {
28
61
  return false;
29
62
  }
30
63
  }
64
+ function resolvePort(value) {
65
+ return Number.isInteger(value) && value && value > 0 ? value : 3456;
66
+ }
67
+ function listen(app, port) {
68
+ return new Promise((resolve, reject) => {
69
+ const server = app.listen(port);
70
+ const handleListening = () => {
71
+ cleanup();
72
+ resolve(server);
73
+ };
74
+ const handleError = (error) => {
75
+ cleanup();
76
+ reject(error);
77
+ };
78
+ const cleanup = () => {
79
+ server.off('listening', handleListening);
80
+ server.off('error', handleError);
81
+ };
82
+ server.once('listening', handleListening);
83
+ server.once('error', handleError);
84
+ });
85
+ }
86
+ async function listenWithPortFallback(app, preferredPort) {
87
+ let port = preferredPort;
88
+ for (let attempt = 0; attempt < 20; attempt++, port++) {
89
+ try {
90
+ const server = await listen(app, port);
91
+ return { server, port, usedFallback: port !== preferredPort };
92
+ }
93
+ catch (error) {
94
+ const err = error;
95
+ if (err.code !== 'EADDRINUSE') {
96
+ throw error;
97
+ }
98
+ }
99
+ }
100
+ throw new Error(`Could not find an available port starting from ${preferredPort}`);
101
+ }
31
102
  async function main() {
32
103
  const args = parseCliArgs();
33
- const port = args.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : 3456);
104
+ if (args.showVersion) {
105
+ console.log(getPackageVersion());
106
+ return;
107
+ }
108
+ const version = getPackageVersion();
109
+ const preferredPort = resolvePort(args.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : undefined));
34
110
  const shouldOpenBrowser = !args.noOpen;
111
+ console.log(`Starting tokendash v${version}...`);
112
+ console.log(`Checking local usage data sources...`);
35
113
  const isUsageSupportAvailable = await ensureUsageSupportAvailable();
36
114
  if (!isUsageSupportAvailable) {
37
115
  process.exit(1);
@@ -53,24 +131,31 @@ async function main() {
53
131
  res.sendFile(clientIndexPath);
54
132
  });
55
133
  }
56
- const server = app.listen(port, () => {
57
- console.log(`ccusage-dashboard running on http://localhost:${port}`);
58
- if (isProduction) {
59
- console.log('Serving production build');
60
- }
61
- else {
62
- console.log('Development mode - use "npm run dev" for full dev experience');
63
- }
64
- });
134
+ const { server, port, usedFallback } = await listenWithPortFallback(app, preferredPort);
135
+ if (usedFallback) {
136
+ console.warn(`tokendash detected that port ${preferredPort} is already in use, switched to http://localhost:${port}`);
137
+ }
138
+ console.log(`tokendash running on http://localhost:${port}`);
139
+ console.log(`API available at http://localhost:${port}/api`);
140
+ if (isProduction) {
141
+ console.log('Serving production build');
142
+ }
143
+ else {
144
+ console.log('Development mode - use "npm run dev" for full dev experience');
145
+ }
65
146
  // Open browser if requested
66
147
  if (shouldOpenBrowser) {
67
148
  // Small delay to ensure server is ready
68
149
  setTimeout(() => {
150
+ console.log('Opening dashboard in your browser...');
69
151
  open(`http://localhost:${port}`).catch((err) => {
70
152
  console.warn('Could not open browser:', err.message);
71
153
  });
72
154
  }, 100);
73
155
  }
156
+ else {
157
+ console.log('Browser auto-open disabled (--no-open)');
158
+ }
74
159
  // Graceful shutdown
75
160
  process.on('SIGTERM', () => {
76
161
  server.close(() => {
@@ -1,15 +1,40 @@
1
1
  import { runCcusage } from '../ccusage.js';
2
2
  import { cache } from '../cache.js';
3
3
  import { validateBlocks } from '../../shared/schemas.js';
4
- import { emptyBlocksResponse } from '../codexNormalizer.js';
4
+ import { getBlocksResponse } from '../codexParser.js';
5
+ import { getClaudeBlocksByProject } from '../claudeBlocksParser.js';
5
6
  export async function getBlocks(req, res) {
6
7
  const agent = req.query.agent || 'claude';
7
- const cacheKey = `blocks:${agent}`;
8
+ const project = req.query.project || undefined;
8
9
  try {
9
10
  if (agent === 'codex') {
10
- res.json(emptyBlocksResponse());
11
+ const projectCacheKey = `blocks:${agent}:${project || 'all'}`;
12
+ const cached = cache.get(projectCacheKey);
13
+ if (cached) {
14
+ res.json(cached);
15
+ return;
16
+ }
17
+ const data = getBlocksResponse({ project: project || null });
18
+ cache.set(projectCacheKey, data);
19
+ res.json(data);
11
20
  return;
12
21
  }
22
+ // Claude Code with project filter: use custom JSONL parser
23
+ if (project) {
24
+ const projectCacheKey = `blocks:claude:${project}`;
25
+ const cached = cache.get(projectCacheKey);
26
+ if (cached) {
27
+ res.json(cached);
28
+ return;
29
+ }
30
+ const blocks = getClaudeBlocksByProject(project);
31
+ const data = { blocks };
32
+ cache.set(projectCacheKey, data);
33
+ res.json(data);
34
+ return;
35
+ }
36
+ // Claude Code without project filter: use ccusage blocks
37
+ const cacheKey = `blocks:${agent}`;
13
38
  const cached = cache.get(cacheKey);
14
39
  if (cached) {
15
40
  res.json(cached);
@@ -1,7 +1,7 @@
1
- import { runCcusage, runCodex } from '../ccusage.js';
1
+ import { runCcusage } from '../ccusage.js';
2
2
  import { cache } from '../cache.js';
3
3
  import { validateDaily } from '../../shared/schemas.js';
4
- import { normalizeCodexDailyResponse } from '../codexNormalizer.js';
4
+ import { getDailyResponse } from '../codexParser.js';
5
5
  export async function getDaily(req, res) {
6
6
  const agent = req.query.agent || 'claude';
7
7
  const cacheKey = `daily:${agent}`;
@@ -12,11 +12,9 @@ export async function getDaily(req, res) {
12
12
  return;
13
13
  }
14
14
  if (agent === 'codex') {
15
- const stdout = await runCodex(['daily']);
16
- const data = JSON.parse(stdout);
17
- const normalized = normalizeCodexDailyResponse(data);
18
- cache.set(cacheKey, normalized);
19
- res.json(normalized);
15
+ const data = getDailyResponse();
16
+ cache.set(cacheKey, data);
17
+ res.json(data);
20
18
  }
21
19
  else {
22
20
  const stdout = await runCcusage(['daily', '--breakdown']);
@@ -1,7 +1,7 @@
1
- import { runCcusage, runCodex } from '../ccusage.js';
1
+ import { runCcusage } from '../ccusage.js';
2
2
  import { cache } from '../cache.js';
3
3
  import { validateProjects } from '../../shared/schemas.js';
4
- import { normalizeCodexProjectsResponse } from '../codexNormalizer.js';
4
+ import { getProjectsResponse } from '../codexParser.js';
5
5
  export async function getProjects(req, res) {
6
6
  const agent = req.query.agent || 'claude';
7
7
  const cacheKey = `projects:${agent}`;
@@ -12,11 +12,9 @@ export async function getProjects(req, res) {
12
12
  return;
13
13
  }
14
14
  if (agent === 'codex') {
15
- const stdout = await runCodex(['daily']);
16
- const data = JSON.parse(stdout);
17
- const normalized = normalizeCodexProjectsResponse(data);
18
- cache.set(cacheKey, normalized);
19
- res.json(normalized);
15
+ const data = getProjectsResponse();
16
+ cache.set(cacheKey, data);
17
+ res.json(data);
20
18
  }
21
19
  else {
22
20
  const stdout = await runCcusage(['daily', '--instances', '--breakdown']);
@@ -53,11 +53,11 @@ export declare const DailyEntrySchema: z.ZodObject<{
53
53
  cost?: number | undefined;
54
54
  }>, "many">>;
55
55
  }, "strip", z.ZodTypeAny, {
56
+ date: string;
56
57
  inputTokens: number;
57
58
  outputTokens: number;
58
59
  cacheCreationTokens: number;
59
60
  cacheReadTokens: number;
60
- date: string;
61
61
  totalTokens: number;
62
62
  totalCost: number;
63
63
  modelsUsed: string[];
@@ -142,11 +142,11 @@ export declare const DailyResponseSchema: z.ZodObject<{
142
142
  cost?: number | undefined;
143
143
  }>, "many">>;
144
144
  }, "strip", z.ZodTypeAny, {
145
+ date: string;
145
146
  inputTokens: number;
146
147
  outputTokens: number;
147
148
  cacheCreationTokens: number;
148
149
  cacheReadTokens: number;
149
- date: string;
150
150
  totalTokens: number;
151
151
  totalCost: number;
152
152
  modelsUsed: string[];
@@ -200,11 +200,11 @@ export declare const DailyResponseSchema: z.ZodObject<{
200
200
  }>;
201
201
  }, "strip", z.ZodTypeAny, {
202
202
  daily: {
203
+ date: string;
203
204
  inputTokens: number;
204
205
  outputTokens: number;
205
206
  cacheCreationTokens: number;
206
207
  cacheReadTokens: number;
207
- date: string;
208
208
  totalTokens: number;
209
209
  totalCost: number;
210
210
  modelsUsed: string[];
@@ -287,11 +287,11 @@ export declare const ProjectEntrySchema: z.ZodObject<{
287
287
  cost?: number | undefined;
288
288
  }>, "many">>;
289
289
  }, "strip", z.ZodTypeAny, {
290
+ date: string;
290
291
  inputTokens: number;
291
292
  outputTokens: number;
292
293
  cacheCreationTokens: number;
293
294
  cacheReadTokens: number;
294
- date: string;
295
295
  totalTokens: number;
296
296
  totalCost: number;
297
297
  modelsUsed: string[];
@@ -324,11 +324,11 @@ export declare const ProjectEntrySchema: z.ZodObject<{
324
324
  }, "strip", z.ZodTypeAny, {
325
325
  projectPath: string;
326
326
  instances: {
327
+ date: string;
327
328
  inputTokens: number;
328
329
  outputTokens: number;
329
330
  cacheCreationTokens: number;
330
331
  cacheReadTokens: number;
331
- date: string;
332
332
  totalTokens: number;
333
333
  totalCost: number;
334
334
  modelsUsed: string[];
@@ -395,11 +395,11 @@ export declare const ProjectsResponseSchema: z.ZodObject<{
395
395
  cost?: number | undefined;
396
396
  }>, "many">>;
397
397
  }, "strip", z.ZodTypeAny, {
398
+ date: string;
398
399
  inputTokens: number;
399
400
  outputTokens: number;
400
401
  cacheCreationTokens: number;
401
402
  cacheReadTokens: number;
402
- date: string;
403
403
  totalTokens: number;
404
404
  totalCost: number;
405
405
  modelsUsed: string[];
@@ -431,11 +431,11 @@ export declare const ProjectsResponseSchema: z.ZodObject<{
431
431
  }>, "many">>>>;
432
432
  }, "strip", z.ZodTypeAny, {
433
433
  projects: Record<string, {
434
+ date: string;
434
435
  inputTokens: number;
435
436
  outputTokens: number;
436
437
  cacheCreationTokens: number;
437
438
  cacheReadTokens: number;
438
- date: string;
439
439
  totalTokens: number;
440
440
  totalCost: number;
441
441
  modelsUsed: string[];
@@ -470,11 +470,11 @@ export declare const ProjectsResponseSchema: z.ZodObject<{
470
470
  }>;
471
471
  export declare function validateDaily(data: unknown): {
472
472
  daily: {
473
+ date: string;
473
474
  inputTokens: number;
474
475
  outputTokens: number;
475
476
  cacheCreationTokens: number;
476
477
  cacheReadTokens: number;
477
- date: string;
478
478
  totalTokens: number;
479
479
  totalCost: number;
480
480
  modelsUsed: string[];
@@ -498,11 +498,11 @@ export declare function validateDaily(data: unknown): {
498
498
  };
499
499
  export declare function validateProjects(data: unknown): {
500
500
  projects: Record<string, {
501
+ date: string;
501
502
  inputTokens: number;
502
503
  outputTokens: number;
503
504
  cacheCreationTokens: number;
504
505
  cacheReadTokens: number;
505
- date: string;
506
506
  totalTokens: number;
507
507
  totalCost: number;
508
508
  modelsUsed: string[];
@@ -546,8 +546,9 @@ export declare const BlocksResponseSchema: z.ZodObject<{
546
546
  models: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
547
547
  }, "strip", z.ZodTypeAny, {
548
548
  entries: number;
549
- totalTokens: number;
550
549
  id: string;
550
+ models: string[];
551
+ totalTokens: number;
551
552
  startTime: string;
552
553
  endTime: string;
553
554
  actualEndTime: string | null;
@@ -560,12 +561,12 @@ export declare const BlocksResponseSchema: z.ZodObject<{
560
561
  cacheReadInputTokens: number;
561
562
  };
562
563
  costUSD: number;
563
- models: string[];
564
564
  }, {
565
565
  id: string;
566
566
  startTime: string;
567
567
  endTime: string;
568
568
  entries?: number | undefined;
569
+ models?: string[] | undefined;
569
570
  totalTokens?: number | undefined;
570
571
  actualEndTime?: string | null | undefined;
571
572
  isActive?: boolean | undefined;
@@ -577,13 +578,13 @@ export declare const BlocksResponseSchema: z.ZodObject<{
577
578
  cacheReadInputTokens?: number | undefined;
578
579
  } | undefined;
579
580
  costUSD?: number | undefined;
580
- models?: string[] | undefined;
581
581
  }>, "many">>;
582
582
  }, "strip", z.ZodTypeAny, {
583
583
  blocks: {
584
584
  entries: number;
585
- totalTokens: number;
586
585
  id: string;
586
+ models: string[];
587
+ totalTokens: number;
587
588
  startTime: string;
588
589
  endTime: string;
589
590
  actualEndTime: string | null;
@@ -596,7 +597,6 @@ export declare const BlocksResponseSchema: z.ZodObject<{
596
597
  cacheReadInputTokens: number;
597
598
  };
598
599
  costUSD: number;
599
- models: string[];
600
600
  }[];
601
601
  }, {
602
602
  blocks?: {
@@ -604,6 +604,7 @@ export declare const BlocksResponseSchema: z.ZodObject<{
604
604
  startTime: string;
605
605
  endTime: string;
606
606
  entries?: number | undefined;
607
+ models?: string[] | undefined;
607
608
  totalTokens?: number | undefined;
608
609
  actualEndTime?: string | null | undefined;
609
610
  isActive?: boolean | undefined;
@@ -615,14 +616,14 @@ export declare const BlocksResponseSchema: z.ZodObject<{
615
616
  cacheReadInputTokens?: number | undefined;
616
617
  } | undefined;
617
618
  costUSD?: number | undefined;
618
- models?: string[] | undefined;
619
619
  }[] | undefined;
620
620
  }>;
621
621
  export declare function validateBlocks(data: unknown): {
622
622
  blocks: {
623
623
  entries: number;
624
- totalTokens: number;
625
624
  id: string;
625
+ models: string[];
626
+ totalTokens: number;
626
627
  startTime: string;
627
628
  endTime: string;
628
629
  actualEndTime: string | null;
@@ -635,6 +636,5 @@ export declare function validateBlocks(data: unknown): {
635
636
  cacheReadInputTokens: number;
636
637
  };
637
638
  costUSD: number;
638
- models: string[];
639
639
  }[];
640
640
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhangferry-dev/tokendash",
3
- "version": "1.0.1",
3
+ "version": "1.1.2",
4
4
  "type": "module",
5
5
  "description": "Token Usage Analytics Dashboard",
6
6
  "publishConfig": {
@@ -9,7 +9,7 @@
9
9
  "main": "./dist/server/index.js",
10
10
  "types": "./dist/server/index.d.ts",
11
11
  "bin": {
12
- "tokendash": "./bin/tokendash.js"
12
+ "tokendash": "bin/tokendash.js"
13
13
  },
14
14
  "files": [
15
15
  "dist",
@@ -1 +0,0 @@
1
- /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:"Geist", "Geist Fallback", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-mono:"Geist Mono", "Geist Mono Fallback", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.absolute{position:absolute}.relative{position:relative}.static{position:static}.start{inset-inline-start:var(--spacing)}.bottom-full{bottom:100%}.left-1\/2{left:50%}.z-20{z-index:20}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-10{margin-left:calc(var(--spacing) * 10)}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-4{height:calc(var(--spacing) * 4)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-20{height:calc(var(--spacing) * 20)}.h-48{height:calc(var(--spacing) * 48)}.h-72{height:calc(var(--spacing) * 72)}.h-\[22px\]{height:22px}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-dvh{min-height:100dvh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-4{width:calc(var(--spacing) * 4)}.w-8{width:calc(var(--spacing) * 8)}.w-48{width:calc(var(--spacing) * 48)}.w-72{width:calc(var(--spacing) * 72)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-\[200px\]{max-width:200px}.max-w-\[220px\]{max-width:220px}.max-w-\[1440px\]{max-width:1440px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-around{justify-content:space-around}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\[3px\]{border-radius:3px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-emerald-100\/50{border-color:#d0fae580}@supports (color:color-mix(in lab,red,red)){.border-emerald-100\/50{border-color:color-mix(in oklab,var(--color-emerald-100) 50%,transparent)}}.border-red-200\/60{border-color:#ffcaca99}@supports (color:color-mix(in lab,red,red)){.border-red-200\/60{border-color:color-mix(in oklab,var(--color-red-200) 60%,transparent)}}.border-stone-100{border-color:var(--color-stone-100)}.border-stone-200{border-color:var(--color-stone-200)}.border-stone-200\/40{border-color:#e7e5e466}@supports (color:color-mix(in lab,red,red)){.border-stone-200\/40{border-color:color-mix(in oklab,var(--color-stone-200) 40%,transparent)}}.border-stone-200\/50{border-color:#e7e5e480}@supports (color:color-mix(in lab,red,red)){.border-stone-200\/50{border-color:color-mix(in oklab,var(--color-stone-200) 50%,transparent)}}.bg-emerald-50\/50{background-color:#ecfdf580}@supports (color:color-mix(in lab,red,red)){.bg-emerald-50\/50{background-color:color-mix(in oklab,var(--color-emerald-50) 50%,transparent)}}.bg-emerald-200\/50{background-color:#a4f4cf80}@supports (color:color-mix(in lab,red,red)){.bg-emerald-200\/50{background-color:color-mix(in oklab,var(--color-emerald-200) 50%,transparent)}}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-red-50{background-color:var(--color-red-50)}.bg-stone-50\/40{background-color:#fafaf966}@supports (color:color-mix(in lab,red,red)){.bg-stone-50\/40{background-color:color-mix(in oklab,var(--color-stone-50) 40%,transparent)}}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200\/50{background-color:#e7e5e480}@supports (color:color-mix(in lab,red,red)){.bg-stone-200\/50{background-color:color-mix(in oklab,var(--color-stone-200) 50%,transparent)}}.bg-stone-200\/60{background-color:#e7e5e499}@supports (color:color-mix(in lab,red,red)){.bg-stone-200\/60{background-color:color-mix(in oklab,var(--color-stone-200) 60%,transparent)}}.bg-stone-800{background-color:var(--color-stone-800)}.bg-stone-900{background-color:var(--color-stone-900)}.bg-white{background-color:var(--color-white)}.bg-\[radial-gradient\(ellipse_at_top\,\#eef2ff_0\%\,\#faf9f7_35\%\,\#faf9f7_100\%\)\]{background-image:radial-gradient(at top,#eef2ff,#faf9f7 35%,#faf9f7)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2\.5{padding-top:calc(var(--spacing) * 2.5)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-600\/70{color:#009767b3}@supports (color:color-mix(in lab,red,red)){.text-emerald-600\/70{color:color-mix(in oklab,var(--color-emerald-600) 70%,transparent)}}.text-emerald-700\/80{color:#007956cc}@supports (color:color-mix(in lab,red,red)){.text-emerald-700\/80{color:color-mix(in oklab,var(--color-emerald-700) 80%,transparent)}}.text-indigo-500\/70{color:#625fffb3}@supports (color:color-mix(in lab,red,red)){.text-indigo-500\/70{color:color-mix(in oklab,var(--color-indigo-500) 70%,transparent)}}.text-indigo-600{color:var(--color-indigo-600)}.text-red-600{color:var(--color-red-600)}.text-stone-400{color:var(--color-stone-400)}.text-stone-500{color:var(--color-stone-500)}.text-stone-600{color:var(--color-stone-600)}.text-stone-700{color:var(--color-stone-700)}.text-stone-800{color:var(--color-stone-800)}.text-stone-900{color:var(--color-stone-900)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-\[0_1px_3px_rgba\(0\,0\,0\,0\.1\)\]{--tw-shadow:0 1px 3px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_1px_3px_rgba\(120\,113\,108\,0\.06\)\]{--tw-shadow:0 1px 3px var(--tw-shadow-color,#78716c0f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_8px_30px_rgba\(120\,113\,108\,0\.12\)\]{--tw-shadow:0 8px 30px var(--tw-shadow-color,#78716c1f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-stone-900\/5{--tw-ring-color:#1c19170d}@supports (color:color-mix(in lab,red,red)){.ring-stone-900\/5{--tw-ring-color:color-mix(in oklab, var(--color-stone-900) 5%, transparent)}}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.outline-none{--tw-outline-style:none;outline-style:none}@media(hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:z-10:hover{z-index:10}.hover\:bg-stone-50:hover{background-color:var(--color-stone-50)}.hover\:bg-stone-50\/60:hover{background-color:#fafaf999}@supports (color:color-mix(in lab,red,red)){.hover\:bg-stone-50\/60:hover{background-color:color-mix(in oklab,var(--color-stone-50) 60%,transparent)}}.hover\:bg-stone-200\/50:hover{background-color:#e7e5e480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-stone-200\/50:hover{background-color:color-mix(in oklab,var(--color-stone-200) 50%,transparent)}}.hover\:text-stone-800:hover{color:var(--color-stone-800)}.hover\:shadow-\[0_4px_12px_rgba\(120\,113\,108\,0\.09\)\]:hover{--tw-shadow:0 4px 12px var(--tw-shadow-color,#78716c17);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-emerald-400:hover{--tw-ring-color:var(--color-emerald-400)}.hover\:ring-offset-1:hover{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-indigo-500\/20:focus{--tw-ring-color:#625fff33}@supports (color:color-mix(in lab,red,red)){.focus\:ring-indigo-500\/20:focus{--tw-ring-color:color-mix(in oklab, var(--color-indigo-500) 20%, transparent)}}@media(min-width:40rem){.sm\:block{display:block}}@media(min-width:48rem){.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}}@media(min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}}html,body{background-color:#faf9f7!important}@font-face{font-family:Geist;src:url(https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/Geist-Variable.woff2)format("woff2");font-weight:100 900;font-display:swap;font-style:normal}@font-face{font-family:Geist Mono;src:url(https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/GeistMono-Variable.woff2)format("woff2");font-weight:100 900;font-display:swap;font-style:normal}body:before{content:"";z-index:9999;pointer-events:none;opacity:.015;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");background-repeat:repeat;background-size:200px 200px;position:fixed;top:0;right:0;bottom:0;left:0}.recharts-cartesian-grid-horizontal line,.recharts-cartesian-grid-vertical line{stroke:#e7e5e4}.recharts-text{fill:#78716c;font-family:var(--font-sans);font-size:11px}.recharts-legend-item-text{color:#57534e;font-size:12px}.recharts-tooltip-wrapper{outline:none}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:#c8c4be;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#a8a29e}@keyframes shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.skeleton{background:linear-gradient(90deg,#e7e5e4 25%,#d6d3d1,#e7e5e4 75%) 0 0/200% 100%;animation:1.5s ease-in-out infinite shimmer}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}
@@ -1,4 +0,0 @@
1
- import type { DailyResponse, ProjectsResponse, BlocksResponse } from '../shared/types.js';
2
- export declare function normalizeCodexDailyResponse(data: unknown): DailyResponse;
3
- export declare function normalizeCodexProjectsResponse(data: unknown): ProjectsResponse;
4
- export declare function emptyBlocksResponse(): BlocksResponse;
@@ -1,50 +0,0 @@
1
- function parseCodexDate(dateStr) {
2
- // "Mar 31, 2026" → "2026-03-31"
3
- const d = new Date(dateStr);
4
- return d.toISOString().slice(0, 10);
5
- }
6
- function normalizeCodexDaily(entry) {
7
- const modelBreakdowns = Object.entries(entry.models).map(([name, m]) => ({
8
- modelName: name,
9
- inputTokens: m.inputTokens,
10
- outputTokens: m.outputTokens,
11
- cacheCreationTokens: 0,
12
- cacheReadTokens: m.cachedInputTokens,
13
- cost: entry.costUSD / Object.keys(entry.models).length,
14
- }));
15
- return {
16
- date: parseCodexDate(entry.date),
17
- inputTokens: entry.inputTokens,
18
- outputTokens: entry.outputTokens,
19
- cacheCreationTokens: 0,
20
- cacheReadTokens: entry.cachedInputTokens,
21
- totalTokens: entry.totalTokens,
22
- totalCost: entry.costUSD,
23
- modelsUsed: Object.keys(entry.models),
24
- modelBreakdowns,
25
- };
26
- }
27
- export function normalizeCodexDailyResponse(data) {
28
- const codex = data;
29
- return {
30
- daily: (codex.daily || []).map(normalizeCodexDaily),
31
- totals: {
32
- inputTokens: codex.totals?.inputTokens ?? 0,
33
- outputTokens: codex.totals?.outputTokens ?? 0,
34
- cacheCreationTokens: 0,
35
- cacheReadTokens: codex.totals?.cachedInputTokens ?? 0,
36
- totalTokens: codex.totals?.totalTokens ?? 0,
37
- totalCost: codex.totals?.costUSD ?? 0,
38
- },
39
- };
40
- }
41
- export function normalizeCodexProjectsResponse(data) {
42
- const codex = data;
43
- const entries = (codex.daily || []).map(normalizeCodexDaily);
44
- return {
45
- projects: { 'OpenAI Codex': entries },
46
- };
47
- }
48
- export function emptyBlocksResponse() {
49
- return { blocks: [] };
50
- }