@vibescope/mcp-server 0.3.0 → 0.3.3

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 (154) hide show
  1. package/dist/api-client/blockers.d.ts +46 -0
  2. package/dist/api-client/blockers.js +43 -0
  3. package/dist/api-client/cost.d.ts +112 -0
  4. package/dist/api-client/cost.js +76 -0
  5. package/dist/api-client/decisions.d.ts +55 -0
  6. package/dist/api-client/decisions.js +32 -0
  7. package/dist/api-client/discovery.d.ts +62 -0
  8. package/dist/api-client/discovery.js +21 -0
  9. package/dist/api-client/ideas.d.ts +75 -0
  10. package/dist/api-client/ideas.js +36 -0
  11. package/dist/api-client/index.d.ts +749 -0
  12. package/dist/api-client/index.js +291 -0
  13. package/dist/api-client/project.d.ts +132 -0
  14. package/dist/api-client/project.js +45 -0
  15. package/dist/api-client/session.d.ts +163 -0
  16. package/dist/api-client/session.js +52 -0
  17. package/dist/api-client/tasks.d.ts +328 -0
  18. package/dist/api-client/tasks.js +132 -0
  19. package/dist/api-client/types.d.ts +25 -0
  20. package/dist/api-client/types.js +4 -0
  21. package/dist/api-client/worktrees.d.ts +33 -0
  22. package/dist/api-client/worktrees.js +26 -0
  23. package/dist/api-client.d.ts +9 -0
  24. package/dist/api-client.js +104 -25
  25. package/dist/cli-init.d.ts +17 -0
  26. package/dist/cli-init.js +445 -0
  27. package/dist/cli.js +0 -0
  28. package/dist/handlers/cloud-agents.d.ts +21 -0
  29. package/dist/handlers/cloud-agents.js +91 -0
  30. package/dist/handlers/discovery.js +7 -0
  31. package/dist/handlers/index.d.ts +1 -0
  32. package/dist/handlers/index.js +3 -0
  33. package/dist/handlers/session.js +3 -1
  34. package/dist/handlers/tasks.js +10 -12
  35. package/dist/handlers/types.d.ts +2 -1
  36. package/dist/handlers/validation.js +5 -1
  37. package/dist/index.js +8 -3
  38. package/dist/token-tracking.js +2 -2
  39. package/dist/tools/blockers.d.ts +13 -0
  40. package/dist/tools/blockers.js +119 -0
  41. package/dist/tools/bodies-of-work.d.ts +19 -0
  42. package/dist/tools/bodies-of-work.js +280 -0
  43. package/dist/tools/cloud-agents.d.ts +9 -0
  44. package/dist/tools/cloud-agents.js +67 -0
  45. package/dist/tools/connectors.d.ts +14 -0
  46. package/dist/tools/connectors.js +188 -0
  47. package/dist/tools/cost.d.ts +11 -0
  48. package/dist/tools/cost.js +108 -0
  49. package/dist/tools/decisions.d.ts +12 -0
  50. package/dist/tools/decisions.js +108 -0
  51. package/dist/tools/deployment.d.ts +24 -0
  52. package/dist/tools/deployment.js +439 -0
  53. package/dist/tools/discovery.d.ts +10 -0
  54. package/dist/tools/discovery.js +73 -0
  55. package/dist/tools/fallback.d.ts +11 -0
  56. package/dist/tools/fallback.js +108 -0
  57. package/dist/tools/file-checkouts.d.ts +13 -0
  58. package/dist/tools/file-checkouts.js +141 -0
  59. package/dist/tools/findings.d.ts +13 -0
  60. package/dist/tools/findings.js +98 -0
  61. package/dist/tools/git-issues.d.ts +11 -0
  62. package/dist/tools/git-issues.js +127 -0
  63. package/dist/tools/ideas.d.ts +13 -0
  64. package/dist/tools/ideas.js +159 -0
  65. package/dist/tools/index.d.ts +71 -0
  66. package/dist/tools/index.js +98 -0
  67. package/dist/tools/milestones.d.ts +12 -0
  68. package/dist/tools/milestones.js +115 -0
  69. package/dist/tools/organizations.d.ts +17 -0
  70. package/dist/tools/organizations.js +221 -0
  71. package/dist/tools/progress.d.ts +9 -0
  72. package/dist/tools/progress.js +70 -0
  73. package/dist/tools/project.d.ts +13 -0
  74. package/dist/tools/project.js +199 -0
  75. package/dist/tools/requests.d.ts +10 -0
  76. package/dist/tools/requests.js +65 -0
  77. package/dist/tools/roles.d.ts +11 -0
  78. package/dist/tools/roles.js +109 -0
  79. package/dist/tools/session.d.ts +15 -0
  80. package/dist/tools/session.js +178 -0
  81. package/dist/tools/sprints.d.ts +18 -0
  82. package/dist/tools/sprints.js +295 -0
  83. package/dist/tools/tasks.d.ts +27 -0
  84. package/dist/tools/tasks.js +539 -0
  85. package/dist/tools/types.d.ts +7 -0
  86. package/dist/tools/types.js +6 -0
  87. package/dist/tools/validation.d.ts +10 -0
  88. package/dist/tools/validation.js +72 -0
  89. package/dist/tools/worktrees.d.ts +9 -0
  90. package/dist/tools/worktrees.js +63 -0
  91. package/dist/utils.d.ts +66 -0
  92. package/dist/utils.js +102 -0
  93. package/docs/TOOLS.md +55 -2
  94. package/package.json +5 -3
  95. package/scripts/generate-docs.ts +1 -1
  96. package/src/api-client/blockers.ts +86 -0
  97. package/src/api-client/cost.ts +185 -0
  98. package/src/api-client/decisions.ts +87 -0
  99. package/src/api-client/discovery.ts +81 -0
  100. package/src/api-client/ideas.ts +112 -0
  101. package/src/api-client/index.ts +378 -0
  102. package/src/api-client/project.ts +179 -0
  103. package/src/api-client/session.ts +220 -0
  104. package/src/api-client/tasks.ts +450 -0
  105. package/src/api-client/types.ts +32 -0
  106. package/src/api-client/worktrees.ts +53 -0
  107. package/src/api-client.test.ts +136 -9
  108. package/src/api-client.ts +125 -27
  109. package/src/cli-init.ts +504 -0
  110. package/src/handlers/__test-utils__.ts +2 -0
  111. package/src/handlers/cloud-agents.ts +138 -0
  112. package/src/handlers/discovery.ts +7 -0
  113. package/src/handlers/index.ts +3 -0
  114. package/src/handlers/session.ts +3 -1
  115. package/src/handlers/tasks.ts +10 -12
  116. package/src/handlers/tool-categories.test.ts +1 -1
  117. package/src/handlers/types.ts +2 -1
  118. package/src/handlers/validation.ts +6 -1
  119. package/src/index.test.ts +2 -2
  120. package/src/index.ts +8 -2
  121. package/src/token-tracking.ts +3 -2
  122. package/src/tools/blockers.ts +122 -0
  123. package/src/tools/bodies-of-work.ts +283 -0
  124. package/src/tools/cloud-agents.ts +70 -0
  125. package/src/tools/connectors.ts +191 -0
  126. package/src/tools/cost.ts +111 -0
  127. package/src/tools/decisions.ts +111 -0
  128. package/src/tools/deployment.ts +442 -0
  129. package/src/tools/discovery.ts +76 -0
  130. package/src/tools/fallback.ts +111 -0
  131. package/src/tools/file-checkouts.ts +145 -0
  132. package/src/tools/findings.ts +101 -0
  133. package/src/tools/git-issues.ts +130 -0
  134. package/src/tools/ideas.ts +162 -0
  135. package/src/tools/index.ts +131 -0
  136. package/src/tools/milestones.ts +118 -0
  137. package/src/tools/organizations.ts +224 -0
  138. package/src/tools/progress.ts +73 -0
  139. package/src/tools/project.ts +202 -0
  140. package/src/tools/requests.ts +68 -0
  141. package/src/tools/roles.ts +112 -0
  142. package/src/tools/session.ts +181 -0
  143. package/src/tools/sprints.ts +298 -0
  144. package/src/tools/tasks.ts +542 -0
  145. package/src/tools/tools.test.ts +222 -0
  146. package/src/tools/types.ts +9 -0
  147. package/src/tools/validation.ts +75 -0
  148. package/src/tools/worktrees.ts +66 -0
  149. package/src/tools.test.ts +1 -1
  150. package/src/utils.test.ts +229 -0
  151. package/src/utils.ts +117 -0
  152. package/dist/tools.d.ts +0 -2
  153. package/dist/tools.js +0 -3602
  154. package/src/tools.ts +0 -3607
@@ -5,46 +5,125 @@
5
5
  * All database operations are handled server-side through these endpoints.
6
6
  */
7
7
  const DEFAULT_API_URL = 'https://vibescope.dev';
8
+ // Retry configuration defaults
9
+ const DEFAULT_RETRY_STATUS_CODES = [429, 503, 504];
10
+ const DEFAULT_MAX_RETRIES = 3;
11
+ const DEFAULT_BASE_DELAY_MS = 1000;
12
+ const DEFAULT_MAX_DELAY_MS = 30000;
13
+ /**
14
+ * Calculate delay for exponential backoff with jitter
15
+ */
16
+ function calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs, retryAfter) {
17
+ // If Retry-After header is present, use it (in seconds)
18
+ if (retryAfter !== undefined && retryAfter > 0) {
19
+ return Math.min(retryAfter * 1000, maxDelayMs);
20
+ }
21
+ // Exponential backoff: base * 2^attempt with jitter
22
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
23
+ const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter
24
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
25
+ }
26
+ /**
27
+ * Sleep for a specified duration
28
+ */
29
+ function sleep(ms) {
30
+ return new Promise(resolve => setTimeout(resolve, ms));
31
+ }
8
32
  export class VibescopeApiClient {
9
33
  apiKey;
10
34
  baseUrl;
35
+ retryConfig;
11
36
  constructor(config) {
12
37
  this.apiKey = config.apiKey;
13
38
  this.baseUrl = config.baseUrl || process.env.VIBESCOPE_API_URL || DEFAULT_API_URL;
39
+ this.retryConfig = {
40
+ maxRetries: config.retry?.maxRetries ?? DEFAULT_MAX_RETRIES,
41
+ baseDelayMs: config.retry?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS,
42
+ maxDelayMs: config.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,
43
+ retryStatusCodes: config.retry?.retryStatusCodes ?? DEFAULT_RETRY_STATUS_CODES,
44
+ };
14
45
  }
15
46
  async request(method, path, body) {
16
47
  const url = `${this.baseUrl}${path}`;
17
- try {
18
- const response = await fetch(url, {
19
- method,
20
- headers: {
21
- 'Content-Type': 'application/json',
22
- 'X-API-Key': this.apiKey
23
- },
24
- body: body ? JSON.stringify(body) : undefined
25
- });
26
- const data = await response.json();
27
- if (!response.ok) {
48
+ const { maxRetries, baseDelayMs, maxDelayMs, retryStatusCodes } = this.retryConfig;
49
+ let lastError = null;
50
+ let lastResponse = null;
51
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
52
+ try {
53
+ const response = await fetch(url, {
54
+ method,
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ 'X-API-Key': this.apiKey
58
+ },
59
+ body: body ? JSON.stringify(body) : undefined
60
+ });
61
+ // Check if we should retry this status code
62
+ if (retryStatusCodes.includes(response.status) && attempt < maxRetries) {
63
+ lastResponse = response;
64
+ // Parse Retry-After header if present (can be seconds or HTTP-date)
65
+ const retryAfterHeader = response.headers.get('Retry-After');
66
+ let retryAfter;
67
+ if (retryAfterHeader) {
68
+ const seconds = parseInt(retryAfterHeader, 10);
69
+ if (!isNaN(seconds)) {
70
+ retryAfter = seconds;
71
+ }
72
+ }
73
+ const delay = calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs, retryAfter);
74
+ await sleep(delay);
75
+ continue;
76
+ }
77
+ const data = await response.json();
78
+ if (!response.ok) {
79
+ return {
80
+ ok: false,
81
+ status: response.status,
82
+ error: data.error || `HTTP ${response.status}`,
83
+ data // Include full response data for additional error context
84
+ };
85
+ }
28
86
  return {
29
- ok: false,
87
+ ok: true,
30
88
  status: response.status,
31
- error: data.error || `HTTP ${response.status}`,
32
- data // Include full response data for additional error context
89
+ data
33
90
  };
34
91
  }
35
- return {
36
- ok: true,
37
- status: response.status,
38
- data
39
- };
92
+ catch (err) {
93
+ lastError = err instanceof Error ? err : new Error('Network error');
94
+ // Retry on network errors (connection failures, timeouts)
95
+ if (attempt < maxRetries) {
96
+ const delay = calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs);
97
+ await sleep(delay);
98
+ continue;
99
+ }
100
+ }
40
101
  }
41
- catch (err) {
42
- return {
43
- ok: false,
44
- status: 0,
45
- error: err instanceof Error ? err.message : 'Network error'
46
- };
102
+ // All retries exhausted
103
+ if (lastResponse) {
104
+ // We had a response but it was a retryable error status
105
+ try {
106
+ const data = await lastResponse.json();
107
+ return {
108
+ ok: false,
109
+ status: lastResponse.status,
110
+ error: data.error || `HTTP ${lastResponse.status} after ${maxRetries} retries`,
111
+ data
112
+ };
113
+ }
114
+ catch {
115
+ return {
116
+ ok: false,
117
+ status: lastResponse.status,
118
+ error: `HTTP ${lastResponse.status} after ${maxRetries} retries`
119
+ };
120
+ }
47
121
  }
122
+ return {
123
+ ok: false,
124
+ status: 0,
125
+ error: lastError?.message || 'Network error after retries'
126
+ };
48
127
  }
49
128
  // Auth endpoints
50
129
  async validateAuth() {
@@ -0,0 +1,17 @@
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
+ export declare function readCredentials(): {
10
+ apiKey?: string;
11
+ };
12
+ export declare function writeCredentials(apiKey: string): void;
13
+ /**
14
+ * Resolve API key from env var or credentials file.
15
+ * Used by the MCP server at startup.
16
+ */
17
+ export declare function resolveApiKey(): string | null;
@@ -0,0 +1,445 @@
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 { homedir, platform } from 'node:os';
11
+ import { join, dirname } from 'node:path';
12
+ import { exec, execSync } from 'node:child_process';
13
+ import { createInterface } from 'node:readline';
14
+ const VIBESCOPE_SETTINGS_URL = 'https://vibescope.dev/dashboard/settings';
15
+ const VIBESCOPE_API_URL = 'https://vibescope.dev';
16
+ const CREDENTIALS_DIR = join(homedir(), '.vibescope');
17
+ const CREDENTIALS_PATH = join(CREDENTIALS_DIR, 'credentials.json');
18
+ // ============================================================================
19
+ // Terminal Colors
20
+ // ============================================================================
21
+ const c = {
22
+ reset: '\x1b[0m',
23
+ bold: '\x1b[1m',
24
+ dim: '\x1b[2m',
25
+ green: '\x1b[32m',
26
+ yellow: '\x1b[33m',
27
+ blue: '\x1b[34m',
28
+ magenta: '\x1b[35m',
29
+ cyan: '\x1b[36m',
30
+ red: '\x1b[31m',
31
+ white: '\x1b[37m',
32
+ };
33
+ const icon = {
34
+ check: `${c.green}✔${c.reset}`,
35
+ cross: `${c.red}✘${c.reset}`,
36
+ arrow: `${c.cyan}→${c.reset}`,
37
+ dot: `${c.dim}·${c.reset}`,
38
+ warn: `${c.yellow}⚠${c.reset}`,
39
+ };
40
+ // ============================================================================
41
+ // Prompts
42
+ // ============================================================================
43
+ function prompt(question) {
44
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
45
+ return new Promise((resolve) => {
46
+ rl.question(question, (answer) => {
47
+ rl.close();
48
+ resolve(answer.trim());
49
+ });
50
+ });
51
+ }
52
+ async function promptConfirm(question, defaultYes = true) {
53
+ const hint = defaultYes ? 'Y/n' : 'y/N';
54
+ const answer = await prompt(`${question} (${hint}): `);
55
+ if (!answer)
56
+ return defaultYes;
57
+ return answer.toLowerCase().startsWith('y');
58
+ }
59
+ async function promptCheckboxes(label, items) {
60
+ console.log(`\n${c.bold}${label}${c.reset}\n`);
61
+ items.forEach((item, i) => {
62
+ const tag = item.detected ? `${c.green}detected${c.reset}` : `${c.dim}not detected${c.reset}`;
63
+ console.log(` ${i + 1}) ${item.name} [${tag}]`);
64
+ });
65
+ console.log(` ${c.dim}a) All detected${c.reset}`);
66
+ const answer = await prompt(`\nSelect agents (comma-separated numbers, or 'a' for all detected): `);
67
+ if (answer.toLowerCase() === 'a') {
68
+ const detected = items.filter(i => i.detected).map(i => i.id);
69
+ return detected.length > 0 ? detected : items.map(i => i.id);
70
+ }
71
+ const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < items.length);
72
+ if (indices.length === 0) {
73
+ // Default to all detected
74
+ const detected = items.filter(i => i.detected).map(i => i.id);
75
+ return detected.length > 0 ? detected : [items[0].id];
76
+ }
77
+ return indices.map(i => items[i].id);
78
+ }
79
+ // ============================================================================
80
+ // Credential Storage
81
+ // ============================================================================
82
+ export function readCredentials() {
83
+ try {
84
+ if (existsSync(CREDENTIALS_PATH)) {
85
+ const data = JSON.parse(readFileSync(CREDENTIALS_PATH, 'utf-8'));
86
+ return { apiKey: data.apiKey };
87
+ }
88
+ }
89
+ catch { /* ignore */ }
90
+ return {};
91
+ }
92
+ export function writeCredentials(apiKey) {
93
+ if (!existsSync(CREDENTIALS_DIR)) {
94
+ mkdirSync(CREDENTIALS_DIR, { recursive: true });
95
+ }
96
+ writeFileSync(CREDENTIALS_PATH, JSON.stringify({ apiKey }, null, 2) + '\n', { mode: 0o600 });
97
+ try {
98
+ chmodSync(CREDENTIALS_PATH, 0o600);
99
+ }
100
+ catch { /* Windows doesn't support chmod, that's ok */ }
101
+ }
102
+ /**
103
+ * Resolve API key from env var or credentials file.
104
+ * Used by the MCP server at startup.
105
+ */
106
+ export function resolveApiKey() {
107
+ // 1. Environment variable (highest priority)
108
+ if (process.env.VIBESCOPE_API_KEY) {
109
+ return process.env.VIBESCOPE_API_KEY;
110
+ }
111
+ // 2. Credentials file
112
+ const creds = readCredentials();
113
+ if (creds.apiKey) {
114
+ return creds.apiKey;
115
+ }
116
+ // 3. Not found
117
+ return null;
118
+ }
119
+ // ============================================================================
120
+ // Agent Detection & Configuration
121
+ // ============================================================================
122
+ function commandExists(cmd) {
123
+ try {
124
+ execSync(`which ${cmd}`, { stdio: 'pipe', timeout: 3000 });
125
+ return true;
126
+ }
127
+ catch {
128
+ // On Windows, try 'where'
129
+ if (platform() === 'win32') {
130
+ try {
131
+ execSync(`where ${cmd}`, { stdio: 'pipe', timeout: 3000 });
132
+ return true;
133
+ }
134
+ catch { /* not found */ }
135
+ }
136
+ return false;
137
+ }
138
+ }
139
+ function dirExists(path) {
140
+ return existsSync(path);
141
+ }
142
+ function detectAgents() {
143
+ const home = homedir();
144
+ const agents = [
145
+ {
146
+ id: 'claude-code',
147
+ name: 'Claude Code',
148
+ detected: commandExists('claude') || dirExists(join(home, '.claude')),
149
+ configure: configureClaudeCode,
150
+ },
151
+ {
152
+ id: 'cursor',
153
+ name: 'Cursor',
154
+ detected: commandExists('cursor') || dirExists(join(home, '.cursor')),
155
+ configure: configureCursor,
156
+ },
157
+ {
158
+ id: 'windsurf',
159
+ name: 'Windsurf',
160
+ detected: dirExists(join(home, '.windsurf')) || dirExists(join(home, '.codeium')),
161
+ configure: configureWindsurf,
162
+ },
163
+ {
164
+ id: 'gemini',
165
+ name: 'Gemini CLI',
166
+ detected: commandExists('gemini') || dirExists(join(home, '.gemini')),
167
+ configure: configureGemini,
168
+ },
169
+ ];
170
+ return agents;
171
+ }
172
+ // ============================================================================
173
+ // Agent Configurators
174
+ // ============================================================================
175
+ async function configureClaudeCode(apiKey) {
176
+ // Try using `claude mcp add` command
177
+ try {
178
+ execSync('claude --version', { stdio: 'pipe', timeout: 5000 });
179
+ // Use project scope for claude mcp add
180
+ try {
181
+ execSync(`claude mcp add vibescope -e VIBESCOPE_API_KEY=${apiKey} -- npx -y -p @vibescope/mcp-server@latest vibescope-mcp`, { stdio: 'pipe', timeout: 15000 });
182
+ console.log(` ${icon.check} Claude Code configured via ${c.cyan}claude mcp add${c.reset}`);
183
+ return;
184
+ }
185
+ catch {
186
+ // claude mcp add not available (older version), fall through to manual config
187
+ }
188
+ }
189
+ catch { /* claude CLI not available */ }
190
+ // Fallback: write .mcp.json
191
+ const configPath = join(process.cwd(), '.mcp.json');
192
+ writeMcpJson(configPath, apiKey);
193
+ console.log(` ${icon.check} Wrote ${c.cyan}.mcp.json${c.reset} (Claude Code project config)`);
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
+ command: 'npx',
211
+ args: ['-y', '-p', '@vibescope/mcp-server@latest', 'vibescope-mcp'],
212
+ env: { VIBESCOPE_API_KEY: apiKey },
213
+ timeout: 30000,
214
+ trust: true,
215
+ };
216
+ existing.mcpServers = mcpServers;
217
+ writeJsonFile(configPath, existing);
218
+ console.log(` ${icon.check} Wrote ${c.cyan}~/.gemini/settings.json${c.reset}`);
219
+ }
220
+ // ============================================================================
221
+ // Config File Helpers
222
+ // ============================================================================
223
+ function readJsonFile(path) {
224
+ try {
225
+ if (existsSync(path)) {
226
+ return JSON.parse(readFileSync(path, 'utf-8'));
227
+ }
228
+ }
229
+ catch { /* ignore */ }
230
+ return {};
231
+ }
232
+ function writeJsonFile(path, data) {
233
+ const dir = dirname(path);
234
+ if (!existsSync(dir)) {
235
+ mkdirSync(dir, { recursive: true });
236
+ }
237
+ writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
238
+ }
239
+ function writeMcpJson(configPath, apiKey) {
240
+ const existing = readJsonFile(configPath);
241
+ const mcpServers = existing.mcpServers || {};
242
+ mcpServers['vibescope'] = {
243
+ command: 'npx',
244
+ args: ['-y', '-p', '@vibescope/mcp-server@latest', 'vibescope-mcp'],
245
+ env: { VIBESCOPE_API_KEY: apiKey },
246
+ };
247
+ existing.mcpServers = mcpServers;
248
+ writeJsonFile(configPath, existing);
249
+ }
250
+ function checkExistingConfig(agentId) {
251
+ switch (agentId) {
252
+ case 'claude-code':
253
+ return existsSync(join(process.cwd(), '.mcp.json')) &&
254
+ readJsonFile(join(process.cwd(), '.mcp.json')).mcpServers !== undefined &&
255
+ 'vibescope' in (readJsonFile(join(process.cwd(), '.mcp.json')).mcpServers || {});
256
+ case 'cursor':
257
+ return existsSync(join(process.cwd(), '.cursor', 'mcp.json')) &&
258
+ 'vibescope' in (readJsonFile(join(process.cwd(), '.cursor', 'mcp.json')).mcpServers || {});
259
+ case 'windsurf':
260
+ return 'vibescope' in (readJsonFile(join(homedir(), '.codeium', 'windsurf', 'mcp_config.json')).mcpServers || {});
261
+ case 'gemini':
262
+ return 'vibescope' in (readJsonFile(join(homedir(), '.gemini', 'settings.json')).mcpServers || {});
263
+ default:
264
+ return false;
265
+ }
266
+ }
267
+ // ============================================================================
268
+ // API Key Validation
269
+ // ============================================================================
270
+ async function validateApiKey(apiKey) {
271
+ try {
272
+ const response = await fetch(`${VIBESCOPE_API_URL}/api/mcp/auth/validate`, {
273
+ method: 'POST',
274
+ headers: {
275
+ 'Content-Type': 'application/json',
276
+ 'X-API-Key': apiKey,
277
+ },
278
+ body: JSON.stringify({ api_key: apiKey }),
279
+ });
280
+ const data = await response.json();
281
+ if (response.ok && data.valid) {
282
+ return { valid: true, message: 'API key is valid' };
283
+ }
284
+ return { valid: false, message: data.error || 'Invalid API key' };
285
+ }
286
+ catch {
287
+ return { valid: true, message: 'Could not validate (network issue), proceeding' };
288
+ }
289
+ }
290
+ // ============================================================================
291
+ // Browser
292
+ // ============================================================================
293
+ function openBrowser(url) {
294
+ return new Promise((resolve) => {
295
+ const plat = platform();
296
+ const cmd = plat === 'darwin' ? `open "${url}"` :
297
+ plat === 'win32' ? `start "" "${url}"` :
298
+ `xdg-open "${url}"`;
299
+ exec(cmd, () => resolve());
300
+ });
301
+ }
302
+ // ============================================================================
303
+ // Main Init Flow
304
+ // ============================================================================
305
+ async function runInit() {
306
+ console.log(`
307
+ ${c.bold}${c.magenta} ╦ ╦╦╔╗ ╔═╗╔═╗╔═╗╔═╗╔═╗╔═╗${c.reset}
308
+ ${c.bold}${c.magenta} ╚╗╔╝║╠╩╗║╣ ╚═╗║ ║ ║╠═╝║╣ ${c.reset}
309
+ ${c.bold}${c.magenta} ╚╝ ╩╚═╝╚═╝╚═╝╚═╝╚═╝╩ ╚═╝${c.reset}
310
+ ${c.dim} AI project tracking for vibe coders${c.reset}
311
+ `);
312
+ // Step 1: Detect agents
313
+ const agents = detectAgents();
314
+ const detected = agents.filter(a => a.detected);
315
+ if (detected.length > 0) {
316
+ console.log(`${icon.check} Detected agents:`);
317
+ detected.forEach(a => console.log(` ${icon.dot} ${a.name}`));
318
+ }
319
+ else {
320
+ console.log(`${icon.warn} No AI agents detected automatically`);
321
+ }
322
+ // Step 2: Select agents
323
+ const selectedIds = await promptCheckboxes('Which agents do you want to configure?', agents.map(a => ({ id: a.id, name: a.name, detected: a.detected })));
324
+ const selectedAgents = agents.filter(a => selectedIds.includes(a.id));
325
+ if (selectedAgents.length === 0) {
326
+ console.log(`\n${icon.cross} No agents selected. Exiting.`);
327
+ process.exit(0);
328
+ }
329
+ // Step 3: API Key
330
+ let apiKey = null;
331
+ // Check env var first
332
+ if (process.env.VIBESCOPE_API_KEY) {
333
+ apiKey = process.env.VIBESCOPE_API_KEY;
334
+ console.log(`\n${icon.check} Using API key from ${c.cyan}VIBESCOPE_API_KEY${c.reset} env var`);
335
+ }
336
+ // Check credentials file
337
+ if (!apiKey) {
338
+ const creds = readCredentials();
339
+ if (creds.apiKey) {
340
+ apiKey = creds.apiKey;
341
+ console.log(`\n${icon.check} Found existing API key in ${c.cyan}~/.vibescope/credentials.json${c.reset}`);
342
+ const reuse = await promptConfirm('Use existing API key?', true);
343
+ if (!reuse)
344
+ apiKey = null;
345
+ }
346
+ }
347
+ // Prompt for key if needed
348
+ if (!apiKey) {
349
+ console.log(`\n${c.bold}API Key Setup${c.reset}`);
350
+ console.log(`${icon.arrow} Get your API key at ${c.cyan}${VIBESCOPE_SETTINGS_URL}${c.reset}`);
351
+ const openIt = await promptConfirm('Open settings page in browser?', true);
352
+ if (openIt)
353
+ await openBrowser(VIBESCOPE_SETTINGS_URL);
354
+ for (let attempt = 0; attempt < 3; attempt++) {
355
+ apiKey = await prompt(`\n${c.bold}Paste your API key:${c.reset} `);
356
+ if (!apiKey) {
357
+ console.log(`${icon.cross} API key is required`);
358
+ continue;
359
+ }
360
+ process.stdout.write(` Validating... `);
361
+ const result = await validateApiKey(apiKey);
362
+ if (result.valid) {
363
+ console.log(`${icon.check} ${result.message}`);
364
+ break;
365
+ }
366
+ else {
367
+ console.log(`${icon.cross} ${result.message}`);
368
+ apiKey = null;
369
+ }
370
+ }
371
+ if (!apiKey) {
372
+ console.log(`\n${icon.cross} Could not get a valid API key. Exiting.`);
373
+ process.exit(1);
374
+ }
375
+ // Store credentials
376
+ try {
377
+ writeCredentials(apiKey);
378
+ console.log(`\n${icon.check} API key saved to ${c.cyan}~/.vibescope/credentials.json${c.reset}`);
379
+ }
380
+ catch (err) {
381
+ console.log(`\n${icon.warn} Could not save credentials: ${err instanceof Error ? err.message : 'unknown error'}`);
382
+ console.log(` ${c.dim}Set VIBESCOPE_API_KEY env var as fallback${c.reset}`);
383
+ }
384
+ }
385
+ // Step 4: Configure each agent
386
+ console.log(`\n${c.bold}Configuring agents...${c.reset}\n`);
387
+ for (const agent of selectedAgents) {
388
+ const hasExisting = checkExistingConfig(agent.id);
389
+ if (hasExisting) {
390
+ const overwrite = await promptConfirm(` ${agent.name} already configured. Update?`, true);
391
+ if (!overwrite) {
392
+ console.log(` ${icon.dot} Skipped ${agent.name}`);
393
+ continue;
394
+ }
395
+ }
396
+ try {
397
+ await agent.configure(apiKey);
398
+ }
399
+ catch (err) {
400
+ console.log(` ${icon.cross} Failed to configure ${agent.name}: ${err instanceof Error ? err.message : 'unknown error'}`);
401
+ }
402
+ }
403
+ // Done!
404
+ console.log(`
405
+ ${c.green}${c.bold}Setup complete!${c.reset}
406
+
407
+ ${c.bold}Next steps:${c.reset}
408
+ ${icon.arrow} Restart your AI agent / IDE
409
+ ${icon.arrow} Start coding — Vibescope tracks automatically
410
+
411
+ ${c.dim}Need help? https://vibescope.dev/docs${c.reset}
412
+ `);
413
+ }
414
+ // ============================================================================
415
+ // CLI Entry Point
416
+ // ============================================================================
417
+ async function main() {
418
+ const args = process.argv.slice(2);
419
+ const command = args[0];
420
+ if (command === 'init' || !command) {
421
+ await runInit();
422
+ }
423
+ else if (command === '--help' || command === '-h' || command === 'help') {
424
+ console.log(`
425
+ ${c.bold}Vibescope CLI${c.reset}
426
+
427
+ Usage:
428
+ npx vibescope init Set up Vibescope for your AI agents
429
+ npx vibescope help Show this help
430
+
431
+ Docs: https://vibescope.dev/docs
432
+ `);
433
+ }
434
+ else {
435
+ console.log(`Unknown command: ${command}\nRun ${c.cyan}npx vibescope help${c.reset} for usage.`);
436
+ process.exit(1);
437
+ }
438
+ }
439
+ const isMainModule = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, '/')}`;
440
+ if (isMainModule || process.argv[1]?.endsWith('cli-init.js')) {
441
+ main().catch((err) => {
442
+ console.error(`${icon.cross} ${err instanceof Error ? err.message : 'Unknown error'}`);
443
+ process.exit(1);
444
+ });
445
+ }
package/dist/cli.js CHANGED
File without changes
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Cloud Agents Handlers
3
+ *
4
+ * Handles cloud agent management:
5
+ * - cleanup_stale_cloud_agents
6
+ * - list_cloud_agents
7
+ */
8
+ import type { Handler, HandlerRegistry } from './types.js';
9
+ /**
10
+ * Clean up stale cloud agents that failed to start or lost connection.
11
+ * Only operates on agents in the specified project (security scoped).
12
+ */
13
+ export declare const cleanupStaleCloudAgents: Handler;
14
+ /**
15
+ * List cloud agents for a project with optional status filter.
16
+ */
17
+ export declare const listCloudAgents: Handler;
18
+ /**
19
+ * Cloud agents handlers registry
20
+ */
21
+ export declare const cloudAgentHandlers: HandlerRegistry;