code-meter 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {code_meter-0.4.0 → code_meter-0.5.0}/PKG-INFO +1 -1
- {code_meter-0.4.0 → code_meter-0.5.0}/pyproject.toml +1 -1
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/storage/repository.py +82 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/web/server.py +2 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/web/static.py +167 -83
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/PKG-INFO +1 -1
- {code_meter-0.4.0 → code_meter-0.5.0}/README.md +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/setup.cfg +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/setup.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/costs.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/reports.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/tokens.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/cli.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/config.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/models/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/models/project.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/models/usage.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/anthropic.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/engine.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/google.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/live.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/openai.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/antigravity.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/base.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/claude_code.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/codex.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/storage/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/storage/database.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/dashboard.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/tables.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/web/__init__.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/watcher.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/SOURCES.txt +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/dependency_links.txt +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/entry_points.txt +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/requires.txt +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/top_level.txt +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_analytics.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_database.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_pricing.py +0 -0
- {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_scanner.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-meter"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Local AI coding token usage tracker and cost estimator for Claude Code and future agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Dhanush Nayak", email = "dhanushnayak.ram@mail.com" }]
|
|
@@ -511,3 +511,85 @@ class UsageRepository:
|
|
|
511
511
|
conn.commit()
|
|
512
512
|
finally:
|
|
513
513
|
conn.close()
|
|
514
|
+
|
|
515
|
+
def query_prompt_details(self, limit: int = 50, pricing_engine: Optional[Any] = None) -> List[Dict[str, Any]]:
|
|
516
|
+
"""Query user prompts enriched with consumed tokens, internal request count, cost, and output code edits."""
|
|
517
|
+
prompts = self.query_prompts(limit=limit)
|
|
518
|
+
details: List[Dict[str, Any]] = []
|
|
519
|
+
|
|
520
|
+
conn = self.db.get_connection()
|
|
521
|
+
try:
|
|
522
|
+
cursor = conn.cursor()
|
|
523
|
+
for p in prompts:
|
|
524
|
+
session_id = p.session_id
|
|
525
|
+
|
|
526
|
+
cursor.execute(
|
|
527
|
+
"""
|
|
528
|
+
SELECT provider, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, timestamp
|
|
529
|
+
FROM usage_events
|
|
530
|
+
WHERE session_id = ?;
|
|
531
|
+
""",
|
|
532
|
+
(session_id,),
|
|
533
|
+
)
|
|
534
|
+
events = cursor.fetchall()
|
|
535
|
+
total_requests = len(events)
|
|
536
|
+
total_tokens = sum(
|
|
537
|
+
(e["input_tokens"] + e["output_tokens"] + e["cache_read_tokens"] + e["cache_write_tokens"])
|
|
538
|
+
for e in events
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
total_cost = 0.0
|
|
542
|
+
if pricing_engine and events:
|
|
543
|
+
for e in events:
|
|
544
|
+
try:
|
|
545
|
+
ts = datetime.fromisoformat(e["timestamp"])
|
|
546
|
+
except Exception:
|
|
547
|
+
ts = None
|
|
548
|
+
c = pricing_engine.calculate_cost(
|
|
549
|
+
provider=e["provider"],
|
|
550
|
+
model=e["model"],
|
|
551
|
+
input_tokens=e["input_tokens"],
|
|
552
|
+
output_tokens=e["output_tokens"],
|
|
553
|
+
cache_read_tokens=e["cache_read_tokens"],
|
|
554
|
+
cache_write_tokens=e["cache_write_tokens"],
|
|
555
|
+
timestamp=ts,
|
|
556
|
+
)
|
|
557
|
+
if c:
|
|
558
|
+
total_cost += c
|
|
559
|
+
|
|
560
|
+
cursor.execute(
|
|
561
|
+
"""
|
|
562
|
+
SELECT change_type, file_path, diff_summary
|
|
563
|
+
FROM session_code_changes
|
|
564
|
+
WHERE session_id = ?
|
|
565
|
+
ORDER BY timestamp DESC;
|
|
566
|
+
""",
|
|
567
|
+
(session_id,),
|
|
568
|
+
)
|
|
569
|
+
changes = [
|
|
570
|
+
{
|
|
571
|
+
"change_type": r["change_type"],
|
|
572
|
+
"file_path": r["file_path"],
|
|
573
|
+
"diff_summary": r["diff_summary"],
|
|
574
|
+
}
|
|
575
|
+
for r in cursor.fetchall()
|
|
576
|
+
]
|
|
577
|
+
|
|
578
|
+
details.append(
|
|
579
|
+
{
|
|
580
|
+
"prompt_id": p.prompt_id,
|
|
581
|
+
"session_id": p.session_id,
|
|
582
|
+
"provider": p.provider,
|
|
583
|
+
"timestamp": p.timestamp.isoformat(),
|
|
584
|
+
"prompt_text": p.prompt_text,
|
|
585
|
+
"project_id": p.project_id or "",
|
|
586
|
+
"internal_requests": max(total_requests, 1),
|
|
587
|
+
"total_tokens": total_tokens,
|
|
588
|
+
"cost_usd": round(total_cost, 4),
|
|
589
|
+
"outputs": changes,
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
finally:
|
|
593
|
+
conn.close()
|
|
594
|
+
|
|
595
|
+
return details
|
|
@@ -65,6 +65,7 @@ def create_web_handler(repo: UsageRepository, pricing_engine: PricingEngine, rep
|
|
|
65
65
|
summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
|
|
66
66
|
by_model = report_gen.aggregate_by_model(records, costs)
|
|
67
67
|
by_project = report_gen.aggregate_by_project(records, costs)
|
|
68
|
+
prompt_details = repo.query_prompt_details(limit=25, pricing_engine=pricing_engine)
|
|
68
69
|
|
|
69
70
|
payload = {
|
|
70
71
|
"summary": {
|
|
@@ -94,6 +95,7 @@ def create_web_handler(repo: UsageRepository, pricing_engine: PricingEngine, rep
|
|
|
94
95
|
}
|
|
95
96
|
for p in by_project
|
|
96
97
|
],
|
|
98
|
+
"prompt_details": prompt_details,
|
|
97
99
|
"prompts": [
|
|
98
100
|
{
|
|
99
101
|
"timestamp": p.timestamp,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Embedded Single-Page Web Dashboard HTML/CSS/JS template for code-meter on http://localhost:5005."""
|
|
1
|
+
"""Embedded Single-Page Modern Web Dashboard HTML/CSS/JS template for code-meter on http://localhost:5005."""
|
|
2
2
|
|
|
3
3
|
HTML_DASHBOARD = """<!DOCTYPE html>
|
|
4
4
|
<html lang="en">
|
|
@@ -6,25 +6,30 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
6
6
|
<meta charset="UTF-8">
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
8
|
<title>code-meter | AI Token & Cost Dashboard</title>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
9
12
|
<style>
|
|
10
13
|
:root {
|
|
11
|
-
--bg-dark: #
|
|
12
|
-
--card-bg: rgba(
|
|
13
|
-
--border-color: rgba(255, 255, 255, 0.
|
|
14
|
+
--bg-dark: #090d16;
|
|
15
|
+
--card-bg: rgba(15, 23, 42, 0.75);
|
|
16
|
+
--border-color: rgba(255, 255, 255, 0.08);
|
|
17
|
+
--border-hover: rgba(56, 189, 248, 0.3);
|
|
14
18
|
--text-main: #f8fafc;
|
|
15
19
|
--text-muted: #94a3b8;
|
|
16
20
|
--accent-blue: #38bdf8;
|
|
17
21
|
--accent-emerald: #34d399;
|
|
18
22
|
--accent-purple: #c084fc;
|
|
19
23
|
--accent-amber: #fbbf24;
|
|
20
|
-
--
|
|
24
|
+
--accent-orange: #fb923c;
|
|
25
|
+
--shadow-glow: 0 10px 30px -5px rgba(56, 189, 248, 0.2);
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
* {
|
|
24
29
|
box-sizing: border-box;
|
|
25
30
|
margin: 0;
|
|
26
31
|
padding: 0;
|
|
27
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
32
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
body {
|
|
@@ -33,12 +38,13 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
33
38
|
padding: 24px;
|
|
34
39
|
min-height: 100vh;
|
|
35
40
|
background-image:
|
|
36
|
-
radial-gradient(at 0% 0%, rgba(56, 189, 248, 0.
|
|
37
|
-
radial-gradient(at 100% 100%, rgba(192, 132, 252, 0.
|
|
41
|
+
radial-gradient(at 0% 0%, rgba(56, 189, 248, 0.12) 0px, transparent 50%),
|
|
42
|
+
radial-gradient(at 100% 100%, rgba(192, 132, 252, 0.12) 0px, transparent 50%),
|
|
43
|
+
radial-gradient(at 50% 50%, rgba(52, 211, 153, 0.05) 0px, transparent 60%);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
.container {
|
|
41
|
-
max-width:
|
|
47
|
+
max-width: 1400px;
|
|
42
48
|
margin: 0 auto;
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -47,33 +53,36 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
47
53
|
justify-content: space-between;
|
|
48
54
|
align-items: center;
|
|
49
55
|
margin-bottom: 28px;
|
|
50
|
-
padding-bottom:
|
|
56
|
+
padding-bottom: 18px;
|
|
51
57
|
border-bottom: 1px solid var(--border-color);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
.brand {
|
|
55
61
|
display: flex;
|
|
56
62
|
align-items: center;
|
|
57
|
-
gap:
|
|
63
|
+
gap: 14px;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
.logo-badge {
|
|
61
|
-
background: linear-gradient(135deg, #0284c7, #
|
|
62
|
-
width:
|
|
63
|
-
height:
|
|
64
|
-
border-radius:
|
|
67
|
+
background: linear-gradient(135deg, #0284c7, #9333ea);
|
|
68
|
+
width: 46px;
|
|
69
|
+
height: 46px;
|
|
70
|
+
border-radius: 12px;
|
|
65
71
|
display: flex;
|
|
66
72
|
align-items: center;
|
|
67
73
|
justify-content: center;
|
|
68
74
|
font-weight: 800;
|
|
69
|
-
font-size:
|
|
70
|
-
box-shadow: 0 4px
|
|
75
|
+
font-size: 22px;
|
|
76
|
+
box-shadow: 0 4px 16px rgba(147, 51, 234, 0.4);
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
h1 {
|
|
74
|
-
font-size:
|
|
75
|
-
font-weight:
|
|
80
|
+
font-size: 26px;
|
|
81
|
+
font-weight: 800;
|
|
76
82
|
letter-spacing: -0.5px;
|
|
83
|
+
background: linear-gradient(to right, #ffffff, #94a3b8);
|
|
84
|
+
-webkit-background-clip: text;
|
|
85
|
+
-webkit-text-fill-color: transparent;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
.subtitle {
|
|
@@ -84,18 +93,20 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
84
93
|
.live-status {
|
|
85
94
|
display: flex;
|
|
86
95
|
align-items: center;
|
|
87
|
-
gap:
|
|
88
|
-
background: rgba(52, 211, 153, 0.
|
|
89
|
-
border: 1px solid rgba(52, 211, 153, 0.
|
|
90
|
-
padding:
|
|
91
|
-
border-radius:
|
|
96
|
+
gap: 10px;
|
|
97
|
+
background: rgba(52, 211, 153, 0.08);
|
|
98
|
+
border: 1px solid rgba(52, 211, 153, 0.25);
|
|
99
|
+
padding: 8px 16px;
|
|
100
|
+
border-radius: 24px;
|
|
92
101
|
font-size: 13px;
|
|
102
|
+
font-weight: 600;
|
|
93
103
|
color: var(--accent-emerald);
|
|
104
|
+
backdrop-filter: blur(8px);
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
.pulse {
|
|
97
|
-
width:
|
|
98
|
-
height:
|
|
108
|
+
width: 9px;
|
|
109
|
+
height: 9px;
|
|
99
110
|
background-color: var(--accent-emerald);
|
|
100
111
|
border-radius: 50%;
|
|
101
112
|
box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.7);
|
|
@@ -111,41 +122,44 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
111
122
|
/* Metrics Grid */
|
|
112
123
|
.metrics-grid {
|
|
113
124
|
display: grid;
|
|
114
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
115
|
-
gap:
|
|
125
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
126
|
+
gap: 18px;
|
|
116
127
|
margin-bottom: 28px;
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
.metric-card {
|
|
120
131
|
background: var(--card-bg);
|
|
121
|
-
backdrop-filter: blur(
|
|
132
|
+
backdrop-filter: blur(16px);
|
|
122
133
|
border: 1px solid var(--border-color);
|
|
123
|
-
border-radius:
|
|
124
|
-
padding:
|
|
125
|
-
transition: transform 0.
|
|
134
|
+
border-radius: 16px;
|
|
135
|
+
padding: 22px;
|
|
136
|
+
transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
.metric-card:hover {
|
|
129
|
-
transform: translateY(-
|
|
140
|
+
transform: translateY(-3px);
|
|
141
|
+
border-color: var(--border-hover);
|
|
130
142
|
box-shadow: var(--shadow-glow);
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
.metric-label {
|
|
134
146
|
font-size: 12px;
|
|
147
|
+
font-weight: 600;
|
|
135
148
|
text-transform: uppercase;
|
|
136
|
-
letter-spacing: 0.
|
|
149
|
+
letter-spacing: 0.8px;
|
|
137
150
|
color: var(--text-muted);
|
|
138
|
-
margin-bottom:
|
|
151
|
+
margin-bottom: 10px;
|
|
139
152
|
}
|
|
140
153
|
|
|
141
154
|
.metric-value {
|
|
142
|
-
font-size:
|
|
143
|
-
font-weight:
|
|
155
|
+
font-size: 28px;
|
|
156
|
+
font-weight: 800;
|
|
144
157
|
letter-spacing: -0.5px;
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
.metric-value.highlight {
|
|
148
161
|
color: var(--accent-emerald);
|
|
162
|
+
text-shadow: 0 0 20px rgba(52, 211, 153, 0.3);
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
/* Dashboard Layout */
|
|
@@ -156,6 +170,10 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
156
170
|
margin-bottom: 28px;
|
|
157
171
|
}
|
|
158
172
|
|
|
173
|
+
.dashboard-row.full {
|
|
174
|
+
grid-template-columns: 1fr;
|
|
175
|
+
}
|
|
176
|
+
|
|
159
177
|
@media (max-width: 900px) {
|
|
160
178
|
.dashboard-row {
|
|
161
179
|
grid-template-columns: 1fr;
|
|
@@ -164,25 +182,26 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
164
182
|
|
|
165
183
|
.panel {
|
|
166
184
|
background: var(--card-bg);
|
|
167
|
-
backdrop-filter: blur(
|
|
185
|
+
backdrop-filter: blur(16px);
|
|
168
186
|
border: 1px solid var(--border-color);
|
|
169
|
-
border-radius:
|
|
187
|
+
border-radius: 18px;
|
|
170
188
|
padding: 24px;
|
|
189
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
.panel-header {
|
|
174
193
|
display: flex;
|
|
175
194
|
justify-content: space-between;
|
|
176
195
|
align-items: center;
|
|
177
|
-
margin-bottom:
|
|
196
|
+
margin-bottom: 20px;
|
|
178
197
|
}
|
|
179
198
|
|
|
180
199
|
.panel-title {
|
|
181
200
|
font-size: 16px;
|
|
182
|
-
font-weight:
|
|
201
|
+
font-weight: 700;
|
|
183
202
|
display: flex;
|
|
184
203
|
align-items: center;
|
|
185
|
-
gap:
|
|
204
|
+
gap: 10px;
|
|
186
205
|
}
|
|
187
206
|
|
|
188
207
|
/* Tables */
|
|
@@ -194,17 +213,19 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
194
213
|
|
|
195
214
|
th {
|
|
196
215
|
text-align: left;
|
|
197
|
-
padding:
|
|
216
|
+
padding: 12px 14px;
|
|
198
217
|
color: var(--text-muted);
|
|
199
|
-
font-weight:
|
|
218
|
+
font-weight: 600;
|
|
200
219
|
border-bottom: 1px solid var(--border-color);
|
|
201
220
|
font-size: 12px;
|
|
202
221
|
text-transform: uppercase;
|
|
222
|
+
letter-spacing: 0.5px;
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
td {
|
|
206
|
-
padding:
|
|
207
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.
|
|
226
|
+
padding: 14px;
|
|
227
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
|
228
|
+
vertical-align: middle;
|
|
208
229
|
}
|
|
209
230
|
|
|
210
231
|
tr:last-child td {
|
|
@@ -212,42 +233,55 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
212
233
|
}
|
|
213
234
|
|
|
214
235
|
.badge {
|
|
215
|
-
display: inline-
|
|
216
|
-
|
|
217
|
-
|
|
236
|
+
display: inline-flex;
|
|
237
|
+
align-items: center;
|
|
238
|
+
gap: 4px;
|
|
239
|
+
padding: 4px 10px;
|
|
240
|
+
border-radius: 8px;
|
|
218
241
|
font-size: 11px;
|
|
219
|
-
font-weight:
|
|
242
|
+
font-weight: 700;
|
|
220
243
|
text-transform: uppercase;
|
|
244
|
+
letter-spacing: 0.5px;
|
|
221
245
|
}
|
|
222
246
|
|
|
223
|
-
.badge-claude { background: rgba(249, 115, 22, 0.
|
|
224
|
-
.badge-codex { background: rgba(56, 189, 248, 0.
|
|
225
|
-
.badge-antigravity { background: rgba(192, 132, 252, 0.
|
|
247
|
+
.badge-claude { background: rgba(249, 115, 22, 0.12); color: var(--accent-orange); border: 1px solid rgba(249, 115, 22, 0.3); }
|
|
248
|
+
.badge-codex { background: rgba(56, 189, 248, 0.12); color: var(--accent-blue); border: 1px solid rgba(56, 189, 248, 0.3); }
|
|
249
|
+
.badge-antigravity { background: rgba(192, 132, 252, 0.12); color: var(--accent-purple); border: 1px solid rgba(192, 132, 252, 0.3); }
|
|
250
|
+
.badge-emerald { background: rgba(52, 211, 153, 0.12); color: var(--accent-emerald); border: 1px solid rgba(52, 211, 153, 0.3); }
|
|
226
251
|
|
|
227
252
|
.prompt-text {
|
|
228
|
-
font-family: monospace;
|
|
253
|
+
font-family: 'JetBrains Mono', monospace;
|
|
229
254
|
font-size: 13px;
|
|
230
|
-
color: #
|
|
255
|
+
color: #f1f5f9;
|
|
256
|
+
line-height: 1.5;
|
|
231
257
|
word-break: break-word;
|
|
232
258
|
}
|
|
233
259
|
|
|
234
260
|
.file-path {
|
|
235
|
-
font-family: monospace;
|
|
261
|
+
font-family: 'JetBrains Mono', monospace;
|
|
236
262
|
font-size: 12px;
|
|
237
263
|
color: var(--accent-blue);
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
gap: 6px;
|
|
267
|
+
margin-bottom: 4px;
|
|
238
268
|
}
|
|
239
269
|
|
|
240
270
|
.diff-summary {
|
|
241
|
-
font-family: monospace;
|
|
271
|
+
font-family: 'JetBrains Mono', monospace;
|
|
242
272
|
font-size: 12px;
|
|
243
|
-
color:
|
|
273
|
+
color: #a7f3d0;
|
|
274
|
+
background: rgba(16, 185, 129, 0.08);
|
|
275
|
+
border: 1px solid rgba(16, 185, 129, 0.2);
|
|
276
|
+
padding: 6px 10px;
|
|
277
|
+
border-radius: 6px;
|
|
244
278
|
white-space: pre-wrap;
|
|
245
279
|
}
|
|
246
280
|
|
|
247
281
|
footer {
|
|
248
282
|
text-align: center;
|
|
249
283
|
margin-top: 40px;
|
|
250
|
-
padding-top:
|
|
284
|
+
padding-top: 24px;
|
|
251
285
|
border-top: 1px solid var(--border-color);
|
|
252
286
|
font-size: 13px;
|
|
253
287
|
color: var(--text-muted);
|
|
@@ -256,6 +290,7 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
256
290
|
footer a {
|
|
257
291
|
color: var(--accent-blue);
|
|
258
292
|
text-decoration: none;
|
|
293
|
+
font-weight: 600;
|
|
259
294
|
}
|
|
260
295
|
</style>
|
|
261
296
|
</head>
|
|
@@ -266,12 +301,12 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
266
301
|
<div class="logo-badge">⚡</div>
|
|
267
302
|
<div>
|
|
268
303
|
<h1>code-meter</h1>
|
|
269
|
-
<div class="subtitle">
|
|
304
|
+
<div class="subtitle">Real-Time AI Token, Prompt Trace & Cost Dashboard</div>
|
|
270
305
|
</div>
|
|
271
306
|
</div>
|
|
272
307
|
<div class="live-status">
|
|
273
308
|
<div class="pulse"></div>
|
|
274
|
-
<span>Live Dashboard (localhost:5005)</span>
|
|
309
|
+
<span>Live Web Dashboard (localhost:5005)</span>
|
|
275
310
|
</div>
|
|
276
311
|
</header>
|
|
277
312
|
|
|
@@ -294,57 +329,82 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
294
329
|
<div class="metric-value" id="val-cache">0</div>
|
|
295
330
|
</div>
|
|
296
331
|
<div class="metric-card">
|
|
297
|
-
<div class="metric-label">Total Cost</div>
|
|
332
|
+
<div class="metric-label">Total Estimated Cost</div>
|
|
298
333
|
<div class="metric-value highlight" id="val-cost">$0.00</div>
|
|
299
334
|
</div>
|
|
300
335
|
</div>
|
|
301
336
|
|
|
302
|
-
<!-- Row 1:
|
|
337
|
+
<!-- Row 1: Detailed User Prompt Trace (Queries, Consumed Tokens, Internal Requests & Output Edits) -->
|
|
338
|
+
<div class="dashboard-row full">
|
|
339
|
+
<div class="panel">
|
|
340
|
+
<div class="panel-header">
|
|
341
|
+
<div class="panel-title">💡 User Prompt Traces (Queries, Consumed Tokens, Internal Requests & Output Edits)</div>
|
|
342
|
+
</div>
|
|
343
|
+
<table>
|
|
344
|
+
<thead>
|
|
345
|
+
<tr>
|
|
346
|
+
<th style="width: 8%;">Time</th>
|
|
347
|
+
<th style="width: 10%;">Provider</th>
|
|
348
|
+
<th style="width: 32%;">User Prompt / Query</th>
|
|
349
|
+
<th style="width: 12%;">Internal Requests</th>
|
|
350
|
+
<th style="width: 12%;">Tokens Consumed</th>
|
|
351
|
+
<th style="width: 10%;">Cost ($)</th>
|
|
352
|
+
<th style="width: 16%;">Output Code Edits</th>
|
|
353
|
+
</tr>
|
|
354
|
+
</thead>
|
|
355
|
+
<tbody id="tbl-prompt-details">
|
|
356
|
+
<tr><td colspan="7" style="text-align:center; color: var(--text-muted);">Loading user prompt trace details...</td></tr>
|
|
357
|
+
</tbody>
|
|
358
|
+
</table>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
<!-- Row 2: Model & Project Breakdown -->
|
|
303
363
|
<div class="dashboard-row">
|
|
304
364
|
<div class="panel">
|
|
305
365
|
<div class="panel-header">
|
|
306
|
-
<div class="panel-title">🤖 Model
|
|
366
|
+
<div class="panel-title">🤖 Model Usage & Costs</div>
|
|
307
367
|
</div>
|
|
308
368
|
<table>
|
|
309
369
|
<thead>
|
|
310
370
|
<tr>
|
|
311
|
-
<th>Model</th>
|
|
312
|
-
<th>
|
|
313
|
-
<th>Tokens</th>
|
|
371
|
+
<th>Model Name</th>
|
|
372
|
+
<th>Requests</th>
|
|
373
|
+
<th>Total Tokens</th>
|
|
314
374
|
<th>Est. Cost</th>
|
|
315
375
|
</tr>
|
|
316
376
|
</thead>
|
|
317
377
|
<tbody id="tbl-models">
|
|
318
|
-
<tr><td colspan="4" style="text-align:center; color: var(--text-muted);">Loading
|
|
378
|
+
<tr><td colspan="4" style="text-align:center; color: var(--text-muted);">Loading model stats...</td></tr>
|
|
319
379
|
</tbody>
|
|
320
380
|
</table>
|
|
321
381
|
</div>
|
|
322
382
|
|
|
323
383
|
<div class="panel">
|
|
324
384
|
<div class="panel-header">
|
|
325
|
-
<div class="panel-title">📁 Project
|
|
385
|
+
<div class="panel-title">📁 Project Directory Breakdown</div>
|
|
326
386
|
</div>
|
|
327
387
|
<table>
|
|
328
388
|
<thead>
|
|
329
389
|
<tr>
|
|
330
|
-
<th>Project</th>
|
|
390
|
+
<th>Project Directory</th>
|
|
331
391
|
<th>Requests</th>
|
|
332
|
-
<th>Tokens</th>
|
|
392
|
+
<th>Total Tokens</th>
|
|
333
393
|
<th>Est. Cost</th>
|
|
334
394
|
</tr>
|
|
335
395
|
</thead>
|
|
336
396
|
<tbody id="tbl-projects">
|
|
337
|
-
<tr><td colspan="4" style="text-align:center; color: var(--text-muted);">Loading
|
|
397
|
+
<tr><td colspan="4" style="text-align:center; color: var(--text-muted);">Loading project stats...</td></tr>
|
|
338
398
|
</tbody>
|
|
339
399
|
</table>
|
|
340
400
|
</div>
|
|
341
401
|
</div>
|
|
342
402
|
|
|
343
|
-
<!-- Row
|
|
403
|
+
<!-- Row 3: Recent User Prompts & Output Code Edits -->
|
|
344
404
|
<div class="dashboard-row">
|
|
345
405
|
<div class="panel">
|
|
346
406
|
<div class="panel-header">
|
|
347
|
-
<div class="panel-title">💬 Recent User
|
|
407
|
+
<div class="panel-title">💬 Recent User Prompt Log</div>
|
|
348
408
|
</div>
|
|
349
409
|
<table>
|
|
350
410
|
<thead>
|
|
@@ -355,14 +415,14 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
355
415
|
</tr>
|
|
356
416
|
</thead>
|
|
357
417
|
<tbody id="tbl-prompts">
|
|
358
|
-
<tr><td colspan="3" style="text-align:center; color: var(--text-muted);">Loading
|
|
418
|
+
<tr><td colspan="3" style="text-align:center; color: var(--text-muted);">Loading prompt log...</td></tr>
|
|
359
419
|
</tbody>
|
|
360
420
|
</table>
|
|
361
421
|
</div>
|
|
362
422
|
|
|
363
423
|
<div class="panel">
|
|
364
424
|
<div class="panel-header">
|
|
365
|
-
<div class="panel-title">🛠️
|
|
425
|
+
<div class="panel-title">🛠️ Recent Code Edits & Diff Actions</div>
|
|
366
426
|
</div>
|
|
367
427
|
<table>
|
|
368
428
|
<thead>
|
|
@@ -380,12 +440,13 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
380
440
|
</div>
|
|
381
441
|
|
|
382
442
|
<footer>
|
|
383
|
-
code-meter v0.
|
|
443
|
+
code-meter v0.4.0 | Running on <strong>http://localhost:5005</strong> | <a href="https://github.com/dhanushnayak/code-meter" target="_blank">GitHub Repository</a>
|
|
384
444
|
</footer>
|
|
385
445
|
</div>
|
|
386
446
|
|
|
387
447
|
<script>
|
|
388
448
|
function formatNumber(num) {
|
|
449
|
+
if (!num) return '0';
|
|
389
450
|
if (num >= 1000000) return (num / 1000000).toFixed(2) + 'M';
|
|
390
451
|
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
|
|
391
452
|
return num.toLocaleString();
|
|
@@ -397,7 +458,7 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
397
458
|
const d = new Date(isoStr);
|
|
398
459
|
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
399
460
|
} catch (e) {
|
|
400
|
-
return isoStr.substring(11, 16);
|
|
461
|
+
return (isoStr || '').substring(11, 16);
|
|
401
462
|
}
|
|
402
463
|
}
|
|
403
464
|
|
|
@@ -422,15 +483,38 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
422
483
|
document.getElementById('val-cache').textContent = formatNumber((s.total_cache_read_tokens || 0) + (s.total_cache_write_tokens || 0));
|
|
423
484
|
document.getElementById('val-cost').textContent = '$' + (s.total_cost || 0).toFixed(4);
|
|
424
485
|
|
|
486
|
+
// Prompt Trace Details Table (Query, Consumed Tokens, Internal Requests & Outputs)
|
|
487
|
+
const tblDetails = document.getElementById('tbl-prompt-details');
|
|
488
|
+
if (data.prompt_details && data.prompt_details.length > 0) {
|
|
489
|
+
tblDetails.innerHTML = data.prompt_details.map(pd => {
|
|
490
|
+
const outputsHtml = (pd.outputs && pd.outputs.length > 0)
|
|
491
|
+
? pd.outputs.map(o => `<div class="file-path">📄 ${o.file_path} <span class="badge badge-emerald">${o.change_type}</span></div>`).join('')
|
|
492
|
+
: '<span style="color:var(--text-muted); font-size:12px;">No code edits</span>';
|
|
493
|
+
return `
|
|
494
|
+
<tr>
|
|
495
|
+
<td style="white-space:nowrap; color:var(--text-muted);">${formatTime(pd.timestamp)}</td>
|
|
496
|
+
<td><span class="badge ${getBadgeClass(pd.provider)}">${pd.provider}</span></td>
|
|
497
|
+
<td><div class="prompt-text">${pd.prompt_text}</div></td>
|
|
498
|
+
<td><span class="badge badge-codex">${pd.internal_requests} turn${pd.internal_requests > 1 ? 's' : ''}</span></td>
|
|
499
|
+
<td><strong>${formatNumber(pd.total_tokens)}</strong></td>
|
|
500
|
+
<td><strong style="color:var(--accent-emerald);">$${(pd.cost_usd || 0).toFixed(4)}</strong></td>
|
|
501
|
+
<td>${outputsHtml}</td>
|
|
502
|
+
</tr>
|
|
503
|
+
`;
|
|
504
|
+
}).join('');
|
|
505
|
+
} else {
|
|
506
|
+
tblDetails.innerHTML = '<tr><td colspan="7" style="text-align:center; color: var(--text-muted);">No prompt trace details recorded yet</td></tr>';
|
|
507
|
+
}
|
|
508
|
+
|
|
425
509
|
// Models Table
|
|
426
510
|
const tblModels = document.getElementById('tbl-models');
|
|
427
511
|
if (data.by_model && data.by_model.length > 0) {
|
|
428
512
|
tblModels.innerHTML = data.by_model.map(m => `
|
|
429
513
|
<tr>
|
|
430
514
|
<td><strong>${m.model}</strong></td>
|
|
431
|
-
<td
|
|
515
|
+
<td>${formatNumber(m.requests)}</td>
|
|
432
516
|
<td>${formatNumber(m.total_tokens)}</td>
|
|
433
|
-
<td><strong>$${(m.total_cost || 0).toFixed(4)}</strong></td>
|
|
517
|
+
<td><strong style="color:var(--accent-emerald);">$${(m.total_cost || 0).toFixed(4)}</strong></td>
|
|
434
518
|
</tr>
|
|
435
519
|
`).join('');
|
|
436
520
|
} else {
|
|
@@ -442,10 +526,10 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
442
526
|
if (data.by_project && data.by_project.length > 0) {
|
|
443
527
|
tblProjects.innerHTML = data.by_project.map(p => `
|
|
444
528
|
<tr>
|
|
445
|
-
<td><span class="file-path">${p.
|
|
529
|
+
<td><span class="file-path">${p.project_name || 'default'}</span></td>
|
|
446
530
|
<td>${formatNumber(p.requests)}</td>
|
|
447
531
|
<td>${formatNumber(p.total_tokens)}</td>
|
|
448
|
-
<td><strong>$${(p.total_cost || 0).toFixed(4)}</strong></td>
|
|
532
|
+
<td><strong style="color:var(--accent-emerald);">$${(p.total_cost || 0).toFixed(4)}</strong></td>
|
|
449
533
|
</tr>
|
|
450
534
|
`).join('');
|
|
451
535
|
} else {
|
|
@@ -474,7 +558,7 @@ HTML_DASHBOARD = """<!DOCTYPE html>
|
|
|
474
558
|
<td style="white-space:nowrap; color:var(--text-muted);">${formatTime(c.timestamp)}</td>
|
|
475
559
|
<td><span class="badge badge-emerald">${c.change_type}</span></td>
|
|
476
560
|
<td>
|
|
477
|
-
<div class="file-path"
|
|
561
|
+
<div class="file-path">📄 ${c.file_path}</div>
|
|
478
562
|
<div class="diff-summary">${c.diff_summary}</div>
|
|
479
563
|
</td>
|
|
480
564
|
</tr>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|