diff --git a/src/frontends/tui/commands.test.ts b/src/frontends/tui/commands.test.ts index 9478979..eca95e8 100644 --- a/src/frontends/tui/commands.test.ts +++ b/src/frontends/tui/commands.test.ts @@ -188,6 +188,25 @@ describe('/pair command', () => { }); }); +describe('/login command with mode subcommand', () => { + 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' }); + }); +}); + describe('PROVIDER_NAMES', () => { it('matches all providers from the config schema', () => { expect(PROVIDER_NAMES).toEqual(MODEL_PROVIDERS); diff --git a/src/frontends/tui/commands.ts b/src/frontends/tui/commands.ts index bebe25c..b675a0f 100644 --- a/src/frontends/tui/commands.ts +++ b/src/frontends/tui/commands.ts @@ -15,7 +15,7 @@ export type Command = | { type: 'model'; name?: string; providerModel?: string } | { type: 'backend'; provider?: string } | { type: 'runtime'; input?: string } - | { type: 'login'; provider?: string } + | { type: 'login'; provider?: string; mode?: 'api_key' | 'oauth' | 'auto' } | { type: 'transfer'; target: string } | { type: 'pair'; action?: 'generate' | 'list' | 'revoke'; args?: string } | { type: 'queue'; action?: 'show' | 'set' | 'reset'; args?: string } @@ -178,8 +178,16 @@ export function parseCommand(input: string): Command | null { return { type: 'login' }; } if (trimmed.startsWith('/login ')) { - const provider = trimmed.slice('/login '.length).trim(); - return { type: 'login', provider: provider || undefined }; + 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 }; } // Pair @@ -237,7 +245,8 @@ Commands: /research Delegate a task to the configured research agent /council Run the councils pipeline for a task /council preflight Check council tier routing, endpoint/auth mode, and probe latency - /login [provider] Authenticate with GitHub, OpenAI, Anthropic, or Z.AI + /login [provider] Authenticate (github, openai, anthropic, zai) + /login

mode Set auth mode for provider (api_key|oauth|auto) /pair List pending pairing codes and approved senders /pair generate [label] Generate a new DM pairing code /pair revoke Revoke an approved sender @@ -318,7 +327,7 @@ export const COMMAND_TOOLTIPS: Record = { '/status': 'Show session info and token usage', '/fullscreen': 'Switch to fullscreen mode', '/fs': 'Switch to fullscreen mode', - '/login': 'Authenticate with GitHub/OpenAI/Anthropic (OAuth/token or API key) or Z.AI (API key store)', + '/login': 'Authenticate with provider; use "mode api_key|oauth|auto" to switch auth mode', '/pair': 'Generate/list/revoke DM pairing codes', '/queue': 'Show or update per-session queue policy', '/approvals': 'List pending guarded actions for this session',