agent-window 1.0.0

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/bin/cli.js ADDED
@@ -0,0 +1,743 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * AgentBridge - Command Line Interface
5
+ *
6
+ * Bridge AI coding agents to chat platforms
7
+ *
8
+ * Usage:
9
+ * agent-bridge setup - Interactive setup wizard
10
+ * agent-bridge start - Start the bridge
11
+ * agent-bridge stop - Stop the bridge
12
+ * agent-bridge restart - Restart the bridge
13
+ * agent-bridge status - Check status
14
+ * agent-bridge logs - View logs
15
+ * agent-bridge update - Update to latest version
16
+ * agent-bridge config - Show config location
17
+ */
18
+
19
+ import { spawn, execSync } from 'child_process';
20
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
21
+ import { dirname, join } from 'path';
22
+ import { fileURLToPath } from 'url';
23
+ import { createInterface } from 'readline';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const PROJECT_ROOT = join(__dirname, '..');
27
+ const CONFIG_DIR = join(PROJECT_ROOT, 'config');
28
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
29
+ const CONFIG_EXAMPLE = join(CONFIG_DIR, 'config.example.json');
30
+ const PM2_NAME = 'agent-bridge';
31
+
32
+ // Colors for terminal output
33
+ const colors = {
34
+ reset: '\x1b[0m',
35
+ bright: '\x1b[1m',
36
+ dim: '\x1b[2m',
37
+ green: '\x1b[32m',
38
+ yellow: '\x1b[33m',
39
+ red: '\x1b[31m',
40
+ cyan: '\x1b[36m',
41
+ magenta: '\x1b[35m',
42
+ };
43
+
44
+ function log(msg, color = '') {
45
+ console.log(`${color}${msg}${colors.reset}`);
46
+ }
47
+
48
+ function logSuccess(msg) { log(`✅ ${msg}`, colors.green); }
49
+ function logWarning(msg) { log(`⚠️ ${msg}`, colors.yellow); }
50
+ function logError(msg) { log(`❌ ${msg}`, colors.red); }
51
+ function logInfo(msg) { log(`ℹ️ ${msg}`, colors.cyan); }
52
+
53
+ // Banner
54
+ function showBanner() {
55
+ log(`
56
+ _ _ ____ _ _
57
+ / \\ __ _ ___ _ __ | |_| __ ) _ __(_) __| | __ _ ___
58
+ / _ \\ / _\` |/ _ \\ '_ \\| __| _ \\| '__| |/ _\` |/ _\` |/ _ \\
59
+ / ___ \\ (_| | __/ | | | |_| |_) | | | | (_| | (_| | __/
60
+ /_/ \\_\\__, |\\___|_| |_|\\__|____/|_| |_|\\__,_|\\__, |\\___|
61
+ |___/ |___/
62
+ `, colors.cyan);
63
+ log(' Bridge AI coding agents to chat platforms\n', colors.dim);
64
+ }
65
+
66
+ // Check if PM2 is installed
67
+ function checkPM2() {
68
+ try {
69
+ execSync('pm2 --version', { stdio: 'ignore' });
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ // Check if Docker is installed
77
+ function checkDocker() {
78
+ try {
79
+ execSync('docker --version', { stdio: 'ignore' });
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ // Get existing container names to avoid conflicts
87
+ function getExistingContainerNames() {
88
+ try {
89
+ const output = execSync('docker ps -a --format "{{.Names}}"', { encoding: 'utf-8' });
90
+ return new Set(output.trim().split('\n').filter(Boolean));
91
+ } catch {
92
+ return new Set();
93
+ }
94
+ }
95
+
96
+ // Generate a unique container name
97
+ function generateUniqueContainerName(existingNames) {
98
+ const baseName = 'claude-discord-bot';
99
+ if (!existingNames.has(baseName)) {
100
+ return baseName;
101
+ }
102
+
103
+ // Find next available number
104
+ let i = 2;
105
+ while (existingNames.has(`${baseName}-${i}`)) {
106
+ i++;
107
+ }
108
+ return `${baseName}-${i}`;
109
+ }
110
+
111
+ // Interactive prompt
112
+ function prompt(question, sensitive = false) {
113
+ const rl = createInterface({
114
+ input: process.stdin,
115
+ output: process.stdout,
116
+ });
117
+ return new Promise((resolve) => {
118
+ rl.question(question, (answer) => {
119
+ rl.close();
120
+ resolve(answer.trim());
121
+ });
122
+ });
123
+ }
124
+
125
+ // Verify Discord Bot Token and check intents
126
+ async function verifyDiscordToken(token) {
127
+ log('\n🔍 Verifying Discord Bot configuration...', colors.cyan);
128
+ try {
129
+ const response = await fetch('https://discord.com/api/v10/users/@me', {
130
+ headers: { 'Authorization': `Bot ${token}` }
131
+ });
132
+
133
+ if (!response.ok) {
134
+ if (response.status === 401) {
135
+ logError('Invalid Discord Bot Token');
136
+ logInfo('Get your token from: https://discord.com/developers/applications');
137
+ return false;
138
+ }
139
+ logError(`Discord API error: ${response.status}`);
140
+ return false;
141
+ }
142
+
143
+ const bot = await response.json();
144
+ logSuccess(`Bot verified: ${bot.username} (${bot.id})`);
145
+
146
+ // Check gateway intents for the application
147
+ const appResponse = await fetch('https://discord.com/api/v10/applications/@me', {
148
+ headers: { 'Authorization': `Bot ${token}` }
149
+ });
150
+
151
+ if (appResponse.ok) {
152
+ const app = await appResponse.json();
153
+ const flags = app.flags || 0;
154
+
155
+ // Flag 1 << 16 is Message Content Intent
156
+ const hasMessageContent = (flags & (1 << 16)) !== 0;
157
+
158
+ if (!hasMessageContent) {
159
+ logError('Message Content Intent is NOT enabled!');
160
+ log('\n⚠️ REQUIRED ACTION:', colors.yellow);
161
+ log('1. Go to: https://discord.com/developers/applications');
162
+ log('2. Select your application');
163
+ log('3. Click "Bot" on the left sidebar');
164
+ log('4. Scroll to "Privileged Gateway Intents"');
165
+ log('5. Toggle ON "Message Content Intent"');
166
+ log('6. Save Changes\n');
167
+ log('After enabling, run setup again.', colors.dim);
168
+ return false;
169
+ }
170
+ logSuccess('Message Content Intent: ✅ enabled');
171
+ }
172
+
173
+ return true;
174
+ } catch (e) {
175
+ logWarning(`Could not verify Discord config: ${e.message}`);
176
+ logInfo('Will verify on startup...');
177
+ return true; // Don't block setup on network errors
178
+ }
179
+ }
180
+
181
+ // Test Docker volume mount for the install directory
182
+ function testDockerMount(testPath) {
183
+ log('\n🔍 Testing Docker file sharing...', colors.cyan);
184
+ try {
185
+ const testContainer = 'agent-bridge-mount-test';
186
+ // Remove any old test container
187
+ execSync(`docker rm -f ${testContainer} 2>/dev/null`, { stdio: 'ignore' });
188
+
189
+ // Test mounting the directory
190
+ execSync(
191
+ `docker run --rm --name ${testContainer} -v ${testPath}:/test:ro alpine:latest ls /test >/dev/null 2>&1`,
192
+ { stdio: 'ignore', timeout: 10000 }
193
+ );
194
+
195
+ logSuccess('Docker can access install directory');
196
+ return true;
197
+ } catch (e) {
198
+ logError(`Docker cannot mount: ${testPath}`);
199
+ log('\n⚠️ REQUIRED ACTION:', colors.yellow);
200
+ log('macOS Docker Desktop requires path sharing:');
201
+ log('1. Open Docker Desktop');
202
+ log('2. Go to: Settings → Resources → File Sharing');
203
+ log(`3. Add: ${testPath}`);
204
+ log('4. Click "Apply & Restart"');
205
+ log('\nAlternative: Install to user directory instead of global:');
206
+ log(' mkdir -p ~/agent-bridge && cd ~/agent-bridge && npm install cyrus-agent-bridge\n');
207
+ return false;
208
+ }
209
+ }
210
+
211
+ // Verify Claude OAuth Token
212
+ async function verifyClaudeToken(token) {
213
+ log('\n🔍 Verifying Claude Code OAuth Token...', colors.cyan);
214
+ try {
215
+ // Simple validation - token should start with sk-ant-
216
+ if (!token.startsWith('sk-ant-')) {
217
+ logWarning('Token format looks incorrect (should start with sk-ant-)');
218
+ return false;
219
+ }
220
+ logSuccess('Claude OAuth Token format valid');
221
+ return true;
222
+ } catch (e) {
223
+ return false;
224
+ }
225
+ }
226
+
227
+ // Setup wizard
228
+ async function setup() {
229
+ showBanner();
230
+ log('🚀 Setup Wizard\n', colors.bright);
231
+
232
+ // Check prerequisites
233
+ log('Checking prerequisites...', colors.cyan);
234
+
235
+ if (!checkPM2()) {
236
+ logWarning('PM2 not found. Installing globally...');
237
+ try {
238
+ execSync('npm install -g pm2', { stdio: 'inherit' });
239
+ logSuccess('PM2 installed');
240
+ } catch {
241
+ logError('Failed to install PM2. Please install manually: npm install -g pm2');
242
+ process.exit(1);
243
+ }
244
+ } else {
245
+ logSuccess('PM2 found');
246
+ }
247
+
248
+ if (!checkDocker()) {
249
+ logError('Docker not found. Please install Docker first.');
250
+ logInfo('Visit: https://docs.docker.com/get-docker/');
251
+ process.exit(1);
252
+ } else {
253
+ logSuccess('Docker found');
254
+ }
255
+
256
+ // Check for container name conflicts
257
+ const existingNames = getExistingContainerNames();
258
+ const containerName = generateUniqueContainerName(existingNames);
259
+ if (existingNames.has('claude-discord-bot') || existingNames.has('agentbridge-bot')) {
260
+ logWarning(`Existing containers detected. Using unique name: ${containerName}`);
261
+ }
262
+
263
+ // Load or create config
264
+ let config;
265
+ let configExisted = existsSync(CONFIG_FILE);
266
+
267
+ if (configExisted) {
268
+ log('\n📋 Existing configuration found, validating...', colors.cyan);
269
+ config = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
270
+ logSuccess('Configuration loaded');
271
+ } else {
272
+ log('\n📝 Required Configuration (4 items)\n', colors.cyan);
273
+
274
+ log('1/4 Discord Bot Token', colors.dim);
275
+ const botToken = await prompt(' > ');
276
+
277
+ log('2/4 Claude OAuth Token', colors.dim);
278
+ const oauthToken = await prompt(' > ');
279
+
280
+ log('3/4 Project directory (absolute path)', colors.dim);
281
+ const projectDir = await prompt(' > ');
282
+
283
+ log('4/4 Discord channel ID(s)', colors.dim);
284
+ const allowedChannels = await prompt(' > ');
285
+
286
+ // Load example config and modify
287
+ config = JSON.parse(readFileSync(CONFIG_EXAMPLE, 'utf-8'));
288
+ config.BOT_TOKEN = botToken;
289
+ config.CLAUDE_CODE_OAUTH_TOKEN = oauthToken;
290
+ config.PROJECT_DIR = projectDir;
291
+ config.ALLOWED_CHANNELS = allowedChannels;
292
+ config.workspace.containerName = containerName; // Use unique container name
293
+
294
+ // Remove comments
295
+ Object.keys(config).forEach(key => {
296
+ if (key.startsWith('_comment')) delete config[key];
297
+ });
298
+
299
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
300
+ logSuccess(`Configuration saved (container: ${containerName})`);
301
+ }
302
+
303
+ // Verify Discord configuration
304
+ if (config.BOT_TOKEN) {
305
+ const discordValid = await verifyDiscordToken(config.BOT_TOKEN);
306
+ if (!discordValid) {
307
+ logError('\n❌ Setup failed: Discord configuration invalid');
308
+ logInfo('Fix the issues above and run: agent-bridge setup');
309
+ process.exit(1);
310
+ }
311
+ }
312
+
313
+ // Verify Claude token format
314
+ if (config.CLAUDE_CODE_OAUTH_TOKEN) {
315
+ await verifyClaudeToken(config.CLAUDE_CODE_OAUTH_TOKEN);
316
+ }
317
+
318
+ // Test Docker mount for the install directory
319
+ const mountOk = testDockerMount(PROJECT_ROOT);
320
+ if (!mountOk) {
321
+ logError('\n❌ Setup failed: Docker file sharing not configured');
322
+ logInfo('Fix the issues above and run: agent-bridge setup');
323
+ process.exit(1);
324
+ }
325
+
326
+ // Build Docker image
327
+ log('\n🐳 Building Docker sandbox...\n', colors.cyan);
328
+ try {
329
+ execSync('docker build -t claude-sandbox ./sandbox', {
330
+ cwd: PROJECT_ROOT,
331
+ stdio: 'inherit'
332
+ });
333
+ logSuccess('Docker image built');
334
+ } catch {
335
+ logError('Failed to build Docker image');
336
+ process.exit(1);
337
+ }
338
+
339
+ // Create PM2 ecosystem file
340
+ const ecosystemFile = join(PROJECT_ROOT, 'ecosystem.config.cjs');
341
+ if (!existsSync(ecosystemFile)) {
342
+ const ecosystemConfig = `module.exports = {
343
+ apps: [{
344
+ name: '${PM2_NAME}',
345
+ script: 'src/bot.js',
346
+ cwd: '${PROJECT_ROOT}',
347
+ watch: false,
348
+ autorestart: true,
349
+ max_restarts: 10,
350
+ env: {
351
+ NODE_ENV: 'production'
352
+ }
353
+ }]
354
+ };
355
+ `;
356
+ writeFileSync(ecosystemFile, ecosystemConfig);
357
+ logSuccess('PM2 config created');
358
+ }
359
+
360
+ log('\n✨ Setup complete!\n', colors.green + colors.bright);
361
+ log('Start the bridge with:', colors.cyan);
362
+ log(' agent-bridge start\n');
363
+ }
364
+
365
+ // Start
366
+ function start() {
367
+ if (!existsSync(CONFIG_FILE)) {
368
+ logError('Configuration not found. Run "agent-bridge setup" first.');
369
+ process.exit(1);
370
+ }
371
+
372
+ log('Starting AgentBridge...', colors.cyan);
373
+ try {
374
+ execSync(`pm2 start ecosystem.config.cjs`, {
375
+ cwd: PROJECT_ROOT,
376
+ stdio: 'inherit'
377
+ });
378
+ logSuccess('AgentBridge started');
379
+ log('\nView logs: agent-bridge logs', colors.dim);
380
+ } catch (e) {
381
+ logError('Failed to start: ' + e.message);
382
+ }
383
+ }
384
+
385
+ // Stop
386
+ function stop() {
387
+ log('Stopping AgentBridge...', colors.cyan);
388
+ try {
389
+ execSync(`pm2 stop ${PM2_NAME}`, { stdio: 'inherit' });
390
+ logSuccess('AgentBridge stopped');
391
+ } catch {
392
+ logWarning('AgentBridge may not be running');
393
+ }
394
+ }
395
+
396
+ // Restart
397
+ function restart() {
398
+ log('Restarting AgentBridge...', colors.cyan);
399
+ try {
400
+ execSync(`pm2 restart ${PM2_NAME}`, { stdio: 'inherit' });
401
+ logSuccess('AgentBridge restarted');
402
+ } catch {
403
+ logWarning('AgentBridge may not be running. Starting...');
404
+ start();
405
+ }
406
+ }
407
+
408
+ // Status
409
+ function status() {
410
+ try {
411
+ execSync(`pm2 describe ${PM2_NAME}`, { stdio: 'inherit' });
412
+ } catch {
413
+ logWarning('AgentBridge is not running');
414
+ }
415
+ }
416
+
417
+ // Logs
418
+ function logs() {
419
+ try {
420
+ const child = spawn('pm2', ['logs', PM2_NAME, '--lines', '50'], {
421
+ stdio: 'inherit'
422
+ });
423
+ child.on('error', () => {
424
+ logError('Failed to show logs');
425
+ });
426
+ } catch {
427
+ logError('Failed to show logs');
428
+ }
429
+ }
430
+
431
+ // Update
432
+ async function update() {
433
+ log('Checking for updates...', colors.cyan);
434
+
435
+ try {
436
+ const isGit = existsSync(join(PROJECT_ROOT, '.git'));
437
+
438
+ if (isGit) {
439
+ log('Fetching updates...', colors.cyan);
440
+ execSync('git fetch origin', { cwd: PROJECT_ROOT, stdio: 'inherit' });
441
+
442
+ const localHash = execSync('git rev-parse HEAD', { cwd: PROJECT_ROOT, encoding: 'utf-8' }).trim();
443
+ const remoteHash = execSync('git rev-parse origin/main', { cwd: PROJECT_ROOT, encoding: 'utf-8' }).trim();
444
+
445
+ if (localHash === remoteHash) {
446
+ logSuccess('Already up to date');
447
+ return;
448
+ }
449
+
450
+ log('Stopping for update...', colors.yellow);
451
+ try { execSync(`pm2 stop ${PM2_NAME}`, { stdio: 'ignore' }); } catch {}
452
+
453
+ execSync('git pull origin main', { cwd: PROJECT_ROOT, stdio: 'inherit' });
454
+
455
+ log('Installing dependencies...', colors.cyan);
456
+ execSync('npm install', { cwd: PROJECT_ROOT, stdio: 'inherit' });
457
+
458
+ log('Rebuilding Docker image...', colors.cyan);
459
+ execSync('docker build -t claude-sandbox ./sandbox', { cwd: PROJECT_ROOT, stdio: 'inherit' });
460
+
461
+ log('Restarting...', colors.cyan);
462
+ execSync(`pm2 restart ${PM2_NAME}`, { cwd: PROJECT_ROOT, stdio: 'inherit' });
463
+
464
+ logSuccess('Update complete');
465
+ } else {
466
+ log('Updating via npm...', colors.cyan);
467
+ execSync('npm update -g agent-bridge', { stdio: 'inherit' });
468
+ logSuccess('Update complete. Please restart.');
469
+ }
470
+ } catch (e) {
471
+ logError('Update failed: ' + e.message);
472
+ }
473
+ }
474
+
475
+ // Config
476
+ function showConfig() {
477
+ log('\nConfiguration:', colors.cyan);
478
+ log(` Config: ${CONFIG_FILE}`);
479
+ log(` Example: ${CONFIG_EXAMPLE}`);
480
+
481
+ if (existsSync(CONFIG_FILE)) {
482
+ logSuccess('Config exists');
483
+ } else {
484
+ logWarning('Config not found. Run "agent-bridge setup"');
485
+ }
486
+ }
487
+
488
+ // Init - Initialize multi-bot management directory
489
+ async function initBotsDir() {
490
+ showBanner();
491
+ log('🚀 Initialize Multi-Bot Management\n', colors.bright);
492
+
493
+ const defaultBotsDir = join(process.env.HOME || process.env.USERPROFILE, 'bots');
494
+ log(`Default bots directory: ${defaultBotsDir}`, colors.dim);
495
+
496
+ const botsDir = await prompt('Bots directory (press Enter for default): ') || defaultBotsDir;
497
+
498
+ // Create directory structure
499
+ const botTypesDir = join(botsDir, 'agent-bridge@latest');
500
+ try {
501
+ execSync(`mkdir -p "${botTypesDir}"`, { stdio: 'inherit' });
502
+ } catch {
503
+ // Directory already exists or was created
504
+ }
505
+
506
+ // Create symlink to global agent-bridge
507
+ const globalAgentBridge = '/opt/homebrew/lib/node_modules/agent-bridge';
508
+ if (existsSync(globalAgentBridge)) {
509
+ const linkPath = join(botsDir, 'agent-bridge');
510
+ try {
511
+ execSync(`ln -sf "${globalAgentBridge}" "${linkPath}"`, { stdio: 'inherit' });
512
+ logSuccess('Linked to global agent-bridge installation');
513
+ } catch {
514
+ logWarning('Could not create symlink (may already exist)');
515
+ }
516
+ }
517
+
518
+ // Create ecosystem template
519
+ const ecosystemFile = join(botsDir, 'ecosystem.config.cjs');
520
+ if (!existsSync(ecosystemFile)) {
521
+ const ecosystemTemplate = `/**
522
+ * AgentBridge - Multi-Bot Management
523
+ *
524
+ * Add new bots with: agent-bridge add-bot <name>
525
+ */
526
+
527
+ const AGENT_BRIDGE = '${globalAgentBridge}';
528
+ const BOTS_DIR = '${botsDir}';
529
+
530
+ module.exports = {
531
+ apps: [
532
+ // Add your bot instances here
533
+ // {
534
+ // name: 'bot-myproject',
535
+ // script: AGENT_BRIDGE + '/src/bot.js',
536
+ // cwd: AGENT_BRIDGE,
537
+ // env: {
538
+ // CONFIG_PATH: BOTS_DIR + '/myproject/config.json'
539
+ // },
540
+ // watch: false,
541
+ // autorestart: true,
542
+ // max_restarts: 10,
543
+ // }
544
+ ]
545
+ };
546
+ `;
547
+ writeFileSync(ecosystemFile, ecosystemTemplate);
548
+ logSuccess(`Created: ${ecosystemFile}`);
549
+ }
550
+
551
+ log('\n✨ Multi-bot directory initialized!\n', colors.green + colors.bright);
552
+ log('Next steps:', colors.cyan);
553
+ log(` cd ${botsDir}`);
554
+ log(' agent-bridge add-bot <name> # Add a new bot');
555
+ log(' pm2 start ecosystem.config.cjs # Start all bots\n');
556
+ }
557
+
558
+ // Add Bot - Add a new bot instance
559
+ async function addBot() {
560
+ const botName = process.argv[3];
561
+ if (!botName) {
562
+ logError('Usage: agent-bridge add-bot <name>');
563
+ logInfo('Example: agent-bridge add-bot myproject');
564
+ process.exit(1);
565
+ }
566
+
567
+ // Find bots directory
568
+ const defaultBotsDir = join(process.env.HOME || process.env.USERPROFILE, 'bots');
569
+ const botsDir = existsSync(defaultBotsDir) ? defaultBotsDir : null;
570
+
571
+ if (!botsDir || !existsSync(join(botsDir, 'ecosystem.config.cjs'))) {
572
+ logError('Bots directory not initialized.');
573
+ logInfo('Run: agent-bridge init');
574
+ process.exit(1);
575
+ }
576
+
577
+ log(`🤖 Adding bot: ${botName}\n`, colors.cyan);
578
+
579
+ const botDir = join(botsDir, botName);
580
+ execSync(`mkdir -p "${botDir}"`, { stdio: 'inherit' });
581
+
582
+ // Check if config already exists
583
+ const configFile = join(botDir, 'config.json');
584
+ if (existsSync(configFile)) {
585
+ logWarning(`Bot already exists: ${botName}`);
586
+ logInfo(`To reconfigure, edit: ${configFile}`);
587
+ process.exit(0);
588
+ }
589
+
590
+ // Prompt for configuration
591
+ log('\n📝 Bot Configuration\n', colors.cyan);
592
+
593
+ log('Discord Bot Token', colors.dim);
594
+ const botToken = await prompt(' > ');
595
+
596
+ log('Claude OAuth Token', colors.dim);
597
+ const oauthToken = await prompt(' > ');
598
+
599
+ log('Project directory (absolute path)', colors.dim);
600
+ const projectDir = await prompt(' > ');
601
+
602
+ log('Discord channel ID(s)', colors.dim);
603
+ const allowedChannels = await prompt(' > ');
604
+
605
+ log('Container name', colors.dim);
606
+ const containerName = await prompt(` > `, `bot-${botName}`);
607
+
608
+ // Create config
609
+ const config = {
610
+ BOT_TOKEN: botToken,
611
+ CLAUDE_CODE_OAUTH_TOKEN: oauthToken,
612
+ PROJECT_DIR: projectDir,
613
+ ALLOWED_CHANNELS: allowedChannels,
614
+ workspace: {
615
+ containerName: containerName
616
+ }
617
+ };
618
+
619
+ writeFileSync(configFile, JSON.stringify(config, null, 2));
620
+ logSuccess(`Configuration saved: ${configFile}`);
621
+
622
+ // Update ecosystem file
623
+ const ecosystemFile = join(botsDir, 'ecosystem.config.cjs');
624
+ const ecosystemContent = readFileSync(ecosystemFile, 'utf-8');
625
+
626
+ // Check if bot already in ecosystem
627
+ if (ecosystemContent.includes(`name: 'bot-${botName}'`) || ecosystemContent.includes(`name: "bot-${botName}"`)) {
628
+ logWarning('Bot already in ecosystem.config.cjs');
629
+ } else {
630
+ // Add bot to ecosystem
631
+ const newApp = `,
632
+ {
633
+ name: 'bot-` + botName + `',
634
+ script: process.env.GLOBAL_AGENT_BRIDGE || '/opt/homebrew/lib/node_modules/agent-bridge/src/bot.js',
635
+ cwd: process.env.GLOBAL_AGENT_BRIDGE ? process.env.GLOBAL_AGENT_BRIDGE : '/opt/homebrew/lib/node_modules/agent-bridge',
636
+ env: {
637
+ CONFIG_PATH: '` + botsDir + '/' + botName + `/config.json'
638
+ },
639
+ watch: false,
640
+ autorestart: true,
641
+ max_restarts: 10,
642
+ }`;
643
+
644
+ const updated = ecosystemContent.replace(
645
+ /apps: \[([^\]]*)\]/,
646
+ (match, content) => {
647
+ if (content.trim() === '' || content.includes('// Add your bot')) {
648
+ return `apps: [${newApp}\n ]`;
649
+ }
650
+ return `apps: [${content}${newApp}\n ]`;
651
+ }
652
+ );
653
+
654
+ writeFileSync(ecosystemFile, updated);
655
+ logSuccess(`Added to ecosystem.config.cjs`);
656
+ }
657
+
658
+ log('\n✨ Bot added!\n', colors.green + colors.bright);
659
+ log('To start all bots:', colors.cyan);
660
+ log(' pm2 start ' + ecosystemFile);
661
+ log('\nTo start just this bot:');
662
+ log(' pm2 start ' + ecosystemFile + ' --only bot-' + botName);
663
+ }
664
+
665
+ // Help
666
+ function showHelp() {
667
+ showBanner();
668
+ log(`${colors.cyan}Usage:${colors.reset}
669
+ agent-bridge <command>
670
+
671
+ ${colors.cyan}Single-Bot Commands:${colors.reset}
672
+ setup Interactive setup wizard
673
+ start Start the bridge
674
+ stop Stop the bridge
675
+ restart Restart the bridge
676
+ status Show status
677
+ logs View logs (live)
678
+ update Update to latest version
679
+ config Show config location
680
+
681
+ ${colors.cyan}Multi-Bot Commands:${colors.reset}
682
+ init Initialize multi-bot management directory
683
+ add-bot Add a new bot instance
684
+
685
+ ${colors.cyan}General:${colors.reset}
686
+ help Show this help
687
+
688
+ ${colors.cyan}Examples:${colors.reset}
689
+ agent-bridge setup ${colors.dim}# First time setup${colors.reset}
690
+ agent-bridge init ${colors.dim}# Initialize ~/bots directory${colors.reset}
691
+ agent-bridge add-bot myproject ${colors.dim}# Add a new bot${colors.reset}
692
+ pm2 start ~/bots/ecosystem.config.cjs ${colors.dim}# Start all bots${colors.reset}
693
+
694
+ ${colors.cyan}Documentation:${colors.reset}
695
+ https://github.com/YOUR_USERNAME/AgentBridge
696
+ `);
697
+ }
698
+
699
+ // Main
700
+ const command = process.argv[2];
701
+
702
+ switch (command) {
703
+ case 'setup':
704
+ setup();
705
+ break;
706
+ case 'start':
707
+ start();
708
+ break;
709
+ case 'stop':
710
+ stop();
711
+ break;
712
+ case 'restart':
713
+ restart();
714
+ break;
715
+ case 'status':
716
+ status();
717
+ break;
718
+ case 'logs':
719
+ logs();
720
+ break;
721
+ case 'update':
722
+ update();
723
+ break;
724
+ case 'config':
725
+ showConfig();
726
+ break;
727
+ case 'init':
728
+ initBotsDir();
729
+ break;
730
+ case 'add-bot':
731
+ addBot();
732
+ break;
733
+ case 'help':
734
+ case '--help':
735
+ case '-h':
736
+ case undefined:
737
+ showHelp();
738
+ break;
739
+ default:
740
+ logError(`Unknown command: ${command}`);
741
+ showHelp();
742
+ process.exit(1);
743
+ }