@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,1000 @@
1
+ /**
2
+ * Host App Wiring Discovery
3
+ *
4
+ * Scans the host application codebase to discover integration points for Velt:
5
+ * - Document ID source (route params, query params, DB records, editor state)
6
+ * - User information source (auth providers, user contexts)
7
+ * - JWT authentication patterns
8
+ * - Recommended wiring locations
9
+ *
10
+ * HARD RULE: If scan is unsure, DO NOT guess. Emit explicit questions
11
+ * and tell the user we need a human answer.
12
+ */
13
+
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+
17
+ /**
18
+ * Signal confidence levels
19
+ */
20
+ export const Confidence = {
21
+ HIGH: 'high', // Clear pattern found
22
+ MEDIUM: 'medium', // Likely pattern but needs confirmation
23
+ LOW: 'low', // Possible pattern, should ask user
24
+ NONE: 'none', // No signals found, must ask user
25
+ };
26
+
27
+ /**
28
+ * Discovery result structure
29
+ */
30
+ function createDiscoveryResult() {
31
+ return {
32
+ documentId: {
33
+ signals: [],
34
+ recommendedSource: null,
35
+ confidence: Confidence.NONE,
36
+ questions: [],
37
+ },
38
+ user: {
39
+ signals: [],
40
+ authProvider: null,
41
+ userContextFile: null,
42
+ confidence: Confidence.NONE,
43
+ questions: [],
44
+ },
45
+ setDocuments: {
46
+ signals: [],
47
+ recommendedLocation: null,
48
+ confidence: Confidence.NONE,
49
+ questions: [],
50
+ },
51
+ jwtAuth: {
52
+ signals: [],
53
+ hasJwtPattern: false,
54
+ tokenEndpoint: null,
55
+ confidence: Confidence.NONE,
56
+ questions: [],
57
+ },
58
+ summary: {
59
+ totalSignals: 0,
60
+ questionsForDeveloper: [],
61
+ },
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Scans project files for patterns
67
+ */
68
+ function scanFiles(projectPath, extensions = ['.tsx', '.ts', '.jsx', '.js']) {
69
+ const files = [];
70
+ const dirsToSkip = ['node_modules', '.git', '.next', 'dist', 'build', '.vercel'];
71
+
72
+ function walkDir(dir) {
73
+ try {
74
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
75
+ for (const entry of entries) {
76
+ const fullPath = path.join(dir, entry.name);
77
+ if (entry.isDirectory()) {
78
+ if (!dirsToSkip.includes(entry.name)) {
79
+ walkDir(fullPath);
80
+ }
81
+ } else if (entry.isFile()) {
82
+ const ext = path.extname(entry.name);
83
+ if (extensions.includes(ext)) {
84
+ files.push(fullPath);
85
+ }
86
+ }
87
+ }
88
+ } catch (err) {
89
+ // Skip directories we can't read
90
+ }
91
+ }
92
+
93
+ walkDir(projectPath);
94
+ return files;
95
+ }
96
+
97
+ /**
98
+ * Reads file content safely
99
+ */
100
+ function readFileSafe(filePath) {
101
+ try {
102
+ return fs.readFileSync(filePath, 'utf-8');
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Detects document ID sources in the codebase
110
+ */
111
+ function detectDocumentIdSource(projectPath, files) {
112
+ const result = {
113
+ signals: [],
114
+ recommendedSource: null,
115
+ confidence: Confidence.NONE,
116
+ questions: [],
117
+ };
118
+
119
+ const patterns = {
120
+ // Route params (Next.js App Router)
121
+ routeParams: {
122
+ pattern: /params\s*(?::\s*\{[^}]*\})?\s*=>\s*|useParams\s*\(\s*\)|params\.(\w+)|searchParams\.get\s*\(\s*['"`](\w+)['"`]\s*\)/g,
123
+ type: 'route-param',
124
+ description: 'Dynamic route parameter',
125
+ },
126
+ // Next.js App Router folder pattern [id], [slug], etc.
127
+ dynamicRoute: {
128
+ pattern: /\[(\w+)\]/,
129
+ type: 'dynamic-route',
130
+ description: 'Dynamic route folder',
131
+ checkPath: true,
132
+ },
133
+ // Query params
134
+ queryParams: {
135
+ pattern: /searchParams\.get\s*\(\s*['"`](\w+)['"`]\s*\)|useSearchParams|router\.query\.(\w+)|query\.(\w+)/g,
136
+ type: 'query-param',
137
+ description: 'URL query parameter',
138
+ },
139
+ // Common ID patterns in state/props
140
+ idState: {
141
+ pattern: /(?:document|doc|project|page|editor|item|resource|file)Id\s*[:=]/gi,
142
+ type: 'state-variable',
143
+ description: 'ID in component state or props',
144
+ },
145
+ // Database/API patterns
146
+ dbFetch: {
147
+ pattern: /(?:findOne|findById|getDoc|fetch.*\/(?:document|project|page)s?\/)/gi,
148
+ type: 'db-fetch',
149
+ description: 'Database or API fetch for document',
150
+ },
151
+ // Existing Velt document setup
152
+ veltDocument: {
153
+ pattern: /setDocument\s*\(|useSetDocumentId|VeltInitializeDocument/g,
154
+ type: 'velt-existing',
155
+ description: 'Existing Velt document setup',
156
+ },
157
+ };
158
+
159
+ for (const file of files) {
160
+ const content = readFileSafe(file);
161
+ if (!content) continue;
162
+
163
+ const relativePath = path.relative(projectPath, file);
164
+
165
+ // Check for dynamic route folders
166
+ if (patterns.dynamicRoute.checkPath && patterns.dynamicRoute.pattern.test(relativePath)) {
167
+ const match = relativePath.match(patterns.dynamicRoute.pattern);
168
+ if (match) {
169
+ result.signals.push({
170
+ type: 'dynamic-route',
171
+ file: relativePath,
172
+ paramName: match[1],
173
+ confidence: Confidence.HIGH,
174
+ description: `Dynamic route folder [${match[1]}] - likely document ID source`,
175
+ });
176
+ }
177
+ }
178
+
179
+ // Check content patterns
180
+ for (const [name, patternDef] of Object.entries(patterns)) {
181
+ if (patternDef.checkPath) continue; // Already handled above
182
+
183
+ const matches = content.matchAll(patternDef.pattern);
184
+ for (const match of matches) {
185
+ result.signals.push({
186
+ type: patternDef.type,
187
+ file: relativePath,
188
+ match: match[0].substring(0, 100),
189
+ lineNumber: getLineNumber(content, match.index),
190
+ confidence: patternDef.type === 'velt-existing' ? Confidence.HIGH : Confidence.MEDIUM,
191
+ description: patternDef.description,
192
+ });
193
+ }
194
+ }
195
+ }
196
+
197
+ // Determine recommendation and confidence
198
+ if (result.signals.length === 0) {
199
+ result.confidence = Confidence.NONE;
200
+ result.questions.push({
201
+ question: 'Where does the document ID come from in your application?',
202
+ options: [
203
+ 'URL route parameter (e.g., /documents/[id])',
204
+ 'URL query parameter (e.g., ?docId=123)',
205
+ 'Fetched from database/API based on current page',
206
+ 'Stored in application state (Redux, Context, etc.)',
207
+ 'Not sure / Need help deciding',
208
+ ],
209
+ });
210
+ } else {
211
+ // Prioritize signals
212
+ const highConfidence = result.signals.filter(s => s.confidence === Confidence.HIGH);
213
+ const dynamicRoutes = result.signals.filter(s => s.type === 'dynamic-route');
214
+
215
+ if (dynamicRoutes.length > 0) {
216
+ result.recommendedSource = dynamicRoutes[0];
217
+ result.confidence = Confidence.HIGH;
218
+ } else if (highConfidence.length > 0) {
219
+ result.recommendedSource = highConfidence[0];
220
+ result.confidence = Confidence.HIGH;
221
+ } else {
222
+ result.recommendedSource = result.signals[0];
223
+ result.confidence = Confidence.MEDIUM;
224
+ result.questions.push({
225
+ question: `Found ${result.signals.length} potential document ID sources. Please confirm which one to use:`,
226
+ options: result.signals.slice(0, 4).map(s => `${s.file}:${s.lineNumber || '?'} - ${s.description}`),
227
+ });
228
+ }
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ /**
235
+ * Detects user authentication patterns
236
+ */
237
+ function detectUserSource(projectPath, files) {
238
+ const result = {
239
+ signals: [],
240
+ authProvider: null,
241
+ userContextFile: null,
242
+ confidence: Confidence.NONE,
243
+ questions: [],
244
+ };
245
+
246
+ const authPatterns = {
247
+ nextAuth: {
248
+ pattern: /useSession|getServerSession|NextAuth|next-auth|authOptions/g,
249
+ provider: 'next-auth',
250
+ description: 'Next-Auth authentication',
251
+ },
252
+ clerk: {
253
+ pattern: /useUser|useAuth|ClerkProvider|@clerk\/nextjs|SignedIn|SignedOut/g,
254
+ provider: 'clerk',
255
+ description: 'Clerk authentication',
256
+ },
257
+ auth0: {
258
+ pattern: /useAuth0|Auth0Provider|@auth0\/nextjs-auth0|withPageAuthRequired/g,
259
+ provider: 'auth0',
260
+ description: 'Auth0 authentication',
261
+ },
262
+ firebase: {
263
+ pattern: /useAuthState|firebase\/auth|getAuth|onAuthStateChanged/g,
264
+ provider: 'firebase',
265
+ description: 'Firebase authentication',
266
+ },
267
+ supabase: {
268
+ pattern: /useSupabaseClient|createClientComponentClient|supabase\.auth|@supabase\/auth-helpers/g,
269
+ provider: 'supabase',
270
+ description: 'Supabase authentication',
271
+ },
272
+ customAuth: {
273
+ pattern: /AuthContext|UserContext|useCurrentUser|useAuthenticatedUser|AuthProvider|UserProvider/g,
274
+ provider: 'custom',
275
+ description: 'Custom authentication context',
276
+ },
277
+ };
278
+
279
+ const userDataPatterns = {
280
+ userObject: /(?:const|let|var)\s+(?:user|currentUser|authUser)\s*[:=]/gi,
281
+ userProps: /user\s*[:=]\s*\{[^}]*(?:id|userId|email|name)[^}]*\}/gi,
282
+ userHook: /use(?:User|CurrentUser|Auth|Session)\s*\(/gi,
283
+ };
284
+
285
+ for (const file of files) {
286
+ const content = readFileSafe(file);
287
+ if (!content) continue;
288
+
289
+ const relativePath = path.relative(projectPath, file);
290
+
291
+ // Check for auth providers
292
+ for (const [name, patternDef] of Object.entries(authPatterns)) {
293
+ if (patternDef.pattern.test(content)) {
294
+ result.signals.push({
295
+ type: 'auth-provider',
296
+ provider: patternDef.provider,
297
+ file: relativePath,
298
+ confidence: Confidence.HIGH,
299
+ description: patternDef.description,
300
+ });
301
+ // Reset pattern lastIndex for reuse
302
+ patternDef.pattern.lastIndex = 0;
303
+ }
304
+ }
305
+
306
+ // Check for user data patterns
307
+ for (const [name, pattern] of Object.entries(userDataPatterns)) {
308
+ const matches = content.matchAll(pattern);
309
+ for (const match of matches) {
310
+ result.signals.push({
311
+ type: 'user-data',
312
+ file: relativePath,
313
+ match: match[0].substring(0, 80),
314
+ lineNumber: getLineNumber(content, match.index),
315
+ confidence: Confidence.MEDIUM,
316
+ description: `User data pattern: ${name}`,
317
+ });
318
+ }
319
+ }
320
+ }
321
+
322
+ // Determine auth provider
323
+ const authProviderSignals = result.signals.filter(s => s.type === 'auth-provider');
324
+ const uniqueProviders = [...new Set(authProviderSignals.map(s => s.provider))];
325
+
326
+ if (uniqueProviders.length === 1) {
327
+ result.authProvider = uniqueProviders[0];
328
+ result.confidence = Confidence.HIGH;
329
+ result.userContextFile = authProviderSignals[0].file;
330
+ } else if (uniqueProviders.length > 1) {
331
+ result.confidence = Confidence.LOW;
332
+ result.questions.push({
333
+ question: `Found multiple authentication patterns. Which one is your primary auth provider?`,
334
+ options: uniqueProviders.map(p => `${p} (${authPatterns[p === 'custom' ? 'customAuth' : p]?.description || p})`),
335
+ });
336
+ } else if (result.signals.length > 0) {
337
+ result.confidence = Confidence.MEDIUM;
338
+ result.questions.push({
339
+ question: 'Found user data patterns but no clear auth provider. How do users authenticate?',
340
+ options: [
341
+ 'Next-Auth (NextAuth.js)',
342
+ 'Clerk',
343
+ 'Auth0',
344
+ 'Firebase Auth',
345
+ 'Supabase Auth',
346
+ 'Custom authentication system',
347
+ 'No authentication (using mock/demo users)',
348
+ ],
349
+ });
350
+ } else {
351
+ result.confidence = Confidence.NONE;
352
+ result.questions.push({
353
+ question: 'No authentication patterns detected. How should Velt identify users?',
354
+ options: [
355
+ 'I have authentication - I\'ll provide the integration details',
356
+ 'Use anonymous/demo users for testing',
357
+ 'Help me set up authentication',
358
+ ],
359
+ });
360
+ }
361
+
362
+ return result;
363
+ }
364
+
365
+ /**
366
+ * Detects where setDocuments should be called
367
+ */
368
+ function detectSetDocumentsLocation(projectPath, files, documentIdResult) {
369
+ const result = {
370
+ signals: [],
371
+ recommendedLocation: null,
372
+ confidence: Confidence.NONE,
373
+ questions: [],
374
+ };
375
+
376
+ // Look for layout files, page files, and provider files
377
+ const locationPatterns = {
378
+ layout: /app\/.*layout\.(tsx?|jsx?)/,
379
+ page: /app\/.*page\.(tsx?|jsx?)/,
380
+ provider: /providers?\.(tsx?|jsx?)/i,
381
+ context: /context\.(tsx?|jsx?)/i,
382
+ };
383
+
384
+ for (const file of files) {
385
+ const relativePath = path.relative(projectPath, file);
386
+ const content = readFileSafe(file);
387
+ if (!content) continue;
388
+
389
+ // Check if file matches location patterns
390
+ for (const [type, pattern] of Object.entries(locationPatterns)) {
391
+ if (pattern.test(relativePath)) {
392
+ // Check if file has VeltProvider or could be a good location
393
+ const hasVeltProvider = /VeltProvider/g.test(content);
394
+ const hasUseEffect = /useEffect/g.test(content);
395
+
396
+ result.signals.push({
397
+ type: type,
398
+ file: relativePath,
399
+ hasVeltProvider,
400
+ hasUseEffect,
401
+ confidence: hasVeltProvider ? Confidence.HIGH : Confidence.MEDIUM,
402
+ description: `${type} file - ${hasVeltProvider ? 'already has VeltProvider' : 'potential location'}`,
403
+ });
404
+ }
405
+ }
406
+ }
407
+
408
+ // If we found dynamic routes for documentId, the page in that route is ideal
409
+ if (documentIdResult.recommendedSource?.type === 'dynamic-route') {
410
+ const routeFolder = documentIdResult.recommendedSource.file;
411
+ const pageInRoute = result.signals.find(s =>
412
+ s.file.includes(path.dirname(routeFolder)) && s.type === 'page'
413
+ );
414
+ if (pageInRoute) {
415
+ result.recommendedLocation = pageInRoute;
416
+ result.confidence = Confidence.HIGH;
417
+ }
418
+ }
419
+
420
+ // Check for existing VeltProvider
421
+ const veltProviderLocation = result.signals.find(s => s.hasVeltProvider);
422
+ if (veltProviderLocation && !result.recommendedLocation) {
423
+ result.recommendedLocation = veltProviderLocation;
424
+ result.confidence = Confidence.HIGH;
425
+ }
426
+
427
+ // Default to root layout or page
428
+ if (!result.recommendedLocation && result.signals.length > 0) {
429
+ const rootLayout = result.signals.find(s => s.file === 'app/layout.tsx' || s.file === 'src/app/layout.tsx');
430
+ const rootPage = result.signals.find(s => s.file === 'app/page.tsx' || s.file === 'src/app/page.tsx');
431
+
432
+ result.recommendedLocation = rootLayout || rootPage || result.signals[0];
433
+ result.confidence = Confidence.MEDIUM;
434
+ result.questions.push({
435
+ question: 'Where should VeltProvider and document initialization be set up?',
436
+ options: [
437
+ `${result.recommendedLocation.file} (Recommended)`,
438
+ ...result.signals.slice(0, 3).filter(s => s !== result.recommendedLocation).map(s => s.file),
439
+ 'Other location (I\'ll specify)',
440
+ ],
441
+ });
442
+ }
443
+
444
+ if (!result.recommendedLocation) {
445
+ result.confidence = Confidence.NONE;
446
+ result.questions.push({
447
+ question: 'Could not detect where to set up Velt. Which file should contain VeltProvider?',
448
+ options: [
449
+ 'app/layout.tsx (root layout)',
450
+ 'app/page.tsx (root page)',
451
+ 'A specific feature page (I\'ll specify the path)',
452
+ 'Help me understand where it should go',
453
+ ],
454
+ });
455
+ }
456
+
457
+ return result;
458
+ }
459
+
460
+ /**
461
+ * Detects JWT authentication patterns
462
+ */
463
+ function detectJwtAuth(projectPath, files) {
464
+ const result = {
465
+ signals: [],
466
+ hasJwtPattern: false,
467
+ tokenEndpoint: null,
468
+ confidence: Confidence.NONE,
469
+ questions: [],
470
+ };
471
+
472
+ const jwtPatterns = {
473
+ jwtSign: /jwt\.sign|jsonwebtoken|jose|sign\s*\(\s*\{/gi,
474
+ jwtVerify: /jwt\.verify|jwtVerify|verifyToken/gi,
475
+ tokenRoute: /api\/.*token|\/auth\/token|generateToken/gi,
476
+ bearerAuth: /Bearer\s+|Authorization.*Bearer/gi,
477
+ veltToken: /velt.*token|VELT_AUTH_TOKEN/gi,
478
+ };
479
+
480
+ for (const file of files) {
481
+ const content = readFileSafe(file);
482
+ if (!content) continue;
483
+
484
+ const relativePath = path.relative(projectPath, file);
485
+
486
+ for (const [name, pattern] of Object.entries(jwtPatterns)) {
487
+ if (pattern.test(content)) {
488
+ pattern.lastIndex = 0; // Reset for reuse
489
+
490
+ const isTokenRoute = relativePath.includes('api/') && relativePath.includes('token');
491
+
492
+ result.signals.push({
493
+ type: name,
494
+ file: relativePath,
495
+ isTokenRoute,
496
+ confidence: isTokenRoute ? Confidence.HIGH : Confidence.MEDIUM,
497
+ description: `JWT pattern: ${name}`,
498
+ });
499
+
500
+ if (isTokenRoute && !result.tokenEndpoint) {
501
+ result.tokenEndpoint = relativePath;
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ result.hasJwtPattern = result.signals.length > 0;
508
+
509
+ if (result.tokenEndpoint) {
510
+ result.confidence = Confidence.HIGH;
511
+ } else if (result.hasJwtPattern) {
512
+ result.confidence = Confidence.MEDIUM;
513
+ result.questions.push({
514
+ question: 'JWT patterns detected but no token endpoint found. Does your app need JWT auth for Velt?',
515
+ options: [
516
+ 'Yes, I have a token endpoint (I\'ll provide the path)',
517
+ 'Yes, but I need to create one',
518
+ 'No, I\'ll use simple authentication',
519
+ 'Not sure - explain the options',
520
+ ],
521
+ });
522
+ } else {
523
+ result.confidence = Confidence.LOW;
524
+ // Don't ask about JWT by default - it's optional
525
+ }
526
+
527
+ return result;
528
+ }
529
+
530
+ /**
531
+ * Gets line number from content and index
532
+ */
533
+ function getLineNumber(content, index) {
534
+ if (index === undefined) return null;
535
+ const lines = content.substring(0, index).split('\n');
536
+ return lines.length;
537
+ }
538
+
539
+ /**
540
+ * Main discovery function
541
+ *
542
+ * @param {string} projectPath - Path to the project
543
+ * @returns {Object} Discovery results with signals, recommendations, and questions
544
+ */
545
+ export function discoverHostAppWiring(projectPath) {
546
+ const result = createDiscoveryResult();
547
+
548
+ console.error(' 🔍 Scanning host app for integration signals...');
549
+
550
+ // Scan all relevant files
551
+ const files = scanFiles(projectPath);
552
+ console.error(` Found ${files.length} source files to analyze`);
553
+
554
+ // Run all detections
555
+ result.documentId = detectDocumentIdSource(projectPath, files);
556
+ console.error(` Document ID: ${result.documentId.signals.length} signals (${result.documentId.confidence} confidence)`);
557
+
558
+ result.user = detectUserSource(projectPath, files);
559
+ console.error(` User auth: ${result.user.signals.length} signals (${result.user.confidence} confidence)`);
560
+
561
+ result.setDocuments = detectSetDocumentsLocation(projectPath, files, result.documentId);
562
+ console.error(` Setup location: ${result.setDocuments.signals.length} signals (${result.setDocuments.confidence} confidence)`);
563
+
564
+ result.jwtAuth = detectJwtAuth(projectPath, files);
565
+ console.error(` JWT auth: ${result.jwtAuth.signals.length} signals (${result.jwtAuth.confidence} confidence)`);
566
+
567
+ // Build summary
568
+ result.summary.totalSignals =
569
+ result.documentId.signals.length +
570
+ result.user.signals.length +
571
+ result.setDocuments.signals.length +
572
+ result.jwtAuth.signals.length;
573
+
574
+ // Collect all questions
575
+ const allQuestions = [
576
+ ...result.documentId.questions,
577
+ ...result.user.questions,
578
+ ...result.setDocuments.questions,
579
+ ...result.jwtAuth.questions,
580
+ ];
581
+
582
+ result.summary.questionsForDeveloper = allQuestions;
583
+ result.summary.hasUnansweredQuestions = allQuestions.length > 0;
584
+
585
+ console.error(` ✅ Discovery complete: ${result.summary.totalSignals} signals, ${allQuestions.length} questions for developer`);
586
+
587
+ return result;
588
+ }
589
+
590
+ /**
591
+ * Formats discovery results for plan output
592
+ *
593
+ * @param {Object} discovery - Discovery results from discoverHostAppWiring
594
+ * @returns {string} Markdown-formatted findings
595
+ */
596
+ export function formatDiscoveryForPlan(discovery) {
597
+ let output = `\n## 🔍 Integration Findings (Host App Wiring Discovery)\n\n`;
598
+
599
+ // Document ID section
600
+ output += `### Document ID Source\n`;
601
+ if (discovery.documentId.confidence === Confidence.HIGH && discovery.documentId.recommendedSource) {
602
+ output += `✅ **Recommended**: ${discovery.documentId.recommendedSource.description}\n`;
603
+ output += ` - File: \`${discovery.documentId.recommendedSource.file}\`\n`;
604
+ if (discovery.documentId.recommendedSource.paramName) {
605
+ output += ` - Parameter: \`${discovery.documentId.recommendedSource.paramName}\`\n`;
606
+ }
607
+ } else if (discovery.documentId.signals.length > 0) {
608
+ output += `⚠️ **Found ${discovery.documentId.signals.length} potential sources** (needs confirmation):\n`;
609
+ for (const signal of discovery.documentId.signals.slice(0, 3)) {
610
+ output += ` - ${signal.file}: ${signal.description}\n`;
611
+ }
612
+ } else {
613
+ output += `❓ **No document ID source detected**\n`;
614
+ }
615
+ output += '\n';
616
+
617
+ // User Auth section
618
+ output += `### User Authentication\n`;
619
+ if (discovery.user.authProvider) {
620
+ output += `✅ **Detected**: ${discovery.user.authProvider}\n`;
621
+ if (discovery.user.userContextFile) {
622
+ output += ` - Context file: \`${discovery.user.userContextFile}\`\n`;
623
+ }
624
+ } else if (discovery.user.signals.length > 0) {
625
+ output += `⚠️ **Found patterns but unclear provider**:\n`;
626
+ for (const signal of discovery.user.signals.slice(0, 3)) {
627
+ output += ` - ${signal.file}: ${signal.description}\n`;
628
+ }
629
+ } else {
630
+ output += `❓ **No authentication pattern detected**\n`;
631
+ }
632
+ output += '\n';
633
+
634
+ // Setup Location section
635
+ output += `### Recommended Setup Location\n`;
636
+ if (discovery.setDocuments.recommendedLocation) {
637
+ output += `✅ **Recommended file**: \`${discovery.setDocuments.recommendedLocation.file}\`\n`;
638
+ output += ` - Type: ${discovery.setDocuments.recommendedLocation.type}\n`;
639
+ if (discovery.setDocuments.recommendedLocation.hasVeltProvider) {
640
+ output += ` - Already has VeltProvider ✓\n`;
641
+ }
642
+ } else {
643
+ output += `❓ **Could not determine optimal setup location**\n`;
644
+ }
645
+ output += '\n';
646
+
647
+ // JWT Auth section (only if relevant)
648
+ if (discovery.jwtAuth.hasJwtPattern) {
649
+ output += `### JWT Authentication\n`;
650
+ if (discovery.jwtAuth.tokenEndpoint) {
651
+ output += `✅ **Token endpoint found**: \`${discovery.jwtAuth.tokenEndpoint}\`\n`;
652
+ } else {
653
+ output += `⚠️ **JWT patterns found but no token endpoint**\n`;
654
+ }
655
+ output += '\n';
656
+ }
657
+
658
+ // Questions for Developer section
659
+ if (discovery.summary.questionsForDeveloper.length > 0) {
660
+ output += `### ❓ Questions for Developer\n\n`;
661
+ output += `**IMPORTANT**: The following items need your input before proceeding:\n\n`;
662
+ for (let i = 0; i < discovery.summary.questionsForDeveloper.length; i++) {
663
+ const q = discovery.summary.questionsForDeveloper[i];
664
+ output += `**${i + 1}. ${q.question}**\n`;
665
+ for (const option of q.options) {
666
+ output += ` - ${option}\n`;
667
+ }
668
+ output += '\n';
669
+ }
670
+ }
671
+
672
+ return output;
673
+ }
674
+
675
+ /**
676
+ * Formats discovery results for user verification (structured format)
677
+ * Used when user says YES to scanning - they need to verify findings
678
+ *
679
+ * @param {Object} discovery - Discovery results from discoverHostAppWiring
680
+ * @returns {string} Markdown-formatted verification UI
681
+ */
682
+ export function formatDiscoveryForVerification(discovery) {
683
+ let output = `## 🔍 Discovery Scan Results - Please Verify\n\n`;
684
+ output += `Found **${discovery.summary.totalSignals}** integration signals. Please review and confirm:\n\n`;
685
+
686
+ // Document ID section
687
+ output += `### A) Document ID Source\n`;
688
+ if (discovery.documentId.recommendedSource) {
689
+ const src = discovery.documentId.recommendedSource;
690
+ output += `| Field | Detected Value |\n|-------|----------------|\n`;
691
+ output += `| Method | ${src.type || 'unknown'} |\n`;
692
+ output += `| File | \`${src.file || 'not detected'}\` |\n`;
693
+ output += `| Variable | \`${src.paramName || src.variableName || 'not detected'}\` |\n`;
694
+ output += `| Confidence | ${discovery.documentId.confidence} |\n\n`;
695
+ } else {
696
+ output += `⚠️ **Not detected** - You'll need to provide this information.\n\n`;
697
+ }
698
+
699
+ // User Auth section
700
+ output += `### B) User Authentication\n`;
701
+ if (discovery.user.authProvider) {
702
+ output += `| Field | Detected Value |\n|-------|----------------|\n`;
703
+ output += `| Provider | ${discovery.user.authProvider} |\n`;
704
+ output += `| Context File | \`${discovery.user.userContextFile || 'not detected'}\` |\n`;
705
+ output += `| Confidence | ${discovery.user.confidence} |\n\n`;
706
+ } else if (discovery.user.signals.length > 0) {
707
+ output += `⚠️ **Possible providers detected** (needs confirmation):\n`;
708
+ const uniqueProviders = [...new Set(discovery.user.signals.filter(s => s.provider).map(s => s.provider))];
709
+ output += uniqueProviders.map(p => `- ${p}`).join('\n') + '\n\n';
710
+ } else {
711
+ output += `⚠️ **Not detected** - You'll need to provide this information.\n\n`;
712
+ }
713
+
714
+ // Setup Location section
715
+ output += `### C) Velt Setup Location\n`;
716
+ if (discovery.setDocuments.recommendedLocation) {
717
+ const loc = discovery.setDocuments.recommendedLocation;
718
+ output += `| Field | Detected Value |\n|-------|----------------|\n`;
719
+ output += `| Location Type | ${loc.type || 'unknown'} |\n`;
720
+ output += `| File | \`${loc.file || 'not detected'}\` |\n`;
721
+ output += `| Has VeltProvider | ${loc.hasVeltProvider ? 'Yes' : 'No'} |\n`;
722
+ output += `| Confidence | ${discovery.setDocuments.confidence} |\n\n`;
723
+ } else {
724
+ output += `⚠️ **Not detected** - You'll need to provide this information.\n\n`;
725
+ }
726
+
727
+ // JWT Auth section
728
+ output += `### D) JWT/Auth Token\n`;
729
+ if (discovery.jwtAuth.hasJwtPattern) {
730
+ output += `| Field | Detected Value |\n|-------|----------------|\n`;
731
+ output += `| Uses JWT | Yes |\n`;
732
+ output += `| Token Endpoint | \`${discovery.jwtAuth.tokenEndpoint || 'not detected'}\` |\n`;
733
+ output += `| Confidence | ${discovery.jwtAuth.confidence} |\n\n`;
734
+ } else {
735
+ output += `| Field | Detected Value |\n|-------|----------------|\n`;
736
+ output += `| Uses JWT | Not detected |\n\n`;
737
+ }
738
+
739
+ output += `---\n\n`;
740
+ output += `**Please verify**: Are these findings correct?\n`;
741
+
742
+ return output;
743
+ }
744
+
745
+ /**
746
+ * Returns the questionnaire structure for manual wiring
747
+ * Used when user says NO to scanning
748
+ *
749
+ * @returns {Object} Questionnaire structure with questions and options
750
+ */
751
+ export function getManualWiringQuestionnaire() {
752
+ return {
753
+ documentId: {
754
+ question: 'How do you obtain the documentId in this app?',
755
+ options: [
756
+ { value: 'query-param', label: 'From URL query param (e.g., ?documentId=...)' },
757
+ { value: 'route-param', label: 'From route param (e.g., /docs/[id])' },
758
+ { value: 'database', label: 'From database record created on page load' },
759
+ { value: 'storage', label: 'From localStorage/sessionStorage' },
760
+ { value: 'other', label: 'Other (describe)' },
761
+ ],
762
+ followUp: [
763
+ { field: 'filePath', question: 'Which file/component currently reads/creates it? (path)' },
764
+ { field: 'variableName', question: 'What is the variable name?' },
765
+ { field: 'example', question: 'Example value (optional)' },
766
+ ],
767
+ },
768
+ user: {
769
+ question: 'How do you get the current user (userId + name/email/photo)?',
770
+ options: [
771
+ { value: 'next-auth', label: 'next-auth session' },
772
+ { value: 'clerk', label: 'Clerk' },
773
+ { value: 'firebase', label: 'Firebase auth' },
774
+ { value: 'supabase', label: 'Supabase auth' },
775
+ { value: 'custom-api', label: 'Custom /api/me endpoint' },
776
+ { value: 'other', label: 'Other' },
777
+ ],
778
+ followUp: [
779
+ { field: 'filePath', question: 'Which file/hook provides it? (path)' },
780
+ { field: 'fields', question: 'What fields are available? (userId, name, email, photoUrl)' },
781
+ ],
782
+ },
783
+ auth: {
784
+ question: 'Do you use a JWT or auth token for API calls?',
785
+ options: [
786
+ { value: 'cookie', label: 'Yes, stored in cookie' },
787
+ { value: 'localStorage', label: 'Yes, stored in localStorage' },
788
+ { value: 'provider-sdk', label: 'Yes, managed by auth provider SDK (I don\'t directly handle it)' },
789
+ { value: 'none', label: 'No token needed' },
790
+ { value: 'unsure', label: 'Unsure' },
791
+ ],
792
+ followUp: [
793
+ { field: 'source', question: 'Where is it obtained? (login endpoint, session object, middleware, etc.)' },
794
+ { field: 'refresh', question: 'Is there a refresh flow? (yes/no)' },
795
+ ],
796
+ },
797
+ insertion: {
798
+ question: 'Where should we call setDocuments / initialize Velt?',
799
+ options: [
800
+ { value: 'root-layout', label: 'Root layout/provider file (e.g. app/layout.tsx or pages/_app.tsx)' },
801
+ { value: 'specific-page', label: 'Specific page (e.g. app/page.tsx)' },
802
+ { value: 'editor-wrapper', label: 'Editor wrapper component' },
803
+ { value: 'other', label: 'Other (describe)' },
804
+ ],
805
+ followUp: [
806
+ { field: 'filePath', question: 'What file path should we modify?' },
807
+ ],
808
+ },
809
+ };
810
+ }
811
+
812
+ /**
813
+ * Creates normalized wiring object from manual questionnaire answers
814
+ *
815
+ * @param {Object} manualWiring - Manual wiring answers from user
816
+ * @returns {Object} Normalized wiring object for plan generation
817
+ */
818
+ export function createWiringFromManualAnswers(manualWiring) {
819
+ const wiring = {
820
+ source: 'manual',
821
+ documentId: null,
822
+ user: null,
823
+ auth: null,
824
+ insertion: null,
825
+ todos: [],
826
+ };
827
+
828
+ // Process documentId
829
+ if (manualWiring.documentId) {
830
+ const d = manualWiring.documentId;
831
+ if (d.unsure) {
832
+ wiring.todos.push('TODO: Determine document ID source - user was unsure');
833
+ wiring.documentId = { method: 'unknown', unsure: true };
834
+ } else {
835
+ wiring.documentId = {
836
+ method: d.method,
837
+ filePath: d.filePath || null,
838
+ variableName: d.variableName || null,
839
+ example: d.example || null,
840
+ };
841
+ }
842
+ } else {
843
+ wiring.todos.push('TODO: Document ID source not provided');
844
+ }
845
+
846
+ // Process user
847
+ if (manualWiring.user) {
848
+ const u = manualWiring.user;
849
+ if (u.unsure) {
850
+ wiring.todos.push('TODO: Determine user authentication source - user was unsure');
851
+ wiring.user = { providerType: 'unknown', unsure: true };
852
+ } else {
853
+ wiring.user = {
854
+ providerType: u.providerType,
855
+ filePath: u.filePath || null,
856
+ fields: u.fields || ['userId', 'name', 'email'],
857
+ };
858
+ }
859
+ } else {
860
+ wiring.todos.push('TODO: User authentication source not provided');
861
+ }
862
+
863
+ // Process auth
864
+ if (manualWiring.auth) {
865
+ const a = manualWiring.auth;
866
+ if (a.unsure || a.storage === 'unsure') {
867
+ wiring.todos.push('TODO: Determine JWT/auth token handling - user was unsure');
868
+ wiring.auth = { usesToken: null, unsure: true };
869
+ } else {
870
+ wiring.auth = {
871
+ usesToken: a.usesToken !== false && a.storage !== 'none',
872
+ storage: a.storage || 'none',
873
+ source: a.source || null,
874
+ refresh: a.refresh || false,
875
+ };
876
+ }
877
+ } else {
878
+ // Auth is optional, don't add TODO
879
+ wiring.auth = { usesToken: false, storage: 'none' };
880
+ }
881
+
882
+ // Process insertion
883
+ if (manualWiring.insertion) {
884
+ const i = manualWiring.insertion;
885
+ if (i.unsure) {
886
+ wiring.todos.push('TODO: Determine Velt initialization location - user was unsure');
887
+ wiring.insertion = { locationType: 'unknown', unsure: true };
888
+ } else {
889
+ wiring.insertion = {
890
+ locationType: i.locationType,
891
+ filePath: i.filePath || null,
892
+ };
893
+ }
894
+ } else {
895
+ wiring.todos.push('TODO: Velt initialization location not provided');
896
+ }
897
+
898
+ return wiring;
899
+ }
900
+
901
+ /**
902
+ * Resolves final wiring from discovery verification (merges overrides)
903
+ *
904
+ * @param {Object} discovery - Discovery results from scan
905
+ * @param {Object} verification - Verification response from user
906
+ * @returns {Object} Resolved wiring object for plan generation
907
+ */
908
+ export function resolveWiringFromVerification(discovery, verification) {
909
+ const wiring = {
910
+ source: verification.status === 'confirmed' ? 'verified-discovery' : 'partial-manual',
911
+ documentId: null,
912
+ user: null,
913
+ auth: null,
914
+ insertion: null,
915
+ todos: [],
916
+ };
917
+
918
+ // If user said UNSURE, everything becomes a TODO
919
+ if (verification.status === 'unsure') {
920
+ wiring.source = 'needs-clarification';
921
+ wiring.todos.push('TODO: Document ID source needs human clarification');
922
+ wiring.todos.push('TODO: User authentication needs human clarification');
923
+ wiring.todos.push('TODO: Velt initialization location needs human clarification');
924
+ wiring.documentId = { method: 'unknown', unsure: true };
925
+ wiring.user = { providerType: 'unknown', unsure: true };
926
+ wiring.auth = { usesToken: null, unsure: true };
927
+ wiring.insertion = { locationType: 'unknown', unsure: true };
928
+ return wiring;
929
+ }
930
+
931
+ const overrides = verification.overrides || {};
932
+
933
+ // Document ID - use override or discovery
934
+ if (overrides.documentId) {
935
+ wiring.documentId = overrides.documentId;
936
+ } else if (discovery.documentId.recommendedSource) {
937
+ const src = discovery.documentId.recommendedSource;
938
+ wiring.documentId = {
939
+ method: src.type || 'route-param',
940
+ filePath: src.file,
941
+ variableName: src.paramName || src.variableName,
942
+ };
943
+ } else {
944
+ wiring.todos.push('TODO: Document ID source not confirmed');
945
+ wiring.documentId = { method: 'unknown', unsure: true };
946
+ }
947
+
948
+ // User - use override or discovery
949
+ if (overrides.user) {
950
+ wiring.user = overrides.user;
951
+ } else if (discovery.user.authProvider) {
952
+ wiring.user = {
953
+ providerType: discovery.user.authProvider,
954
+ filePath: discovery.user.userContextFile,
955
+ fields: ['userId', 'name', 'email', 'photoUrl'],
956
+ };
957
+ } else {
958
+ wiring.todos.push('TODO: User authentication source not confirmed');
959
+ wiring.user = { providerType: 'unknown', unsure: true };
960
+ }
961
+
962
+ // Auth - use override or discovery
963
+ if (overrides.auth) {
964
+ wiring.auth = overrides.auth;
965
+ } else if (discovery.jwtAuth.hasJwtPattern) {
966
+ wiring.auth = {
967
+ usesToken: true,
968
+ storage: 'unknown',
969
+ source: discovery.jwtAuth.tokenEndpoint,
970
+ };
971
+ } else {
972
+ wiring.auth = { usesToken: false, storage: 'none' };
973
+ }
974
+
975
+ // Insertion - use override or discovery
976
+ if (overrides.insertion) {
977
+ wiring.insertion = overrides.insertion;
978
+ } else if (discovery.setDocuments.recommendedLocation) {
979
+ const loc = discovery.setDocuments.recommendedLocation;
980
+ wiring.insertion = {
981
+ locationType: loc.type || 'specific-page',
982
+ filePath: loc.file,
983
+ };
984
+ } else {
985
+ wiring.todos.push('TODO: Velt initialization location not confirmed');
986
+ wiring.insertion = { locationType: 'unknown', unsure: true };
987
+ }
988
+
989
+ return wiring;
990
+ }
991
+
992
+ export default {
993
+ discoverHostAppWiring,
994
+ formatDiscoveryForPlan,
995
+ formatDiscoveryForVerification,
996
+ getManualWiringQuestionnaire,
997
+ createWiringFromManualAnswers,
998
+ resolveWiringFromVerification,
999
+ Confidence,
1000
+ };