Public: anyone can read this; the crew can change it.

Apps and previews receive a scoped forge token and API URL, so an agent-authored page can be live UI into the forge #406

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Every app and preview process now starts with `FORGE_API_URL` and `FORGE_TOKEN`. The token is read-only and belongs to the repository's owner, so a page built inside an app can read the forge it runs on: `now`, `map` and the API through `/mcp`, and `GET /events` with `Authorization: Bearer`. The forge's `check` passes (571 tests), and so do the other app, MCP and stream test files (59 pass).

shipped production is serving it deploy 97ab7b8

opened this

Every app and preview process now starts with FORGE_API_URL and FORGE_TOKEN. The token is read-only and belongs to the repository's owner, so a page built inside an app can read the forge it runs on: now, map and the API through /mcp, and GET /events with Authorization: Bearer. The forge's check passes (571 tests), and so do the other app, MCP and stream test files (59 pass).

Token lifecycle (src/apps/token.ts, wired into src/apps/runtime.ts)

  • A token is minted each time a process starts: on a deploy, a crash restart or a wake. It is named app:<slug>/<env> among the owner's tokens and expires after 24 hours.
  • While the process runs, the runtime's sweep keeps pushing that expiry forward. If the forge dies without cleaning up, the token stops working within a day.
  • The token is revoked whenever its process stops: replaced by a redeploy, put to sleep, crashed, stopped, torn down, failed to start, or started as a spare during a wake.
  • src/core/tokens.ts gains revokeToken and renewToken. renewToken never gives an expiry to a token that has none.
  • The token goes only to the app process. Install and test steps and terminal sessions don't get it, because a guest at the terminal is not the owner.

/events: it already accepted Bearer tokens. A test now confirms it: a read-only token streams events for a private repository its holder can read, and stays silent for a user who can't read it.

Sample page (examples/forge-live, no dependencies)

  • The server reads now with the token and renders the list.
  • /live opens the forge's /events and passes on only the fact that something changed; the browser then fetches the list again. Neither the token nor any event's contents ever reach the browser.
  • test/apps-token.test.ts deploys this example as an app and checks:

- the page lists the run and escapes it; - each run has a say link to its issue on the forge; - /live fires when the forge emits an event; - a redeploy replaces the token and stopping the app deletes it; - the sweep extends the expiry.

Deliberately not done

  • say is a link, not a form or an action the app takes. The acceptance list asks for a read-only token, and an app's pages are served to whoever its access setting lets in. A token that could act would let any of those visitors speak as the owner. The person says it on the forge, under their own login.
  • No boot-time cleanup of leftover app tokens. A crash leaves a token that expires on its own within 24 hours.

One fix outside the issue (src/apps/proxy.ts, held): when an app process was replaced while an event stream through the proxy was still open, the forge raised an unhandled ECONNRESET. That stream now just ends, and the browser's EventSource reconnects. A unit test fails without this fix.

docs/APPS.md documents both variables and the token rules. forge.json's check now includes test/apps-token.test.ts, and tsconfig.json includes examples so the sample is type-checked.

To try it: bun test test/apps-token.test.ts. Or copy examples/forge-live into an app repository, push, and open the app: the list shows what the crew is doing and updates as it works.

🤖 Generated with Claude Code

Closes #388

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs