@_xtribe/cli 1.0.0-beta.10

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 ADDED
@@ -0,0 +1,123 @@
1
+ # TRIBE CLI - Zero to Productive in One Command
2
+
3
+ TRIBE is an AI-powered multi-agent development system that manages your entire development workflow. From project creation to task implementation and PR reviews, TRIBE's AI agents handle it all.
4
+
5
+ ## 🚀 One-Command Installation
6
+
7
+ ```bash
8
+ npx @_xtribe/cli
9
+ ```
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
17
+
18
+ ## 🎯 What is TRIBE?
19
+
20
+ TRIBE is a complete development ecosystem where AI agents:
21
+ - 📝 Implement features based on your descriptions
22
+ - 🔧 Fix bugs autonomously
23
+ - 🔀 Create pull requests
24
+ - 👀 Handle code reviews
25
+ - 🚀 Manage the entire development lifecycle
26
+
27
+ ## 💡 Quick Start
28
+
29
+ After installation, just run:
30
+
31
+ ```bash
32
+ tribe
33
+ ```
34
+
35
+ The interactive CLI will:
36
+ 1. **First time?** Guide you through creating your first project
37
+ 2. **Returning?** Show your projects, tasks, and agent activity
38
+
39
+ ### Creating Tasks
40
+
41
+ ```bash
42
+ tribe create-task
43
+ # Select project, describe what you want built
44
+ # An AI agent picks it up and implements it!
45
+ ```
46
+
47
+ ### Reviewing PRs
48
+
49
+ ```bash
50
+ tribe review-task
51
+ # See PRs created by agents
52
+ # Review diffs, add comments, merge
53
+ ```
54
+
55
+ ## 🛠️ System Requirements
56
+
57
+ - **macOS** or **Linux**
58
+ - **4GB RAM** minimum (8GB recommended)
59
+ - **20GB disk space**
60
+ - **Node.js 16+**
61
+
62
+ ## 📚 Common Commands
63
+
64
+ ```bash
65
+ tribe # Interactive mode
66
+ tribe status # Check system status
67
+ tribe create-task # Create a new task
68
+ tribe review-task # Review agent PRs
69
+ tribe list-projects # Show all projects
70
+ tribe list-agents # Show agent status
71
+ ```
72
+
73
+ ## 🔧 Architecture
74
+
75
+ TRIBE runs a local Kubernetes cluster with:
76
+ - **Bridge** - API gateway and orchestrator
77
+ - **TaskMaster** - Task queue and agent coordinator
78
+ - **Claude Agents** - AI workers powered by Claude
79
+ - **Gitea** - Local Git server for repositories
80
+ - **PostgreSQL** - Database for state management
81
+
82
+ ## 🤝 Contributing
83
+
84
+ TRIBE is open source! Visit our [GitHub repository](https://github.com/0zen/0zen) to contribute.
85
+
86
+ ## 📖 Documentation
87
+
88
+ For detailed documentation, visit the [TRIBE Flow Guide](https://github.com/0zen/0zen/blob/main/TRIBE-SYSTEM-FLOW-GUIDE.md).
89
+
90
+ ## 🆘 Troubleshooting
91
+
92
+ ### Cluster not starting?
93
+ ```bash
94
+ # Check if Colima is running
95
+ colima status
96
+
97
+ # Start manually if needed
98
+ colima start --kubernetes
99
+ tribe start
100
+ ```
101
+
102
+ ### Port conflicts?
103
+ ```bash
104
+ # Check what's using ports
105
+ lsof -i :30080
106
+ lsof -i :3456
107
+ ```
108
+
109
+ ### Reset everything?
110
+ ```bash
111
+ # Stop cluster
112
+ colima stop
113
+
114
+ # Remove TRIBE namespace
115
+ kubectl delete namespace tribe-system
116
+
117
+ # Start fresh
118
+ tribe start
119
+ ```
120
+
121
+ ## 📄 License
122
+
123
+ MIT License - see [LICENSE](https://github.com/0zen/0zen/blob/main/LICENSE) for details.
@@ -0,0 +1,671 @@
1
+ #!/usr/bin/env node
2
+
3
+ const os = require('os');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const https = require('https');
7
+ const { execSync, spawn } = require('child_process');
8
+ const chalk = require('chalk');
9
+ const ora = require('ora');
10
+ const which = require('which');
11
+
12
+ const platform = os.platform();
13
+ const arch = os.arch();
14
+ const homeDir = os.homedir();
15
+ const binDir = path.join(homeDir, 'bin');
16
+ const tribeDir = path.join(homeDir, '.tribe');
17
+
18
+ // Ensure local bin directory exists
19
+ if (!fs.existsSync(binDir)) {
20
+ fs.mkdirSync(binDir, { recursive: true });
21
+ }
22
+
23
+ // Ensure TRIBE config directory exists
24
+ if (!fs.existsSync(tribeDir)) {
25
+ fs.mkdirSync(tribeDir, { recursive: true });
26
+ }
27
+
28
+ const log = {
29
+ success: (msg) => console.log(chalk.green('✓'), msg),
30
+ error: (msg) => console.log(chalk.red('✗'), msg),
31
+ warning: (msg) => console.log(chalk.yellow('⚠'), msg),
32
+ info: (msg) => console.log(chalk.blue('ℹ'), msg),
33
+ step: (msg) => console.log(chalk.cyan('→'), msg)
34
+ };
35
+
36
+ async function checkCommand(cmd) {
37
+ try {
38
+ await which(cmd);
39
+ return true;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ async function findCommand(cmd) {
46
+ // Try to find command in various locations
47
+ const possiblePaths = [
48
+ path.join(binDir, cmd), // Our install location
49
+ path.join('/opt/homebrew/bin', cmd), // Homebrew on M1 Macs
50
+ path.join('/usr/local/bin', cmd), // Homebrew on Intel Macs
51
+ cmd // In PATH
52
+ ];
53
+
54
+ // First try 'which' command
55
+ try {
56
+ const cmdPath = await which(cmd);
57
+ return cmdPath;
58
+ } catch {
59
+ // If not in PATH, check known locations
60
+ for (const cmdPath of possiblePaths) {
61
+ try {
62
+ await fs.promises.access(cmdPath, fs.constants.X_OK);
63
+ return cmdPath;
64
+ } catch {
65
+ // Continue searching
66
+ }
67
+ }
68
+ }
69
+
70
+ return null;
71
+ }
72
+
73
+ async function downloadFile(url, dest) {
74
+ return new Promise((resolve, reject) => {
75
+ const file = fs.createWriteStream(dest);
76
+ https.get(url, (response) => {
77
+ if (response.statusCode === 302 || response.statusCode === 301) {
78
+ // Handle redirects
79
+ return downloadFile(response.headers.location, dest).then(resolve, reject);
80
+ }
81
+ if (response.statusCode !== 200) {
82
+ reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
83
+ return;
84
+ }
85
+ response.pipe(file);
86
+ file.on('finish', () => {
87
+ file.close();
88
+ resolve();
89
+ });
90
+ }).on('error', reject);
91
+ });
92
+ }
93
+
94
+ // Docker installation removed - Colima provides Docker runtime
95
+
96
+ async function installColima() {
97
+ if (platform !== 'darwin') {
98
+ log.info('Colima is only needed on macOS - skipping');
99
+ return true;
100
+ }
101
+
102
+ const existingColima = await findCommand('colima');
103
+ if (existingColima) {
104
+ log.success(`Colima already installed at: ${existingColima}`);
105
+ return true;
106
+ }
107
+
108
+ const spinner = ora('Installing Colima...').start();
109
+
110
+ try {
111
+ // Strategy 1: Try Homebrew if available (better signing)
112
+ try {
113
+ execSync('brew --version', { stdio: 'ignore' });
114
+ spinner.text = 'Installing Colima via Homebrew...';
115
+ execSync('brew install colima', { stdio: 'ignore' });
116
+ spinner.succeed('Colima installed via Homebrew');
117
+ return true;
118
+ } catch (brewError) {
119
+ // Homebrew not available, continue with direct download
120
+ spinner.text = 'Installing Colima (direct download)...';
121
+ }
122
+
123
+ // Strategy 2: Direct download with Gatekeeper approval
124
+ const colimaUrl = `https://github.com/abiosoft/colima/releases/latest/download/colima-${platform}-${arch}`;
125
+ const colimaDest = path.join(binDir, 'colima');
126
+
127
+ await downloadFile(colimaUrl, colimaDest);
128
+ fs.chmodSync(colimaDest, '755');
129
+
130
+ // Try to remove quarantine attribute (macOS Sequoia workaround)
131
+ try {
132
+ execSync(`xattr -d com.apple.quarantine ${colimaDest}`, { stdio: 'ignore' });
133
+ log.info('Removed quarantine attribute from Colima');
134
+ } catch (error) {
135
+ // Quarantine attribute may not exist, which is fine
136
+ log.info('Colima installed (quarantine handling not needed)');
137
+ }
138
+
139
+ spinner.succeed('Colima installed');
140
+ return true;
141
+ } catch (error) {
142
+ spinner.fail(`Colima installation failed: ${error.message}`);
143
+ return false;
144
+ }
145
+ }
146
+
147
+ // Lima installation removed - Colima handles virtualization
148
+
149
+ // KIND installation removed - using Colima's built-in Kubernetes
150
+
151
+ async function installKubectl() {
152
+ if (await checkCommand('kubectl')) {
153
+ log.success('kubectl already installed');
154
+ return true;
155
+ }
156
+
157
+ const spinner = ora('Installing kubectl...').start();
158
+
159
+ try {
160
+ // Get latest stable version
161
+ const versionResponse = await fetch('https://dl.k8s.io/release/stable.txt');
162
+ const version = await versionResponse.text();
163
+ const kubectlUrl = `https://dl.k8s.io/release/${version.trim()}/bin/${platform}/${arch}/kubectl`;
164
+ const kubectlDest = path.join(binDir, 'kubectl');
165
+
166
+ await downloadFile(kubectlUrl, kubectlDest);
167
+ fs.chmodSync(kubectlDest, '755');
168
+
169
+ spinner.succeed('kubectl installed');
170
+ return true;
171
+ } catch (error) {
172
+ spinner.fail(`kubectl installation failed: ${error.message}`);
173
+ return false;
174
+ }
175
+ }
176
+
177
+ async function installTribeCli() {
178
+ const spinner = ora('Installing TRIBE CLI...').start();
179
+
180
+ try {
181
+ const tribeDest = path.join(binDir, 'tribe');
182
+
183
+ // First check if we have a bundled binary
184
+ const bundledBinary = path.join(__dirname, 'tribe');
185
+ if (fs.existsSync(bundledBinary)) {
186
+ fs.copyFileSync(bundledBinary, tribeDest);
187
+ fs.chmodSync(tribeDest, '755');
188
+ spinner.succeed('TRIBE CLI installed from bundled binary');
189
+ return true;
190
+ }
191
+
192
+ // Check if we have local source
193
+ const sourceFile = path.join(__dirname, 'cluster-cli.go');
194
+ if (fs.existsSync(sourceFile)) {
195
+ // Build from source
196
+ try {
197
+ execSync('go version', { stdio: 'ignore' });
198
+ execSync(`cd ${__dirname} && go build -o tribe cluster-cli.go client.go`);
199
+ fs.copyFileSync(path.join(__dirname, 'tribe'), tribeDest);
200
+ fs.chmodSync(tribeDest, '755');
201
+ spinner.succeed('TRIBE CLI built from source');
202
+ return true;
203
+ } catch {
204
+ spinner.warn('Go not available, trying pre-built binary...');
205
+ }
206
+ }
207
+
208
+ // Try pre-built binary from GitHub
209
+ const tribeUrl = `https://github.com/0zen/0zen/releases/latest/download/tribe-${platform}-${arch}`;
210
+
211
+ try {
212
+ await downloadFile(tribeUrl, tribeDest);
213
+ fs.chmodSync(tribeDest, '755');
214
+ spinner.succeed('TRIBE CLI installed');
215
+ return true;
216
+ } catch {
217
+ // Fallback: look for any existing tribe binary
218
+ const possiblePaths = [
219
+ path.join(__dirname, '..', 'tribe-cli'),
220
+ path.join(__dirname, '..', 'tribe'),
221
+ './tribe-cli',
222
+ './tribe'
223
+ ];
224
+
225
+ for (const possiblePath of possiblePaths) {
226
+ if (fs.existsSync(possiblePath)) {
227
+ fs.copyFileSync(possiblePath, tribeDest);
228
+ fs.chmodSync(tribeDest, '755');
229
+ spinner.succeed('TRIBE CLI installed from local binary');
230
+ return true;
231
+ }
232
+ }
233
+
234
+ throw new Error('No TRIBE CLI binary available');
235
+ }
236
+ } catch (error) {
237
+ spinner.fail(`TRIBE CLI installation failed: ${error.message}`);
238
+ return false;
239
+ }
240
+ }
241
+
242
+ async function startContainerRuntime() {
243
+ if (platform !== 'darwin') {
244
+ return true; // Linux uses Docker daemon directly
245
+ }
246
+
247
+ // Check if container runtime is already working
248
+ try {
249
+ execSync('docker info', { stdio: 'ignore' });
250
+ log.success('Container runtime is already working');
251
+ return true;
252
+ } catch {
253
+ // Try to start Colima with different approaches
254
+ if (await checkCommand('colima')) {
255
+ const spinner = ora('Starting Colima container runtime...').start();
256
+
257
+ try {
258
+ // Strategy 1: Quick start with minimal resources and Kubernetes
259
+ spinner.text = 'Starting Colima with Kubernetes...';
260
+ const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
261
+ execSync(`${colimaPath} start --cpu 2 --memory 4 --disk 10 --kubernetes --vm-type=vz`, {
262
+ stdio: 'pipe',
263
+ timeout: 60000 // 60 second timeout for K8s
264
+ });
265
+
266
+ // Test if it worked
267
+ execSync('docker info', { stdio: 'ignore' });
268
+ spinner.succeed('Colima started successfully');
269
+ return true;
270
+
271
+ } catch (error) {
272
+ // Strategy 2: Start in background with Kubernetes
273
+ try {
274
+ spinner.text = 'Starting Colima with Kubernetes in background...';
275
+ const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
276
+ const child = spawn(colimaPath, ['start', '--cpu', '2', '--memory', '4', '--disk', '10', '--kubernetes'], {
277
+ detached: true,
278
+ stdio: 'ignore',
279
+ env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
280
+ });
281
+ child.unref(); // Don't wait for completion
282
+
283
+ spinner.succeed('Colima startup initiated (background)');
284
+ log.info('Colima is starting in the background');
285
+ log.info('Run "colima status" to check progress');
286
+ log.info('Run "docker info" to test when ready');
287
+ return true;
288
+
289
+ } catch (bgError) {
290
+ spinner.fail('Failed to start Colima');
291
+ log.warning('Container runtime startup failed (likely due to macOS system restrictions)');
292
+ log.info('Options to fix:');
293
+ log.info('');
294
+ log.info('Option 1 - Manual Colima start:');
295
+ log.info(' colima start --cpu 2 --memory 4 # Start container runtime');
296
+ log.info(' # This downloads a 344MB disk image (may take time)');
297
+ log.info('');
298
+ log.info('Option 2 - Use Docker Desktop (easier):');
299
+ log.info(' Download from: https://docs.docker.com/desktop/install/mac-install/');
300
+ log.info(' # Docker Desktop handles all container runtime setup');
301
+ log.info('');
302
+ log.info('Option 3 - Use Homebrew (recommended):');
303
+ log.info(' brew install colima docker');
304
+ log.info(' colima start');
305
+ log.info('');
306
+ return false;
307
+ }
308
+ }
309
+ }
310
+ }
311
+ return false;
312
+ }
313
+
314
+ async function updatePath() {
315
+ const shell = process.env.SHELL || '/bin/zsh';
316
+ const rcFile = shell.includes('zsh') ? '.zshrc' : '.bashrc';
317
+ const rcPath = path.join(homeDir, rcFile);
318
+
319
+ const pathExport = `export PATH="${binDir}:$PATH"`;
320
+
321
+ try {
322
+ const rcContent = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, 'utf8') : '';
323
+ if (!rcContent.includes(pathExport)) {
324
+ fs.appendFileSync(rcPath, `\n# Added by TRIBE CLI installer\n${pathExport}\n`);
325
+ log.success(`Updated ${rcFile} with PATH`);
326
+ }
327
+ } catch (error) {
328
+ log.warning(`Failed to update ${rcFile}: ${error.message}`);
329
+ }
330
+
331
+ // Update current process PATH
332
+ process.env.PATH = `${binDir}:${process.env.PATH}`;
333
+ }
334
+
335
+ async function verifyInstallation() {
336
+ const spinner = ora('Verifying installation...').start();
337
+
338
+ const tools = ['docker', 'kubectl', 'tribe', 'colima'];
339
+ const results = {};
340
+
341
+ for (const tool of tools) {
342
+ results[tool] = await checkCommand(tool);
343
+ }
344
+
345
+ // Special check for container runtime
346
+ let containerWorking = false;
347
+ try {
348
+ execSync('docker info', { stdio: 'ignore' });
349
+ containerWorking = true;
350
+ } catch (error) {
351
+ containerWorking = false;
352
+ }
353
+
354
+ spinner.stop();
355
+
356
+ console.log('\n' + chalk.bold('Installation Summary:'));
357
+ tools.forEach(tool => {
358
+ const status = results[tool] ? chalk.green('✓') : chalk.red('✗');
359
+ console.log(`${status} ${tool}`);
360
+ });
361
+
362
+ const extraStatus = containerWorking ? chalk.green('✓') : chalk.yellow('⚠');
363
+ console.log(`${extraStatus} Container runtime`);
364
+
365
+ if (platform === 'darwin') {
366
+ const colimaInstalled = await checkCommand('colima');
367
+ const limaInstalled = await checkCommand('limactl');
368
+ console.log(`${colimaInstalled ? chalk.green('✓') : chalk.yellow('⚠')} Colima`);
369
+ console.log(`${limaInstalled ? chalk.green('✓') : chalk.yellow('⚠')} Lima`);
370
+ }
371
+
372
+ return Object.values(results).every(r => r) && containerWorking;
373
+ }
374
+
375
+ async function checkClusterExists() {
376
+ try {
377
+ // Check if TRIBE namespace exists in any context
378
+ execSync('kubectl get namespace tribe-system', { stdio: 'ignore' });
379
+ return true;
380
+ } catch {
381
+ return false;
382
+ }
383
+ }
384
+
385
+ async function checkColimaRunning() {
386
+ try {
387
+ const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
388
+ execSync(`${colimaPath} status`, { stdio: 'ignore' });
389
+ return true;
390
+ } catch {
391
+ return false;
392
+ }
393
+ }
394
+
395
+ async function startColimaWithKubernetes() {
396
+ const spinner = ora('Starting Colima with Kubernetes...').start();
397
+
398
+ try {
399
+ // Check if already running
400
+ if (await checkColimaRunning()) {
401
+ spinner.succeed('Colima is already running');
402
+ return true;
403
+ }
404
+
405
+ // Start Colima with Kubernetes enabled
406
+ spinner.text = 'Starting Colima (this may take a few minutes on first run)...';
407
+ const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
408
+ execSync(`${colimaPath} start --kubernetes --cpu 4 --memory 8 --disk 20`, {
409
+ stdio: 'pipe',
410
+ env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
411
+ });
412
+
413
+ // Verify it's working
414
+ const kubectlPath = await findCommand('kubectl') || 'kubectl';
415
+ execSync(`${kubectlPath} version --client`, { stdio: 'ignore' });
416
+ spinner.succeed('Colima started with Kubernetes');
417
+ return true;
418
+ } catch (error) {
419
+ spinner.fail('Failed to start Colima with Kubernetes');
420
+ log.error(error.message);
421
+ return false;
422
+ }
423
+ }
424
+
425
+ async function deployTribeCluster() {
426
+ const spinner = ora('Deploying TRIBE cluster...').start();
427
+
428
+ try {
429
+ // Create a marker file to indicate first-run deployment
430
+ const deploymentMarker = path.join(tribeDir, '.cluster-deployed');
431
+
432
+ // Check if we've already deployed
433
+ if (fs.existsSync(deploymentMarker)) {
434
+ // Check if cluster actually exists
435
+ if (await checkClusterExists()) {
436
+ spinner.succeed('TRIBE cluster already deployed');
437
+ return true;
438
+ }
439
+ // Marker exists but cluster doesn't - remove marker and redeploy
440
+ fs.unlinkSync(deploymentMarker);
441
+ }
442
+
443
+ // Copy deployment YAML to .tribe directory
444
+ const sourceYaml = path.join(__dirname, 'tribe-deployment.yaml');
445
+ const destYaml = path.join(tribeDir, 'tribe-deployment.yaml');
446
+
447
+ if (fs.existsSync(sourceYaml)) {
448
+ fs.copyFileSync(sourceYaml, destYaml);
449
+ log.info('Copied deployment configuration');
450
+ }
451
+
452
+ // Run tribe start command with deployment YAML path
453
+ spinner.text = 'Running TRIBE cluster deployment...';
454
+ const tribePath = path.join(binDir, 'tribe');
455
+
456
+ // Set environment variable for the deployment YAML location
457
+ const env = {
458
+ ...process.env,
459
+ PATH: `${binDir}:${process.env.PATH}`,
460
+ TRIBE_DEPLOYMENT_YAML: destYaml
461
+ };
462
+
463
+ // Execute tribe start without validation (services need time to start)
464
+ execSync(`${tribePath} start --validate=false`, {
465
+ stdio: 'pipe',
466
+ env: env
467
+ });
468
+
469
+ // Create marker file
470
+ fs.writeFileSync(deploymentMarker, new Date().toISOString());
471
+
472
+ spinner.succeed('TRIBE cluster deployed successfully');
473
+ log.info('Services are starting up. Check status with: tribe status');
474
+ return true;
475
+ } catch (error) {
476
+ spinner.fail('Failed to deploy TRIBE cluster');
477
+ log.error(error.message);
478
+ log.info('You can manually deploy later with: tribe start');
479
+ return false;
480
+ }
481
+ }
482
+
483
+ async function promptForClusterSetup() {
484
+ // Simple prompt without external dependencies
485
+ return new Promise((resolve) => {
486
+ console.log('\n' + chalk.bold('🚀 TRIBE Cluster Setup'));
487
+ console.log('\nWould you like to set up the TRIBE cluster now?');
488
+ console.log('This will:');
489
+ console.log(' • Start Colima with Kubernetes');
490
+ console.log(' • Deploy all TRIBE services');
491
+ console.log(' • Set up port forwarding');
492
+ console.log('\n' + chalk.yellow('Note: This requires ~2GB disk space and may take a few minutes'));
493
+
494
+ process.stdout.write('\nSet up now? [Y/n]: ');
495
+
496
+ process.stdin.resume();
497
+ process.stdin.setEncoding('utf8');
498
+ process.stdin.once('data', (data) => {
499
+ process.stdin.pause();
500
+ const answer = data.toString().trim().toLowerCase();
501
+ resolve(answer === '' || answer === 'y' || answer === 'yes');
502
+ });
503
+ });
504
+ }
505
+
506
+ async function main() {
507
+ console.log(chalk.bold.blue('\n🚀 TRIBE CLI Complete Installer\n'));
508
+
509
+ log.info(`Detected: ${platform} (${arch})`);
510
+ log.info(`Installing to: ${binDir}`);
511
+
512
+ // Update PATH first
513
+ await updatePath();
514
+
515
+ const tasks = [
516
+ { name: 'Colima', fn: installColima },
517
+ { name: 'kubectl', fn: installKubectl },
518
+ { name: 'TRIBE CLI', fn: installTribeCli }
519
+ ];
520
+
521
+ let allSuccess = true;
522
+
523
+ for (const task of tasks) {
524
+ log.step(`Installing ${task.name}...`);
525
+ const success = await task.fn();
526
+ if (!success) allSuccess = false;
527
+ }
528
+
529
+ // Try to start container runtime
530
+ await startContainerRuntime();
531
+
532
+ // Verify everything
533
+ const verified = await verifyInstallation();
534
+
535
+ if (verified) {
536
+ // Check if cluster already exists
537
+ const clusterExists = await checkClusterExists();
538
+
539
+ if (!clusterExists) {
540
+ // Prompt for cluster setup
541
+ const shouldSetup = await promptForClusterSetup();
542
+
543
+ if (shouldSetup) {
544
+ console.log('');
545
+
546
+ // Start Colima with Kubernetes if on macOS
547
+ if (platform === 'darwin') {
548
+ const colimaStarted = await startColimaWithKubernetes();
549
+ if (!colimaStarted) {
550
+ log.error('Failed to start Colima. Please run manually:');
551
+ console.log(' colima start --kubernetes');
552
+ console.log(' tribe start');
553
+ process.exit(1);
554
+ }
555
+ }
556
+
557
+ // Deploy TRIBE cluster
558
+ const deployed = await deployTribeCluster();
559
+
560
+ if (deployed) {
561
+ console.log('\n' + chalk.bold.green('✨ TRIBE is ready!'));
562
+ console.log('');
563
+
564
+ // Check if PATH needs updating
565
+ const needsPathUpdate = !process.env.PATH.includes(binDir);
566
+ if (needsPathUpdate) {
567
+ log.warning('PATH update required for current shell:');
568
+ console.log(chalk.yellow(` source ~/.${process.env.SHELL?.includes('zsh') ? 'zshrc' : 'bashrc'}`));
569
+ console.log(' ' + chalk.gray('or restart your terminal'));
570
+ console.log('');
571
+ }
572
+
573
+ log.info('Quick start:');
574
+ console.log(' tribe # Launch interactive CLI');
575
+ console.log(' tribe status # Check cluster status');
576
+ console.log(' tribe create-task # Create a new task');
577
+ console.log('');
578
+ log.info('First time? The CLI will guide you through creating your first project!');
579
+ } else {
580
+ log.warning('Cluster deployment failed, but you can try manually:');
581
+ console.log(' tribe start');
582
+ }
583
+ } else {
584
+ console.log('\n' + chalk.bold('Setup Complete!'));
585
+ log.info('You can set up the cluster later with:');
586
+ console.log(' tribe start');
587
+ }
588
+ } else {
589
+ console.log('\n' + chalk.bold.green('✨ TRIBE is ready!'));
590
+ log.success('Cluster is already running');
591
+ console.log('');
592
+ log.info('Commands:');
593
+ console.log(' tribe # Launch interactive CLI');
594
+ console.log(' tribe status # Check status');
595
+ console.log(' tribe create-task # Create a new task');
596
+ }
597
+ } else {
598
+ console.log('\n' + chalk.bold('Next Steps:'));
599
+ log.warning('Some components need attention:');
600
+ console.log('');
601
+ // Check container runtime for guidance
602
+ let runtimeWorking = false;
603
+ try {
604
+ execSync('docker info', { stdio: 'ignore' });
605
+ runtimeWorking = true;
606
+ } catch {
607
+ runtimeWorking = false;
608
+ }
609
+
610
+ if (!runtimeWorking) {
611
+ log.info('Start container runtime:');
612
+ console.log(' colima start # macOS');
613
+ console.log(' sudo systemctl start docker # Linux');
614
+ }
615
+ console.log('');
616
+ log.info('Restart your shell or run: source ~/.zshrc');
617
+ log.info('Then run: tribe start');
618
+ }
619
+ }
620
+
621
+ // Handle fetch polyfill for older Node versions
622
+ if (!global.fetch) {
623
+ global.fetch = require('node-fetch');
624
+ }
625
+
626
+ if (require.main === module) {
627
+ const args = process.argv.slice(2);
628
+
629
+ if (args.includes('--help') || args.includes('-h')) {
630
+ console.log(chalk.bold.blue('TRIBE CLI Installer\n'));
631
+ console.log('Usage: npx tribe-cli-local [options]\n');
632
+ console.log('Options:');
633
+ console.log(' --help, -h Show this help message');
634
+ console.log(' --verify Only verify existing installation');
635
+ console.log(' --dry-run Show what would be installed');
636
+ console.log('\nThis installer sets up the complete TRIBE development environment:');
637
+ console.log('• Colima (Container runtime with Kubernetes)');
638
+ console.log('• kubectl (Kubernetes CLI)');
639
+ console.log('• TRIBE CLI (Multi-agent orchestration)');
640
+ console.log('\nAfter installation:');
641
+ console.log(' tribe start # Start TRIBE cluster');
642
+ console.log(' tribe status # Check cluster status');
643
+ process.exit(0);
644
+ }
645
+
646
+ if (args.includes('--verify')) {
647
+ console.log(chalk.bold.blue('🔍 Verifying TRIBE Installation\n'));
648
+ verifyInstallation().then(success => {
649
+ process.exit(success ? 0 : 1);
650
+ });
651
+ return;
652
+ }
653
+
654
+ if (args.includes('--dry-run')) {
655
+ console.log(chalk.bold.blue('🔍 TRIBE CLI Installation Preview\n'));
656
+ log.info(`Platform: ${platform} (${arch})`);
657
+ log.info(`Install directory: ${binDir}`);
658
+ console.log('\nWould install:');
659
+ console.log('• Colima - Container runtime with Kubernetes (macOS only)');
660
+ console.log('• kubectl - Kubernetes CLI');
661
+ console.log('• TRIBE CLI - Multi-agent system');
662
+ process.exit(0);
663
+ }
664
+
665
+ main().catch(error => {
666
+ console.error(chalk.red('Installation failed:'), error.message);
667
+ process.exit(1);
668
+ });
669
+ }
670
+
671
+ module.exports = { main };
Binary file
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@_xtribe/cli",
3
+ "version": "1.0.0-beta.10",
4
+ "description": "TRIBE multi-agent development system - Zero to productive with one command",
5
+ "main": "install-tribe.js",
6
+ "bin": {
7
+ "tribe": "install-tribe.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "tribe",
14
+ "cli",
15
+ "kubernetes",
16
+ "docker",
17
+ "colima",
18
+ "multi-agent",
19
+ "ai",
20
+ "development",
21
+ "autonomous",
22
+ "agents"
23
+ ],
24
+ "engines": {
25
+ "node": ">=16.0.0"
26
+ },
27
+ "os": [
28
+ "darwin",
29
+ "linux"
30
+ ],
31
+ "preferGlobal": true,
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "registry": "https://registry.npmjs.org/"
35
+ },
36
+ "files": [
37
+ "install-tribe.js",
38
+ "lima-guestagent.Linux-aarch64.gz",
39
+ "tribe-deployment.yaml",
40
+ "tribe",
41
+ "README.md",
42
+ "package.json"
43
+ ],
44
+ "dependencies": {
45
+ "chalk": "^4.1.2",
46
+ "ora": "^5.4.1",
47
+ "node-fetch": "^2.6.7",
48
+ "tar": "^6.1.15",
49
+ "which": "^2.0.2"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/0zen/0zen.git"
54
+ },
55
+ "author": "0zen",
56
+ "license": "MIT",
57
+ "homepage": "https://github.com/0zen/0zen",
58
+ "bugs": {
59
+ "url": "https://github.com/0zen/0zen/issues"
60
+ }
61
+ }
package/tribe ADDED
Binary file
@@ -0,0 +1,448 @@
1
+ ---
2
+ # TRIBE Complete Deployment - Bundled with NPM Package
3
+ # This file is automatically deployed by 'npx @_xtribe/cli'
4
+ ---
5
+ apiVersion: v1
6
+ kind: Namespace
7
+ metadata:
8
+ name: tribe-system
9
+ ---
10
+ # ServiceAccount for Bridge
11
+ apiVersion: v1
12
+ kind: ServiceAccount
13
+ metadata:
14
+ name: bridge
15
+ namespace: tribe-system
16
+ ---
17
+ # Role for Bridge
18
+ apiVersion: rbac.authorization.k8s.io/v1
19
+ kind: Role
20
+ metadata:
21
+ name: bridge-role
22
+ namespace: tribe-system
23
+ rules:
24
+ - apiGroups: [""]
25
+ resources: ["pods", "services", "pods/exec", "pods/log", "configmaps", "secrets", "namespaces"]
26
+ verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
27
+ - apiGroups: ["apps"]
28
+ resources: ["deployments", "replicasets"]
29
+ verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
30
+ - apiGroups: ["batch"]
31
+ resources: ["jobs"]
32
+ verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
33
+ ---
34
+ # RoleBinding for Bridge
35
+ apiVersion: rbac.authorization.k8s.io/v1
36
+ kind: RoleBinding
37
+ metadata:
38
+ name: bridge-rolebinding
39
+ namespace: tribe-system
40
+ subjects:
41
+ - kind: ServiceAccount
42
+ name: bridge
43
+ namespace: tribe-system
44
+ roleRef:
45
+ kind: Role
46
+ name: bridge-role
47
+ apiGroup: rbac.authorization.k8s.io
48
+ ---
49
+ # PostgreSQL
50
+ apiVersion: apps/v1
51
+ kind: Deployment
52
+ metadata:
53
+ name: postgres
54
+ namespace: tribe-system
55
+ spec:
56
+ replicas: 1
57
+ selector:
58
+ matchLabels:
59
+ app: postgres
60
+ template:
61
+ metadata:
62
+ labels:
63
+ app: postgres
64
+ spec:
65
+ containers:
66
+ - name: postgres
67
+ image: postgres:15
68
+ env:
69
+ - name: POSTGRES_DB
70
+ value: gitea
71
+ - name: POSTGRES_USER
72
+ value: gitea
73
+ - name: POSTGRES_PASSWORD
74
+ value: gitea
75
+ ports:
76
+ - containerPort: 5432
77
+ readinessProbe:
78
+ exec:
79
+ command:
80
+ - pg_isready
81
+ - -U
82
+ - gitea
83
+ initialDelaySeconds: 5
84
+ periodSeconds: 5
85
+ ---
86
+ apiVersion: v1
87
+ kind: Service
88
+ metadata:
89
+ name: postgres
90
+ namespace: tribe-system
91
+ spec:
92
+ selector:
93
+ app: postgres
94
+ ports:
95
+ - port: 5432
96
+ ---
97
+ # Gitea
98
+ apiVersion: apps/v1
99
+ kind: Deployment
100
+ metadata:
101
+ name: gitea
102
+ namespace: tribe-system
103
+ spec:
104
+ replicas: 1
105
+ selector:
106
+ matchLabels:
107
+ app: gitea
108
+ template:
109
+ metadata:
110
+ labels:
111
+ app: gitea
112
+ spec:
113
+ initContainers:
114
+ - name: wait-for-db
115
+ image: busybox:1.35
116
+ command: ['sh', '-c', 'until nc -z postgres 5432; do echo waiting for db; sleep 2; done']
117
+ - name: init-gitea
118
+ image: gitea/gitea:1.20.5
119
+ command: ['/bin/bash', '-c']
120
+ args:
121
+ - |
122
+ # Create app.ini
123
+ mkdir -p /data/gitea/conf
124
+ cat > /data/gitea/conf/app.ini << 'EOF'
125
+ APP_NAME = Gitea
126
+ RUN_MODE = prod
127
+
128
+ [database]
129
+ DB_TYPE = postgres
130
+ HOST = postgres:5432
131
+ NAME = gitea
132
+ USER = gitea
133
+ PASSWD = gitea
134
+
135
+ [server]
136
+ DOMAIN = gitea
137
+ ROOT_URL = http://gitea:3000/
138
+ HTTP_PORT = 3000
139
+
140
+ [service]
141
+ DISABLE_REGISTRATION = true
142
+
143
+ [security]
144
+ INSTALL_LOCK = true
145
+ SECRET_KEY = changeme
146
+ EOF
147
+
148
+ # Run migrations
149
+ gitea migrate
150
+
151
+ # Create admin user
152
+ gitea admin user create --admin --username gitea_admin --password admin123 --email admin@example.com || true
153
+ env:
154
+ - name: GITEA_WORK_DIR
155
+ value: /data
156
+ - name: GITEA_CUSTOM
157
+ value: /data/gitea
158
+ volumeMounts:
159
+ - name: gitea-data
160
+ mountPath: /data
161
+ containers:
162
+ - name: gitea
163
+ image: gitea/gitea:1.20.5
164
+ ports:
165
+ - containerPort: 3000
166
+ env:
167
+ - name: GITEA_WORK_DIR
168
+ value: /data
169
+ - name: GITEA_CUSTOM
170
+ value: /data/gitea
171
+ volumeMounts:
172
+ - name: gitea-data
173
+ mountPath: /data
174
+ readinessProbe:
175
+ httpGet:
176
+ path: /
177
+ port: 3000
178
+ initialDelaySeconds: 30
179
+ periodSeconds: 10
180
+ volumes:
181
+ - name: gitea-data
182
+ emptyDir: {}
183
+ ---
184
+ apiVersion: v1
185
+ kind: Service
186
+ metadata:
187
+ name: gitea
188
+ namespace: tribe-system
189
+ spec:
190
+ selector:
191
+ app: gitea
192
+ ports:
193
+ - port: 3000
194
+ ---
195
+ # TaskMaster
196
+ apiVersion: apps/v1
197
+ kind: Deployment
198
+ metadata:
199
+ name: taskmaster
200
+ namespace: tribe-system
201
+ spec:
202
+ replicas: 1
203
+ selector:
204
+ matchLabels:
205
+ app: taskmaster
206
+ template:
207
+ metadata:
208
+ labels:
209
+ app: taskmaster
210
+ spec:
211
+ initContainers:
212
+ - name: wait-for-db
213
+ image: busybox:1.35
214
+ command: ['sh', '-c', 'until nc -z postgres 5432; do echo waiting for db; sleep 2; done']
215
+ containers:
216
+ - name: taskmaster
217
+ image: taskmaster:latest
218
+ imagePullPolicy: IfNotPresent
219
+ ports:
220
+ - containerPort: 5000
221
+ env:
222
+ - name: FLASK_ENV
223
+ value: development
224
+ - name: DATABASE_URL
225
+ value: postgresql://gitea:gitea@postgres:5432/gitea
226
+ - name: GITEA_URL
227
+ value: http://gitea:3000
228
+ - name: GITEA_TOKEN
229
+ value: will-be-set-by-init-job
230
+ readinessProbe:
231
+ httpGet:
232
+ path: /
233
+ port: 5000
234
+ initialDelaySeconds: 10
235
+ periodSeconds: 5
236
+ ---
237
+ apiVersion: v1
238
+ kind: Service
239
+ metadata:
240
+ name: taskmaster
241
+ namespace: tribe-system
242
+ spec:
243
+ selector:
244
+ app: taskmaster
245
+ ports:
246
+ - port: 5000
247
+ ---
248
+ # Bridge
249
+ apiVersion: apps/v1
250
+ kind: Deployment
251
+ metadata:
252
+ name: bridge
253
+ namespace: tribe-system
254
+ spec:
255
+ replicas: 1
256
+ selector:
257
+ matchLabels:
258
+ app: bridge
259
+ template:
260
+ metadata:
261
+ labels:
262
+ app: bridge
263
+ spec:
264
+ serviceAccountName: bridge
265
+ initContainers:
266
+ - name: wait-for-services
267
+ image: busybox:1.35
268
+ command: ['sh', '-c']
269
+ args:
270
+ - |
271
+ echo "Waiting for services..."
272
+ until nc -z taskmaster 5000; do echo waiting for taskmaster; sleep 2; done
273
+ until nc -z gitea 3000; do echo waiting for gitea; sleep 2; done
274
+ echo "All services ready!"
275
+ containers:
276
+ - name: bridge
277
+ image: bridge:latest
278
+ imagePullPolicy: IfNotPresent
279
+ ports:
280
+ - containerPort: 8080
281
+ - containerPort: 3456
282
+ env:
283
+ - name: TASKMASTER_URL
284
+ value: http://taskmaster:5000
285
+ - name: GITEA_URL
286
+ value: http://gitea:3000
287
+ - name: GITEA_ADMIN_USER
288
+ value: gitea_admin
289
+ - name: GITEA_ADMIN_PASSWORD
290
+ value: admin123
291
+ - name: NAMESPACE
292
+ value: tribe-system
293
+ readinessProbe:
294
+ httpGet:
295
+ path: /health
296
+ port: 8080
297
+ initialDelaySeconds: 10
298
+ periodSeconds: 5
299
+ ---
300
+ apiVersion: v1
301
+ kind: Service
302
+ metadata:
303
+ name: bridge
304
+ namespace: tribe-system
305
+ spec:
306
+ selector:
307
+ app: bridge
308
+ ports:
309
+ - name: http
310
+ port: 8080
311
+ - name: websocket
312
+ port: 3456
313
+ ---
314
+ # Claude Worker Deployment (starts with 0 replicas)
315
+ apiVersion: apps/v1
316
+ kind: Deployment
317
+ metadata:
318
+ name: claude-worker-deployment
319
+ namespace: tribe-system
320
+ spec:
321
+ replicas: 0
322
+ selector:
323
+ matchLabels:
324
+ app: claude-worker
325
+ template:
326
+ metadata:
327
+ labels:
328
+ app: claude-worker
329
+ spec:
330
+ containers:
331
+ - name: claude-agent
332
+ image: claude-agent:latest
333
+ imagePullPolicy: IfNotPresent
334
+ env:
335
+ - name: ROLE
336
+ value: worker
337
+ - name: TASKMASTER_URL
338
+ value: http://taskmaster:5000
339
+ - name: GITEA_URL
340
+ value: http://gitea:3000
341
+ - name: NAMESPACE
342
+ value: tribe-system
343
+ - name: ANTHROPIC_API_KEY
344
+ valueFrom:
345
+ secretKeyRef:
346
+ name: claude-api-key
347
+ key: api-key
348
+ optional: true
349
+ volumeMounts:
350
+ - name: workspace
351
+ mountPath: /workspace
352
+ - name: config
353
+ mountPath: /app/minimal-config
354
+ readinessProbe:
355
+ exec:
356
+ command:
357
+ - /bin/sh
358
+ - -c
359
+ - test -f /tmp/worker-ready
360
+ initialDelaySeconds: 30
361
+ periodSeconds: 10
362
+ volumes:
363
+ - name: workspace
364
+ emptyDir: {}
365
+ - name: config
366
+ configMap:
367
+ name: claude-config
368
+ optional: true
369
+ ---
370
+ # NodePort service for easy access
371
+ apiVersion: v1
372
+ kind: Service
373
+ metadata:
374
+ name: bridge-nodeport
375
+ namespace: tribe-system
376
+ spec:
377
+ type: NodePort
378
+ selector:
379
+ app: bridge
380
+ ports:
381
+ - name: http
382
+ port: 8080
383
+ nodePort: 30080
384
+ - name: websocket
385
+ port: 3456
386
+ nodePort: 30456
387
+ ---
388
+ # ConfigMap for Claude agent configuration
389
+ apiVersion: v1
390
+ kind: ConfigMap
391
+ metadata:
392
+ name: claude-config
393
+ namespace: tribe-system
394
+ data:
395
+ config.yaml: |
396
+ mcp_servers:
397
+ filesystem:
398
+ command: npx
399
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
400
+ git:
401
+ command: npx
402
+ args: ["-y", "@modelcontextprotocol/server-git"]
403
+ env:
404
+ PATH: /usr/local/bin:/usr/bin:/bin
405
+ ---
406
+ # Initialization Job
407
+ apiVersion: batch/v1
408
+ kind: Job
409
+ metadata:
410
+ name: tribe-init
411
+ namespace: tribe-system
412
+ spec:
413
+ template:
414
+ spec:
415
+ restartPolicy: OnFailure
416
+ containers:
417
+ - name: init
418
+ image: curlimages/curl:8.4.0
419
+ command: ['/bin/sh', '-c']
420
+ args:
421
+ - |
422
+ echo "Waiting for Gitea to be ready..."
423
+ until curl -s http://gitea:3000 > /dev/null; do
424
+ echo "Waiting for Gitea..."
425
+ sleep 5
426
+ done
427
+
428
+ echo "Creating Gitea access token..."
429
+ TOKEN=$(curl -s -X POST http://gitea:3000/api/v1/users/gitea_admin/tokens \
430
+ -u gitea_admin:admin123 \
431
+ -H "Content-Type: application/json" \
432
+ -d '{"name":"taskmaster-'$(date +%s)'","scopes":["write:repository","write:user","write:issue","write:organization","read:repository"]}' \
433
+ | grep -o '"sha1":"[^"]*' | cut -d'"' -f4)
434
+
435
+ if [ -z "$TOKEN" ]; then
436
+ echo "Failed to create token!"
437
+ exit 1
438
+ fi
439
+
440
+ echo "Token created successfully"
441
+
442
+ # Update TaskMaster with the token
443
+ echo "Updating TaskMaster configuration..."
444
+ curl -X POST http://taskmaster:5000/api/config \
445
+ -H "Content-Type: application/json" \
446
+ -d "{\"gitea_token\":\"$TOKEN\"}"
447
+
448
+ echo "Initialization complete!"