diff --git a/src/cli/anthropic-auth.ts b/src/cli/anthropic-auth.ts index 25fafb0..f88451c 100644 --- a/src/cli/anthropic-auth.ts +++ b/src/cli/anthropic-auth.ts @@ -1,6 +1,11 @@ import type { Command } from 'commander'; import readline from 'readline'; -import { loadStoredAnthropicAuth, storeAnthropicAuth } from '../auth/index.js'; +import { + loadStoredAnthropicAuth, + loadStoredAnthropicAuthToken, + storeAnthropicAuth, + storeAnthropicAuthToken, +} from '../auth/index.js'; async function promptHidden(question: string): Promise { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true }); @@ -29,13 +34,23 @@ async function promptHidden(question: string): Promise { export function registerAnthropicAuthCommand(program: Command): void { program .command('anthropic-auth') - .description('Store an Anthropic API key (auth.json)') - .action(async () => { - const existing = loadStoredAnthropicAuth(); - if (existing) { - console.log('Anthropic credential already exists.'); - console.log('Delete ~/.config/flynn/auth.json anthropic entry if you want to re-authenticate.'); - process.exit(0); + .description('Store an Anthropic API key or auth token (auth.json)') + .option('--token', 'Store an Anthropic auth token instead of an API key') + .action(async (opts: { token?: boolean }) => { + + if (opts.token) { + if (loadStoredAnthropicAuthToken()) { + console.log('Anthropic auth token already exists.'); + console.log('Delete ~/.config/flynn/auth.json anthropic.auth_token entry if you want to re-authenticate.'); + process.exit(0); + } + } else { + const existing = loadStoredAnthropicAuth(); + if (existing?.api_key) { + console.log('Anthropic API key already exists.'); + console.log('Delete ~/.config/flynn/auth.json anthropic.api_key entry if you want to re-authenticate.'); + process.exit(0); + } } console.log('Anthropic uses API keys for authentication.'); @@ -43,8 +58,13 @@ export function registerAnthropicAuthCommand(program: Command): void { console.log(''); try { - const apiKey = await promptHidden('Enter Anthropic API key: '); - storeAnthropicAuth(apiKey); + if (opts.token) { + const token = await promptHidden('Enter Anthropic auth token: '); + storeAnthropicAuthToken(token); + } else { + const apiKey = await promptHidden('Enter Anthropic API key: '); + storeAnthropicAuth(apiKey); + } console.log(''); console.log('Anthropic credential stored in ~/.config/flynn/auth.json'); } catch (error) { diff --git a/src/cli/index.test.ts b/src/cli/index.test.ts index 2c99d1b..fdcd9e0 100644 --- a/src/cli/index.test.ts +++ b/src/cli/index.test.ts @@ -13,6 +13,11 @@ describe('CLI program', () => { expect(commandNames).toContain('doctor'); expect(commandNames).toContain('config'); expect(commandNames).toContain('skills'); + + expect(commandNames).toContain('openai-auth'); + expect(commandNames).toContain('openai-key'); + expect(commandNames).toContain('anthropic-auth'); + expect(commandNames).toContain('zai-auth'); }); it('has version info', () => { diff --git a/src/cli/index.ts b/src/cli/index.ts index 7a0a278..727c4b0 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -19,6 +19,7 @@ import { registerGdocsAuthCommand } from './gdocs-auth.js'; import { registerGdriveAuthCommand } from './gdrive-auth.js'; import { registerGtasksAuthCommand } from './gtasks-auth.js'; import { registerOpenaiAuthCommand } from './openai-auth.js'; +import { registerOpenaiKeyCommand } from './openai-key.js'; import { registerZaiAuthCommand } from './zai-auth.js'; import { registerAnthropicAuthCommand } from './anthropic-auth.js'; import { registerSkillsCommand } from './skills.js'; @@ -45,6 +46,7 @@ export function createProgram(): Command { registerGdriveAuthCommand(program); registerGtasksAuthCommand(program); registerOpenaiAuthCommand(program); + registerOpenaiKeyCommand(program); registerZaiAuthCommand(program); registerAnthropicAuthCommand(program); registerSkillsCommand(program); diff --git a/src/cli/openai-key.ts b/src/cli/openai-key.ts new file mode 100644 index 0000000..824f4be --- /dev/null +++ b/src/cli/openai-key.ts @@ -0,0 +1,56 @@ +import type { Command } from 'commander'; +import readline from 'readline'; +import { loadStoredOpenAIApiKey, storeOpenAIApiKey } from '../auth/index.js'; + +async function promptHidden(question: string): Promise { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true }); + const rlAny = rl as unknown as { stdoutMuted?: boolean; _writeToOutput?: (s: string) => void }; + rlAny.stdoutMuted = true; + + rlAny._writeToOutput = (s: string) => { + if (!rlAny.stdoutMuted) { + process.stdout.write(s); + return; + } + if (s.includes('\n')) { + process.stdout.write('\n'); + } else { + process.stdout.write('*'); + } + }; + + const answer = await new Promise((resolve) => rl.question(question, resolve)); + rlAny.stdoutMuted = false; + rl.close(); + process.stdout.write('\n'); + return answer.trim(); +} + +export function registerOpenaiKeyCommand(program: Command): void { + program + .command('openai-key') + .description('Store an OpenAI API key (auth.json)') + .action(async () => { + const existing = loadStoredOpenAIApiKey(); + if (existing) { + console.log('OpenAI API key already exists.'); + console.log('Delete ~/.config/flynn/auth.json openai.api_key entry if you want to re-authenticate.'); + process.exit(0); + } + + console.log('OpenAI uses API keys for standard API access.'); + console.log('Create a key at: https://platform.openai.com/api-keys'); + console.log(''); + + try { + const apiKey = await promptHidden('Enter OpenAI API key: '); + storeOpenAIApiKey(apiKey); + console.log(''); + console.log('OpenAI API key stored in ~/.config/flynn/auth.json'); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`OpenAI API key storage failed: ${message}`); + process.exit(1); + } + }); +}