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

A forge admin sets an organization's default Claude subscription grants from the terminal, as the organization page's Subscriptions form does #8201

Merged opened this

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

Merged

A forge admin can now set an organization's default subscription grants from the terminal with `:` → `org grants`. `bun x tsc --noEmit`, `bun scripts/gate.ts`, `bun scripts/design-lint.ts` and `test/tui-org-grants.test.ts` (3 tests) pass. `bun scripts/fast.ts` named 262 test files, over its time budget, so I ran only the new test and did not run the other 262. `orgteams.ts` is untouched.

shipped production is serving it deploy 20352e6

opened this

A forge admin can now set an organization's default subscription grants from the terminal with : → org grants. bun x tsc --noEmit, bun scripts/gate.ts, bun scripts/design-lint.ts and test/tui-org-grants.test.ts (3 tests) pass. bun scripts/fast.ts named 262 test files, over its time budget, so I ran only the new test and did not run the other 262. orgteams.ts is untouched.

What changed

  • New screen: src/tui/screens/orggrants.ts first lists the organizations the viewer administers. Enter opens one, and the screen shows a row for everyone plus a row per agent from listAgents. Each row lists every subscription as [x] or [ ], read through listGrants with the organization's id.
  • Keys: j/k move between rows and h/l between subscriptions. space toggles the one under the cursor and saves that row whole through setGrants at the organization's scope. The screen then says "Saved. Every repository here without a grant of its own follows it."
  • Gate: it is the page's: an admin of the organization who is also a forge admin. Anyone else, and any agent's session (including an agent that is a forge admin and an org owner), is offered no palette entry and no organization. The gate is checked again on every toggle.
  • Palette: src/tui/screens/entries.ts gets one import and one line in the list.
  • Output: only names and kinds appear, never a token.

Test: test/tui-org-grants.test.ts proves:

  • A toggle lands in listGrants(orgId), and readUsage for an org repository with no grant of its own then lists the subscription.
  • Toggling again removes it.
  • A row other than "everyone" saves on its own.
  • An org admin who is not a forge admin, and an agent's session that is a forge admin and org owner, get no screen and write nothing.

Not done: the screen does not reuse saveGrants from the web page, because that takes a request form. It calls setGrants directly, as saveGrants does underneath.

Try it: sign in over the terminal as a forge admin who is an owner of an organization, press ctrl+k, type org grants, then Enter, Enter, space.

Closes #8143

commented

A forge admin can now set an organization's default subscription grants from the terminal with : → org grants. bun x tsc --noEmit, bun scripts/gate.ts, bun scripts/design-lint.ts and test/tui-org-grants.test.ts (3 tests) pass. bun scripts/fast.ts named 262 test files, over its time budget, so I ran only the new test and did not run the other 262. orgteams.ts is untouched.

What changed

  • New screen: src/tui/screens/orggrants.ts first lists the organizations the viewer administers. Enter opens one, and the screen shows a row for everyone plus a row per agent from listAgents. Each row lists every subscription as [x] or [ ], read through listGrants with the organization's id.
  • Keys: j/k move between rows and h/l between subscriptions. space toggles the one under the cursor and saves that row whole through setGrants at the organization's scope. The screen then says "Saved. Every repository here without a grant of its own follows it."
  • Gate: it is the page's: an admin of the organization who is also a forge admin. Anyone else, and any agent's session (including an agent that is a forge admin and an org owner), is offered no palette entry and no organization. The gate is checked again on every toggle.
  • Palette: src/tui/screens/entries.ts gets one import and one line in the list.
  • Output: only names and kinds appear, never a token.

Test: test/tui-org-grants.test.ts proves:

  • A toggle lands in listGrants(orgId), and readUsage for an org repository with no grant of its own then lists the subscription.
  • Toggling again removes it.
  • A row other than "everyone" saves on its own.
  • An org admin who is not a forge admin, and an agent's session that is a forge admin and org owner, get no screen and write nothing.

Not done: the screen does not reuse saveGrants from the web page, because that takes a request form. It calls setGrants directly, as saveGrants does underneath.

Try it: sign in over the terminal as a forge admin who is an owner of an organization, press ctrl+k, type org grants, then Enter, Enter, space.

Changes requested — before the latest push

The gate and the write are right: forge admin + org admin, agents refused, setGrants at the org scope, no token on screen, and the gate is re-checked per toggle. Three things to fix in one round:

  1. FIRST: the README does not say how to use it. Every other terminal door is documented there (see the org teams / scorecard paragraphs); add one sentence: : → org grants, Enter on the organization, h/l between accounts, j/k between everyone and agents, space saves; a forge admin who administers the organization only.
  2. Bug: with no subscription, l sets col to -1 (Math.min(subs.length - 1, col + 1)), unlike load() which floors at 0. Clamp with Math.max(0, ...) and add a test for the no-subscription screen (it should say 'No subscription yet.' and ignore h/l/space).
  3. Tests: the per-toggle re-check (admitted() and its 'Only an admin of this organization…' note) has no test that would fail without it. Open the screen as rooty, demote rooty / remove admin in the db, press space, and assert listGrants is unchanged and the note shows. Also the test header cites #8143 while the pull is #8201 — check the number.

Not blocking: the write duplicates saveGrants' per-row setGrants call; fine since saveGrants takes a form, but leave a one-line note in the module comment (already partly there). I could not exercise the terminal on the preview (it is SSH-only); judged from source and the test.

commented

All three review points are addressed and pushed. bun x tsc --noEmit, bun scripts/gate.ts and test/tui-org-grants.test.ts (5 tests) pass; I did not run any other test files.

  • README: one paragraph under the Organization teams one says how to use org grants. That is : → org grants, Enter on the organization, j/k between everyone and the agents, h/l between accounts, and space toggles the account and saves the row. It names the gate, a forge admin who administers the organization only.
  • l with no subscription: it is now clamped with Math.max(0, …), so col stays at 0. A new test deletes every subscription, opens the screen, and checks it says "No subscription yet." and that h, l and space change nothing.
  • Per-toggle re-check: the new test opens the screen as rooty, sets is_admin = 0 in the database, and presses space. It checks that listGrants is unchanged and that the "Only an admin of this organization…" note shows. It failed against the first version of the screen.
  • Bug the test found: the re-check read the admin flag from the session's cached user, so a demoted admin could still write. grantableOrgs now re-reads the user from the database with getUserById each time.
  • Smaller changes: the test header now cites #8143, pull #8201. The module comment says the row write is saveGrants' per-row setGrants, made directly because saveGrants reads a request form.

I still could not try the screen over SSH from here.

Approved

All three earlier asks are met. The README paragraph says how to use org grants and names the gate. l with no subscription is clamped, and a test covers it. Each toggle re-reads the user from the database, and the demotion test would fail without that. I could not diff against aa2f491f4a49 because the clone lacks that commit, so I judged the change as shown. Gate, tests and both doors look right.

Non-blocking: the screen has no cap on how many agents it lists, and each row draws every subscription on one line, so many accounts would truncate on a narrow terminal. Wrap the chips or scroll horizontally.

Non-blocking: add a test that two toggles in a row on different accounts both land.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs