tokentrace 0.10.0 → 0.10.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/CHANGELOG.md +25 -0
- package/README.md +19 -15
- package/app/debug/page.tsx +22 -1
- package/app/diagnostics/page.tsx +18 -18
- package/app/discovery/page.tsx +9 -3
- package/app/evidence/page.tsx +165 -5
- package/app/global-error.tsx +2 -2
- package/app/guide/page.tsx +469 -441
- package/app/layout.tsx +1 -1
- package/app/loading.tsx +39 -0
- package/app/models/page.tsx +16 -0
- package/app/optimisation/page.tsx +63 -49
- package/app/page.tsx +609 -514
- package/app/parser-debug/page.tsx +10 -4
- package/app/pricing/page.tsx +2 -2
- package/app/projects/page.tsx +16 -0
- package/app/repair/page.tsx +67 -17
- package/app/sessions/page.tsx +2 -2
- package/app/tools/page.tsx +16 -0
- package/bin/tokentrace.js +1 -1
- package/components/charts/rank-bar-chart.tsx +13 -13
- package/components/charts/trend-chart.tsx +13 -14
- package/components/charts/trend-section.tsx +15 -4
- package/components/charts/use-chart-size.ts +41 -0
- package/components/empty-state.tsx +37 -5
- package/components/period-filter.tsx +23 -25
- package/components/pricing-settings.tsx +8 -8
- package/components/scan-health-summary.tsx +3 -3
- package/components/scan-now-button.tsx +122 -0
- package/components/session-explorer.tsx +18 -2
- package/components/settings-panel.tsx +2 -2
- package/components/sidebar.tsx +85 -30
- package/dist/runtime/agent.mjs +7 -7
- package/dist/runtime/db-seed.mjs +5 -5
- package/dist/runtime/digest.mjs +8 -8
- package/dist/runtime/doctor.mjs +19 -19
- package/dist/runtime/evidence.mjs +1 -1
- package/dist/runtime/insights.mjs +8 -8
- package/dist/runtime/pricing-refresh.mjs +8 -8
- package/dist/runtime/report.mjs +8 -8
- package/dist/runtime/reset.mjs +5 -5
- package/dist/runtime/review.mjs +8 -8
- package/dist/runtime/roadmap.mjs +16 -3
- package/dist/runtime/scan.mjs +2 -2
- package/docs/assets/evidence-0.10.0.png +0 -0
- package/docs/assets/overview-0.10.0.png +0 -0
- package/docs/assets/repair-0.10.0.png +0 -0
- package/docs/assets/scan-health-0.10.0.png +0 -0
- package/package.json +4 -3
- package/scripts/roadmap.ts +1 -1
- package/scripts/seed-screenshot-data.ts +503 -0
- package/src/ingestion/persist.ts +1 -1
- package/src/lib/agent-discovery.ts +7 -7
- package/src/lib/cost.ts +1 -1
- package/src/lib/doctor.ts +9 -9
- package/src/lib/evidence-trail.ts +1 -1
- package/src/lib/first-run-status.ts +11 -11
- package/src/lib/pricing-manifest.ts +4 -4
- package/src/lib/pricing-refresh.ts +3 -3
- package/src/lib/project-signals.ts +1 -1
- package/src/lib/recommendations.ts +4 -4
- package/src/lib/review-queue.ts +1 -1
- package/src/lib/roadmap-status.ts +19 -2
- package/src/lib/scan-health.ts +2 -2
- package/src/lib/support-matrix.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to TokenTrace are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
No unreleased changes yet.
|
|
8
|
+
|
|
9
|
+
## [0.10.1] - 2026-05-18
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Guide now uses a manual-style layout with section navigation, compact setup status, workflow rows, command tables, and tighter troubleshooting copy.
|
|
14
|
+
- Sidebar Guide access now lives in a Help area above the version footer so it reads as documentation instead of a product feature.
|
|
15
|
+
- Guide section navigation now sticks while scrolling on desktop widths, and the standalone sidebar Guide link no longer shows redundant Help chrome.
|
|
16
|
+
- Guide Scan now controls now run the local scan directly with inline feedback instead of linking to Settings.
|
|
17
|
+
- Overview now groups processed, fresh, and cached token metrics into one Token Accounting card with direct evidence pivots.
|
|
18
|
+
- Evidence pages now include metric tabs and drill-down actions for source files, sessions, parser confidence, and model-rate or repair follow-up.
|
|
19
|
+
- User-facing model price configuration is now labeled Model Rates so it is not mistaken for TokenTrace product pricing.
|
|
20
|
+
- Overview Usage Pulse now labels current, previous, and change values explicitly inside each metric block.
|
|
21
|
+
- Overview now groups cost and sessions into one split card with a shared help tooltip, compact pane labels, and aligned trust notes/actions.
|
|
22
|
+
- Overview trend charts now default all-time views to the latest 30 days, while keeping 60-day, 90-day, and All history options available.
|
|
23
|
+
- Overview now compacts below-chart diagnostics into Review Status and Top repair items strips, with the full unknown-cost table moved to the Repair page.
|
|
24
|
+
- Evidence, repair, parser, and model-rate links now use consistent action labels: View evidence, Open repair, Set model rate, and Review parser.
|
|
25
|
+
- Sidebar navigation now shows the active page, first-run empty states point to the next useful action, direct scans return richer result feedback, and repair/evidence pages guide users through the next drill-down.
|
|
26
|
+
- Page names now stay aligned around Parsers, Discovery, Insights, Scan Health, Model Rates, and privacy-oriented Raw Data copy.
|
|
27
|
+
- User-facing diagnostic copy now uses Scan Health consistently instead of older mixed diagnostic wording.
|
|
28
|
+
- Product metadata, README, Guide, and agent discovery now point to the TokenTrace product website while creator attribution points to Abhi Yoheswaran's homepage.
|
|
29
|
+
- Period filters use a mobile-friendly preset scroller with custom dates on a compact second row, and trend controls now say Display window with a showing-latest badge.
|
|
30
|
+
- README screenshots were refreshed from a guarded public-safe screenshot database seeded by `npm run screenshots:seed`.
|
|
31
|
+
|
|
7
32
|
## [0.10.0] - 2026-05-18
|
|
8
33
|
|
|
9
34
|
### Added
|
package/README.md
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# TokenTrace CLI
|
|
6
6
|
|
|
7
|
-
Local-first
|
|
7
|
+
Local-first AI CLI usage analytics. TokenTrace scans local CLI logs, normalizes token usage, estimates missing counts, and shows cost, model, project, and session analytics in a browser dashboard.
|
|
8
8
|
|
|
9
9
|
TokenTrace is designed for local development machines first, with macOS-oriented defaults. It does not require a cloud account and does not send telemetry or logs anywhere.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
[Website](https://www.abhiyoheswaran.com/apps/tokentrace) · [Source](https://github.com/abhiyoheswaran1/tokentrace)
|
|
12
|
+
|
|
13
|
+

|
|
12
14
|
|
|
13
15
|
## Start In Seconds
|
|
14
16
|
|
|
@@ -38,7 +40,7 @@ tokentrace agent --json # Print machine-readable agent discovery manifest
|
|
|
38
40
|
tokentrace capabilities --json
|
|
39
41
|
# Alias for agent discovery manifest
|
|
40
42
|
tokentrace roadmap --json
|
|
41
|
-
# Print
|
|
43
|
+
# Print Guided Operator release implementation status
|
|
42
44
|
tokentrace scan # Scan local AI CLI usage logs
|
|
43
45
|
tokentrace doctor --json
|
|
44
46
|
# Inspect scan health and repair recommendations
|
|
@@ -112,7 +114,7 @@ curl http://127.0.0.1:3030/api/agent
|
|
|
112
114
|
curl http://127.0.0.1:3030/api/capabilities
|
|
113
115
|
```
|
|
114
116
|
|
|
115
|
-
The
|
|
117
|
+
The Guided Operator release status is also machine-readable:
|
|
116
118
|
|
|
117
119
|
```bash
|
|
118
120
|
tokentrace roadmap --json
|
|
@@ -139,6 +141,8 @@ npm run start # Serve the production build
|
|
|
139
141
|
npm run scan # Scan default and configured folders
|
|
140
142
|
npm run db:migrate # Create/update local SQLite tables
|
|
141
143
|
npm run db:seed # Seed editable provider/model prices
|
|
144
|
+
npm run screenshots:seed
|
|
145
|
+
# Seed a guarded public-safe screenshot database
|
|
142
146
|
npm run reset # Clear imported data and scan history
|
|
143
147
|
npm test # Run parser and cost tests
|
|
144
148
|
npm run verify # Run Vitest, TypeScript, and ESLint checks
|
|
@@ -148,7 +152,7 @@ npm run package:inspect
|
|
|
148
152
|
npm run smoke:packed
|
|
149
153
|
# Inspect packed tarball and smoke test packed CLI
|
|
150
154
|
tokentrace roadmap --json
|
|
151
|
-
# Inspect
|
|
155
|
+
# Inspect evidence gates and release status
|
|
152
156
|
```
|
|
153
157
|
|
|
154
158
|
Release work uses internal milestone commits until the next public minor
|
|
@@ -190,7 +194,7 @@ Default discovery checks these locations when present:
|
|
|
190
194
|
- TokenTrace wrapper logs in the local app-data directory
|
|
191
195
|
- Any custom folders configured in Settings
|
|
192
196
|
|
|
193
|
-
Use **Settings** in the dashboard to add custom folders, toggle raw message storage, and trigger scans. Use **
|
|
197
|
+
Use **Settings** in the dashboard to add custom folders, toggle raw message storage, and trigger scans. Use **Scan Health**, **Discovery**, **Parsers**, and **Raw Data** to inspect discovered files, parser decisions, warnings, failures, extracted metadata, and confidence levels.
|
|
194
198
|
|
|
195
199
|
Settings also supports optional local monthly usage guardrails. Set a cost
|
|
196
200
|
limit, token limit, or both, and Overview will show month-to-date progress from
|
|
@@ -280,15 +284,15 @@ Codex CLI status-line integration is intentionally deferred until its status-lin
|
|
|
280
284
|
|
|
281
285
|
## Screenshots
|
|
282
286
|
|
|
283
|
-
|
|
287
|
+
Dashboard views:
|
|
284
288
|
|
|
285
|
-

|
|
286
290
|
|
|
287
|
-

|
|
288
292
|
|
|
289
|
-

|
|
290
294
|
|
|
291
|
-

|
|
292
296
|
|
|
293
297
|
CLI startup and help:
|
|
294
298
|
|
|
@@ -326,9 +330,9 @@ Stop the server with `Ctrl+C` in the terminal where `tokentrace` is running.
|
|
|
326
330
|
|
|
327
331
|
See [SECURITY.md](SECURITY.md) for the full security and privacy model.
|
|
328
332
|
|
|
329
|
-
##
|
|
333
|
+
## Model Rates
|
|
330
334
|
|
|
331
|
-
Model prices change. TokenTrace ships with bundled public list prices and can refresh them from a public TokenTrace
|
|
335
|
+
Model prices change. TokenTrace ships with bundled public list prices and can refresh them from a public TokenTrace model-rate manifest. Manual edits made in **Model Rates** are preserved by future refreshes.
|
|
332
336
|
|
|
333
337
|
The bundled catalog includes common OpenAI, Anthropic, Google Gemini, xAI, DeepSeek, Mistral, and Cohere models, checked on May 8, 2026.
|
|
334
338
|
|
|
@@ -342,7 +346,7 @@ Seed sources:
|
|
|
342
346
|
- [Mistral model docs](https://docs.mistral.ai/models)
|
|
343
347
|
- [Cohere pricing](https://cohere.com/pricing)
|
|
344
348
|
|
|
345
|
-
Review and update
|
|
349
|
+
Review and update rates in **Model Rates** before treating cost estimates as financial truth, especially if you use batch processing, priority/flex modes, data residency, long-context surcharges, subscriptions, or provider-specific discounts.
|
|
346
350
|
|
|
347
351
|
Refresh from the dashboard or from the CLI:
|
|
348
352
|
|
|
@@ -411,7 +415,7 @@ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for local setu
|
|
|
411
415
|
|
|
412
416
|
## License
|
|
413
417
|
|
|
414
|
-
Open source by [Abhi Yoheswaran](https://
|
|
418
|
+
Open source by [Abhi Yoheswaran](https://www.abhiyoheswaran.com). Released under the MIT License. See `LICENSE`.
|
|
415
419
|
|
|
416
420
|
## Next Improvements
|
|
417
421
|
|
package/app/debug/page.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Badge } from "@/components/ui/badge";
|
|
2
|
+
import { EmptyState } from "@/components/empty-state";
|
|
3
|
+
import { ScanNowButton } from "@/components/scan-now-button";
|
|
2
4
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
3
5
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
4
6
|
import { MonoText, PageHeader } from "@/components/ui/typography";
|
|
@@ -21,9 +23,26 @@ export default function DebugPage() {
|
|
|
21
23
|
<div className="space-y-6">
|
|
22
24
|
<PageHeader
|
|
23
25
|
title="Raw Data"
|
|
24
|
-
description="
|
|
26
|
+
description="Local raw data for scan runs, parser selection, imported records, warnings, and failures."
|
|
25
27
|
/>
|
|
26
28
|
|
|
29
|
+
<div className="rounded-md border bg-card p-3 text-sm leading-6 text-muted-foreground">
|
|
30
|
+
<span className="font-medium text-foreground">Local raw data:</span>{" "}
|
|
31
|
+
Treat file paths and parser metadata as local sensitive data. This page is for debugging imports, not sharing screenshots.
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
{!data.scanRuns.length && !data.scanFiles.length ? (
|
|
35
|
+
<EmptyState
|
|
36
|
+
title="No raw scan data yet"
|
|
37
|
+
description="Run a scan to populate local raw data. If nothing appears after a scan, open Scan Health to review roots and parser status."
|
|
38
|
+
actions={[
|
|
39
|
+
{ label: "Open Scan Health", href: "/diagnostics", variant: "outline" }
|
|
40
|
+
]}
|
|
41
|
+
>
|
|
42
|
+
<ScanNowButton size="sm" />
|
|
43
|
+
</EmptyState>
|
|
44
|
+
) : (
|
|
45
|
+
<>
|
|
27
46
|
<Card>
|
|
28
47
|
<CardHeader>
|
|
29
48
|
<CardTitle>Recent Scan Runs</CardTitle>
|
|
@@ -97,6 +116,8 @@ export default function DebugPage() {
|
|
|
97
116
|
</Table>
|
|
98
117
|
</CardContent>
|
|
99
118
|
</Card>
|
|
119
|
+
</>
|
|
120
|
+
)}
|
|
100
121
|
</div>
|
|
101
122
|
);
|
|
102
123
|
}
|
package/app/diagnostics/page.tsx
CHANGED
|
@@ -43,7 +43,7 @@ function TrustChecklist({
|
|
|
43
43
|
const hasInteractions = data.confidence.interactions > 0;
|
|
44
44
|
const unknownCauses = data.health.costCoverage.unknownCauses;
|
|
45
45
|
const unknownCauseText = [
|
|
46
|
-
unknownCauses.missingPricing > 0 ? `${unknownCauses.missingPricing.toLocaleString()} missing
|
|
46
|
+
unknownCauses.missingPricing > 0 ? `${unknownCauses.missingPricing.toLocaleString()} missing model rate` : null,
|
|
47
47
|
unknownCauses.missingModelName > 0 ? `${unknownCauses.missingModelName.toLocaleString()} missing model` : null,
|
|
48
48
|
unknownCauses.missingTokenCount > 0 ? `${unknownCauses.missingTokenCount.toLocaleString()} missing token count` : null,
|
|
49
49
|
unknownCauses.other > 0 ? `${unknownCauses.other.toLocaleString()} other` : null
|
|
@@ -51,11 +51,11 @@ function TrustChecklist({
|
|
|
51
51
|
|
|
52
52
|
const items: Array<{ label: string; detail: string; status: ChecklistStatus }> = [
|
|
53
53
|
{
|
|
54
|
-
label: "
|
|
54
|
+
label: "Model rates loaded",
|
|
55
55
|
status: data.pricedModelCount > 0 ? "pass" : "warn",
|
|
56
56
|
detail: data.pricedModelCount > 0
|
|
57
|
-
? `${data.pricedModelCount.toLocaleString()}
|
|
58
|
-
: "Seed
|
|
57
|
+
? `${data.pricedModelCount.toLocaleString()} rated models are available.`
|
|
58
|
+
: "Seed model rates before trusting cost totals."
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
label: "CLI roots found",
|
|
@@ -77,10 +77,10 @@ function TrustChecklist({
|
|
|
77
77
|
detail: latest ? `${latest.recordsImported.toLocaleString()} interactions imported in the latest scan.` : "No scan has imported records yet."
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
|
-
label: "Unknown
|
|
80
|
+
label: "Unknown cost",
|
|
81
81
|
status: !hasInteractions ? "pending" : data.health.costCoverage.unknown > 0 ? "warn" : "pass",
|
|
82
82
|
detail: !hasInteractions
|
|
83
|
-
? "
|
|
83
|
+
? "Model-rate coverage appears after records are imported."
|
|
84
84
|
: data.health.costCoverage.unknown > 0
|
|
85
85
|
? `${data.health.costCoverage.unknown.toLocaleString()} interactions need repair: ${unknownCauseText || "cause unavailable"}.`
|
|
86
86
|
: "Imported interactions have usable cost coverage."
|
|
@@ -153,9 +153,9 @@ function DoctorReportPanel({ report }: { report: DoctorReport }) {
|
|
|
153
153
|
return (
|
|
154
154
|
<Card>
|
|
155
155
|
<CardHeader>
|
|
156
|
-
<CardTitle>
|
|
156
|
+
<CardTitle>Scan Health report</CardTitle>
|
|
157
157
|
<CardDescription>
|
|
158
|
-
|
|
158
|
+
The same local Scan Health data returned by `tokentrace doctor --json`.
|
|
159
159
|
</CardDescription>
|
|
160
160
|
</CardHeader>
|
|
161
161
|
<CardContent className="space-y-4">
|
|
@@ -217,7 +217,7 @@ function DoctorReportPanel({ report }: { report: DoctorReport }) {
|
|
|
217
217
|
</div>
|
|
218
218
|
|
|
219
219
|
<div className="space-y-2">
|
|
220
|
-
<div className="text-sm font-semibold">
|
|
220
|
+
<div className="text-sm font-semibold">Recommended fixes</div>
|
|
221
221
|
<div className="grid gap-2 lg:grid-cols-2">
|
|
222
222
|
{report.recommendations.slice(0, 6).map((item) => (
|
|
223
223
|
<Link key={item.id} href={item.href ?? "/diagnostics"} className="border-t p-3 transition-colors hover:bg-muted/40">
|
|
@@ -236,7 +236,7 @@ function DoctorReportPanel({ report }: { report: DoctorReport }) {
|
|
|
236
236
|
|
|
237
237
|
<div className="space-y-3">
|
|
238
238
|
<div>
|
|
239
|
-
<div className="text-sm font-semibold">
|
|
239
|
+
<div className="text-sm font-semibold">Supported file types</div>
|
|
240
240
|
<div className="mt-1 text-xs text-muted-foreground">
|
|
241
241
|
{report.supportMatrix.summary.stable.toLocaleString()} stable,{" "}
|
|
242
242
|
{report.supportMatrix.summary.bestEffort.toLocaleString()} best-effort,{" "}
|
|
@@ -267,7 +267,7 @@ function ParserTrustPanel({ report }: { report: DoctorReport["parserTrust"] }) {
|
|
|
267
267
|
return (
|
|
268
268
|
<Card>
|
|
269
269
|
<CardHeader>
|
|
270
|
-
<CardTitle>
|
|
270
|
+
<CardTitle>File parser review</CardTitle>
|
|
271
271
|
<CardDescription>
|
|
272
272
|
Latest scan files grouped by parser, source family, version, status, and import yield. Ignored files are known support files, not usage transcripts. Unsupported files need parser review before they become usage.
|
|
273
273
|
</CardDescription>
|
|
@@ -342,7 +342,7 @@ function ScanDiffPanel({ report }: { report: DoctorReport["scanDiff"] }) {
|
|
|
342
342
|
return (
|
|
343
343
|
<Card>
|
|
344
344
|
<CardHeader>
|
|
345
|
-
<CardTitle>Scan
|
|
345
|
+
<CardTitle>Scan history comparison</CardTitle>
|
|
346
346
|
<CardDescription>
|
|
347
347
|
Latest scan compared with the previous scan using deterministic scan ordering. Ignored files are known support files, not usage transcripts.
|
|
348
348
|
</CardDescription>
|
|
@@ -472,8 +472,8 @@ export default async function DiagnosticsPage() {
|
|
|
472
472
|
return (
|
|
473
473
|
<div className="space-y-6">
|
|
474
474
|
<PageHeader
|
|
475
|
-
title="Scan
|
|
476
|
-
description="
|
|
475
|
+
title="Scan Health"
|
|
476
|
+
description="Shows whether local usage was imported, which files need review, and whether model-rate coverage is usable."
|
|
477
477
|
/>
|
|
478
478
|
|
|
479
479
|
<TrustChecklist data={data} rootCount={roots.length} />
|
|
@@ -481,7 +481,7 @@ export default async function DiagnosticsPage() {
|
|
|
481
481
|
<Card>
|
|
482
482
|
<CardHeader>
|
|
483
483
|
<CardTitle>Local recommendations</CardTitle>
|
|
484
|
-
<CardDescription>Deterministic next actions from local scan,
|
|
484
|
+
<CardDescription>Deterministic next actions from local scan, model rates, parser, project, and cache data.</CardDescription>
|
|
485
485
|
</CardHeader>
|
|
486
486
|
<CardContent className="grid divide-y overflow-hidden p-0 lg:grid-cols-3 lg:divide-x lg:divide-y-0">
|
|
487
487
|
{analytics.recommendations.slice(0, 3).map((item) => (
|
|
@@ -513,12 +513,12 @@ export default async function DiagnosticsPage() {
|
|
|
513
513
|
{[
|
|
514
514
|
{
|
|
515
515
|
href: "/discovery",
|
|
516
|
-
title: "
|
|
516
|
+
title: "Discovered files",
|
|
517
517
|
description: "Inspect which local files were discovered, skipped, imported, or unsupported."
|
|
518
518
|
},
|
|
519
519
|
{
|
|
520
520
|
href: "/parser-debug",
|
|
521
|
-
title: "Parser
|
|
521
|
+
title: "Parser review",
|
|
522
522
|
description: "Review adapter selection, parser confidence, warnings, errors, and extracted metadata."
|
|
523
523
|
},
|
|
524
524
|
{
|
|
@@ -543,7 +543,7 @@ export default async function DiagnosticsPage() {
|
|
|
543
543
|
|
|
544
544
|
<Card>
|
|
545
545
|
<CardHeader>
|
|
546
|
-
<CardTitle>
|
|
546
|
+
<CardTitle>Local privacy rules</CardTitle>
|
|
547
547
|
<CardDescription>TokenTrace uses direct local filesystem ingestion as the primary architecture.</CardDescription>
|
|
548
548
|
</CardHeader>
|
|
549
549
|
<CardContent className="flex flex-wrap gap-2">
|
package/app/discovery/page.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|
|
3
3
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
4
4
|
import { DataValue, FieldLabel, MonoText, PageHeader } from "@/components/ui/typography";
|
|
5
5
|
import { EmptyState } from "@/components/empty-state";
|
|
6
|
+
import { ScanNowButton } from "@/components/scan-now-button";
|
|
6
7
|
import { getScanTrustData } from "@/src/lib/analytics";
|
|
7
8
|
import { formatDate } from "@/src/lib/format";
|
|
8
9
|
|
|
@@ -27,7 +28,7 @@ export default function DiscoveryPage() {
|
|
|
27
28
|
return (
|
|
28
29
|
<div className="space-y-6">
|
|
29
30
|
<PageHeader
|
|
30
|
-
title="
|
|
31
|
+
title="Discovery"
|
|
31
32
|
description="Every file shown here was discovered by passive local filesystem scanning."
|
|
32
33
|
/>
|
|
33
34
|
<div className="grid overflow-hidden rounded-md border bg-card sm:grid-cols-2 lg:grid-cols-5">
|
|
@@ -55,8 +56,13 @@ export default function DiscoveryPage() {
|
|
|
55
56
|
{!scanFiles.length ? (
|
|
56
57
|
<EmptyState
|
|
57
58
|
title="No files discovered yet"
|
|
58
|
-
description="Run a scan from Settings to populate
|
|
59
|
-
|
|
59
|
+
description="Run a scan from Settings to populate Discovery. If expected folders are missing, add them in Settings."
|
|
60
|
+
actions={[
|
|
61
|
+
{ label: "Add folder", href: "/settings", variant: "outline" }
|
|
62
|
+
]}
|
|
63
|
+
>
|
|
64
|
+
<ScanNowButton size="sm" />
|
|
65
|
+
</EmptyState>
|
|
60
66
|
) : (
|
|
61
67
|
<Card>
|
|
62
68
|
<CardHeader>
|
package/app/evidence/page.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
2
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
3
|
+
import { EmptyState } from "@/components/empty-state";
|
|
4
|
+
import { ScanNowButton } from "@/components/scan-now-button";
|
|
3
5
|
import { PeriodFilter } from "@/components/period-filter";
|
|
4
6
|
import { Badge } from "@/components/ui/badge";
|
|
5
7
|
import { Button } from "@/components/ui/button";
|
|
@@ -7,8 +9,9 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|
|
7
9
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
8
10
|
import { DataValue, FieldLabel, MonoText, PageHeader } from "@/components/ui/typography";
|
|
9
11
|
import { dateRangeQueryParams, mergeHrefParams, resolveDateRange } from "@/src/lib/date-range";
|
|
10
|
-
import { buildEvidenceTrail, parseEvidenceMetric } from "@/src/lib/evidence-trail";
|
|
12
|
+
import { buildEvidenceTrail, parseEvidenceMetric, type EvidenceMetric } from "@/src/lib/evidence-trail";
|
|
11
13
|
import { formatCurrency, formatExactTokens, percent } from "@/src/lib/format";
|
|
14
|
+
import { cn } from "@/src/lib/utils";
|
|
12
15
|
|
|
13
16
|
export const dynamic = "force-dynamic";
|
|
14
17
|
|
|
@@ -29,6 +32,100 @@ function parserStatusVariant(value: string | null) {
|
|
|
29
32
|
return "outline";
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
const evidenceMetricTabs: Array<{ metric: EvidenceMetric; label: string }> = [
|
|
36
|
+
{ metric: "processed-tokens", label: "Processed" },
|
|
37
|
+
{ metric: "non-cache-tokens", label: "Fresh / non-cache" },
|
|
38
|
+
{ metric: "cached-tokens", label: "Cache" },
|
|
39
|
+
{ metric: "estimated-cost", label: "Cost" },
|
|
40
|
+
{ metric: "sessions", label: "Sessions" }
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function EvidenceMetricTabs({
|
|
44
|
+
current,
|
|
45
|
+
rangeLinkParams
|
|
46
|
+
}: {
|
|
47
|
+
current: EvidenceMetric;
|
|
48
|
+
rangeLinkParams: Record<string, string | undefined>;
|
|
49
|
+
}) {
|
|
50
|
+
return (
|
|
51
|
+
<nav className="flex flex-wrap items-center gap-2" aria-label="Evidence metric views">
|
|
52
|
+
{evidenceMetricTabs.map((item) => (
|
|
53
|
+
<Link
|
|
54
|
+
key={item.metric}
|
|
55
|
+
href={mergeHrefParams(`/evidence?metric=${item.metric}`, rangeLinkParams)}
|
|
56
|
+
className={cn(
|
|
57
|
+
"inline-flex h-8 items-center rounded-md border px-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
58
|
+
current === item.metric
|
|
59
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
60
|
+
: "border-border bg-card text-foreground hover:bg-muted"
|
|
61
|
+
)}
|
|
62
|
+
aria-current={current === item.metric ? "page" : undefined}
|
|
63
|
+
>
|
|
64
|
+
{item.label}
|
|
65
|
+
</Link>
|
|
66
|
+
))}
|
|
67
|
+
</nav>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function EvidenceDrilldownStrip({
|
|
72
|
+
actions
|
|
73
|
+
}: {
|
|
74
|
+
actions: Array<{
|
|
75
|
+
label: string;
|
|
76
|
+
detail: string;
|
|
77
|
+
href: string;
|
|
78
|
+
}>;
|
|
79
|
+
}) {
|
|
80
|
+
return (
|
|
81
|
+
<div className="grid overflow-hidden rounded-md border border-border md:grid-cols-4">
|
|
82
|
+
{actions.map((action, index) => (
|
|
83
|
+
<Link
|
|
84
|
+
key={action.label}
|
|
85
|
+
href={action.href}
|
|
86
|
+
className={cn(
|
|
87
|
+
"group min-w-0 p-3 transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
88
|
+
index > 0 ? "border-t border-border md:border-l md:border-t-0" : ""
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
<span className="flex items-center gap-1.5 text-sm font-semibold text-foreground">
|
|
92
|
+
{action.label}
|
|
93
|
+
<ArrowRight className="h-3.5 w-3.5 text-primary transition-transform group-hover:translate-x-0.5" aria-hidden="true" />
|
|
94
|
+
</span>
|
|
95
|
+
<span className="mt-1 block text-xs leading-5 text-muted-foreground">{action.detail}</span>
|
|
96
|
+
</Link>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function WhyThisNumberPanel({ title }: { title: string }) {
|
|
103
|
+
const items = [
|
|
104
|
+
["Metric definition", `${title} uses the selected period, metric type, and imported local interactions.`],
|
|
105
|
+
["Source files explain", "Source files explain which local artifacts contributed records to this number."],
|
|
106
|
+
["Sessions explain", "Sessions explain which conversations, tools, models, and projects produced the total."],
|
|
107
|
+
["Parser confidence explains", "Parser confidence explains whether imported values are exact, estimated, or need review."],
|
|
108
|
+
["Model-rate state explains", "Model-rate state explains whether cost is exact, estimated, or blocked by unknown pricing."]
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Card>
|
|
113
|
+
<CardHeader>
|
|
114
|
+
<CardTitle>Why this number</CardTitle>
|
|
115
|
+
<CardDescription>Use these checks before treating the metric as a final answer.</CardDescription>
|
|
116
|
+
</CardHeader>
|
|
117
|
+
<CardContent className="grid gap-0 overflow-x-auto p-0 sm:grid-cols-2 xl:grid-cols-5">
|
|
118
|
+
{items.map(([label, detail], index) => (
|
|
119
|
+
<div key={label} className={cn("min-w-48 p-3", index > 0 ? "border-t sm:border-l sm:border-t-0" : "")}>
|
|
120
|
+
<FieldLabel>{label}</FieldLabel>
|
|
121
|
+
<p className="mt-2 text-xs leading-5 text-muted-foreground">{detail}</p>
|
|
122
|
+
</div>
|
|
123
|
+
))}
|
|
124
|
+
</CardContent>
|
|
125
|
+
</Card>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
32
129
|
export default async function EvidencePage({ searchParams }: EvidencePageProps) {
|
|
33
130
|
const params = (await searchParams) ?? {};
|
|
34
131
|
const range = resolveDateRange(params);
|
|
@@ -39,6 +136,32 @@ export default async function EvidencePage({ searchParams }: EvidencePageProps)
|
|
|
39
136
|
const currentEvidenceHref = mergeHrefParams(`/evidence?metric=${trail.metric}`, rangeLinkParams);
|
|
40
137
|
const pricingReturnParams = { returnTo: currentEvidenceHref };
|
|
41
138
|
const confidenceTotal = Math.max(1, trail.confidence.exact + trail.confidence.estimated + trail.confidence.unknown);
|
|
139
|
+
const leadingSource = trail.sourceFiles[0];
|
|
140
|
+
const leadingSession = trail.sessions[0];
|
|
141
|
+
const drilldownActions = [
|
|
142
|
+
{
|
|
143
|
+
label: "Top source files",
|
|
144
|
+
detail: "Compare the local files contributing most to this metric.",
|
|
145
|
+
href: "#top-source-files"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
label: "Largest sessions",
|
|
149
|
+
detail: "Open the session evidence table and continue into filtered sessions.",
|
|
150
|
+
href: "#session-evidence"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
label: "Parser confidence",
|
|
154
|
+
detail: "Check whether parser status affects the imported records.",
|
|
155
|
+
href: leadingSource ? mergeHrefParams(leadingSource.parserHref, rangeLinkParams) : "/parser-debug"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
label: "Model rates / repair",
|
|
159
|
+
detail: "Follow provider model rates or unknown-cost repair when cost needs review.",
|
|
160
|
+
href: leadingSession?.pricingHref
|
|
161
|
+
? mergeHrefParams(leadingSession.pricingHref, pricingReturnParams)
|
|
162
|
+
: mergeHrefParams("/repair", rangeLinkParams)
|
|
163
|
+
}
|
|
164
|
+
];
|
|
42
165
|
|
|
43
166
|
return (
|
|
44
167
|
<div className="space-y-6">
|
|
@@ -57,6 +180,43 @@ export default async function EvidencePage({ searchParams }: EvidencePageProps)
|
|
|
57
180
|
|
|
58
181
|
<PeriodFilter range={range} />
|
|
59
182
|
|
|
183
|
+
<Card>
|
|
184
|
+
<CardHeader className="space-y-4">
|
|
185
|
+
<div className="space-y-1">
|
|
186
|
+
<CardTitle>Token Evidence</CardTitle>
|
|
187
|
+
<CardDescription>
|
|
188
|
+
You are viewing {trail.title}. Pivot across related metrics, then continue into source files, sessions, parser status, or model-rate repair.
|
|
189
|
+
</CardDescription>
|
|
190
|
+
</div>
|
|
191
|
+
<EvidenceMetricTabs current={trail.metric} rangeLinkParams={rangeLinkParams} />
|
|
192
|
+
</CardHeader>
|
|
193
|
+
<CardContent className="space-y-3">
|
|
194
|
+
<p className="text-sm leading-6 text-muted-foreground">
|
|
195
|
+
<span className="font-medium text-foreground">Definition:</span>{" "}
|
|
196
|
+
{trail.description}
|
|
197
|
+
</p>
|
|
198
|
+
<div className="space-y-2">
|
|
199
|
+
<FieldLabel>Continue drilling down</FieldLabel>
|
|
200
|
+
<EvidenceDrilldownStrip actions={drilldownActions} />
|
|
201
|
+
</div>
|
|
202
|
+
</CardContent>
|
|
203
|
+
</Card>
|
|
204
|
+
|
|
205
|
+
<WhyThisNumberPanel title={trail.title} />
|
|
206
|
+
|
|
207
|
+
{!trail.sessions.length && !trail.sourceFiles.length ? (
|
|
208
|
+
<EmptyState
|
|
209
|
+
title="No evidence for this metric yet"
|
|
210
|
+
description="Run a scan or open sessions to confirm whether local usage exists for the selected period and metric."
|
|
211
|
+
actions={[
|
|
212
|
+
{ label: "Open sessions", href: "/sessions", variant: "outline" },
|
|
213
|
+
{ label: "Open Scan Health", href: "/diagnostics", variant: "outline" }
|
|
214
|
+
]}
|
|
215
|
+
>
|
|
216
|
+
<ScanNowButton size="sm" />
|
|
217
|
+
</EmptyState>
|
|
218
|
+
) : null}
|
|
219
|
+
|
|
60
220
|
<Card>
|
|
61
221
|
<CardHeader>
|
|
62
222
|
<CardTitle>Metric Totals</CardTitle>
|
|
@@ -113,7 +273,7 @@ export default async function EvidencePage({ searchParams }: EvidencePageProps)
|
|
|
113
273
|
</CardContent>
|
|
114
274
|
</Card>
|
|
115
275
|
|
|
116
|
-
<Card>
|
|
276
|
+
<Card id="top-source-files">
|
|
117
277
|
<CardHeader>
|
|
118
278
|
<CardTitle>Top Source Files</CardTitle>
|
|
119
279
|
<CardDescription>Largest contributing local files for the same metric definition.</CardDescription>
|
|
@@ -168,9 +328,9 @@ export default async function EvidencePage({ searchParams }: EvidencePageProps)
|
|
|
168
328
|
</Card>
|
|
169
329
|
</div>
|
|
170
330
|
|
|
171
|
-
<Card>
|
|
331
|
+
<Card id="session-evidence">
|
|
172
332
|
<CardHeader>
|
|
173
|
-
<CardTitle>Session, Source, Parser, And
|
|
333
|
+
<CardTitle>Session, Source, Parser, And Model Rate Evidence</CardTitle>
|
|
174
334
|
<CardDescription>
|
|
175
335
|
The table is capped at the top 100 contributing sessions; totals above include the full metric set.
|
|
176
336
|
</CardDescription>
|
|
@@ -184,7 +344,7 @@ export default async function EvidencePage({ searchParams }: EvidencePageProps)
|
|
|
184
344
|
<TableHead>Cost</TableHead>
|
|
185
345
|
<TableHead>Source</TableHead>
|
|
186
346
|
<TableHead>Parser</TableHead>
|
|
187
|
-
<TableHead>
|
|
347
|
+
<TableHead>Model rates</TableHead>
|
|
188
348
|
<TableHead>Confidence</TableHead>
|
|
189
349
|
</TableRow>
|
|
190
350
|
</TableHeader>
|
package/app/global-error.tsx
CHANGED
|
@@ -18,7 +18,7 @@ export default function GlobalError({
|
|
|
18
18
|
<div className="text-sm font-medium text-muted-foreground">TokenTrace error</div>
|
|
19
19
|
<h1 className="text-2xl font-semibold leading-tight tracking-normal">Something went wrong</h1>
|
|
20
20
|
<p className="text-sm leading-6 text-muted-foreground">
|
|
21
|
-
The local dashboard could not render this view. Try again, then
|
|
21
|
+
The local dashboard could not render this view. Try again, then open Scan Health if the issue repeats.
|
|
22
22
|
</p>
|
|
23
23
|
{error.digest ? (
|
|
24
24
|
<p className="font-mono text-xs text-muted-foreground">Digest: {error.digest}</p>
|
|
@@ -28,7 +28,7 @@ export default function GlobalError({
|
|
|
28
28
|
Try again
|
|
29
29
|
</Button>
|
|
30
30
|
<Button asChild variant="outline">
|
|
31
|
-
<Link href="/diagnostics">Open
|
|
31
|
+
<Link href="/diagnostics">Open Scan Health</Link>
|
|
32
32
|
</Button>
|
|
33
33
|
</div>
|
|
34
34
|
</div>
|