@velt-js/mcp-installer 0.1.0
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 +373 -0
- package/bin/mcp-server.js +22 -0
- package/package.json +42 -0
- package/src/index.js +754 -0
- package/src/tools/orchestrator.js +299 -0
- package/src/tools/unified-installer.js +886 -0
- package/src/utils/cli.js +380 -0
- package/src/utils/comment-detector.js +305 -0
- package/src/utils/config.js +149 -0
- package/src/utils/framework-detection.js +262 -0
- package/src/utils/header-positioning.js +146 -0
- package/src/utils/host-app-discovery.js +1000 -0
- package/src/utils/integration.js +803 -0
- package/src/utils/plan-formatter.js +1698 -0
- package/src/utils/screenshot.js +151 -0
- package/src/utils/use-client.js +366 -0
- package/src/utils/validation.js +556 -0
- package/src/utils/velt-docs-fetcher.js +288 -0
- package/src/utils/velt-docs-urls.js +140 -0
- package/src/utils/velt-mcp-client.js +202 -0
- package/src/utils/velt-mcp.js +718 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Velt MCP Query Utilities
|
|
3
|
+
*
|
|
4
|
+
* Queries the Velt Docs MCP server for implementation patterns and best practices
|
|
5
|
+
*
|
|
6
|
+
* Velt Docs MCP Server: https://docs.velt.dev/mcp
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import https from 'https';
|
|
10
|
+
import { URL } from 'url';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Queries Velt documentation for implementation patterns
|
|
16
|
+
*
|
|
17
|
+
* Tries three strategies in order:
|
|
18
|
+
* 1. Velt Docs MCP server (https://docs.velt.dev/mcp)
|
|
19
|
+
* 2. Direct URL fetch (https://docs.velt.dev/async-collaboration/comments/setup/freestyle)
|
|
20
|
+
* 3. Hardcoded fallback patterns
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} params
|
|
23
|
+
* @param {string} params.question - Question to ask Velt MCP
|
|
24
|
+
* @returns {Promise<Object>} Query result with patterns
|
|
25
|
+
*/
|
|
26
|
+
export async function queryVeltMCP({ question }) {
|
|
27
|
+
const query = question || 'How do I implement freestyle comments in Next.js app router?';
|
|
28
|
+
const veltDocsMCPUrl = 'https://docs.velt.dev/mcp';
|
|
29
|
+
const veltDocsUrl = 'https://docs.velt.dev/async-collaboration/comments/setup/freestyle';
|
|
30
|
+
|
|
31
|
+
console.error('🔍 Fetching Velt documentation for implementation patterns...');
|
|
32
|
+
|
|
33
|
+
// ========================================================================
|
|
34
|
+
// Strategy 1: Try Velt Docs MCP server first
|
|
35
|
+
// ========================================================================
|
|
36
|
+
console.error(' Strategy 1: Attempting Velt Docs MCP server...');
|
|
37
|
+
console.error(` MCP URL: ${veltDocsMCPUrl}`);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
if (typeof fetch !== 'undefined') {
|
|
41
|
+
// Node 18+ with native fetch
|
|
42
|
+
console.error(' Using native fetch (Node 18+)');
|
|
43
|
+
|
|
44
|
+
// Try to discover available tools
|
|
45
|
+
console.error(' Step 1: Discovering available tools...');
|
|
46
|
+
let toolsListResponse;
|
|
47
|
+
try {
|
|
48
|
+
toolsListResponse = await fetch(veltDocsMCPUrl, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
'Accept': 'application/json',
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
jsonrpc: '2.0',
|
|
56
|
+
method: 'tools/list',
|
|
57
|
+
id: Date.now(),
|
|
58
|
+
}),
|
|
59
|
+
signal: AbortSignal.timeout(5000), // Reduced to 5 seconds
|
|
60
|
+
});
|
|
61
|
+
} catch (listError) {
|
|
62
|
+
throw new Error(`Failed to connect to Velt Docs MCP: ${listError.message}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!toolsListResponse.ok) {
|
|
66
|
+
throw new Error(`HTTP ${toolsListResponse.status}: ${toolsListResponse.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const toolsList = await toolsListResponse.json();
|
|
70
|
+
|
|
71
|
+
if (toolsList.error) {
|
|
72
|
+
throw new Error(`MCP error: ${toolsList.error.message || 'Unknown error'}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const availableTools = toolsList.result?.tools || [];
|
|
76
|
+
console.error(` ✓ Found ${availableTools.length} available tools`);
|
|
77
|
+
|
|
78
|
+
if (availableTools.length > 0) {
|
|
79
|
+
console.error(` Tools: ${availableTools.map(t => t.name).join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Find the appropriate tool for querying documentation
|
|
83
|
+
const searchTool = availableTools.find(t =>
|
|
84
|
+
t.name.toLowerCase().includes('search') ||
|
|
85
|
+
t.name.toLowerCase().includes('query') ||
|
|
86
|
+
t.name.toLowerCase().includes('docs') ||
|
|
87
|
+
t.name.toLowerCase().includes('velt')
|
|
88
|
+
) || availableTools[0];
|
|
89
|
+
|
|
90
|
+
if (!searchTool) {
|
|
91
|
+
throw new Error('No tools available in Velt Docs MCP server');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.error(` Step 2: Calling tool: ${searchTool.name}`);
|
|
95
|
+
|
|
96
|
+
// Call the tool
|
|
97
|
+
const response = await fetch(veltDocsMCPUrl, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
'Accept': 'application/json',
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
jsonrpc: '2.0',
|
|
105
|
+
method: 'tools/call',
|
|
106
|
+
id: Date.now(),
|
|
107
|
+
params: {
|
|
108
|
+
name: searchTool.name,
|
|
109
|
+
arguments: {
|
|
110
|
+
query: query,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
signal: AbortSignal.timeout(5000), // Reduced to 5 seconds for faster failure
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const errorText = await response.text();
|
|
119
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const mcpResponse = await response.json();
|
|
123
|
+
|
|
124
|
+
// Parse the MCP response
|
|
125
|
+
if (mcpResponse.error) {
|
|
126
|
+
throw new Error(mcpResponse.error.message || 'MCP query failed');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Extract patterns from the response
|
|
130
|
+
const result = mcpResponse.result || mcpResponse;
|
|
131
|
+
const patterns = extractPatternsFromMCPResponse(result);
|
|
132
|
+
|
|
133
|
+
// Show what patterns were found
|
|
134
|
+
const foundPatterns = [];
|
|
135
|
+
if (patterns.providerPattern) foundPatterns.push('VeltProvider');
|
|
136
|
+
if (patterns.commentsPattern) foundPatterns.push('VeltComments');
|
|
137
|
+
if (patterns.sidebarPattern) foundPatterns.push('VeltCommentsSidebar');
|
|
138
|
+
if (patterns.environmentPattern) foundPatterns.push('Environment variables');
|
|
139
|
+
|
|
140
|
+
console.error('✅ Successfully queried Velt Docs MCP server!');
|
|
141
|
+
console.error(` ✓ Found patterns: ${foundPatterns.length > 0 ? foundPatterns.join(', ') : 'Using fallback patterns'}`);
|
|
142
|
+
console.error(` ✓ Source: Velt Docs MCP (${veltDocsMCPUrl})`);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
data: patterns,
|
|
147
|
+
query: query,
|
|
148
|
+
source: 'velt-docs-mcp',
|
|
149
|
+
message: `✅ Successfully queried Velt Docs MCP server and extracted patterns from real documentation`,
|
|
150
|
+
};
|
|
151
|
+
} else {
|
|
152
|
+
// Node < 18: Use https module for MCP
|
|
153
|
+
console.error(' Using https module (Node < 18)');
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const toolsList = await makeHttpsRequest(veltDocsMCPUrl, {
|
|
157
|
+
jsonrpc: '2.0',
|
|
158
|
+
method: 'tools/list',
|
|
159
|
+
id: Date.now(),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const toolName = toolsList.result?.tools?.find(t =>
|
|
163
|
+
t.name.toLowerCase().includes('search') ||
|
|
164
|
+
t.name.toLowerCase().includes('query') ||
|
|
165
|
+
t.name.toLowerCase().includes('docs')
|
|
166
|
+
)?.name || toolsList.result?.tools?.[0]?.name || 'query_docs';
|
|
167
|
+
|
|
168
|
+
console.error(` Discovered tool: ${toolName}`);
|
|
169
|
+
|
|
170
|
+
const mcpResponse = await makeHttpsRequest(veltDocsMCPUrl, {
|
|
171
|
+
jsonrpc: '2.0',
|
|
172
|
+
method: 'tools/call',
|
|
173
|
+
id: Date.now(),
|
|
174
|
+
params: {
|
|
175
|
+
name: toolName,
|
|
176
|
+
arguments: {
|
|
177
|
+
query: query,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (mcpResponse.error) {
|
|
183
|
+
throw new Error(mcpResponse.error.message || 'MCP query failed');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const result = mcpResponse.result || mcpResponse;
|
|
187
|
+
const patterns = extractPatternsFromMCPResponse(result);
|
|
188
|
+
|
|
189
|
+
console.error('✅ Successfully queried Velt Docs MCP server!');
|
|
190
|
+
console.error(` ✓ Source: Velt Docs MCP (${veltDocsMCPUrl})`);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
data: patterns,
|
|
195
|
+
query: query,
|
|
196
|
+
source: 'velt-docs-mcp',
|
|
197
|
+
message: `✅ Successfully queried Velt Docs MCP server and extracted patterns from real documentation`,
|
|
198
|
+
};
|
|
199
|
+
} catch (mcpError) {
|
|
200
|
+
throw mcpError;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch (mcpError) {
|
|
204
|
+
// ========================================================================
|
|
205
|
+
// Strategy 2: Fallback to direct URL fetch
|
|
206
|
+
// ========================================================================
|
|
207
|
+
console.error(` ❌ MCP server failed: ${mcpError.message}`);
|
|
208
|
+
console.error(' Strategy 2: Falling back to direct URL fetch...');
|
|
209
|
+
console.error(` URL: ${veltDocsUrl}`);
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
// Fetch documentation page directly
|
|
213
|
+
console.error(` Fetching documentation page...`);
|
|
214
|
+
|
|
215
|
+
let htmlContent;
|
|
216
|
+
|
|
217
|
+
if (typeof fetch !== 'undefined') {
|
|
218
|
+
// Node 18+ with native fetch
|
|
219
|
+
console.error(' Using native fetch (Node 18+)');
|
|
220
|
+
|
|
221
|
+
const response = await fetch(veltDocsUrl, {
|
|
222
|
+
headers: {
|
|
223
|
+
'Accept': 'text/html',
|
|
224
|
+
'User-Agent': 'Velt-MCP-Installer/1.0',
|
|
225
|
+
},
|
|
226
|
+
signal: AbortSignal.timeout(5000), // Reduced to 5 seconds
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
htmlContent = await response.text();
|
|
234
|
+
} else {
|
|
235
|
+
// Node < 18: Use https module
|
|
236
|
+
console.error(' Using https module (Node < 18)');
|
|
237
|
+
htmlContent = await fetchHtmlPage(veltDocsUrl);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.error(' ✓ Successfully fetched documentation page');
|
|
241
|
+
|
|
242
|
+
// Extract patterns from HTML content
|
|
243
|
+
console.error(' 📖 Extracting code patterns from documentation...');
|
|
244
|
+
const patterns = extractPatternsFromDocsContent(htmlContent, query);
|
|
245
|
+
|
|
246
|
+
// Show what patterns were found
|
|
247
|
+
const foundPatterns = [];
|
|
248
|
+
if (patterns.providerPattern?.code) foundPatterns.push('VeltProvider');
|
|
249
|
+
if (patterns.commentsPattern?.code) foundPatterns.push('VeltComments');
|
|
250
|
+
if (patterns.sidebarPattern?.code) foundPatterns.push('VeltCommentsSidebar');
|
|
251
|
+
if (patterns.environmentPattern?.code) foundPatterns.push('Environment variables');
|
|
252
|
+
|
|
253
|
+
console.error('✅ Successfully extracted patterns from Velt documentation!');
|
|
254
|
+
console.error(` ✓ Found patterns: ${foundPatterns.length > 0 ? foundPatterns.join(', ') : 'Using fallback patterns'}`);
|
|
255
|
+
console.error(` ✓ Source: Velt Docs URL (${veltDocsUrl})`);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
data: patterns,
|
|
260
|
+
query: query,
|
|
261
|
+
source: 'velt-docs-url',
|
|
262
|
+
message: `✅ Successfully fetched and extracted patterns from Velt documentation (fallback from MCP)`,
|
|
263
|
+
};
|
|
264
|
+
} catch (urlError) {
|
|
265
|
+
// ========================================================================
|
|
266
|
+
// Strategy 3: Fallback to hardcoded patterns
|
|
267
|
+
// ========================================================================
|
|
268
|
+
console.error(` ❌ URL fetch failed: ${urlError.message}`);
|
|
269
|
+
console.error(' Strategy 3: Using fallback patterns based on known best practices');
|
|
270
|
+
|
|
271
|
+
const fallbackPatterns = getFallbackPatterns();
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
success: true,
|
|
275
|
+
data: fallbackPatterns,
|
|
276
|
+
query: query,
|
|
277
|
+
source: 'fallback',
|
|
278
|
+
warning: 'Using fallback patterns. Both MCP and URL fetch failed.',
|
|
279
|
+
message: `⚠️ Using fallback patterns (MCP and URL fetch failed). Patterns are based on known best practices.`,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Makes HTTPS request using Node.js built-in https module
|
|
287
|
+
* (for Node < 18 compatibility)
|
|
288
|
+
*
|
|
289
|
+
* @param {string} url - URL to request
|
|
290
|
+
* @param {Object} data - JSON data to send
|
|
291
|
+
* @param {number} timeout - Timeout in milliseconds (default: 5000)
|
|
292
|
+
* @returns {Promise<Object>} Parsed JSON response
|
|
293
|
+
*/
|
|
294
|
+
function makeHttpsRequest(url, data, timeout = 5000) {
|
|
295
|
+
// Wrap in Promise.race to guarantee timeout
|
|
296
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
297
|
+
setTimeout(() => {
|
|
298
|
+
reject(new Error(`Request timeout after ${timeout}ms`));
|
|
299
|
+
}, timeout);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const requestPromise = new Promise((resolve, reject) => {
|
|
303
|
+
const urlObj = new URL(url);
|
|
304
|
+
const postData = JSON.stringify(data);
|
|
305
|
+
|
|
306
|
+
const options = {
|
|
307
|
+
hostname: urlObj.hostname,
|
|
308
|
+
port: urlObj.port || 443,
|
|
309
|
+
path: urlObj.pathname,
|
|
310
|
+
method: 'POST',
|
|
311
|
+
headers: {
|
|
312
|
+
'Content-Type': 'application/json',
|
|
313
|
+
'Accept': 'application/json, text/event-stream',
|
|
314
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
315
|
+
},
|
|
316
|
+
timeout: timeout,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const req = https.request(options, (res) => {
|
|
320
|
+
let responseData = '';
|
|
321
|
+
let responseSize = 0;
|
|
322
|
+
const maxResponseSize = 10 * 1024 * 1024; // 10MB max
|
|
323
|
+
|
|
324
|
+
res.on('data', (chunk) => {
|
|
325
|
+
responseSize += chunk.length;
|
|
326
|
+
|
|
327
|
+
// Prevent memory exhaustion
|
|
328
|
+
if (responseSize > maxResponseSize) {
|
|
329
|
+
req.destroy();
|
|
330
|
+
reject(new Error(`Response too large (exceeded ${maxResponseSize} bytes)`));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
responseData += chunk;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
res.on('end', () => {
|
|
338
|
+
try {
|
|
339
|
+
const parsed = JSON.parse(responseData);
|
|
340
|
+
resolve(parsed);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
reject(new Error(`Failed to parse response: ${error.message}`));
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
req.on('error', (error) => {
|
|
348
|
+
reject(new Error(`Request error: ${error.message}`));
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
req.on('timeout', () => {
|
|
352
|
+
req.destroy();
|
|
353
|
+
reject(new Error('Socket timeout'));
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
req.write(postData);
|
|
357
|
+
req.end();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return Promise.race([requestPromise, timeoutPromise]);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Extracts code patterns from Velt MCP response
|
|
365
|
+
*
|
|
366
|
+
* @param {Object} mcpResult - Result from Velt Docs MCP server
|
|
367
|
+
* @returns {Object} Extracted patterns
|
|
368
|
+
*/
|
|
369
|
+
function extractPatternsFromMCPResponse(mcpResult) {
|
|
370
|
+
// The MCP response contains documentation content
|
|
371
|
+
// We need to parse it to extract code patterns
|
|
372
|
+
|
|
373
|
+
const content = mcpResult?.content || [];
|
|
374
|
+
let patterns = {
|
|
375
|
+
summary: 'Freestyle comments implementation patterns from Velt documentation',
|
|
376
|
+
providerPattern: null,
|
|
377
|
+
commentsPattern: null,
|
|
378
|
+
sidebarPattern: null,
|
|
379
|
+
environmentPattern: null,
|
|
380
|
+
rawContent: content,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Parse content to find code examples
|
|
384
|
+
for (const item of content) {
|
|
385
|
+
if (item.type === 'text') {
|
|
386
|
+
const text = item.text;
|
|
387
|
+
|
|
388
|
+
// Look for VeltProvider pattern
|
|
389
|
+
if (text.includes('VeltProvider') && !patterns.providerPattern) {
|
|
390
|
+
patterns.providerPattern = extractCodeBlock(text, 'VeltProvider');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Look for VeltComments pattern
|
|
394
|
+
if (text.includes('VeltComments') && !text.includes('VeltCommentsSidebar') && !patterns.commentsPattern) {
|
|
395
|
+
patterns.commentsPattern = extractCodeBlock(text, 'VeltComments');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Look for VeltCommentsSidebar pattern
|
|
399
|
+
if (text.includes('VeltCommentsSidebar') && !patterns.sidebarPattern) {
|
|
400
|
+
patterns.sidebarPattern = extractCodeBlock(text, 'VeltCommentsSidebar');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Look for environment variables
|
|
404
|
+
if (text.includes('NEXT_PUBLIC_VELT_API_KEY') && !patterns.environmentPattern) {
|
|
405
|
+
patterns.environmentPattern = extractCodeBlock(text, 'NEXT_PUBLIC_VELT_API_KEY');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// If we didn't find patterns, use fallback
|
|
411
|
+
if (!patterns.providerPattern || !patterns.commentsPattern) {
|
|
412
|
+
const fallback = getFallbackPatterns();
|
|
413
|
+
return {
|
|
414
|
+
...fallback,
|
|
415
|
+
source: 'mcp-with-fallback',
|
|
416
|
+
rawContent: content,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return patterns;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Extracts code block from text
|
|
425
|
+
*
|
|
426
|
+
* @param {string} text - Text to search
|
|
427
|
+
* @param {string} keyword - Keyword to find
|
|
428
|
+
* @returns {Object|null} Extracted code pattern
|
|
429
|
+
*/
|
|
430
|
+
function extractCodeBlock(text, keyword) {
|
|
431
|
+
// Try to find code blocks containing the keyword
|
|
432
|
+
const codeBlockRegex = /```(?:tsx?|jsx?|javascript|typescript)?\n([\s\S]*?)```/g;
|
|
433
|
+
let match;
|
|
434
|
+
|
|
435
|
+
while ((match = codeBlockRegex.exec(text)) !== null) {
|
|
436
|
+
if (match[1].includes(keyword)) {
|
|
437
|
+
return {
|
|
438
|
+
code: match[1].trim(),
|
|
439
|
+
description: `Code pattern for ${keyword}`,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Fetches HTML page using https module (for Node < 18)
|
|
449
|
+
*
|
|
450
|
+
* @param {string} url - URL to fetch
|
|
451
|
+
* @returns {Promise<string>} HTML content
|
|
452
|
+
*/
|
|
453
|
+
function fetchHtmlPage(url) {
|
|
454
|
+
return new Promise((resolve, reject) => {
|
|
455
|
+
const urlObj = new URL(url);
|
|
456
|
+
|
|
457
|
+
const options = {
|
|
458
|
+
hostname: urlObj.hostname,
|
|
459
|
+
port: urlObj.port || 443,
|
|
460
|
+
path: urlObj.pathname + (urlObj.search || ''),
|
|
461
|
+
method: 'GET',
|
|
462
|
+
headers: {
|
|
463
|
+
'Accept': 'text/html',
|
|
464
|
+
'User-Agent': 'Velt-MCP-Installer/1.0',
|
|
465
|
+
},
|
|
466
|
+
timeout: 5000, // Reduced to 5 seconds for faster failure
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const req = https.request(options, (res) => {
|
|
470
|
+
let htmlContent = '';
|
|
471
|
+
|
|
472
|
+
res.on('data', (chunk) => {
|
|
473
|
+
htmlContent += chunk;
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
res.on('end', () => {
|
|
477
|
+
if (res.statusCode !== 200) {
|
|
478
|
+
reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
479
|
+
} else {
|
|
480
|
+
resolve(htmlContent);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
req.on('error', (error) => {
|
|
486
|
+
reject(error);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
req.on('timeout', () => {
|
|
490
|
+
req.destroy();
|
|
491
|
+
reject(new Error('Request timeout'));
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
req.end();
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Extracts patterns from Velt documentation website content
|
|
500
|
+
*
|
|
501
|
+
* @param {string} htmlContent - HTML content from docs.velt.dev
|
|
502
|
+
* @param {string} query - Original query
|
|
503
|
+
* @returns {Object} Extracted patterns
|
|
504
|
+
*/
|
|
505
|
+
function extractPatternsFromDocsContent(htmlContent, query) {
|
|
506
|
+
// Start with fallback patterns
|
|
507
|
+
const patterns = getFallbackPatterns();
|
|
508
|
+
|
|
509
|
+
// Try to extract code blocks from HTML
|
|
510
|
+
// Look for <pre><code> blocks or markdown code fences in the HTML
|
|
511
|
+
|
|
512
|
+
// Extract code blocks from HTML
|
|
513
|
+
const codeBlockRegex = /<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi;
|
|
514
|
+
const codeBlocks = [];
|
|
515
|
+
let match;
|
|
516
|
+
|
|
517
|
+
while ((match = codeBlockRegex.exec(htmlContent)) !== null) {
|
|
518
|
+
const code = match[1]
|
|
519
|
+
.replace(/</g, '<')
|
|
520
|
+
.replace(/>/g, '>')
|
|
521
|
+
.replace(/&/g, '&')
|
|
522
|
+
.replace(/"/g, '"')
|
|
523
|
+
.trim();
|
|
524
|
+
|
|
525
|
+
if (code.length > 20) { // Only keep substantial code blocks
|
|
526
|
+
codeBlocks.push(code);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Try to find patterns in code blocks
|
|
531
|
+
for (const code of codeBlocks) {
|
|
532
|
+
// Look for VeltProvider pattern
|
|
533
|
+
if (code.includes('VeltProvider') && !patterns.providerPattern?.code) {
|
|
534
|
+
patterns.providerPattern = {
|
|
535
|
+
description: 'Wrap root layout with VeltProvider',
|
|
536
|
+
code: code,
|
|
537
|
+
location: 'app/layout.tsx',
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Look for VeltComments pattern
|
|
542
|
+
if (code.includes('VeltComments') && !code.includes('VeltCommentsSidebar') && !patterns.commentsPattern?.code) {
|
|
543
|
+
patterns.commentsPattern = {
|
|
544
|
+
description: 'Add VeltComments component to enable freestyle comments',
|
|
545
|
+
code: code,
|
|
546
|
+
location: 'app/page.tsx',
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Look for VeltCommentsSidebar pattern
|
|
551
|
+
if (code.includes('VeltCommentsSidebar') && !patterns.sidebarPattern?.code) {
|
|
552
|
+
patterns.sidebarPattern = {
|
|
553
|
+
description: 'Add VeltCommentsSidebar for comment UI',
|
|
554
|
+
code: code,
|
|
555
|
+
location: 'app/layout.tsx or components',
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Look for environment variables
|
|
560
|
+
if (code.includes('NEXT_PUBLIC_VELT_API_KEY') && !patterns.environmentPattern?.code) {
|
|
561
|
+
patterns.environmentPattern = {
|
|
562
|
+
description: 'Environment variables needed',
|
|
563
|
+
code: code,
|
|
564
|
+
location: '.env.local',
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Mark source if we found patterns
|
|
570
|
+
if (codeBlocks.length > 0) {
|
|
571
|
+
patterns.source = 'extracted-from-docs';
|
|
572
|
+
patterns.summary = 'Freestyle comments implementation patterns extracted from Velt documentation';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return patterns;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Detects libraries in the project by checking package.json
|
|
580
|
+
*
|
|
581
|
+
* @param {string} projectPath - Project root path
|
|
582
|
+
* @returns {Object} Library detection flags
|
|
583
|
+
*/
|
|
584
|
+
export function detectLibraries(projectPath) {
|
|
585
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
586
|
+
|
|
587
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
588
|
+
return {
|
|
589
|
+
hasReactFlow: false,
|
|
590
|
+
hasTiptap: false,
|
|
591
|
+
hasCodeMirror: false,
|
|
592
|
+
hasAgGrid: false,
|
|
593
|
+
hasTanStack: false,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
599
|
+
const allDeps = {
|
|
600
|
+
...(packageJson.dependencies || {}),
|
|
601
|
+
...(packageJson.devDependencies || {}),
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Check for ReactFlow (multiple possible package names)
|
|
605
|
+
const hasReactFlow = !!(
|
|
606
|
+
allDeps['reactflow'] ||
|
|
607
|
+
allDeps['@xyflow/react'] ||
|
|
608
|
+
allDeps['react-flow-renderer'] ||
|
|
609
|
+
allDeps['@reactflow/core']
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Check for Tiptap
|
|
613
|
+
const hasTiptap = !!(
|
|
614
|
+
allDeps['@tiptap/react'] ||
|
|
615
|
+
allDeps['@tiptap/core'] ||
|
|
616
|
+
allDeps['tiptap']
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
// Check for CodeMirror
|
|
620
|
+
const hasCodeMirror = !!(
|
|
621
|
+
allDeps['@codemirror/state'] ||
|
|
622
|
+
allDeps['@codemirror/view'] ||
|
|
623
|
+
allDeps['codemirror']
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
// Check for AG-Grid
|
|
627
|
+
const hasAgGrid = !!(
|
|
628
|
+
allDeps['ag-grid-react'] ||
|
|
629
|
+
allDeps['ag-grid-community'] ||
|
|
630
|
+
allDeps['ag-grid-enterprise']
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
// Check for TanStack Table
|
|
634
|
+
const hasTanStack = !!(
|
|
635
|
+
allDeps['@tanstack/react-table'] ||
|
|
636
|
+
allDeps['@tanstack/table-core']
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
return {
|
|
640
|
+
hasReactFlow,
|
|
641
|
+
hasTiptap,
|
|
642
|
+
hasCodeMirror,
|
|
643
|
+
hasAgGrid,
|
|
644
|
+
hasTanStack,
|
|
645
|
+
};
|
|
646
|
+
} catch (error) {
|
|
647
|
+
console.error(`Error detecting libraries: ${error.message}`);
|
|
648
|
+
return {
|
|
649
|
+
hasReactFlow: false,
|
|
650
|
+
hasTiptap: false,
|
|
651
|
+
hasCodeMirror: false,
|
|
652
|
+
hasAgGrid: false,
|
|
653
|
+
hasTanStack: false,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Fallback patterns if MCP server is unavailable
|
|
660
|
+
*
|
|
661
|
+
* @returns {Object} Hardcoded patterns
|
|
662
|
+
*/
|
|
663
|
+
function getFallbackPatterns() {
|
|
664
|
+
return {
|
|
665
|
+
summary: 'Freestyle comments implementation patterns for Next.js App Router',
|
|
666
|
+
providerPattern: {
|
|
667
|
+
description: 'Wrap root layout with VeltProvider',
|
|
668
|
+
code: `import { VeltProvider } from '@veltdev/react';
|
|
669
|
+
|
|
670
|
+
export default function RootLayout({ children }) {
|
|
671
|
+
return (
|
|
672
|
+
<html>
|
|
673
|
+
<body>
|
|
674
|
+
<VeltProvider>
|
|
675
|
+
{children}
|
|
676
|
+
</VeltProvider>
|
|
677
|
+
</body>
|
|
678
|
+
</html>
|
|
679
|
+
);
|
|
680
|
+
}`,
|
|
681
|
+
location: 'app/layout.tsx',
|
|
682
|
+
},
|
|
683
|
+
commentsPattern: {
|
|
684
|
+
description: 'Add VeltComments component to enable freestyle comments',
|
|
685
|
+
code: `import { VeltComments } from '@veltdev/react';
|
|
686
|
+
|
|
687
|
+
export default function Page() {
|
|
688
|
+
return (
|
|
689
|
+
<div>
|
|
690
|
+
<VeltComments />
|
|
691
|
+
{/* Your page content */}
|
|
692
|
+
</div>
|
|
693
|
+
);
|
|
694
|
+
}`,
|
|
695
|
+
location: 'app/page.tsx',
|
|
696
|
+
},
|
|
697
|
+
sidebarPattern: {
|
|
698
|
+
description: 'Add VeltCommentsSidebar for comment UI',
|
|
699
|
+
code: `import { VeltCommentsSidebar } from '@veltdev/react';
|
|
700
|
+
|
|
701
|
+
export default function Layout({ children }) {
|
|
702
|
+
return (
|
|
703
|
+
<>
|
|
704
|
+
{children}
|
|
705
|
+
<VeltCommentsSidebar />
|
|
706
|
+
</>
|
|
707
|
+
);
|
|
708
|
+
}`,
|
|
709
|
+
location: 'app/layout.tsx or components',
|
|
710
|
+
},
|
|
711
|
+
environmentPattern: {
|
|
712
|
+
description: 'Environment variables needed',
|
|
713
|
+
code: `NEXT_PUBLIC_VELT_API_KEY=your_api_key_here`,
|
|
714
|
+
location: '.env.local',
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|