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