@clyde-code-ai/clyde-code
Version:
A CLI AI code assistant.
214 lines (184 loc) ⢠5.98 kB
JavaScript
const { spawn, exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
function runCommand(command, args, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { stdio: 'pipe', ...options });
let stdout = '';
let stderr = '';
child.stdout?.on('data', (data) => stdout += data);
child.stderr?.on('data', (data) => stderr += data);
child.on('close', (code) => {
if (code === 0) {
resolve({ stdout, stderr });
} else {
reject(new Error(`Command failed with code ${code}: ${stderr}`));
}
});
});
}
function promptUser(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.toLowerCase().trim());
});
});
}
async function checkPythonVersion() {
try {
const { stdout } = await runCommand('python3', ['--version']);
const version = stdout.trim().match(/Python (\d+)\.(\d+)/);
if (version) {
const major = parseInt(version[1]);
const minor = parseInt(version[2]);
return major > 3 || (major === 3 && minor >= 12);
}
return false;
} catch {
return false;
}
}
async function checkUv() {
try {
await runCommand('uv', ['--version']);
return true;
} catch {
return false;
}
}
async function checkPipx() {
try {
await runCommand('pipx', ['--version']);
return true;
} catch {
return false;
}
}
async function installUv() {
console.log('š¦ Installing uv...');
try {
await runCommand('sh', ['-c', 'curl -LsSf https://astral.sh/uv/install.sh | sh']);
console.log('ā
Successfully installed uv');
console.log('ā¹ļø You may need to restart your terminal or run: source ~/.bashrc');
return true;
} catch (error) {
console.warn('ā ļø uv installation failed:', error.message);
return false;
}
}
async function installWithUv() {
console.log('š¦ Installing clyde-code using uv (recommended)...');
console.log(' This may take a moment while dependencies are resolved...');
try {
await runCommand('uv', ['tool', 'install', '.'], { cwd: __dirname + '/..' });
console.log('ā
Successfully installed clyde-code with uv');
return true;
} catch (error) {
console.warn('ā ļø uv installation failed:', error.message);
return false;
}
}
async function installWithPipx() {
console.log('š¦ Installing clyde-code using pipx...');
console.log(' This may take a moment while dependencies are resolved...');
try {
await runCommand('pipx', ['install', '.'], { cwd: __dirname + '/..' });
console.log('ā
Successfully installed clyde-code with pipx');
return true;
} catch (error) {
console.warn('ā ļø pipx installation failed:', error.message);
return false;
}
}
async function createConfigFile() {
const configDir = path.join(os.homedir(), '.clyde-code');
const configPath = path.join(configDir, 'config.json');
// Check if config already exists
if (fs.existsSync(configPath)) {
console.log('ā¹ļø Config file already exists at ~/.clyde-code/config.json');
return;
}
// Create config directory if it doesn't exist
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// Create config file with placeholder values
const config = {
llm: {
model_name: "your-model-here",
api_key: "your-api-key-here",
base_url: "your-optional-url-here"
},
agent: {
thread_id: "main"
}
};
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
console.log(`ā
Created config file at ~/.clyde-code/config.json`);
console.log('š Please update the config file with your API key and model before using clyde-code');
} catch (error) {
console.warn('ā ļø Failed to create config file:', error.message);
console.log('You can create it manually at ~/.clyde-code/config.json');
}
}
async function main() {
console.log('Installing clyde-code Python package...');
// Check Python version
const pythonOk = await checkPythonVersion();
if (!pythonOk) {
console.error('ā Python 3.12+ is required but not found.');
console.error('Please install Python 3.12 or later.');
process.exit(1);
}
// Try installation methods in order of preference (uv first, then pipx)
console.log('š Checking for Python package managers...');
let hasUv = await checkUv();
const hasPipx = await checkPipx();
if (hasUv) {
console.log(' Found uv ā');
}
if (hasPipx) {
console.log(' Found pipx ā');
}
// If uv is not found, ask user if they want to install it
if (!hasUv) {
console.log(' uv not found');
const response = await promptUser('Would you like to install uv? (recommended) [y/N]: ');
if (response === 'y' || response === 'yes') {
const uvInstalled = await installUv();
if (uvInstalled) {
hasUv = await checkUv();
}
}
}
let success = false;
if (hasUv) {
success = await installWithUv();
}
if (!success && hasPipx) {
success = await installWithPipx();
}
if (!success) {
console.error('ā Installation failed. Please install uv or pipx first:');
console.error(' pip install uv # then: uv tool install clyde-code');
console.error(' or: pip install pipx # then: pipx install clyde-code');
console.error(' or: brew install uv # on macOS');
process.exit(1);
}
// Create config file
await createConfigFile();
console.log('\nš Installation complete! You can now use: clyde-code');
}
main().catch((error) => {
console.error('Installation failed:', error.message);
process.exit(1);
});