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.
Files changed (206) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/README.md +44 -6
  3. package/SECURITY.md +6 -3
  4. package/app/api/settings/route.ts +3 -2
  5. package/app/diagnostics/page.tsx +32 -0
  6. package/app/optimisation/page.tsx +55 -2
  7. package/app/page.tsx +150 -7
  8. package/app/projects/page.tsx +51 -0
  9. package/app/sessions/page.tsx +55 -0
  10. package/bin/tokentrace.js +105 -7
  11. package/components/period-filter.tsx +19 -17
  12. package/components/settings-panel.tsx +75 -11
  13. package/dist/runtime/db-migrate.mjs +1 -0
  14. package/dist/runtime/db-seed.mjs +13 -0
  15. package/dist/runtime/digest.mjs +2227 -0
  16. package/dist/runtime/doctor.mjs +157 -1
  17. package/dist/runtime/insights.mjs +588 -73
  18. package/dist/runtime/pricing-refresh.mjs +13 -0
  19. package/dist/runtime/reset.mjs +13 -0
  20. package/dist/runtime/scan.mjs +40 -4
  21. package/dist/runtime/status.mjs +1 -0
  22. package/docs/assets/doctor-0.6.0.png +0 -0
  23. package/docs/assets/overview-0.6.0.png +0 -0
  24. package/docs/assets/overview-0.7.0.png +0 -0
  25. package/docs/assets/projects-0.7.0.png +0 -0
  26. package/docs/assets/sessions-0.7.0.png +0 -0
  27. package/docs/assets/settings-guardrails-0.7.0.png +0 -0
  28. package/docs/assets/settings-package-trust-0.6.0.png +0 -0
  29. package/docs/assets/usage-intelligence-0.7.0.png +0 -0
  30. package/next.config.mjs +5 -0
  31. package/package.json +17 -18
  32. package/scripts/build-cli-runtime.mjs +1 -0
  33. package/scripts/digest.ts +27 -0
  34. package/scripts/doctor.ts +2 -0
  35. package/scripts/insights.ts +2 -1
  36. package/scripts/package-inspect.mjs +51 -38
  37. package/scripts/smoke-cli.mjs +182 -0
  38. package/scripts/smoke-packed-install.mjs +180 -0
  39. package/src/db/client.ts +1 -0
  40. package/src/db/settings.ts +31 -2
  41. package/src/ingestion/adapters/claude-code.ts +2 -1
  42. package/src/ingestion/adapters/codex-cli.ts +2 -1
  43. package/src/lib/analytics.ts +29 -0
  44. package/src/lib/daily-digest.ts +101 -0
  45. package/src/lib/doctor.ts +17 -0
  46. package/src/lib/first-run-status.ts +128 -0
  47. package/src/lib/model-aliases.ts +14 -0
  48. package/src/lib/project-signals.ts +139 -0
  49. package/src/lib/recommendations.ts +36 -0
  50. package/src/lib/review-queue.ts +222 -0
  51. package/src/lib/scan-health.ts +67 -1
  52. package/src/lib/session-comparison.ts +94 -0
  53. package/src/lib/support-matrix.ts +113 -0
  54. package/src/lib/usage-guardrails.ts +113 -0
  55. package/.next/BUILD_ID +0 -1
  56. package/.next/app-build-manifest.json +0 -192
  57. package/.next/app-path-routes-manifest.json +0 -24
  58. package/.next/build-manifest.json +0 -33
  59. package/.next/export-marker.json +0 -6
  60. package/.next/images-manifest.json +0 -58
  61. package/.next/next-minimal-server.js.nft.json +0 -1
  62. package/.next/next-server.js.nft.json +0 -1
  63. package/.next/package.json +0 -1
  64. package/.next/prerender-manifest.json +0 -66
  65. package/.next/react-loadable-manifest.json +0 -1
  66. package/.next/required-server-files.json +0 -323
  67. package/.next/routes-manifest.json +0 -125
  68. package/.next/server/app/_not-found/page.js +0 -1111
  69. package/.next/server/app/_not-found/page.js.nft.json +0 -1
  70. package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  71. package/.next/server/app/_not-found.html +0 -1
  72. package/.next/server/app/_not-found.meta +0 -8
  73. package/.next/server/app/_not-found.rsc +0 -40
  74. package/.next/server/app/api/analytics/route.js +0 -669
  75. package/.next/server/app/api/analytics/route.js.nft.json +0 -1
  76. package/.next/server/app/api/analytics/route_client-reference-manifest.js +0 -1
  77. package/.next/server/app/api/data/route.js +0 -1355
  78. package/.next/server/app/api/data/route.js.nft.json +0 -1
  79. package/.next/server/app/api/data/route_client-reference-manifest.js +0 -1
  80. package/.next/server/app/api/export/route.js +0 -545
  81. package/.next/server/app/api/export/route.js.nft.json +0 -1
  82. package/.next/server/app/api/export/route_client-reference-manifest.js +0 -1
  83. package/.next/server/app/api/files/route.js +0 -512
  84. package/.next/server/app/api/files/route.js.nft.json +0 -1
  85. package/.next/server/app/api/files/route_client-reference-manifest.js +0 -1
  86. package/.next/server/app/api/prices/refresh/route.js +0 -1449
  87. package/.next/server/app/api/prices/refresh/route.js.nft.json +0 -1
  88. package/.next/server/app/api/prices/refresh/route_client-reference-manifest.js +0 -1
  89. package/.next/server/app/api/prices/route.js +0 -1382
  90. package/.next/server/app/api/prices/route.js.nft.json +0 -1
  91. package/.next/server/app/api/prices/route_client-reference-manifest.js +0 -1
  92. package/.next/server/app/api/scan/route.js +0 -3066
  93. package/.next/server/app/api/scan/route.js.nft.json +0 -1
  94. package/.next/server/app/api/scan/route_client-reference-manifest.js +0 -1
  95. package/.next/server/app/api/settings/route.js +0 -1076
  96. package/.next/server/app/api/settings/route.js.nft.json +0 -1
  97. package/.next/server/app/api/settings/route_client-reference-manifest.js +0 -1
  98. package/.next/server/app/debug/page.js +0 -1638
  99. package/.next/server/app/debug/page.js.nft.json +0 -1
  100. package/.next/server/app/debug/page_client-reference-manifest.js +0 -1
  101. package/.next/server/app/diagnostics/page.js +0 -3045
  102. package/.next/server/app/diagnostics/page.js.nft.json +0 -1
  103. package/.next/server/app/diagnostics/page_client-reference-manifest.js +0 -1
  104. package/.next/server/app/discovery/page.js +0 -1729
  105. package/.next/server/app/discovery/page.js.nft.json +0 -1
  106. package/.next/server/app/discovery/page_client-reference-manifest.js +0 -1
  107. package/.next/server/app/icon.svg/route.js +0 -469
  108. package/.next/server/app/icon.svg/route.js.nft.json +0 -1
  109. package/.next/server/app/icon.svg.body +0 -10
  110. package/.next/server/app/icon.svg.meta +0 -1
  111. package/.next/server/app/models/page.js +0 -1789
  112. package/.next/server/app/models/page.js.nft.json +0 -1
  113. package/.next/server/app/models/page_client-reference-manifest.js +0 -1
  114. package/.next/server/app/optimisation/page.js +0 -1602
  115. package/.next/server/app/optimisation/page.js.nft.json +0 -1
  116. package/.next/server/app/optimisation/page_client-reference-manifest.js +0 -1
  117. package/.next/server/app/page.js +0 -3113
  118. package/.next/server/app/page.js.nft.json +0 -1
  119. package/.next/server/app/page_client-reference-manifest.js +0 -1
  120. package/.next/server/app/parser-debug/page.js +0 -1646
  121. package/.next/server/app/parser-debug/page.js.nft.json +0 -1
  122. package/.next/server/app/parser-debug/page_client-reference-manifest.js +0 -1
  123. package/.next/server/app/pricing/page.js +0 -2590
  124. package/.next/server/app/pricing/page.js.nft.json +0 -1
  125. package/.next/server/app/pricing/page_client-reference-manifest.js +0 -1
  126. package/.next/server/app/projects/page.js +0 -1967
  127. package/.next/server/app/projects/page.js.nft.json +0 -1
  128. package/.next/server/app/projects/page_client-reference-manifest.js +0 -1
  129. package/.next/server/app/sessions/page.js +0 -2526
  130. package/.next/server/app/sessions/page.js.nft.json +0 -1
  131. package/.next/server/app/sessions/page_client-reference-manifest.js +0 -1
  132. package/.next/server/app/settings/page.js +0 -2467
  133. package/.next/server/app/settings/page.js.nft.json +0 -1
  134. package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
  135. package/.next/server/app/tools/page.js +0 -1745
  136. package/.next/server/app/tools/page.js.nft.json +0 -1
  137. package/.next/server/app/tools/page_client-reference-manifest.js +0 -1
  138. package/.next/server/app-paths-manifest.json +0 -24
  139. package/.next/server/chunks/287.js +0 -1650
  140. package/.next/server/chunks/331.js +0 -7896
  141. package/.next/server/chunks/366.js +0 -32801
  142. package/.next/server/chunks/483.js +0 -31264
  143. package/.next/server/chunks/576.js +0 -797
  144. package/.next/server/chunks/611.js +0 -2979
  145. package/.next/server/chunks/692.js +0 -905
  146. package/.next/server/chunks/722.js +0 -6318
  147. package/.next/server/chunks/868.js +0 -730
  148. package/.next/server/functions-config-manifest.json +0 -4
  149. package/.next/server/interception-route-rewrite-manifest.js +0 -1
  150. package/.next/server/middleware-build-manifest.js +0 -1
  151. package/.next/server/middleware-manifest.json +0 -6
  152. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  153. package/.next/server/next-font-manifest.js +0 -1
  154. package/.next/server/next-font-manifest.json +0 -1
  155. package/.next/server/pages/404.html +0 -1
  156. package/.next/server/pages/500.html +0 -1
  157. package/.next/server/pages/_app.js +0 -277
  158. package/.next/server/pages/_app.js.nft.json +0 -1
  159. package/.next/server/pages/_document.js +0 -46
  160. package/.next/server/pages/_document.js.nft.json +0 -1
  161. package/.next/server/pages/_error.js +0 -6315
  162. package/.next/server/pages/_error.js.nft.json +0 -1
  163. package/.next/server/pages-manifest.json +0 -6
  164. package/.next/server/server-reference-manifest.js +0 -1
  165. package/.next/server/server-reference-manifest.json +0 -1
  166. package/.next/server/webpack-runtime.js +0 -207
  167. package/.next/static/cNv_sOa0AntpwuwvPjuRA/_buildManifest.js +0 -1
  168. package/.next/static/cNv_sOa0AntpwuwvPjuRA/_ssgManifest.js +0 -1
  169. package/.next/static/chunks/125-ab0f8db8f84c1166.js +0 -1
  170. package/.next/static/chunks/255-e881f48ae1d2333a.js +0 -1
  171. package/.next/static/chunks/4bd1b696-409494caf8c83275.js +0 -1
  172. package/.next/static/chunks/619-f072ac750404f9da.js +0 -1
  173. package/.next/static/chunks/850-8bc31e41590b5831.js +0 -1
  174. package/.next/static/chunks/938-23236de1c47554ea.js +0 -1
  175. package/.next/static/chunks/app/_not-found/page-73368c3ff767c206.js +0 -1
  176. package/.next/static/chunks/app/api/analytics/route-73368c3ff767c206.js +0 -1
  177. package/.next/static/chunks/app/api/data/route-73368c3ff767c206.js +0 -1
  178. package/.next/static/chunks/app/api/export/route-73368c3ff767c206.js +0 -1
  179. package/.next/static/chunks/app/api/files/route-73368c3ff767c206.js +0 -1
  180. package/.next/static/chunks/app/api/prices/refresh/route-73368c3ff767c206.js +0 -1
  181. package/.next/static/chunks/app/api/prices/route-73368c3ff767c206.js +0 -1
  182. package/.next/static/chunks/app/api/scan/route-73368c3ff767c206.js +0 -1
  183. package/.next/static/chunks/app/api/settings/route-73368c3ff767c206.js +0 -1
  184. package/.next/static/chunks/app/debug/page-73368c3ff767c206.js +0 -1
  185. package/.next/static/chunks/app/diagnostics/page-3b65a4b6734f0c62.js +0 -1
  186. package/.next/static/chunks/app/discovery/page-73368c3ff767c206.js +0 -1
  187. package/.next/static/chunks/app/global-error-88b965bb96071bef.js +0 -1
  188. package/.next/static/chunks/app/layout-234c4399a92e808b.js +0 -1
  189. package/.next/static/chunks/app/models/page-b2b7c974754ac991.js +0 -1
  190. package/.next/static/chunks/app/not-found-0644ad2dcc40cfd9.js +0 -1
  191. package/.next/static/chunks/app/optimisation/page-73368c3ff767c206.js +0 -1
  192. package/.next/static/chunks/app/page-509085a4fdf00d49.js +0 -1
  193. package/.next/static/chunks/app/parser-debug/page-73368c3ff767c206.js +0 -1
  194. package/.next/static/chunks/app/pricing/page-d8419f42f65f7429.js +0 -1
  195. package/.next/static/chunks/app/projects/page-a49976ccd65bd81a.js +0 -1
  196. package/.next/static/chunks/app/sessions/page-86f6b8c220f4aa95.js +0 -1
  197. package/.next/static/chunks/app/settings/page-b2d78790be6a114d.js +0 -1
  198. package/.next/static/chunks/app/tools/page-b2b7c974754ac991.js +0 -1
  199. package/.next/static/chunks/framework-3457b9c2619cdd96.js +0 -1
  200. package/.next/static/chunks/main-8744520a8a31e6ae.js +0 -1
  201. package/.next/static/chunks/main-app-039335219a472e20.js +0 -1
  202. package/.next/static/chunks/pages/_app-5addca2b3b969fde.js +0 -1
  203. package/.next/static/chunks/pages/_error-022e4ac7bbb9914f.js +0 -1
  204. package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  205. package/.next/static/chunks/webpack-3fcacae817f3ffab.js +0 -1
  206. 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
- ## Unreleased
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
- ![TokenTrace overview dashboard](docs/assets/overview.png)
11
+ ![TokenTrace 0.7.0 overview dashboard](docs/assets/overview-0.7.0.png)
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 TypeScript checks
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
+ ![TokenTrace 0.7.0 overview dashboard](docs/assets/overview-0.7.0.png)
203
+
204
+ ![TokenTrace 0.7.0 Usage Intelligence review queue](docs/assets/usage-intelligence-0.7.0.png)
205
+
206
+ ![TokenTrace 0.7.0 session comparison flags](docs/assets/sessions-0.7.0.png)
207
+
208
+ ![TokenTrace 0.7.0 project signals](docs/assets/projects-0.7.0.png)
209
+
210
+ ![TokenTrace 0.7.0 local guardrail settings](docs/assets/settings-guardrails-0.7.0.png)
211
+
212
+ Stable Daily Tool views from `0.6.0`:
213
+
214
+ ![TokenTrace 0.6.0 Scan Doctor](docs/assets/doctor-0.6.0.png)
215
+
194
216
  CLI startup and help:
195
217
 
196
218
  ![TokenTrace CLI help](docs/assets/cli-help.gif)
@@ -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
- - Published Next.js server bundles are intentionally left readable instead of server-minified so Socket, npm, maintainers, and users can inspect generated runtime code.
240
- - `npm run package:inspect` fails if generated app route bundles look packed or unreadable.
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 --omit=dev --audit-level=high`.
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
- - No `preinstall`, `install`, or `postinstall` npm lifecycle scripts are used.
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
- - Generated Next.js server bundles are intentionally published without server
18
- minification so package scanners and maintainers can inspect them.
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);
@@ -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="Optimisation Insights"
28
- description="Deterministic recommendations based on imported usage patterns."
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
- <EmptyState
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>
@@ -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>
@@ -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}