tokentrace 0.19.2 → 0.21.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 (64) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +17 -4
  3. package/TOKENTRACE_AGENT.md +32 -5
  4. package/app/api/evidence-pack/route.ts +3 -40
  5. package/app/evidence/evidence-context-panel.tsx +1 -1
  6. package/app/guide/guide-content.ts +9 -6
  7. package/app/guide/page.tsx +4 -4
  8. package/app/page.tsx +6 -6
  9. package/app/repair/page.tsx +2 -2
  10. package/components/overview/summary-cards.tsx +64 -34
  11. package/components/sidebar.tsx +72 -28
  12. package/dist/cli/main.mjs +22 -0
  13. package/dist/runtime/agent.mjs +65 -3
  14. package/dist/runtime/anomalies.mjs +7 -2
  15. package/dist/runtime/chatgpt-app.mjs +3901 -0
  16. package/dist/runtime/digest.mjs +7 -2
  17. package/dist/runtime/doctor.mjs +7 -2
  18. package/dist/runtime/evidence.mjs +7 -2
  19. package/dist/runtime/insights.mjs +7 -2
  20. package/dist/runtime/mcp.mjs +3540 -3137
  21. package/dist/runtime/pricing-refresh.mjs +7 -2
  22. package/dist/runtime/query.mjs +7 -2
  23. package/dist/runtime/repair.mjs +7 -2
  24. package/dist/runtime/report.mjs +7 -2
  25. package/dist/runtime/review.mjs +7 -2
  26. package/dist/runtime/scan.mjs +7 -2
  27. package/dist/runtime/status.mjs +7 -2
  28. package/docs/CHATGPT_APP_PROTOTYPE.md +103 -0
  29. package/docs/CHATGPT_APP_RELEASE.md +131 -0
  30. package/docs/agent-adoption.md +24 -10
  31. package/docs/chatgpt-app/README.md +58 -0
  32. package/docs/chatgpt-app/assets/icon.png +0 -0
  33. package/docs/chatgpt-app/assets/listing-card.svg +34 -0
  34. package/docs/chatgpt-app/assets/widget-preview.svg +24 -0
  35. package/docs/chatgpt-app/dashboard-fields.json +52 -0
  36. package/docs/chatgpt-app/manual-release-steps.md +139 -0
  37. package/docs/chatgpt-app/privacy-and-data.md +39 -0
  38. package/docs/chatgpt-app/review-response-template.md +53 -0
  39. package/docs/chatgpt-app/screenshot-checklist.md +46 -0
  40. package/docs/chatgpt-app/submission-copy.md +45 -0
  41. package/docs/chatgpt-app/test-prompts-and-responses.md +61 -0
  42. package/llms.txt +24 -0
  43. package/next.config.mjs +8 -2
  44. package/package.json +11 -4
  45. package/scripts/build-cli-runtime.mjs +1 -0
  46. package/scripts/chatgpt-app-release-check.mjs +489 -0
  47. package/scripts/chatgpt-app.ts +107 -0
  48. package/scripts/package-inspect.mjs +6 -1
  49. package/scripts/preflight.ts +26 -0
  50. package/scripts/smoke-packed-install.mjs +5 -0
  51. package/scripts/visual-smoke.mjs +19 -7
  52. package/server.json +3 -3
  53. package/src/cli/commands.ts +18 -0
  54. package/src/cli/help.ts +6 -0
  55. package/src/lib/agent-discovery.ts +60 -1
  56. package/src/lib/chatgpt-app/prototype.ts +452 -0
  57. package/src/lib/chatgpt-app/server.ts +162 -0
  58. package/src/lib/evidence-pack.ts +47 -0
  59. package/src/lib/first-run-status.ts +3 -3
  60. package/src/lib/mcp/agent-guide.ts +8 -2
  61. package/src/lib/mcp/tools.ts +7 -0
  62. package/src/lib/mcp-server.ts +20 -0
  63. package/src/lib/overview-data.ts +29 -3
  64. package/src/lib/preflight.ts +431 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,61 @@ All notable changes to TokenTrace are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.21.0] - 2026-06-26
8
+
9
+ ### Added
10
+
11
+ - **Local agent preflight.** Added `tokentrace preflight --json` and MCP
12
+ `get_preflight`, a read-only readiness report that returns proceed, caution,
13
+ or blocked before another coding-agent run. It combines scan freshness, data
14
+ confidence, guardrails, anomaly signals, findings, next actions, and privacy
15
+ notes without scanning files or inspecting raw prompts.
16
+
17
+ ### Changed
18
+
19
+ - **Simpler dashboard shell.** Primary navigation now follows the daily loop:
20
+ Today, Sessions, Evidence, Fix Data, Reports, and Settings. Diagnostic
21
+ surfaces such as Tools, Models, Projects, Query, Scan Health, Discovery,
22
+ Parsers, Raw Data, and Model Rates remain available under Advanced.
23
+
24
+ ### Fixed
25
+
26
+ - **First-run scan state uses latest scan evidence.** The Today first-run panel
27
+ now distinguishes "scan ran but imported no usage" from "no scan has run,"
28
+ preserving zero-import explanations from Scan Health.
29
+
30
+ ## [0.20.0] - 2026-06-12
31
+
32
+ ### Changed
33
+
34
+ - **Overview and shell UI polish.** The dashboard shell now groups navigation by
35
+ task area, and the Overview summary surface makes local evidence,
36
+ cached/non-cache token states, and exact/estimated/unknown cost distinctions
37
+ easier to scan.
38
+ - **ChatGPT app feasibility documented.** Added a recommendation to prototype
39
+ only a private, redacted evidence-pack ChatGPT app workflow until public
40
+ distribution can preserve TokenTrace's local-first privacy boundary.
41
+ - **Private ChatGPT app prototype.** Added `tokentrace chatgpt-app` with a
42
+ local HTTP `/mcp` server, one read-only `get_redacted_evidence_pack` Apps SDK
43
+ tool, a compact MCP Apps widget resource, and a developer-mode runbook. The
44
+ prototype uses redacted evidence packs and does not scan files on startup or
45
+ publish a public app.
46
+ - **ChatGPT app release readiness.** Added `npm run release:chatgpt:check`, a
47
+ hosted `/mcp` validator, a release runbook, and optional tag-workflow wiring
48
+ so ChatGPT app readiness can be checked alongside npm and MCP registry
49
+ releases while keeping OpenAI Dashboard submission and publishing manual.
50
+ - **ChatGPT app submission kit.** Added `docs/chatgpt-app/` with step-by-step
51
+ personal-account Dashboard release instructions, ready-to-paste submission
52
+ copy, privacy and review templates, test prompts, screenshot guidance, and
53
+ reusable SVG app assets for the manual ChatGPT app submission flow.
54
+ - **Development dashboard CSP avoids React issue noise.** The local dev server
55
+ now allows React's development-only diagnostics without relaxing the
56
+ production Content Security Policy.
57
+ - **Product website moved to Baseframe Labs.** The package `homepage`, MCP
58
+ registry `websiteUrl`, README link, and in-app guide link now point to
59
+ `https://www.baseframelabs.com/apps/tokentrace`. The previous
60
+ `abhiyoheswaran.com` URL redirects there.
61
+
7
62
  ## [0.19.2] - 2026-06-05
8
63
 
9
64
  ### Changed
package/README.md CHANGED
@@ -8,7 +8,7 @@ Local-first AI CLI usage analytics. TokenTrace scans local CLI logs and local us
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
- [Website](https://www.abhiyoheswaran.com/apps/tokentrace) · [Source](https://github.com/abhiyoheswaran1/tokentrace)
11
+ [Website](https://www.baseframelabs.com/apps/tokentrace) · [Source](https://github.com/abhiyoheswaran1/tokentrace)
12
12
 
13
13
  ![TokenTrace overview dashboard](docs/assets/overview-0.12.0.png)
14
14
 
@@ -45,6 +45,8 @@ tokentrace mcp # Start the local stdio MCP server
45
45
  tokentrace scan # Scan local AI CLI usage logs
46
46
  tokentrace doctor --json
47
47
  # Inspect scan health and repair recommendations
48
+ tokentrace preflight --json
49
+ # Check readiness before another coding-agent run
48
50
  tokentrace evidence --json
49
51
  # Print metric evidence trails as JSON
50
52
  tokentrace repair --json
@@ -126,11 +128,22 @@ tokentrace mcp selftest --json
126
128
  ```
127
129
 
128
130
  The MCP server exposes the same local-first surfaces as tools: capabilities,
129
- status, Scan Health, evidence, repair queue, reports, and an explicit scan tool.
130
- It does not scan files on startup, and its scan tool requires
131
+ preflight, status, Scan Health, evidence, repair queue, reports, and an
132
+ explicit scan tool. It does not scan files on startup, and its scan tool requires
131
133
  `confirmLocalScan=true` before reading local usage files or writing the local
132
134
  database.
133
135
 
136
+ Before starting another long coding-agent run, use the read-only preflight
137
+ surface:
138
+
139
+ ```bash
140
+ tokentrace preflight --json
141
+ ```
142
+
143
+ In MCP clients, call `get_preflight` for the same proceed, caution, or blocked
144
+ decision with local scan freshness, confidence, guardrail, anomaly, and repair
145
+ findings. Preflight does not scan files or inspect raw prompts.
146
+
134
147
  When the local dashboard is already running, agents can fetch the same manifest
135
148
  over localhost:
136
149
 
@@ -216,7 +229,7 @@ TokenTrace labels the trust level behind imported numbers:
216
229
  - source-provided costs from local SQLite histories
217
230
  - unknown cost repair groups when model, token, or rate evidence is missing
218
231
 
219
- The dashboard surfaces a Data Confidence score on Overview, Projects, Sessions,
232
+ The dashboard surfaces a Data Confidence score on Today, Projects, Sessions,
220
233
  and Session Timeline pages. Scan Health also includes a supply-chain IOC check
221
234
  so package trust is visible in the product, not only in release scripts.
222
235
 
@@ -44,6 +44,27 @@ The MCP server does not scan on startup. Its `run_scan` tool requires
44
44
  `confirmLocalScan=true` before reading local usage files or writing the local
45
45
  database.
46
46
 
47
+ Before starting another long coding-agent run, use the read-only preflight:
48
+
49
+ ```bash
50
+ tokentrace preflight --json
51
+ ```
52
+
53
+ The matching MCP tool is `get_preflight`. It returns a proceed, caution, or
54
+ blocked decision with local scan freshness, data confidence, guardrail status,
55
+ anomaly signals, findings, and next actions. It does not scan files.
56
+
57
+ Private ChatGPT developer-mode prototype:
58
+
59
+ ```bash
60
+ tokentrace chatgpt-app selftest --json
61
+ tokentrace chatgpt-app --port 8787 --hostname 127.0.0.1
62
+ ```
63
+
64
+ This starts a local HTTP `/mcp` server for one read-only redacted evidence-pack
65
+ tool. ChatGPT requires an HTTPS tunnel for developer-mode connector testing.
66
+ See `docs/CHATGPT_APP_PROTOTYPE.md`.
67
+
47
68
  If the local dashboard is already running, the same manifest is available from:
48
69
 
49
70
  ```bash
@@ -74,19 +95,25 @@ curl http://127.0.0.1:3030/api/roadmap
74
95
  get_agent_guide
75
96
  ```
76
97
 
77
- 3. Refresh local data when the human expects current usage:
98
+ 3. Check whether local evidence is ready for the next agent run:
99
+
100
+ ```bash
101
+ tokentrace preflight --json
102
+ ```
103
+
104
+ 4. Refresh local data when the human expects current usage:
78
105
 
79
106
  ```bash
80
107
  tokentrace scan --json
81
108
  ```
82
109
 
83
- 4. Check trust before making claims:
110
+ 5. Check trust before making claims:
84
111
 
85
112
  ```bash
86
113
  tokentrace doctor --json
87
114
  ```
88
115
 
89
- 5. Explain totals with evidence:
116
+ 6. Explain totals with evidence:
90
117
 
91
118
  ```bash
92
119
  tokentrace evidence --json
@@ -117,8 +144,8 @@ Get deterministic classification suggestions for the unknown-cost queue:
117
144
  tokentrace repair auto-classify --json [--min-confidence=N]
118
145
  ```
119
146
 
120
- The matching MCP tools are `get_anomalies`, `query_usage`, and
121
- `get_classifications`. All three are read-only and require no
147
+ The matching MCP tools are `get_preflight`, `get_anomalies`, `query_usage`, and
148
+ `get_classifications`. All four are read-only and require no
122
149
  `confirmLocalScan` acknowledgement.
123
150
 
124
151
  ## Guardrails
@@ -1,7 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
- import { getAnalyticsData } from "@/src/lib/analytics";
3
- import { buildEvidencePack, renderEvidencePackMarkdown } from "@/src/lib/evidence-pack";
4
- import { buildEvidenceTrail, parseEvidenceMetric } from "@/src/lib/evidence-trail";
2
+ import { buildMetricEvidencePack, renderEvidencePackMarkdown } from "@/src/lib/evidence-pack";
3
+ import { parseEvidenceMetric } from "@/src/lib/evidence-trail";
5
4
 
6
5
  export const dynamic = "force-dynamic";
7
6
 
@@ -9,43 +8,7 @@ export async function GET(request: Request) {
9
8
  const url = new URL(request.url);
10
9
  const format = url.searchParams.get("format") === "markdown" ? "markdown" : "json";
11
10
  const metric = parseEvidenceMetric(url.searchParams.get("metric"));
12
- const trail = buildEvidenceTrail({ metric });
13
- const analytics = getAnalyticsData();
14
- const pack = buildEvidencePack({
15
- scope: {
16
- type: "metric",
17
- id: metric,
18
- label: trail.title
19
- },
20
- totals: trail.totals,
21
- confidenceDrivers: [
22
- `${trail.confidence.exact.toLocaleString()} exact interactions`,
23
- `${trail.confidence.estimated.toLocaleString()} estimated interactions`,
24
- `${trail.confidence.unknown.toLocaleString()} unknown interactions`,
25
- `Data confidence ${analytics.dataConfidence.score}/100`
26
- ],
27
- sourceFiles: trail.sourceFiles.map((source) => source.sourceFile),
28
- parserNotes: trail.sessions
29
- .map((session) => `${session.parser ?? "unknown parser"}: ${session.parserStatus ?? "unknown status"}`)
30
- .slice(0, 20),
31
- modelRateState: trail.sessions
32
- .map((session) =>
33
- session.pricingHref ? `${session.model}: model-rate link available` : `${session.model}: no model-rate link`
34
- )
35
- .slice(0, 20),
36
- repairLinks: trail.sessions
37
- .filter((session) => session.unknownCostInteractions > 0)
38
- .map((session) => `/repair?source=${encodeURIComponent(session.sourceFile)}`),
39
- records: trail.sessions.map((session) => ({
40
- id: session.id,
41
- role: "session",
42
- model: session.model,
43
- sourceFile: session.sourceFile,
44
- totalTokens: session.totalTokens,
45
- cost: session.cost,
46
- interactions: session.interactions
47
- }))
48
- });
11
+ const pack = buildMetricEvidencePack({ metric });
49
12
 
50
13
  if (format === "markdown") {
51
14
  return new NextResponse(renderEvidencePackMarkdown(pack), {
@@ -16,7 +16,7 @@ export function EvidenceContextPanel({ actions }: { actions: EvidenceContextActi
16
16
  <div className="max-w-[72ch]">
17
17
  <FieldLabel>Evidence path</FieldLabel>
18
18
  <p className="mt-1 text-sm leading-6 text-muted-foreground">
19
- Evidence is a contextual drill-down from Overview, Sessions, Repair, and exported packs. If you opened this page directly, start with processed tokens, then pivot by metric or follow the next action that matches what looks incomplete.
19
+ Evidence is a contextual drill-down from Today, Sessions, Fix Data, and exported packs. If you opened this page directly, start with processed tokens, then pivot by metric or follow the next action that matches what looks incomplete.
20
20
  </p>
21
21
  </div>
22
22
  <div className="grid min-w-0 gap-2 sm:grid-cols-2 xl:grid-cols-4">
@@ -1,6 +1,6 @@
1
1
  import { ClipboardList, Gauge, Search, Wrench } from "lucide-react";
2
2
 
3
- export const PRODUCT_WEBSITE_URL = "https://www.abhiyoheswaran.com/apps/tokentrace";
3
+ export const PRODUCT_WEBSITE_URL = "https://www.baseframelabs.com/apps/tokentrace";
4
4
 
5
5
  export const guideNav = [
6
6
  ["#start", "Start here", "First scan to first evidence"],
@@ -13,8 +13,8 @@ export const guideNav = [
13
13
  export const dailyLoop = [
14
14
  {
15
15
  title: "Read the pulse",
16
- page: "Overview",
17
- detail: "Check current period usage, cost, sessions, unknown cost, and the latest trend window before chasing details.",
16
+ page: "Today",
17
+ detail: "Check readiness, usage, cost, sessions, unknown cost, and the latest trend window before chasing details.",
18
18
  href: "/",
19
19
  icon: Gauge
20
20
  },
@@ -27,7 +27,7 @@ export const dailyLoop = [
27
27
  },
28
28
  {
29
29
  title: "Repair what blocks trust",
30
- page: "Repair",
30
+ page: "Fix Data",
31
31
  detail: "Unknown cost usually needs a known model name, nonzero tokens, or an editable provider model rate.",
32
32
  href: "/repair",
33
33
  icon: Wrench
@@ -86,8 +86,11 @@ export const mcpAgentEntries = [
86
86
  ];
87
87
 
88
88
  export const pageMap = [
89
- ["Overview", "Top-level totals, trends, repair queue, guardrails, and recommended next actions."],
89
+ ["Today", "Top-level readiness, totals, trends, repair queue, guardrails, and recommended next actions."],
90
90
  ["Sessions", "Per-session evidence with models, costs, cache activity, parser provenance, and tool calls."],
91
+ ["Evidence", "Trace a metric back to sessions, source files, confidence drivers, and model-rate state."],
92
+ ["Fix Data", "Repair unknown cost, missing model names, missing token counts, and trust blockers."],
93
+ ["Reports", "Reusable local reports for weekly usage, high-cost sessions, confidence trends, and source coverage."],
91
94
  ["Model Rates", "Editable provider model rates used for dashboard cost estimates and unknown-cost repair."],
92
95
  ["Scan Health", "First-run checklist, scan health, supply-chain IOC check, supported file types, and diagnostics for missing data."],
93
96
  ["Discovery", "Recently scanned files grouped by parser, source family, status, and import yield."],
@@ -97,7 +100,7 @@ export const pageMap = [
97
100
  export const emptyStatePlaybook = [
98
101
  ["No data", "Run Scan now from Settings, then use Scan Health if records stay at zero."],
99
102
  ["No logs found", "Add a custom folder or use Claude Code, Codex, or another supported CLI before scanning again."],
100
- ["Unknown cost", "Open repair or Model Rates to decide whether the missing piece is model name, token count, or provider rate."],
103
+ ["Unknown cost", "Open Fix Data or Model Rates to decide whether the missing piece is model name, token count, or provider rate."],
101
104
  ["Parser warnings", "Open Discovery and Parsers to separate unsupported files from imported-with-errors rows."],
102
105
  ["Sandbox smoke skipped", "Local sandbox runs can skip server binding checks. Run the packed smoke or release check outside that constraint before release."]
103
106
  ];
@@ -55,7 +55,7 @@ const firstRunSteps: FirstRunStep[] = [
55
55
  number: "5",
56
56
  title: "Inspect evidence",
57
57
  page: "Sessions",
58
- detail: "Use Sessions, Evidence, Repair, and Projects after the first useful import.",
58
+ detail: "Use Sessions, Evidence, Fix Data, and Projects after the first useful import.",
59
59
  href: "/sessions",
60
60
  action: "Open Sessions"
61
61
  }
@@ -69,12 +69,12 @@ const workflows: GuideWorkflow[] = [
69
69
  },
70
70
  {
71
71
  problem: "Unknown cost",
72
- path: "Repair, Model Rates, Evidence",
72
+ path: "Fix Data, Model Rates, Evidence",
73
73
  action: "Find whether the missing piece is model name, token count, or editable provider rate."
74
74
  },
75
75
  {
76
76
  problem: "High token usage",
77
- path: "Overview, Sessions, Projects",
77
+ path: "Today, Sessions, Projects",
78
78
  action: "Compare processed, non-cache, and cache totals before treating a spike as fresh context growth."
79
79
  },
80
80
  {
@@ -111,7 +111,7 @@ export default function GuidePage() {
111
111
  {
112
112
  label: "Unknown cost",
113
113
  value: `${unknownCosts.toLocaleString()} unknown costs`,
114
- detail: unknownCosts > 0 ? "Open repair or Model Rates." : "Cost coverage is clear.",
114
+ detail: unknownCosts > 0 ? "Open Fix Data or Model Rates." : "Cost coverage is clear.",
115
115
  ok: unknownCosts === 0
116
116
  },
117
117
  {
package/app/page.tsx CHANGED
@@ -53,11 +53,11 @@ async function OverviewPrimarySection({ range }: { range: ResolvedDateRange }) {
53
53
  .map((entry) => ({ date: entry.date, value: entry.value, severity: entry.severity }));
54
54
 
55
55
  return (
56
- <div className="space-y-8">
56
+ <div className="overview-primary-section space-y-7">
57
57
  {summary.interactions === 0 ? <FirstRunPanel status={firstRunStatus} /> : null}
58
58
  <UsagePulsePanel comparison={data.comparison} />
59
59
  <DataConfidenceStrip confidence={data.dataConfidence} />
60
- <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-6">
60
+ <div className="overview-summary-grid grid gap-3 md:grid-cols-2 xl:grid-cols-6">
61
61
  <TokenAccountingCard
62
62
  summary={summary}
63
63
  processedHref={evidenceLinks["processed-tokens"]}
@@ -109,7 +109,7 @@ async function OverviewRepairSection({ range }: { range: ResolvedDateRange }) {
109
109
  }
110
110
 
111
111
  return (
112
- <div className="space-y-8">
112
+ <div className="overview-repair-section space-y-7">
113
113
  {summary.interactions > 0 ? (
114
114
  <OverviewReviewStatusStrip
115
115
  report={doctorReport}
@@ -140,10 +140,10 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
140
140
  const range = resolveDateRange(params);
141
141
 
142
142
  return (
143
- <div className="space-y-8">
143
+ <div className="overview-workbench space-y-7">
144
144
  <PageHeader
145
- title="Overview"
146
- description="Local token, cost, model, and session analytics across AI CLI tools."
145
+ title="Today"
146
+ description="Local readiness, cost, token, session, and repair signals before the next AI CLI run."
147
147
  actions={
148
148
  <Button asChild>
149
149
  <Link href="/settings#scan-controls">
@@ -22,8 +22,8 @@ export default async function RepairPage({ searchParams }: RepairPageProps) {
22
22
  return (
23
23
  <div className="space-y-6">
24
24
  <PageHeader
25
- title="Unknown Cost Repair"
26
- description="Grouped local evidence for interactions that could not be priced."
25
+ title="Fix Data"
26
+ description="Grouped local evidence for interactions that could not be priced or trusted yet."
27
27
  actions={
28
28
  <div className="flex flex-wrap gap-2">
29
29
  <Button asChild variant="outline">
@@ -6,6 +6,34 @@ import { DataValue, FieldLabel } from "@/components/ui/typography";
6
6
  import { formatCurrency, formatTokens } from "@/src/lib/format";
7
7
  import { cn } from "@/src/lib/utils";
8
8
 
9
+ type DetailItem = {
10
+ label: string;
11
+ value: string;
12
+ };
13
+
14
+ function EvidenceAction({
15
+ href,
16
+ label,
17
+ muted = false
18
+ }: {
19
+ href: string;
20
+ label: string;
21
+ muted?: boolean;
22
+ }) {
23
+ return (
24
+ <Link
25
+ href={href}
26
+ className={cn(
27
+ "inline-flex w-fit items-center gap-1.5 text-xs font-medium underline-offset-4 hover:underline",
28
+ muted ? "text-muted-foreground hover:text-foreground" : "text-primary"
29
+ )}
30
+ >
31
+ <span>{label}</span>
32
+ <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
33
+ </Link>
34
+ );
35
+ }
36
+
9
37
  function CostSessionsMetricPane({
10
38
  label,
11
39
  value,
@@ -22,7 +50,7 @@ function CostSessionsMetricPane({
22
50
  }: {
23
51
  label: string;
24
52
  value: string;
25
- detailItems: string[];
53
+ detailItems: DetailItem[];
26
54
  href: string;
27
55
  actionLabel: string;
28
56
  secondaryHref?: string;
@@ -36,7 +64,7 @@ function CostSessionsMetricPane({
36
64
  return (
37
65
  <section
38
66
  className={cn(
39
- "cost-sessions-section grid min-w-0 grid-rows-[auto_auto_auto_auto_1fr_auto] p-4 md:row-span-6 md:grid-rows-subgrid",
67
+ "cost-sessions-section cost-sessions-metric-pane grid min-w-0 grid-rows-[auto_auto_auto_auto_1fr_auto] p-4 md:row-span-6 md:grid-rows-subgrid",
40
68
  className
41
69
  )}
42
70
  >
@@ -53,8 +81,9 @@ function CostSessionsMetricPane({
53
81
 
54
82
  <div className="mt-3 flex flex-wrap items-start gap-x-2 gap-y-1 text-xs leading-snug text-muted-foreground">
55
83
  {detailItems.map((item, index) => (
56
- <span key={item} className="inline-flex min-w-0 items-center gap-2">
57
- <span className="min-w-0">{item}</span>
84
+ <span key={item.label} className="inline-flex min-w-0 items-center gap-1.5">
85
+ <span className="font-medium text-foreground">{item.label}</span>
86
+ <span className="min-w-0">{item.value}</span>
58
87
  {index < detailItems.length - 1 ? <span className="hidden text-border sm:inline">/</span> : null}
59
88
  </span>
60
89
  ))}
@@ -67,16 +96,14 @@ function CostSessionsMetricPane({
67
96
 
68
97
  <div aria-hidden="true" />
69
98
 
70
- <div className="flex flex-wrap items-center gap-x-3 gap-y-1 pt-4">
71
- <Link href={href} className="inline-flex w-fit items-center gap-1.5 text-xs font-medium text-primary underline-offset-4 hover:underline">
72
- {actionLabel}
73
- <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
74
- </Link>
75
- {secondaryHref ? (
76
- <Link href={secondaryHref} className="inline-flex w-fit items-center gap-1.5 text-xs font-medium text-muted-foreground underline-offset-4 hover:text-foreground hover:underline">
77
- {secondaryActionLabel ?? "View details"}
78
- </Link>
79
- ) : null}
99
+ <div className="pt-4">
100
+ <FieldLabel className="mb-2">Local evidence</FieldLabel>
101
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
102
+ <EvidenceAction href={href} label={actionLabel} />
103
+ {secondaryHref ? (
104
+ <EvidenceAction href={secondaryHref} label={secondaryActionLabel ?? "View details"} muted />
105
+ ) : null}
106
+ </div>
80
107
  </div>
81
108
  </section>
82
109
  );
@@ -124,9 +151,9 @@ export function CostSessionsCard({
124
151
  value={formatCurrency(summary.totalCost)}
125
152
  valueClassName="wrap-break-word text-2xl"
126
153
  detailItems={[
127
- `${formatCurrency(summary.exactCost)} exact`,
128
- `${formatCurrency(summary.estimatedCost)} estimated`,
129
- `${summary.unknownCostInteractions.toLocaleString()} unknown`
154
+ { label: "Exact", value: formatCurrency(summary.exactCost) },
155
+ { label: "Estimated", value: formatCurrency(summary.estimatedCost) },
156
+ { label: "Unknown", value: summary.unknownCostInteractions.toLocaleString() }
130
157
  ]}
131
158
  href={costHref}
132
159
  actionLabel={costActionLabel}
@@ -140,7 +167,7 @@ export function CostSessionsCard({
140
167
  className="border-t md:border-l md:border-t-0"
141
168
  label="Sessions"
142
169
  value={summary.sessions.toLocaleString()}
143
- detailItems={[`${summary.interactions.toLocaleString()} interactions`]}
170
+ detailItems={[{ label: "Interactions", value: summary.interactions.toLocaleString() }]}
144
171
  href={sessionsHref}
145
172
  actionLabel="View evidence"
146
173
  trustLabel="Session trust"
@@ -195,8 +222,8 @@ export function TokenAccountingCard({
195
222
  }) {
196
223
  const evidenceActions = [
197
224
  { label: "Processed", href: processedHref },
198
- { label: "Fresh", href: freshHref },
199
- { label: "Cache", href: cacheHref }
225
+ { label: "Fresh / non-cache", href: freshHref },
226
+ { label: "Cache read/write", href: cacheHref }
200
227
  ];
201
228
 
202
229
  return (
@@ -248,7 +275,7 @@ export function TokenAccountingCard({
248
275
  detail={`${formatTokens(summary.inputTokens)} input / ${formatTokens(summary.outputTokens)} output / ${formatTokens(summary.reasoningTokens)} reasoning`}
249
276
  />
250
277
  <TokenAccountingSlice
251
- label="Cache"
278
+ label="Cache read/write"
252
279
  value={formatTokens(summary.cachedTokens)}
253
280
  detail={`${formatTokens(summary.cacheReadTokens)} read / ${formatTokens(summary.cacheWriteTokens)} write`}
254
281
  />
@@ -261,19 +288,22 @@ export function TokenAccountingCard({
261
288
  </span>
262
289
  </p>
263
290
 
264
- <div className="mt-auto flex flex-wrap items-center gap-x-3 gap-y-1 pt-4">
265
- {evidenceActions.map((action) => (
266
- <Link
267
- key={action.href}
268
- href={action.href}
269
- aria-label={`View ${action.label.toLowerCase()} token evidence`}
270
- className="inline-flex w-fit items-center gap-1.5 text-xs font-medium text-primary underline-offset-4 hover:underline"
271
- >
272
- <span className="text-muted-foreground">{action.label}</span>
273
- <span>View evidence</span>
274
- <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
275
- </Link>
276
- ))}
291
+ <div className="mt-auto pt-4">
292
+ <FieldLabel className="mb-2">Local evidence</FieldLabel>
293
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
294
+ {evidenceActions.map((action) => (
295
+ <Link
296
+ key={action.href}
297
+ href={action.href}
298
+ aria-label={`View ${action.label.toLowerCase()} token evidence`}
299
+ className="inline-flex w-fit items-center gap-1.5 text-xs font-medium text-primary underline-offset-4 hover:underline"
300
+ >
301
+ <span className="text-muted-foreground">{action.label}</span>
302
+ <span>View evidence</span>
303
+ <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
304
+ </Link>
305
+ ))}
306
+ </div>
277
307
  </div>
278
308
  </CardContent>
279
309
  </Card>