Getting a Nous Portal Provider Merged into CodexBar
Fig. 01 — Getting a Nous Portal Provider Merged into CodexBar
CodexBar is a macOS menu bar app with 21k+ GitHub stars that shows how much of your AI coding subscription you have used: Codex, Claude, Cursor, and about forty other providers. I run Hermes Agent on a Nous Portal subscription, and Nous was the one thing missing from my menu bar. This is the story of the sixteen days it took to change that.
Add Nous Portal provider - steipete/CodexBar #3376 Merged 18 Sep 2026 as 88aab2b. 25 files, +1,444 / -80. The Problem Nobody Had Solved
Someone had asked for this before me. Issue #1367 sat open for months, and the maintainer had given it a fair hearing: three research comments, an explicit acceptance bar, and finally, on 3 August, a close.
"Closing until Nous/Hermes exposes a documented usage or credits API. Reopen with the contract and we'll treat it as a normal provider addition."
The blocker was real. Nous Portal API keys only authenticate inference calls. There was no documented endpoint that would tell you your credit balance. Everyone who had looked at it, including the maintainer, had concluded the same thing.
Finding the Contract
What had changed since August was Hermes Agent itself. Its own /credits command, shipped in v2026.6.19, was already reading a balance from somewhere. I read the source and found it: GET /api/oauth/account, authenticated with the OAuth access token that the Hermes device-code login mints into ~/.hermes/auth.json.
That gave me a first-party contract, even if an undocumented one. It also gave me a hard constraint that shaped everything that followed.
The Refresh Token Trap
Nous refresh tokens are single-use. The portal rotates the token on every refresh and revokes the whole session if it ever sees an old one replayed. If CodexBar refreshed the token behind Hermes's back, it would log Hermes out.
So the provider had to be strictly read-only: read the current access token, send it to exactly one origin, and when it expires, tell the user to run hermes rather than trying to be clever. This "never refresh, never write" rule became the spine of the design and, later, the thing the maintainer cared most about.
Ten Review Cycles in Sixteen Days
CodexBar runs an automated reviewer called ClawSweeper on every revision. It reviews like a strict senior engineer and does not get tired. My first push, at 04:53 on 2 September, was reviewed at 04:57. Here is what it found across ten cycles, and how each one was closed.
| Cycle | Finding | Fix |
|---|---|---|
| 1 | P1: auth-file portal_base_url was trusted blindly, so a tampered file could exfiltrate the bearer token | Only hosts under nousresearch.com are honoured; anything else falls back to the default portal and is logged as a rejected host |
| 1 | P2: an expired NOUS_PORTAL_ACCESS_TOKEN env var still produced a network request | Decode the JWT exp claim and refuse before building the request |
| 2 | P2: HERMES_HOME was a hint, not a boundary; a missing custom profile silently fell back to ~/.hermes | HERMES_HOME became the exclusive credential root, with three tests for missing, invalid and expired custom profiles |
| 3 | P1: a plain-HTTP override to loopback would send the token in cleartext | Refuse every non-HTTPS override, loopback included |
| 3 | P3: docs described a plan row the code did not render | Aligned docs with implementation |
| 4 | None. Rated "needs maintainer review" | Waiting |
| 5, 6 | P1: my merge from main had dropped ten release-note bullets and an entire release section from CHANGELOG.md | See below |
| 7 | None. Rated "platinum hermit, ready for maintainer look" | Waiting |
| 8, 9 | Maintainer's rewrite: "needs real behavior proof" | See below |
| 10 | Merged |
Each fix landed with a request-level test. The final security test asserts that the Authorization header only ever reaches https://portal.nousresearch.com, even when an http://127.0.0.1 override or a stored.example auth-file host is supplied.
The Merge That Deleted the Changelog
Cycle five taught me something about merge conflicts. When I merged main into the branch, the changelog conflict was resolved in favour of my branch. That looked fine in the diff view. It was not. It had silently dropped every Unreleased bullet from main plus the complete 0.56.4 release section, and the release pipeline consumes that file.
The fix was to stop hand-reconciling bullets. I took main's changelog verbatim, re-added the single Nous entry under a new Added heading, and proved the result with two numbers:
$ git diff origin/main --stat -- CHANGELOG.md
CHANGELOG.md | 3 +++
1 file changed, 3 insertions(+)
$ git diff origin/main -- CHANGELOG.md | grep -c "^-[^-]"
0Zero deletions. The bot cleared the finding within ten minutes.
Proof, Not Screenshots
CodexBar's review bar asks for "real behavior proof", and the maintainer was explicit that synthetic card renders would not substitute for it. Every revision shipped with redacted verbose CLI traces from my real Hermes login: a trusted-origin success, a tampered stored.example host being rejected while the token still went only to the real portal, an expired token refused before any request, and an empty custom profile refusing to borrow the default profile's login.
Then the Maintainer Rewrote It
After two weeks of quiet, the PR head changed under me. Peter Steinberger, the maintainer, had force-pushed to my branch: two commits authored by him, replacing my eight. The Swift HTTP fetcher and parser were gone. In their place, a bundled TypeScript plugin that runs in QuickJS on macOS and Linux, with Swift reduced to discovering the Hermes credential and registering the provider.
He also found a real bug in my version. I had defaulted missing money fields to zero, so a response carrying only a monthly grant would render as "100% used, $0 top-up". His plugin leaves missing amounts unavailable and only draws a meter when both grant and remaining balance are known.
I could have felt territorial about this. I did not. The credential model I had fought ten review cycles for survived intact: read-only, one origin, HTTPS only, HERMES_HOME exclusive, expired tokens refused before a request. He kept my name on the commit. And the plugin architecture was straightforwardly better for a codebase that has to maintain forty providers.
The Last Gate
His rewrite had been verified with fixtures only, because he has no Nous account. He asked for one thing: a real-account check of his exact head, 64d41c6, with tokens, identity, full payloads and private amounts kept out of the public report.
I built his head, ran it against my live login, and compared every rendered field against the same /api/oauth/account response fetched moments earlier by Hermes's own client. Grant, remaining, percentage, reset date, top-up balance, total usable, plan: all matched to the cent. Then the negative cases:
Expired token in auth.json
-> "Nous Portal access token in <path>/auth.json has expired.
Run `hermes` so Hermes Agent refreshes it." (exit 1)
Expired NOUS_PORTAL_ACCESS_TOKEN
-> "NOUS_PORTAL_ACCESS_TOKEN has expired. Export a fresh token
or unset it to use the Hermes Agent login." (exit 1)
Empty custom HERMES_HOME while ~/.hermes holds a valid login
-> nous.api (api) unavailable (default profile NOT borrowed)The subtle one was proving that no request is built before the expiry check. URLSession ignores proxy environment variables, so a sinkhole proxy told me nothing. Instead I used the app's own NOUS_PORTAL_BASE_URL override pointed at an unresolvable HTTPS host. A valid token produced a DNS error, proving a request was attempted. Both expired cases produced the expiry message instead, proving none was.
Finally, SHA-256 and mtime of both Hermes auth files, identical before and after every run. No refresh, no write.
I posted a concise report pinned to the SHA. Six hours later, it was merged.
What I Took Away
- Read the first-party client before declaring an API missing. The contract was sitting in Hermes's own source the whole time the issue was closed as blocked.
- A merge that looks clean can still destroy things. Check the diff against the base for deletions you did not intend, especially in files a release pipeline consumes.
- Redacted proof beats confident prose. Every finding closed faster when it came with a trace.
- Being rewritten by the maintainer is a good outcome. The design constraints survived, the code got better, and the feature shipped.
steipete/CodexBar macOS menu bar app for AI coding subscription usage.