vibe-collab 0.8.6 → 0.8.7

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.
@@ -5,20 +5,23 @@ import chalk from 'chalk';
5
5
  const SERVER_KEY = 'vibe-orchestrator';
6
6
  // Windows에서 npx는 cmd /c 래퍼 없이 stdio MCP 연결이 실패할 수 있음
7
7
  const IS_WIN = process.platform === 'win32';
8
- function getStandardMcpEntry() {
8
+ function getStandardMcpEntry(cwdArg) {
9
+ const extra = cwdArg ? ['--cwd', cwdArg] : [];
9
10
  return IS_WIN
10
- ? { command: 'cmd', args: ['/c', 'npx', 'vibe-collab', 'serve'], env: {} }
11
- : { command: 'npx', args: ['vibe-collab', 'serve'], env: {} };
11
+ ? { command: 'cmd', args: ['/c', 'npx', 'vibe-collab', 'serve', ...extra], env: {} }
12
+ : { command: 'npx', args: ['vibe-collab', 'serve', ...extra], env: {} };
12
13
  }
13
- function getVscodeMcpEntry() {
14
+ function getVscodeMcpEntry(cwdArg) {
15
+ const extra = cwdArg ? ['--cwd', cwdArg] : [];
14
16
  return IS_WIN
15
- ? { type: 'stdio', command: 'cmd', args: ['/c', 'npx', 'vibe-collab', 'serve'] }
16
- : { type: 'stdio', command: 'npx', args: ['vibe-collab', 'serve'] };
17
+ ? { type: 'stdio', command: 'cmd', args: ['/c', 'npx', 'vibe-collab', 'serve', ...extra] }
18
+ : { type: 'stdio', command: 'npx', args: ['vibe-collab', 'serve', ...extra] };
17
19
  }
18
- function getArrayMcpEntry() {
20
+ function getArrayMcpEntry(cwdArg) {
21
+ const extra = cwdArg ? ['--cwd', cwdArg] : [];
19
22
  return IS_WIN
20
- ? { name: SERVER_KEY, command: 'cmd', args: ['/c', 'npx', 'vibe-collab', 'serve'] }
21
- : { name: SERVER_KEY, command: 'npx', args: ['vibe-collab', 'serve'] };
23
+ ? { name: SERVER_KEY, command: 'cmd', args: ['/c', 'npx', 'vibe-collab', 'serve', ...extra] }
24
+ : { name: SERVER_KEY, command: 'npx', args: ['vibe-collab', 'serve', ...extra] };
22
25
  }
23
26
  function getToolDefs() {
24
27
  return {
@@ -66,12 +69,12 @@ function writeJson(filePath, data) {
66
69
  function deepEqual(a, b) {
67
70
  return JSON.stringify(a) === JSON.stringify(b);
68
71
  }
69
- function checkConfigJson(existing, format) {
72
+ function checkConfigJson(existing, format, cwdArg) {
70
73
  if (format === 'vscode') {
71
74
  const entry = existing['servers']?.[SERVER_KEY];
72
75
  if (!entry)
73
76
  return 'missing';
74
- return deepEqual(entry, getVscodeMcpEntry()) ? 'match' : 'stale';
77
+ return deepEqual(entry, getVscodeMcpEntry(cwdArg)) ? 'match' : 'stale';
75
78
  }
76
79
  const raw = existing['mcpServers'];
77
80
  if (format === 'array') {
@@ -79,38 +82,38 @@ function checkConfigJson(existing, format) {
79
82
  const entry = raw.find((s) => s.name === SERVER_KEY);
80
83
  if (!entry)
81
84
  return 'missing';
82
- return deepEqual(entry, getArrayMcpEntry()) ? 'match' : 'stale';
85
+ return deepEqual(entry, getArrayMcpEntry(cwdArg)) ? 'match' : 'stale';
83
86
  }
84
87
  if (raw && typeof raw === 'object') {
85
88
  const entry = raw[SERVER_KEY];
86
89
  if (!entry)
87
90
  return 'missing';
88
- return deepEqual(entry, getStandardMcpEntry()) ? 'match' : 'stale';
91
+ return deepEqual(entry, getStandardMcpEntry(cwdArg)) ? 'match' : 'stale';
89
92
  }
90
93
  return 'missing';
91
94
  }
92
95
  const entry = raw?.[SERVER_KEY];
93
96
  if (!entry)
94
97
  return 'missing';
95
- return deepEqual(entry, getStandardMcpEntry()) ? 'match' : 'stale';
98
+ return deepEqual(entry, getStandardMcpEntry(cwdArg)) ? 'match' : 'stale';
96
99
  }
97
- function addEntryJson(existing, format) {
100
+ function addEntryJson(existing, format, cwdArg) {
98
101
  if (format === 'vscode') {
99
102
  const servers = existing['servers'] ?? {};
100
- return { ...existing, servers: { ...servers, [SERVER_KEY]: getVscodeMcpEntry() } };
103
+ return { ...existing, servers: { ...servers, [SERVER_KEY]: getVscodeMcpEntry(cwdArg) } };
101
104
  }
102
105
  if (format === 'array') {
103
106
  const raw = existing['mcpServers'];
104
107
  // 이미 객체 형식이면 객체 형식 유지 (다른 도구가 먼저 설정한 경우)
105
108
  if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
106
- return { ...existing, mcpServers: { ...raw, [SERVER_KEY]: getStandardMcpEntry() } };
109
+ return { ...existing, mcpServers: { ...raw, [SERVER_KEY]: getStandardMcpEntry(cwdArg) } };
107
110
  }
108
111
  // 배열에서 기존 항목 제거 후 최신 항목 추가
109
112
  const arr = (Array.isArray(raw) ? raw.filter((s) => s.name !== SERVER_KEY) : []);
110
- return { ...existing, mcpServers: [...arr, getArrayMcpEntry()] };
113
+ return { ...existing, mcpServers: [...arr, getArrayMcpEntry(cwdArg)] };
111
114
  }
112
115
  const mcpServers = existing['mcpServers'] ?? {};
113
- return { ...existing, mcpServers: { ...mcpServers, [SERVER_KEY]: getStandardMcpEntry() } };
116
+ return { ...existing, mcpServers: { ...mcpServers, [SERVER_KEY]: getStandardMcpEntry(cwdArg) } };
114
117
  }
115
118
  // ── TOML 헬퍼 ────────────────────────────────────────────────────────────────
116
119
  function readFileSafe(filePath) {
@@ -157,12 +160,14 @@ function configureTool(cwd, def, force) {
157
160
  return { tool: def.label, configPath, status: 'added' };
158
161
  }
159
162
  // JSON 기반 형식
163
+ // global 도구는 --cwd를 주입해 MCP 서버가 프로젝트를 찾을 수 있게 함
164
+ const cwdArg = def.scope === 'global' ? cwd : undefined;
160
165
  const existing = readJsonSafe(configPath);
161
- const state = checkConfigJson(existing, def.format);
166
+ const state = checkConfigJson(existing, def.format, cwdArg);
162
167
  if (state === 'match') {
163
168
  return { tool: def.label, configPath, status: 'already' };
164
169
  }
165
- writeJson(configPath, addEntryJson(existing, def.format));
170
+ writeJson(configPath, addEntryJson(existing, def.format, cwdArg));
166
171
  return { tool: def.label, configPath, status: state === 'stale' ? 'updated' : 'added' };
167
172
  }
168
173
  // ── 메인 커맨드 ──────────────────────────────────────────────────────────────
@@ -242,9 +247,9 @@ export async function connectCommand(cwd, aiFlag) {
242
247
  const targetKeys = key
243
248
  ? (key === 'all' ? Object.keys(toolDefs) : [key])
244
249
  : Object.keys(toolDefs);
245
- printManualGuide(targetKeys);
250
+ printManualGuide(targetKeys, cwd);
246
251
  }
247
- function buildGuideEntries() {
252
+ function buildGuideEntries(projectCwd) {
248
253
  return [
249
254
  {
250
255
  keys: ['claude', 'cursor', 'roocode', 'kiro', 'continue', 'qoder', 'codebuddy', 'droid'],
@@ -257,10 +262,15 @@ function buildGuideEntries() {
257
262
  content: JSON.stringify({ servers: { [SERVER_KEY]: getVscodeMcpEntry() } }, null, 2),
258
263
  },
259
264
  {
260
- keys: ['trae', 'gemini'],
261
- label: 'Trae (.trae/mcp.json) / Gemini CLI (~/.gemini/settings.json):',
265
+ keys: ['trae'],
266
+ label: 'Trae (.trae/mcp.json):',
262
267
  content: JSON.stringify({ mcpServers: [getArrayMcpEntry()] }, null, 2),
263
268
  },
269
+ {
270
+ keys: ['gemini'],
271
+ label: 'Gemini CLI (~/.gemini/settings.json):',
272
+ content: JSON.stringify({ mcpServers: [getArrayMcpEntry(projectCwd)] }, null, 2),
273
+ },
264
274
  {
265
275
  keys: ['opencode'],
266
276
  label: 'OpenCode (.opencode/config.toml):',
@@ -274,17 +284,17 @@ function buildGuideEntries() {
274
284
  {
275
285
  keys: ['antigravity'],
276
286
  label: 'Antigravity (~/.gemini/antigravity/mcp_config.json):',
277
- content: JSON.stringify({ mcpServers: { [SERVER_KEY]: getStandardMcpEntry() } }, null, 2),
287
+ content: JSON.stringify({ mcpServers: { [SERVER_KEY]: getStandardMcpEntry(projectCwd) } }, null, 2),
278
288
  },
279
289
  {
280
290
  keys: ['windsurf'],
281
291
  label: 'Windsurf (~/.codeium/windsurf/mcp_config.json):',
282
- content: JSON.stringify({ mcpServers: { [SERVER_KEY]: getStandardMcpEntry() } }, null, 2),
292
+ content: JSON.stringify({ mcpServers: { [SERVER_KEY]: getStandardMcpEntry(projectCwd) } }, null, 2),
283
293
  },
284
294
  ];
285
295
  }
286
- function printManualGuide(targetKeys) {
287
- const entries = buildGuideEntries();
296
+ function printManualGuide(targetKeys, projectCwd) {
297
+ const entries = buildGuideEntries(projectCwd);
288
298
  const matched = entries.filter(e => e.keys.some(k => targetKeys.includes(k)));
289
299
  if (matched.length === 0)
290
300
  return;
@@ -1,2 +1,4 @@
1
1
  import 'dotenv/config';
2
- export declare function serveCommand(): Promise<void>;
2
+ export declare function serveCommand(options?: {
3
+ cwd?: string;
4
+ }): Promise<void>;
@@ -2,7 +2,7 @@ import 'dotenv/config';
2
2
  import { startMCPServer } from '../../mcp/server.js';
3
3
  import { detectGitHubToken } from '../../github/auth.js';
4
4
  import { getAIProvider } from '../../ai/client.js';
5
- export async function serveCommand() {
5
+ export async function serveCommand(options = {}) {
6
6
  // AI 제공자 확인 (경고만, 종료 안 함)
7
7
  const aiProvider = getAIProvider();
8
8
  if (!aiProvider) {
@@ -21,6 +21,6 @@ export async function serveCommand() {
21
21
  else {
22
22
  process.stderr.write('[Vibe Orchestrator] ✓ GitHub 인증 확인됨\n');
23
23
  }
24
- await startMCPServer();
24
+ await startMCPServer(options.cwd);
25
25
  }
26
26
  //# sourceMappingURL=serve.js.map
package/dist/cli/index.js CHANGED
@@ -36,8 +36,9 @@ program
36
36
  program
37
37
  .command('serve')
38
38
  .description('MCP 서버를 시작합니다 (AI 에이전트 연결용)')
39
- .action(async () => {
40
- await serveCommand();
39
+ .option('--cwd <path>', '프로젝트 디렉토리 경로 (전역 MCP 설정에서 필요)')
40
+ .action(async (options) => {
41
+ await serveCommand(options);
41
42
  });
42
43
  program
43
44
  .command('connect')
@@ -1 +1 @@
1
- export declare function startMCPServer(): Promise<void>;
1
+ export declare function startMCPServer(explicitPath?: string): Promise<void>;
@@ -1,3 +1,5 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
1
3
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
5
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
@@ -9,8 +11,24 @@ import { requestQA } from './tools/requestQA.js';
9
11
  import { createPR } from './tools/createPR.js';
10
12
  import { requestMergeReview } from './tools/requestMergeReview.js';
11
13
  import { executeMerge } from './tools/executeMerge.js';
12
- export async function startMCPServer() {
13
- const repoPath = process.cwd();
14
+ // cwd에서 위로 올라가며 .vibe/state.json이 있는 프로젝트 루트를 탐색
15
+ function findProjectRoot(startDir) {
16
+ let dir = startDir;
17
+ for (let i = 0; i < 20; i++) {
18
+ if (fs.existsSync(path.join(dir, '.vibe', 'state.json'))) {
19
+ return dir;
20
+ }
21
+ const parent = path.dirname(dir);
22
+ if (parent === dir)
23
+ break; // 파일시스템 루트 도달
24
+ dir = parent;
25
+ }
26
+ return startDir; // 못 찾으면 원래 cwd
27
+ }
28
+ export async function startMCPServer(explicitPath) {
29
+ const repoPath = explicitPath
30
+ ? path.resolve(explicitPath)
31
+ : findProjectRoot(process.cwd());
14
32
  const server = new Server({ name: 'vibe-orchestrator', version: '1.0.0' }, { capabilities: { tools: {} } });
15
33
  // ListTools 핸들러
16
34
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-collab",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "누가 어떤 AI를 써도, 항상 한 팀처럼 작동하는 바이브 코딩 협업 도구",
5
5
  "type": "module",
6
6
  "bin": {