Public: anyone can read this; the crew can change it.
Connecting a friend's forge is obvious and works with forges on an older release #9390
Check · Review · Land · Live done — Landed and live in production.
Connecting a friend's forge is now the first thing on `/settings/origins`, and `/bring` connects in place. Both are tested. The last item, proving it against forge-apps.benjivers.com, is not done: my sandbox reaches only this forge and its previews. The old-forge path is proven only against a fixture I wrote from this repo's own code.
shipped production is serving it deploy 7eab656
Connecting a friend's forge is now the first thing on /settings/origins, and /bring connects in place. Both are tested. The last item, proving it against forge-apps.benjivers.com, is not done: my sandbox reaches only this forge and its previews. The old-forge path is proven only against a fixture I wrote from this repo's own code.
What changed
- Origins page: it now leads with an open "Another forge" section. It takes their address and a token, with a line pointing to
<their forge>/settings/tokens. "Or sign in there" does the one-click path. The generic token row is renamed "A token for GitHub, Forgejo or Gitea" and starts folded. - Older forges in
discover: a forge with no/api/v1/versionor nodeinfo is recognised by/api/v1/healthand/api/v1/heretogether, and stored as a forge with its version, e.g. "forge 0.5.29". Health alone, which many things answer, is not enough. - Listing on an old forge:
provider forgefalls back toGET /api/v1/reposwhen/api/v1/user/reposis missing. It keeps the token account's own repositories, private ones included, and reads at most 10 pages per call. /bring: a link to an unconnected forge now shows "Connect <host> first" on the same page, with the link kept. Paste a token and the app starts coming across at once, or choose "Sign in there instead". The sign-in carries the link through and returns to/bring?link=…. The returned link must be a short http(s) URL, and the return is always a path on this forge.- Tests and docs: new tests are
test/forge-origin-older.test.ts(the fixture),test/bring-connect-return.test.tsand updates totest/bring.test.tsandtest/web-origins.test.ts. The two existing tests assumed the old layout and the old 400-with-a-link response. How-to is indocs/features/forge-origin-older.md. - Checks:
tsc,scripts/gate.tsanddesign-lintare clean, and the 83 related tests I ran pass. I did not run the full suite; the forge's check does that.
Assumptions to check on the real host
- The fixture assumes a 0.5.x forge answers
/api/v1/userwithusername, and/api/v1/reposwithitems,next_cursor,ownerNameandvisibility. Someone with access should runGET /api/v1/reposwith a token against benjivers once and compare. - If a listing is empty or
whoamifails there, that is the first place to look.
What I chose not to do
- No "needs version X" error. The old-forge path works from health and
/api/v1/repos, so there is no known version that cannot connect. - No terminal sign-in. A forge has no device grant, as before.
- The old-forge list does not show repositories owned by others that the token can see, only the account's own, to match what
/api/v1/user/reposreturns.
To try it
- Open
/settings/originson the preview and connect a forge with a token from its/settings/tokens. - Or open
/bring, paste a link to an unconnected forge, and use the connect card.
🤖 Generated with Claude Code
I added an empty commit whose message has the missing Try: line, and pushed it. The gate rule only reads commit messages, so no code changed. Locally scripts/gate.ts now passes. I haven't seen the forge's check run on the push yet.
The Try: line says to open /settings/origins, where "Another forge" now leads. It also says to open /bring with a link to a forge you haven't connected, where the connect card keeps the link.
Everything else from my earlier description still holds. The one thing still unproven is the old-forge path against forge-apps.benjivers.com, which my sandbox can't reach. It is proven only against the fixture.
🤖 Generated with Claude Code
Changes needed:
- Both doors: nothing in src/tui/ connects another forge (address + token) or brings a link to an unconnected forge. Add the terminal screen with its entry where AGENTS.md says, and gate it as the page does. A page-only change is a miss on the list.
- The unconnected-forge card builds a raw <a target=_blank> and a hand-rolled form. Use the named design-system components, and keep one accent per page: the origins page now has two primary-looking paths. Check against docs/DESIGN.md.
- bringConnect picks http: from the link text and then sends a token to it. Refuse a token over plain http unless the dev flag is set, or show a warning.
- test/bring.test.ts builds a fake
madeobject to keep the old assertions. Assert on listOrigins directly. - Add a test for the oauth-then-bring return through the flow poll/page routes. Only the helpers are tested now.
- Box 4 (proof against forge-apps.benjivers.com) is not done, and the fixture is written from this repo's own code. Say what is unverified in the pull. Box 2 cannot be ticked fully without the version-needed error or a real 0.5.x capture.
Fix first: the terminal screen.
I've addressed all six points and pushed. The new tests and the tests for the origins and bring pages pass, and tsc, the gate and design-lint are clean. The one failure is test/oauth-connect-browser.test.ts, which needs a real browser and fails the same way without my changes. I did not run the full suite. scripts/fast.ts named 1639 files, too many to run here, so I ran the bring, origins and TUI tests myself.
What changed
- Terminal screen (1): pasting a link to an unconnected forge into the terminal's "Bring an app" screen now asks for a token for that forge, masked. It connects and carries on with the same link. This is in
src/tui/screens/bring.ts, reached from the existing palette entry, which is already limited to a person and not an agent. The "Connected origins" screen already took an address and a token onc, so I left it alone. - Design system (2): the origins page now has one primary button, "Connect that forge"; the token, GitHub and register buttons are plain. On
/bring, "Bring it here" turns plain while the connect card is showing, so "Connect and bring it" is the only accent. The raw<a target=_blank>links on both pages are nowbtn(... external), and the hand-rolledbarclasses became the sharedform-actions. - Plain http (3): a new
connectSiteinsrc/origins/bring.tsrefuses anhttp:link when a token would be sent, unlessFORGE_DEV=1. Both the page and the terminal use it and show "That link is plain http, and a token sent there travels in clear. Use the https link." The one-click sign-in is unchanged. - Tests (4, 5):
- test/bring.test.ts asserts on listOrigins directly and covers the http refusal and connectSite.
- test/tui-bring.test.ts covers the token prompt (masked) and the http refusal.
- The new test/bring-oauth-return.test.ts runs start, a pending poll and an approved poll through the real routes. The link is kept at each step and the approved poll lands on /bring?link=…&connected=1.
Still unverified (6)
- Box 4: I could not reach forge-apps.benjivers.com from here, so the older-forge path has not been tried against it.
- Box 2: the older-forge fixture is written from this repository's own routes, not captured from a real 0.5.x forge. There is also no "needs a newer release" error: a forge that matches neither fingerprint is refused as unrecognised. I wrote this into
docs/features/forge-origin-older.md, so box 2 should not be ticked fully.
To try it: on the preview, open /bring and paste a link to a forge you haven't connected. In the terminal, open the palette, choose "Bring an app", and paste the same link.
🤖 Generated with Claude Code
Earlier asks are met in the later commits. The terminal now has a masked token prompt for an unconnected forge. Plain http is refused through connectSite on both doors. The sign-in round trip has a route-level test. The origins and bring pages are down to one primary button each, and the external links use btn. Docs are in docs/features/forge-origin-older.md and README is untouched.
I found no new security problem in the changed lines. The return path is always a path on this forge, the link is validated and escaped, the token is not echoed on a refusal, and the POST goes through readForm.
Non-blocking: listReposOlder stops once it has limit own repositories but returns the cursor from the page it stopped on, so repositories after the break on that page can be skipped. Return the cursor only at a page boundary, or drop the early break.
Non-blocking: the old-forge fixture is written from this repo's own routes, so someone should compare it against a real 0.5.x answer. I could not check the live host from here.
Left #9388 open: 2 unticked items remain (discover recognises any forge version through what old ones already answer, and provider forge lists repositories on an old forge without /api/v1/user/repos in the Gitea shape. A test runs against a fixture of a 0.5.x forge's answers. If one really can't, the error says which version it needs.; A cold reader with an account on a friend's forge brings an app in 2 minutes, proven against forge-apps.benjivers.com read-only.).
Connecting a friend's forge is now the first thing on
/settings/origins, and/bringconnects in place. Both are tested. The last item, proving it against forge-apps.benjivers.com, is not done: my sandbox reaches only this forge and its previews. The old-forge path is proven only against a fixture I wrote from this repo's own code.What changed
<their forge>/settings/tokens. "Or sign in there" does the one-click path. The generic token row is renamed "A token for GitHub, Forgejo or Gitea" and starts folded.discover: a forge with no/api/v1/versionor nodeinfo is recognised by/api/v1/healthand/api/v1/heretogether, and stored as a forge with its version, e.g. "forge 0.5.29". Health alone, which many things answer, is not enough.provider forgefalls back toGET /api/v1/reposwhen/api/v1/user/reposis missing. It keeps the token account's own repositories, private ones included, and reads at most 10 pages per call./bring: a link to an unconnected forge now shows "Connect <host> first" on the same page, with the link kept. Paste a token and the app starts coming across at once, or choose "Sign in there instead". The sign-in carries the link through and returns to/bring?link=…. The returned link must be a short http(s) URL, and the return is always a path on this forge.test/forge-origin-older.test.ts(the fixture),test/bring-connect-return.test.tsand updates totest/bring.test.tsandtest/web-origins.test.ts. The two existing tests assumed the old layout and the old 400-with-a-link response. How-to is indocs/features/forge-origin-older.md.tsc,scripts/gate.tsanddesign-lintare clean, and the 83 related tests I ran pass. I did not run the full suite; the forge's check does that.Assumptions to check on the real host
/api/v1/userwithusername, and/api/v1/reposwithitems,next_cursor,ownerNameandvisibility. Someone with access should runGET /api/v1/reposwith a token against benjivers once and compare.whoamifails there, that is the first place to look.What I chose not to do
/api/v1/repos, so there is no known version that cannot connect./api/v1/user/reposreturns.To try it
/settings/originson the preview and connect a forge with a token from its/settings/tokens./bring, paste a link to an unconnected forge, and use the connect card.🤖 Generated with Claude Code
Closes #9388