@vibescope/mcp-server 0.4.0 → 0.4.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/dist/api-client/fallback.d.ts +33 -0
- package/dist/api-client/fallback.js +21 -0
- package/dist/api-client/findings.d.ts +69 -0
- package/dist/api-client/findings.js +36 -0
- package/dist/api-client/index.d.ts +78 -1
- package/dist/api-client/index.js +74 -4
- package/dist/api-client/milestones.d.ts +59 -0
- package/dist/api-client/milestones.js +30 -0
- package/dist/api-client/validation.d.ts +35 -0
- package/dist/api-client/validation.js +23 -0
- package/dist/api-client.d.ts +4 -0
- package/dist/cli-init.d.ts +17 -0
- package/dist/cli-init.js +497 -0
- package/dist/handlers/cloud-agents.d.ts +4 -0
- package/dist/handlers/cloud-agents.js +26 -12
- package/dist/handlers/discovery.js +15 -0
- package/dist/handlers/findings.js +1 -1
- package/dist/handlers/ideas.js +1 -1
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/session.js +115 -2
- package/dist/handlers/tasks.js +7 -5
- package/dist/handlers/tool-docs.js +344 -0
- package/dist/handlers/version.d.ts +5 -0
- package/dist/handlers/version.js +53 -0
- package/dist/index.js +7 -0
- package/dist/templates/agent-guidelines.d.ts +3 -1
- package/dist/templates/agent-guidelines.js +5 -1
- package/dist/templates/help-content.js +2 -2
- package/dist/tools/chat.d.ts +7 -0
- package/dist/tools/chat.js +43 -0
- package/dist/tools/cloud-agents.js +31 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.js +6 -1
- package/dist/tools/project.js +1 -1
- package/dist/tools/tasks.js +8 -0
- package/dist/tools/version.d.ts +5 -0
- package/dist/tools/version.js +28 -0
- package/dist/version.d.ts +28 -0
- package/dist/version.js +91 -0
- package/docs/TOOLS.md +93 -3
- package/package.json +4 -2
- package/src/api-client/fallback.ts +52 -0
- package/src/api-client/findings.ts +100 -0
- package/src/api-client/index.ts +91 -9
- package/src/api-client/milestones.ts +83 -0
- package/src/api-client/validation.ts +60 -0
- package/src/api-client.ts +4 -0
- package/src/cli-init.ts +557 -0
- package/src/handlers/cloud-agents.test.ts +438 -0
- package/src/handlers/cloud-agents.ts +35 -17
- package/src/handlers/discovery.ts +15 -0
- package/src/handlers/findings.ts +1 -1
- package/src/handlers/ideas.ts +1 -1
- package/src/handlers/index.ts +3 -0
- package/src/handlers/session.ts +128 -2
- package/src/handlers/tasks.ts +7 -5
- package/src/handlers/tool-docs.test.ts +511 -0
- package/src/handlers/tool-docs.ts +382 -0
- package/src/handlers/version.ts +63 -0
- package/src/index.ts +9 -0
- package/src/templates/agent-guidelines.ts +6 -1
- package/src/templates/help-content.ts +2 -2
- package/src/tools/chat.ts +46 -0
- package/src/tools/cloud-agents.ts +31 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/project.ts +1 -1
- package/src/tools/tasks.ts +8 -0
- package/src/tools/version.ts +34 -0
- package/src/version.ts +109 -0
package/src/cli-init.ts
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vibescope Init CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage: npx vibescope init
|
|
7
|
+
*
|
|
8
|
+
* Detects installed AI agents, configures MCP, and stores credentials.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
|
|
12
|
+
import { getAgentGuidelinesTemplate, VIBESCOPE_SECTION_START, VIBESCOPE_SECTION_END } from './templates/agent-guidelines.js';
|
|
13
|
+
import { homedir, platform } from 'node:os';
|
|
14
|
+
import { join, dirname } from 'node:path';
|
|
15
|
+
import { exec, execSync } from 'node:child_process';
|
|
16
|
+
import { createInterface } from 'node:readline';
|
|
17
|
+
|
|
18
|
+
const VIBESCOPE_SETTINGS_URL = 'https://vibescope.dev/dashboard/settings';
|
|
19
|
+
const VIBESCOPE_API_URL = 'https://vibescope.dev';
|
|
20
|
+
const CREDENTIALS_DIR = join(homedir(), '.vibescope');
|
|
21
|
+
const CREDENTIALS_PATH = join(CREDENTIALS_DIR, 'credentials.json');
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Terminal Colors
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
const c = {
|
|
28
|
+
reset: '\x1b[0m',
|
|
29
|
+
bold: '\x1b[1m',
|
|
30
|
+
dim: '\x1b[2m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
blue: '\x1b[34m',
|
|
34
|
+
magenta: '\x1b[35m',
|
|
35
|
+
cyan: '\x1b[36m',
|
|
36
|
+
red: '\x1b[31m',
|
|
37
|
+
white: '\x1b[37m',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const icon = {
|
|
41
|
+
check: `${c.green}✔${c.reset}`,
|
|
42
|
+
cross: `${c.red}✘${c.reset}`,
|
|
43
|
+
arrow: `${c.cyan}→${c.reset}`,
|
|
44
|
+
dot: `${c.dim}·${c.reset}`,
|
|
45
|
+
warn: `${c.yellow}⚠${c.reset}`,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Types
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
interface Agent {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
detected: boolean;
|
|
56
|
+
configure: (apiKey: string) => Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Prompts
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
function prompt(question: string): Promise<string> {
|
|
64
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
rl.question(question, (answer) => {
|
|
67
|
+
rl.close();
|
|
68
|
+
resolve(answer.trim());
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function promptConfirm(question: string, defaultYes = true): Promise<boolean> {
|
|
74
|
+
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
75
|
+
const answer = await prompt(`${question} (${hint}): `);
|
|
76
|
+
if (!answer) return defaultYes;
|
|
77
|
+
return answer.toLowerCase().startsWith('y');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function promptCheckboxes(label: string, items: { id: string; name: string; detected: boolean }[]): Promise<string[]> {
|
|
81
|
+
console.log(`\n${c.bold}${label}${c.reset}\n`);
|
|
82
|
+
items.forEach((item, i) => {
|
|
83
|
+
const tag = item.detected ? `${c.green}detected${c.reset}` : `${c.dim}not detected${c.reset}`;
|
|
84
|
+
console.log(` ${i + 1}) ${item.name} [${tag}]`);
|
|
85
|
+
});
|
|
86
|
+
console.log(` ${c.dim}a) All detected${c.reset}`);
|
|
87
|
+
|
|
88
|
+
const answer = await prompt(`\nSelect agents (comma-separated numbers, or 'a' for all detected): `);
|
|
89
|
+
|
|
90
|
+
if (answer.toLowerCase() === 'a') {
|
|
91
|
+
const detected = items.filter(i => i.detected).map(i => i.id);
|
|
92
|
+
return detected.length > 0 ? detected : items.map(i => i.id);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < items.length);
|
|
96
|
+
if (indices.length === 0) {
|
|
97
|
+
// Default to all detected
|
|
98
|
+
const detected = items.filter(i => i.detected).map(i => i.id);
|
|
99
|
+
return detected.length > 0 ? detected : [items[0].id];
|
|
100
|
+
}
|
|
101
|
+
return indices.map(i => items[i].id);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Credential Storage
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
export function readCredentials(): { apiKey?: string } {
|
|
109
|
+
try {
|
|
110
|
+
if (existsSync(CREDENTIALS_PATH)) {
|
|
111
|
+
const data = JSON.parse(readFileSync(CREDENTIALS_PATH, 'utf-8'));
|
|
112
|
+
return { apiKey: data.apiKey };
|
|
113
|
+
}
|
|
114
|
+
} catch { /* ignore */ }
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function writeCredentials(apiKey: string): void {
|
|
119
|
+
if (!existsSync(CREDENTIALS_DIR)) {
|
|
120
|
+
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
writeFileSync(CREDENTIALS_PATH, JSON.stringify({ apiKey }, null, 2) + '\n', { mode: 0o600 });
|
|
123
|
+
try {
|
|
124
|
+
chmodSync(CREDENTIALS_PATH, 0o600);
|
|
125
|
+
} catch { /* Windows doesn't support chmod, that's ok */ }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve API key from env var or credentials file.
|
|
130
|
+
* Used by the MCP server at startup.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveApiKey(): string | null {
|
|
133
|
+
// 1. Environment variable (highest priority)
|
|
134
|
+
if (process.env.VIBESCOPE_API_KEY) {
|
|
135
|
+
return process.env.VIBESCOPE_API_KEY;
|
|
136
|
+
}
|
|
137
|
+
// 2. Credentials file
|
|
138
|
+
const creds = readCredentials();
|
|
139
|
+
if (creds.apiKey) {
|
|
140
|
+
return creds.apiKey;
|
|
141
|
+
}
|
|
142
|
+
// 3. Not found
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Agent Detection & Configuration
|
|
148
|
+
// ============================================================================
|
|
149
|
+
|
|
150
|
+
function commandExists(cmd: string): boolean {
|
|
151
|
+
try {
|
|
152
|
+
execSync(`which ${cmd}`, { stdio: 'pipe', timeout: 3000 });
|
|
153
|
+
return true;
|
|
154
|
+
} catch {
|
|
155
|
+
// On Windows, try 'where'
|
|
156
|
+
if (platform() === 'win32') {
|
|
157
|
+
try {
|
|
158
|
+
execSync(`where ${cmd}`, { stdio: 'pipe', timeout: 3000 });
|
|
159
|
+
return true;
|
|
160
|
+
} catch { /* not found */ }
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function dirExists(path: string): boolean {
|
|
167
|
+
return existsSync(path);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function detectAgents(): Agent[] {
|
|
171
|
+
const home = homedir();
|
|
172
|
+
|
|
173
|
+
const agents: Agent[] = [
|
|
174
|
+
{
|
|
175
|
+
id: 'claude-code',
|
|
176
|
+
name: 'Claude Code',
|
|
177
|
+
detected: commandExists('claude') || dirExists(join(home, '.claude')),
|
|
178
|
+
configure: configureClaudeCode,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'cursor',
|
|
182
|
+
name: 'Cursor',
|
|
183
|
+
detected: commandExists('cursor') || dirExists(join(home, '.cursor')),
|
|
184
|
+
configure: configureCursor,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'windsurf',
|
|
188
|
+
name: 'Windsurf',
|
|
189
|
+
detected: dirExists(join(home, '.windsurf')) || dirExists(join(home, '.codeium')),
|
|
190
|
+
configure: configureWindsurf,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 'gemini',
|
|
194
|
+
name: 'Gemini CLI',
|
|
195
|
+
detected: commandExists('gemini') || dirExists(join(home, '.gemini')),
|
|
196
|
+
configure: configureGemini,
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
return agents;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Agent Configurators
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
async function configureClaudeCode(apiKey: string): Promise<void> {
|
|
208
|
+
// Write project-level .mcp.json (works reliably across all platforms)
|
|
209
|
+
const configPath = join(process.cwd(), '.mcp.json');
|
|
210
|
+
writeMcpJson(configPath, apiKey);
|
|
211
|
+
console.log(` ${icon.check} Wrote ${c.cyan}.mcp.json${c.reset} (Claude Code project config)`);
|
|
212
|
+
|
|
213
|
+
// Also offer to write user-level config for global access
|
|
214
|
+
const globalConfig = platform() === 'win32'
|
|
215
|
+
? join(homedir(), '.claude', 'settings.json')
|
|
216
|
+
: join(homedir(), '.claude', 'settings.json');
|
|
217
|
+
|
|
218
|
+
const addGlobal = await promptConfirm(` Also add Vibescope globally (all projects)?`, true);
|
|
219
|
+
if (addGlobal) {
|
|
220
|
+
const existing = readJsonFile(globalConfig);
|
|
221
|
+
const mcpServers = (existing.mcpServers as Record<string, unknown>) || {};
|
|
222
|
+
mcpServers['vibescope'] = buildMcpServerConfig(apiKey);
|
|
223
|
+
existing.mcpServers = mcpServers;
|
|
224
|
+
writeJsonFile(globalConfig, existing);
|
|
225
|
+
console.log(` ${icon.check} Wrote ${c.cyan}~/.claude/settings.json${c.reset} (global config)`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function configureCursor(apiKey: string): Promise<void> {
|
|
230
|
+
const configPath = join(process.cwd(), '.cursor', 'mcp.json');
|
|
231
|
+
writeMcpJson(configPath, apiKey);
|
|
232
|
+
console.log(` ${icon.check} Wrote ${c.cyan}.cursor/mcp.json${c.reset}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function configureWindsurf(apiKey: string): Promise<void> {
|
|
236
|
+
const configPath = join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
237
|
+
writeMcpJson(configPath, apiKey);
|
|
238
|
+
console.log(` ${icon.check} Wrote ${c.cyan}~/.codeium/windsurf/mcp_config.json${c.reset}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function configureGemini(apiKey: string): Promise<void> {
|
|
242
|
+
const configPath = join(homedir(), '.gemini', 'settings.json');
|
|
243
|
+
const existing = readJsonFile(configPath);
|
|
244
|
+
const mcpServers = (existing.mcpServers as Record<string, unknown>) || {};
|
|
245
|
+
mcpServers['vibescope'] = {
|
|
246
|
+
...buildMcpServerConfig(apiKey),
|
|
247
|
+
timeout: 30000,
|
|
248
|
+
trust: true,
|
|
249
|
+
};
|
|
250
|
+
existing.mcpServers = mcpServers;
|
|
251
|
+
writeJsonFile(configPath, existing);
|
|
252
|
+
console.log(` ${icon.check} Wrote ${c.cyan}~/.gemini/settings.json${c.reset}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// Config File Helpers
|
|
257
|
+
// ============================================================================
|
|
258
|
+
|
|
259
|
+
function readJsonFile(path: string): Record<string, unknown> {
|
|
260
|
+
try {
|
|
261
|
+
if (existsSync(path)) {
|
|
262
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
263
|
+
}
|
|
264
|
+
} catch { /* ignore */ }
|
|
265
|
+
return {};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function writeJsonFile(path: string, data: Record<string, unknown>): void {
|
|
269
|
+
const dir = dirname(path);
|
|
270
|
+
if (!existsSync(dir)) {
|
|
271
|
+
mkdirSync(dir, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function buildMcpServerConfig(apiKey: string): Record<string, unknown> {
|
|
277
|
+
const isWindows = platform() === 'win32';
|
|
278
|
+
if (isWindows) {
|
|
279
|
+
return {
|
|
280
|
+
command: 'cmd',
|
|
281
|
+
args: ['/c', 'npx', '-y', '-p', '@vibescope/mcp-server@latest', 'vibescope-mcp'],
|
|
282
|
+
env: { VIBESCOPE_API_KEY: apiKey },
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
command: 'npx',
|
|
287
|
+
args: ['-y', '-p', '@vibescope/mcp-server@latest', 'vibescope-mcp'],
|
|
288
|
+
env: { VIBESCOPE_API_KEY: apiKey },
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function writeMcpJson(configPath: string, apiKey: string): void {
|
|
293
|
+
const existing = readJsonFile(configPath);
|
|
294
|
+
const mcpServers = (existing.mcpServers as Record<string, unknown>) || {};
|
|
295
|
+
mcpServers['vibescope'] = buildMcpServerConfig(apiKey);
|
|
296
|
+
existing.mcpServers = mcpServers;
|
|
297
|
+
writeJsonFile(configPath, existing);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function checkExistingConfig(agentId: string): boolean {
|
|
301
|
+
switch (agentId) {
|
|
302
|
+
case 'claude-code':
|
|
303
|
+
return existsSync(join(process.cwd(), '.mcp.json')) &&
|
|
304
|
+
readJsonFile(join(process.cwd(), '.mcp.json')).mcpServers !== undefined &&
|
|
305
|
+
'vibescope' in ((readJsonFile(join(process.cwd(), '.mcp.json')).mcpServers as Record<string, unknown>) || {});
|
|
306
|
+
case 'cursor':
|
|
307
|
+
return existsSync(join(process.cwd(), '.cursor', 'mcp.json')) &&
|
|
308
|
+
'vibescope' in ((readJsonFile(join(process.cwd(), '.cursor', 'mcp.json')).mcpServers as Record<string, unknown>) || {});
|
|
309
|
+
case 'windsurf':
|
|
310
|
+
return 'vibescope' in ((readJsonFile(join(homedir(), '.codeium', 'windsurf', 'mcp_config.json')).mcpServers as Record<string, unknown>) || {});
|
|
311
|
+
case 'gemini':
|
|
312
|
+
return 'vibescope' in ((readJsonFile(join(homedir(), '.gemini', 'settings.json')).mcpServers as Record<string, unknown>) || {});
|
|
313
|
+
default:
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// API Key Validation
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
async function validateApiKey(apiKey: string): Promise<{ valid: boolean; message: string }> {
|
|
323
|
+
try {
|
|
324
|
+
const response = await fetch(`${VIBESCOPE_API_URL}/api/mcp/auth/validate`, {
|
|
325
|
+
method: 'POST',
|
|
326
|
+
headers: {
|
|
327
|
+
'Content-Type': 'application/json',
|
|
328
|
+
'X-API-Key': apiKey,
|
|
329
|
+
},
|
|
330
|
+
body: JSON.stringify({ api_key: apiKey }),
|
|
331
|
+
});
|
|
332
|
+
const data = await response.json() as { valid?: boolean; error?: string };
|
|
333
|
+
if (response.ok && data.valid) {
|
|
334
|
+
return { valid: true, message: 'API key is valid' };
|
|
335
|
+
}
|
|
336
|
+
return { valid: false, message: data.error || 'Invalid API key' };
|
|
337
|
+
} catch {
|
|
338
|
+
return { valid: true, message: 'Could not validate (network issue), proceeding' };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// Browser
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
function openBrowser(url: string): Promise<void> {
|
|
347
|
+
return new Promise((resolve) => {
|
|
348
|
+
const plat = platform();
|
|
349
|
+
const cmd = plat === 'darwin' ? `open "${url}"` :
|
|
350
|
+
plat === 'win32' ? `start "" "${url}"` :
|
|
351
|
+
`xdg-open "${url}"`;
|
|
352
|
+
exec(cmd, () => resolve());
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Main Init Flow
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
async function runInit(): Promise<void> {
|
|
361
|
+
console.log(`
|
|
362
|
+
${c.bold}${c.magenta} ╦ ╦╦╔╗ ╔═╗╔═╗╔═╗╔═╗╔═╗╔═╗${c.reset}
|
|
363
|
+
${c.bold}${c.magenta} ╚╗╔╝║╠╩╗║╣ ╚═╗║ ║ ║╠═╝║╣ ${c.reset}
|
|
364
|
+
${c.bold}${c.magenta} ╚╝ ╩╚═╝╚═╝╚═╝╚═╝╚═╝╩ ╚═╝${c.reset}
|
|
365
|
+
${c.dim} AI project tracking for vibe coders${c.reset}
|
|
366
|
+
`);
|
|
367
|
+
|
|
368
|
+
// Step 1: Detect agents
|
|
369
|
+
const agents = detectAgents();
|
|
370
|
+
const detected = agents.filter(a => a.detected);
|
|
371
|
+
|
|
372
|
+
if (detected.length > 0) {
|
|
373
|
+
console.log(`${icon.check} Detected agents:`);
|
|
374
|
+
detected.forEach(a => console.log(` ${icon.dot} ${a.name}`));
|
|
375
|
+
} else {
|
|
376
|
+
console.log(`${icon.warn} No AI agents detected automatically`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Step 2: Select agents
|
|
380
|
+
const selectedIds = await promptCheckboxes(
|
|
381
|
+
'Which agents do you want to configure?',
|
|
382
|
+
agents.map(a => ({ id: a.id, name: a.name, detected: a.detected }))
|
|
383
|
+
);
|
|
384
|
+
const selectedAgents = agents.filter(a => selectedIds.includes(a.id));
|
|
385
|
+
|
|
386
|
+
if (selectedAgents.length === 0) {
|
|
387
|
+
console.log(`\n${icon.cross} No agents selected. Exiting.`);
|
|
388
|
+
process.exit(0);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Step 3: API Key
|
|
392
|
+
let apiKey: string | null = null;
|
|
393
|
+
|
|
394
|
+
// Check env var first
|
|
395
|
+
if (process.env.VIBESCOPE_API_KEY) {
|
|
396
|
+
apiKey = process.env.VIBESCOPE_API_KEY;
|
|
397
|
+
console.log(`\n${icon.check} Using API key from ${c.cyan}VIBESCOPE_API_KEY${c.reset} env var`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Check credentials file
|
|
401
|
+
if (!apiKey) {
|
|
402
|
+
const creds = readCredentials();
|
|
403
|
+
if (creds.apiKey) {
|
|
404
|
+
apiKey = creds.apiKey;
|
|
405
|
+
console.log(`\n${icon.check} Found existing API key in ${c.cyan}~/.vibescope/credentials.json${c.reset}`);
|
|
406
|
+
const reuse = await promptConfirm('Use existing API key?', true);
|
|
407
|
+
if (!reuse) apiKey = null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Prompt for key if needed
|
|
412
|
+
if (!apiKey) {
|
|
413
|
+
console.log(`\n${c.bold}API Key Setup${c.reset}`);
|
|
414
|
+
console.log(`${icon.arrow} Get your API key at ${c.cyan}${VIBESCOPE_SETTINGS_URL}${c.reset}`);
|
|
415
|
+
|
|
416
|
+
const openIt = await promptConfirm('Open settings page in browser?', true);
|
|
417
|
+
if (openIt) await openBrowser(VIBESCOPE_SETTINGS_URL);
|
|
418
|
+
|
|
419
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
420
|
+
apiKey = await prompt(`\n${c.bold}Paste your API key:${c.reset} `);
|
|
421
|
+
if (!apiKey) {
|
|
422
|
+
console.log(`${icon.cross} API key is required`);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
process.stdout.write(` Validating... `);
|
|
426
|
+
const result = await validateApiKey(apiKey);
|
|
427
|
+
if (result.valid) {
|
|
428
|
+
console.log(`${icon.check} ${result.message}`);
|
|
429
|
+
break;
|
|
430
|
+
} else {
|
|
431
|
+
console.log(`${icon.cross} ${result.message}`);
|
|
432
|
+
apiKey = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!apiKey) {
|
|
437
|
+
console.log(`\n${icon.cross} Could not get a valid API key. Exiting.`);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Store credentials
|
|
442
|
+
try {
|
|
443
|
+
writeCredentials(apiKey);
|
|
444
|
+
console.log(`\n${icon.check} API key saved to ${c.cyan}~/.vibescope/credentials.json${c.reset}`);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
console.log(`\n${icon.warn} Could not save credentials: ${err instanceof Error ? err.message : 'unknown error'}`);
|
|
447
|
+
console.log(` ${c.dim}Set VIBESCOPE_API_KEY env var as fallback${c.reset}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Step 4: Configure each agent
|
|
452
|
+
console.log(`\n${c.bold}Configuring agents...${c.reset}\n`);
|
|
453
|
+
|
|
454
|
+
for (const agent of selectedAgents) {
|
|
455
|
+
const hasExisting = checkExistingConfig(agent.id);
|
|
456
|
+
if (hasExisting) {
|
|
457
|
+
const overwrite = await promptConfirm(` ${agent.name} already configured. Update?`, true);
|
|
458
|
+
if (!overwrite) {
|
|
459
|
+
console.log(` ${icon.dot} Skipped ${agent.name}`);
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
await agent.configure(apiKey);
|
|
465
|
+
} catch (err) {
|
|
466
|
+
console.log(` ${icon.cross} Failed to configure ${agent.name}: ${err instanceof Error ? err.message : 'unknown error'}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Step 5: Generate or update .claude/CLAUDE.md
|
|
471
|
+
const claudeMdPath = join(process.cwd(), '.claude', 'CLAUDE.md');
|
|
472
|
+
const claudeDir = join(process.cwd(), '.claude');
|
|
473
|
+
if (!existsSync(claudeDir)) {
|
|
474
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const vibescopeGuidelines = getAgentGuidelinesTemplate();
|
|
478
|
+
|
|
479
|
+
if (existsSync(claudeMdPath)) {
|
|
480
|
+
const existing = readFileSync(claudeMdPath, 'utf-8');
|
|
481
|
+
const hasVibescopeSection = existing.includes(VIBESCOPE_SECTION_START);
|
|
482
|
+
|
|
483
|
+
if (hasVibescopeSection) {
|
|
484
|
+
// Replace existing Vibescope section
|
|
485
|
+
const update = await promptConfirm(`\n ${c.cyan}.claude/CLAUDE.md${c.reset} already has Vibescope guidelines. Update them?`, true);
|
|
486
|
+
if (update) {
|
|
487
|
+
const startIdx = existing.indexOf(VIBESCOPE_SECTION_START);
|
|
488
|
+
const endIdx = existing.indexOf(VIBESCOPE_SECTION_END);
|
|
489
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
490
|
+
const updated = existing.substring(0, startIdx) + vibescopeGuidelines + existing.substring(endIdx + VIBESCOPE_SECTION_END.length);
|
|
491
|
+
writeFileSync(claudeMdPath, updated);
|
|
492
|
+
console.log(` ${icon.check} Updated Vibescope section in ${c.cyan}.claude/CLAUDE.md${c.reset}`);
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
console.log(` ${icon.dot} Skipped CLAUDE.md update`);
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
// Append Vibescope guidelines to existing file
|
|
499
|
+
const append = await promptConfirm(`\n ${c.cyan}.claude/CLAUDE.md${c.reset} exists. Append Vibescope guidelines?`, true);
|
|
500
|
+
if (append) {
|
|
501
|
+
const separator = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
502
|
+
writeFileSync(claudeMdPath, existing + separator + vibescopeGuidelines);
|
|
503
|
+
console.log(` ${icon.check} Appended Vibescope guidelines to ${c.cyan}.claude/CLAUDE.md${c.reset}`);
|
|
504
|
+
} else {
|
|
505
|
+
console.log(` ${icon.dot} Skipped CLAUDE.md`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
writeFileSync(claudeMdPath, vibescopeGuidelines);
|
|
510
|
+
console.log(` ${icon.check} Created ${c.cyan}.claude/CLAUDE.md${c.reset} with agent guidelines`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Done!
|
|
514
|
+
console.log(`
|
|
515
|
+
${c.green}${c.bold}Setup complete!${c.reset}
|
|
516
|
+
|
|
517
|
+
${c.bold}Next steps:${c.reset}
|
|
518
|
+
${icon.arrow} Restart your AI agent / IDE
|
|
519
|
+
${icon.arrow} Start coding — Vibescope tracks automatically
|
|
520
|
+
|
|
521
|
+
${c.dim}Need help? https://vibescope.dev/docs${c.reset}
|
|
522
|
+
`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ============================================================================
|
|
526
|
+
// CLI Entry Point
|
|
527
|
+
// ============================================================================
|
|
528
|
+
|
|
529
|
+
async function main() {
|
|
530
|
+
const args = process.argv.slice(2);
|
|
531
|
+
const command = args[0];
|
|
532
|
+
|
|
533
|
+
if (command === 'init' || !command) {
|
|
534
|
+
await runInit();
|
|
535
|
+
} else if (command === '--help' || command === '-h' || command === 'help') {
|
|
536
|
+
console.log(`
|
|
537
|
+
${c.bold}Vibescope CLI${c.reset}
|
|
538
|
+
|
|
539
|
+
Usage:
|
|
540
|
+
npx vibescope init Set up Vibescope for your AI agents
|
|
541
|
+
npx vibescope help Show this help
|
|
542
|
+
|
|
543
|
+
Docs: https://vibescope.dev/docs
|
|
544
|
+
`);
|
|
545
|
+
} else {
|
|
546
|
+
console.log(`Unknown command: ${command}\nRun ${c.cyan}npx vibescope help${c.reset} for usage.`);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, '/')}`;
|
|
552
|
+
if (isMainModule || process.argv[1]?.endsWith('cli-init.js')) {
|
|
553
|
+
main().catch((err) => {
|
|
554
|
+
console.error(`${icon.cross} ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
555
|
+
process.exit(1);
|
|
556
|
+
});
|
|
557
|
+
}
|