diff --git a/packages/cli/src/commands/$default.ts b/packages/cli/src/commands/$default.ts index 48cb3ef..b50ac44 100644 --- a/packages/cli/src/commands/$default.ts +++ b/packages/cli/src/commands/$default.ts @@ -107,12 +107,25 @@ export const command: CommandModule = { if (providerSettings) { const { keyName } = providerSettings; - const apiKey = process.env[keyName]; + + // First check if the API key is in the config + const configApiKey = userConfig[keyName as keyof typeof userConfig] as string; + // Then fall back to environment variable + const envApiKey = process.env[keyName]; + // Use config key if available, otherwise use env key + const apiKey = configApiKey || envApiKey; if (!apiKey) { logger.error(getProviderApiKeyError(userModelProvider)); throw new Error(`${userModelProvider} API key not found`); } + + // If we're using a key from config, set it as an environment variable + // This ensures it's available to the provider libraries + if (configApiKey && !envApiKey) { + process.env[keyName] = configApiKey; + logger.debug(`Using ${keyName} from configuration`); + } } // No API key check needed for Ollama as it uses a local server diff --git a/packages/cli/src/commands/config.ts b/packages/cli/src/commands/config.ts index 8ff72d5..caed533 100644 --- a/packages/cli/src/commands/config.ts +++ b/packages/cli/src/commands/config.ts @@ -73,6 +73,10 @@ export const command: CommandModule = { '$0 config clear customPrompt', 'Reset customPrompt to default value', ) + .example( + '$0 config set ANTHROPIC_API_KEY ', + 'Store your Anthropic API key in configuration', + ) .example( '$0 config clear --all', 'Clear all configuration settings', @@ -151,6 +155,30 @@ export const command: CommandModule = { return; } + // Check if this is an API key and add a warning + if (argv.key.includes('API_KEY')) { + logger.warn( + chalk.yellow( + 'Warning: Storing API keys in configuration is less secure than using environment variables.' + ) + ); + logger.warn( + chalk.yellow( + 'Your API key will be stored in plaintext in the configuration file.' + ) + ); + + // Ask for confirmation + const isConfirmed = await confirm( + 'Do you want to continue storing your API key in the configuration?' + ); + + if (!isConfirmed) { + logger.info('Operation cancelled.'); + return; + } + } + // Parse the value based on current type or infer boolean/number let parsedValue: string | boolean | number = argv.value; diff --git a/packages/cli/src/settings/config.ts b/packages/cli/src/settings/config.ts index 4501378..7c3c730 100644 --- a/packages/cli/src/settings/config.ts +++ b/packages/cli/src/settings/config.ts @@ -20,6 +20,11 @@ const defaultConfig = { customPrompt: '', profile: false, tokenCache: true, + // API keys (empty by default) + ANTHROPIC_API_KEY: '', + OPENAI_API_KEY: '', + XAI_API_KEY: '', + MISTRAL_API_KEY: '', }; export type Config = typeof defaultConfig; diff --git a/packages/cli/tests/settings/config.test.ts b/packages/cli/tests/settings/config.test.ts index 814c82a..75b0c1b 100644 --- a/packages/cli/tests/settings/config.test.ts +++ b/packages/cli/tests/settings/config.test.ts @@ -49,6 +49,11 @@ describe('Config', () => { profile: false, customPrompt: '', tokenCache: true, + // API keys + ANTHROPIC_API_KEY: '', + OPENAI_API_KEY: '', + XAI_API_KEY: '', + MISTRAL_API_KEY: '', }); expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFile); }); @@ -86,6 +91,11 @@ describe('Config', () => { profile: false, customPrompt: '', tokenCache: true, + // API keys + ANTHROPIC_API_KEY: '', + OPENAI_API_KEY: '', + XAI_API_KEY: '', + MISTRAL_API_KEY: '', }); }); }); @@ -117,4 +127,4 @@ describe('Config', () => { expect(result).toEqual({ githubMode: true, existingSetting: 'value' }); }); }); -}); +}); \ No newline at end of file