feat(dashboard): API Keys page — create, list, revoke, connect AI agent (DEV-950)#2049
feat(dashboard): API Keys page — create, list, revoke, connect AI agent (DEV-950)#2049brunod-e wants to merge 5 commits into
Conversation
… AI agent) Signed-in users manage personal API keys for the MCP server / public API: create (name -> one-time plaintext reveal), list (name/status/created/last used), and revoke, plus a Connect-your-AI-agent section with copy-paste install commands for Claude Code, Cursor, and Codex. Talks to the User API via the /api/user proxy (session cookie); gated behind sign-in and reachable from the new API sidebar entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a7ac0b8606
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| `claude mcp add anticapture --transport http ${MCP_URL} --header "X-API-KEY: ${key}"`, | ||
| Cursor: (key: string) => | ||
| `cursor mcp add anticapture --transport http ${MCP_URL} --header "X-API-KEY: ${key}"`, | ||
| Codex: (key: string) => | ||
| `codex mcp add anticapture --url ${MCP_URL} --header "X-API-KEY: ${key}"`, |
There was a problem hiding this comment.
Use the deployed MCP endpoint and bearer header
These copied commands point users at https://mcp.anticapture.com/v1 and send X-API-KEY, but the repo's public proxy exposes the MCP server under /mcp (infra/proxy/default.conf.template) and the MCP/Gateful auth path only forwards and validates Authorization: Bearer <token> (packages/anticapture-client/src/configure-upstream-client.ts, apps/gateful/src/auth/token-auth.ts). Anyone following the new Connect your AI agent section will get a non-working 404/401 setup even with a freshly created key.
Useful? React with 👍 / 👎.
| const queryClient = useQueryClient(); | ||
|
|
||
| const query = useQuery({ | ||
| queryKey: QUERY_KEY, |
There was a problem hiding this comment.
Scope the API key query cache by user
Because /me/api-keys is session-scoped, using a fixed query key lets React Query synchronously return the previous signed-in user's cached key list as soon as a different session becomes authenticated, until the refetch completes. In an account-switch/shared-browser flow, the second user can briefly see the first user's key labels/status; include session.user.id in the query key or clear/remove this query on auth changes.
Useful? React with 👍 / 👎.
isadorable-png
left a comment
There was a problem hiding this comment.
🎨 UI Review
Automated review ·
⚠️ Figma link found (figma.com/design/mUgy2KpQ3gJ07yZaUaXu8l, node3315-19892) but the Figma MCP is unreachable in this run (config/auth error, not a "not found") — visual validation skipped. The dashboard's own Vercel preview is marked Ignored on this PR (onlyanticapture-storybookdeployed), so there's no live preview either. Grounded instead in: the diff, the DS component inventory, and the ClickUp spec task (86ac8e1y5, "🔐 Login & API Keys" — very detailed, node IDs per screen listed in its "Design status" section) plus its linked Authful task (DEV-950).
API Keys page — access gating (question for author)
ApiKeysManager.tsx:33 gates the entire page behind sign-in (if (!isAuthed) return <...sign-in prompt.../>). The ClickUp spec explicitly designs an "anonymous-first funnel": "the API Keys page is reachable logged out. Empty state ("NO KEY YET") + "+ Create key". Clicking Create key while disconnected triggers the sign-in modal, then returns the user to the create flow with any typed name preserved (resume intent)." — also called out as an acceptance criterion. This PR instead blocks the page itself and has no "resume intent" (typed name preserved across login). The PR's own scope notes list Rotate and the onboarding tooltip as deferred, but don't mention this — please confirm whether the full-page gate is an agreed V1 cut or should follow the anonymous-first design. [Code-only]
Empty / signed-out states — component reuse
Both the signed-out gate (ApiKeysManager.tsx:34-45) and the "no keys yet" empty state (ApiKeysManager.tsx:76-79) are hand-rolled <div>s. The project already has a BlankSlate component (shared/components/design-system/blank-slate/BlankSlate.tsx) built for exactly this — icon + uppercase mono title + description — which happens to match the spec's "NO KEY YET" styling far better than the current sentence-case copy ("No API keys yet. Create one to connect your AI agent."). Swap both states to BlankSlate and align the copy with the spec string. [Code-only]
ApiKeysTable — component reuse & touch target
- Hand-rolled
<table>/<thead>/<tbody>(ApiKeysTable.tsx:20-41) duplicates the project's own DS table primitives (TableRow,TableHeader,TableHead,TableCell, etc. indesign-system/table/components) — rebuild on those so hover/border states match every other data table in the app. - The row action is a bare
<button>wrapping<Trash2 className="size-4" />with no padding (ApiKeysTable.tsx:66-73) — hit area is ~16×16px, well under the 44×44px minimum touch target. UseIconButton(design-system/buttons/icon-button/IconButton) instead. [Code-only]
Row action model — question for author
Spec's row menu is Rotate API key · Disable API key · divider · Delete API key for an active key (Activate API key · divider · Delete API key for a disabled one) — a recoverable Disable/Activate toggle distinct from permanent delete. This PR collapses everything to a single destructive Revoke (permanent), and the badge/table copy says "Active/Revoked" instead of the spec's "Active/Disabled." The PR's scope notes mention Rotate as deferred but not this — worth a quick confirm that revoke-only is the agreed V1 cut. [Code-only]
Connect your AI agent — component reuse & touch target
- The Claude Code / Cursor / Codex picker (
ConnectAgentSection.tsx:35-48) is a hand-rolled toggle-button row; the project already has aSegmentedControlcomponent (design-system/segmented-control) built for exactly this (radiogroup semantics, active/inactive styling, size variants) — swap in for consistency. - The copy button here and in
SaveApiKeyModal.tsxis an unpadded<Copy/>/<Check/>icon in a raw<button>— same sub-44px touch-target issue as the table row action. UseIconButton. [Code-only]
Mobile
The touch-target gaps above (revoke button, copy buttons, client chips) apply directly on mobile, where these are the only way to trigger the action — no hidden lg:* alternative exists, so the small hit areas aren't just cosmetic. [Code-only]
Generated by Claude Code — automated UI review routine
Generated by Claude Code
…-auth - the three connect-wallet buttons called RainbowKit's openConnectModal directly, so the custom LoginModal never appeared; they now go through useLogin().openLogin - the better-auth client baseURL was missing the service's /api/auth base path, so every auth call (get-session, siwe/nonce, ...) 404'd through the /api/user proxy - the modal shows the ANTICAPTURE wordmark per the Figma (whitelabel keeps the neutral mark); the wordmark SVG now uses currentColor, with the watermark call-site compensating for its previously baked-in opacity Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…design
The page is user-scoped, not DAO-scoped: it now lives at /api-keys with an
"API" entry in the main sidebar (and mobile menu), and the misplaced entry
in the DAO sidebar is gone.
Visual pass against the Figma spec:
- table rebuilt on the design-system Table (contrast header, Name/Status/
Created/Last Used columns, per-row ... options menu with revoke)
- connect section: client chips as design-system buttons, a key selector
("Key: <label>"), labeled Copy button inside the code block, and an
activity status line driven by lastUsedAt
- keys created this session keep their plaintext in memory so the connect
command ships with the real key (truncated on screen, full on copy)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8fca5038b2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| {created && ( | ||
| <SaveApiKeyModal | ||
| open={!!created} | ||
| onOpenChange={(open) => !open && setCreated(null)} |
There was a problem hiding this comment.
Preserve one-time keys until explicit confirmation
When the save dialog is dismissed via the modal's X button, overlay click, or Escape key, Radix calls onOpenChange(false), and this handler immediately clears created. Because this plaintext key is never retrievable again, a user who accidentally closes the dialog before copying it loses the only copy and has to revoke/recreate the key; clear this state only from the explicit Done path, or disable passive dismissal for this one-time secret.
Useful? React with 👍 / 👎.
Summary
Frontend for self-service API keys (DEV-950), stacked on #2047. Adds the API Keys page from the Figma design: signed-in users create, view, and revoke personal API keys for the MCP server / public API.
What's included
/{dao}/api-keys, new "API" sidebar entry), gated behind sign-in (prompts the login modal when signed out).apiKeysClienton the/api/userproxy (session cookie) + a React Query hook.Scope notes (agreed)
<YOUR_API_KEY>placeholder (the plaintext is only shown once at creation); real tokens use theact_prefix.Verification
pnpm typecheckclean; eslint clean on the new/changed files.PROVISIONING_API_KEY+ User APIAUTHFUL_URL/AUTHFUL_PROVISIONING_API_KEY), so the page works end to end against dev.Refs DEV-950.
🤖 Generated with Claude Code