tokentrace 0.19.2 → 0.20.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 +32 -0
- package/README.md +1 -1
- package/TOKENTRACE_AGENT.md +11 -0
- package/app/api/evidence-pack/route.ts +3 -40
- package/app/guide/guide-content.ts +1 -1
- package/app/page.tsx +4 -4
- package/components/overview/summary-cards.tsx +64 -34
- package/components/sidebar.tsx +61 -25
- package/dist/cli/main.mjs +10 -0
- package/dist/runtime/agent.mjs +31 -1
- package/dist/runtime/chatgpt-app.mjs +3901 -0
- package/dist/runtime/mcp.mjs +31 -1
- package/docs/CHATGPT_APP_PROTOTYPE.md +103 -0
- package/docs/CHATGPT_APP_RELEASE.md +101 -0
- package/docs/chatgpt-app/README.md +24 -0
- package/docs/chatgpt-app/assets/icon.svg +10 -0
- package/docs/chatgpt-app/assets/listing-card.svg +34 -0
- package/docs/chatgpt-app/assets/widget-preview.svg +24 -0
- package/docs/chatgpt-app/dashboard-fields.json +39 -0
- package/docs/chatgpt-app/manual-release-steps.md +114 -0
- package/docs/chatgpt-app/privacy-and-data.md +39 -0
- package/docs/chatgpt-app/review-response-template.md +53 -0
- package/docs/chatgpt-app/screenshot-checklist.md +46 -0
- package/docs/chatgpt-app/submission-copy.md +45 -0
- package/docs/chatgpt-app/test-prompts-and-responses.md +61 -0
- package/llms.txt +13 -0
- package/next.config.mjs +8 -2
- package/package.json +11 -4
- package/scripts/build-cli-runtime.mjs +1 -0
- package/scripts/chatgpt-app-release-check.mjs +489 -0
- package/scripts/chatgpt-app.ts +107 -0
- package/scripts/package-inspect.mjs +6 -1
- package/scripts/smoke-packed-install.mjs +5 -0
- package/server.json +3 -3
- package/src/cli/commands.ts +8 -0
- package/src/cli/help.ts +3 -0
- package/src/lib/agent-discovery.ts +32 -1
- package/src/lib/chatgpt-app/prototype.ts +452 -0
- package/src/lib/chatgpt-app/server.ts +162 -0
- package/src/lib/evidence-pack.ts +47 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,38 @@ All notable changes to TokenTrace are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [0.20.0] - 2026-06-12
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Overview and shell UI polish.** The dashboard shell now groups navigation by
|
|
12
|
+
task area, and the Overview summary surface makes local evidence,
|
|
13
|
+
cached/non-cache token states, and exact/estimated/unknown cost distinctions
|
|
14
|
+
easier to scan.
|
|
15
|
+
- **ChatGPT app feasibility documented.** Added a recommendation to prototype
|
|
16
|
+
only a private, redacted evidence-pack ChatGPT app workflow until public
|
|
17
|
+
distribution can preserve TokenTrace's local-first privacy boundary.
|
|
18
|
+
- **Private ChatGPT app prototype.** Added `tokentrace chatgpt-app` with a
|
|
19
|
+
local HTTP `/mcp` server, one read-only `get_redacted_evidence_pack` Apps SDK
|
|
20
|
+
tool, a compact MCP Apps widget resource, and a developer-mode runbook. The
|
|
21
|
+
prototype uses redacted evidence packs and does not scan files on startup or
|
|
22
|
+
publish a public app.
|
|
23
|
+
- **ChatGPT app release readiness.** Added `npm run release:chatgpt:check`, a
|
|
24
|
+
hosted `/mcp` validator, a release runbook, and optional tag-workflow wiring
|
|
25
|
+
so ChatGPT app readiness can be checked alongside npm and MCP registry
|
|
26
|
+
releases while keeping OpenAI Dashboard submission and publishing manual.
|
|
27
|
+
- **ChatGPT app submission kit.** Added `docs/chatgpt-app/` with step-by-step
|
|
28
|
+
personal-account Dashboard release instructions, ready-to-paste submission
|
|
29
|
+
copy, privacy and review templates, test prompts, screenshot guidance, and
|
|
30
|
+
reusable SVG app assets for the manual ChatGPT app submission flow.
|
|
31
|
+
- **Development dashboard CSP avoids React issue noise.** The local dev server
|
|
32
|
+
now allows React's development-only diagnostics without relaxing the
|
|
33
|
+
production Content Security Policy.
|
|
34
|
+
- **Product website moved to Baseframe Labs.** The package `homepage`, MCP
|
|
35
|
+
registry `websiteUrl`, README link, and in-app guide link now point to
|
|
36
|
+
`https://www.baseframelabs.com/apps/tokentrace`. The previous
|
|
37
|
+
`abhiyoheswaran.com` URL redirects there.
|
|
38
|
+
|
|
7
39
|
## [0.19.2] - 2026-06-05
|
|
8
40
|
|
|
9
41
|
### 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.
|
|
11
|
+
[Website](https://www.baseframelabs.com/apps/tokentrace) · [Source](https://github.com/abhiyoheswaran1/tokentrace)
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
package/TOKENTRACE_AGENT.md
CHANGED
|
@@ -44,6 +44,17 @@ 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
|
+
Private ChatGPT developer-mode prototype:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
tokentrace chatgpt-app selftest --json
|
|
51
|
+
tokentrace chatgpt-app --port 8787 --hostname 127.0.0.1
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This starts a local HTTP `/mcp` server for one read-only redacted evidence-pack
|
|
55
|
+
tool. ChatGPT requires an HTTPS tunnel for developer-mode connector testing.
|
|
56
|
+
See `docs/CHATGPT_APP_PROTOTYPE.md`.
|
|
57
|
+
|
|
47
58
|
If the local dashboard is already running, the same manifest is available from:
|
|
48
59
|
|
|
49
60
|
```bash
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
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), {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClipboardList, Gauge, Search, Wrench } from "lucide-react";
|
|
2
2
|
|
|
3
|
-
export const PRODUCT_WEBSITE_URL = "https://www.
|
|
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"],
|
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-
|
|
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-
|
|
112
|
+
<div className="overview-repair-section space-y-7">
|
|
113
113
|
{summary.interactions > 0 ? (
|
|
114
114
|
<OverviewReviewStatusStrip
|
|
115
115
|
report={doctorReport}
|
|
@@ -140,7 +140,7 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
140
140
|
const range = resolveDateRange(params);
|
|
141
141
|
|
|
142
142
|
return (
|
|
143
|
-
<div className="space-y-
|
|
143
|
+
<div className="overview-workbench space-y-7">
|
|
144
144
|
<PageHeader
|
|
145
145
|
title="Overview"
|
|
146
146
|
description="Local token, cost, model, and session analytics across AI CLI tools."
|
|
@@ -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:
|
|
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-
|
|
57
|
-
<span className="
|
|
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="
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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={[
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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>
|
package/components/sidebar.tsx
CHANGED
|
@@ -26,28 +26,52 @@ import { formatAppVersion, getAppVersion } from "@/src/lib/app-version";
|
|
|
26
26
|
import { cn } from "@/src/lib/utils";
|
|
27
27
|
|
|
28
28
|
const overviewNavItem = { href: "/", label: "Overview", icon: Gauge };
|
|
29
|
+
const toolsNavItem = { href: "/tools", label: "Tools", icon: Terminal };
|
|
30
|
+
const modelsNavItem = { href: "/models", label: "Models", icon: Bot };
|
|
31
|
+
const projectsNavItem = { href: "/projects", label: "Projects", icon: FolderGit2 };
|
|
32
|
+
const sessionsNavItem = { href: "/sessions", label: "Sessions", icon: Search };
|
|
33
|
+
const insightsNavItem = { href: "/optimisation", label: "Insights", icon: Sparkles };
|
|
34
|
+
const queryNavItem = { href: "/query", label: "Query", icon: Database };
|
|
35
|
+
const repairNavItem = { href: "/repair", label: "Repair", icon: Wrench };
|
|
36
|
+
const pricingNavItem = { href: "/pricing", label: "Model Rates", icon: SlidersHorizontal };
|
|
37
|
+
const diagnosticsNavItem = { href: "/diagnostics", label: "Scan Health", icon: ClipboardList };
|
|
38
|
+
const discoveryNavItem = { href: "/discovery", label: "Discovery", icon: BarChart3 };
|
|
39
|
+
const parsersNavItem = { href: "/parser-debug", label: "Parsers", icon: Bug };
|
|
40
|
+
const rawDataNavItem = { href: "/debug", label: "Raw Data", icon: Bug };
|
|
41
|
+
const settingsNavItem = { href: "/settings", label: "Settings", icon: Settings };
|
|
29
42
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const navSections = [
|
|
44
|
+
{
|
|
45
|
+
label: "Analyze",
|
|
46
|
+
items: [overviewNavItem, toolsNavItem, modelsNavItem, projectsNavItem]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: "Investigate",
|
|
50
|
+
items: [sessionsNavItem, insightsNavItem, queryNavItem, repairNavItem]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "Maintain",
|
|
54
|
+
items: [
|
|
55
|
+
pricingNavItem,
|
|
56
|
+
diagnosticsNavItem,
|
|
57
|
+
discoveryNavItem,
|
|
58
|
+
parsersNavItem,
|
|
59
|
+
rawDataNavItem,
|
|
60
|
+
settingsNavItem
|
|
61
|
+
]
|
|
62
|
+
}
|
|
45
63
|
];
|
|
46
64
|
|
|
47
65
|
const supportNavItems = [
|
|
48
66
|
{ href: "/guide", label: "Guide", icon: BookOpen }
|
|
49
67
|
];
|
|
50
68
|
|
|
69
|
+
const primaryNavItems = navSections.flatMap((section) => section.items);
|
|
70
|
+
const sidebarSections = [
|
|
71
|
+
...navSections.map((section) => ({ ...section, variant: "default" as const })),
|
|
72
|
+
{ label: "Reference", items: supportNavItems, variant: "support" as const }
|
|
73
|
+
];
|
|
74
|
+
|
|
51
75
|
const priorityMobileItems = primaryNavItems.filter((item) =>
|
|
52
76
|
["/", "/sessions", "/repair", "/diagnostics", "/settings"].includes(item.href)
|
|
53
77
|
);
|
|
@@ -76,7 +100,7 @@ function NavLink({
|
|
|
76
100
|
: cn(
|
|
77
101
|
"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
|
|
78
102
|
isActive
|
|
79
|
-
? "bg-
|
|
103
|
+
? "bg-muted font-medium text-primary shadow-xs ring-1 ring-border/60"
|
|
80
104
|
: variant === "support"
|
|
81
105
|
? "border bg-muted/40 font-medium text-foreground hover:bg-muted"
|
|
82
106
|
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
@@ -140,17 +164,29 @@ export function Sidebar({ appVersion = getAppVersion() }: { appVersion?: string
|
|
|
140
164
|
</div>
|
|
141
165
|
</div>
|
|
142
166
|
</div>
|
|
143
|
-
<nav aria-label="Primary navigation" className="flex-1
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
<nav aria-label="Primary navigation" className="flex-1 overflow-y-auto p-3">
|
|
168
|
+
<div className="space-y-4">
|
|
169
|
+
{sidebarSections.map((section) => (
|
|
170
|
+
<section key={section.label} aria-label={`${section.label} navigation`} className="space-y-1">
|
|
171
|
+
<div className="px-3 pb-1 text-[0.68rem] font-semibold uppercase leading-none tracking-normal text-muted-foreground">
|
|
172
|
+
{section.label}
|
|
173
|
+
</div>
|
|
174
|
+
{section.items.map((item) => (
|
|
175
|
+
<NavLink
|
|
176
|
+
key={item.href}
|
|
177
|
+
item={item}
|
|
178
|
+
variant={section.variant}
|
|
179
|
+
isActive={isActiveRoute(pathname, item.href)}
|
|
180
|
+
/>
|
|
181
|
+
))}
|
|
182
|
+
</section>
|
|
183
|
+
))}
|
|
184
|
+
</div>
|
|
152
185
|
</nav>
|
|
153
186
|
<div className="border-t p-4 text-xs text-muted-foreground">
|
|
187
|
+
<div className="mb-2 text-[0.68rem] font-semibold uppercase leading-none tracking-normal text-muted-foreground">
|
|
188
|
+
Local processing
|
|
189
|
+
</div>
|
|
154
190
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 leading-relaxed">
|
|
155
191
|
<span className="font-medium text-foreground">{formatAppVersion(appVersion)}</span>
|
|
156
192
|
<span aria-hidden="true">·</span>
|
package/dist/cli/main.mjs
CHANGED
|
@@ -82,6 +82,9 @@ Usage:
|
|
|
82
82
|
tokentrace mcp Start the local stdio MCP server
|
|
83
83
|
tokentrace mcp selftest --json
|
|
84
84
|
Verify the local MCP entrypoint without scanning files
|
|
85
|
+
tokentrace chatgpt-app Start the private ChatGPT app prototype
|
|
86
|
+
tokentrace chatgpt-app selftest --json
|
|
87
|
+
Verify the ChatGPT app prototype without scanning files
|
|
85
88
|
tokentrace scan Scan local AI CLI usage logs
|
|
86
89
|
tokentrace doctor --json
|
|
87
90
|
Inspect scan health and repair recommendations
|
|
@@ -458,6 +461,9 @@ async function mcp(context, args) {
|
|
|
458
461
|
}
|
|
459
462
|
await runNodeScript(context, "mcp", [], { env: process.env });
|
|
460
463
|
}
|
|
464
|
+
async function chatGptApp(context, args) {
|
|
465
|
+
await runNodeScript(context, "chatgpt-app", args, { env: process.env });
|
|
466
|
+
}
|
|
461
467
|
async function doctor(context, args) {
|
|
462
468
|
if (await printScriptHelp(context, "doctor", args)) return;
|
|
463
469
|
await initializeDatabase(context, { quiet: true, refreshPrices: false });
|
|
@@ -651,6 +657,10 @@ async function runCliCommand(context, rawArgs = process.argv.slice(2)) {
|
|
|
651
657
|
await mcp(context, args);
|
|
652
658
|
return;
|
|
653
659
|
}
|
|
660
|
+
if (command === "chatgpt-app") {
|
|
661
|
+
await chatGptApp(context, args);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
654
664
|
if (command === "scan") {
|
|
655
665
|
await scan(context, args);
|
|
656
666
|
return;
|
package/dist/runtime/agent.mjs
CHANGED
|
@@ -4006,7 +4006,7 @@ var product = {
|
|
|
4006
4006
|
name: "TokenTrace",
|
|
4007
4007
|
packageName: "tokentrace",
|
|
4008
4008
|
description: "Local-first dashboard and CLI for AI coding-agent token, cost, session, and parser analytics.",
|
|
4009
|
-
homepage: "https://www.
|
|
4009
|
+
homepage: "https://www.baseframelabs.com/apps/tokentrace",
|
|
4010
4010
|
repository: "https://github.com/abhiyoheswaran1/tokentrace"
|
|
4011
4011
|
};
|
|
4012
4012
|
var commands = [
|
|
@@ -4143,6 +4143,27 @@ var commands = [
|
|
|
4143
4143
|
"The run_scan MCP tool requires confirmLocalScan=true before reading local usage files and writing the local database."
|
|
4144
4144
|
]
|
|
4145
4145
|
},
|
|
4146
|
+
{
|
|
4147
|
+
id: "chatgpt-app",
|
|
4148
|
+
title: "Start private ChatGPT app prototype",
|
|
4149
|
+
command: ["tokentrace", "chatgpt-app"],
|
|
4150
|
+
description: "Start a local HTTP MCP Apps server for ChatGPT developer-mode testing. It exposes one read-only redacted evidence-pack tool and widget.",
|
|
4151
|
+
output: "terminal",
|
|
4152
|
+
mutatesLocalState: false,
|
|
4153
|
+
startsLongRunningProcess: true,
|
|
4154
|
+
requiresNetwork: false,
|
|
4155
|
+
safeForAutomation: false,
|
|
4156
|
+
useWhen: "The human wants to test TokenTrace as a private ChatGPT connector without publishing a public app.",
|
|
4157
|
+
followUps: [
|
|
4158
|
+
["tokentrace", "chatgpt-app", "selftest", "--json"],
|
|
4159
|
+
["tokentrace", "chatgpt-app", "--port", "8787", "--hostname", "127.0.0.1"]
|
|
4160
|
+
],
|
|
4161
|
+
notes: [
|
|
4162
|
+
"ChatGPT developer mode requires exposing the local /mcp endpoint through an HTTPS tunnel.",
|
|
4163
|
+
"The prototype does not scan files on startup and returns only redacted evidence packs.",
|
|
4164
|
+
"See docs/CHATGPT_APP_PROTOTYPE.md for connector setup steps."
|
|
4165
|
+
]
|
|
4166
|
+
},
|
|
4146
4167
|
{
|
|
4147
4168
|
id: "status",
|
|
4148
4169
|
title: "Print local live usage status",
|
|
@@ -4373,6 +4394,15 @@ function buildAgentDiscoveryManifest(options = {}) {
|
|
|
4373
4394
|
steps: [
|
|
4374
4395
|
["tokentrace", "watch", "--session", "--compact"]
|
|
4375
4396
|
]
|
|
4397
|
+
},
|
|
4398
|
+
{
|
|
4399
|
+
id: "chatgpt-app-prototype",
|
|
4400
|
+
title: "Private ChatGPT app prototype",
|
|
4401
|
+
goal: "Start the local Apps SDK prototype and expose a redacted evidence-pack connector for developer-mode testing.",
|
|
4402
|
+
steps: [
|
|
4403
|
+
["tokentrace", "chatgpt-app", "selftest", "--json"],
|
|
4404
|
+
["tokentrace", "chatgpt-app", "--port", "8787", "--hostname", "127.0.0.1"]
|
|
4405
|
+
]
|
|
4376
4406
|
}
|
|
4377
4407
|
],
|
|
4378
4408
|
integrations: {
|