tokentrace 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -1
- package/README.md +20 -3
- package/SECURITY.md +6 -2
- package/app/api/repair-items/route.ts +22 -0
- package/app/api/settings/route.ts +3 -1
- package/app/diagnostics/page.tsx +13 -1
- package/app/guide/page.tsx +8 -3
- package/app/page.tsx +38 -0
- package/app/projects/page.tsx +13 -0
- package/app/repair/page.tsx +91 -91
- package/app/sessions/[id]/page.tsx +59 -0
- package/bin/tokentrace.js +1 -1
- package/components/repair-bulk-actions.tsx +84 -0
- package/components/scan-health-summary.tsx +22 -1
- package/components/session-explorer.tsx +14 -1
- package/components/settings-panel.tsx +103 -1
- package/dist/runtime/agent.mjs +2 -2
- package/dist/runtime/db-seed.mjs +26 -1
- package/dist/runtime/digest.mjs +241 -29
- package/dist/runtime/doctor.mjs +258 -47
- package/dist/runtime/insights.mjs +241 -29
- package/dist/runtime/pricing-refresh.mjs +26 -1
- package/dist/runtime/report.mjs +241 -29
- package/dist/runtime/reset.mjs +26 -1
- package/dist/runtime/review.mjs +241 -29
- package/dist/runtime/roadmap.mjs +89 -77
- package/dist/runtime/scan.mjs +425 -74
- package/package.json +3 -2
- package/scripts/roadmap.ts +1 -1
- package/scripts/security-ioc.mjs +384 -0
- package/scripts/smoke-cli.mjs +2 -2
- package/scripts/smoke-packed-install.mjs +2 -2
- package/src/db/settings.ts +6 -2
- package/src/ingestion/adapters/index.ts +2 -0
- package/src/ingestion/adapters/sqlite-history.ts +201 -0
- package/src/ingestion/discovery.ts +15 -18
- package/src/ingestion/persist.ts +32 -9
- package/src/ingestion/scan.ts +3 -1
- package/src/ingestion/types.ts +9 -1
- package/src/lib/agent-discovery.ts +2 -2
- package/src/lib/analytics.ts +102 -15
- package/src/lib/cost-recalculation.ts +28 -1
- package/src/lib/data-confidence.ts +83 -0
- package/src/lib/import-profiles.ts +124 -0
- package/src/lib/roadmap-status.ts +90 -78
- package/src/lib/scan-health.ts +33 -6
- package/src/lib/session-timeline.ts +64 -0
- package/src/lib/supply-chain-health.ts +31 -0
- package/src/lib/token-estimator.ts +72 -5
- package/src/lib/unknown-cost-repair.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,27 @@ All notable changes to TokenTrace are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## [0.11.0] - 2026-05-18
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `npm run security:ioc` scans project files and local Claude/VS Code hook files for high-signal Mini Shai-Hulud/TanStack supply-chain indicators before release.
|
|
12
|
+
- Tokenizer-backed estimates for recognized OpenAI/Codex and Claude-family model names, with explicit `tokenizer estimate` and `simple estimate` confidence labels.
|
|
13
|
+
- Native SQLite history ingestion for local usage databases with usage-shaped tables, source-file evidence, import-profile metadata, and source-provided cost preservation.
|
|
14
|
+
- Data Confidence scoring on Overview, Projects, Sessions, and Session Timeline views, combining token source, cost coverage, parser confidence, and scan freshness.
|
|
15
|
+
- Import Profiles in Settings so users can define safe local wrapper-log matchers without writing parser code.
|
|
16
|
+
- Repair Workbench bulk actions for marking visible unknown-cost groups verified, parser-review, ignored, or reopened.
|
|
17
|
+
- Supply-chain IOC status in Scan Health, backed by the same local `security:ioc` scanner used by release checks.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Session Timeline pages now explain token spikes, model changes, cache activity, cost coverage, confidence drivers, and direct unknown-cost repair links.
|
|
22
|
+
- Scan Health now distinguishes exact, tokenizer-estimated, simple-estimated, high-confidence, low-confidence, and unknown token rows.
|
|
23
|
+
- The roadmap API and CLI now report the 0.11.0 Accuracy & Evidence release contract.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Source-provided interaction costs are no longer overwritten as unknown during post-scan cost recalculation when model rates are missing.
|
|
8
28
|
|
|
9
29
|
## [0.10.1] - 2026-05-18
|
|
10
30
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# TokenTrace CLI
|
|
6
6
|
|
|
7
|
-
Local-first AI CLI usage analytics. TokenTrace scans local CLI logs, normalizes token usage, estimates missing counts, and shows cost, model, project, and
|
|
7
|
+
Local-first AI CLI usage analytics. TokenTrace scans local CLI logs and local usage databases, normalizes token usage, estimates missing counts with confidence labels, and shows cost, model, project, session, repair, and evidence analytics in a browser dashboard.
|
|
8
8
|
|
|
9
9
|
TokenTrace is designed for local development machines first, with macOS-oriented defaults. It does not require a cloud account and does not send telemetry or logs anywhere.
|
|
10
10
|
|
|
@@ -40,7 +40,7 @@ tokentrace agent --json # Print machine-readable agent discovery manifest
|
|
|
40
40
|
tokentrace capabilities --json
|
|
41
41
|
# Alias for agent discovery manifest
|
|
42
42
|
tokentrace roadmap --json
|
|
43
|
-
# Print
|
|
43
|
+
# Print Accuracy & Evidence release implementation status
|
|
44
44
|
tokentrace scan # Scan local AI CLI usage logs
|
|
45
45
|
tokentrace doctor --json
|
|
46
46
|
# Inspect scan health and repair recommendations
|
|
@@ -114,7 +114,7 @@ curl http://127.0.0.1:3030/api/agent
|
|
|
114
114
|
curl http://127.0.0.1:3030/api/capabilities
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
The
|
|
117
|
+
The Accuracy & Evidence release status is also machine-readable:
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
120
|
tokentrace roadmap --json
|
|
@@ -149,12 +149,28 @@ npm run verify # Run Vitest, TypeScript, and ESLint checks
|
|
|
149
149
|
npm run package:test # Verify, build, and dry-run the npm package
|
|
150
150
|
npm run package:inspect
|
|
151
151
|
# Check package transparency guardrails
|
|
152
|
+
npm run security:ioc
|
|
153
|
+
# Scan lockfiles, workflows, and local AI-tool hooks for supply-chain IOCs
|
|
152
154
|
npm run smoke:packed
|
|
153
155
|
# Inspect packed tarball and smoke test packed CLI
|
|
154
156
|
tokentrace roadmap --json
|
|
155
157
|
# Inspect evidence gates and release status
|
|
156
158
|
```
|
|
157
159
|
|
|
160
|
+
## Accuracy And Evidence
|
|
161
|
+
|
|
162
|
+
TokenTrace labels the trust level behind imported numbers:
|
|
163
|
+
|
|
164
|
+
- exact provider token counts
|
|
165
|
+
- tokenizer estimates for recognized OpenAI/Codex and Claude-family model names
|
|
166
|
+
- simple estimates when only text-like content is available
|
|
167
|
+
- source-provided costs from local SQLite histories
|
|
168
|
+
- unknown cost repair groups when model, token, or rate evidence is missing
|
|
169
|
+
|
|
170
|
+
The dashboard surfaces a Data Confidence score on Overview, Projects, Sessions,
|
|
171
|
+
and Session Timeline pages. Scan Health also includes a supply-chain IOC check
|
|
172
|
+
so package trust is visible in the product, not only in release scripts.
|
|
173
|
+
|
|
158
174
|
Release work uses internal milestone commits until the next public minor
|
|
159
175
|
release. See [docs/RELEASE_CHECKLIST.md](docs/RELEASE_CHECKLIST.md) before
|
|
160
176
|
bumping versions, tagging, creating GitHub releases, or publishing npm.
|
|
@@ -324,6 +340,7 @@ Stop the server with `Ctrl+C` in the terminal where `tokentrace` is running.
|
|
|
324
340
|
- The published package ships readable application source and the compiled CLI runtime, not generated `.next/server` route bundles.
|
|
325
341
|
- `tokentrace serve` prepares the local dashboard build in the user's TokenTrace app-data directory the first time it is needed.
|
|
326
342
|
- `npm run package:inspect` fails if generated Next.js build output appears in the published tarball.
|
|
343
|
+
- `npm run security:ioc` scans lockfiles, workflows, and local Claude/VS Code hook files for high-signal supply-chain compromise indicators.
|
|
327
344
|
- Public npm publishing is configured through GitHub Actions Trusted Publishing and provenance from version tags.
|
|
328
345
|
- Socket GitHub checks and ProjScan are used as release guardrails, alongside `npm audit --audit-level=moderate`.
|
|
329
346
|
- Release notes are published directly in GitHub Releases from the relevant changelog section, not as a link-only summary.
|
package/SECURITY.md
CHANGED
|
@@ -40,8 +40,12 @@ When running from source, the default development database is
|
|
|
40
40
|
## Release Trust
|
|
41
41
|
|
|
42
42
|
Public package releases are built from Git tags. The release flow includes
|
|
43
|
-
package inspection, production dependency audit,
|
|
44
|
-
Trusted Publishing through GitHub Actions.
|
|
43
|
+
package inspection, supply-chain IOC scanning, production dependency audit,
|
|
44
|
+
ProjScan checks, and npm Trusted Publishing through GitHub Actions.
|
|
45
|
+
|
|
46
|
+
Run `npm run security:ioc` to check lockfiles, GitHub Actions workflows, and
|
|
47
|
+
local Claude/VS Code hook files for high-signal Mini Shai-Hulud/TanStack-style
|
|
48
|
+
indicators before release or after a dependency scare.
|
|
45
49
|
|
|
46
50
|
## Reporting
|
|
47
51
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import {
|
|
3
|
+
bulkUpdateUnknownCostRepairs,
|
|
3
4
|
buildUnknownCostRepairWorkbench,
|
|
4
5
|
getUnknownCostReview,
|
|
5
6
|
saveUnknownCostReview,
|
|
@@ -40,9 +41,30 @@ export async function PUT(request: Request) {
|
|
|
40
41
|
return NextResponse.json({ error: parsed.error }, { status: 400 });
|
|
41
42
|
}
|
|
42
43
|
const body = parsed.body;
|
|
44
|
+
const keys = Array.isArray(body.keys)
|
|
45
|
+
? body.keys.map((value) => text(value, 1000)).filter(Boolean)
|
|
46
|
+
: [];
|
|
43
47
|
const key = text(body.key, 1000);
|
|
44
48
|
const status = reviewState(body.status ?? body.state);
|
|
45
49
|
|
|
50
|
+
if (keys.length) {
|
|
51
|
+
if (!status) {
|
|
52
|
+
return NextResponse.json({ error: "status must be unresolved, ignored, resolved, or needs-parser-review" }, { status: 400 });
|
|
53
|
+
}
|
|
54
|
+
const currentKeys = new Set(buildUnknownCostRepairWorkbench().groups.map((group) => group.key));
|
|
55
|
+
const missingKey = keys.find((item) => !currentKeys.has(item) && !getUnknownCostReview(item)?.updatedAt);
|
|
56
|
+
if (missingKey) {
|
|
57
|
+
return NextResponse.json({ error: "one or more repair keys were not found in current workbench evidence" }, { status: 404 });
|
|
58
|
+
}
|
|
59
|
+
return NextResponse.json(
|
|
60
|
+
bulkUpdateUnknownCostRepairs({
|
|
61
|
+
keys,
|
|
62
|
+
status,
|
|
63
|
+
notes: text(body.notes ?? body.note, 500)
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
if (!key) {
|
|
47
69
|
return NextResponse.json({ error: "key is required" }, { status: 400 });
|
|
48
70
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { getDatabasePath } from "@/src/db/client";
|
|
3
3
|
import { getAppSettings, normalizeUsageGuardrails, saveAppSettings } from "@/src/db/settings";
|
|
4
4
|
import { jsonBooleanFlag, readJsonObject } from "@/src/lib/api-json";
|
|
5
|
+
import { normalizeImportProfiles } from "@/src/lib/import-profiles";
|
|
5
6
|
|
|
6
7
|
export const dynamic = "force-dynamic";
|
|
7
8
|
|
|
@@ -27,7 +28,8 @@ export async function PUT(request: Request) {
|
|
|
27
28
|
const saved = saveAppSettings({
|
|
28
29
|
customFolders,
|
|
29
30
|
storeRawMessageContent: jsonBooleanFlag(body.storeRawMessageContent),
|
|
30
|
-
usageGuardrails: normalizeUsageGuardrails(body.usageGuardrails)
|
|
31
|
+
usageGuardrails: normalizeUsageGuardrails(body.usageGuardrails),
|
|
32
|
+
importProfiles: normalizeImportProfiles(body.importProfiles)
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
return NextResponse.json(saved);
|
package/app/diagnostics/page.tsx
CHANGED
|
@@ -7,8 +7,10 @@ import { DataValue, FieldLabel, MonoText, PageHeader } from "@/components/ui/typ
|
|
|
7
7
|
import { ScanHealthSummary } from "@/components/scan-health-summary";
|
|
8
8
|
import { getAnalyticsData, getScanTrustData, type DebugScanRun } from "@/src/lib/analytics";
|
|
9
9
|
import { buildDoctorReport, type DoctorReport } from "@/src/lib/doctor";
|
|
10
|
+
import { buildScanHealth } from "@/src/lib/scan-health";
|
|
10
11
|
import { getDefaultSearchRoots } from "@/src/ingestion/discovery";
|
|
11
12
|
import { formatDate } from "@/src/lib/format";
|
|
13
|
+
import { getSupplyChainHealth } from "@/src/lib/supply-chain-health";
|
|
12
14
|
|
|
13
15
|
export const dynamic = "force-dynamic";
|
|
14
16
|
|
|
@@ -461,7 +463,17 @@ function ScanHistoryPanel({ scanRuns }: { scanRuns: DebugScanRun[] }) {
|
|
|
461
463
|
}
|
|
462
464
|
|
|
463
465
|
export default async function DiagnosticsPage() {
|
|
464
|
-
const
|
|
466
|
+
const baseData = getScanTrustData();
|
|
467
|
+
const supplyChain = getSupplyChainHealth();
|
|
468
|
+
const data = {
|
|
469
|
+
...baseData,
|
|
470
|
+
health: buildScanHealth({
|
|
471
|
+
scanRuns: baseData.scanRuns,
|
|
472
|
+
scanFiles: baseData.scanFiles,
|
|
473
|
+
confidence: baseData.confidence,
|
|
474
|
+
supplyChain
|
|
475
|
+
})
|
|
476
|
+
};
|
|
465
477
|
const analytics = getAnalyticsData();
|
|
466
478
|
const roots = await getDefaultSearchRoots();
|
|
467
479
|
const doctorReport = buildDoctorReport({
|
package/app/guide/page.tsx
CHANGED
|
@@ -168,6 +168,11 @@ const workflows = [
|
|
|
168
168
|
problem: "Parser warnings",
|
|
169
169
|
path: "Parsers, Discovery, Scan Health",
|
|
170
170
|
action: "Check parser confidence, unsupported files, and imported-with-errors rows before trusting an adapter."
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
problem: "Package trust check",
|
|
174
|
+
path: "Scan Health",
|
|
175
|
+
action: "Review the supply-chain IOC check alongside parser and model-rate health before release or upgrade work."
|
|
171
176
|
}
|
|
172
177
|
];
|
|
173
178
|
|
|
@@ -175,7 +180,7 @@ const pageMap = [
|
|
|
175
180
|
["Overview", "Top-level totals, trends, repair queue, guardrails, and recommended next actions."],
|
|
176
181
|
["Sessions", "Per-session evidence with models, costs, cache activity, parser provenance, and tool calls."],
|
|
177
182
|
["Model Rates", "Editable provider model rates used for dashboard cost estimates and unknown-cost repair."],
|
|
178
|
-
["Scan Health", "First-run checklist, scan health, supported file types, and diagnostics for missing data."],
|
|
183
|
+
["Scan Health", "First-run checklist, scan health, supply-chain IOC check, supported file types, and diagnostics for missing data."],
|
|
179
184
|
["Discovery", "Recently scanned files grouped by parser, source family, status, and import yield."],
|
|
180
185
|
["Parsers", "Adapter choices, warnings, confidence, and parser repair clues for local files."]
|
|
181
186
|
];
|
|
@@ -483,8 +488,8 @@ export default function GuidePage() {
|
|
|
483
488
|
<h3 className="text-sm font-semibold leading-tight">Release readiness</h3>
|
|
484
489
|
</div>
|
|
485
490
|
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
|
486
|
-
Current package version is {roadmapStatus.packageVersion}. The
|
|
487
|
-
|
|
491
|
+
Current package version is {roadmapStatus.packageVersion}. The current roadmap focuses on accuracy, evidence,
|
|
492
|
+
repair workflow, import profiles, and package-trust checks.
|
|
488
493
|
</p>
|
|
489
494
|
<div className="mt-4 grid grid-cols-2 gap-3 text-sm">
|
|
490
495
|
<div>
|
package/app/page.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import { buildPostSessionReview, type PostSessionReview } from "@/src/lib/post-s
|
|
|
26
26
|
export const dynamic = "force-dynamic";
|
|
27
27
|
|
|
28
28
|
type UsageComparison = ReturnType<typeof getAnalyticsData>["comparison"];
|
|
29
|
+
type DataConfidence = ReturnType<typeof getAnalyticsData>["dataConfidence"];
|
|
29
30
|
|
|
30
31
|
function CostSessionsMetricPane({
|
|
31
32
|
label,
|
|
@@ -638,6 +639,41 @@ function UsagePulsePanel({ comparison }: { comparison: UsageComparison }) {
|
|
|
638
639
|
);
|
|
639
640
|
}
|
|
640
641
|
|
|
642
|
+
function confidenceVariant(grade: DataConfidence["grade"]) {
|
|
643
|
+
if (grade === "high") return "success";
|
|
644
|
+
if (grade === "medium") return "warning";
|
|
645
|
+
if (grade === "low") return "destructive";
|
|
646
|
+
return "secondary";
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function DataConfidenceStrip({ confidence }: { confidence: DataConfidence }) {
|
|
650
|
+
return (
|
|
651
|
+
<Card>
|
|
652
|
+
<CardContent className="flex flex-col gap-3 p-4 lg:flex-row lg:items-center lg:justify-between">
|
|
653
|
+
<div className="min-w-0">
|
|
654
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
655
|
+
<div className="text-sm font-semibold">Data Confidence</div>
|
|
656
|
+
<Badge variant={confidenceVariant(confidence.grade)}>{confidence.score}/100 {confidence.grade}</Badge>
|
|
657
|
+
</div>
|
|
658
|
+
<p className="mt-1 text-sm leading-6 text-muted-foreground">
|
|
659
|
+
{confidence.drivers.slice(0, 2).join(" ")}
|
|
660
|
+
</p>
|
|
661
|
+
</div>
|
|
662
|
+
<div className="flex flex-wrap gap-2">
|
|
663
|
+
{confidence.repairHref ? (
|
|
664
|
+
<Button asChild variant="outline" size="sm">
|
|
665
|
+
<Link href={confidence.repairHref}>Open repair <ArrowRight className="h-4 w-4" /></Link>
|
|
666
|
+
</Button>
|
|
667
|
+
) : null}
|
|
668
|
+
<Button asChild variant="outline" size="sm">
|
|
669
|
+
<Link href="/diagnostics">Open Scan Health <ArrowRight className="h-4 w-4" /></Link>
|
|
670
|
+
</Button>
|
|
671
|
+
</div>
|
|
672
|
+
</CardContent>
|
|
673
|
+
</Card>
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
641
677
|
function guardrailBadgeVariant(status: UsageGuardrailMetric["status"]) {
|
|
642
678
|
if (status === "exceeded") return "destructive";
|
|
643
679
|
if (status === "warning") return "warning";
|
|
@@ -934,6 +970,8 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
934
970
|
|
|
935
971
|
<UsagePulsePanel comparison={data.comparison} />
|
|
936
972
|
|
|
973
|
+
<DataConfidenceStrip confidence={data.dataConfidence} />
|
|
974
|
+
|
|
937
975
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-6">
|
|
938
976
|
<TokenAccountingCard
|
|
939
977
|
summary={summary}
|
package/app/projects/page.tsx
CHANGED
|
@@ -12,6 +12,13 @@ import { formatCurrency, formatDate, formatTokens } from "@/src/lib/format";
|
|
|
12
12
|
|
|
13
13
|
export const dynamic = "force-dynamic";
|
|
14
14
|
|
|
15
|
+
function confidenceVariant(grade: string) {
|
|
16
|
+
if (grade === "high") return "success";
|
|
17
|
+
if (grade === "medium") return "warning";
|
|
18
|
+
if (grade === "low") return "destructive";
|
|
19
|
+
return "secondary";
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
export default function ProjectAnalyticsPage() {
|
|
16
23
|
const data = getAnalyticsData();
|
|
17
24
|
const mostExpensive = data.projects[0];
|
|
@@ -145,6 +152,7 @@ export default function ProjectAnalyticsPage() {
|
|
|
145
152
|
<TableHead>Cost</TableHead>
|
|
146
153
|
<TableHead>Sessions</TableHead>
|
|
147
154
|
<TableHead>Interactions</TableHead>
|
|
155
|
+
<TableHead>Confidence</TableHead>
|
|
148
156
|
<TableHead>Output/Input</TableHead>
|
|
149
157
|
<TableHead>Last used</TableHead>
|
|
150
158
|
</TableRow>
|
|
@@ -164,6 +172,11 @@ export default function ProjectAnalyticsPage() {
|
|
|
164
172
|
<TableCell>{formatCurrency(project.cost)}</TableCell>
|
|
165
173
|
<TableCell>{project.sessions.toLocaleString()}</TableCell>
|
|
166
174
|
<TableCell>{project.interactions.toLocaleString()}</TableCell>
|
|
175
|
+
<TableCell>
|
|
176
|
+
<Badge variant={confidenceVariant(project.confidenceGrade ?? "empty")}>
|
|
177
|
+
{project.confidenceScore ?? 0}/100 {project.confidenceGrade ?? "empty"}
|
|
178
|
+
</Badge>
|
|
179
|
+
</TableCell>
|
|
167
180
|
<TableCell>{project.outputInputRatio.toFixed(2)}x</TableCell>
|
|
168
181
|
<TableCell>{formatDate(project.lastUsedAt)}</TableCell>
|
|
169
182
|
</TableRow>
|
package/app/repair/page.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import Link from "next/link";
|
|
|
2
2
|
import { ArrowRight, CheckCircle2, CircleDashed, Search, Settings2 } from "lucide-react";
|
|
3
3
|
import { EmptyState } from "@/components/empty-state";
|
|
4
4
|
import { PeriodFilter } from "@/components/period-filter";
|
|
5
|
+
import { RepairBulkActions } from "@/components/repair-bulk-actions";
|
|
5
6
|
import { RepairStateControl } from "@/components/repair-state-control";
|
|
6
7
|
import { Badge } from "@/components/ui/badge";
|
|
7
8
|
import { Button } from "@/components/ui/button";
|
|
@@ -257,99 +258,98 @@ export default async function RepairPage({ searchParams }: RepairPageProps) {
|
|
|
257
258
|
</CardHeader>
|
|
258
259
|
<CardContent className="table-scroll overflow-x-auto">
|
|
259
260
|
{hasGroups ? (
|
|
260
|
-
<
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
<
|
|
310
|
-
{group.
|
|
311
|
-
</
|
|
312
|
-
</
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
{formatTokens(group.inputTokens)} in, {formatTokens(group.outputTokens)} out
|
|
319
|
-
</div>
|
|
320
|
-
</TableCell>
|
|
321
|
-
<TableCell className="align-top">
|
|
322
|
-
<div className="flex flex-wrap gap-2">
|
|
323
|
-
<Link
|
|
324
|
-
href={
|
|
325
|
-
group.pricingHref
|
|
326
|
-
? mergeHrefParams(group.pricingHref, { returnTo: mergeHrefParams(group.itemHref, rangeLinkParams) })
|
|
327
|
-
: mergeHrefParams(group.repairHref, rangeLinkParams)
|
|
328
|
-
}
|
|
329
|
-
className="inline-flex items-center gap-1 font-medium text-primary underline-offset-4 hover:underline"
|
|
330
|
-
>
|
|
331
|
-
{group.pricingHref ? "Set model rate" : "Review parser"} <ArrowRight className="h-3.5 w-3.5" />
|
|
332
|
-
</Link>
|
|
333
|
-
<Link href={mergeHrefParams(group.itemHref, rangeLinkParams)} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
334
|
-
Open repair
|
|
335
|
-
</Link>
|
|
336
|
-
<Link href={mergeHrefParams(group.parserHref, rangeLinkParams)} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
337
|
-
Review parser
|
|
261
|
+
<div className="space-y-3">
|
|
262
|
+
<RepairBulkActions
|
|
263
|
+
keys={workbench.groups.map((group) => group.key)}
|
|
264
|
+
modelRatesHref={mergeHrefParams("/pricing", rangeLinkParams)}
|
|
265
|
+
scanHealthHref="/diagnostics"
|
|
266
|
+
/>
|
|
267
|
+
<Table className="min-w-[84rem]">
|
|
268
|
+
<TableHeader>
|
|
269
|
+
<TableRow>
|
|
270
|
+
<TableHead>State</TableHead>
|
|
271
|
+
<TableHead>Cause</TableHead>
|
|
272
|
+
<TableHead className="min-w-56">Model</TableHead>
|
|
273
|
+
<TableHead className="min-w-72">Suggestion</TableHead>
|
|
274
|
+
<TableHead className="min-w-80">Source</TableHead>
|
|
275
|
+
<TableHead>Interactions</TableHead>
|
|
276
|
+
<TableHead>Tokens</TableHead>
|
|
277
|
+
<TableHead className="min-w-28">Actions</TableHead>
|
|
278
|
+
</TableRow>
|
|
279
|
+
</TableHeader>
|
|
280
|
+
<TableBody>
|
|
281
|
+
{workbench.groups.map((group) => (
|
|
282
|
+
<TableRow key={group.key} className={focusKey === group.key ? "bg-muted/40" : undefined}>
|
|
283
|
+
<TableCell className="align-top">
|
|
284
|
+
<RepairStateControl
|
|
285
|
+
repairKey={group.key}
|
|
286
|
+
initialStatus={group.review.status}
|
|
287
|
+
initialNotes={group.review.notes}
|
|
288
|
+
sourceFile={group.sourceFile}
|
|
289
|
+
model={group.model}
|
|
290
|
+
provider={group.provider}
|
|
291
|
+
cause={group.cause}
|
|
292
|
+
/>
|
|
293
|
+
</TableCell>
|
|
294
|
+
<TableCell className="align-top">
|
|
295
|
+
<Badge variant={causeVariant(group.cause)}>{causeLabel(group.cause)}</Badge>
|
|
296
|
+
</TableCell>
|
|
297
|
+
<TableCell className="align-top">
|
|
298
|
+
<div className="font-medium">{group.model}</div>
|
|
299
|
+
<div className="mt-1 text-xs text-muted-foreground">{group.provider} / {group.tool}</div>
|
|
300
|
+
</TableCell>
|
|
301
|
+
<TableCell className="min-w-72 max-w-80 align-top">
|
|
302
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
303
|
+
{group.suggestion.suggestedModel ? (
|
|
304
|
+
<CheckCircle2 className="h-4 w-4 text-primary" />
|
|
305
|
+
) : (
|
|
306
|
+
<CircleDashed className="h-4 w-4 text-muted-foreground" />
|
|
307
|
+
)}
|
|
308
|
+
{suggestionLabel(group)}
|
|
309
|
+
</div>
|
|
310
|
+
<div className="mt-1 text-xs leading-relaxed text-muted-foreground">
|
|
311
|
+
{group.suggestion.confidence} confidence. {group.suggestion.reason}
|
|
312
|
+
</div>
|
|
313
|
+
</TableCell>
|
|
314
|
+
<TableCell className="max-w-96 align-top">
|
|
315
|
+
<Link href={mergeHrefParams(group.sourceHref, rangeLinkParams)} title={group.sourceFile}>
|
|
316
|
+
<MonoText className="block truncate text-xs text-muted-foreground underline-offset-4 hover:underline">
|
|
317
|
+
{group.sourceFile}
|
|
318
|
+
</MonoText>
|
|
338
319
|
</Link>
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
320
|
+
</TableCell>
|
|
321
|
+
<TableCell className="align-top">{group.interactions.toLocaleString()}</TableCell>
|
|
322
|
+
<TableCell className="min-w-28 align-top">
|
|
323
|
+
<div>{formatTokens(group.totalTokens)}</div>
|
|
324
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
325
|
+
{formatTokens(group.inputTokens)} in, {formatTokens(group.outputTokens)} out
|
|
326
|
+
</div>
|
|
327
|
+
</TableCell>
|
|
328
|
+
<TableCell className="align-top">
|
|
329
|
+
<div className="flex flex-wrap gap-2">
|
|
330
|
+
<Link
|
|
331
|
+
href={
|
|
332
|
+
group.pricingHref
|
|
333
|
+
? mergeHrefParams(group.pricingHref, { returnTo: mergeHrefParams(group.itemHref, rangeLinkParams) })
|
|
334
|
+
: mergeHrefParams(group.repairHref, rangeLinkParams)
|
|
335
|
+
}
|
|
336
|
+
className="inline-flex items-center gap-1 font-medium text-primary underline-offset-4 hover:underline"
|
|
337
|
+
>
|
|
338
|
+
{group.pricingHref ? "Set model rate" : "Review parser"} <ArrowRight className="h-3.5 w-3.5" />
|
|
342
339
|
</Link>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
340
|
+
<Link href={mergeHrefParams(group.itemHref, rangeLinkParams)} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
341
|
+
Open repair
|
|
342
|
+
</Link>
|
|
343
|
+
<Link href={mergeHrefParams(group.sourceHref, rangeLinkParams)} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
344
|
+
View evidence
|
|
345
|
+
</Link>
|
|
346
|
+
</div>
|
|
347
|
+
</TableCell>
|
|
348
|
+
</TableRow>
|
|
349
|
+
))}
|
|
350
|
+
</TableBody>
|
|
351
|
+
</Table>
|
|
352
|
+
</div>
|
|
353
353
|
) : (
|
|
354
354
|
<EmptyState
|
|
355
355
|
title="No unknown-cost repair items"
|
|
@@ -22,6 +22,13 @@ function eventLabel(kind: SessionTimelineEventKind) {
|
|
|
22
22
|
return kind.replace(/-/g, " ");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function confidenceVariant(grade: string) {
|
|
26
|
+
if (grade === "high") return "success";
|
|
27
|
+
if (grade === "medium") return "warning";
|
|
28
|
+
if (grade === "low") return "destructive";
|
|
29
|
+
return "secondary";
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
function SummaryTile({
|
|
26
33
|
label,
|
|
27
34
|
value,
|
|
@@ -146,6 +153,58 @@ export default async function SessionTimelinePage({
|
|
|
146
153
|
/>
|
|
147
154
|
</div>
|
|
148
155
|
|
|
156
|
+
<Card>
|
|
157
|
+
<CardHeader className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
158
|
+
<div>
|
|
159
|
+
<CardTitle>Session Evidence</CardTitle>
|
|
160
|
+
<CardDescription>
|
|
161
|
+
Why this session costs what it costs, which token counts are exact or estimated, and what can be repaired.
|
|
162
|
+
</CardDescription>
|
|
163
|
+
</div>
|
|
164
|
+
<Badge variant={confidenceVariant(timeline.confidence.grade)}>
|
|
165
|
+
{timeline.confidence.score}/100 {timeline.confidence.grade} confidence
|
|
166
|
+
</Badge>
|
|
167
|
+
</CardHeader>
|
|
168
|
+
<CardContent className="grid gap-4 border-t p-4 lg:grid-cols-[1.2fr_0.8fr]">
|
|
169
|
+
<div className="grid gap-3 sm:grid-cols-3">
|
|
170
|
+
<div>
|
|
171
|
+
<FieldLabel>Token source</FieldLabel>
|
|
172
|
+
<div className="mt-1 text-sm font-medium">
|
|
173
|
+
{timeline.confidence.exactTokenInteractions.toLocaleString()} exact /{" "}
|
|
174
|
+
{timeline.confidence.tokenizerEstimateInteractions.toLocaleString()} tokenizer /{" "}
|
|
175
|
+
{timeline.confidence.simpleEstimateInteractions.toLocaleString()} simple
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<div>
|
|
179
|
+
<FieldLabel>Cost coverage</FieldLabel>
|
|
180
|
+
<div className="mt-1 text-sm font-medium">
|
|
181
|
+
{timeline.confidence.pricedCostInteractions.toLocaleString()} priced /{" "}
|
|
182
|
+
{timeline.confidence.unknownCostInteractions.toLocaleString()} unknown
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div>
|
|
186
|
+
<FieldLabel>Spike clues</FieldLabel>
|
|
187
|
+
<div className="mt-1 text-sm font-medium">
|
|
188
|
+
{timeline.spikes.length ? `${timeline.spikes.length.toLocaleString()} token spikes` : "No token spikes"}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
<div className="rounded-md border bg-muted/20 p-3">
|
|
193
|
+
<FieldLabel>Next repair step</FieldLabel>
|
|
194
|
+
<div className="mt-1 text-sm text-muted-foreground">
|
|
195
|
+
{timeline.repair.repairHref
|
|
196
|
+
? `Unknown cost cause: ${timeline.repair.unknownCostCause}. Open the repair workbench for this source.`
|
|
197
|
+
: "No session-specific cost repair is required."}
|
|
198
|
+
</div>
|
|
199
|
+
{timeline.repair.repairHref ? (
|
|
200
|
+
<Button asChild size="sm" variant="outline" className="mt-3">
|
|
201
|
+
<Link href={timeline.repair.repairHref}>Open repair</Link>
|
|
202
|
+
</Button>
|
|
203
|
+
) : null}
|
|
204
|
+
</div>
|
|
205
|
+
</CardContent>
|
|
206
|
+
</Card>
|
|
207
|
+
|
|
149
208
|
<Card>
|
|
150
209
|
<CardHeader>
|
|
151
210
|
<CardTitle>Timeline</CardTitle>
|
package/bin/tokentrace.js
CHANGED
|
@@ -30,7 +30,7 @@ Usage:
|
|
|
30
30
|
tokentrace capabilities --json
|
|
31
31
|
Alias for agent discovery manifest
|
|
32
32
|
tokentrace roadmap --json
|
|
33
|
-
Print
|
|
33
|
+
Print Accuracy & Evidence release implementation status
|
|
34
34
|
tokentrace scan Scan local AI CLI usage logs
|
|
35
35
|
tokentrace doctor --json
|
|
36
36
|
Inspect scan health and repair recommendations
|