Introduces a configurable fallback model and adds support for additional AI provider API keys in the environment setup.
- **Add Fallback Model Configuration (.taskmasterconfig):**
- Implemented a new section in .
- Configured as the default fallback model, enhancing resilience if the primary model fails.
- **Update Default Model Configuration (.taskmasterconfig):**
- Changed the default model to .
- Changed the default model to .
- **Add API Key Examples (assets/env.example):**
- Added example environment variables for:
- (for OpenAI/OpenRouter)
- (for Google Gemini)
- (for XAI Grok)
- Included format comments for clarity.
709 lines
21 KiB
JavaScript
709 lines
21 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import chalk from 'chalk';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
// Calculate __dirname in ESM
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Load supported models from JSON file using the calculated __dirname
|
|
let MODEL_MAP;
|
|
try {
|
|
const supportedModelsRaw = fs.readFileSync(
|
|
path.join(__dirname, 'supported-models.json'),
|
|
'utf-8'
|
|
);
|
|
MODEL_MAP = JSON.parse(supportedModelsRaw);
|
|
} catch (error) {
|
|
console.error(
|
|
chalk.red(
|
|
'FATAL ERROR: Could not load supported-models.json. Please ensure the file exists and is valid JSON.'
|
|
),
|
|
error
|
|
);
|
|
MODEL_MAP = {}; // Default to empty map on error to avoid crashing, though functionality will be limited
|
|
process.exit(1); // Exit if models can't be loaded
|
|
}
|
|
|
|
const CONFIG_FILE_NAME = '.taskmasterconfig';
|
|
|
|
// Default configuration
|
|
const DEFAULT_MAIN_PROVIDER = 'anthropic';
|
|
const DEFAULT_MAIN_MODEL_ID = 'claude-3.7-sonnet-20250219';
|
|
const DEFAULT_RESEARCH_PROVIDER = 'perplexity';
|
|
const DEFAULT_RESEARCH_MODEL_ID = 'sonar-pro';
|
|
|
|
// Define ONE list of all supported providers
|
|
const VALID_PROVIDERS = [
|
|
'anthropic',
|
|
'openai',
|
|
'google',
|
|
'perplexity',
|
|
'ollama',
|
|
'openrouter',
|
|
'grok'
|
|
];
|
|
|
|
let projectRoot = null;
|
|
|
|
function findProjectRoot() {
|
|
// Keep this function as is for CLI context
|
|
if (projectRoot) return projectRoot;
|
|
|
|
let currentDir = process.cwd();
|
|
while (currentDir !== path.parse(currentDir).root) {
|
|
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
|
projectRoot = currentDir;
|
|
return projectRoot;
|
|
}
|
|
currentDir = path.dirname(currentDir);
|
|
}
|
|
|
|
// Check root directory as a last resort
|
|
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
|
projectRoot = currentDir;
|
|
return projectRoot;
|
|
}
|
|
|
|
// If still not found, maybe look for other markers or return null
|
|
// For now, returning null if package.json isn't found up to the root
|
|
projectRoot = null;
|
|
return null;
|
|
}
|
|
|
|
function readConfig(explicitRoot = null) {
|
|
// Determine the root path to use
|
|
const rootToUse = explicitRoot || findProjectRoot();
|
|
|
|
const defaults = {
|
|
models: {
|
|
main: { provider: DEFAULT_MAIN_PROVIDER, modelId: DEFAULT_MAIN_MODEL_ID },
|
|
research: {
|
|
provider: DEFAULT_RESEARCH_PROVIDER,
|
|
modelId: DEFAULT_RESEARCH_MODEL_ID
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!rootToUse) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
'Warning: Could not determine project root. Using default configuration.'
|
|
)
|
|
);
|
|
return defaults;
|
|
}
|
|
const configPath = path.join(rootToUse, CONFIG_FILE_NAME);
|
|
|
|
if (fs.existsSync(configPath)) {
|
|
try {
|
|
const rawData = fs.readFileSync(configPath, 'utf-8');
|
|
const parsedConfig = JSON.parse(rawData);
|
|
|
|
// Deep merge defaults to ensure structure and handle partial configs
|
|
const config = {
|
|
models: {
|
|
main: {
|
|
provider:
|
|
parsedConfig?.models?.main?.provider ??
|
|
defaults.models.main.provider,
|
|
modelId:
|
|
parsedConfig?.models?.main?.modelId ??
|
|
defaults.models.main.modelId
|
|
},
|
|
research: {
|
|
provider:
|
|
parsedConfig?.models?.research?.provider ??
|
|
defaults.models.research.provider,
|
|
modelId:
|
|
parsedConfig?.models?.research?.modelId ??
|
|
defaults.models.research.modelId
|
|
},
|
|
// Add merge logic for the fallback model
|
|
fallback: {
|
|
provider: parsedConfig?.models?.fallback?.provider,
|
|
modelId: parsedConfig?.models?.fallback?.modelId
|
|
}
|
|
}
|
|
};
|
|
|
|
// Validate loaded providers (main, research, and fallback if it exists)
|
|
if (!validateProvider(config.models.main.provider)) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Invalid main provider "${config.models.main.provider}" in ${CONFIG_FILE_NAME}. Falling back to default.`
|
|
)
|
|
);
|
|
config.models.main = {
|
|
provider: defaults.models.main.provider,
|
|
modelId: defaults.models.main.modelId
|
|
};
|
|
}
|
|
// Optional: Add warning for model combination if desired, but don't block
|
|
// else if (!validateProviderModelCombination(config.models.main.provider, config.models.main.modelId)) { ... }
|
|
|
|
if (!validateProvider(config.models.research.provider)) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Invalid research provider "${config.models.research.provider}" in ${CONFIG_FILE_NAME}. Falling back to default.`
|
|
)
|
|
);
|
|
config.models.research = {
|
|
provider: defaults.models.research.provider,
|
|
modelId: defaults.models.research.modelId
|
|
};
|
|
}
|
|
// Optional: Add warning for model combination if desired, but don't block
|
|
// else if (!validateProviderModelCombination(config.models.research.provider, config.models.research.modelId)) { ... }
|
|
|
|
// Add validation for fallback provider if it exists
|
|
if (
|
|
config.models.fallback &&
|
|
config.models.fallback.provider &&
|
|
!validateProvider(config.models.fallback.provider)
|
|
) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Invalid fallback provider "${config.models.fallback.provider}" in ${CONFIG_FILE_NAME}. Fallback model will be ignored.`
|
|
)
|
|
);
|
|
// Unlike main/research, we don't set a default fallback, just ignore it
|
|
delete config.models.fallback;
|
|
}
|
|
|
|
return config;
|
|
} catch (error) {
|
|
console.error(
|
|
chalk.red(
|
|
`Error reading or parsing ${configPath}: ${error.message}. Using default configuration.`
|
|
)
|
|
);
|
|
return defaults;
|
|
}
|
|
} else {
|
|
return defaults;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates if a provider name is in the list of supported providers.
|
|
* @param {string} providerName The name of the provider.
|
|
* @returns {boolean} True if the provider is valid, false otherwise.
|
|
*/
|
|
function validateProvider(providerName) {
|
|
return VALID_PROVIDERS.includes(providerName);
|
|
}
|
|
|
|
/**
|
|
* Optional: Validates if a modelId is known for a given provider based on MODEL_MAP.
|
|
* This is a non-strict validation; an unknown model might still be valid.
|
|
* @param {string} providerName The name of the provider.
|
|
* @param {string} modelId The model ID.
|
|
* @returns {boolean} True if the modelId is in the map for the provider, false otherwise.
|
|
*/
|
|
function validateProviderModelCombination(providerName, modelId) {
|
|
// If provider isn't even in our map, we can't validate the model
|
|
if (!MODEL_MAP[providerName]) {
|
|
return true; // Allow unknown providers or those without specific model lists
|
|
}
|
|
// If the provider is known, check if the model is in its list OR if the list is empty (meaning accept any)
|
|
return (
|
|
MODEL_MAP[providerName].length === 0 ||
|
|
// Use .some() to check the 'id' property of objects in the array
|
|
MODEL_MAP[providerName].some((modelObj) => modelObj.id === modelId)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets the currently configured main AI provider.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {string} The name of the main provider.
|
|
*/
|
|
function getMainProvider(explicitRoot = null) {
|
|
const config = readConfig(explicitRoot);
|
|
return config.models.main.provider;
|
|
}
|
|
|
|
/**
|
|
* Gets the currently configured main AI model ID.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {string} The ID of the main model.
|
|
*/
|
|
function getMainModelId(explicitRoot = null) {
|
|
const config = readConfig(explicitRoot);
|
|
return config.models.main.modelId;
|
|
}
|
|
|
|
/**
|
|
* Gets the currently configured research AI provider.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {string} The name of the research provider.
|
|
*/
|
|
function getResearchProvider(explicitRoot = null) {
|
|
const config = readConfig(explicitRoot);
|
|
return config.models.research.provider;
|
|
}
|
|
|
|
/**
|
|
* Gets the currently configured research AI model ID.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {string} The ID of the research model.
|
|
*/
|
|
function getResearchModelId(explicitRoot = null) {
|
|
const config = readConfig(explicitRoot);
|
|
return config.models.research.modelId;
|
|
}
|
|
|
|
/**
|
|
* Gets the currently configured fallback AI provider.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {string|undefined} The name of the fallback provider, or undefined if not set.
|
|
*/
|
|
function getFallbackProvider(explicitRoot = null) {
|
|
const config = readConfig(explicitRoot);
|
|
return config.models?.fallback?.provider;
|
|
}
|
|
|
|
/**
|
|
* Gets the currently configured fallback AI model ID.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {string|undefined} The ID of the fallback model, or undefined if not set.
|
|
*/
|
|
function getFallbackModelId(explicitRoot = null) {
|
|
const config = readConfig(explicitRoot);
|
|
return config.models?.fallback?.modelId;
|
|
}
|
|
|
|
/**
|
|
* Sets the main AI model (provider and modelId) in the configuration file.
|
|
* @param {string} providerName The name of the provider to set.
|
|
* @param {string} modelId The ID of the model to set.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {boolean} True if successful, false otherwise.
|
|
*/
|
|
function setMainModel(providerName, modelId, explicitRoot = null) {
|
|
// --- 1. Validate Provider First ---
|
|
if (!validateProvider(providerName)) {
|
|
console.error(
|
|
chalk.red(`Error: "${providerName}" is not a valid provider.`)
|
|
);
|
|
console.log(
|
|
chalk.yellow(`Available providers: ${VALID_PROVIDERS.join(', ')}`)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// --- 2. Validate Role Second ---
|
|
const allModels = getAvailableModels(); // Get all models to check roles
|
|
const modelData = allModels.find(
|
|
(m) => m.id === modelId && m.provider === providerName
|
|
);
|
|
|
|
if (
|
|
!modelData ||
|
|
!modelData.allowed_roles ||
|
|
!modelData.allowed_roles.includes('main')
|
|
) {
|
|
console.error(
|
|
chalk.red(`Error: Model "${modelId}" is not allowed for the 'main' role.`)
|
|
);
|
|
// Try to suggest valid models for the role
|
|
const allowedMainModels = allModels
|
|
.filter((m) => m.allowed_roles?.includes('main'))
|
|
.map((m) => ` - ${m.provider} / ${m.id}`)
|
|
.join('\n');
|
|
if (allowedMainModels) {
|
|
console.log(
|
|
chalk.yellow('\nAllowed models for main role:\n' + allowedMainModels)
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- 3. Validate Model Combination (Optional Warning) ---
|
|
if (!validateProviderModelCombination(providerName, modelId)) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Model "${modelId}" is not in the known list for provider "${providerName}". Ensure it is valid.`
|
|
)
|
|
);
|
|
}
|
|
|
|
// --- Proceed with setting ---
|
|
const config = readConfig(explicitRoot);
|
|
config.models.main = { provider: providerName, modelId: modelId };
|
|
// Pass explicitRoot down
|
|
if (writeConfig(config, explicitRoot)) {
|
|
console.log(
|
|
chalk.green(`Main AI model set to: ${providerName} / ${modelId}`)
|
|
);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the research AI model (provider and modelId) in the configuration file.
|
|
* @param {string} providerName The name of the provider to set.
|
|
* @param {string} modelId The ID of the model to set.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {boolean} True if successful, false otherwise.
|
|
*/
|
|
function setResearchModel(providerName, modelId, explicitRoot = null) {
|
|
// --- 1. Validate Provider First ---
|
|
if (!validateProvider(providerName)) {
|
|
console.error(
|
|
chalk.red(`Error: "${providerName}" is not a valid provider.`)
|
|
);
|
|
console.log(
|
|
chalk.yellow(`Available providers: ${VALID_PROVIDERS.join(', ')}`)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// --- 2. Validate Role Second ---
|
|
const allModels = getAvailableModels(); // Get all models to check roles
|
|
const modelData = allModels.find(
|
|
(m) => m.id === modelId && m.provider === providerName
|
|
);
|
|
|
|
if (
|
|
!modelData ||
|
|
!modelData.allowed_roles ||
|
|
!modelData.allowed_roles.includes('research')
|
|
) {
|
|
console.error(
|
|
chalk.red(
|
|
`Error: Model "${modelId}" is not allowed for the 'research' role.`
|
|
)
|
|
);
|
|
// Try to suggest valid models for the role
|
|
const allowedResearchModels = allModels
|
|
.filter((m) => m.allowed_roles?.includes('research'))
|
|
.map((m) => ` - ${m.provider} / ${m.id}`)
|
|
.join('\n');
|
|
if (allowedResearchModels) {
|
|
console.log(
|
|
chalk.yellow(
|
|
'\nAllowed models for research role:\n' + allowedResearchModels
|
|
)
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- 3. Validate Model Combination (Optional Warning) ---
|
|
if (!validateProviderModelCombination(providerName, modelId)) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Model "${modelId}" is not in the known list for provider "${providerName}". Ensure it is valid.`
|
|
)
|
|
);
|
|
}
|
|
|
|
// --- 4. Specific Research Warning (Optional) ---
|
|
if (
|
|
providerName === 'anthropic' ||
|
|
(providerName === 'openai' && modelId.includes('3.5'))
|
|
) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Provider "${providerName}" with model "${modelId}" may not be ideal for research tasks. Perplexity or Grok recommended.`
|
|
)
|
|
);
|
|
}
|
|
|
|
// --- Proceed with setting ---
|
|
const config = readConfig(explicitRoot);
|
|
config.models.research = { provider: providerName, modelId: modelId };
|
|
// Pass explicitRoot down
|
|
if (writeConfig(config, explicitRoot)) {
|
|
console.log(
|
|
chalk.green(`Research AI model set to: ${providerName} / ${modelId}`)
|
|
);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the fallback AI model (provider and modelId) in the configuration file.
|
|
* @param {string} providerName The name of the provider to set.
|
|
* @param {string} modelId The ID of the model to set.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {boolean} True if successful, false otherwise.
|
|
*/
|
|
function setFallbackModel(providerName, modelId, explicitRoot = null) {
|
|
// --- 1. Validate Provider First ---
|
|
if (!validateProvider(providerName)) {
|
|
console.error(
|
|
chalk.red(`Error: "${providerName}" is not a valid provider.`)
|
|
);
|
|
console.log(
|
|
chalk.yellow(`Available providers: ${VALID_PROVIDERS.join(', ')}`)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// --- 2. Validate Role Second ---
|
|
const allModels = getAvailableModels(); // Get all models to check roles
|
|
const modelData = allModels.find(
|
|
(m) => m.id === modelId && m.provider === providerName
|
|
);
|
|
|
|
if (
|
|
!modelData ||
|
|
!modelData.allowed_roles ||
|
|
!modelData.allowed_roles.includes('fallback')
|
|
) {
|
|
console.error(
|
|
chalk.red(
|
|
`Error: Model "${modelId}" is not allowed for the 'fallback' role.`
|
|
)
|
|
);
|
|
// Try to suggest valid models for the role
|
|
const allowedFallbackModels = allModels
|
|
.filter((m) => m.allowed_roles?.includes('fallback'))
|
|
.map((m) => ` - ${m.provider} / ${m.id}`)
|
|
.join('\n');
|
|
if (allowedFallbackModels) {
|
|
console.log(
|
|
chalk.yellow(
|
|
'\nAllowed models for fallback role:\n' + allowedFallbackModels
|
|
)
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- 3. Validate Model Combination (Optional Warning) ---
|
|
if (!validateProviderModelCombination(providerName, modelId)) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Model "${modelId}" is not in the known list for provider "${providerName}". Ensure it is valid.`
|
|
)
|
|
);
|
|
}
|
|
|
|
// --- Proceed with setting ---
|
|
const config = readConfig(explicitRoot);
|
|
if (!config.models) {
|
|
config.models = {}; // Ensure models object exists
|
|
}
|
|
// Ensure fallback object exists
|
|
if (!config.models.fallback) {
|
|
config.models.fallback = {};
|
|
}
|
|
|
|
config.models.fallback = { provider: providerName, modelId: modelId };
|
|
|
|
return writeConfig(config, explicitRoot);
|
|
}
|
|
|
|
/**
|
|
* Gets a list of available models based on the MODEL_MAP.
|
|
* @returns {Array<{id: string, name: string, provider: string, swe_score: number|null, cost_per_1m_tokens: {input: number|null, output: number|null}|null, allowed_roles: string[]}>}
|
|
*/
|
|
function getAvailableModels() {
|
|
const available = [];
|
|
for (const [provider, models] of Object.entries(MODEL_MAP)) {
|
|
if (models.length > 0) {
|
|
models.forEach((modelObj) => {
|
|
// Basic name generation - can be improved
|
|
const modelId = modelObj.id;
|
|
const sweScore = modelObj.swe_score;
|
|
const cost = modelObj.cost_per_1m_tokens;
|
|
const allowedRoles = modelObj.allowed_roles || ['main', 'fallback'];
|
|
const nameParts = modelId
|
|
.split('-')
|
|
.map((p) => p.charAt(0).toUpperCase() + p.slice(1));
|
|
// Handle specific known names better if needed
|
|
let name = nameParts.join(' ');
|
|
if (modelId === 'claude-3.5-sonnet-20240620')
|
|
name = 'Claude 3.5 Sonnet';
|
|
if (modelId === 'claude-3-7-sonnet-20250219')
|
|
name = 'Claude 3.7 Sonnet';
|
|
if (modelId === 'gpt-4o') name = 'GPT-4o';
|
|
if (modelId === 'gpt-4-turbo') name = 'GPT-4 Turbo';
|
|
if (modelId === 'sonar-pro') name = 'Perplexity Sonar Pro';
|
|
if (modelId === 'sonar-mini') name = 'Perplexity Sonar Mini';
|
|
|
|
available.push({
|
|
id: modelId,
|
|
name: name,
|
|
provider: provider,
|
|
swe_score: sweScore,
|
|
cost_per_1m_tokens: cost,
|
|
allowed_roles: allowedRoles
|
|
});
|
|
});
|
|
} else {
|
|
// For providers with empty lists (like ollama), maybe add a placeholder or skip
|
|
available.push({
|
|
id: `[${provider}-any]`,
|
|
name: `Any (${provider})`,
|
|
provider: provider
|
|
});
|
|
}
|
|
}
|
|
return available;
|
|
}
|
|
|
|
/**
|
|
* Writes the configuration object to the file.
|
|
* @param {Object} config The configuration object to write.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {boolean} True if successful, false otherwise.
|
|
*/
|
|
function writeConfig(config, explicitRoot = null) {
|
|
const rootPath = explicitRoot || findProjectRoot();
|
|
if (!rootPath) {
|
|
console.error(
|
|
chalk.red(
|
|
'Error: Could not determine project root. Configuration not saved.'
|
|
)
|
|
);
|
|
return false;
|
|
}
|
|
// Ensure we don't double-join if explicitRoot already contains the filename
|
|
const configPath =
|
|
path.basename(rootPath) === CONFIG_FILE_NAME
|
|
? rootPath
|
|
: path.join(rootPath, CONFIG_FILE_NAME);
|
|
|
|
try {
|
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
return true;
|
|
} catch (error) {
|
|
console.error(
|
|
chalk.red(
|
|
`Error writing configuration to ${configPath}: ${error.message}`
|
|
)
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the required API key environment variable is set for a given provider.
|
|
* @param {string} providerName The name of the provider.
|
|
* @returns {boolean} True if the API key environment variable exists and is non-empty, false otherwise.
|
|
*/
|
|
function hasApiKeyForProvider(providerName) {
|
|
switch (providerName) {
|
|
case 'anthropic':
|
|
return !!process.env.ANTHROPIC_API_KEY;
|
|
case 'openai':
|
|
case 'openrouter': // OpenRouter uses OpenAI-compatible key
|
|
return !!process.env.OPENAI_API_KEY;
|
|
case 'google':
|
|
return !!process.env.GOOGLE_API_KEY;
|
|
case 'perplexity':
|
|
return !!process.env.PERPLEXITY_API_KEY;
|
|
case 'grok':
|
|
case 'xai': // Added alias for Grok
|
|
return !!process.env.GROK_API_KEY;
|
|
case 'ollama':
|
|
return true; // Ollama runs locally, no cloud API key needed
|
|
default:
|
|
return false; // Unknown provider cannot have a key checked
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the API key status within .cursor/mcp.json for a given provider.
|
|
* Reads the mcp.json file, finds the taskmaster-ai server config, and checks the relevant env var.
|
|
* @param {string} providerName The name of the provider.
|
|
* @returns {boolean} True if the key exists and is not a placeholder, false otherwise.
|
|
*/
|
|
function getMcpApiKeyStatus(providerName) {
|
|
const rootDir = findProjectRoot(); // Use existing root finding
|
|
if (!rootDir) {
|
|
console.warn(
|
|
chalk.yellow('Warning: Could not find project root to check mcp.json.')
|
|
);
|
|
return false; // Cannot check without root
|
|
}
|
|
const mcpConfigPath = path.join(rootDir, '.cursor', 'mcp.json');
|
|
|
|
if (!fs.existsSync(mcpConfigPath)) {
|
|
// console.warn(chalk.yellow('Warning: .cursor/mcp.json not found.'));
|
|
return false; // File doesn't exist
|
|
}
|
|
|
|
try {
|
|
const mcpConfigRaw = fs.readFileSync(mcpConfigPath, 'utf-8');
|
|
const mcpConfig = JSON.parse(mcpConfigRaw);
|
|
|
|
const mcpEnv = mcpConfig?.mcpServers?.['taskmaster-ai']?.env;
|
|
if (!mcpEnv) {
|
|
// console.warn(chalk.yellow('Warning: Could not find taskmaster-ai env in mcp.json.'));
|
|
return false; // Structure missing
|
|
}
|
|
|
|
let apiKeyToCheck = null;
|
|
let placeholderValue = null;
|
|
|
|
switch (providerName) {
|
|
case 'anthropic':
|
|
apiKeyToCheck = mcpEnv.ANTHROPIC_API_KEY;
|
|
placeholderValue = 'YOUR_ANTHROPIC_API_KEY_HERE';
|
|
break;
|
|
case 'openai':
|
|
case 'openrouter':
|
|
apiKeyToCheck = mcpEnv.OPENAI_API_KEY;
|
|
placeholderValue = 'YOUR_OPENAI_API_KEY_HERE'; // Assuming placeholder matches OPENAI
|
|
break;
|
|
case 'google':
|
|
apiKeyToCheck = mcpEnv.GOOGLE_API_KEY;
|
|
placeholderValue = 'YOUR_GOOGLE_API_KEY_HERE';
|
|
break;
|
|
case 'perplexity':
|
|
apiKeyToCheck = mcpEnv.PERPLEXITY_API_KEY;
|
|
placeholderValue = 'YOUR_PERPLEXITY_API_KEY_HERE';
|
|
break;
|
|
case 'grok':
|
|
case 'xai':
|
|
apiKeyToCheck = mcpEnv.GROK_API_KEY;
|
|
placeholderValue = 'YOUR_GROK_API_KEY_HERE';
|
|
break;
|
|
case 'ollama':
|
|
return true; // No key needed
|
|
default:
|
|
return false; // Unknown provider
|
|
}
|
|
|
|
return !!apiKeyToCheck && apiKeyToCheck !== placeholderValue;
|
|
} catch (error) {
|
|
console.error(
|
|
chalk.red(`Error reading or parsing .cursor/mcp.json: ${error.message}`)
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export {
|
|
// Not exporting findProjectRoot as it's internal for CLI context now
|
|
readConfig, // Keep exporting if direct access is needed elsewhere
|
|
writeConfig, // Keep exporting if direct access is needed elsewhere
|
|
validateProvider,
|
|
validateProviderModelCombination,
|
|
getMainProvider,
|
|
getMainModelId,
|
|
getResearchProvider,
|
|
getResearchModelId,
|
|
getFallbackProvider,
|
|
getFallbackModelId,
|
|
setMainModel,
|
|
setResearchModel,
|
|
setFallbackModel,
|
|
VALID_PROVIDERS,
|
|
MODEL_MAP,
|
|
getAvailableModels,
|
|
hasApiKeyForProvider,
|
|
getMcpApiKeyStatus
|
|
};
|