@_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 +123 -0
- package/install-tribe.js +671 -0
- package/lima-guestagent.Linux-aarch64.gz +0 -0
- package/package.json +61 -0
- package/tribe +0 -0
- package/tribe-deployment.yaml +448 -0
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.
|
package/install-tribe.js
ADDED
|
@@ -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!"
|