tokentrace 0.1.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 (224) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-build-manifest.json +167 -0
  3. package/.next/app-path-routes-manifest.json +22 -0
  4. package/.next/build-manifest.json +33 -0
  5. package/.next/export-marker.json +6 -0
  6. package/.next/images-manifest.json +58 -0
  7. package/.next/next-minimal-server.js.nft.json +1 -0
  8. package/.next/next-server.js.nft.json +1 -0
  9. package/.next/package.json +1 -0
  10. package/.next/prerender-manifest.json +37 -0
  11. package/.next/react-loadable-manifest.json +1 -0
  12. package/.next/required-server-files.json +323 -0
  13. package/.next/routes-manifest.json +119 -0
  14. package/.next/server/app/_not-found/page.js +2 -0
  15. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  17. package/.next/server/app/_not-found.html +1 -0
  18. package/.next/server/app/_not-found.meta +8 -0
  19. package/.next/server/app/_not-found.rsc +37 -0
  20. package/.next/server/app/api/analytics/route.js +1 -0
  21. package/.next/server/app/api/analytics/route.js.nft.json +1 -0
  22. package/.next/server/app/api/analytics/route_client-reference-manifest.js +1 -0
  23. package/.next/server/app/api/data/route.js +151 -0
  24. package/.next/server/app/api/data/route.js.nft.json +1 -0
  25. package/.next/server/app/api/data/route_client-reference-manifest.js +1 -0
  26. package/.next/server/app/api/export/route.js +1 -0
  27. package/.next/server/app/api/export/route.js.nft.json +1 -0
  28. package/.next/server/app/api/export/route_client-reference-manifest.js +1 -0
  29. package/.next/server/app/api/files/route.js +1 -0
  30. package/.next/server/app/api/files/route.js.nft.json +1 -0
  31. package/.next/server/app/api/files/route_client-reference-manifest.js +1 -0
  32. package/.next/server/app/api/prices/route.js +151 -0
  33. package/.next/server/app/api/prices/route.js.nft.json +1 -0
  34. package/.next/server/app/api/prices/route_client-reference-manifest.js +1 -0
  35. package/.next/server/app/api/scan/route.js +144 -0
  36. package/.next/server/app/api/scan/route.js.nft.json +1 -0
  37. package/.next/server/app/api/scan/route_client-reference-manifest.js +1 -0
  38. package/.next/server/app/api/settings/route.js +128 -0
  39. package/.next/server/app/api/settings/route.js.nft.json +1 -0
  40. package/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
  41. package/.next/server/app/debug/page.js +2 -0
  42. package/.next/server/app/debug/page.js.nft.json +1 -0
  43. package/.next/server/app/debug/page_client-reference-manifest.js +1 -0
  44. package/.next/server/app/diagnostics/page.js +2 -0
  45. package/.next/server/app/diagnostics/page.js.nft.json +1 -0
  46. package/.next/server/app/diagnostics/page_client-reference-manifest.js +1 -0
  47. package/.next/server/app/discovery/page.js +2 -0
  48. package/.next/server/app/discovery/page.js.nft.json +1 -0
  49. package/.next/server/app/discovery/page_client-reference-manifest.js +1 -0
  50. package/.next/server/app/models/page.js +2 -0
  51. package/.next/server/app/models/page.js.nft.json +1 -0
  52. package/.next/server/app/models/page_client-reference-manifest.js +1 -0
  53. package/.next/server/app/optimisation/page.js +2 -0
  54. package/.next/server/app/optimisation/page.js.nft.json +1 -0
  55. package/.next/server/app/optimisation/page_client-reference-manifest.js +1 -0
  56. package/.next/server/app/page.js +2 -0
  57. package/.next/server/app/page.js.nft.json +1 -0
  58. package/.next/server/app/page_client-reference-manifest.js +1 -0
  59. package/.next/server/app/parser-debug/page.js +2 -0
  60. package/.next/server/app/parser-debug/page.js.nft.json +1 -0
  61. package/.next/server/app/parser-debug/page_client-reference-manifest.js +1 -0
  62. package/.next/server/app/pricing/page.js +152 -0
  63. package/.next/server/app/pricing/page.js.nft.json +1 -0
  64. package/.next/server/app/pricing/page_client-reference-manifest.js +1 -0
  65. package/.next/server/app/projects/page.js +2 -0
  66. package/.next/server/app/projects/page.js.nft.json +1 -0
  67. package/.next/server/app/projects/page_client-reference-manifest.js +1 -0
  68. package/.next/server/app/sessions/page.js +2 -0
  69. package/.next/server/app/sessions/page.js.nft.json +1 -0
  70. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -0
  71. package/.next/server/app/settings/page.js +129 -0
  72. package/.next/server/app/settings/page.js.nft.json +1 -0
  73. package/.next/server/app/settings/page_client-reference-manifest.js +1 -0
  74. package/.next/server/app/tools/page.js +2 -0
  75. package/.next/server/app/tools/page.js.nft.json +1 -0
  76. package/.next/server/app/tools/page_client-reference-manifest.js +1 -0
  77. package/.next/server/app-paths-manifest.json +22 -0
  78. package/.next/server/chunks/123.js +9 -0
  79. package/.next/server/chunks/153.js +1 -0
  80. package/.next/server/chunks/237.js +13 -0
  81. package/.next/server/chunks/331.js +22 -0
  82. package/.next/server/chunks/366.js +1 -0
  83. package/.next/server/chunks/444.js +267 -0
  84. package/.next/server/chunks/611.js +6 -0
  85. package/.next/server/chunks/692.js +1 -0
  86. package/.next/server/chunks/779.js +1 -0
  87. package/.next/server/chunks/815.js +1 -0
  88. package/.next/server/chunks/868.js +1 -0
  89. package/.next/server/functions-config-manifest.json +4 -0
  90. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  91. package/.next/server/middleware-build-manifest.js +1 -0
  92. package/.next/server/middleware-manifest.json +6 -0
  93. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  94. package/.next/server/next-font-manifest.js +1 -0
  95. package/.next/server/next-font-manifest.json +1 -0
  96. package/.next/server/pages/404.html +1 -0
  97. package/.next/server/pages/500.html +1 -0
  98. package/.next/server/pages/_app.js +1 -0
  99. package/.next/server/pages/_app.js.nft.json +1 -0
  100. package/.next/server/pages/_document.js +1 -0
  101. package/.next/server/pages/_document.js.nft.json +1 -0
  102. package/.next/server/pages/_error.js +19 -0
  103. package/.next/server/pages/_error.js.nft.json +1 -0
  104. package/.next/server/pages-manifest.json +6 -0
  105. package/.next/server/server-reference-manifest.js +1 -0
  106. package/.next/server/server-reference-manifest.json +1 -0
  107. package/.next/server/webpack-runtime.js +1 -0
  108. package/.next/static/Fh8usqK3dgfncUx9s3VR1/_buildManifest.js +1 -0
  109. package/.next/static/Fh8usqK3dgfncUx9s3VR1/_ssgManifest.js +1 -0
  110. package/.next/static/chunks/125-ab0f8db8f84c1166.js +1 -0
  111. package/.next/static/chunks/255-e881f48ae1d2333a.js +1 -0
  112. package/.next/static/chunks/4bd1b696-409494caf8c83275.js +1 -0
  113. package/.next/static/chunks/619-f072ac750404f9da.js +1 -0
  114. package/.next/static/chunks/850-8bc31e41590b5831.js +1 -0
  115. package/.next/static/chunks/938-23236de1c47554ea.js +1 -0
  116. package/.next/static/chunks/app/_not-found/page-6d75243350d9e0b5.js +1 -0
  117. package/.next/static/chunks/app/api/analytics/route-33d3f29973de91a4.js +1 -0
  118. package/.next/static/chunks/app/api/data/route-33d3f29973de91a4.js +1 -0
  119. package/.next/static/chunks/app/api/export/route-33d3f29973de91a4.js +1 -0
  120. package/.next/static/chunks/app/api/files/route-33d3f29973de91a4.js +1 -0
  121. package/.next/static/chunks/app/api/prices/route-33d3f29973de91a4.js +1 -0
  122. package/.next/static/chunks/app/api/scan/route-33d3f29973de91a4.js +1 -0
  123. package/.next/static/chunks/app/api/settings/route-33d3f29973de91a4.js +1 -0
  124. package/.next/static/chunks/app/debug/page-33d3f29973de91a4.js +1 -0
  125. package/.next/static/chunks/app/diagnostics/page-053a5e810a59e548.js +1 -0
  126. package/.next/static/chunks/app/discovery/page-33d3f29973de91a4.js +1 -0
  127. package/.next/static/chunks/app/layout-8942804176ff26f3.js +1 -0
  128. package/.next/static/chunks/app/models/page-c0acf74dd8197e01.js +1 -0
  129. package/.next/static/chunks/app/optimisation/page-33d3f29973de91a4.js +1 -0
  130. package/.next/static/chunks/app/page-b6886ec802c03cbf.js +1 -0
  131. package/.next/static/chunks/app/parser-debug/page-33d3f29973de91a4.js +1 -0
  132. package/.next/static/chunks/app/pricing/page-5e27b1ae27314539.js +1 -0
  133. package/.next/static/chunks/app/projects/page-b6886ec802c03cbf.js +1 -0
  134. package/.next/static/chunks/app/sessions/page-0abcdc88aac9dcaf.js +1 -0
  135. package/.next/static/chunks/app/settings/page-59fc80673f0750cd.js +1 -0
  136. package/.next/static/chunks/app/tools/page-c0acf74dd8197e01.js +1 -0
  137. package/.next/static/chunks/framework-3457b9c2619cdd96.js +1 -0
  138. package/.next/static/chunks/main-8744520a8a31e6ae.js +1 -0
  139. package/.next/static/chunks/main-app-e9ccddef393e28c3.js +1 -0
  140. package/.next/static/chunks/pages/_app-5addca2b3b969fde.js +1 -0
  141. package/.next/static/chunks/pages/_error-022e4ac7bbb9914f.js +1 -0
  142. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  143. package/.next/static/chunks/webpack-3fcacae817f3ffab.js +1 -0
  144. package/.next/static/css/366bb38b386229a5.css +3 -0
  145. package/LICENSE +21 -0
  146. package/README.md +216 -0
  147. package/app/api/analytics/route.ts +8 -0
  148. package/app/api/data/route.ts +9 -0
  149. package/app/api/export/route.ts +26 -0
  150. package/app/api/files/route.ts +8 -0
  151. package/app/api/prices/route.ts +33 -0
  152. package/app/api/scan/route.ts +15 -0
  153. package/app/api/settings/route.ts +25 -0
  154. package/app/debug/page.tsx +101 -0
  155. package/app/diagnostics/page.tsx +113 -0
  156. package/app/discovery/page.tsx +61 -0
  157. package/app/globals.css +51 -0
  158. package/app/layout.tsx +30 -0
  159. package/app/models/page.tsx +97 -0
  160. package/app/optimisation/page.tsx +67 -0
  161. package/app/page.tsx +164 -0
  162. package/app/parser-debug/page.tsx +57 -0
  163. package/app/pricing/page.tsx +18 -0
  164. package/app/projects/page.tsx +111 -0
  165. package/app/sessions/page.tsx +24 -0
  166. package/app/settings/page.tsx +26 -0
  167. package/app/tools/page.tsx +92 -0
  168. package/bin/tokentrace.js +316 -0
  169. package/components/charts/rank-bar-chart.tsx +69 -0
  170. package/components/charts/trend-chart.tsx +123 -0
  171. package/components/empty-state.tsx +14 -0
  172. package/components/pricing-settings.tsx +171 -0
  173. package/components/session-explorer.tsx +210 -0
  174. package/components/settings-panel.tsx +203 -0
  175. package/components/sidebar.tsx +88 -0
  176. package/components/ui/badge.tsx +30 -0
  177. package/components/ui/button.tsx +47 -0
  178. package/components/ui/card.tsx +22 -0
  179. package/components/ui/input.tsx +19 -0
  180. package/components/ui/label.tsx +6 -0
  181. package/components/ui/table.tsx +31 -0
  182. package/components/ui/textarea.tsx +18 -0
  183. package/components.json +16 -0
  184. package/dist/runtime/db-migrate.mjs +410 -0
  185. package/dist/runtime/db-seed.mjs +506 -0
  186. package/dist/runtime/reset.mjs +519 -0
  187. package/dist/runtime/scan.mjs +1817 -0
  188. package/fixtures/generic-jsonl/sample.jsonl +2 -0
  189. package/next.config.mjs +7 -0
  190. package/package.json +96 -0
  191. package/postcss.config.mjs +8 -0
  192. package/scripts/build-cli-runtime.mjs +40 -0
  193. package/scripts/db-migrate.ts +5 -0
  194. package/scripts/db-seed.ts +5 -0
  195. package/scripts/reset.ts +5 -0
  196. package/scripts/scan.ts +30 -0
  197. package/src/db/client.ts +32 -0
  198. package/src/db/migrate-core.ts +147 -0
  199. package/src/db/reset.ts +14 -0
  200. package/src/db/schema.ts +259 -0
  201. package/src/db/seed.ts +110 -0
  202. package/src/db/settings.ts +47 -0
  203. package/src/ingestion/adapters/claude-code.ts +78 -0
  204. package/src/ingestion/adapters/codex-cli.ts +82 -0
  205. package/src/ingestion/adapters/generic-json.ts +93 -0
  206. package/src/ingestion/adapters/generic-jsonl.ts +62 -0
  207. package/src/ingestion/adapters/generic-log.ts +144 -0
  208. package/src/ingestion/adapters/generic-records.ts +178 -0
  209. package/src/ingestion/adapters/helpers.ts +309 -0
  210. package/src/ingestion/adapters/index.ts +15 -0
  211. package/src/ingestion/discovery.ts +130 -0
  212. package/src/ingestion/persist.ts +283 -0
  213. package/src/ingestion/scan.ts +247 -0
  214. package/src/ingestion/types.ts +78 -0
  215. package/src/lib/analytics.ts +592 -0
  216. package/src/lib/cost.ts +62 -0
  217. package/src/lib/csv.ts +15 -0
  218. package/src/lib/format.ts +51 -0
  219. package/src/lib/ids.ts +23 -0
  220. package/src/lib/pricing.ts +86 -0
  221. package/src/lib/token-estimator.ts +24 -0
  222. package/src/lib/utils.ts +6 -0
  223. package/tailwind.config.ts +53 -0
  224. package/tsconfig.json +28 -0
package/app/page.tsx ADDED
@@ -0,0 +1,164 @@
1
+ import Link from "next/link";
2
+ import { ArrowRight, Coins, Database, MessageSquare, Sparkles } from "lucide-react";
3
+ import { EmptyState } from "@/components/empty-state";
4
+ import { RankBarChart } from "@/components/charts/rank-bar-chart";
5
+ import { TrendChart } from "@/components/charts/trend-chart";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
9
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
10
+ import { getAnalyticsData } from "@/src/lib/analytics";
11
+ import { formatCurrency, formatTokens, percent } from "@/src/lib/format";
12
+
13
+ export const dynamic = "force-dynamic";
14
+
15
+ function MetricCard({
16
+ label,
17
+ value,
18
+ detail,
19
+ icon: Icon
20
+ }: {
21
+ label: string;
22
+ value: string;
23
+ detail?: string;
24
+ icon: typeof Database;
25
+ }) {
26
+ return (
27
+ <Card>
28
+ <CardHeader className="flex flex-row items-center justify-between space-y-0">
29
+ <CardTitle>{label}</CardTitle>
30
+ <Icon className="h-4 w-4 text-muted-foreground" />
31
+ </CardHeader>
32
+ <CardContent>
33
+ <div className="text-2xl font-semibold">{value}</div>
34
+ {detail ? <p className="mt-1 text-xs text-muted-foreground">{detail}</p> : null}
35
+ </CardContent>
36
+ </Card>
37
+ );
38
+ }
39
+
40
+ export default function OverviewPage() {
41
+ const data = getAnalyticsData();
42
+ const { summary } = data;
43
+
44
+ return (
45
+ <div className="space-y-6">
46
+ <div className="flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
47
+ <div>
48
+ <h1 className="text-2xl font-semibold tracking-normal">Overview</h1>
49
+ <p className="text-sm text-muted-foreground">
50
+ Local token, cost, model, and session analytics across AI CLI tools.
51
+ </p>
52
+ </div>
53
+ <Button asChild>
54
+ <Link href="/settings">
55
+ Configure scan <ArrowRight className="h-4 w-4" />
56
+ </Link>
57
+ </Button>
58
+ </div>
59
+
60
+ {summary.interactions === 0 ? (
61
+ <EmptyState
62
+ title="No usage imported yet"
63
+ description="Add custom folders if needed, run a scan from Settings, then return here for analytics."
64
+ />
65
+ ) : null}
66
+
67
+ <div className="dashboard-grid">
68
+ <MetricCard
69
+ label="Total tokens"
70
+ value={formatTokens(summary.totalTokens)}
71
+ detail={`${formatTokens(summary.inputTokens)} input, ${formatTokens(summary.outputTokens)} output`}
72
+ icon={Database}
73
+ />
74
+ <MetricCard
75
+ label="Cached tokens"
76
+ value={formatTokens(summary.cachedTokens)}
77
+ detail={`${formatTokens(summary.reasoningTokens)} reasoning tokens`}
78
+ icon={Sparkles}
79
+ />
80
+ <MetricCard
81
+ label="Estimated cost"
82
+ value={formatCurrency(summary.totalCost)}
83
+ detail={`${formatCurrency(summary.exactCost)} exact, ${formatCurrency(summary.estimatedCost)} estimated, ${summary.unknownCostInteractions.toLocaleString()} unknown`}
84
+ icon={Coins}
85
+ />
86
+ <MetricCard
87
+ label="Sessions"
88
+ value={summary.sessions.toLocaleString()}
89
+ detail={`${summary.interactions.toLocaleString()} interactions`}
90
+ icon={MessageSquare}
91
+ />
92
+ </div>
93
+
94
+ <div className="grid gap-4 lg:grid-cols-2">
95
+ <Card>
96
+ <CardHeader>
97
+ <CardTitle>Token Trend</CardTitle>
98
+ <CardDescription>Daily, weekly, and monthly token usage.</CardDescription>
99
+ </CardHeader>
100
+ <CardContent>
101
+ <TrendChart data={data.trends} metric="totalTokens" />
102
+ </CardContent>
103
+ </Card>
104
+ <Card>
105
+ <CardHeader>
106
+ <CardTitle>Cost Trend</CardTitle>
107
+ <CardDescription>Costs use editable model prices from Pricing.</CardDescription>
108
+ </CardHeader>
109
+ <CardContent>
110
+ <TrendChart data={data.trends} metric="cost" color="#ea580c" />
111
+ </CardContent>
112
+ </Card>
113
+ </div>
114
+
115
+ <div className="grid gap-4 lg:grid-cols-[1fr_1.2fr]">
116
+ <Card>
117
+ <CardHeader>
118
+ <CardTitle>Usage By Tool</CardTitle>
119
+ <CardDescription>Top tools by total tokens.</CardDescription>
120
+ </CardHeader>
121
+ <CardContent>
122
+ <RankBarChart data={data.tools as unknown as Array<Record<string, string | number | null>>} nameKey="tool" valueKey="totalTokens" />
123
+ </CardContent>
124
+ </Card>
125
+ <Card>
126
+ <CardHeader>
127
+ <CardTitle>Current Mix</CardTitle>
128
+ <CardDescription>
129
+ Most used tool: {summary.mostUsedTool}. Most used model: {summary.mostUsedModel}.
130
+ </CardDescription>
131
+ </CardHeader>
132
+ <CardContent className="table-scroll">
133
+ <Table>
134
+ <TableHeader>
135
+ <TableRow>
136
+ <TableHead>Tool</TableHead>
137
+ <TableHead>Provider</TableHead>
138
+ <TableHead>Tokens</TableHead>
139
+ <TableHead>Cost</TableHead>
140
+ <TableHead>Cache</TableHead>
141
+ </TableRow>
142
+ </TableHeader>
143
+ <TableBody>
144
+ {data.tools.slice(0, 5).map((tool) => (
145
+ <TableRow key={tool.tool}>
146
+ <TableCell className="font-medium">{tool.tool}</TableCell>
147
+ <TableCell>{tool.provider}</TableCell>
148
+ <TableCell>{formatTokens(tool.totalTokens)}</TableCell>
149
+ <TableCell>{formatCurrency(tool.cost)}</TableCell>
150
+ <TableCell>
151
+ <Badge variant={tool.cacheEfficiency > 0.1 ? "success" : "secondary"}>
152
+ {percent(tool.cacheEfficiency)}
153
+ </Badge>
154
+ </TableCell>
155
+ </TableRow>
156
+ ))}
157
+ </TableBody>
158
+ </Table>
159
+ </CardContent>
160
+ </Card>
161
+ </div>
162
+ </div>
163
+ );
164
+ }
@@ -0,0 +1,57 @@
1
+ import { Badge } from "@/components/ui/badge";
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
3
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
4
+ import { getDebugData } from "@/src/lib/analytics";
5
+
6
+ export const dynamic = "force-dynamic";
7
+
8
+ export default function ParserDebugPage() {
9
+ const { scanFiles } = getDebugData();
10
+
11
+ return (
12
+ <div className="space-y-6">
13
+ <div>
14
+ <h1 className="text-2xl font-semibold tracking-normal">Parser Debug</h1>
15
+ <p className="text-sm text-muted-foreground">
16
+ Inspect adapter selection, parser confidence, extraction confidence, warnings, and failures.
17
+ </p>
18
+ </div>
19
+ <Card>
20
+ <CardHeader>
21
+ <CardTitle>Parser Results</CardTitle>
22
+ <CardDescription>Useful when vendors change local file formats.</CardDescription>
23
+ </CardHeader>
24
+ <CardContent className="table-scroll">
25
+ <Table>
26
+ <TableHeader>
27
+ <TableRow>
28
+ <TableHead>Parser</TableHead>
29
+ <TableHead>Status</TableHead>
30
+ <TableHead>Parser confidence</TableHead>
31
+ <TableHead>Token confidence</TableHead>
32
+ <TableHead>Imported</TableHead>
33
+ <TableHead>Warnings</TableHead>
34
+ <TableHead>Errors</TableHead>
35
+ <TableHead>File</TableHead>
36
+ </TableRow>
37
+ </TableHeader>
38
+ <TableBody>
39
+ {scanFiles.map((file) => (
40
+ <TableRow key={file.id}>
41
+ <TableCell>{file.parser ?? "None"}</TableCell>
42
+ <TableCell><Badge variant={file.errors.length ? "destructive" : file.parser ? "success" : "secondary"}>{file.status}</Badge></TableCell>
43
+ <TableCell>{file.rawMetadata.confidence == null ? "Unknown" : Number(file.rawMetadata.confidence).toFixed(2)}</TableCell>
44
+ <TableCell className="max-w-xs truncate">{JSON.stringify(file.rawMetadata.tokenConfidence ?? { unknown: 0 })}</TableCell>
45
+ <TableCell>{file.recordsImported.toLocaleString()}</TableCell>
46
+ <TableCell className="max-w-sm truncate">{file.warnings.join("; ") || "None"}</TableCell>
47
+ <TableCell className="max-w-sm truncate">{file.errors.join("; ") || "None"}</TableCell>
48
+ <TableCell className="max-w-md truncate font-mono text-xs">{file.path}</TableCell>
49
+ </TableRow>
50
+ ))}
51
+ </TableBody>
52
+ </Table>
53
+ </CardContent>
54
+ </Card>
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,18 @@
1
+ import { PricingSettings } from "@/components/pricing-settings";
2
+ import { getPricingRows } from "@/src/lib/pricing";
3
+
4
+ export const dynamic = "force-dynamic";
5
+
6
+ export default function PricingPage() {
7
+ return (
8
+ <div className="space-y-6">
9
+ <div>
10
+ <h1 className="text-2xl font-semibold tracking-normal">Pricing Configuration</h1>
11
+ <p className="text-sm text-muted-foreground">
12
+ Configure editable provider and model prices for transparent local cost estimates.
13
+ </p>
14
+ </div>
15
+ <PricingSettings initialRows={getPricingRows()} />
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,111 @@
1
+ import Link from "next/link";
2
+ import { RankBarChart } from "@/components/charts/rank-bar-chart";
3
+ import { TrendChart } from "@/components/charts/trend-chart";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
5
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
6
+ import { getAnalyticsData } from "@/src/lib/analytics";
7
+ import { formatCurrency, formatDate, formatTokens } from "@/src/lib/format";
8
+
9
+ export const dynamic = "force-dynamic";
10
+
11
+ export default function ProjectAnalyticsPage() {
12
+ const data = getAnalyticsData();
13
+ const mostExpensive = data.projects[0];
14
+
15
+ return (
16
+ <div className="space-y-6">
17
+ <div>
18
+ <h1 className="text-2xl font-semibold tracking-normal">Project Analytics</h1>
19
+ <p className="text-sm text-muted-foreground">
20
+ Group usage by local repository or inferred project path.
21
+ </p>
22
+ </div>
23
+
24
+ <div className="grid gap-4 lg:grid-cols-[1fr_1.4fr]">
25
+ <Card>
26
+ <CardHeader>
27
+ <CardTitle>Most Expensive Project</CardTitle>
28
+ <CardDescription>Based on configured cost estimates.</CardDescription>
29
+ </CardHeader>
30
+ <CardContent>
31
+ {mostExpensive ? (
32
+ <div className="space-y-2">
33
+ <div className="text-2xl font-semibold">{mostExpensive.project}</div>
34
+ <div className="text-sm text-muted-foreground">{mostExpensive.path}</div>
35
+ <div className="text-sm">
36
+ {formatTokens(mostExpensive.totalTokens)} tokens, {formatCurrency(mostExpensive.cost)}
37
+ </div>
38
+ </div>
39
+ ) : (
40
+ <div className="text-sm text-muted-foreground">No project data imported yet.</div>
41
+ )}
42
+ </CardContent>
43
+ </Card>
44
+ <Card>
45
+ <CardHeader>
46
+ <CardTitle>Tokens By Project</CardTitle>
47
+ <CardDescription>Top local projects by token usage.</CardDescription>
48
+ </CardHeader>
49
+ <CardContent>
50
+ <RankBarChart
51
+ data={data.projects as unknown as Array<Record<string, string | number | null>>}
52
+ nameKey="project"
53
+ valueKey="totalTokens"
54
+ />
55
+ </CardContent>
56
+ </Card>
57
+ </div>
58
+
59
+ <Card>
60
+ <CardHeader>
61
+ <CardTitle>Project Usage Trend</CardTitle>
62
+ <CardDescription>Overall local project activity over time.</CardDescription>
63
+ </CardHeader>
64
+ <CardContent>
65
+ <TrendChart data={data.trends} metric="totalTokens" color="#0f766e" />
66
+ </CardContent>
67
+ </Card>
68
+
69
+ <Card>
70
+ <CardHeader>
71
+ <CardTitle>Project Table</CardTitle>
72
+ <CardDescription>Drill into sessions using the session explorer filters.</CardDescription>
73
+ </CardHeader>
74
+ <CardContent className="table-scroll">
75
+ <Table>
76
+ <TableHeader>
77
+ <TableRow>
78
+ <TableHead>Project</TableHead>
79
+ <TableHead>Path</TableHead>
80
+ <TableHead>Tokens</TableHead>
81
+ <TableHead>Cost</TableHead>
82
+ <TableHead>Sessions</TableHead>
83
+ <TableHead>Interactions</TableHead>
84
+ <TableHead>Output/Input</TableHead>
85
+ <TableHead>Last used</TableHead>
86
+ </TableRow>
87
+ </TableHeader>
88
+ <TableBody>
89
+ {data.projects.map((project) => (
90
+ <TableRow key={project.id}>
91
+ <TableCell className="font-medium">
92
+ <Link className="hover:underline" href={`/sessions?project=${encodeURIComponent(project.project)}`}>
93
+ {project.project}
94
+ </Link>
95
+ </TableCell>
96
+ <TableCell className="max-w-sm truncate">{project.path}</TableCell>
97
+ <TableCell>{formatTokens(project.totalTokens)}</TableCell>
98
+ <TableCell>{formatCurrency(project.cost)}</TableCell>
99
+ <TableCell>{project.sessions.toLocaleString()}</TableCell>
100
+ <TableCell>{project.interactions.toLocaleString()}</TableCell>
101
+ <TableCell>{project.outputInputRatio.toFixed(2)}x</TableCell>
102
+ <TableCell>{formatDate(project.lastUsedAt)}</TableCell>
103
+ </TableRow>
104
+ ))}
105
+ </TableBody>
106
+ </Table>
107
+ </CardContent>
108
+ </Card>
109
+ </div>
110
+ );
111
+ }
@@ -0,0 +1,24 @@
1
+ import { SessionExplorer } from "@/components/session-explorer";
2
+ import { getAnalyticsData } from "@/src/lib/analytics";
3
+
4
+ export const dynamic = "force-dynamic";
5
+
6
+ export default async function SessionsPage({
7
+ searchParams
8
+ }: {
9
+ searchParams?: Promise<{ project?: string }>;
10
+ }) {
11
+ const params = await searchParams;
12
+ const data = getAnalyticsData();
13
+ return (
14
+ <div className="space-y-6">
15
+ <div>
16
+ <h1 className="text-2xl font-semibold tracking-normal">Session Explorer</h1>
17
+ <p className="text-sm text-muted-foreground">
18
+ Search and filter imported sessions by tool, model, project, cost, and estimation status.
19
+ </p>
20
+ </div>
21
+ <SessionExplorer sessions={data.sessions} initialProject={params?.project} />
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,26 @@
1
+ import { getDatabasePath } from "@/src/db/client";
2
+ import { getAppSettings } from "@/src/db/settings";
3
+ import { SettingsPanel } from "@/components/settings-panel";
4
+
5
+ export const dynamic = "force-dynamic";
6
+
7
+ export default function SettingsPage() {
8
+ const settings = getAppSettings();
9
+
10
+ return (
11
+ <div className="space-y-6">
12
+ <div>
13
+ <h1 className="text-2xl font-semibold tracking-normal">Settings</h1>
14
+ <p className="text-sm text-muted-foreground">
15
+ Configure local discovery folders, privacy controls, scans, and imported data.
16
+ </p>
17
+ </div>
18
+ <SettingsPanel
19
+ initialSettings={{
20
+ ...settings,
21
+ databasePath: getDatabasePath()
22
+ }}
23
+ />
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,92 @@
1
+ import { RankBarChart } from "@/components/charts/rank-bar-chart";
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
3
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
4
+ import { getAnalyticsData } from "@/src/lib/analytics";
5
+ import { formatCurrency, formatTokens, percent } from "@/src/lib/format";
6
+
7
+ export const dynamic = "force-dynamic";
8
+
9
+ export default function ToolComparisonPage() {
10
+ const data = getAnalyticsData();
11
+
12
+ return (
13
+ <div className="space-y-6">
14
+ <div>
15
+ <h1 className="text-2xl font-semibold tracking-normal">Tool Comparison</h1>
16
+ <p className="text-sm text-muted-foreground">
17
+ Compare Claude Code, Codex CLI, and generic imported tools.
18
+ </p>
19
+ </div>
20
+
21
+ <div className="grid gap-4 lg:grid-cols-2">
22
+ <Card>
23
+ <CardHeader>
24
+ <CardTitle>Tokens By Tool</CardTitle>
25
+ <CardDescription>Total token volume by CLI source.</CardDescription>
26
+ </CardHeader>
27
+ <CardContent>
28
+ <RankBarChart
29
+ data={data.tools as unknown as Array<Record<string, string | number | null>>}
30
+ nameKey="tool"
31
+ valueKey="totalTokens"
32
+ />
33
+ </CardContent>
34
+ </Card>
35
+ <Card>
36
+ <CardHeader>
37
+ <CardTitle>Cost By Tool</CardTitle>
38
+ <CardDescription>Costs depend on configured model prices.</CardDescription>
39
+ </CardHeader>
40
+ <CardContent>
41
+ <RankBarChart
42
+ data={data.tools as unknown as Array<Record<string, string | number | null>>}
43
+ nameKey="tool"
44
+ valueKey="cost"
45
+ mode="cost"
46
+ color="#0f766e"
47
+ />
48
+ </CardContent>
49
+ </Card>
50
+ </div>
51
+
52
+ <Card>
53
+ <CardHeader>
54
+ <CardTitle>Comparison Table</CardTitle>
55
+ <CardDescription>Efficiency, averages, cache behavior, and expensive models.</CardDescription>
56
+ </CardHeader>
57
+ <CardContent className="table-scroll">
58
+ <Table>
59
+ <TableHeader>
60
+ <TableRow>
61
+ <TableHead>Tool</TableHead>
62
+ <TableHead>Provider</TableHead>
63
+ <TableHead>Tokens</TableHead>
64
+ <TableHead>Cost</TableHead>
65
+ <TableHead>Avg/session</TableHead>
66
+ <TableHead>Avg/interaction</TableHead>
67
+ <TableHead>Output/Input</TableHead>
68
+ <TableHead>Cache</TableHead>
69
+ <TableHead>Most expensive model</TableHead>
70
+ </TableRow>
71
+ </TableHeader>
72
+ <TableBody>
73
+ {data.tools.map((tool) => (
74
+ <TableRow key={`${tool.provider}-${tool.tool}`}>
75
+ <TableCell className="font-medium">{tool.tool}</TableCell>
76
+ <TableCell>{tool.provider}</TableCell>
77
+ <TableCell>{formatTokens(tool.totalTokens)}</TableCell>
78
+ <TableCell>{formatCurrency(tool.cost)}</TableCell>
79
+ <TableCell>{formatTokens(tool.averageTokensPerSession)}</TableCell>
80
+ <TableCell>{formatTokens(tool.averageTokensPerInteraction)}</TableCell>
81
+ <TableCell>{tool.outputInputRatio.toFixed(2)}x</TableCell>
82
+ <TableCell>{percent(tool.cacheEfficiency)}</TableCell>
83
+ <TableCell>{tool.mostExpensiveModel}</TableCell>
84
+ </TableRow>
85
+ ))}
86
+ </TableBody>
87
+ </Table>
88
+ </CardContent>
89
+ </Card>
90
+ </div>
91
+ );
92
+ }