@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,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
|
+
};
|