feat(usage): open the TokenTracker dashboard from the usage monitor

The modal deliberately shows only a slice of the analytics; this hands off
to TokenTracker's own local dashboard for the rest. The worker starts
`tokentracker serve` on demand and returns the URL, the UI opens the browser.

Three things the spawn has to get right: port 7680 is not free on Windows
(Delivery Optimization holds [::]:7680) and serve does not fall back, so we
scan 7680-7689 with a dual-stack bind probe; --no-open because the CLI would
open the browser before the server answers; and the child is a cmd.exe shim,
so shutdown kills the process tree. --no-sync keeps our no-cloud-sync rule.
This commit is contained in:
mika kuns
2026-08-24 16:51:00 +02:00
parent 2dec590dfc
commit 3003cfa561
19 changed files with 413 additions and 4 deletions
+17 -1
View File
@@ -162,6 +162,17 @@ The per-model / per-task / cost view comes from **TokenTracker**, an external MI
- **Fail-open everywhere:** `TokenTrackerState` mirrors `UsageState` — a failed or unparseable
fetch never overwrites a good export, it only sets `LastError`. CLI missing → empty analytics
plus a hint card in the UI. Nothing here can throw into the queue.
- **Local dashboard:** `tokentracker serve --port <p> --no-open --no-sync`, started by the worker on
demand (`ITokenTrackerClient.StartDashboardAsync`) for the "Open TokenTracker dashboard" button.
Three things this path has to get right: TokenTracker's default port **7680 is not free** on
Windows (Delivery Optimization holds `[::]:7680`) and `serve` exits instead of falling back, so
we scan 76807689 with a **dual-stack** bind probe (an IPv4-only probe calls 7680 free and the
spawn then dies); `--no-open` because the CLI would open the browser before the server answers,
so we poll `GET http://127.0.0.1:<p>/` (30 s cap) and let the **UI** open the URL; and the child
is a `cmd.exe` shim, so shutdown kills the **process tree** or node keeps listening. The
spawned server is reused while alive and dies with the worker (`TokenTrackerClient` is
`IDisposable`, registered as a singleton) — a worker *crash* leaks it until the port is scanned
past.
- **Limits, gate and throttle do *not* come from TokenTracker.** They are the OAuth poll above.
A broken or absent TokenTracker costs you the cost columns, nothing else.
@@ -199,7 +210,9 @@ The per-model / per-task / cost view comes from **TokenTracker**, an external MI
(`GetTaskUsageAsync`) over a 7d/30d preset or custom range — both with a **cost** column, the
task table additionally with retries.
- **Analytics header vs. hint card** — the modal shows exactly one of the two. With TokenTracker
present: total tokens + total cost, a freshness stamp, and a manual `RefreshAnalytics` button.
present: total tokens + total cost, a freshness stamp, a manual `RefreshAnalytics` button and an
**Open TokenTracker dashboard** button (spinner while the server boots; everything this modal
deliberately doesn't show lives there).
Without it: a hint card explaining what TokenTracker is (local only, no `init`, no cloud sync)
plus an `Install TokenTracker` button — which is hidden and replaced by a Node-version hint when
Node < 20 or Node is missing, because the install would just fail. Install progress and every
@@ -223,6 +236,9 @@ The per-model / per-task / cost view comes from **TokenTracker**, an external MI
NodeVersion, LastFetchedUtc, LastError, FormatVersion, SessionCount)`. Never throws; an
unconfigured worker comes back as not-installed with a reason.
- `RefreshTokenTracker()` — forces a fetch, then re-probes and returns the status.
- `OpenTokenTrackerDashboard()` — brings up the local dashboard server and returns
`TokenTrackerDashboardDto(Ok, Url, Error)`; the UI opens the browser (`ShellOpen.Url`, which
refuses anything that isn't http/https). Never throws — a failure also lands in `WorkerLog`.
- `InstallTokenTracker()` — `npm i -g tokentracker-cli`, streaming npm's output line by line into
`WorkerLog` (npm writes progress to stderr; forwarding it is what keeps a multi-minute global
install from looking hung), then force-re-probes.