codexmeter 1.0.6 → 1.0.8

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 @@
1
+ :root{--bg-base:#06080f;--bg-surface:#0d1117;--bg-card:#161b22;--bg-card-hover:#1c2333;--bg-elevated:#21262d;--border:#30363d;--border-accent:#6366f140;--text-primary:#e6edf3;--text-secondary:#8b949e;--text-muted:#484f58;--accent:#6366f1;--accent-dim:#6366f11f;--green:#3fb950;--yellow:#d29922;--red:#f85149;--orange:#f97316;--cyan:#39d2f5;--pink:#f472b6;--font-sans:"Inter", -apple-system, BlinkMacSystemFont, sans-serif;--font-mono:"JetBrains Mono", "Fira Code", monospace;--radius:8px;--radius-lg:12px}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font-sans);background:var(--bg-base);color:var(--text-primary);-webkit-font-smoothing:antialiased;line-height:1.6}.app{flex-direction:column;min-height:100vh;display:flex}.app-content{opacity:0;flex-direction:column;min-height:100vh;display:flex}.app-content-revealed{opacity:1;animation:.5s forwards contentReveal}@keyframes contentReveal{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.navbar{background:var(--bg-surface);border-bottom:1px solid var(--border);z-index:100;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);position:sticky;top:0}.navbar-inner{box-sizing:border-box;align-items:center;gap:1.5rem;width:100%;max-width:1400px;height:48px;margin:0 auto;padding:0 2rem;display:flex}.navbar-brand{letter-spacing:-.03em;background:linear-gradient(135deg,#818cf8,#6366f1);-webkit-text-fill-color:transparent;-webkit-background-clip:text;font-size:.95rem;font-weight:700}.navbar-tabs{gap:2px;display:flex}.navbar-tab{cursor:pointer;color:var(--text-secondary);background:0 0;border:none;border-radius:6px;padding:.35rem .85rem;font-size:.8rem;font-weight:500;transition:all .15s,opacity .35s}.navbar-tab:hover{color:var(--text-primary);background:var(--bg-card)}.navbar-tab.active{color:#fff;background:var(--accent)}.navbar-tab.navbar-tab-dimmed,.navbar-tab.navbar-tab-dimmed:hover{opacity:.4;color:var(--text-muted)}.navbar-meta{flex:1;align-items:center;gap:.75rem;min-width:0;margin-left:auto;display:flex}.navbar-ingest-wrap{flex:1;align-items:center;gap:.75rem;min-width:0;transition:opacity .3s;display:flex}.navbar-ingest-wrap.navbar-ingest-fade-out{opacity:0;pointer-events:none}.navbar-date-wrap{flex-shrink:0;align-items:center;gap:.75rem;margin-left:auto;transition:opacity .3s;display:flex}.navbar-date-wrap-dimmed{opacity:.5;pointer-events:none}.navbar-date{font-variant-numeric:tabular-nums;text-align:right;text-overflow:ellipsis;width:22ch;min-width:22ch;display:inline-block;overflow:hidden}.navbar-progress-wrap{background:var(--bg-elevated);border-radius:2px;flex:1;height:4px;overflow:hidden}.navbar-progress-bar{background:linear-gradient(90deg, var(--accent), #818cf8);border-radius:2px;height:100%;transition:width .4s}.navbar-status{color:var(--text-muted);font-size:.7rem;font-family:var(--font-mono)}.main-content{flex:1;width:100%;min-width:0;max-width:1400px;margin:0 auto;padding:1.5rem 2rem 1.25rem;container:main/inline-size}.app-footer{color:var(--text-muted);text-align:center;box-sizing:border-box;width:100%;max-width:1400px;margin:auto auto 0;padding:.5rem 2rem .75rem;font-size:.7rem}.section-header{justify-content:space-between;align-items:center;margin-bottom:1.25rem;display:flex}.section-title{letter-spacing:-.01em;font-size:1rem;font-weight:600}.stat-row{flex-wrap:wrap;align-items:stretch;gap:1rem;margin-bottom:1.5rem;display:flex}.stat-cards{flex:none;grid-template-columns:repeat(4,minmax(160px,1fr));gap:.75rem;min-width:0;display:grid}@media (width<=920px){.stat-cards{flex:auto;grid-template-columns:repeat(2,minmax(160px,1fr))}}@media (width<=500px){.stat-cards{grid-template-columns:1fr}}.stat-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:1rem 1.1rem;transition:border-color .2s}.stat-card:hover{border-color:var(--border-accent)}.stat-label{text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);margin-bottom:.35rem;font-size:.65rem;font-weight:600}.stat-value{font-size:1.6rem;font-weight:700;font-family:var(--font-mono);letter-spacing:-.03em;line-height:1.1}.stat-sub{color:var(--text-muted);margin-top:.2rem;font-size:.72rem}.stat-per-day{color:var(--text-muted);font-size:.72rem;font-family:var(--font-mono);margin-top:.25rem}.stat-per-day .stat-per-day-value{color:var(--text-secondary);font-weight:600}.range-toggle{background:var(--bg-card);border:1px solid var(--border);border-radius:6px;display:inline-flex;overflow:hidden}.range-btn{color:var(--text-muted);cursor:pointer;background:0 0;border:none;padding:.3rem .7rem;font-size:.72rem;font-weight:600;transition:all .15s}.range-btn:hover{color:var(--text-secondary)}.range-btn.active{color:#fff;background:var(--accent)}.range-btn:disabled{cursor:not-allowed}.export-video-btn{min-width:110px}.export-video-btn:disabled{cursor:not-allowed}.range-btn-unsupported{color:#ffffffeb;background:var(--accent);border-left:1px solid #f0b90b38;position:relative}.range-btn-unsupported:before{content:"";pointer-events:none;background:repeating-linear-gradient(-45deg,#f0b90b0d 0 6px,#ffffff03 6px 12px);position:absolute;inset:0}.range-btn-unsupported:disabled{opacity:.78}.heatmap-wrap{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);margin-bottom:1.5rem;padding:1.25rem;overflow-x:auto}.heatmap-grid{grid-template-rows:repeat(7,1fr);grid-auto-flow:column;gap:3px;display:grid}.heatmap-cell{background:var(--bg-elevated);opacity:.4;border-radius:2px;width:13px;height:13px;transition:background .18s,opacity .22s,transform .22s;transform:scale(.82)}.heatmap-cell.active{opacity:1;transform:scale(1)}.heatmap-cell:hover{outline:1px solid var(--text-muted)}.heatmap-cell.heatmap-cell-pop{animation:.38s cubic-bezier(.34,1.56,.64,1) heatmap-cell-pop}@keyframes heatmap-cell-pop{0%{box-shadow:none;transform:scale(1)}40%{transform:scale(1.4);box-shadow:0 0 10px #6366f1b3,0 0 0 1.5px #0003}70%{transform:scale(1.1);box-shadow:0 0 4px #6366f159,0 0 0 .5px #0000001a}to{box-shadow:none;transform:scale(1)}}.heatmap-cell.heatmap-cell-pop-dim{animation:.32s cubic-bezier(.34,1.56,.64,1) heatmap-cell-pop-dim}@keyframes heatmap-cell-pop-dim{0%{transform:scale(1)}50%{transform:scale(.85)}to{transform:scale(1)}}.heatmap-labels{color:var(--text-muted);justify-content:space-between;margin-top:.5rem;font-size:.65rem;display:flex}.chart-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);margin-bottom:1.5rem;padding:1.25rem;position:relative}.chart-header{justify-content:space-between;align-items:center;margin-bottom:.75rem;display:flex}.chart-title{font-size:.85rem;font-weight:600}.chart-badge{color:var(--yellow);background:#d299221a;border-radius:4px;padding:.15rem .5rem;font-size:.62rem}.export-btn{border:1px solid var(--border);background:var(--bg-surface);color:var(--text-muted);cursor:pointer;opacity:0;z-index:2;border-radius:4px;padding:.25rem .5rem;font-size:.65rem;transition:all .15s;position:absolute;top:.75rem;right:.75rem}.chart-card:hover .export-btn{opacity:.7}.export-btn:hover{color:var(--text-primary);border-color:var(--accent);opacity:1!important}.grid-2{grid-template-columns:1fr 1fr;gap:1.25rem;min-width:0;margin-bottom:1.5rem;display:grid}.grid-3{grid-template-columns:repeat(3,minmax(0,1fr));gap:1.25rem;min-width:0;margin-bottom:1.5rem;display:grid}.grid-3>.chart-card{min-width:0;overflow:hidden}.grid-3>.overview-donut-card{overflow:visible}.overview-donut-card .echarts-for-react,.overview-donut-card .echarts-for-react>div{overflow:visible!important}@media (width<=900px){.grid-2,.grid-3{grid-template-columns:1fr}}.btn-group{gap:2px;display:flex}.btn{border:1px solid var(--border);background:var(--bg-surface);color:var(--text-muted);cursor:pointer;border-radius:5px;padding:.3rem .65rem;font-size:.72rem;font-weight:500;transition:all .12s}.btn:hover{color:var(--text-secondary);border-color:var(--text-muted)}.btn.active{color:#fff;background:var(--accent);border-color:var(--accent)}.icon-btn{border:1px solid var(--border);background:var(--bg-card);width:28px;height:28px;color:var(--text-muted);cursor:pointer;border-radius:6px;justify-content:center;align-items:center;font-size:.9rem;line-height:1;transition:color .12s,border-color .12s,background .12s,transform .12s;display:inline-flex}.icon-btn:hover:not(:disabled){color:var(--text-primary);border-color:var(--accent);background:var(--bg-elevated);transform:rotate(20deg)}.icon-btn:disabled{opacity:.55;cursor:wait}.overview-daily-spark{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);flex:140px;min-width:140px;min-height:0;position:relative;overflow:visible}.overview-daily-spark-title{text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);z-index:1;pointer-events:none;font-size:.65rem;font-weight:600;position:absolute;top:1rem;left:1.1rem}.overview-daily-spark-chart{position:absolute;inset:0;overflow:hidden}.overview-daily-spark-empty{justify-content:center;align-items:center;display:flex}.overview-daily-spark-empty-text{color:var(--text-muted);font-size:.7rem}@media (width<=920px){.overview-daily-spark{flex:100%;min-width:100%;min-height:140px}}.table-wrap{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden}.model-detail-wrap{flex-direction:column;gap:.85rem;display:flex}.model-detail-note{color:var(--text-muted);font-size:.74rem;line-height:1.4}.model-detail-footer{justify-content:flex-start;margin-top:.25rem;display:flex}.model-detail-toggle{width:fit-content;color:var(--text-muted);border:1px solid var(--border);cursor:pointer;background:#ffffff0a;border-radius:6px;align-items:center;gap:.35rem;padding:.35rem .6rem;font-size:.72rem;font-weight:500;transition:color .2s,background .2s,border-color .2s;display:inline-flex}.model-detail-toggle:hover{color:var(--text-secondary);background:#ffffff0f;border-color:#6366f14d}.model-detail-toggle-text{font-family:var(--font-sans)}.model-detail-toggle-arrow{opacity:.8;font-size:.6rem;transition:transform .25s}.model-detail-toggle-arrow.expanded{transform:rotate(180deg)}.model-detail-charts{z-index:10;flex-wrap:wrap;align-items:flex-start;gap:.25rem;width:100%;display:flex;position:relative;overflow:visible}.model-detail-donut{z-index:10;flex-direction:column;flex:0 0 200px;width:200px;min-width:200px;height:200px;display:flex;overflow:visible}.model-detail-donut .chart-title{flex-shrink:0}.model-detail-donut .echarts-for-react{flex:1;min-height:0}.model-detail-wrap .model-detail-donut{flex:0 0 340px;width:340px;min-width:340px;height:220px}.model-detail-donut .echarts-for-react,.model-detail-donut .echarts-for-react>div,.model-detail-bar .echarts-for-react,.model-detail-bar .echarts-for-react>div{overflow:visible!important}.model-detail-bar{flex-direction:column;flex:300px;min-width:300px;height:200px;display:flex;overflow:visible}.model-detail-bar .chart-title{flex-shrink:0}.model-detail-bar .echarts-for-react{flex:1;min-height:0}.model-detail-summary-wrap{max-height:0;transition:max-height .2s ease-out;overflow:hidden}.model-detail-summary-wrap.expanded{max-height:400px}.model-detail-summary{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.6rem;padding-top:.5rem;display:grid}.model-detail-summary-card{border:1px solid var(--border);background:#ffffff05;border-radius:10px;padding:.65rem .75rem}.model-detail-summary-head{color:var(--text-primary);text-transform:capitalize;align-items:center;gap:.45rem;min-width:0;margin-bottom:.45rem;font-size:.76rem;font-weight:600;display:flex}.model-detail-summary-head .model-detail-summary-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.model-detail-swatch{border-radius:999px;flex:none;width:9px;height:9px}.model-detail-summary-line{color:var(--text-muted);font-size:.72rem;line-height:1.45}.detail-summary-grid{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.75rem;margin-bottom:1rem;display:grid}.detail-summary-stat{border:1px solid var(--border);background:#ffffff08;border-radius:10px;padding:.8rem .9rem}.detail-summary-label{text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:.25rem;font-size:.68rem}.detail-summary-value{font-size:1.1rem;font-family:var(--font-mono);color:var(--text-primary)}.table-search{border-bottom:1px solid var(--border);padding:.75rem 1rem}.table-search input{border:1px solid var(--border);background:var(--bg-surface);width:100%;color:var(--text-primary);font-size:.82rem;font-family:var(--font-sans);border-radius:6px;outline:none;padding:.5rem .85rem}.table-search input:focus{border-color:var(--accent)}.table-search input::placeholder{color:var(--text-muted)}table{border-collapse:collapse;width:100%;font-size:.78rem}th{text-align:left;color:var(--text-muted);border-bottom:1px solid var(--border);text-transform:uppercase;letter-spacing:.05em;cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;padding:.55rem .85rem;font-size:.68rem;font-weight:600}td{color:var(--text-secondary);white-space:nowrap;text-overflow:ellipsis;border-bottom:1px solid #30363d80;max-width:240px;padding:.5rem .85rem;overflow:hidden}tr:hover td{color:var(--text-primary);background:#6366f108}.tag{font-size:.66rem;font-family:var(--font-mono);border-radius:4px;padding:.1rem .4rem;font-weight:500;display:inline-block}.tag-review{color:var(--cyan);background:#06b6d41f}.tag-exploration{color:var(--orange);background:#f973161f}.tag-planning{color:var(--yellow);background:#eab3081f}.tag-memory{color:var(--pink);background:#ec48991f}.tag-generic{color:var(--text-secondary);background:#64748b1f}.loading-overlay{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);z-index:999;background:#06080feb;flex-direction:column;justify-content:center;align-items:center;transition:opacity .6s;display:flex;position:fixed;inset:0}.loading-overlay.fading{opacity:0;pointer-events:none}.loading-logo{background:linear-gradient(135deg,#818cf8,#6366f1);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text;margin-bottom:1.5rem;font-size:1.5rem;font-weight:700}.loading-bar-wrap{background:var(--bg-elevated);border-radius:2px;width:260px;height:4px;margin-bottom:1rem;overflow:hidden}.loading-bar{background:linear-gradient(90deg, var(--accent), #818cf8);border-radius:2px;height:100%;transition:width .4s}.loading-phase{color:var(--text-secondary);margin-bottom:.2rem;font-size:.82rem;font-weight:500}.loading-detail{font-family:var(--font-mono);color:var(--text-muted);font-size:.72rem}.loading-footer{color:var(--text-muted);font-size:.7rem;position:absolute;bottom:1.5rem}.loading-heart{color:#f43f5e}.coverage-subtle{color:var(--text-muted);align-items:center;gap:1.5rem;margin-top:-.5rem;margin-bottom:.5rem;padding:.25rem 0;font-size:.68rem;display:flex}.coverage-item{font-variant-numeric:tabular-nums;align-items:center;gap:.35rem;display:flex}.coverage-nums{text-align:right;min-width:9ch;display:inline-block}.coverage-nums-single{min-width:4ch}.coverage-dot{border-radius:50%;width:6px;height:6px}html{scrollbar-width:none}::-webkit-scrollbar{width:0;height:0;display:none}@keyframes fadeUp{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.animate-in{animation:.35s forwards fadeUp}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.incomplete-badge{color:var(--yellow);background:#d299221a;border-radius:3px;padding:.12rem .45rem;font-size:.65rem;animation:2s infinite pulse}.ingesting-badge .ingesting-pct{text-align:right;font-variant-numeric:tabular-nums;min-width:4ch;display:inline-block}.echarts-tooltip,[class*=ec-component-tooltip]{max-width:90vw!important;max-height:80vh!important;overflow:auto!important}
package/dist/index.html CHANGED
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-ATBSFYaH.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-DhDq9dI8.css">
10
+ <script type="module" crossorigin src="/assets/index-B1DauiX5.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-CVvcsIkn.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "codexmeter",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Local telemetry dashboard for Codex CLI usage",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "codexmeter": "./bin/codexmeter.js"
8
8
  },
9
+ "engines": {
10
+ "node": "^20.19.0 || >=22.12.0"
11
+ },
9
12
  "files": [
10
13
  "bin",
11
14
  "server",
@@ -17,31 +20,33 @@
17
20
  "scripts": {
18
21
  "dev": "node dev-runner.js",
19
22
  "build": "vite build",
20
- "test": "node --test test/*.test.js",
23
+ "prepack": "npm run build",
24
+ "pretest": "npm run build",
25
+ "test": "node --test \"test/*.test.js\" \"test/**/*.test.js\"",
21
26
  "start": "node bin/codexmeter.js",
22
27
  "preview": "npm run build && node bin/codexmeter.js",
23
28
  "prepublishOnly": "npm run build"
24
29
  },
25
30
  "dependencies": {
26
- "better-sqlite3": "^11.8.1",
27
- "commander": "^13.1.0",
31
+ "better-sqlite3": "^12.8.0",
32
+ "commander": "^14.0.3",
28
33
  "express": "^5.1.0",
29
34
  "fast-glob": "^3.3.3",
30
35
  "ffmpeg-static": "^5.2.0",
31
- "open": "^10.1.0",
36
+ "open": "^11.0.0",
32
37
  "playwright-core": "^1.58.2"
33
38
  },
34
39
  "devDependencies": {
35
40
  "@tanstack/react-table": "^8.21.3",
36
- "@tanstack/react-virtual": "^3.13.6",
41
+ "@tanstack/react-virtual": "^3.13.23",
37
42
  "@types/react": "^19.0.0",
38
43
  "@types/react-dom": "^19.0.0",
39
- "@vitejs/plugin-react": "^4.4.1",
40
- "echarts": "^5.6.0",
44
+ "@vitejs/plugin-react": "^6.0.1",
45
+ "echarts": "^6.0.0",
41
46
  "echarts-for-react": "^3.0.2",
42
47
  "nodemon": "^3.1.14",
43
48
  "react": "^19.1.0",
44
49
  "react-dom": "^19.1.0",
45
- "vite": "^6.3.5"
50
+ "vite": "^8.0.0"
46
51
  }
47
52
  }
@@ -0,0 +1,73 @@
1
+ import fsSync from 'fs';
2
+ import path from 'path';
3
+ import { spawnSync } from 'child_process';
4
+
5
+ export function findSupportedBrowserExecutable() {
6
+ if (process.env.CODEXMETER_EXPORT_DEBUG_FORCE_UNSUPPORTED === '1') {
7
+ return null;
8
+ }
9
+ if (process.env.CODEXMETER_EXPORT_BROWSER && fsSync.existsSync(process.env.CODEXMETER_EXPORT_BROWSER)) {
10
+ return process.env.CODEXMETER_EXPORT_BROWSER;
11
+ }
12
+
13
+ const candidates = process.platform === 'win32'
14
+ ? [
15
+ process.env['PROGRAMFILES'] ? path.join(process.env['PROGRAMFILES'], 'Google', 'Chrome', 'Application', 'chrome.exe') : null,
16
+ process.env['PROGRAMFILES(X86)'] ? path.join(process.env['PROGRAMFILES(X86)'], 'Google', 'Chrome', 'Application', 'chrome.exe') : null,
17
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe') : null,
18
+ process.env['PROGRAMFILES'] ? path.join(process.env['PROGRAMFILES'], 'Microsoft', 'Edge', 'Application', 'msedge.exe') : null,
19
+ process.env['PROGRAMFILES(X86)'] ? path.join(process.env['PROGRAMFILES(X86)'], 'Microsoft', 'Edge', 'Application', 'msedge.exe') : null,
20
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Microsoft', 'Edge', 'Application', 'msedge.exe') : null,
21
+ ]
22
+ : process.platform === 'darwin'
23
+ ? [
24
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
25
+ path.join(process.env.HOME || '', 'Applications', 'Google Chrome.app', 'Contents', 'MacOS', 'Google Chrome'),
26
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
27
+ path.join(process.env.HOME || '', 'Applications', 'Microsoft Edge.app', 'Contents', 'MacOS', 'Microsoft Edge'),
28
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
29
+ path.join(process.env.HOME || '', 'Applications', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'),
30
+ ]
31
+ : [
32
+ '/usr/bin/google-chrome',
33
+ '/usr/bin/google-chrome-stable',
34
+ '/usr/bin/chromium',
35
+ '/usr/bin/chromium-browser',
36
+ '/usr/bin/microsoft-edge',
37
+ '/snap/bin/chromium',
38
+ ];
39
+
40
+ for (const candidate of candidates.filter(Boolean)) {
41
+ if (fsSync.existsSync(candidate)) return candidate;
42
+ }
43
+
44
+ return findBrowserOnPath();
45
+ }
46
+
47
+ function findBrowserOnPath() {
48
+ const commands = process.platform === 'win32'
49
+ ? ['chrome', 'msedge', 'chromium']
50
+ : ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'microsoft-edge'];
51
+
52
+ for (const command of commands) {
53
+ const resolved = resolveCommand(command);
54
+ if (resolved) return resolved;
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ function resolveCommand(command) {
61
+ const lookup = process.platform === 'win32'
62
+ ? spawnSync('where.exe', [command], { encoding: 'utf8' })
63
+ : spawnSync('which', [command], { encoding: 'utf8' });
64
+
65
+ if (lookup.status !== 0) return null;
66
+
67
+ const firstLine = lookup.stdout
68
+ .split(/\r?\n/)
69
+ .map((line) => line.trim())
70
+ .find(Boolean);
71
+
72
+ return firstLine && fsSync.existsSync(firstLine) ? firstLine : null;
73
+ }
@@ -77,7 +77,7 @@ export function getModelPricing(modelName) {
77
77
  return getPricing()[modelName] || null;
78
78
  }
79
79
 
80
- export const CATALOG_VERSION = '2026-03-13';
80
+ export const CATALOG_VERSION = '2026-03-17';
81
81
  export const CACHE_ASSUMPTIONS = {
82
82
  input_fraction: INPUT_FRACTION,
83
83
  output_fraction: OUTPUT_FRACTION,
@@ -1,5 +1,4 @@
1
1
  import fs from 'fs/promises';
2
- import fsSync from 'fs';
3
2
  import os from 'os';
4
3
  import path from 'path';
5
4
  import { randomUUID } from 'crypto';
@@ -9,6 +8,7 @@ import { createRequire } from 'module';
9
8
  import ffmpegPath from 'ffmpeg-static';
10
9
  import { chromium } from 'playwright-core';
11
10
  import { OVERVIEW_INGEST_ANIMATION } from '../src/utils/animationsDefault.js';
11
+ import { findSupportedBrowserExecutable } from './browser-detection.js';
12
12
 
13
13
  const EXPORT_WIDTH = OVERVIEW_INGEST_ANIMATION.videoExport?.width ?? 1080;
14
14
  const EXPORT_HEIGHT = OVERVIEW_INGEST_ANIMATION.videoExport?.height ?? 864;
@@ -705,39 +705,6 @@ function resolvePlaywrightCliPath() {
705
705
  return path.join(packageRoot, 'cli.js');
706
706
  }
707
707
 
708
- function findSupportedBrowserExecutable() {
709
- if (process.env.CODEXMETER_EXPORT_DEBUG_FORCE_UNSUPPORTED === '1') {
710
- return null;
711
- }
712
- if (process.env.CODEXMETER_EXPORT_BROWSER && fsSync.existsSync(process.env.CODEXMETER_EXPORT_BROWSER)) {
713
- return process.env.CODEXMETER_EXPORT_BROWSER;
714
- }
715
-
716
- const candidates = process.platform === 'win32'
717
- ? [
718
- process.env['PROGRAMFILES'] ? path.join(process.env['PROGRAMFILES'], 'Google', 'Chrome', 'Application', 'chrome.exe') : null,
719
- process.env['PROGRAMFILES(X86)'] ? path.join(process.env['PROGRAMFILES(X86)'], 'Google', 'Chrome', 'Application', 'chrome.exe') : null,
720
- process.env['PROGRAMFILES'] ? path.join(process.env['PROGRAMFILES'], 'Microsoft', 'Edge', 'Application', 'msedge.exe') : null,
721
- process.env['PROGRAMFILES(X86)'] ? path.join(process.env['PROGRAMFILES(X86)'], 'Microsoft', 'Edge', 'Application', 'msedge.exe') : null,
722
- ]
723
- : process.platform === 'darwin'
724
- ? [
725
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
726
- '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
727
- ]
728
- : [
729
- '/usr/bin/google-chrome',
730
- '/usr/bin/chromium',
731
- '/usr/bin/chromium-browser',
732
- '/usr/bin/microsoft-edge',
733
- ];
734
-
735
- for (const candidate of candidates.filter(Boolean)) {
736
- if (fsSync.existsSync(candidate)) return candidate;
737
- }
738
- return null;
739
- }
740
-
741
708
  async function encodeFramesToMp4(framesDir, outputPath, { captureFormat, fps, frameCount, durationMs, crf, encoderPreset, outputWidth, outputHeight }) {
742
709
  const extension = captureFormat === 'jpeg' ? 'jpg' : 'png';
743
710
  const inputPattern = path.join(framesDir, `%05d.${extension}`);
package/server/ingest.js CHANGED
@@ -12,10 +12,20 @@ import { createRolloutWorkerPool } from './rollout-worker-pool.js';
12
12
  import { beginReplayCapture, createReplayCaptureState, failReplayCapture, getReplaySnapshot, recordReplayEvent, resetReplayCapture } from './export-replay.js';
13
13
  import { OVERVIEW_INGEST_ANIMATION } from '../src/utils/animationsDefault.js';
14
14
 
15
- const LIVE_FRAME_INTERVAL_MS = 50;
15
+ const LIVE_FRAME_INTERVAL_MS = Math.max(
16
+ 16,
17
+ Math.round(Number(OVERVIEW_INGEST_ANIMATION.live?.frameIntervalMs) || 50)
18
+ );
16
19
  const LIVE_DAYS_PER_SECOND = 6;
17
- const LIVE_OVERVIEW_CADENCE_MS = Math.round(1000 / 10);
18
- const LIVE_DAY_KEYS_PER_EMIT = 1;
20
+ const LIVE_OVERVIEW_HZ = Math.max(
21
+ 1,
22
+ Number(OVERVIEW_INGEST_ANIMATION.live?.overviewHz) || 10
23
+ );
24
+ const LIVE_OVERVIEW_CADENCE_MS = Math.round(1000 / LIVE_OVERVIEW_HZ);
25
+ const LIVE_DAY_KEYS_PER_EMIT = Math.max(
26
+ 1,
27
+ Math.round(Number(OVERVIEW_INGEST_ANIMATION.live?.dayKeysPerEmit) || 1)
28
+ );
19
29
  const LIVE_SESSION_REORDER_BUFFER = 160;
20
30
 
21
31
  export function createIngestState() {
@@ -696,5 +706,5 @@ function broadcastBootstrap(state) {
696
706
  };
697
707
  broadcastLive(state, 'bootstrap', payload);
698
708
  }
699
-
700
-
709
+
710
+
@@ -63,6 +63,12 @@ export function isSubagent(agentRole) {
63
63
  const MODEL_ALIASES = {
64
64
  'codex-mini-latest': 'o4-mini',
65
65
  'gpt-5.2': 'gpt-5.2-codex',
66
+ 'gpt-5 mini': 'gpt-5-mini',
67
+ 'gpt-5.4-mini': 'gpt-5-mini',
68
+ 'gpt-5.4 mini': 'gpt-5-mini',
69
+ 'gpt-5 nano': 'gpt-5.4-nano',
70
+ 'gpt-5-nano': 'gpt-5.4-nano',
71
+ 'gpt-5.4 nano': 'gpt-5.4-nano',
66
72
  };
67
73
 
68
74
  export function normalizeModelName(rawModel) {
@@ -1,43 +1,45 @@
1
1
  // Local pricing catalog for models used in codex-cli/codex app lifetime.
2
2
  const FALLBACK = {
3
- 'gpt-5.4': { input: 2.50, output: 15.00, cached_input: 0.25 },
4
- 'gpt-5.3-codex': { input: 2.00, output: 10.00, cached_input: 0.20 },
5
- 'gpt-5.3-codex-spark': { input: 2.00, output: 10.00, cached_input: 0.20 },
6
- 'gpt-5.2-codex': { input: 2.00, output: 10.00, cached_input: 0.20 },
7
- 'gpt-5.2': { input: 2.00, output: 10.00, cached_input: 0.20 },
8
- 'gpt-5.1-codex-mini': { input: 0.25, output: 2.00, cached_input: 0.025},
9
- 'gpt-4.1': { input: 2.00, output: 8.00, cached_input: 0.50 },
10
- 'gpt-4.1-mini': { input: 0.40, output: 1.60, cached_input: 0.10 },
11
- 'gpt-4.1-nano': { input: 0.10, output: 0.40, cached_input: 0.025 },
12
- 'o3': { input: 2.00, output: 8.00, cached_input: 0.50 },
13
- 'o3-mini': { input: 1.10, output: 4.40, cached_input: 0.275},
14
- 'o4-mini': { input: 1.10, output: 4.40, cached_input: 0.275},
15
- 'gpt-4o': { input: 2.50, output: 10.00, cached_input: 1.25 },
16
- 'gpt-4o-mini': { input: 0.15, output: 0.60, cached_input: 0.075},
17
- 'gpt-4-turbo': { input: 10.00, output: 30.00, cached_input: 1.00 },
18
- 'gpt-4': { input: 30.00, output: 60.00, cached_input: 3.00 },
19
- 'gpt-3.5-turbo': { input: 0.50, output: 1.50, cached_input: 0.05 },
20
- };
21
-
3
+ 'gpt-5.4': { input: 2.50, output: 15.00, cached_input: 0.25 },
4
+ 'gpt-5-mini': { input: 0.75, output: 4.50, cached_input: 0.075},
5
+ 'gpt-5.4-nano': { input: 0.15, output: 0.60, cached_input: 0.015},
6
+ 'gpt-5.3-codex': { input: 2.00, output: 10.00, cached_input: 0.20 },
7
+ 'gpt-5.3-codex-spark': { input: 2.00, output: 10.00, cached_input: 0.20 },
8
+ 'gpt-5.2-codex': { input: 2.00, output: 10.00, cached_input: 0.20 },
9
+ 'gpt-5.2': { input: 2.00, output: 10.00, cached_input: 0.20 },
10
+ 'gpt-5.1-codex-mini': { input: 0.25, output: 2.00, cached_input: 0.025},
11
+ 'gpt-4.1': { input: 2.00, output: 8.00, cached_input: 0.50 },
12
+ 'gpt-4.1-mini': { input: 0.40, output: 1.60, cached_input: 0.10 },
13
+ 'gpt-4.1-nano': { input: 0.10, output: 0.40, cached_input: 0.025 },
14
+ 'o3': { input: 2.00, output: 8.00, cached_input: 0.50 },
15
+ 'o3-mini': { input: 1.10, output: 4.40, cached_input: 0.275},
16
+ 'o4-mini': { input: 1.10, output: 4.40, cached_input: 0.275},
17
+ 'gpt-4o': { input: 2.50, output: 10.00, cached_input: 1.25 },
18
+ 'gpt-4o-mini': { input: 0.15, output: 0.60, cached_input: 0.075},
19
+ 'gpt-4-turbo': { input: 10.00, output: 30.00, cached_input: 1.00 },
20
+ 'gpt-4': { input: 30.00, output: 60.00, cached_input: 3.00 },
21
+ 'gpt-3.5-turbo': { input: 0.50, output: 1.50, cached_input: 0.05 },
22
+ };
23
+
22
24
  const PRICING_URL = process.env.CODEXMETER_PRICING_URL || null;
23
-
24
- function normalizeEntry(entry) {
25
- if (!entry || typeof entry !== 'object' || entry.input == null || entry.output == null) return null;
26
- const cached = entry.cached_input ?? entry.cachedInput ?? entry.input * 0.1;
27
- return { input: entry.input, output: entry.output, cached_input: cached };
28
- }
29
-
30
- function normalizePricing(raw) {
31
- const models = raw?.models && typeof raw.models === 'object' ? raw.models : raw;
32
- if (!models || typeof models !== 'object') return null;
33
- const out = {};
34
- for (const [name, entry] of Object.entries(models)) {
35
- const norm = normalizeEntry(entry);
36
- if (norm) out[name] = norm;
37
- }
38
- return Object.keys(out).length > 0 ? out : null;
39
- }
40
-
25
+
26
+ function normalizeEntry(entry) {
27
+ if (!entry || typeof entry !== 'object' || entry.input == null || entry.output == null) return null;
28
+ const cached = entry.cached_input ?? entry.cachedInput ?? entry.input * 0.1;
29
+ return { input: entry.input, output: entry.output, cached_input: cached };
30
+ }
31
+
32
+ function normalizePricing(raw) {
33
+ const models = raw?.models && typeof raw.models === 'object' ? raw.models : raw;
34
+ if (!models || typeof models !== 'object') return null;
35
+ const out = {};
36
+ for (const [name, entry] of Object.entries(models)) {
37
+ const norm = normalizeEntry(entry);
38
+ if (norm) out[name] = norm;
39
+ }
40
+ return Object.keys(out).length > 0 ? out : null;
41
+ }
42
+
41
43
  export async function fetchPricing() {
42
44
  if (!PRICING_URL) return FALLBACK;
43
45
 
@@ -45,15 +47,15 @@ export async function fetchPricing() {
45
47
  const ctrl = new AbortController();
46
48
  const t = setTimeout(() => ctrl.abort(), 3000);
47
49
  const res = await fetch(PRICING_URL, { signal: ctrl.signal });
48
- clearTimeout(t);
49
- if (!res.ok) return FALLBACK;
50
- const data = await res.json();
51
- const normalized = normalizePricing(data);
52
- if (normalized) {
53
- return { ...FALLBACK, ...normalized };
54
- }
55
- } catch {
56
- // timeout, network error, invalid JSON - use fallback
57
- }
58
- return FALLBACK;
59
- }
50
+ clearTimeout(t);
51
+ if (!res.ok) return FALLBACK;
52
+ const data = await res.json();
53
+ const normalized = normalizePricing(data);
54
+ if (normalized) {
55
+ return { ...FALLBACK, ...normalized };
56
+ }
57
+ } catch {
58
+ // timeout, network error, invalid JSON - use fallback
59
+ }
60
+ return FALLBACK;
61
+ }
@@ -19,8 +19,13 @@ const AUTO = 'auto';
19
19
  */
20
20
  export const OVERVIEW_INGEST_ANIMATION = {
21
21
  speed: 1,
22
+ live: {
23
+ frameIntervalMs: 33,
24
+ overviewHz: 20,
25
+ dayKeysPerEmit: 1,
26
+ },
22
27
  main: {
23
- presentationDurationMs: 300,
28
+ presentationDurationMs: 220,
24
29
  chartAppearDurationMs: 0,
25
30
  chartUpdateDurationMs: 200,
26
31
  easing: 'linear',
@@ -30,19 +35,19 @@ export const OVERVIEW_INGEST_ANIMATION = {
30
35
  // Master switch for the end-of-ingest slowdown behavior.
31
36
  enabled: true,
32
37
  // Progress threshold where the tail mode starts, expressed from 0..1.
33
- startPercent: 0.85,
38
+ startPercent: 0.5,
34
39
  // Fixed presentation duration in ms during the tail; use AUTO to derive it from main.durationScale.
35
40
  durationMs: AUTO,
36
41
  // Multiplier applied to the normal presentation duration when durationMs is AUTO.
37
- durationScale: 20,
42
+ durationScale: 10,
38
43
  // Shared easing applied by the Overview presentation animator during the tail.
39
44
  easing: 'cubicOut',
40
45
  // Backend Overview live-update cadence during the tail, in patches per second.
41
- overviewHz: 5,
46
+ overviewHz: 10,
42
47
  },
43
48
  daily: {
44
49
  chartAppearDurationMs: AUTO,
45
- chartUpdateDurationMs: 30,
50
+ chartUpdateDurationMs: 60,
46
51
  easing: AUTO,
47
52
  easingUpdate: AUTO,
48
53
  },