tribe-cli
Version:
TRIBE multi-agent development system - Zero to productive with one command
881 lines (753 loc) • 33.8 kB
JavaScript
const os = require('os');
const fs = require('fs');
const path = require('path');
const https = require('https');
const { execSync, spawn } = require('child_process');
const chalk = require('chalk');
const ora = require('ora');
const which = require('which');
const platform = os.platform();
const arch = os.arch();
const homeDir = os.homedir();
const binDir = path.join(homeDir, 'bin');
const tribeDir = path.join(homeDir, '.tribe');
// Ensure local bin directory exists
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
}
// Ensure TRIBE config directory exists
if (!fs.existsSync(tribeDir)) {
fs.mkdirSync(tribeDir, { recursive: true });
}
const log = {
success: (msg) => console.log(chalk.green('✓'), msg),
error: (msg) => console.log(chalk.red('✗'), msg),
warning: (msg) => console.log(chalk.yellow('⚠'), msg),
info: (msg) => console.log(chalk.blue('ℹ'), msg),
step: (msg) => console.log(chalk.cyan('→'), msg)
};
async function checkCommand(cmd) {
try {
await which(cmd);
return true;
} catch {
return false;
}
}
async function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
https.get(url, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
// Handle redirects
return downloadFile(response.headers.location, dest).then(resolve, reject);
}
if (response.statusCode !== 200) {
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
return;
}
response.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
}).on('error', reject);
});
}
async function installDocker() {
if (await checkCommand('docker')) {
// Check if Docker daemon is running
try {
execSync('docker info', { stdio: 'ignore' });
log.success('Docker is already working');
return true;
} catch {
log.warning('Docker CLI found but daemon not running');
}
}
const spinner = ora('Installing Docker CLI...').start();
try {
let dockerUrl;
if (platform === 'darwin') {
// Docker uses aarch64 instead of arm64 for macOS
const dockerArch = arch === 'arm64' ? 'aarch64' : 'x86_64';
dockerUrl = `https://download.docker.com/mac/static/stable/${dockerArch}/docker-24.0.7.tgz`;
} else if (platform === 'linux') {
dockerUrl = `https://download.docker.com/linux/static/stable/${arch}/docker-24.0.7.tgz`;
} else {
throw new Error(`Unsupported platform: ${platform}`);
}
const tempFile = path.join(os.tmpdir(), 'docker.tgz');
await downloadFile(dockerUrl, tempFile);
// Extract Docker CLI
execSync(`tar -xzf ${tempFile} -C ${os.tmpdir()}`);
const dockerBinary = path.join(os.tmpdir(), 'docker', 'docker');
const dockerDest = path.join(binDir, 'docker');
fs.copyFileSync(dockerBinary, dockerDest);
fs.chmodSync(dockerDest, '755');
// Cleanup
fs.rmSync(tempFile);
fs.rmSync(path.join(os.tmpdir(), 'docker'), { recursive: true });
spinner.succeed('Docker CLI installed');
return true;
} catch (error) {
spinner.fail(`Docker CLI installation failed: ${error.message}`);
return false;
}
}
async function installColima() {
if (platform !== 'darwin') {
log.info('Colima is only needed on macOS - skipping');
return true;
}
if (await checkCommand('colima')) {
log.success('Colima already installed');
return true;
}
const spinner = ora('Installing Colima...').start();
try {
// Strategy 1: Try Homebrew if available (better signing)
try {
execSync('brew --version', { stdio: 'ignore' });
spinner.text = 'Installing Colima via Homebrew...';
execSync('brew install colima', { stdio: 'ignore' });
spinner.succeed('Colima installed via Homebrew');
return true;
} catch (brewError) {
// Homebrew not available, continue with direct download
spinner.text = 'Installing Colima (direct download)...';
}
// Strategy 2: Direct download with Gatekeeper approval
const colimaUrl = `https://github.com/abiosoft/colima/releases/latest/download/colima-${platform}-${arch}`;
const colimaDest = path.join(binDir, 'colima');
await downloadFile(colimaUrl, colimaDest);
fs.chmodSync(colimaDest, '755');
// Try to remove quarantine attribute (macOS Sequoia workaround)
try {
execSync(`xattr -d com.apple.quarantine ${colimaDest}`, { stdio: 'ignore' });
log.info('Removed quarantine attribute from Colima');
} catch (error) {
// Quarantine attribute may not exist, which is fine
log.info('Colima installed (quarantine handling not needed)');
}
spinner.succeed('Colima installed');
return true;
} catch (error) {
spinner.fail(`Colima installation failed: ${error.message}`);
return false;
}
}
async function installLima() {
if (platform !== 'darwin') {
return true; // Lima only needed on macOS
}
if (await checkCommand('limactl')) {
log.success('Lima already installed');
return true;
}
const spinner = ora('Installing Lima...').start();
try {
// Strategy 1: Try Homebrew if available (better signing)
try {
execSync('brew --version', { stdio: 'ignore' });
spinner.text = 'Installing Lima via Homebrew...';
execSync('brew install lima', { stdio: 'ignore' });
spinner.succeed('Lima installed via Homebrew');
return true;
} catch (brewError) {
// Homebrew not available, continue with direct download
spinner.text = 'Installing Lima (direct download)...';
}
// Strategy 2: Direct download
// Get latest Lima release
const response = await fetch('https://api.github.com/repos/lima-vm/lima/releases/latest');
const release = await response.json();
const version = release.tag_name;
const archName = arch === 'arm64' ? 'arm64' : 'x86_64';
const limaUrl = `https://github.com/lima-vm/lima/releases/download/${version}/lima-${version.replace('v', '')}-Darwin-${archName}.tar.gz`;
const tempFile = path.join(os.tmpdir(), 'lima.tar.gz');
await downloadFile(limaUrl, tempFile);
// Extract Lima
const extractDir = path.join(os.tmpdir(), 'lima-extract');
fs.mkdirSync(extractDir, { recursive: true });
execSync(`tar -xzf ${tempFile} -C ${extractDir}`);
// Lima tarball extracts directly to bin/ directory
const limaBinDir = path.join(extractDir, 'bin');
if (fs.existsSync(limaBinDir)) {
// Copy all Lima binaries
const binaries = fs.readdirSync(limaBinDir);
binaries.forEach(binary => {
const src = path.join(limaBinDir, binary);
const dest = path.join(binDir, binary);
fs.copyFileSync(src, dest);
fs.chmodSync(dest, '755');
// Remove quarantine attribute
try {
execSync(`xattr -d com.apple.quarantine ${dest}`, { stdio: 'ignore' });
} catch (error) {
// Quarantine attribute may not exist, which is fine
}
});
} else {
throw new Error('Lima binaries not found in expected location');
}
// Copy share directory (required for guest agents)
const limaShareDir = path.join(extractDir, 'share');
const destShareDir = path.join(homeDir, 'share');
if (fs.existsSync(limaShareDir)) {
if (!fs.existsSync(destShareDir)) {
fs.mkdirSync(destShareDir, { recursive: true });
}
try {
// Create lima directory in share
const limaDir = path.join(destShareDir, 'lima');
if (!fs.existsSync(limaDir)) {
fs.mkdirSync(limaDir, { recursive: true });
}
// Strategy 1: Try bundled guest agent (packaged with NPX)
const bundledGuestAgent = path.join(__dirname, 'lima-guestagent.Linux-aarch64.gz');
if (fs.existsSync(bundledGuestAgent)) {
log.info('Using bundled Lima guest agent');
const guestAgentDest = path.join(limaDir, 'lima-guestagent.Linux-aarch64.gz');
fs.copyFileSync(bundledGuestAgent, guestAgentDest);
// Extract the guest agent (Colima needs it uncompressed)
try {
execSync(`gunzip -f "${guestAgentDest}"`);
} catch (gunzipError) {
// If gunzip fails, try manual extraction
const zlib = require('zlib');
const compressed = fs.readFileSync(guestAgentDest);
const decompressed = zlib.gunzipSync(compressed);
const extractedPath = guestAgentDest.replace('.gz', '');
fs.writeFileSync(extractedPath, decompressed);
fs.unlinkSync(guestAgentDest); // Remove compressed version
}
// Create basic default template for Colima
const templatesDir = path.join(limaDir, 'templates');
if (!fs.existsSync(templatesDir)) {
fs.mkdirSync(templatesDir, { recursive: true });
}
const defaultTemplate = `# Basic default template for Colima
images:
- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
arch: "x86_64"
- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img"
arch: "aarch64"
mounts:
- location: "~"
writable: false
- location: "/tmp/lima"
writable: true
containerd:
system: false
user: false
`;
fs.writeFileSync(path.join(templatesDir, 'default.yaml'), defaultTemplate);
log.info('Created basic Lima template');
} else {
// Strategy 2: Try from tarball (fallback)
const guestAgentSrc = path.join(limaShareDir, 'lima-guestagent.Linux-aarch64.gz');
const templatesSrc = path.join(limaShareDir, 'templates');
if (fs.existsSync(guestAgentSrc)) {
// Copy and extract guest agent
const guestAgentDest = path.join(limaDir, 'lima-guestagent.Linux-aarch64.gz');
fs.copyFileSync(guestAgentSrc, guestAgentDest);
// Extract the guest agent (Colima needs it uncompressed)
try {
execSync(`gunzip -f "${guestAgentDest}"`);
} catch (gunzipError) {
// If gunzip fails, try manual extraction
const zlib = require('zlib');
const compressed = fs.readFileSync(guestAgentDest);
const decompressed = zlib.gunzipSync(compressed);
const extractedPath = guestAgentDest.replace('.gz', '');
fs.writeFileSync(extractedPath, decompressed);
fs.unlinkSync(guestAgentDest); // Remove compressed version
}
} else {
throw new Error('Lima guest agent not found in tarball or bundled');
}
if (fs.existsSync(templatesSrc)) {
// Copy templates directory
execSync(`cp -R "${templatesSrc}" "${limaDir}/templates"`);
} else {
// Create minimal template as fallback
const templatesDir = path.join(limaDir, 'templates');
fs.mkdirSync(templatesDir, { recursive: true });
fs.writeFileSync(path.join(templatesDir, 'default.yaml'), defaultTemplate);
}
}
} catch (error) {
log.warning(`Lima share installation failed: ${error.message}`);
log.warning('You may need to install Lima manually: brew install lima');
}
}
// Cleanup
fs.rmSync(tempFile);
fs.rmSync(extractDir, { recursive: true });
spinner.succeed('Lima installed');
return true;
} catch (error) {
spinner.fail(`Lima installation failed: ${error.message}`);
log.warning('You may need to install Lima manually: brew install lima');
return false;
}
}
async function installKind() {
if (await checkCommand('kind')) {
log.success('KIND already installed');
return true;
}
const spinner = ora('Installing KIND...').start();
try {
const kindUrl = `https://kind.sigs.k8s.io/dl/v0.20.0/kind-${platform}-${arch}`;
const kindDest = path.join(binDir, 'kind');
await downloadFile(kindUrl, kindDest);
fs.chmodSync(kindDest, '755');
spinner.succeed('KIND installed');
return true;
} catch (error) {
spinner.fail(`KIND installation failed: ${error.message}`);
return false;
}
}
async function installKubectl() {
if (await checkCommand('kubectl')) {
log.success('kubectl already installed');
return true;
}
const spinner = ora('Installing kubectl...').start();
try {
// Get latest stable version
const versionResponse = await fetch('https://dl.k8s.io/release/stable.txt');
const version = await versionResponse.text();
const kubectlUrl = `https://dl.k8s.io/release/${version.trim()}/bin/${platform}/${arch}/kubectl`;
const kubectlDest = path.join(binDir, 'kubectl');
await downloadFile(kubectlUrl, kubectlDest);
fs.chmodSync(kubectlDest, '755');
spinner.succeed('kubectl installed');
return true;
} catch (error) {
spinner.fail(`kubectl installation failed: ${error.message}`);
return false;
}
}
async function installTribeCli() {
const spinner = ora('Installing TRIBE CLI...').start();
try {
const tribeDest = path.join(binDir, 'tribe');
// First check if we have a bundled binary
const bundledBinary = path.join(__dirname, 'tribe');
if (fs.existsSync(bundledBinary)) {
fs.copyFileSync(bundledBinary, tribeDest);
fs.chmodSync(tribeDest, '755');
spinner.succeed('TRIBE CLI installed from bundled binary');
return true;
}
// Check if we have local source
const sourceFile = path.join(__dirname, 'cluster-cli.go');
if (fs.existsSync(sourceFile)) {
// Build from source
try {
execSync('go version', { stdio: 'ignore' });
execSync(`cd ${__dirname} && go build -o tribe cluster-cli.go client.go`);
fs.copyFileSync(path.join(__dirname, 'tribe'), tribeDest);
fs.chmodSync(tribeDest, '755');
spinner.succeed('TRIBE CLI built from source');
return true;
} catch {
spinner.warn('Go not available, trying pre-built binary...');
}
}
// Try pre-built binary from GitHub
const tribeUrl = `https://github.com/0zen/0zen/releases/latest/download/tribe-${platform}-${arch}`;
try {
await downloadFile(tribeUrl, tribeDest);
fs.chmodSync(tribeDest, '755');
spinner.succeed('TRIBE CLI installed');
return true;
} catch {
// Fallback: look for any existing tribe binary
const possiblePaths = [
path.join(__dirname, '..', 'tribe-cli'),
path.join(__dirname, '..', 'tribe'),
'./tribe-cli',
'./tribe'
];
for (const possiblePath of possiblePaths) {
if (fs.existsSync(possiblePath)) {
fs.copyFileSync(possiblePath, tribeDest);
fs.chmodSync(tribeDest, '755');
spinner.succeed('TRIBE CLI installed from local binary');
return true;
}
}
throw new Error('No TRIBE CLI binary available');
}
} catch (error) {
spinner.fail(`TRIBE CLI installation failed: ${error.message}`);
return false;
}
}
async function startContainerRuntime() {
if (platform !== 'darwin') {
return true; // Linux uses Docker daemon directly
}
// Check if container runtime is already working
try {
execSync('docker info', { stdio: 'ignore' });
log.success('Container runtime is already working');
return true;
} catch {
// Try to start Colima with different approaches
if (await checkCommand('colima')) {
const spinner = ora('Starting Colima container runtime...').start();
try {
// Strategy 1: Quick start with minimal resources
spinner.text = 'Starting Colima (minimal setup)...';
execSync('colima start --cpu 1 --memory 2 --disk 5 --vm-type=vz', {
stdio: 'pipe',
timeout: 30000 // 30 second timeout
});
// Test if it worked
execSync('docker info', { stdio: 'ignore' });
spinner.succeed('Colima started successfully');
return true;
} catch (error) {
// Strategy 2: Start in background and don't wait
try {
spinner.text = 'Starting Colima in background...';
const child = spawn(path.join(binDir, 'colima'), ['start', '--cpu', '2', '--memory', '4'], {
detached: true,
stdio: 'ignore',
env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
});
child.unref(); // Don't wait for completion
spinner.succeed('Colima startup initiated (background)');
log.info('Colima is starting in the background');
log.info('Run "colima status" to check progress');
log.info('Run "docker info" to test when ready');
return true;
} catch (bgError) {
spinner.fail('Failed to start Colima');
log.warning('Container runtime startup failed (likely due to macOS system restrictions)');
log.info('Options to fix:');
log.info('');
log.info('Option 1 - Manual Colima start:');
log.info(' colima start --cpu 2 --memory 4 # Start container runtime');
log.info(' # This downloads a 344MB disk image (may take time)');
log.info('');
log.info('Option 2 - Use Docker Desktop (easier):');
log.info(' Download from: https://docs.docker.com/desktop/install/mac-install/');
log.info(' # Docker Desktop handles all container runtime setup');
log.info('');
log.info('Option 3 - Use Homebrew (recommended):');
log.info(' brew install colima docker');
log.info(' colima start');
log.info('');
return false;
}
}
}
}
return false;
}
async function updatePath() {
const shell = process.env.SHELL || '/bin/zsh';
const rcFile = shell.includes('zsh') ? '.zshrc' : '.bashrc';
const rcPath = path.join(homeDir, rcFile);
const pathExport = `export PATH="${binDir}:$PATH"`;
try {
const rcContent = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, 'utf8') : '';
if (!rcContent.includes(pathExport)) {
fs.appendFileSync(rcPath, `\n# Added by TRIBE CLI installer\n${pathExport}\n`);
log.success(`Updated ${rcFile} with PATH`);
}
} catch (error) {
log.warning(`Failed to update ${rcFile}: ${error.message}`);
}
// Update current process PATH
process.env.PATH = `${binDir}:${process.env.PATH}`;
}
async function verifyInstallation() {
const spinner = ora('Verifying installation...').start();
const tools = ['docker', 'kind', 'kubectl', 'tribe'];
const results = {};
for (const tool of tools) {
results[tool] = await checkCommand(tool);
}
// Special check for container runtime
let containerWorking = false;
try {
execSync('docker info', { stdio: 'ignore' });
containerWorking = true;
} catch (error) {
containerWorking = false;
}
spinner.stop();
console.log('\n' + chalk.bold('Installation Summary:'));
tools.forEach(tool => {
const status = results[tool] ? chalk.green('✓') : chalk.red('✗');
console.log(`${status} ${tool}`);
});
const extraStatus = containerWorking ? chalk.green('✓') : chalk.yellow('⚠');
console.log(`${extraStatus} Container runtime`);
if (platform === 'darwin') {
const colimaInstalled = await checkCommand('colima');
const limaInstalled = await checkCommand('limactl');
console.log(`${colimaInstalled ? chalk.green('✓') : chalk.yellow('⚠')} Colima`);
console.log(`${limaInstalled ? chalk.green('✓') : chalk.yellow('⚠')} Lima`);
}
return Object.values(results).every(r => r) && containerWorking;
}
async function checkClusterExists() {
try {
// Check if TRIBE namespace exists in any context
execSync('kubectl get namespace tribe-system', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
async function checkColimaRunning() {
try {
execSync('colima status', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
async function startColimaWithKubernetes() {
const spinner = ora('Starting Colima with Kubernetes...').start();
try {
// Check if already running
if (await checkColimaRunning()) {
spinner.succeed('Colima is already running');
return true;
}
// Start Colima with Kubernetes enabled
spinner.text = 'Starting Colima (this may take a few minutes on first run)...';
execSync('colima start --kubernetes --cpu 4 --memory 8 --disk 20', {
stdio: 'pipe',
env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
});
// Verify it's working
execSync('kubectl version --client', { stdio: 'ignore' });
spinner.succeed('Colima started with Kubernetes');
return true;
} catch (error) {
spinner.fail('Failed to start Colima with Kubernetes');
log.error(error.message);
return false;
}
}
async function deployTribeCluster() {
const spinner = ora('Deploying TRIBE cluster...').start();
try {
// Create a marker file to indicate first-run deployment
const deploymentMarker = path.join(tribeDir, '.cluster-deployed');
// Check if we've already deployed
if (fs.existsSync(deploymentMarker)) {
// Check if cluster actually exists
if (await checkClusterExists()) {
spinner.succeed('TRIBE cluster already deployed');
return true;
}
// Marker exists but cluster doesn't - remove marker and redeploy
fs.unlinkSync(deploymentMarker);
}
// Copy deployment YAML to .tribe directory
const sourceYaml = path.join(__dirname, 'tribe-deployment.yaml');
const destYaml = path.join(tribeDir, 'tribe-deployment.yaml');
if (fs.existsSync(sourceYaml)) {
fs.copyFileSync(sourceYaml, destYaml);
log.info('Copied deployment configuration');
}
// Run tribe start command with deployment YAML path
spinner.text = 'Running TRIBE cluster deployment...';
const tribePath = path.join(binDir, 'tribe');
// Set environment variable for the deployment YAML location
const env = {
...process.env,
PATH: `${binDir}:${process.env.PATH}`,
TRIBE_DEPLOYMENT_YAML: destYaml
};
// Execute tribe start
execSync(`${tribePath} start`, {
stdio: 'pipe',
env: env
});
// Create marker file
fs.writeFileSync(deploymentMarker, new Date().toISOString());
spinner.succeed('TRIBE cluster deployed successfully');
return true;
} catch (error) {
spinner.fail('Failed to deploy TRIBE cluster');
log.error(error.message);
log.info('You can manually deploy later with: tribe start');
return false;
}
}
async function promptForClusterSetup() {
// Simple prompt without external dependencies
return new Promise((resolve) => {
console.log('\n' + chalk.bold('🚀 TRIBE Cluster Setup'));
console.log('\nWould you like to set up the TRIBE cluster now?');
console.log('This will:');
console.log(' • Start Colima with Kubernetes');
console.log(' • Deploy all TRIBE services');
console.log(' • Set up port forwarding');
console.log('\n' + chalk.yellow('Note: This requires ~2GB disk space and may take a few minutes'));
process.stdout.write('\nSet up now? [Y/n]: ');
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.once('data', (data) => {
process.stdin.pause();
const answer = data.toString().trim().toLowerCase();
resolve(answer === '' || answer === 'y' || answer === 'yes');
});
});
}
async function main() {
console.log(chalk.bold.blue('\n🚀 TRIBE CLI Complete Installer\n'));
log.info(`Detected: ${platform} (${arch})`);
log.info(`Installing to: ${binDir}`);
// Update PATH first
await updatePath();
const tasks = [
{ name: 'Docker CLI', fn: installDocker },
{ name: 'Colima', fn: installColima },
{ name: 'Lima', fn: installLima },
{ name: 'KIND', fn: installKind },
{ name: 'kubectl', fn: installKubectl },
{ name: 'TRIBE CLI', fn: installTribeCli }
];
let allSuccess = true;
for (const task of tasks) {
log.step(`Installing ${task.name}...`);
const success = await task.fn();
if (!success) allSuccess = false;
}
// Try to start container runtime
await startContainerRuntime();
// Verify everything
const verified = await verifyInstallation();
if (verified) {
// Check if cluster already exists
const clusterExists = await checkClusterExists();
if (!clusterExists) {
// Prompt for cluster setup
const shouldSetup = await promptForClusterSetup();
if (shouldSetup) {
console.log('');
// Start Colima with Kubernetes if on macOS
if (platform === 'darwin') {
const colimaStarted = await startColimaWithKubernetes();
if (!colimaStarted) {
log.error('Failed to start Colima. Please run manually:');
console.log(' colima start --kubernetes');
console.log(' tribe start');
process.exit(1);
}
}
// Deploy TRIBE cluster
const deployed = await deployTribeCluster();
if (deployed) {
console.log('\n' + chalk.bold.green('✨ TRIBE is ready!'));
console.log('');
log.info('Quick start:');
console.log(' tribe # Launch interactive CLI');
console.log(' tribe status # Check cluster status');
console.log(' tribe create-task # Create a new task');
console.log('');
log.info('First time? The CLI will guide you through creating your first project!');
} else {
log.warning('Cluster deployment failed, but you can try manually:');
console.log(' tribe start');
}
} else {
console.log('\n' + chalk.bold('Setup Complete!'));
log.info('You can set up the cluster later with:');
console.log(' tribe start');
}
} else {
console.log('\n' + chalk.bold.green('✨ TRIBE is ready!'));
log.success('Cluster is already running');
console.log('');
log.info('Commands:');
console.log(' tribe # Launch interactive CLI');
console.log(' tribe status # Check status');
console.log(' tribe create-task # Create a new task');
}
} else {
console.log('\n' + chalk.bold('Next Steps:'));
log.warning('Some components need attention:');
console.log('');
// Check container runtime for guidance
let runtimeWorking = false;
try {
execSync('docker info', { stdio: 'ignore' });
runtimeWorking = true;
} catch {
runtimeWorking = false;
}
if (!runtimeWorking) {
log.info('Start container runtime:');
console.log(' colima start # macOS');
console.log(' sudo systemctl start docker # Linux');
}
console.log('');
log.info('Restart your shell or run: source ~/.zshrc');
log.info('Then run: tribe start');
}
}
// Handle fetch polyfill for older Node versions
if (!global.fetch) {
global.fetch = require('node-fetch');
}
if (require.main === module) {
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log(chalk.bold.blue('TRIBE CLI Installer\n'));
console.log('Usage: npx tribe-cli-local [options]\n');
console.log('Options:');
console.log(' --help, -h Show this help message');
console.log(' --verify Only verify existing installation');
console.log(' --dry-run Show what would be installed');
console.log('\nThis installer sets up the complete TRIBE development environment:');
console.log('• Docker CLI + Colima (macOS container runtime)');
console.log('• KIND (Kubernetes in Docker)');
console.log('• kubectl (Kubernetes CLI)');
console.log('• TRIBE CLI (Multi-agent orchestration)');
console.log('\nAfter installation:');
console.log(' tribe start # Start TRIBE cluster');
console.log(' tribe status # Check cluster status');
process.exit(0);
}
if (args.includes('--verify')) {
console.log(chalk.bold.blue('🔍 Verifying TRIBE Installation\n'));
verifyInstallation().then(success => {
process.exit(success ? 0 : 1);
});
return;
}
if (args.includes('--dry-run')) {
console.log(chalk.bold.blue('🔍 TRIBE CLI Installation Preview\n'));
log.info(`Platform: ${platform} (${arch})`);
log.info(`Install directory: ${binDir}`);
console.log('\nWould install:');
console.log('• Docker CLI (if not present)');
console.log('• Colima container runtime (macOS only)');
console.log('• Lima virtualization (macOS only)');
console.log('• KIND - Kubernetes in Docker');
console.log('• kubectl - Kubernetes CLI');
console.log('• TRIBE CLI - Multi-agent system');
process.exit(0);
}
main().catch(error => {
console.error(chalk.red('Installation failed:'), error.message);
process.exit(1);
});
}
module.exports = { main };