sonance-brand-mcp 1.3.61 → 1.3.63
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.
|
@@ -106,7 +106,7 @@ async function analyzeScreenshotForSearch(
|
|
|
106
106
|
|
|
107
107
|
const response = await anthropic.messages.create({
|
|
108
108
|
model: "claude-sonnet-4-20250514",
|
|
109
|
-
max_tokens:
|
|
109
|
+
max_tokens: 2048, // Increased for better analysis
|
|
110
110
|
messages: [
|
|
111
111
|
{
|
|
112
112
|
role: "user",
|
|
@@ -174,6 +174,8 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
174
174
|
/**
|
|
175
175
|
* Search candidate files for JSX code matching the focused element
|
|
176
176
|
* This helps identify which file actually contains the element the user clicked on
|
|
177
|
+
*
|
|
178
|
+
* CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
|
|
177
179
|
*/
|
|
178
180
|
function findFilesContainingElement(
|
|
179
181
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
@@ -188,44 +190,68 @@ function findFilesContainingElement(
|
|
|
188
190
|
for (const file of candidateFiles) {
|
|
189
191
|
let score = 0;
|
|
190
192
|
const matches: string[] = [];
|
|
191
|
-
const content = file.content
|
|
193
|
+
const content = file.content;
|
|
194
|
+
const contentLower = content.toLowerCase();
|
|
192
195
|
|
|
193
196
|
for (const el of focusedElements) {
|
|
194
197
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
195
198
|
const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
|
|
196
199
|
const componentType = el.type.toLowerCase();
|
|
200
|
+
const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
|
|
197
201
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
// HIGH VALUE: Component is EXPORTED from this file (this is likely THE file)
|
|
203
|
+
if (content.includes(`export function ${capitalizedType}`) ||
|
|
204
|
+
content.includes(`export const ${capitalizedType}`) ||
|
|
205
|
+
content.includes(`export default function ${capitalizedType}`)) {
|
|
206
|
+
score += 100;
|
|
207
|
+
matches.push(`EXPORTS ${capitalizedType}`);
|
|
203
208
|
}
|
|
204
209
|
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
// HIGH VALUE: Component is DEFINED in this file
|
|
211
|
+
if (content.includes(`function ${capitalizedType}(`) ||
|
|
212
|
+
content.includes(`const ${capitalizedType} =`) ||
|
|
213
|
+
content.includes(`const ${capitalizedType}:`) ||
|
|
214
|
+
content.includes(`function ${capitalizedType} (`)) {
|
|
215
|
+
score += 50;
|
|
216
|
+
matches.push(`defines ${capitalizedType}`);
|
|
210
217
|
}
|
|
211
218
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
// MEDIUM VALUE: JSX usage of this element type (rendered in this file)
|
|
220
|
+
const jsxTagPattern = new RegExp(`<${capitalizedType}[\\s/>]`, 'g');
|
|
221
|
+
const jsxMatches = content.match(jsxTagPattern);
|
|
222
|
+
if (jsxMatches) {
|
|
223
|
+
score += 20 * jsxMatches.length;
|
|
224
|
+
matches.push(`<${capitalizedType}> x${jsxMatches.length}`);
|
|
215
225
|
}
|
|
216
226
|
|
|
217
|
-
//
|
|
227
|
+
// MEDIUM VALUE: HTML tag usage
|
|
228
|
+
const htmlTagPattern = new RegExp(`<${elementType}[\\s>]`, 'gi');
|
|
229
|
+
const htmlMatches = content.match(htmlTagPattern);
|
|
230
|
+
if (htmlMatches) {
|
|
231
|
+
score += 10 * htmlMatches.length;
|
|
232
|
+
matches.push(`<${elementType}> x${htmlMatches.length}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// LOW VALUE: Event handlers (onClick, onPress, etc.)
|
|
218
236
|
if (elementType === "button" || componentType === "button") {
|
|
219
|
-
|
|
220
|
-
|
|
237
|
+
const clickHandlers = content.match(/onClick\s*=/gi);
|
|
238
|
+
if (clickHandlers) {
|
|
239
|
+
score += 5 * clickHandlers.length;
|
|
240
|
+
matches.push(`onClick x${clickHandlers.length}`);
|
|
221
241
|
}
|
|
222
242
|
}
|
|
223
243
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
score +=
|
|
228
|
-
matches.push(`
|
|
244
|
+
// BONUS: File name contains the element type
|
|
245
|
+
const fileName = file.path.toLowerCase();
|
|
246
|
+
if (fileName.includes(elementType) || fileName.includes(componentType)) {
|
|
247
|
+
score += 30;
|
|
248
|
+
matches.push(`filename contains ${elementType}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// BONUS: File is a Row/Cell component (likely contains action buttons)
|
|
252
|
+
if (fileName.includes('row') || fileName.includes('cell') || fileName.includes('item')) {
|
|
253
|
+
score += 15;
|
|
254
|
+
matches.push('row/cell component');
|
|
229
255
|
}
|
|
230
256
|
}
|
|
231
257
|
|
|
@@ -511,7 +537,7 @@ function searchFilesSmart(
|
|
|
511
537
|
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
|
|
512
538
|
}
|
|
513
539
|
|
|
514
|
-
const VISION_SYSTEM_PROMPT = `You edit code.
|
|
540
|
+
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
515
541
|
|
|
516
542
|
RULES:
|
|
517
543
|
1. Copy code EXACTLY from the file - character for character
|
|
@@ -519,13 +545,8 @@ RULES:
|
|
|
519
545
|
3. For color changes, just change the className
|
|
520
546
|
4. Do not restructure or reorganize code
|
|
521
547
|
|
|
522
|
-
|
|
523
|
-
{
|
|
524
|
-
"modifications": [{
|
|
525
|
-
"filePath": "path",
|
|
526
|
-
"patches": [{ "search": "exact code from file", "replace": "changed code" }]
|
|
527
|
-
}]
|
|
528
|
-
}`;
|
|
548
|
+
RESPOND WITH ONLY THIS JSON FORMAT (nothing else):
|
|
549
|
+
{"modifications":[{"filePath":"path","patches":[{"search":"exact code","replace":"changed code"}]}]}`;
|
|
529
550
|
|
|
530
551
|
export async function POST(request: Request) {
|
|
531
552
|
// Only allow in development
|
|
@@ -673,8 +694,19 @@ export async function POST(request: Request) {
|
|
|
673
694
|
});
|
|
674
695
|
}
|
|
675
696
|
|
|
676
|
-
//
|
|
677
|
-
|
|
697
|
+
// CURSOR-STYLE: If focused element search has high confidence, use it directly
|
|
698
|
+
// Skip the LLM guessing phase - we found the file that contains the code
|
|
699
|
+
const HIGH_CONFIDENCE_THRESHOLD = 50; // Score of 50+ means we found exact matches
|
|
700
|
+
|
|
701
|
+
if (focusedElementHints.length > 0 && focusedElementHints[0].score >= HIGH_CONFIDENCE_THRESHOLD) {
|
|
702
|
+
// HIGH CONFIDENCE: Use focused element match directly
|
|
703
|
+
recommendedFile = {
|
|
704
|
+
path: focusedElementHints[0].path,
|
|
705
|
+
reason: `Content search found element (score: ${focusedElementHints[0].score}, matches: ${focusedElementHints[0].matches.join(', ')})`
|
|
706
|
+
};
|
|
707
|
+
debugLog("HIGH CONFIDENCE: Using focused element match directly", recommendedFile);
|
|
708
|
+
} else if (searchResults.length > 0) {
|
|
709
|
+
// LOW CONFIDENCE: Fall back to LLM selection
|
|
678
710
|
const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
|
|
679
711
|
const selectedFile = await selectBestFileFromList(
|
|
680
712
|
screenshot,
|
|
@@ -992,12 +1024,42 @@ This is better than generating patches with made-up code.`,
|
|
|
992
1024
|
|
|
993
1025
|
jsonText = jsonText.trim();
|
|
994
1026
|
|
|
995
|
-
// Robust JSON extraction:
|
|
996
|
-
// This handles cases where the LLM includes preamble text
|
|
1027
|
+
// Robust JSON extraction: look for {"modifications" pattern specifically
|
|
1028
|
+
// This handles cases where the LLM includes preamble text with code blocks
|
|
1029
|
+
const jsonStartPatterns = ['{"modifications"', '{ "modifications"', '{\n "modifications"'];
|
|
1030
|
+
let jsonStart = -1;
|
|
1031
|
+
|
|
1032
|
+
for (const pattern of jsonStartPatterns) {
|
|
1033
|
+
const idx = jsonText.indexOf(pattern);
|
|
1034
|
+
if (idx !== -1 && (jsonStart === -1 || idx < jsonStart)) {
|
|
1035
|
+
jsonStart = idx;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (jsonStart !== -1) {
|
|
1040
|
+
// Find the matching closing brace by counting braces
|
|
1041
|
+
let braceCount = 0;
|
|
1042
|
+
let jsonEnd = -1;
|
|
1043
|
+
for (let i = jsonStart; i < jsonText.length; i++) {
|
|
1044
|
+
if (jsonText[i] === '{') braceCount++;
|
|
1045
|
+
if (jsonText[i] === '}') {
|
|
1046
|
+
braceCount--;
|
|
1047
|
+
if (braceCount === 0) {
|
|
1048
|
+
jsonEnd = i;
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (jsonEnd !== -1) {
|
|
1054
|
+
jsonText = jsonText.substring(jsonStart, jsonEnd + 1);
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
// Fallback: try first { to last }
|
|
997
1058
|
const firstBrace = jsonText.indexOf('{');
|
|
998
1059
|
const lastBrace = jsonText.lastIndexOf('}');
|
|
999
1060
|
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
1000
1061
|
jsonText = jsonText.substring(firstBrace, lastBrace + 1);
|
|
1062
|
+
}
|
|
1001
1063
|
}
|
|
1002
1064
|
|
|
1003
1065
|
aiResponse = JSON.parse(jsonText);
|
|
@@ -2397,3 +2459,4 @@ function applyPatches(originalContent: string, patches: Patch[]): ApplyPatchesRe
|
|
|
2397
2459
|
failedPatches,
|
|
2398
2460
|
};
|
|
2399
2461
|
}
|
|
2462
|
+
|
|
@@ -102,7 +102,7 @@ async function analyzeScreenshotForSearch(
|
|
|
102
102
|
|
|
103
103
|
const response = await anthropic.messages.create({
|
|
104
104
|
model: "claude-sonnet-4-20250514",
|
|
105
|
-
max_tokens:
|
|
105
|
+
max_tokens: 2048, // Increased for better analysis
|
|
106
106
|
messages: [
|
|
107
107
|
{
|
|
108
108
|
role: "user",
|
|
@@ -170,6 +170,8 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
170
170
|
/**
|
|
171
171
|
* Search candidate files for JSX code matching the focused element
|
|
172
172
|
* This helps identify which file actually contains the element the user clicked on
|
|
173
|
+
*
|
|
174
|
+
* CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
|
|
173
175
|
*/
|
|
174
176
|
function findFilesContainingElement(
|
|
175
177
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
@@ -184,44 +186,68 @@ function findFilesContainingElement(
|
|
|
184
186
|
for (const file of candidateFiles) {
|
|
185
187
|
let score = 0;
|
|
186
188
|
const matches: string[] = [];
|
|
187
|
-
const content = file.content
|
|
189
|
+
const content = file.content;
|
|
190
|
+
const contentLower = content.toLowerCase();
|
|
188
191
|
|
|
189
192
|
for (const el of focusedElements) {
|
|
190
193
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
191
194
|
const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
|
|
192
195
|
const componentType = el.type.toLowerCase();
|
|
196
|
+
const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
|
|
193
197
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
// HIGH VALUE: Component is EXPORTED from this file (this is likely THE file)
|
|
199
|
+
if (content.includes(`export function ${capitalizedType}`) ||
|
|
200
|
+
content.includes(`export const ${capitalizedType}`) ||
|
|
201
|
+
content.includes(`export default function ${capitalizedType}`)) {
|
|
202
|
+
score += 100;
|
|
203
|
+
matches.push(`EXPORTS ${capitalizedType}`);
|
|
199
204
|
}
|
|
200
205
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
// HIGH VALUE: Component is DEFINED in this file
|
|
207
|
+
if (content.includes(`function ${capitalizedType}(`) ||
|
|
208
|
+
content.includes(`const ${capitalizedType} =`) ||
|
|
209
|
+
content.includes(`const ${capitalizedType}:`) ||
|
|
210
|
+
content.includes(`function ${capitalizedType} (`)) {
|
|
211
|
+
score += 50;
|
|
212
|
+
matches.push(`defines ${capitalizedType}`);
|
|
206
213
|
}
|
|
207
214
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
215
|
+
// MEDIUM VALUE: JSX usage of this element type (rendered in this file)
|
|
216
|
+
const jsxTagPattern = new RegExp(`<${capitalizedType}[\\s/>]`, 'g');
|
|
217
|
+
const jsxMatches = content.match(jsxTagPattern);
|
|
218
|
+
if (jsxMatches) {
|
|
219
|
+
score += 20 * jsxMatches.length;
|
|
220
|
+
matches.push(`<${capitalizedType}> x${jsxMatches.length}`);
|
|
211
221
|
}
|
|
212
222
|
|
|
213
|
-
//
|
|
223
|
+
// MEDIUM VALUE: HTML tag usage
|
|
224
|
+
const htmlTagPattern = new RegExp(`<${elementType}[\\s>]`, 'gi');
|
|
225
|
+
const htmlMatches = content.match(htmlTagPattern);
|
|
226
|
+
if (htmlMatches) {
|
|
227
|
+
score += 10 * htmlMatches.length;
|
|
228
|
+
matches.push(`<${elementType}> x${htmlMatches.length}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// LOW VALUE: Event handlers (onClick, onPress, etc.)
|
|
214
232
|
if (elementType === "button" || componentType === "button") {
|
|
215
|
-
|
|
216
|
-
|
|
233
|
+
const clickHandlers = content.match(/onClick\s*=/gi);
|
|
234
|
+
if (clickHandlers) {
|
|
235
|
+
score += 5 * clickHandlers.length;
|
|
236
|
+
matches.push(`onClick x${clickHandlers.length}`);
|
|
217
237
|
}
|
|
218
238
|
}
|
|
219
239
|
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
score +=
|
|
224
|
-
matches.push(`
|
|
240
|
+
// BONUS: File name contains the element type
|
|
241
|
+
const fileName = file.path.toLowerCase();
|
|
242
|
+
if (fileName.includes(elementType) || fileName.includes(componentType)) {
|
|
243
|
+
score += 30;
|
|
244
|
+
matches.push(`filename contains ${elementType}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// BONUS: File is a Row/Cell component (likely contains action buttons)
|
|
248
|
+
if (fileName.includes('row') || fileName.includes('cell') || fileName.includes('item')) {
|
|
249
|
+
score += 15;
|
|
250
|
+
matches.push('row/cell component');
|
|
225
251
|
}
|
|
226
252
|
}
|
|
227
253
|
|
|
@@ -507,7 +533,7 @@ function searchFilesSmart(
|
|
|
507
533
|
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
|
|
508
534
|
}
|
|
509
535
|
|
|
510
|
-
const VISION_SYSTEM_PROMPT = `You edit code.
|
|
536
|
+
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
511
537
|
|
|
512
538
|
RULES:
|
|
513
539
|
1. Copy code EXACTLY from the file - character for character
|
|
@@ -515,13 +541,8 @@ RULES:
|
|
|
515
541
|
3. For color changes, just change the className
|
|
516
542
|
4. Do not restructure or reorganize code
|
|
517
543
|
|
|
518
|
-
|
|
519
|
-
{
|
|
520
|
-
"modifications": [{
|
|
521
|
-
"filePath": "path",
|
|
522
|
-
"patches": [{ "search": "exact code from file", "replace": "changed code" }]
|
|
523
|
-
}]
|
|
524
|
-
}`;
|
|
544
|
+
RESPOND WITH ONLY THIS JSON FORMAT (nothing else):
|
|
545
|
+
{"modifications":[{"filePath":"path","patches":[{"search":"exact code","replace":"changed code"}]}]}`;
|
|
525
546
|
|
|
526
547
|
export async function POST(request: Request) {
|
|
527
548
|
// Only allow in development
|
|
@@ -642,8 +663,19 @@ export async function POST(request: Request) {
|
|
|
642
663
|
});
|
|
643
664
|
}
|
|
644
665
|
|
|
645
|
-
//
|
|
646
|
-
|
|
666
|
+
// CURSOR-STYLE: If focused element search has high confidence, use it directly
|
|
667
|
+
// Skip the LLM guessing phase - we found the file that contains the code
|
|
668
|
+
const HIGH_CONFIDENCE_THRESHOLD = 50; // Score of 50+ means we found exact matches
|
|
669
|
+
|
|
670
|
+
if (focusedElementHints.length > 0 && focusedElementHints[0].score >= HIGH_CONFIDENCE_THRESHOLD) {
|
|
671
|
+
// HIGH CONFIDENCE: Use focused element match directly
|
|
672
|
+
recommendedFile = {
|
|
673
|
+
path: focusedElementHints[0].path,
|
|
674
|
+
reason: `Content search found element (score: ${focusedElementHints[0].score}, matches: ${focusedElementHints[0].matches.join(', ')})`
|
|
675
|
+
};
|
|
676
|
+
debugLog("HIGH CONFIDENCE: Using focused element match directly", recommendedFile);
|
|
677
|
+
} else if (searchResults.length > 0) {
|
|
678
|
+
// LOW CONFIDENCE: Fall back to LLM selection
|
|
647
679
|
const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
|
|
648
680
|
const selectedFile = await selectBestFileFromList(
|
|
649
681
|
screenshot,
|
|
@@ -958,12 +990,42 @@ This is better than generating patches with made-up code.`,
|
|
|
958
990
|
// Clean up any remaining whitespace
|
|
959
991
|
jsonText = jsonText.trim();
|
|
960
992
|
|
|
961
|
-
// Robust JSON extraction:
|
|
962
|
-
// This handles cases where the LLM includes preamble text
|
|
993
|
+
// Robust JSON extraction: look for {"modifications" pattern specifically
|
|
994
|
+
// This handles cases where the LLM includes preamble text with code blocks
|
|
995
|
+
const jsonStartPatterns = ['{"modifications"', '{ "modifications"', '{\n "modifications"'];
|
|
996
|
+
let jsonStart = -1;
|
|
997
|
+
|
|
998
|
+
for (const pattern of jsonStartPatterns) {
|
|
999
|
+
const idx = jsonText.indexOf(pattern);
|
|
1000
|
+
if (idx !== -1 && (jsonStart === -1 || idx < jsonStart)) {
|
|
1001
|
+
jsonStart = idx;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (jsonStart !== -1) {
|
|
1006
|
+
// Find the matching closing brace by counting braces
|
|
1007
|
+
let braceCount = 0;
|
|
1008
|
+
let jsonEnd = -1;
|
|
1009
|
+
for (let i = jsonStart; i < jsonText.length; i++) {
|
|
1010
|
+
if (jsonText[i] === '{') braceCount++;
|
|
1011
|
+
if (jsonText[i] === '}') {
|
|
1012
|
+
braceCount--;
|
|
1013
|
+
if (braceCount === 0) {
|
|
1014
|
+
jsonEnd = i;
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (jsonEnd !== -1) {
|
|
1020
|
+
jsonText = jsonText.substring(jsonStart, jsonEnd + 1);
|
|
1021
|
+
}
|
|
1022
|
+
} else {
|
|
1023
|
+
// Fallback: try first { to last }
|
|
963
1024
|
const firstBrace = jsonText.indexOf('{');
|
|
964
1025
|
const lastBrace = jsonText.lastIndexOf('}');
|
|
965
1026
|
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
966
1027
|
jsonText = jsonText.substring(firstBrace, lastBrace + 1);
|
|
1028
|
+
}
|
|
967
1029
|
}
|
|
968
1030
|
|
|
969
1031
|
aiResponse = JSON.parse(jsonText);
|
|
@@ -2206,3 +2268,4 @@ function applyPatches(originalContent: string, patches: Patch[]): ApplyPatchesRe
|
|
|
2206
2268
|
failedPatches,
|
|
2207
2269
|
};
|
|
2208
2270
|
}
|
|
2271
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.63",
|
|
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",
|