@_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.
- package/README.md +30 -7
- package/install-tribe.js +724 -191
- 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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- ✅
|
|
15
|
-
- ✅
|
|
16
|
-
- ✅
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
114
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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(
|
|
134
|
-
|
|
135
|
-
|
|
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(
|
|
145
|
-
log.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
447
|
+
const tribeDest = path.join(tribeBinDir, 'tribe');
|
|
253
448
|
|
|
254
|
-
//
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
626
|
+
// macOS - Check if Colima is running
|
|
318
627
|
try {
|
|
319
|
-
execSync('
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
//
|
|
324
|
-
|
|
325
|
-
const spinner = ora('Starting Colima container runtime...').start();
|
|
634
|
+
// Colima not running, need to start it
|
|
635
|
+
}
|
|
326
636
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
spinner
|
|
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
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
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 (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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.
|
|
398
|
-
log.
|
|
399
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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 = ['
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
791
|
-
console.log(chalk.cyan(`Platform: ${platform} (${
|
|
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: ${
|
|
1318
|
+
log.info(`Installing to: ${tribeBinDir}`);
|
|
805
1319
|
|
|
806
|
-
//
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
log.
|
|
871
|
-
console.log(chalk.
|
|
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(
|
|
879
|
-
console.log(
|
|
880
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
909
|
-
console.log(
|
|
910
|
-
console.log(
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
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
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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.
|
|
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",
|