sonance-brand-mcp 1.3.19 → 1.3.20

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,532 @@
1
+ import { NextResponse } from "next/server";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import Anthropic from "@anthropic-ai/sdk";
5
+
6
+ /**
7
+ * Sonance DevTools API - Vision Mode Editor
8
+ *
9
+ * Uses Claude's vision capabilities to understand page screenshots and modify
10
+ * multiple files based on natural language requests.
11
+ *
12
+ * Supports actions:
13
+ * - "edit": Analyze screenshot + code context, generate multi-file modifications
14
+ * - "save": Write all modified files to disk
15
+ *
16
+ * DEVELOPMENT ONLY.
17
+ */
18
+
19
+ interface VisionFocusedElement {
20
+ name: string;
21
+ type: string;
22
+ variantId?: string;
23
+ coordinates: {
24
+ x: number;
25
+ y: number;
26
+ width: number;
27
+ height: number;
28
+ };
29
+ description?: string;
30
+ }
31
+
32
+ interface VisionFileModification {
33
+ filePath: string;
34
+ originalContent: string;
35
+ modifiedContent: string;
36
+ diff: string;
37
+ explanation: string;
38
+ previewCSS?: string;
39
+ }
40
+
41
+ interface VisionEditRequest {
42
+ action: "edit" | "save";
43
+ screenshot?: string;
44
+ pageRoute: string;
45
+ userPrompt: string;
46
+ focusedElements?: VisionFocusedElement[];
47
+ modifications?: VisionFileModification[];
48
+ }
49
+
50
+ interface VisionEditResponse {
51
+ success: boolean;
52
+ modifications?: VisionFileModification[];
53
+ aggregatedPreviewCSS?: string;
54
+ explanation?: string;
55
+ reasoning?: string;
56
+ error?: string;
57
+ }
58
+
59
+ const VISION_SYSTEM_PROMPT = `You are a React/Tailwind CSS expert with vision capabilities for the Sonance brand system.
60
+
61
+ You can see screenshots of web pages and understand their visual layout, then modify code to implement requested changes.
62
+
63
+ ═══════════════════════════════════════════════════════════════════════════════
64
+ CRITICAL RULES
65
+ ═══════════════════════════════════════════════════════════════════════════════
66
+
67
+ **ANALYSIS:**
68
+ 1. CAREFULLY analyze the screenshot to understand the current visual state
69
+ 2. Identify elements mentioned in the user's request
70
+ 3. Understand the current styling and layout
71
+ 4. Consider how changes will affect the overall design
72
+
73
+ **PRESERVATION RULES:**
74
+ 5. NEVER delete or remove existing content, children, or JSX elements
75
+ 6. NEVER change component structure unless specifically requested
76
+ 7. NEVER modify TypeScript types, imports, or exports unless necessary
77
+ 8. NEVER remove data-sonance-* attributes
78
+
79
+ **CHANGE RULES:**
80
+ 9. Make ONLY the changes requested by the user
81
+ 10. Modify the minimum amount of code necessary
82
+ 11. Use semantic Tailwind classes (bg-primary, text-foreground, etc.)
83
+ 12. Maintain dark mode compatibility with CSS variables
84
+ 13. Keep the cn() utility for className merging
85
+
86
+ **SONANCE BRAND COLORS:**
87
+ - Charcoal: #333F48, #343D46 (primary)
88
+ - Silver: #E2E2E2, #D1D1D6 (secondary)
89
+ - IPORT Orange: #FC4C02
90
+ - IPORT Dark: #0F161D
91
+ - Blaze Blue: #00A3E1
92
+ - Blaze Red: #C02B0A
93
+
94
+ **RESPONSE FORMAT:**
95
+ Return ONLY a valid JSON object. Do not include any conversational text before or after the JSON.
96
+ The JSON must include:
97
+ - "reasoning": Brief explanation of what you see in the screenshot and your plan
98
+ - "modifications": Array of file modifications, each with:
99
+ - "filePath": Path to the file
100
+ - "modifiedContent": Complete updated file content
101
+ - "explanation": What changed in this file
102
+ - "previewCSS": CSS for live preview (use [data-sonance-name="ComponentName"] selectors)
103
+ - "aggregatedPreviewCSS": Combined CSS for all changes
104
+ - "explanation": Overall summary of changes`;
105
+
106
+ export async function POST(request: Request) {
107
+ // Only allow in development
108
+ if (process.env.NODE_ENV !== "development") {
109
+ return NextResponse.json(
110
+ { error: "Vision edit is only available in development mode" },
111
+ { status: 403 }
112
+ );
113
+ }
114
+
115
+ try {
116
+ const body: VisionEditRequest = await request.json();
117
+ const { action, screenshot, pageRoute, userPrompt, focusedElements, modifications } = body;
118
+
119
+ const projectRoot = process.cwd();
120
+
121
+ // Handle save action - write all modified files
122
+ if (action === "save") {
123
+ if (!modifications || modifications.length === 0) {
124
+ return NextResponse.json(
125
+ { error: "modifications array is required for save action" },
126
+ { status: 400 }
127
+ );
128
+ }
129
+
130
+ // Create backups first
131
+ const backups: { filePath: string; content: string }[] = [];
132
+ for (const mod of modifications) {
133
+ const fullPath = path.join(projectRoot, mod.filePath);
134
+ if (fs.existsSync(fullPath)) {
135
+ backups.push({
136
+ filePath: mod.filePath,
137
+ content: fs.readFileSync(fullPath, "utf-8"),
138
+ });
139
+ }
140
+ }
141
+
142
+ try {
143
+ // Write all files
144
+ for (const mod of modifications) {
145
+ const fullPath = path.join(projectRoot, mod.filePath);
146
+
147
+ // Validate path is within project
148
+ if (!fullPath.startsWith(projectRoot)) {
149
+ throw new Error(`Invalid file path: ${mod.filePath}`);
150
+ }
151
+
152
+ fs.writeFileSync(fullPath, mod.modifiedContent, "utf-8");
153
+ }
154
+
155
+ return NextResponse.json({
156
+ success: true,
157
+ message: `Successfully updated ${modifications.length} file(s)`,
158
+ });
159
+ } catch (writeError) {
160
+ // Rollback on error
161
+ for (const backup of backups) {
162
+ try {
163
+ const fullPath = path.join(projectRoot, backup.filePath);
164
+ fs.writeFileSync(fullPath, backup.content, "utf-8");
165
+ } catch {
166
+ console.error(`Failed to rollback ${backup.filePath}`);
167
+ }
168
+ }
169
+ throw writeError;
170
+ }
171
+ }
172
+
173
+ // Handle edit action (Vision AI modification)
174
+ if (action === "edit") {
175
+ if (!userPrompt) {
176
+ return NextResponse.json(
177
+ { error: "userPrompt is required for edit action" },
178
+ { status: 400 }
179
+ );
180
+ }
181
+
182
+ // Check for API key
183
+ const apiKey = process.env.NEXT_PUBLIC_CLAUDE_API_KEY;
184
+ if (!apiKey) {
185
+ return NextResponse.json(
186
+ { error: "NEXT_PUBLIC_CLAUDE_API_KEY not configured. Add it to your .env.local file." },
187
+ { status: 500 }
188
+ );
189
+ }
190
+
191
+ // Gather page context
192
+ const pageContext = gatherPageContext(pageRoute, projectRoot);
193
+
194
+ // Build user message with vision
195
+ const messageContent: Anthropic.MessageCreateParams["messages"][0]["content"] = [];
196
+
197
+ // Add screenshot if provided
198
+ if (screenshot) {
199
+ const base64Data = screenshot.split(",")[1] || screenshot;
200
+ messageContent.push({
201
+ type: "image",
202
+ source: {
203
+ type: "base64",
204
+ media_type: "image/png",
205
+ data: base64Data,
206
+ },
207
+ });
208
+ }
209
+
210
+ // Build text content
211
+ let textContent = `VISION MODE EDIT REQUEST
212
+
213
+ Page Route: ${pageRoute}
214
+ User Request: "${userPrompt}"
215
+
216
+ `;
217
+
218
+ if (focusedElements && focusedElements.length > 0) {
219
+ textContent += `FOCUSED ELEMENTS (user clicked on these):
220
+ ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
221
+
222
+ `;
223
+ }
224
+
225
+ textContent += `PAGE CONTEXT:
226
+
227
+ Page File: ${pageContext.pageFile || "Not found"}
228
+ ${pageContext.pageContent ? `\`\`\`tsx\n${pageContext.pageContent}\n\`\`\`` : ""}
229
+
230
+ `;
231
+
232
+ if (pageContext.componentSources.length > 0) {
233
+ textContent += `IMPORTED COMPONENTS:\n`;
234
+ for (const comp of pageContext.componentSources) {
235
+ textContent += `
236
+ File: ${comp.path}
237
+ \`\`\`tsx
238
+ ${comp.content.substring(0, 3000)}${comp.content.length > 3000 ? "\n// ... (truncated)" : ""}
239
+ \`\`\`
240
+ `;
241
+ }
242
+ }
243
+
244
+ textContent += `
245
+ GLOBALS.CSS (relevant theme variables):
246
+ \`\`\`css
247
+ ${pageContext.globalsCSS.substring(0, 2000)}${pageContext.globalsCSS.length > 2000 ? "\n/* ... (truncated) */" : ""}
248
+ \`\`\`
249
+
250
+ INSTRUCTIONS:
251
+ 1. Look at the screenshot and identify elements mentioned in the user's request
252
+ 2. Review the code to understand current implementation
253
+ 3. Determine which files need modifications
254
+ 4. Generate complete modified code for each file
255
+ 5. Provide previewCSS for immediate visual feedback
256
+ 6. Return as JSON in the specified format`;
257
+
258
+ messageContent.push({
259
+ type: "text",
260
+ text: textContent,
261
+ });
262
+
263
+ // Call Claude Vision API
264
+ const anthropic = new Anthropic({ apiKey });
265
+
266
+ const response = await anthropic.messages.create({
267
+ model: "claude-sonnet-4-20250514",
268
+ max_tokens: 16384,
269
+ messages: [
270
+ {
271
+ role: "user",
272
+ content: messageContent,
273
+ },
274
+ ],
275
+ system: VISION_SYSTEM_PROMPT,
276
+ });
277
+
278
+ // Extract text content from response
279
+ const textResponse = response.content.find((block) => block.type === "text");
280
+ if (!textResponse || textResponse.type !== "text") {
281
+ return NextResponse.json(
282
+ { error: "No text response from AI" },
283
+ { status: 500 }
284
+ );
285
+ }
286
+
287
+ // Parse AI response
288
+ let aiResponse: {
289
+ reasoning?: string;
290
+ modifications: Array<{
291
+ filePath: string;
292
+ modifiedContent: string;
293
+ explanation: string;
294
+ previewCSS?: string;
295
+ }>;
296
+ aggregatedPreviewCSS?: string;
297
+ explanation?: string;
298
+ };
299
+
300
+ try {
301
+ let jsonText = textResponse.text.trim();
302
+
303
+ // Try to extract JSON from markdown code blocks
304
+ const jsonMatch = jsonText.match(/```json\n([\s\S]*?)\n```/) ||
305
+ jsonText.match(/```\n([\s\S]*?)\n```/);
306
+
307
+ if (jsonMatch) {
308
+ jsonText = jsonMatch[1];
309
+ } else if (jsonText.includes("```json")) {
310
+ // Fallback for cases where regex might miss due to newlines
311
+ const start = jsonText.indexOf("```json") + 7;
312
+ const end = jsonText.lastIndexOf("```");
313
+ if (end > start) {
314
+ jsonText = jsonText.substring(start, end);
315
+ }
316
+ }
317
+
318
+ // Clean up any remaining whitespace
319
+ jsonText = jsonText.trim();
320
+
321
+ aiResponse = JSON.parse(jsonText);
322
+ } catch {
323
+ console.error("Failed to parse AI response:", textResponse.text);
324
+ return NextResponse.json(
325
+ { error: "Failed to parse AI response. Please try again." },
326
+ { status: 500 }
327
+ );
328
+ }
329
+
330
+ // Read original content and generate diffs for each modification
331
+ const modificationsWithOriginals: VisionFileModification[] = [];
332
+ for (const mod of aiResponse.modifications || []) {
333
+ const fullPath = path.join(projectRoot, mod.filePath);
334
+ let originalContent = "";
335
+ if (fs.existsSync(fullPath)) {
336
+ originalContent = fs.readFileSync(fullPath, "utf-8");
337
+ }
338
+
339
+ modificationsWithOriginals.push({
340
+ filePath: mod.filePath,
341
+ originalContent,
342
+ modifiedContent: mod.modifiedContent,
343
+ diff: generateSimpleDiff(originalContent, mod.modifiedContent),
344
+ explanation: mod.explanation,
345
+ previewCSS: mod.previewCSS,
346
+ });
347
+ }
348
+
349
+ // Aggregate preview CSS
350
+ const aggregatedCSS = modificationsWithOriginals
351
+ .filter((m) => m.previewCSS)
352
+ .map((m) => `/* ${m.filePath} */\n${m.previewCSS}`)
353
+ .join("\n\n");
354
+
355
+ return NextResponse.json({
356
+ success: true,
357
+ modifications: modificationsWithOriginals,
358
+ aggregatedPreviewCSS: aiResponse.aggregatedPreviewCSS || aggregatedCSS,
359
+ explanation: aiResponse.explanation,
360
+ reasoning: aiResponse.reasoning,
361
+ } as VisionEditResponse);
362
+ }
363
+
364
+ return NextResponse.json(
365
+ { error: "Invalid action. Use 'edit' or 'save'." },
366
+ { status: 400 }
367
+ );
368
+ } catch (error) {
369
+ console.error("Vision edit error:", error);
370
+ return NextResponse.json(
371
+ { error: "Failed to process request", details: String(error) },
372
+ { status: 500 }
373
+ );
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Gather context about the current page for AI analysis
379
+ */
380
+ function gatherPageContext(
381
+ pageRoute: string,
382
+ projectRoot: string
383
+ ): {
384
+ pageFile: string | null;
385
+ pageContent: string;
386
+ componentSources: { path: string; content: string }[];
387
+ globalsCSS: string;
388
+ } {
389
+ // Map route to page file
390
+ const pageFile = discoverPageFile(pageRoute, projectRoot);
391
+ let pageContent = "";
392
+ const componentSources: { path: string; content: string }[] = [];
393
+
394
+ if (pageFile) {
395
+ const fullPath = path.join(projectRoot, pageFile);
396
+ if (fs.existsSync(fullPath)) {
397
+ pageContent = fs.readFileSync(fullPath, "utf-8");
398
+
399
+ // Extract imports and read component files
400
+ const imports = extractImports(pageContent);
401
+ for (const importPath of imports) {
402
+ const resolvedPath = resolveImportPath(importPath, pageFile, projectRoot);
403
+ if (resolvedPath && fs.existsSync(path.join(projectRoot, resolvedPath))) {
404
+ try {
405
+ const content = fs.readFileSync(path.join(projectRoot, resolvedPath), "utf-8");
406
+ componentSources.push({ path: resolvedPath, content });
407
+ } catch {
408
+ // Skip files that can't be read
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ // Read globals.css
416
+ let globalsCSS = "";
417
+ const globalsPath = path.join(projectRoot, "src/app/globals.css");
418
+ if (fs.existsSync(globalsPath)) {
419
+ globalsCSS = fs.readFileSync(globalsPath, "utf-8");
420
+ }
421
+
422
+ return { pageFile, pageContent, componentSources, globalsCSS };
423
+ }
424
+
425
+ /**
426
+ * Discover the page file for a given route
427
+ */
428
+ function discoverPageFile(route: string, projectRoot: string): string | null {
429
+ // Handle root route
430
+ if (route === "/" || route === "") {
431
+ const rootPage = "src/app/page.tsx";
432
+ if (fs.existsSync(path.join(projectRoot, rootPage))) {
433
+ return rootPage;
434
+ }
435
+ return null;
436
+ }
437
+
438
+ // Remove leading slash
439
+ const cleanRoute = route.replace(/^\//, "");
440
+
441
+ // Try different patterns
442
+ const patterns = [
443
+ `src/app/${cleanRoute}/page.tsx`,
444
+ `src/app/${cleanRoute}/page.jsx`,
445
+ `src/app/${cleanRoute}.tsx`,
446
+ ];
447
+
448
+ for (const pattern of patterns) {
449
+ if (fs.existsSync(path.join(projectRoot, pattern))) {
450
+ return pattern;
451
+ }
452
+ }
453
+
454
+ return null;
455
+ }
456
+
457
+ /**
458
+ * Extract import paths from file content
459
+ */
460
+ function extractImports(content: string): string[] {
461
+ const importRegex = /import\s+.*?\s+from\s+["'](@\/components\/[^"']+|\.\.?\/[^"']+)["']/g;
462
+ const imports: string[] = [];
463
+ let match;
464
+
465
+ while ((match = importRegex.exec(content)) !== null) {
466
+ imports.push(match[1]);
467
+ }
468
+
469
+ return imports;
470
+ }
471
+
472
+ /**
473
+ * Resolve an import path to a file system path
474
+ */
475
+ function resolveImportPath(
476
+ importPath: string,
477
+ fromFile: string,
478
+ projectRoot: string
479
+ ): string | null {
480
+ // Handle @ alias (maps to src/)
481
+ if (importPath.startsWith("@/")) {
482
+ const resolved = importPath.replace("@/", "src/");
483
+ // Try with .tsx extension
484
+ const withExt = resolved.endsWith(".tsx") ? resolved : `${resolved}.tsx`;
485
+ if (fs.existsSync(path.join(projectRoot, withExt))) {
486
+ return withExt;
487
+ }
488
+ // Try as directory with index
489
+ const indexPath = `${resolved}/index.tsx`;
490
+ if (fs.existsSync(path.join(projectRoot, indexPath))) {
491
+ return indexPath;
492
+ }
493
+ return withExt; // Return even if not found for context
494
+ }
495
+
496
+ // Handle relative imports
497
+ if (importPath.startsWith(".")) {
498
+ const dir = path.dirname(fromFile);
499
+ const resolved = path.join(dir, importPath);
500
+ const withExt = resolved.endsWith(".tsx") ? resolved : `${resolved}.tsx`;
501
+ return withExt;
502
+ }
503
+
504
+ return null;
505
+ }
506
+
507
+ /**
508
+ * Generate a simple unified diff between two strings
509
+ */
510
+ function generateSimpleDiff(original: string, modified: string): string {
511
+ const originalLines = original.split("\n");
512
+ const modifiedLines = modified.split("\n");
513
+
514
+ const diff: string[] = [];
515
+ const maxLines = Math.max(originalLines.length, modifiedLines.length);
516
+
517
+ for (let i = 0; i < maxLines; i++) {
518
+ const origLine = originalLines[i];
519
+ const modLine = modifiedLines[i];
520
+
521
+ if (origLine === undefined && modLine !== undefined) {
522
+ diff.push(`+ ${modLine}`);
523
+ } else if (origLine !== undefined && modLine === undefined) {
524
+ diff.push(`- ${origLine}`);
525
+ } else if (origLine !== modLine) {
526
+ diff.push(`- ${origLine}`);
527
+ diff.push(`+ ${modLine}`);
528
+ }
529
+ }
530
+
531
+ return diff.join("\n");
532
+ }
package/dist/index.js CHANGED
@@ -356,6 +356,12 @@ function runDevToolsInstaller() {
356
356
  const apiAssetsDir = path.join(targetDir, baseDir, "app/api/sonance-assets");
357
357
  const apiInjectIdDir = path.join(targetDir, baseDir, "app/api/sonance-inject-id");
358
358
  const apiAnalyzeDir = path.join(targetDir, baseDir, "app/api/sonance-analyze");
359
+ const apiVisionApplyDir = path.join(targetDir, baseDir, "app/api/sonance-vision-apply");
360
+ const apiVisionEditDir = path.join(targetDir, baseDir, "app/api/sonance-vision-edit");
361
+ const apiAiEditDir = path.join(targetDir, baseDir, "app/api/sonance-ai-edit");
362
+ const apiSaveColorsDir = path.join(targetDir, baseDir, "app/api/sonance-save-colors");
363
+ const apiComponentSourceDir = path.join(targetDir, baseDir, "app/api/sonance-component-source");
364
+ const apiFindComponentDir = path.join(targetDir, baseDir, "app/api/sonance-find-component");
359
365
  const themeDir = path.join(targetDir, baseDir, "theme");
360
366
  // Source resolution
361
367
  let sourceBrandSystem;
@@ -369,6 +375,12 @@ function runDevToolsInstaller() {
369
375
  let sourceApiAssets;
370
376
  let sourceApiInjectId;
371
377
  let sourceApiAnalyze;
378
+ let sourceApiVisionApply;
379
+ let sourceApiVisionEdit;
380
+ let sourceApiAiEdit;
381
+ let sourceApiSaveColors;
382
+ let sourceApiComponentSource;
383
+ let sourceApiFindComponent;
372
384
  if (IS_BUNDLED) {
373
385
  sourceBrandSystem = path.join(BUNDLED_ASSETS, "brand-system.ts");
374
386
  sourceBrandContext = path.join(BUNDLED_ASSETS, "brand-context.tsx");
@@ -381,6 +393,12 @@ function runDevToolsInstaller() {
381
393
  sourceApiAssets = path.join(BUNDLED_ASSETS, "api/sonance-assets/route.ts");
382
394
  sourceApiInjectId = path.join(BUNDLED_ASSETS, "api/sonance-inject-id/route.ts");
383
395
  sourceApiAnalyze = path.join(BUNDLED_ASSETS, "api/sonance-analyze/route.ts");
396
+ sourceApiVisionApply = path.join(BUNDLED_ASSETS, "api/sonance-vision-apply/route.ts");
397
+ sourceApiVisionEdit = path.join(BUNDLED_ASSETS, "api/sonance-vision-edit/route.ts");
398
+ sourceApiAiEdit = path.join(BUNDLED_ASSETS, "api/sonance-ai-edit/route.ts");
399
+ sourceApiSaveColors = path.join(BUNDLED_ASSETS, "api/sonance-save-colors/route.ts");
400
+ sourceApiComponentSource = path.join(BUNDLED_ASSETS, "api/sonance-component-source/route.ts");
401
+ sourceApiFindComponent = path.join(BUNDLED_ASSETS, "api/sonance-find-component/route.ts");
384
402
  }
385
403
  else {
386
404
  sourceBrandSystem = path.join(DEV_PROJECT_ROOT, "src/lib/brand-system.ts");
@@ -394,6 +412,12 @@ function runDevToolsInstaller() {
394
412
  sourceApiAssets = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-assets/route.ts");
395
413
  sourceApiInjectId = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-inject-id/route.ts");
396
414
  sourceApiAnalyze = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-analyze/route.ts");
415
+ sourceApiVisionApply = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-vision-apply/route.ts");
416
+ sourceApiVisionEdit = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-vision-edit/route.ts");
417
+ sourceApiAiEdit = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-ai-edit/route.ts");
418
+ sourceApiSaveColors = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-save-colors/route.ts");
419
+ sourceApiComponentSource = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-component-source/route.ts");
420
+ sourceApiFindComponent = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-find-component/route.ts");
397
421
  }
398
422
  // Verify sources exist
399
423
  if (!fs.existsSync(sourceBrandSystem)) {
@@ -530,6 +554,49 @@ function runDevToolsInstaller() {
530
554
  fs.copyFileSync(sourceApiAnalyze, path.join(apiAnalyzeDir, "route.ts"));
531
555
  createdFiles.push(`${pathPrefix}app/api/sonance-analyze/route.ts`);
532
556
  console.log(` ✓ Created ${pathPrefix}app/api/sonance-analyze/route.ts`);
557
+ // 10. Install Vision and AI API routes
558
+ if (!fs.existsSync(apiVisionApplyDir)) {
559
+ fs.mkdirSync(apiVisionApplyDir, { recursive: true });
560
+ }
561
+ createdDirectories.push(`${pathPrefix}app/api/sonance-vision-apply`);
562
+ fs.copyFileSync(sourceApiVisionApply, path.join(apiVisionApplyDir, "route.ts"));
563
+ createdFiles.push(`${pathPrefix}app/api/sonance-vision-apply/route.ts`);
564
+ console.log(` ✓ Created ${pathPrefix}app/api/sonance-vision-apply/route.ts`);
565
+ if (!fs.existsSync(apiVisionEditDir)) {
566
+ fs.mkdirSync(apiVisionEditDir, { recursive: true });
567
+ }
568
+ createdDirectories.push(`${pathPrefix}app/api/sonance-vision-edit`);
569
+ fs.copyFileSync(sourceApiVisionEdit, path.join(apiVisionEditDir, "route.ts"));
570
+ createdFiles.push(`${pathPrefix}app/api/sonance-vision-edit/route.ts`);
571
+ console.log(` ✓ Created ${pathPrefix}app/api/sonance-vision-edit/route.ts`);
572
+ if (!fs.existsSync(apiAiEditDir)) {
573
+ fs.mkdirSync(apiAiEditDir, { recursive: true });
574
+ }
575
+ createdDirectories.push(`${pathPrefix}app/api/sonance-ai-edit`);
576
+ fs.copyFileSync(sourceApiAiEdit, path.join(apiAiEditDir, "route.ts"));
577
+ createdFiles.push(`${pathPrefix}app/api/sonance-ai-edit/route.ts`);
578
+ console.log(` ✓ Created ${pathPrefix}app/api/sonance-ai-edit/route.ts`);
579
+ if (!fs.existsSync(apiSaveColorsDir)) {
580
+ fs.mkdirSync(apiSaveColorsDir, { recursive: true });
581
+ }
582
+ createdDirectories.push(`${pathPrefix}app/api/sonance-save-colors`);
583
+ fs.copyFileSync(sourceApiSaveColors, path.join(apiSaveColorsDir, "route.ts"));
584
+ createdFiles.push(`${pathPrefix}app/api/sonance-save-colors/route.ts`);
585
+ console.log(` ✓ Created ${pathPrefix}app/api/sonance-save-colors/route.ts`);
586
+ if (!fs.existsSync(apiComponentSourceDir)) {
587
+ fs.mkdirSync(apiComponentSourceDir, { recursive: true });
588
+ }
589
+ createdDirectories.push(`${pathPrefix}app/api/sonance-component-source`);
590
+ fs.copyFileSync(sourceApiComponentSource, path.join(apiComponentSourceDir, "route.ts"));
591
+ createdFiles.push(`${pathPrefix}app/api/sonance-component-source/route.ts`);
592
+ console.log(` ✓ Created ${pathPrefix}app/api/sonance-component-source/route.ts`);
593
+ if (!fs.existsSync(apiFindComponentDir)) {
594
+ fs.mkdirSync(apiFindComponentDir, { recursive: true });
595
+ }
596
+ createdDirectories.push(`${pathPrefix}app/api/sonance-find-component`);
597
+ fs.copyFileSync(sourceApiFindComponent, path.join(apiFindComponentDir, "route.ts"));
598
+ createdFiles.push(`${pathPrefix}app/api/sonance-find-component/route.ts`);
599
+ console.log(` ✓ Created ${pathPrefix}app/api/sonance-find-component/route.ts`);
533
600
  // 11. Install brand-overrides.css for production logo sizing
534
601
  if (!fs.existsSync(stylesDir)) {
535
602
  fs.mkdirSync(stylesDir, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.19",
3
+ "version": "1.3.20",
4
4
  "description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",