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.
Files changed (46) hide show
  1. {code_meter-0.4.0 → code_meter-0.5.0}/PKG-INFO +1 -1
  2. {code_meter-0.4.0 → code_meter-0.5.0}/pyproject.toml +1 -1
  3. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/storage/repository.py +82 -0
  4. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/web/server.py +2 -0
  5. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/web/static.py +167 -83
  6. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/PKG-INFO +1 -1
  7. {code_meter-0.4.0 → code_meter-0.5.0}/README.md +0 -0
  8. {code_meter-0.4.0 → code_meter-0.5.0}/setup.cfg +0 -0
  9. {code_meter-0.4.0 → code_meter-0.5.0}/setup.py +0 -0
  10. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/__init__.py +0 -0
  11. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/__init__.py +0 -0
  12. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/costs.py +0 -0
  13. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/reports.py +0 -0
  14. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/analytics/tokens.py +0 -0
  15. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/cli.py +0 -0
  16. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/config.py +0 -0
  17. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/models/__init__.py +0 -0
  18. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/models/project.py +0 -0
  19. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/models/usage.py +0 -0
  20. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/__init__.py +0 -0
  21. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/anthropic.py +0 -0
  22. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/engine.py +0 -0
  23. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/google.py +0 -0
  24. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/live.py +0 -0
  25. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/pricing/openai.py +0 -0
  26. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/__init__.py +0 -0
  27. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/antigravity.py +0 -0
  28. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/base.py +0 -0
  29. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/claude_code.py +0 -0
  30. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/providers/codex.py +0 -0
  31. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/storage/__init__.py +0 -0
  32. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/storage/database.py +0 -0
  33. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/__init__.py +0 -0
  34. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/dashboard.py +0 -0
  35. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/tables.py +0 -0
  36. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/ui/web/__init__.py +0 -0
  37. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter/watcher.py +0 -0
  38. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/SOURCES.txt +0 -0
  39. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/dependency_links.txt +0 -0
  40. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/entry_points.txt +0 -0
  41. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/requires.txt +0 -0
  42. {code_meter-0.4.0 → code_meter-0.5.0}/src/code_meter.egg-info/top_level.txt +0 -0
  43. {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_analytics.py +0 -0
  44. {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_database.py +0 -0
  45. {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_pricing.py +0 -0
  46. {code_meter-0.4.0 → code_meter-0.5.0}/tests/test_scanner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-meter
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Local AI coding token usage tracker and cost estimator for Claude Code and future agents.
5
5
  Author-email: Dhanush Nayak <dhanushnayak.ram@mail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-meter"
7
- version = "0.4.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: #0f172a;
12
- --card-bg: rgba(30, 41, 59, 0.7);
13
- --border-color: rgba(255, 255, 255, 0.1);
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
- --shadow-glow: 0 10px 25px -5px rgba(56, 189, 248, 0.15);
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, Helvetica, Arial, sans-serif;
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.08) 0px, transparent 50%),
37
- radial-gradient(at 100% 100%, rgba(192, 132, 252, 0.08) 0px, transparent 50%);
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: 1300px;
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: 16px;
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: 12px;
63
+ gap: 14px;
58
64
  }
59
65
 
60
66
  .logo-badge {
61
- background: linear-gradient(135deg, #0284c7, #7e22ce);
62
- width: 42px;
63
- height: 42px;
64
- border-radius: 10px;
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: 20px;
70
- box-shadow: 0 4px 12px rgba(126, 34, 206, 0.3);
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: 24px;
75
- font-weight: 700;
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: 8px;
88
- background: rgba(52, 211, 153, 0.1);
89
- border: 1px solid rgba(52, 211, 153, 0.2);
90
- padding: 6px 14px;
91
- border-radius: 20px;
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: 8px;
98
- height: 8px;
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(200px, 1fr));
115
- gap: 16px;
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(12px);
132
+ backdrop-filter: blur(16px);
122
133
  border: 1px solid var(--border-color);
123
- border-radius: 14px;
124
- padding: 20px;
125
- transition: transform 0.2s, box-shadow 0.2s;
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(-2px);
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.5px;
149
+ letter-spacing: 0.8px;
137
150
  color: var(--text-muted);
138
- margin-bottom: 8px;
151
+ margin-bottom: 10px;
139
152
  }
140
153
 
141
154
  .metric-value {
142
- font-size: 26px;
143
- font-weight: 700;
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(12px);
185
+ backdrop-filter: blur(16px);
168
186
  border: 1px solid var(--border-color);
169
- border-radius: 16px;
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: 18px;
196
+ margin-bottom: 20px;
178
197
  }
179
198
 
180
199
  .panel-title {
181
200
  font-size: 16px;
182
- font-weight: 600;
201
+ font-weight: 700;
183
202
  display: flex;
184
203
  align-items: center;
185
- gap: 8px;
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: 10px 12px;
216
+ padding: 12px 14px;
198
217
  color: var(--text-muted);
199
- font-weight: 500;
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: 12px;
207
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
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-block;
216
- padding: 3px 8px;
217
- border-radius: 6px;
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: 600;
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.15); color: #fb923c; border: 1px solid rgba(249, 115, 22, 0.3); }
224
- .badge-codex { background: rgba(56, 189, 248, 0.15); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.3); }
225
- .badge-antigravity { background: rgba(192, 132, 252, 0.15); color: #c084fc; border: 1px solid rgba(192, 132, 252, 0.3); }
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: #e2e8f0;
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: var(--accent-emerald);
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: 20px;
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">Local AI Coding Token & Cost Dashboard</div>
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: Model & Project Breakdown -->
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 Breakdown & Costs</div>
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>Provider</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 data...</td></tr>
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 Usage Breakdown</div>
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 data...</td></tr>
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 2: User Prompts & Code Edits -->
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 Queries & Prompts</div>
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 prompts...</td></tr>
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">🛠️ Output Code Edits & Actions</div>
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.3.0 | Running on <strong>http://localhost:5005</strong> | <a href="https://github.com/dhanushnayak/code-meter" target="_blank">GitHub Repository</a>
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><span class="badge ${getBadgeClass(m.provider)}">${m.provider}</span></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.project_id || 'default'}</span></td>
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">${c.file_path}</div>
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>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-meter
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Local AI coding token usage tracker and cost estimator for Claude Code and future agents.
5
5
  Author-email: Dhanush Nayak <dhanushnayak.ram@mail.com>
6
6
  License: MIT
File without changes
File without changes
File without changes