vibe-collab 0.8.0 → 0.8.2

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 CHANGED
@@ -86,16 +86,31 @@ vibe start
86
86
 
87
87
  ### `vibe connect --ai` 지원 도구
88
88
 
89
- ```bash
90
- vibe connect --ai claude # Claude Code → .claude/settings.json
91
- vibe connect --ai cursor # Cursor → .cursor/mcp.json
92
- vibe connect --ai vscode # VS Code → .vscode/mcp.json
93
- vibe connect --ai windsurf # Windsurf → .windsurf/mcp.json
94
- vibe connect --ai gemini # Gemini CLI → .gemini/settings.json
95
- vibe connect --ai kiro # Kiro → .kiro/mcp.json
96
- vibe connect --ai copilot # Copilot .vscode/mcp.json
97
- vibe connect --ai all # 전체 설정
98
- ```
89
+ 각 도구별로 올바른 설정 파일 경로와 형식을 자동으로 적용합니다.
90
+
91
+ | 명령어 | 도구 | 설정 파일 |
92
+ |--------|------|-----------|
93
+ | `vibe connect --ai claude` | Claude Code | `.claude/settings.json` |
94
+ | `vibe connect --ai cursor` | Cursor | `.cursor/mcp.json` |
95
+ | `vibe connect --ai vscode` | VS Code | `.vscode/mcp.json` |
96
+ | `vibe connect --ai copilot` | Copilot | `.vscode/mcp.json` |
97
+ | `vibe connect --ai kiro` | Kiro | `.kiro/settings/mcp.json` |
98
+ | `vibe connect --ai roocode` | Roo Code | `.roo/mcp.json` |
99
+ | `vibe connect --ai trae` | Trae | `.trae/mcp.json` (배열 형식) |
100
+ | `vibe connect --ai continue` | Continue | `.continue/mcpServers/mcp.json` |
101
+ | `vibe connect --ai opencode` | OpenCode | `.opencode/config.toml` |
102
+ | `vibe connect --ai codex` | Codex CLI | `.codex/config.toml` |
103
+ | `vibe connect --ai qoder` | Qoder | `.qoder/mcp.json` |
104
+ | `vibe connect --ai codebuddy` | CodeBuddy | `.codebuddy/mcp.json` |
105
+ | `vibe connect --ai droid` | Droid | `.droid/mcp.json` |
106
+ | `vibe connect --ai windsurf` | Windsurf | `~/.codeium/windsurf/mcp_config.json` (전역) |
107
+ | `vibe connect --ai gemini` | Gemini CLI | `~/.gemini/settings.json` (전역, 배열 형식) |
108
+ | `vibe connect --ai antigravity` | Antigravity | `antigravity mcp add` 자동 실행 |
109
+ | `vibe connect --ai all` | 위 전체 설정 | — |
110
+
111
+ > **Antigravity**: 파일 기반 MCP 설정을 지원하지 않으므로 `antigravity mcp add vibe-orchestrator -- npx vibe-collab serve` 명령어를 자동으로 실행합니다.
112
+ >
113
+ > **Windsurf / Gemini CLI**: 전역 설정 파일에 기록됩니다 (프로젝트 디렉토리 불필요).
99
114
 
100
115
  ---
101
116
 
@@ -1,6 +1,9 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import os from 'os';
4
+ import { spawnSync } from 'child_process';
3
5
  import chalk from 'chalk';
6
+ const SERVER_KEY = 'vibe-orchestrator';
4
7
  const STANDARD_MCP_ENTRY = {
5
8
  command: 'npx',
6
9
  args: ['vibe-collab', 'serve'],
@@ -11,25 +14,74 @@ const VSCODE_MCP_ENTRY = {
11
14
  command: 'npx',
12
15
  args: ['vibe-collab', 'serve'],
13
16
  };
14
- function getToolDefs(cwd) {
17
+ // Trae / Gemini CLI 공통 배열 엔트리
18
+ const ARRAY_MCP_ENTRY = {
19
+ name: SERVER_KEY,
20
+ command: 'npx',
21
+ args: ['vibe-collab', 'serve'],
22
+ };
23
+ function getToolDefs() {
15
24
  return {
16
- claude: { label: 'Claude Code', configDir: '.claude', configFile: 'settings.json', format: 'standard' },
17
- cursor: { label: 'Cursor', configDir: '.cursor', configFile: 'mcp.json', format: 'standard' },
18
- vscode: { label: 'VS Code', configDir: '.vscode', configFile: 'mcp.json', format: 'vscode' },
19
- copilot: { label: 'Copilot', configDir: '.vscode', configFile: 'mcp.json', format: 'vscode' },
20
- windsurf: { label: 'Windsurf', configDir: '.windsurf', configFile: 'mcp.json', format: 'standard' },
21
- gemini: { label: 'Gemini CLI', configDir: '.gemini', configFile: 'settings.json', format: 'standard' },
22
- kiro: { label: 'Kiro', configDir: '.kiro', configFile: 'mcp.json', format: 'standard' },
25
+ // ── 프로젝트 로컬 (JSON) ───────────────────────────────────────────────────
26
+ claude: { label: 'Claude Code', configDir: '.claude', configFile: 'settings.json', format: 'standard', scope: 'project' },
27
+ cursor: { label: 'Cursor', configDir: '.cursor', configFile: 'mcp.json', format: 'standard', scope: 'project' },
28
+ vscode: { label: 'VS Code', configDir: '.vscode', configFile: 'mcp.json', format: 'vscode', scope: 'project' },
29
+ copilot: { label: 'Copilot', configDir: '.vscode', configFile: 'mcp.json', format: 'vscode', scope: 'project' },
30
+ kiro: { label: 'Kiro', configDir: '.kiro/settings', configFile: 'mcp.json', format: 'standard', scope: 'project' },
31
+ roocode: { label: 'Roo Code', configDir: '.roo', configFile: 'mcp.json', format: 'standard', scope: 'project' },
32
+ trae: { label: 'Trae', configDir: '.trae', configFile: 'mcp.json', format: 'array', scope: 'project' },
33
+ continue: { label: 'Continue', configDir: '.continue/mcpServers', configFile: 'mcp.json', format: 'standard', scope: 'project' },
34
+ qoder: { label: 'Qoder', configDir: '.qoder', configFile: 'mcp.json', format: 'standard', scope: 'project' },
35
+ codebuddy: { label: 'CodeBuddy', configDir: '.codebuddy', configFile: 'mcp.json', format: 'standard', scope: 'project' },
36
+ droid: { label: 'Droid', configDir: '.droid', configFile: 'mcp.json', format: 'standard', scope: 'project' },
37
+ // ── 프로젝트 로컬 (TOML) ──────────────────────────────────────────────────
38
+ opencode: { label: 'OpenCode', configDir: '.opencode', configFile: 'config.toml', format: 'toml-opencode', scope: 'project' },
39
+ codex: { label: 'Codex CLI', configDir: '.codex', configFile: 'config.toml', format: 'toml-codex', scope: 'project' },
40
+ // ── 전역 (홈 디렉토리) ────────────────────────────────────────────────────
41
+ windsurf: { label: 'Windsurf', configDir: '.codeium/windsurf', configFile: 'mcp_config.json', format: 'standard', scope: 'global' },
42
+ gemini: { label: 'Gemini CLI', configDir: '.gemini', configFile: 'settings.json', format: 'array', scope: 'global' },
43
+ // ── CLI 명령어 실행 ───────────────────────────────────────────────────────
44
+ antigravity: { label: 'Antigravity', configDir: 'antigravity', configFile: '', format: 'cli', scope: 'cli' },
23
45
  };
24
- void cwd;
25
46
  }
26
- function hasDir(dir) {
27
- return fs.existsSync(dir);
47
+ function resolveDir(cwd, def) {
48
+ if (def.scope === 'cli')
49
+ return ''; // CLI 도구는 디렉토리 불필요
50
+ const base = def.scope === 'global' ? os.homedir() : cwd;
51
+ return path.join(base, def.configDir);
52
+ }
53
+ function isCommandAvailable(cmd) {
54
+ const check = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], { stdio: 'pipe' });
55
+ return check.status === 0;
56
+ }
57
+ function configureViaCli(def) {
58
+ const binary = def.configDir; // configDir에 바이너리명 저장
59
+ const result = spawnSync(binary, ['mcp', 'add', SERVER_KEY, '--', 'npx', 'vibe-collab', 'serve'], {
60
+ encoding: 'utf-8',
61
+ shell: true,
62
+ stdio: 'pipe',
63
+ });
64
+ const output = ((result.stdout ?? '') + (result.stderr ?? '')).toLowerCase();
65
+ if (result.status === 0) {
66
+ // 이미 존재한다는 메시지가 포함된 경우 (도구마다 다를 수 있음)
67
+ if (output.includes('already') || output.includes('exist') || output.includes('duplicate')) {
68
+ return { tool: def.label, configPath: '', status: 'already' };
69
+ }
70
+ return { tool: def.label, configPath: '', status: 'added' };
71
+ }
72
+ // 종료 코드 비정상
73
+ if (output.includes('already') || output.includes('exist') || output.includes('duplicate')) {
74
+ return { tool: def.label, configPath: '', status: 'already' };
75
+ }
76
+ if (result.error?.message?.includes('ENOENT') || output.includes('not found')) {
77
+ return { tool: def.label, configPath: '', status: 'skipped' }; // 미설치
78
+ }
79
+ return { tool: def.label, configPath: '', status: 'error', errorMsg: (result.stderr ?? '').trim() };
28
80
  }
81
+ // ── JSON 헬퍼 ────────────────────────────────────────────────────────────────
29
82
  function readJsonSafe(filePath) {
30
83
  try {
31
- const raw = fs.readFileSync(filePath, 'utf-8');
32
- return JSON.parse(raw);
84
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
33
85
  }
34
86
  catch {
35
87
  return {};
@@ -37,72 +89,112 @@ function readJsonSafe(filePath) {
37
89
  }
38
90
  function writeJson(filePath, data) {
39
91
  const dir = path.dirname(filePath);
40
- if (!fs.existsSync(dir)) {
92
+ if (!fs.existsSync(dir))
41
93
  fs.mkdirSync(dir, { recursive: true });
42
- }
43
94
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
44
95
  }
45
- function isAlreadyConfigured(existing, serverKey) {
46
- const mcpServers = existing['mcpServers'];
47
- const servers = existing['servers'];
48
- return !!(mcpServers?.[serverKey] || servers?.[serverKey]);
96
+ function isConfiguredJson(existing, format) {
97
+ if (format === 'vscode') {
98
+ return !!(existing['servers']?.[SERVER_KEY]);
99
+ }
100
+ if (format === 'array') {
101
+ const arr = existing['mcpServers'];
102
+ return !!(arr?.some(s => s.name === SERVER_KEY));
103
+ }
104
+ return !!(existing['mcpServers']?.[SERVER_KEY]);
49
105
  }
50
- function addStandardEntry(existing, serverKey) {
106
+ function addEntryJson(existing, format) {
107
+ if (format === 'vscode') {
108
+ const servers = existing['servers'] ?? {};
109
+ return { ...existing, servers: { ...servers, [SERVER_KEY]: VSCODE_MCP_ENTRY } };
110
+ }
111
+ if (format === 'array') {
112
+ const arr = existing['mcpServers'] ?? [];
113
+ return { ...existing, mcpServers: [...arr, ARRAY_MCP_ENTRY] };
114
+ }
51
115
  const mcpServers = existing['mcpServers'] ?? {};
52
- return {
53
- ...existing,
54
- mcpServers: {
55
- ...mcpServers,
56
- [serverKey]: STANDARD_MCP_ENTRY,
57
- },
58
- };
116
+ return { ...existing, mcpServers: { ...mcpServers, [SERVER_KEY]: STANDARD_MCP_ENTRY } };
59
117
  }
60
- function addVSCodeEntry(existing, serverKey) {
61
- const servers = existing['servers'] ?? {};
62
- return {
63
- ...existing,
64
- servers: {
65
- ...servers,
66
- [serverKey]: VSCODE_MCP_ENTRY,
67
- },
68
- };
118
+ // ── TOML 헬퍼 ────────────────────────────────────────────────────────────────
119
+ function readFileSafe(filePath) {
120
+ try {
121
+ return fs.readFileSync(filePath, 'utf-8');
122
+ }
123
+ catch {
124
+ return '';
125
+ }
69
126
  }
127
+ function appendToFile(filePath, content) {
128
+ const dir = path.dirname(filePath);
129
+ if (!fs.existsSync(dir))
130
+ fs.mkdirSync(dir, { recursive: true });
131
+ fs.appendFileSync(filePath, content, 'utf-8');
132
+ }
133
+ // ── 도구 설정 ────────────────────────────────────────────────────────────────
70
134
  function configureTool(cwd, def, force) {
71
- const dir = path.join(cwd, def.configDir);
72
- if (!force && !hasDir(dir)) {
135
+ // CLI 방식 도구 (Antigravity )
136
+ if (def.scope === 'cli') {
137
+ if (!isCommandAvailable(def.configDir)) {
138
+ return { tool: def.label, configPath: '', status: 'skipped' };
139
+ }
140
+ return configureViaCli(def);
141
+ }
142
+ // 자동 감지 모드: 디렉토리 존재 여부만 확인 (--ai 지정 시 force=true → 무조건 설정)
143
+ if (!force && !fs.existsSync(resolveDir(cwd, def))) {
73
144
  return { tool: def.label, configPath: '', status: 'skipped' };
74
145
  }
146
+ const dir = resolveDir(cwd, def);
75
147
  const configPath = path.join(dir, def.configFile);
148
+ // OpenCode: [mcp.vibe-orchestrator] 섹션 형식
149
+ if (def.format === 'toml-opencode') {
150
+ const content = readFileSafe(configPath);
151
+ if (content.includes(`[mcp.${SERVER_KEY}]`)) {
152
+ return { tool: def.label, configPath, status: 'already' };
153
+ }
154
+ const entry = `\n[mcp.${SERVER_KEY}]\ntype = "stdio"\ncommand = "npx"\nargs = ["vibe-collab", "serve"]\n`;
155
+ appendToFile(configPath, entry);
156
+ return { tool: def.label, configPath, status: 'added' };
157
+ }
158
+ // Codex CLI: [[mcp]] 배열 항목 형식
159
+ if (def.format === 'toml-codex') {
160
+ const content = readFileSafe(configPath);
161
+ const alreadyExists = content.split('[[mcp]]').some(b => b.includes(`name = "${SERVER_KEY}"`));
162
+ if (alreadyExists) {
163
+ return { tool: def.label, configPath, status: 'already' };
164
+ }
165
+ const entry = `\n[[mcp]]\nname = "${SERVER_KEY}"\ncommand = "npx"\nargs = ["vibe-collab", "serve"]\n`;
166
+ appendToFile(configPath, entry);
167
+ return { tool: def.label, configPath, status: 'added' };
168
+ }
169
+ // JSON 기반 형식
76
170
  const existing = readJsonSafe(configPath);
77
- if (isAlreadyConfigured(existing, 'vibe-orchestrator')) {
171
+ if (isConfiguredJson(existing, def.format)) {
78
172
  return { tool: def.label, configPath, status: 'already' };
79
173
  }
80
- const updated = def.format === 'vscode'
81
- ? addVSCodeEntry(existing, 'vibe-orchestrator')
82
- : addStandardEntry(existing, 'vibe-orchestrator');
83
- writeJson(configPath, updated);
174
+ writeJson(configPath, addEntryJson(existing, def.format));
84
175
  return { tool: def.label, configPath, status: 'added' };
85
176
  }
177
+ // ── 메인 커맨드 ──────────────────────────────────────────────────────────────
86
178
  export async function connectCommand(cwd, aiFlag) {
87
- const toolDefs = getToolDefs(cwd);
179
+ const toolDefs = getToolDefs();
180
+ const key = aiFlag?.toLowerCase();
88
181
  let targets;
89
- if (aiFlag) {
90
- const key = aiFlag.toLowerCase();
182
+ if (key) {
91
183
  if (key === 'all') {
92
184
  targets = Object.values(toolDefs).map(def => ({ def, force: true }));
185
+ console.log(chalk.cyan('\n🔌 전체 AI 도구 MCP 설정 중...\n'));
93
186
  }
94
187
  else if (toolDefs[key]) {
95
188
  targets = [{ def: toolDefs[key], force: true }];
189
+ console.log(chalk.cyan(`\n🔌 ${toolDefs[key].label} MCP 설정 중...\n`));
96
190
  }
97
191
  else {
98
192
  console.log(chalk.red(`알 수 없는 AI 도구: ${aiFlag}`));
99
193
  console.log(chalk.gray(`지원 목록: ${Object.keys(toolDefs).join(', ')}, all`));
100
194
  return;
101
195
  }
102
- console.log(chalk.cyan(`\n🔌 ${aiFlag === 'all' ? '전체' : toolDefs[aiFlag.toLowerCase()]?.label} MCP 설정 중...\n`));
103
196
  }
104
197
  else {
105
- // 자동 감지: 프로젝트 디렉토리가 있는 것만
106
198
  targets = Object.values(toolDefs).map(def => ({ def, force: false }));
107
199
  console.log(chalk.cyan('\n🔌 AI 도구 자동 감지 중...\n'));
108
200
  }
@@ -113,13 +205,19 @@ export async function connectCommand(cwd, aiFlag) {
113
205
  for (const result of results) {
114
206
  if (result.status === 'added') {
115
207
  console.log(chalk.green(` ✅ ${result.tool} — 설정 완료`));
116
- console.log(chalk.gray(` ${result.configPath}`));
208
+ if (result.configPath)
209
+ console.log(chalk.gray(` ${result.configPath}`));
117
210
  addedCount++;
118
211
  }
119
212
  else if (result.status === 'already') {
120
213
  console.log(chalk.yellow(` ☑️ ${result.tool} — 이미 설정됨`));
121
214
  alreadyCount++;
122
215
  }
216
+ else if (result.status === 'error') {
217
+ console.log(chalk.red(` ❌ ${result.tool} — 설정 실패`));
218
+ if (result.errorMsg)
219
+ console.log(chalk.gray(` ${result.errorMsg}`));
220
+ }
123
221
  else {
124
222
  console.log(chalk.gray(` ⬜ ${result.tool} — 미감지`));
125
223
  skippedCount++;
@@ -127,7 +225,7 @@ export async function connectCommand(cwd, aiFlag) {
127
225
  }
128
226
  console.log('');
129
227
  if (addedCount === 0 && alreadyCount === 0) {
130
- console.log(chalk.yellow('감지된 AI 도구가 없습니다.\nCursor, VS Code, Claude Code 중 하나를 설치하거나\n해당 도구의 프로젝트 디렉터리(.cursor, .vscode, .claude)를 먼저 만들어주세요.'));
228
+ console.log(chalk.yellow('감지된 AI 도구가 없습니다.\n--ai 플래그로 수동 설정하거나 해당 도구를 먼저 설치하세요.'));
131
229
  }
132
230
  else {
133
231
  const parts = [];
@@ -140,16 +238,29 @@ export async function connectCommand(cwd, aiFlag) {
140
238
  console.log(chalk.green(`✨ ${parts.join(', ')}`));
141
239
  console.log(chalk.gray(' AI 도구를 재시작하면 vibe-orchestrator MCP가 활성화됩니다.\n'));
142
240
  }
143
- console.log(chalk.cyan('─── 수동 설정 (다른 MCP 지원 도구에 직접 추가) ───\n'));
144
- console.log(chalk.white('Claude Code / Cursor / 기타:'));
145
- console.log(chalk.gray(JSON.stringify({
146
- mcpServers: { 'vibe-orchestrator': STANDARD_MCP_ENTRY },
147
- }, null, 2)));
241
+ // ── 수동 설정 가이드 ───────────────────────────────────────────────────────
242
+ console.log(chalk.cyan('─── 수동 설정 가이드 ───\n'));
243
+ console.log(chalk.white('Claude Code / Cursor / Roo Code / Kiro / Continue / Qoder / CodeBuddy / Droid:'));
244
+ console.log(chalk.gray(JSON.stringify({ mcpServers: { [SERVER_KEY]: STANDARD_MCP_ENTRY } }, null, 2)));
245
+ console.log('');
246
+ console.log(chalk.white('VS Code / Copilot (.vscode/mcp.json):'));
247
+ console.log(chalk.gray(JSON.stringify({ servers: { [SERVER_KEY]: VSCODE_MCP_ENTRY } }, null, 2)));
248
+ console.log('');
249
+ console.log(chalk.white('Trae (.trae/mcp.json) / Gemini CLI (~/.gemini/settings.json):'));
250
+ console.log(chalk.gray(JSON.stringify({ mcpServers: [ARRAY_MCP_ENTRY] }, null, 2)));
251
+ console.log('');
252
+ console.log(chalk.white('OpenCode (.opencode/config.toml):'));
253
+ console.log(chalk.gray(`[mcp.${SERVER_KEY}]\ntype = "stdio"\ncommand = "npx"\nargs = ["vibe-collab", "serve"]`));
254
+ console.log('');
255
+ console.log(chalk.white('Codex CLI (.codex/config.toml):'));
256
+ console.log(chalk.gray(`[[mcp]]\nname = "${SERVER_KEY}"\ncommand = "npx"\nargs = ["vibe-collab", "serve"]`));
257
+ console.log('');
258
+ console.log(chalk.white('Antigravity (파일 기반 MCP 미지원 — CLI 명령어 사용):'));
259
+ console.log(chalk.gray(`antigravity mcp add ${SERVER_KEY} -- npx vibe-collab serve`));
260
+ console.log(chalk.gray('(vibe connect --ai antigravity 실행 시 자동으로 위 명령어를 실행합니다)'));
148
261
  console.log('');
149
- console.log(chalk.white('VS Code:'));
150
- console.log(chalk.gray(JSON.stringify({
151
- servers: { 'vibe-orchestrator': VSCODE_MCP_ENTRY },
152
- }, null, 2)));
262
+ console.log(chalk.white('Windsurf (~/.codeium/windsurf/mcp_config.json):'));
263
+ console.log(chalk.gray(JSON.stringify({ mcpServers: { [SERVER_KEY]: STANDARD_MCP_ENTRY } }, null, 2)));
153
264
  console.log('');
154
265
  }
155
266
  //# sourceMappingURL=connect.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-collab",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "누가 어떤 AI를 써도, 항상 한 팀처럼 작동하는 바이브 코딩 협업 도구",
5
5
  "type": "module",
6
6
  "bin": {