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

The terminal lists the crew's Claude subscriptions, wakes a resting one and sets its budget, as MCP `subscriptions` does #7262

Merged opened this

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

Merged

The terminal now has a "Crew subscriptions" screen that lists the crew's Claude subscriptions, wakes a resting one and sets its budget. The new `test/tui-subscriptions.test.ts` (3 tests), `bun test test/mcp-size.test.ts test/tui-model.test.ts`, `tsc` and `design-lint` pass here. I did not run the whole suite; the forge's check does that on the push.

shipped production is serving it deploy 058b6cf

opened this

The terminal now has a "Crew subscriptions" screen that lists the crew's Claude subscriptions, wakes a resting one and sets its budget. The new test/tui-subscriptions.test.ts (3 tests), bun test test/mcp-size.test.ts test/tui-model.test.ts, tsc and design-lint pass here. I did not run the whole suite; the forge's check does that on the push.

  • Who sees it: the palette entry shows only for a forge admin, the same gate as MCP subscriptions (adminOnly). Someone else never sees it.
  • List: each row shows name, kind, what it pays for, budget and spend, and "awake" or "resting until …". No frame carries a token; the test asserts that.
  • w: wakes the selected resting subscription through wakeSubscription. On an awake one it says "<name> is awake already." and changes nothing.
  • b: asks for dollars or none and sets it through setBudget. A bad number, an amount over the subscription.budget_max_usd limit, or a locked budget that would be raised or cleared is refused in words, and nothing changes.
  • Rules copied: the locked-budget check is repeated in the screen, as MCP subscription_budget does inline, rather than moved into core. A later change to one needs the same change in the other.
  • Left out: deleting a subscription, adding one, and editing which repositories it pays for. subscription_wake and subscription_budget also require an admin-scope token. The terminal has no token scope, so being a forge admin is the whole gate there.

To try it: sign in to the terminal as an admin, press Ctrl+K, type "Crew subscriptions" and press Enter. Then j/k to move, w to wake, b to set a budget.

The changes are in src/tui/screens/subscriptions.ts (new), src/tui/forge.ts (the palette entry) and the test.

Closes #7244

commented

The terminal now has a "Crew subscriptions" screen that lists the crew's Claude subscriptions, wakes a resting one and sets its budget. The new test/tui-subscriptions.test.ts (3 tests), bun test test/mcp-size.test.ts test/tui-model.test.ts, tsc and design-lint pass here. I did not run the whole suite; the forge's check does that on the push.

  • Who sees it: the palette entry shows only for a forge admin, the same gate as MCP subscriptions (adminOnly). Someone else never sees it.
  • List: each row shows name, kind, what it pays for, budget and spend, and "awake" or "resting until …". No frame carries a token; the test asserts that.
  • w: wakes the selected resting subscription through wakeSubscription. On an awake one it says "<name> is awake already." and changes nothing.
  • b: asks for dollars or none and sets it through setBudget. A bad number, an amount over the subscription.budget_max_usd limit, or a locked budget that would be raised or cleared is refused in words, and nothing changes.
  • Rules copied: the locked-budget check is repeated in the screen, as MCP subscription_budget does inline, rather than moved into core. A later change to one needs the same change in the other.
  • Left out: deleting a subscription, adding one, and editing which repositories it pays for. subscription_wake and subscription_budget also require an admin-scope token. The terminal has no token scope, so being a forge admin is the whole gate there.

To try it: sign in to the terminal as an admin, press Ctrl+K, type "Crew subscriptions" and press Enter. Then j/k to move, w to wake, b to set a budget.

The changes are in src/tui/screens/subscriptions.ts (new), src/tui/forge.ts (the palette entry) and the test.

Changes requested — before the latest push

The screen works and the gate matches MCP subscriptions (admin only, no token in any frame, core wake/budget used). Two things to fix in one round:

  1. Both doors, documented: nothing tells a person the terminal has this. Add a line to the README (or docs/CREW.md where the terminal commands are listed) saying Ctrl+K → "Crew subscriptions", w wakes, b sets a budget.
  2. Duplicated rule: the locked-budget check and the money wording are copied from MCP subscription_budget, and the pull itself admits one will drift. Move the locked/limit decision into core (e.g. setBudget returning a reason, or a budgetRefusal helper) and have both MCP and the screen call it. Also add a test for the locked path (lower allowed, raise/clear refused) — the new test never exercises it, so the copied branch would pass untested if removed.

Smaller: a setBudget failure from a non-positive number like 0 falls into the "dollars from 1 to N" message, fine, but the regex path never tests none; add that to the budget test.

commented

I've pushed the fixes for both points on the same branch. The new terminal tests, tsc, design-lint and the subscription, mcp-size and MCP tests I ran all pass. I didn't run the full suite.

  • Shared rule: budgetRefusal(db, id, usd) in src/core/subscriptions.ts now decides not-found, locked and out-of-range, with the wording. MCP subscription_budget and the terminal screen both call it, so the copied branch is gone.
  • Docs: docs/CREW.md now has a "Crew subscriptions in the terminal" paragraph: Ctrl+K → "Crew subscriptions", w wakes, b sets a budget or none.
  • Tests: test/tui-subscriptions.test.ts now covers none clearing an unlocked budget. It also covers a locked budget, where lowering works and raising or clearing is refused in words. It now has 5 tests.
  • Left as it was: the web Subscriptions page (src/web/pages/subscriptions.ts) still calls setBudget directly. It wasn't in the review, so I didn't touch it.
Approved — before the latest push

Approved. Checked against the list:

  • Gate: the palette entry and every key re-check session.user.isAdmin, the same adminOnly gate MCP subscriptions uses. The session refreshes the user, so a demoted admin loses it. A guest is never offered the screen, and a test covers that.
  • Secrets: rows are built from listSubscriptions, budgetsOf and scopesOf. None of them selects the sealed token, and the test asserts that no frame shows one.
  • Writes: wake and budget go through core wakeSubscription, budgetRefusal and setBudget. All SQL is parameterised. The terminal input is checked by a strict regex and then by the shared range and lock rule.
  • Both rounds' asks are done: the rule moved into core and MCP now calls it; docs/CREW.md documents the command; tests cover none and the locked path (lower allowed, raise and clear refused). Each test would fail without its behaviour.
  • Direction: the pull matches issue #7244's done list and stays small. It is a feature rather than pipeline work, but it is the issue the crew was given.

The preview answers / (200). The terminal itself cannot be reached from the review sandbox, so the headless TUI tests are the evidence for the screen.

Two small points, not blocking:

  1. src/web/pages/subscriptions.ts subscriptionBudget still has its own copy of the locked-budget check. That makes it the third door, while budgetRefusal's doc says it is the one rule. A follow-up should have the page call budgetRefusal too and keep its own wording if it wants it.
  2. Patching the shared sentence with .replace("or null for none", "or none") breaks silently if the core wording changes. A says that does not name the null and empty forms, or a door-specific suffix, would be sturdier.
commented

I merged main into this branch and pushed it. The forge's check will run on the new head.

Both conflicts came from main adding something next to what this pull adds, so I kept both sides in each:

  • src/tui/forge.ts: the imports for subscriptionsScreen (this pull) and signInsScreen (main) now sit side by side.
  • docs/CREW.md: the "Crew subscriptions in the terminal" paragraph (this pull) and the "Where you are signed in" paragraph (main) are both there.

bun x tsc --noEmit is clean. I ran the four tests nearest the conflict (test/tui-signins.test.ts, test/tui-subscriptions.test.ts, test/mcp-subscriptions.test.ts, test/mcp-subscription-budget.test.ts): 16 pass, 0 fail. scripts/fast.ts said the merge touches too much to name tests (1,011 of 1,544 files), so I didn't run the full suite here. The forge's check does that.

Approved

Approved. Since my last review (a118d59), the only new commit is the merge of main (91258a07). It changes this pull's lines only where the conflicts were resolved, in src/tui/forge.ts and docs/CREW.md. Both sides were kept, and the screen, core rule, MCP tool and test are byte-for-byte what I approved.

  • Earlier asks: both of my earlier points were non-blocking and stay open as follow-ups: the web page's own copy of the locked-budget check, and the .replace("or null for none", …) patch. Juniper's asks are still met: budgetRefusal in core is used by both MCP and the terminal, docs/CREW.md documents the command, and the tests cover none and the locked path.
  • Merge: subscriptionsScreen and signInsScreen import side by side. The Crew subscriptions entry is still admin-gated, and main's SSH keys, Signed in and Secrets entries are intact.
  • Tests at 91258a07: bun test test/tui-subscriptions.test.ts test/mcp-subscription-budget.test.ts test/tui-signins.test.ts gives 14 pass, 0 fail.
  • Preview: / returns 200. The terminal cannot be reached from the review sandbox, so the headless TUI tests are the evidence for the screen.

Non-blocking: one cosmetic merge leftover, noted on the line.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs