feat(settings): paste a Claude setup-token

Merged
CH
5d ago
+627-1

P13 of the native-harness rollout (task "Bringing Codex or Claude Code Max plans into Luke"). It builds on P04/P04b/P04c (the claude_setup_token kind and the subscriptions list) and reuses P19's (#4243) cohort gate and Codex group as its model.

Settings › Coding agents gains a Claude Code group, drawn only for accounts in LUKE_NATIVE_AGENT_ACCOUNTS (nativeAgents). Unlike the ChatGPT sign-in, it needs no loopback, so the web panel shows it too, still only to the cohort.

What changed

  • claude-plan-section.tsx, one row, "Claude plan":
    • Not connected: "Run claude setup-token in a terminal, then paste the token it prints." and Add token. Add token opens a password field (autocomplete off) with Cancel and Save, as the API key rows do.
    • Save sends providerKeys.save with { provider: anthropic, kind: claude_setup_token, key } one time. When the service keeps the token, the field closes and its value is cleared. A refusal shows its reason under the field and leaves the paste in place.
    • Connected: the stored subscriptions entry's status ("Connected" for unverified/verified), its hint, and the date it was added, with Disconnect. Disconnect asks first, then removes the token with kind: claude_setup_token.
    • A token whose stored status is invalid, quota_exceeded or unreadable shows that status and offers Replace token. P15 owns the statuses themselves.
    • The group sits inside ph-no-capture, like the API keys group.
  • host-operator.ts: providerKeySave dropped kind before it reached the gateway, so the service would have stored a setup-token as an API key. It now forwards kind, the same way providerKeyRemove already does.
  • coding-agents-page.tsx: mounts the group for the cohort, between API keys and Codex.

Judgment calls

  • The row is labelled "Claude plan" (the brief says "Subscription"). This matches P19's "ChatGPT plan" and P15's planned "Using your Claude plan", following one wording per idea.
  • The token never reaches renderer state beyond the open field. Every answer carries only hint and status.

Screenshots (synthetic data, rendered from the panel in a throwaway harness)

Not connected · pasting · connected:

The OpenAI row's mark shows as broken only because the harness doesn't serve the panel's marks/ assets, which the desktop build copies next to the bundle. This PR doesn't touch that mark, and the real panel draws it as on main.

Tests

  • packages/panel/src/settings/claude-plan-section.test.ts:
    • The group appears only for the cohort.
    • The field is masked (type=password, autocomplete off).
    • Save sends the trimmed token one time with its kind, then clears the field.
    • The token appears nowhere in the DOM after save and is never passed to console.*.
    • A refused save keeps the paste and shows why.
    • Disconnect asks first and removes the token by its kind.
    • A rejected token offers Replace token.
    • Escape closes the field, but a composition's Escape (Safari's keyCode 229) is left to the input method (per review).
  • packages/host/src/host-operator.test.ts: a save forwards kind, and an API key save sends none. This test fails without the fix.

pnpm check:affected is green. The developer waived Preview runs for this rollout, so this PR merges on green.

Made with Luke


Devin Review