tokentrace 0.5.1 → 0.7.0
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 +53 -1
- package/README.md +44 -6
- package/SECURITY.md +6 -3
- package/app/api/settings/route.ts +3 -2
- package/app/diagnostics/page.tsx +32 -0
- package/app/optimisation/page.tsx +55 -2
- package/app/page.tsx +150 -7
- package/app/projects/page.tsx +51 -0
- package/app/sessions/page.tsx +55 -0
- package/bin/tokentrace.js +105 -7
- package/components/period-filter.tsx +19 -17
- package/components/settings-panel.tsx +75 -11
- package/dist/runtime/db-migrate.mjs +1 -0
- package/dist/runtime/db-seed.mjs +13 -0
- package/dist/runtime/digest.mjs +2227 -0
- package/dist/runtime/doctor.mjs +157 -1
- package/dist/runtime/insights.mjs +588 -73
- package/dist/runtime/pricing-refresh.mjs +13 -0
- package/dist/runtime/reset.mjs +13 -0
- package/dist/runtime/scan.mjs +40 -4
- package/dist/runtime/status.mjs +1 -0
- package/docs/assets/doctor-0.6.0.png +0 -0
- package/docs/assets/overview-0.6.0.png +0 -0
- package/docs/assets/overview-0.7.0.png +0 -0
- package/docs/assets/projects-0.7.0.png +0 -0
- package/docs/assets/sessions-0.7.0.png +0 -0
- package/docs/assets/settings-guardrails-0.7.0.png +0 -0
- package/docs/assets/settings-package-trust-0.6.0.png +0 -0
- package/docs/assets/usage-intelligence-0.7.0.png +0 -0
- package/next.config.mjs +5 -0
- package/package.json +17 -18
- package/scripts/build-cli-runtime.mjs +1 -0
- package/scripts/digest.ts +27 -0
- package/scripts/doctor.ts +2 -0
- package/scripts/insights.ts +2 -1
- package/scripts/package-inspect.mjs +51 -38
- package/scripts/smoke-cli.mjs +182 -0
- package/scripts/smoke-packed-install.mjs +180 -0
- package/src/db/client.ts +1 -0
- package/src/db/settings.ts +31 -2
- package/src/ingestion/adapters/claude-code.ts +2 -1
- package/src/ingestion/adapters/codex-cli.ts +2 -1
- package/src/lib/analytics.ts +29 -0
- package/src/lib/daily-digest.ts +101 -0
- package/src/lib/doctor.ts +17 -0
- package/src/lib/first-run-status.ts +128 -0
- package/src/lib/model-aliases.ts +14 -0
- package/src/lib/project-signals.ts +139 -0
- package/src/lib/recommendations.ts +36 -0
- package/src/lib/review-queue.ts +222 -0
- package/src/lib/scan-health.ts +67 -1
- package/src/lib/session-comparison.ts +94 -0
- package/src/lib/support-matrix.ts +113 -0
- package/src/lib/usage-guardrails.ts +113 -0
- package/.next/BUILD_ID +0 -1
- package/.next/app-build-manifest.json +0 -192
- package/.next/app-path-routes-manifest.json +0 -24
- package/.next/build-manifest.json +0 -33
- package/.next/export-marker.json +0 -6
- package/.next/images-manifest.json +0 -58
- package/.next/next-minimal-server.js.nft.json +0 -1
- package/.next/next-server.js.nft.json +0 -1
- package/.next/package.json +0 -1
- package/.next/prerender-manifest.json +0 -66
- package/.next/react-loadable-manifest.json +0 -1
- package/.next/required-server-files.json +0 -323
- package/.next/routes-manifest.json +0 -125
- package/.next/server/app/_not-found/page.js +0 -1111
- package/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -8
- package/.next/server/app/_not-found.rsc +0 -40
- package/.next/server/app/api/analytics/route.js +0 -669
- package/.next/server/app/api/analytics/route.js.nft.json +0 -1
- package/.next/server/app/api/analytics/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/data/route.js +0 -1355
- package/.next/server/app/api/data/route.js.nft.json +0 -1
- package/.next/server/app/api/data/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/export/route.js +0 -545
- package/.next/server/app/api/export/route.js.nft.json +0 -1
- package/.next/server/app/api/export/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/files/route.js +0 -512
- package/.next/server/app/api/files/route.js.nft.json +0 -1
- package/.next/server/app/api/files/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/prices/refresh/route.js +0 -1449
- package/.next/server/app/api/prices/refresh/route.js.nft.json +0 -1
- package/.next/server/app/api/prices/refresh/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/prices/route.js +0 -1382
- package/.next/server/app/api/prices/route.js.nft.json +0 -1
- package/.next/server/app/api/prices/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/scan/route.js +0 -3066
- package/.next/server/app/api/scan/route.js.nft.json +0 -1
- package/.next/server/app/api/scan/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/settings/route.js +0 -1076
- package/.next/server/app/api/settings/route.js.nft.json +0 -1
- package/.next/server/app/api/settings/route_client-reference-manifest.js +0 -1
- package/.next/server/app/debug/page.js +0 -1638
- package/.next/server/app/debug/page.js.nft.json +0 -1
- package/.next/server/app/debug/page_client-reference-manifest.js +0 -1
- package/.next/server/app/diagnostics/page.js +0 -3045
- package/.next/server/app/diagnostics/page.js.nft.json +0 -1
- package/.next/server/app/diagnostics/page_client-reference-manifest.js +0 -1
- package/.next/server/app/discovery/page.js +0 -1729
- package/.next/server/app/discovery/page.js.nft.json +0 -1
- package/.next/server/app/discovery/page_client-reference-manifest.js +0 -1
- package/.next/server/app/icon.svg/route.js +0 -469
- package/.next/server/app/icon.svg/route.js.nft.json +0 -1
- package/.next/server/app/icon.svg.body +0 -10
- package/.next/server/app/icon.svg.meta +0 -1
- package/.next/server/app/models/page.js +0 -1789
- package/.next/server/app/models/page.js.nft.json +0 -1
- package/.next/server/app/models/page_client-reference-manifest.js +0 -1
- package/.next/server/app/optimisation/page.js +0 -1602
- package/.next/server/app/optimisation/page.js.nft.json +0 -1
- package/.next/server/app/optimisation/page_client-reference-manifest.js +0 -1
- package/.next/server/app/page.js +0 -3113
- package/.next/server/app/page.js.nft.json +0 -1
- package/.next/server/app/page_client-reference-manifest.js +0 -1
- package/.next/server/app/parser-debug/page.js +0 -1646
- package/.next/server/app/parser-debug/page.js.nft.json +0 -1
- package/.next/server/app/parser-debug/page_client-reference-manifest.js +0 -1
- package/.next/server/app/pricing/page.js +0 -2590
- package/.next/server/app/pricing/page.js.nft.json +0 -1
- package/.next/server/app/pricing/page_client-reference-manifest.js +0 -1
- package/.next/server/app/projects/page.js +0 -1967
- package/.next/server/app/projects/page.js.nft.json +0 -1
- package/.next/server/app/projects/page_client-reference-manifest.js +0 -1
- package/.next/server/app/sessions/page.js +0 -2526
- package/.next/server/app/sessions/page.js.nft.json +0 -1
- package/.next/server/app/sessions/page_client-reference-manifest.js +0 -1
- package/.next/server/app/settings/page.js +0 -2467
- package/.next/server/app/settings/page.js.nft.json +0 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
- package/.next/server/app/tools/page.js +0 -1745
- package/.next/server/app/tools/page.js.nft.json +0 -1
- package/.next/server/app/tools/page_client-reference-manifest.js +0 -1
- package/.next/server/app-paths-manifest.json +0 -24
- package/.next/server/chunks/287.js +0 -1650
- package/.next/server/chunks/331.js +0 -7896
- package/.next/server/chunks/366.js +0 -32801
- package/.next/server/chunks/483.js +0 -31264
- package/.next/server/chunks/576.js +0 -797
- package/.next/server/chunks/611.js +0 -2979
- package/.next/server/chunks/692.js +0 -905
- package/.next/server/chunks/722.js +0 -6318
- package/.next/server/chunks/868.js +0 -730
- package/.next/server/functions-config-manifest.json +0 -4
- package/.next/server/interception-route-rewrite-manifest.js +0 -1
- package/.next/server/middleware-build-manifest.js +0 -1
- package/.next/server/middleware-manifest.json +0 -6
- package/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/.next/server/next-font-manifest.js +0 -1
- package/.next/server/next-font-manifest.json +0 -1
- package/.next/server/pages/404.html +0 -1
- package/.next/server/pages/500.html +0 -1
- package/.next/server/pages/_app.js +0 -277
- package/.next/server/pages/_app.js.nft.json +0 -1
- package/.next/server/pages/_document.js +0 -46
- package/.next/server/pages/_document.js.nft.json +0 -1
- package/.next/server/pages/_error.js +0 -6315
- package/.next/server/pages/_error.js.nft.json +0 -1
- package/.next/server/pages-manifest.json +0 -6
- package/.next/server/server-reference-manifest.js +0 -1
- package/.next/server/server-reference-manifest.json +0 -1
- package/.next/server/webpack-runtime.js +0 -207
- package/.next/static/cNv_sOa0AntpwuwvPjuRA/_buildManifest.js +0 -1
- package/.next/static/cNv_sOa0AntpwuwvPjuRA/_ssgManifest.js +0 -1
- package/.next/static/chunks/125-ab0f8db8f84c1166.js +0 -1
- package/.next/static/chunks/255-e881f48ae1d2333a.js +0 -1
- package/.next/static/chunks/4bd1b696-409494caf8c83275.js +0 -1
- package/.next/static/chunks/619-f072ac750404f9da.js +0 -1
- package/.next/static/chunks/850-8bc31e41590b5831.js +0 -1
- package/.next/static/chunks/938-23236de1c47554ea.js +0 -1
- package/.next/static/chunks/app/_not-found/page-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/analytics/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/data/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/export/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/files/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/prices/refresh/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/prices/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/scan/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/api/settings/route-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/debug/page-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/diagnostics/page-3b65a4b6734f0c62.js +0 -1
- package/.next/static/chunks/app/discovery/page-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/global-error-88b965bb96071bef.js +0 -1
- package/.next/static/chunks/app/layout-234c4399a92e808b.js +0 -1
- package/.next/static/chunks/app/models/page-b2b7c974754ac991.js +0 -1
- package/.next/static/chunks/app/not-found-0644ad2dcc40cfd9.js +0 -1
- package/.next/static/chunks/app/optimisation/page-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/page-509085a4fdf00d49.js +0 -1
- package/.next/static/chunks/app/parser-debug/page-73368c3ff767c206.js +0 -1
- package/.next/static/chunks/app/pricing/page-d8419f42f65f7429.js +0 -1
- package/.next/static/chunks/app/projects/page-a49976ccd65bd81a.js +0 -1
- package/.next/static/chunks/app/sessions/page-86f6b8c220f4aa95.js +0 -1
- package/.next/static/chunks/app/settings/page-b2d78790be6a114d.js +0 -1
- package/.next/static/chunks/app/tools/page-b2b7c974754ac991.js +0 -1
- package/.next/static/chunks/framework-3457b9c2619cdd96.js +0 -1
- package/.next/static/chunks/main-8744520a8a31e6ae.js +0 -1
- package/.next/static/chunks/main-app-039335219a472e20.js +0 -1
- package/.next/static/chunks/pages/_app-5addca2b3b969fde.js +0 -1
- package/.next/static/chunks/pages/_error-022e4ac7bbb9914f.js +0 -1
- package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/.next/static/chunks/webpack-3fcacae817f3ffab.js +0 -1
- package/.next/static/css/46d6a87bebe3b542.css +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to TokenTrace are documented here.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## [0.7.0] - 2026-05-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- 0.7.0 Usage Intelligence roadmap for local guardrails, savings review, session comparison, project intelligence, and daily digest work.
|
|
10
|
+
- Local monthly cost and token guardrails stored in Settings.
|
|
11
|
+
- Overview Monthly Guardrails panel showing month-to-date local usage against configured limits.
|
|
12
|
+
- Recommendation rules for monthly guardrails that are near or over limit.
|
|
13
|
+
- Evidence-backed Review Queue on Insights, ranked from guardrails, unknown cost repair, high-impact sessions, dominant projects, model review, and cache reuse.
|
|
14
|
+
- `tokentrace insights --json` now includes the Review Queue for local automation.
|
|
15
|
+
- `tokentrace digest` and `tokentrace digest --json` for current-month local usage, guardrails, top review item, unknown-cost count, top project, and latest scan status.
|
|
16
|
+
- Session Comparison Flags on Sessions to highlight token and cost outliers compared with matching project, tool, and primary-model peers.
|
|
17
|
+
- Project Signals on Projects for dominant usage, unknown cost, estimated-token confidence, and model concentration patterns.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Renamed the Insights page header to Usage Intelligence to match the 0.7.0 product theme.
|
|
22
|
+
- Refreshed README screenshots for Overview, Usage Intelligence, Sessions, Projects, and local guardrail Settings using public-safe synthetic local data.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Increased the forced packed-install smoke test timeout so native SQLite dependency installation is not killed on slower machines.
|
|
27
|
+
|
|
28
|
+
## [0.6.0] - 2026-05-10
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- 0.6.0 Stable Daily Tool roadmap with explicit release cards and gates.
|
|
33
|
+
- Support matrix for stable, best-effort, ignored, and unsupported TokenTrace surfaces.
|
|
34
|
+
- Scan Doctor support matrix and scan freshness status.
|
|
35
|
+
- Overview first-run checklist that explains missing roots, no scans, zero imports, and next actions.
|
|
36
|
+
- `npm run smoke:cli` for clean-home CLI checks across scan, serve, doctor, status, Claude status-line, and watch-mode commands.
|
|
37
|
+
- `npm run smoke:packed` for installing the packed tarball into a temp project and verifying the published CLI entrypoint.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Claude Code and Codex JSONL parsers now keep valid records when a transcript contains malformed lines.
|
|
42
|
+
- Model alias suggestions now handle OpenAI/Codex provider-prefixed and dated snapshot names.
|
|
43
|
+
- Overview period filter keeps the custom date fields and Apply button visible on desktop while presets absorb overflow.
|
|
44
|
+
- Local development config declares localhost/127.0.0.1 dev origins and disables the standard Next.js dev indicator preference.
|
|
45
|
+
- README documents the support matrix and unsupported product boundaries.
|
|
46
|
+
- `npm run release:check` now includes CLI and packed-install smoke checks before package security inspection.
|
|
47
|
+
- The npm package now excludes generated `.next` output and prepares the dashboard build in the user's TokenTrace app-data directory on first `tokentrace serve`.
|
|
48
|
+
- Package trust inspection now verifies the publish tarball directly and fails if generated Next.js build output is included.
|
|
49
|
+
- Packed-install smoke now starts `tokentrace serve` from the packed tarball and allows dependency install scripts needed by native SQLite bindings while keeping TokenTrace itself free of lifecycle scripts.
|
|
50
|
+
- Dev security posture is cleaner: Vitest is updated to the Node 18-compatible patched line, and the unused `drizzle-kit` dev dependency has been removed.
|
|
51
|
+
- `npm run security:package` now audits the full dependency graph at moderate-or-higher severity instead of checking only production high-severity advisories.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- Package inspection no longer depends on the user's global npm cache, which can contain root-owned files on some machines.
|
|
56
|
+
- First-run dashboard preparation from a packed install no longer hits SQLite `database is locked` errors while Next.js imports API routes during build.
|
|
57
|
+
- `tokentrace serve` now exits nonzero when the underlying Next.js server fails before readiness.
|
|
6
58
|
|
|
7
59
|
## [0.5.1] - 2026-05-10
|
|
8
60
|
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Local-first analytics for AI CLI usage. TokenTrace scans local CLI logs, normali
|
|
|
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
|
+

|
|
12
12
|
|
|
13
13
|
## Start In Seconds
|
|
14
14
|
|
|
@@ -37,6 +37,8 @@ tokentrace serve --port 3210 --no-open
|
|
|
37
37
|
tokentrace scan # Scan local AI CLI usage logs
|
|
38
38
|
tokentrace doctor --json
|
|
39
39
|
# Inspect scan health and repair recommendations
|
|
40
|
+
tokentrace digest --json
|
|
41
|
+
# Print current-month local usage digest
|
|
40
42
|
tokentrace insights --json
|
|
41
43
|
# Print local recommendations as JSON
|
|
42
44
|
tokentrace status --json
|
|
@@ -82,7 +84,7 @@ npm run db:migrate # Create/update local SQLite tables
|
|
|
82
84
|
npm run db:seed # Seed editable provider/model prices
|
|
83
85
|
npm run reset # Clear imported data and scan history
|
|
84
86
|
npm test # Run parser and cost tests
|
|
85
|
-
npm run verify # Run Vitest and
|
|
87
|
+
npm run verify # Run Vitest, TypeScript, and ESLint checks
|
|
86
88
|
npm run package:test # Verify, build, and dry-run the npm package
|
|
87
89
|
npm run package:inspect
|
|
88
90
|
# Check package transparency guardrails
|
|
@@ -129,6 +131,10 @@ Default discovery checks these locations when present:
|
|
|
129
131
|
|
|
130
132
|
Use **Settings** in the dashboard to add custom folders, toggle raw message storage, and trigger scans. Use **Doctor**, **Discovery**, **Parser Debug**, and **Raw Data** to inspect discovered files, parser decisions, warnings, failures, extracted metadata, and confidence levels.
|
|
131
133
|
|
|
134
|
+
Settings also supports optional local monthly usage guardrails. Set a cost
|
|
135
|
+
limit, token limit, or both, and Overview will show month-to-date progress from
|
|
136
|
+
imported local CLI usage.
|
|
137
|
+
|
|
132
138
|
## Ingestion Architecture
|
|
133
139
|
|
|
134
140
|
TokenTrace's primary ingestion architecture is direct local filesystem ingestion:
|
|
@@ -191,6 +197,22 @@ Codex CLI status-line integration is intentionally deferred until its status-lin
|
|
|
191
197
|
|
|
192
198
|
## Screenshots
|
|
193
199
|
|
|
200
|
+
Usage Intelligence views from `0.7.0`:
|
|
201
|
+
|
|
202
|
+

|
|
203
|
+
|
|
204
|
+

|
|
205
|
+
|
|
206
|
+

|
|
207
|
+
|
|
208
|
+

|
|
209
|
+
|
|
210
|
+

|
|
211
|
+
|
|
212
|
+
Stable Daily Tool views from `0.6.0`:
|
|
213
|
+
|
|
214
|
+

|
|
215
|
+
|
|
194
216
|
CLI startup and help:
|
|
195
217
|
|
|
196
218
|

|
|
@@ -235,11 +257,12 @@ Stop the server with `Ctrl+C` in the terminal where `tokentrace` is running.
|
|
|
235
257
|
|
|
236
258
|
## Package Trust
|
|
237
259
|
|
|
238
|
-
- The npm package has no `preinstall`, `install`, or `postinstall` scripts.
|
|
239
|
-
-
|
|
240
|
-
- `
|
|
260
|
+
- The TokenTrace npm package has no `preinstall`, `install`, or `postinstall` scripts.
|
|
261
|
+
- The published package ships readable application source and the compiled CLI runtime, not generated `.next/server` route bundles.
|
|
262
|
+
- `tokentrace serve` prepares the local dashboard build in the user's TokenTrace app-data directory the first time it is needed.
|
|
263
|
+
- `npm run package:inspect` fails if generated Next.js build output appears in the published tarball.
|
|
241
264
|
- Public npm publishing is configured through GitHub Actions Trusted Publishing and provenance from version tags.
|
|
242
|
-
- Socket GitHub checks and ProjScan are used as release guardrails, alongside `npm audit --
|
|
265
|
+
- Socket GitHub checks and ProjScan are used as release guardrails, alongside `npm audit --audit-level=moderate`.
|
|
243
266
|
- Release notes are published directly in GitHub Releases from the relevant changelog section, not as a link-only summary.
|
|
244
267
|
|
|
245
268
|
See [SECURITY.md](SECURITY.md) for the full security and privacy model.
|
|
@@ -291,6 +314,21 @@ Adapters live under `src/ingestion/adapters/`:
|
|
|
291
314
|
|
|
292
315
|
Formats for Claude Code and Codex CLI can vary across versions, so these adapters are defensive and best-effort. Unknown files fail safely and show warnings in the Raw Data page.
|
|
293
316
|
|
|
317
|
+
## Support Matrix
|
|
318
|
+
|
|
319
|
+
TokenTrace keeps a visible support contract so daily scans are easier to trust:
|
|
320
|
+
|
|
321
|
+
| Surface | Support level | Notes |
|
|
322
|
+
| --- | --- | --- |
|
|
323
|
+
| Claude Code project transcripts | Stable | Primary local CLI ingestion source. |
|
|
324
|
+
| Codex CLI session artifacts | Best-effort | Parsed defensively while CLI formats evolve. |
|
|
325
|
+
| Generic JSONL, JSON, and text logs | Best-effort | Conservative usage-shaped records only. |
|
|
326
|
+
| Claude/Codex cache, plugin, todo, config, and support files | Ignored | Tracked as non-usage files, not parser failures. |
|
|
327
|
+
| Editable model pricing | Stable | Local pricing rows drive costs and unknown-cost repair queues. |
|
|
328
|
+
| Claude Code status line | Stable | Uses Claude Code's documented statusLine stdin contract. |
|
|
329
|
+
| Codex sticky status line | Best-effort fallback | Use `tokentrace watch --session --compact` in a split or tmux pane. |
|
|
330
|
+
| Desktop app scraping, browser extensions, proxying, packet capture, telemetry | Unsupported | Outside TokenTrace's product boundary. |
|
|
331
|
+
|
|
294
332
|
## Extending Parsers
|
|
295
333
|
|
|
296
334
|
Example generic JSONL fixtures are in `fixtures/generic-jsonl/`.
|
package/SECURITY.md
CHANGED
|
@@ -8,14 +8,17 @@ downloads.
|
|
|
8
8
|
|
|
9
9
|
- No telemetry, cloud sync, traffic interception, proxying, packet sniffing, or
|
|
10
10
|
browser extension is part of the product.
|
|
11
|
-
-
|
|
11
|
+
- The TokenTrace package has no `preinstall`, `install`, or `postinstall`
|
|
12
|
+
npm lifecycle scripts.
|
|
12
13
|
- Runtime state is stored locally in the user's TokenTrace app-data directory.
|
|
13
14
|
- Raw full prompts and responses are off by default.
|
|
14
15
|
- Pricing refresh downloads a public model-pricing manifest only. It does not
|
|
15
16
|
send usage logs, prompts, file paths, analytics, or identifiers. Set
|
|
16
17
|
`TOKENTRACE_DISABLE_PRICE_REFRESH=1` to use bundled prices only.
|
|
17
|
-
-
|
|
18
|
-
|
|
18
|
+
- The published package ships readable application source and the compiled CLI
|
|
19
|
+
runtime, not generated `.next/server` route bundles. `tokentrace serve`
|
|
20
|
+
prepares the local dashboard build in the user's app-data directory when
|
|
21
|
+
needed.
|
|
19
22
|
|
|
20
23
|
## What TokenTrace Reads
|
|
21
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { getDatabasePath } from "@/src/db/client";
|
|
3
|
-
import { getAppSettings, saveAppSettings } from "@/src/db/settings";
|
|
3
|
+
import { getAppSettings, normalizeUsageGuardrails, saveAppSettings } from "@/src/db/settings";
|
|
4
4
|
|
|
5
5
|
export const dynamic = "force-dynamic";
|
|
6
6
|
|
|
@@ -18,7 +18,8 @@ export async function PUT(request: Request) {
|
|
|
18
18
|
: [];
|
|
19
19
|
const saved = saveAppSettings({
|
|
20
20
|
customFolders,
|
|
21
|
-
storeRawMessageContent: Boolean(body.storeRawMessageContent)
|
|
21
|
+
storeRawMessageContent: Boolean(body.storeRawMessageContent),
|
|
22
|
+
usageGuardrails: normalizeUsageGuardrails(body.usageGuardrails)
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
return NextResponse.json(saved);
|
package/app/diagnostics/page.tsx
CHANGED
|
@@ -176,6 +176,13 @@ function DoctorReportPanel({ report }: { report: DoctorReport }) {
|
|
|
176
176
|
</div>
|
|
177
177
|
</div>
|
|
178
178
|
|
|
179
|
+
<div className="rounded-md border bg-muted/30 p-3">
|
|
180
|
+
<div className="text-sm font-semibold">Scan freshness</div>
|
|
181
|
+
<div className="mt-1 text-xs leading-relaxed text-muted-foreground">
|
|
182
|
+
{report.scanFreshness.description}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
179
186
|
{report.latestScan.zeroImportExplanation ? (
|
|
180
187
|
<div className="rounded-md border border-amber-300 bg-amber-50 p-3 text-sm text-amber-950">
|
|
181
188
|
{report.latestScan.zeroImportExplanation}
|
|
@@ -224,6 +231,31 @@ function DoctorReportPanel({ report }: { report: DoctorReport }) {
|
|
|
224
231
|
))}
|
|
225
232
|
</div>
|
|
226
233
|
</div>
|
|
234
|
+
|
|
235
|
+
<div className="space-y-3">
|
|
236
|
+
<div>
|
|
237
|
+
<div className="text-sm font-semibold">Support matrix</div>
|
|
238
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
239
|
+
{report.supportMatrix.summary.stable.toLocaleString()} stable,{" "}
|
|
240
|
+
{report.supportMatrix.summary.bestEffort.toLocaleString()} best-effort,{" "}
|
|
241
|
+
{report.supportMatrix.summary.ignored.toLocaleString()} ignored,{" "}
|
|
242
|
+
{report.supportMatrix.summary.unsupported.toLocaleString()} unsupported.
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
<div className="grid divide-y border-y lg:grid-cols-2 lg:divide-x lg:divide-y-0">
|
|
246
|
+
{report.supportMatrix.items.map((item) => (
|
|
247
|
+
<div key={item.id} className="p-3">
|
|
248
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
249
|
+
<div className="text-sm font-semibold">{item.label}</div>
|
|
250
|
+
<Badge variant={item.level === "stable" ? "success" : item.level === "unsupported" ? "destructive" : item.level === "best-effort" ? "warning" : "secondary"}>
|
|
251
|
+
{item.level}
|
|
252
|
+
</Badge>
|
|
253
|
+
</div>
|
|
254
|
+
<div className="mt-1 text-xs leading-relaxed text-muted-foreground">{item.description}</div>
|
|
255
|
+
</div>
|
|
256
|
+
))}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
227
259
|
</CardContent>
|
|
228
260
|
</Card>
|
|
229
261
|
);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
1
2
|
import { AlertTriangle, CheckCircle2, Info, Lightbulb } from "lucide-react";
|
|
2
3
|
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
3
5
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
6
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
4
7
|
import { PageHeader } from "@/components/ui/typography";
|
|
5
8
|
import { getAnalyticsData } from "@/src/lib/analytics";
|
|
6
9
|
|
|
@@ -24,10 +27,60 @@ export default function OptimisationPage() {
|
|
|
24
27
|
return (
|
|
25
28
|
<div className="space-y-6">
|
|
26
29
|
<PageHeader
|
|
27
|
-
title="
|
|
28
|
-
description="Deterministic
|
|
30
|
+
title="Usage Intelligence"
|
|
31
|
+
description="Deterministic local review queue based on guardrails, repair work, and usage impact."
|
|
29
32
|
/>
|
|
30
33
|
|
|
34
|
+
<Card>
|
|
35
|
+
<CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
36
|
+
<div>
|
|
37
|
+
<CardTitle>Review Queue</CardTitle>
|
|
38
|
+
<CardDescription>
|
|
39
|
+
Evidence-backed next actions ordered by guardrails, repair work, and usage impact.
|
|
40
|
+
</CardDescription>
|
|
41
|
+
</div>
|
|
42
|
+
<Button asChild variant="outline" size="sm">
|
|
43
|
+
<Link href="/sessions">Open sessions</Link>
|
|
44
|
+
</Button>
|
|
45
|
+
</CardHeader>
|
|
46
|
+
<CardContent className="table-scroll">
|
|
47
|
+
<Table>
|
|
48
|
+
<TableHeader>
|
|
49
|
+
<TableRow>
|
|
50
|
+
<TableHead>Priority</TableHead>
|
|
51
|
+
<TableHead>Category</TableHead>
|
|
52
|
+
<TableHead>Review item</TableHead>
|
|
53
|
+
<TableHead>Impact</TableHead>
|
|
54
|
+
<TableHead>Action</TableHead>
|
|
55
|
+
</TableRow>
|
|
56
|
+
</TableHeader>
|
|
57
|
+
<TableBody>
|
|
58
|
+
{data.reviewQueue.map((item) => (
|
|
59
|
+
<TableRow key={item.id}>
|
|
60
|
+
<TableCell>
|
|
61
|
+
<Badge variant={severityVariant[item.severity]}>{item.severity}</Badge>
|
|
62
|
+
</TableCell>
|
|
63
|
+
<TableCell className="capitalize">{item.category.replace("-", " ")}</TableCell>
|
|
64
|
+
<TableCell className="max-w-xl">
|
|
65
|
+
<div className="font-medium">{item.title}</div>
|
|
66
|
+
<div className="mt-1 text-xs leading-relaxed text-muted-foreground">{item.evidence}</div>
|
|
67
|
+
</TableCell>
|
|
68
|
+
<TableCell>
|
|
69
|
+
<div className="text-sm font-medium">{item.impactValue}</div>
|
|
70
|
+
<div className="text-xs text-muted-foreground">{item.impactLabel}</div>
|
|
71
|
+
</TableCell>
|
|
72
|
+
<TableCell>
|
|
73
|
+
<Link href={item.href} className="font-medium text-primary underline-offset-4 hover:underline">
|
|
74
|
+
{item.action}
|
|
75
|
+
</Link>
|
|
76
|
+
</TableCell>
|
|
77
|
+
</TableRow>
|
|
78
|
+
))}
|
|
79
|
+
</TableBody>
|
|
80
|
+
</Table>
|
|
81
|
+
</CardContent>
|
|
82
|
+
</Card>
|
|
83
|
+
|
|
31
84
|
<div className="grid gap-4">
|
|
32
85
|
{data.insights.map((insight) => {
|
|
33
86
|
const Icon = severityIcon[insight.severity] ?? CheckCircle2;
|
package/app/page.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
-
import { ArrowRight, Coins, Database, MessageSquare, Minus, Sparkles, TrendingDown, TrendingUp } from "lucide-react";
|
|
3
|
-
import { EmptyState } from "@/components/empty-state";
|
|
2
|
+
import { ArrowRight, Coins, Database, Gauge, MessageSquare, Minus, Sparkles, TrendingDown, TrendingUp } from "lucide-react";
|
|
4
3
|
import { RankBarChart } from "@/components/charts/rank-bar-chart";
|
|
5
4
|
import { TrendChart } from "@/components/charts/trend-chart";
|
|
6
5
|
import { PeriodFilter } from "@/components/period-filter";
|
|
@@ -10,10 +9,14 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|
|
10
9
|
import { HelpTooltip } from "@/components/ui/help-tooltip";
|
|
11
10
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
12
11
|
import { DataValue, FieldLabel, MonoText, PageHeader } from "@/components/ui/typography";
|
|
13
|
-
import { getAnalyticsData } from "@/src/lib/analytics";
|
|
12
|
+
import { getAnalyticsData, getScanTrustData } from "@/src/lib/analytics";
|
|
13
|
+
import { buildDoctorReport } from "@/src/lib/doctor";
|
|
14
|
+
import { getDefaultSearchRoots } from "@/src/ingestion/discovery";
|
|
15
|
+
import { buildFirstRunStatus, type FirstRunStatus } from "@/src/lib/first-run-status";
|
|
14
16
|
import { resolveDateRange } from "@/src/lib/date-range";
|
|
15
17
|
import { formatCurrency, formatTokens, percent } from "@/src/lib/format";
|
|
16
18
|
import { cn } from "@/src/lib/utils";
|
|
19
|
+
import type { UsageGuardrailMetric } from "@/src/lib/usage-guardrails";
|
|
17
20
|
|
|
18
21
|
export const dynamic = "force-dynamic";
|
|
19
22
|
|
|
@@ -125,6 +128,131 @@ function DeltaMetric({
|
|
|
125
128
|
);
|
|
126
129
|
}
|
|
127
130
|
|
|
131
|
+
function guardrailBadgeVariant(status: UsageGuardrailMetric["status"]) {
|
|
132
|
+
if (status === "exceeded") return "destructive";
|
|
133
|
+
if (status === "warning") return "warning";
|
|
134
|
+
if (status === "ok") return "success";
|
|
135
|
+
return "secondary";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function guardrailLabel(status: UsageGuardrailMetric["status"]) {
|
|
139
|
+
if (status === "exceeded") return "exceeded";
|
|
140
|
+
if (status === "warning") return "watch";
|
|
141
|
+
if (status === "ok") return "within limit";
|
|
142
|
+
return "not set";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function GuardrailMetricPanel({
|
|
146
|
+
label,
|
|
147
|
+
metric,
|
|
148
|
+
formatValue
|
|
149
|
+
}: {
|
|
150
|
+
label: string;
|
|
151
|
+
metric: UsageGuardrailMetric;
|
|
152
|
+
formatValue: (value: number | null) => string;
|
|
153
|
+
}) {
|
|
154
|
+
const cappedPercent = Math.min(1, Math.max(0, metric.percent));
|
|
155
|
+
const barClass =
|
|
156
|
+
metric.status === "exceeded"
|
|
157
|
+
? "bg-destructive"
|
|
158
|
+
: metric.status === "warning"
|
|
159
|
+
? "bg-amber-500"
|
|
160
|
+
: "bg-primary";
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="min-w-0 border-t p-3 first:border-t-0 md:border-l md:border-t-0 md:first:border-l-0">
|
|
164
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
165
|
+
<FieldLabel>{label}</FieldLabel>
|
|
166
|
+
<Badge variant={guardrailBadgeVariant(metric.status)}>{guardrailLabel(metric.status)}</Badge>
|
|
167
|
+
</div>
|
|
168
|
+
<DataValue className="mt-1" size="md">
|
|
169
|
+
{metric.configured ? `${formatValue(metric.used)} / ${formatValue(metric.limit)}` : formatValue(metric.used)}
|
|
170
|
+
</DataValue>
|
|
171
|
+
<div className="mt-3 h-2 overflow-hidden rounded-full bg-muted">
|
|
172
|
+
<div className={cn("h-full rounded-full", barClass)} style={{ width: `${cappedPercent * 100}%` }} />
|
|
173
|
+
</div>
|
|
174
|
+
<div className="mt-2 text-xs text-muted-foreground">
|
|
175
|
+
{metric.configured
|
|
176
|
+
? `${percent(metric.percent)} used, ${formatValue(metric.remaining)} remaining`
|
|
177
|
+
: "Set a local monthly limit in Settings."}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function UsageGuardrailsPanel({
|
|
184
|
+
progress
|
|
185
|
+
}: {
|
|
186
|
+
progress: {
|
|
187
|
+
monthLabel: string;
|
|
188
|
+
cost: UsageGuardrailMetric;
|
|
189
|
+
tokens: UsageGuardrailMetric;
|
|
190
|
+
};
|
|
191
|
+
}) {
|
|
192
|
+
const configured = progress.cost.configured || progress.tokens.configured;
|
|
193
|
+
return (
|
|
194
|
+
<Card>
|
|
195
|
+
<CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
196
|
+
<div>
|
|
197
|
+
<CardTitle className="flex items-center gap-2">
|
|
198
|
+
<Gauge className="h-4 w-4 text-primary" />
|
|
199
|
+
Monthly Guardrails
|
|
200
|
+
</CardTitle>
|
|
201
|
+
<CardDescription>
|
|
202
|
+
{configured
|
|
203
|
+
? `Month-to-date checks for ${progress.monthLabel}, based only on imported local CLI usage.`
|
|
204
|
+
: "Optional local limits for monthly cost and tokens."}
|
|
205
|
+
</CardDescription>
|
|
206
|
+
</div>
|
|
207
|
+
<Button asChild variant="outline" size="sm">
|
|
208
|
+
<Link href="/settings">
|
|
209
|
+
Configure <ArrowRight className="h-4 w-4" />
|
|
210
|
+
</Link>
|
|
211
|
+
</Button>
|
|
212
|
+
</CardHeader>
|
|
213
|
+
<CardContent className="p-0">
|
|
214
|
+
<div className="grid border-t md:grid-cols-2">
|
|
215
|
+
<GuardrailMetricPanel label="Cost" metric={progress.cost} formatValue={formatCurrency} />
|
|
216
|
+
<GuardrailMetricPanel label="Tokens" metric={progress.tokens} formatValue={formatTokens} />
|
|
217
|
+
</div>
|
|
218
|
+
</CardContent>
|
|
219
|
+
</Card>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function FirstRunPanel({ status }: { status: FirstRunStatus }) {
|
|
224
|
+
return (
|
|
225
|
+
<Card className={status.tone === "warning" ? "border-amber-300 bg-amber-50/50" : undefined}>
|
|
226
|
+
<CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
227
|
+
<div>
|
|
228
|
+
<CardTitle>{status.title}</CardTitle>
|
|
229
|
+
<CardDescription>{status.description}</CardDescription>
|
|
230
|
+
</div>
|
|
231
|
+
<Button asChild variant={status.tone === "warning" ? "outline" : "default"}>
|
|
232
|
+
<Link href={status.primaryAction.href}>
|
|
233
|
+
{status.primaryAction.label} <ArrowRight className="h-4 w-4" />
|
|
234
|
+
</Link>
|
|
235
|
+
</Button>
|
|
236
|
+
</CardHeader>
|
|
237
|
+
<CardContent>
|
|
238
|
+
<div className="grid divide-y border-y md:grid-cols-5 md:divide-x md:divide-y-0">
|
|
239
|
+
{status.checks.map((check) => (
|
|
240
|
+
<div key={check.id} className="p-3">
|
|
241
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
242
|
+
<div className="text-sm font-semibold">{check.label}</div>
|
|
243
|
+
<Badge variant={check.state === "pass" ? "success" : check.state === "warn" ? "warning" : "secondary"}>
|
|
244
|
+
{check.state}
|
|
245
|
+
</Badge>
|
|
246
|
+
</div>
|
|
247
|
+
<div className="mt-1 text-xs leading-relaxed text-muted-foreground">{check.detail}</div>
|
|
248
|
+
</div>
|
|
249
|
+
))}
|
|
250
|
+
</div>
|
|
251
|
+
</CardContent>
|
|
252
|
+
</Card>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
128
256
|
type OverviewPageProps = {
|
|
129
257
|
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
|
130
258
|
};
|
|
@@ -133,6 +261,22 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
133
261
|
const params = (await searchParams) ?? {};
|
|
134
262
|
const range = resolveDateRange(params);
|
|
135
263
|
const data = getAnalyticsData(range.filters);
|
|
264
|
+
const trust = getScanTrustData();
|
|
265
|
+
const roots = await getDefaultSearchRoots();
|
|
266
|
+
const doctorReport = buildDoctorReport({ ...trust, roots });
|
|
267
|
+
const firstRunStatus = buildFirstRunStatus({
|
|
268
|
+
rootCount: roots.length,
|
|
269
|
+
pricedModelCount: trust.pricedModelCount,
|
|
270
|
+
latestScan: doctorReport.latestScan.id
|
|
271
|
+
? {
|
|
272
|
+
filesScanned: doctorReport.latestScan.filesScanned,
|
|
273
|
+
recordsImported: doctorReport.latestScan.recordsImported,
|
|
274
|
+
zeroImportExplanation: doctorReport.latestScan.zeroImportExplanation
|
|
275
|
+
}
|
|
276
|
+
: null,
|
|
277
|
+
interactions: trust.confidence.interactions,
|
|
278
|
+
unknownCostInteractions: trust.confidence.unknownCostInteractions
|
|
279
|
+
});
|
|
136
280
|
const { summary } = data;
|
|
137
281
|
|
|
138
282
|
return (
|
|
@@ -152,10 +296,7 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
152
296
|
<PeriodFilter range={range} />
|
|
153
297
|
|
|
154
298
|
{summary.interactions === 0 ? (
|
|
155
|
-
<
|
|
156
|
-
title="No usage imported yet"
|
|
157
|
-
description="Add custom folders if needed, run a scan from Settings, then return here for analytics."
|
|
158
|
-
/>
|
|
299
|
+
<FirstRunPanel status={firstRunStatus} />
|
|
159
300
|
) : null}
|
|
160
301
|
|
|
161
302
|
<Card>
|
|
@@ -205,6 +346,8 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
205
346
|
</CardContent>
|
|
206
347
|
</Card>
|
|
207
348
|
|
|
349
|
+
<UsageGuardrailsPanel progress={data.usageGuardrails} />
|
|
350
|
+
|
|
208
351
|
<Card>
|
|
209
352
|
<CardHeader>
|
|
210
353
|
<h2 className="text-sm font-semibold leading-tight">Recommended Next Actions</h2>
|
package/app/projects/page.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
2
|
import { RankBarChart } from "@/components/charts/rank-bar-chart";
|
|
3
3
|
import { TrendChart } from "@/components/charts/trend-chart";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
5
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
5
6
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
6
7
|
import { DataValue, MonoText, PageHeader } from "@/components/ui/typography";
|
|
@@ -20,6 +21,56 @@ export default function ProjectAnalyticsPage() {
|
|
|
20
21
|
description="Group usage by local repository or inferred project path."
|
|
21
22
|
/>
|
|
22
23
|
|
|
24
|
+
{data.projectSignals.length ? (
|
|
25
|
+
<Card>
|
|
26
|
+
<CardHeader>
|
|
27
|
+
<CardTitle>Project Signals</CardTitle>
|
|
28
|
+
<CardDescription>
|
|
29
|
+
Local project-level patterns that deserve review before optimizing individual sessions.
|
|
30
|
+
</CardDescription>
|
|
31
|
+
</CardHeader>
|
|
32
|
+
<CardContent className="table-scroll">
|
|
33
|
+
<Table>
|
|
34
|
+
<TableHeader>
|
|
35
|
+
<TableRow>
|
|
36
|
+
<TableHead>Priority</TableHead>
|
|
37
|
+
<TableHead>Signal</TableHead>
|
|
38
|
+
<TableHead>Project</TableHead>
|
|
39
|
+
<TableHead>Metric</TableHead>
|
|
40
|
+
<TableHead>Action</TableHead>
|
|
41
|
+
</TableRow>
|
|
42
|
+
</TableHeader>
|
|
43
|
+
<TableBody>
|
|
44
|
+
{data.projectSignals.slice(0, 8).map((signal) => (
|
|
45
|
+
<TableRow key={signal.id}>
|
|
46
|
+
<TableCell>
|
|
47
|
+
<Badge variant={signal.severity === "high" ? "destructive" : signal.severity === "medium" ? "warning" : "secondary"}>
|
|
48
|
+
{signal.severity}
|
|
49
|
+
</Badge>
|
|
50
|
+
</TableCell>
|
|
51
|
+
<TableCell className="capitalize">{signal.signal}</TableCell>
|
|
52
|
+
<TableCell className="max-w-lg">
|
|
53
|
+
<div className="font-medium">{signal.project}</div>
|
|
54
|
+
<MonoText className="mt-1 block truncate text-muted-foreground">{signal.path}</MonoText>
|
|
55
|
+
<div className="mt-1 text-xs text-muted-foreground">{signal.evidence}</div>
|
|
56
|
+
</TableCell>
|
|
57
|
+
<TableCell>
|
|
58
|
+
<div className="font-medium">{signal.metricValue}</div>
|
|
59
|
+
<div className="text-xs text-muted-foreground">{signal.metricLabel}</div>
|
|
60
|
+
</TableCell>
|
|
61
|
+
<TableCell>
|
|
62
|
+
<Link href={signal.href} className="font-medium text-primary underline-offset-4 hover:underline">
|
|
63
|
+
{signal.action}
|
|
64
|
+
</Link>
|
|
65
|
+
</TableCell>
|
|
66
|
+
</TableRow>
|
|
67
|
+
))}
|
|
68
|
+
</TableBody>
|
|
69
|
+
</Table>
|
|
70
|
+
</CardContent>
|
|
71
|
+
</Card>
|
|
72
|
+
) : null}
|
|
73
|
+
|
|
23
74
|
<div className="grid gap-4 lg:grid-cols-[1fr_1.4fr]">
|
|
24
75
|
<Card>
|
|
25
76
|
<CardHeader>
|
package/app/sessions/page.tsx
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
1
2
|
import { SessionExplorer } from "@/components/session-explorer";
|
|
3
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
5
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
2
6
|
import { PageHeader } from "@/components/ui/typography";
|
|
3
7
|
import { getAnalyticsData } from "@/src/lib/analytics";
|
|
8
|
+
import { formatCurrency, formatTokens } from "@/src/lib/format";
|
|
4
9
|
|
|
5
10
|
export const dynamic = "force-dynamic";
|
|
6
11
|
|
|
@@ -24,6 +29,56 @@ export default async function SessionsPage({
|
|
|
24
29
|
title="Session Explorer"
|
|
25
30
|
description="Search and filter imported sessions by tool, model, project, cost, and estimation status."
|
|
26
31
|
/>
|
|
32
|
+
{data.sessionComparisons.length ? (
|
|
33
|
+
<Card>
|
|
34
|
+
<CardHeader>
|
|
35
|
+
<CardTitle>Session Comparison Flags</CardTitle>
|
|
36
|
+
<CardDescription>
|
|
37
|
+
Sessions that are unusual compared with the same project, tool, and primary model.
|
|
38
|
+
</CardDescription>
|
|
39
|
+
</CardHeader>
|
|
40
|
+
<CardContent className="table-scroll">
|
|
41
|
+
<Table>
|
|
42
|
+
<TableHeader>
|
|
43
|
+
<TableRow>
|
|
44
|
+
<TableHead>Priority</TableHead>
|
|
45
|
+
<TableHead>Flag</TableHead>
|
|
46
|
+
<TableHead>Session</TableHead>
|
|
47
|
+
<TableHead>Tokens</TableHead>
|
|
48
|
+
<TableHead>Cost</TableHead>
|
|
49
|
+
<TableHead>Peer median</TableHead>
|
|
50
|
+
<TableHead>Action</TableHead>
|
|
51
|
+
</TableRow>
|
|
52
|
+
</TableHeader>
|
|
53
|
+
<TableBody>
|
|
54
|
+
{data.sessionComparisons.slice(0, 6).map((row) => (
|
|
55
|
+
<TableRow key={row.sessionId}>
|
|
56
|
+
<TableCell>
|
|
57
|
+
<Badge variant={row.severity === "high" ? "destructive" : "warning"}>{row.severity}</Badge>
|
|
58
|
+
</TableCell>
|
|
59
|
+
<TableCell>{row.flag}</TableCell>
|
|
60
|
+
<TableCell className="max-w-lg">
|
|
61
|
+
<div className="font-medium">{row.title}</div>
|
|
62
|
+
<div className="mt-1 text-xs text-muted-foreground">{row.project} / {row.tool} / {row.models}</div>
|
|
63
|
+
</TableCell>
|
|
64
|
+
<TableCell>{formatTokens(row.totalTokens)}</TableCell>
|
|
65
|
+
<TableCell>{formatCurrency(row.cost)}</TableCell>
|
|
66
|
+
<TableCell>
|
|
67
|
+
<div>{formatTokens(row.peerMedianTokens)}</div>
|
|
68
|
+
<div className="text-xs text-muted-foreground">{row.peerSessions} peer sessions</div>
|
|
69
|
+
</TableCell>
|
|
70
|
+
<TableCell>
|
|
71
|
+
<Link href={row.href} className="font-medium text-primary underline-offset-4 hover:underline">
|
|
72
|
+
Compare evidence
|
|
73
|
+
</Link>
|
|
74
|
+
</TableCell>
|
|
75
|
+
</TableRow>
|
|
76
|
+
))}
|
|
77
|
+
</TableBody>
|
|
78
|
+
</Table>
|
|
79
|
+
</CardContent>
|
|
80
|
+
</Card>
|
|
81
|
+
) : null}
|
|
27
82
|
<SessionExplorer
|
|
28
83
|
sessions={data.sessions}
|
|
29
84
|
initialProject={params?.project}
|