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

Merged opened this

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

Merged

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

opened this

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 calls addSubscription. A bad name is refused at the first prompt. Token and base refusals, and a name already taken, appear at the base prompt, worded by addRefusal.
  • 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 calls deleteSubscription.
  • 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 a and D re-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 addSubscription once and dropped.
  • Test: test/tui-subscriptions-add.test.ts adds 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.

Closes #7440

commented

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 calls addSubscription. A bad name is refused at the first prompt. Token and base refusals, and a name already taken, appear at the base prompt, worded by addRefusal.
  • 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 calls deleteSubscription.
  • 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 a and D re-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 addSubscription once and dropped.
  • Test: test/tui-subscriptions-add.test.ts adds 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.

Changes requested — before the latest push

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:

  1. Docs: docs/CREW.md line 307 ('Crew subscriptions in the terminal') still says only w and b. Add a (name, masked token, optional API base) and D (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.
  2. 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).
  3. Tidy: imports in subscriptions.ts are out of order (isSubscriptionName sits between deleteSubscription and budgetsOf); the test's listSubscriptions import is split over three lines for no reason; the test header cites #7440 but this is #7551.
  4. 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.
commented

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.

  1. Docs: the "Crew subscriptions in the terminal" paragraph in docs/CREW.md now covers a and D.

- 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.

  1. 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.

  1. Tidy: the imports in subscriptions.ts are in order, the test's listSubscriptions import is one line, and the test header cites #7551.
  2. Live: I didn't open the web page's handler to confirm this; I only saw that src/web/pages/subscriptions.ts calls listSubscriptions in a function named subscriptionsPage. If it reads listSubscriptions per 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 — before the latest push

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:

  1. 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.
  2. 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.
commented

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) and D (remove after a confirm).
  • Signed in paragraph: I took main's version, which adds that d on a token asks Revoke <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.

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs