refactor: make TokenTracker local-only#72
Merged
Conversation
added 6 commits
July 21, 2026 06:11
… CLI First step of making this fork local-only. Two things go away here: the queue upload path and the browser-pairing command that existed to obtain the credential it needed. `sync` no longer resolves a runtime cloud config, no longer calls drainQueueToCloud, and no longer POSTs to /functions/tokentracker-ingest. Parsing is untouched — the local queue still fills exactly as before, it simply has nowhere to be sent. Removing the upload call alone would have left a trap: the queue offset only ever advanced on a successful upload, so `pendingBytes` would have been permanently positive and the auto-retry scheduler would have rebooked itself forever with nothing to retry. The whole retry chain goes with it — scheduleAutoRetry, clearAutoRetry, spawnAutoRetryProcess, buildAutoRetryScript, coerceRetryMs, the auto.retry.json artifact and the upload throttle state. `device-login` is deleted outright, along with its CLI registration, help text and test. Its only purpose was pairing a headless session with a browser sign-in to mint a device token. Help text now says what is true: sync parses into the local queue and nothing is uploaded. Zero new test failures — 778/782, the same 4 pre-existing parseKiroCliIncremental failures that are red on main (#65). The count drops from 795 because device-login.test.js is gone with its subject. Refs #67 (local-only direction)
Second phase of the local-only change. Deletes the Leaderboard, Login, Device pairing, Marketing landing and Share features from the dashboard, along with the InsForge auth context and every helper that existed to serve them. The file count hid the real work. `lib/auth-token` was imported by six data hooks (usage, trend, model-breakdown, heatmap, project-summary, pulse), so the dashboard had been gating its *local* API fetches on a *cloud* access token. Removing the token meant unpicking that gate in each one; leaving it would have left the hooks waiting for a token that can never arrive, and the dashboard permanently empty. Two tests were INVERTED rather than deleted, because their subjects did not disappear — the intended behavior reversed: - `app-route-pathname-guard` asserted /leaderboard and /login exist; it now asserts they stay gone, along with the pages behind them. - `dashboard-missing-jwt-guard` asserted the hooks refuse to fetch without a token; it now asserts they do not gate on one. Deleting it would have left the reversal unprotected. Test files whose subject was genuinely removed were deleted outright. Also rewires the HTML meta. `index.html` reads its title and Open Graph tags from copy keys, and those keys lived under `landing.meta.*`. Dropping them with the landing page left the dashboard shipping an empty `<title>` — silently, because a missing copy key only warns and the build still exits 0. They are now `app.meta.*` with local-appropriate values, and `share.html` is removed from the Vite inputs and the Vercel rewrites rather than being built as a dead entry. Verified: `npm test` 789 pass, `npm --prefix dashboard test` 233 pass, `npm run dashboard:build` exit 0 with no missing-key warnings, `<title>` renders as TokenTracker, and `grep -rlEi "insforge|deviceToken|tokentracker\.cc|leaderboard" dashboard/src` returns nothing. The CLI side (local-api auth proxy, runtime-config, doctor, diagnostics, status, init) is still to do.
Third and final phase. The CLI no longer proxies auth, holds a device token, points at a remote endpoint, or reports upload state. - `local-api.js`: the `/api/auth-bridge/verifier` endpoint and the `/api/auth/*` reverse proxy to InsForge are gone, along with the on-disk cookie relay that existed to share a login session between the browser and the WKWebView. The local `localAuthToken` stays — that one guards local mutations and is not a cloud credential. - `browser-auth.js` → `browser-open.js`: the sign-in flow and its local callback server are removed. Only `openInBrowser` survives, which `serve.js` uses, so the old name would have described nothing. - `runtime-config.js`: `baseUrl`, `dashboardUrl` and `deviceToken` removed. `doctor` loses the cloud reachability probe and the base_url / device_token checks; its `upload.last_error` check went too, since `diagnostics.upload` no longer exists and it could only ever report a permanent green. - `diagnostics.js` / `status.js`: upload-throttle and auto-retry reporting removed, and `upload-throttle.js` deleted once nothing imported it. - `init.js`: cloud setup, the `--base-url` / `--dashboard-url` flags and the written endpoint config are gone. `--no-auth` and friends are accepted and ignored rather than removed, so existing hooks and scripts do not start failing with "Unknown option". - Docs: README, PRODUCT and CLAUDE no longer describe a hosted product, a leaderboard, or an opt-in upload. The openwiki route and command tables lost their removed entries, and the extractor's hardcoded route table lost them too — it matched `/u/:userId` by substring against `/u`, so the fact check was reporting a route that no longer exists anywhere. - `index.html` drops `og:image`, `og:url` and `twitter:image`: a local-only app has no canonical URL or hosted preview, and empty tags are worse than absent. Tests were inverted rather than deleted wherever the behavior reversed instead of disappearing — `local-api-security` now pins that no auth proxy or cookie relay can return, `doctor` pins that the cloud checks stay gone, and `runtime-config` keeps its precedence and env-isolation guarantees by testing them on a field that still exists. Tests whose subject was genuinely removed were deleted. The UI hardcode baseline was regenerated: 19 of its 53 files no longer exist. The three "new" tokens it then reported are heuristic false positives — a code fragment, a test label, and a reworded comment — not user-facing copy. Verified: `npm test` 762 pass, `npm --prefix dashboard test` 233 pass, `npm run ci:local` exit 0, `docs:openwiki:check` 0 findings, `tracker status` and `tracker doctor` both exit 0, and no shipped file under src/, bin/ or dashboard/src/ mentions insforge, deviceToken, tokentracker.cc or leaderboard.
Three independent reviews (security, silent-failure, quality) found no
over-deletion — every removed symbol was checked for surviving callers — but a
number of things that now lied or leaked.
The two that mattered, both flagged independently by two reviewers:
- **A live cloud credential survived the upgrade, invisibly.** `init` rebuilt
config with `{...existingPlainConfig}`, carrying `deviceToken` forward, and
this same release removed the `status` / `diagnostics` lines that used to
report it. A user told "local-only, nothing is uploaded" had a valid InsForge
bearer token on disk with nothing left to reveal or rotate it. `init` now
scrubs `deviceToken` / `baseUrl` / `dashboardUrl` and unlinks
`relay-cookies.json`, `upload.throttle.json` and `auto.retry.json` — the last
of which nothing could remove any more, because `clearRelayCookies()` went
with the auth proxy. Verified on a populated fixture.
- **The shipped dashboard still marketed the cloud.** `index.html` told readers
"cloud sync is optional and used only for the public leaderboard" in eight
places that survived into `dist/`, directly contradicting README's "there is
no upload path at all". Fixed, along with `sitemap.xml` and `llms.txt`.
`dist/index.html` was also shipping a literal `__TOKENTRACKER_OG_URL__` in a
canonical link, because the placeholder was dropped from the substitution map
but not from the HTML.
Also:
- `--link-code` was advertised in `cli.js` and silently discarded, so a user
pairing a device saw "Setup complete!" and believed it worked. It now fails
loudly. `doctor --base-url` left the help text too.
- `doctor` reported `runtime.auto_retry_no_spawn :: auto retry spawn enabled`
for a spawn path deleted in the first commit. Removed end to end.
- `init` stopped telling the user where to find the dashboard once the cloud
default URL was gone; it now prints the local address.
- `--password=VALUE` fell through to `Unknown option`, which is printed as a
stack trace — putting the secret on stderr. Swallowed like its space form.
- Dead code and dependencies removed: `normalizeRemoteHttpBaseUrl`,
`buildProxyHeaders`, `leaderboard-ui.js`, `host-mode.ts`, `@insforge/sdk`,
`html-to-image`, `ogl`, `three`, the four `@dnd-kit` packages, and
`verify:share-clipboard`.
Two of my own inverted tests were judged too weak and were strengthened:
`dashboard-missing-jwt-guard` asserted only the ABSENCE of a token gate, which
an empty file would satisfy — it now asserts the hook still issues a request
first. `runtime-config` never pinned the env rung it claimed to; deleting env
from the pick chain left it green.
`init preserves existing config fields and custom URLs` was inverted to
`init scrubs cloud credentials and endpoints while preserving local config`,
since that preservation is precisely the defect.
Verified: `npm test` 763 pass, dashboard 233 pass, `ci:local` exit 0,
`dashboard:build` exit 0, and the built `dist/index.html` contains no
leaderboard, cloud-sync, InsForge or unsubstituted-placeholder text.
Both reach npm users, unlike the remaining cloud surface: - root `package.json` published `"leaderboard"` as a keyword for a feature this release removes. - `@vercel/analytics` and `@vercel/speed-insights` were imported in `App.jsx` and bundled into `dashboard/dist/`. `shouldEnableVercelInsights()` gated them on non-loopback, so a normal install never fired them — but reaching the dashboard over a LAN IP beaconed to Vercel, contradicting README's "nothing leaves your machine". With no hosted deploy left, that gate could only ever be true in a case the product no longer supports. Caught by actually running the app rather than trusting the build: `vite` does not resolve undefined JSX identifiers, so a partial removal left `enableVercelInsights` referenced at three sites with a green build. Three dashboard tests failed; the smoke test would have caught it either way. Smoke tested end to end (`serve --port 7791` against a synthetic queue): `/` returns 200 with the app root and no cloud text, the JS bundle serves, and `usage-summary`, `usage-daily` and `user-status` all return 200. Noted, not changed: removed endpoints such as `/api/auth/refresh` return 200 with the SPA shell rather than 404, because `serve.js:141` has always had an unconditional SPA fallback for unmatched paths. Pre-existing, and no caller for those routes remains.
Found by clicking through the running dashboard, not by grep or build. The earlier cloud sweep missed it because the grep pattern was case-sensitive and the host is "tokentracker.statuspage.io" — it matches neither "insforge" nor "leaderboard". - dashboard/src/lib/config.ts: drop STATUSPAGE_URL - MenuBarSection.jsx: NativeAppFooter now returns null when the native app is absent. The status link was the only thing it rendered in that case, so the alternative was an empty flex row. - copy.csv + 4 i18n locales: remove the orphaned settings.footer.statusPage key. validate:copy does not catch i18n orphans in this direction. Verified: dashboard:build clean, tsc --noEmit clean, ci:local green (763 + 4, fail 0), dashboard vitest 233/233, and all six routes re-smoked in a real browser with zero console errors.
This was referenced Jul 21, 2026
Closed
Owner
Author
Reviewer summary — clean, self-contained deletion, merge-readyIndependent audit of this PR (not just CI-green). Verified on head The two questions a deletion PR must answer: 1. Does anything still reference the removed surface? — No.
2. Does the remaining app still work? — Yes.
Deletion breakdown (~18.6k lines)
Two notes — neither blocking
Bottom line: no dangling cloud reference in shipped runtime code; the deletion is internally consistent. The only substantive decision is the |
4 tasks
pitimon
added a commit
that referenced
this pull request
Jul 21, 2026
Part of #71. Removes the repo/CI/web-dev cloud surface. None of this ships in the npm package, so deleting it undeploys nothing — the live InsForge backend is retired separately by rotating its service-role key (a user action on the InsForge dashboard; nothing in this repo held that secret). Scope kept to web/CI/repo per decision; native-app OAuth (Swift/C#) is a separate follow-up on #71. Removed: - dashboard/edge-patches/ — all 18 InsForge Edge functions (ingest, leaderboard*, device-*, account-*, badge/embed svg, public-visibility, profile-likes) and the 3 tests that only guarded them (edge-patch- pricing-sync, badge-svg-render, and two leaderboard-refresh byte-equality cases in model-breakdown.test.js; the live buildFleetData/pricing tests stay). - InsForge creds from all 4 workflows (ci, npm-publish, release-dmg, release-windows) + the stale comments claiming their absence breaks the build or "disables cloud OAuth (isCloudInsforgeConfigured())" — that function was deleted in #72 and nothing reads VITE_INSFORGE_* anymore. - dashboard/vercel.json (deploy that no longer exists), dashboard/.mcp.json (InsForge MCP dev tooling), the /api/auth→InsForge proxy and the insforgeBaseUrl passthrough in dashboard/vite.config.js. - InsForge/service-role/device rows from .env.example; rewrote dashboard/.env.example to the one local (mock) var. - Stale references in SECURITY.md (insforge/ path), CLAUDE.md, the curated-overrides pricing note, the guardrails ignore list, and the leaderboard-absence item in the publish checklist. Verified: dashboard:build succeeds with NO VITE_INSFORGE_* env (proving the "must be present or the build fails" comments were stale), bundle carries no insforge/statuspage host, and ci:local is green end-to-end — dashboard 233/233, root 752 + 4, all validators pass. No secret was ever in the repo (edge functions read INSFORGE_SERVICE_ROLE_KEY from Deno env; CI injected only the public ik_ anon key, now removed too). Co-authored-by: itarun.p <itarun.p@somapait.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes TokenTracker local-only. Removes its own cloud upload, device pairing, InsForge auth, the hosted dashboard, and the Leaderboard, Login, Device, Marketing landing and Share features.
Scope was decided explicitly: delete the cloud rather than make it opt-in. Three judgment calls that were genuinely product decisions, not mechanical ones, were settled up front — the marketing landing goes and
/serves the dashboard; Share goes (it built links as/u/{userId}, a cloud path that would 404); the leaderboard-social UI (LikeButton,MacAppBanner) goes.What moved
CLI —
syncno longer uploads;device-loginis gone.local-api.jsdrops the/api/auth-bridge/verifierendpoint, the/api/auth/*reverse proxy to InsForge, and the on-disk cookie relay. The locallocalAuthTokenstays: it guards local mutations and is not a cloud credential.browser-auth.jsbecamebrowser-open.js— onlyopenInBrowsersurvived, so the old name described nothing.runtime-configlosesbaseUrl/dashboardUrl/deviceToken;doctorloses the cloud reachability probe and two checks that could only ever report green;upload-throttle.jsis deleted.Dashboard — 65 files deleted. The real work was not the deletions:
lib/auth-tokenwas imported by six data hooks, so the dashboard had been gating its local API fetches on a cloud access token. Removing the token meant unpicking that gate in each one, or they would wait forever for a token that can never arrive.Docs — README, PRODUCT, CLAUDE and the openwiki tables no longer describe a hosted product. The openwiki extractor's hardcoded route table matched
/u/:userIdby substring against/u, so the fact check was demanding a route that exists nowhere.Migration behavior worth knowing
initnow scrubsdeviceToken,baseUrlanddashboardUrlfrom an existing config and unlinksrelay-cookies.json,upload.throttle.jsonandauto.retry.json. This matters: previouslyinitspread the whole prior config forward, and this same change removed thestatus/diagnosticslines that used to report the token — so a user told "local-only, nothing is uploaded" would have kept a valid InsForge bearer token on disk with nothing left to reveal or rotate it.relay-cookies.jsonwas worse: the only code that ever deleted it wasclearRelayCookies(), which went with the auth proxy.--link-codenow fails loudly instead of being ignored — it was advertised incli.jsand silently discarded, so a user pairing a device saw "Setup complete!" and believed it worked.--no-auth,--email,--passwordand--device-nameare accepted and ignored so existing hooks and scripts do not start failing with "Unknown option".status --jsonnarrowed:base_url,device_token_set,queue.pending_bytes,queue.offset,last_upload,next_upload_after,backoff_until,last_upload_error,auto_retryare gone. In-tree consumers verified safe — both menu-bar apps talk to the local HTTP server, not tostatus --json. Third-party scripts reading those fields will getundefined.Test plan
npm test— 763 passnpm --prefix dashboard test— 233 passnpm run ci:local— exit 0npm run dashboard:build— exit 0,dist/index.htmlcontains no leaderboard, cloud-sync, InsForge or unsubstituted-placeholder textdocs:openwiki:check— 0 findingsserveagainst a synthetic queue —/returns 200 with the app root, the JS bundle serves, andusage-summary/usage-daily/user-statusall return 200deviceToken/baseUrl/dashboardUrlremoved,deviceIdpreserved, stale files unlinkedpackage.json, bothproject.ymlMARKETING_VERSION,TokenTrackerWin.csproj) + DMG workflow — deliberately not bumped, pending the release decision, same as the PRs merged before this.Review
Three independent reviews ran against this branch — security, silent-failure, and quality. No CRITICAL, and no over-deletion: every removed symbol was grepped for surviving callers. What they found was under-deletion and things that lied, and those are fixed in the last two commits:
index.htmlstill told readers "cloud sync is optional and used only for the public leaderboard" in eight places that survived intodist/, directly contradicting READMEdist/index.htmlwas shipping a literal__TOKENTRACKER_OG_URL__in a canonical linkdoctorreportedauto_retry_no_spawnfor a spawn path deleted in the first commitinitstopped telling the user where to find the dashboard once the cloud default URL was removed--password=VALUEfell through toUnknown option, printed as a stack trace, putting the secret on stderr@vercel/analyticsstill bundled: gated on non-loopback, so LAN-IP access beaconed out, against README's "nothing leaves your machine"Two tests I had written as inversions were judged too weak and were strengthened.
dashboard-missing-jwt-guardasserted only the absence of a token gate — an empty file would have satisfied it, and it is the designated guard for that reversal.runtime-confignever pinned the env rung its comment claimed; deleting env from the pick chain left it green.Tests were inverted rather than deleted wherever behavior reversed instead of disappearing, so the removals stay pinned: no auth proxy or cookie relay can return to
local-api, the clouddoctorchecks stay gone,/leaderboardand/loginstay unrouted, and the hooks must keep fetching without a token. Tests whose subject genuinely no longer exists were deleted.Deferred
Everything still cloud-facing that does not ship in the npm package —
edge-patches/, CI credentials, the native apps' OAuth flow,vercel.json, and the dormant dev-server proxy — is tracked in #71 rather than grown into this PR. The real remediation there is rotating the InsForge service-role key at the source, not editing YAML.Refs #71