Onboarding a New AI Coding CLI onto the NeuroLink Proxy
Status: Understanding document — no code changes proposed yet
This document maps what it actually costs to add a fourth, fifth or sixth AI coding
CLI to the NeuroLink proxy. It is the result of verifying an earlier audit (performed
against v11.2.2, commit 0e935499) line by line against v11.2.3, commit
8728bbb3.
Everything below was re-read in this worktree. Where the earlier audit was wrong or
imprecise, §2 says so plainly. Line numbers are from 8728bbb3 and were checked with
grep -n / sed -n; they will drift.
Branch state, as of the audit (2026-08-21):
feat/cli-supportcarried zero commits of its own —git log origin/release..HEADwas empty andorigin/releasewas one commit ahead (67386e95,.releaserc.jsononly). Recorded as the starting point this audit worked from; it is a historical snapshot, not a claim about the branch today.
Contents
- The shape of the problem
- Corrections to the v11.2.2 audit
- Touch points: a config-writer-only CLI
- Touch points: a new-wire-format CLI
- What the existing machinery gives you — and where it stops
- Position: the account namespace is not a prerequisite
- The observability / routing split
- Defects
- Repo conventions this work must follow
1. The shape of the problem
The proxy is a Hono app bound to 127.0.0.1:55669 by default (proxy.ts:3864,
:4532, :5590; port default 55669 at :4536). It terminates a CLI's own OAuth
token, swaps in one from a pooled account, forwards upstream, and relays SSE back.
It exposes three wire surfaces:
| Door | Factory | Upstream |
|---|---|---|
POST /v1/messages | createClaudeProxyRoutes (claudeProxyRoutes.ts:8316) | api.anthropic.com |
POST /v1/chat/completions | createOpenAIProxyRoutes (openaiProxyRoutes.ts:351) | translation engine / Anthropic loopback |
POST /backend-api/codex/responses | createCodexProxyRoutes (codexProxyRoutes.ts:568) | chatgpt.com/backend-api |
Dispatch is by URL path. A CLI is "onboarded" by writing that CLI's own config file so it points at the right door. Three such writers exist, hand-authored, sharing no abstraction:
| CLI | Writer | Restore | Target |
|---|---|---|---|
| Claude Code | setClaudeProxySettings proxy.ts:625-652 | clearClaudeProxySettings :654 | ~/.claude/settings.json (:248) |
| OpenCode | setOpenCodeProxySettings proxy.ts:734-784 | clearOpenCodeProxySettings :786-843 | opencode.json (:722) |
| Codex | setCodexProxySettings proxy.ts:922-994 | clearCodexProxySettings :996 | ~/.codex/config.toml (:857) |
/v1/chat/completions is the important door and nothing is pointed at it.
/v1/chat/completions speaks plain OpenAI Chat Completions and requires no inbound
authentication at all — grep for authorization in openaiProxyRoutes.ts returns
nothing. The OpenCode writer supplies a placeholder apiKey: proxyKey || "neurolink-proxy" (proxy.ts:778) purely because the AI SDK demands a non-empty
string. Any CLI that can be told a base URL and an arbitrary API key lands here with
zero protocol work.
Coverage, verified on this machine
| CLI | Installed | Verdict | Mechanism |
|---|---|---|---|
| Claude Code | yes | live | ANTHROPIC_BASE_URL |
| Codex | yes | live | config.toml base_url + wire_api="responses" |
| OpenCode | 1.3.13 | live | provider.neurolink in opencode.json (fixed: #1366/#1367) |
| Qwen Code | @qwen-code/[email protected] | live | security.auth.baseUrl → /v1/chat/completions |
| Copilot CLI | @github/[email protected] | live | COPILOT_PROVIDER_* via a sourceable env script |
| Hermes Agent | no | easy (unverified on disk) | ANTHROPIC_BASE_URL / OPENAI_BASE_URL |
| Gemini CLI | @google/[email protected] | moderate | GOOGLE_GEMINI_BASE_URL, API-key mode only |
| Cursor | cursor-agent 2026.05.28 | refuted | env vars are dead code — proven by live test |
| Antigravity | 1.107.0 | hard | proprietary Cascade protobuf |
| Grok CLI | no | unconfirmed | not installed |
| Kiro CLI | no | hard | fixed AWS hosts, OAuth device flow |
Near-term: 2 live → 6 — five of those are now live (Claude Code, Codex, OpenCode, Qwen Code, Copilot CLI), with config-writer work only and zero new route modules. The remaining one is Hermes Agent, which the table above marks easy but unverified on disk.
The eleven-CLI roster above is PokeTokenBar's list plus Qwen Code, which this audit added after verifying it directly. It is still not the whole field: SARA tracks Amp, which neither of the others does, while PokeTokenBar tracks Hermes, Kiro, Antigravity and Grok, which SARA does not. The union is 12. Scope coverage against the union, not any single list.
2. Corrections to the v11.2.2 audit
The earlier audit's structural claims hold. Five of its specific claims do not.
2.1 The cost defect is attributed to the wrong route — and the failure mode is different
Claimed: proxyTracer.ts:497 and :826 hard-code calculateCost("anthropic", …),
so Codex traffic to chatgpt.com is costed against Anthropic's price table.
Actually:
- There are four hard-coded
"anthropic"sites, not two:proxyTracer.ts:497,:826,:874(allcalculateCost) and:883(TelemetryService.recordAIRequest("anthropic", …)). codexProxyRoutes.tsnever importsProxyTracerand never callscalculateCost. ItslogRequest()object literal (:322-339) has no token keys at all, and it never parsesusageout of the SSE stream. Codex traffic is not mis-priced — it is entirely unaccounted. Fixing it starts with parsingusage, not with the pricing call.- The hard-coded provider actually mis-prices
/v1/chat/completions, which routes throughModelRouterto any provider. - The failure mode is usually $0, not a wrong number.
findRatesreturnsundefinedwhen nothing matches, andcalculateCostthen returns0(pricing.ts:755-758). Theanthropictable (pricing.ts:26-161) has no_defaultsentinel — the first one is at:406. SofindRates("anthropic", "gpt-4o")→undefined→ $0. Real mis-pricing only happens when a Claude-named model is routed elsewhere (e.g. aclaude-*alias mapped to Vertex Gemini), which prices Gemini traffic at Sonnet rates. - Compounding it:
ProxyTracer.modelisprivate readonly(proxyTracer.ts:248), fixed at construction to the model the client asked for.setModelSubstitution()only writes span attributes. So even with a correct provider string, cost is computed against the requested model, not the served one.
Good news: pricing.ts already ships 18 provider tables including openai with a
"gpt-5.1-codex" entry (:162+). For the /v1/chat/completions path this is a
parameter change, not new pricing data.
2.2 "Nothing identifies the calling CLI at request time" is false — and this is an onboarding hazard
Claimed: detectClientApp() (proxyTracer.ts:226-235) is the only User-Agent
sniff and it only labels trace spans.
Actually: there is a second sniff that drives real request behaviour.
isLikelyClaudeClient() (claudeProxyRoutes.ts:2341-2350) tests
headers["user-agent"]?.startsWith("claude-cli/") (among other signals), and its
result gates:
- which OAuth beta header set is sent —
CLAUDE_CODE_OAUTH_BETASvsNON_CLAUDE_OAUTH_BETAS(:7221-7236); - whether
polyfillOAuthBody()preserves the client's own system-prompt / agent identity blocks verbatim or strips and relocates them; maybeRefreshClaudeSnapshot()(:2398).
This matters directly for CLI #4 and #5. Any non-Claude CLI pointed at
/v1/messages via ANTHROPIC_BASE_URL — Hermes is exactly this case — takes the
NON_CLAUDE_OAUTH_BETAS branch and a different system-prompt path. That is probably
correct behaviour, but it is behaviour, and it must be tested rather than assumed.
2.3 "314,116 requests and no ledger" is overstated
usageStats.ts (1,478 lines, zero occurrences of inputTokens / outputTokens /
totalTokens / cost) is confirmed. But neurolink proxy analyze is a real, wired
command (src/cli/parser.ts:260) and proxyAnalysis.ts:437-527 reads the same
proxy-YYYY-MM-DD.jsonl files requestLogger.ts writes and sums inputTokens,
cacheReadTokens and cacheCreationTokens per window.
The gap is narrower and more specific than "no ledger": outputTokens and cost
are absent from proxyAnalysis.ts entirely, and nothing aggregates the Codex engine
at all (§2.1).
2.4 OpenCode is two bugs, not a one-line fix
The path bug is confirmed and is genuinely one line — but a path-only fix leaves a second, independent bug in place.
getOpenCodeConfigDir()(proxy.ts:711-720) returns~/Library/Application Support/opencodeon darwin. The installed OpenCode 1.3.13 binary embeds the unmodifiedxdg-basedirnpm package (XDG_CONFIG_HOME || join(homedir, ".config")) with no platform branch at all. Empirically:~/.config/opencode/opencode.jsonexists (2,640 bytes, holds a working custom provider thatopencode modelsconfirms is loaded);~/Library/Application Support/opencodedoes not exist. Deleting thedarwinbranch is the whole fix.- The second bug survives that.
setOpenCodeProxySettingsreturnsPromise<void>(:734) and the call sites print✓ Auto-configured OpenCode settingsunconditionally (:3491-3493,:5403-5405).setCodexProxySettingsreturnsPromise<boolean>and its✓is gated on it (:3502,:5413). So even after the path fix, a user without OpenCode installed still gets a success message for work that did not happen.
Origin of the mistake: the OpenCode binary does contain the literal
/Library/Application Support/opencode — as systemManagedConfigDir(), an MDM /
enterprise policy tier at the filesystem root (no $HOME), paired with
/etc/opencode and %ProgramData%\opencode. Someone found that string and read it as
the per-user path.
It is also baked into our own docs. docs/features/opencode-proxy-support.md:67
asserts the macOS path is ~/Library/Application Support/opencode/opencode.json,
while :313-323 of the same file shows resolution code with no darwin branch that
would compute ~/.config/opencode. The doc contradicts itself, and it is titled
"Implemented & Verified".
How it escaped verification: §11's E2E playbook runs the dev proxy with --dev,
which by its own option description performs "no client auto-configuration"
(proxy.ts:3910-3917), and hand-copies a fixture to
/tmp/opencode-proxy-test/opencode.json. The writer is never executed. There is also
zero automated coverage — grep for setOpenCodeProxySettings across test/ returns
nothing; only two unused fixture JSON files mention OpenCode.
2.5 Smaller drifts
| Claim | Correction |
|---|---|
RouteGroup at types/server.ts:395-448 | RouteGroup is :443-458. :395-431 is RouteDefinition. |
Copilot works via OPENAI_API_KEY+OPENAI_BASE_URL | True only with COPILOT_ENABLE_ALT_PROVIDERS=true (app.js:6119). The COPILOT_PROVIDER_* path has no such gate. |
Gemini OAuth is pinned to cloudcode-pa.googleapis.com | Pinned by default, but CODE_ASSIST_ENDPOINT overrides it unconditionally (getBaseUrl(), chunk :309590-309592), documented by Google as dev/test-only. |
Claude writer range 622-651 | Function is 625-652; :623 is PROXY_MANAGED_KEYS, correct. |
| Cursor refutation | Upheld, and strengthened. A live run with CURSOR_LOCAL_AGENT_BASE_URL set returned Authentication required — the env vars had zero effect. Do not build for it. |
3. Touch points: a config-writer-only CLI
This section described eleven edits. It is now two.
The writers moved behind a CliProxyClientConfigurator contract
(src/lib/types/proxyClient.ts), one module per client under
src/cli/proxy-clients/, assembled by registry.ts. The four duplicated
call-site blocks in proxy.ts collapsed into applyAllClients() and
restoreAllClients().
To add a CLI that only needs to be told a base URL:
| # | File | What to do |
|---|---|---|
| 1 | src/cli/proxy-clients/<cli>.ts | Implement CliProxyClientConfigurator: id, displayName, detect(), apply(proxyBaseUrl), restore(proxyBaseUrl). Snapshot the user's prior config before overwriting, and return false from apply when nothing was written. |
| 2 | src/cli/proxy-clients/registry.ts | Add it to PROXY_CLIENT_CONFIGURATORS. |
Plus a test and a doc entry, as for any change. Nothing in proxy.ts is
touched at all.
The one client that needs a shell, not a file
Copilot CLI reads its provider settings from process.env only — app.js
resolves COPILOT_PROVIDER_BASE_URL and siblings directly, and
~/.copilot/config.json (which announces itself as "managed automatically")
carries no provider block. There is no file the proxy can write that Copilot
will read.
Rather than edit a shell profile — which lives outside the proxy's blast radius
and runs on every shell — the configurator writes
~/.neurolink/copilot-env.sh and expects one line in your profile:
[ -f ~/.neurolink/copilot-env.sh ] && . ~/.neurolink/copilot-env.sh
The proxy deletes the script on stop, and the -f guard makes a missing file a
no-op, so no NEW shell picks up a stale export. A shell that already sourced it
keeps the variables for its own lifetime — deleting a file cannot unset
variables in a running process. Run unset COPILOT_PROVIDER_URL COPILOT_PROVIDER_API_KEY (or start a new shell) if you stopped the proxy in a
session that had it loaded.
Note also that Copilot's OPENAI_API_KEY + OPENAI_BASE_URL path works only
with COPILOT_ENABLE_ALT_PROVIDERS=true; the COPILOT_PROVIDER_* path has no
such gate, which is why it is the one used.
What the contract enforces
Three defects came from the writers disagreeing with each other. The contract makes each one unrepresentable:
detect()is required, so a writer cannot create config for a CLI that was never installed — the bug Claude Code shipped with.apply()returnsboolean, so a caller cannot print✓for work that did not happen — the bug OpenCode shipped with.- The base-URL suffix belongs to the client (
/v1for OpenAI-compatible clients, bare origin for Codex), so no call site has to remember it.
Behaviour the loops preserve
applyAllClients wraps each client independently: one failing can neither stop
the others nor abort shutdown. The daemon-start path reports failures at debug
level and the setup wizard prints a visible warning — deliberately different,
and both preserved. The proxy guard path still keys its cleared flag off
Claude Code specifically.
4. Touch points: a new-wire-format CLI
For Gemini CLI, which needs Google's generateContent shape and usageMetadata
translation. Everything in §3 plus:
| # | File | Lines | What to do |
|---|---|---|---|
| A | src/lib/server/routes/<cli>ProxyRoutes.ts | new | create<Cli>ProxyRoutes(basePath = ""): RouteGroup. Model it on codexProxyRoutes.ts (self-contained) rather than claudeProxyRoutes.ts (8,300+ lines). |
| B | src/cli/commands/proxy.ts | :1978-1983 | Add the dynamic import alongside the other three. |
| C | src/cli/commands/proxy.ts | :2121-2145 | Add to the hand-assembled allProxyRoutes array. Mounting at :2148-2150 is generic and needs no change. |
| D | src/lib/server/routes/index.ts | :11-29, :36-66 | The second seam. Import, re-export, and add to createAllRoutes — otherwise the door is CLI-only, as Codex is today. |
| E | src/lib/types/server.ts | :1401-1409 | Add a CreateRoutesOptions flag if the door should be independently toggleable. |
| F | docs/guides/server-adapters/api-reference.md | :657-660, :680-686 | Now documented — the proxy/claudeProxy/openaiProxy flags and all three proxy factories are listed. Keep it current when a door is added, or the next provider repeats the drift that made this row necessary. |
| G | src/lib/proxy/modelRouter.ts | resolve(), :50-65 | Only if model-name prefixes need extending (gemini- → vertex, claude- → anthropic). Codex bypasses ModelRouter entirely. |
| H | <cli>OAuth.ts + <cli>AccountUsage.ts | new | Only if the CLI brings its own subscription pool. See §6. |
Two things you do not need to touch, contrary to reasonable expectation:
src/lib/proxy/proxyDispatcher.ts— installs one global undici keep-aliveAgentprocess-wide. No provider or path names appear in the file.ProviderTransportCoordinator— provider-agnostic, but instantiated only insideclaudeProxyRoutes.ts:249. There is no global instance to register into; a new door may optionally construct its own.
A naming trap: there are two unrelated ModelRouter classes.
src/lib/proxy/modelRouter.ts is the HTTP one; src/lib/utils/modelRouter.ts is an
unrelated task-classification router. Grepping without checking the import path finds
the wrong one.
5. What the existing machinery gives you — and where it stops
What RouteGroup genuinely provides
RouteGroup (types/server.ts:443-458) is { prefix, routes: RouteDefinition[], middleware?, auth?, roles? }, and RouteDefinition (:395-431) carries method,
path, handler, plus optional schemas, auth, rate limits and streaming config.
The real payoff is at the mount: proxy.ts:2148-2150 iterates
allProxyRoutes and calls app[method](route.path, …) generically, wrapping every
route in the same draining check, request-metadata tracking and error envelope. A
new door inherits all of that for free simply by being in the array. That is a
genuine, load-bearing abstraction.
Where it stops — "registry" overstates it
- There is no plugin loader, manifest, or dynamic registry for route groups. The
array at
proxy.ts:2121-2145is hand-edited, andcreateAllRoutesinroutes/index.tsis a second hand-edited list. The two are not derived from each other, which is exactly why Codex exists in one and not the other. - The config writers had no abstraction — since fixed; kept here as the
finding that motivated the fix. They now sit behind a
CliProxyClientConfiguratorcontract with one module per client undersrc/cli/proxy-clients/, assembled byregistry.ts(see the section above). What follows is what the audit found at the time, which is why the strategies still differ per client: Claude and OpenCode do JSON round-trips with an inline snapshot key (__proxy_original_env,__proxy_original_neurolink); Codex does regex-driven TOML text manipulation with a marker-delimited block and a separate sidecar snapshot at~/.neurolink/codex-proxy-snapshot.json. At the time this was a notable departure from the codebase's own stated Factory+Registry convention for providers, processors, chunkers and rerankers. - The SDK seam is untested by this repo's own CLI.
neurolink proxynever callscreateAllRoutes;neurolink servercalls it but never passes any proxy flag (grep -i proxy src/cli/commands/server.ts→ nothing). Theproxy/claudeProxy/openaiProxyoptions exist solely for external SDK consumers, and no code in this repo exercises them.
The single highest-leverage refactor was extracting a ClientConfigurator
registry — { id, detect(), apply(baseUrl), restore(baseUrl) } — so the four
duplicated call-site blocks collapse into one loop. This has since been done:
the writers live under src/cli/proxy-clients/ behind
CliProxyClientConfigurator, and the call sites are applyAllClients() /
restoreAllClients(). It turned "eleven edits across one huge file" into "one
new file plus one registry line," and it is what would have prevented both
halves of the OpenCode bug. Do not re-extract it; onboarding a new CLI now
means adding a module and a registry line.
6. Position: the account namespace is not a prerequisite
The earlier audit's headline recommendation was to generalise the account-key namespace before CLI #5 and #6. I disagree, and the code says so.
The facts are right
accountSelection.ts is 52 lines and entirely Anthropic-shaped —
LEGACY_ANTHROPIC_ACCOUNT_KEY, ENV_ANTHROPIC_ACCOUNT_KEY,
normalizeAnthropicAccountKey(), anthropicAccountKeysEqual(), all normalising to an
anthropic: prefix. Codex runs a parallel namespace via CODEX_ACCOUNT_PREFIX = "codex:" (codexAccountUsage.ts:29). codexProxyRoutes.ts imports nothing from
accountSelection.ts — verified, zero hits. Pooling really is written twice.
But it does not block a config-writer CLI — at all
Trace an inbound /v1/chat/completions request:
- If
ModelRouterresolves the model toanthropic, the handler forwards it by loopback to the proxy's own/v1/messages(openaiProxyRoutes.ts:396, bridge at:141-148). Its comment is explicit: this "reuses the full Claude passthrough path (OAuth account rotation, retry, SSE interception, etc.)". The request rides the existing Anthropic pool, unchanged. - Otherwise it falls through to the translation engine and
ctx.neurolink.stream()— normal SDK credential resolution, no account pool involved at all.
Either way, accountSelection.ts, accountCooldown.ts, accountQuota.ts and the
token store are untouched. Reinforcing this: account selection has no concept of
caller identity. clientApp exists but is telemetry-only. Pooling is scoped by
provider key prefix, never by which CLI called. A new caller is invisible to that
subsystem by construction, not by luck.
So: Copilot CLI, Hermes and a fixed OpenCode need zero namespace work. Sequencing a refactor ahead of them would be pure delay.
And for a CLI that does bring its own pool
Copy the Codex pattern. The repo's own CLAUDE.md already prescribes it — "a new
<provider>OAuth.ts, a <provider>AccountUsage.ts quota parser, and a
<provider>ProxyRoutes.ts engine — do not modify the Anthropic hot path" — and the
code supports it cheaply: accountCooldown.ts (162 lines) and the storage half of
accountQuota.ts (:500-541) are already provider-agnostic Record<string, T> keyed
by an opaque accountKey, with no prefix branching. tokenStore.listByPrefix() is
generic. Cost: roughly three new files, ~800–1,000 lines, zero risk to the Anthropic
hot path.
Generalising the namespace first would also mean confronting the migration
CLAUDE.md explicitly defers: Anthropic quota is keyed by bare label and persisted
that way in every user's ~/.neurolink/account-quotas.json, so re-keying means either
discarding every stored snapshot or shipping a tolerate-both migration for a release.
Recommended sequence:
- Fix OpenCode (path + boolean return + the doc). Smallest possible change, restores a feature users already believe they have.
- Add Copilot CLI. Env-var only, existing door, no new route module.
- Add Qwen Code and Hermes. Qwen is the same shape as Copilot (
OPENAI_BASE_URL, existing door). Hermes needs theisLikelyClaudeClientbranch (§2.2) tested deliberately, since it lands on/v1/messageswithout aclaude-cli/User-Agent. - Then extract the
ClientConfiguratorregistry, with four real implementations to generalise from rather than three. - Only when a CLI with its own subscription pool arrives, copy the Codex pattern — and revisit the namespace only if a fourth pool appears after that.
7. The observability / routing split
These are independent capabilities with different ceilings, and treating them as one thing has hidden how cheap the second is.
Routing tops out at 5–6 of 10. It depends on vendors shipping base-URL overrides nobody here controls. Kiro and Antigravity are structurally closed. Cursor looked like the cleanest win in the matrix and turned out to be dead code.
Reading each CLI's own local logs reaches 10 of 10. No auth, no vendor cooperation, no proxy in the request path. It works for CLIs that can never be routed, and it recovers months of history the proxy can never see. It is also an independent source of truth — it would have caught §2.1 immediately.
Prior art — start with SARA, not PokeTokenBar
The file-walking layer already exists in TypeScript, one repo over. SARA
(the sara project, a sibling checkout) ships an eight-CLI session reader registry
at packages/agents/src/sessions/:
registry.ts:171-213— dedicated lazy-factory readers for Claude, Codex, OpenCode and Gemini, registered viaregisterReader(id, async () => …)with dynamic imports — the same shape asproviderRegistry.ts.registry.ts:228— a genericProviderSpecloop covering Cursor, Amp, Qwen and Copilot, with the specs injsonlReaders.ts:94-200.- Per-CLI on-disk paths already resolved:
~/.cursor/projects/…,~/.local/share/amp/threads,~/.qwen/projects/…,~/.copilot/session-state. registry.ts:252keepsconst VERIFIED = ["claude", "codex"]and exposes it asverifiedon each descriptor (:272) — an honesty marker separating readers confirmed against real data from ones written to spec. Worth copying that idea regardless of what else we take.
What SARA does not do is extract usage. Only claudeReader.ts touches tokens at
all (18 hits for token/usage/cost; codexReader.ts, geminiReader.ts,
opencodeReader.ts and jsonlReaders.ts each have zero) — and it does so only to
compute current context-window occupancy, deliberately non-cumulative so it
self-corrects after compaction (claudeReader.ts:52-69). The other readers parse
transcript parts and stop.
So the split is: take file-walking, path resolution, provider detection and the registry shape from SARA — same language, already written. Take the token extraction and aggregation semantics from PokeTokenBar, which is the part SARA lacks and the part that is actually hard.
The poketokenbar project is ~3,600 lines of Swift covering
these ten formats: LocalUsageReader.swift (1,264), LocalUsageCache.swift
(373), LocalAdditionalUsageProvider.swift (1,077),
LocalAntigravityUsageReader.swift (542), LocalUsageProvider.swift (171).
A TypeScript port is smaller than a transliteration, because Node needs neither
Swift's actor/NSLock concurrency scaffolding nor BinaryLocator.swift (230 lines
that exist only so a Finder-launched .app can see shell exports).
Per-format estimates: Claude ~120 lines, Gemini ~80, Hermes ~70, OpenCode ~150, Grok ~180, Cursor ~180 + ~150 shared incremental-SQLite scaffolding. Risk concentrates in two: Codex (~600 lines — a session-DAG/prefix-match reconciliation algorithm, not a file parse) and Antigravity (a mini protobuf codec with no schema). Budget and review those separately from the other eight.
Three things a port must decide up front:
- A tri-state cost field. Claude/Gemini/Grok/OpenCode/Hermes report real or
well-modelled cost; Cursor/Copilot/Antigravity are flat-rate and report none; Kiro's
numbers are a byte heuristic and must never be shown with the same confidence.
This maps onto the existing
jsonRepaired/jsonTruncated"exact or salvaged" convention inCLAUDE.mdrule 3 — reuse it rather than invent one. - Dedup is per-format, not generic. Claude/Grok/OpenCode use content-hash
keep-max; Gemini needs last-write-wins because
message_updaterecords intentionally supersede; Cursor/Copilot need SQLite rowid high-water marks plus adidResetfull-rescan fallback for post-VACUUM. Codex needs the DAG resolution. - SQLite is a real dependency decision. Six of ten formats need it.
package.jsonlistssqlite3inonlyBuiltDependenciesbut the lockfile shows it is not actually installed — the only SQLite reference isbetter-sqlite3as an unmet optional peer of@juspay/hippocampus. Given this repo's recent CI commits specifically about avoiding unnecessary native binaries (6f839235,a3490501),node:sqlite(Node 22.5+) deserves serious consideration over a native module.
Only 4 of 10 formats are testable against real data on this machine today: Claude
(16,830 .jsonl files), Codex (122 rollouts), Gemini (9 files), OpenCode (a 305 MB
opencode.db). PokeTokenBar's own per-format test fixtures are worth reusing for the
other six. Note also that ~/.copilot/session-store.db on this machine has no
assistant_usage_events table — PokeTokenBar's Copilot reader targets a schema that
has since changed, so "the file exists" is not evidence a format is portable.
Where it belongs
Not on this branch. feat/cli-support is about routing — config writers and route
modules. Local-log reading shares no code with any of it: it never touches
proxy.ts, routes/, or the account pools, and it is explicitly not a modification
of accountUsage.ts (which polls providers' remote usage APIs for accounts in
NeuroLink's own pool, covering only Anthropic and Codex).
It should be its own branch and its own subsystem — a new src/lib/localUsage/ with
one reader per CLI behind a lazy dynamic-import registry mirroring
providerRegistry.ts, and types in src/lib/types/localUsage.ts per rule 2. One
wrinkle worth designing for early: PokeTokenBar is a long-running menu-bar app, so it
keeps Kiro's and Codex's cross-scan merge state in memory. A CLI invocation has no
equivalent, so that state must be persisted to disk.
8. Defects
Eleven filed on juspay/neurolink. Eight are fixed and released (v11.13.0 and
v11.14.0, via #1399-#1402); three remain open.
| # | Defect | Location | Issue | Status |
|---|---|---|---|---|
| 1a | OpenCode writer targets a path OpenCode never reads on macOS | proxy.ts:711-720 | #1366 | fixed |
| 1b | ✓ Auto-configured OpenCode prints unconditionally — survives 1a's fix | proxy.ts:734, :3491, :5403 | #1367 | fixed |
| 1c | The same wrong path is asserted in our own docs, which contradict themselves | opencode-proxy-support.md:67 vs :313-323 | #1366 | fixed |
| 1d | Zero test coverage for the writers; §11's playbook uses --dev, which skips them | test/ | #1368 | fixed — all three writers covered |
| 2a | Codex engine has no token/cost accounting at all — never parses usage | codexProxyRoutes.ts:322-339 | #1369 | fixed |
| 2b | Provider hard-coded "anthropic" at four sites | proxyTracer.ts:497, :826, :874, :883 | #1370 | fixed |
| 2c | Cost computed against the requested model, not the served one | proxyTracer.ts:248 | #1370 | fixed |
| 3 | proxyAnalysis.ts aggregates no outputTokens and no cost | proxyAnalysis.ts:437-527 | #1371 | fixed |
| 4 | SDK server-adapter docs omit all three proxy factories and the proxy flags | api-reference.md:657-686 | #1372 | docs written, issue still open |
| 5 | Codex CLI model discovery 404s — proxy has /responses but not /backend-api/codex/models | codexProxyRoutes.ts:615 | #1383 | open |
| 6 | Client config writers: no locking or atomic writes between apply and restore | src/cli/proxy-clients/ | #1384 | open |
Security note, not a defect: the proxy takes no inbound authentication on any door.
That is defensible at the 127.0.0.1 default, but --host accepts 0.0.0.0, which
would expose pooled OAuth subscription tokens to the network. Any new writer must not
assume an auth layer exists.
9. Repo conventions this work must follow
CLAUDE.mdrules 2, 7–15 are ESLint-enforced. Most relevant here: types go insrc/lib/types/only (rule 2);type, neverinterface(rule 7); barrel-only internal type imports (rule 13); no double assertions (rule 14).- Rule 15 — tests are end-to-end only. Import from
dist/index.js, or drivenode dist/cli/index.js. Never mixdistandsrcin one suite. Bothtest/continuous-test-suite-proxy.tsand-codex.tsare on thee2e-tests-onlyallowlist ineslint.config.js:262-291with written justifications, and the codex suite deliberately keeps its last two cases driving the built CLI. A new proxy suite should follow that shape, and adding to the allow list is a review decision. - Keep payloads out of assertion messages.
defineSuitedowngrades a failure to SKIP when the message matchesisExpectedProviderError(), so quoting a payload containing502orECONNREFUSEDturns a real failure green. Sanity-check any new suite by breaking one assertion on purpose. - CI has six jobs, not two.
CLAUDE.md's note is incomplete: alongsidetestandprovider-safety-netthere arebuild-check,proxy-performance("Proxy Performance Gates", runspnpm run proxy:performanceagainstproxyLifecycle.ts/proxyActivity.ts),quality-gate(validate, commit-message validation,tsc --noEmit --strict) andsemantic-release-validation.provider-safety-netalso runs a third suite the pre-push hook does not —test:error-classifier-contract. A new proxy engine can tripproxy-performance. - Docs PRs are gated.
docs-pr-validation.ymltriggers ondocs/**and runs a Docusaurus typecheck and build withoutcontinue-on-error. Frontmatter and link checks are soft. - Decide the orphaning question deliberately.
codex-proxy-support.mdandopencode-proxy-support.mdhave no frontmatter, are absent fromdocs-site/scripts/sync-docs.ts, and are unlinked fromdocs/features/index.md. Following that precedent exactly means a new doc is not published and not discoverable. This document follows the precedent; that should be revisited. - Branch naming: no ticket numbers here. The global
<type>/BZ-<n>-<desc>convention is Juspay-internal Bitbucket. This OSS repo uses plain<type>/<kebab-description>—feat/cli-support,fix/adjust-body-after-400. Conventional commits; never commit directly torelease. - ⚠️
docs/development/contributing.mdis stale — it teachesinterfacetypes (:161) and vitestdescribe/it(:169-172), both of which contradict enforced rules 7 and 15. Do not cite it as authoritative.
Appendix: verification method
Eight independent auditors, one per claim cluster, each required to cite file:line
actually read and to report corrections rather than agree. Findings that could be
checked against this machine were checked against installed binaries and live config,
not documentation — including a live cursor-agent run that confirmed its env vars are
inert, and a opencode models run that confirmed which config file is really loaded.
Claims that did not survive are listed in §2 rather than quietly dropped.