@juspay/neurolink
Version:
Universal AI Development Platform with external MCP server integration, multi-provider support, and professional CLI. Connect to 65+ MCP servers for filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major pr
640 lines (639 loc) • 23.5 kB
JavaScript
#!/usr/bin/env node
/**
* NeuroLink CLI Configuration Management
*
* Enhanced configuration system with interactive setup,
* multi-profile support, and smart validation.
*/
import inquirer from 'inquirer';
import fs from 'fs';
import path from 'path';
import os from 'os';
import chalk from 'chalk';
import { z } from 'zod';
// Configuration schema for validation
const ConfigSchema = z.object({
defaultProvider: z.enum(['auto', 'openai', 'bedrock', 'vertex', 'anthropic', 'azure', 'google-ai', 'huggingface', 'ollama', 'mistral']).default('auto'),
providers: z.object({
openai: z.object({
apiKey: z.string().optional(),
model: z.string().default('gpt-4'),
baseURL: z.string().optional()
}).optional(),
bedrock: z.object({
region: z.string().optional(),
accessKeyId: z.string().optional(),
secretAccessKey: z.string().optional(),
sessionToken: z.string().optional(),
model: z.string().default('arn:aws:bedrock:us-east-2:225681119357:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0')
}).optional(),
vertex: z.object({
projectId: z.string().optional(),
location: z.string().default('us-east5'),
credentials: z.string().optional(),
serviceAccountKey: z.string().optional(),
clientEmail: z.string().optional(),
privateKey: z.string().optional(),
model: z.string().default('gemini-1.5-pro')
}).optional(),
anthropic: z.object({
apiKey: z.string().optional(),
model: z.string().default('claude-3-5-sonnet-20241022')
}).optional(),
azure: z.object({
apiKey: z.string().optional(),
endpoint: z.string().optional(),
deploymentId: z.string().optional(),
model: z.string().default('gpt-4')
}).optional(),
'google-ai': z.object({
apiKey: z.string().optional(),
model: z.string().default('gemini-1.5-pro-latest')
}).optional(),
huggingface: z.object({
apiKey: z.string().optional(),
model: z.string().default('microsoft/DialoGPT-large')
}).optional(),
ollama: z.object({
baseUrl: z.string().default('http://localhost:11434'),
model: z.string().default('llama2'),
timeout: z.number().default(60000)
}).optional(),
mistral: z.object({
apiKey: z.string().optional(),
model: z.string().default('mistral-small')
}).optional()
}).default({}),
profiles: z.record(z.string(), z.any()).default({}),
preferences: z.object({
outputFormat: z.enum(['text', 'json', 'yaml']).default('text'),
temperature: z.number().min(0).max(2).default(0.7),
maxTokens: z.number().min(1).max(4000).default(500),
enableLogging: z.boolean().default(false),
enableCaching: z.boolean().default(true),
cacheStrategy: z.enum(['memory', 'file', 'redis']).default('memory')
}).default({})
});
export class ConfigManager {
configDir;
configFile;
config;
constructor() {
this.configDir = path.join(os.homedir(), '.neurolink');
this.configFile = path.join(this.configDir, 'config.json');
this.config = this.loadConfig();
}
/**
* Load configuration from file or create default
*/
loadConfig() {
try {
if (fs.existsSync(this.configFile)) {
const configData = JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
return ConfigSchema.parse(configData);
}
}
catch (error) {
console.warn(chalk.yellow(`⚠️ Invalid config file: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
return ConfigSchema.parse({});
}
/**
* Save configuration to file
*/
saveConfig() {
try {
// Ensure config directory exists
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, { recursive: true });
}
// Validate before saving
const validatedConfig = ConfigSchema.parse(this.config);
fs.writeFileSync(this.configFile, JSON.stringify(validatedConfig, null, 2));
console.log(chalk.green(`✅ Configuration saved to ${this.configFile}`));
}
catch (error) {
console.error(chalk.red(`❌ Failed to save config: ${error instanceof Error ? error.message : 'Unknown error'}`));
process.exit(1);
}
}
/**
* Interactive configuration setup
*/
async initInteractive() {
console.log(chalk.blue('🧠 NeuroLink Configuration Setup\n'));
try {
// Basic preferences
const preferences = await inquirer.prompt([
{
type: 'list',
name: 'defaultProvider',
message: 'Select your default AI provider:',
choices: [
{ name: 'Auto (recommended) - Automatically select best available', value: 'auto' },
{ name: 'OpenAI - GPT models', value: 'openai' },
{ name: 'Amazon Bedrock - Claude, Llama, Titan', value: 'bedrock' },
{ name: 'Google Vertex AI - Gemini models', value: 'vertex' },
{ name: 'Anthropic - Claude models (direct)', value: 'anthropic' },
{ name: 'Azure OpenAI - Enterprise GPT', value: 'azure' },
{ name: 'Google AI Studio - Gemini models (direct)', value: 'google-ai' },
{ name: 'Hugging Face - Open source models', value: 'huggingface' },
{ name: 'Mistral AI - European AI with competitive pricing', value: 'mistral' }
],
default: this.config.defaultProvider
},
{
type: 'list',
name: 'outputFormat',
message: 'Preferred output format:',
choices: ['text', 'json', 'yaml'],
default: this.config.preferences.outputFormat
},
{
type: 'number',
name: 'temperature',
message: 'Default creativity level (0.0 = focused, 1.0 = creative):',
default: this.config.preferences.temperature,
validate: (value) => value >= 0 && value <= 2
},
{
type: 'confirm',
name: 'setupProviders',
message: 'Would you like to configure provider credentials now?',
default: true
}
]);
// Update config with preferences
this.config.defaultProvider = preferences.defaultProvider;
this.config.preferences.outputFormat = preferences.outputFormat;
this.config.preferences.temperature = preferences.temperature;
// Setup providers if requested
if (preferences.setupProviders) {
await this.setupProviders();
}
this.saveConfig();
console.log(chalk.green('\n✅ Configuration setup complete!'));
console.log(chalk.blue('💡 You can modify settings anytime with: neurolink config edit'));
console.log(chalk.blue('💡 Test your setup with: neurolink status'));
}
catch (error) {
if (error instanceof Error && error.message === 'User force closed the prompt with 0 null') {
console.log(chalk.yellow('\n⚠️ Setup cancelled by user'));
process.exit(0);
}
throw error;
}
}
/**
* Setup individual providers
*/
async setupProviders() {
const { selectedProviders } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedProviders',
message: 'Select providers to configure:',
choices: [
{ name: 'OpenAI (GPT-4, GPT-3.5)', value: 'openai' },
{ name: 'Amazon Bedrock (Claude, Llama)', value: 'bedrock' },
{ name: 'Google Vertex AI (Gemini)', value: 'vertex' },
{ name: 'Anthropic Direct (Claude)', value: 'anthropic' },
{ name: 'Azure OpenAI (Enterprise)', value: 'azure' },
{ name: 'Google AI Studio (Gemini Direct)', value: 'google-ai' },
{ name: 'Hugging Face (Open Source)', value: 'huggingface' },
{ name: 'Ollama (Local AI Models)', value: 'ollama' },
{ name: 'Mistral AI (European AI)', value: 'mistral' }
]
}
]);
for (const provider of selectedProviders) {
await this.setupProvider(provider);
}
}
/**
* Setup individual provider
*/
async setupProvider(provider) {
console.log(chalk.blue(`\n🔧 Configuring ${provider.toUpperCase()}`));
switch (provider) {
case 'openai':
await this.setupOpenAI();
break;
case 'bedrock':
await this.setupBedrock();
break;
case 'vertex':
await this.setupVertex();
break;
case 'anthropic':
await this.setupAnthropic();
break;
case 'azure':
await this.setupAzure();
break;
case 'google-ai':
await this.setupGoogleAI();
break;
case 'huggingface':
await this.setupHuggingFace();
break;
case 'ollama':
await this.setupOllama();
break;
case 'mistral':
await this.setupMistral();
break;
}
}
/**
* OpenAI provider setup
*/
async setupOpenAI() {
const answers = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: 'OpenAI API Key (sk-...):',
validate: (value) => value.startsWith('sk-') || 'API key should start with "sk-"'
},
{
type: 'list',
name: 'model',
message: 'Default model:',
choices: ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo'],
default: 'gpt-4'
},
{
type: 'input',
name: 'baseURL',
message: 'Custom base URL (optional):',
default: ''
}
]);
this.config.providers.openai = {
apiKey: answers.apiKey,
model: answers.model,
...(answers.baseURL && { baseURL: answers.baseURL })
};
}
/**
* Amazon Bedrock provider setup
*/
async setupBedrock() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'region',
message: 'AWS Region:',
default: 'us-east-1'
},
{
type: 'input',
name: 'accessKeyId',
message: 'AWS Access Key ID (optional if using IAM roles):'
},
{
type: 'password',
name: 'secretAccessKey',
message: 'AWS Secret Access Key (optional if using IAM roles):'
},
{
type: 'password',
name: 'sessionToken',
message: 'AWS Session Token (optional):'
},
{
type: 'input',
name: 'model',
message: 'Model ARN:',
default: 'arn:aws:bedrock:us-east-2:225681119357:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0'
}
]);
this.config.providers.bedrock = {
region: answers.region,
...(answers.accessKeyId && { accessKeyId: answers.accessKeyId }),
...(answers.secretAccessKey && { secretAccessKey: answers.secretAccessKey }),
...(answers.sessionToken && { sessionToken: answers.sessionToken }),
model: answers.model
};
}
/**
* Google Vertex AI provider setup
*/
async setupVertex() {
const { authMethod } = await inquirer.prompt([
{
type: 'list',
name: 'authMethod',
message: 'Authentication method:',
choices: [
{ name: 'Service Account File', value: 'file' },
{ name: 'Service Account JSON String', value: 'json' },
{ name: 'Individual Environment Variables', value: 'env' }
]
}
]);
const commonAnswers = await inquirer.prompt([
{
type: 'input',
name: 'projectId',
message: 'Google Cloud Project ID:',
validate: (value) => value.length > 0 || 'Project ID is required'
},
{
type: 'input',
name: 'location',
message: 'Vertex AI Location:',
default: 'us-east5'
},
{
type: 'list',
name: 'model',
message: 'Default model:',
choices: ['gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-pro'],
default: 'gemini-1.5-pro'
}
]);
let authConfig = {};
switch (authMethod) {
case 'file':
const fileAnswers = await inquirer.prompt([
{
type: 'input',
name: 'credentials',
message: 'Path to service account JSON file:',
validate: (value) => fs.existsSync(value) || 'File does not exist'
}
]);
authConfig = { credentials: fileAnswers.credentials };
break;
case 'json':
const jsonAnswers = await inquirer.prompt([
{
type: 'input',
name: 'serviceAccountKey',
message: 'Service account JSON string:',
validate: (value) => {
try {
JSON.parse(value);
return true;
}
catch {
return 'Invalid JSON';
}
}
}
]);
authConfig = { serviceAccountKey: jsonAnswers.serviceAccountKey };
break;
case 'env':
const envAnswers = await inquirer.prompt([
{
type: 'input',
name: 'clientEmail',
message: 'Service account email:',
validate: (value) => value.includes('@') || 'Invalid email format'
},
{
type: 'password',
name: 'privateKey',
message: 'Private key:'
}
]);
authConfig = {
clientEmail: envAnswers.clientEmail,
privateKey: envAnswers.privateKey
};
break;
}
this.config.providers.vertex = {
projectId: commonAnswers.projectId,
location: commonAnswers.location,
model: commonAnswers.model,
...authConfig
};
}
/**
* Anthropic provider setup
*/
async setupAnthropic() {
const answers = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: 'Anthropic API Key:',
validate: (value) => value.length > 0 || 'API key is required'
},
{
type: 'list',
name: 'model',
message: 'Default model:',
choices: [
'claude-3-5-sonnet-20241022',
'claude-3-5-haiku-20241022',
'claude-3-opus-20240229'
],
default: 'claude-3-5-sonnet-20241022'
}
]);
this.config.providers.anthropic = answers;
}
/**
* Azure OpenAI provider setup
*/
async setupAzure() {
const answers = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: 'Azure OpenAI API Key:'
},
{
type: 'input',
name: 'endpoint',
message: 'Azure OpenAI Endpoint:',
validate: (value) => value.startsWith('https://') || 'Endpoint should start with https://'
},
{
type: 'input',
name: 'deploymentId',
message: 'Deployment ID:'
},
{
type: 'list',
name: 'model',
message: 'Model:',
choices: ['gpt-4', 'gpt-4-turbo', 'gpt-35-turbo'],
default: 'gpt-4'
}
]);
this.config.providers.azure = answers;
}
/**
* Google AI Studio provider setup
*/
async setupGoogleAI() {
const answers = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: 'Google AI API Key:',
validate: (value) => value.length > 0 || 'API key is required'
},
{
type: 'list',
name: 'model',
message: 'Default model:',
choices: [
'gemini-1.5-pro-latest',
'gemini-2.0-flash-exp',
'gemini-1.5-flash-latest',
'gemini-1.0-pro'
],
default: 'gemini-1.5-pro-latest'
}
]);
this.config.providers['google-ai'] = answers;
}
/**
* Hugging Face provider setup
*/
async setupHuggingFace() {
const answers = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: 'Hugging Face API Key:'
},
{
type: 'input',
name: 'model',
message: 'Model name:',
default: 'microsoft/DialoGPT-large'
}
]);
this.config.providers.huggingface = answers;
}
/**
* Ollama provider setup
*/
async setupOllama() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'baseUrl',
message: 'Ollama base URL:',
default: 'http://localhost:11434',
validate: (value) => value.startsWith('http') || 'URL should start with http:// or https://'
},
{
type: 'input',
name: 'model',
message: 'Default model:',
default: 'llama2'
},
{
type: 'number',
name: 'timeout',
message: 'Request timeout (milliseconds):',
default: 60000,
validate: (value) => value > 0 || 'Timeout must be positive'
}
]);
this.config.providers.ollama = answers;
}
/**
* Mistral AI provider setup
*/
async setupMistral() {
const answers = await inquirer.prompt([
{
type: 'password',
name: 'apiKey',
message: 'Mistral AI API Key:',
validate: (value) => value.length > 0 || 'API key is required'
},
{
type: 'list',
name: 'model',
message: 'Default model:',
choices: [
'mistral-small',
'mistral-medium',
'mistral-large',
'mistral-tiny'
],
default: 'mistral-small'
}
]);
this.config.providers.mistral = answers;
}
/**
* Get current configuration
*/
getConfig() {
return this.config;
}
/**
* Update configuration
*/
updateConfig(updates) {
this.config = { ...this.config, ...updates };
this.saveConfig();
}
/**
* Show current configuration
*/
showConfig() {
console.log(chalk.blue('📋 Current NeuroLink Configuration\n'));
console.log(chalk.cyan('General Settings:'));
console.log(` Default Provider: ${chalk.white(this.config.defaultProvider)}`);
console.log(` Output Format: ${chalk.white(this.config.preferences.outputFormat)}`);
console.log(` Temperature: ${chalk.white(this.config.preferences.temperature)}`);
console.log(` Max Tokens: ${chalk.white(this.config.preferences.maxTokens)}`);
console.log(chalk.cyan('\nConfigured Providers:'));
Object.entries(this.config.providers).forEach(([name, config]) => {
if (config && Object.keys(config).length > 0) {
console.log(` ${chalk.green('✅')} ${name.toUpperCase()}`);
if ('model' in config) {
console.log(` Model: ${chalk.white(config.model)}`);
}
}
});
console.log(chalk.cyan('\nConfiguration File:'));
console.log(` Location: ${chalk.white(this.configFile)}`);
}
/**
* Validate configuration
*/
validateConfig() {
const errors = [];
try {
ConfigSchema.parse(this.config);
}
catch (error) {
if (error instanceof z.ZodError) {
errors.push(...error.errors.map(e => `${e.path.join('.')}: ${e.message}`));
}
}
// Check for at least one configured provider
const hasProvider = Object.values(this.config.providers).some(provider => provider && Object.keys(provider).length > 0);
if (!hasProvider) {
errors.push('No providers configured. Run "neurolink config init" to set up providers.');
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Reset configuration to defaults
*/
resetConfig() {
this.config = ConfigSchema.parse({});
this.saveConfig();
console.log(chalk.green('✅ Configuration reset to defaults'));
}
}
// Export for use in other CLI commands
export const configManager = new ConfigManager();