@oh-my-pi/omp-stats 13.5.8 → 13.6.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/omp-stats",
4
- "version": "13.5.8",
4
+ "version": "13.6.1",
5
5
  "description": "Local observability dashboard for pi AI usage statistics",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -33,12 +33,12 @@
33
33
  "build": "bun run build.ts"
34
34
  },
35
35
  "dependencies": {
36
- "@oh-my-pi/pi-ai": "13.5.8",
37
- "@oh-my-pi/pi-utils": "13.5.8",
38
- "@tailwindcss/node": "4",
36
+ "@oh-my-pi/pi-ai": "13.6.1",
37
+ "@oh-my-pi/pi-utils": "13.6.1",
38
+ "@tailwindcss/node": "^4.2",
39
39
  "chart.js": "^4.5",
40
40
  "date-fns": "^4.1",
41
- "lucide-react": "^0.575",
41
+ "lucide-react": "^0.576",
42
42
  "react": "^19.2",
43
43
  "react-chartjs-2": "^5.3",
44
44
  "react-dom": "^19.2"
@@ -48,7 +48,7 @@
48
48
  "@types/react": "^19.2",
49
49
  "@types/react-dom": "^19.2",
50
50
  "postcss": "^8.5",
51
- "tailwindcss": "4"
51
+ "tailwindcss": "^4.2"
52
52
  },
53
53
  "engines": {
54
54
  "bun": ">=1.3.7"
@@ -1,4 +1,4 @@
1
- import { Clock, Coins, FileJson, Gauge, Hash, X, Zap } from "lucide-react";
1
+ import { Clock, Coins, FileJson, Gauge, Hash, Star, X, Zap } from "lucide-react";
2
2
  import { useEffect, useState } from "react";
3
3
  import { getRequestDetails } from "../api";
4
4
  import type { RequestDetails } from "../types";
@@ -93,6 +93,15 @@ export function RequestDetail({ id, onClose }: RequestDetailProps) {
93
93
  </div>
94
94
  </div>
95
95
 
96
+ <div className="surface p-4">
97
+ <div className="flex items-center gap-2 text-[var(--text-muted)] mb-2">
98
+ <Star size={14} />
99
+ <span className="text-xs uppercase tracking-wide">Premium Reqs</span>
100
+ </div>
101
+ <div className="text-xl font-semibold text-[var(--text-primary)]">
102
+ {(details.usage.premiumRequests ?? 0).toLocaleString()}
103
+ </div>
104
+ </div>
96
105
  <div className="surface p-4">
97
106
  <div className="flex items-center gap-2 text-[var(--text-muted)] mb-2">
98
107
  <Hash size={14} />
@@ -1,4 +1,4 @@
1
- import { Activity, AlertCircle, BarChart3, Database, Server, Zap } from "lucide-react";
1
+ import { Activity, AlertCircle, BarChart3, Database, Server, Star, Zap } from "lucide-react";
2
2
  import type { AggregatedStats } from "../types";
3
3
 
4
4
  interface StatsGridProps {
@@ -24,6 +24,15 @@ const statConfig = [
24
24
  getDetail: (s: AggregatedStats) =>
25
25
  s.totalRequests > 0 ? `$${(s.totalCost / s.totalRequests).toFixed(4)} avg/req` : "-",
26
26
  },
27
+ {
28
+ key: "premiumRequests",
29
+ title: "Premium Reqs",
30
+ icon: Star,
31
+ color: "var(--accent-amber)",
32
+ getValue: (s: AggregatedStats) => s.totalPremiumRequests.toLocaleString(),
33
+ getDetail: (s: AggregatedStats) =>
34
+ s.totalRequests > 0 ? `${((s.totalPremiumRequests / s.totalRequests) * 100).toFixed(1)}% of requests` : "-",
35
+ },
27
36
  {
28
37
  key: "cache",
29
38
  title: "Cache Rate",
@@ -60,7 +69,7 @@ const statConfig = [
60
69
 
61
70
  export function StatsGrid({ stats }: StatsGridProps) {
62
71
  return (
63
- <div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4 mb-8">
72
+ <div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-7 gap-4 mb-8">
64
73
  {statConfig.map(stat => {
65
74
  const Icon = stat.icon;
66
75
  return (
@@ -9,6 +9,7 @@ export interface Usage {
9
9
  cacheRead: number;
10
10
  cacheWrite: number;
11
11
  totalTokens: number;
12
+ premiumRequests?: number;
12
13
  cost: {
13
14
  input: number;
14
15
  output: number;
@@ -50,6 +51,7 @@ export interface AggregatedStats {
50
51
  totalCacheWriteTokens: number;
51
52
  cacheRate: number;
52
53
  totalCost: number;
54
+ totalPremiumRequests: number;
53
55
  avgDuration: number | null;
54
56
  avgTtft: number | null;
55
57
  avgTokensPerSecond: number | null;
package/src/db.ts CHANGED
@@ -47,6 +47,7 @@ export async function initDb(): Promise<Database> {
47
47
  cache_read_tokens INTEGER NOT NULL,
48
48
  cache_write_tokens INTEGER NOT NULL,
49
49
  total_tokens INTEGER NOT NULL,
50
+ premium_requests REAL NOT NULL,
50
51
  cost_input REAL NOT NULL,
51
52
  cost_output REAL NOT NULL,
52
53
  cost_cache_read REAL NOT NULL,
@@ -67,6 +68,11 @@ export async function initDb(): Promise<Database> {
67
68
  );
68
69
  `);
69
70
 
71
+ const messageColumns = db.prepare("PRAGMA table_info(messages)").all() as { name: string }[];
72
+ if (!messageColumns.some(column => column.name === "premium_requests")) {
73
+ db.exec("ALTER TABLE messages ADD COLUMN premium_requests REAL NOT NULL DEFAULT 0");
74
+ }
75
+ db.exec("UPDATE messages SET premium_requests = 0 WHERE premium_requests IS NULL");
70
76
  return db;
71
77
  }
72
78
 
@@ -105,9 +111,9 @@ export function insertMessageStats(stats: MessageStats[]): number {
105
111
  INSERT OR IGNORE INTO messages (
106
112
  session_file, entry_id, folder, model, provider, api, timestamp,
107
113
  duration, ttft, stop_reason, error_message,
108
- input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, total_tokens,
114
+ input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, total_tokens, premium_requests,
109
115
  cost_input, cost_output, cost_cache_read, cost_cache_write, cost_total
110
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
116
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
111
117
  `);
112
118
 
113
119
  let inserted = 0;
@@ -130,6 +136,7 @@ export function insertMessageStats(stats: MessageStats[]): number {
130
136
  s.usage.cacheRead,
131
137
  s.usage.cacheWrite,
132
138
  s.usage.totalTokens,
139
+ s.usage.premiumRequests ?? 0,
133
140
  s.usage.cost.input,
134
141
  s.usage.cost.output,
135
142
  s.usage.cost.cacheRead,
@@ -160,6 +167,7 @@ function buildAggregatedStats(rows: any[]): AggregatedStats {
160
167
  totalCacheWriteTokens: 0,
161
168
  cacheRate: 0,
162
169
  totalCost: 0,
170
+ totalPremiumRequests: 0,
163
171
  avgDuration: null,
164
172
  avgTtft: null,
165
173
  avgTokensPerSecond: null,
@@ -174,6 +182,7 @@ function buildAggregatedStats(rows: any[]): AggregatedStats {
174
182
  const successfulRequests = totalRequests - failedRequests;
175
183
  const totalInputTokens = row.total_input_tokens || 0;
176
184
  const totalCacheReadTokens = row.total_cache_read_tokens || 0;
185
+ const totalPremiumRequests = row.total_premium_requests || 0;
177
186
 
178
187
  return {
179
188
  totalRequests,
@@ -189,6 +198,7 @@ function buildAggregatedStats(rows: any[]): AggregatedStats {
189
198
  ? totalCacheReadTokens / (totalInputTokens + totalCacheReadTokens)
190
199
  : 0,
191
200
  totalCost: row.total_cost || 0,
201
+ totalPremiumRequests,
192
202
  avgDuration: row.avg_duration,
193
203
  avgTtft: row.avg_ttft,
194
204
  avgTokensPerSecond: row.avg_tokens_per_second,
@@ -211,6 +221,7 @@ export function getOverallStats(): AggregatedStats {
211
221
  SUM(output_tokens) as total_output_tokens,
212
222
  SUM(cache_read_tokens) as total_cache_read_tokens,
213
223
  SUM(cache_write_tokens) as total_cache_write_tokens,
224
+ SUM(premium_requests) as total_premium_requests,
214
225
  SUM(cost_total) as total_cost,
215
226
  AVG(duration) as avg_duration,
216
227
  AVG(ttft) as avg_ttft,
@@ -240,6 +251,7 @@ export function getStatsByModel(): ModelStats[] {
240
251
  SUM(output_tokens) as total_output_tokens,
241
252
  SUM(cache_read_tokens) as total_cache_read_tokens,
242
253
  SUM(cache_write_tokens) as total_cache_write_tokens,
254
+ SUM(premium_requests) as total_premium_requests,
243
255
  SUM(cost_total) as total_cost,
244
256
  AVG(duration) as avg_duration,
245
257
  AVG(ttft) as avg_ttft,
@@ -274,6 +286,7 @@ export function getStatsByFolder(): FolderStats[] {
274
286
  SUM(output_tokens) as total_output_tokens,
275
287
  SUM(cache_read_tokens) as total_cache_read_tokens,
276
288
  SUM(cache_write_tokens) as total_cache_write_tokens,
289
+ SUM(premium_requests) as total_premium_requests,
277
290
  SUM(cost_total) as total_cost,
278
291
  AVG(duration) as avg_duration,
279
292
  AVG(ttft) as avg_ttft,
@@ -428,6 +441,7 @@ function rowToMessageStats(row: any): MessageStats {
428
441
  cacheRead: row.cache_read_tokens,
429
442
  cacheWrite: row.cache_write_tokens,
430
443
  totalTokens: row.total_tokens,
444
+ premiumRequests: row.premium_requests ?? 0,
431
445
  cost: {
432
446
  input: row.cost_input,
433
447
  output: row.cost_output,
package/src/index.ts CHANGED
@@ -29,6 +29,10 @@ function formatCost(n: number): string {
29
29
  return `$${n.toFixed(2)}`;
30
30
  }
31
31
 
32
+ function normalizePremiumRequests(n: number): number {
33
+ return Math.round((n + Number.EPSILON) * 100) / 100;
34
+ }
35
+
32
36
  /**
33
37
  * Print stats summary to console.
34
38
  */
@@ -44,6 +48,7 @@ async function printStats(): Promise<void> {
44
48
  console.log(` Total Tokens: ${formatNumber(overall.totalInputTokens + overall.totalOutputTokens)}`);
45
49
  console.log(` Cache Rate: ${formatPercent(overall.cacheRate)}`);
46
50
  console.log(` Total Cost: ${formatCost(overall.totalCost)}`);
51
+ console.log(` Premium Requests: ${formatNumber(normalizePremiumRequests(overall.totalPremiumRequests ?? 0))}`);
47
52
  console.log(` Avg Duration: ${overall.avgDuration !== null ? formatDuration(overall.avgDuration) : "-"}`);
48
53
  console.log(` Avg TTFT: ${overall.avgTtft !== null ? formatDuration(overall.avgTtft) : "-"}`);
49
54
  if (overall.avgTokensPerSecond !== null) {
package/src/parser.ts CHANGED
@@ -10,9 +10,11 @@ import type { MessageStats, SessionEntry, SessionMessageEntry } from "./types";
10
10
  * The folder part uses -- as path separator.
11
11
  */
12
12
  function extractFolderFromPath(sessionPath: string): string {
13
- const dir = path.basename(sessionPath.replace(/\/[^/]+\.jsonl$/, ""));
13
+ const sessionsDir = getSessionsDir();
14
+ const rel = path.relative(sessionsDir, sessionPath);
15
+ const projectDir = rel.split(path.sep)[0];
14
16
  // Convert --work--pi-- to /work/pi
15
- return dir.replace(/^--/, "/").replace(/--/g, "/");
17
+ return projectDir.replace(/^--/, "/").replace(/--/g, "/");
16
18
  }
17
19
 
18
20
  /**
@@ -99,8 +101,8 @@ export async function listSessionFolders(): Promise<string[]> {
99
101
  */
100
102
  export async function listSessionFiles(folderPath: string): Promise<string[]> {
101
103
  try {
102
- const entries = await fs.readdir(folderPath, { withFileTypes: true });
103
- return entries.filter(e => e.isFile() && e.name.endsWith(".jsonl")).map(e => path.join(folderPath, e.name));
104
+ const entries = await fs.readdir(folderPath, { recursive: true, withFileTypes: true });
105
+ return entries.filter(e => e.isFile() && e.name.endsWith(".jsonl")).map(e => path.join(e.parentPath, e.name));
104
106
  } catch {
105
107
  return [];
106
108
  }
package/src/types.ts CHANGED
@@ -64,6 +64,8 @@ export interface AggregatedStats {
64
64
  cacheRate: number;
65
65
  /** Total cost */
66
66
  totalCost: number;
67
+ /** Total premium requests */
68
+ totalPremiumRequests: number;
67
69
  /** Average duration in ms */
68
70
  avgDuration: number | null;
69
71
  /** Average TTFT in ms */