Skip to content

Add Nous Portal provider - #3376

Open
asispan wants to merge 8 commits into
steipete:mainfrom
asispan:feat/nous-portal-provider
Open

Add Nous Portal provider#3376
asispan wants to merge 8 commits into
steipete:mainfrom
asispan:feat/nous-portal-provider

Conversation

@asispan

@asispan asispan commented Sep 2, 2026

Copy link
Copy Markdown

Adds a disabled-by-default Nous Portal provider showing monthly subscription credits, cycle reset, plan (plan row shows the plan name only), and the purchased (top-up) credit balance in the Credits section. Refs #1367.

How it authenticates

Nous Portal's account and billing endpoints only accept the OAuth access token minted by the Hermes Agent device-code login (API keys are inference-only, per https://portal.nousresearch.com/api-docs). CodexBar reuses that token from ~/.hermes/auth.json (or $HERMES_HOME/auth.json, or ~/.hermes/shared/nous_auth.json) and never calls the refresh endpoint: Nous refresh tokens are single-use and the portal revokes the whole session on reuse, so a second refresher would log Hermes out. An expired token yields a "run hermes" error instead.

One request per refresh: GET {portal}/api/oauth/account.

Token destination boundary (addresses the P1/P2 findings)

The bearer token is sent to exactly one origin, resolved in this order:

  1. Explicit NOUS_PORTAL_BASE_URL / HERMES_PORTAL_BASE_URL (operator-set; HTTPS only, plain HTTP is refused for every host including loopback and the default portal is used instead).
  2. The stored portal_base_url from the Hermes auth file, only if its host is nousresearch.com or a subdomain.
  3. https://portal.nousresearch.com.

A stored host outside that policy is never used: it is replaced by the default portal, logged as a warning, and reported as rejectedStoredHost=<host> in the verbose trace. Expired tokens from either the auth file or NOUS_PORTAL_ACCESS_TOKEN are rejected before any request is created (NousUsageError.sessionExpired / .environmentTokenExpired).

Credential root (addresses the HERMES_HOME finding)

HERMES_HOME is the exclusive credential root. When it is set, only $HERMES_HOME/auth.json and $HERMES_HOME/shared/nous_auth.json are consulted; ~/.hermes is never appended as a fallback. A missing, invalid, or expired custom profile therefore reports its own error (missingCredentials, authFileInvalid(path), sessionExpired(path)) instead of silently using another profile's token. Tests cover all three cases.

Real behavior proof (redacted CLI traces)

A. Real Hermes login, trusted origin

$ swift run CodexBarCLI usage --provider nous --verbose
info com.steipete.codexbar.nous-usage: Nous Portal account request → portal.nousresearch.com (source: hermes)
  - nous.api (api) available
== Nous Portal (api) ==
Monthly credits: 0% left [------------]
Resets in 9d 23h
Subscription credits: $0.00 of $220.00 left
Renews: Sep 12, 2026 at 9:59 AM
Top-up credits: $19.35
Total usable: $19.35
Plan: Ultra
Note: portal=portal.nousresearch.com credential=hermes

B. Same real login, auth file copied to a temp HERMES_HOME with portal_base_url tampered to https://stored.example

$ HERMES_HOME=/tmp/hermes-tampered swift run CodexBarCLI usage --provider nous --verbose
info com.steipete.codexbar.nous-usage: Nous Portal account request → portal.nousresearch.com (source: hermes)
warning com.steipete.codexbar.nous-usage: Ignored untrusted stored portal_base_url host stored.example; using portal.nousresearch.com
  - nous.api (api) available
== Nous Portal (api) ==
Monthly credits: 0% left [------------]
...
Note: portal=portal.nousresearch.com credential=hermes rejectedStoredHost=stored.example

The token reached portal.nousresearch.com only; stored.example received nothing.

C. Expired NOUS_PORTAL_ACCESS_TOKEN (JWT with exp in 2000)

$ NOUS_PORTAL_ACCESS_TOKEN=<expired jwt> swift run CodexBarCLI usage --provider nous --verbose
  - nous.api (api) available error=NOUS_PORTAL_ACCESS_TOKEN has expired. Export a fresh token or unset it to use the Hermes Agent login.
Error: NOUS_PORTAL_ACCESS_TOKEN has expired. Export a fresh token or unset it to use the Hermes Agent login.

No network request is made in this case.

D. Explicit HERMES_HOME pointing at an empty custom profile, while the default ~/.hermes login is valid

$ HERMES_HOME=/path/to/empty-profile swift run CodexBarCLI usage --provider nous --verbose
  - nous.api (api) unavailable
Error: No available fetch strategy for nous.

The default profile's token was not used.

Tests

swift test --filter 'Nous|ProviderArchitectureGatekeeper' (67 tests) passes, including a request-level test that the Authorization header only ever targets https://portal.nousresearch.com when an http://127.0.0.1 override or a stored.example auth-file host is supplied, refusal of every HTTP override (loopback included), exclusive HERMES_HOME resolution (missing, invalid, expired custom profile), trusted-host acceptance (portal.nousresearch.com, preview.portal.nousresearch.com), rejection of stored.example and the look-alike nousresearch.com.evil.example, env-override precedence, expired env/auth-file tokens, auth-file shape parsing, payload mapping, and the architecture gatekeeper.

Docs: docs/nous.md, docs/providers.md, changelog.

🤖 Generated with Claude Code

@clawsweeper

clawsweeper Bot commented Sep 2, 2026

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added P1 Urgent regression or broken agent/channel workflow affecting real users now. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 2, 2026
@clawsweeper

clawsweeper Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex review: blocked before merge. Reviewed September 3, 2026, 10:58 PM ET / September 4, 2026, 02:58 UTC.

ClawSweeper review

What this changes

The PR adds a disabled-by-default Nous Portal provider that reuses a Hermes OAuth access token to show subscription credits, renewal timing, plan, and purchased-credit balance in CodexBar.

Merge readiness

Blocked before merge - 3 items remain

Keep this PR open for maintainer sign-off: the prior changelog finding is fixed, the implementation and real CLI evidence are strong, but the new Hermes OAuth/file-format contract needs an explicit product and maintenance owner.

Priority: P2
Reviewed head: 6b655b45573efe0493c61b691921f03362ce8603
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A well-covered provider implementation with convincing terminal proof; explicit ownership of the external auth contract is the remaining merge decision.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The PR body contains redacted after-fix CodexBarCLI usage --provider nous --verbose traces from a real Hermes setup, including trusted-host success and observed recovery for tampered stored-host, expired-token, and explicit-profile cases; these exercise the added credential resolver and account fetch path.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body contains redacted after-fix CodexBarCLI usage --provider nous --verbose traces from a real Hermes setup, including trusted-host success and observed recovery for tampered stored-host, expired-token, and explicit-profile cases; these exercise the added credential resolver and account fetch path.
Evidence reviewed 6 items Provider integration: The descriptor registers a disabled-by-default Nous provider with the shared API fetch pipeline and credits support.
Credential and request boundary: The implementation reads only an environment token or Hermes files, rejects expired credentials, refuses untrusted stored hosts, and attaches the bearer token to the account request; the shared client blocks cross-origin and non-HTTPS redirects.
Boundary coverage: Focused tests cover expiry, exclusive HERMES_HOME resolution, stored-host rejection, and final outgoing request destination and Authorization header.
Findings None None.
Security None None.

How this fits together

CodexBar registers provider descriptors for the menu bar and CLI, then resolves provider credentials and fetches usage snapshots for display. This adds Nous Portal as a first-party provider using a Hermes-managed OAuth credential and the portal account endpoint.

flowchart LR
    A[Hermes login or environment token] --> B[Credential resolution]
    B --> C[Trusted portal selection]
    C --> D[Nous account request]
    D --> E[Usage and credit snapshots]
    E --> F[CodexBar menu bar and CLI]
Loading

Decision needed

Question Recommendation
Should CodexBar take ongoing ownership of the Hermes OAuth credential-file and Nous account API contract for this provider? Sponsor and merge: Approve CodexBar maintaining this disabled-by-default provider and its documented Hermes/Nous compatibility boundary.

Why: The implementation follows the provider architecture and includes direct proof, but VISION.md requires sign-off for provider-auth and unclear privacy/maintenance contracts.

Before merge

  • Resolve merge risk (P1) - Merging makes CodexBar responsible for compatibility with Hermes credential-file formats and the Nous account API; a third-party contract change can leave affected users unable to refresh this provider.
  • Complete next step (P2) - Obtain maintainer sign-off for ownership of the Hermes OAuth credential-file and Nous account API contract before merge.
  • Resolve maintainer decision - Resolve the maintainer decision shown above before merge.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Change surface 17 files, +1,379 / -4 lines The PR adds one complete provider across registration, app presentation, credential handling, tests, and docs.
Implementation versus coverage production +715, tests +565, docs/changelog +99 The provider’s credential and response logic is accompanied by substantial focused parser and request-boundary coverage.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #1367
Summary: This PR is the unmerged candidate implementation of the Nous/Hermes provider request tracked by the referenced issue.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Sponsor the provider contract (recommended)
    Approve ongoing ownership of Hermes credential parsing and the Nous account endpoint before merging this otherwise complete provider addition.
  2. Pause first-party adoption
    Keep the integration out of the release if maintaining a third-party OAuth-file and billing-response contract is outside CodexBar’s intended scope.

Technical review

Best possible solution:

Obtain explicit sponsorship for the Hermes and Nous contract, then land the disabled-by-default provider with its existing credential-boundary tests and documentation.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a feature request; the PR body nevertheless provides high-confidence redacted CLI traces for the implemented provider and its credential-boundary cases.

Is this the best way to solve the issue?

Yes, conditionally: the descriptor and shared transport approach fit current provider patterns, but maintainers must first accept ownership of the Hermes and Nous contract.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 476573dddc9f.

Labels

Label justifications:

  • P2: This is a disabled-by-default provider addition with bounded user impact, pending routine maintainer sponsorship.
  • merge-risk: 🚨 auth-provider: The PR adds a new OAuth-token acquisition and bearer-request path based on an external Hermes credential contract.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body contains redacted after-fix CodexBarCLI usage --provider nous --verbose traces from a real Hermes setup, including trusted-host success and observed recovery for tampered stored-host, expired-token, and explicit-profile cases; these exercise the added credential resolver and account fetch path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body contains redacted after-fix CodexBarCLI usage --provider nous --verbose traces from a real Hermes setup, including trusted-host success and observed recovery for tampered stored-host, expired-token, and explicit-profile cases; these exercise the added credential resolver and account fetch path.

Evidence

What I checked:

  • Provider integration: The descriptor registers a disabled-by-default Nous provider with the shared API fetch pipeline and credits support. (Sources/CodexBarCore/Providers/Nous/NousProviderDescriptor.swift:34, 6b655b45573e)
  • Credential and request boundary: The implementation reads only an environment token or Hermes files, rejects expired credentials, refuses untrusted stored hosts, and attaches the bearer token to the account request; the shared client blocks cross-origin and non-HTTPS redirects. (Sources/CodexBarCore/Providers/Nous/NousSettingsReader.swift:69, 6b655b45573e)
  • Boundary coverage: Focused tests cover expiry, exclusive HERMES_HOME resolution, stored-host rejection, and final outgoing request destination and Authorization header. (Tests/CodexBarTests/NousUsageFetcherTests.swift:159, 6b655b45573e)
  • Prior finding resolved: The current changelog adds the Nous entry while preserving the Unreleased Fixed entries and v0.56.4 notes; only CHANGELOG.md differs from the prior reviewed head. (CHANGELOG.md:5, 6b655b45573e)
  • Vision and ownership history: VISION.md permits provider support following established patterns but requires sign-off for provider-auth or unclear privacy contracts; the current registration architecture dates to Peter Steinberger’s descriptor refactor. (VISION.md:11, 45793d72c4fb)
  • Real behavior proof: The complete PR body supplies redacted after-fix CLI traces from a real Hermes login, an untrusted stored-host case, an expired environment token, and an empty explicit HERMES_HOME profile; the traces match the implemented CLI fetch path. (6b655b45573e)

Likely related people:

  • Peter Steinberger: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • Vito Cappello: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Obtain maintainer sign-off for ongoing Hermes credential-file and Nous account-endpoint compatibility.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (6 earlier review cycles)
  • reviewed 2026-09-02T04:57:17.056Z sha ae5d65f :: needs real behavior proof before merge. :: [P1] Restrict auth-file portal hosts before sending the bearer token | [P2] Reject expired environment JWTs before making a request
  • reviewed 2026-09-02T05:24:56.984Z sha 1573715 :: found issues before merge. :: [P2] Honor HERMES_HOME as the exclusive credential root
  • reviewed 2026-09-02T05:45:15.800Z sha c4ac4b3 :: found issues before merge. :: [P1] Reject loopback HTTP overrides before attaching the Hermes token | [P3] Align the plan-row documentation with the implementation
  • reviewed 2026-09-02T06:20:13.258Z sha 9d8a990 :: needs maintainer review before merge. :: none
  • reviewed 2026-09-03T09:32:33.341Z sha 3c55a22 :: blocked before merge. :: [P1] Preserve the existing Unreleased changelog entries
  • reviewed 2026-09-04T02:48:15.162Z sha f926988 :: blocked before merge. :: [P1] Preserve the existing Unreleased changelog entries

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. P1 Urgent regression or broken agent/channel workflow affecting real users now. labels Sep 2, 2026
@asispan asispan closed this Sep 2, 2026
@asispan
asispan force-pushed the feat/nous-portal-provider branch from 1573715 to ec8534d Compare September 2, 2026 05:31
@asispan asispan reopened this Sep 2, 2026
asispan and others added 4 commits September 2, 2026 11:09
Show Nous Portal monthly subscription credits, cycle reset, plan, and
purchased credit balance by reusing the Hermes Agent OAuth login stored in
~/.hermes/auth.json. The provider reads GET /api/oauth/account with the
existing access token and never calls the refresh endpoint, because Nous
refresh tokens are single-use and reuse revokes the Hermes session.

Refs steipete#1367

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…op-up

- Only send the Hermes bearer token to nousresearch.com hosts (or an explicit
  NOUS_PORTAL_BASE_URL override). An untrusted stored portal_base_url is
  ignored, logged, and reported as rejectedStoredHost in the verbose trace.
- Reject an expired NOUS_PORTAL_ACCESS_TOKEN before any request, and keep the
  strategy available so the fetch reports the specific expiry reason.
- Always attach the purchased (top-up) balance as credits and show it in the
  plan row; label detail rows to match the portal billing page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ses only

The app renders the fetch diagnostic as a warning line, so the routine
"portal=… credential=…" trace note now only appears in verbose runs or when
an untrusted stored portal host was ignored. Keep the plan row to the plan
name; the top-up balance lives in the Credits section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When HERMES_HOME is set, only that root's auth.json and shared/nous_auth.json
are consulted; the default ~/.hermes root is never used as a fallback, so a
missing, invalid, or expired custom profile reports its own error instead of
silently querying with another profile's token.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@asispan
asispan force-pushed the feat/nous-portal-provider branch from 11b9cef to c4ac4b3 Compare September 2, 2026 05:39
@clawsweeper clawsweeper Bot added P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Sep 2, 2026
Reject every non-HTTPS portal base URL, loopback included, so the Hermes
bearer token can never be sent in cleartext. Add a request-level test that
the Authorization header only targets https://portal.nousresearch.com when
an http://127.0.0.1 override or an untrusted stored host is supplied.
Document that the plan row shows the plan name only and the top-up balance
lives in the Credits section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@clawsweeper clawsweeper Bot removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. P1 Urgent regression or broken agent/channel workflow affecting real users now. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Sep 2, 2026
@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Sep 2, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Sep 3, 2026
asispan and others added 2 commits September 4, 2026 08:14
The merge from main resolved CHANGELOG.md in favor of the branch, which
dropped the current Unreleased Fixed bullets and the whole 0.56.4
section. Take main's changelog verbatim and add the Nous Portal entry
under a new Added heading alongside it, so the diff against main is
purely additive.
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. label Sep 4, 2026
@asispan

asispan commented Sep 4, 2026

Copy link
Copy Markdown
Author

Fixed the P1 changelog finding in 6b655b4.

The earlier main merge resolved CHANGELOG.md in favour of the branch, which silently dropped the current Unreleased Fixed bullets and the whole 0.56.4 release section. I took main's changelog verbatim and re-added the Nous entry under its own Added heading alongside the existing sections.

The diff against main is now purely additive — zero deleted lines:

$ git diff origin/main --stat -- CHANGELOG.md
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

$ git diff origin/main -- CHANGELOG.md | grep -c "^-[^-]"
0

No Swift files changed in this commit. swift test --filter 'Nous|ProviderArchitectureGatekeeper' still passes (69 tests).

That leaves only the maintainer product decision: whether CodexBar wants to adopt a disabled-by-default Nous Portal provider. Happy to keep it out-of-tree as a plugin instead if that fits the project better.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant