vibeteam 0.2.2 → 0.2.4

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 CHANGED
@@ -384,7 +384,71 @@ if (args[0] === 'setup') {
384
384
  }
385
385
 
386
386
  // ==========================================================================
387
- // Step 5: Verify and report
387
+ // Step 5: MCP Server Installation
388
+ // ==========================================================================
389
+
390
+ let mcpInstalled = false
391
+
392
+ // Check if MCP is already configured
393
+ const mcpAlreadyConfigured = (() => {
394
+ try {
395
+ const s = JSON.parse(readFileSync(settingsPath, 'utf-8'))
396
+ return !!s.mcpServers?.vibeteam
397
+ } catch { return false }
398
+ })()
399
+
400
+ // Also check settings.local.json
401
+ const mcpInLocal = (() => {
402
+ try {
403
+ const localPath = settingsPath.replace('settings.json', 'settings.local.json')
404
+ if (!existsSync(localPath)) return false
405
+ const s = JSON.parse(readFileSync(localPath, 'utf-8'))
406
+ return !!s.mcpServers?.vibeteam
407
+ } catch { return false }
408
+ })()
409
+
410
+ if (mcpAlreadyConfigured || mcpInLocal) {
411
+ console.log('\nMCP server: already configured')
412
+ mcpInstalled = true
413
+ } else {
414
+ // Check if claude CLI is available
415
+ let hasClaudeCli = false
416
+ try {
417
+ execSync('which claude', { stdio: 'ignore' })
418
+ hasClaudeCli = true
419
+ } catch {}
420
+
421
+ if (hasClaudeCli) {
422
+ console.log('\n VibeTeam MCP server lets Claude Code control your agents remotely.')
423
+ const mcpAnswer = await askQuestion(' Install MCP integration for Claude Code? (y/n): ')
424
+
425
+ if (mcpAnswer.toLowerCase() === 'y' || mcpAnswer.toLowerCase() === 'yes') {
426
+ // Determine install method: global npm binary vs local dev path
427
+ let mcpCommand = 'vibeteam-mcp'
428
+ try {
429
+ execSync('which vibeteam-mcp', { stdio: 'ignore' })
430
+ } catch {
431
+ // vibeteam-mcp not in PATH — use absolute path to mcp/server.js
432
+ mcpCommand = resolve(ROOT, 'mcp/server.js')
433
+ }
434
+
435
+ try {
436
+ execSync(
437
+ `CLAUDECODE= claude mcp add vibeteam --scope user -- ${mcpCommand}`,
438
+ { stdio: 'pipe', timeout: 10000 }
439
+ )
440
+ console.log(' ✓ MCP server installed for Claude Code')
441
+ mcpInstalled = true
442
+ } catch (e) {
443
+ console.log(` ✗ Failed to install MCP server: ${e.message}`)
444
+ console.log(` You can install manually: claude mcp add vibeteam -- ${mcpCommand}`)
445
+ }
446
+ }
447
+ }
448
+ }
449
+
450
+ // ==========================================================================
451
+ // Step 6: Verify and report
388
452
  // ==========================================================================
389
453
 
390
454
  console.log('\n' + '='.repeat(50))
@@ -401,6 +465,10 @@ if (args[0] === 'setup') {
401
465
  console.log(' - UserPromptSubmit')
402
466
  console.log(' - Notification')
403
467
 
468
+ if (mcpInstalled) {
469
+ console.log('\nMCP server: ✓ configured')
470
+ }
471
+
404
472
  // Check dependencies
405
473
  let hasWarnings = false
406
474
 
@@ -866,7 +934,221 @@ console.log(`
866
934
  ╰─────────────────────────────────────╯
867
935
  `)
868
936
 
869
- // Run health checks
937
+ // ============================================================================
938
+ // Critical Dependency Check (blocks startup if missing)
939
+ // ============================================================================
940
+
941
+ function detectPackageManager() {
942
+ const platform = process.platform
943
+
944
+ if (platform === 'darwin') {
945
+ // macOS - check for brew
946
+ try {
947
+ execSync('which brew', { stdio: 'ignore' })
948
+ return { name: 'brew', install: (pkg) => `brew install ${pkg}` }
949
+ } catch {
950
+ return null
951
+ }
952
+ } else if (platform === 'linux') {
953
+ // Linux - check for apt, then yum/dnf
954
+ try {
955
+ execSync('which apt', { stdio: 'ignore' })
956
+ return { name: 'apt', install: (pkg) => `sudo apt install -y ${pkg}` }
957
+ } catch {
958
+ try {
959
+ execSync('which dnf', { stdio: 'ignore' })
960
+ return { name: 'dnf', install: (pkg) => `sudo dnf install -y ${pkg}` }
961
+ } catch {
962
+ try {
963
+ execSync('which yum', { stdio: 'ignore' })
964
+ return { name: 'yum', install: (pkg) => `sudo yum install -y ${pkg}` }
965
+ } catch {
966
+ return null
967
+ }
968
+ }
969
+ }
970
+ }
971
+ return null
972
+ }
973
+
974
+ async function installDependency(pkgManager, depName) {
975
+ const cmd = pkgManager.install(depName)
976
+ console.log(` Running: ${cmd}`)
977
+ console.log()
978
+
979
+ try {
980
+ execSync(cmd, { stdio: 'inherit' })
981
+ return true
982
+ } catch (e) {
983
+ console.log(` ✗ Failed to install ${depName}`)
984
+ return false
985
+ }
986
+ }
987
+
988
+ async function checkRequiredDependencies() {
989
+ const missing = []
990
+
991
+ // Check tmux (REQUIRED - agents won't spawn without it)
992
+ if (!checkTmux()) {
993
+ missing.push({
994
+ name: 'tmux',
995
+ reason: 'Required for running Claude agents in background sessions',
996
+ })
997
+ }
998
+
999
+ // Check jq (REQUIRED - hooks won't work without it)
1000
+ if (!checkJq()) {
1001
+ missing.push({
1002
+ name: 'jq',
1003
+ reason: 'Required for Claude Code hooks to capture events',
1004
+ })
1005
+ }
1006
+
1007
+ if (missing.length === 0) {
1008
+ return true // All good
1009
+ }
1010
+
1011
+ // Show what's missing
1012
+ console.log(' ╭─────────────────────────────────────╮')
1013
+ console.log(' │ Missing Required Dependencies │')
1014
+ console.log(' ╰─────────────────────────────────────╯')
1015
+ console.log()
1016
+
1017
+ for (const dep of missing) {
1018
+ console.log(` ✗ ${dep.name} - ${dep.reason}`)
1019
+ }
1020
+ console.log()
1021
+
1022
+ // Detect package manager
1023
+ const pkgManager = detectPackageManager()
1024
+
1025
+ if (pkgManager) {
1026
+ const answer = await askQuestion(` Install missing dependencies using ${pkgManager.name}? (y/n): `)
1027
+
1028
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
1029
+ console.log()
1030
+
1031
+ let allInstalled = true
1032
+ for (const dep of missing) {
1033
+ const success = await installDependency(pkgManager, dep.name)
1034
+ if (!success) {
1035
+ allInstalled = false
1036
+ }
1037
+ }
1038
+
1039
+ if (allInstalled) {
1040
+ console.log()
1041
+ console.log(' ✓ All dependencies installed successfully!')
1042
+ console.log()
1043
+ return true
1044
+ } else {
1045
+ console.log()
1046
+ console.log(' Some dependencies failed to install. Please install them manually.')
1047
+ process.exit(1)
1048
+ }
1049
+ }
1050
+ } else {
1051
+ // No package manager detected - show manual instructions
1052
+ console.log(' Could not detect package manager. Please install manually:')
1053
+ console.log()
1054
+ console.log(' macOS: brew install tmux jq')
1055
+ console.log(' Ubuntu: sudo apt install tmux jq')
1056
+ console.log(' Fedora: sudo dnf install tmux jq')
1057
+ console.log()
1058
+ }
1059
+
1060
+ // Ask if they want to continue anyway
1061
+ const continueAnswer = await askQuestion(' Continue without these dependencies? (y/n): ')
1062
+
1063
+ if (continueAnswer.toLowerCase() === 'y' || continueAnswer.toLowerCase() === 'yes') {
1064
+ console.log()
1065
+ console.log(' ⚠ Continuing without all dependencies - some features may not work!')
1066
+ console.log()
1067
+ return true
1068
+ }
1069
+
1070
+ console.log()
1071
+ console.log(' Install the missing dependencies and run vibeteam again.')
1072
+ console.log()
1073
+ process.exit(1)
1074
+ }
1075
+
1076
+ // Check dependencies before starting
1077
+ await checkRequiredDependencies()
1078
+
1079
+ // ============================================================================
1080
+ // First-run MCP install offer
1081
+ // ============================================================================
1082
+
1083
+ async function offerMcpInstall() {
1084
+ // Check if we already offered (don't nag on every start)
1085
+ const flagFile = join(homedir(), '.vibeteam', '.mcp-offered')
1086
+ if (existsSync(flagFile)) return
1087
+
1088
+ // Check if MCP is already configured in settings or settings.local
1089
+ const settingsPaths = [
1090
+ join(homedir(), '.claude', 'settings.json'),
1091
+ join(homedir(), '.claude', 'settings.local.json'),
1092
+ join(homedir(), '.config', 'claude', 'settings.json'),
1093
+ join(homedir(), '.config', 'claude', 'settings.local.json'),
1094
+ ]
1095
+
1096
+ for (const sp of settingsPaths) {
1097
+ try {
1098
+ if (!existsSync(sp)) continue
1099
+ const s = JSON.parse(readFileSync(sp, 'utf-8'))
1100
+ if (s.mcpServers?.vibeteam) return // Already configured
1101
+ } catch {}
1102
+ }
1103
+
1104
+ // Check if claude CLI is available
1105
+ try {
1106
+ execSync('which claude', { stdio: 'ignore' })
1107
+ } catch {
1108
+ return // No claude CLI, skip
1109
+ }
1110
+
1111
+ console.log(' VibeTeam MCP server lets Claude Code control your agents remotely.')
1112
+ const answer = await askQuestion(' Install MCP integration for Claude Code? (y/n): ')
1113
+
1114
+ // Ensure .vibeteam dir exists for the flag file
1115
+ const vibeteamDir = join(homedir(), '.vibeteam')
1116
+ if (!existsSync(vibeteamDir)) {
1117
+ mkdirSync(vibeteamDir, { recursive: true })
1118
+ }
1119
+
1120
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
1121
+ let mcpCommand = 'vibeteam-mcp'
1122
+ try {
1123
+ execSync('which vibeteam-mcp', { stdio: 'ignore' })
1124
+ } catch {
1125
+ mcpCommand = resolve(ROOT, 'mcp/server.js')
1126
+ }
1127
+
1128
+ try {
1129
+ execSync(
1130
+ `CLAUDECODE= claude mcp add vibeteam --scope user -- ${mcpCommand}`,
1131
+ { stdio: 'pipe', timeout: 10000 }
1132
+ )
1133
+ console.log(' ✓ MCP server installed for Claude Code\n')
1134
+ } catch (e) {
1135
+ console.log(` ✗ Failed to install MCP: ${e.message}`)
1136
+ console.log(` Install manually: claude mcp add vibeteam -- ${mcpCommand}\n`)
1137
+ }
1138
+ } else {
1139
+ console.log()
1140
+ }
1141
+
1142
+ // Write flag so we don't ask again
1143
+ const { writeFileSync } = await import('fs')
1144
+ try {
1145
+ writeFileSync(flagFile, new Date().toISOString())
1146
+ } catch {}
1147
+ }
1148
+
1149
+ await offerMcpInstall()
1150
+
1151
+ // Run health checks (warnings only)
870
1152
  printHealthCheck()
871
1153
 
872
1154
  console.log(`Starting server on port ${port}...`)
@@ -882,44 +1164,58 @@ console.log()
882
1164
  const compiledPath = resolve(ROOT, 'dist/server/server/index.js')
883
1165
  const sourcePath = resolve(ROOT, 'server/index.ts')
884
1166
 
885
- let server
886
- if (existsSync(compiledPath)) {
887
- // Use compiled JS (production/npm install)
888
- server = spawn('node', [compiledPath], {
889
- cwd: ROOT,
890
- env: {
891
- ...process.env,
892
- VIBETEAM_PORT: port,
893
- },
894
- stdio: 'inherit',
895
- })
896
- } else {
897
- // Fall back to tsx (development)
898
- console.log('(dev mode - using tsx)')
899
- server = spawn('npx', ['tsx', sourcePath], {
900
- cwd: ROOT,
901
- env: {
902
- ...process.env,
903
- VIBETEAM_PORT: port,
904
- },
905
- stdio: 'inherit',
1167
+ /** Exit code 75 = restart requested (e.g. via POST /api/restart) */
1168
+ const RESTART_EXIT_CODE = 75
1169
+
1170
+ function spawnServer() {
1171
+ let server
1172
+ if (existsSync(compiledPath)) {
1173
+ // Use compiled JS (production/npm install)
1174
+ server = spawn('node', [compiledPath], {
1175
+ cwd: ROOT,
1176
+ env: {
1177
+ ...process.env,
1178
+ VIBETEAM_PORT: port,
1179
+ },
1180
+ stdio: 'inherit',
1181
+ })
1182
+ } else {
1183
+ // Fall back to tsx (development)
1184
+ console.log('(dev mode - using tsx)')
1185
+ server = spawn('npx', ['tsx', sourcePath], {
1186
+ cwd: ROOT,
1187
+ env: {
1188
+ ...process.env,
1189
+ VIBETEAM_PORT: port,
1190
+ },
1191
+ stdio: 'inherit',
1192
+ })
1193
+ }
1194
+
1195
+ server.on('error', (err) => {
1196
+ console.error('Failed to start server:', err.message)
1197
+ process.exit(1)
906
1198
  })
907
- }
908
1199
 
909
- server.on('error', (err) => {
910
- console.error('Failed to start server:', err.message)
911
- process.exit(1)
912
- })
1200
+ server.on('close', (code) => {
1201
+ if (code === RESTART_EXIT_CODE) {
1202
+ console.log('\n Restarting server...\n')
1203
+ // Small delay to let port release
1204
+ setTimeout(() => spawnServer(), 1000)
1205
+ return
1206
+ }
1207
+ process.exit(code || 0)
1208
+ })
913
1209
 
914
- server.on('close', (code) => {
915
- process.exit(code || 0)
916
- })
1210
+ // Handle signals - forward to child
1211
+ const onSigInt = () => server.kill('SIGINT')
1212
+ const onSigTerm = () => server.kill('SIGTERM')
917
1213
 
918
- // Handle signals
919
- process.on('SIGINT', () => {
920
- server.kill('SIGINT')
921
- })
1214
+ // Remove old listeners to avoid stacking on restart
1215
+ process.removeAllListeners('SIGINT')
1216
+ process.removeAllListeners('SIGTERM')
1217
+ process.on('SIGINT', onSigInt)
1218
+ process.on('SIGTERM', onSigTerm)
1219
+ }
922
1220
 
923
- process.on('SIGTERM', () => {
924
- server.kill('SIGTERM')
925
- })
1221
+ spawnServer()