@vibescope/mcp-server 0.2.4 → 0.2.6
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 +15 -2
- package/dist/cli.d.ts +6 -3
- package/dist/cli.js +41 -23
- package/dist/setup.d.ts +22 -0
- package/dist/setup.js +313 -0
- package/dist/token-tracking.js +4 -2
- package/package.json +1 -1
- package/src/cli.ts +212 -195
- package/src/setup.test.ts +233 -0
- package/src/setup.ts +370 -0
- package/src/token-tracking.test.ts +12 -2
- package/src/token-tracking.ts +4 -2
package/src/cli.ts
CHANGED
|
@@ -1,195 +1,212 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Vibescope CLI -
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function
|
|
45
|
-
return process.env.
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vibescope CLI - Setup wizard and enforcement verification tool
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* setup - Interactive wizard to configure Vibescope for your IDE
|
|
8
|
+
* verify - Check agent compliance with Vibescope tracking (used by hooks)
|
|
9
|
+
*
|
|
10
|
+
* Exit codes (for verify command):
|
|
11
|
+
* 0 = Compliant (allow exit)
|
|
12
|
+
* 1 = Non-compliant (block exit, loop back)
|
|
13
|
+
* 2 = Error (allow exit with warning)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
import { runSetup } from './setup.js';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export interface VerificationResult {
|
|
24
|
+
status: 'compliant' | 'non_compliant' | 'no_session' | 'error';
|
|
25
|
+
reason: string;
|
|
26
|
+
continuation_prompt?: string;
|
|
27
|
+
details?: {
|
|
28
|
+
session_started: boolean;
|
|
29
|
+
project_id: string | null;
|
|
30
|
+
project_name: string | null;
|
|
31
|
+
git_url: string | null;
|
|
32
|
+
in_progress_tasks: number;
|
|
33
|
+
tasks_completed_this_session: number;
|
|
34
|
+
progress_logs_this_session: number;
|
|
35
|
+
blockers_logged_this_session: number;
|
|
36
|
+
session_duration_minutes: number | null;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Configuration (read at runtime for testability)
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
function getApiKey(): string | undefined {
|
|
45
|
+
return process.env.VIBESCOPE_API_KEY;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getApiUrl(): string {
|
|
49
|
+
return process.env.VIBESCOPE_API_URL || 'https://vibescope.dev';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Git URL Detection
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
export function normalizeGitUrl(url: string): string {
|
|
57
|
+
// Remove .git suffix
|
|
58
|
+
let normalized = url.replace(/\.git$/, '');
|
|
59
|
+
// Convert SSH to HTTPS format
|
|
60
|
+
if (normalized.startsWith('git@')) {
|
|
61
|
+
normalized = normalized
|
|
62
|
+
.replace(/^git@/, 'https://')
|
|
63
|
+
.replace(/:([^/])/, '/$1');
|
|
64
|
+
}
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function detectGitUrl(): string | null {
|
|
69
|
+
try {
|
|
70
|
+
const url = execSync('git config --get remote.origin.url', {
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
timeout: 5000,
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
74
|
+
}).trim();
|
|
75
|
+
|
|
76
|
+
return normalizeGitUrl(url);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Verification Logic
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
export async function verify(
|
|
87
|
+
gitUrl?: string,
|
|
88
|
+
projectId?: string
|
|
89
|
+
): Promise<VerificationResult> {
|
|
90
|
+
// Check environment (read at runtime for testability)
|
|
91
|
+
const apiKey = getApiKey();
|
|
92
|
+
if (!apiKey) {
|
|
93
|
+
return {
|
|
94
|
+
status: 'error',
|
|
95
|
+
reason: 'VIBESCOPE_API_KEY environment variable not set',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Auto-detect git URL if not provided
|
|
100
|
+
if (!gitUrl && !projectId) {
|
|
101
|
+
gitUrl = detectGitUrl() || undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(`${getApiUrl()}/api/mcp/verify`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: {
|
|
108
|
+
'Content-Type': 'application/json',
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
api_key: apiKey,
|
|
112
|
+
git_url: gitUrl,
|
|
113
|
+
project_id: projectId,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const result = await response.json() as VerificationResult;
|
|
118
|
+
return result;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return {
|
|
121
|
+
status: 'error',
|
|
122
|
+
reason: err instanceof Error ? err.message : 'Network error',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// CLI Entry Point
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
async function main() {
|
|
132
|
+
const args = process.argv.slice(2);
|
|
133
|
+
const command = args[0];
|
|
134
|
+
|
|
135
|
+
if (command === 'setup') {
|
|
136
|
+
// Run interactive setup wizard
|
|
137
|
+
await runSetup();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
} else if (command === 'verify') {
|
|
140
|
+
// Parse --git-url and --project-id flags
|
|
141
|
+
let gitUrl: string | undefined;
|
|
142
|
+
let projectId: string | undefined;
|
|
143
|
+
|
|
144
|
+
for (let i = 1; i < args.length; i++) {
|
|
145
|
+
if (args[i] === '--git-url' && args[i + 1]) {
|
|
146
|
+
gitUrl = args[++i];
|
|
147
|
+
} else if (args[i] === '--project-id' && args[i + 1]) {
|
|
148
|
+
projectId = args[++i];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result = await verify(gitUrl, projectId);
|
|
153
|
+
console.log(JSON.stringify(result, null, 2));
|
|
154
|
+
|
|
155
|
+
// Exit codes: 0=compliant, 1=non-compliant, 2=error
|
|
156
|
+
if (result.status === 'compliant') {
|
|
157
|
+
process.exit(0);
|
|
158
|
+
} else if (result.status === 'error') {
|
|
159
|
+
process.exit(2);
|
|
160
|
+
} else {
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
164
|
+
console.log(`
|
|
165
|
+
Vibescope CLI - Setup wizard and enforcement verification tool
|
|
166
|
+
|
|
167
|
+
Usage:
|
|
168
|
+
vibescope-cli setup Interactive setup wizard for your IDE
|
|
169
|
+
vibescope-cli verify [options] Check Vibescope compliance before exit
|
|
170
|
+
|
|
171
|
+
Setup:
|
|
172
|
+
Configures Vibescope MCP integration for:
|
|
173
|
+
- Claude Code (CLI)
|
|
174
|
+
- Claude Desktop
|
|
175
|
+
- Cursor
|
|
176
|
+
- Gemini CLI
|
|
177
|
+
|
|
178
|
+
Verify Options:
|
|
179
|
+
--git-url <url> Git repository URL (auto-detected if not provided)
|
|
180
|
+
--project-id <id> Vibescope project UUID
|
|
181
|
+
|
|
182
|
+
Exit Codes (verify):
|
|
183
|
+
0 Compliant - agent can exit
|
|
184
|
+
1 Non-compliant - agent should continue work
|
|
185
|
+
2 Error - allow exit with warning
|
|
186
|
+
|
|
187
|
+
Environment Variables:
|
|
188
|
+
VIBESCOPE_API_KEY Required for verify - Your Vibescope API key
|
|
189
|
+
VIBESCOPE_API_URL Optional - API URL (default: https://vibescope.dev)
|
|
190
|
+
`);
|
|
191
|
+
process.exit(0);
|
|
192
|
+
} else {
|
|
193
|
+
console.error('Usage: vibescope-cli setup');
|
|
194
|
+
console.error(' vibescope-cli verify [--git-url <url>] [--project-id <id>]');
|
|
195
|
+
console.error(' vibescope-cli --help');
|
|
196
|
+
process.exit(2);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Only run main when executed directly (not when imported for testing)
|
|
201
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, '/')}`;
|
|
202
|
+
if (isMainModule || process.argv[1]?.endsWith('cli.js')) {
|
|
203
|
+
main().catch((err) => {
|
|
204
|
+
console.error(
|
|
205
|
+
JSON.stringify({
|
|
206
|
+
status: 'error',
|
|
207
|
+
reason: err instanceof Error ? err.message : 'Unknown error',
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
process.exit(2);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Mock node:fs before importing setup module
|
|
4
|
+
vi.mock('node:fs', () => ({
|
|
5
|
+
existsSync: vi.fn(),
|
|
6
|
+
readFileSync: vi.fn(),
|
|
7
|
+
writeFileSync: vi.fn(),
|
|
8
|
+
mkdirSync: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
// Mock node:os
|
|
12
|
+
vi.mock('node:os', () => ({
|
|
13
|
+
homedir: vi.fn(() => '/home/testuser'),
|
|
14
|
+
platform: vi.fn(() => 'darwin'),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { homedir, platform } from 'node:os';
|
|
19
|
+
import { detectIdes, generateMcpConfig, type IdeConfig } from './setup.js';
|
|
20
|
+
|
|
21
|
+
describe('Setup module', () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
vi.mocked(homedir).mockReturnValue('/home/testuser');
|
|
25
|
+
vi.mocked(platform).mockReturnValue('darwin');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('detectIdes', () => {
|
|
29
|
+
it('should always include Claude Code as detected', () => {
|
|
30
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
31
|
+
const ides = detectIdes();
|
|
32
|
+
const claudeCode = ides.find(ide => ide.name === 'claude-code');
|
|
33
|
+
|
|
34
|
+
expect(claudeCode).toBeDefined();
|
|
35
|
+
expect(claudeCode?.detected).toBe(true);
|
|
36
|
+
expect(claudeCode?.configPath).toBe('.mcp.json');
|
|
37
|
+
expect(claudeCode?.configFormat).toBe('mcp-json');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should always include Gemini CLI as detected', () => {
|
|
41
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
42
|
+
const ides = detectIdes();
|
|
43
|
+
const gemini = ides.find(ide => ide.name === 'gemini');
|
|
44
|
+
|
|
45
|
+
expect(gemini).toBeDefined();
|
|
46
|
+
expect(gemini?.detected).toBe(true);
|
|
47
|
+
expect(gemini?.configFormat).toBe('settings-json');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should detect Claude Desktop when directory exists', () => {
|
|
51
|
+
vi.mocked(existsSync).mockImplementation((path: unknown) => {
|
|
52
|
+
if (typeof path === 'string' && path.includes('Claude')) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const ides = detectIdes();
|
|
59
|
+
const claudeDesktop = ides.find(ide => ide.name === 'claude-desktop');
|
|
60
|
+
|
|
61
|
+
expect(claudeDesktop).toBeDefined();
|
|
62
|
+
expect(claudeDesktop?.detected).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should not detect Claude Desktop when directory does not exist', () => {
|
|
66
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
67
|
+
|
|
68
|
+
const ides = detectIdes();
|
|
69
|
+
const claudeDesktop = ides.find(ide => ide.name === 'claude-desktop');
|
|
70
|
+
|
|
71
|
+
expect(claudeDesktop).toBeDefined();
|
|
72
|
+
expect(claudeDesktop?.detected).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should detect Cursor when directory exists', () => {
|
|
76
|
+
vi.mocked(existsSync).mockImplementation((path: unknown) => {
|
|
77
|
+
if (typeof path === 'string' && path.includes('Cursor')) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const ides = detectIdes();
|
|
84
|
+
const cursor = ides.find(ide => ide.name === 'cursor');
|
|
85
|
+
|
|
86
|
+
expect(cursor).toBeDefined();
|
|
87
|
+
expect(cursor?.detected).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return all four IDE configs', () => {
|
|
91
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
92
|
+
const ides = detectIdes();
|
|
93
|
+
|
|
94
|
+
expect(ides).toHaveLength(4);
|
|
95
|
+
expect(ides.map(ide => ide.name)).toEqual([
|
|
96
|
+
'claude-code',
|
|
97
|
+
'claude-desktop',
|
|
98
|
+
'cursor',
|
|
99
|
+
'gemini',
|
|
100
|
+
]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should use correct config paths on macOS', () => {
|
|
104
|
+
vi.mocked(platform).mockReturnValue('darwin');
|
|
105
|
+
vi.mocked(homedir).mockReturnValue('/Users/testuser');
|
|
106
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
107
|
+
|
|
108
|
+
const ides = detectIdes();
|
|
109
|
+
|
|
110
|
+
const claudeDesktop = ides.find(ide => ide.name === 'claude-desktop');
|
|
111
|
+
// Note: path.join uses OS separator, so we check for path components
|
|
112
|
+
expect(claudeDesktop?.configPath).toMatch(/Library.*Application Support.*Claude/);
|
|
113
|
+
|
|
114
|
+
const cursor = ides.find(ide => ide.name === 'cursor');
|
|
115
|
+
expect(cursor?.configPath).toMatch(/Library.*Application Support.*Cursor/);
|
|
116
|
+
|
|
117
|
+
const gemini = ides.find(ide => ide.name === 'gemini');
|
|
118
|
+
expect(gemini?.configPath).toMatch(/Users.*testuser.*\.gemini.*settings\.json/);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should use correct config paths on Windows', () => {
|
|
122
|
+
vi.mocked(platform).mockReturnValue('win32');
|
|
123
|
+
vi.mocked(homedir).mockReturnValue('C:\\Users\\testuser');
|
|
124
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
125
|
+
|
|
126
|
+
const ides = detectIdes();
|
|
127
|
+
|
|
128
|
+
const claudeDesktop = ides.find(ide => ide.name === 'claude-desktop');
|
|
129
|
+
// Note: path.join uses OS separator, so we check for path components
|
|
130
|
+
expect(claudeDesktop?.configPath).toMatch(/AppData.*Roaming.*Claude/);
|
|
131
|
+
|
|
132
|
+
const cursor = ides.find(ide => ide.name === 'cursor');
|
|
133
|
+
expect(cursor?.configPath).toMatch(/AppData.*Roaming.*Cursor/);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should use correct config paths on Linux', () => {
|
|
137
|
+
vi.mocked(platform).mockReturnValue('linux');
|
|
138
|
+
vi.mocked(homedir).mockReturnValue('/home/testuser');
|
|
139
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
140
|
+
|
|
141
|
+
const ides = detectIdes();
|
|
142
|
+
|
|
143
|
+
const claudeDesktop = ides.find(ide => ide.name === 'claude-desktop');
|
|
144
|
+
// Note: path.join uses OS separator, so we check for path components
|
|
145
|
+
expect(claudeDesktop?.configPath).toMatch(/\.config.*Claude/);
|
|
146
|
+
|
|
147
|
+
const cursor = ides.find(ide => ide.name === 'cursor');
|
|
148
|
+
expect(cursor?.configPath).toMatch(/\.config.*Cursor/);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('IdeConfig types', () => {
|
|
153
|
+
it('should have correct config format for MCP-based IDEs', () => {
|
|
154
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
155
|
+
const ides = detectIdes();
|
|
156
|
+
|
|
157
|
+
const claudeCode = ides.find(ide => ide.name === 'claude-code');
|
|
158
|
+
const claudeDesktop = ides.find(ide => ide.name === 'claude-desktop');
|
|
159
|
+
const cursor = ides.find(ide => ide.name === 'cursor');
|
|
160
|
+
|
|
161
|
+
expect(claudeCode?.configFormat).toBe('mcp-json');
|
|
162
|
+
expect(claudeDesktop?.configFormat).toBe('mcp-json');
|
|
163
|
+
expect(cursor?.configFormat).toBe('mcp-json');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should have correct config format for Gemini CLI', () => {
|
|
167
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
168
|
+
const ides = detectIdes();
|
|
169
|
+
|
|
170
|
+
const gemini = ides.find(ide => ide.name === 'gemini');
|
|
171
|
+
expect(gemini?.configFormat).toBe('settings-json');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('generateMcpConfig', () => {
|
|
176
|
+
it('should generate correct config structure for standard MCP IDEs', () => {
|
|
177
|
+
const claudeCodeIde: IdeConfig = {
|
|
178
|
+
name: 'claude-code',
|
|
179
|
+
displayName: 'Claude Code (CLI)',
|
|
180
|
+
configPath: '.mcp.json',
|
|
181
|
+
detected: true,
|
|
182
|
+
configFormat: 'mcp-json',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const config = generateMcpConfig('test-api-key', claudeCodeIde);
|
|
186
|
+
const mcpServers = config.mcpServers as Record<string, unknown>;
|
|
187
|
+
const vibescope = mcpServers.vibescope as Record<string, unknown>;
|
|
188
|
+
|
|
189
|
+
expect(vibescope.command).toBe('npx');
|
|
190
|
+
expect(vibescope.args).toContain('@vibescope/mcp-server@latest');
|
|
191
|
+
expect((vibescope.env as Record<string, string>).VIBESCOPE_API_KEY).toBe('test-api-key');
|
|
192
|
+
// Standard MCP config should NOT have timeout/trust
|
|
193
|
+
expect(vibescope.timeout).toBeUndefined();
|
|
194
|
+
expect(vibescope.trust).toBeUndefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should include timeout and trust for Gemini CLI config', () => {
|
|
198
|
+
const geminiIde: IdeConfig = {
|
|
199
|
+
name: 'gemini',
|
|
200
|
+
displayName: 'Gemini CLI',
|
|
201
|
+
configPath: '~/.gemini/settings.json',
|
|
202
|
+
detected: true,
|
|
203
|
+
configFormat: 'settings-json',
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const config = generateMcpConfig('test-api-key', geminiIde);
|
|
207
|
+
const mcpServers = config.mcpServers as Record<string, unknown>;
|
|
208
|
+
const vibescope = mcpServers.vibescope as Record<string, unknown>;
|
|
209
|
+
|
|
210
|
+
expect(vibescope.command).toBe('npx');
|
|
211
|
+
expect(vibescope.args).toContain('@vibescope/mcp-server@latest');
|
|
212
|
+
expect((vibescope.env as Record<string, string>).VIBESCOPE_API_KEY).toBe('test-api-key');
|
|
213
|
+
expect(vibescope.timeout).toBe(30000);
|
|
214
|
+
expect(vibescope.trust).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should use correct npx args format', () => {
|
|
218
|
+
const ide: IdeConfig = {
|
|
219
|
+
name: 'claude-code',
|
|
220
|
+
displayName: 'Claude Code (CLI)',
|
|
221
|
+
configPath: '.mcp.json',
|
|
222
|
+
detected: true,
|
|
223
|
+
configFormat: 'mcp-json',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const config = generateMcpConfig('my-key', ide);
|
|
227
|
+
const mcpServers = config.mcpServers as Record<string, unknown>;
|
|
228
|
+
const vibescope = mcpServers.vibescope as Record<string, unknown>;
|
|
229
|
+
|
|
230
|
+
expect(vibescope.args).toEqual(['-y', '-p', '@vibescope/mcp-server@latest', 'vibescope-mcp']);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|