tokenlens-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,195 @@
1
+ import express from "express";
2
+ import { analyzeUsage } from "../analyzer/index.js";
3
+ export class DashboardServer {
4
+ app;
5
+ store;
6
+ server;
7
+ constructor(store) {
8
+ this.store = store;
9
+ this.app = express();
10
+ this.setupRoutes();
11
+ }
12
+ setupRoutes() {
13
+ this.app.get("/", (_req, res) => {
14
+ res.send(this.renderHtml());
15
+ });
16
+ this.app.get("/api/stats", (req, res) => {
17
+ const hours = parseInt(req.query.hours, 10) || 24;
18
+ res.json(this.store.getStats(hours));
19
+ });
20
+ this.app.get("/api/summary", (_req, res) => {
21
+ res.json(this.store.getSummary());
22
+ });
23
+ this.app.get("/api/recent", (req, res) => {
24
+ const limit = parseInt(req.query.limit, 10) || 50;
25
+ res.json(this.store.getRecentCalls(limit));
26
+ });
27
+ this.app.get("/api/analysis", (req, res) => {
28
+ const hours = parseInt(req.query.hours, 10) || 168;
29
+ res.json(analyzeUsage(this.store, hours));
30
+ });
31
+ }
32
+ start(port) {
33
+ this.server = this.app.listen(port, () => {
34
+ console.log(`[dashboard] Web UI at http://localhost:${port}`);
35
+ });
36
+ }
37
+ stop() {
38
+ this.server?.close();
39
+ }
40
+ renderHtml() {
41
+ return `<!DOCTYPE html>
42
+ <html lang="en">
43
+ <head>
44
+ <meta charset="UTF-8">
45
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
46
+ <title>TokenLens</title>
47
+ <style>
48
+ * { margin: 0; padding: 0; box-sizing: border-box; }
49
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; }
50
+ .container { max-width: 1200px; margin: 0 auto; }
51
+ h1 { font-size: 1.8rem; margin-bottom: 8px; color: #58a6ff; }
52
+ .subtitle { color: #8b949e; margin-bottom: 24px; }
53
+ .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 32px; }
54
+ .stat-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; }
55
+ .stat-card .value { font-size: 1.8rem; font-weight: 700; color: #f0f6fc; }
56
+ .stat-card .label { font-size: 0.85rem; color: #8b949e; margin-top: 4px; }
57
+ .stat-card .sub { font-size: 0.8rem; color: #58a6ff; margin-top: 2px; }
58
+ .section { margin-bottom: 32px; }
59
+ .section h2 { font-size: 1.2rem; margin-bottom: 12px; color: #f0f6fc; }
60
+ table { width: 100%; border-collapse: collapse; }
61
+ th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #21262d; font-size: 0.9rem; }
62
+ th { color: #8b949e; font-weight: 600; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; }
63
+ td { color: #c9d1d9; }
64
+ td.tool { color: #58a6ff; }
65
+ td.preview { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #8b949e; font-size: 0.85rem; }
66
+ tr:hover td { background: #1c2128; }
67
+ .breakdowns { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
68
+ .bar { height: 6px; background: #21262d; border-radius: 3px; margin-top: 6px; overflow: hidden; }
69
+ .bar-fill { height: 100%; background: #58a6ff; border-radius: 3px; transition: width 0.3s; }
70
+ .controls { margin-bottom: 16px; display: flex; gap: 8px; align-items: center; }
71
+ .controls select, .controls button { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 6px; padding: 6px 12px; font-size: 0.85rem; cursor: pointer; }
72
+ .controls button:hover { background: #30363d; }
73
+ .error { color: #f85149; padding: 20px; text-align: center; }
74
+ .loading { color: #8b949e; text-align: center; padding: 20px; }
75
+ @media (max-width: 768px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } .breakdowns { grid-template-columns: 1fr; } }
76
+ </style>
77
+ </head>
78
+ <body>
79
+ <div class="container">
80
+ <h1>TokenLens</h1>
81
+ <p class="subtitle">AI token usage monitor &mdash; tracking your AI API consumption</p>
82
+
83
+ <div class="controls">
84
+ <select id="hoursSelect">
85
+ <option value="1">Last hour</option>
86
+ <option value="6">Last 6 hours</option>
87
+ <option value="24" selected>Last 24 hours</option>
88
+ <option value="168">Last 7 days</option>
89
+ <option value="720">Last 30 days</option>
90
+ </select>
91
+ <button onclick="refresh()">Refresh</button>
92
+ </div>
93
+
94
+ <div id="stats" class="loading">Loading...</div>
95
+ </div>
96
+
97
+ <script>
98
+ async function refresh() {
99
+ const hours = document.getElementById('hoursSelect').value;
100
+ const el = document.getElementById('stats');
101
+ el.innerHTML = '<div class="loading">Loading...</div>';
102
+
103
+ try {
104
+ const [stats, summary] = await Promise.all([
105
+ fetch('/api/stats?hours=' + hours).then(r => r.json()),
106
+ fetch('/api/summary').then(r => r.json())
107
+ ]);
108
+ render(stats, summary, hours);
109
+ } catch (e) {
110
+ el.innerHTML = '<div class="error">Failed to load data</div>';
111
+ }
112
+ }
113
+
114
+ function render(stats, summary, hours) {
115
+ const costFmt = (c) => '$' + (c < 0.01 ? c.toFixed(5) : c.toFixed(4));
116
+ const tokenFmt = (n) => n >= 1000000 ? (n/1000000).toFixed(1)+'M' : n >= 1000 ? (n/1000).toFixed(1)+'K' : n;
117
+
118
+ // Update the period display based on hours
119
+ const unit = hours >= 168 ? 'days' : 'hours';
120
+
121
+ let html = '';
122
+
123
+ // Summary stats cards
124
+ html += '<div class="stats-grid">';
125
+ html += '<div class="stat-card"><div class="value">'+tokenFmt(summary.totalTokens)+'</div><div class="label">Total tokens (all time)</div></div>';
126
+ html += '<div class="stat-card"><div class="value">'+costFmt(summary.totalCost)+'</div><div class="label">Total cost (all time)</div></div>';
127
+ html += '<div class="stat-card"><div class="value">'+summary.totalCalls+'</div><div class="label">Total calls (all time)</div></div>';
128
+ html += '<div class="stat-card"><div class="value">'+(summary.totalCalls > 0 ? Math.round(summary.totalTokens / summary.totalCalls)+' <span style="font-size:0.9rem;color:#8b949e">tokens/call</span>' : '—')+'</div><div class="label">Average per call</div></div>';
129
+ html += '</div>';
130
+
131
+ // Period stats
132
+ html += '<div class="section"><h2>Last ' + hours + ' ' + unit + '</h2>';
133
+ html += '<div class="stats-grid">';
134
+ html += '<div class="stat-card"><div class="value">'+tokenFmt(stats.totalTokens)+'</div><div class="label">Tokens used</div></div>';
135
+ html += '<div class="stat-card"><div class="value">'+costFmt(stats.totalCost)+'</div><div class="label">Estimated cost</div></div>';
136
+ html += '<div class="stat-card"><div class="value">'+stats.totalCalls+'</div><div class="label">API calls</div></div>';
137
+ html += '<div class="stat-card"><div class="value">'+tokenFmt(stats.totalTokens / (parseInt(hours) || 24) * 24)+'</div><div class="label">Projected daily tokens</div></div>';
138
+ html += '</div></div>';
139
+
140
+ // By tool breakdown
141
+ if (stats.byTool.length) {
142
+ const maxTool = Math.max(...stats.byTool.map(t => t.tokens));
143
+ html += '<div class="section"><h2>By Tool</h2><table><tr><th>Tool</th><th>Tokens</th><th>Cost</th><th>Calls</th><th></th></tr>';
144
+ stats.byTool.forEach(t => {
145
+ html += '<tr><td class="tool">'+t.tool+'</td><td>'+tokenFmt(t.tokens)+'</td><td>'+costFmt(t.cost)+'</td><td>'+t.calls+'</td><td><div class="bar"><div class="bar-fill" style="width:'+(t.tokens/maxTool*100)+'%"></div></div></td></tr>';
146
+ });
147
+ html += '</table></div>';
148
+ }
149
+
150
+ // By model breakdown
151
+ if (stats.byModel.length) {
152
+ const maxModel = Math.max(...stats.byModel.map(m => m.tokens));
153
+ html += '<div class="section"><h2>By Model</h2><table><tr><th>Model</th><th>Tokens</th><th>Cost</th><th>Calls</th><th></th></tr>';
154
+ stats.byModel.forEach(m => {
155
+ html += '<tr><td class="tool">'+m.model+'</td><td>'+tokenFmt(m.tokens)+'</td><td>'+costFmt(m.cost)+'</td><td>'+m.calls+'</td><td><div class="bar"><div class="bar-fill" style="width:'+(m.tokens/maxModel*100)+'%"></div></div></td></tr>';
156
+ });
157
+ html += '</table></div>';
158
+ }
159
+
160
+ // By day breakdown
161
+ if (stats.byDay.length) {
162
+ const maxDay = Math.max(...stats.byDay.map(d => d.tokens));
163
+ html += '<div class="section"><h2>By Day</h2><table><tr><th>Date</th><th>Tokens</th><th>Cost</th><th>Calls</th><th></th></tr>';
164
+ stats.byDay.forEach(d => {
165
+ html += '<tr><td>'+d.day+'</td><td>'+tokenFmt(d.tokens)+'</td><td>'+costFmt(d.cost)+'</td><td>'+d.calls+'</td><td><div class="bar"><div class="bar-fill" style="width:'+(d.tokens/maxDay*100)+'%"></div></div></td></tr>';
166
+ });
167
+ html += '</table></div>';
168
+ }
169
+
170
+ // Top calls
171
+ if (stats.topCalls.length) {
172
+ html += '<div class="section"><h2>Largest Calls</h2><table><tr><th>Tool</th><th>Model</th><th>Tokens</th><th>Cost</th><th>Prompt</th><th>Response</th></tr>';
173
+ stats.topCalls.forEach(c => {
174
+ html += '<tr><td class="tool">'+c.tool_name+'</td><td>'+c.model+'</td><td>'+tokenFmt(c.total_tokens)+'</td><td>'+costFmt(c.cost)+'</td><td class="preview">'+esc(c.prompt_preview)+'</td><td class="preview">'+esc(c.completion_preview)+'</td></tr>';
175
+ });
176
+ html += '</table></div>';
177
+ }
178
+
179
+ if (!stats.totalCalls) {
180
+ html += '<div class="section"><div class="error">No API calls recorded in this period. Make sure your apps are configured to use the proxy.</div></div>';
181
+ }
182
+
183
+ el.innerHTML = html;
184
+ }
185
+
186
+ function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
187
+
188
+ refresh();
189
+ setInterval(refresh, 10000);
190
+ </script>
191
+ </body>
192
+ </html>`;
193
+ }
194
+ }
195
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,MAAM,OAAO,eAAe;IAClB,GAAG,CAAsB;IACzB,KAAK,CAAa;IAClB,MAAM,CAAsC;IAEpD,YAAY,KAAiB;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,GAAG,GAAG,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,WAAW,EAAE,CAAC;IACrB,CAAC;IAEO,WAAW;QACjB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YAC9B,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,KAAe,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;YAC5D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YACzC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,KAAe,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;YAC5D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,KAAe,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;YAC7D,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAY;QAChB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YACvC,OAAO,CAAC,GAAG,CAAC,0CAA0C,IAAI,EAAE,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;IACvB,CAAC;IAEO,UAAU;QAChB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAuJH,CAAC;IACP,CAAC;CACF"}
@@ -0,0 +1,34 @@
1
+ import Database from "better-sqlite3";
2
+ export interface ApiCallRow {
3
+ id: number;
4
+ tool_name: string;
5
+ source: string;
6
+ provider: string;
7
+ model: string;
8
+ endpoint: string;
9
+ prompt_tokens: number;
10
+ cached_tokens: number;
11
+ completion_tokens: number;
12
+ reasoning_tokens: number;
13
+ total_tokens: number;
14
+ cost: number;
15
+ prompt_preview: string;
16
+ completion_preview: string;
17
+ session_id: string | null;
18
+ prompt_hash: string | null;
19
+ prompt_family_hash: string | null;
20
+ created_at: string;
21
+ }
22
+ export interface SessionRow {
23
+ id: string;
24
+ tool_name: string;
25
+ first_call_at: string;
26
+ last_call_at: string;
27
+ total_tokens: number;
28
+ total_cost: number;
29
+ call_count: number;
30
+ }
31
+ export declare function estimateCost(model: string, promptTokens: number, completionTokens: number): number;
32
+ export declare function getModelName(raw: string): string;
33
+ export declare function initDb(dbPath: string): Database.Database;
34
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AA+BD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAKR;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CA8ExD"}
@@ -0,0 +1,118 @@
1
+ import Database from "better-sqlite3";
2
+ const MODEL_PRICING = {
3
+ // Anthropic Claude
4
+ "claude-3-5-sonnet-20241022": { input: 0.003, output: 0.015 },
5
+ "claude-3-5-haiku-20241022": { input: 0.0008, output: 0.004 },
6
+ "claude-3-opus-20240229": { input: 0.015, output: 0.075 },
7
+ "claude-3-sonnet-20240229": { input: 0.003, output: 0.015 },
8
+ "claude-3-haiku-20240307": { input: 0.00025, output: 0.00125 },
9
+ "claude-sonnet-4-20250514": { input: 0.003, output: 0.015 },
10
+ "claude-sonnet-4": { input: 0.003, output: 0.015 },
11
+ "claude-opus-4-20250514": { input: 0.015, output: 0.075 },
12
+ "claude-opus-4": { input: 0.015, output: 0.075 },
13
+ // OpenAI
14
+ "gpt-4o": { input: 0.0025, output: 0.01 },
15
+ "gpt-4o-mini": { input: 0.00015, output: 0.0006 },
16
+ "gpt-4-turbo": { input: 0.01, output: 0.03 },
17
+ "gpt-4": { input: 0.03, output: 0.06 },
18
+ "gpt-3.5-turbo": { input: 0.0005, output: 0.0015 },
19
+ "o1": { input: 0.015, output: 0.06 },
20
+ "o1-mini": { input: 0.0011, output: 0.0044 },
21
+ "o3-mini": { input: 0.0011, output: 0.0044 },
22
+ // Google
23
+ "gemini-2.0-flash": { input: 0.0001, output: 0.0004 },
24
+ "gemini-2.0-pro": { input: 0.002, output: 0.005 },
25
+ "gemini-1.5-pro": { input: 0.00125, output: 0.005 },
26
+ "gemini-1.5-flash": { input: 0.000075, output: 0.0003 },
27
+ // Default fallback
28
+ default: { input: 0.003, output: 0.015 },
29
+ };
30
+ export function estimateCost(model, promptTokens, completionTokens) {
31
+ const pricing = MODEL_PRICING[model] ?? MODEL_PRICING["default"];
32
+ const inputCost = (promptTokens / 1000) * pricing.input;
33
+ const outputCost = (completionTokens / 1000) * pricing.output;
34
+ return inputCost + outputCost;
35
+ }
36
+ export function getModelName(raw) {
37
+ // Extract model name from various API formats
38
+ for (const key of Object.keys(MODEL_PRICING)) {
39
+ if (raw.includes(key) || key.includes(raw))
40
+ return key;
41
+ }
42
+ return raw || "unknown";
43
+ }
44
+ export function initDb(dbPath) {
45
+ const db = new Database(dbPath);
46
+ db.pragma("journal_mode = WAL");
47
+ db.pragma("foreign_keys = ON");
48
+ db.exec(`
49
+ CREATE TABLE IF NOT EXISTS api_calls (
50
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
51
+ tool_name TEXT NOT NULL DEFAULT 'unknown',
52
+ source TEXT NOT NULL DEFAULT 'proxy',
53
+ provider TEXT NOT NULL DEFAULT 'unknown',
54
+ model TEXT NOT NULL DEFAULT 'unknown',
55
+ endpoint TEXT NOT NULL DEFAULT '',
56
+ prompt_tokens INTEGER NOT NULL DEFAULT 0,
57
+ cached_tokens INTEGER NOT NULL DEFAULT 0,
58
+ completion_tokens INTEGER NOT NULL DEFAULT 0,
59
+ reasoning_tokens INTEGER NOT NULL DEFAULT 0,
60
+ total_tokens INTEGER NOT NULL DEFAULT 0,
61
+ cost REAL NOT NULL DEFAULT 0,
62
+ prompt_preview TEXT NOT NULL DEFAULT '',
63
+ completion_preview TEXT NOT NULL DEFAULT '',
64
+ session_id TEXT,
65
+ external_id TEXT,
66
+ prompt_hash TEXT,
67
+ prompt_family_hash TEXT,
68
+ request_body TEXT,
69
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
70
+ );
71
+
72
+ CREATE TABLE IF NOT EXISTS sessions (
73
+ id TEXT PRIMARY KEY,
74
+ tool_name TEXT NOT NULL DEFAULT 'unknown',
75
+ first_call_at TEXT NOT NULL DEFAULT (datetime('now')),
76
+ last_call_at TEXT NOT NULL DEFAULT (datetime('now')),
77
+ total_tokens INTEGER NOT NULL DEFAULT 0,
78
+ total_cost REAL NOT NULL DEFAULT 0,
79
+ call_count INTEGER NOT NULL DEFAULT 0
80
+ );
81
+
82
+ CREATE INDEX IF NOT EXISTS idx_api_calls_created_at ON api_calls(created_at);
83
+ CREATE INDEX IF NOT EXISTS idx_api_calls_tool ON api_calls(tool_name);
84
+ CREATE INDEX IF NOT EXISTS idx_api_calls_model ON api_calls(model);
85
+ CREATE INDEX IF NOT EXISTS idx_api_calls_session ON api_calls(session_id);
86
+ CREATE INDEX IF NOT EXISTS idx_sessions_last_call ON sessions(last_call_at);
87
+
88
+ CREATE TABLE IF NOT EXISTS imported_files (
89
+ path TEXT PRIMARY KEY,
90
+ size INTEGER NOT NULL,
91
+ modified_at INTEGER NOT NULL,
92
+ imported_at TEXT NOT NULL DEFAULT (datetime('now'))
93
+ );
94
+ `);
95
+ const columns = new Set(db.prepare("PRAGMA table_info(api_calls)").all().map((row) => row.name));
96
+ const migrations = [
97
+ ["source", "TEXT NOT NULL DEFAULT 'proxy'"],
98
+ ["provider", "TEXT NOT NULL DEFAULT 'unknown'"],
99
+ ["cached_tokens", "INTEGER NOT NULL DEFAULT 0"],
100
+ ["reasoning_tokens", "INTEGER NOT NULL DEFAULT 0"],
101
+ ["external_id", "TEXT"],
102
+ ["prompt_hash", "TEXT"],
103
+ ["prompt_family_hash", "TEXT"],
104
+ ];
105
+ for (const [name, definition] of migrations) {
106
+ if (!columns.has(name))
107
+ db.exec(`ALTER TABLE api_calls ADD COLUMN ${name} ${definition}`);
108
+ }
109
+ db.exec(`
110
+ CREATE INDEX IF NOT EXISTS idx_api_calls_provider ON api_calls(provider);
111
+ CREATE INDEX IF NOT EXISTS idx_api_calls_prompt_hash ON api_calls(prompt_hash);
112
+ CREATE INDEX IF NOT EXISTS idx_api_calls_prompt_family_hash ON api_calls(prompt_family_hash);
113
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_api_calls_external_id
114
+ ON api_calls(external_id) WHERE external_id IS NOT NULL
115
+ `);
116
+ return db;
117
+ }
118
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { ApiCallRow } from "./schema.js";
2
+ export interface InsertCallParams {
3
+ toolName: string;
4
+ source?: string;
5
+ provider?: string;
6
+ model: string;
7
+ endpoint: string;
8
+ promptTokens: number;
9
+ cachedTokens?: number;
10
+ completionTokens: number;
11
+ reasoningTokens?: number;
12
+ promptPreview: string;
13
+ completionPreview: string;
14
+ sessionId: string | null;
15
+ externalId?: string;
16
+ promptHash?: string | null;
17
+ promptFamilyHash?: string | null;
18
+ createdAt?: string;
19
+ requestBody?: string;
20
+ }
21
+ export interface UsageStats {
22
+ totalTokens: number;
23
+ totalCost: number;
24
+ totalCalls: number;
25
+ byTool: {
26
+ tool: string;
27
+ tokens: number;
28
+ cost: number;
29
+ calls: number;
30
+ }[];
31
+ byModel: {
32
+ model: string;
33
+ tokens: number;
34
+ cost: number;
35
+ calls: number;
36
+ }[];
37
+ byDay: {
38
+ day: string;
39
+ tokens: number;
40
+ cost: number;
41
+ calls: number;
42
+ }[];
43
+ topCalls: ApiCallRow[];
44
+ }
45
+ export declare class TokenStore {
46
+ private db;
47
+ constructor(dbPath: string);
48
+ insertCall(params: InsertCallParams): number;
49
+ isFileImported(path: string, size: number, modifiedAt: number): boolean;
50
+ markFileImported(path: string, size: number, modifiedAt: number): void;
51
+ getCalls(hours?: number): ApiCallRow[];
52
+ getStats(hours?: number): UsageStats;
53
+ getRecentCalls(limit?: number): ApiCallRow[];
54
+ getSummary(): {
55
+ totalTokens: number;
56
+ totalCost: number;
57
+ totalCalls: number;
58
+ };
59
+ close(): void;
60
+ private toSqliteTimestamp;
61
+ }
62
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/db/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAG1D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxE,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1E,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtE,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,EAAE,MAAM;IAI1B,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM;IAmD5C,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAOvE,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAWtE,QAAQ,CAAC,KAAK,GAAE,MAAW,GAAG,UAAU,EAAE;IAO1C,QAAQ,CAAC,KAAK,GAAE,MAAW,GAAG,UAAU;IAkDxC,cAAc,CAAC,KAAK,GAAE,MAAW,GAAG,UAAU,EAAE;IAMhD,UAAU,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAU5E,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,iBAAiB;CAK1B"}
@@ -0,0 +1,110 @@
1
+ import { estimateCost, getModelName, initDb } from "./schema.js";
2
+ export class TokenStore {
3
+ db;
4
+ constructor(dbPath) {
5
+ this.db = initDb(dbPath);
6
+ }
7
+ insertCall(params) {
8
+ const totalTokens = params.promptTokens + params.completionTokens;
9
+ const cost = estimateCost(params.model, params.promptTokens, params.completionTokens);
10
+ const stmt = this.db.prepare(`
11
+ INSERT OR IGNORE INTO api_calls (
12
+ tool_name, source, provider, model, endpoint, prompt_tokens, cached_tokens,
13
+ completion_tokens, reasoning_tokens, total_tokens, cost, prompt_preview,
14
+ completion_preview, session_id, external_id, prompt_hash, prompt_family_hash, request_body, created_at
15
+ )
16
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, COALESCE(?, datetime('now')))
17
+ `);
18
+ const result = stmt.run(params.toolName, params.source ?? "proxy", params.provider ?? "unknown", getModelName(params.model), params.endpoint, params.promptTokens, params.cachedTokens ?? 0, params.completionTokens, params.reasoningTokens ?? 0, totalTokens, cost, params.promptPreview.slice(0, 500), params.completionPreview.slice(0, 500), params.sessionId, params.externalId ?? null, params.promptHash ?? null, params.promptFamilyHash ?? null, params.requestBody?.slice(0, 2000) ?? null, params.createdAt ? this.toSqliteTimestamp(params.createdAt) : null);
19
+ // Update or create session
20
+ if (params.sessionId && result.changes > 0) {
21
+ this.db.prepare(`
22
+ INSERT INTO sessions (id, tool_name, first_call_at, last_call_at, total_tokens, total_cost, call_count)
23
+ VALUES (?, ?, datetime('now'), datetime('now'), ?, ?, 1)
24
+ ON CONFLICT(id) DO UPDATE SET
25
+ last_call_at = datetime('now'),
26
+ total_tokens = total_tokens + ?,
27
+ total_cost = total_cost + ?,
28
+ call_count = call_count + 1
29
+ `).run(params.sessionId, params.toolName, totalTokens, cost, totalTokens, cost);
30
+ }
31
+ return result.changes > 0 ? Number(result.lastInsertRowid) : 0;
32
+ }
33
+ isFileImported(path, size, modifiedAt) {
34
+ const row = this.db
35
+ .prepare("SELECT 1 FROM imported_files WHERE path = ? AND size = ? AND modified_at = ?")
36
+ .get(path, size, modifiedAt);
37
+ return Boolean(row);
38
+ }
39
+ markFileImported(path, size, modifiedAt) {
40
+ this.db.prepare(`
41
+ INSERT INTO imported_files (path, size, modified_at, imported_at)
42
+ VALUES (?, ?, ?, datetime('now'))
43
+ ON CONFLICT(path) DO UPDATE SET
44
+ size = excluded.size,
45
+ modified_at = excluded.modified_at,
46
+ imported_at = datetime('now')
47
+ `).run(path, size, modifiedAt);
48
+ }
49
+ getCalls(hours = 24) {
50
+ const since = this.toSqliteTimestamp(new Date(Date.now() - hours * 3600_000).toISOString());
51
+ return this.db
52
+ .prepare("SELECT * FROM api_calls WHERE created_at >= ? ORDER BY created_at DESC")
53
+ .all(since);
54
+ }
55
+ getStats(hours = 24) {
56
+ const since = this.toSqliteTimestamp(new Date(Date.now() - hours * 3600_000).toISOString());
57
+ const totals = this.db
58
+ .prepare(`SELECT COALESCE(SUM(total_tokens),0) as totalTokens,
59
+ COALESCE(SUM(cost),0) as totalCost,
60
+ COUNT(*) as totalCalls
61
+ FROM api_calls WHERE created_at >= ?`)
62
+ .get(since);
63
+ const byTool = this.db
64
+ .prepare(`SELECT tool_name as tool, SUM(total_tokens) as tokens, SUM(cost) as cost, COUNT(*) as calls
65
+ FROM api_calls WHERE created_at >= ? GROUP BY tool_name ORDER BY tokens DESC`)
66
+ .all(since);
67
+ const byModel = this.db
68
+ .prepare(`SELECT model, SUM(total_tokens) as tokens, SUM(cost) as cost, COUNT(*) as calls
69
+ FROM api_calls WHERE created_at >= ? GROUP BY model ORDER BY tokens DESC`)
70
+ .all(since);
71
+ const byDay = this.db
72
+ .prepare(`SELECT date(created_at) as day, SUM(total_tokens) as tokens, SUM(cost) as cost, COUNT(*) as calls
73
+ FROM api_calls WHERE created_at >= ? GROUP BY day ORDER BY day`)
74
+ .all(since);
75
+ const topCalls = this.db
76
+ .prepare(`SELECT * FROM api_calls WHERE created_at >= ? ORDER BY total_tokens DESC LIMIT 10`)
77
+ .all(since);
78
+ return {
79
+ totalTokens: totals.totalTokens,
80
+ totalCost: totals.totalCost,
81
+ totalCalls: totals.totalCalls,
82
+ byTool,
83
+ byModel,
84
+ byDay,
85
+ topCalls,
86
+ };
87
+ }
88
+ getRecentCalls(limit = 50) {
89
+ return this.db
90
+ .prepare("SELECT * FROM api_calls ORDER BY created_at DESC LIMIT ?")
91
+ .all(limit);
92
+ }
93
+ getSummary() {
94
+ return this.db
95
+ .prepare(`SELECT COALESCE(SUM(total_tokens),0) as totalTokens,
96
+ COALESCE(SUM(cost),0) as totalCost,
97
+ COUNT(*) as totalCalls FROM api_calls`)
98
+ .get();
99
+ }
100
+ close() {
101
+ this.db.close();
102
+ }
103
+ toSqliteTimestamp(value) {
104
+ const parsed = new Date(value);
105
+ if (Number.isNaN(parsed.getTime()))
106
+ return value.replace("T", " ").replace(/Z$/, "");
107
+ return parsed.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "");
108
+ }
109
+ }
110
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/db/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { TokenStore } from "../db/store.js";
2
+ export interface ImportResult {
3
+ filesFound: number;
4
+ filesRead: number;
5
+ callsImported: number;
6
+ callsSkipped: number;
7
+ errors: string[];
8
+ sources: Record<string, number>;
9
+ }
10
+ export declare function importLogs(store: TokenStore, paths?: string[], force?: boolean, configuredDefaults?: string[]): Promise<ImportResult>;
11
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/importers/logs.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAmND,wBAAsB,UAAU,CAC9B,KAAK,EAAE,UAAU,EACjB,KAAK,GAAE,MAAM,EAAO,EACpB,KAAK,UAAQ,EACb,kBAAkB,GAAE,MAAM,EAAyB,GAClD,OAAO,CAAC,YAAY,CAAC,CA4BvB"}