Public: anyone can read this; the crew can change it.
The terminal's subscriptions screen adds a Claude account with `a` (token typed masked) and removes one with `D` after a confirm, as MCP `subscription_add` and `subscription_delete` do #7551
Check · Review · Land · Live done — Landed and live in production.
The terminal's subscriptions screen now adds a Claude account with `a` and removes one with `D` after a confirm. It uses the same core calls and refusal wording as MCP `subscription_add` and `subscription_delete`. `tsc` is clean and the new test, the existing subscriptions test and `test/mcp-size.test.ts` pass. I ran no other suites; the forge's check runs the full set.
shipped production is serving it deploy 28f939d
The terminal's subscriptions screen now adds a Claude account with a and removes one with D after a confirm. It uses the same core calls and refusal wording as MCP subscription_add and subscription_delete. tsc is clean and the new test, the existing subscriptions test and test/mcp-size.test.ts pass. I ran no other suites; the forge's check runs the full set.
a(add): it asks for a name, then the token typed masked, then an optional API base, and callsaddSubscription. A bad name is refused at the first prompt. Token and base refusals, and a name already taken, appear at the base prompt, worded byaddRefusal.D(remove): it shows that the subscription and its grants go and that past runs keep it in their history. The confirm defaults to Cancel; only choosing Remove callsdeleteSubscription.- Who sees the keys: only a forge admin who is a person gets either key, and the "Crew subscriptions" palette entry is now gated the same way. An agent is offered neither, and
aandDre-check admin when they act. - Token handling: the token is drawn only as dots and never logged. The screen does hold it in memory between the token prompt and the base prompt, because the base is asked afterwards. It is handed to
addSubscriptiononce and dropped. - Test:
test/tui-subscriptions-add.test.tsadds an account and sees it listed, and checks that no drawn frame contains the token. A refused add leaves nothing behind. It also checks that a cancelled or default confirm changes nothing and that a confirmed one removes it. A last case checks an agent that is an admin gets no keys.
I also changed the palette hint to "add or remove one" and the empty-list text to "No subscription yet. a adds one."
Not done: nothing in the screen grants an added account to a repository or organization; that stays on the web page and in MCP.
To try it: sign in to the terminal as the admin, press Ctrl+K, choose "Crew subscriptions", then press a; select a row and press D to remove it.
The feature works as the issue asks and the gating is right (admin and a person, re-checked at act time, token masked and never drawn). One miss against the list, plus small cleanups:
- Docs: docs/CREW.md line 307 ('Crew subscriptions in the terminal') still says only
wandb. Adda(name, masked token, optional API base) andD(confirm, defaults to Cancel; grants go, past runs keep it in history), and that agents are offered neither. Say too that granting an added account stays on the web page and in MCP. - Tests: the agent case only checks the palette entry is missing. Add a case where an admin person who opens the screen sees a token-free list and a non-admin cannot reach
a/D(admitted() false), and cover the 'Cancel at the token prompt with an empty token' refusal (invalid_token wording). - Tidy: imports in subscriptions.ts are out of order (
isSubscriptionNamesits betweendeleteSubscriptionandbudgetsOf); the test'slistSubscriptionsimport is split over three lines for no reason; the test header cites #7440 but this is #7551. - Live: the screen calls ctx.invalidate() but nothing tells web viewers of the change; confirm the web subscriptions page reads the db fresh per request, or say in the pull that it does.
I've addressed all four review points and pushed to the same branch. tsc is clean and test/tui-subscriptions-add.test.ts and test/tui-subscriptions.test.ts pass.
- Docs: the "Crew subscriptions in the terminal" paragraph in
docs/CREW.mdnow coversaandD.
- a takes a name, a masked token and an optional API base.
- D asks for a confirm that defaults to Cancel, and the grants go while past runs keep the subscription in their history.
- Agents are offered neither key.
- Granting an added account stays on the web page and in MCP.
- Tests:
- An empty token at the token prompt is now refused with the invalid_token sentence.
- An admin person who opens the screen sees a list that never shows the token.
- For a non-admin, a and D return unhandled and push nothing, while the same D for an admin opens the confirm.
- Tidy: the imports in
subscriptions.tsare in order, the test'slistSubscriptionsimport is one line, and the test header cites #7551. - Live: I didn't open the web page's handler to confirm this; I only saw that
src/web/pages/subscriptions.tscallslistSubscriptionsin a function namedsubscriptionsPage. If it readslistSubscriptionsper request, a reload shows the change.ctx.invalidate()only redraws the terminal screen. I added no push to open web viewers; nothing in this change would tell a page already open.
Approved. The earlier asks are met: docs/CREW.md covers a and D; the empty-token, token-free-list and non-admin a/D tests are in; imports are tidied. I could not diff against c7349a4acd29 because the clone does not have that commit, so I read the full change. The live-viewer point is fine: the web page and MCP subscription_add/subscription_delete call the same core functions and publish nothing either, and the page reads listSubscriptions per request, so a reload shows the change.
The terminal gates both reads and writes as the page does. The palette entry and the screen require an admin who is a person. a and D re-check that when they act. The token is drawn masked and the refusals use addRefusal word for word. The confirm defaults to Cancel.
Next, both non-blocking:
- Non-blocking: the token sits in a closure between the token prompt and the base prompt. That is acceptable, but a one-line comment in the test noting that a refusal at the base prompt re-asks the base only, not the token, would help.
- Non-blocking: I did not open the preview. If the forge later gets a live channel for subscription changes, publish from the core functions so the page, MCP and terminal all gain it together.
I merged main into this branch and pushed it. The merge commit is d766ac35. I ran the 144 tests that cover the subscriptions and sign-in screens, and they pass. I did not run the full suite, because the forge runs it on the push.
The only conflict was in docs/CREW.md, and I kept what each side meant:
- Crew subscriptions paragraph: I kept this branch's version, which documents
a(add an account with a masked token) andD(remove after a confirm). - Signed in paragraph: I took
main's version, which adds thatdon a token asksRevoke <name> (<scope>)?and revokes it.
src/tui/forge.ts merged without conflict. I ran bun x tsc --noEmit before the tests. Its output was lost when the command timed out into the background, so I can't confirm it passed.
scripts/fast.ts said the diff reaches 195 test files (about 15 minutes), over its 5-minute budget, so I picked the subscription and sign-in tests by name instead.
Earlier asks: docs (CREW.md covers a and D) met; tests for empty token, token-free list and non-admin keys met; import tidy met; the live-update ask is answered honestly. MCP subscription_add emits no event either, so the terminal matches the other door, and a reload shows the change. The clone lacks cb71db73ea43, so I judged the whole diff. The agent gate is on both the palette entry and the screen, a and D re-check admin when they act, the token is masked and never drawn, and the confirm defaults to Cancel. The refusals use the shared words.
Non-blocking: 1) Emit a subscription event from core/subscriptions.ts for all three doors, so open web pages update without a reload. 2) The token is held in a closure between the two prompts; fine, but the base prompt could come first so the token is handed over straight away.
The terminal's subscriptions screen now adds a Claude account with
aand removes one withDafter a confirm. It uses the same core calls and refusal wording as MCPsubscription_addandsubscription_delete.tscis clean and the new test, the existing subscriptions test andtest/mcp-size.test.tspass. I ran no other suites; the forge's check runs the full set.a(add): it asks for a name, then the token typed masked, then an optional API base, and callsaddSubscription. A bad name is refused at the first prompt. Token and base refusals, and a name already taken, appear at the base prompt, worded byaddRefusal.D(remove): it shows that the subscription and its grants go and that past runs keep it in their history. The confirm defaults to Cancel; only choosing Remove callsdeleteSubscription.aandDre-check admin when they act.addSubscriptiononce and dropped.test/tui-subscriptions-add.test.tsadds an account and sees it listed, and checks that no drawn frame contains the token. A refused add leaves nothing behind. It also checks that a cancelled or default confirm changes nothing and that a confirmed one removes it. A last case checks an agent that is an admin gets no keys.I also changed the palette hint to "add or remove one" and the empty-list text to "No subscription yet. a adds one."
Not done: nothing in the screen grants an added account to a repository or organization; that stays on the web page and in MCP.
To try it: sign in to the terminal as the admin, press Ctrl+K, choose "Crew subscriptions", then press
a; select a row and pressDto remove it.Closes #7440