mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-06-27 00:29:58 +00:00

Removes unused import statements identified after the major refactoring of the AI service layer and other components. This cleanup improves code clarity and removes unnecessary dependencies. Unused imports removed from: - **`mcp-server/src/core/direct-functions/analyze-task-complexity.js`:** - Removed `path` - **`mcp-server/src/core/direct-functions/complexity-report.js`:** - Removed `path` - **`mcp-server/src/core/direct-functions/expand-all-tasks.js`:** - Removed `path`, `fs` - **`mcp-server/src/core/direct-functions/generate-task-files.js`:** - Removed `path` - **`mcp-server/src/core/direct-functions/parse-prd.js`:** - Removed `os`, `findTasksJsonPath` - **`mcp-server/src/core/direct-functions/update-tasks.js`:** - Removed `isSilentMode` - **`mcp-server/src/tools/add-task.js`:** - Removed `createContentResponse`, `executeTaskMasterCommand` - **`mcp-server/src/tools/analyze.js`:** - Removed `getProjectRootFromSession` (as `projectRoot` is now required in args) - **`mcp-server/src/tools/expand-task.js`:** - Removed `path` - **`mcp-server/src/tools/initialize-project.js`:** - Removed `createContentResponse` - **`mcp-server/src/tools/parse-prd.js`:** - Removed `findPRDDocumentPath`, `resolveTasksOutputPath` (logic moved or handled by `resolveProjectPaths`) - **`mcp-server/src/tools/update.js`:** - Removed `getProjectRootFromSession` (as `projectRoot` is now required in args) - **`scripts/modules/commands.js`:** - Removed `exec`, `readline` - Removed AI config getters (`getMainModelId`, etc.) - Removed MCP helpers (`getMcpApiKeyStatus`) - **`scripts/modules/config-manager.js`:** - Removed `ZodError`, `readJSON`, `writeJSON` - **`scripts/modules/task-manager/analyze-task-complexity.js`:** - Removed AI config getters (`getMainModelId`, etc.) - **`scripts/modules/task-manager/expand-all-tasks.js`:** - Removed `fs`, `path`, `writeJSON` - **`scripts/modules/task-manager/models.js`:** - Removed `VALID_PROVIDERS` - **`scripts/modules/task-manager/update-subtask-by-id.js`:** - Removed AI config getters (`getMainModelId`, etc.) - **`scripts/modules/task-manager/update-tasks.js`:** - Removed AI config getters (`getMainModelId`, etc.) - **`scripts/modules/ui.js`:** - Removed `getDebugFlag` - **`scripts/modules/utils.js`:** - Removed `ZodError`
611 lines
19 KiB
JavaScript
611 lines
19 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import chalk from 'chalk';
|
|
import { fileURLToPath } from 'url';
|
|
import { log, resolveEnvVariable, findProjectRoot } from './utils.js';
|
|
|
|
// 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';
|
|
|
|
// Define valid providers dynamically from the loaded MODEL_MAP
|
|
const VALID_PROVIDERS = Object.keys(MODEL_MAP);
|
|
|
|
// Default configuration values (used if .taskmasterconfig is missing or incomplete)
|
|
const DEFAULTS = {
|
|
models: {
|
|
main: {
|
|
provider: 'anthropic',
|
|
modelId: 'claude-3-7-sonnet-20250219',
|
|
maxTokens: 64000,
|
|
temperature: 0.2
|
|
},
|
|
research: {
|
|
provider: 'perplexity',
|
|
modelId: 'sonar-pro',
|
|
maxTokens: 8700,
|
|
temperature: 0.1
|
|
},
|
|
fallback: {
|
|
// No default fallback provider/model initially
|
|
provider: 'anthropic',
|
|
modelId: 'claude-3-5-sonnet',
|
|
maxTokens: 64000, // Default parameters if fallback IS configured
|
|
temperature: 0.2
|
|
}
|
|
},
|
|
global: {
|
|
logLevel: 'info',
|
|
debug: false,
|
|
defaultSubtasks: 5,
|
|
defaultPriority: 'medium',
|
|
projectName: 'Task Master',
|
|
ollamaBaseUrl: 'http://localhost:11434/api'
|
|
}
|
|
};
|
|
|
|
// --- Internal Config Loading ---
|
|
let loadedConfig = null;
|
|
let loadedConfigRoot = null; // Track which root loaded the config
|
|
|
|
// Custom Error for configuration issues
|
|
class ConfigurationError extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'ConfigurationError';
|
|
}
|
|
}
|
|
|
|
function _loadAndValidateConfig(explicitRoot = null) {
|
|
const defaults = DEFAULTS; // Use the defined defaults
|
|
|
|
// If no explicit root is provided (e.g., during initial server load),
|
|
// return defaults immediately and silently.
|
|
if (!explicitRoot) {
|
|
return defaults;
|
|
}
|
|
|
|
// --- Proceed with loading from the provided explicitRoot ---
|
|
const configPath = path.join(explicitRoot, CONFIG_FILE_NAME);
|
|
let config = { ...defaults }; // Start with a deep copy of defaults
|
|
let configExists = false;
|
|
|
|
if (fs.existsSync(configPath)) {
|
|
configExists = true;
|
|
try {
|
|
const rawData = fs.readFileSync(configPath, 'utf-8');
|
|
const parsedConfig = JSON.parse(rawData);
|
|
|
|
// Deep merge parsed config onto defaults
|
|
config = {
|
|
models: {
|
|
main: { ...defaults.models.main, ...parsedConfig?.models?.main },
|
|
research: {
|
|
...defaults.models.research,
|
|
...parsedConfig?.models?.research
|
|
},
|
|
fallback:
|
|
parsedConfig?.models?.fallback?.provider &&
|
|
parsedConfig?.models?.fallback?.modelId
|
|
? { ...defaults.models.fallback, ...parsedConfig.models.fallback }
|
|
: { ...defaults.models.fallback }
|
|
},
|
|
global: { ...defaults.global, ...parsedConfig?.global }
|
|
};
|
|
|
|
// --- Validation (Warn if file content is invalid) ---
|
|
// Only use console.warn here, as this part runs only when an explicitRoot *is* provided
|
|
if (!validateProvider(config.models.main.provider)) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Invalid main provider "${config.models.main.provider}" in ${configPath}. Falling back to default.`
|
|
)
|
|
);
|
|
config.models.main = { ...defaults.models.main };
|
|
}
|
|
if (!validateProvider(config.models.research.provider)) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Invalid research provider "${config.models.research.provider}" in ${configPath}. Falling back to default.`
|
|
)
|
|
);
|
|
config.models.research = { ...defaults.models.research };
|
|
}
|
|
if (
|
|
config.models.fallback?.provider &&
|
|
!validateProvider(config.models.fallback.provider)
|
|
) {
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: Invalid fallback provider "${config.models.fallback.provider}" in ${configPath}. Fallback model configuration will be ignored.`
|
|
)
|
|
);
|
|
config.models.fallback.provider = undefined;
|
|
config.models.fallback.modelId = undefined;
|
|
}
|
|
} catch (error) {
|
|
// Use console.error for actual errors during parsing
|
|
console.error(
|
|
chalk.red(
|
|
`Error reading or parsing ${configPath}: ${error.message}. Using default configuration.`
|
|
)
|
|
);
|
|
config = { ...defaults }; // Reset to defaults on parse error
|
|
}
|
|
} else {
|
|
// Config file doesn't exist at the provided explicitRoot.
|
|
// Use console.warn because an explicit root *was* given.
|
|
console.warn(
|
|
chalk.yellow(
|
|
`Warning: ${CONFIG_FILE_NAME} not found at provided project root (${explicitRoot}). Using default configuration. Run 'task-master models --setup' to configure.`
|
|
)
|
|
);
|
|
// Keep config as defaults
|
|
config = { ...defaults };
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Gets the current configuration, loading it if necessary.
|
|
* Handles MCP initialization context gracefully.
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @param {boolean} forceReload - Force reloading the config file.
|
|
* @returns {object} The loaded configuration object.
|
|
*/
|
|
function getConfig(explicitRoot = null, forceReload = false) {
|
|
// Determine if a reload is necessary
|
|
const needsLoad =
|
|
!loadedConfig ||
|
|
forceReload ||
|
|
(explicitRoot && explicitRoot !== loadedConfigRoot);
|
|
|
|
if (needsLoad) {
|
|
const newConfig = _loadAndValidateConfig(explicitRoot); // _load handles null explicitRoot
|
|
|
|
// Only update the global cache if loading was forced or if an explicit root
|
|
// was provided (meaning we attempted to load a specific project's config).
|
|
// We avoid caching the initial default load triggered without an explicitRoot.
|
|
if (forceReload || explicitRoot) {
|
|
loadedConfig = newConfig;
|
|
loadedConfigRoot = explicitRoot; // Store the root used for this loaded config
|
|
}
|
|
return newConfig; // Return the newly loaded/default config
|
|
}
|
|
|
|
// If no load was needed, return the cached config
|
|
return loadedConfig;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
);
|
|
}
|
|
|
|
// --- Role-Specific Getters ---
|
|
|
|
function getModelConfigForRole(role, explicitRoot = null) {
|
|
const config = getConfig(explicitRoot);
|
|
const roleConfig = config?.models?.[role];
|
|
if (!roleConfig) {
|
|
// This shouldn't happen if _loadAndValidateConfig ensures defaults
|
|
// But as a safety net, log and return defaults
|
|
log(
|
|
'warn',
|
|
`No model configuration found for role: ${role}. Returning default.`
|
|
);
|
|
return DEFAULTS.models[role] || {};
|
|
}
|
|
return roleConfig;
|
|
}
|
|
|
|
function getMainProvider(explicitRoot = null) {
|
|
return getModelConfigForRole('main', explicitRoot).provider;
|
|
}
|
|
|
|
function getMainModelId(explicitRoot = null) {
|
|
return getModelConfigForRole('main', explicitRoot).modelId;
|
|
}
|
|
|
|
function getMainMaxTokens(explicitRoot = null) {
|
|
// Directly return value from config (which includes defaults)
|
|
return getModelConfigForRole('main', explicitRoot).maxTokens;
|
|
}
|
|
|
|
function getMainTemperature(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getModelConfigForRole('main', explicitRoot).temperature;
|
|
}
|
|
|
|
function getResearchProvider(explicitRoot = null) {
|
|
return getModelConfigForRole('research', explicitRoot).provider;
|
|
}
|
|
|
|
function getResearchModelId(explicitRoot = null) {
|
|
return getModelConfigForRole('research', explicitRoot).modelId;
|
|
}
|
|
|
|
function getResearchMaxTokens(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getModelConfigForRole('research', explicitRoot).maxTokens;
|
|
}
|
|
|
|
function getResearchTemperature(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getModelConfigForRole('research', explicitRoot).temperature;
|
|
}
|
|
|
|
function getFallbackProvider(explicitRoot = null) {
|
|
// Directly return value from config (will be undefined if not set)
|
|
return getModelConfigForRole('fallback', explicitRoot).provider;
|
|
}
|
|
|
|
function getFallbackModelId(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getModelConfigForRole('fallback', explicitRoot).modelId;
|
|
}
|
|
|
|
function getFallbackMaxTokens(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getModelConfigForRole('fallback', explicitRoot).maxTokens;
|
|
}
|
|
|
|
function getFallbackTemperature(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getModelConfigForRole('fallback', explicitRoot).temperature;
|
|
}
|
|
|
|
// --- Global Settings Getters ---
|
|
|
|
function getGlobalConfig(explicitRoot = null) {
|
|
const config = getConfig(explicitRoot);
|
|
// Ensure global defaults are applied if global section is missing
|
|
return { ...DEFAULTS.global, ...(config?.global || {}) };
|
|
}
|
|
|
|
function getLogLevel(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getGlobalConfig(explicitRoot).logLevel.toLowerCase();
|
|
}
|
|
|
|
function getDebugFlag(explicitRoot = null) {
|
|
// Directly return value from config, ensure boolean
|
|
return getGlobalConfig(explicitRoot).debug === true;
|
|
}
|
|
|
|
function getDefaultSubtasks(explicitRoot = null) {
|
|
// Directly return value from config, ensure integer
|
|
const val = getGlobalConfig(explicitRoot).defaultSubtasks;
|
|
const parsedVal = parseInt(val, 10);
|
|
return isNaN(parsedVal) ? DEFAULTS.global.defaultSubtasks : parsedVal;
|
|
}
|
|
|
|
function getDefaultPriority(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getGlobalConfig(explicitRoot).defaultPriority;
|
|
}
|
|
|
|
function getProjectName(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getGlobalConfig(explicitRoot).projectName;
|
|
}
|
|
|
|
function getOllamaBaseUrl(explicitRoot = null) {
|
|
// Directly return value from config
|
|
return getGlobalConfig(explicitRoot).ollamaBaseUrl;
|
|
}
|
|
|
|
/**
|
|
* Gets model parameters (maxTokens, temperature) for a specific role.
|
|
* @param {string} role - The role ('main', 'research', 'fallback').
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
|
* @returns {{maxTokens: number, temperature: number}}
|
|
*/
|
|
function getParametersForRole(role, explicitRoot = null) {
|
|
const roleConfig = getModelConfigForRole(role, explicitRoot);
|
|
return {
|
|
maxTokens: roleConfig.maxTokens,
|
|
temperature: roleConfig.temperature
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Checks if the API key for a given provider is set in the environment.
|
|
* Checks process.env first, then session.env if session is provided.
|
|
* @param {string} providerName - The name of the provider (e.g., 'openai', 'anthropic').
|
|
* @param {object|null} [session=null] - The MCP session object (optional).
|
|
* @returns {boolean} True if the API key is set, false otherwise.
|
|
*/
|
|
function isApiKeySet(providerName, session = null) {
|
|
// Define the expected environment variable name for each provider
|
|
const keyMap = {
|
|
openai: 'OPENAI_API_KEY',
|
|
anthropic: 'ANTHROPIC_API_KEY',
|
|
google: 'GOOGLE_API_KEY',
|
|
perplexity: 'PERPLEXITY_API_KEY',
|
|
mistral: 'MISTRAL_API_KEY',
|
|
azure: 'AZURE_OPENAI_API_KEY', // Azure needs endpoint too, but key presence is a start
|
|
openrouter: 'OPENROUTER_API_KEY',
|
|
xai: 'XAI_API_KEY',
|
|
ollama: 'OLLAMA_API_KEY'
|
|
// Add other providers as needed
|
|
};
|
|
|
|
const providerKey = providerName?.toLowerCase();
|
|
if (!providerKey || !keyMap[providerKey]) {
|
|
log('warn', `Unknown provider name: ${providerName} in isApiKeySet check.`);
|
|
return false;
|
|
}
|
|
|
|
const envVarName = keyMap[providerKey];
|
|
// Use resolveEnvVariable to check both process.env and session.env
|
|
return !!resolveEnvVariable(envVarName, session);
|
|
}
|
|
|
|
/**
|
|
* 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':
|
|
apiKeyToCheck = mcpEnv.OPENAI_API_KEY;
|
|
placeholderValue = 'YOUR_OPENAI_API_KEY_HERE'; // Assuming placeholder matches OPENAI
|
|
break;
|
|
case 'openrouter':
|
|
apiKeyToCheck = mcpEnv.OPENROUTER_API_KEY;
|
|
placeholderValue = 'YOUR_OPENROUTER_API_KEY_HERE';
|
|
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 'xai':
|
|
apiKeyToCheck = mcpEnv.XAI_API_KEY;
|
|
placeholderValue = 'YOUR_XAI_API_KEY_HERE';
|
|
break;
|
|
case 'ollama':
|
|
return true; // No key needed
|
|
case 'mistral':
|
|
apiKeyToCheck = mcpEnv.MISTRAL_API_KEY;
|
|
placeholderValue = 'YOUR_MISTRAL_API_KEY_HERE';
|
|
break;
|
|
case 'azure':
|
|
apiKeyToCheck = mcpEnv.AZURE_OPENAI_API_KEY;
|
|
placeholderValue = 'YOUR_AZURE_OPENAI_API_KEY_HERE';
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
const configPath =
|
|
path.basename(rootPath) === CONFIG_FILE_NAME
|
|
? rootPath
|
|
: path.join(rootPath, CONFIG_FILE_NAME);
|
|
|
|
try {
|
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
loadedConfig = config; // Update the cache after successful write
|
|
return true;
|
|
} catch (error) {
|
|
console.error(
|
|
chalk.red(
|
|
`Error writing configuration to ${configPath}: ${error.message}`
|
|
)
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the .taskmasterconfig file exists at the project root
|
|
* @param {string|null} explicitRoot - Optional explicit path to the project root
|
|
* @returns {boolean} True if the file exists, false otherwise
|
|
*/
|
|
function isConfigFilePresent(explicitRoot = null) {
|
|
const rootPath = explicitRoot || findProjectRoot();
|
|
if (!rootPath) {
|
|
return false;
|
|
}
|
|
const configPath = path.join(rootPath, CONFIG_FILE_NAME);
|
|
return fs.existsSync(configPath);
|
|
}
|
|
|
|
export {
|
|
// Core config access
|
|
getConfig,
|
|
writeConfig,
|
|
ConfigurationError, // Export custom error type
|
|
isConfigFilePresent, // Add the new function export
|
|
|
|
// Validation
|
|
validateProvider,
|
|
validateProviderModelCombination,
|
|
VALID_PROVIDERS,
|
|
MODEL_MAP,
|
|
getAvailableModels,
|
|
|
|
// Role-specific getters (No env var overrides)
|
|
getMainProvider,
|
|
getMainModelId,
|
|
getMainMaxTokens,
|
|
getMainTemperature,
|
|
getResearchProvider,
|
|
getResearchModelId,
|
|
getResearchMaxTokens,
|
|
getResearchTemperature,
|
|
getFallbackProvider,
|
|
getFallbackModelId,
|
|
getFallbackMaxTokens,
|
|
getFallbackTemperature,
|
|
|
|
// Global setting getters (No env var overrides)
|
|
getLogLevel,
|
|
getDebugFlag,
|
|
getDefaultSubtasks,
|
|
getDefaultPriority,
|
|
getProjectName,
|
|
getOllamaBaseUrl,
|
|
getParametersForRole,
|
|
|
|
// API Key Checkers (still relevant)
|
|
isApiKeySet,
|
|
getMcpApiKeyStatus
|
|
};
|