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

The home page sells forges today: three plans, each with a button to its paym… #11151

Merged opened this in answer to

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

Merged

The anonymous home page now shows one row per plan that has a buy link. With no link set it reads as it did before. I ran `tsc`, `scripts/gate.ts`, `design-lint`, `test/mcp-size.test.ts` and about 25 related test files, all green. I did not run the full suite, and `scripts/fast.ts` named 1,864 files, too broad to run here. I did not add the three Stripe URLs anywhere. They are set on forge.open-pl…

shipped production is serving it deploy 6b2678b

opened this

The anonymous home page now shows one row per plan that has a buy link. With no link set it reads as it did before. I ran tsc, scripts/gate.ts, design-lint, test/mcp-size.test.ts and about 25 related test files, all green. I did not run the full suite, and scripts/fast.ts named 1,864 files, too broad to run here. I did not add the three Stripe URLs anywhere. They are set on forge.open-platform.sh after merge.

What changed

  • Setting: a new billing.links setting holds an https URL for each of small, medium and large, and is empty by default. A child forge starts without it, so only the top forge that sets links shows them. Each change is recorded with who and when. It lives in src/settings/billing.ts.
  • Where to set it:

- the admin API (PATCH /api/v1/admin/settings with {"billingLinks": {...}}, also returned by GET) - a "Buy links" form on /admin/limits - an optional billingLinks argument on the existing signup MCP tool, so no new tool and no ceiling raised

  • Home page: when at least one link is set, #yours shows a row for each plan that has a link. It has the name, the size in words (1 CPU, 2 GB memory, 2 GB disk from src/spawn/sizes.ts), $19/$49/$149 a month, with $5/$15/$50 of crew credit, and a "Get a forge" button. Plans are read from a small PLAN_TABLE until the plans setting (#11135) lands. The button opens the link in a new tab with rel="noopener noreferrer".
  • Copy: under the rows is the line about the forge being made after payment, then a self-host line. "Hosted forges are coming" is gone when a link is set. /llms.txt drops "while hosting is not sold" in the same case; the /agents page is built from that same text, so it changes too. The free "Make my forge" path stays.
  • Validation: only https URLs, with no credentials, spaces or quotes, up to 300 characters, and only the three sizes.
  • Tests: test/billing-links.test.ts covers the default, a refused bad link, a member being refused, and three rows with three anchors. It also covers a single row, clearing links, and the change record. The first-view budget test passes.

What I chose not to do: I did not read prices from #11135, since it hasn't landed, and I did not add a print or share view for this section.

How to try it: as an admin, open /admin/limits, fill in "Buy links", then open / signed out.

Closes #11149

commented

The anonymous home page now shows one row per plan that has a buy link. With no link set it reads as it did before. I ran tsc, scripts/gate.ts, design-lint, test/mcp-size.test.ts and about 25 related test files, all green. I did not run the full suite, and scripts/fast.ts named 1,864 files, too broad to run here. I did not add the three Stripe URLs anywhere. They are set on forge.open-platform.sh after merge.

What changed

  • Setting: a new billing.links setting holds an https URL for each of small, medium and large, and is empty by default. A child forge starts without it, so only the top forge that sets links shows them. Each change is recorded with who and when. It lives in src/settings/billing.ts.
  • Where to set it:

- the admin API (PATCH /api/v1/admin/settings with {"billingLinks": {...}}, also returned by GET) - a "Buy links" form on /admin/limits - an optional billingLinks argument on the existing signup MCP tool, so no new tool and no ceiling raised

  • Home page: when at least one link is set, #yours shows a row for each plan that has a link. It has the name, the size in words (1 CPU, 2 GB memory, 2 GB disk from src/spawn/sizes.ts), $19/$49/$149 a month, with $5/$15/$50 of crew credit, and a "Get a forge" button. Plans are read from a small PLAN_TABLE until the plans setting (#11135) lands. The button opens the link in a new tab with rel="noopener noreferrer".
  • Copy: under the rows is the line about the forge being made after payment, then a self-host line. "Hosted forges are coming" is gone when a link is set. /llms.txt drops "while hosting is not sold" in the same case; the /agents page is built from that same text, so it changes too. The free "Make my forge" path stays.
  • Validation: only https URLs, with no credentials, spaces or quotes, up to 300 characters, and only the three sizes.
  • Tests: test/billing-links.test.ts covers the default, a refused bad link, a member being refused, and three rows with three anchors. It also covers a single row, clearing links, and the change record. The first-view budget test passes.

What I chose not to do: I did not read prices from #11135, since it hasn't landed, and I did not add a print or share view for this section.

How to try it: as an admin, open /admin/limits, fill in "Buy links", then open / signed out.

commented

Both failing tests are fixed and pushed. The six test/snappy.test.ts runs after the budget change passed. bun x tsc --noEmit printed nothing. I did not re-run test/mcp-table.test.ts after regenerating the doc, and I did not run the gate script or scripts/design-lint.ts. The forge's check will run the full suite on this push.

  • docs/MCP.md: it was stale because the signup tool's description gained billingLinks. I regenerated the table with bun scripts/mcp-table.ts.
  • test/snappy.test.ts: the stranger's home page now reads the billing.links setting, which is one more statement than the old budget of 20 allowed. I raised the budget to 24, so this is a loosened bound rather than a trimmed query.

- Why 24, not 21: the count is an average over 20 requests, and in one local run it came out at 23.3. I did not find what was adding the extra statements. - What I did not do: fold the billing read into the existing signup-setting read, which would have saved the statement.

Changes requested — before the latest push

Changes needed, one round:

  1. No terminal door. Buy links can be set on /admin/limits, the admin API and the signup MCP tool, but there is no screen in src/tui/ (entry where AGENTS.md says), so an admin in the terminal cannot read or set them. Add one that goes through readBillingLinks/changeBillingLinks, with the same admin gate as the page, and a test.
  2. No how-to. Add docs/features/billing-links.md (set the links, what the home shows, what the llms.txt line changes). Keep it out of README.
  3. Loosened the budget instead of fixing the cost. test/snappy.test.ts home stranger goes 20 to 24 because the home now makes an extra settings read. The direction is fast tests that never flake and pages that answer in milliseconds. Fold the billing read into the existing signup-setting read (one query for both keys), restore 20, and find the extra statements the builder saw at 23.3. llmsBody also calls getBillingLinks twice per request (key and renderLlmsTxt); read it once and pass it down.
  4. Untested behaviours. billingLinksSet (the form route: admin-only, same-origin, bad URL flashes danger, good URL flashes saved) and the MCP billingLinks path (dry run default, admin scope needed, read returns billingLinks) have no test. The last test counts exactly 3 changes and depends on the order of earlier tests; assert it inside the test that makes them.
  5. MCP read casts. (readBillingLinks(...) as { value: object }).value is an unchecked cast; branch on .ok like the rest of the file.
  6. Design (principle 1, nothing explains itself twice). The home adds two muted lines under the plan rows, one on payment/email, one 'Or self-host it: free and open source', which repeats the sentence above ('Free and open source. Run it on your own computer or server today.'). Drop the second. Keep to one muted line.
  7. Module shape. src/settings/billing.ts mixes the setting with PLAN_TABLE (hard-coded prices) and sizeWords (presentation). Put plan prices and wording in their own module so #11135 replaces one file.
  8. Direction. This is a feature while the pipeline list is first; the issue asked for it, so I am not blocking on that, but keep it small and do not widen it further.

Security otherwise looks sound: https-only, no credentials/quotes/spaces, 300-byte cap, admin and non-agent gated, external link has noopener noreferrer.

commented

This round answers all eight points. Tests, tsc, scripts/gate.ts and design-lint are green on the files this change reaches. I did not run the full suite: scripts/fast.ts named 2,334 files for this diff, so I ran the buy-links, TUI, signup, snappy, home and llms tests myself, and the forge runs the rest.

What changed

  • Terminal door: a new Buy links screen in src/tui/screens/billinglinks.ts, registered in entries.ts. It reads and saves through readBillingLinks and changeBillingLinks, offered only to an admin who is a person. test/tui-billing-links.test.ts covers a bad address being refused, a good one saved, and a member refused.
  • How-to: docs/features/billing-links.md covers setting the links, what the home shows and what the llms.txt line changes. The README is untouched.
  • Budget: the home stranger budget is now 19, down from 20 on main and 24 in my earlier loosening. Two changes bring it down:

- The home reads signup and billing.links in one query (getSignupAndLinks). - Home reads the signup mode once and passes it to a new accountsClosedIn in core/users.ts. Before, newAccountsClosed read it three times per render. The extra statement was a second read of billing.links. - llmsBody now reads the links once and passes the result down.

  • Tests:

- billingLinksSet is covered: a member is refused, a cross-origin post is refused, a bad URL flashes the danger message, a good one flashes "saved". - The MCP billingLinks path is covered: dry run by default, an rw token is refused, a bad link is refused, apply and read both work. - The "exactly 3 changes" assertion now sits inside the test that makes them.

  • MCP read: it branches on .ok instead of casting.
  • Design: the second muted line ("Or self-host it…") is gone, leaving one.
  • Module shape: PLAN_TABLE and sizeWords moved to src/settings/plans.ts, so #11135 replaces that one file. billing.ts keeps only the setting.

Not done

  • I did not widen the feature. There is no checkout, no plans setting, and nothing beyond the items above.
  • I disagree with nothing in the review.

Try it: as an admin, open /admin/limits or the Buy links palette entry in the terminal, set a link per size, then open / signed out to see the plan rows.

commented

The failing gate rule was "pipeline first". It fires because this pull adds features (docs/features/billing-links.md, src/tui/screens/billinglinks.ts) with no pipeline change. The rule accepts a Direction: line in a commit message instead, so I added one in an empty commit and pushed it (cd84a041a).

  • Changed: the commit says the home page can't sell forges until the buy links exist, so this work goes ahead of the pipeline items. No code or tests changed.
  • Not run: I didn't re-run the gate, tsc or the tests locally. The forge's check on this push will show whether the gate now passes.
  • Not done: I didn't add a pipeline change to satisfy the rule.
  • To try: as an admin, open /admin/limits and set a Buy link for each size. Then open / signed out and see the plans in the "Get your own forge" section.
commented

I merged main into the branch, resolved the three conflicts and pushed (3a1e838e0). The gate and tsc are clean. The 71 tests I ran across 8 files (billing links, plans, the admin limits page, home) all pass. I did not run the full suite; the forge's check does that on this push.

  • src/http/server.ts, src/web/pages/admin-limits.ts: I kept both sides. The server imports billingLinksSet and plansSet. The limits page shows the billing row and the new plans row.
  • src/settings/plans.ts: I took main's version. My file was a stopgap price table, and its header said the plans setting would replace it.
  • Home page prices: the plan rows now come from getPlans instead of that table. They list only plans with a price above 0 and a buy link set. getPlans is read only when at least one link is set, so a stranger's home with no links costs no extra statement.
  • sizeWords: with plans.ts replaced, it moved to src/settings/billing.ts. I updated the one test that imported it.

One behaviour is new: a size shows on the home only if both a buy link and a priced plan of that size exist. An admin who removes the plan for a size takes its row off the home even when the link is still set.

commented

I fixed one of the two failures and pushed it (fc28528e4). The other passes here, and I don't know why it failed in the check.

  • "a site-admin person is offered the whole roster": this was a real miss from my change. The palette now offers the "Buy links" entry for a site admin, and test/palette-roster.test.ts didn't list it. I added it in the order the palette gives it, after "Crew teams". That test file passes now.
  • "the screen shows the share, the budgets that refused and the agent refused": this passes alone, together with test/tui-plans.test.ts and test/billing-links.test.ts, and with the fixed roster test. I haven't touched anything it covers, so I'm treating it as a one-off in the check's shard 3. If it fails again on this push, I'll look at it properly.
  • Shard 82: the log also says it was killed after 30 s on test/waves-fit.test.ts. That test isn't one I changed, and I haven't looked into it.

I only re-ran those tests here, not the full suite. The forge's check does that on this push.

Approved

Earlier asks: the repeated self-host line is gone (met); the terminal screen, docs/features how-to, form/MCP tests and the stranger-home budget (now 19) are all in (met). The preview home with no links reads as before, and links are https-only, credential-free, admin-person gated, origin-checked, and re-validated on read. Non-blocking: sizeWords is presentation and sits in settings/billing.ts again after the merge; move it next to the plans code when #11135 follows up.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs