@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.
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Screenshot Utility
3
+ *
4
+ * Captures screenshots of running web applications using Playwright.
5
+ */
6
+
7
+ import { chromium } from 'playwright';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+
11
+ /**
12
+ * Takes a screenshot of a running Next.js application
13
+ *
14
+ * @param {Object} options - Screenshot options
15
+ * @param {string} options.url - URL to screenshot (default: http://localhost:3000)
16
+ * @param {number} [options.width=1920] - Viewport width
17
+ * @param {number} [options.height=1080] - Viewport height
18
+ * @param {boolean} [options.fullPage=false] - Capture full page or just viewport
19
+ * @returns {Promise<Object>} Screenshot result with base64 data
20
+ */
21
+ export async function takeScreenshot(options = {}) {
22
+ const {
23
+ url = 'http://localhost:3000',
24
+ width = 1920,
25
+ height = 1080,
26
+ fullPage = false,
27
+ } = options;
28
+
29
+ let browser = null;
30
+
31
+ try {
32
+ console.error(`📸 Taking screenshot of ${url}...`);
33
+
34
+ // Launch browser
35
+ browser = await chromium.launch({
36
+ headless: true,
37
+ });
38
+
39
+ const context = await browser.newContext({
40
+ viewport: { width, height },
41
+ });
42
+
43
+ const page = await context.newPage();
44
+
45
+ // Navigate to the URL
46
+ console.error(` ⏳ Loading page...`);
47
+ await page.goto(url, {
48
+ waitUntil: 'networkidle',
49
+ timeout: 30000,
50
+ });
51
+
52
+ // Wait a bit for any animations/hydration
53
+ await page.waitForTimeout(2000);
54
+
55
+ console.error(` 📷 Capturing screenshot...`);
56
+
57
+ // Take screenshot
58
+ const screenshotBuffer = await page.screenshot({
59
+ type: 'png',
60
+ fullPage: fullPage,
61
+ });
62
+
63
+ await browser.close();
64
+ browser = null;
65
+
66
+ // Convert to base64
67
+ const base64Data = screenshotBuffer.toString('base64');
68
+
69
+ console.error(` ✅ Screenshot captured successfully (${Math.round(base64Data.length / 1024)}KB)`);
70
+
71
+ return {
72
+ success: true,
73
+ data: {
74
+ base64: base64Data,
75
+ mimeType: 'image/png',
76
+ width,
77
+ height,
78
+ url,
79
+ },
80
+ };
81
+ } catch (error) {
82
+ if (browser) {
83
+ await browser.close();
84
+ }
85
+
86
+ console.error(` ❌ Failed to capture screenshot: ${error.message}`);
87
+
88
+ // Check if it's a connection error
89
+ if (error.message.includes('net::ERR_CONNECTION_REFUSED') ||
90
+ error.message.includes('Navigation timeout')) {
91
+ return {
92
+ success: false,
93
+ error: `Could not connect to ${url}. Please make sure your Next.js dev server is running (npm run dev).`,
94
+ hint: 'Run "npm run dev" in your project directory first, then try again.',
95
+ };
96
+ }
97
+
98
+ return {
99
+ success: false,
100
+ error: error.message,
101
+ stack: error.stack,
102
+ };
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Detects if a Next.js dev server is running
108
+ *
109
+ * @param {string} url - URL to check (default: http://localhost:3000)
110
+ * @returns {Promise<Object>} Result indicating if server is running
111
+ */
112
+ export async function checkDevServerRunning(url = 'http://localhost:3000') {
113
+ let browser = null;
114
+
115
+ try {
116
+ browser = await chromium.launch({
117
+ headless: true,
118
+ });
119
+
120
+ const context = await browser.newContext();
121
+ const page = await context.newPage();
122
+
123
+ await page.goto(url, {
124
+ timeout: 5000,
125
+ });
126
+
127
+ await browser.close();
128
+
129
+ return {
130
+ success: true,
131
+ running: true,
132
+ url,
133
+ };
134
+ } catch (error) {
135
+ if (browser) {
136
+ await browser.close();
137
+ }
138
+
139
+ return {
140
+ success: true,
141
+ running: false,
142
+ url,
143
+ message: `Dev server not detected at ${url}`,
144
+ };
145
+ }
146
+ }
147
+
148
+ export default {
149
+ takeScreenshot,
150
+ checkDevServerRunning,
151
+ };
@@ -0,0 +1,366 @@
1
+ /**
2
+ * "use client" Directive Utility
3
+ *
4
+ * Ensures Next.js App Router files that need client-side behavior
5
+ * have the "use client" directive at the top of the file.
6
+ *
7
+ * This is required for files that use:
8
+ * - React hooks (useState, useEffect, useContext, etc.)
9
+ * - Browser APIs (window, document, localStorage, etc.)
10
+ * - Event handlers (onClick, onChange, onSubmit, etc.)
11
+ * - Velt UI components (VeltComments, VeltProvider, etc.)
12
+ */
13
+
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+
17
+ /**
18
+ * Patterns that indicate a file needs "use client"
19
+ */
20
+ const CLIENT_INDICATORS = {
21
+ // React hooks
22
+ hooks: [
23
+ /\buse[A-Z]\w*\s*\(/, // useXxx() pattern
24
+ /\buseState\b/,
25
+ /\buseEffect\b/,
26
+ /\buseContext\b/,
27
+ /\buseRef\b/,
28
+ /\buseCallback\b/,
29
+ /\buseMemo\b/,
30
+ /\buseReducer\b/,
31
+ /\buseLayoutEffect\b/,
32
+ ],
33
+
34
+ // Browser APIs
35
+ browserApis: [
36
+ /\bwindow\b/,
37
+ /\bdocument\b/,
38
+ /\blocalStorage\b/,
39
+ /\bsessionStorage\b/,
40
+ /\bnavigator\b/,
41
+ /\blocation\b/,
42
+ ],
43
+
44
+ // Event handlers in JSX
45
+ eventHandlers: [
46
+ /\bonClick\s*=/,
47
+ /\bonChange\s*=/,
48
+ /\bonSubmit\s*=/,
49
+ /\bonKeyDown\s*=/,
50
+ /\bonKeyUp\s*=/,
51
+ /\bonKeyPress\s*=/,
52
+ /\bonFocus\s*=/,
53
+ /\bonBlur\s*=/,
54
+ /\bonInput\s*=/,
55
+ /\bonMouseEnter\s*=/,
56
+ /\bonMouseLeave\s*=/,
57
+ /\bonScroll\s*=/,
58
+ /\bonLoad\s*=/,
59
+ /\bonError\s*=/,
60
+ ],
61
+
62
+ // Velt client components (these require client-side rendering)
63
+ veltComponents: [
64
+ /\bVeltComments\b/,
65
+ /\bVeltCommentsSidebar\b/,
66
+ /\bVeltProvider\b/,
67
+ /\bVeltPresence\b/,
68
+ /\bVeltCursor\b/,
69
+ /\bVeltCursors\b/,
70
+ /\bVeltNotificationsTool\b/,
71
+ /\bVeltSidebarButton\b/,
72
+ /\bVeltRecorder\b/,
73
+ /\bVeltHuddleTool\b/,
74
+ /\bVeltCommentTool\b/,
75
+ /\bVeltCommentBubble\b/,
76
+ /\bVeltWireframe\b/,
77
+ /\buseVeltClient\b/,
78
+ /\buseSetDocuments\b/,
79
+ ],
80
+ };
81
+
82
+ /**
83
+ * Checks if file content already has "use client" directive
84
+ *
85
+ * @param {string} content - File content
86
+ * @returns {boolean} True if has "use client"
87
+ */
88
+ export function hasUseClientDirective(content) {
89
+ // "use client" must be at the very start (possibly after whitespace/comments)
90
+ const trimmed = content.trimStart();
91
+ return (
92
+ trimmed.startsWith('"use client"') ||
93
+ trimmed.startsWith("'use client'") ||
94
+ trimmed.startsWith('`use client`')
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Checks if file content needs "use client" directive
100
+ *
101
+ * @param {string} content - File content
102
+ * @returns {Object} { needsDirective: boolean, reasons: string[] }
103
+ */
104
+ export function needsUseClientDirective(content) {
105
+ const reasons = [];
106
+
107
+ // Check hooks
108
+ for (const pattern of CLIENT_INDICATORS.hooks) {
109
+ if (pattern.test(content)) {
110
+ reasons.push(`React hook detected: ${pattern.source}`);
111
+ break; // One hook is enough
112
+ }
113
+ }
114
+
115
+ // Check browser APIs
116
+ for (const pattern of CLIENT_INDICATORS.browserApis) {
117
+ if (pattern.test(content)) {
118
+ reasons.push(`Browser API detected: ${pattern.source}`);
119
+ break;
120
+ }
121
+ }
122
+
123
+ // Check event handlers
124
+ for (const pattern of CLIENT_INDICATORS.eventHandlers) {
125
+ if (pattern.test(content)) {
126
+ reasons.push(`Event handler detected: ${pattern.source}`);
127
+ break;
128
+ }
129
+ }
130
+
131
+ // Check Velt components
132
+ for (const pattern of CLIENT_INDICATORS.veltComponents) {
133
+ if (pattern.test(content)) {
134
+ reasons.push(`Velt client component detected: ${pattern.source}`);
135
+ break;
136
+ }
137
+ }
138
+
139
+ return {
140
+ needsDirective: reasons.length > 0,
141
+ reasons,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Ensures file has "use client" directive if needed
147
+ *
148
+ * @param {string} filePath - Path to file (for logging)
149
+ * @param {string} content - File content
150
+ * @returns {Object} { modified: boolean, content: string, reason?: string }
151
+ */
152
+ export function ensureUseClient(filePath, content) {
153
+ // Already has directive
154
+ if (hasUseClientDirective(content)) {
155
+ return {
156
+ modified: false,
157
+ content,
158
+ reason: 'Already has "use client" directive',
159
+ };
160
+ }
161
+
162
+ // Check if needs directive
163
+ const check = needsUseClientDirective(content);
164
+
165
+ if (!check.needsDirective) {
166
+ return {
167
+ modified: false,
168
+ content,
169
+ reason: 'Does not need "use client" (no client-only code detected)',
170
+ };
171
+ }
172
+
173
+ // Add directive at the very top
174
+ const newContent = `"use client";\n\n${content}`;
175
+
176
+ return {
177
+ modified: true,
178
+ content: newContent,
179
+ reason: `Added "use client" - ${check.reasons[0]}`,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Scans a directory for files that need "use client" and optionally fixes them
185
+ *
186
+ * @param {Object} params
187
+ * @param {string} params.projectPath - Project root path
188
+ * @param {string[]} [params.scanPaths] - Relative paths to scan (default: Velt component directories)
189
+ * @param {boolean} [params.fix=false] - Whether to actually fix files
190
+ * @returns {Object} Scan results
191
+ */
192
+ export function scanAndFixUseClient({
193
+ projectPath,
194
+ scanPaths = null,
195
+ fix = false,
196
+ }) {
197
+ const results = {
198
+ scanned: [],
199
+ needsFix: [],
200
+ fixed: [],
201
+ alreadyCorrect: [],
202
+ errors: [],
203
+ };
204
+
205
+ // Default scan paths for Velt components
206
+ const defaultPaths = [
207
+ 'components/velt',
208
+ 'src/components/velt',
209
+ 'app',
210
+ 'src/app',
211
+ ];
212
+
213
+ const pathsToScan = scanPaths || defaultPaths;
214
+
215
+ // Recursively find all .tsx and .jsx files
216
+ function findFiles(dir) {
217
+ const files = [];
218
+
219
+ if (!fs.existsSync(dir)) {
220
+ return files;
221
+ }
222
+
223
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
224
+
225
+ for (const entry of entries) {
226
+ const fullPath = path.join(dir, entry.name);
227
+
228
+ if (entry.isDirectory()) {
229
+ // Skip node_modules and hidden directories
230
+ if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
231
+ files.push(...findFiles(fullPath));
232
+ }
233
+ } else if (entry.isFile()) {
234
+ // Only process .tsx, .jsx, .ts, .js files
235
+ if (/\.(tsx|jsx|ts|js)$/.test(entry.name)) {
236
+ files.push(fullPath);
237
+ }
238
+ }
239
+ }
240
+
241
+ return files;
242
+ }
243
+
244
+ // Scan each path
245
+ for (const scanPath of pathsToScan) {
246
+ const fullScanPath = path.join(projectPath, scanPath);
247
+ const files = findFiles(fullScanPath);
248
+
249
+ for (const filePath of files) {
250
+ const relativePath = path.relative(projectPath, filePath);
251
+ results.scanned.push(relativePath);
252
+
253
+ try {
254
+ const content = fs.readFileSync(filePath, 'utf-8');
255
+ const result = ensureUseClient(filePath, content);
256
+
257
+ if (result.modified) {
258
+ results.needsFix.push({
259
+ path: relativePath,
260
+ reason: result.reason,
261
+ });
262
+
263
+ if (fix) {
264
+ fs.writeFileSync(filePath, result.content, 'utf-8');
265
+ results.fixed.push({
266
+ path: relativePath,
267
+ reason: result.reason,
268
+ });
269
+ }
270
+ } else if (hasUseClientDirective(content)) {
271
+ results.alreadyCorrect.push(relativePath);
272
+ }
273
+ } catch (err) {
274
+ results.errors.push({
275
+ path: relativePath,
276
+ error: err.message,
277
+ });
278
+ }
279
+ }
280
+ }
281
+
282
+ return results;
283
+ }
284
+
285
+ /**
286
+ * Validates "use client" directives in a Next.js project
287
+ *
288
+ * @param {string} projectPath - Project root path
289
+ * @returns {Object} Validation result with checks array
290
+ */
291
+ export function validateUseClientDirectives(projectPath) {
292
+ const checks = [];
293
+ let passed = 0;
294
+
295
+ // Scan Velt component directories
296
+ const scanResult = scanAndFixUseClient({
297
+ projectPath,
298
+ fix: false,
299
+ });
300
+
301
+ // Check: All Velt component files have proper directives
302
+ const veltFiles = scanResult.scanned.filter(f =>
303
+ f.includes('velt') || f.includes('Velt')
304
+ );
305
+
306
+ const veltNeedsFix = scanResult.needsFix.filter(f =>
307
+ f.path.includes('velt') || f.path.includes('Velt')
308
+ );
309
+
310
+ if (veltFiles.length > 0) {
311
+ const allCorrect = veltNeedsFix.length === 0;
312
+
313
+ checks.push({
314
+ name: 'Velt components "use client"',
315
+ status: allCorrect ? 'pass' : 'warning',
316
+ message: allCorrect
317
+ ? `All ${veltFiles.length} Velt component files have correct directives`
318
+ : `${veltNeedsFix.length} Velt component file(s) missing "use client": ${veltNeedsFix.map(f => f.path).join(', ')}`,
319
+ });
320
+
321
+ if (allCorrect) passed++;
322
+ }
323
+
324
+ // Check: App Router files with client code have directives
325
+ const appFiles = scanResult.scanned.filter(f =>
326
+ f.startsWith('app/') || f.startsWith('src/app/')
327
+ );
328
+
329
+ const appNeedsFix = scanResult.needsFix.filter(f =>
330
+ f.path.startsWith('app/') || f.path.startsWith('src/app/')
331
+ );
332
+
333
+ if (appFiles.length > 0) {
334
+ const allCorrect = appNeedsFix.length === 0;
335
+
336
+ checks.push({
337
+ name: 'App Router "use client"',
338
+ status: allCorrect ? 'pass' : 'warning',
339
+ message: allCorrect
340
+ ? `All App Router files have correct directives`
341
+ : `${appNeedsFix.length} App Router file(s) may need "use client": ${appNeedsFix.map(f => f.path).join(', ')}`,
342
+ });
343
+
344
+ if (allCorrect) passed++;
345
+ }
346
+
347
+ return {
348
+ checks,
349
+ passed,
350
+ total: checks.length,
351
+ score: `${passed}/${checks.length}`,
352
+ details: {
353
+ scannedFiles: scanResult.scanned.length,
354
+ needsFix: scanResult.needsFix,
355
+ alreadyCorrect: scanResult.alreadyCorrect.length,
356
+ },
357
+ };
358
+ }
359
+
360
+ export default {
361
+ hasUseClientDirective,
362
+ needsUseClientDirective,
363
+ ensureUseClient,
364
+ scanAndFixUseClient,
365
+ validateUseClientDirectives,
366
+ };