# `/login` Auth Mode Switching Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Extend the `/login mode ` slash command to set `auth_mode` (api_key/oauth/auto) in `config.yaml` for all model tiers using a given provider, with a warning for providers that don't support auth_mode. **Architecture:** Add a `mode` field to the `login` command type; extend `parseCommand` to recognise `/login mode `; add a standalone `setProviderAuthMode()` helper in `minimal.ts` that updates all matching tiers via `persistConfig`; thread `configPath` + `config` through `MinimalTuiConfig` so the handler can persist; skip the auth_mode prompt for providers not in `AUTH_MODE_PROVIDERS`. **Tech Stack:** TypeScript, Vitest, `yaml` (via `persistConfig`), existing `src/config/persistence.ts`, `src/frontends/tui/commands.ts`, `src/frontends/tui/minimal.ts`, `src/cli/tui.ts`. --- ### Task 1: Extend the `login` command type and parser **Files:** - Modify: `src/frontends/tui/commands.ts:18` (Command union type) - Modify: `src/frontends/tui/commands.ts:178-182` (parseCommand login branch) - Modify: `src/frontends/tui/commands.ts:240` (help text) - Modify: `src/frontends/tui/commands.ts:289` (SLASH_COMMANDS list — no change needed) - Modify: `src/frontends/tui/commands.ts:321` (COMMAND_TOOLTIPS) - Test: `src/frontends/tui/commands.test.ts` **Step 1: Write the failing tests** Add to `commands.test.ts` inside the existing `describe('parseCommand', ...)` block: ```typescript it('parses /login with mode subcommand', () => { expect(parseCommand('/login anthropic mode oauth')).toEqual({ type: 'login', provider: 'anthropic', mode: 'oauth', }); expect(parseCommand('/login openai mode api_key')).toEqual({ type: 'login', provider: 'openai', mode: 'api_key', }); expect(parseCommand('/login anthropic mode auto')).toEqual({ type: 'login', provider: 'anthropic', mode: 'auto', }); }); it('parses /login without mode unchanged', () => { expect(parseCommand('/login')).toEqual({ type: 'login' }); expect(parseCommand('/login anthropic')).toEqual({ type: 'login', provider: 'anthropic' }); }); ``` **Step 2: Run test to verify it fails** ```bash pnpm test:run src/frontends/tui/commands.test.ts ``` Expected: FAIL — `mode` property not present on result. **Step 3: Update the Command union type** In `commands.ts` line 18, change: ```typescript | { type: 'login'; provider?: string } ``` to: ```typescript | { type: 'login'; provider?: string; mode?: 'api_key' | 'oauth' | 'auto' } ``` **Step 4: Update parseCommand** Replace the existing login block (lines ~177–183): ```typescript // Login if (trimmed === '/login') { return { type: 'login' }; } if (trimmed.startsWith('/login ')) { const provider = trimmed.slice('/login '.length).trim(); return { type: 'login', provider: provider || undefined }; } ``` with: ```typescript // Login if (trimmed === '/login') { return { type: 'login' }; } if (trimmed.startsWith('/login ')) { const rest = trimmed.slice('/login '.length).trim(); // /login mode const modeMatch = rest.match(/^(\S+)\s+mode\s+(\S+)$/); if (modeMatch) { const modeValue = modeMatch[2].toLowerCase(); if (modeValue === 'api_key' || modeValue === 'oauth' || modeValue === 'auto') { return { type: 'login', provider: modeMatch[1] || undefined, mode: modeValue }; } } return { type: 'login', provider: rest || undefined }; } ``` **Step 5: Update help text** In `getHelpText()`, update the `/login` line to: ``` /login [provider] Authenticate (github, openai, anthropic, zai) /login

mode Set auth mode for provider (api_key|oauth|auto) ``` Update `COMMAND_TOOLTIPS['/login']` to: ```typescript '/login': 'Authenticate with provider; use "mode api_key|oauth|auto" to switch auth mode', ``` **Step 6: Run tests to verify they pass** ```bash pnpm test:run src/frontends/tui/commands.test.ts ``` Expected: all PASS. **Step 7: Commit** ```bash git add src/frontends/tui/commands.ts src/frontends/tui/commands.test.ts git commit -m "feat(tui): extend /login parser to accept mode subcommand" ``` --- ### Task 2: Thread `configPath` + `config` through `MinimalTuiConfig` **Files:** - Modify: `src/frontends/tui/minimal.ts:63-81` (MinimalTuiConfig interface) - Modify: `src/cli/tui.ts:437-458` (MinimalTui constructor call) No new tests needed — this is plumbing only. Existing tests cover no regression. **Step 1: Add fields to `MinimalTuiConfig`** In `minimal.ts`, import `Config` and `persistConfig` at the top. The file already imports from `../../config/index.js` — add `Config` to that import and add a new import for `persistConfig`: ```typescript import type { Config, ModelConfig, ModelProvider } from '../../config/index.js'; import { persistConfig } from '../../config/persistence.js'; ``` Then in `MinimalTuiConfig` add: ```typescript configPath?: string; currentConfig?: Config; ``` **Step 2: Pass `configPath` and `config` when constructing `MinimalTui` in `tui.ts`** In `tui.ts` line ~437, add two properties to the constructor object: ```typescript configPath, currentConfig: config, ``` **Step 3: Typecheck** ```bash pnpm typecheck ``` Expected: no errors. **Step 4: Commit** ```bash git add src/frontends/tui/minimal.ts src/cli/tui.ts git commit -m "feat(tui): thread configPath and currentConfig into MinimalTuiConfig" ``` --- ### Task 3: Implement `setProviderAuthMode` and wire into the login handler **Files:** - Modify: `src/frontends/tui/minimal.ts` (new helper + updated handler) - Test: `src/frontends/tui/minimal.test.ts` **Step 1: Write the failing test** `minimal.test.ts` tests the TUI at a higher level. Add a focused unit test for the new helper by extracting it. For now, add to `minimal.test.ts`: ```typescript import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock persistConfig so we can assert it was called correctly const { mockPersistConfig } = vi.hoisted(() => ({ mockPersistConfig: vi.fn() })); vi.mock('../../config/persistence.js', () => ({ persistConfig: mockPersistConfig })); describe('applyAuthModeToConfig', () => { it('sets auth_mode on all tiers whose provider matches', () => { const config = { models: { default: { provider: 'anthropic', model: 'claude-sonnet-4' }, fast: { provider: 'openai', model: 'gpt-4o-mini' }, complex: { provider: 'anthropic', model: 'claude-opus-4' }, }, } as unknown as import('../../config/schema.js').Config; const updated = applyAuthModeToConfig(config, 'anthropic', 'oauth'); expect(updated.models.default.auth_mode).toBe('oauth'); expect(updated.models.complex.auth_mode).toBe('oauth'); // openai tier must be untouched expect((updated.models.fast as { auth_mode?: string }).auth_mode).toBeUndefined(); }); it('updates local_providers entries that match', () => { const config = { models: { default: { provider: 'openai', model: 'gpt-4o' }, local_providers: { myAnthropic: { provider: 'anthropic', model: 'claude-haiku' }, }, }, } as unknown as import('../../config/schema.js').Config; const updated = applyAuthModeToConfig(config, 'anthropic', 'api_key'); expect(updated.models.local_providers!['myAnthropic'].auth_mode).toBe('api_key'); expect((updated.models.default as { auth_mode?: string }).auth_mode).toBeUndefined(); }); }); ``` Note: `applyAuthModeToConfig` must be exported from `minimal.ts` for test access. **Step 2: Run test to verify it fails** ```bash pnpm test:run src/frontends/tui/minimal.test.ts ``` Expected: FAIL — `applyAuthModeToConfig` is not exported. **Step 3: Implement `applyAuthModeToConfig` and `AUTH_MODE_PROVIDERS`** Add near the top of `minimal.ts` (after imports): ```typescript /** Providers that honour auth_mode at runtime. All others get a warning. */ export const AUTH_MODE_PROVIDERS: ReadonlySet = new Set(['anthropic', 'openai']); /** * Return a new Config with auth_mode set on every model tier whose provider * matches targetProvider. Does not mutate the original. */ export function applyAuthModeToConfig( config: Config, targetProvider: string, mode: 'api_key' | 'oauth' | 'auto', ): Config { const applyToTier = (tier: ModelConfig): ModelConfig => tier.provider === targetProvider ? { ...tier, auth_mode: mode } : tier; const updatedModels = { ...config.models }; if (updatedModels.default) { updatedModels.default = applyToTier(updatedModels.default); } for (const key of ['fast', 'complex', 'local'] as const) { const tier = updatedModels[key]; if (tier) { updatedModels[key] = applyToTier(tier); } } if (updatedModels.local_providers) { const updatedLocalProviders: Record = {}; for (const [name, tier] of Object.entries(updatedModels.local_providers)) { updatedLocalProviders[name] = applyToTier(tier); } updatedModels.local_providers = updatedLocalProviders; } return { ...config, models: updatedModels }; } ``` **Step 4: Run tests to verify they pass** ```bash pnpm test:run src/frontends/tui/minimal.test.ts ``` Expected: PASS. **Step 5: Wire into `handleLoginCommand`** Update the switch dispatch in `minimal.ts` line ~537: ```typescript case 'login': await this.handleLoginCommand(command.provider, command.mode); break; ``` Update the method signature: ```typescript private async handleLoginCommand( provider?: string, mode?: 'api_key' | 'oauth' | 'auto', ): Promise { ``` At the very top of `handleLoginCommand`, before the existing `target` resolution, add the mode-switch fast path: ```typescript if (mode !== undefined) { const resolvedProvider = provider ?? 'anthropic'; if (!AUTH_MODE_PROVIDERS.has(resolvedProvider)) { console.log( `${colors.gray}auth_mode has no effect for ${resolvedProvider}. ` + `It is only supported for: ${[...AUTH_MODE_PROVIDERS].join(', ')}.${colors.reset}\n`, ); return; } if (!this.config.currentConfig || !this.config.configPath) { console.log(`${colors.gray}Config not available — cannot persist auth_mode.${colors.reset}\n`); return; } const updated = applyAuthModeToConfig(this.config.currentConfig, resolvedProvider, mode); persistConfig(this.config.configPath, updated); console.log( `${colors.gray}auth_mode for ${resolvedProvider} set to ${colors.reset}${mode}` + `${colors.gray}. Restart Flynn for the change to take effect.${colors.reset}\n`, ); return; } ``` **Step 6: Add post-credential auth_mode prompt for supported providers** In the `anthropic` branch of `handleLoginCommand`, after the credential is stored and before `return`, add (for both the api_key and token paths): ```typescript // Offer to set auth_mode if config is available and provider supports it if (this.config.currentConfig && this.config.configPath) { const modeInput = (await this.prompt( `${colors.orange}Set active auth mode?${colors.reset} ${colors.gray}[api_key/oauth/auto/skip] (default: skip):${colors.reset} `, )).trim().toLowerCase(); if (modeInput === 'api_key' || modeInput === 'oauth' || modeInput === 'auto') { const updated = applyAuthModeToConfig(this.config.currentConfig, 'anthropic', modeInput); persistConfig(this.config.configPath, updated); console.log(`${colors.gray}auth_mode set to ${modeInput}. Restart Flynn to apply.${colors.reset}\n`); } } ``` Apply the same block to the `openai` branch (using `'openai'` as the provider string). Do **not** add this block to `github` or `zai` branches (they're not in `AUTH_MODE_PROVIDERS`). **Step 7: Typecheck** ```bash pnpm typecheck ``` Expected: no errors. **Step 8: Run full test suite** ```bash pnpm test:run ``` Expected: all pass. **Step 9: Commit** ```bash git add src/frontends/tui/minimal.ts src/frontends/tui/minimal.test.ts git commit -m "feat(tui): implement /login mode auth mode switching" ``` --- ### Task 4: Completions for the mode subcommand **Files:** - Modify: `src/frontends/tui/commands.ts` (getCommandCompletions + getCommandTooltip) - Test: `src/frontends/tui/commands.test.ts` **Step 1: Write the failing test** Add to `commands.test.ts` inside `describe('getCommandCompletions', ...)`: ```typescript it('completes /login mode values', () => { const completions = getCommandCompletions('/login anthropic mode '); expect(completions).toContain('/login anthropic mode api_key'); expect(completions).toContain('/login anthropic mode oauth'); expect(completions).toContain('/login anthropic mode auto'); }); it('filters mode completions by partial input', () => { const completions = getCommandCompletions('/login anthropic mode o'); expect(completions).toEqual(['/login anthropic mode oauth']); }); ``` **Step 2: Run test to verify it fails** ```bash pnpm test:run src/frontends/tui/commands.test.ts ``` Expected: FAIL. **Step 3: Implement completions** In `getCommandCompletions`, add before the generic slash-command fallback: ```typescript // Complete /login mode if (trimmed.startsWith('/login ')) { const rest = trimmed.slice('/login '.length); const parts = rest.split(/\s+/); if (parts.length === 3 && parts[1] === 'mode') { const partial = parts[2].toLowerCase(); const modes = ['api_key', 'oauth', 'auto']; return modes .filter(m => m.startsWith(partial)) .map(m => `/login ${parts[0]} mode ${m}`); } if (parts.length === 2 && parts[1] === 'mod') { return [`/login ${parts[0]} mode`]; } } ``` **Step 4: Run tests** ```bash pnpm test:run src/frontends/tui/commands.test.ts ``` Expected: all PASS. **Step 5: Run full suite and typecheck** ```bash pnpm test:run && pnpm typecheck ``` Expected: all pass, no type errors. **Step 6: Commit** ```bash git add src/frontends/tui/commands.ts src/frontends/tui/commands.test.ts git commit -m "feat(tui): add tab completions for /login mode subcommand" ``` --- ### Verification ```bash pnpm test:run # full suite passes pnpm typecheck # no type errors pnpm lint # no lint errors ``` Manual smoke test: 1. `pnpm tui` → type `/login anthropic mode oauth` → confirm config written + restart message 2. `pnpm tui` → type `/login zhipuai mode oauth` → confirm warning printed, no config write 3. `pnpm tui` → type `/login anthropic` → confirm auth_mode prompt appears after credential entry