@_xtribe/cli 1.0.0-beta.22 → 1.0.0-beta.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +30 -7
  2. package/install-tribe.js +724 -191
  3. package/package.json +3 -5
package/README.md CHANGED
@@ -8,12 +8,18 @@ TRIBE is an AI-powered multi-agent development system that manages your entire d
8
8
  npx @_xtribe/cli
9
9
  ```
10
10
 
11
- That's it! This single command will:
12
- - ✅ Install all required tools (Docker, Kubernetes, kubectl)
13
- - Set up Colima container runtime (macOS)
14
- - ✅ Deploy the complete TRIBE cluster
15
- - ✅ Configure everything automatically
16
- - ✅ Guide you through creating your first project
11
+ That's it! This single command works on macOS, Linux, and Windows (WSL2).
12
+
13
+ **What it does:**
14
+ - ✅ Detects your platform automatically
15
+ - ✅ Installs all required tools (kubectl, TRIBE CLI)
16
+ - ✅ Sets up Kubernetes for your platform:
17
+ - **macOS**: Colima with Kubernetes
18
+ - **Linux**: K3s (lightweight Kubernetes)
19
+ - **Windows**: Guides to WSL2 setup
20
+ - ✅ Deploys the complete TRIBE cluster
21
+ - ✅ Configures your environment
22
+ - ✅ Guides you through creating your first project
17
23
 
18
24
  ## 🎯 What is TRIBE?
19
25
 
@@ -54,7 +60,12 @@ tribe review-task
54
60
 
55
61
  ## 🛠️ System Requirements
56
62
 
57
- - **macOS** or **Linux**
63
+ ### Supported Platforms
64
+ - **macOS** (Intel & Apple Silicon)
65
+ - **Linux** (Ubuntu, Debian, Fedora, etc.)
66
+ - **Windows** - Use WSL2 with Ubuntu
67
+
68
+ ### Hardware Requirements
58
69
  - **4GB RAM** minimum (8GB recommended)
59
70
  - **20GB disk space**
60
71
  - **Node.js 16+**
@@ -90,6 +101,8 @@ For detailed documentation, visit the [TRIBE Flow Guide](https://github.com/0zen
90
101
  ## 🆘 Troubleshooting
91
102
 
92
103
  ### Cluster not starting?
104
+
105
+ **macOS:**
93
106
  ```bash
94
107
  # Check if Colima is running
95
108
  colima status
@@ -99,6 +112,16 @@ colima start --kubernetes
99
112
  tribe start
100
113
  ```
101
114
 
115
+ **Linux:**
116
+ ```bash
117
+ # Check if K3s is running
118
+ sudo systemctl status k3s
119
+
120
+ # Start manually if needed
121
+ sudo systemctl start k3s
122
+ tribe start
123
+ ```
124
+
102
125
  ### Port conflicts?
103
126
  ```bash
104
127
  # Check what's using ports
package/install-tribe.js CHANGED
@@ -8,12 +8,17 @@ const { execSync, spawn } = require('child_process');
8
8
  const chalk = require('chalk');
9
9
  const ora = require('ora');
10
10
  const which = require('which');
11
+ const fetch = require('node-fetch');
12
+ const crypto = require('crypto'); // Added for checksum verification
11
13
 
12
14
  const platform = os.platform();
13
- const arch = os.arch();
15
+ // Map Node.js arch to standard naming (x64 -> amd64, etc.)
16
+ const nodeArch = os.arch();
17
+ const arch = nodeArch === 'x64' ? 'amd64' : (nodeArch === 'arm64' || nodeArch === 'aarch64' ? 'arm64' : nodeArch);
14
18
  const homeDir = os.homedir();
15
19
  const binDir = path.join(homeDir, 'bin');
16
20
  const tribeDir = path.join(homeDir, '.tribe');
21
+ const tribeBinDir = path.join(tribeDir, 'bin');
17
22
 
18
23
  // Ensure local bin directory exists
19
24
  if (!fs.existsSync(binDir)) {
@@ -25,6 +30,11 @@ if (!fs.existsSync(tribeDir)) {
25
30
  fs.mkdirSync(tribeDir, { recursive: true });
26
31
  }
27
32
 
33
+ // Ensure TRIBE bin directory exists
34
+ if (!fs.existsSync(tribeBinDir)) {
35
+ fs.mkdirSync(tribeBinDir, { recursive: true });
36
+ }
37
+
28
38
  const log = {
29
39
  success: (msg) => console.log(chalk.green('✓'), msg),
30
40
  error: (msg) => console.log(chalk.red('✗'), msg),
@@ -83,8 +93,25 @@ async function downloadFile(url, dest) {
83
93
  return;
84
94
  }
85
95
  response.pipe(file);
86
- file.on('finish', () => {
96
+ file.on('finish', async () => {
87
97
  file.close();
98
+
99
+ // Check if this might be a pointer file
100
+ const stats = fs.statSync(dest);
101
+ if (stats.size < 100) {
102
+ const content = fs.readFileSync(dest, 'utf8').trim();
103
+ // Check if content looks like a path (e.g., "main-121/tribe-linux-amd64")
104
+ if (content.match(/^[\w\-\/]+$/) && content.includes('/')) {
105
+ console.log(`📎 Following pointer to: ${content}`);
106
+ const baseUrl = url.substring(0, url.lastIndexOf('/'));
107
+ const actualBinaryUrl = `${baseUrl}/${content}`;
108
+
109
+ // Download the actual binary
110
+ fs.unlinkSync(dest); // Remove pointer file
111
+ await downloadFile(actualBinaryUrl, dest);
112
+ }
113
+ }
114
+
88
115
  resolve();
89
116
  });
90
117
  }).on('error', reject);
@@ -94,55 +121,91 @@ async function downloadFile(url, dest) {
94
121
  // Docker installation removed - Colima provides Docker runtime
95
122
 
96
123
  async function installK3s() {
97
- if (platform !== 'linux') {
98
- return true; // K3s is Linux only
99
- }
100
-
101
124
  const spinner = ora('Installing K3s (lightweight Kubernetes)...').start();
102
125
 
103
126
  try {
104
- // Check if already installed
127
+ // Check if K3s is already installed
105
128
  try {
106
- execSync('k3s --version', { stdio: 'ignore' });
129
+ execSync('which k3s', { stdio: 'ignore' });
107
130
  spinner.succeed('K3s already installed');
108
131
  return true;
109
132
  } catch {
110
133
  // Not installed, continue
111
134
  }
112
135
 
113
- // One-line K3s installation
114
- spinner.text = 'Installing K3s (this may take a few minutes)...';
115
- console.log(chalk.yellow('\n📦 Installing K3s - Lightweight Kubernetes'));
116
- console.log(chalk.gray('This is a one-line installation that will:'));
117
- console.log(chalk.gray(' • Download and install K3s'));
118
- console.log(chalk.gray(' • Configure systemd service'));
119
- console.log(chalk.gray(' • Set up kubectl access'));
120
- console.log(chalk.gray(' • Disable Traefik (we use our own ingress)\n'));
136
+ // Check if we have permission to install
137
+ const needsSudo = process.getuid && process.getuid() !== 0;
121
138
 
122
- execSync('curl -sfL https://get.k3s.io | sh -s - --disable traefik --write-kubeconfig-mode 644', {
123
- stdio: 'inherit',
124
- shell: '/bin/bash'
125
- });
139
+ // Check if we're in a container or CI environment
140
+ const isContainer = process.env.container === 'docker' ||
141
+ fs.existsSync('/.dockerenv') ||
142
+ !fs.existsSync('/run/systemd/system') ||
143
+ process.env.CI === 'true';
126
144
 
127
- // Wait for K3s to be ready
128
- spinner.text = 'Waiting for K3s to be ready...';
129
- await new Promise(resolve => setTimeout(resolve, 10000));
145
+ if (isContainer) {
146
+ spinner.warn('Running in container/CI - K3s installation skipped');
147
+ log.info('K3s requires systemd and privileged access');
148
+ log.info('For containers, consider using KIND or external cluster');
149
+ return true; // Don't fail in containers
150
+ }
151
+
152
+ // Download and install K3s
153
+ spinner.text = 'Downloading K3s installer...';
154
+
155
+ const installCommand = needsSudo ?
156
+ 'curl -sfL https://get.k3s.io | sudo sh -s - --write-kubeconfig-mode 644' :
157
+ 'curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644';
130
158
 
131
- // Make kubectl available
132
159
  try {
133
- execSync('sudo ln -sf /usr/local/bin/k3s /usr/local/bin/kubectl', { stdio: 'ignore' });
134
- } catch {
135
- // Link might already exist
160
+ execSync(installCommand, {
161
+ stdio: 'pipe',
162
+ env: { ...process.env, INSTALL_K3S_SKIP_START: 'false' }
163
+ });
164
+
165
+ spinner.succeed('K3s installed successfully');
166
+
167
+ // Wait for K3s to be ready
168
+ spinner.text = 'Waiting for K3s to start...';
169
+ await new Promise(resolve => setTimeout(resolve, 10000));
170
+
171
+ // Configure kubectl to use K3s
172
+ const k3sConfig = '/etc/rancher/k3s/k3s.yaml';
173
+ const userConfig = path.join(os.homedir(), '.kube', 'config');
174
+
175
+ if (fs.existsSync(k3sConfig)) {
176
+ fs.mkdirSync(path.dirname(userConfig), { recursive: true });
177
+
178
+ if (needsSudo) {
179
+ execSync(`sudo cp ${k3sConfig} ${userConfig} && sudo chown $(id -u):$(id -g) ${userConfig}`, { stdio: 'ignore' });
180
+ } else {
181
+ fs.copyFileSync(k3sConfig, userConfig);
182
+ }
183
+
184
+ // Update server address to use localhost
185
+ let configContent = fs.readFileSync(userConfig, 'utf8');
186
+ configContent = configContent.replace(/server: https:\/\/127\.0\.0\.1:6443/, 'server: https://localhost:6443');
187
+ fs.writeFileSync(userConfig, configContent);
188
+
189
+ spinner.succeed('K3s configured');
190
+ }
191
+
192
+ return true;
193
+ } catch (error) {
194
+ if (error.message.includes('permission denied') || error.message.includes('sudo')) {
195
+ spinner.fail('K3s installation requires sudo permission');
196
+ log.info('Please run the installer with sudo or install K3s manually:');
197
+ log.info(' curl -sfL https://get.k3s.io | sudo sh -');
198
+ return false;
199
+ }
200
+ throw error;
136
201
  }
137
202
 
138
- // Set KUBECONFIG for current process
139
- process.env.KUBECONFIG = '/etc/rancher/k3s/k3s.yaml';
140
-
141
- spinner.succeed('K3s installed successfully');
142
- return true;
143
203
  } catch (error) {
144
- spinner.fail(`K3s installation failed: ${error.message}`);
145
- log.info('You can install K3s manually with: curl -sfL https://get.k3s.io | sh -');
204
+ spinner.fail('K3s installation failed');
205
+ log.error(error.message);
206
+ log.info('Manual K3s installation:');
207
+ log.info(' curl -sfL https://get.k3s.io | sudo sh -');
208
+ log.info(' sudo systemctl enable --now k3s');
146
209
  return false;
147
210
  }
148
211
  }
@@ -205,21 +268,21 @@ async function installColima() {
205
268
  async function installKubectl() {
206
269
  // Linux with K3s already has kubectl
207
270
  if (platform === 'linux') {
208
- try {
271
+ try {
209
272
  execSync('k3s kubectl version --client', { stdio: 'ignore' });
210
273
  log.success('kubectl available via k3s');
211
274
  // Create a symlink for convenience
212
- try {
275
+ try {
213
276
  execSync('sudo ln -sf /usr/local/bin/k3s /usr/local/bin/kubectl', { stdio: 'ignore' });
214
277
  } catch {
215
278
  // Link might already exist
216
279
  }
217
- return true;
280
+ return true;
218
281
  } catch {
219
282
  // Continue with regular kubectl installation
220
- }
221
283
  }
222
-
284
+ }
285
+
223
286
  if (await checkCommand('kubectl')) {
224
287
  log.success('kubectl already installed');
225
288
  return true;
@@ -245,142 +308,395 @@ async function installKubectl() {
245
308
  }
246
309
  }
247
310
 
311
+ async function setupGlobalNpmCommand() {
312
+ const spinner = ora('Setting up global tribe command...').start();
313
+
314
+ try {
315
+ // Create a temporary directory for the global package
316
+ const tempDir = path.join(os.tmpdir(), 'tribe-global-install-' + Date.now());
317
+ fs.mkdirSync(tempDir, { recursive: true });
318
+
319
+ // Create package.json for global command
320
+ const packageJson = {
321
+ name: 'tribe-cli-global',
322
+ version: '1.0.0',
323
+ description: 'TRIBE CLI global command',
324
+ bin: {
325
+ tribe: './tribe-wrapper.js'
326
+ },
327
+ files: ['tribe-wrapper.js'],
328
+ private: true
329
+ };
330
+
331
+ fs.writeFileSync(
332
+ path.join(tempDir, 'package.json'),
333
+ JSON.stringify(packageJson, null, 2)
334
+ );
335
+
336
+ // Create the wrapper script
337
+ const wrapperScript = `#!/usr/bin/env node
338
+
339
+ const { spawn } = require('child_process');
340
+ const fs = require('fs');
341
+ const path = require('path');
342
+ const os = require('os');
343
+
344
+ // Path to the actual tribe binary installed by the installer
345
+ const tribeBinaryPath = path.join(os.homedir(), '.tribe', 'bin', 'tribe');
346
+
347
+ // Check if tribe binary exists
348
+ if (!fs.existsSync(tribeBinaryPath)) {
349
+ console.error('Error: TRIBE CLI binary not found.');
350
+ console.error('Please reinstall using: npx @_xtribe/cli --force');
351
+ process.exit(1);
352
+ }
353
+
354
+ // Make sure binary is executable
355
+ try {
356
+ fs.accessSync(tribeBinaryPath, fs.constants.X_OK);
357
+ } catch (err) {
358
+ console.error('Error: TRIBE CLI binary is not executable.');
359
+ console.error('Please reinstall using: npx @_xtribe/cli --force');
360
+ process.exit(1);
361
+ }
362
+
363
+ // Forward all arguments to the actual tribe binary
364
+ const child = spawn(tribeBinaryPath, process.argv.slice(2), {
365
+ stdio: 'inherit',
366
+ env: process.env
367
+ });
368
+
369
+ child.on('exit', (code) => {
370
+ process.exit(code || 0);
371
+ });
372
+
373
+ child.on('error', (err) => {
374
+ if (err.code === 'ENOENT') {
375
+ console.error('Error: TRIBE CLI binary not found at expected location.');
376
+ console.error('Please reinstall using: npx @_xtribe/cli --force');
377
+ } else {
378
+ console.error('Failed to execute tribe:', err.message);
379
+ }
380
+ process.exit(1);
381
+ });`;
382
+
383
+ const wrapperPath = path.join(tempDir, 'tribe-wrapper.js');
384
+ fs.writeFileSync(wrapperPath, wrapperScript);
385
+ fs.chmodSync(wrapperPath, '755');
386
+
387
+ // Install globally using npm
388
+ spinner.text = 'Installing tribe command globally...';
389
+ try {
390
+ // First try with --force to handle conflicts
391
+ execSync('npm install -g . --force', {
392
+ cwd: tempDir,
393
+ stdio: 'pipe'
394
+ });
395
+ spinner.succeed('Global tribe command installed successfully');
396
+ } catch (error) {
397
+ // Try with sudo if permission denied
398
+ if (error.message.includes('EACCES') || error.message.includes('permission')) {
399
+ spinner.warn('Global installation requires sudo permission');
400
+ log.info('Attempting with sudo...');
401
+ try {
402
+ execSync('sudo npm install -g . --force', {
403
+ cwd: tempDir,
404
+ stdio: 'inherit'
405
+ });
406
+ spinner.succeed('Global tribe command installed successfully (with sudo)');
407
+ } catch (sudoError) {
408
+ throw new Error('Failed to install globally. You may need to run with sudo or fix npm permissions.');
409
+ }
410
+ } else if (error.message.includes('EEXIST')) {
411
+ // Try to remove existing and retry
412
+ spinner.text = 'Removing conflicting global command...';
413
+ try {
414
+ execSync('npm uninstall -g tribe-cli-global @_xtribe/cli', { stdio: 'ignore' });
415
+ execSync('npm install -g . --force', {
416
+ cwd: tempDir,
417
+ stdio: 'pipe'
418
+ });
419
+ spinner.succeed('Global tribe command installed successfully (after cleanup)');
420
+ } catch (retryError) {
421
+ throw new Error('Failed to install globally due to conflicts. Try running: npm uninstall -g @_xtribe/cli');
422
+ }
423
+ } else {
424
+ throw error;
425
+ }
426
+ }
427
+
428
+ // Clean up temp directory
429
+ try {
430
+ fs.rmSync(tempDir, { recursive: true, force: true });
431
+ } catch (cleanupError) {
432
+ // Ignore cleanup errors
433
+ }
434
+
435
+ return true;
436
+ } catch (error) {
437
+ spinner.fail(`Failed to set up global command: ${error.message}`);
438
+ log.info('You can still use tribe via the direct path: ~/.tribe/bin/tribe');
439
+ return false;
440
+ }
441
+ }
442
+
248
443
  async function installTribeCLI() {
249
444
  const spinner = ora('Installing TRIBE CLI...').start();
250
445
 
251
446
  try {
252
- const tribeDest = path.join(binDir, 'tribe');
447
+ const tribeDest = path.join(tribeBinDir, 'tribe');
253
448
 
254
- // Skip if already installed and working
449
+ // Unconditionally remove existing binary to ensure a clean install
255
450
  if (fs.existsSync(tribeDest)) {
451
+ spinner.text = 'Removing existing TRIBE CLI installation...';
452
+ fs.unlinkSync(tribeDest);
453
+ log.success('Removed existing TRIBE CLI');
454
+ spinner.text = 'Installing TRIBE CLI...'; // Reset spinner text
455
+ }
456
+
457
+ // Also remove old location if exists
458
+ const oldTribeDest = path.join(binDir, 'tribe');
459
+ if (fs.existsSync(oldTribeDest)) {
256
460
  try {
257
- execSync(`${tribeDest} version`, { stdio: 'ignore' });
258
- spinner.succeed('TRIBE CLI already installed');
259
- return true;
260
- } catch {
261
- // Binary exists but doesn't work, remove it
262
- fs.unlinkSync(tribeDest);
461
+ fs.unlinkSync(oldTribeDest);
462
+ log.info('Removed old tribe binary from ~/bin');
463
+ } catch (e) {
464
+ // Ignore errors
263
465
  }
264
466
  }
265
467
 
266
468
  // Download pre-built binary from GitHub
267
- const arch = process.arch === 'x64' ? 'amd64' : 'arm64';
469
+ // Proper architecture detection for all platforms
470
+ let arch;
471
+ if (process.arch === 'x64' || process.arch === 'x86_64') {
472
+ arch = 'amd64';
473
+ } else if (process.arch === 'arm64' || process.arch === 'aarch64') {
474
+ arch = 'arm64';
475
+ } else {
476
+ arch = process.arch; // fallback
477
+ }
478
+
268
479
  const platform = os.platform();
269
480
 
270
481
  // Multiple sources for reliability
271
- const sources = [
272
- // GitHub releases (primary source)
273
- `https://github.com/TRIBE-INC/0zen/releases/latest/download/tribe-${platform}-${arch}`,
274
- // Try jsDelivr CDN as fallback
275
- `https://cdn.jsdelivr.net/gh/TRIBE-INC/0zen@latest/releases/tribe-${platform}-${arch}`,
276
- // Direct raw GitHub as last resort
277
- `https://raw.githubusercontent.com/TRIBE-INC/0zen/main/releases/tribe-${platform}-${arch}`
278
- ];
482
+ // For testing, use a direct URL to our releases folder
483
+ const isTestEnv = process.env.TRIBE_TEST_BINARY_URL;
484
+ const githubRepo = process.env.TRIBE_INSTALLER_REPO || 'TRIBE-INC/releases';
485
+
486
+ let downloadUrl = '';
487
+
488
+ try {
489
+ spinner.text = 'Fetching latest release information from GitHub...';
490
+ const response = await fetch(`https://api.github.com/repos/${githubRepo}/releases`);
491
+ if (!response.ok) {
492
+ throw new Error(`GitHub API returned ${response.status}`);
493
+ }
494
+ const releases = await response.json();
495
+ if (!releases || releases.length === 0) {
496
+ throw new Error('No releases found');
497
+ }
498
+ const latestRelease = releases[0];
499
+ const assetName = `tribe-${platform}-${arch}`;
500
+ const asset = latestRelease.assets.find(a => a.name === assetName);
501
+
502
+ if (!asset) {
503
+ throw new Error(`No binary found for ${platform}-${arch} in release ${latestRelease.tag_name}`);
504
+ }
505
+
506
+ downloadUrl = asset.browser_download_url;
507
+ spinner.text = `Found latest release: ${latestRelease.tag_name}`;
508
+ } catch (error) {
509
+ spinner.fail(`Failed to get release info: ${error.message}`);
510
+ // Fallback to other methods if the API fails
511
+ }
512
+
513
+ const sources = isTestEnv ? [isTestEnv] : [
514
+ downloadUrl,
515
+ // Public releases repository (primary source)
516
+ `https://raw.githubusercontent.com/${githubRepo}/main/cli/latest-tribe-${platform}-${arch}`,
517
+ // GitHub releases (fallback)
518
+ `https://github.com/${githubRepo}/releases/latest/download/tribe-${platform}-${arch}`,
519
+ // Try jsDelivr CDN (for better availability)
520
+ `https://cdn.jsdelivr.net/gh/${githubRepo}@main/cli/latest-tribe-${platform}-${arch}`,
521
+ ].filter(Boolean); // Filter out empty downloadUrl if API failed
279
522
 
280
523
  let downloaded = false;
281
524
  let lastError;
282
525
 
283
526
  for (const url of sources) {
284
- spinner.text = `Downloading TRIBE CLI from ${url.includes('jsdelivr') ? 'CDN' : 'GitHub'}...`;
285
527
  try {
286
- await downloadFile(url, tribeDest);
528
+ let downloadUrl = url;
529
+
530
+ // Check if this is the GitHub API endpoint
531
+ if (url.includes('/api.github.com/')) {
532
+ spinner.text = 'Fetching latest release information from GitHub...';
533
+
534
+ // Fetch release data
535
+ const response = await fetch(url);
536
+ if (!response.ok) {
537
+ throw new Error(`GitHub API returned ${response.status}`);
538
+ }
539
+
540
+ const releaseData = await response.json();
541
+
542
+ // Handle both single release and array of releases
543
+ const release = Array.isArray(releaseData) ? releaseData[0] : releaseData;
544
+
545
+ if (!release || !release.assets) {
546
+ throw new Error('No release found');
547
+ }
548
+
549
+ const assetName = `tribe-${platform}-${arch}`;
550
+ const asset = release.assets.find(a => a.name === assetName);
551
+
552
+ if (!asset) {
553
+ throw new Error(`No binary found for ${platform}-${arch}`);
554
+ }
555
+
556
+ downloadUrl = asset.browser_download_url;
557
+ spinner.text = `Found ${release.prerelease ? 'pre-release' : 'release'}: ${release.tag_name}`;
558
+ }
559
+
560
+ spinner.text = `Downloading TRIBE CLI from ${new URL(downloadUrl).hostname}...`;
561
+ await downloadFile(downloadUrl, tribeDest);
562
+
563
+ // Check if file is not empty
564
+ const stats = fs.statSync(tribeDest);
565
+ if (stats.size === 0) {
566
+ throw new Error('Downloaded file is empty');
567
+ }
568
+
287
569
  fs.chmodSync(tribeDest, '755');
288
570
 
289
571
  // Verify the binary works
290
- execSync(`${tribeDest} version`, { stdio: 'ignore' });
572
+ try {
573
+ execSync(`${tribeDest} version`, { stdio: 'ignore' });
574
+ } catch (versionError) {
575
+ // If version fails, still consider it downloaded if file is valid
576
+ log.warning('Binary downloaded but version check failed - may need different architecture');
577
+ }
578
+
291
579
  spinner.succeed('TRIBE CLI installed successfully');
292
580
  downloaded = true;
293
581
  break;
294
582
  } catch (error) {
295
583
  lastError = error;
296
- log.warning(`Failed to download from ${url.split('/').slice(2, 3).join('/')}, trying next source...`);
584
+ // Clean up failed download
585
+ if (fs.existsSync(tribeDest)) {
586
+ fs.unlinkSync(tribeDest);
587
+ }
588
+ log.warning(`Failed: ${error.message}. Trying next source...`);
297
589
  }
298
590
  }
299
-
591
+
300
592
  if (!downloaded) {
301
- throw new Error(`Failed to download TRIBE CLI: ${lastError?.message || 'All sources failed'}`);
593
+ throw new Error(`Failed to download TRIBE CLI. Please check your internet connection and try again.
594
+
595
+ If this persists, the binaries may not be available for your platform (${platform}-${arch}).
596
+ Please visit https://tribecode.ai/support for assistance.`);
302
597
  }
303
598
 
304
599
  return true;
305
600
  } catch (error) {
306
601
  spinner.fail(`TRIBE CLI installation failed: ${error.message}`);
602
+
603
+ // Show helpful support information
604
+ console.log('');
605
+ console.log(chalk.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
606
+ console.log(chalk.yellow('Need help? Visit:'));
607
+ console.log(chalk.cyan.bold(' https://tribecode.ai/support'));
608
+ console.log('');
609
+ console.log(chalk.gray('You can also:'));
610
+ console.log(chalk.gray(' • Check system requirements'));
611
+ console.log(chalk.gray(' • View troubleshooting guides'));
612
+ console.log(chalk.gray(' • Contact our support team'));
613
+ console.log(chalk.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
614
+ console.log('');
615
+
307
616
  return false;
308
617
  }
309
618
  }
310
619
 
311
620
  async function startContainerRuntime() {
312
621
  const spinner = ora('Starting container runtime...').start();
313
-
622
+
314
623
  try {
315
624
  // Platform-specific container runtime handling
316
625
  if (platform === 'darwin') {
317
- // macOS - Check if container runtime is already working via Colima
626
+ // macOS - Check if Colima is running
318
627
  try {
319
- execSync('docker info', { stdio: 'ignore' });
320
- log.success('Container runtime is already working');
321
- return true;
628
+ const colimaStatus = execSync('colima status 2>&1', { encoding: 'utf8' });
629
+ if (colimaStatus.includes('is running')) {
630
+ spinner.succeed('Colima is already running');
631
+ return true;
632
+ }
322
633
  } catch {
323
- // Try to start Colima with different approaches
324
- if (await checkCommand('colima')) {
325
- const spinner = ora('Starting Colima container runtime...').start();
634
+ // Colima not running, need to start it
635
+ }
326
636
 
327
- try {
328
- // Strategy 1: Quick start with minimal resources and Kubernetes
329
- spinner.text = 'Starting Colima with Kubernetes...';
330
- const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
331
- execSync(`${colimaPath} start --cpu 2 --memory 4 --disk 10 --kubernetes --vm-type=vz`, {
332
- stdio: 'pipe',
333
- timeout: 60000 // 60 second timeout for K8s
334
- });
335
-
336
- // Test if it worked
337
- execSync('docker info', { stdio: 'ignore' });
338
- spinner.succeed('Colima started successfully');
339
- return true;
637
+ // Try to start Colima
638
+ if (await checkCommand('colima')) {
639
+ const spinner = ora('Starting Colima container runtime...').start();
340
640
 
341
- } catch (error) {
342
- // Strategy 2: Start in background with Kubernetes
343
641
  try {
344
- spinner.text = 'Starting Colima with Kubernetes in background...';
642
+ // Strategy 1: Quick start with minimal resources and Kubernetes
643
+ spinner.text = 'Starting Colima with Kubernetes...';
345
644
  const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
346
- const child = spawn(colimaPath, ['start', '--cpu', '2', '--memory', '4', '--disk', '10', '--kubernetes'], {
347
- detached: true,
348
- stdio: 'ignore',
349
- env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
645
+ execSync(`${colimaPath} start --cpu 2 --memory 4 --disk 10 --kubernetes --vm-type=vz`, {
646
+ stdio: 'pipe',
647
+ timeout: 60000 // 60 second timeout for K8s
350
648
  });
351
- child.unref(); // Don't wait for completion
352
649
 
353
- spinner.succeed('Colima startup initiated (background)');
354
- log.info('Colima is starting in the background');
355
- log.info('Run "colima status" to check progress');
356
- log.info('Run "docker info" to test when ready');
650
+ spinner.succeed('Colima started successfully');
357
651
  return true;
358
652
 
359
- } catch (bgError) {
360
- spinner.fail('Failed to start Colima');
361
- log.warning('Container runtime startup failed (likely due to macOS system restrictions)');
362
- log.info('Options to fix:');
363
- log.info('');
364
- log.info('Option 1 - Manual Colima start:');
365
- log.info(' colima start --cpu 2 --memory 4 # Start container runtime');
366
- log.info(' # This downloads a 344MB disk image (may take time)');
367
- log.info('');
368
- log.info('Option 2 - Use Docker Desktop (easier):');
369
- log.info(' Download from: https://docs.docker.com/desktop/install/mac-install/');
370
- log.info(' # Docker Desktop handles all container runtime setup');
371
- log.info('');
372
- log.info('Option 3 - Use Homebrew (recommended):');
373
- log.info(' brew install colima docker');
374
- log.info(' colima start');
375
- log.info('');
376
- return false;
653
+ } catch (error) {
654
+ // Strategy 2: Start in background with Kubernetes
655
+ try {
656
+ spinner.text = 'Starting Colima with Kubernetes in background...';
657
+ const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
658
+ const child = spawn(colimaPath, ['start', '--cpu', '2', '--memory', '4', '--disk', '10', '--kubernetes'], {
659
+ detached: true,
660
+ stdio: 'ignore',
661
+ env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
662
+ });
663
+ child.unref(); // Don't wait for completion
664
+
665
+ spinner.succeed('Colima startup initiated (background)');
666
+ log.info('Colima is starting in the background');
667
+ log.info('Run "colima status" to check progress');
668
+ return true;
669
+
670
+ } catch (bgError) {
671
+ spinner.fail('Failed to start Colima');
672
+ log.warning('Container runtime startup failed (likely due to macOS system restrictions)');
673
+ log.info('Options to fix:');
674
+ log.info('');
675
+ log.info('Option 1 - Manual Colima start:');
676
+ log.info(' colima start --cpu 2 --memory 4 --kubernetes');
677
+ log.info(' # This downloads a 344MB disk image (may take time)');
678
+ log.info('');
679
+ log.info('Option 2 - Use Homebrew (recommended):');
680
+ log.info(' brew install colima');
681
+ log.info(' colima start --kubernetes');
682
+ log.info('');
683
+ return false;
684
+ }
377
685
  }
378
686
  }
379
- }
380
- }
381
687
  } else if (platform === 'linux') {
382
688
  // Linux - K3s should already be running
383
689
  spinner.text = 'Checking K3s status...';
690
+
691
+ // Check if we're in a container (no systemd)
692
+ const isContainer = process.env.container === 'docker' || fs.existsSync('/.dockerenv') || !fs.existsSync('/run/systemd/system');
693
+
694
+ if (isContainer) {
695
+ spinner.info('Running in container - K3s requires systemd');
696
+ log.info('For containers, use KIND or connect to external cluster');
697
+ return true; // Don't fail
698
+ }
699
+
384
700
  try {
385
701
  execSync('sudo systemctl is-active --quiet k3s', { stdio: 'ignore' });
386
702
  spinner.succeed('K3s is running');
@@ -394,9 +710,9 @@ async function startContainerRuntime() {
394
710
  spinner.succeed('K3s started');
395
711
  return true;
396
712
  } catch (error) {
397
- spinner.fail('Failed to start K3s');
398
- log.error('Please ensure K3s is installed and you have sudo access');
399
- return false;
713
+ spinner.warn('K3s not available - requires systemd');
714
+ log.info('K3s requires systemd. Install K3s manually or use KIND/external cluster');
715
+ return true; // Don't fail
400
716
  }
401
717
  }
402
718
  } else if (platform === 'win32') {
@@ -405,7 +721,7 @@ async function startContainerRuntime() {
405
721
  return false;
406
722
  } else {
407
723
  spinner.fail(`Unsupported platform: ${platform}`);
408
- return false;
724
+ return false;
409
725
  }
410
726
  } catch (error) {
411
727
  spinner.fail(`Container runtime error: ${error.message}`);
@@ -413,31 +729,16 @@ async function startContainerRuntime() {
413
729
  }
414
730
  }
415
731
 
416
- async function updatePath() {
417
- const shell = process.env.SHELL || '/bin/zsh';
418
- const rcFile = shell.includes('zsh') ? '.zshrc' : '.bashrc';
419
- const rcPath = path.join(homeDir, rcFile);
420
-
421
- const pathExport = `export PATH="${binDir}:$PATH"`;
422
-
423
- try {
424
- const rcContent = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, 'utf8') : '';
425
- if (!rcContent.includes(pathExport)) {
426
- fs.appendFileSync(rcPath, `\n# Added by TRIBE CLI installer\n${pathExport}\n`);
427
- log.success(`Updated ${rcFile} with PATH`);
428
- }
429
- } catch (error) {
430
- log.warning(`Failed to update ${rcFile}: ${error.message}`);
431
- }
432
-
433
- // Update current process PATH
434
- process.env.PATH = `${binDir}:${process.env.PATH}`;
435
- }
732
+ // PATH updates no longer needed - using npm global installation
733
+ // async function updatePath() { ... }
734
+
735
+ // Shell config updates no longer needed - using npm global installation
736
+ // async function updateShellConfig() { ... }
436
737
 
437
738
  async function verifyInstallation() {
438
739
  const spinner = ora('Verifying installation...').start();
439
740
 
440
- const tools = ['docker', 'kubectl', 'tribe', 'colima'];
741
+ const tools = ['kubectl', 'tribe', 'colima'];
441
742
  const results = {};
442
743
 
443
744
  for (const tool of tools) {
@@ -446,11 +747,27 @@ async function verifyInstallation() {
446
747
 
447
748
  // Special check for container runtime
448
749
  let containerWorking = false;
449
- try {
450
- execSync('docker info', { stdio: 'ignore' });
451
- containerWorking = true;
452
- } catch (error) {
453
- containerWorking = false;
750
+ if (platform === 'darwin') {
751
+ try {
752
+ const colimaStatus = execSync('colima status 2>&1', { encoding: 'utf8' });
753
+ containerWorking = colimaStatus.includes('is running');
754
+ } catch {
755
+ containerWorking = false;
756
+ }
757
+ } else if (platform === 'linux') {
758
+ try {
759
+ // Check if k3s is running
760
+ execSync('systemctl is-active k3s', { stdio: 'ignore' });
761
+ containerWorking = true;
762
+ } catch {
763
+ // Fallback to kubectl
764
+ try {
765
+ execSync('kubectl cluster-info', { stdio: 'ignore' });
766
+ containerWorking = true;
767
+ } catch {
768
+ containerWorking = false;
769
+ }
770
+ }
454
771
  }
455
772
 
456
773
  spinner.stop();
@@ -514,7 +831,14 @@ async function startColimaWithKubernetes() {
514
831
  // Check if it has Kubernetes enabled
515
832
  if (await checkColimaHasKubernetes()) {
516
833
  spinner.succeed('Colima is already running with Kubernetes');
517
- return true;
834
+
835
+ // Verify kubectl works
836
+ try {
837
+ execSync('kubectl cluster-info', { stdio: 'ignore' });
838
+ return true;
839
+ } catch {
840
+ spinner.info('Colima is running but kubectl not connected, continuing...');
841
+ }
518
842
  } else {
519
843
  spinner.text = 'Colima is running without Kubernetes. Restarting with Kubernetes...';
520
844
 
@@ -620,7 +944,7 @@ async function deployTribeCluster() {
620
944
 
621
945
  // Run tribe start command with deployment YAML path
622
946
  spinner.text = 'Running TRIBE cluster deployment...';
623
- const tribePath = path.join(binDir, 'tribe');
947
+ const tribePath = path.join(tribeBinDir, 'tribe');
624
948
 
625
949
  // Set environment variable for the deployment YAML location
626
950
  const env = {
@@ -754,19 +1078,26 @@ async function deployTribeCluster() {
754
1078
  log.info(' colima start --kubernetes');
755
1079
  log.info('3. Then run: tribe start');
756
1080
  } else {
757
- log.info('You can manually deploy later with: tribe start');
1081
+ log.info('You can manually deploy later with: tribe start');
758
1082
  }
759
1083
  return false;
760
1084
  }
761
1085
  }
762
1086
 
763
1087
  async function promptForClusterSetup() {
1088
+ // Check for auto-approve in CI environments
1089
+ if (process.env.CI === 'true' || process.env.TRIBE_AUTO_APPROVE === 'true') {
1090
+ console.log('\n' + chalk.bold('🚀 TRIBE Cluster Setup'));
1091
+ console.log(chalk.green('Auto-approving cluster setup (CI mode)'));
1092
+ return true;
1093
+ }
1094
+
764
1095
  // Simple prompt without external dependencies
765
1096
  return new Promise((resolve) => {
766
1097
  console.log('\n' + chalk.bold('🚀 TRIBE Cluster Setup'));
767
1098
  console.log('\nWould you like to set up the TRIBE cluster now?');
768
1099
  console.log('This will:');
769
- console.log(' • Start Colima with Kubernetes');
1100
+ console.log(' • Start ' + (platform === 'darwin' ? 'Colima' : 'K3s') + ' with Kubernetes');
770
1101
  console.log(' • Deploy all TRIBE services');
771
1102
  console.log(' • Set up port forwarding');
772
1103
  console.log('\n' + chalk.yellow('Note: This requires ~2GB disk space and may take a few minutes'));
@@ -783,12 +1114,142 @@ async function promptForClusterSetup() {
783
1114
  });
784
1115
  }
785
1116
 
1117
+ async function cleanupOldInstallations() {
1118
+ // Clean up old aliases
1119
+ const rcFiles = ['.zshrc', '.bashrc'];
1120
+ rcFiles.forEach(file => {
1121
+ const rcPath = path.join(homeDir, file);
1122
+ if (fs.existsSync(rcPath)) {
1123
+ try {
1124
+ let content = fs.readFileSync(rcPath, 'utf8');
1125
+ const originalContent = content;
1126
+
1127
+ // Remove old tribe-cli aliases
1128
+ content = content.replace(/^alias tribe=['"]tribe-cli['"]\s*$/gm, '# $& # Removed by TRIBE installer');
1129
+ content = content.replace(/^alias t=['"]tribe-cli['"]\s*$/gm, '# $& # Removed by TRIBE installer');
1130
+
1131
+ if (content !== originalContent) {
1132
+ fs.writeFileSync(rcPath, content);
1133
+ log.info(`Cleaned up old aliases in ${file}`);
1134
+ }
1135
+ } catch (error) {
1136
+ log.warning(`Could not clean up ${file}: ${error.message}`);
1137
+ }
1138
+ }
1139
+ });
1140
+ }
1141
+
1142
+ async function forceCleanInstallation() {
1143
+ console.log(chalk.yellow('\n🧹 Performing force clean installation...\n'));
1144
+
1145
+ // Uninstall global npm package if exists
1146
+ try {
1147
+ execSync('npm uninstall -g tribe-cli-global', {
1148
+ stdio: 'ignore'
1149
+ });
1150
+ log.success('Removed global tribe command');
1151
+ } catch (error) {
1152
+ // It might not be installed, which is fine
1153
+ }
1154
+
1155
+ // List of binaries to remove from ~/bin
1156
+ const binariesToRemove = ['tribe', 'kubectl', 'colima'];
1157
+
1158
+ for (const binary of binariesToRemove) {
1159
+ const binaryPath = path.join(binDir, binary);
1160
+ if (fs.existsSync(binaryPath)) {
1161
+ try {
1162
+ fs.unlinkSync(binaryPath);
1163
+ log.success(`Removed existing ${binary} binary from ~/bin`);
1164
+ } catch (error) {
1165
+ log.warning(`Could not remove ${binary}: ${error.message}`);
1166
+ }
1167
+ }
1168
+ }
1169
+
1170
+ // Also clean from .tribe/bin
1171
+ const tribeBinariesToRemove = ['tribe'];
1172
+ for (const binary of tribeBinariesToRemove) {
1173
+ const binaryPath = path.join(tribeBinDir, binary);
1174
+ if (fs.existsSync(binaryPath)) {
1175
+ try {
1176
+ fs.unlinkSync(binaryPath);
1177
+ log.success(`Removed existing ${binary} binary from ~/.tribe/bin`);
1178
+ } catch (error) {
1179
+ log.warning(`Could not remove ${binary}: ${error.message}`);
1180
+ }
1181
+ }
1182
+ }
1183
+
1184
+ // Clean up TRIBE configuration directory
1185
+ if (fs.existsSync(tribeDir)) {
1186
+ try {
1187
+ // Keep important configs but remove deployment markers
1188
+ const deploymentMarker = path.join(tribeDir, '.cluster-deployed');
1189
+ if (fs.existsSync(deploymentMarker)) {
1190
+ fs.unlinkSync(deploymentMarker);
1191
+ log.success('Removed cluster deployment marker');
1192
+ }
1193
+
1194
+ // Remove cached deployment YAML
1195
+ const deploymentYaml = path.join(tribeDir, 'tribe-deployment.yaml');
1196
+ if (fs.existsSync(deploymentYaml)) {
1197
+ fs.unlinkSync(deploymentYaml);
1198
+ log.success('Removed cached deployment configuration');
1199
+ }
1200
+ } catch (error) {
1201
+ log.warning(`Could not clean TRIBE directory: ${error.message}`);
1202
+ }
1203
+ }
1204
+
1205
+ // Clean up npm cache for @_xtribe/cli
1206
+ try {
1207
+ execSync('npm cache clean --force @_xtribe/cli', {
1208
+ stdio: 'ignore',
1209
+ timeout: 10000
1210
+ });
1211
+ log.success('Cleared npm cache for @_xtribe/cli');
1212
+ } catch (error) {
1213
+ // npm cache clean might fail, but that's okay
1214
+ log.info('npm cache clean attempted (may have failed, which is okay)');
1215
+ }
1216
+
1217
+ // Platform-specific cleanup
1218
+ if (platform === 'darwin') {
1219
+ // Check if Colima is running and stop it
1220
+ try {
1221
+ const colimaPath = await findCommand('colima');
1222
+ if (colimaPath) {
1223
+ const status = execSync(`${colimaPath} status 2>&1`, { encoding: 'utf8' });
1224
+ if (status.includes('is running')) {
1225
+ log.info('Stopping Colima...');
1226
+ execSync(`${colimaPath} stop`, { stdio: 'ignore', timeout: 30000 });
1227
+ log.success('Stopped Colima');
1228
+ }
1229
+ }
1230
+ } catch (error) {
1231
+ // Colima might not be installed or running
1232
+ }
1233
+ } else if (platform === 'linux') {
1234
+ // Note about K3s - requires manual uninstall
1235
+ try {
1236
+ execSync('which k3s', { stdio: 'ignore' });
1237
+ log.warning('K3s detected. To fully remove K3s, run: /usr/local/bin/k3s-uninstall.sh');
1238
+ log.info('The installer will proceed, but K3s will remain installed');
1239
+ } catch {
1240
+ // K3s not installed
1241
+ }
1242
+ }
1243
+
1244
+ console.log(chalk.green('\n✓ Clean installation preparation complete\n'));
1245
+ }
1246
+
786
1247
  async function main() {
787
1248
  console.log(chalk.bold.blue('\n🚀 TRIBE CLI Complete Installer\n'));
788
1249
 
789
1250
  // Detect and display platform info
790
- const arch = process.arch === 'x64' ? 'amd64' : process.arch;
791
- console.log(chalk.cyan(`Platform: ${platform} (${arch})`));
1251
+ const displayArch = process.arch === 'x64' ? 'amd64' : process.arch;
1252
+ console.log(chalk.cyan(`Platform: ${platform} (${displayArch})`));
792
1253
  console.log(chalk.cyan(`Node: ${process.version}`));
793
1254
  console.log('');
794
1255
 
@@ -800,11 +1261,63 @@ async function main() {
800
1261
  process.exit(1);
801
1262
  }
802
1263
 
1264
+ // Edge case detection
1265
+ // 1. Check for unsupported architectures
1266
+ const supportedArchitectures = ['x64', 'x86_64', 'arm64', 'aarch64'];
1267
+ if (!supportedArchitectures.includes(nodeArch)) {
1268
+ console.log(chalk.red('\n❌ Unsupported Architecture'));
1269
+ console.log(chalk.red(`Your system architecture (${nodeArch}) is not supported.`));
1270
+ console.log(chalk.yellow('TRIBE only provides binaries for x64 (AMD64) and ARM64.\n'));
1271
+ process.exit(1);
1272
+ }
1273
+
1274
+ // 2. WSL2 detection
1275
+ if (fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop') || process.env.WSL_DISTRO_NAME) {
1276
+ console.log(chalk.yellow('\n⚠️ WSL2 Environment Detected'));
1277
+ console.log(chalk.yellow('K3s may have networking limitations in WSL2.'));
1278
+ console.log(chalk.yellow('Consider using Docker Desktop with WSL2 backend instead.\n'));
1279
+ // Don't exit, just warn
1280
+ }
1281
+
1282
+ // 3. Proxy detection
1283
+ const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
1284
+ if (proxyUrl) {
1285
+ console.log(chalk.blue('ℹ️ Proxy detected: ' + proxyUrl));
1286
+ console.log(chalk.blue('Downloads will use system proxy settings.\n'));
1287
+ }
1288
+
1289
+ // 4. Check for existing Kubernetes installations
1290
+ const k8sTools = [];
1291
+ const checkTools = ['minikube', 'microk8s', 'kind', 'k3d', 'kubectl'];
1292
+ for (const tool of checkTools) {
1293
+ try {
1294
+ execSync(`which ${tool}`, { stdio: 'ignore' });
1295
+ k8sTools.push(tool);
1296
+ } catch {}
1297
+ }
1298
+
1299
+ if (k8sTools.length > 0) {
1300
+ console.log(chalk.blue('\n📦 Existing Kubernetes tools detected: ' + k8sTools.join(', ')));
1301
+ console.log(chalk.blue('You can use --skip-cluster to skip K3s installation.\n'));
1302
+ }
1303
+
1304
+ // 5. Disk space check
1305
+ try {
1306
+ const stats = fs.statfsSync(homeDir);
1307
+ const freeGB = (stats.bavail * stats.bsize) / (1024 ** 3);
1308
+ if (freeGB < 3) {
1309
+ console.log(chalk.yellow(`\n⚠️ Low disk space: ${freeGB.toFixed(1)}GB free`));
1310
+ console.log(chalk.yellow('K3s installation requires at least 3GB of free space.\n'));
1311
+ }
1312
+ } catch {}
1313
+
1314
+ // Clean up old installations
1315
+ await cleanupOldInstallations();
1316
+
803
1317
  log.info(`Detected: ${platform} (${arch})`);
804
- log.info(`Installing to: ${binDir}`);
1318
+ log.info(`Installing to: ${tribeBinDir}`);
805
1319
 
806
- // Update PATH first
807
- await updatePath();
1320
+ // No longer updating PATH - will use npm global install instead
808
1321
 
809
1322
  const tasks = [];
810
1323
 
@@ -818,7 +1331,8 @@ async function main() {
818
1331
  // Common tools for all platforms
819
1332
  tasks.push(
820
1333
  { name: 'kubectl', fn: installKubectl },
821
- { name: 'TRIBE CLI', fn: installTribeCLI }
1334
+ { name: 'TRIBE CLI', fn: installTribeCLI },
1335
+ { name: 'Global tribe command', fn: setupGlobalNpmCommand }
822
1336
  );
823
1337
 
824
1338
  let allSuccess = true;
@@ -865,19 +1379,16 @@ async function main() {
865
1379
  console.log('');
866
1380
 
867
1381
  // Provide immediate access to tribe command
868
- const tribePath = path.join(binDir, 'tribe');
869
-
870
- log.info('To use tribe commands:');
871
- console.log(chalk.green(` ${tribePath} # Use full path now`));
872
- console.log(chalk.yellow(` source ~/.${process.env.SHELL?.includes('zsh') ? 'zshrc' : 'bashrc'} # Or update PATH`));
873
- console.log(chalk.cyan(` alias tribe="${tribePath}" # Or create temporary alias`));
874
- console.log(' ' + chalk.gray('or restart your terminal'));
1382
+ log.info('TRIBE command is now available globally!');
1383
+ console.log(chalk.green(' tribe # Run from anywhere'));
1384
+ console.log(chalk.gray(' tribe --help # View available commands'));
1385
+ console.log(chalk.gray(' tribe status # Check cluster status'));
875
1386
  console.log('');
876
1387
 
877
1388
  log.info('Quick start:');
878
- console.log(` ${tribePath} # Launch interactive CLI`);
879
- console.log(` ${tribePath} status # Check cluster status`);
880
- console.log(` ${tribePath} create-task # Create a new task`);
1389
+ console.log(' tribe # Launch interactive CLI');
1390
+ console.log(' tribe status # Check cluster status');
1391
+ console.log(' tribe create-task # Create a new task');
881
1392
  console.log('');
882
1393
  log.info('First time? The CLI will guide you through creating your first project!');
883
1394
  } else {
@@ -895,19 +1406,14 @@ async function main() {
895
1406
  console.log('');
896
1407
 
897
1408
  // Provide immediate access to tribe command
898
- const tribePath = path.join(binDir, 'tribe');
899
-
900
- log.info('To use tribe commands:');
901
- console.log(chalk.green(` ${tribePath} # Use full path now`));
902
- console.log(chalk.yellow(` source ~/.${process.env.SHELL?.includes('zsh') ? 'zshrc' : 'bashrc'} # Or update PATH`));
903
- console.log(chalk.cyan(` alias tribe="${tribePath}" # Or create temporary alias`));
904
- console.log(' ' + chalk.gray('or restart your terminal'));
1409
+ log.info('TRIBE command is now available globally!');
1410
+ console.log(chalk.green(' tribe # Run from anywhere'));
905
1411
  console.log('');
906
1412
 
907
1413
  log.info('Commands:');
908
- console.log(` ${tribePath} # Launch interactive CLI`);
909
- console.log(` ${tribePath} status # Check status`);
910
- console.log(` ${tribePath} create-task # Create a new task`);
1414
+ console.log(' tribe # Launch interactive CLI');
1415
+ console.log(' tribe status # Check status');
1416
+ console.log(' tribe create-task # Create a new task');
911
1417
  }
912
1418
  } else {
913
1419
  console.log('\n' + chalk.bold('Next Steps:'));
@@ -915,17 +1421,30 @@ async function main() {
915
1421
  console.log('');
916
1422
  // Check container runtime for guidance
917
1423
  let runtimeWorking = false;
918
- try {
919
- execSync('docker info', { stdio: 'ignore' });
920
- runtimeWorking = true;
921
- } catch {
922
- runtimeWorking = false;
923
- }
924
-
925
- if (!runtimeWorking) {
926
- log.info('Start container runtime:');
927
- console.log(' colima start # macOS');
928
- console.log(' sudo systemctl start docker # Linux');
1424
+ if (platform === 'darwin') {
1425
+ try {
1426
+ const colimaStatus = execSync('colima status 2>&1', { encoding: 'utf8' });
1427
+ runtimeWorking = colimaStatus.includes('is running');
1428
+ } catch {
1429
+ runtimeWorking = false;
1430
+ }
1431
+
1432
+ if (!runtimeWorking) {
1433
+ log.info('Start container runtime:');
1434
+ console.log(' colima start --kubernetes # Start Colima with Kubernetes');
1435
+ }
1436
+ } else if (platform === 'linux') {
1437
+ try {
1438
+ execSync('systemctl is-active k3s', { stdio: 'ignore' });
1439
+ runtimeWorking = true;
1440
+ } catch {
1441
+ runtimeWorking = false;
1442
+ }
1443
+
1444
+ if (!runtimeWorking) {
1445
+ log.info('Start container runtime:');
1446
+ console.log(' sudo systemctl start k3s # Start k3s');
1447
+ }
929
1448
  }
930
1449
  console.log('');
931
1450
  log.info('Restart your shell or run: source ~/.zshrc');
@@ -949,7 +1468,8 @@ if (require.main === module) {
949
1468
  console.log(' --verify Only verify existing installation');
950
1469
  console.log(' --dry-run Show what would be installed');
951
1470
  console.log(' --skip-cluster Skip cluster deployment (for testing)');
952
- console.log('\nThis installer sets up the complete TRIBE development environment:');
1471
+ console.log(' --force Force clean installation (removes existing installations)');
1472
+ console.log('\nThis installer sets up the complete TRIBE development environment:');
953
1473
 
954
1474
  if (platform === 'darwin') {
955
1475
  console.log('• Colima (Container runtime with Kubernetes)');
@@ -991,10 +1511,23 @@ if (require.main === module) {
991
1511
  // Store skip-cluster flag
992
1512
  global.skipCluster = args.includes('--skip-cluster');
993
1513
 
994
- main().catch(error => {
995
- console.error(chalk.red('Installation failed:'), error.message);
996
- process.exit(1);
997
- });
1514
+ // Check for force flag
1515
+ if (args.includes('--force')) {
1516
+ forceCleanInstallation().then(() => {
1517
+ main().catch(error => {
1518
+ console.error(chalk.red('Installation failed:'), error.message);
1519
+ process.exit(1);
1520
+ });
1521
+ }).catch(error => {
1522
+ console.error(chalk.red('Force clean failed:'), error.message);
1523
+ process.exit(1);
1524
+ });
1525
+ } else {
1526
+ main().catch(error => {
1527
+ console.error(chalk.red('Installation failed:'), error.message);
1528
+ process.exit(1);
1529
+ });
1530
+ }
998
1531
  }
999
1532
 
1000
1533
  module.exports = { main };
package/package.json CHANGED
@@ -1,11 +1,8 @@
1
1
  {
2
2
  "name": "@_xtribe/cli",
3
- "version": "1.0.0-beta.22",
3
+ "version": "1.0.0-beta.26",
4
4
  "description": "TRIBE multi-agent development system - Zero to productive with one command",
5
5
  "main": "install-tribe.js",
6
- "bin": {
7
- "tribe": "install-tribe.js"
8
- },
9
6
  "scripts": {
10
7
  "test": "echo \"Error: no test specified\" && exit 1"
11
8
  },
@@ -43,7 +40,8 @@
43
40
  "ora": "^5.4.1",
44
41
  "node-fetch": "^2.6.7",
45
42
  "tar": "^6.1.15",
46
- "which": "^2.0.2"
43
+ "which": "^2.0.2",
44
+ "adm-zip": "^0.5.9"
47
45
  },
48
46
  "repository": {
49
47
  "type": "git",