@_xtribe/cli 1.0.0-beta.25 → 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 (2) hide show
  1. package/install-tribe.js +297 -147
  2. package/package.json +1 -4
package/install-tribe.js CHANGED
@@ -18,6 +18,7 @@ const arch = nodeArch === 'x64' ? 'amd64' : (nodeArch === 'arm64' || nodeArch ==
18
18
  const homeDir = os.homedir();
19
19
  const binDir = path.join(homeDir, 'bin');
20
20
  const tribeDir = path.join(homeDir, '.tribe');
21
+ const tribeBinDir = path.join(tribeDir, 'bin');
21
22
 
22
23
  // Ensure local bin directory exists
23
24
  if (!fs.existsSync(binDir)) {
@@ -29,6 +30,11 @@ if (!fs.existsSync(tribeDir)) {
29
30
  fs.mkdirSync(tribeDir, { recursive: true });
30
31
  }
31
32
 
33
+ // Ensure TRIBE bin directory exists
34
+ if (!fs.existsSync(tribeBinDir)) {
35
+ fs.mkdirSync(tribeBinDir, { recursive: true });
36
+ }
37
+
32
38
  const log = {
33
39
  success: (msg) => console.log(chalk.green('✓'), msg),
34
40
  error: (msg) => console.log(chalk.red('✗'), msg),
@@ -302,21 +308,160 @@ async function installKubectl() {
302
308
  }
303
309
  }
304
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
+
305
443
  async function installTribeCLI() {
306
444
  const spinner = ora('Installing TRIBE CLI...').start();
307
445
 
308
446
  try {
309
- const tribeDest = path.join(binDir, 'tribe');
447
+ const tribeDest = path.join(tribeBinDir, 'tribe');
310
448
 
311
- // Skip if already installed and working
449
+ // Unconditionally remove existing binary to ensure a clean install
312
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)) {
313
460
  try {
314
- execSync(`${tribeDest} version`, { stdio: 'ignore' });
315
- spinner.succeed('TRIBE CLI already installed');
316
- return true;
317
- } catch {
318
- // Binary exists but doesn't work, remove it
319
- fs.unlinkSync(tribeDest);
461
+ fs.unlinkSync(oldTribeDest);
462
+ log.info('Removed old tribe binary from ~/bin');
463
+ } catch (e) {
464
+ // Ignore errors
320
465
  }
321
466
  }
322
467
 
@@ -584,114 +729,11 @@ async function startContainerRuntime() {
584
729
  }
585
730
  }
586
731
 
587
- async function updatePath() {
588
- const shell = process.env.SHELL || '/bin/zsh';
589
- const rcFile = shell.includes('zsh') ? '.zshrc' : '.bashrc';
590
- const rcPath = path.join(homeDir, rcFile);
591
-
592
- const pathExport = `export PATH="${binDir}:$PATH"`;
593
-
594
- try {
595
- const rcContent = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, 'utf8') : '';
596
- if (!rcContent.includes(pathExport)) {
597
- fs.appendFileSync(rcPath, `\n# Added by TRIBE CLI installer\n${pathExport}\n`);
598
- log.success(`Updated ${rcFile} with PATH`);
599
- }
600
- } catch (error) {
601
- log.warning(`Failed to update ${rcFile}: ${error.message}`);
602
- }
603
-
604
- // Update current process PATH
605
- process.env.PATH = `${binDir}:${process.env.PATH}`;
606
- }
732
+ // PATH updates no longer needed - using npm global installation
733
+ // async function updatePath() { ... }
607
734
 
608
- async function updateShellConfig() {
609
- const homeDir = os.homedir();
610
- const pathExport = `export PATH="$HOME/bin:$PATH"`;
611
-
612
- // Get current shell
613
- const currentShell = process.env.SHELL || '/bin/sh';
614
- const shellName = path.basename(currentShell);
615
-
616
- // Platform-specific shell config files
617
- const shellConfigs = {
618
- 'zsh': ['.zshrc'],
619
- 'bash': ['.bashrc', '.bash_profile'],
620
- 'sh': ['.profile'],
621
- 'fish': ['.config/fish/config.fish'],
622
- 'ksh': ['.kshrc'],
623
- 'tcsh': ['.tcshrc', '.cshrc']
624
- };
625
-
626
- // On Linux, also update .profile for login shells
627
- if (platform === 'linux') {
628
- shellConfigs['bash'].push('.profile');
629
- }
630
-
631
- // Get config files for current shell
632
- const configFiles = shellConfigs[shellName] || ['.profile'];
633
-
634
- let updated = false;
635
-
636
- for (const configFile of configFiles) {
637
- const configPath = path.join(homeDir, configFile);
638
-
639
- try {
640
- // Create config file if it doesn't exist
641
- if (!fs.existsSync(configPath)) {
642
- // Handle fish shell special syntax
643
- if (configFile.includes('fish')) {
644
- fs.mkdirSync(path.dirname(configPath), { recursive: true });
645
- fs.writeFileSync(configPath, `# Created by TRIBE installer\nset -gx PATH $HOME/bin $PATH\n`);
646
- } else {
647
- fs.writeFileSync(configPath, `# Created by TRIBE installer\n${pathExport}\n`);
648
- }
649
- updated = true;
650
- continue;
651
- }
652
-
653
- // Check if PATH is already configured
654
- const content = fs.readFileSync(configPath, 'utf8');
655
- if (content.includes('$HOME/bin') || content.includes('~/bin')) {
656
- continue; // Already configured
657
- }
658
-
659
- // Add PATH export
660
- if (configFile.includes('fish')) {
661
- fs.appendFileSync(configPath, `\n# Added by TRIBE installer\nset -gx PATH $HOME/bin $PATH\n`);
662
- } else {
663
- fs.appendFileSync(configPath, `\n# Added by TRIBE installer\n${pathExport}\n`);
664
- }
665
- updated = true;
666
- } catch (error) {
667
- log.warning(`Could not update ${configFile}: ${error.message}`);
668
- }
669
- }
670
-
671
- if (updated) {
672
- log.success(`Updated shell configuration`);
673
-
674
- // Linux-specific: also update /etc/environment if we have permission
675
- if (platform === 'linux' && process.getuid && process.getuid() === 0) {
676
- try {
677
- const envPath = '/etc/environment';
678
- if (fs.existsSync(envPath)) {
679
- const envContent = fs.readFileSync(envPath, 'utf8');
680
- if (!envContent.includes('/usr/local/bin')) {
681
- // Update system PATH
682
- const newPath = envContent.replace(/PATH="([^"]*)"/, 'PATH="/usr/local/bin:$1"');
683
- fs.writeFileSync(envPath, newPath);
684
- log.success('Updated system PATH in /etc/environment');
685
- }
686
- }
687
- } catch (error) {
688
- // Ignore permission errors
689
- }
690
- }
691
-
692
- log.info(`Run "source ~/${configFiles[0]}" or restart your terminal`);
693
- }
694
- }
735
+ // Shell config updates no longer needed - using npm global installation
736
+ // async function updateShellConfig() { ... }
695
737
 
696
738
  async function verifyInstallation() {
697
739
  const spinner = ora('Verifying installation...').start();
@@ -902,7 +944,7 @@ async function deployTribeCluster() {
902
944
 
903
945
  // Run tribe start command with deployment YAML path
904
946
  spinner.text = 'Running TRIBE cluster deployment...';
905
- const tribePath = path.join(binDir, 'tribe');
947
+ const tribePath = path.join(tribeBinDir, 'tribe');
906
948
 
907
949
  // Set environment variable for the deployment YAML location
908
950
  const env = {
@@ -1097,6 +1139,111 @@ async function cleanupOldInstallations() {
1097
1139
  });
1098
1140
  }
1099
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
+
1100
1247
  async function main() {
1101
1248
  console.log(chalk.bold.blue('\n🚀 TRIBE CLI Complete Installer\n'));
1102
1249
 
@@ -1168,13 +1315,9 @@ async function main() {
1168
1315
  await cleanupOldInstallations();
1169
1316
 
1170
1317
  log.info(`Detected: ${platform} (${arch})`);
1171
- log.info(`Installing to: ${binDir}`);
1172
-
1173
- // Update PATH first
1174
- await updatePath();
1318
+ log.info(`Installing to: ${tribeBinDir}`);
1175
1319
 
1176
- // Update shell configuration files
1177
- await updateShellConfig();
1320
+ // No longer updating PATH - will use npm global install instead
1178
1321
 
1179
1322
  const tasks = [];
1180
1323
 
@@ -1188,7 +1331,8 @@ async function main() {
1188
1331
  // Common tools for all platforms
1189
1332
  tasks.push(
1190
1333
  { name: 'kubectl', fn: installKubectl },
1191
- { name: 'TRIBE CLI', fn: installTribeCLI }
1334
+ { name: 'TRIBE CLI', fn: installTribeCLI },
1335
+ { name: 'Global tribe command', fn: setupGlobalNpmCommand }
1192
1336
  );
1193
1337
 
1194
1338
  let allSuccess = true;
@@ -1235,19 +1379,16 @@ async function main() {
1235
1379
  console.log('');
1236
1380
 
1237
1381
  // Provide immediate access to tribe command
1238
- const tribePath = path.join(binDir, 'tribe');
1239
-
1240
- log.info('To use tribe commands:');
1241
- console.log(chalk.green(` ${tribePath} # Use full path now`));
1242
- console.log(chalk.yellow(` source ~/.${process.env.SHELL?.includes('zsh') ? 'zshrc' : 'bashrc'} # Or update PATH`));
1243
- console.log(chalk.cyan(` alias tribe="${tribePath}" # Or create temporary alias`));
1244
- 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'));
1245
1386
  console.log('');
1246
1387
 
1247
1388
  log.info('Quick start:');
1248
- console.log(` ${tribePath} # Launch interactive CLI`);
1249
- console.log(` ${tribePath} status # Check cluster status`);
1250
- 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');
1251
1392
  console.log('');
1252
1393
  log.info('First time? The CLI will guide you through creating your first project!');
1253
1394
  } else {
@@ -1265,19 +1406,14 @@ async function main() {
1265
1406
  console.log('');
1266
1407
 
1267
1408
  // Provide immediate access to tribe command
1268
- const tribePath = path.join(binDir, 'tribe');
1269
-
1270
- log.info('To use tribe commands:');
1271
- console.log(chalk.green(` ${tribePath} # Use full path now`));
1272
- console.log(chalk.yellow(` source ~/.${process.env.SHELL?.includes('zsh') ? 'zshrc' : 'bashrc'} # Or update PATH`));
1273
- console.log(chalk.cyan(` alias tribe="${tribePath}" # Or create temporary alias`));
1274
- 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'));
1275
1411
  console.log('');
1276
1412
 
1277
1413
  log.info('Commands:');
1278
- console.log(` ${tribePath} # Launch interactive CLI`);
1279
- console.log(` ${tribePath} status # Check status`);
1280
- 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');
1281
1417
  }
1282
1418
  } else {
1283
1419
  console.log('\n' + chalk.bold('Next Steps:'));
@@ -1332,6 +1468,7 @@ if (require.main === module) {
1332
1468
  console.log(' --verify Only verify existing installation');
1333
1469
  console.log(' --dry-run Show what would be installed');
1334
1470
  console.log(' --skip-cluster Skip cluster deployment (for testing)');
1471
+ console.log(' --force Force clean installation (removes existing installations)');
1335
1472
  console.log('\nThis installer sets up the complete TRIBE development environment:');
1336
1473
 
1337
1474
  if (platform === 'darwin') {
@@ -1374,10 +1511,23 @@ if (require.main === module) {
1374
1511
  // Store skip-cluster flag
1375
1512
  global.skipCluster = args.includes('--skip-cluster');
1376
1513
 
1377
- main().catch(error => {
1378
- console.error(chalk.red('Installation failed:'), error.message);
1379
- process.exit(1);
1380
- });
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
+ }
1381
1531
  }
1382
1532
 
1383
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.25",
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
  },