@diogonzafe/tokenwatch 0.9.0 → 0.10.1
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 +41 -1
- package/dist/adapters.cjs +16 -6
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +2 -1
- package/dist/adapters.d.ts +2 -1
- package/dist/adapters.js +16 -6
- package/dist/adapters.js.map +1 -1
- package/dist/cli.js +114 -11
- package/dist/cli.js.map +1 -1
- package/dist/exporters.d.cts +1 -1
- package/dist/exporters.d.ts +1 -1
- package/dist/{index-fD5QLTWg.d.cts → index-B5OF0YCl.d.cts} +9 -0
- package/dist/{index-fD5QLTWg.d.ts → index-B5OF0YCl.d.ts} +9 -0
- package/dist/index.cjs +57 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +57 -27
- package/dist/index.js.map +1 -1
- package/dist/langchain.d.cts +1 -1
- package/dist/langchain.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/postgres.ts","../src/adapters/mysql.ts","../src/adapters/mongodb.ts","../bin/cli.ts","../src/core/sync.ts","../src/core/storage.ts","../src/core/tracker.ts","../src/core/pricing.ts","../src/exporters/cloud.ts","../src/core/suggestions.ts","../prices.json","../src/dashboard/server.ts","../src/dashboard/data.ts","../src/dashboard/html.ts"],"sourcesContent":["import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for PostgreSQL using the `pg` driver.\n *\n * Install peer dep: npm install pg\n * Types (optional): npm install -D @types/pg\n *\n * @example\n * ```ts\n * import { Pool } from 'pg'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL })\n * const storage = new PostgresStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `pg` installed\ninterface QueryClient {\n query(sql: string, values?: unknown[]): Promise<{ rows: unknown[] }>\n}\n\nexport class PostgresStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0 / v0.3.0. */\n async migrate(): Promise<void> {\n await this.client.query(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGSERIAL PRIMARY KEY,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n reasoning_tokens INTEGER NOT NULL DEFAULT 0,\n cached_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd NUMERIC NOT NULL,\n session_id TEXT,\n user_id TEXT,\n feature TEXT,\n app_id TEXT,\n timestamp TIMESTAMPTZ NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n for (const col of [\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cached_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cache_creation_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS app_id TEXT',\n ]) {\n await this.client.query(col).catch(() => { /* column already exists */ })\n }\n }\n\n record(entry: UsageEntry): void {\n this.client\n .query(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, app_id, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.appId ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] PostgresStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const result = await this.client.query(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (result.rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.query('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.query(\n 'DELETE FROM tokenwatch_usage WHERE session_id = $1',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n const cachedTokens = (r['cached_tokens'] as number | null) ?? 0\n const cacheCreationTokens = (r['cache_creation_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n ...(cachedTokens > 0 && { cachedTokens }),\n ...(cacheCreationTokens > 0 && { cacheCreationTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n ...(r['app_id'] != null && { appId: r['app_id'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MySQL / MariaDB using the `mysql2` driver.\n *\n * Install peer dep: npm install mysql2\n *\n * @example\n * ```ts\n * import mysql from 'mysql2/promise'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = mysql.createPool({ uri: process.env.MYSQL_URL })\n * const storage = new MySQLStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural type so the adapter compiles without `mysql2` installed\ninterface QueryClient {\n execute(sql: string, values?: unknown[]): Promise<[unknown]>\n}\n\nexport class MySQLStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0 / v0.3.0. */\n async migrate(): Promise<void> {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n model VARCHAR(255) NOT NULL,\n input_tokens INT NOT NULL,\n output_tokens INT NOT NULL,\n reasoning_tokens INT NOT NULL DEFAULT 0,\n cached_tokens INT NOT NULL DEFAULT 0,\n cache_creation_tokens INT NOT NULL DEFAULT 0,\n cost_usd DECIMAL(18,8) NOT NULL,\n session_id VARCHAR(255),\n user_id VARCHAR(255),\n feature VARCHAR(255),\n app_id VARCHAR(255),\n timestamp DATETIME(3) NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n await this.client.execute(`\n ALTER TABLE tokenwatch_usage\n ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS feature VARCHAR(255),\n ADD COLUMN IF NOT EXISTS cached_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS cache_creation_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS app_id VARCHAR(255)\n `).catch(() => { /* MySQL < 8.0 may not support IF NOT EXISTS — ignore if columns already exist */ })\n }\n\n record(entry: UsageEntry): void {\n this.client\n .execute(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, app_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.appId ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MySQLStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const [rows] = await this.client.execute(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.execute('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.execute(\n 'DELETE FROM tokenwatch_usage WHERE session_id = ?',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n const cachedTokens = (r['cached_tokens'] as number | null) ?? 0\n const cacheCreationTokens = (r['cache_creation_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n ...(cachedTokens > 0 && { cachedTokens }),\n ...(cacheCreationTokens > 0 && { cacheCreationTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n ...(r['app_id'] != null && { appId: r['app_id'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MongoDB using the official `mongodb` driver.\n *\n * Install peer dep: npm install mongodb\n *\n * @example\n * ```ts\n * import { MongoClient } from 'mongodb'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const client = new MongoClient(process.env.MONGO_URL!)\n * await client.connect()\n *\n * const storage = new MongoStorage(client.db('myapp'))\n * const tracker = createTracker({ storage })\n * ```\n *\n * Recommended index (run once at startup):\n * ```ts\n * await storage.createIndexes()\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `mongodb` installed\ninterface MongoDocument {\n _id?: unknown\n model: string\n inputTokens: number\n outputTokens: number\n reasoningTokens?: number\n cachedTokens?: number\n cacheCreationTokens?: number\n costUSD: number\n sessionId?: string | null\n userId?: string | null\n feature?: string | null\n appId?: string | null\n timestamp: string\n}\n\ninterface MongoCursor {\n sort(sort: Record<string, unknown>): MongoCursor\n toArray(): Promise<MongoDocument[]>\n}\n\ninterface Collection {\n insertOne(doc: MongoDocument): Promise<unknown>\n find(filter: Record<string, unknown>): MongoCursor\n deleteMany(filter: Record<string, unknown>): Promise<unknown>\n createIndex(index: Record<string, unknown>): Promise<unknown>\n}\n\ninterface Database {\n collection(name: string): Collection\n}\n\nconst COLLECTION = 'tokenwatch_usage'\n\nexport class MongoStorage implements IStorage {\n private readonly col: Collection\n\n constructor(db: Database) {\n this.col = db.collection(COLLECTION)\n }\n\n /** Creates recommended indexes for query performance. Call once at startup. */\n async createIndexes(): Promise<void> {\n await this.col.createIndex({ timestamp: 1 })\n await this.col.createIndex({ sessionId: 1 })\n await this.col.createIndex({ userId: 1 })\n await this.col.createIndex({ model: 1 })\n await this.col.createIndex({ appId: 1 })\n }\n\n record(entry: UsageEntry): void {\n this.col\n .insertOne({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n ...(entry.reasoningTokens !== undefined && { reasoningTokens: entry.reasoningTokens }),\n ...(entry.cachedTokens !== undefined && { cachedTokens: entry.cachedTokens }),\n ...(entry.cacheCreationTokens !== undefined && { cacheCreationTokens: entry.cacheCreationTokens }),\n costUSD: entry.costUSD,\n sessionId: entry.sessionId ?? null,\n userId: entry.userId ?? null,\n ...(entry.feature !== undefined && { feature: entry.feature }),\n ...(entry.appId !== undefined && { appId: entry.appId }),\n timestamp: entry.timestamp,\n })\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MongoStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const docs = await this.col.find({}).sort({ timestamp: 1 }).toArray()\n return docs.map(docToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.col.deleteMany({})\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.col.deleteMany({ sessionId })\n }\n}\n\nfunction docToEntry(doc: MongoDocument): UsageEntry {\n return {\n model: doc.model,\n inputTokens: doc.inputTokens,\n outputTokens: doc.outputTokens,\n ...(doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens }),\n ...(doc.cachedTokens != null && doc.cachedTokens > 0 && { cachedTokens: doc.cachedTokens }),\n ...(doc.cacheCreationTokens != null && doc.cacheCreationTokens > 0 && { cacheCreationTokens: doc.cacheCreationTokens }),\n costUSD: doc.costUSD,\n ...(doc.sessionId != null && { sessionId: doc.sessionId }),\n ...(doc.userId != null && { userId: doc.userId }),\n ...(doc.feature != null && { feature: doc.feature }),\n ...(doc.appId != null && { appId: doc.appId }),\n timestamp: doc.timestamp,\n }\n}\n","#!/usr/bin/env node\nimport { readFileSync, existsSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport { homedir } from 'node:os'\nimport { fileURLToPath } from 'node:url'\nimport { fetchRemotePrices } from '../src/core/sync.js'\nimport { SqliteStorage } from '../src/core/storage.js'\nimport { createTracker } from '../src/core/tracker.js'\nimport { startDashboardServer } from '../src/dashboard/server.js'\nimport type { IStorage, PricesFile } from '../src/types/index.js'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst DEFAULT_DB_PATH = join(homedir(), '.tokenwatch', 'usage.db')\n\n// ─── Arg helpers ──────────────────────────────────────────────────────────────\n\nfunction getFlag(args: string[], flag: string): string | undefined {\n const idx = args.indexOf(flag)\n if (idx === -1) return undefined\n const value = args[idx + 1]\n // Treat missing value or another flag as absent (e.g. --db --port would be wrong)\n return value !== undefined && !value.startsWith('--') ? value : undefined\n}\n\n// ─── Storage factory ──────────────────────────────────────────────────────────\n\n// Local interface stubs — avoids compile-time deps on optional peer packages.\n// Same pattern used in src/exporters/otel.ts for @opentelemetry/api.\ninterface PgPoolLike { end(): Promise<void> }\ninterface MysqlPoolLike { end(): Promise<void> }\ninterface MongoClientLike { connect(): Promise<void>; close(): Promise<void>; db(name?: string): unknown }\n\ninterface StorageHandle {\n storage: IStorage\n close: () => Promise<void>\n}\n\nasync function openStorage(dbUrl: string | undefined): Promise<StorageHandle> {\n // ── Default: SQLite ──────────────────────────────────────────────────────\n if (!dbUrl) {\n if (!existsSync(DEFAULT_DB_PATH)) {\n console.error(`No SQLite database found at ${DEFAULT_DB_PATH}`)\n console.error(\"Start your app with storage: 'sqlite' to begin recording usage.\")\n console.error('Or pass --db <url> to connect to Postgres, MySQL, or MongoDB.')\n process.exit(1)\n }\n let storage: SqliteStorage\n try {\n storage = new SqliteStorage(DEFAULT_DB_PATH)\n } catch {\n console.error('Failed to open SQLite database. Is better-sqlite3 installed?')\n console.error('Run: npm install better-sqlite3')\n process.exit(1)\n }\n return { storage, close: async () => {} }\n }\n\n // ── Postgres ─────────────────────────────────────────────────────────────\n if (dbUrl.startsWith('postgres://') || dbUrl.startsWith('postgresql://')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let pgMod: any\n try {\n pgMod = (await import('pg' as string)).default\n } catch {\n console.error('[tokenwatch] Postgres requires the pg package.')\n console.error('Run: npm install pg')\n process.exit(1)\n }\n const { PostgresStorage } = await import('../src/adapters/postgres.js')\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n const pool = new pgMod.Pool({ connectionString: dbUrl }) as PgPoolLike\n const storage = new PostgresStorage(pool as never)\n return { storage, close: () => pool.end() }\n }\n\n // ── MySQL ─────────────────────────────────────────────────────────────────\n if (dbUrl.startsWith('mysql://')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let mysqlMod: any\n try {\n mysqlMod = await import('mysql2/promise' as string)\n } catch {\n console.error('[tokenwatch] MySQL requires the mysql2 package.')\n console.error('Run: npm install mysql2')\n process.exit(1)\n }\n const { MySQLStorage } = await import('../src/adapters/mysql.js')\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n const pool = mysqlMod.createPool(dbUrl) as MysqlPoolLike\n const storage = new MySQLStorage(pool as never)\n return { storage, close: () => pool.end() }\n }\n\n // ── MongoDB ───────────────────────────────────────────────────────────────\n if (dbUrl.startsWith('mongodb://') || dbUrl.startsWith('mongodb+srv://')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let mongoMod: any\n try {\n mongoMod = await import('mongodb' as string)\n } catch {\n console.error('[tokenwatch] MongoDB requires the mongodb package.')\n console.error('Run: npm install mongodb')\n process.exit(1)\n }\n const { MongoStorage } = await import('../src/adapters/mongodb.js')\n const urlObj = new URL(dbUrl)\n const dbName = urlObj.pathname.replace(/^\\//, '') || 'tokenwatch'\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n const client = new mongoMod.MongoClient(dbUrl) as MongoClientLike\n await client.connect()\n const db = client.db(dbName)\n const storage = new MongoStorage(db as never)\n return { storage, close: () => client.close() }\n }\n\n console.error(`[tokenwatch] Unsupported database URL: \"${dbUrl}\"`)\n console.error('Supported protocols: postgres://, mysql://, mongodb://, mongodb+srv://')\n process.exit(1)\n}\n\n// ─── Commands ─────────────────────────────────────────────────────────────────\n\nfunction loadBundledPrices(): PricesFile['models'] {\n const pricesPath = join(__dirname, '..', 'prices.json')\n const raw = readFileSync(pricesPath, 'utf8')\n const data = JSON.parse(raw) as PricesFile\n return data.models\n}\n\nasync function cmdSync(): Promise<void> {\n console.log('Fetching latest prices from remote...')\n const result = await fetchRemotePrices()\n if (result) {\n console.log(`✓ Prices updated. ${Object.keys(result.models).length} models cached (updated_at: ${result.updated_at}).`)\n } else {\n console.error('✗ Failed to fetch remote prices. Check your internet connection.')\n process.exit(1)\n }\n}\n\nfunction cmdPrices(): void {\n const models = loadBundledPrices()\n const rows = Object.entries(models).map(([name, price]) => ({\n model: name,\n input: `$${price.input.toFixed(2)}/M`,\n output: `$${price.output.toFixed(2)}/M`,\n }))\n\n const maxName = Math.max(...rows.map((r) => r.model.length), 5)\n const header = `${'Model'.padEnd(maxName)} ${'Input'.padStart(12)} ${'Output'.padStart(12)}`\n const sep = '-'.repeat(header.length)\n\n console.log(header)\n console.log(sep)\n for (const row of rows) {\n console.log(`${row.model.padEnd(maxName)} ${row.input.padStart(12)} ${row.output.padStart(12)}`)\n }\n}\n\nasync function cmdReport(args: string[]): Promise<void> {\n const dbUrl = getFlag(args, '--db')\n const { storage, close } = await openStorage(dbUrl)\n\n let report: Awaited<ReturnType<ReturnType<typeof createTracker>['getReport']>>\n try {\n const tracker = createTracker({ storage, syncPrices: false })\n report = await tracker.getReport()\n } finally {\n await close()\n }\n\n if (report.totalCostUSD === 0 && Object.keys(report.byModel).length === 0) {\n console.log('No usage recorded yet.')\n return\n }\n\n console.log('\\n── tokenwatch report ──────────────────────────────')\n console.log(` Total cost: $${report.totalCostUSD.toFixed(6)} USD`)\n console.log(` Total tokens: ${report.totalTokens.input.toLocaleString()} in / ${report.totalTokens.output.toLocaleString()} out`)\n console.log(` Period: ${report.period.from} → ${report.period.to}`)\n if (report.pricesUpdatedAt) {\n console.log(` Prices as of: ${report.pricesUpdatedAt}`)\n }\n\n if (Object.keys(report.byModel).length > 0) {\n console.log('\\n By model:')\n for (const [model, stats] of Object.entries(report.byModel)) {\n console.log(` ${model.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n if (Object.keys(report.byUser).length > 0) {\n console.log('\\n By user:')\n for (const [user, stats] of Object.entries(report.byUser)) {\n console.log(` ${user.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n if (Object.keys(report.bySession).length > 0) {\n console.log('\\n By session:')\n for (const [session, stats] of Object.entries(report.bySession)) {\n console.log(` ${session.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n if (Object.keys(report.byFeature).length > 0) {\n console.log('\\n By feature:')\n for (const [feature, stats] of Object.entries(report.byFeature)) {\n console.log(` ${feature.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n console.log('───────────────────────────────────────────────────\\n')\n}\n\nasync function cmdDashboard(args: string[]): Promise<void> {\n const portFlag = getFlag(args, '--port')\n const port = portFlag !== undefined ? parseInt(portFlag, 10) : 4242\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(`[tokenwatch] Invalid port: \"${portFlag}\". Must be a number between 1 and 65535.`)\n process.exit(1)\n }\n const dbUrl = getFlag(args, '--db')\n\n const { storage, close } = await openStorage(dbUrl)\n\n // Graceful shutdown — close DB connection when the process exits\n const shutdown = (): void => { void close().then(() => process.exit(0)) }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n startDashboardServer(storage, port)\n}\n\nfunction cmdHelp(): void {\n console.log(`\ntokenwatch — CLI\n\nCommands:\n sync Fetch and cache latest model prices from remote\n prices List all bundled models and their current prices\n report [--db <url>] Show usage report (default: SQLite at ~/.tokenwatch/usage.db)\n dashboard [--port N] Open local web dashboard (default port: 4242)\n [--db <url>] Connect to a database instead of the default SQLite\n\nDatabase URL formats:\n (none) ~/.tokenwatch/usage.db (SQLite, default)\n postgres://user:pass@host:5432/dbname\n mysql://user:pass@host:3306/dbname\n mongodb://user:pass@host:27017/dbname\n\nExamples:\n tokenwatch dashboard\n tokenwatch dashboard --port 8080\n tokenwatch dashboard --db postgres://user:pass@localhost:5432/myapp\n tokenwatch report --db mysql://root:pass@localhost:3306/myapp\n`.trim())\n}\n\n// ─── Entry point ──────────────────────────────────────────────────────────────\n\nasync function main(): Promise<void> {\n const [, , cmd, ...args] = process.argv\n\n switch (cmd) {\n case 'sync':\n await cmdSync()\n break\n case 'prices':\n cmdPrices()\n break\n case 'report':\n await cmdReport(args)\n break\n case 'dashboard':\n await cmdDashboard(args)\n break\n case 'help':\n case undefined:\n cmdHelp()\n break\n default:\n console.error(`Unknown command: ${cmd}\\nRun \"tokenwatch help\" for usage.`)\n process.exit(1)\n }\n}\n\nmain().catch((err: unknown) => {\n console.error(err)\n process.exit(1)\n})\n","import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { PricesFile, PriceMap } from '../types/index.js'\n\nconst CACHE_DIR = join(homedir(), '.tokenwatch')\nconst CACHE_FILE = join(CACHE_DIR, 'prices.json')\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\nconst REMOTE_URL =\n 'https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json'\n\nexport interface PricesResult {\n models: PriceMap\n updated_at: string\n}\n\nexport async function fetchRemotePrices(url = REMOTE_URL): Promise<PricesResult | null> {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(8_000) })\n if (!res.ok) return null\n const data = (await res.json()) as PricesFile\n if (!data?.models) return null\n await persistCache(data)\n return { models: data.models, updated_at: data.updated_at ?? '' }\n } catch {\n return null\n }\n}\n\nexport async function loadCachedPrices(): Promise<PricesResult | null> {\n if (!existsSync(CACHE_FILE)) return null\n try {\n const raw = await readFile(CACHE_FILE, 'utf8')\n const data = JSON.parse(raw) as PricesFile & { _cachedAt?: number }\n const age = Date.now() - (data._cachedAt ?? 0)\n if (age > CACHE_TTL_MS) return null\n if (!data.models) return null\n return { models: data.models, updated_at: data.updated_at ?? '' }\n } catch {\n return null\n }\n}\n\nasync function persistCache(data: PricesFile): Promise<void> {\n try {\n await mkdir(CACHE_DIR, { recursive: true })\n const payload = { ...data, _cachedAt: Date.now() }\n await writeFile(CACHE_FILE, JSON.stringify(payload, null, 2), 'utf8')\n } catch {\n // best-effort — never throw\n }\n}\n\n/**\n * Returns the best available remote price result:\n * 1. Valid local cache (< 24h)\n * 2. Fresh remote fetch (also updates cache)\n * 3. null if both fail\n */\nexport async function getRemotePrices(): Promise<PricesResult | null> {\n const cached = await loadCachedPrices()\n if (cached) return cached\n return fetchRemotePrices()\n}\n","import { createRequire } from 'node:module'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { mkdirSync } from 'node:fs'\nimport type { IStorage, UsageEntry } from '../types/index.js'\n\n// ─── Memory storage ───────────────────────────────────────────────────────────\n\nexport class MemoryStorage implements IStorage {\n private entries: UsageEntry[] = []\n\n record(entry: UsageEntry): void {\n this.entries.push(entry)\n }\n\n getAll(): UsageEntry[] {\n return [...this.entries]\n }\n\n clearAll(): void {\n this.entries = []\n }\n\n clearSession(sessionId: string): void {\n this.entries = this.entries.filter((e) => e.sessionId !== sessionId)\n }\n}\n\n// ─── SQLite storage ───────────────────────────────────────────────────────────\n\nconst DB_DIR = join(homedir(), '.tokenwatch')\nconst DB_PATH = join(DB_DIR, 'usage.db')\n\nexport class SqliteStorage implements IStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private db: any\n\n constructor(dbPath = DB_PATH) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let BetterSqlite3: any\n try {\n // In CJS context globalThis.require is the native require; in ESM use createRequire.\n // This makes the lazy load work in both output formats produced by tsup.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: NodeRequire =\n typeof (globalThis as any).require === 'function'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (globalThis as any).require\n : createRequire(import.meta.url)\n BetterSqlite3 = req('better-sqlite3')\n } catch {\n throw new Error(\n '[tokenwatch] SQLite storage requires better-sqlite3. ' +\n 'Run: npm install better-sqlite3',\n )\n }\n\n mkdirSync(DB_DIR, { recursive: true })\n this.db = new BetterSqlite3(dbPath)\n this.migrate()\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS usage (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n reasoning_tokens INTEGER NOT NULL DEFAULT 0,\n cached_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd REAL NOT NULL,\n session_id TEXT,\n user_id TEXT,\n feature TEXT,\n app_id TEXT,\n timestamp TEXT NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n const cols = (this.db.prepare(`PRAGMA table_info(usage)`).all() as Array<{ name: string }>)\n .map((c) => c.name)\n if (!cols.includes('reasoning_tokens')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN reasoning_tokens INTEGER NOT NULL DEFAULT 0`)\n }\n if (!cols.includes('feature')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN feature TEXT`)\n }\n if (!cols.includes('cached_tokens')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN cached_tokens INTEGER NOT NULL DEFAULT 0`)\n }\n if (!cols.includes('cache_creation_tokens')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN cache_creation_tokens INTEGER NOT NULL DEFAULT 0`)\n }\n if (!cols.includes('app_id')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN app_id TEXT`)\n }\n }\n\n record(entry: UsageEntry): void {\n this.db\n .prepare(\n `INSERT INTO usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, app_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.appId ?? null,\n entry.timestamp,\n )\n }\n\n getAll(): UsageEntry[] {\n const rows = this.db.prepare('SELECT * FROM usage ORDER BY timestamp ASC').all() as Array<{\n model: string\n input_tokens: number\n output_tokens: number\n reasoning_tokens: number\n cached_tokens: number\n cache_creation_tokens: number\n cost_usd: number\n session_id: string | null\n user_id: string | null\n feature: string | null\n app_id: string | null\n timestamp: string\n }>\n\n return rows.map((r) => ({\n model: r.model,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n ...(r.reasoning_tokens > 0 && { reasoningTokens: r.reasoning_tokens }),\n ...(r.cached_tokens > 0 && { cachedTokens: r.cached_tokens }),\n ...(r.cache_creation_tokens > 0 && { cacheCreationTokens: r.cache_creation_tokens }),\n costUSD: r.cost_usd,\n ...(r.session_id != null && { sessionId: r.session_id }),\n ...(r.user_id != null && { userId: r.user_id }),\n ...(r.feature != null && { feature: r.feature }),\n ...(r.app_id != null && { appId: r.app_id }),\n timestamp: r.timestamp,\n }))\n }\n\n clearAll(): void {\n this.db.exec('DELETE FROM usage')\n }\n\n clearSession(sessionId: string): void {\n this.db.prepare('DELETE FROM usage WHERE session_id = ?').run(sessionId)\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createStorage(type: 'memory' | 'sqlite'): IStorage {\n if (type === 'sqlite') return new SqliteStorage()\n return new MemoryStorage()\n}\n","import { z } from 'zod'\nimport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ReportOptions,\n CostForecast,\n ForecastOptions,\n ModelStats,\n SessionStats,\n UserStats,\n FeatureStats,\n AppStats,\n ModelPrice,\n PriceMap,\n IStorage,\n BudgetConfig,\n IExporter,\n} from '../types/index.js'\nimport { resolvePrice, findPrice, calculateCost } from './pricing.js'\nimport { CloudExporter } from '../exporters/cloud.js'\nimport { maybeSuggestCheaperModel } from './suggestions.js'\nimport { createStorage } from './storage.js'\nimport { getRemotePrices } from './sync.js'\nimport bundledPricesFile from '../../prices.json' assert { type: 'json' }\n\nconst bundledPrices: PriceMap = bundledPricesFile.models as PriceMap\nconst bundledUpdatedAt: string = (bundledPricesFile as { updated_at?: string }).updated_at ?? ''\n\n// ─── Config validation schema ─────────────────────────────────────────────────\n\nconst ModelPriceSchema = z.object({\n input: z.number().nonnegative(),\n output: z.number().nonnegative(),\n cachedInput: z.number().nonnegative().optional(),\n cacheCreationInput: z.number().nonnegative().optional(),\n maxInputTokens: z.number().positive().optional(),\n})\n\nconst BudgetConfigSchema = z.object({\n threshold: z.number().positive(),\n webhookUrl: z.string().url(),\n mode: z.enum(['once', 'always']).optional().default('once'),\n})\n\n// storage can be a string enum or an IStorage instance — validated separately\nconst TrackerConfigSchema = z.object({\n storage: z.union([z.enum(['memory', 'sqlite']), z.custom<IStorage>((v) => {\n return (\n v !== null &&\n typeof v === 'object' &&\n typeof (v as IStorage).record === 'function' &&\n typeof (v as IStorage).getAll === 'function' &&\n typeof (v as IStorage).clearAll === 'function' &&\n typeof (v as IStorage).clearSession === 'function'\n )\n })]).optional().default('memory'),\n alertThreshold: z.number().positive().optional(),\n webhookUrl: z.string().url().optional(),\n syncPrices: z.boolean().optional().default(true),\n customPrices: z.record(z.string(), ModelPriceSchema).optional(),\n warnIfStaleAfterHours: z.number().nonnegative().optional().default(72),\n budgets: z.object({\n perUser: BudgetConfigSchema.optional(),\n perSession: BudgetConfigSchema.optional(),\n }).optional(),\n suggestions: z.boolean().optional().default(false),\n anomalyDetection: z.object({\n multiplierThreshold: z.number().positive(),\n webhookUrl: z.string().url(),\n windowHours: z.number().positive().optional().default(24),\n mode: z.enum(['once', 'always']).optional().default('once'),\n }).optional(),\n exporter: z.custom<IExporter>((v) => (\n v !== null &&\n typeof v === 'object' &&\n typeof (v as IExporter).export === 'function'\n )).optional(),\n appId: z.string().optional(),\n cloudApiKey: z.string().optional(),\n cloudEndpoint: z.string().url().optional(),\n})\n\nexport function createTracker(config: TrackerConfig = {}): Tracker {\n const parsed = TrackerConfigSchema.safeParse(config)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\\n')\n throw new Error(`[tokenwatch] Invalid config:\\n${issues}`)\n }\n\n const {\n storage: storageOption,\n alertThreshold,\n webhookUrl,\n syncPrices,\n customPrices,\n warnIfStaleAfterHours,\n budgets,\n suggestions,\n anomalyDetection,\n exporter,\n appId,\n cloudApiKey,\n cloudEndpoint,\n } = parsed.data\n\n const storage: IStorage =\n typeof storageOption === 'object'\n ? storageOption\n : createStorage(storageOption)\n\n const cloudExporter = cloudApiKey\n ? new CloudExporter(cloudApiKey, cloudEndpoint)\n : null\n\n // Fetch remote prices in the background — bundled prices are used as fallback\n // until the sync resolves. Negligible overhead added to createTracker().\n let remotePrices: PriceMap | undefined\n let pricesUpdatedAt: string = bundledUpdatedAt\n if (syncPrices) {\n getRemotePrices()\n .then((result) => {\n if (result) {\n remotePrices = result.models\n pricesUpdatedAt = result.updated_at\n }\n })\n .catch(() => {\n // best-effort — bundled prices remain in use\n })\n }\n\n // Warn if prices are stale (checked lazily on first access)\n let stalenessChecked = false\n function maybeWarnStaleness(): void {\n if (stalenessChecked || !warnIfStaleAfterHours) return\n stalenessChecked = true\n if (!pricesUpdatedAt) return\n try {\n const updatedMs = new Date(pricesUpdatedAt).getTime()\n const ageHours = (Date.now() - updatedMs) / (1000 * 60 * 60)\n if (ageHours > warnIfStaleAfterHours) {\n console.warn(\n `[tokenwatch] Price data is ${Math.round(ageHours)}h old (updated_at: ${pricesUpdatedAt}). ` +\n `Run \"tokenwatch sync\" to refresh, or set warnIfStaleAfterHours: 0 to suppress.`,\n )\n }\n } catch {\n // best-effort\n }\n }\n\n let alertFired = false\n const firedUserAlerts = new Set<string>()\n const firedSessionAlerts = new Set<string>()\n const firedAnomalyKeys = new Set<string>()\n const startedAt = new Date().toISOString()\n\n function resolveModelPrice(model: string) {\n maybeWarnStaleness()\n return resolvePrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices: customPrices as PriceMap }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n\n function track(entry: Omit<UsageEntry, 'costUSD' | 'timestamp'>): void {\n const price = resolveModelPrice(entry.model)\n const costUSD = calculateCost(\n entry.inputTokens,\n entry.outputTokens,\n price,\n entry.cachedTokens,\n entry.cacheCreationTokens,\n )\n const full: UsageEntry = {\n ...entry,\n costUSD,\n timestamp: new Date().toISOString(),\n ...(appId !== undefined && entry.appId === undefined && { appId }),\n }\n storage.record(full)\n if (exporter) {\n Promise.resolve(exporter.export(full)).catch(() => { /* fire-and-forget */ })\n }\n if (cloudExporter) {\n cloudExporter.export(full)\n }\n maybeFireAlerts(full)\n if (anomalyDetection) maybeDetectAnomaly(full)\n if (suggestions) {\n maybeSuggestCheaperModel(entry.model, costUSD, entry.inputTokens, entry.outputTokens, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices: customPrices as PriceMap }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n }\n\n function maybeFireAlerts(entry: UsageEntry): void {\n // Global threshold alert\n if (alertThreshold && webhookUrl && !alertFired) {\n alertFired = true\n Promise.resolve(storage.getAll()).then((entries) => {\n const total = computeTotal(entries)\n if (total < alertThreshold!) {\n alertFired = false\n return\n }\n fireWebhook(webhookUrl!, {\n text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`,\n })\n }).catch(() => {\n alertFired = false\n })\n }\n\n // Per-user budget alert\n if (budgets?.perUser && entry.userId) {\n const cfg = budgets.perUser\n const uid = entry.userId\n if (cfg.mode === 'always' || !firedUserAlerts.has(uid)) {\n // Claim the slot synchronously before going async — prevents double-fire\n if (cfg.mode !== 'always') firedUserAlerts.add(uid)\n Promise.resolve(storage.getAll()).then((entries) => {\n const userCost = entries\n .filter((e) => e.userId === uid)\n .reduce((s, e) => s + e.costUSD, 0)\n if (userCost >= cfg.threshold) {\n fireWebhook(cfg.webhookUrl, {\n text: `[tokenwatch] Budget alert: user \"${uid}\" reached $${userCost.toFixed(4)} USD (threshold: $${cfg.threshold})`,\n })\n } else {\n if (cfg.mode !== 'always') firedUserAlerts.delete(uid) // release — threshold not yet met\n }\n }).catch(() => {\n if (cfg.mode !== 'always') firedUserAlerts.delete(uid) // release on storage error\n })\n }\n }\n\n // Per-session budget alert\n if (budgets?.perSession && entry.sessionId) {\n const cfg = budgets.perSession\n const sid = entry.sessionId\n if (cfg.mode === 'always' || !firedSessionAlerts.has(sid)) {\n // Claim the slot synchronously before going async — prevents double-fire\n if (cfg.mode !== 'always') firedSessionAlerts.add(sid)\n Promise.resolve(storage.getAll()).then((entries) => {\n const sessionCost = entries\n .filter((e) => e.sessionId === sid)\n .reduce((s, e) => s + e.costUSD, 0)\n if (sessionCost >= cfg.threshold) {\n fireWebhook(cfg.webhookUrl, {\n text: `[tokenwatch] Budget alert: session \"${sid}\" reached $${sessionCost.toFixed(4)} USD (threshold: $${cfg.threshold})`,\n })\n } else {\n if (cfg.mode !== 'always') firedSessionAlerts.delete(sid) // release\n }\n }).catch(() => {\n if (cfg.mode !== 'always') firedSessionAlerts.delete(sid) // release on storage error\n })\n }\n }\n }\n\n function fireWebhook(url: string, payload: { text: string }): void {\n fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }).catch(() => {\n // fire-and-forget\n })\n }\n\n async function getReport(options?: ReportOptions): Promise<Report> {\n const allEntries = await Promise.resolve(storage.getAll())\n const entries = filterEntries(allEntries, options)\n\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n const byFeature: Record<string, FeatureStats> = {}\n const byApp: Record<string, AppStats> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n let periodFrom = options ? (entries[0]?.timestamp ?? startedAt) : startedAt\n let lastTimestamp = periodFrom\n\n for (const e of entries) {\n totalInput += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp\n\n // byModel\n const m = (byModel[e.model] ??= {\n costUSD: 0,\n calls: 0,\n tokens: { input: 0, output: 0, reasoning: 0, cached: 0 },\n })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n m.tokens.output += e.outputTokens\n m.tokens.reasoning += e.reasoningTokens ?? 0\n m.tokens.cached += e.cachedTokens ?? 0\n\n // bySession\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n // byUser\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n\n // byFeature\n if (e.feature) {\n const f = (byFeature[e.feature] ??= { costUSD: 0, calls: 0 })\n f.costUSD += e.costUSD\n f.calls += 1\n }\n\n // byApp\n if (e.appId) {\n const a = (byApp[e.appId] ??= { costUSD: 0, calls: 0 })\n a.costUSD += e.costUSD\n a.calls += 1\n }\n }\n\n // When filtering, use the actual first entry's timestamp as period.from\n if (options && entries.length > 0) {\n periodFrom = entries[0]?.timestamp ?? periodFrom\n }\n\n return {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n byFeature,\n byApp,\n period: { from: periodFrom, to: lastTimestamp },\n ...(pricesUpdatedAt ? { pricesUpdatedAt } : {}),\n }\n }\n\n async function getCostForecast(options: ForecastOptions = {}): Promise<CostForecast> {\n const windowHours = options.windowHours ?? 24\n const allEntries = await Promise.resolve(storage.getAll())\n\n const now = Date.now()\n const windowStart = now - windowHours * 60 * 60 * 1000\n const windowEntries = allEntries.filter(\n (e) => new Date(e.timestamp).getTime() >= windowStart,\n )\n\n if (windowEntries.length < 2) {\n return {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: null,\n }\n }\n\n const first = windowEntries[0]?.timestamp ?? ''\n const last = windowEntries[windowEntries.length - 1]?.timestamp ?? ''\n const actualMs = new Date(last).getTime() - new Date(first).getTime()\n const actualHours = actualMs / (1000 * 60 * 60)\n\n if (actualHours < 0.001) {\n return {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: { from: first, to: last },\n }\n }\n\n const totalCost = windowEntries.reduce((s, e) => s + e.costUSD, 0)\n const burnRatePerHour = totalCost / actualHours\n\n return {\n burnRatePerHour,\n projectedDailyCostUSD: burnRatePerHour * 24,\n projectedMonthlyCostUSD: burnRatePerHour * 24 * 30,\n basedOnHours: Math.round(actualHours * 100) / 100,\n basedOnPeriod: { from: first, to: last },\n }\n }\n\n function maybeDetectAnomaly(entry: UsageEntry): void {\n if (entry.costUSD <= 0) return\n const { multiplierThreshold, webhookUrl: aUrl, windowHours: wh, mode: modeRaw } = anomalyDetection!\n const wHours = wh ?? 24\n const mode = modeRaw ?? 'once'\n const windowStart = Date.now() - wHours * 60 * 60 * 1000\n const entryTs = new Date(entry.timestamp).getTime()\n\n function checkEntity(key: string, label: string, predicate: (e: UsageEntry) => boolean): void {\n if (mode !== 'always' && firedAnomalyKeys.has(key)) return\n if (mode !== 'always') firedAnomalyKeys.add(key)\n Promise.resolve(storage.getAll()).then((all) => {\n const history = all.filter(\n (e) =>\n predicate(e) &&\n new Date(e.timestamp).getTime() >= windowStart &&\n new Date(e.timestamp).getTime() !== entryTs,\n )\n if (history.length === 0) {\n if (mode !== 'always') firedAnomalyKeys.delete(key)\n return\n }\n const avg = history.reduce((s, e) => s + e.costUSD, 0) / history.length\n if (avg <= 0 || entry.costUSD <= avg * multiplierThreshold) {\n if (mode !== 'always') firedAnomalyKeys.delete(key)\n return\n }\n const multiple = (entry.costUSD / avg).toFixed(1)\n fireWebhook(aUrl, {\n text: `[tokenwatch] Anomaly: ${label} call cost $${entry.costUSD.toFixed(4)} is ${multiple}x above ${wHours}h average ($${avg.toFixed(4)})`,\n })\n }).catch(() => {\n if (mode !== 'always') firedAnomalyKeys.delete(key)\n })\n }\n\n if (entry.userId) {\n checkEntity(\n `user:${entry.userId}`,\n `user \"${entry.userId}\"`,\n (e) => e.userId === entry.userId,\n )\n }\n checkEntity(\n `model:${entry.model}`,\n `model \"${entry.model}\"`,\n (e) => e.model === entry.model,\n )\n }\n\n async function reset(): Promise<void> {\n await Promise.resolve(storage.clearAll())\n alertFired = false\n firedUserAlerts.clear()\n firedSessionAlerts.clear()\n firedAnomalyKeys.clear()\n }\n\n async function resetSession(sessionId: string): Promise<void> {\n await Promise.resolve(storage.clearSession(sessionId))\n firedSessionAlerts.delete(sessionId)\n }\n\n async function exportJSON(): Promise<string> {\n return JSON.stringify(await getReport(), null, 2)\n }\n\n async function exportCSV(): Promise<string> {\n const entries = await Promise.resolve(storage.getAll())\n const header =\n 'timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId'\n const rows = entries.map((e) =>\n [\n csvEscape(e.timestamp),\n csvEscape(e.model),\n e.inputTokens,\n e.outputTokens,\n e.reasoningTokens ?? 0,\n e.cachedTokens ?? 0,\n e.cacheCreationTokens ?? 0,\n e.costUSD.toFixed(8),\n csvEscape(e.sessionId ?? ''),\n csvEscape(e.userId ?? ''),\n csvEscape(e.feature ?? ''),\n csvEscape(e.appId ?? ''),\n ].join(','),\n )\n return [header, ...rows].join('\\n')\n }\n\n function getModelInfo(model: string): ModelPrice | null {\n return findPrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices: customPrices as PriceMap }),\n ...(remotePrices !== undefined && { remotePrices }),\n }) ?? null\n }\n\n return {\n track,\n getReport,\n getCostForecast,\n reset,\n resetSession,\n exportJSON,\n exportCSV,\n getModelInfo,\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction computeTotal(entries: UsageEntry[]): number {\n return entries.reduce((sum, e) => sum + e.costUSD, 0)\n}\n\n/** Parse a 'last' shorthand like '24h', '7d' into milliseconds */\nfunction parseLastMs(last: string): number {\n const match = /^(\\d+(?:\\.\\d+)?)(h|d)$/.exec(last.trim())\n if (!match) throw new Error(`[tokenwatch] Invalid \"last\" value: \"${last}\". Use e.g. \"24h\", \"7d\".`)\n const value = parseFloat(match[1] ?? '0')\n const unit = match[2] ?? 'h'\n return unit === 'h' ? value * 60 * 60 * 1000 : value * 24 * 60 * 60 * 1000\n}\n\nfunction filterEntries(entries: UsageEntry[], options?: ReportOptions): UsageEntry[] {\n if (!options) return entries\n\n let sinceMs: number | undefined\n let untilMs: number | undefined\n\n if (options.last) {\n sinceMs = Date.now() - parseLastMs(options.last)\n } else if (options.since) {\n sinceMs = new Date(options.since).getTime()\n }\n if (options.until) {\n untilMs = new Date(options.until).getTime()\n }\n\n if (sinceMs === undefined && untilMs === undefined) return entries\n\n return entries.filter((e) => {\n const ts = new Date(e.timestamp).getTime()\n if (sinceMs !== undefined && ts < sinceMs) return false\n if (untilMs !== undefined && ts > untilMs) return false\n return true\n })\n}\n\n/** Wrap a CSV field value in double-quotes if it contains commas, quotes, or newlines. */\nfunction csvEscape(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`\n }\n return value\n}\n","import type { ModelPrice, PriceMap } from '../types/index.js'\n\n/**\n * Resolve price for a model using 3-layer priority:\n * 1. customPrices (user override)\n * 2. remotePrices (synced from GitHub, cached 24h)\n * 3. bundledPrices (always-present fallback)\n *\n * Falls back to zero-cost with a console warning when model is not found anywhere.\n */\nexport function resolvePrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice {\n const { customPrices, remotePrices, bundledPrices } = layers\n\n const found =\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n\n if (found) return found\n\n console.warn(\n `[tokenwatch] Unknown model \"${model}\". Cost will be recorded as $0. ` +\n `Add it via customPrices or update prices with: tokenwatch sync`,\n )\n return { input: 0, output: 0 }\n}\n\n/**\n * Find price for a model without the zero-cost fallback.\n * Returns undefined if the model is not found in any layer.\n */\nexport function findPrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice | undefined {\n const { customPrices, remotePrices, bundledPrices } = layers\n return (\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n )\n}\n\n/**\n * Look up a model in a PriceMap using:\n * 1. exact key match\n * 2. prefix match — map key is a prefix of the model string (e.g. \"gpt-4o\" matches \"gpt-4o-2024-11-20\")\n * 3. reverse prefix — model string is a prefix of a map key (unusual, safety net)\n */\nfunction lookupInMap(model: string, map: PriceMap | undefined): ModelPrice | undefined {\n if (!map) return undefined\n\n if (model in map) return map[model]\n\n // prefix match\n for (const key of Object.keys(map)) {\n if (model.startsWith(key) || key.startsWith(model)) {\n return map[key]\n }\n }\n\n return undefined\n}\n\n/**\n * Calculate cost in USD given token counts and per-million-token prices.\n *\n * - `inputTokens` — regular (non-cached) input tokens\n * - `outputTokens` — output tokens (includes reasoning tokens for OpenAI, which are billed as output)\n * - `cachedTokens` — cache-read input tokens (billed at price.cachedInput or full input price if absent)\n * - `cacheCreationTokens` — cache-creation input tokens, Anthropic only (billed at price.cacheCreationInput\n * or 1.25× input price if absent)\n */\nexport function calculateCost(\n inputTokens: number,\n outputTokens: number,\n price: ModelPrice,\n cachedTokens = 0,\n cacheCreationTokens = 0,\n): number {\n const regularInputCost = (inputTokens / 1_000_000) * price.input\n const cachedReadCost = (cachedTokens / 1_000_000) * (price.cachedInput ?? price.input)\n const cacheCreationCost =\n (cacheCreationTokens / 1_000_000) * (price.cacheCreationInput ?? price.input * 1.25)\n const outputCost = (outputTokens / 1_000_000) * price.output\n return regularInputCost + cachedReadCost + cacheCreationCost + outputCost\n}\n","import type { IExporter, UsageEntry } from '../types/index.js'\n\nconst DEFAULT_ENDPOINT = 'https://api.tokenwatch.dev/v1/ingest'\n\nexport class CloudExporter implements IExporter {\n private readonly endpoint: string\n\n constructor(\n private readonly apiKey: string,\n endpoint?: string,\n ) {\n this.endpoint = endpoint ?? DEFAULT_ENDPOINT\n }\n\n export(entry: UsageEntry): void {\n fetch(this.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n reasoningTokens: entry.reasoningTokens ?? 0,\n cachedTokens: entry.cachedTokens ?? 0,\n cacheCreationTokens: entry.cacheCreationTokens ?? 0,\n costUSD: entry.costUSD,\n sessionId: entry.sessionId,\n userId: entry.userId,\n feature: entry.feature,\n timestamp: entry.timestamp,\n }),\n }).catch(() => {\n // fire-and-forget — cloud failure never interrupts the caller\n })\n }\n}\n","import type { PriceMap } from '../types/index.js'\nimport { calculateCost } from './pricing.js'\n\nconst PROVIDER_PREFIXES = ['gpt-', 'claude-', 'gemini-', 'deepseek-'] as const\n\nfunction getProviderPrefix(model: string): string | undefined {\n return PROVIDER_PREFIXES.find((p) => model.startsWith(p))\n}\n\n/**\n * After a tracked call, check if there is a cheaper model in the same provider family\n * (defined by model name prefix). Logs a hint if savings are strictly greater than 50%.\n * No-ops if the model is unknown, costUSD is zero, or no cheaper candidate is found.\n */\nexport function maybeSuggestCheaperModel(\n model: string,\n costUSD: number,\n inputTokens: number,\n outputTokens: number,\n layers: { bundledPrices: PriceMap; customPrices?: PriceMap; remotePrices?: PriceMap },\n): void {\n if (costUSD <= 0) return\n\n const prefix = getProviderPrefix(model)\n if (!prefix) return\n\n // Merge layers: bundled < remote < custom (custom wins)\n const mergedMap: PriceMap = {\n ...layers.bundledPrices,\n ...(layers.remotePrices ?? {}),\n ...(layers.customPrices ?? {}),\n }\n\n let cheapestModel: string | undefined\n let cheapestCost = Infinity\n\n for (const key of Object.keys(mergedMap)) {\n if (key === model || !key.startsWith(prefix)) continue\n const price = mergedMap[key]\n if (!price) continue\n const candidateCost = calculateCost(inputTokens, outputTokens, price)\n if (candidateCost < cheapestCost) {\n cheapestCost = candidateCost\n cheapestModel = key\n }\n }\n\n // Must be strictly more than 50% cheaper\n if (cheapestModel === undefined || cheapestCost >= costUSD * 0.5) return\n\n const savingsPct = Math.round((1 - cheapestCost / costUSD) * 100)\n console.log(\n `[tokenwatch] Suggestion: ${cheapestModel} could handle this for ~$${cheapestCost.toFixed(4)} (${savingsPct}% cheaper than ${model})`,\n )\n}\n","{\n \"updated_at\": \"2026-06-09\",\n \"source\": \"https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json\",\n \"models\": {\n \"gpt-4o\": {\n \"input\": 2.5,\n \"output\": 10,\n \"cachedInput\": 1.25,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 128000\n },\n \"gpt-5\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-mini\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-nano\": {\n \"input\": 0.05,\n \"output\": 0.4,\n \"cachedInput\": 0.005,\n \"maxInputTokens\": 272000\n },\n \"claude-opus-4-6\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-6\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"claude-haiku-4-5\": {\n \"input\": 1,\n \"output\": 5,\n \"cachedInput\": 0.1,\n \"maxInputTokens\": 200000\n },\n \"gemini-2.5-pro\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 1048576\n },\n \"deepseek-chat\": {\n \"input\": 0.28,\n \"output\": 0.42,\n \"cachedInput\": 0.028,\n \"maxInputTokens\": 131072\n },\n \"deepseek-reasoner\": {\n \"input\": 0.28,\n \"output\": 0.42,\n \"cachedInput\": 0.028,\n \"maxInputTokens\": 131072\n },\n \"claude-opus-4-5\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-7\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-1\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-sonnet-4-5\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"gpt-oss-120b\": {\n \"input\": 3,\n \"output\": 4.5,\n \"maxInputTokens\": 131072\n },\n \"gpt-3.5-turbo\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 16385\n },\n \"gpt-3.5-turbo-0125\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 16385\n },\n \"gpt-35-turbo\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 4097\n },\n \"gpt-35-turbo-0125\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 16384\n },\n \"gpt-35-turbo-1106\": {\n \"input\": 1,\n \"output\": 2,\n \"maxInputTokens\": 16384\n },\n \"gpt-35-turbo-16k\": {\n \"input\": 3,\n \"output\": 4,\n \"maxInputTokens\": 16385\n },\n \"gpt-35-turbo-16k-0613\": {\n \"input\": 3,\n \"output\": 4,\n \"maxInputTokens\": 16385\n },\n \"gpt-4\": {\n \"input\": 30,\n \"output\": 60,\n \"maxInputTokens\": 8192\n },\n \"gpt-4-0125-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-0613\": {\n \"input\": 30,\n \"output\": 60,\n \"maxInputTokens\": 8192\n },\n \"gpt-4-1106-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-32k\": {\n \"input\": 60,\n \"output\": 120,\n \"maxInputTokens\": 32768\n },\n \"gpt-4-32k-0613\": {\n \"input\": 60,\n \"output\": 120,\n \"maxInputTokens\": 32768\n },\n \"gpt-4-turbo\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-turbo-2024-04-09\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-turbo-vision-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4.1\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-2025-04-14\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-mini\": {\n \"input\": 0.4,\n \"output\": 1.6,\n \"cachedInput\": 0.1,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-mini-2025-04-14\": {\n \"input\": 0.4,\n \"output\": 1.6,\n \"cachedInput\": 0.1,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-nano\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-nano-2025-04-14\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.5-preview\": {\n \"input\": 75,\n \"output\": 150,\n \"cachedInput\": 37.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-2024-05-13\": {\n \"input\": 5,\n \"output\": 15,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-2024-08-06\": {\n \"input\": 2.5,\n \"output\": 10,\n \"cachedInput\": 1.25,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-2024-11-20\": {\n \"input\": 2.5,\n \"output\": 10,\n \"cachedInput\": 1.25,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-2025-08-28\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-1.5-2026-02-23\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-mini-2025-10-06\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-audio-preview-2024-12-17\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-2024-07-18\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-audio-preview-2024-12-17\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-realtime-preview-2024-12-17\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 128000\n },\n \"gpt-realtime-2025-08-28\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-1.5-2026-02-23\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-mini-2025-10-06\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.06,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-transcribe\": {\n \"input\": 1.25,\n \"output\": 5,\n \"maxInputTokens\": 16000\n },\n \"gpt-4o-realtime-preview-2024-10-01\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-realtime-preview-2024-12-17\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-transcribe\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 16000\n },\n \"gpt-4o-transcribe-diarize\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 16000\n },\n \"gpt-5.1-2025-11-13\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-chat-2025-11-13\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.1-codex-2025-11-13\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-codex-mini-2025-11-13\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-2025-08-07\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-chat\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5-chat-latest\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5-codex\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-mini-2025-08-07\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-nano-2025-08-07\": {\n \"input\": 0.05,\n \"output\": 0.4,\n \"cachedInput\": 0.005,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-pro\": {\n \"input\": 15,\n \"output\": 120,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.1\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-chat\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.1-codex\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-codex-max\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-codex-mini\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-2025-12-11\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-chat\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.2-chat-2025-12-11\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.2-codex\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.3-chat\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.3-codex\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-pro\": {\n \"input\": 21,\n \"output\": 168,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-pro-2025-12-11\": {\n \"input\": 21,\n \"output\": 168,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.4\": {\n \"input\": 2.5,\n \"output\": 15,\n \"cachedInput\": 0.25,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-2026-03-05\": {\n \"input\": 2.5,\n \"output\": 15,\n \"cachedInput\": 0.25,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-pro\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-pro-2026-03-05\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-mini\": {\n \"input\": 0.75,\n \"output\": 4.5,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.4-nano\": {\n \"input\": 0.2,\n \"output\": 1.25,\n \"cachedInput\": 0.02,\n \"maxInputTokens\": 272000\n },\n \"o1-2024-12-17\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 200000\n },\n \"o1-mini\": {\n \"input\": 1.21,\n \"output\": 4.84,\n \"cachedInput\": 0.605,\n \"maxInputTokens\": 128000\n },\n \"o1-mini-2024-09-12\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.55,\n \"maxInputTokens\": 128000\n },\n \"o1-preview\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 128000\n },\n \"o1-preview-2024-09-12\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 128000\n },\n \"o3-2025-04-16\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 200000\n },\n \"o3-mini\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.55,\n \"maxInputTokens\": 200000\n },\n \"o3-mini-2025-01-31\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.55,\n \"maxInputTokens\": 200000\n },\n \"o3-pro\": {\n \"input\": 20,\n \"output\": 80,\n \"maxInputTokens\": 200000\n },\n \"o3-pro-2025-06-10\": {\n \"input\": 20,\n \"output\": 80,\n \"maxInputTokens\": 200000\n },\n \"o4-mini\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.275,\n \"maxInputTokens\": 200000\n },\n \"o4-mini-2025-04-16\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.275,\n \"maxInputTokens\": 200000\n },\n \"deepseek-v3.2\": {\n \"input\": 0.28,\n \"output\": 0.4,\n \"maxInputTokens\": 163840\n },\n \"deepseek-v3.2-speciale\": {\n \"input\": 0.58,\n \"output\": 1.68,\n \"maxInputTokens\": 163840\n },\n \"deepseek-r1\": {\n \"input\": 1.35,\n \"output\": 5.4,\n \"maxInputTokens\": 128000\n },\n \"deepseek-v3\": {\n \"input\": 0.27,\n \"output\": 1.1,\n \"cachedInput\": 0.07,\n \"maxInputTokens\": 65536\n },\n \"deepseek-v3-0324\": {\n \"input\": 0.2,\n \"output\": 0.6,\n \"maxInputTokens\": 131072\n },\n \"chatgpt-4o-latest\": {\n \"input\": 5,\n \"output\": 15,\n \"maxInputTokens\": 128000\n },\n \"claude-haiku-4-5-20251001\": {\n \"input\": 1,\n \"output\": 5,\n \"cachedInput\": 0.1,\n \"cacheCreationInput\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-7-sonnet-20250219\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-haiku-20240307\": {\n \"input\": 0.25,\n \"output\": 1.25,\n \"cachedInput\": 0.03,\n \"cacheCreationInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"claude-3-opus-20240229\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-4-opus-20250514\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-4-sonnet-20250514\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-5-20250929\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-sonnet-4-5-20250929-v1:0\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-1-20250805\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-20250514\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-5-20251101\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-6-20260205\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-7-20260416\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-20250514\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"codex-mini-latest\": {\n \"input\": 1.5,\n \"output\": 6,\n \"cachedInput\": 0.375,\n \"maxInputTokens\": 200000\n },\n \"deepseek-ai/deepseek-r1\": {\n \"input\": 3,\n \"output\": 7,\n \"maxInputTokens\": 128000\n },\n \"deepseek-ai/deepseek-r1-0528\": {\n \"input\": 135000,\n \"output\": 540000,\n \"maxInputTokens\": 161000\n },\n \"deepseek-ai/deepseek-r1-0528-turbo\": {\n \"input\": 1,\n \"output\": 3,\n \"maxInputTokens\": 32768\n },\n \"deepseek-ai/deepseek-r1-distill-llama-70b\": {\n \"input\": 0.25,\n \"output\": 0.75,\n \"maxInputTokens\": 128000\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-32b\": {\n \"input\": 0.15,\n \"output\": 0.15\n },\n \"deepseek-ai/deepseek-r1-turbo\": {\n \"input\": 1,\n \"output\": 3,\n \"maxInputTokens\": 40960\n },\n \"deepseek-ai/deepseek-v3\": {\n \"input\": 1.25,\n \"output\": 1.25,\n \"maxInputTokens\": 65536\n },\n \"deepseek-ai/deepseek-v3-0324\": {\n \"input\": 114000,\n \"output\": 275000,\n \"maxInputTokens\": 161000\n },\n \"deepseek-ai/deepseek-v3.1\": {\n \"input\": 55000,\n \"output\": 165000,\n \"maxInputTokens\": 128000\n },\n \"deepseek-ai/deepseek-v3.1-terminus\": {\n \"input\": 0.27,\n \"output\": 1,\n \"cachedInput\": 0.216,\n \"maxInputTokens\": 163840\n },\n \"deepseek-coder\": {\n \"input\": 0.14,\n \"output\": 0.28,\n \"maxInputTokens\": 128000\n },\n \"gemini-2.0-flash\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.0-flash-001\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.0-flash-lite\": {\n \"input\": 0.075,\n \"output\": 0.3,\n \"cachedInput\": 0.01875,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.0-flash-lite-001\": {\n \"input\": 0.075,\n \"output\": 0.3,\n \"cachedInput\": 0.01875,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-image\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 32768\n },\n \"gemini-3-pro-image-preview\": {\n \"input\": 2,\n \"output\": 12,\n \"maxInputTokens\": 65536\n },\n \"gemini-3.1-flash-image-preview\": {\n \"input\": 0.5,\n \"output\": 3,\n \"maxInputTokens\": 65536\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"input\": 0.25,\n \"output\": 1.5,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-lite\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.01,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-lite-preview-09-2025\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.01,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-preview-09-2025\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 1048576\n },\n \"gemini-live-2.5-flash-preview-native-audio-09-2025\": {\n \"input\": 0.3,\n \"output\": 2,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-lite-preview-06-17\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3-pro-preview\": {\n \"input\": 2,\n \"output\": 12,\n \"cachedInput\": 0.2,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-pro-preview\": {\n \"input\": 2,\n \"output\": 12,\n \"cachedInput\": 0.2,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"input\": 2,\n \"output\": 12,\n \"cachedInput\": 0.2,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3-flash-preview\": {\n \"input\": 0.5,\n \"output\": 3,\n \"cachedInput\": 0.05,\n \"maxInputTokens\": 1048576\n },\n \"gemini-robotics-er-1.5-preview\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-computer-use-preview-10-2025\": {\n \"input\": 1.25,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gemini-flash-latest\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 1048576\n },\n \"gemini-flash-lite-latest\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.01,\n \"maxInputTokens\": 1048576\n },\n \"gemini-gemma-2-27b-it\": {\n \"input\": 0.35,\n \"output\": 1.05,\n \"maxInputTokens\": 8192\n },\n \"gemini-gemma-2-9b-it\": {\n \"input\": 0.35,\n \"output\": 1.05,\n \"maxInputTokens\": 8192\n },\n \"deepseek-ai/deepseek-v3.2\": {\n \"input\": 0.28,\n \"output\": 0.4,\n \"maxInputTokens\": 163840\n },\n \"gpt-3.5-turbo-1106\": {\n \"input\": 1,\n \"output\": 2,\n \"maxInputTokens\": 16385\n },\n \"gpt-3.5-turbo-16k\": {\n \"input\": 3,\n \"output\": 4,\n \"maxInputTokens\": 16385\n },\n \"gpt-4-0314\": {\n \"input\": 30,\n \"output\": 60,\n \"maxInputTokens\": 8192\n },\n \"gpt-4-turbo-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-audio-preview\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-audio-preview-2025-06-03\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-1.5\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-mini\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-mini-2025-12-15\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-audio-preview\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-realtime-preview\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-realtime-preview\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-realtime-preview-2025-06-03\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-image-1.5\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-image-1.5-2025-12-16\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-5.1-chat-latest\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.2-chat-latest\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.3-chat-latest\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5-pro-2025-10-06\": {\n \"input\": 15,\n \"output\": 120,\n \"maxInputTokens\": 128000\n },\n \"gpt-realtime\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-1.5\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-mini\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"deepseek-r1-distill-llama-70b\": {\n \"input\": 0.99,\n \"output\": 0.99,\n \"maxInputTokens\": 32768\n },\n \"deepseek-llama3.3-70b\": {\n \"input\": 0.2,\n \"output\": 0.6,\n \"maxInputTokens\": 131072\n },\n \"deepseek-r1-0528\": {\n \"input\": 0.2,\n \"output\": 0.6,\n \"maxInputTokens\": 131072\n },\n \"deepseek-r1-671b\": {\n \"input\": 0.8,\n \"output\": 0.8,\n \"maxInputTokens\": 131072\n },\n \"deepseek-ai/deepseek-r1-distill-llama-8b\": {\n \"input\": 0.025,\n \"output\": 0.025\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-1.5b\": {\n \"input\": 0.09,\n \"output\": 0.09\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-14b\": {\n \"input\": 0.07,\n \"output\": 0.07\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-7b\": {\n \"input\": 0.2,\n \"output\": 0.2\n },\n \"o1\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 200000\n },\n \"o1-pro\": {\n \"input\": 150,\n \"output\": 600,\n \"maxInputTokens\": 200000\n },\n \"o1-pro-2025-03-19\": {\n \"input\": 150,\n \"output\": 600,\n \"maxInputTokens\": 200000\n },\n \"o3\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 200000\n },\n \"gpt-oss-20b\": {\n \"input\": 0.09,\n \"output\": 0.36\n },\n \"deepseek-ai/deepseek-r1-0528-tput\": {\n \"input\": 0.55,\n \"output\": 2.19,\n \"maxInputTokens\": 128000\n },\n \"claude-3-5-haiku\": {\n \"input\": 1,\n \"output\": 5,\n \"maxInputTokens\": 200000\n },\n \"claude-3-5-haiku@20241022\": {\n \"input\": 1,\n \"output\": 5,\n \"maxInputTokens\": 200000\n },\n \"claude-haiku-4-5@20251001\": {\n \"input\": 1,\n \"output\": 5,\n \"cachedInput\": 0.1,\n \"cacheCreationInput\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-5-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-3-5-sonnet@20240620\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-3-7-sonnet@20250219\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-haiku\": {\n \"input\": 0.25,\n \"output\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-haiku@20240307\": {\n \"input\": 0.25,\n \"output\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-opus\": {\n \"input\": 15,\n \"output\": 75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-opus@20240229\": {\n \"input\": 15,\n \"output\": 75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-3-sonnet@20240229\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-1@20250805\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-5@20251101\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-6@default\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-7@default\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-5@20250929\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4@20250514\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-sonnet-4\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4@20250514\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"deepseek-ai/deepseek-v3.1-maas\": {\n \"input\": 1.35,\n \"output\": 5.4,\n \"maxInputTokens\": 163840\n },\n \"deepseek-ai/deepseek-v3.2-maas\": {\n \"input\": 0.56,\n \"output\": 1.68,\n \"maxInputTokens\": 163840\n },\n \"deepseek-ai/deepseek-r1-0528-maas\": {\n \"input\": 1.35,\n \"output\": 5.4,\n \"maxInputTokens\": 65336\n },\n \"deepseek-ai/deepseek-ocr-maas\": {\n \"input\": 0.3,\n \"output\": 1.2\n },\n \"deepseek-r1-8b\": {\n \"input\": 0.1,\n \"output\": 0.2,\n \"maxInputTokens\": 65536\n },\n \"deepseek-r1-7b-qwen\": {\n \"input\": 0.08,\n \"output\": 0.15,\n \"maxInputTokens\": 131072\n },\n \"deepseek-coder-6.7b\": {\n \"input\": 0.06,\n \"output\": 0.12,\n \"maxInputTokens\": 16384\n },\n \"gpt-4o-mini-transcribe-2025-03-20\": {\n \"input\": 1.25,\n \"output\": 5,\n \"maxInputTokens\": 16000\n },\n \"gpt-4o-mini-transcribe-2025-12-15\": {\n \"input\": 1.25,\n \"output\": 5,\n \"maxInputTokens\": 16000\n },\n \"gpt-realtime-mini-2025-12-15\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.06,\n \"maxInputTokens\": 128000\n },\n \"gemini-2.5-flash-native-audio-latest\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-native-audio-preview-09-2025\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-native-audio-preview-12-2025\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-flash-live-preview\": {\n \"input\": 0.75,\n \"output\": 4.5,\n \"maxInputTokens\": 131072\n },\n \"gemini-pro-latest\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 1048576\n },\n \"gemini-exp-1206\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 1048576\n },\n \"claude-sonnet-4-6@default\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"gpt-5.5\": {\n \"input\": 5,\n \"output\": 30,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.5-2026-04-23\": {\n \"input\": 5,\n \"output\": 30,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.5-pro\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.5-pro-2026-04-23\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-mini-2026-03-17\": {\n \"input\": 0.75,\n \"output\": 4.5,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.4-nano-2026-03-17\": {\n \"input\": 0.2,\n \"output\": 1.25,\n \"cachedInput\": 0.02,\n \"maxInputTokens\": 272000\n },\n \"gpt-image-2\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-image-2-2026-04-21\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-realtime-2\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gemini-3.5-flash\": {\n \"input\": 1.5,\n \"output\": 9,\n \"cachedInput\": 0.15,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-flash-lite\": {\n \"input\": 0.25,\n \"output\": 1.5,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"claude-opus-4-8\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-8@default\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-4-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"claude-4-opus\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 200000\n },\n \"claude-3-7-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n }\n }\n}\n","import { createServer } from 'node:http'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { getDashboardData, getFingerprint } from './data.js'\nimport { getHtml } from './html.js'\nimport type { IStorage } from '../types/index.js'\n\nexport function startDashboardServer(storage: IStorage, port: number): void {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? '/', `http://localhost:${port}`)\n\n if (req.method === 'GET' && url.pathname === '/') {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })\n res.end(getHtml(port))\n return\n }\n\n if (req.method === 'GET' && url.pathname === '/events') {\n const filter = url.searchParams.get('filter') ?? '24h'\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n })\n res.flushHeaders()\n\n let lastFingerprint = ''\n\n async function sendData(): Promise<void> {\n try {\n const data = await getDashboardData(storage, filter)\n const fp = getFingerprint(data)\n if (fp !== lastFingerprint) {\n lastFingerprint = fp\n res.write(`data: ${JSON.stringify(data)}\\n\\n`)\n }\n } catch {\n // best-effort — storage errors must not crash the server\n }\n }\n\n // Send immediately on connect\n void sendData()\n\n const timer = setInterval(() => { void sendData() }, 3000)\n\n res.on('close', () => {\n clearInterval(timer)\n })\n return\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' })\n res.end('Not found')\n })\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n console.error(`[tokenwatch] Port ${port} is already in use. Try: tokenwatch dashboard --port <other>`)\n process.exit(1)\n }\n throw err\n })\n\n server.listen(port, () => {\n console.log(`tokenwatch dashboard → http://localhost:${port}`)\n })\n}\n","import type {\n IStorage,\n UsageEntry,\n Report,\n CostForecast,\n ModelStats,\n SessionStats,\n UserStats,\n FeatureStats,\n AppStats,\n} from '../types/index.js'\n\nexport interface TimeSeriesBucket {\n bucket: string\n cost: number\n calls: number\n}\n\nexport interface DashboardData {\n report: Report\n forecast: CostForecast\n timeSeries: TimeSeriesBucket[]\n lastUpdated: string\n}\n\n/**\n * Maps a filter string to a Unix ms timestamp (entries before this are excluded).\n * Returns undefined for 'all' or undefined (no filter).\n */\nexport function parseSince(filter: string | undefined): number | undefined {\n if (!filter || filter === 'all') return undefined\n const now = Date.now()\n switch (filter) {\n case '1h': return now - 60 * 60 * 1000\n case '24h': return now - 24 * 60 * 60 * 1000\n case '7d': return now - 7 * 24 * 60 * 60 * 1000\n case '30d': return now - 30 * 24 * 60 * 60 * 1000\n default: return undefined\n }\n}\n\n/**\n * Groups entries into time-series buckets.\n * Bucket size: 1h window → 5min, 24h window → 1h, 7d/30d/all → 1day.\n */\nexport function buildTimeSeries(\n entries: UsageEntry[],\n sinceMs: number | undefined,\n): TimeSeriesBucket[] {\n const now = Date.now()\n const windowMs = sinceMs !== undefined ? now - sinceMs : undefined\n\n let bucketMs: number\n if (windowMs !== undefined && windowMs <= 60 * 60 * 1000) {\n bucketMs = 5 * 60 * 1000 // 5-min buckets for ≤1h window\n } else if (windowMs !== undefined && windowMs <= 24 * 60 * 60 * 1000) {\n bucketMs = 60 * 60 * 1000 // 1h buckets for ≤24h window\n } else {\n bucketMs = 24 * 60 * 60 * 1000 // 1-day buckets for 7d/30d/all\n }\n\n const filtered = sinceMs !== undefined\n ? entries.filter((e) => new Date(e.timestamp).getTime() >= sinceMs)\n : entries\n\n const buckets = new Map<string, TimeSeriesBucket>()\n\n for (const entry of filtered) {\n const ts = new Date(entry.timestamp).getTime()\n const bucketTs = Math.floor(ts / bucketMs) * bucketMs\n const bucketKey = new Date(bucketTs).toISOString()\n const existing = buckets.get(bucketKey)\n if (existing) {\n existing.cost += entry.costUSD\n existing.calls += 1\n } else {\n buckets.set(bucketKey, { bucket: bucketKey, cost: entry.costUSD, calls: 1 })\n }\n }\n\n return Array.from(buckets.values()).sort((a, b) => a.bucket.localeCompare(b.bucket))\n}\n\nexport function getFingerprint(data: DashboardData): string {\n return `${data.report.totalCostUSD.toFixed(8)}-${data.report.totalTokens.input}-${data.timeSeries.length}`\n}\n\nexport async function getDashboardData(\n storage: IStorage,\n filter?: string,\n): Promise<DashboardData> {\n const allEntries = await Promise.resolve(storage.getAll())\n const sinceMs = parseSince(filter)\n\n const entries = sinceMs !== undefined\n ? allEntries.filter((e) => new Date(e.timestamp).getTime() >= sinceMs)\n : allEntries\n\n // ── Build report ────────────────────────────────────────────────────────────\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n const byFeature: Record<string, FeatureStats> = {}\n const byApp: Record<string, AppStats> = {}\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n\n for (const e of entries) {\n totalInput += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n\n const m = (byModel[e.model] ??= {\n costUSD: 0, calls: 0, tokens: { input: 0, output: 0, reasoning: 0, cached: 0 },\n })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n m.tokens.output += e.outputTokens\n m.tokens.reasoning += e.reasoningTokens ?? 0\n m.tokens.cached += e.cachedTokens ?? 0\n\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n\n if (e.feature) {\n const f = (byFeature[e.feature] ??= { costUSD: 0, calls: 0 })\n f.costUSD += e.costUSD\n f.calls += 1\n }\n\n if (e.appId) {\n const a = (byApp[e.appId] ??= { costUSD: 0, calls: 0 })\n a.costUSD += e.costUSD\n a.calls += 1\n }\n }\n\n const now = new Date().toISOString()\n const periodFrom = entries[0]?.timestamp ?? now\n const periodTo = entries[entries.length - 1]?.timestamp ?? now\n\n const report: Report = {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n byFeature,\n byApp,\n period: { from: periodFrom, to: periodTo },\n }\n\n // ── Build forecast (always 24h window over all entries) ─────────────────────\n const forecastWindowMs = 24 * 60 * 60 * 1000\n const windowStart = Date.now() - forecastWindowMs\n const windowEntries = allEntries.filter(\n (e) => new Date(e.timestamp).getTime() >= windowStart,\n )\n\n let forecast: CostForecast\n if (windowEntries.length < 2) {\n forecast = {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: null,\n }\n } else {\n const first = windowEntries[0]?.timestamp ?? ''\n const last = windowEntries[windowEntries.length - 1]?.timestamp ?? ''\n const actualMs = new Date(last).getTime() - new Date(first).getTime()\n const actualHours = actualMs / (1000 * 60 * 60)\n if (actualHours < 0.001) {\n forecast = {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: { from: first, to: last },\n }\n } else {\n const windowCost = windowEntries.reduce((s, e) => s + e.costUSD, 0)\n const burnRatePerHour = windowCost / actualHours\n forecast = {\n burnRatePerHour,\n projectedDailyCostUSD: burnRatePerHour * 24,\n projectedMonthlyCostUSD: burnRatePerHour * 24 * 30,\n basedOnHours: Math.round(actualHours * 100) / 100,\n basedOnPeriod: { from: first, to: last },\n }\n }\n }\n\n const timeSeries = buildTimeSeries(allEntries, sinceMs)\n\n return { report, forecast, timeSeries, lastUpdated: now }\n}\n","export function getHtml(_port: number): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<title>tokenwatch — Overview</title>\n<style>\n :root{\n --bg:#0d1117; --surface:#161b22; --surface2:#1c2128;\n --border:#30363d; --border-muted:#21262d;\n --text:#e6edf3; --muted:#7d8590; --dim:#484f58;\n --accent:#58a6ff; --green:#3fb950; --yellow:#d29922; --red:#f85149;\n --mono:ui-monospace,SFMono-Regular,\"SF Mono\",Menlo,Consolas,monospace;\n --sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;\n }\n *{box-sizing:border-box}\n html,body{margin:0}\n body{background:var(--bg);color:var(--text);font-family:var(--sans);\n font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased;\n font-variant-numeric:tabular-nums}\n h1,h2,h3{margin:0;font-weight:600}\n a{color:inherit;text-decoration:none}\n kbd{font-family:var(--sans);font-size:10px;color:var(--muted);\n background:#22272e;border:1px solid var(--border);border-radius:4px;\n padding:1px 5px;line-height:1.4}\n .tw-root{max-width:1440px;margin:0 auto;min-height:100vh;\n border-left:1px solid var(--border-muted);border-right:1px solid var(--border-muted)}\n .num,.tw-kpi-value,.tw-cost,.tw-num-cell,.tw-fc-value{font-variant-numeric:tabular-nums}\n .tw-header{position:sticky;top:0;z-index:40;height:54px;display:flex;\n align-items:center;justify-content:space-between;gap:16px;padding:0 18px;\n background:rgba(13,17,23,.86);backdrop-filter:blur(10px);\n border-bottom:1px solid var(--border)}\n .tw-hgroup{display:flex;align-items:center;gap:14px}\n .tw-logo{font-size:15px;font-weight:700;letter-spacing:-.01em}\n .tw-logo span{color:var(--accent)}\n .tw-ws{position:relative;display:flex;align-items:center;gap:7px;height:30px;\n padding:0 9px;background:var(--surface);border:1px solid var(--border);\n border-radius:7px;color:var(--text);font-size:12.5px;font-weight:500;cursor:pointer}\n .tw-ws:hover{border-color:#444c56}\n .tw-ws-dot{width:7px;height:7px;border-radius:50%;background:var(--green);\n box-shadow:0 0 0 3px rgba(63,185,80,.15)}\n .tw-ws-env{font-size:10px;color:var(--muted);background:#22272e;\n border-radius:4px;padding:1px 5px;text-transform:uppercase;letter-spacing:.03em}\n .tw-ws-menu{position:absolute;top:36px;left:0;width:230px;background:var(--surface2);\n border:1px solid var(--border);border-radius:9px;padding:5px;z-index:50;\n box-shadow:0 12px 32px rgba(0,0,0,.5)}\n .tw-ws-item{padding:7px 9px;border-radius:6px;font-size:12.5px;cursor:pointer;color:var(--text)}\n .tw-ws-item:hover{background:#22272e}\n .tw-ws-item.on{color:var(--accent)}\n .tw-ws-item.muted{color:var(--muted)}\n .tw-ws-sep{height:1px;background:var(--border);margin:5px 0}\n .tw-nav{display:flex;gap:2px;margin-left:6px}\n .tw-nav a{padding:6px 10px;border-radius:6px;font-size:13px;color:var(--muted);font-weight:500}\n .tw-nav a:hover{color:var(--text);background:var(--surface)}\n .tw-nav a.on{color:var(--text);background:var(--surface);box-shadow:inset 0 -2px 0 var(--accent)}\n .tw-search{display:flex;align-items:center;gap:8px;height:30px;padding:0 9px;\n width:200px;background:var(--surface);border:1px solid var(--border);\n border-radius:7px;color:var(--muted);font-size:12.5px;cursor:text}\n .tw-search:hover{border-color:#444c56}\n .tw-search span{flex:1;text-align:left}\n .tw-btn-2{display:flex;align-items:center;gap:6px;height:30px;padding:0 11px;\n background:var(--surface);border:1px solid var(--border);border-radius:7px;\n color:var(--text);font-size:12.5px;font-weight:500;cursor:pointer}\n .tw-btn-2:hover{border-color:#444c56;background:#1b2128}\n .tw-user{display:flex;align-items:center;gap:8px;margin-left:2px}\n .tw-plan{font-size:10px;font-weight:600;color:var(--accent);\n border:1px solid rgba(88,166,255,.4);background:rgba(88,166,255,.1);\n border-radius:5px;padding:2px 6px;text-transform:uppercase;letter-spacing:.04em}\n .tw-avatar{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;\n font-size:11px;font-weight:600;color:#fff;\n background:linear-gradient(135deg,#bc8cff,#58a6ff)}\n .tw-main{padding:16px 18px 64px;display:flex;flex-direction:column;gap:14px}\n .tw-budget{background:var(--surface);border:1px solid var(--border);\n border-radius:10px;padding:13px 16px}\n .tw-budget-top{display:flex;align-items:center;gap:16px;margin-bottom:10px}\n .tw-budget-label{display:flex;align-items:baseline;gap:10px}\n .tw-bud-strong{font-weight:600;font-size:13px}\n .tw-bud-nums{color:var(--muted);font-size:12.5px}\n .tw-bud-nums b{color:var(--text)}\n .tw-bud-alert{display:flex;align-items:center;gap:6px;font-size:12px;\n padding:3px 9px;border-radius:6px;color:var(--green);\n background:rgba(63,185,80,.1);border:1px solid rgba(63,185,80,.25)}\n .tw-bud-alert b{color:var(--text)}\n .tw-budget-days{margin-left:auto;color:var(--muted);font-size:12px}\n .tw-bar{position:relative;height:9px;background:var(--border-muted);\n border-radius:5px;overflow:visible}\n .tw-bar-fill{height:100%;border-radius:5px;transition:width .5s}\n .tw-bar-proj{position:absolute;top:0;height:100%;\n background:repeating-linear-gradient(45deg,rgba(125,133,144,.35),rgba(125,133,144,.35) 4px,transparent 4px,transparent 8px);\n border-radius:0 5px 5px 0}\n .tw-bar-marker{position:absolute;top:-3px;width:2px;height:15px;background:var(--text);border-radius:2px}\n .tw-bar-marker::after{content:attr(data-tip);position:absolute;top:-18px;left:50%;\n transform:translateX(-50%);font-size:9.5px;color:var(--muted);white-space:nowrap}\n .tw-kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}\n .tw-kpi{background:var(--surface);border:1px solid var(--border);border-radius:10px;\n padding:12px 14px;display:flex;flex-direction:column;gap:7px;min-width:0}\n .tw-kpi-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}\n .tw-kpi-main{display:flex;align-items:flex-end;justify-content:space-between;gap:8px}\n .tw-kpi-value{font-size:21px;font-weight:600;letter-spacing:-.01em;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n .tw-kpi-foot{display:flex;align-items:center;gap:8px;font-size:11.5px;min-height:15px}\n .tw-delta{font-weight:600}\n .tw-kpi-sub{color:var(--muted)}\n .tw-timefilter{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 2px}\n .tw-tabs{display:flex;gap:2px;background:var(--surface);border:1px solid var(--border);\n border-radius:8px;padding:3px}\n .tw-tab{height:26px;padding:0 13px;border:0;background:transparent;color:var(--muted);\n font-size:12.5px;font-weight:600;font-family:var(--sans);border-radius:6px;cursor:pointer}\n .tw-tab:hover{color:var(--text)}\n .tw-tab.on{background:var(--accent);color:#0d1117}\n .tw-compare{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12.5px;cursor:pointer}\n .tw-toggle-sm{position:relative;width:30px;height:17px;border:0;border-radius:999px;\n background:#30363d;cursor:pointer;padding:0;transition:background .15s}\n .tw-toggle-sm[data-on=\"1\"]{background:var(--accent)}\n .tw-toggle-sm i{position:absolute;top:2px;left:2px;width:13px;height:13px;border-radius:50%;\n background:#fff;transition:transform .15s}\n .tw-toggle-sm[data-on=\"1\"] i{transform:translateX(13px)}\n .tw-card,.tw-section{background:var(--surface);border:1px solid var(--border);border-radius:10px}\n .tw-section{padding:0}\n .tw-card{padding:14px 16px}\n .tw-sec-head{display:flex;align-items:center;justify-content:space-between;gap:10px;\n padding:13px 16px 11px}\n .tw-card .tw-sec-head{padding:0 0 10px}\n .tw-sec-head h3{font-size:13.5px}\n .tw-sec-sub{color:var(--muted);font-size:11.5px;font-weight:400}\n .tw-charts{display:grid;grid-template-columns:2fr 1fr;gap:14px}\n .tw-chart-main,.tw-chart-side{min-width:0}\n .tw-chart-legend{display:flex;align-items:center;gap:12px;font-size:11.5px;color:var(--muted)}\n .tw-chart-legend span{display:flex;align-items:center;gap:5px}\n .tw-chart-legend i{width:14px;height:3px;border-radius:2px;background:var(--accent)}\n .tw-chart-legend i.dash{background:repeating-linear-gradient(90deg,#6e7681,#6e7681 3px,transparent 3px,transparent 6px)}\n .tw-chart-legend .muted i{background:#6e7681}\n .tw-draw-line{stroke-dasharray:2600;stroke-dashoffset:2600;animation:tw-draw 1.1s cubic-bezier(.4,0,.2,1) forwards}\n .tw-draw-area{opacity:0;animation:tw-fade .9s ease .25s forwards}\n @keyframes tw-draw{to{stroke-dashoffset:0}}\n @keyframes tw-fade{to{opacity:1}}\n .tw-doughnut-wrap{display:grid;place-items:center;padding:6px 0 10px}\n .tw-legend{display:flex;flex-direction:column;gap:1px}\n .tw-legend-row{display:flex;align-items:center;gap:8px;padding:5px 7px;border-radius:6px;cursor:pointer}\n .tw-legend-row:hover,.tw-legend-row.on{background:#1b2128}\n .tw-legend-name{flex:1;font-size:12px;font-family:var(--mono);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .tw-legend-val{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}\n .tw-legend-pct{font-size:11px;color:var(--dim);width:30px;text-align:right;font-variant-numeric:tabular-nums}\n .tw-model-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}\n .tw-model-dot.lg{width:11px;height:11px;border-radius:3px}\n .tw-table-wrap{overflow-x:auto}\n .tw-table{width:100%;border-collapse:collapse;font-size:12.5px}\n .tw-table thead th{position:sticky;top:0;text-align:left;padding:8px 16px;\n font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;\n letter-spacing:.04em;border-bottom:1px solid var(--border);user-select:none;\n background:var(--surface)}\n .tw-th-inner{display:flex;align-items:center;gap:4px}\n .tw-th-inner:hover{color:var(--text)}\n .tw-sort{color:var(--accent);font-size:10px}\n .tw-row{border-bottom:1px solid var(--border-muted);cursor:pointer;transition:background .1s}\n .tw-row:last-child{border-bottom:0}\n .tw-row td{padding:10px 16px;vertical-align:middle}\n .tw-table.compact .tw-row td{padding:6px 16px}\n .tw-table.compact thead th{padding:6px 16px}\n .tw-row:hover{background:#1b2128}\n .tw-row.example{background:rgba(88,166,255,.07);box-shadow:inset 2px 0 0 var(--accent)}\n .tw-row.example:hover{background:rgba(88,166,255,.11)}\n .tw-model-cell{display:flex;align-items:center;gap:9px}\n .tw-model-name{font-family:var(--mono);font-size:12.5px;color:var(--text)}\n .tw-model-prov{font-size:10px;color:var(--muted);background:#22272e;border-radius:4px;padding:1px 5px}\n .tw-num-cell{text-align:right;font-variant-numeric:tabular-nums;color:var(--text);white-space:nowrap}\n .tw-cost{font-weight:600}\n .tw-row-actions{display:flex;gap:6px;justify-content:flex-end}\n .tw-row-actions button{height:24px;padding:0 9px;border:1px solid var(--border);\n background:var(--surface2);color:var(--text);border-radius:6px;font-size:11px;\n font-weight:500;cursor:pointer;font-family:var(--sans)}\n .tw-row-actions button:hover{border-color:var(--accent);color:var(--accent)}\n .tw-forecast{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;padding:0 16px 16px}\n .tw-fc{background:var(--surface2);border:1px solid var(--border);border-radius:9px;\n padding:12px 14px;display:flex;flex-direction:column;gap:6px}\n .tw-fc.flag{border-color:rgba(248,81,73,.5);background:rgba(248,81,73,.07)}\n .tw-fc-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.03em}\n .tw-fc-value{font-size:20px;font-weight:600;letter-spacing:-.01em}\n .tw-fc-sub{font-size:11.5px;color:var(--muted)}\n .tw-act-title{display:flex;align-items:center;gap:9px}\n .tw-collapse{border:0;background:transparent;color:var(--muted);cursor:pointer;\n display:grid;place-items:center;padding:2px;transition:transform .15s}\n .tw-live-dot{width:8px;height:8px;border-radius:50%;background:var(--dim)}\n .tw-live-dot.on{background:var(--green);animation:tw-pulse 1.6s ease-in-out infinite}\n @keyframes tw-pulse{0%,100%{box-shadow:0 0 0 0 rgba(63,185,80,.5)}50%{box-shadow:0 0 0 5px rgba(63,185,80,0)}}\n .tw-act-controls{display:flex;align-items:center;gap:10px}\n .tw-seg-sm{display:flex;gap:1px;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:2px}\n .tw-seg-sm button{height:22px;padding:0 9px;border:0;background:transparent;color:var(--muted);\n font-size:11px;font-weight:600;border-radius:5px;cursor:pointer;font-family:var(--sans)}\n .tw-seg-sm button.on{background:var(--border);color:var(--text)}\n .tw-act-pause{height:26px;padding:0 10px;border:1px solid var(--border);background:var(--surface2);\n color:var(--muted);border-radius:6px;font-size:11.5px;cursor:pointer;font-family:var(--sans)}\n .tw-act-pause:hover{color:var(--text);border-color:#444c56}\n .tw-feed{padding:0 6px 8px}\n .tw-feed-head,.tw-feed-row{display:grid;grid-template-columns:72px 1.4fr 1fr 1fr 96px;gap:12px;align-items:center}\n .tw-feed-head{padding:6px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--dim);border-bottom:1px solid var(--border-muted)}\n .tw-feed-body{display:flex;flex-direction:column}\n .tw-feed-row{padding:7px 10px;font-size:12px;border-bottom:1px solid var(--border-muted)}\n .tw-feed-row:last-child{border-bottom:0}\n .tw-feed-row.fresh{animation:tw-flash 1.4s ease}\n .tw-feed-row.err{background:rgba(248,81,73,.05)}\n @keyframes tw-flash{0%{background:rgba(88,166,255,.16)}100%{background:transparent}}\n .tw-feed-time{color:var(--muted);font-variant-numeric:tabular-nums}\n .tw-feed-model{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11.5px;color:var(--text);overflow:hidden}\n .tw-feed-sess{font-family:var(--mono);font-size:11.5px;color:var(--muted)}\n .tw-feed-cost{text-align:right;font-variant-numeric:tabular-nums;color:var(--text);font-weight:500;display:flex;align-items:center;justify-content:flex-end;gap:6px}\n .tw-feed-flag{font-size:9px;color:var(--red);border:1px solid rgba(248,81,73,.4);border-radius:4px;padding:0 4px;text-transform:uppercase}\n .tw-feed-flag.slow{color:var(--yellow);border-color:rgba(210,153,34,.4)}\n .tw-feat{font-size:10.5px;font-family:var(--mono);border:1px solid;border-radius:5px;padding:1px 6px;white-space:nowrap}\n .tw-feat.sm{font-size:10px;padding:0 5px}\n .tw-scrim{position:fixed;inset:0;background:rgba(1,4,9,.6);opacity:0;pointer-events:none;\n transition:opacity .25s;z-index:60}\n .tw-scrim.open{opacity:1;pointer-events:auto}\n .tw-slideover{position:fixed;top:0;right:0;height:100vh;width:400px;background:var(--surface);\n border-left:1px solid var(--border);transform:translateX(100%);transition:transform .28s cubic-bezier(.4,0,.2,1);\n z-index:61;box-shadow:-16px 0 40px rgba(0,0,0,.4);overflow-y:auto}\n .tw-slideover.open{transform:translateX(0)}\n .tw-so-inner{padding:18px}\n .tw-so-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:16px}\n .tw-so-title{display:flex;align-items:center;gap:10px}\n .tw-so-name{font-family:var(--mono);font-size:15px;font-weight:600}\n .tw-so-prov{font-size:11.5px;color:var(--muted);margin-top:2px}\n .tw-so-close{width:28px;height:28px;border:1px solid var(--border);background:var(--surface2);\n color:var(--muted);border-radius:7px;cursor:pointer;font-size:13px}\n .tw-so-close:hover{color:var(--text);border-color:#444c56}\n .tw-so-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:12px}\n .tw-so-stat{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:10px}\n .tw-so-stat-l{font-size:10.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.03em;margin-bottom:5px}\n .tw-so-stat-v{font-size:16px;font-weight:600;font-variant-numeric:tabular-nums}\n .tw-so-metarow{display:flex;justify-content:space-between;padding:11px 12px;background:var(--surface2);\n border:1px solid var(--border);border-radius:8px;margin-bottom:16px}\n .tw-so-metarow>div{display:flex;flex-direction:column;gap:3px}\n .tw-so-meta-l{font-size:10.5px;color:var(--muted)}\n .tw-so-meta-v{font-size:12.5px;font-weight:500;font-variant-numeric:tabular-nums}\n .tw-so-section-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}\n .tw-so-calls{display:flex;flex-direction:column;gap:7px;margin-bottom:14px}\n .tw-so-call{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:9px 11px}\n .tw-so-call-top{display:flex;align-items:center;gap:9px;margin-bottom:5px}\n .tw-so-call-time{font-size:11px;color:var(--muted);flex:1}\n .tw-so-call-cost{font-size:12px;font-weight:600;font-variant-numeric:tabular-nums}\n .tw-so-call-bot{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}\n .tw-so-viewall{width:100%;height:36px;border:1px solid var(--border);background:var(--surface2);\n color:var(--accent);border-radius:8px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:var(--sans);\n display:flex;align-items:center;justify-content:center;gap:6px}\n .tw-so-viewall:hover{border-color:var(--accent);background:rgba(88,166,255,.08)}\n .tw-cmdk-scrim{position:fixed;inset:0;background:rgba(1,4,9,.55);z-index:80;\n display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;\n animation:tw-fade .15s ease}\n .tw-cmdk{width:560px;max-width:92vw;background:var(--surface2);border:1px solid var(--border);\n border-radius:12px;box-shadow:0 24px 70px rgba(0,0,0,.6);overflow:hidden}\n .tw-cmdk-input{display:flex;align-items:center;gap:10px;padding:13px 15px;border-bottom:1px solid var(--border)}\n .tw-cmdk-input input{flex:1;background:transparent;border:0;outline:none;color:var(--text);\n font-size:14px;font-family:var(--sans)}\n .tw-cmdk-list{max-height:360px;overflow-y:auto;padding:6px}\n .tw-cmdk-item{display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:7px;cursor:pointer}\n .tw-cmdk-item:hover{background:#22272e}\n .tw-cmdk-g{font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--dim);width:62px}\n .tw-cmdk-l{flex:1;font-size:13px}\n .tw-cmdk-empty{padding:18px;text-align:center;color:var(--muted);font-size:12.5px}\n .tw-skel-wrap{display:flex;flex-direction:column;gap:14px}\n .tw-skel{background:linear-gradient(90deg,var(--surface) 25%,#1b2128 50%,var(--surface) 75%);\n background-size:200% 100%;border:1px solid var(--border);border-radius:10px;\n animation:tw-shim 1.4s linear infinite}\n @keyframes tw-shim{to{background-position:-200% 0}}\n .tw-empty{display:flex;flex-direction:column;align-items:center;gap:12px;\n padding:80px 20px;text-align:center}\n .tw-empty-art{width:96px;height:96px;display:grid;place-items:center;\n background:var(--surface);border:1px solid var(--border);border-radius:20px}\n .tw-empty h2{font-size:18px}\n .tw-empty p{max-width:420px;color:var(--muted);font-size:13px;margin:0}\n .tw-empty-actions{display:flex;gap:10px;margin-top:6px}\n .tw-btn-primary{display:flex;align-items:center;gap:6px;height:34px;padding:0 14px;\n background:var(--accent);color:#0d1117;border:0;border-radius:8px;font-size:13px;\n font-weight:600;cursor:pointer;font-family:var(--sans)}\n .tw-btn-primary:hover{filter:brightness(1.08)}\n .tw-empty-snippet{margin-top:12px;font-family:var(--mono);font-size:12px;color:var(--muted);\n background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px 14px}\n .tw-snip-c{color:#ff7b72}.tw-snip-s{color:var(--green)}\n .tw-so-call-lat{font-variant-numeric:tabular-nums}\n @media(prefers-reduced-motion:reduce){*{animation-duration:.001s!important}}\n\n /* tweaks panel */\n .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;\n max-height:calc(100vh - 32px);display:flex;flex-direction:column;\n transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right;\n background:rgba(250,249,247,.78);color:#29261b;\n -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);\n border:.5px solid rgba(255,255,255,.6);border-radius:14px;\n box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);\n font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}\n .twk-hd{display:flex;align-items:center;justify-content:space-between;\n padding:10px 8px 10px 14px;cursor:move;user-select:none}\n .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}\n .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);\n width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}\n .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}\n .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;\n overflow-y:auto;overflow-x:hidden;min-height:0;\n scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}\n .twk-body::-webkit-scrollbar{width:8px}\n .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}\n .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;\n border:2px solid transparent;background-clip:content-box}\n .twk-row{display:flex;flex-direction:column;gap:5px}\n .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}\n .twk-lbl{display:flex;justify-content:space-between;align-items:baseline;color:rgba(41,38,27,.72)}\n .twk-lbl>span:first-child{font-weight:500}\n .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}\n .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;\n color:rgba(41,38,27,.45);padding:10px 0 0}\n .twk-sect:first-child{padding-top:0}\n .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px;\n border:.5px solid rgba(0,0,0,.1);border-radius:7px;\n background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}\n .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}\n select.twk-field{padding-right:22px}\n .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;\n border-radius:999px;background:rgba(0,0,0,.12);outline:none}\n .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;\n width:14px;height:14px;border-radius:50%;background:#fff;\n border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}\n .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;\n background:rgba(0,0,0,.06);user-select:none}\n .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;\n background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);\n transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}\n .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;\n background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;\n border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2}\n .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;\n background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}\n .twk-toggle[data-on=\"1\"]{background:#34c759}\n .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;\n background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}\n .twk-toggle[data-on=\"1\"] i{transform:translateX(14px)}\n .twk-chips{display:flex;gap:6px}\n .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px;\n padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default;\n box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06);transition:transform .12s}\n .twk-chip[data-on=\"1\"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85),0 2px 6px rgba(0,0,0,.15)}\n .twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%;display:flex;flex-direction:column}\n .twk-chip>span>i{flex:1}\n .twk-chip svg{position:absolute;top:6px;left:6px;width:13px;height:13px}\n .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;\n border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;background:transparent}\n .twk-swatch::-webkit-color-swatch-wrapper{padding:0}\n .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}\n</style>\n</head>\n<body>\n<div id=\"root\"></div>\n<script src=\"https://unpkg.com/react@18.3.1/umd/react.production.min.js\" crossorigin=\"anonymous\"></script>\n<script src=\"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js\" crossorigin=\"anonymous\"></script>\n<script src=\"https://unpkg.com/@babel/standalone@7.29.0/babel.min.js\" crossorigin=\"anonymous\"></script>\n\n<script type=\"text/babel\">\n// tweaks-panel — without runtime style injection (CSS is in the main style block)\nfunction useTweaks(defaults) {\n const [values, setValues] = React.useState(defaults);\n const setTweak = React.useCallback((keyOrEdits, val) => {\n const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null\n ? keyOrEdits : { [keyOrEdits]: val };\n setValues((prev) => ({ ...prev, ...edits }));\n window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*');\n window.dispatchEvent(new CustomEvent('tweakchange', { detail: edits }));\n }, []);\n return [values, setTweak];\n}\n\nfunction TweaksPanel({ title = 'Tweaks', children }) {\n const [open, setOpen] = React.useState(false);\n const dragRef = React.useRef(null);\n const offsetRef = React.useRef({ x: 16, y: 16 });\n const PAD = 16;\n const clampToViewport = React.useCallback(() => {\n const panel = dragRef.current; if (!panel) return;\n const w = panel.offsetWidth, h = panel.offsetHeight;\n offsetRef.current = {\n x: Math.min(Math.max(PAD, window.innerWidth - w - PAD), Math.max(PAD, offsetRef.current.x)),\n y: Math.min(Math.max(PAD, window.innerHeight - h - PAD), Math.max(PAD, offsetRef.current.y)),\n };\n panel.style.right = offsetRef.current.x + 'px';\n panel.style.bottom = offsetRef.current.y + 'px';\n }, []);\n React.useEffect(() => {\n if (!open) return;\n clampToViewport();\n const ro = new ResizeObserver(clampToViewport);\n ro.observe(document.documentElement);\n return () => ro.disconnect();\n }, [open, clampToViewport]);\n React.useEffect(() => {\n const onMsg = (e) => {\n const t = e && e.data && e.data.type;\n if (t === '__activate_edit_mode') setOpen(true);\n else if (t === '__deactivate_edit_mode') setOpen(false);\n };\n window.addEventListener('message', onMsg);\n window.parent.postMessage({ type: '__edit_mode_available' }, '*');\n return () => window.removeEventListener('message', onMsg);\n }, []);\n const dismiss = () => { setOpen(false); window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*'); };\n const onDragStart = (e) => {\n const panel = dragRef.current; if (!panel) return;\n const r = panel.getBoundingClientRect();\n const sx = e.clientX, sy = e.clientY;\n const startRight = window.innerWidth - r.right, startBottom = window.innerHeight - r.bottom;\n const move = (ev) => { offsetRef.current = { x: startRight - (ev.clientX - sx), y: startBottom - (ev.clientY - sy) }; clampToViewport(); };\n const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };\n window.addEventListener('mousemove', move); window.addEventListener('mouseup', up);\n };\n if (!open) return null;\n return (\n <div ref={dragRef} className=\"twk-panel\" style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>\n <div className=\"twk-hd\" onMouseDown={onDragStart}>\n <b>{title}</b>\n <button className=\"twk-x\" onMouseDown={(e) => e.stopPropagation()} onClick={dismiss}>✕</button>\n </div>\n <div className=\"twk-body\">{children}</div>\n </div>\n );\n}\nfunction TweakSection({ label }) { return <div className=\"twk-sect\">{label}</div>; }\nfunction TweakRow({ label, value, children, inline }) {\n return (\n <div className={inline ? 'twk-row twk-row-h' : 'twk-row'}>\n <div className=\"twk-lbl\"><span>{label}</span>{value != null && <span className=\"twk-val\">{value}</span>}</div>\n {children}\n </div>\n );\n}\nfunction TweakSlider({ label, value, min=0, max=100, step=1, unit='', onChange }) {\n return (\n <TweakRow label={label} value={value + unit}>\n <input type=\"range\" className=\"twk-slider\" min={min} max={max} step={step} value={value} onChange={(e) => onChange(Number(e.target.value))} />\n </TweakRow>\n );\n}\nfunction TweakToggle({ label, value, onChange }) {\n return (\n <div className=\"twk-row twk-row-h\">\n <div className=\"twk-lbl\"><span>{label}</span></div>\n <button type=\"button\" className=\"twk-toggle\" data-on={value ? '1' : '0'} onClick={() => onChange(!value)}><i /></button>\n </div>\n );\n}\nfunction TweakSelect({ label, value, options, onChange }) {\n return (\n <TweakRow label={label}>\n <select className=\"twk-field\" value={value} onChange={(e) => onChange(e.target.value)}>\n {options.map((o) => { const v = typeof o === 'object' ? o.value : o; const l = typeof o === 'object' ? o.label : o; return <option key={v} value={v}>{l}</option>; })}\n </select>\n </TweakRow>\n );\n}\nfunction TweakRadio({ label, value, options, onChange }) {\n const trackRef = React.useRef(null);\n const [dragging, setDragging] = React.useState(false);\n const valueRef = React.useRef(value); valueRef.current = value;\n const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length;\n const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0);\n const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] || 0);\n if (!fitsAsSegments) {\n const resolve = (s) => { const m = options.find((o) => String(typeof o === 'object' ? o.value : o) === s); return m === undefined ? s : typeof m === 'object' ? m.value : m; };\n return <TweakSelect label={label} value={value} options={options} onChange={(s) => onChange(resolve(s))} />;\n }\n const opts = options.map((o) => (typeof o === 'object' ? o : { value: o, label: o }));\n const idx = Math.max(0, opts.findIndex((o) => o.value === value));\n const n = opts.length;\n const segAt = (clientX) => {\n const r = trackRef.current.getBoundingClientRect();\n const i = Math.floor(((clientX - r.left - 2) / (r.width - 4)) * n);\n return opts[Math.max(0, Math.min(n - 1, i))].value;\n };\n const onPointerDown = (e) => {\n setDragging(true);\n const v0 = segAt(e.clientX); if (v0 !== valueRef.current) onChange(v0);\n const move = (ev) => { if (!trackRef.current) return; const v = segAt(ev.clientX); if (v !== valueRef.current) onChange(v); };\n const up = () => { setDragging(false); window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); };\n window.addEventListener('pointermove', move); window.addEventListener('pointerup', up);\n };\n return (\n <TweakRow label={label}>\n <div ref={trackRef} role=\"radiogroup\" onPointerDown={onPointerDown} className={dragging ? 'twk-seg dragging' : 'twk-seg'}>\n <div className=\"twk-seg-thumb\" style={{ left: 'calc(2px + ' + idx + ' * (100% - 4px) / ' + n + ')', width: 'calc((100% - 4px) / ' + n + ')' }} />\n {opts.map((o) => <button key={o.value} type=\"button\" role=\"radio\" aria-checked={o.value === value}>{o.label}</button>)}\n </div>\n </TweakRow>\n );\n}\nfunction __twkIsLight(hex) {\n const h = String(hex).replace('#', '');\n const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0');\n const n = parseInt(x.slice(0, 6), 16);\n if (Number.isNaN(n)) return true;\n const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;\n return r * 299 + g * 587 + b * 114 > 148000;\n}\nconst __TwkCheck = ({ light }) => (\n <svg viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <path d=\"M3 7.2 5.8 10 11 4.2\" fill=\"none\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" stroke={light ? 'rgba(0,0,0,.78)' : '#fff'} />\n </svg>\n);\nfunction TweakColor({ label, value, options, onChange }) {\n if (!options || !options.length) {\n return (\n <div className=\"twk-row twk-row-h\">\n <div className=\"twk-lbl\"><span>{label}</span></div>\n <input type=\"color\" className=\"twk-swatch\" value={value} onChange={(e) => onChange(e.target.value)} />\n </div>\n );\n }\n const key = (o) => String(JSON.stringify(o)).toLowerCase();\n const cur = key(value);\n return (\n <TweakRow label={label}>\n <div className=\"twk-chips\" role=\"radiogroup\">\n {options.map((o, i) => {\n const colors = Array.isArray(o) ? o : [o];\n const [hero, ...rest] = colors;\n const sup = rest.slice(0, 4);\n const on = key(o) === cur;\n return (\n <button key={i} type=\"button\" className=\"twk-chip\" role=\"radio\" data-on={on ? '1' : '0'} style={{ background: hero }} onClick={() => onChange(o)}>\n {sup.length > 0 && <span>{sup.map((c, j) => <i key={j} style={{ background: c }} />)}</span>}\n {on && <__TwkCheck light={__twkIsLight(hero)} />}\n </button>\n );\n })}\n </div>\n </TweakRow>\n );\n}\nObject.assign(window, { useTweaks, TweaksPanel, TweakSection, TweakRow, TweakSlider, TweakToggle, TweakRadio, TweakSelect, TweakColor });\n</script>\n\n<script type=\"text/babel\">\n// tw-charts.jsx\nconst { useRef, useState, useEffect, useLayoutEffect } = React;\nfunction useMeasure() {\n const ref = useRef(null);\n const [w, setW] = useState(0);\n useLayoutEffect(() => {\n if (!ref.current) return;\n const ro = new ResizeObserver((es) => setW(es[0].contentRect.width));\n ro.observe(ref.current);\n setW(ref.current.clientWidth);\n return () => ro.disconnect();\n }, []);\n return [ref, w];\n}\nfunction useCountUp(target, { duration = 700, enabled = true, decimals = 0 } = {}) {\n const [val, setVal] = useState(enabled ? 0 : target);\n const fromRef = useRef(enabled ? 0 : target);\n useEffect(() => {\n if (!enabled) { setVal(target); return; }\n const from = fromRef.current, start = performance.now();\n let raf;\n const tick = (t) => {\n const p = Math.min(1, (t - start) / duration);\n const e = 1 - Math.pow(1 - p, 3);\n setVal(from + (target - from) * e);\n if (p < 1) raf = requestAnimationFrame(tick);\n else fromRef.current = target;\n };\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [target, enabled, duration]);\n return val;\n}\nfunction LineChart({ current, previous, n, color = '#58a6ff', compare = false, animate = true, fmt }) {\n const [ref, w] = useMeasure();\n const [hover, setHover] = useState(null);\n const H = 248, padT = 16, padB = 26, padL = 8, padR = 8;\n const innerW = Math.max(1, w - padL - padR), innerH = H - padT - padB;\n const series = compare && previous ? [...current, ...previous] : current;\n const max = Math.max(...series, 0.0001) * 1.18;\n const X = (i) => padL + (i / Math.max(n - 1, 1)) * innerW;\n const Y = (v) => padT + innerH - (v / max) * innerH;\n const path = (arr) => arr.map((v, i) => (i ? 'L' : 'M') + X(i).toFixed(1) + ' ' + Y(v).toFixed(1)).join(' ');\n const area = (arr) => path(arr) + ' L' + X(n - 1).toFixed(1) + ' ' + (padT + innerH).toFixed(1) + ' L' + padL.toFixed(1) + ' ' + (padT + innerH).toFixed(1) + ' Z';\n const gid = 'lg' + color.replace('#', '');\n const onMove = (e) => {\n const r = e.currentTarget.getBoundingClientRect();\n const x = e.clientX - r.left - padL;\n const i = Math.max(0, Math.min(n - 1, Math.round((x / innerW) * (n - 1))));\n setHover(i);\n };\n return (\n <div ref={ref} style={{ position: 'relative', width: '100%' }}>\n {w > 0 && (\n <svg width={w} height={H} onMouseMove={onMove} onMouseLeave={() => setHover(null)} style={{ display: 'block', cursor: 'crosshair' }}>\n <defs>\n <linearGradient id={gid} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={color} stopOpacity=\"0.22\" />\n <stop offset=\"100%\" stopColor={color} stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n {[0.25, 0.5, 0.75, 1].map((g, i) => (\n <line key={i} x1={padL} x2={w - padR} y1={padT + innerH * g} y2={padT + innerH * g} stroke=\"#21262d\" strokeWidth=\"1\" />\n ))}\n {current.length > 1 && <path d={area(current)} fill={'url(#' + gid + ')'} className={animate ? 'tw-draw-area' : ''} />}\n {compare && previous && previous.length > 1 && (\n <path d={path(previous)} fill=\"none\" stroke=\"#6e7681\" strokeWidth=\"1.6\" strokeDasharray=\"5 4\" opacity=\"0.85\" />\n )}\n {current.length > 1 && <path d={path(current)} fill=\"none\" stroke={color} strokeWidth=\"2.2\" strokeLinejoin=\"round\" strokeLinecap=\"round\" className={animate ? 'tw-draw-line' : ''} />}\n {hover != null && (\n <g>\n <line x1={X(hover)} x2={X(hover)} y1={padT} y2={padT + innerH} stroke=\"#484f58\" strokeWidth=\"1\" strokeDasharray=\"3 3\" />\n {compare && previous && previous[hover] != null && <circle cx={X(hover)} cy={Y(previous[hover])} r=\"3.5\" fill=\"#21262d\" stroke=\"#6e7681\" strokeWidth=\"1.6\" />}\n <circle cx={X(hover)} cy={Y(current[hover])} r=\"4.5\" fill=\"#0d1117\" stroke={color} strokeWidth=\"2.2\" />\n </g>\n )}\n </svg>\n )}\n {hover != null && w > 0 && (\n <div style={{ position: 'absolute', top: 6, pointerEvents: 'none', left: Math.min(Math.max(X(hover) - 70, 4), w - 144), width: 140, background: '#1c2128', border: '1px solid #30363d', borderRadius: 6, padding: '6px 8px', fontSize: 11, boxShadow: '0 6px 20px rgba(0,0,0,.5)' }}>\n <div style={{ color: '#7d8590', marginBottom: 3 }}>point {hover + 1}/{n}</div>\n <div style={{ display: 'flex', justifyContent: 'space-between', color: '#e6edf3', fontVariantNumeric: 'tabular-nums' }}>\n <span style={{ color }}>● current</span><b>{fmt ? fmt(current[hover]) : current[hover].toFixed(4)}</b>\n </div>\n {compare && previous && previous[hover] != null && (\n <div style={{ display: 'flex', justifyContent: 'space-between', color: '#8b949e', fontVariantNumeric: 'tabular-nums', marginTop: 2 }}>\n <span>○ previous</span><span>{fmt ? fmt(previous[hover]) : previous[hover].toFixed(4)}</span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\nfunction Doughnut({ data, total, fmt, active, onHover }) {\n const size = 188, stroke = 26, r = (size - stroke) / 2, c = 2 * Math.PI * r;\n let acc = 0;\n const safeTotal = total || 0.0001;\n return (\n <svg width={size} height={size} viewBox={'0 0 ' + size + ' ' + size}>\n <g transform={'rotate(-90 ' + (size / 2) + ' ' + (size / 2) + ')'}>\n {data.map((d) => {\n const frac = d.cost / safeTotal;\n const dash = frac * c;\n const seg = (\n <circle key={d.id} cx={size / 2} cy={size / 2} r={r} fill=\"none\"\n stroke={d.color} strokeWidth={active === d.id ? stroke + 5 : stroke}\n strokeDasharray={dash + ' ' + (c - dash)} strokeDashoffset={-acc}\n opacity={active && active !== d.id ? 0.32 : 1}\n style={{ transition: 'opacity .15s, stroke-width .15s', cursor: 'pointer' }}\n onMouseEnter={() => onHover && onHover(d.id)} onMouseLeave={() => onHover && onHover(null)} />\n );\n acc += dash;\n return seg;\n })}\n </g>\n <text x=\"50%\" y=\"46%\" textAnchor=\"middle\" fill=\"#7d8590\" fontSize=\"11\" style={{ textTransform: 'uppercase', letterSpacing: '.05em' }}>\n {active ? ((data.find((d) => d.id === active) || {}).id || 'total') : 'total'}\n </text>\n <text x=\"50%\" y=\"58%\" textAnchor=\"middle\" fill=\"#e6edf3\" fontSize=\"20\" fontWeight=\"600\" style={{ fontVariantNumeric: 'tabular-nums' }}>\n {active ? fmt(((data.find((d) => d.id === active) || {}).cost) || 0) : fmt(total)}\n </text>\n </svg>\n );\n}\nfunction Sparkline({ data, color = '#58a6ff', w = 92, h = 30 }) {\n if (!data || data.length < 2) return null;\n const max = Math.max(...data, 0.0001), min = Math.min(...data);\n const X = (i) => (i / (data.length - 1)) * w;\n const Y = (v) => h - 2 - ((v - min) / (max - min || 1)) * (h - 4);\n const d = data.map((v, i) => (i ? 'L' : 'M') + X(i).toFixed(1) + ' ' + Y(v).toFixed(1)).join(' ');\n return (\n <svg width={w} height={h} style={{ display: 'block' }}>\n <path d={d + ' L' + w + ' ' + h + ' L0 ' + h + ' Z'} fill={color} opacity=\"0.12\" />\n <path d={d} fill=\"none\" stroke={color} strokeWidth=\"1.6\" strokeLinejoin=\"round\" strokeLinecap=\"round\" />\n </svg>\n );\n}\nfunction ShareBar({ frac, color }) {\n return (\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <div style={{ flex: 1, height: 6, background: '#21262d', borderRadius: 3, overflow: 'hidden', minWidth: 40 }}>\n <div style={{ width: (frac * 100).toFixed(1) + '%', height: '100%', background: color, borderRadius: 3, transition: 'width .4s' }} />\n </div>\n <span style={{ color: '#7d8590', fontSize: 11, width: 34, textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}>{(frac * 100).toFixed(1)}%</span>\n </div>\n );\n}\nObject.assign(window, { useMeasure, useCountUp, LineChart, Doughnut, Sparkline, ShareBar });\n</script>\n\n<script type=\"text/babel\">\n// tw-cards.jsx\nconst { useState: useStateC } = React;\nconst Ico = {\n chevron: (p) => <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...p}><path d=\"M3 4.5 6 7.5 9 4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n search: (p) => <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" {...p}><circle cx=\"6\" cy=\"6\" r=\"4.2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" /><path d=\"M9.2 9.2 12 12\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" /></svg>,\n download: (p) => <svg width=\"13\" height=\"13\" viewBox=\"0 0 14 14\" {...p}><path d=\"M7 1.5v7m0 0 2.6-2.6M7 8.5 4.4 5.9M2 11.5h10\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n bolt: (p) => <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...p}><path d=\"M7 1 2.5 7H5l-.5 4L9 5H6.5z\" fill=\"currentColor\" /></svg>,\n arrow: (p) => <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...p}><path d=\"M2.5 6h7m0 0L7 3.5M9.5 6 7 8.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n warn: (p) => <svg width=\"13\" height=\"13\" viewBox=\"0 0 14 14\" {...p}><path d=\"M7 1.5 13 12H1z\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinejoin=\"round\" /><path d=\"M7 5.5v3M7 10.2v.1\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" /></svg>,\n check: (p) => <svg width=\"13\" height=\"13\" viewBox=\"0 0 14 14\" {...p}><path d=\"M2.5 7.5 5.5 10.5 11.5 3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n};\nfunction Header({ t, onOpenPalette }) {\n const [wsOpen, setWsOpen] = useStateC(false);\n const nav = ['Overview', 'Sessions', 'Users', 'Features', 'Settings'];\n return (\n <header className=\"tw-header\">\n <div className=\"tw-hgroup\">\n <div className=\"tw-logo\">token<span>watch</span></div>\n <button className=\"tw-ws\" onClick={() => setWsOpen((v) => !v)}>\n <span className=\"tw-ws-dot\" />\n <span>tokenwatch</span>\n <span className=\"tw-ws-env\">local</span>\n <Ico.chevron style={{ color: '#7d8590' }} />\n </button>\n <nav className=\"tw-nav\">\n {nav.map((n, i) => <a key={n} className={i === 0 ? 'on' : ''} href=\"#\" onClick={(e) => e.preventDefault()}>{n}</a>)}\n </nav>\n </div>\n <div className=\"tw-hgroup\">\n {t.commandPalette && (\n <button className=\"tw-search\" onClick={onOpenPalette}>\n <Ico.search style={{ color: '#7d8590' }} />\n <span>Search</span>\n <kbd>⌘K</kbd>\n </button>\n )}\n <button className=\"tw-btn-2\"><Ico.download /> Export CSV</button>\n </div>\n </header>\n );\n}\nfunction BudgetBar({ t }) {\n const { BUDGET, fmtMoney } = window.TW;\n const { used, limit, daysLeft, cycleDays } = BUDGET;\n const pct = Math.min(used / Math.max(limit, 0.0001), 1);\n const elapsed = cycleDays - daysLeft;\n const barColor = pct < 0.5 ? '#3fb950' : pct < 0.8 ? '#d29922' : '#f85149';\n const projectedDaily = used / Math.max(elapsed, 1);\n const projected = used + projectedDaily * daysLeft;\n const projPct = Math.min(projected / Math.max(limit, 0.0001), 1);\n return (\n <div className=\"tw-budget\">\n <div className=\"tw-budget-top\">\n <div className=\"tw-budget-label\">\n <span className=\"tw-bud-strong\">Monthly budget</span>\n <span className=\"tw-bud-nums\"><b>{fmtMoney(used)}</b> used of {fmtMoney(limit)}</span>\n </div>\n {t.budgetAlerts && (\n <div className=\"tw-bud-alert ok\">\n <Ico.check style={{ color: '#3fb950' }} />\n Projected <b>{fmtMoney(projected)}</b> by cycle end\n </div>\n )}\n <div className=\"tw-budget-days\">{daysLeft} days left · day {elapsed}/{cycleDays}</div>\n </div>\n <div className=\"tw-bar\">\n <div className=\"tw-bar-fill\" style={{ width: (pct * 100) + '%', background: barColor }} />\n {t.budgetAlerts && <div className=\"tw-bar-proj\" style={{ left: (pct * 100) + '%', width: ((projPct - pct) * 100) + '%' }} />}\n {t.budgetAlerts && <div className=\"tw-bar-marker\" style={{ left: (projPct * 100) + '%' }} data-tip={'proj ' + fmtMoney(projected)} />}\n </div>\n </div>\n );\n}\nfunction KpiCard({ label, value, sub, delta, deltaColor, spark, sparkColor, t }) {\n return (\n <div className=\"tw-kpi\">\n <div className=\"tw-kpi-label\">{label}</div>\n <div className=\"tw-kpi-main\">\n <div className=\"tw-kpi-value\">{value}</div>\n {t.kpiSparklines && spark && spark.length >= 2 && <Sparkline data={spark} color={sparkColor} w={84} h={28} />}\n </div>\n <div className=\"tw-kpi-foot\">\n {delta && <span className=\"tw-delta\" style={{ color: t.smartHighlight ? deltaColor : '#7d8590' }}>{delta}</span>}\n {sub && <span className=\"tw-kpi-sub\">{sub}</span>}\n </div>\n </div>\n );\n}\nfunction KpiRow({ kpis, range, t }) {\n const { fmtUSD, fmtInt, seriesForRange } = window.TW;\n const s = seriesForRange(range).current;\n const callsSeries = s.map((v, i) => 6 + Math.abs(Math.sin(i * 1.7)) * 14);\n return (\n <div className=\"tw-kpis\">\n <KpiCard t={t} label=\"Total cost\" value={fmtUSD(kpis.cost)} delta=\"↑ 12% vs last week\" deltaColor=\"#f85149\" spark={s} sparkColor=\"#58a6ff\" />\n <KpiCard t={t} label=\"Input tokens\" value={fmtInt(kpis.inTok)} sub=\"tokens in\" spark={s} sparkColor=\"#3fb950\" />\n <KpiCard t={t} label=\"Output tokens\" value={fmtInt(kpis.outTok)} sub=\"tokens out\" spark={s.map((v) => v * 0.9)} sparkColor=\"#bc8cff\" />\n <KpiCard t={t} label=\"Total calls\" value={fmtInt(kpis.calls)} delta=\"↓ 5% vs last week\" deltaColor=\"#3fb950\" spark={callsSeries} sparkColor=\"#56d4dd\" />\n <KpiCard t={t} label=\"Burn rate\" value={fmtUSD(kpis.burnHr, 4) + '/hr'} sub={'proj ' + fmtUSD(kpis.burnHr * 24, 2) + '/day'} spark={s.map((v) => v * 1.05)} sparkColor=\"#e3b341\" />\n </div>\n );\n}\nfunction TimeFilter({ range, setRange, t, setTweak }) {\n const ranges = ['1h', '24h', '7d', '30d', 'All'];\n return (\n <div className=\"tw-timefilter\">\n <div className=\"tw-tabs\">\n {ranges.map((r) => (\n <button key={r} className={'tw-tab' + (r === range ? ' on' : '')} onClick={() => setRange(r)}>{r}</button>\n ))}\n </div>\n <label className=\"tw-compare\">\n <button className=\"tw-toggle-sm\" data-on={t.compareMode ? '1' : '0'} onClick={() => setTweak('compareMode', !t.compareMode)}><i /></button>\n Compare to previous period\n </label>\n </div>\n );\n}\nfunction ForecastCard({ label, value, sub, accent, flag }) {\n return (\n <div className={'tw-fc' + (flag ? ' flag' : '')}>\n <div className=\"tw-fc-label\">{label}</div>\n <div className=\"tw-fc-value\" style={accent ? { color: accent } : null}>{value}</div>\n {sub && <div className=\"tw-fc-sub\">{sub}</div>}\n </div>\n );\n}\nfunction ForecastSection({ t }) {\n const { fmtUSD, fmtMoney, BUDGET } = window.TW;\n const daily = BUDGET.daily != null ? BUDGET.daily : 0.8473;\n const burnHr = daily / 24;\n const remaining = daily * BUDGET.daysLeft;\n const projCycle = BUDGET.used + remaining;\n const g = t.forecastScenario / 100;\n const scenarioCycle = BUDGET.used + remaining * (1 + g);\n const over = scenarioCycle > BUDGET.limit;\n return (\n <section className=\"tw-section\">\n <div className=\"tw-sec-head\"><h3>Cost forecast</h3><span className=\"tw-sec-sub\">based on current run-rate</span></div>\n <div className=\"tw-forecast\">\n <ForecastCard label=\"Projected daily\" value={fmtUSD(daily, 2)} sub=\"next 24h at this rate\" />\n <ForecastCard label=\"Projected this cycle\" value={fmtMoney(projCycle)} sub={'of ' + fmtMoney(BUDGET.limit) + ' budget'} />\n <ForecastCard label=\"Burn rate\" value={fmtUSD(burnHr, 4)} sub=\"per hour\" accent=\"#e3b341\" />\n <ForecastCard label={'If usage grows ' + t.forecastScenario + '%'} value={fmtMoney(scenarioCycle)} sub={over ? 'over budget ⚠' : fmtMoney(BUDGET.limit - scenarioCycle) + ' headroom'} accent={over ? '#f85149' : '#58a6ff'} flag={over} />\n </div>\n </section>\n );\n}\nObject.assign(window, { Ico, Header, BudgetBar, KpiCard, KpiRow, TimeFilter, ForecastCard, ForecastSection });\n</script>\n\n<script type=\"text/babel\">\n// tw-table.jsx\nconst { useState: useStateT } = React;\nfunction ModelTable({ models, total, t, onRowClick, exampleHover }) {\n const { fmtInt, fmtUSD, fmtCompact } = window.TW;\n const [sort, setSort] = useStateT({ key: 'cost', dir: -1 });\n const [hoverRow, setHoverRow] = useStateT(null);\n const cols = [\n { key: 'id', label: 'Model', align: 'left' },\n { key: 'calls', label: 'Calls', align: 'right' },\n { key: 'inTok', label: 'In tokens', align: 'right' },\n { key: 'outTok', label: 'Out tokens', align: 'right' },\n { key: 'cost', label: 'Cost', align: 'right' },\n { key: 'share', label: 'Share', align: 'left' },\n { key: 'avg', label: 'Avg / call', align: 'right' },\n ];\n const safeTotal = total || 0.0001;\n const rows = [...models].map((m) => ({ ...m, avg: m.cost / Math.max(m.calls, 1), share: m.cost / safeTotal }));\n if (t.tableSort) {\n rows.sort((a, b) => {\n const A = a[sort.key], B = b[sort.key];\n if (typeof A === 'string') return A.localeCompare(B) * sort.dir;\n return (A - B) * sort.dir;\n });\n }\n const clickHeader = (key) => {\n if (!t.tableSort) return;\n setSort((s) => s.key === key ? { key, dir: -s.dir } : { key, dir: key === 'id' ? 1 : -1 });\n };\n return (\n <section className=\"tw-section\">\n <div className=\"tw-sec-head\">\n <h3>Model breakdown</h3>\n <span className=\"tw-sec-sub\">{models.length} models · click a row for detail</span>\n </div>\n <div className=\"tw-table-wrap\">\n <table className={'tw-table' + (t.density === 'compact' ? ' compact' : '')}>\n <thead>\n <tr>\n {cols.map((c) => (\n <th key={c.key} style={{ textAlign: c.align, cursor: t.tableSort && c.key !== 'share' ? 'pointer' : 'default' }}\n onClick={() => c.key !== 'share' && clickHeader(c.key)}>\n <span className=\"tw-th-inner\" style={{ justifyContent: c.align === 'right' ? 'flex-end' : 'flex-start' }}>\n {c.label}\n {t.tableSort && sort.key === c.key && <span className=\"tw-sort\">{sort.dir < 0 ? '▾' : '▴'}</span>}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((m) => {\n const isExample = exampleHover && m.id === 'claude-sonnet-4-6';\n return (\n <tr key={m.id} className={'tw-row' + (isExample ? ' example' : '')}\n onMouseEnter={() => setHoverRow(m.id)} onMouseLeave={() => setHoverRow(null)}\n onClick={() => onRowClick(m)}>\n <td>\n <div className=\"tw-model-cell\">\n <span className=\"tw-model-dot\" style={{ background: m.color }} />\n <span className=\"tw-model-name\">{m.id}</span>\n <span className=\"tw-model-prov\">{m.provider}</span>\n </div>\n </td>\n <td className=\"tw-num-cell\">{fmtInt(m.calls)}</td>\n <td className=\"tw-num-cell\">{fmtCompact(m.inTok)}</td>\n <td className=\"tw-num-cell\">{fmtCompact(m.outTok)}</td>\n <td className=\"tw-num-cell tw-cost\">{fmtUSD(m.cost, 4)}</td>\n <td style={{ minWidth: 140 }}><ShareBar frac={m.share} color={m.color} /></td>\n <td className=\"tw-num-cell\">\n {hoverRow === m.id && t.tableSort ? (\n <div className=\"tw-row-actions\" onClick={(e) => e.stopPropagation()}>\n <button onClick={() => onRowClick(m)}>Details</button>\n <button>Alert</button>\n </div>\n ) : (\n <span>{fmtUSD(m.avg, 4)}</span>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\nObject.assign(window, { ModelTable });\n</script>\n\n<script type=\"text/babel\">\n// tw-panel.jsx\nconst { useEffect: useEffectP } = React;\nfunction SlideOver({ model, onClose, t }) {\n const { fmtInt, fmtUSD, callsForModel, fmtAgo } = window.TW;\n useEffectP(() => {\n const onKey = (e) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [onClose]);\n const open = !!model;\n const m = model;\n const calls = m ? callsForModel(m.id, 5) : [];\n const totalIn = m ? m.inTok : 0, totalOut = m ? m.outTok : 0;\n return (\n <>\n <div className={'tw-scrim' + (open ? ' open' : '')} onClick={onClose} />\n <aside className={'tw-slideover' + (open ? ' open' : '')}>\n {m && (\n <div className=\"tw-so-inner\">\n <div className=\"tw-so-head\">\n <div className=\"tw-so-title\">\n <span className=\"tw-model-dot lg\" style={{ background: m.color }} />\n <div>\n <div className=\"tw-so-name\">{m.id}</div>\n <div className=\"tw-so-prov\">{m.provider} · {fmtInt(m.calls)} calls in window</div>\n </div>\n </div>\n <button className=\"tw-so-close\" onClick={onClose}>✕</button>\n </div>\n <div className=\"tw-so-stats\">\n <div className=\"tw-so-stat\"><div className=\"tw-so-stat-l\">Total cost</div><div className=\"tw-so-stat-v\">{fmtUSD(m.cost, 4)}</div></div>\n <div className=\"tw-so-stat\"><div className=\"tw-so-stat-l\">Calls</div><div className=\"tw-so-stat-v\">{fmtInt(m.calls)}</div></div>\n <div className=\"tw-so-stat\"><div className=\"tw-so-stat-l\">Avg / call</div><div className=\"tw-so-stat-v\">{fmtUSD(m.cost / Math.max(m.calls, 1), 4)}</div></div>\n </div>\n <div className=\"tw-so-metarow\">\n <div><span className=\"tw-so-meta-l\">In tokens</span><span className=\"tw-so-meta-v\">{fmtInt(totalIn)}</span></div>\n <div><span className=\"tw-so-meta-l\">Out tokens</span><span className=\"tw-so-meta-v\">{fmtInt(totalOut)}</span></div>\n <div><span className=\"tw-so-meta-l\">Cost / call</span><span className=\"tw-so-meta-v\">{fmtUSD(m.cost / Math.max(m.calls, 1), 4)}</span></div>\n </div>\n <div className=\"tw-so-section-label\">Last 5 calls (estimated)</div>\n <div className=\"tw-so-calls\">\n {calls.map((c) => (\n <div key={c.id} className=\"tw-so-call\">\n <div className=\"tw-so-call-top\">\n <span className=\"tw-so-call-time\">{fmtAgo(c.secondsAgo)}</span>\n <span className=\"tw-feat\" style={{ color: c.featureColor, borderColor: c.featureColor + '55' }}>{c.feature}</span>\n <span className=\"tw-so-call-cost\">{fmtUSD(c.cost, 4)}</span>\n </div>\n <div className=\"tw-so-call-bot\">\n <span>{fmtInt(c.inTok)} in · {fmtInt(c.outTok)} out</span>\n <span className=\"tw-so-call-lat\">{c.latency}ms{c.status === 'slow' ? ' · slow' : c.status === 'error' ? ' · error' : ''}</span>\n </div>\n </div>\n ))}\n </div>\n <button className=\"tw-so-viewall\">View all {fmtInt(m.calls)} calls <Ico.arrow /></button>\n </div>\n )}\n </aside>\n </>\n );\n}\nObject.assign(window, { SlideOver });\n</script>\n\n<script type=\"text/babel\">\n// tw-activity.jsx\nconst { useState: useStateA, useEffect: useEffectA, useRef: useRefA } = React;\nfunction LiveActivity({ t }) {\n const { seedFeed, makeCall, fmtUSD, fmtInt, fmtAgo } = window.TW;\n const [feed, setFeed] = useStateA(() => seedFeed(14));\n const [now, setNow] = useStateA(Date.now());\n const [paused, setPaused] = useStateA(false);\n const [collapsed, setCollapsed] = useStateA(false);\n const [filter, setFilter] = useStateA('all');\n const seedRef = useRefA(5000);\n const streaming = t.liveFeed && t.animLevel !== 'minimal' && !paused;\n useEffectA(() => {\n if (!streaming) return;\n const iv = setInterval(() => {\n const c = makeCall(0, seedRef.current++);\n c.ts = Date.now(); c.fresh = true;\n setFeed((f) => [c, ...f].slice(0, 40));\n }, t.animLevel === 'subtle' ? 3600 : 2100);\n return () => clearInterval(iv);\n }, [streaming, t.animLevel]);\n useEffectA(() => {\n const iv = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(iv);\n }, []);\n const shown = feed\n .map((c) => ({ ...c, secondsAgo: Math.max(0, Math.round((now - c.ts) / 1000)) }))\n .filter((c) => filter === 'all' ? true : filter === 'errors' ? c.status !== 'ok' : c.cost >= 0.001)\n .slice(0, 10);\n return (\n <section className=\"tw-section tw-activity\">\n <div className=\"tw-sec-head\">\n <div className=\"tw-act-title\">\n <button className=\"tw-collapse\" onClick={() => setCollapsed((v) => !v)} style={{ transform: collapsed ? 'rotate(-90deg)' : 'none' }}><Ico.chevron /></button>\n <span className={'tw-live-dot' + (streaming ? ' on' : '')} />\n <h3>Live activity</h3>\n <span className=\"tw-sec-sub\">{streaming ? 'streaming' : t.liveFeed ? 'paused' : 'static'}</span>\n </div>\n <div className=\"tw-act-controls\">\n <div className=\"tw-seg-sm\">\n {[['all', 'All'], ['signal', 'Signal'], ['errors', 'Errors']].map(([k, l]) => (\n <button key={k} className={filter === k ? 'on' : ''} onClick={() => setFilter(k)}>{l}</button>\n ))}\n </div>\n {t.liveFeed && (\n <button className=\"tw-act-pause\" onClick={() => setPaused((p) => !p)}>{paused ? '▶ Resume' : '❚❚ Pause'}</button>\n )}\n </div>\n </div>\n {!collapsed && (\n <div className=\"tw-feed\">\n <div className=\"tw-feed-head\">\n <span>Time</span><span>Model</span><span>Session</span><span>Feature</span><span style={{ textAlign: 'right' }}>Cost</span>\n </div>\n <div className=\"tw-feed-body\">\n {shown.map((c) => (\n <div key={c.id} className={'tw-feed-row' + (c.fresh ? ' fresh' : '') + (c.status === 'error' ? ' err' : '')}>\n <span className=\"tw-feed-time\">{c.secondsAgo === 0 ? 'now' : fmtAgo(c.secondsAgo)}</span>\n <span className=\"tw-feed-model\"><span className=\"tw-model-dot\" style={{ background: c.modelColor }} />{c.model}</span>\n <span className=\"tw-feed-sess\">{c.session}</span>\n <span><span className=\"tw-feat sm\" style={{ color: c.featureColor, borderColor: c.featureColor + '55' }}>{c.feature}</span></span>\n <span className=\"tw-feed-cost\">{fmtUSD(c.cost, 4)}{c.status === 'error' && <span className=\"tw-feed-flag\">err</span>}{c.status === 'slow' && <span className=\"tw-feed-flag slow\">slow</span>}</span>\n </div>\n ))}\n </div>\n </div>\n )}\n </section>\n );\n}\nfunction CommandPalette({ open, onClose, onAction }) {\n const [q, setQ] = useStateA('');\n const inputRef = useRefA(null);\n useEffectA(() => { if (open && inputRef.current) inputRef.current.focus(); if (open) setQ(''); }, [open]);\n useEffectA(() => {\n const onKey = (e) => { if (e.key === 'Escape') onClose(); };\n if (open) window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [open, onClose]);\n const items = [\n { g: 'Filter', label: 'Set range: Last 1h', act: { range: '1h' } },\n { g: 'Filter', label: 'Set range: Last 24h', act: { range: '24h' } },\n { g: 'Filter', label: 'Set range: Last 7 days', act: { range: '7d' } },\n { g: 'Filter', label: 'Set range: Last 30 days', act: { range: '30d' } },\n { g: 'Filter', label: 'Set range: All time', act: { range: 'All' } },\n { g: 'Action', label: 'Export CSV', hint: '⌘E' },\n { g: 'Action', label: 'Create budget alert' },\n ];\n const filtered = items.filter((i) => i.label.toLowerCase().includes(q.toLowerCase()));\n if (!open) return null;\n return (\n <div className=\"tw-cmdk-scrim\" onClick={onClose}>\n <div className=\"tw-cmdk\" onClick={(e) => e.stopPropagation()}>\n <div className=\"tw-cmdk-input\">\n <Ico.search style={{ color: '#7d8590' }} />\n <input ref={inputRef} value={q} onChange={(e) => setQ(e.target.value)} placeholder=\"Search models, sessions, actions…\" />\n <kbd>esc</kbd>\n </div>\n <div className=\"tw-cmdk-list\">\n {filtered.length === 0 && <div className=\"tw-cmdk-empty\">No results for \"{q}\"</div>}\n {filtered.map((i, idx) => (\n <div key={idx} className=\"tw-cmdk-item\" onClick={() => { onAction(i.act); onClose(); }}>\n <span className=\"tw-cmdk-g\">{i.g}</span>\n <span className=\"tw-cmdk-l\">{i.label}</span>\n {i.hint && <kbd>{i.hint}</kbd>}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\nObject.assign(window, { LiveActivity, CommandPalette });\n</script>\n\n<script type=\"text/babel\">\n// tw-data.jsx — mock data + formatters (always-populated baseline)\nconst fmtUSD = (n, d = 4) =>\n '$' + Number(n).toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d });\nconst fmtMoney = (n) =>\n '$' + Number(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\nconst fmtInt = (n) => Math.round(n || 0).toLocaleString('en-US');\nconst fmtCompact = (n) => {\n n = n || 0;\n if (n >= 1e6) return (n / 1e6).toFixed(2).replace(/\\.?0+$/, '') + 'M';\n if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\\.?0+$/, '') + 'K';\n return String(Math.round(n));\n};\nconst fmtAgo = (s) => {\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.floor(s / 60) + 'm ago';\n if (s < 86400) return Math.floor(s / 3600) + 'h ago';\n return Math.floor(s / 86400) + 'd ago';\n};\nconst BASE_MODELS = [\n { id: 'gpt-5-mini', provider: 'OpenAI', color: '#3fb950', calls: 94, inTok: 1040000, outTok: 118000, cost: 0.2110, latency: 590 },\n { id: 'claude-sonnet-4-6', provider: 'Anthropic', color: '#bc8cff', calls: 58, inTok: 612000, outTok: 84000, cost: 0.3120, latency: 1840 },\n { id: 'gemini-2.5-flash', provider: 'Google', color: '#58a6ff', calls: 47, inTok: 430000, outTok: 56000, cost: 0.1190, latency: 510 },\n { id: 'claude-haiku-4-5', provider: 'Anthropic', color: '#f778ba', calls: 38, inTok: 268100, outTok: 38540, cost: 0.1240, latency: 680 },\n { id: 'gpt-5.1', provider: 'OpenAI', color: '#e3b341', calls: 18, inTok: 88000, outTok: 12000, cost: 0.0613, latency: 2210 },\n { id: 'gemini-2.5-pro', provider: 'Google', color: '#56d4dd', calls: 8, inTok: 42000, outTok: 4000, cost: 0.0200, latency: 1990 },\n];\nconst RANGES = {\n '1h': { factor: 0.052, points: 12, label: 'last hour', step: '5 min' },\n '24h': { factor: 1, points: 24, label: 'last 24h', step: 'hour' },\n '7d': { factor: 6.4, points: 28, label: 'last 7 days', step: '6h' },\n '30d': { factor: 53.1, points: 30, label: 'last 30 days',step: 'day' },\n 'All': { factor: 142, points: 26, label: 'all time', step: 'week' },\n};\nfunction modelsForRange(range) {\n const f = RANGES[range].factor;\n return BASE_MODELS.map((m) => ({\n ...m,\n calls: Math.max(1, Math.round(m.calls * f)),\n inTok: Math.round(m.inTok * f),\n outTok: Math.round(m.outTok * f),\n cost: m.cost * f,\n }));\n}\nfunction kpisForRange(range) {\n const ms = modelsForRange(range);\n const sum = (k) => ms.reduce((a, m) => a + m[k], 0);\n const cost = sum('cost'), calls = sum('calls');\n return {\n cost, calls,\n inTok: sum('inTok'), outTok: sum('outTok'),\n models: ms,\n burnHr: cost / ({ '1h': 1, '24h': 24, '7d': 168, '30d': 720, 'All': 3408 }[range]),\n };\n}\nconst DAY_SHAPE = [0.2,0.15,0.12,0.1,0.1,0.15,0.3,0.6,1.0,1.4,1.7,1.8,1.6,1.5,1.9,2.0,1.7,1.3,1.0,0.8,0.6,0.45,0.35,0.25];\nfunction shapeFor(n) {\n const out = [];\n for (let i = 0; i < n; i++) {\n const t = (i / n) * DAY_SHAPE.length;\n const a = DAY_SHAPE[Math.floor(t) % DAY_SHAPE.length];\n const b = DAY_SHAPE[(Math.floor(t) + 1) % DAY_SHAPE.length];\n out.push(a + (b - a) * (t - Math.floor(t)));\n }\n return out;\n}\nfunction buildSeries(total, n, jitterSeed = 1) {\n const shape = shapeFor(n);\n const j = shape.map((v, i) => v * (0.82 + 0.36 * Math.abs(Math.sin(i * 12.9898 * jitterSeed))));\n const s = j.reduce((a, b) => a + b, 0);\n return j.map((v) => (v / s) * total);\n}\nfunction seriesForRange(range) {\n const { cost } = kpisForRange(range);\n const n = RANGES[range].points;\n return { current: buildSeries(cost, n, 1), previous: buildSeries(cost / 1.12, n, 1.7), n };\n}\nconst FEATURES = ['chat', 'rag-search', 'summarize', 'classify', 'agent-loop', 'embeddings', 'code-review', 'extract'];\nconst FEATURE_COLOR = {\n 'chat': '#58a6ff', 'rag-search': '#3fb950', 'summarize': '#bc8cff', 'classify': '#e3b341',\n 'agent-loop': '#f778ba', 'embeddings': '#56d4dd', 'code-review': '#ff7b72', 'extract': '#7ee787',\n};\nlet __callSeq = 48213;\nfunction rng(seed) { let x = Math.sin(seed) * 10000; return x - Math.floor(x); }\nfunction makeCall(secondsAgo, seed) {\n const m = BASE_MODELS[Math.floor(rng(seed) * BASE_MODELS.length)];\n const feat = FEATURES[Math.floor(rng(seed * 1.3) * FEATURES.length)];\n const inTok = Math.round(800 + rng(seed * 2.1) * 14000);\n const outTok = Math.round(60 + rng(seed * 3.7) * 2200);\n const cost = (inTok / 1e6) * 0.4 + (outTok / 1e6) * 3.2;\n const r = rng(seed * 5.9);\n const status = r > 0.965 ? 'error' : r > 0.9 ? 'slow' : 'ok';\n return {\n id: ++__callSeq, secondsAgo, ts: Date.now() - secondsAgo * 1000,\n model: m.id, modelColor: m.color,\n session: 'sess_' + (seed * 7919 % 1e6 | 0).toString(36).padStart(4, '0'),\n feature: feat, featureColor: FEATURE_COLOR[feat],\n inTok, outTok, cost, latency: Math.round(m.latency * (0.6 + rng(seed * 8.3) * 1.2)), status,\n };\n}\nfunction seedFeed(count) {\n const arr = [];\n for (let i = 0; i < count; i++) arr.push(makeCall(2 + i * 7 + Math.floor(rng(i * 3.3) * 6), 100 + i));\n return arr;\n}\nfunction callsForModel(modelId, count = 5) {\n const arr = [];\n let sa = 12;\n for (let i = 0; i < count; i++) {\n const c = makeCall(sa, 900 + i + modelId.length);\n c.model = modelId;\n c.modelColor = (BASE_MODELS.find((m) => m.id === modelId) || {}).color || '#58a6ff';\n arr.push(c);\n sa += 40 + Math.floor(rng(i * 2.2) * 220);\n }\n return arr;\n}\nconst BUDGET = { used: 45.0, limit: 100.0, daysLeft: 18, cycleDays: 30 };\n\nObject.assign(window, {\n TW: {\n fmtUSD, fmtMoney, fmtInt, fmtCompact, fmtAgo,\n BASE_MODELS, RANGES, modelsForRange, kpisForRange,\n seriesForRange, seedFeed, makeCall, callsForModel,\n FEATURES, FEATURE_COLOR, BUDGET, _buildSeries: buildSeries,\n },\n});\n\n// SSE overlay — patches window.TW functions when real data arrives\n(function () {\n var MC = ['#bc8cff','#3fb950','#58a6ff','#f778ba','#e3b341','#56d4dd','#79c0ff','#ffa657','#ff7b72','#a5d6ff'];\n function guessProv(id) {\n if (/claude/i.test(id)) return 'Anthropic';\n if (/gpt|o1|o3|o4/i.test(id)) return 'OpenAI';\n if (/gemini/i.test(id)) return 'Google';\n return 'Other';\n }\n function buildRealModels(byModel) {\n return Object.entries(byModel).map(function(e, i) {\n var id = e[0], s = e[1];\n return { id: id, provider: guessProv(id), color: MC[i % MC.length],\n calls: s.calls || 0, inTok: (s.tokens && s.tokens.input) || 0,\n outTok: (s.tokens && s.tokens.output) || 0, cost: s.costUSD || 0, latency: 1200 };\n }).sort(function(a, b) { return b.cost - a.cost; });\n }\n function applySSEData(data) {\n var r = data.report, fc = data.forecast, ts = data.timeSeries || [];\n if (!r || !r.byModel || Object.keys(r.byModel).length === 0) return;\n var mods = buildRealModels(r.byModel);\n var totalCalls = mods.reduce(function(s, m) { return s + m.calls; }, 0);\n var totalCost = r.totalCostUSD || 0;\n var totalIn = 0, totalOut = 0;\n if (r.totalTokens) { totalIn = r.totalTokens.input || 0; totalOut = r.totalTokens.output || 0; }\n else { mods.forEach(function(m) { totalIn += m.inTok; totalOut += m.outTok; }); }\n var costs = ts.map(function(b) { return b.cost || 0; });\n window.TW.kpisForRange = function() {\n return { cost: totalCost, calls: totalCalls, inTok: totalIn, outTok: totalOut,\n models: mods, burnHr: (fc && fc.burnRatePerHour) || 0 };\n };\n if (costs.length >= 2) {\n window.TW.seriesForRange = function() {\n return { current: costs, previous: buildSeries(totalCost / 1.12, costs.length, 1.7), n: costs.length };\n };\n }\n if (fc && fc.projectedDailyCostUSD) window.TW.BUDGET.daily = fc.projectedDailyCostUSD;\n if (fc && fc.burnRatePerHour) {\n var elapsed = window.TW.BUDGET.cycleDays - window.TW.BUDGET.daysLeft;\n window.TW.BUDGET.used = fc.burnRatePerHour * 24 * Math.max(elapsed, 1);\n }\n window.dispatchEvent(new CustomEvent('tw-data-update'));\n }\n var evtSource = null;\n function connect(filter) {\n if (evtSource) { try { evtSource.close(); } catch(e) {} }\n var f = filter === 'All' ? 'all' : filter;\n evtSource = new EventSource('/events?filter=' + encodeURIComponent(f));\n evtSource.onmessage = function(e) { try { applySSEData(JSON.parse(e.data)); } catch(_) {} };\n evtSource.onerror = function() {\n try { evtSource.close(); } catch(e) {}\n setTimeout(function() { connect(f); }, 5000);\n };\n }\n window.__twSSEConnect = connect;\n connect('24h');\n})();\n</script>\n\n<script type=\"text/babel\">\n// tw-app.jsx\nconst { useState: useStateApp, useEffect: useEffectApp, useMemo: useMemoApp } = React;\n\n\nfunction LoadingSkeleton() {\n return (\n <div className=\"tw-skel-wrap\">\n <div className=\"tw-skel\" style={{ height: 56 }} />\n <div className=\"tw-kpis\">{[0,0,0,0,0].map((_,i) => <div key={i} className=\"tw-skel\" style={{ height: 96 }} />)}</div>\n <div className=\"tw-charts\">\n <div className=\"tw-skel tw-chart-main\" style={{ height: 320 }} />\n <div className=\"tw-skel tw-chart-side\" style={{ height: 320 }} />\n </div>\n <div className=\"tw-skel\" style={{ height: 260 }} />\n </div>\n );\n}\nfunction EmptyState() {\n return (\n <div className=\"tw-empty\">\n <div className=\"tw-empty-art\">\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <rect x=\"8\" y=\"14\" width=\"48\" height=\"36\" rx=\"4\" stroke=\"#30363d\" strokeWidth=\"2\" />\n <path d=\"M16 40l8-9 7 6 9-13 8 10\" stroke=\"#484f58\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"24\" cy=\"31\" r=\"2\" fill=\"#484f58\" />\n </svg>\n </div>\n <h2>No usage yet</h2>\n <p>Once your app starts making LLM calls through tokenwatch, cost & token metrics show up here in real time.</p>\n <div className=\"tw-empty-actions\">\n <button className=\"tw-btn-primary\">View integration guide <Ico.arrow /></button>\n <button className=\"tw-btn-2\">Copy API key</button>\n </div>\n <div className=\"tw-empty-snippet\">\n <span className=\"tw-snip-c\">import</span> TokenWatch <span className=\"tw-snip-c\">from</span> <span className=\"tw-snip-s\">'@diogonzafe/tokenwatch'</span>\n </div>\n </div>\n );\n}\nfunction ChartsRow({ range, models, kpis, series, t }) {\n const { fmtUSD, RANGES } = window.TW;\n const [activeSlice, setActiveSlice] = useStateApp(null);\n const animate = t.animLevel !== 'minimal';\n const rangeConfig = RANGES[range] || RANGES['24h'];\n const safeTotal = kpis.cost || 0.0001;\n return (\n <div className=\"tw-charts\">\n <div className=\"tw-card tw-chart-main\">\n <div className=\"tw-sec-head\">\n <h3>Cost over time</h3>\n <div className=\"tw-chart-legend\">\n <span><i style={{ background: t.accent }} /> current</span>\n {t.compareMode && series.previous && <span className=\"muted\"><i className=\"dash\" /> previous</span>}\n <span className=\"tw-sec-sub\">· per {rangeConfig.step}</span>\n </div>\n </div>\n <LineChart current={series.current} previous={t.compareMode ? series.previous : null}\n n={series.n} color={t.accent} compare={t.compareMode && !!series.previous}\n animate={animate} fmt={(v) => fmtUSD(v, 4)} />\n </div>\n <div className=\"tw-card tw-chart-side\">\n <div className=\"tw-sec-head\"><h3>By model</h3></div>\n <div className=\"tw-doughnut-wrap\">\n <Doughnut data={models} total={safeTotal} fmt={(v) => fmtUSD(v, 4)} active={activeSlice} onHover={setActiveSlice} />\n </div>\n <div className=\"tw-legend\">\n {[...models].sort((a, b) => b.cost - a.cost).map((m) => (\n <div key={m.id} className={'tw-legend-row' + (activeSlice === m.id ? ' on' : '')}\n onMouseEnter={() => setActiveSlice(m.id)} onMouseLeave={() => setActiveSlice(null)}>\n <span className=\"tw-model-dot\" style={{ background: m.color }} />\n <span className=\"tw-legend-name\">{m.id}</span>\n <span className=\"tw-legend-val\">{fmtUSD(m.cost, 4)}</span>\n <span className=\"tw-legend-pct\">{((m.cost / safeTotal) * 100).toFixed(0)}%</span>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\nconst TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{\n \"density\": \"compact\",\n \"kpiSparklines\": true,\n \"smartHighlight\": true,\n \"compareMode\": false,\n \"tableSort\": true,\n \"budgetAlerts\": true,\n \"forecastScenario\": 20,\n \"liveFeed\": true,\n \"animLevel\": \"lively\",\n \"commandPalette\": true,\n \"appState\": \"data\",\n \"accent\": \"#58a6ff\"\n}/*EDITMODE-END*/;\nfunction App() {\n const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);\n const [range, setRange] = useStateApp('24h');\n const [selModel, setSelModel] = useStateApp(null);\n const [paletteOpen, setPaletteOpen] = useStateApp(false);\n const [_sseV, setSseV] = useStateApp(0);\n\n useEffectApp(() => {\n const h = function() { setSseV(function(v) { return v + 1; }); };\n window.addEventListener('tw-data-update', h);\n return function() { window.removeEventListener('tw-data-update', h); };\n }, []);\n\n useEffectApp(() => {\n if (window.__twSSEConnect) window.__twSSEConnect(range);\n }, [range]);\n\n const kpis = useMemoApp(() => {\n const { kpisForRange } = window.TW;\n return kpisForRange(range);\n }, [range, _sseV]);\n\n const series = useMemoApp(() => {\n const { seriesForRange } = window.TW;\n return seriesForRange(range);\n }, [range, _sseV]);\n\n const models = kpis.models;\n\n useEffectApp(() => {\n const onKey = (e) => {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {\n e.preventDefault();\n if (t.commandPalette) setPaletteOpen((v) => !v);\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [t.commandPalette]);\n\n const handleAction = (act) => {\n if (!act) return;\n if (act.range) setRange(act.range);\n if (act.model) {\n const m = models.find((x) => x.id === act.model);\n if (m) setSelModel({ ...m, share: m.cost / Math.max(kpis.cost, 0.0001) });\n }\n };\n\n return (\n <div className=\"tw-root\" style={{ '--accent': t.accent }}>\n <Header t={t} onOpenPalette={() => setPaletteOpen(true)} />\n {t.appState === 'loading' ? (\n <main className=\"tw-main\"><LoadingSkeleton /></main>\n ) : t.appState === 'empty' ? (\n <main className=\"tw-main\"><EmptyState /></main>\n ) : (\n <main className=\"tw-main\">\n <BudgetBar t={t} />\n <KpiRow kpis={kpis} range={range} t={t} />\n <TimeFilter range={range} setRange={setRange} t={t} setTweak={setTweak} />\n <ChartsRow range={range} models={models} kpis={kpis} series={series} t={t} />\n <ModelTable models={models} total={kpis.cost} t={t} exampleHover={t.smartHighlight}\n onRowClick={(m) => setSelModel({ ...m, share: m.cost / Math.max(kpis.cost, 0.0001) })} />\n <ForecastSection t={t} />\n <LiveActivity t={t} />\n </main>\n )}\n <SlideOver model={selModel} onClose={() => setSelModel(null)} t={t} />\n <CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} onAction={handleAction} />\n <TweaksPanel title=\"Tweaks \\xb7 UX\">\n <TweakSection label=\"Hierarquia & densidade\" />\n <TweakRadio label=\"Densidade\" value={t.density} options={[{ value: 'compact', label: 'Compacto' }, { value: 'comfy', label: 'Confort.' }]} onChange={(v) => setTweak('density', v)} />\n <TweakToggle label=\"Sparklines nos KPIs\" value={t.kpiSparklines} onChange={(v) => setTweak('kpiSparklines', v)} />\n <TweakToggle label=\"Smart highlight\" value={t.smartHighlight} onChange={(v) => setTweak('smartHighlight', v)} />\n <TweakSection label=\"An\\xe1lise\" />\n <TweakToggle label=\"Comparar c/ per\\xedodo anterior\" value={t.compareMode} onChange={(v) => setTweak('compareMode', v)} />\n <TweakToggle label=\"Sorting + a\\xe7\\xf5es na tabela\" value={t.tableSort} onChange={(v) => setTweak('tableSort', v)} />\n <TweakSection label=\"Custo proativo\" />\n <TweakToggle label=\"Alertas de budget\" value={t.budgetAlerts} onChange={(v) => setTweak('budgetAlerts', v)} />\n <TweakSlider label=\"Cen\\xe1rio: crescimento\" value={t.forecastScenario} min={0} max={300} step={5} unit=\"%\" onChange={(v) => setTweak('forecastScenario', v)} />\n <TweakSection label=\"Tempo real\" />\n <TweakToggle label=\"Live feed\" value={t.liveFeed} onChange={(v) => setTweak('liveFeed', v)} />\n <TweakRadio label=\"Anima\\xe7\\xe3o\" value={t.animLevel} options={[{ value: 'lively', label: 'Vivo' }, { value: 'subtle', label: 'Sutil' }, { value: 'minimal', label: 'M\\xedn.' }]} onChange={(v) => setTweak('animLevel', v)} />\n <TweakSection label=\"Navega\\xe7\\xe3o & estado\" />\n <TweakToggle label=\"Command palette (⌘K)\" value={t.commandPalette} onChange={(v) => setTweak('commandPalette', v)} />\n <TweakRadio label=\"Estado\" value={t.appState} options={[{ value: 'data', label: 'Dados' }, { value: 'loading', label: 'Load' }, { value: 'empty', label: 'Vazio' }]} onChange={(v) => setTweak('appState', v)} />\n <TweakSection label=\"Visual\" />\n <TweakColor label=\"Accent\" value={t.accent} options={['#58a6ff', '#3fb950', '#bc8cff', '#f778ba']} onChange={(v) => setTweak('accent', v)} />\n </TweaksPanel>\n </div>\n );\n}\nReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));\n</script>\n</body>\n</html>`;\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["rowToEntry","existsSync","join","homedir","homedir","join","bundledPrices","join","homedir","existsSync","PostgresStorage","MySQLStorage","MongoStorage"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapters/postgres.ts","../src/adapters/mysql.ts","../src/adapters/mongodb.ts","../bin/cli.ts","../src/core/sync.ts","../src/core/storage.ts","../src/core/tracker.ts","../src/core/pricing.ts","../src/exporters/cloud.ts","../src/core/suggestions.ts","../prices.json","../src/dashboard/server.ts","../src/dashboard/data.ts","../src/dashboard/html.ts"],"sourcesContent":["import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for PostgreSQL using the `pg` driver.\n *\n * Install peer dep: npm install pg\n * Types (optional): npm install -D @types/pg\n *\n * @example\n * ```ts\n * import { Pool } from 'pg'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL })\n * const storage = new PostgresStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `pg` installed\ninterface QueryClient {\n query(sql: string, values?: unknown[]): Promise<{ rows: unknown[] }>\n}\n\nexport class PostgresStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0 / v0.3.0. */\n async migrate(): Promise<void> {\n await this.client.query(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGSERIAL PRIMARY KEY,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n reasoning_tokens INTEGER NOT NULL DEFAULT 0,\n cached_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd NUMERIC NOT NULL,\n session_id TEXT,\n user_id TEXT,\n feature TEXT,\n app_id TEXT,\n metadata JSONB,\n timestamp TIMESTAMPTZ NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n for (const col of [\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cached_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cache_creation_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS app_id TEXT',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS metadata JSONB',\n ]) {\n await this.client.query(col).catch(() => { /* column already exists */ })\n }\n }\n\n record(entry: UsageEntry): void {\n this.client\n .query(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, app_id, metadata, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.appId ?? null,\n entry.metadata ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] PostgresStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const result = await this.client.query(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (result.rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.query('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.query(\n 'DELETE FROM tokenwatch_usage WHERE session_id = $1',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n const cachedTokens = (r['cached_tokens'] as number | null) ?? 0\n const cacheCreationTokens = (r['cache_creation_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n ...(cachedTokens > 0 && { cachedTokens }),\n ...(cacheCreationTokens > 0 && { cacheCreationTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n ...(r['app_id'] != null && { appId: r['app_id'] as string }),\n ...(r['metadata'] != null && { metadata: r['metadata'] as Record<string, string> }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MySQL / MariaDB using the `mysql2` driver.\n *\n * Install peer dep: npm install mysql2\n *\n * @example\n * ```ts\n * import mysql from 'mysql2/promise'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = mysql.createPool({ uri: process.env.MYSQL_URL })\n * const storage = new MySQLStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural type so the adapter compiles without `mysql2` installed\ninterface QueryClient {\n execute(sql: string, values?: unknown[]): Promise<[unknown]>\n}\n\nexport class MySQLStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0 / v0.3.0. */\n async migrate(): Promise<void> {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n model VARCHAR(255) NOT NULL,\n input_tokens INT NOT NULL,\n output_tokens INT NOT NULL,\n reasoning_tokens INT NOT NULL DEFAULT 0,\n cached_tokens INT NOT NULL DEFAULT 0,\n cache_creation_tokens INT NOT NULL DEFAULT 0,\n cost_usd DECIMAL(18,8) NOT NULL,\n session_id VARCHAR(255),\n user_id VARCHAR(255),\n feature VARCHAR(255),\n app_id VARCHAR(255),\n metadata JSON,\n timestamp DATETIME(3) NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n await this.client.execute(`\n ALTER TABLE tokenwatch_usage\n ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS feature VARCHAR(255),\n ADD COLUMN IF NOT EXISTS cached_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS cache_creation_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS app_id VARCHAR(255),\n ADD COLUMN IF NOT EXISTS metadata JSON\n `).catch(() => { /* MySQL < 8.0 may not support IF NOT EXISTS — ignore if columns already exist */ })\n }\n\n record(entry: UsageEntry): void {\n this.client\n .execute(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, app_id, metadata, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.appId ?? null,\n entry.metadata != null ? JSON.stringify(entry.metadata) : null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MySQLStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const [rows] = await this.client.execute(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.execute('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.execute(\n 'DELETE FROM tokenwatch_usage WHERE session_id = ?',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n const cachedTokens = (r['cached_tokens'] as number | null) ?? 0\n const cacheCreationTokens = (r['cache_creation_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n ...(cachedTokens > 0 && { cachedTokens }),\n ...(cacheCreationTokens > 0 && { cacheCreationTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n ...(r['app_id'] != null && { appId: r['app_id'] as string }),\n ...(r['metadata'] != null && { metadata: (typeof r['metadata'] === 'string' ? JSON.parse(r['metadata']) : r['metadata']) as Record<string, string> }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MongoDB using the official `mongodb` driver.\n *\n * Install peer dep: npm install mongodb\n *\n * @example\n * ```ts\n * import { MongoClient } from 'mongodb'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const client = new MongoClient(process.env.MONGO_URL!)\n * await client.connect()\n *\n * const storage = new MongoStorage(client.db('myapp'))\n * const tracker = createTracker({ storage })\n * ```\n *\n * Recommended index (run once at startup):\n * ```ts\n * await storage.createIndexes()\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `mongodb` installed\ninterface MongoDocument {\n _id?: unknown\n model: string\n inputTokens: number\n outputTokens: number\n reasoningTokens?: number\n cachedTokens?: number\n cacheCreationTokens?: number\n costUSD: number\n sessionId?: string | null\n userId?: string | null\n feature?: string | null\n appId?: string | null\n metadata?: Record<string, string> | null\n timestamp: string\n}\n\ninterface MongoCursor {\n sort(sort: Record<string, unknown>): MongoCursor\n toArray(): Promise<MongoDocument[]>\n}\n\ninterface Collection {\n insertOne(doc: MongoDocument): Promise<unknown>\n find(filter: Record<string, unknown>): MongoCursor\n deleteMany(filter: Record<string, unknown>): Promise<unknown>\n createIndex(index: Record<string, unknown>): Promise<unknown>\n}\n\ninterface Database {\n collection(name: string): Collection\n}\n\nconst COLLECTION = 'tokenwatch_usage'\n\nexport class MongoStorage implements IStorage {\n private readonly col: Collection\n\n constructor(db: Database) {\n this.col = db.collection(COLLECTION)\n }\n\n /** Creates recommended indexes for query performance. Call once at startup. */\n async createIndexes(): Promise<void> {\n await this.col.createIndex({ timestamp: 1 })\n await this.col.createIndex({ sessionId: 1 })\n await this.col.createIndex({ userId: 1 })\n await this.col.createIndex({ model: 1 })\n await this.col.createIndex({ appId: 1 })\n }\n\n record(entry: UsageEntry): void {\n this.col\n .insertOne({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n ...(entry.reasoningTokens !== undefined && { reasoningTokens: entry.reasoningTokens }),\n ...(entry.cachedTokens !== undefined && { cachedTokens: entry.cachedTokens }),\n ...(entry.cacheCreationTokens !== undefined && { cacheCreationTokens: entry.cacheCreationTokens }),\n costUSD: entry.costUSD,\n sessionId: entry.sessionId ?? null,\n userId: entry.userId ?? null,\n ...(entry.feature !== undefined && { feature: entry.feature }),\n ...(entry.appId !== undefined && { appId: entry.appId }),\n ...(entry.metadata !== undefined && { metadata: entry.metadata }),\n timestamp: entry.timestamp,\n })\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MongoStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const docs = await this.col.find({}).sort({ timestamp: 1 }).toArray()\n return docs.map(docToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.col.deleteMany({})\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.col.deleteMany({ sessionId })\n }\n}\n\nfunction docToEntry(doc: MongoDocument): UsageEntry {\n return {\n model: doc.model,\n inputTokens: doc.inputTokens,\n outputTokens: doc.outputTokens,\n ...(doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens }),\n ...(doc.cachedTokens != null && doc.cachedTokens > 0 && { cachedTokens: doc.cachedTokens }),\n ...(doc.cacheCreationTokens != null && doc.cacheCreationTokens > 0 && { cacheCreationTokens: doc.cacheCreationTokens }),\n costUSD: doc.costUSD,\n ...(doc.sessionId != null && { sessionId: doc.sessionId }),\n ...(doc.userId != null && { userId: doc.userId }),\n ...(doc.feature != null && { feature: doc.feature }),\n ...(doc.appId != null && { appId: doc.appId }),\n ...(doc.metadata != null && { metadata: doc.metadata }),\n timestamp: doc.timestamp,\n }\n}\n","#!/usr/bin/env node\nimport { readFileSync, existsSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport { homedir } from 'node:os'\nimport { fileURLToPath } from 'node:url'\nimport { fetchRemotePrices } from '../src/core/sync.js'\nimport { SqliteStorage } from '../src/core/storage.js'\nimport { createTracker } from '../src/core/tracker.js'\nimport { startDashboardServer } from '../src/dashboard/server.js'\nimport type { IStorage, PricesFile } from '../src/types/index.js'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst DEFAULT_DB_PATH = join(homedir(), '.tokenwatch', 'usage.db')\n\n// ─── Arg helpers ──────────────────────────────────────────────────────────────\n\nfunction getFlag(args: string[], flag: string): string | undefined {\n const idx = args.indexOf(flag)\n if (idx === -1) return undefined\n const value = args[idx + 1]\n // Treat missing value or another flag as absent (e.g. --db --port would be wrong)\n return value !== undefined && !value.startsWith('--') ? value : undefined\n}\n\n// ─── Storage factory ──────────────────────────────────────────────────────────\n\n// Local interface stubs — avoids compile-time deps on optional peer packages.\n// Same pattern used in src/exporters/otel.ts for @opentelemetry/api.\ninterface PgPoolLike { end(): Promise<void> }\ninterface MysqlPoolLike { end(): Promise<void> }\ninterface MongoClientLike { connect(): Promise<void>; close(): Promise<void>; db(name?: string): unknown }\n\ninterface StorageHandle {\n storage: IStorage\n close: () => Promise<void>\n}\n\nasync function openStorage(dbUrl: string | undefined): Promise<StorageHandle> {\n // ── Default: SQLite ──────────────────────────────────────────────────────\n if (!dbUrl) {\n if (!existsSync(DEFAULT_DB_PATH)) {\n console.error(`No SQLite database found at ${DEFAULT_DB_PATH}`)\n console.error(\"Start your app with storage: 'sqlite' to begin recording usage.\")\n console.error('Or pass --db <url> to connect to Postgres, MySQL, or MongoDB.')\n process.exit(1)\n }\n let storage: SqliteStorage\n try {\n storage = new SqliteStorage(DEFAULT_DB_PATH)\n } catch {\n console.error('Failed to open SQLite database. Is better-sqlite3 installed?')\n console.error('Run: npm install better-sqlite3')\n process.exit(1)\n }\n return { storage, close: async () => {} }\n }\n\n // ── Postgres ─────────────────────────────────────────────────────────────\n if (dbUrl.startsWith('postgres://') || dbUrl.startsWith('postgresql://')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let pgMod: any\n try {\n pgMod = (await import('pg' as string)).default\n } catch {\n console.error('[tokenwatch] Postgres requires the pg package.')\n console.error('Run: npm install pg')\n process.exit(1)\n }\n const { PostgresStorage } = await import('../src/adapters/postgres.js')\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n const pool = new pgMod.Pool({ connectionString: dbUrl }) as PgPoolLike\n const storage = new PostgresStorage(pool as never)\n return { storage, close: () => pool.end() }\n }\n\n // ── MySQL ─────────────────────────────────────────────────────────────────\n if (dbUrl.startsWith('mysql://')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let mysqlMod: any\n try {\n mysqlMod = await import('mysql2/promise' as string)\n } catch {\n console.error('[tokenwatch] MySQL requires the mysql2 package.')\n console.error('Run: npm install mysql2')\n process.exit(1)\n }\n const { MySQLStorage } = await import('../src/adapters/mysql.js')\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n const pool = mysqlMod.createPool(dbUrl) as MysqlPoolLike\n const storage = new MySQLStorage(pool as never)\n return { storage, close: () => pool.end() }\n }\n\n // ── MongoDB ───────────────────────────────────────────────────────────────\n if (dbUrl.startsWith('mongodb://') || dbUrl.startsWith('mongodb+srv://')) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let mongoMod: any\n try {\n mongoMod = await import('mongodb' as string)\n } catch {\n console.error('[tokenwatch] MongoDB requires the mongodb package.')\n console.error('Run: npm install mongodb')\n process.exit(1)\n }\n const { MongoStorage } = await import('../src/adapters/mongodb.js')\n const urlObj = new URL(dbUrl)\n const dbName = urlObj.pathname.replace(/^\\//, '') || 'tokenwatch'\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n const client = new mongoMod.MongoClient(dbUrl) as MongoClientLike\n await client.connect()\n const db = client.db(dbName)\n const storage = new MongoStorage(db as never)\n return { storage, close: () => client.close() }\n }\n\n console.error(`[tokenwatch] Unsupported database URL: \"${dbUrl}\"`)\n console.error('Supported protocols: postgres://, mysql://, mongodb://, mongodb+srv://')\n process.exit(1)\n}\n\n// ─── Commands ─────────────────────────────────────────────────────────────────\n\nfunction loadBundledPrices(): PricesFile['models'] {\n const pricesPath = join(__dirname, '..', 'prices.json')\n const raw = readFileSync(pricesPath, 'utf8')\n const data = JSON.parse(raw) as PricesFile\n return data.models\n}\n\nasync function cmdSync(): Promise<void> {\n console.log('Fetching latest prices from remote...')\n const result = await fetchRemotePrices()\n if (result) {\n console.log(`✓ Prices updated. ${Object.keys(result.models).length} models cached (updated_at: ${result.updated_at}).`)\n } else {\n console.error('✗ Failed to fetch remote prices. Check your internet connection.')\n process.exit(1)\n }\n}\n\nfunction cmdPrices(): void {\n const models = loadBundledPrices()\n const rows = Object.entries(models).map(([name, price]) => ({\n model: name,\n input: `$${price.input.toFixed(2)}/M`,\n output: `$${price.output.toFixed(2)}/M`,\n }))\n\n const maxName = Math.max(...rows.map((r) => r.model.length), 5)\n const header = `${'Model'.padEnd(maxName)} ${'Input'.padStart(12)} ${'Output'.padStart(12)}`\n const sep = '-'.repeat(header.length)\n\n console.log(header)\n console.log(sep)\n for (const row of rows) {\n console.log(`${row.model.padEnd(maxName)} ${row.input.padStart(12)} ${row.output.padStart(12)}`)\n }\n}\n\nasync function cmdReport(args: string[]): Promise<void> {\n const dbUrl = getFlag(args, '--db')\n const { storage, close } = await openStorage(dbUrl)\n\n let report: Awaited<ReturnType<ReturnType<typeof createTracker>['getReport']>>\n try {\n const tracker = createTracker({ storage, syncPrices: false })\n report = await tracker.getReport()\n } finally {\n await close()\n }\n\n if (report.totalCostUSD === 0 && Object.keys(report.byModel).length === 0) {\n console.log('No usage recorded yet.')\n return\n }\n\n console.log('\\n── tokenwatch report ──────────────────────────────')\n console.log(` Total cost: $${report.totalCostUSD.toFixed(6)} USD`)\n console.log(` Total tokens: ${report.totalTokens.input.toLocaleString()} in / ${report.totalTokens.output.toLocaleString()} out`)\n console.log(` Period: ${report.period.from} → ${report.period.to}`)\n if (report.pricesUpdatedAt) {\n console.log(` Prices as of: ${report.pricesUpdatedAt}`)\n }\n\n if (Object.keys(report.byModel).length > 0) {\n console.log('\\n By model:')\n for (const [model, stats] of Object.entries(report.byModel)) {\n console.log(` ${model.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n if (Object.keys(report.byUser).length > 0) {\n console.log('\\n By user:')\n for (const [user, stats] of Object.entries(report.byUser)) {\n console.log(` ${user.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n if (Object.keys(report.bySession).length > 0) {\n console.log('\\n By session:')\n for (const [session, stats] of Object.entries(report.bySession)) {\n console.log(` ${session.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n if (Object.keys(report.byFeature).length > 0) {\n console.log('\\n By feature:')\n for (const [feature, stats] of Object.entries(report.byFeature)) {\n console.log(` ${feature.padEnd(30)} $${stats.costUSD.toFixed(6)} (${stats.calls} calls)`)\n }\n }\n\n console.log('───────────────────────────────────────────────────\\n')\n}\n\nasync function cmdDashboard(args: string[]): Promise<void> {\n const portFlag = getFlag(args, '--port')\n const port = portFlag !== undefined ? parseInt(portFlag, 10) : 4242\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(`[tokenwatch] Invalid port: \"${portFlag}\". Must be a number between 1 and 65535.`)\n process.exit(1)\n }\n const dbUrl = getFlag(args, '--db')\n\n const { storage, close } = await openStorage(dbUrl)\n\n // Graceful shutdown — close DB connection when the process exits\n const shutdown = (): void => { void close().then(() => process.exit(0)) }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n startDashboardServer(storage, port)\n}\n\nfunction cmdHelp(): void {\n console.log(`\ntokenwatch — CLI\n\nCommands:\n sync Fetch and cache latest model prices from remote\n prices List all bundled models and their current prices\n report [--db <url>] Show usage report (default: SQLite at ~/.tokenwatch/usage.db)\n dashboard [--port N] Open local web dashboard (default port: 4242)\n [--db <url>] Connect to a database instead of the default SQLite\n\nDatabase URL formats:\n (none) ~/.tokenwatch/usage.db (SQLite, default)\n postgres://user:pass@host:5432/dbname\n mysql://user:pass@host:3306/dbname\n mongodb://user:pass@host:27017/dbname\n\nExamples:\n tokenwatch dashboard\n tokenwatch dashboard --port 8080\n tokenwatch dashboard --db postgres://user:pass@localhost:5432/myapp\n tokenwatch report --db mysql://root:pass@localhost:3306/myapp\n`.trim())\n}\n\n// ─── Entry point ──────────────────────────────────────────────────────────────\n\nasync function main(): Promise<void> {\n const [, , cmd, ...args] = process.argv\n\n switch (cmd) {\n case 'sync':\n await cmdSync()\n break\n case 'prices':\n cmdPrices()\n break\n case 'report':\n await cmdReport(args)\n break\n case 'dashboard':\n await cmdDashboard(args)\n break\n case 'help':\n case undefined:\n cmdHelp()\n break\n default:\n console.error(`Unknown command: ${cmd}\\nRun \"tokenwatch help\" for usage.`)\n process.exit(1)\n }\n}\n\nmain().catch((err: unknown) => {\n console.error(err)\n process.exit(1)\n})\n","import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { PricesFile, PriceMap } from '../types/index.js'\n\nconst CACHE_DIR = join(homedir(), '.tokenwatch')\nconst CACHE_FILE = join(CACHE_DIR, 'prices.json')\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\nconst REMOTE_URL =\n 'https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json'\n\nexport interface PricesResult {\n models: PriceMap\n updated_at: string\n}\n\nexport async function fetchRemotePrices(url = REMOTE_URL): Promise<PricesResult | null> {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(8_000) })\n if (!res.ok) return null\n const data = (await res.json()) as PricesFile\n if (!data?.models) return null\n await persistCache(data)\n return { models: data.models, updated_at: data.updated_at ?? '' }\n } catch {\n return null\n }\n}\n\nexport async function loadCachedPrices(): Promise<PricesResult | null> {\n if (!existsSync(CACHE_FILE)) return null\n try {\n const raw = await readFile(CACHE_FILE, 'utf8')\n const data = JSON.parse(raw) as PricesFile & { _cachedAt?: number }\n const age = Date.now() - (data._cachedAt ?? 0)\n if (age > CACHE_TTL_MS) return null\n if (!data.models) return null\n return { models: data.models, updated_at: data.updated_at ?? '' }\n } catch {\n return null\n }\n}\n\nasync function persistCache(data: PricesFile): Promise<void> {\n try {\n await mkdir(CACHE_DIR, { recursive: true })\n const payload = { ...data, _cachedAt: Date.now() }\n await writeFile(CACHE_FILE, JSON.stringify(payload, null, 2), 'utf8')\n } catch {\n // best-effort — never throw\n }\n}\n\n/**\n * Returns the best available remote price result:\n * 1. Valid local cache (< 24h)\n * 2. Fresh remote fetch (also updates cache)\n * 3. null if both fail\n */\nexport async function getRemotePrices(): Promise<PricesResult | null> {\n const cached = await loadCachedPrices()\n if (cached) return cached\n return fetchRemotePrices()\n}\n","import { createRequire } from 'node:module'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { mkdirSync } from 'node:fs'\nimport type { IStorage, UsageEntry } from '../types/index.js'\n\n// ─── Memory storage ───────────────────────────────────────────────────────────\n\nexport class MemoryStorage implements IStorage {\n private entries: UsageEntry[] = []\n\n record(entry: UsageEntry): void {\n this.entries.push(entry)\n }\n\n getAll(): UsageEntry[] {\n return [...this.entries]\n }\n\n clearAll(): void {\n this.entries = []\n }\n\n clearSession(sessionId: string): void {\n this.entries = this.entries.filter((e) => e.sessionId !== sessionId)\n }\n}\n\n// ─── SQLite storage ───────────────────────────────────────────────────────────\n\nconst DB_DIR = join(homedir(), '.tokenwatch')\nconst DB_PATH = join(DB_DIR, 'usage.db')\n\nexport class SqliteStorage implements IStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private db: any\n\n constructor(dbPath = DB_PATH) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let BetterSqlite3: any\n try {\n // In CJS context globalThis.require is the native require; in ESM use createRequire.\n // This makes the lazy load work in both output formats produced by tsup.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: NodeRequire =\n typeof (globalThis as any).require === 'function'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (globalThis as any).require\n : createRequire(import.meta.url)\n BetterSqlite3 = req('better-sqlite3')\n } catch {\n throw new Error(\n '[tokenwatch] SQLite storage requires better-sqlite3. ' +\n 'Run: npm install better-sqlite3',\n )\n }\n\n mkdirSync(DB_DIR, { recursive: true })\n this.db = new BetterSqlite3(dbPath)\n this.migrate()\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS usage (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n reasoning_tokens INTEGER NOT NULL DEFAULT 0,\n cached_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd REAL NOT NULL,\n session_id TEXT,\n user_id TEXT,\n feature TEXT,\n app_id TEXT,\n metadata TEXT,\n timestamp TEXT NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n const cols = (this.db.prepare(`PRAGMA table_info(usage)`).all() as Array<{ name: string }>)\n .map((c) => c.name)\n if (!cols.includes('reasoning_tokens')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN reasoning_tokens INTEGER NOT NULL DEFAULT 0`)\n }\n if (!cols.includes('feature')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN feature TEXT`)\n }\n if (!cols.includes('cached_tokens')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN cached_tokens INTEGER NOT NULL DEFAULT 0`)\n }\n if (!cols.includes('cache_creation_tokens')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN cache_creation_tokens INTEGER NOT NULL DEFAULT 0`)\n }\n if (!cols.includes('app_id')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN app_id TEXT`)\n }\n if (!cols.includes('metadata')) {\n this.db.exec(`ALTER TABLE usage ADD COLUMN metadata TEXT`)\n }\n }\n\n record(entry: UsageEntry): void {\n this.db\n .prepare(\n `INSERT INTO usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, app_id, metadata, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.appId ?? null,\n entry.metadata != null ? JSON.stringify(entry.metadata) : null,\n entry.timestamp,\n )\n }\n\n getAll(): UsageEntry[] {\n const rows = this.db.prepare('SELECT * FROM usage ORDER BY timestamp ASC').all() as Array<{\n model: string\n input_tokens: number\n output_tokens: number\n reasoning_tokens: number\n cached_tokens: number\n cache_creation_tokens: number\n cost_usd: number\n session_id: string | null\n user_id: string | null\n feature: string | null\n app_id: string | null\n metadata: string | null\n timestamp: string\n }>\n\n return rows.map((r) => ({\n model: r.model,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n ...(r.reasoning_tokens > 0 && { reasoningTokens: r.reasoning_tokens }),\n ...(r.cached_tokens > 0 && { cachedTokens: r.cached_tokens }),\n ...(r.cache_creation_tokens > 0 && { cacheCreationTokens: r.cache_creation_tokens }),\n costUSD: r.cost_usd,\n ...(r.session_id != null && { sessionId: r.session_id }),\n ...(r.user_id != null && { userId: r.user_id }),\n ...(r.feature != null && { feature: r.feature }),\n ...(r.app_id != null && { appId: r.app_id }),\n ...(r.metadata != null && { metadata: JSON.parse(r.metadata) as Record<string, string> }),\n timestamp: r.timestamp,\n }))\n }\n\n clearAll(): void {\n this.db.exec('DELETE FROM usage')\n }\n\n clearSession(sessionId: string): void {\n this.db.prepare('DELETE FROM usage WHERE session_id = ?').run(sessionId)\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createStorage(type: 'memory' | 'sqlite'): IStorage {\n if (type === 'sqlite') return new SqliteStorage()\n return new MemoryStorage()\n}\n","import { z } from 'zod'\nimport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ReportOptions,\n CostForecast,\n ForecastOptions,\n ModelStats,\n SessionStats,\n UserStats,\n FeatureStats,\n AppStats,\n ModelPrice,\n PriceMap,\n IStorage,\n BudgetConfig,\n IExporter,\n} from '../types/index.js'\nimport { resolvePrice, findPrice, calculateCost } from './pricing.js'\nimport { CloudExporter } from '../exporters/cloud.js'\nimport { maybeSuggestCheaperModel } from './suggestions.js'\nimport { createStorage } from './storage.js'\nimport { getRemotePrices } from './sync.js'\nimport bundledPricesFile from '../../prices.json' assert { type: 'json' }\n\nconst bundledPrices: PriceMap = bundledPricesFile.models as PriceMap\nconst bundledUpdatedAt: string = (bundledPricesFile as { updated_at?: string }).updated_at ?? ''\n\n// ─── Config validation schema ─────────────────────────────────────────────────\n\nconst ModelPriceSchema = z.object({\n input: z.number().nonnegative(),\n output: z.number().nonnegative(),\n cachedInput: z.number().nonnegative().optional(),\n cacheCreationInput: z.number().nonnegative().optional(),\n maxInputTokens: z.number().positive().optional(),\n})\n\nconst BudgetConfigSchema = z.object({\n threshold: z.number().positive(),\n webhookUrl: z.string().url(),\n mode: z.enum(['once', 'always']).optional().default('once'),\n})\n\n// storage can be a string enum or an IStorage instance — validated separately\nconst TrackerConfigSchema = z.object({\n storage: z.union([z.enum(['memory', 'sqlite']), z.custom<IStorage>((v) => {\n return (\n v !== null &&\n typeof v === 'object' &&\n typeof (v as IStorage).record === 'function' &&\n typeof (v as IStorage).getAll === 'function' &&\n typeof (v as IStorage).clearAll === 'function' &&\n typeof (v as IStorage).clearSession === 'function'\n )\n })]).optional().default('memory'),\n alertThreshold: z.number().positive().optional(),\n webhookUrl: z.string().url().optional(),\n syncPrices: z.boolean().optional().default(true),\n customPrices: z.record(z.string(), ModelPriceSchema).optional(),\n warnIfStaleAfterHours: z.number().nonnegative().optional().default(72),\n budgets: z.object({\n perUser: BudgetConfigSchema.optional(),\n perSession: BudgetConfigSchema.optional(),\n }).optional(),\n suggestions: z.boolean().optional().default(false),\n anomalyDetection: z.object({\n multiplierThreshold: z.number().positive(),\n webhookUrl: z.string().url(),\n windowHours: z.number().positive().optional().default(24),\n mode: z.enum(['once', 'always']).optional().default('once'),\n }).optional(),\n exporter: z.custom<IExporter>((v) => (\n v !== null &&\n typeof v === 'object' &&\n typeof (v as IExporter).export === 'function'\n )).optional(),\n appId: z.string().optional(),\n cloudApiKey: z.string().optional(),\n cloudEndpoint: z.string().url().optional(),\n})\n\nexport function createTracker(config: TrackerConfig = {}): Tracker {\n const parsed = TrackerConfigSchema.safeParse(config)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\\n')\n throw new Error(`[tokenwatch] Invalid config:\\n${issues}`)\n }\n\n const {\n storage: storageOption,\n alertThreshold,\n webhookUrl,\n syncPrices,\n customPrices,\n warnIfStaleAfterHours,\n budgets,\n suggestions,\n anomalyDetection,\n exporter,\n appId,\n cloudApiKey,\n cloudEndpoint,\n } = parsed.data\n\n const storage: IStorage =\n typeof storageOption === 'object'\n ? storageOption\n : createStorage(storageOption)\n\n const cloudExporter = cloudApiKey\n ? new CloudExporter(cloudApiKey, cloudEndpoint)\n : null\n\n // Fetch remote prices in the background — bundled prices are used as fallback\n // until the sync resolves. Negligible overhead added to createTracker().\n let remotePrices: PriceMap | undefined\n let pricesUpdatedAt: string = bundledUpdatedAt\n if (syncPrices) {\n getRemotePrices()\n .then((result) => {\n if (result) {\n remotePrices = result.models\n pricesUpdatedAt = result.updated_at\n }\n })\n .catch(() => {\n // best-effort — bundled prices remain in use\n })\n }\n\n // Warn if prices are stale (checked lazily on first access)\n let stalenessChecked = false\n function maybeWarnStaleness(): void {\n if (stalenessChecked || !warnIfStaleAfterHours) return\n stalenessChecked = true\n if (!pricesUpdatedAt) return\n try {\n const updatedMs = new Date(pricesUpdatedAt).getTime()\n const ageHours = (Date.now() - updatedMs) / (1000 * 60 * 60)\n if (ageHours > warnIfStaleAfterHours) {\n console.warn(\n `[tokenwatch] Price data is ${Math.round(ageHours)}h old (updated_at: ${pricesUpdatedAt}). ` +\n `Run \"tokenwatch sync\" to refresh, or set warnIfStaleAfterHours: 0 to suppress.`,\n )\n }\n } catch {\n // best-effort\n }\n }\n\n let alertFired = false\n const firedUserAlerts = new Set<string>()\n const firedSessionAlerts = new Set<string>()\n const firedAnomalyKeys = new Set<string>()\n const startedAt = new Date().toISOString()\n\n function resolveModelPrice(model: string) {\n maybeWarnStaleness()\n return resolvePrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices: customPrices as PriceMap }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n\n function track(entry: Omit<UsageEntry, 'costUSD' | 'timestamp'>): void {\n const price = resolveModelPrice(entry.model)\n const costUSD = calculateCost(\n entry.inputTokens,\n entry.outputTokens,\n price,\n entry.cachedTokens,\n entry.cacheCreationTokens,\n )\n const full: UsageEntry = {\n ...entry,\n costUSD,\n timestamp: new Date().toISOString(),\n ...(appId !== undefined && entry.appId === undefined && { appId }),\n }\n storage.record(full)\n if (exporter) {\n Promise.resolve(exporter.export(full)).catch(() => { /* fire-and-forget */ })\n }\n if (cloudExporter) {\n cloudExporter.export(full)\n }\n maybeFireAlerts(full)\n if (anomalyDetection) maybeDetectAnomaly(full)\n if (suggestions) {\n maybeSuggestCheaperModel(entry.model, costUSD, entry.inputTokens, entry.outputTokens, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices: customPrices as PriceMap }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n }\n\n function maybeFireAlerts(entry: UsageEntry): void {\n // Global threshold alert\n if (alertThreshold && webhookUrl && !alertFired) {\n alertFired = true\n Promise.resolve(storage.getAll()).then((entries) => {\n const total = computeTotal(entries)\n if (total < alertThreshold!) {\n alertFired = false\n return\n }\n fireWebhook(webhookUrl!, {\n text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`,\n })\n }).catch(() => {\n alertFired = false\n })\n }\n\n // Per-user budget alert\n if (budgets?.perUser && entry.userId) {\n const cfg = budgets.perUser\n const uid = entry.userId\n if (cfg.mode === 'always' || !firedUserAlerts.has(uid)) {\n // Claim the slot synchronously before going async — prevents double-fire\n if (cfg.mode !== 'always') firedUserAlerts.add(uid)\n Promise.resolve(storage.getAll()).then((entries) => {\n const userCost = entries\n .filter((e) => e.userId === uid)\n .reduce((s, e) => s + e.costUSD, 0)\n if (userCost >= cfg.threshold) {\n fireWebhook(cfg.webhookUrl, {\n text: `[tokenwatch] Budget alert: user \"${uid}\" reached $${userCost.toFixed(4)} USD (threshold: $${cfg.threshold})`,\n })\n } else {\n if (cfg.mode !== 'always') firedUserAlerts.delete(uid) // release — threshold not yet met\n }\n }).catch(() => {\n if (cfg.mode !== 'always') firedUserAlerts.delete(uid) // release on storage error\n })\n }\n }\n\n // Per-session budget alert\n if (budgets?.perSession && entry.sessionId) {\n const cfg = budgets.perSession\n const sid = entry.sessionId\n if (cfg.mode === 'always' || !firedSessionAlerts.has(sid)) {\n // Claim the slot synchronously before going async — prevents double-fire\n if (cfg.mode !== 'always') firedSessionAlerts.add(sid)\n Promise.resolve(storage.getAll()).then((entries) => {\n const sessionCost = entries\n .filter((e) => e.sessionId === sid)\n .reduce((s, e) => s + e.costUSD, 0)\n if (sessionCost >= cfg.threshold) {\n fireWebhook(cfg.webhookUrl, {\n text: `[tokenwatch] Budget alert: session \"${sid}\" reached $${sessionCost.toFixed(4)} USD (threshold: $${cfg.threshold})`,\n })\n } else {\n if (cfg.mode !== 'always') firedSessionAlerts.delete(sid) // release\n }\n }).catch(() => {\n if (cfg.mode !== 'always') firedSessionAlerts.delete(sid) // release on storage error\n })\n }\n }\n }\n\n function fireWebhook(url: string, payload: { text: string }): void {\n fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }).catch(() => {\n // fire-and-forget\n })\n }\n\n async function getReport(options?: ReportOptions): Promise<Report> {\n const allEntries = await Promise.resolve(storage.getAll())\n const entries = filterEntries(allEntries, options)\n\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n const byFeature: Record<string, FeatureStats> = {}\n const byApp: Record<string, AppStats> = {}\n const byMetadata: Record<string, Record<string, { costUSD: number; calls: number }>> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n let periodFrom = options ? (entries[0]?.timestamp ?? startedAt) : startedAt\n let lastTimestamp = periodFrom\n\n for (const e of entries) {\n totalInput += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp\n\n // byModel\n const m = (byModel[e.model] ??= {\n costUSD: 0,\n calls: 0,\n tokens: { input: 0, output: 0, reasoning: 0, cached: 0 },\n })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n m.tokens.output += e.outputTokens\n m.tokens.reasoning += e.reasoningTokens ?? 0\n m.tokens.cached += e.cachedTokens ?? 0\n\n // bySession\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n // byUser\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n\n // byFeature\n if (e.feature) {\n const f = (byFeature[e.feature] ??= { costUSD: 0, calls: 0 })\n f.costUSD += e.costUSD\n f.calls += 1\n }\n\n // byApp\n if (e.appId) {\n const a = (byApp[e.appId] ??= { costUSD: 0, calls: 0 })\n a.costUSD += e.costUSD\n a.calls += 1\n }\n\n // byMetadata\n if (e.metadata) {\n for (const [key, val] of Object.entries(e.metadata)) {\n const group = (byMetadata[key] ??= {})\n const slot = (group[val] ??= { costUSD: 0, calls: 0 })\n slot.costUSD += e.costUSD\n slot.calls += 1\n }\n }\n }\n\n // When filtering, use the actual first entry's timestamp as period.from\n if (options && entries.length > 0) {\n periodFrom = entries[0]?.timestamp ?? periodFrom\n }\n\n return {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n byFeature,\n byApp,\n byMetadata,\n period: { from: periodFrom, to: lastTimestamp },\n ...(pricesUpdatedAt ? { pricesUpdatedAt } : {}),\n }\n }\n\n async function getCostForecast(options: ForecastOptions = {}): Promise<CostForecast> {\n const windowHours = options.windowHours ?? 24\n const allEntries = await Promise.resolve(storage.getAll())\n\n const now = Date.now()\n const windowStart = now - windowHours * 60 * 60 * 1000\n const windowEntries = allEntries.filter(\n (e) => new Date(e.timestamp).getTime() >= windowStart,\n )\n\n if (windowEntries.length < 2) {\n return {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: null,\n }\n }\n\n const first = windowEntries[0]?.timestamp ?? ''\n const last = windowEntries[windowEntries.length - 1]?.timestamp ?? ''\n const actualMs = new Date(last).getTime() - new Date(first).getTime()\n const actualHours = actualMs / (1000 * 60 * 60)\n\n if (actualHours < 0.001) {\n return {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: { from: first, to: last },\n }\n }\n\n const totalCost = windowEntries.reduce((s, e) => s + e.costUSD, 0)\n const burnRatePerHour = totalCost / actualHours\n\n return {\n burnRatePerHour,\n projectedDailyCostUSD: burnRatePerHour * 24,\n projectedMonthlyCostUSD: burnRatePerHour * 24 * 30,\n basedOnHours: Math.round(actualHours * 100) / 100,\n basedOnPeriod: { from: first, to: last },\n }\n }\n\n function maybeDetectAnomaly(entry: UsageEntry): void {\n if (entry.costUSD <= 0) return\n const { multiplierThreshold, webhookUrl: aUrl, windowHours: wh, mode: modeRaw } = anomalyDetection!\n const wHours = wh ?? 24\n const mode = modeRaw ?? 'once'\n const windowStart = Date.now() - wHours * 60 * 60 * 1000\n const entryTs = new Date(entry.timestamp).getTime()\n\n function checkEntity(key: string, label: string, predicate: (e: UsageEntry) => boolean): void {\n if (mode !== 'always' && firedAnomalyKeys.has(key)) return\n if (mode !== 'always') firedAnomalyKeys.add(key)\n Promise.resolve(storage.getAll()).then((all) => {\n const history = all.filter(\n (e) =>\n predicate(e) &&\n new Date(e.timestamp).getTime() >= windowStart &&\n new Date(e.timestamp).getTime() !== entryTs,\n )\n if (history.length === 0) {\n if (mode !== 'always') firedAnomalyKeys.delete(key)\n return\n }\n const avg = history.reduce((s, e) => s + e.costUSD, 0) / history.length\n if (avg <= 0 || entry.costUSD <= avg * multiplierThreshold) {\n if (mode !== 'always') firedAnomalyKeys.delete(key)\n return\n }\n const multiple = (entry.costUSD / avg).toFixed(1)\n fireWebhook(aUrl, {\n text: `[tokenwatch] Anomaly: ${label} call cost $${entry.costUSD.toFixed(4)} is ${multiple}x above ${wHours}h average ($${avg.toFixed(4)})`,\n })\n }).catch(() => {\n if (mode !== 'always') firedAnomalyKeys.delete(key)\n })\n }\n\n if (entry.userId) {\n checkEntity(\n `user:${entry.userId}`,\n `user \"${entry.userId}\"`,\n (e) => e.userId === entry.userId,\n )\n }\n checkEntity(\n `model:${entry.model}`,\n `model \"${entry.model}\"`,\n (e) => e.model === entry.model,\n )\n }\n\n async function reset(): Promise<void> {\n await Promise.resolve(storage.clearAll())\n alertFired = false\n firedUserAlerts.clear()\n firedSessionAlerts.clear()\n firedAnomalyKeys.clear()\n }\n\n async function resetSession(sessionId: string): Promise<void> {\n await Promise.resolve(storage.clearSession(sessionId))\n firedSessionAlerts.delete(sessionId)\n }\n\n async function exportJSON(): Promise<string> {\n return JSON.stringify(await getReport(), null, 2)\n }\n\n async function exportCSV(): Promise<string> {\n const entries = await Promise.resolve(storage.getAll())\n const header =\n 'timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId,metadata'\n const rows = entries.map((e) =>\n [\n csvEscape(e.timestamp),\n csvEscape(e.model),\n e.inputTokens,\n e.outputTokens,\n e.reasoningTokens ?? 0,\n e.cachedTokens ?? 0,\n e.cacheCreationTokens ?? 0,\n e.costUSD.toFixed(8),\n csvEscape(e.sessionId ?? ''),\n csvEscape(e.userId ?? ''),\n csvEscape(e.feature ?? ''),\n csvEscape(e.appId ?? ''),\n csvEscape(e.metadata ? JSON.stringify(e.metadata) : ''),\n ].join(','),\n )\n return [header, ...rows].join('\\n')\n }\n\n function getModelInfo(model: string): ModelPrice | null {\n return findPrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices: customPrices as PriceMap }),\n ...(remotePrices !== undefined && { remotePrices }),\n }) ?? null\n }\n\n return {\n track,\n getReport,\n getCostForecast,\n reset,\n resetSession,\n exportJSON,\n exportCSV,\n getModelInfo,\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction computeTotal(entries: UsageEntry[]): number {\n return entries.reduce((sum, e) => sum + e.costUSD, 0)\n}\n\n/** Parse a 'last' shorthand like '24h', '7d' into milliseconds */\nfunction parseLastMs(last: string): number {\n const match = /^(\\d+(?:\\.\\d+)?)(h|d)$/.exec(last.trim())\n if (!match) throw new Error(`[tokenwatch] Invalid \"last\" value: \"${last}\". Use e.g. \"24h\", \"7d\".`)\n const value = parseFloat(match[1] ?? '0')\n const unit = match[2] ?? 'h'\n return unit === 'h' ? value * 60 * 60 * 1000 : value * 24 * 60 * 60 * 1000\n}\n\nfunction filterEntries(entries: UsageEntry[], options?: ReportOptions): UsageEntry[] {\n if (!options) return entries\n\n let sinceMs: number | undefined\n let untilMs: number | undefined\n\n if (options.last) {\n sinceMs = Date.now() - parseLastMs(options.last)\n } else if (options.since) {\n sinceMs = new Date(options.since).getTime()\n }\n if (options.until) {\n untilMs = new Date(options.until).getTime()\n }\n\n if (sinceMs === undefined && untilMs === undefined) return entries\n\n return entries.filter((e) => {\n const ts = new Date(e.timestamp).getTime()\n if (sinceMs !== undefined && ts < sinceMs) return false\n if (untilMs !== undefined && ts > untilMs) return false\n return true\n })\n}\n\n/** Wrap a CSV field value in double-quotes if it contains commas, quotes, or newlines. */\nfunction csvEscape(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`\n }\n return value\n}\n","import type { ModelPrice, PriceMap } from '../types/index.js'\n\n/**\n * Resolve price for a model using 3-layer priority:\n * 1. customPrices (user override)\n * 2. remotePrices (synced from GitHub, cached 24h)\n * 3. bundledPrices (always-present fallback)\n *\n * Falls back to zero-cost with a console warning when model is not found anywhere.\n */\nexport function resolvePrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice {\n const { customPrices, remotePrices, bundledPrices } = layers\n\n const found =\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n\n if (found) return found\n\n console.warn(\n `[tokenwatch] Unknown model \"${model}\". Cost will be recorded as $0. ` +\n `Add it via customPrices or update prices with: tokenwatch sync`,\n )\n return { input: 0, output: 0 }\n}\n\n/**\n * Find price for a model without the zero-cost fallback.\n * Returns undefined if the model is not found in any layer.\n */\nexport function findPrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice | undefined {\n const { customPrices, remotePrices, bundledPrices } = layers\n return (\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n )\n}\n\n/**\n * Look up a model in a PriceMap using:\n * 1. exact key match\n * 2. prefix match — map key is a prefix of the model string (e.g. \"gpt-4o\" matches \"gpt-4o-2024-11-20\")\n * 3. reverse prefix — model string is a prefix of a map key (unusual, safety net)\n */\nfunction lookupInMap(model: string, map: PriceMap | undefined): ModelPrice | undefined {\n if (!map) return undefined\n\n if (model in map) return map[model]\n\n // prefix match\n for (const key of Object.keys(map)) {\n if (model.startsWith(key) || key.startsWith(model)) {\n return map[key]\n }\n }\n\n return undefined\n}\n\n/**\n * Calculate cost in USD given token counts and per-million-token prices.\n *\n * - `inputTokens` — regular (non-cached) input tokens\n * - `outputTokens` — output tokens (includes reasoning tokens for OpenAI, which are billed as output)\n * - `cachedTokens` — cache-read input tokens (billed at price.cachedInput or full input price if absent)\n * - `cacheCreationTokens` — cache-creation input tokens, Anthropic only (billed at price.cacheCreationInput\n * or 1.25× input price if absent)\n */\nexport function calculateCost(\n inputTokens: number,\n outputTokens: number,\n price: ModelPrice,\n cachedTokens = 0,\n cacheCreationTokens = 0,\n): number {\n const regularInputCost = (inputTokens / 1_000_000) * price.input\n const cachedReadCost = (cachedTokens / 1_000_000) * (price.cachedInput ?? price.input)\n const cacheCreationCost =\n (cacheCreationTokens / 1_000_000) * (price.cacheCreationInput ?? price.input * 1.25)\n const outputCost = (outputTokens / 1_000_000) * price.output\n return regularInputCost + cachedReadCost + cacheCreationCost + outputCost\n}\n","import type { IExporter, UsageEntry } from '../types/index.js'\n\nconst DEFAULT_ENDPOINT = 'https://api.tokenwatch.dev/v1/ingest'\n\nexport class CloudExporter implements IExporter {\n private readonly endpoint: string\n\n constructor(\n private readonly apiKey: string,\n endpoint?: string,\n ) {\n this.endpoint = endpoint ?? DEFAULT_ENDPOINT\n }\n\n export(entry: UsageEntry): void {\n fetch(this.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n reasoningTokens: entry.reasoningTokens ?? 0,\n cachedTokens: entry.cachedTokens ?? 0,\n cacheCreationTokens: entry.cacheCreationTokens ?? 0,\n costUSD: entry.costUSD,\n sessionId: entry.sessionId,\n userId: entry.userId,\n feature: entry.feature,\n metadata: entry.metadata,\n timestamp: entry.timestamp,\n }),\n }).catch(() => {\n // fire-and-forget — cloud failure never interrupts the caller\n })\n }\n}\n","import type { PriceMap } from '../types/index.js'\nimport { calculateCost } from './pricing.js'\n\nconst PROVIDER_PREFIXES = ['gpt-', 'claude-', 'gemini-', 'deepseek-'] as const\n\nfunction getProviderPrefix(model: string): string | undefined {\n return PROVIDER_PREFIXES.find((p) => model.startsWith(p))\n}\n\n/**\n * After a tracked call, check if there is a cheaper model in the same provider family\n * (defined by model name prefix). Logs a hint if savings are strictly greater than 50%.\n * No-ops if the model is unknown, costUSD is zero, or no cheaper candidate is found.\n */\nexport function maybeSuggestCheaperModel(\n model: string,\n costUSD: number,\n inputTokens: number,\n outputTokens: number,\n layers: { bundledPrices: PriceMap; customPrices?: PriceMap; remotePrices?: PriceMap },\n): void {\n if (costUSD <= 0) return\n\n const prefix = getProviderPrefix(model)\n if (!prefix) return\n\n // Merge layers: bundled < remote < custom (custom wins)\n const mergedMap: PriceMap = {\n ...layers.bundledPrices,\n ...(layers.remotePrices ?? {}),\n ...(layers.customPrices ?? {}),\n }\n\n let cheapestModel: string | undefined\n let cheapestCost = Infinity\n\n for (const key of Object.keys(mergedMap)) {\n if (key === model || !key.startsWith(prefix)) continue\n const price = mergedMap[key]\n if (!price) continue\n const candidateCost = calculateCost(inputTokens, outputTokens, price)\n if (candidateCost < cheapestCost) {\n cheapestCost = candidateCost\n cheapestModel = key\n }\n }\n\n // Must be strictly more than 50% cheaper\n if (cheapestModel === undefined || cheapestCost >= costUSD * 0.5) return\n\n const savingsPct = Math.round((1 - cheapestCost / costUSD) * 100)\n console.log(\n `[tokenwatch] Suggestion: ${cheapestModel} could handle this for ~$${cheapestCost.toFixed(4)} (${savingsPct}% cheaper than ${model})`,\n )\n}\n","{\n \"updated_at\": \"2026-06-09\",\n \"source\": \"https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json\",\n \"models\": {\n \"gpt-4o\": {\n \"input\": 2.5,\n \"output\": 10,\n \"cachedInput\": 1.25,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 128000\n },\n \"gpt-5\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-mini\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-nano\": {\n \"input\": 0.05,\n \"output\": 0.4,\n \"cachedInput\": 0.005,\n \"maxInputTokens\": 272000\n },\n \"claude-opus-4-6\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-6\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"claude-haiku-4-5\": {\n \"input\": 1,\n \"output\": 5,\n \"cachedInput\": 0.1,\n \"maxInputTokens\": 200000\n },\n \"gemini-2.5-pro\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 1048576\n },\n \"deepseek-chat\": {\n \"input\": 0.28,\n \"output\": 0.42,\n \"cachedInput\": 0.028,\n \"maxInputTokens\": 131072\n },\n \"deepseek-reasoner\": {\n \"input\": 0.28,\n \"output\": 0.42,\n \"cachedInput\": 0.028,\n \"maxInputTokens\": 131072\n },\n \"claude-opus-4-5\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-7\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-1\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-sonnet-4-5\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"gpt-oss-120b\": {\n \"input\": 3,\n \"output\": 4.5,\n \"maxInputTokens\": 131072\n },\n \"gpt-3.5-turbo\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 16385\n },\n \"gpt-3.5-turbo-0125\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 16385\n },\n \"gpt-35-turbo\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 4097\n },\n \"gpt-35-turbo-0125\": {\n \"input\": 0.5,\n \"output\": 1.5,\n \"maxInputTokens\": 16384\n },\n \"gpt-35-turbo-1106\": {\n \"input\": 1,\n \"output\": 2,\n \"maxInputTokens\": 16384\n },\n \"gpt-35-turbo-16k\": {\n \"input\": 3,\n \"output\": 4,\n \"maxInputTokens\": 16385\n },\n \"gpt-35-turbo-16k-0613\": {\n \"input\": 3,\n \"output\": 4,\n \"maxInputTokens\": 16385\n },\n \"gpt-4\": {\n \"input\": 30,\n \"output\": 60,\n \"maxInputTokens\": 8192\n },\n \"gpt-4-0125-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-0613\": {\n \"input\": 30,\n \"output\": 60,\n \"maxInputTokens\": 8192\n },\n \"gpt-4-1106-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-32k\": {\n \"input\": 60,\n \"output\": 120,\n \"maxInputTokens\": 32768\n },\n \"gpt-4-32k-0613\": {\n \"input\": 60,\n \"output\": 120,\n \"maxInputTokens\": 32768\n },\n \"gpt-4-turbo\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-turbo-2024-04-09\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4-turbo-vision-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4.1\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-2025-04-14\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-mini\": {\n \"input\": 0.4,\n \"output\": 1.6,\n \"cachedInput\": 0.1,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-mini-2025-04-14\": {\n \"input\": 0.4,\n \"output\": 1.6,\n \"cachedInput\": 0.1,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-nano\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.1-nano-2025-04-14\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1047576\n },\n \"gpt-4.5-preview\": {\n \"input\": 75,\n \"output\": 150,\n \"cachedInput\": 37.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-2024-05-13\": {\n \"input\": 5,\n \"output\": 15,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-2024-08-06\": {\n \"input\": 2.5,\n \"output\": 10,\n \"cachedInput\": 1.25,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-2024-11-20\": {\n \"input\": 2.5,\n \"output\": 10,\n \"cachedInput\": 1.25,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-2025-08-28\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-1.5-2026-02-23\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-mini-2025-10-06\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-audio-preview-2024-12-17\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-2024-07-18\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-audio-preview-2024-12-17\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-realtime-preview-2024-12-17\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 128000\n },\n \"gpt-realtime-2025-08-28\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-1.5-2026-02-23\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-mini-2025-10-06\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.06,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-transcribe\": {\n \"input\": 1.25,\n \"output\": 5,\n \"maxInputTokens\": 16000\n },\n \"gpt-4o-realtime-preview-2024-10-01\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-realtime-preview-2024-12-17\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-transcribe\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 16000\n },\n \"gpt-4o-transcribe-diarize\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 16000\n },\n \"gpt-5.1-2025-11-13\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-chat-2025-11-13\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.1-codex-2025-11-13\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-codex-mini-2025-11-13\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-2025-08-07\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-chat\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5-chat-latest\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5-codex\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-mini-2025-08-07\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-nano-2025-08-07\": {\n \"input\": 0.05,\n \"output\": 0.4,\n \"cachedInput\": 0.005,\n \"maxInputTokens\": 272000\n },\n \"gpt-5-pro\": {\n \"input\": 15,\n \"output\": 120,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.1\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-chat\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.1-codex\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-codex-max\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.1-codex-mini\": {\n \"input\": 0.25,\n \"output\": 2,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-2025-12-11\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-chat\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.2-chat-2025-12-11\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.2-codex\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.3-chat\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.3-codex\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-pro\": {\n \"input\": 21,\n \"output\": 168,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.2-pro-2025-12-11\": {\n \"input\": 21,\n \"output\": 168,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.4\": {\n \"input\": 2.5,\n \"output\": 15,\n \"cachedInput\": 0.25,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-2026-03-05\": {\n \"input\": 2.5,\n \"output\": 15,\n \"cachedInput\": 0.25,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-pro\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-pro-2026-03-05\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-mini\": {\n \"input\": 0.75,\n \"output\": 4.5,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.4-nano\": {\n \"input\": 0.2,\n \"output\": 1.25,\n \"cachedInput\": 0.02,\n \"maxInputTokens\": 272000\n },\n \"o1-2024-12-17\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 200000\n },\n \"o1-mini\": {\n \"input\": 1.21,\n \"output\": 4.84,\n \"cachedInput\": 0.605,\n \"maxInputTokens\": 128000\n },\n \"o1-mini-2024-09-12\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.55,\n \"maxInputTokens\": 128000\n },\n \"o1-preview\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 128000\n },\n \"o1-preview-2024-09-12\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 128000\n },\n \"o3-2025-04-16\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 200000\n },\n \"o3-mini\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.55,\n \"maxInputTokens\": 200000\n },\n \"o3-mini-2025-01-31\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.55,\n \"maxInputTokens\": 200000\n },\n \"o3-pro\": {\n \"input\": 20,\n \"output\": 80,\n \"maxInputTokens\": 200000\n },\n \"o3-pro-2025-06-10\": {\n \"input\": 20,\n \"output\": 80,\n \"maxInputTokens\": 200000\n },\n \"o4-mini\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.275,\n \"maxInputTokens\": 200000\n },\n \"o4-mini-2025-04-16\": {\n \"input\": 1.1,\n \"output\": 4.4,\n \"cachedInput\": 0.275,\n \"maxInputTokens\": 200000\n },\n \"deepseek-v3.2\": {\n \"input\": 0.28,\n \"output\": 0.4,\n \"maxInputTokens\": 163840\n },\n \"deepseek-v3.2-speciale\": {\n \"input\": 0.58,\n \"output\": 1.68,\n \"maxInputTokens\": 163840\n },\n \"deepseek-r1\": {\n \"input\": 1.35,\n \"output\": 5.4,\n \"maxInputTokens\": 128000\n },\n \"deepseek-v3\": {\n \"input\": 0.27,\n \"output\": 1.1,\n \"cachedInput\": 0.07,\n \"maxInputTokens\": 65536\n },\n \"deepseek-v3-0324\": {\n \"input\": 0.2,\n \"output\": 0.6,\n \"maxInputTokens\": 131072\n },\n \"chatgpt-4o-latest\": {\n \"input\": 5,\n \"output\": 15,\n \"maxInputTokens\": 128000\n },\n \"claude-haiku-4-5-20251001\": {\n \"input\": 1,\n \"output\": 5,\n \"cachedInput\": 0.1,\n \"cacheCreationInput\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-7-sonnet-20250219\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-haiku-20240307\": {\n \"input\": 0.25,\n \"output\": 1.25,\n \"cachedInput\": 0.03,\n \"cacheCreationInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"claude-3-opus-20240229\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-4-opus-20250514\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-4-sonnet-20250514\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-5-20250929\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-sonnet-4-5-20250929-v1:0\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-1-20250805\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-20250514\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-5-20251101\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-6-20260205\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-7-20260416\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-20250514\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"codex-mini-latest\": {\n \"input\": 1.5,\n \"output\": 6,\n \"cachedInput\": 0.375,\n \"maxInputTokens\": 200000\n },\n \"deepseek-ai/deepseek-r1\": {\n \"input\": 3,\n \"output\": 7,\n \"maxInputTokens\": 128000\n },\n \"deepseek-ai/deepseek-r1-0528\": {\n \"input\": 135000,\n \"output\": 540000,\n \"maxInputTokens\": 161000\n },\n \"deepseek-ai/deepseek-r1-0528-turbo\": {\n \"input\": 1,\n \"output\": 3,\n \"maxInputTokens\": 32768\n },\n \"deepseek-ai/deepseek-r1-distill-llama-70b\": {\n \"input\": 0.25,\n \"output\": 0.75,\n \"maxInputTokens\": 128000\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-32b\": {\n \"input\": 0.15,\n \"output\": 0.15\n },\n \"deepseek-ai/deepseek-r1-turbo\": {\n \"input\": 1,\n \"output\": 3,\n \"maxInputTokens\": 40960\n },\n \"deepseek-ai/deepseek-v3\": {\n \"input\": 1.25,\n \"output\": 1.25,\n \"maxInputTokens\": 65536\n },\n \"deepseek-ai/deepseek-v3-0324\": {\n \"input\": 114000,\n \"output\": 275000,\n \"maxInputTokens\": 161000\n },\n \"deepseek-ai/deepseek-v3.1\": {\n \"input\": 55000,\n \"output\": 165000,\n \"maxInputTokens\": 128000\n },\n \"deepseek-ai/deepseek-v3.1-terminus\": {\n \"input\": 0.27,\n \"output\": 1,\n \"cachedInput\": 0.216,\n \"maxInputTokens\": 163840\n },\n \"deepseek-coder\": {\n \"input\": 0.14,\n \"output\": 0.28,\n \"maxInputTokens\": 128000\n },\n \"gemini-2.0-flash\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.0-flash-001\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.0-flash-lite\": {\n \"input\": 0.075,\n \"output\": 0.3,\n \"cachedInput\": 0.01875,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.0-flash-lite-001\": {\n \"input\": 0.075,\n \"output\": 0.3,\n \"cachedInput\": 0.01875,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-image\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 32768\n },\n \"gemini-3-pro-image-preview\": {\n \"input\": 2,\n \"output\": 12,\n \"maxInputTokens\": 65536\n },\n \"gemini-3.1-flash-image-preview\": {\n \"input\": 0.5,\n \"output\": 3,\n \"maxInputTokens\": 65536\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"input\": 0.25,\n \"output\": 1.5,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-lite\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.01,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-lite-preview-09-2025\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.01,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-preview-09-2025\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 1048576\n },\n \"gemini-live-2.5-flash-preview-native-audio-09-2025\": {\n \"input\": 0.3,\n \"output\": 2,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-lite-preview-06-17\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3-pro-preview\": {\n \"input\": 2,\n \"output\": 12,\n \"cachedInput\": 0.2,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-pro-preview\": {\n \"input\": 2,\n \"output\": 12,\n \"cachedInput\": 0.2,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"input\": 2,\n \"output\": 12,\n \"cachedInput\": 0.2,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3-flash-preview\": {\n \"input\": 0.5,\n \"output\": 3,\n \"cachedInput\": 0.05,\n \"maxInputTokens\": 1048576\n },\n \"gemini-robotics-er-1.5-preview\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-computer-use-preview-10-2025\": {\n \"input\": 1.25,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gemini-flash-latest\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 1048576\n },\n \"gemini-flash-lite-latest\": {\n \"input\": 0.1,\n \"output\": 0.4,\n \"cachedInput\": 0.01,\n \"maxInputTokens\": 1048576\n },\n \"gemini-gemma-2-27b-it\": {\n \"input\": 0.35,\n \"output\": 1.05,\n \"maxInputTokens\": 8192\n },\n \"gemini-gemma-2-9b-it\": {\n \"input\": 0.35,\n \"output\": 1.05,\n \"maxInputTokens\": 8192\n },\n \"deepseek-ai/deepseek-v3.2\": {\n \"input\": 0.28,\n \"output\": 0.4,\n \"maxInputTokens\": 163840\n },\n \"gpt-3.5-turbo-1106\": {\n \"input\": 1,\n \"output\": 2,\n \"maxInputTokens\": 16385\n },\n \"gpt-3.5-turbo-16k\": {\n \"input\": 3,\n \"output\": 4,\n \"maxInputTokens\": 16385\n },\n \"gpt-4-0314\": {\n \"input\": 30,\n \"output\": 60,\n \"maxInputTokens\": 8192\n },\n \"gpt-4-turbo-preview\": {\n \"input\": 10,\n \"output\": 30,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-audio-preview\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-audio-preview-2025-06-03\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-1.5\": {\n \"input\": 2.5,\n \"output\": 10,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-mini\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"gpt-audio-mini-2025-12-15\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-audio-preview\": {\n \"input\": 0.15,\n \"output\": 0.6,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-mini-realtime-preview\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-realtime-preview\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-4o-realtime-preview-2025-06-03\": {\n \"input\": 5,\n \"output\": 20,\n \"cachedInput\": 2.5,\n \"maxInputTokens\": 128000\n },\n \"gpt-image-1.5\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-image-1.5-2025-12-16\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-5.1-chat-latest\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.2-chat-latest\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5.3-chat-latest\": {\n \"input\": 1.75,\n \"output\": 14,\n \"cachedInput\": 0.175,\n \"maxInputTokens\": 128000\n },\n \"gpt-5-pro-2025-10-06\": {\n \"input\": 15,\n \"output\": 120,\n \"maxInputTokens\": 128000\n },\n \"gpt-realtime\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-1.5\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gpt-realtime-mini\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"maxInputTokens\": 128000\n },\n \"deepseek-r1-distill-llama-70b\": {\n \"input\": 0.99,\n \"output\": 0.99,\n \"maxInputTokens\": 32768\n },\n \"deepseek-llama3.3-70b\": {\n \"input\": 0.2,\n \"output\": 0.6,\n \"maxInputTokens\": 131072\n },\n \"deepseek-r1-0528\": {\n \"input\": 0.2,\n \"output\": 0.6,\n \"maxInputTokens\": 131072\n },\n \"deepseek-r1-671b\": {\n \"input\": 0.8,\n \"output\": 0.8,\n \"maxInputTokens\": 131072\n },\n \"deepseek-ai/deepseek-r1-distill-llama-8b\": {\n \"input\": 0.025,\n \"output\": 0.025\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-1.5b\": {\n \"input\": 0.09,\n \"output\": 0.09\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-14b\": {\n \"input\": 0.07,\n \"output\": 0.07\n },\n \"deepseek-ai/deepseek-r1-distill-qwen-7b\": {\n \"input\": 0.2,\n \"output\": 0.2\n },\n \"o1\": {\n \"input\": 15,\n \"output\": 60,\n \"cachedInput\": 7.5,\n \"maxInputTokens\": 200000\n },\n \"o1-pro\": {\n \"input\": 150,\n \"output\": 600,\n \"maxInputTokens\": 200000\n },\n \"o1-pro-2025-03-19\": {\n \"input\": 150,\n \"output\": 600,\n \"maxInputTokens\": 200000\n },\n \"o3\": {\n \"input\": 2,\n \"output\": 8,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 200000\n },\n \"gpt-oss-20b\": {\n \"input\": 0.09,\n \"output\": 0.36\n },\n \"deepseek-ai/deepseek-r1-0528-tput\": {\n \"input\": 0.55,\n \"output\": 2.19,\n \"maxInputTokens\": 128000\n },\n \"claude-3-5-haiku\": {\n \"input\": 1,\n \"output\": 5,\n \"maxInputTokens\": 200000\n },\n \"claude-3-5-haiku@20241022\": {\n \"input\": 1,\n \"output\": 5,\n \"maxInputTokens\": 200000\n },\n \"claude-haiku-4-5@20251001\": {\n \"input\": 1,\n \"output\": 5,\n \"cachedInput\": 0.1,\n \"cacheCreationInput\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-5-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-3-5-sonnet@20240620\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-3-7-sonnet@20250219\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-haiku\": {\n \"input\": 0.25,\n \"output\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-haiku@20240307\": {\n \"input\": 0.25,\n \"output\": 1.25,\n \"maxInputTokens\": 200000\n },\n \"claude-3-opus\": {\n \"input\": 15,\n \"output\": 75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-opus@20240229\": {\n \"input\": 15,\n \"output\": 75,\n \"maxInputTokens\": 200000\n },\n \"claude-3-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-3-sonnet@20240229\": {\n \"input\": 3,\n \"output\": 15,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-1@20250805\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-5@20251101\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4-6@default\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-7@default\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4-5@20250929\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 200000\n },\n \"claude-opus-4@20250514\": {\n \"input\": 15,\n \"output\": 75,\n \"cachedInput\": 1.5,\n \"cacheCreationInput\": 18.75,\n \"maxInputTokens\": 200000\n },\n \"claude-sonnet-4\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"claude-sonnet-4@20250514\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"deepseek-ai/deepseek-v3.1-maas\": {\n \"input\": 1.35,\n \"output\": 5.4,\n \"maxInputTokens\": 163840\n },\n \"deepseek-ai/deepseek-v3.2-maas\": {\n \"input\": 0.56,\n \"output\": 1.68,\n \"maxInputTokens\": 163840\n },\n \"deepseek-ai/deepseek-r1-0528-maas\": {\n \"input\": 1.35,\n \"output\": 5.4,\n \"maxInputTokens\": 65336\n },\n \"deepseek-ai/deepseek-ocr-maas\": {\n \"input\": 0.3,\n \"output\": 1.2\n },\n \"deepseek-r1-8b\": {\n \"input\": 0.1,\n \"output\": 0.2,\n \"maxInputTokens\": 65536\n },\n \"deepseek-r1-7b-qwen\": {\n \"input\": 0.08,\n \"output\": 0.15,\n \"maxInputTokens\": 131072\n },\n \"deepseek-coder-6.7b\": {\n \"input\": 0.06,\n \"output\": 0.12,\n \"maxInputTokens\": 16384\n },\n \"gpt-4o-mini-transcribe-2025-03-20\": {\n \"input\": 1.25,\n \"output\": 5,\n \"maxInputTokens\": 16000\n },\n \"gpt-4o-mini-transcribe-2025-12-15\": {\n \"input\": 1.25,\n \"output\": 5,\n \"maxInputTokens\": 16000\n },\n \"gpt-realtime-mini-2025-12-15\": {\n \"input\": 0.6,\n \"output\": 2.4,\n \"cachedInput\": 0.06,\n \"maxInputTokens\": 128000\n },\n \"gemini-2.5-flash-native-audio-latest\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-native-audio-preview-09-2025\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-2.5-flash-native-audio-preview-12-2025\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-flash-live-preview\": {\n \"input\": 0.75,\n \"output\": 4.5,\n \"maxInputTokens\": 131072\n },\n \"gemini-pro-latest\": {\n \"input\": 1.25,\n \"output\": 10,\n \"cachedInput\": 0.125,\n \"maxInputTokens\": 1048576\n },\n \"gemini-exp-1206\": {\n \"input\": 0.3,\n \"output\": 2.5,\n \"cachedInput\": 0.03,\n \"maxInputTokens\": 1048576\n },\n \"claude-sonnet-4-6@default\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"cacheCreationInput\": 3.75,\n \"maxInputTokens\": 1000000\n },\n \"gpt-5.5\": {\n \"input\": 5,\n \"output\": 30,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.5-2026-04-23\": {\n \"input\": 5,\n \"output\": 30,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.5-pro\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.5-pro-2026-04-23\": {\n \"input\": 30,\n \"output\": 180,\n \"cachedInput\": 3,\n \"maxInputTokens\": 1050000\n },\n \"gpt-5.4-mini-2026-03-17\": {\n \"input\": 0.75,\n \"output\": 4.5,\n \"cachedInput\": 0.075,\n \"maxInputTokens\": 272000\n },\n \"gpt-5.4-nano-2026-03-17\": {\n \"input\": 0.2,\n \"output\": 1.25,\n \"cachedInput\": 0.02,\n \"maxInputTokens\": 272000\n },\n \"gpt-image-2\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-image-2-2026-04-21\": {\n \"input\": 5,\n \"output\": 10,\n \"cachedInput\": 1.25\n },\n \"gpt-realtime-2\": {\n \"input\": 4,\n \"output\": 16,\n \"cachedInput\": 0.4,\n \"maxInputTokens\": 32000\n },\n \"gemini-3.5-flash\": {\n \"input\": 1.5,\n \"output\": 9,\n \"cachedInput\": 0.15,\n \"maxInputTokens\": 1048576\n },\n \"gemini-3.1-flash-lite\": {\n \"input\": 0.25,\n \"output\": 1.5,\n \"cachedInput\": 0.025,\n \"maxInputTokens\": 1048576\n },\n \"claude-opus-4-8\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-opus-4-8@default\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"cacheCreationInput\": 6.25,\n \"maxInputTokens\": 1000000\n },\n \"claude-4-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n },\n \"claude-4-opus\": {\n \"input\": 5,\n \"output\": 25,\n \"cachedInput\": 0.5,\n \"maxInputTokens\": 200000\n },\n \"claude-3-7-sonnet\": {\n \"input\": 3,\n \"output\": 15,\n \"cachedInput\": 0.3,\n \"maxInputTokens\": 200000\n }\n }\n}\n","import { createServer } from 'node:http'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { getDashboardData, getFingerprint } from './data.js'\nimport { getHtml } from './html.js'\nimport type { IStorage } from '../types/index.js'\n\nexport function startDashboardServer(storage: IStorage, port: number): void {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? '/', `http://localhost:${port}`)\n\n if (req.method === 'GET' && url.pathname === '/') {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })\n res.end(getHtml(port))\n return\n }\n\n if (req.method === 'GET' && url.pathname === '/events') {\n const filter = url.searchParams.get('filter') ?? '24h'\n\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n })\n res.flushHeaders()\n\n let lastFingerprint = ''\n\n async function sendData(): Promise<void> {\n try {\n const data = await getDashboardData(storage, filter)\n const fp = getFingerprint(data)\n if (fp !== lastFingerprint) {\n lastFingerprint = fp\n res.write(`data: ${JSON.stringify(data)}\\n\\n`)\n }\n } catch {\n // best-effort — storage errors must not crash the server\n }\n }\n\n // Send immediately on connect\n void sendData()\n\n const timer = setInterval(() => { void sendData() }, 3000)\n\n res.on('close', () => {\n clearInterval(timer)\n })\n return\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' })\n res.end('Not found')\n })\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n console.error(`[tokenwatch] Port ${port} is already in use. Try: tokenwatch dashboard --port <other>`)\n process.exit(1)\n }\n throw err\n })\n\n server.listen(port, () => {\n console.log(`tokenwatch dashboard → http://localhost:${port}`)\n })\n}\n","import type {\n IStorage,\n UsageEntry,\n Report,\n CostForecast,\n ModelStats,\n SessionStats,\n UserStats,\n FeatureStats,\n AppStats,\n} from '../types/index.js'\n\nexport interface TimeSeriesBucket {\n bucket: string\n cost: number\n calls: number\n}\n\nexport interface DashboardData {\n report: Report\n forecast: CostForecast\n timeSeries: TimeSeriesBucket[]\n lastUpdated: string\n}\n\n/**\n * Maps a filter string to a Unix ms timestamp (entries before this are excluded).\n * Returns undefined for 'all' or undefined (no filter).\n */\nexport function parseSince(filter: string | undefined): number | undefined {\n if (!filter || filter === 'all') return undefined\n const now = Date.now()\n switch (filter) {\n case '1h': return now - 60 * 60 * 1000\n case '24h': return now - 24 * 60 * 60 * 1000\n case '7d': return now - 7 * 24 * 60 * 60 * 1000\n case '30d': return now - 30 * 24 * 60 * 60 * 1000\n default: return undefined\n }\n}\n\n/**\n * Groups entries into time-series buckets.\n * Bucket size: 1h window → 5min, 24h window → 1h, 7d/30d/all → 1day.\n */\nexport function buildTimeSeries(\n entries: UsageEntry[],\n sinceMs: number | undefined,\n): TimeSeriesBucket[] {\n const now = Date.now()\n const windowMs = sinceMs !== undefined ? now - sinceMs : undefined\n\n let bucketMs: number\n if (windowMs !== undefined && windowMs <= 60 * 60 * 1000) {\n bucketMs = 5 * 60 * 1000 // 5-min buckets for ≤1h window\n } else if (windowMs !== undefined && windowMs <= 24 * 60 * 60 * 1000) {\n bucketMs = 60 * 60 * 1000 // 1h buckets for ≤24h window\n } else {\n bucketMs = 24 * 60 * 60 * 1000 // 1-day buckets for 7d/30d/all\n }\n\n const filtered = sinceMs !== undefined\n ? entries.filter((e) => new Date(e.timestamp).getTime() >= sinceMs)\n : entries\n\n const buckets = new Map<string, TimeSeriesBucket>()\n\n for (const entry of filtered) {\n const ts = new Date(entry.timestamp).getTime()\n const bucketTs = Math.floor(ts / bucketMs) * bucketMs\n const bucketKey = new Date(bucketTs).toISOString()\n const existing = buckets.get(bucketKey)\n if (existing) {\n existing.cost += entry.costUSD\n existing.calls += 1\n } else {\n buckets.set(bucketKey, { bucket: bucketKey, cost: entry.costUSD, calls: 1 })\n }\n }\n\n return Array.from(buckets.values()).sort((a, b) => a.bucket.localeCompare(b.bucket))\n}\n\nexport function getFingerprint(data: DashboardData): string {\n return `${data.report.totalCostUSD.toFixed(8)}-${data.report.totalTokens.input}-${data.timeSeries.length}`\n}\n\nexport async function getDashboardData(\n storage: IStorage,\n filter?: string,\n): Promise<DashboardData> {\n const allEntries = await Promise.resolve(storage.getAll())\n const sinceMs = parseSince(filter)\n\n const entries = sinceMs !== undefined\n ? allEntries.filter((e) => new Date(e.timestamp).getTime() >= sinceMs)\n : allEntries\n\n // ── Build report ────────────────────────────────────────────────────────────\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n const byFeature: Record<string, FeatureStats> = {}\n const byApp: Record<string, AppStats> = {}\n const byMetadata: Record<string, Record<string, { costUSD: number; calls: number }>> = {}\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n\n for (const e of entries) {\n totalInput += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n\n const m = (byModel[e.model] ??= {\n costUSD: 0, calls: 0, tokens: { input: 0, output: 0, reasoning: 0, cached: 0 },\n })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens + (e.cachedTokens ?? 0) + (e.cacheCreationTokens ?? 0)\n m.tokens.output += e.outputTokens\n m.tokens.reasoning += e.reasoningTokens ?? 0\n m.tokens.cached += e.cachedTokens ?? 0\n\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n\n if (e.feature) {\n const f = (byFeature[e.feature] ??= { costUSD: 0, calls: 0 })\n f.costUSD += e.costUSD\n f.calls += 1\n }\n\n if (e.appId) {\n const a = (byApp[e.appId] ??= { costUSD: 0, calls: 0 })\n a.costUSD += e.costUSD\n a.calls += 1\n }\n\n if (e.metadata) {\n for (const [key, val] of Object.entries(e.metadata)) {\n const group = (byMetadata[key] ??= {})\n const slot = (group[val] ??= { costUSD: 0, calls: 0 })\n slot.costUSD += e.costUSD\n slot.calls += 1\n }\n }\n }\n\n const now = new Date().toISOString()\n const periodFrom = entries[0]?.timestamp ?? now\n const periodTo = entries[entries.length - 1]?.timestamp ?? now\n\n const report: Report = {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n byFeature,\n byApp,\n byMetadata,\n period: { from: periodFrom, to: periodTo },\n }\n\n // ── Build forecast (always 24h window over all entries) ─────────────────────\n const forecastWindowMs = 24 * 60 * 60 * 1000\n const windowStart = Date.now() - forecastWindowMs\n const windowEntries = allEntries.filter(\n (e) => new Date(e.timestamp).getTime() >= windowStart,\n )\n\n let forecast: CostForecast\n if (windowEntries.length < 2) {\n forecast = {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: null,\n }\n } else {\n const first = windowEntries[0]?.timestamp ?? ''\n const last = windowEntries[windowEntries.length - 1]?.timestamp ?? ''\n const actualMs = new Date(last).getTime() - new Date(first).getTime()\n const actualHours = actualMs / (1000 * 60 * 60)\n if (actualHours < 0.001) {\n forecast = {\n burnRatePerHour: 0,\n projectedDailyCostUSD: 0,\n projectedMonthlyCostUSD: 0,\n basedOnHours: 0,\n basedOnPeriod: { from: first, to: last },\n }\n } else {\n const windowCost = windowEntries.reduce((s, e) => s + e.costUSD, 0)\n const burnRatePerHour = windowCost / actualHours\n forecast = {\n burnRatePerHour,\n projectedDailyCostUSD: burnRatePerHour * 24,\n projectedMonthlyCostUSD: burnRatePerHour * 24 * 30,\n basedOnHours: Math.round(actualHours * 100) / 100,\n basedOnPeriod: { from: first, to: last },\n }\n }\n }\n\n const timeSeries = buildTimeSeries(allEntries, sinceMs)\n\n return { report, forecast, timeSeries, lastUpdated: now }\n}\n","export function getHtml(_port: number): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<title>tokenwatch — Overview</title>\n<style>\n :root{\n --bg:#0d1117; --surface:#161b22; --surface2:#1c2128;\n --border:#30363d; --border-muted:#21262d;\n --text:#e6edf3; --muted:#7d8590; --dim:#484f58;\n --accent:#58a6ff; --green:#3fb950; --yellow:#d29922; --red:#f85149;\n --mono:ui-monospace,SFMono-Regular,\"SF Mono\",Menlo,Consolas,monospace;\n --sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;\n }\n *{box-sizing:border-box}\n html,body{margin:0}\n body{background:var(--bg);color:var(--text);font-family:var(--sans);\n font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased;\n font-variant-numeric:tabular-nums}\n h1,h2,h3{margin:0;font-weight:600}\n a{color:inherit;text-decoration:none}\n kbd{font-family:var(--sans);font-size:10px;color:var(--muted);\n background:#22272e;border:1px solid var(--border);border-radius:4px;\n padding:1px 5px;line-height:1.4}\n .tw-root{max-width:1440px;margin:0 auto;min-height:100vh;\n border-left:1px solid var(--border-muted);border-right:1px solid var(--border-muted)}\n .num,.tw-kpi-value,.tw-cost,.tw-num-cell,.tw-fc-value{font-variant-numeric:tabular-nums}\n .tw-header{position:sticky;top:0;z-index:40;height:54px;display:flex;\n align-items:center;justify-content:space-between;gap:16px;padding:0 18px;\n background:rgba(13,17,23,.86);backdrop-filter:blur(10px);\n border-bottom:1px solid var(--border)}\n .tw-hgroup{display:flex;align-items:center;gap:14px}\n .tw-logo{font-size:15px;font-weight:700;letter-spacing:-.01em}\n .tw-logo span{color:var(--accent)}\n .tw-ws{position:relative;display:flex;align-items:center;gap:7px;height:30px;\n padding:0 9px;background:var(--surface);border:1px solid var(--border);\n border-radius:7px;color:var(--text);font-size:12.5px;font-weight:500;cursor:pointer}\n .tw-ws:hover{border-color:#444c56}\n .tw-ws-dot{width:7px;height:7px;border-radius:50%;background:var(--green);\n box-shadow:0 0 0 3px rgba(63,185,80,.15)}\n .tw-ws-env{font-size:10px;color:var(--muted);background:#22272e;\n border-radius:4px;padding:1px 5px;text-transform:uppercase;letter-spacing:.03em}\n .tw-ws-menu{position:absolute;top:36px;left:0;width:230px;background:var(--surface2);\n border:1px solid var(--border);border-radius:9px;padding:5px;z-index:50;\n box-shadow:0 12px 32px rgba(0,0,0,.5)}\n .tw-ws-item{padding:7px 9px;border-radius:6px;font-size:12.5px;cursor:pointer;color:var(--text)}\n .tw-ws-item:hover{background:#22272e}\n .tw-ws-item.on{color:var(--accent)}\n .tw-ws-item.muted{color:var(--muted)}\n .tw-ws-sep{height:1px;background:var(--border);margin:5px 0}\n .tw-nav{display:flex;gap:2px;margin-left:6px}\n .tw-nav a{padding:6px 10px;border-radius:6px;font-size:13px;color:var(--muted);font-weight:500}\n .tw-nav a:hover{color:var(--text);background:var(--surface)}\n .tw-nav a.on{color:var(--text);background:var(--surface);box-shadow:inset 0 -2px 0 var(--accent)}\n .tw-search{display:flex;align-items:center;gap:8px;height:30px;padding:0 9px;\n width:200px;background:var(--surface);border:1px solid var(--border);\n border-radius:7px;color:var(--muted);font-size:12.5px;cursor:text}\n .tw-search:hover{border-color:#444c56}\n .tw-search span{flex:1;text-align:left}\n .tw-btn-2{display:flex;align-items:center;gap:6px;height:30px;padding:0 11px;\n background:var(--surface);border:1px solid var(--border);border-radius:7px;\n color:var(--text);font-size:12.5px;font-weight:500;cursor:pointer}\n .tw-btn-2:hover{border-color:#444c56;background:#1b2128}\n .tw-user{display:flex;align-items:center;gap:8px;margin-left:2px}\n .tw-plan{font-size:10px;font-weight:600;color:var(--accent);\n border:1px solid rgba(88,166,255,.4);background:rgba(88,166,255,.1);\n border-radius:5px;padding:2px 6px;text-transform:uppercase;letter-spacing:.04em}\n .tw-avatar{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;\n font-size:11px;font-weight:600;color:#fff;\n background:linear-gradient(135deg,#bc8cff,#58a6ff)}\n .tw-main{padding:16px 18px 64px;display:flex;flex-direction:column;gap:14px}\n .tw-budget{background:var(--surface);border:1px solid var(--border);\n border-radius:10px;padding:13px 16px}\n .tw-budget-top{display:flex;align-items:center;gap:16px;margin-bottom:10px}\n .tw-budget-label{display:flex;align-items:baseline;gap:10px}\n .tw-bud-strong{font-weight:600;font-size:13px}\n .tw-bud-nums{color:var(--muted);font-size:12.5px}\n .tw-bud-nums b{color:var(--text)}\n .tw-bud-alert{display:flex;align-items:center;gap:6px;font-size:12px;\n padding:3px 9px;border-radius:6px;color:var(--green);\n background:rgba(63,185,80,.1);border:1px solid rgba(63,185,80,.25)}\n .tw-bud-alert b{color:var(--text)}\n .tw-budget-days{margin-left:auto;color:var(--muted);font-size:12px}\n .tw-bar{position:relative;height:9px;background:var(--border-muted);\n border-radius:5px;overflow:visible}\n .tw-bar-fill{height:100%;border-radius:5px;transition:width .5s}\n .tw-bar-proj{position:absolute;top:0;height:100%;\n background:repeating-linear-gradient(45deg,rgba(125,133,144,.35),rgba(125,133,144,.35) 4px,transparent 4px,transparent 8px);\n border-radius:0 5px 5px 0}\n .tw-bar-marker{position:absolute;top:-3px;width:2px;height:15px;background:var(--text);border-radius:2px}\n .tw-bar-marker::after{content:attr(data-tip);position:absolute;top:-18px;left:50%;\n transform:translateX(-50%);font-size:9.5px;color:var(--muted);white-space:nowrap}\n .tw-kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}\n .tw-kpi{background:var(--surface);border:1px solid var(--border);border-radius:10px;\n padding:12px 14px;display:flex;flex-direction:column;gap:7px;min-width:0}\n .tw-kpi-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}\n .tw-kpi-main{display:flex;align-items:flex-end;justify-content:space-between;gap:8px}\n .tw-kpi-value{font-size:21px;font-weight:600;letter-spacing:-.01em;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n .tw-kpi-foot{display:flex;align-items:center;gap:8px;font-size:11.5px;min-height:15px}\n .tw-delta{font-weight:600}\n .tw-kpi-sub{color:var(--muted)}\n .tw-timefilter{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 2px}\n .tw-tabs{display:flex;gap:2px;background:var(--surface);border:1px solid var(--border);\n border-radius:8px;padding:3px}\n .tw-tab{height:26px;padding:0 13px;border:0;background:transparent;color:var(--muted);\n font-size:12.5px;font-weight:600;font-family:var(--sans);border-radius:6px;cursor:pointer}\n .tw-tab:hover{color:var(--text)}\n .tw-tab.on{background:var(--accent);color:#0d1117}\n .tw-compare{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:12.5px;cursor:pointer}\n .tw-toggle-sm{position:relative;width:30px;height:17px;border:0;border-radius:999px;\n background:#30363d;cursor:pointer;padding:0;transition:background .15s}\n .tw-toggle-sm[data-on=\"1\"]{background:var(--accent)}\n .tw-toggle-sm i{position:absolute;top:2px;left:2px;width:13px;height:13px;border-radius:50%;\n background:#fff;transition:transform .15s}\n .tw-toggle-sm[data-on=\"1\"] i{transform:translateX(13px)}\n .tw-card,.tw-section{background:var(--surface);border:1px solid var(--border);border-radius:10px}\n .tw-section{padding:0}\n .tw-card{padding:14px 16px}\n .tw-sec-head{display:flex;align-items:center;justify-content:space-between;gap:10px;\n padding:13px 16px 11px}\n .tw-card .tw-sec-head{padding:0 0 10px}\n .tw-sec-head h3{font-size:13.5px}\n .tw-sec-sub{color:var(--muted);font-size:11.5px;font-weight:400}\n .tw-charts{display:grid;grid-template-columns:2fr 1fr;gap:14px}\n .tw-chart-main,.tw-chart-side{min-width:0}\n .tw-chart-legend{display:flex;align-items:center;gap:12px;font-size:11.5px;color:var(--muted)}\n .tw-chart-legend span{display:flex;align-items:center;gap:5px}\n .tw-chart-legend i{width:14px;height:3px;border-radius:2px;background:var(--accent)}\n .tw-chart-legend i.dash{background:repeating-linear-gradient(90deg,#6e7681,#6e7681 3px,transparent 3px,transparent 6px)}\n .tw-chart-legend .muted i{background:#6e7681}\n .tw-draw-line{stroke-dasharray:2600;stroke-dashoffset:2600;animation:tw-draw 1.1s cubic-bezier(.4,0,.2,1) forwards}\n .tw-draw-area{opacity:0;animation:tw-fade .9s ease .25s forwards}\n @keyframes tw-draw{to{stroke-dashoffset:0}}\n @keyframes tw-fade{to{opacity:1}}\n .tw-doughnut-wrap{display:grid;place-items:center;padding:6px 0 10px}\n .tw-legend{display:flex;flex-direction:column;gap:1px}\n .tw-legend-row{display:flex;align-items:center;gap:8px;padding:5px 7px;border-radius:6px;cursor:pointer}\n .tw-legend-row:hover,.tw-legend-row.on{background:#1b2128}\n .tw-legend-name{flex:1;font-size:12px;font-family:var(--mono);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .tw-legend-val{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}\n .tw-legend-pct{font-size:11px;color:var(--dim);width:30px;text-align:right;font-variant-numeric:tabular-nums}\n .tw-model-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}\n .tw-model-dot.lg{width:11px;height:11px;border-radius:3px}\n .tw-table-wrap{overflow-x:auto}\n .tw-table{width:100%;border-collapse:collapse;font-size:12.5px}\n .tw-table thead th{position:sticky;top:0;text-align:left;padding:8px 16px;\n font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;\n letter-spacing:.04em;border-bottom:1px solid var(--border);user-select:none;\n background:var(--surface)}\n .tw-th-inner{display:flex;align-items:center;gap:4px}\n .tw-th-inner:hover{color:var(--text)}\n .tw-sort{color:var(--accent);font-size:10px}\n .tw-row{border-bottom:1px solid var(--border-muted);cursor:pointer;transition:background .1s}\n .tw-row:last-child{border-bottom:0}\n .tw-row td{padding:10px 16px;vertical-align:middle}\n .tw-table.compact .tw-row td{padding:6px 16px}\n .tw-table.compact thead th{padding:6px 16px}\n .tw-row:hover{background:#1b2128}\n .tw-row.example{background:rgba(88,166,255,.07);box-shadow:inset 2px 0 0 var(--accent)}\n .tw-row.example:hover{background:rgba(88,166,255,.11)}\n .tw-model-cell{display:flex;align-items:center;gap:9px}\n .tw-model-name{font-family:var(--mono);font-size:12.5px;color:var(--text)}\n .tw-model-prov{font-size:10px;color:var(--muted);background:#22272e;border-radius:4px;padding:1px 5px}\n .tw-num-cell{text-align:right;font-variant-numeric:tabular-nums;color:var(--text);white-space:nowrap}\n .tw-cost{font-weight:600}\n .tw-row-actions{display:flex;gap:6px;justify-content:flex-end}\n .tw-row-actions button{height:24px;padding:0 9px;border:1px solid var(--border);\n background:var(--surface2);color:var(--text);border-radius:6px;font-size:11px;\n font-weight:500;cursor:pointer;font-family:var(--sans)}\n .tw-row-actions button:hover{border-color:var(--accent);color:var(--accent)}\n .tw-forecast{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;padding:0 16px 16px}\n .tw-fc{background:var(--surface2);border:1px solid var(--border);border-radius:9px;\n padding:12px 14px;display:flex;flex-direction:column;gap:6px}\n .tw-fc.flag{border-color:rgba(248,81,73,.5);background:rgba(248,81,73,.07)}\n .tw-fc-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.03em}\n .tw-fc-value{font-size:20px;font-weight:600;letter-spacing:-.01em}\n .tw-fc-sub{font-size:11.5px;color:var(--muted)}\n .tw-act-title{display:flex;align-items:center;gap:9px}\n .tw-collapse{border:0;background:transparent;color:var(--muted);cursor:pointer;\n display:grid;place-items:center;padding:2px;transition:transform .15s}\n .tw-live-dot{width:8px;height:8px;border-radius:50%;background:var(--dim)}\n .tw-live-dot.on{background:var(--green);animation:tw-pulse 1.6s ease-in-out infinite}\n @keyframes tw-pulse{0%,100%{box-shadow:0 0 0 0 rgba(63,185,80,.5)}50%{box-shadow:0 0 0 5px rgba(63,185,80,0)}}\n .tw-act-controls{display:flex;align-items:center;gap:10px}\n .tw-seg-sm{display:flex;gap:1px;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:2px}\n .tw-seg-sm button{height:22px;padding:0 9px;border:0;background:transparent;color:var(--muted);\n font-size:11px;font-weight:600;border-radius:5px;cursor:pointer;font-family:var(--sans)}\n .tw-seg-sm button.on{background:var(--border);color:var(--text)}\n .tw-act-pause{height:26px;padding:0 10px;border:1px solid var(--border);background:var(--surface2);\n color:var(--muted);border-radius:6px;font-size:11.5px;cursor:pointer;font-family:var(--sans)}\n .tw-act-pause:hover{color:var(--text);border-color:#444c56}\n .tw-feed{padding:0 6px 8px}\n .tw-feed-head,.tw-feed-row{display:grid;grid-template-columns:72px 1.4fr 1fr 1fr 96px;gap:12px;align-items:center}\n .tw-feed-head{padding:6px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--dim);border-bottom:1px solid var(--border-muted)}\n .tw-feed-body{display:flex;flex-direction:column}\n .tw-feed-row{padding:7px 10px;font-size:12px;border-bottom:1px solid var(--border-muted)}\n .tw-feed-row:last-child{border-bottom:0}\n .tw-feed-row.fresh{animation:tw-flash 1.4s ease}\n .tw-feed-row.err{background:rgba(248,81,73,.05)}\n @keyframes tw-flash{0%{background:rgba(88,166,255,.16)}100%{background:transparent}}\n .tw-feed-time{color:var(--muted);font-variant-numeric:tabular-nums}\n .tw-feed-model{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11.5px;color:var(--text);overflow:hidden}\n .tw-feed-sess{font-family:var(--mono);font-size:11.5px;color:var(--muted)}\n .tw-feed-cost{text-align:right;font-variant-numeric:tabular-nums;color:var(--text);font-weight:500;display:flex;align-items:center;justify-content:flex-end;gap:6px}\n .tw-feed-flag{font-size:9px;color:var(--red);border:1px solid rgba(248,81,73,.4);border-radius:4px;padding:0 4px;text-transform:uppercase}\n .tw-feed-flag.slow{color:var(--yellow);border-color:rgba(210,153,34,.4)}\n .tw-feat{font-size:10.5px;font-family:var(--mono);border:1px solid;border-radius:5px;padding:1px 6px;white-space:nowrap}\n .tw-feat.sm{font-size:10px;padding:0 5px}\n .tw-scrim{position:fixed;inset:0;background:rgba(1,4,9,.6);opacity:0;pointer-events:none;\n transition:opacity .25s;z-index:60}\n .tw-scrim.open{opacity:1;pointer-events:auto}\n .tw-slideover{position:fixed;top:0;right:0;height:100vh;width:400px;background:var(--surface);\n border-left:1px solid var(--border);transform:translateX(100%);transition:transform .28s cubic-bezier(.4,0,.2,1);\n z-index:61;box-shadow:-16px 0 40px rgba(0,0,0,.4);overflow-y:auto}\n .tw-slideover.open{transform:translateX(0)}\n .tw-so-inner{padding:18px}\n .tw-so-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:16px}\n .tw-so-title{display:flex;align-items:center;gap:10px}\n .tw-so-name{font-family:var(--mono);font-size:15px;font-weight:600}\n .tw-so-prov{font-size:11.5px;color:var(--muted);margin-top:2px}\n .tw-so-close{width:28px;height:28px;border:1px solid var(--border);background:var(--surface2);\n color:var(--muted);border-radius:7px;cursor:pointer;font-size:13px}\n .tw-so-close:hover{color:var(--text);border-color:#444c56}\n .tw-so-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:12px}\n .tw-so-stat{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:10px}\n .tw-so-stat-l{font-size:10.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.03em;margin-bottom:5px}\n .tw-so-stat-v{font-size:16px;font-weight:600;font-variant-numeric:tabular-nums}\n .tw-so-metarow{display:flex;justify-content:space-between;padding:11px 12px;background:var(--surface2);\n border:1px solid var(--border);border-radius:8px;margin-bottom:16px}\n .tw-so-metarow>div{display:flex;flex-direction:column;gap:3px}\n .tw-so-meta-l{font-size:10.5px;color:var(--muted)}\n .tw-so-meta-v{font-size:12.5px;font-weight:500;font-variant-numeric:tabular-nums}\n .tw-so-section-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}\n .tw-so-calls{display:flex;flex-direction:column;gap:7px;margin-bottom:14px}\n .tw-so-call{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:9px 11px}\n .tw-so-call-top{display:flex;align-items:center;gap:9px;margin-bottom:5px}\n .tw-so-call-time{font-size:11px;color:var(--muted);flex:1}\n .tw-so-call-cost{font-size:12px;font-weight:600;font-variant-numeric:tabular-nums}\n .tw-so-call-bot{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}\n .tw-so-viewall{width:100%;height:36px;border:1px solid var(--border);background:var(--surface2);\n color:var(--accent);border-radius:8px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:var(--sans);\n display:flex;align-items:center;justify-content:center;gap:6px}\n .tw-so-viewall:hover{border-color:var(--accent);background:rgba(88,166,255,.08)}\n .tw-cmdk-scrim{position:fixed;inset:0;background:rgba(1,4,9,.55);z-index:80;\n display:flex;justify-content:center;align-items:flex-start;padding-top:14vh;\n animation:tw-fade .15s ease}\n .tw-cmdk{width:560px;max-width:92vw;background:var(--surface2);border:1px solid var(--border);\n border-radius:12px;box-shadow:0 24px 70px rgba(0,0,0,.6);overflow:hidden}\n .tw-cmdk-input{display:flex;align-items:center;gap:10px;padding:13px 15px;border-bottom:1px solid var(--border)}\n .tw-cmdk-input input{flex:1;background:transparent;border:0;outline:none;color:var(--text);\n font-size:14px;font-family:var(--sans)}\n .tw-cmdk-list{max-height:360px;overflow-y:auto;padding:6px}\n .tw-cmdk-item{display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:7px;cursor:pointer}\n .tw-cmdk-item:hover{background:#22272e}\n .tw-cmdk-g{font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--dim);width:62px}\n .tw-cmdk-l{flex:1;font-size:13px}\n .tw-cmdk-empty{padding:18px;text-align:center;color:var(--muted);font-size:12.5px}\n .tw-skel-wrap{display:flex;flex-direction:column;gap:14px}\n .tw-skel{background:linear-gradient(90deg,var(--surface) 25%,#1b2128 50%,var(--surface) 75%);\n background-size:200% 100%;border:1px solid var(--border);border-radius:10px;\n animation:tw-shim 1.4s linear infinite}\n @keyframes tw-shim{to{background-position:-200% 0}}\n .tw-empty{display:flex;flex-direction:column;align-items:center;gap:12px;\n padding:80px 20px;text-align:center}\n .tw-empty-art{width:96px;height:96px;display:grid;place-items:center;\n background:var(--surface);border:1px solid var(--border);border-radius:20px}\n .tw-empty h2{font-size:18px}\n .tw-empty p{max-width:420px;color:var(--muted);font-size:13px;margin:0}\n .tw-empty-actions{display:flex;gap:10px;margin-top:6px}\n .tw-btn-primary{display:flex;align-items:center;gap:6px;height:34px;padding:0 14px;\n background:var(--accent);color:#0d1117;border:0;border-radius:8px;font-size:13px;\n font-weight:600;cursor:pointer;font-family:var(--sans)}\n .tw-btn-primary:hover{filter:brightness(1.08)}\n .tw-empty-snippet{margin-top:12px;font-family:var(--mono);font-size:12px;color:var(--muted);\n background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px 14px}\n .tw-snip-c{color:#ff7b72}.tw-snip-s{color:var(--green)}\n .tw-so-call-lat{font-variant-numeric:tabular-nums}\n @media(prefers-reduced-motion:reduce){*{animation-duration:.001s!important}}\n\n /* tweaks panel */\n .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;\n max-height:calc(100vh - 32px);display:flex;flex-direction:column;\n transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right;\n background:rgba(250,249,247,.78);color:#29261b;\n -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);\n border:.5px solid rgba(255,255,255,.6);border-radius:14px;\n box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);\n font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}\n .twk-hd{display:flex;align-items:center;justify-content:space-between;\n padding:10px 8px 10px 14px;cursor:move;user-select:none}\n .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}\n .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);\n width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}\n .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}\n .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;\n overflow-y:auto;overflow-x:hidden;min-height:0;\n scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}\n .twk-body::-webkit-scrollbar{width:8px}\n .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}\n .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;\n border:2px solid transparent;background-clip:content-box}\n .twk-row{display:flex;flex-direction:column;gap:5px}\n .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}\n .twk-lbl{display:flex;justify-content:space-between;align-items:baseline;color:rgba(41,38,27,.72)}\n .twk-lbl>span:first-child{font-weight:500}\n .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}\n .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;\n color:rgba(41,38,27,.45);padding:10px 0 0}\n .twk-sect:first-child{padding-top:0}\n .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px;\n border:.5px solid rgba(0,0,0,.1);border-radius:7px;\n background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}\n .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}\n select.twk-field{padding-right:22px}\n .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;\n border-radius:999px;background:rgba(0,0,0,.12);outline:none}\n .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;\n width:14px;height:14px;border-radius:50%;background:#fff;\n border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}\n .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;\n background:rgba(0,0,0,.06);user-select:none}\n .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;\n background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);\n transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}\n .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;\n background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;\n border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2}\n .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;\n background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}\n .twk-toggle[data-on=\"1\"]{background:#34c759}\n .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;\n background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}\n .twk-toggle[data-on=\"1\"] i{transform:translateX(14px)}\n .twk-chips{display:flex;gap:6px}\n .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px;\n padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default;\n box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06);transition:transform .12s}\n .twk-chip[data-on=\"1\"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85),0 2px 6px rgba(0,0,0,.15)}\n .twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%;display:flex;flex-direction:column}\n .twk-chip>span>i{flex:1}\n .twk-chip svg{position:absolute;top:6px;left:6px;width:13px;height:13px}\n .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;\n border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;background:transparent}\n .twk-swatch::-webkit-color-swatch-wrapper{padding:0}\n .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}\n</style>\n</head>\n<body>\n<div id=\"root\"></div>\n<script src=\"https://unpkg.com/react@18.3.1/umd/react.production.min.js\" crossorigin=\"anonymous\"></script>\n<script src=\"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js\" crossorigin=\"anonymous\"></script>\n<script src=\"https://unpkg.com/@babel/standalone@7.29.0/babel.min.js\" crossorigin=\"anonymous\"></script>\n\n<script type=\"text/babel\">\n// tweaks-panel — without runtime style injection (CSS is in the main style block)\nfunction useTweaks(defaults) {\n const [values, setValues] = React.useState(defaults);\n const setTweak = React.useCallback((keyOrEdits, val) => {\n const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null\n ? keyOrEdits : { [keyOrEdits]: val };\n setValues((prev) => ({ ...prev, ...edits }));\n window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*');\n window.dispatchEvent(new CustomEvent('tweakchange', { detail: edits }));\n }, []);\n return [values, setTweak];\n}\n\nfunction TweaksPanel({ title = 'Tweaks', children }) {\n const [open, setOpen] = React.useState(false);\n const dragRef = React.useRef(null);\n const offsetRef = React.useRef({ x: 16, y: 16 });\n const PAD = 16;\n const clampToViewport = React.useCallback(() => {\n const panel = dragRef.current; if (!panel) return;\n const w = panel.offsetWidth, h = panel.offsetHeight;\n offsetRef.current = {\n x: Math.min(Math.max(PAD, window.innerWidth - w - PAD), Math.max(PAD, offsetRef.current.x)),\n y: Math.min(Math.max(PAD, window.innerHeight - h - PAD), Math.max(PAD, offsetRef.current.y)),\n };\n panel.style.right = offsetRef.current.x + 'px';\n panel.style.bottom = offsetRef.current.y + 'px';\n }, []);\n React.useEffect(() => {\n if (!open) return;\n clampToViewport();\n const ro = new ResizeObserver(clampToViewport);\n ro.observe(document.documentElement);\n return () => ro.disconnect();\n }, [open, clampToViewport]);\n React.useEffect(() => {\n const onMsg = (e) => {\n const t = e && e.data && e.data.type;\n if (t === '__activate_edit_mode') setOpen(true);\n else if (t === '__deactivate_edit_mode') setOpen(false);\n };\n window.addEventListener('message', onMsg);\n window.parent.postMessage({ type: '__edit_mode_available' }, '*');\n return () => window.removeEventListener('message', onMsg);\n }, []);\n const dismiss = () => { setOpen(false); window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*'); };\n const onDragStart = (e) => {\n const panel = dragRef.current; if (!panel) return;\n const r = panel.getBoundingClientRect();\n const sx = e.clientX, sy = e.clientY;\n const startRight = window.innerWidth - r.right, startBottom = window.innerHeight - r.bottom;\n const move = (ev) => { offsetRef.current = { x: startRight - (ev.clientX - sx), y: startBottom - (ev.clientY - sy) }; clampToViewport(); };\n const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };\n window.addEventListener('mousemove', move); window.addEventListener('mouseup', up);\n };\n if (!open) return null;\n return (\n <div ref={dragRef} className=\"twk-panel\" style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>\n <div className=\"twk-hd\" onMouseDown={onDragStart}>\n <b>{title}</b>\n <button className=\"twk-x\" onMouseDown={(e) => e.stopPropagation()} onClick={dismiss}>✕</button>\n </div>\n <div className=\"twk-body\">{children}</div>\n </div>\n );\n}\nfunction TweakSection({ label }) { return <div className=\"twk-sect\">{label}</div>; }\nfunction TweakRow({ label, value, children, inline }) {\n return (\n <div className={inline ? 'twk-row twk-row-h' : 'twk-row'}>\n <div className=\"twk-lbl\"><span>{label}</span>{value != null && <span className=\"twk-val\">{value}</span>}</div>\n {children}\n </div>\n );\n}\nfunction TweakSlider({ label, value, min=0, max=100, step=1, unit='', onChange }) {\n return (\n <TweakRow label={label} value={value + unit}>\n <input type=\"range\" className=\"twk-slider\" min={min} max={max} step={step} value={value} onChange={(e) => onChange(Number(e.target.value))} />\n </TweakRow>\n );\n}\nfunction TweakToggle({ label, value, onChange }) {\n return (\n <div className=\"twk-row twk-row-h\">\n <div className=\"twk-lbl\"><span>{label}</span></div>\n <button type=\"button\" className=\"twk-toggle\" data-on={value ? '1' : '0'} onClick={() => onChange(!value)}><i /></button>\n </div>\n );\n}\nfunction TweakSelect({ label, value, options, onChange }) {\n return (\n <TweakRow label={label}>\n <select className=\"twk-field\" value={value} onChange={(e) => onChange(e.target.value)}>\n {options.map((o) => { const v = typeof o === 'object' ? o.value : o; const l = typeof o === 'object' ? o.label : o; return <option key={v} value={v}>{l}</option>; })}\n </select>\n </TweakRow>\n );\n}\nfunction TweakRadio({ label, value, options, onChange }) {\n const trackRef = React.useRef(null);\n const [dragging, setDragging] = React.useState(false);\n const valueRef = React.useRef(value); valueRef.current = value;\n const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length;\n const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0);\n const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] || 0);\n if (!fitsAsSegments) {\n const resolve = (s) => { const m = options.find((o) => String(typeof o === 'object' ? o.value : o) === s); return m === undefined ? s : typeof m === 'object' ? m.value : m; };\n return <TweakSelect label={label} value={value} options={options} onChange={(s) => onChange(resolve(s))} />;\n }\n const opts = options.map((o) => (typeof o === 'object' ? o : { value: o, label: o }));\n const idx = Math.max(0, opts.findIndex((o) => o.value === value));\n const n = opts.length;\n const segAt = (clientX) => {\n const r = trackRef.current.getBoundingClientRect();\n const i = Math.floor(((clientX - r.left - 2) / (r.width - 4)) * n);\n return opts[Math.max(0, Math.min(n - 1, i))].value;\n };\n const onPointerDown = (e) => {\n setDragging(true);\n const v0 = segAt(e.clientX); if (v0 !== valueRef.current) onChange(v0);\n const move = (ev) => { if (!trackRef.current) return; const v = segAt(ev.clientX); if (v !== valueRef.current) onChange(v); };\n const up = () => { setDragging(false); window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); };\n window.addEventListener('pointermove', move); window.addEventListener('pointerup', up);\n };\n return (\n <TweakRow label={label}>\n <div ref={trackRef} role=\"radiogroup\" onPointerDown={onPointerDown} className={dragging ? 'twk-seg dragging' : 'twk-seg'}>\n <div className=\"twk-seg-thumb\" style={{ left: 'calc(2px + ' + idx + ' * (100% - 4px) / ' + n + ')', width: 'calc((100% - 4px) / ' + n + ')' }} />\n {opts.map((o) => <button key={o.value} type=\"button\" role=\"radio\" aria-checked={o.value === value}>{o.label}</button>)}\n </div>\n </TweakRow>\n );\n}\nfunction __twkIsLight(hex) {\n const h = String(hex).replace('#', '');\n const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0');\n const n = parseInt(x.slice(0, 6), 16);\n if (Number.isNaN(n)) return true;\n const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;\n return r * 299 + g * 587 + b * 114 > 148000;\n}\nconst __TwkCheck = ({ light }) => (\n <svg viewBox=\"0 0 14 14\" aria-hidden=\"true\">\n <path d=\"M3 7.2 5.8 10 11 4.2\" fill=\"none\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" stroke={light ? 'rgba(0,0,0,.78)' : '#fff'} />\n </svg>\n);\nfunction TweakColor({ label, value, options, onChange }) {\n if (!options || !options.length) {\n return (\n <div className=\"twk-row twk-row-h\">\n <div className=\"twk-lbl\"><span>{label}</span></div>\n <input type=\"color\" className=\"twk-swatch\" value={value} onChange={(e) => onChange(e.target.value)} />\n </div>\n );\n }\n const key = (o) => String(JSON.stringify(o)).toLowerCase();\n const cur = key(value);\n return (\n <TweakRow label={label}>\n <div className=\"twk-chips\" role=\"radiogroup\">\n {options.map((o, i) => {\n const colors = Array.isArray(o) ? o : [o];\n const [hero, ...rest] = colors;\n const sup = rest.slice(0, 4);\n const on = key(o) === cur;\n return (\n <button key={i} type=\"button\" className=\"twk-chip\" role=\"radio\" data-on={on ? '1' : '0'} style={{ background: hero }} onClick={() => onChange(o)}>\n {sup.length > 0 && <span>{sup.map((c, j) => <i key={j} style={{ background: c }} />)}</span>}\n {on && <__TwkCheck light={__twkIsLight(hero)} />}\n </button>\n );\n })}\n </div>\n </TweakRow>\n );\n}\nObject.assign(window, { useTweaks, TweaksPanel, TweakSection, TweakRow, TweakSlider, TweakToggle, TweakRadio, TweakSelect, TweakColor });\n</script>\n\n<script type=\"text/babel\">\n// tw-charts.jsx\nconst { useRef, useState, useEffect, useLayoutEffect } = React;\nfunction useMeasure() {\n const ref = useRef(null);\n const [w, setW] = useState(0);\n useLayoutEffect(() => {\n if (!ref.current) return;\n const ro = new ResizeObserver((es) => setW(es[0].contentRect.width));\n ro.observe(ref.current);\n setW(ref.current.clientWidth);\n return () => ro.disconnect();\n }, []);\n return [ref, w];\n}\nfunction useCountUp(target, { duration = 700, enabled = true, decimals = 0 } = {}) {\n const [val, setVal] = useState(enabled ? 0 : target);\n const fromRef = useRef(enabled ? 0 : target);\n useEffect(() => {\n if (!enabled) { setVal(target); return; }\n const from = fromRef.current, start = performance.now();\n let raf;\n const tick = (t) => {\n const p = Math.min(1, (t - start) / duration);\n const e = 1 - Math.pow(1 - p, 3);\n setVal(from + (target - from) * e);\n if (p < 1) raf = requestAnimationFrame(tick);\n else fromRef.current = target;\n };\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [target, enabled, duration]);\n return val;\n}\nfunction LineChart({ current, previous, n, color = '#58a6ff', compare = false, animate = true, fmt }) {\n const [ref, w] = useMeasure();\n const [hover, setHover] = useState(null);\n const H = 248, padT = 16, padB = 26, padL = 8, padR = 8;\n const innerW = Math.max(1, w - padL - padR), innerH = H - padT - padB;\n const series = compare && previous ? [...current, ...previous] : current;\n const max = Math.max(...series, 0.0001) * 1.18;\n const X = (i) => padL + (i / Math.max(n - 1, 1)) * innerW;\n const Y = (v) => padT + innerH - (v / max) * innerH;\n const path = (arr) => arr.map((v, i) => (i ? 'L' : 'M') + X(i).toFixed(1) + ' ' + Y(v).toFixed(1)).join(' ');\n const area = (arr) => path(arr) + ' L' + X(n - 1).toFixed(1) + ' ' + (padT + innerH).toFixed(1) + ' L' + padL.toFixed(1) + ' ' + (padT + innerH).toFixed(1) + ' Z';\n const gid = 'lg' + color.replace('#', '');\n const onMove = (e) => {\n const r = e.currentTarget.getBoundingClientRect();\n const x = e.clientX - r.left - padL;\n const i = Math.max(0, Math.min(n - 1, Math.round((x / innerW) * (n - 1))));\n setHover(i);\n };\n return (\n <div ref={ref} style={{ position: 'relative', width: '100%' }}>\n {w > 0 && (\n <svg width={w} height={H} onMouseMove={onMove} onMouseLeave={() => setHover(null)} style={{ display: 'block', cursor: 'crosshair' }}>\n <defs>\n <linearGradient id={gid} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={color} stopOpacity=\"0.22\" />\n <stop offset=\"100%\" stopColor={color} stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n {[0.25, 0.5, 0.75, 1].map((g, i) => (\n <line key={i} x1={padL} x2={w - padR} y1={padT + innerH * g} y2={padT + innerH * g} stroke=\"#21262d\" strokeWidth=\"1\" />\n ))}\n {current.length > 1 && <path d={area(current)} fill={'url(#' + gid + ')'} className={animate ? 'tw-draw-area' : ''} />}\n {compare && previous && previous.length > 1 && (\n <path d={path(previous)} fill=\"none\" stroke=\"#6e7681\" strokeWidth=\"1.6\" strokeDasharray=\"5 4\" opacity=\"0.85\" />\n )}\n {current.length > 1 && <path d={path(current)} fill=\"none\" stroke={color} strokeWidth=\"2.2\" strokeLinejoin=\"round\" strokeLinecap=\"round\" className={animate ? 'tw-draw-line' : ''} />}\n {hover != null && (\n <g>\n <line x1={X(hover)} x2={X(hover)} y1={padT} y2={padT + innerH} stroke=\"#484f58\" strokeWidth=\"1\" strokeDasharray=\"3 3\" />\n {compare && previous && previous[hover] != null && <circle cx={X(hover)} cy={Y(previous[hover])} r=\"3.5\" fill=\"#21262d\" stroke=\"#6e7681\" strokeWidth=\"1.6\" />}\n <circle cx={X(hover)} cy={Y(current[hover])} r=\"4.5\" fill=\"#0d1117\" stroke={color} strokeWidth=\"2.2\" />\n </g>\n )}\n </svg>\n )}\n {hover != null && w > 0 && (\n <div style={{ position: 'absolute', top: 6, pointerEvents: 'none', left: Math.min(Math.max(X(hover) - 70, 4), w - 144), width: 140, background: '#1c2128', border: '1px solid #30363d', borderRadius: 6, padding: '6px 8px', fontSize: 11, boxShadow: '0 6px 20px rgba(0,0,0,.5)' }}>\n <div style={{ color: '#7d8590', marginBottom: 3 }}>point {hover + 1}/{n}</div>\n <div style={{ display: 'flex', justifyContent: 'space-between', color: '#e6edf3', fontVariantNumeric: 'tabular-nums' }}>\n <span style={{ color }}>● current</span><b>{fmt ? fmt(current[hover]) : current[hover].toFixed(4)}</b>\n </div>\n {compare && previous && previous[hover] != null && (\n <div style={{ display: 'flex', justifyContent: 'space-between', color: '#8b949e', fontVariantNumeric: 'tabular-nums', marginTop: 2 }}>\n <span>○ previous</span><span>{fmt ? fmt(previous[hover]) : previous[hover].toFixed(4)}</span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\nfunction Doughnut({ data, total, fmt, active, onHover }) {\n const size = 188, stroke = 26, r = (size - stroke) / 2, c = 2 * Math.PI * r;\n let acc = 0;\n const safeTotal = total || 0.0001;\n return (\n <svg width={size} height={size} viewBox={'0 0 ' + size + ' ' + size}>\n <g transform={'rotate(-90 ' + (size / 2) + ' ' + (size / 2) + ')'}>\n {data.map((d) => {\n const frac = d.cost / safeTotal;\n const dash = frac * c;\n const seg = (\n <circle key={d.id} cx={size / 2} cy={size / 2} r={r} fill=\"none\"\n stroke={d.color} strokeWidth={active === d.id ? stroke + 5 : stroke}\n strokeDasharray={dash + ' ' + (c - dash)} strokeDashoffset={-acc}\n opacity={active && active !== d.id ? 0.32 : 1}\n style={{ transition: 'opacity .15s, stroke-width .15s', cursor: 'pointer' }}\n onMouseEnter={() => onHover && onHover(d.id)} onMouseLeave={() => onHover && onHover(null)} />\n );\n acc += dash;\n return seg;\n })}\n </g>\n <text x=\"50%\" y=\"46%\" textAnchor=\"middle\" fill=\"#7d8590\" fontSize=\"11\" style={{ textTransform: 'uppercase', letterSpacing: '.05em' }}>\n {active ? ((data.find((d) => d.id === active) || {}).id || 'total') : 'total'}\n </text>\n <text x=\"50%\" y=\"58%\" textAnchor=\"middle\" fill=\"#e6edf3\" fontSize=\"20\" fontWeight=\"600\" style={{ fontVariantNumeric: 'tabular-nums' }}>\n {active ? fmt(((data.find((d) => d.id === active) || {}).cost) || 0) : fmt(total)}\n </text>\n </svg>\n );\n}\nfunction Sparkline({ data, color = '#58a6ff', w = 92, h = 30 }) {\n if (!data || data.length < 2) return null;\n const max = Math.max(...data, 0.0001), min = Math.min(...data);\n const X = (i) => (i / (data.length - 1)) * w;\n const Y = (v) => h - 2 - ((v - min) / (max - min || 1)) * (h - 4);\n const d = data.map((v, i) => (i ? 'L' : 'M') + X(i).toFixed(1) + ' ' + Y(v).toFixed(1)).join(' ');\n return (\n <svg width={w} height={h} style={{ display: 'block' }}>\n <path d={d + ' L' + w + ' ' + h + ' L0 ' + h + ' Z'} fill={color} opacity=\"0.12\" />\n <path d={d} fill=\"none\" stroke={color} strokeWidth=\"1.6\" strokeLinejoin=\"round\" strokeLinecap=\"round\" />\n </svg>\n );\n}\nfunction ShareBar({ frac, color }) {\n return (\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <div style={{ flex: 1, height: 6, background: '#21262d', borderRadius: 3, overflow: 'hidden', minWidth: 40 }}>\n <div style={{ width: (frac * 100).toFixed(1) + '%', height: '100%', background: color, borderRadius: 3, transition: 'width .4s' }} />\n </div>\n <span style={{ color: '#7d8590', fontSize: 11, width: 34, textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}>{(frac * 100).toFixed(1)}%</span>\n </div>\n );\n}\nObject.assign(window, { useMeasure, useCountUp, LineChart, Doughnut, Sparkline, ShareBar });\n</script>\n\n<script type=\"text/babel\">\n// tw-cards.jsx\nconst { useState: useStateC } = React;\nconst Ico = {\n chevron: (p) => <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...p}><path d=\"M3 4.5 6 7.5 9 4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n search: (p) => <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" {...p}><circle cx=\"6\" cy=\"6\" r=\"4.2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" /><path d=\"M9.2 9.2 12 12\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" /></svg>,\n download: (p) => <svg width=\"13\" height=\"13\" viewBox=\"0 0 14 14\" {...p}><path d=\"M7 1.5v7m0 0 2.6-2.6M7 8.5 4.4 5.9M2 11.5h10\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n bolt: (p) => <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...p}><path d=\"M7 1 2.5 7H5l-.5 4L9 5H6.5z\" fill=\"currentColor\" /></svg>,\n arrow: (p) => <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" {...p}><path d=\"M2.5 6h7m0 0L7 3.5M9.5 6 7 8.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n warn: (p) => <svg width=\"13\" height=\"13\" viewBox=\"0 0 14 14\" {...p}><path d=\"M7 1.5 13 12H1z\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinejoin=\"round\" /><path d=\"M7 5.5v3M7 10.2v.1\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" /></svg>,\n check: (p) => <svg width=\"13\" height=\"13\" viewBox=\"0 0 14 14\" {...p}><path d=\"M2.5 7.5 5.5 10.5 11.5 3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>,\n};\nfunction Header({ t, onOpenPalette }) {\n const [wsOpen, setWsOpen] = useStateC(false);\n const nav = ['Overview', 'Sessions', 'Users', 'Features', 'Settings'];\n return (\n <header className=\"tw-header\">\n <div className=\"tw-hgroup\">\n <div className=\"tw-logo\">token<span>watch</span></div>\n <button className=\"tw-ws\" onClick={() => setWsOpen((v) => !v)}>\n <span className=\"tw-ws-dot\" />\n <span>tokenwatch</span>\n <span className=\"tw-ws-env\">local</span>\n <Ico.chevron style={{ color: '#7d8590' }} />\n </button>\n <nav className=\"tw-nav\">\n {nav.map((n, i) => <a key={n} className={i === 0 ? 'on' : ''} href=\"#\" onClick={(e) => e.preventDefault()}>{n}</a>)}\n </nav>\n </div>\n <div className=\"tw-hgroup\">\n {t.commandPalette && (\n <button className=\"tw-search\" onClick={onOpenPalette}>\n <Ico.search style={{ color: '#7d8590' }} />\n <span>Search</span>\n <kbd>⌘K</kbd>\n </button>\n )}\n <button className=\"tw-btn-2\"><Ico.download /> Export CSV</button>\n </div>\n </header>\n );\n}\nfunction BudgetBar({ t }) {\n const { BUDGET, fmtMoney } = window.TW;\n const { used, limit, daysLeft, cycleDays } = BUDGET;\n const pct = Math.min(used / Math.max(limit, 0.0001), 1);\n const elapsed = cycleDays - daysLeft;\n const barColor = pct < 0.5 ? '#3fb950' : pct < 0.8 ? '#d29922' : '#f85149';\n const projectedDaily = used / Math.max(elapsed, 1);\n const projected = used + projectedDaily * daysLeft;\n const projPct = Math.min(projected / Math.max(limit, 0.0001), 1);\n return (\n <div className=\"tw-budget\">\n <div className=\"tw-budget-top\">\n <div className=\"tw-budget-label\">\n <span className=\"tw-bud-strong\">Monthly budget</span>\n <span className=\"tw-bud-nums\"><b>{fmtMoney(used)}</b> used of {fmtMoney(limit)}</span>\n </div>\n {t.budgetAlerts && (\n <div className=\"tw-bud-alert ok\">\n <Ico.check style={{ color: '#3fb950' }} />\n Projected <b>{fmtMoney(projected)}</b> by cycle end\n </div>\n )}\n <div className=\"tw-budget-days\">{daysLeft} days left · day {elapsed}/{cycleDays}</div>\n </div>\n <div className=\"tw-bar\">\n <div className=\"tw-bar-fill\" style={{ width: (pct * 100) + '%', background: barColor }} />\n {t.budgetAlerts && <div className=\"tw-bar-proj\" style={{ left: (pct * 100) + '%', width: ((projPct - pct) * 100) + '%' }} />}\n {t.budgetAlerts && <div className=\"tw-bar-marker\" style={{ left: (projPct * 100) + '%' }} data-tip={'proj ' + fmtMoney(projected)} />}\n </div>\n </div>\n );\n}\nfunction KpiCard({ label, value, sub, delta, deltaColor, spark, sparkColor, t }) {\n return (\n <div className=\"tw-kpi\">\n <div className=\"tw-kpi-label\">{label}</div>\n <div className=\"tw-kpi-main\">\n <div className=\"tw-kpi-value\">{value}</div>\n {t.kpiSparklines && spark && spark.length >= 2 && <Sparkline data={spark} color={sparkColor} w={84} h={28} />}\n </div>\n <div className=\"tw-kpi-foot\">\n {delta && <span className=\"tw-delta\" style={{ color: t.smartHighlight ? deltaColor : '#7d8590' }}>{delta}</span>}\n {sub && <span className=\"tw-kpi-sub\">{sub}</span>}\n </div>\n </div>\n );\n}\nfunction KpiRow({ kpis, range, t }) {\n const { fmtUSD, fmtInt, seriesForRange } = window.TW;\n const s = seriesForRange(range).current;\n const callsSeries = s.map((v, i) => 6 + Math.abs(Math.sin(i * 1.7)) * 14);\n return (\n <div className=\"tw-kpis\">\n <KpiCard t={t} label=\"Total cost\" value={fmtUSD(kpis.cost)} delta=\"↑ 12% vs last week\" deltaColor=\"#f85149\" spark={s} sparkColor=\"#58a6ff\" />\n <KpiCard t={t} label=\"Input tokens\" value={fmtInt(kpis.inTok)} sub=\"tokens in\" spark={s} sparkColor=\"#3fb950\" />\n <KpiCard t={t} label=\"Output tokens\" value={fmtInt(kpis.outTok)} sub=\"tokens out\" spark={s.map((v) => v * 0.9)} sparkColor=\"#bc8cff\" />\n <KpiCard t={t} label=\"Total calls\" value={fmtInt(kpis.calls)} delta=\"↓ 5% vs last week\" deltaColor=\"#3fb950\" spark={callsSeries} sparkColor=\"#56d4dd\" />\n <KpiCard t={t} label=\"Burn rate\" value={fmtUSD(kpis.burnHr, 4) + '/hr'} sub={'proj ' + fmtUSD(kpis.burnHr * 24, 2) + '/day'} spark={s.map((v) => v * 1.05)} sparkColor=\"#e3b341\" />\n </div>\n );\n}\nfunction TimeFilter({ range, setRange, t, setTweak }) {\n const ranges = ['1h', '24h', '7d', '30d', 'All'];\n return (\n <div className=\"tw-timefilter\">\n <div className=\"tw-tabs\">\n {ranges.map((r) => (\n <button key={r} className={'tw-tab' + (r === range ? ' on' : '')} onClick={() => setRange(r)}>{r}</button>\n ))}\n </div>\n <label className=\"tw-compare\">\n <button className=\"tw-toggle-sm\" data-on={t.compareMode ? '1' : '0'} onClick={() => setTweak('compareMode', !t.compareMode)}><i /></button>\n Compare to previous period\n </label>\n </div>\n );\n}\nfunction ForecastCard({ label, value, sub, accent, flag }) {\n return (\n <div className={'tw-fc' + (flag ? ' flag' : '')}>\n <div className=\"tw-fc-label\">{label}</div>\n <div className=\"tw-fc-value\" style={accent ? { color: accent } : null}>{value}</div>\n {sub && <div className=\"tw-fc-sub\">{sub}</div>}\n </div>\n );\n}\nfunction ForecastSection({ t }) {\n const { fmtUSD, fmtMoney, BUDGET } = window.TW;\n const daily = BUDGET.daily != null ? BUDGET.daily : 0.8473;\n const burnHr = daily / 24;\n const remaining = daily * BUDGET.daysLeft;\n const projCycle = BUDGET.used + remaining;\n const g = t.forecastScenario / 100;\n const scenarioCycle = BUDGET.used + remaining * (1 + g);\n const over = scenarioCycle > BUDGET.limit;\n return (\n <section className=\"tw-section\">\n <div className=\"tw-sec-head\"><h3>Cost forecast</h3><span className=\"tw-sec-sub\">based on current run-rate</span></div>\n <div className=\"tw-forecast\">\n <ForecastCard label=\"Projected daily\" value={fmtUSD(daily, 2)} sub=\"next 24h at this rate\" />\n <ForecastCard label=\"Projected this cycle\" value={fmtMoney(projCycle)} sub={'of ' + fmtMoney(BUDGET.limit) + ' budget'} />\n <ForecastCard label=\"Burn rate\" value={fmtUSD(burnHr, 4)} sub=\"per hour\" accent=\"#e3b341\" />\n <ForecastCard label={'If usage grows ' + t.forecastScenario + '%'} value={fmtMoney(scenarioCycle)} sub={over ? 'over budget ⚠' : fmtMoney(BUDGET.limit - scenarioCycle) + ' headroom'} accent={over ? '#f85149' : '#58a6ff'} flag={over} />\n </div>\n </section>\n );\n}\nObject.assign(window, { Ico, Header, BudgetBar, KpiCard, KpiRow, TimeFilter, ForecastCard, ForecastSection });\n</script>\n\n<script type=\"text/babel\">\n// tw-table.jsx\nconst { useState: useStateT } = React;\nfunction ModelTable({ models, total, t, onRowClick, exampleHover }) {\n const { fmtInt, fmtUSD, fmtCompact } = window.TW;\n const [sort, setSort] = useStateT({ key: 'cost', dir: -1 });\n const [hoverRow, setHoverRow] = useStateT(null);\n const cols = [\n { key: 'id', label: 'Model', align: 'left' },\n { key: 'calls', label: 'Calls', align: 'right' },\n { key: 'inTok', label: 'In tokens', align: 'right' },\n { key: 'outTok', label: 'Out tokens', align: 'right' },\n { key: 'cost', label: 'Cost', align: 'right' },\n { key: 'share', label: 'Share', align: 'left' },\n { key: 'avg', label: 'Avg / call', align: 'right' },\n ];\n const safeTotal = total || 0.0001;\n const rows = [...models].map((m) => ({ ...m, avg: m.cost / Math.max(m.calls, 1), share: m.cost / safeTotal }));\n if (t.tableSort) {\n rows.sort((a, b) => {\n const A = a[sort.key], B = b[sort.key];\n if (typeof A === 'string') return A.localeCompare(B) * sort.dir;\n return (A - B) * sort.dir;\n });\n }\n const clickHeader = (key) => {\n if (!t.tableSort) return;\n setSort((s) => s.key === key ? { key, dir: -s.dir } : { key, dir: key === 'id' ? 1 : -1 });\n };\n return (\n <section className=\"tw-section\">\n <div className=\"tw-sec-head\">\n <h3>Model breakdown</h3>\n <span className=\"tw-sec-sub\">{models.length} models · click a row for detail</span>\n </div>\n <div className=\"tw-table-wrap\">\n <table className={'tw-table' + (t.density === 'compact' ? ' compact' : '')}>\n <thead>\n <tr>\n {cols.map((c) => (\n <th key={c.key} style={{ textAlign: c.align, cursor: t.tableSort && c.key !== 'share' ? 'pointer' : 'default' }}\n onClick={() => c.key !== 'share' && clickHeader(c.key)}>\n <span className=\"tw-th-inner\" style={{ justifyContent: c.align === 'right' ? 'flex-end' : 'flex-start' }}>\n {c.label}\n {t.tableSort && sort.key === c.key && <span className=\"tw-sort\">{sort.dir < 0 ? '▾' : '▴'}</span>}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((m) => {\n const isExample = exampleHover && m.id === 'claude-sonnet-4-6';\n return (\n <tr key={m.id} className={'tw-row' + (isExample ? ' example' : '')}\n onMouseEnter={() => setHoverRow(m.id)} onMouseLeave={() => setHoverRow(null)}\n onClick={() => onRowClick(m)}>\n <td>\n <div className=\"tw-model-cell\">\n <span className=\"tw-model-dot\" style={{ background: m.color }} />\n <span className=\"tw-model-name\">{m.id}</span>\n <span className=\"tw-model-prov\">{m.provider}</span>\n </div>\n </td>\n <td className=\"tw-num-cell\">{fmtInt(m.calls)}</td>\n <td className=\"tw-num-cell\">{fmtCompact(m.inTok)}</td>\n <td className=\"tw-num-cell\">{fmtCompact(m.outTok)}</td>\n <td className=\"tw-num-cell tw-cost\">{fmtUSD(m.cost, 4)}</td>\n <td style={{ minWidth: 140 }}><ShareBar frac={m.share} color={m.color} /></td>\n <td className=\"tw-num-cell\">\n {hoverRow === m.id && t.tableSort ? (\n <div className=\"tw-row-actions\" onClick={(e) => e.stopPropagation()}>\n <button onClick={() => onRowClick(m)}>Details</button>\n <button>Alert</button>\n </div>\n ) : (\n <span>{fmtUSD(m.avg, 4)}</span>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\nfunction MetaGroup({ label, rows, t }) {\n const [open, setOpen] = useStateT(true);\n const { fmtUSD, fmtInt } = window.TW;\n const total = rows.reduce(function(s, r) { return s + r[1].costUSD; }, 0);\n return (\n <section className=\"tw-section\">\n <div className=\"tw-sec-head\" style={{ cursor: 'pointer' }} onClick={() => setOpen(function(v) { return !v; })}>\n <h3 style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <button className=\"tw-collapse\" style={{ transform: open ? 'none' : 'rotate(-90deg)' }}><Ico.chevron /></button>\n {label}\n </h3>\n <span className=\"tw-sec-sub\">{rows.length + ' values \\xb7 ' + fmtUSD(total, 4)}</span>\n </div>\n {open && (\n <div className=\"tw-table-wrap\">\n <table className={'tw-table' + (t.density === 'compact' ? ' compact' : '')}>\n <thead>\n <tr>\n <th style={{ textAlign: 'left' }}>Value</th>\n <th style={{ textAlign: 'right' }}>Cost</th>\n <th style={{ textAlign: 'right' }}>Calls</th>\n <th style={{ textAlign: 'right' }}>Avg / call</th>\n </tr>\n </thead>\n <tbody>\n {rows.map(function(r) {\n var val = r[0], stats = r[1];\n return (\n <tr key={val} className=\"tw-row\">\n <td>{val}</td>\n <td style={{ textAlign: 'right' }}>{fmtUSD(stats.costUSD, 4)}</td>\n <td style={{ textAlign: 'right' }}>{fmtInt(stats.calls)}</td>\n <td style={{ textAlign: 'right' }}>{fmtUSD(stats.costUSD / Math.max(stats.calls, 1), 4)}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )}\n </section>\n );\n}\n\nfunction MetadataSection({ byMetadata, t }) {\n var keys = Object.keys(byMetadata || {});\n if (keys.length === 0) return null;\n return (\n <div>\n {keys.map(function(key) {\n var group = byMetadata[key];\n var rows = Object.entries(group).sort(function(a, b) { return b[1].costUSD - a[1].costUSD; });\n return <MetaGroup key={key} label={key} rows={rows} t={t} />;\n })}\n </div>\n );\n}\n\nObject.assign(window, { ModelTable, MetaGroup, MetadataSection });\n</script>\n\n<script type=\"text/babel\">\n// tw-panel.jsx\nconst { useEffect: useEffectP } = React;\nfunction SlideOver({ model, onClose, t }) {\n const { fmtInt, fmtUSD, callsForModel, fmtAgo } = window.TW;\n useEffectP(() => {\n const onKey = (e) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [onClose]);\n const open = !!model;\n const m = model;\n const calls = m ? callsForModel(m.id, 5) : [];\n const totalIn = m ? m.inTok : 0, totalOut = m ? m.outTok : 0;\n return (\n <>\n <div className={'tw-scrim' + (open ? ' open' : '')} onClick={onClose} />\n <aside className={'tw-slideover' + (open ? ' open' : '')}>\n {m && (\n <div className=\"tw-so-inner\">\n <div className=\"tw-so-head\">\n <div className=\"tw-so-title\">\n <span className=\"tw-model-dot lg\" style={{ background: m.color }} />\n <div>\n <div className=\"tw-so-name\">{m.id}</div>\n <div className=\"tw-so-prov\">{m.provider} · {fmtInt(m.calls)} calls in window</div>\n </div>\n </div>\n <button className=\"tw-so-close\" onClick={onClose}>✕</button>\n </div>\n <div className=\"tw-so-stats\">\n <div className=\"tw-so-stat\"><div className=\"tw-so-stat-l\">Total cost</div><div className=\"tw-so-stat-v\">{fmtUSD(m.cost, 4)}</div></div>\n <div className=\"tw-so-stat\"><div className=\"tw-so-stat-l\">Calls</div><div className=\"tw-so-stat-v\">{fmtInt(m.calls)}</div></div>\n <div className=\"tw-so-stat\"><div className=\"tw-so-stat-l\">Avg / call</div><div className=\"tw-so-stat-v\">{fmtUSD(m.cost / Math.max(m.calls, 1), 4)}</div></div>\n </div>\n <div className=\"tw-so-metarow\">\n <div><span className=\"tw-so-meta-l\">In tokens</span><span className=\"tw-so-meta-v\">{fmtInt(totalIn)}</span></div>\n <div><span className=\"tw-so-meta-l\">Out tokens</span><span className=\"tw-so-meta-v\">{fmtInt(totalOut)}</span></div>\n <div><span className=\"tw-so-meta-l\">Cost / call</span><span className=\"tw-so-meta-v\">{fmtUSD(m.cost / Math.max(m.calls, 1), 4)}</span></div>\n </div>\n <div className=\"tw-so-section-label\">Last 5 calls (estimated)</div>\n <div className=\"tw-so-calls\">\n {calls.map((c) => (\n <div key={c.id} className=\"tw-so-call\">\n <div className=\"tw-so-call-top\">\n <span className=\"tw-so-call-time\">{fmtAgo(c.secondsAgo)}</span>\n <span className=\"tw-feat\" style={{ color: c.featureColor, borderColor: c.featureColor + '55' }}>{c.feature}</span>\n <span className=\"tw-so-call-cost\">{fmtUSD(c.cost, 4)}</span>\n </div>\n <div className=\"tw-so-call-bot\">\n <span>{fmtInt(c.inTok)} in · {fmtInt(c.outTok)} out</span>\n <span className=\"tw-so-call-lat\">{c.latency}ms{c.status === 'slow' ? ' · slow' : c.status === 'error' ? ' · error' : ''}</span>\n </div>\n </div>\n ))}\n </div>\n <button className=\"tw-so-viewall\">View all {fmtInt(m.calls)} calls <Ico.arrow /></button>\n </div>\n )}\n </aside>\n </>\n );\n}\nObject.assign(window, { SlideOver });\n</script>\n\n<script type=\"text/babel\">\n// tw-activity.jsx\nconst { useState: useStateA, useEffect: useEffectA, useRef: useRefA } = React;\nfunction LiveActivity({ t }) {\n const { seedFeed, makeCall, fmtUSD, fmtInt, fmtAgo } = window.TW;\n const [feed, setFeed] = useStateA(() => seedFeed(14));\n const [now, setNow] = useStateA(Date.now());\n const [paused, setPaused] = useStateA(false);\n const [collapsed, setCollapsed] = useStateA(false);\n const [filter, setFilter] = useStateA('all');\n const seedRef = useRefA(5000);\n const streaming = t.liveFeed && t.animLevel !== 'minimal' && !paused;\n useEffectA(() => {\n if (!streaming) return;\n const iv = setInterval(() => {\n const c = makeCall(0, seedRef.current++);\n c.ts = Date.now(); c.fresh = true;\n setFeed((f) => [c, ...f].slice(0, 40));\n }, t.animLevel === 'subtle' ? 3600 : 2100);\n return () => clearInterval(iv);\n }, [streaming, t.animLevel]);\n useEffectA(() => {\n const iv = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(iv);\n }, []);\n const shown = feed\n .map((c) => ({ ...c, secondsAgo: Math.max(0, Math.round((now - c.ts) / 1000)) }))\n .filter((c) => filter === 'all' ? true : filter === 'errors' ? c.status !== 'ok' : c.cost >= 0.001)\n .slice(0, 10);\n return (\n <section className=\"tw-section tw-activity\">\n <div className=\"tw-sec-head\">\n <div className=\"tw-act-title\">\n <button className=\"tw-collapse\" onClick={() => setCollapsed((v) => !v)} style={{ transform: collapsed ? 'rotate(-90deg)' : 'none' }}><Ico.chevron /></button>\n <span className={'tw-live-dot' + (streaming ? ' on' : '')} />\n <h3>Live activity</h3>\n <span className=\"tw-sec-sub\">{streaming ? 'streaming' : t.liveFeed ? 'paused' : 'static'}</span>\n </div>\n <div className=\"tw-act-controls\">\n <div className=\"tw-seg-sm\">\n {[['all', 'All'], ['signal', 'Signal'], ['errors', 'Errors']].map(([k, l]) => (\n <button key={k} className={filter === k ? 'on' : ''} onClick={() => setFilter(k)}>{l}</button>\n ))}\n </div>\n {t.liveFeed && (\n <button className=\"tw-act-pause\" onClick={() => setPaused((p) => !p)}>{paused ? '▶ Resume' : '❚❚ Pause'}</button>\n )}\n </div>\n </div>\n {!collapsed && (\n <div className=\"tw-feed\">\n <div className=\"tw-feed-head\">\n <span>Time</span><span>Model</span><span>Session</span><span>Feature</span><span style={{ textAlign: 'right' }}>Cost</span>\n </div>\n <div className=\"tw-feed-body\">\n {shown.map((c) => (\n <div key={c.id} className={'tw-feed-row' + (c.fresh ? ' fresh' : '') + (c.status === 'error' ? ' err' : '')}>\n <span className=\"tw-feed-time\">{c.secondsAgo === 0 ? 'now' : fmtAgo(c.secondsAgo)}</span>\n <span className=\"tw-feed-model\"><span className=\"tw-model-dot\" style={{ background: c.modelColor }} />{c.model}</span>\n <span className=\"tw-feed-sess\">{c.session}</span>\n <span><span className=\"tw-feat sm\" style={{ color: c.featureColor, borderColor: c.featureColor + '55' }}>{c.feature}</span></span>\n <span className=\"tw-feed-cost\">{fmtUSD(c.cost, 4)}{c.status === 'error' && <span className=\"tw-feed-flag\">err</span>}{c.status === 'slow' && <span className=\"tw-feed-flag slow\">slow</span>}</span>\n </div>\n ))}\n </div>\n </div>\n )}\n </section>\n );\n}\nfunction CommandPalette({ open, onClose, onAction }) {\n const [q, setQ] = useStateA('');\n const inputRef = useRefA(null);\n useEffectA(() => { if (open && inputRef.current) inputRef.current.focus(); if (open) setQ(''); }, [open]);\n useEffectA(() => {\n const onKey = (e) => { if (e.key === 'Escape') onClose(); };\n if (open) window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [open, onClose]);\n const items = [\n { g: 'Filter', label: 'Set range: Last 1h', act: { range: '1h' } },\n { g: 'Filter', label: 'Set range: Last 24h', act: { range: '24h' } },\n { g: 'Filter', label: 'Set range: Last 7 days', act: { range: '7d' } },\n { g: 'Filter', label: 'Set range: Last 30 days', act: { range: '30d' } },\n { g: 'Filter', label: 'Set range: All time', act: { range: 'All' } },\n { g: 'Action', label: 'Export CSV', hint: '⌘E' },\n { g: 'Action', label: 'Create budget alert' },\n ];\n const filtered = items.filter((i) => i.label.toLowerCase().includes(q.toLowerCase()));\n if (!open) return null;\n return (\n <div className=\"tw-cmdk-scrim\" onClick={onClose}>\n <div className=\"tw-cmdk\" onClick={(e) => e.stopPropagation()}>\n <div className=\"tw-cmdk-input\">\n <Ico.search style={{ color: '#7d8590' }} />\n <input ref={inputRef} value={q} onChange={(e) => setQ(e.target.value)} placeholder=\"Search models, sessions, actions…\" />\n <kbd>esc</kbd>\n </div>\n <div className=\"tw-cmdk-list\">\n {filtered.length === 0 && <div className=\"tw-cmdk-empty\">No results for \"{q}\"</div>}\n {filtered.map((i, idx) => (\n <div key={idx} className=\"tw-cmdk-item\" onClick={() => { onAction(i.act); onClose(); }}>\n <span className=\"tw-cmdk-g\">{i.g}</span>\n <span className=\"tw-cmdk-l\">{i.label}</span>\n {i.hint && <kbd>{i.hint}</kbd>}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\nObject.assign(window, { LiveActivity, CommandPalette });\n</script>\n\n<script type=\"text/babel\">\n// tw-data.jsx — mock data + formatters (always-populated baseline)\nconst fmtUSD = (n, d = 4) =>\n '$' + Number(n).toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d });\nconst fmtMoney = (n) =>\n '$' + Number(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\nconst fmtInt = (n) => Math.round(n || 0).toLocaleString('en-US');\nconst fmtCompact = (n) => {\n n = n || 0;\n if (n >= 1e6) return (n / 1e6).toFixed(2).replace(/\\.?0+$/, '') + 'M';\n if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\\.?0+$/, '') + 'K';\n return String(Math.round(n));\n};\nconst fmtAgo = (s) => {\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.floor(s / 60) + 'm ago';\n if (s < 86400) return Math.floor(s / 3600) + 'h ago';\n return Math.floor(s / 86400) + 'd ago';\n};\nconst BASE_MODELS = [\n { id: 'gpt-5-mini', provider: 'OpenAI', color: '#3fb950', calls: 94, inTok: 1040000, outTok: 118000, cost: 0.2110, latency: 590 },\n { id: 'claude-sonnet-4-6', provider: 'Anthropic', color: '#bc8cff', calls: 58, inTok: 612000, outTok: 84000, cost: 0.3120, latency: 1840 },\n { id: 'gemini-2.5-flash', provider: 'Google', color: '#58a6ff', calls: 47, inTok: 430000, outTok: 56000, cost: 0.1190, latency: 510 },\n { id: 'claude-haiku-4-5', provider: 'Anthropic', color: '#f778ba', calls: 38, inTok: 268100, outTok: 38540, cost: 0.1240, latency: 680 },\n { id: 'gpt-5.1', provider: 'OpenAI', color: '#e3b341', calls: 18, inTok: 88000, outTok: 12000, cost: 0.0613, latency: 2210 },\n { id: 'gemini-2.5-pro', provider: 'Google', color: '#56d4dd', calls: 8, inTok: 42000, outTok: 4000, cost: 0.0200, latency: 1990 },\n];\nconst RANGES = {\n '1h': { factor: 0.052, points: 12, label: 'last hour', step: '5 min' },\n '24h': { factor: 1, points: 24, label: 'last 24h', step: 'hour' },\n '7d': { factor: 6.4, points: 28, label: 'last 7 days', step: '6h' },\n '30d': { factor: 53.1, points: 30, label: 'last 30 days',step: 'day' },\n 'All': { factor: 142, points: 26, label: 'all time', step: 'week' },\n};\nfunction modelsForRange(range) {\n const f = RANGES[range].factor;\n return BASE_MODELS.map((m) => ({\n ...m,\n calls: Math.max(1, Math.round(m.calls * f)),\n inTok: Math.round(m.inTok * f),\n outTok: Math.round(m.outTok * f),\n cost: m.cost * f,\n }));\n}\nfunction kpisForRange(range) {\n const ms = modelsForRange(range);\n const sum = (k) => ms.reduce((a, m) => a + m[k], 0);\n const cost = sum('cost'), calls = sum('calls');\n return {\n cost, calls,\n inTok: sum('inTok'), outTok: sum('outTok'),\n models: ms,\n burnHr: cost / ({ '1h': 1, '24h': 24, '7d': 168, '30d': 720, 'All': 3408 }[range]),\n };\n}\nconst DAY_SHAPE = [0.2,0.15,0.12,0.1,0.1,0.15,0.3,0.6,1.0,1.4,1.7,1.8,1.6,1.5,1.9,2.0,1.7,1.3,1.0,0.8,0.6,0.45,0.35,0.25];\nfunction shapeFor(n) {\n const out = [];\n for (let i = 0; i < n; i++) {\n const t = (i / n) * DAY_SHAPE.length;\n const a = DAY_SHAPE[Math.floor(t) % DAY_SHAPE.length];\n const b = DAY_SHAPE[(Math.floor(t) + 1) % DAY_SHAPE.length];\n out.push(a + (b - a) * (t - Math.floor(t)));\n }\n return out;\n}\nfunction buildSeries(total, n, jitterSeed = 1) {\n const shape = shapeFor(n);\n const j = shape.map((v, i) => v * (0.82 + 0.36 * Math.abs(Math.sin(i * 12.9898 * jitterSeed))));\n const s = j.reduce((a, b) => a + b, 0);\n return j.map((v) => (v / s) * total);\n}\nfunction seriesForRange(range) {\n const { cost } = kpisForRange(range);\n const n = RANGES[range].points;\n return { current: buildSeries(cost, n, 1), previous: buildSeries(cost / 1.12, n, 1.7), n };\n}\nconst FEATURES = ['chat', 'rag-search', 'summarize', 'classify', 'agent-loop', 'embeddings', 'code-review', 'extract'];\nconst FEATURE_COLOR = {\n 'chat': '#58a6ff', 'rag-search': '#3fb950', 'summarize': '#bc8cff', 'classify': '#e3b341',\n 'agent-loop': '#f778ba', 'embeddings': '#56d4dd', 'code-review': '#ff7b72', 'extract': '#7ee787',\n};\nlet __callSeq = 48213;\nfunction rng(seed) { let x = Math.sin(seed) * 10000; return x - Math.floor(x); }\nfunction makeCall(secondsAgo, seed) {\n const m = BASE_MODELS[Math.floor(rng(seed) * BASE_MODELS.length)];\n const feat = FEATURES[Math.floor(rng(seed * 1.3) * FEATURES.length)];\n const inTok = Math.round(800 + rng(seed * 2.1) * 14000);\n const outTok = Math.round(60 + rng(seed * 3.7) * 2200);\n const cost = (inTok / 1e6) * 0.4 + (outTok / 1e6) * 3.2;\n const r = rng(seed * 5.9);\n const status = r > 0.965 ? 'error' : r > 0.9 ? 'slow' : 'ok';\n return {\n id: ++__callSeq, secondsAgo, ts: Date.now() - secondsAgo * 1000,\n model: m.id, modelColor: m.color,\n session: 'sess_' + (seed * 7919 % 1e6 | 0).toString(36).padStart(4, '0'),\n feature: feat, featureColor: FEATURE_COLOR[feat],\n inTok, outTok, cost, latency: Math.round(m.latency * (0.6 + rng(seed * 8.3) * 1.2)), status,\n };\n}\nfunction seedFeed(count) {\n const arr = [];\n for (let i = 0; i < count; i++) arr.push(makeCall(2 + i * 7 + Math.floor(rng(i * 3.3) * 6), 100 + i));\n return arr;\n}\nfunction callsForModel(modelId, count = 5) {\n const arr = [];\n let sa = 12;\n for (let i = 0; i < count; i++) {\n const c = makeCall(sa, 900 + i + modelId.length);\n c.model = modelId;\n c.modelColor = (BASE_MODELS.find((m) => m.id === modelId) || {}).color || '#58a6ff';\n arr.push(c);\n sa += 40 + Math.floor(rng(i * 2.2) * 220);\n }\n return arr;\n}\nconst BUDGET = { used: 45.0, limit: 100.0, daysLeft: 18, cycleDays: 30 };\n\nObject.assign(window, {\n TW: {\n fmtUSD, fmtMoney, fmtInt, fmtCompact, fmtAgo,\n BASE_MODELS, RANGES, modelsForRange, kpisForRange,\n seriesForRange, seedFeed, makeCall, callsForModel,\n FEATURES, FEATURE_COLOR, BUDGET, _buildSeries: buildSeries,\n byMetadata: {},\n },\n});\n\n// SSE overlay — patches window.TW functions when real data arrives\n(function () {\n var MC = ['#bc8cff','#3fb950','#58a6ff','#f778ba','#e3b341','#56d4dd','#79c0ff','#ffa657','#ff7b72','#a5d6ff'];\n function guessProv(id) {\n if (/claude/i.test(id)) return 'Anthropic';\n if (/gpt|o1|o3|o4/i.test(id)) return 'OpenAI';\n if (/gemini/i.test(id)) return 'Google';\n return 'Other';\n }\n function buildRealModels(byModel) {\n return Object.entries(byModel).map(function(e, i) {\n var id = e[0], s = e[1];\n return { id: id, provider: guessProv(id), color: MC[i % MC.length],\n calls: s.calls || 0, inTok: (s.tokens && s.tokens.input) || 0,\n outTok: (s.tokens && s.tokens.output) || 0, cost: s.costUSD || 0, latency: 1200 };\n }).sort(function(a, b) { return b.cost - a.cost; });\n }\n function applySSEData(data) {\n var r = data.report, fc = data.forecast, ts = data.timeSeries || [];\n if (!r || !r.byModel || Object.keys(r.byModel).length === 0) return;\n var mods = buildRealModels(r.byModel);\n var totalCalls = mods.reduce(function(s, m) { return s + m.calls; }, 0);\n var totalCost = r.totalCostUSD || 0;\n var totalIn = 0, totalOut = 0;\n if (r.totalTokens) { totalIn = r.totalTokens.input || 0; totalOut = r.totalTokens.output || 0; }\n else { mods.forEach(function(m) { totalIn += m.inTok; totalOut += m.outTok; }); }\n var costs = ts.map(function(b) { return b.cost || 0; });\n window.TW.kpisForRange = function() {\n return { cost: totalCost, calls: totalCalls, inTok: totalIn, outTok: totalOut,\n models: mods, burnHr: (fc && fc.burnRatePerHour) || 0 };\n };\n if (costs.length >= 2) {\n window.TW.seriesForRange = function() {\n return { current: costs, previous: buildSeries(totalCost / 1.12, costs.length, 1.7), n: costs.length };\n };\n }\n if (fc && fc.projectedDailyCostUSD) window.TW.BUDGET.daily = fc.projectedDailyCostUSD;\n if (fc && fc.burnRatePerHour) {\n var elapsed = window.TW.BUDGET.cycleDays - window.TW.BUDGET.daysLeft;\n window.TW.BUDGET.used = fc.burnRatePerHour * 24 * Math.max(elapsed, 1);\n }\n if (r.byMetadata) window.TW.byMetadata = r.byMetadata;\n window.dispatchEvent(new CustomEvent('tw-data-update'));\n }\n var evtSource = null;\n function connect(filter) {\n if (evtSource) { try { evtSource.close(); } catch(e) {} }\n var f = filter === 'All' ? 'all' : filter;\n evtSource = new EventSource('/events?filter=' + encodeURIComponent(f));\n evtSource.onmessage = function(e) { try { applySSEData(JSON.parse(e.data)); } catch(_) {} };\n evtSource.onerror = function() {\n try { evtSource.close(); } catch(e) {}\n setTimeout(function() { connect(f); }, 5000);\n };\n }\n window.__twSSEConnect = connect;\n connect('24h');\n})();\n</script>\n\n<script type=\"text/babel\">\n// tw-app.jsx\nconst { useState: useStateApp, useEffect: useEffectApp, useMemo: useMemoApp } = React;\n\n\nfunction LoadingSkeleton() {\n return (\n <div className=\"tw-skel-wrap\">\n <div className=\"tw-skel\" style={{ height: 56 }} />\n <div className=\"tw-kpis\">{[0,0,0,0,0].map((_,i) => <div key={i} className=\"tw-skel\" style={{ height: 96 }} />)}</div>\n <div className=\"tw-charts\">\n <div className=\"tw-skel tw-chart-main\" style={{ height: 320 }} />\n <div className=\"tw-skel tw-chart-side\" style={{ height: 320 }} />\n </div>\n <div className=\"tw-skel\" style={{ height: 260 }} />\n </div>\n );\n}\nfunction EmptyState() {\n return (\n <div className=\"tw-empty\">\n <div className=\"tw-empty-art\">\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <rect x=\"8\" y=\"14\" width=\"48\" height=\"36\" rx=\"4\" stroke=\"#30363d\" strokeWidth=\"2\" />\n <path d=\"M16 40l8-9 7 6 9-13 8 10\" stroke=\"#484f58\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n <circle cx=\"24\" cy=\"31\" r=\"2\" fill=\"#484f58\" />\n </svg>\n </div>\n <h2>No usage yet</h2>\n <p>Once your app starts making LLM calls through tokenwatch, cost & token metrics show up here in real time.</p>\n <div className=\"tw-empty-actions\">\n <button className=\"tw-btn-primary\">View integration guide <Ico.arrow /></button>\n <button className=\"tw-btn-2\">Copy API key</button>\n </div>\n <div className=\"tw-empty-snippet\">\n <span className=\"tw-snip-c\">import</span> TokenWatch <span className=\"tw-snip-c\">from</span> <span className=\"tw-snip-s\">'@diogonzafe/tokenwatch'</span>\n </div>\n </div>\n );\n}\nfunction ChartsRow({ range, models, kpis, series, t }) {\n const { fmtUSD, RANGES } = window.TW;\n const [activeSlice, setActiveSlice] = useStateApp(null);\n const animate = t.animLevel !== 'minimal';\n const rangeConfig = RANGES[range] || RANGES['24h'];\n const safeTotal = kpis.cost || 0.0001;\n return (\n <div className=\"tw-charts\">\n <div className=\"tw-card tw-chart-main\">\n <div className=\"tw-sec-head\">\n <h3>Cost over time</h3>\n <div className=\"tw-chart-legend\">\n <span><i style={{ background: t.accent }} /> current</span>\n {t.compareMode && series.previous && <span className=\"muted\"><i className=\"dash\" /> previous</span>}\n <span className=\"tw-sec-sub\">· per {rangeConfig.step}</span>\n </div>\n </div>\n <LineChart current={series.current} previous={t.compareMode ? series.previous : null}\n n={series.n} color={t.accent} compare={t.compareMode && !!series.previous}\n animate={animate} fmt={(v) => fmtUSD(v, 4)} />\n </div>\n <div className=\"tw-card tw-chart-side\">\n <div className=\"tw-sec-head\"><h3>By model</h3></div>\n <div className=\"tw-doughnut-wrap\">\n <Doughnut data={models} total={safeTotal} fmt={(v) => fmtUSD(v, 4)} active={activeSlice} onHover={setActiveSlice} />\n </div>\n <div className=\"tw-legend\">\n {[...models].sort((a, b) => b.cost - a.cost).map((m) => (\n <div key={m.id} className={'tw-legend-row' + (activeSlice === m.id ? ' on' : '')}\n onMouseEnter={() => setActiveSlice(m.id)} onMouseLeave={() => setActiveSlice(null)}>\n <span className=\"tw-model-dot\" style={{ background: m.color }} />\n <span className=\"tw-legend-name\">{m.id}</span>\n <span className=\"tw-legend-val\">{fmtUSD(m.cost, 4)}</span>\n <span className=\"tw-legend-pct\">{((m.cost / safeTotal) * 100).toFixed(0)}%</span>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\nconst TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{\n \"density\": \"compact\",\n \"kpiSparklines\": true,\n \"smartHighlight\": true,\n \"compareMode\": false,\n \"tableSort\": true,\n \"budgetAlerts\": true,\n \"forecastScenario\": 20,\n \"liveFeed\": true,\n \"animLevel\": \"lively\",\n \"commandPalette\": true,\n \"appState\": \"data\",\n \"accent\": \"#58a6ff\"\n}/*EDITMODE-END*/;\nfunction App() {\n const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);\n const [range, setRange] = useStateApp('24h');\n const [selModel, setSelModel] = useStateApp(null);\n const [paletteOpen, setPaletteOpen] = useStateApp(false);\n const [_sseV, setSseV] = useStateApp(0);\n\n useEffectApp(() => {\n const h = function() { setSseV(function(v) { return v + 1; }); };\n window.addEventListener('tw-data-update', h);\n return function() { window.removeEventListener('tw-data-update', h); };\n }, []);\n\n useEffectApp(() => {\n if (window.__twSSEConnect) window.__twSSEConnect(range);\n }, [range]);\n\n const kpis = useMemoApp(() => {\n const { kpisForRange } = window.TW;\n return kpisForRange(range);\n }, [range, _sseV]);\n\n const series = useMemoApp(() => {\n const { seriesForRange } = window.TW;\n return seriesForRange(range);\n }, [range, _sseV]);\n\n const models = kpis.models;\n\n const byMetadata = useMemoApp(() => {\n return window.TW.byMetadata || {};\n }, [_sseV]);\n\n useEffectApp(() => {\n const onKey = (e) => {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {\n e.preventDefault();\n if (t.commandPalette) setPaletteOpen((v) => !v);\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [t.commandPalette]);\n\n const handleAction = (act) => {\n if (!act) return;\n if (act.range) setRange(act.range);\n if (act.model) {\n const m = models.find((x) => x.id === act.model);\n if (m) setSelModel({ ...m, share: m.cost / Math.max(kpis.cost, 0.0001) });\n }\n };\n\n return (\n <div className=\"tw-root\" style={{ '--accent': t.accent }}>\n <Header t={t} onOpenPalette={() => setPaletteOpen(true)} />\n {t.appState === 'loading' ? (\n <main className=\"tw-main\"><LoadingSkeleton /></main>\n ) : t.appState === 'empty' ? (\n <main className=\"tw-main\"><EmptyState /></main>\n ) : (\n <main className=\"tw-main\">\n <BudgetBar t={t} />\n <KpiRow kpis={kpis} range={range} t={t} />\n <TimeFilter range={range} setRange={setRange} t={t} setTweak={setTweak} />\n <ChartsRow range={range} models={models} kpis={kpis} series={series} t={t} />\n <ModelTable models={models} total={kpis.cost} t={t} exampleHover={t.smartHighlight}\n onRowClick={(m) => setSelModel({ ...m, share: m.cost / Math.max(kpis.cost, 0.0001) })} />\n <MetadataSection byMetadata={byMetadata} t={t} />\n <ForecastSection t={t} />\n <LiveActivity t={t} />\n </main>\n )}\n <SlideOver model={selModel} onClose={() => setSelModel(null)} t={t} />\n <CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} onAction={handleAction} />\n <TweaksPanel title=\"Tweaks \\xb7 UX\">\n <TweakSection label=\"Hierarquia & densidade\" />\n <TweakRadio label=\"Densidade\" value={t.density} options={[{ value: 'compact', label: 'Compacto' }, { value: 'comfy', label: 'Confort.' }]} onChange={(v) => setTweak('density', v)} />\n <TweakToggle label=\"Sparklines nos KPIs\" value={t.kpiSparklines} onChange={(v) => setTweak('kpiSparklines', v)} />\n <TweakToggle label=\"Smart highlight\" value={t.smartHighlight} onChange={(v) => setTweak('smartHighlight', v)} />\n <TweakSection label=\"An\\xe1lise\" />\n <TweakToggle label=\"Comparar c/ per\\xedodo anterior\" value={t.compareMode} onChange={(v) => setTweak('compareMode', v)} />\n <TweakToggle label=\"Sorting + a\\xe7\\xf5es na tabela\" value={t.tableSort} onChange={(v) => setTweak('tableSort', v)} />\n <TweakSection label=\"Custo proativo\" />\n <TweakToggle label=\"Alertas de budget\" value={t.budgetAlerts} onChange={(v) => setTweak('budgetAlerts', v)} />\n <TweakSlider label=\"Cen\\xe1rio: crescimento\" value={t.forecastScenario} min={0} max={300} step={5} unit=\"%\" onChange={(v) => setTweak('forecastScenario', v)} />\n <TweakSection label=\"Tempo real\" />\n <TweakToggle label=\"Live feed\" value={t.liveFeed} onChange={(v) => setTweak('liveFeed', v)} />\n <TweakRadio label=\"Anima\\xe7\\xe3o\" value={t.animLevel} options={[{ value: 'lively', label: 'Vivo' }, { value: 'subtle', label: 'Sutil' }, { value: 'minimal', label: 'M\\xedn.' }]} onChange={(v) => setTweak('animLevel', v)} />\n <TweakSection label=\"Navega\\xe7\\xe3o & estado\" />\n <TweakToggle label=\"Command palette (⌘K)\" value={t.commandPalette} onChange={(v) => setTweak('commandPalette', v)} />\n <TweakRadio label=\"Estado\" value={t.appState} options={[{ value: 'data', label: 'Dados' }, { value: 'loading', label: 'Load' }, { value: 'empty', label: 'Vazio' }]} onChange={(v) => setTweak('appState', v)} />\n <TweakSection label=\"Visual\" />\n <TweakColor label=\"Accent\" value={t.accent} options={['#58a6ff', '#3fb950', '#bc8cff', '#f778ba']} onChange={(v) => setTweak('accent', v)} />\n </TweaksPanel>\n </div>\n );\n}\nReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));\n</script>\n</body>\n</html>`;\n}\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["rowToEntry","existsSync","join","homedir","homedir","join","bundledPrices","join","homedir","existsSync","PostgresStorage","MySQLStorage","MongoStorage"]}
|