visaire
Version:
A fully autonomous agentic CLI tool for interacting with large language models (Claude, Gemini, GPT) that can execute multi-step tasks automatically
339 lines (291 loc) • 9.62 kB
JavaScript
const Utils = require('./utils');
const Config = require('./config');
/**
* Interactive setup for first-time users
*/
class Setup {
constructor() {
this.config = new Config();
}
/**
* Run interactive setup
*/
async run() {
Utils.logInfo('🚀 Welcome to Visaire CLI Setup!');
console.log('');
Utils.logInfo('This setup will help you configure Visaire for first use.');
Utils.logInfo('You can always change these settings later using: visaire config set');
console.log('');
// Check if config already exists
if (this.config.exists()) {
Utils.logWarning('Configuration file already exists at: ' + this.config.getConfigPath());
console.log('');
const shouldContinue = await this.askYesNo('Do you want to update your existing configuration?');
if (!shouldContinue) {
Utils.logInfo('Setup cancelled. Your existing configuration is unchanged.');
return;
}
console.log('');
}
try {
// Step 1: Choose default provider
const provider = await this.chooseProvider();
// Step 2: Set API key for chosen provider
const apiKey = await this.getApiKey(provider);
// Step 3: Choose default model (optional)
const model = await this.chooseModel(provider);
// Step 4: Agent settings
const agentEnabled = await this.configureAgent();
// Step 5: Save configuration
const settings = {
defaultProvider: provider
};
if (model) {
settings.defaultModel = model;
}
if (agentEnabled !== undefined) {
settings['agent.enabled'] = agentEnabled;
}
// Save settings
if (this.config.setMultiple(settings)) {
// Save API key
if (this.config.setApiKey(provider, apiKey)) {
console.log('');
Utils.logSuccess('🎉 Setup completed successfully!');
console.log('');
this.showQuickStart(provider);
} else {
Utils.logError('Failed to save API key');
}
} else {
Utils.logError('Failed to save configuration');
}
} catch (error) {
Utils.logError('Setup failed: ' + error.message);
process.exit(1);
}
}
/**
* Choose default provider
*/
async chooseProvider() {
Utils.logInfo('📡 Choose your default AI provider:');
console.log('');
console.log('1. Claude (Anthropic) - Great for reasoning and analysis');
console.log('2. GPT (OpenAI) - Popular choice with good general capabilities');
console.log('3. Gemini (Google) - Fast and efficient for most tasks');
console.log('');
const choice = await this.askChoice('Enter your choice (1-3)', ['1', '2', '3']);
const providers = {
'1': 'claude',
'2': 'gpt',
'3': 'gemini'
};
return providers[choice];
}
/**
* Get API key for provider
*/
async getApiKey(provider) {
console.log('');
Utils.logInfo(`🔑 Setting up ${provider.toUpperCase()} API key:`);
console.log('');
// Show provider-specific instructions
this.showApiKeyInstructions(provider);
let apiKey;
let isValid = false;
while (!isValid) {
apiKey = await this.askInput(`Enter your ${provider.toUpperCase()} API key`, true);
if (!apiKey || apiKey.trim().length === 0) {
Utils.logError('API key cannot be empty');
continue;
}
// Validate API key format
if (!Utils.validateApiKey(apiKey, provider)) {
Utils.logError(`Invalid API key format for ${provider}`);
this.showApiKeyFormat(provider);
continue;
}
// Test API key
Utils.logInfo('Testing API key...');
const Providers = require('./providers');
const providers = new Providers();
try {
isValid = await providers.testApiKey(provider, apiKey);
if (!isValid) {
Utils.logError('API key test failed. Please check your key and try again.');
}
} catch (error) {
Utils.logError('Failed to test API key: ' + error.message);
isValid = false;
}
}
Utils.logSuccess('API key validated successfully!');
return apiKey;
}
/**
* Choose default model
*/
async chooseModel(provider) {
console.log('');
const useDefault = await this.askYesNo(`Use default model for ${provider}? (recommended for beginners)`);
if (useDefault) {
return null; // Use provider default
}
Utils.logInfo(`Available models for ${provider}:`);
const Providers = require('./providers');
const providers = new Providers();
const models = providers.getAvailableModels(provider);
console.log('');
models.forEach((model, index) => {
console.log(`${index + 1}. ${model}`);
});
console.log(`${models.length + 1}. Use default`);
console.log('');
const choices = models.map((_, index) => (index + 1).toString());
choices.push((models.length + 1).toString());
const choice = await this.askChoice('Choose a model', choices);
const choiceIndex = parseInt(choice) - 1;
if (choiceIndex === models.length) {
return null; // Use default
}
return models[choiceIndex];
}
/**
* Configure agent settings
*/
async configureAgent() {
console.log('');
Utils.logInfo('🤖 Agent Mode Configuration:');
console.log('');
Utils.logInfo('Agent mode allows Visaire to execute actions based on AI responses');
Utils.logInfo('(like creating files, running commands, etc.)');
console.log('');
return await this.askYesNo('Enable agent mode? (recommended)');
}
/**
* Show API key instructions for provider
*/
showApiKeyInstructions(provider) {
const instructions = {
claude: [
'To get your Claude API key:',
'1. Go to https://console.anthropic.com',
'2. Sign up or log in to your account',
'3. Navigate to API Keys section',
'4. Create a new API key',
'5. Copy the key (starts with "sk-ant-")'
],
gpt: [
'To get your OpenAI API key:',
'1. Go to https://platform.openai.com',
'2. Sign up or log in to your account',
'3. Navigate to API Keys section',
'4. Create a new API key',
'5. Copy the key (starts with "sk-")'
],
gemini: [
'To get your Gemini API key:',
'1. Go to https://makersuite.google.com',
'2. Sign in with your Google account',
'3. Create a new API key',
'4. Copy the generated key'
]
};
instructions[provider].forEach(line => {
Utils.logInfo(line);
});
console.log('');
}
/**
* Show API key format for provider
*/
showApiKeyFormat(provider) {
const formats = {
claude: 'Claude API keys start with "sk-ant-" followed by alphanumeric characters',
gpt: 'OpenAI API keys start with "sk-" followed by alphanumeric characters',
gemini: 'Gemini API keys are alphanumeric strings (usually 39 characters)'
};
Utils.logInfo('Expected format: ' + formats[provider]);
}
/**
* Show quick start guide
*/
showQuickStart(provider) {
Utils.logInfo('🎯 Quick Start Guide:');
console.log('');
Utils.logInfo('Now you can use Visaire with simple commands:');
console.log('');
console.log(` visaire "Explain quantum computing"`);
console.log(` echo "Write a Python function" | visaire`);
console.log('');
Utils.logInfo('Other useful commands:');
console.log('');
console.log(' visaire --help Show all options');
console.log(' visaire config show Show current configuration');
console.log(' visaire config set --help Update configuration');
console.log('');
Utils.logInfo('For more information, visit: https://github.com/shoryakumar/visaire-cli');
console.log('');
}
/**
* Ask yes/no question
*/
async askYesNo(question) {
const answer = await this.askChoice(question + ' (y/n)', ['y', 'n', 'yes', 'no']);
return answer.toLowerCase().startsWith('y');
}
/**
* Ask for input with validation
*/
async askInput(question, required = false) {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
const ask = () => {
rl.question(question + ': ', (answer) => {
if (required && (!answer || answer.trim().length === 0)) {
Utils.logError('This field is required');
ask();
} else {
rl.close();
resolve(answer.trim());
}
});
};
ask();
});
}
/**
* Ask multiple choice question
*/
async askChoice(question, validChoices) {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
const ask = () => {
rl.question(question + ': ', (answer) => {
const trimmed = answer.trim().toLowerCase();
const valid = validChoices.find(choice =>
choice.toLowerCase() === trimmed
);
if (valid) {
rl.close();
resolve(valid);
} else {
Utils.logError(`Please enter one of: ${validChoices.join(', ')}`);
ask();
}
});
};
ask();
});
}
}
module.exports = Setup;