@fragments-sdk/cli 0.4.4 → 0.5.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 +1 -1
- package/dist/bin.js +12 -12
- package/dist/{chunk-NOTYONHY.js → chunk-2DJH4F4P.js} +2 -2
- package/dist/{chunk-5CKYLCJH.js → chunk-2H2JAA3U.js} +35 -7
- package/dist/chunk-2H2JAA3U.js.map +1 -0
- package/dist/{chunk-G3M3MPQ6.js → chunk-B2TQKOLW.js} +157 -30
- package/dist/chunk-B2TQKOLW.js.map +1 -0
- package/dist/{chunk-AW7MWOUH.js → chunk-ICAIQ57V.js} +9 -5
- package/dist/chunk-ICAIQ57V.js.map +1 -0
- package/dist/{chunk-5ZYEOHYK.js → chunk-IOJE35DZ.js} +2 -2
- package/dist/{chunk-ZFKGX3QK.js → chunk-UXRGD3DM.js} +47 -14
- package/dist/chunk-UXRGD3DM.js.map +1 -0
- package/dist/{chunk-J4SI5RIH.js → chunk-XNWDI6UT.js} +4 -4
- package/dist/{core-LNXDLXDP.js → core-NJVKKLJ4.js} +11 -3
- package/dist/{generate-OIXXHOWR.js → generate-OVGMDKCJ.js} +4 -4
- package/dist/index.d.ts +30 -4
- package/dist/index.js +6 -6
- package/dist/{init-EVPXIDW4.js → init-EOA7TTOR.js} +4 -4
- package/dist/mcp-bin.js +266 -36
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-YN4LUDKY.js +12 -0
- package/dist/{service-K52ORLCJ.js → service-2T26CBWE.js} +4 -4
- package/dist/{static-viewer-JNQIHA4B.js → static-viewer-CLJJRYHK.js} +4 -4
- package/dist/{test-USARUEFW.js → test-ECPEXFDN.js} +3 -3
- package/dist/{tokens-C6YHBOQE.js → tokens-FHA2DO22.js} +5 -5
- package/dist/{viewer-H7TVFT4E.js → viewer-XDPD52L7.js} +13 -13
- package/package.json +1 -1
- package/src/build.ts +53 -13
- package/src/core/constants.ts +4 -1
- package/src/core/context.ts +28 -28
- package/src/core/defineSegment.ts +21 -11
- package/src/core/discovery.ts +52 -4
- package/src/core/index.ts +14 -4
- package/src/core/loader.ts +3 -0
- package/src/core/node.ts +3 -1
- package/src/core/parser.ts +1 -1
- package/src/core/schema.ts +7 -2
- package/src/core/token-parser.ts +211 -0
- package/src/core/types.ts +46 -6
- package/src/mcp/server.ts +321 -39
- package/dist/chunk-5CKYLCJH.js.map +0 -1
- package/dist/chunk-AW7MWOUH.js.map +0 -1
- package/dist/chunk-G3M3MPQ6.js.map +0 -1
- package/dist/chunk-ZFKGX3QK.js.map +0 -1
- package/dist/scan-YVYD64GD.js +0 -12
- /package/dist/{chunk-NOTYONHY.js.map → chunk-2DJH4F4P.js.map} +0 -0
- /package/dist/{chunk-5ZYEOHYK.js.map → chunk-IOJE35DZ.js.map} +0 -0
- /package/dist/{chunk-J4SI5RIH.js.map → chunk-XNWDI6UT.js.map} +0 -0
- /package/dist/{core-LNXDLXDP.js.map → core-NJVKKLJ4.js.map} +0 -0
- /package/dist/{generate-OIXXHOWR.js.map → generate-OVGMDKCJ.js.map} +0 -0
- /package/dist/{init-EVPXIDW4.js.map → init-EOA7TTOR.js.map} +0 -0
- /package/dist/{scan-YVYD64GD.js.map → scan-YN4LUDKY.js.map} +0 -0
- /package/dist/{service-K52ORLCJ.js.map → service-2T26CBWE.js.map} +0 -0
- /package/dist/{static-viewer-JNQIHA4B.js.map → static-viewer-CLJJRYHK.js.map} +0 -0
- /package/dist/{test-USARUEFW.js.map → test-ECPEXFDN.js.map} +0 -0
- /package/dist/{tokens-C6YHBOQE.js.map → tokens-FHA2DO22.js.map} +0 -0
- /package/dist/{viewer-H7TVFT4E.js.map → viewer-XDPD52L7.js.map} +0 -0
package/dist/mcp-bin.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
generateContext
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-B2TQKOLW.js";
|
|
6
6
|
import {
|
|
7
7
|
BRAND,
|
|
8
8
|
DEFAULTS
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ICAIQ57V.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp/server.ts
|
|
12
12
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -57,7 +57,7 @@ var _service = null;
|
|
|
57
57
|
async function getService() {
|
|
58
58
|
if (!_service) {
|
|
59
59
|
try {
|
|
60
|
-
_service = await import("./service-
|
|
60
|
+
_service = await import("./service-2T26CBWE.js");
|
|
61
61
|
} catch {
|
|
62
62
|
throw new Error(
|
|
63
63
|
"Visual tools require playwright. Install it with: npm install playwright"
|
|
@@ -69,7 +69,9 @@ async function getService() {
|
|
|
69
69
|
var TOOL_NAMES = {
|
|
70
70
|
discover: `${BRAND.nameLower}_discover`,
|
|
71
71
|
inspect: `${BRAND.nameLower}_inspect`,
|
|
72
|
-
|
|
72
|
+
blocks: `${BRAND.nameLower}_blocks`,
|
|
73
|
+
tokens: `${BRAND.nameLower}_tokens`,
|
|
74
|
+
implement: `${BRAND.nameLower}_implement`,
|
|
73
75
|
render: `${BRAND.nameLower}_render`,
|
|
74
76
|
fix: `${BRAND.nameLower}_fix`
|
|
75
77
|
};
|
|
@@ -279,26 +281,61 @@ var TOOLS = [
|
|
|
279
281
|
}
|
|
280
282
|
},
|
|
281
283
|
{
|
|
282
|
-
name: TOOL_NAMES.
|
|
283
|
-
description: `Search and retrieve composition
|
|
284
|
+
name: TOOL_NAMES.blocks,
|
|
285
|
+
description: `Search and retrieve composition blocks \u2014 named patterns showing how design system components wire together for common use cases (e.g., "Login Form", "Settings Page"). Returns the block with its code pattern.`,
|
|
284
286
|
inputSchema: {
|
|
285
287
|
type: "object",
|
|
286
288
|
properties: {
|
|
287
289
|
name: {
|
|
288
290
|
type: "string",
|
|
289
|
-
description: 'Exact
|
|
291
|
+
description: 'Exact block name to retrieve (e.g., "Login Form")'
|
|
290
292
|
},
|
|
291
293
|
search: {
|
|
292
294
|
type: "string",
|
|
293
|
-
description: "Free-text search across
|
|
295
|
+
description: "Free-text search across block names, descriptions, tags, and components"
|
|
294
296
|
},
|
|
295
297
|
component: {
|
|
296
298
|
type: "string",
|
|
297
|
-
description: 'Filter
|
|
299
|
+
description: 'Filter blocks that use a specific component (e.g., "Button")'
|
|
300
|
+
},
|
|
301
|
+
category: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: 'Filter by category (e.g., "authentication", "marketing", "dashboard", "settings", "ecommerce", "ai")'
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: TOOL_NAMES.tokens,
|
|
310
|
+
description: `List available CSS design tokens (custom properties) by category. Use this when you need to style custom elements or override defaults \u2014 no more guessing variable names. Filter by category or search by keyword.`,
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {
|
|
314
|
+
category: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description: 'Filter by category (e.g., "colors", "spacing", "typography", "surfaces", "shadows", "radius", "borders", "text", "focus", "layout", "code", "component-sizing")'
|
|
317
|
+
},
|
|
318
|
+
search: {
|
|
319
|
+
type: "string",
|
|
320
|
+
description: 'Search token names (e.g., "accent", "hover", "padding")'
|
|
298
321
|
}
|
|
299
322
|
}
|
|
300
323
|
}
|
|
301
324
|
},
|
|
325
|
+
{
|
|
326
|
+
name: TOOL_NAMES.implement,
|
|
327
|
+
description: `One-shot implementation helper. Describe what you want to build and get everything needed in a single call: best-matching component(s) with full props and code examples, relevant composition blocks, and applicable CSS tokens. Saves multiple round-trips.`,
|
|
328
|
+
inputSchema: {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
useCase: {
|
|
332
|
+
type: "string",
|
|
333
|
+
description: 'What you want to implement (e.g., "login form", "data table with sorting", "streaming chat messages")'
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
required: ["useCase"]
|
|
337
|
+
}
|
|
338
|
+
},
|
|
302
339
|
{
|
|
303
340
|
name: TOOL_NAMES.render,
|
|
304
341
|
description: `Render a component and return a screenshot. Optionally compare against a stored baseline ('baseline: true') or against a Figma design ('figmaUrl'). Use this to verify your implementation looks correct.`,
|
|
@@ -401,6 +438,9 @@ function createMcpServer(config) {
|
|
|
401
438
|
}
|
|
402
439
|
const content = await readFile(paths[0], "utf-8");
|
|
403
440
|
segmentsData = JSON.parse(content);
|
|
441
|
+
if (!segmentsData.blocks && segmentsData.recipes) {
|
|
442
|
+
segmentsData.blocks = segmentsData.recipes;
|
|
443
|
+
}
|
|
404
444
|
if (segmentsData.packageName) {
|
|
405
445
|
for (const name of Object.keys(segmentsData.segments)) {
|
|
406
446
|
segmentPackageMap.set(name, segmentsData.packageName);
|
|
@@ -414,13 +454,15 @@ function createMcpServer(config) {
|
|
|
414
454
|
}
|
|
415
455
|
}
|
|
416
456
|
Object.assign(segmentsData.segments, extra.segments);
|
|
417
|
-
|
|
418
|
-
|
|
457
|
+
const extraBlocks = extra.blocks ?? extra.recipes;
|
|
458
|
+
if (extraBlocks) {
|
|
459
|
+
segmentsData.blocks = { ...segmentsData.blocks, ...extraBlocks };
|
|
419
460
|
}
|
|
420
461
|
}
|
|
421
462
|
return segmentsData;
|
|
422
463
|
}
|
|
423
464
|
async function getPackageName(segmentName) {
|
|
465
|
+
await loadSegments();
|
|
424
466
|
if (segmentName) {
|
|
425
467
|
const segPkg = segmentPackageMap.get(segmentName);
|
|
426
468
|
if (segPkg) return segPkg;
|
|
@@ -428,9 +470,8 @@ function createMcpServer(config) {
|
|
|
428
470
|
if (defaultPackageName) {
|
|
429
471
|
return defaultPackageName;
|
|
430
472
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
defaultPackageName = data.packageName;
|
|
473
|
+
if (segmentsData?.packageName) {
|
|
474
|
+
defaultPackageName = segmentsData.packageName;
|
|
434
475
|
return defaultPackageName;
|
|
435
476
|
}
|
|
436
477
|
const packageJsonPath = join(config.projectRoot, "package.json");
|
|
@@ -516,7 +557,7 @@ function createMcpServer(config) {
|
|
|
516
557
|
const includeRelations = args2?.includeRelations ?? false;
|
|
517
558
|
if (compact || args2?.format && !useCase && !componentForAlts && !category && !search && !status) {
|
|
518
559
|
const segments2 = Object.values(data.segments);
|
|
519
|
-
const
|
|
560
|
+
const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
|
|
520
561
|
const { content: ctxContent, tokenEstimate } = generateContext(segments2, {
|
|
521
562
|
format,
|
|
522
563
|
compact,
|
|
@@ -524,7 +565,7 @@ function createMcpServer(config) {
|
|
|
524
565
|
code: includeCode,
|
|
525
566
|
relations: includeRelations
|
|
526
567
|
}
|
|
527
|
-
},
|
|
568
|
+
}, allBlocks);
|
|
528
569
|
return {
|
|
529
570
|
content: [{
|
|
530
571
|
type: "text",
|
|
@@ -650,6 +691,22 @@ function createMcpServer(config) {
|
|
|
650
691
|
}
|
|
651
692
|
}
|
|
652
693
|
const compositionHint = suggestions.length >= 2 ? `These components work well together. For example, ${suggestions[0].component} can be combined with ${suggestions.slice(1, 3).map((s) => s.component).join(" and ")}.` : void 0;
|
|
694
|
+
const STYLE_KEYWORDS = ["color", "spacing", "padding", "margin", "font", "border", "radius", "shadow", "variable", "token", "css", "theme", "dark mode", "background", "hover"];
|
|
695
|
+
const isStyleQuery = STYLE_KEYWORDS.some((kw) => useCaseLower.includes(kw));
|
|
696
|
+
const noMatch = suggestions.length === 0;
|
|
697
|
+
const weakMatch = !noMatch && suggestions.every((s) => s.confidence === "low");
|
|
698
|
+
let recommendation;
|
|
699
|
+
let nextStep;
|
|
700
|
+
if (noMatch) {
|
|
701
|
+
recommendation = isStyleQuery ? `No matching components found. Your query seems styling-related \u2014 try ${TOOL_NAMES.tokens} to find CSS custom properties.` : "No matching components found. Try different keywords or browse all components with fragments_discover.";
|
|
702
|
+
nextStep = isStyleQuery ? `Use ${TOOL_NAMES.tokens}(search: "${searchTerms[0]}") to find design tokens.` : void 0;
|
|
703
|
+
} else if (weakMatch) {
|
|
704
|
+
recommendation = `Weak matches only \u2014 ${suggestions[0].component} might work but confidence is low.${isStyleQuery ? ` If you need a CSS variable, try ${TOOL_NAMES.tokens}.` : ""}`;
|
|
705
|
+
nextStep = `Use ${TOOL_NAMES.inspect}("${suggestions[0].component}") to check if it fits, or try broader search terms.`;
|
|
706
|
+
} else {
|
|
707
|
+
recommendation = `Best match: ${suggestions[0].component} (${suggestions[0].confidence} confidence) - ${suggestions[0].description}`;
|
|
708
|
+
nextStep = `Use ${TOOL_NAMES.inspect}("${suggestions[0].component}") for full details.`;
|
|
709
|
+
}
|
|
653
710
|
return {
|
|
654
711
|
content: [{
|
|
655
712
|
type: "text",
|
|
@@ -657,9 +714,11 @@ function createMcpServer(config) {
|
|
|
657
714
|
useCase,
|
|
658
715
|
context: context || void 0,
|
|
659
716
|
suggestions: suggestions.map(({ score, ...rest }) => rest),
|
|
660
|
-
|
|
717
|
+
noMatch,
|
|
718
|
+
weakMatch,
|
|
719
|
+
recommendation,
|
|
661
720
|
compositionHint,
|
|
662
|
-
nextStep
|
|
721
|
+
nextStep
|
|
663
722
|
}, null, 2)
|
|
664
723
|
}]
|
|
665
724
|
};
|
|
@@ -848,47 +907,53 @@ function createMcpServer(config) {
|
|
|
848
907
|
};
|
|
849
908
|
}
|
|
850
909
|
// ================================================================
|
|
851
|
-
//
|
|
910
|
+
// BLOCKS — composition patterns
|
|
852
911
|
// ================================================================
|
|
853
|
-
case TOOL_NAMES.
|
|
912
|
+
case TOOL_NAMES.blocks: {
|
|
854
913
|
const data = await loadSegments();
|
|
855
|
-
const
|
|
914
|
+
const blockName = args2?.name;
|
|
856
915
|
const search = args2?.search?.toLowerCase() ?? void 0;
|
|
857
916
|
const component = args2?.component?.toLowerCase() ?? void 0;
|
|
858
|
-
const
|
|
859
|
-
|
|
917
|
+
const category = args2?.category?.toLowerCase() ?? void 0;
|
|
918
|
+
const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
|
|
919
|
+
if (allBlocks.length === 0) {
|
|
860
920
|
return {
|
|
861
921
|
content: [{
|
|
862
922
|
type: "text",
|
|
863
923
|
text: JSON.stringify({
|
|
864
924
|
total: 0,
|
|
865
|
-
|
|
866
|
-
hint: `No
|
|
925
|
+
blocks: [],
|
|
926
|
+
hint: `No blocks found. Run \`${BRAND.cliCommand} build\` after adding .block.ts files.`
|
|
867
927
|
}, null, 2)
|
|
868
928
|
}]
|
|
869
929
|
};
|
|
870
930
|
}
|
|
871
|
-
let filtered =
|
|
872
|
-
if (
|
|
931
|
+
let filtered = allBlocks;
|
|
932
|
+
if (blockName) {
|
|
873
933
|
filtered = filtered.filter(
|
|
874
|
-
(
|
|
934
|
+
(b) => b.name.toLowerCase() === blockName.toLowerCase()
|
|
875
935
|
);
|
|
876
936
|
}
|
|
877
937
|
if (search) {
|
|
878
|
-
filtered = filtered.filter((
|
|
938
|
+
filtered = filtered.filter((b) => {
|
|
879
939
|
const haystack = [
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
...
|
|
883
|
-
...
|
|
884
|
-
|
|
940
|
+
b.name,
|
|
941
|
+
b.description,
|
|
942
|
+
...b.tags ?? [],
|
|
943
|
+
...b.components,
|
|
944
|
+
b.category
|
|
885
945
|
].join(" ").toLowerCase();
|
|
886
946
|
return haystack.includes(search);
|
|
887
947
|
});
|
|
888
948
|
}
|
|
889
949
|
if (component) {
|
|
890
950
|
filtered = filtered.filter(
|
|
891
|
-
(
|
|
951
|
+
(b) => b.components.some((c) => c.toLowerCase() === component)
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
if (category) {
|
|
955
|
+
filtered = filtered.filter(
|
|
956
|
+
(b) => b.category.toLowerCase() === category
|
|
892
957
|
);
|
|
893
958
|
}
|
|
894
959
|
return {
|
|
@@ -896,7 +961,172 @@ function createMcpServer(config) {
|
|
|
896
961
|
type: "text",
|
|
897
962
|
text: JSON.stringify({
|
|
898
963
|
total: filtered.length,
|
|
899
|
-
|
|
964
|
+
blocks: filtered
|
|
965
|
+
}, null, 2)
|
|
966
|
+
}]
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
// ================================================================
|
|
970
|
+
// TOKENS — list CSS custom properties by category
|
|
971
|
+
// ================================================================
|
|
972
|
+
case TOOL_NAMES.tokens: {
|
|
973
|
+
const data = await loadSegments();
|
|
974
|
+
const category = args2?.category?.toLowerCase() ?? void 0;
|
|
975
|
+
const search = args2?.search?.toLowerCase() ?? void 0;
|
|
976
|
+
const tokenData = data.tokens;
|
|
977
|
+
if (!tokenData || tokenData.total === 0) {
|
|
978
|
+
return {
|
|
979
|
+
content: [{
|
|
980
|
+
type: "text",
|
|
981
|
+
text: JSON.stringify({
|
|
982
|
+
total: 0,
|
|
983
|
+
categories: {},
|
|
984
|
+
hint: `No design tokens found. Add a tokens.include pattern to your ${BRAND.configFile} and run \`${BRAND.cliCommand} build\`.`
|
|
985
|
+
}, null, 2)
|
|
986
|
+
}]
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
let filteredCategories = {};
|
|
990
|
+
let filteredTotal = 0;
|
|
991
|
+
for (const [cat, tokens] of Object.entries(tokenData.categories)) {
|
|
992
|
+
if (category && cat !== category) continue;
|
|
993
|
+
let filtered = tokens;
|
|
994
|
+
if (search) {
|
|
995
|
+
filtered = tokens.filter(
|
|
996
|
+
(t) => t.name.toLowerCase().includes(search) || t.description && t.description.toLowerCase().includes(search)
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
if (filtered.length > 0) {
|
|
1000
|
+
filteredCategories[cat] = filtered;
|
|
1001
|
+
filteredTotal += filtered.length;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
let hint;
|
|
1005
|
+
if (filteredTotal === 0) {
|
|
1006
|
+
const availableCategories = Object.keys(tokenData.categories);
|
|
1007
|
+
hint = search ? `No tokens matching "${search}". Try: ${availableCategories.join(", ")}` : category ? `Category "${category}" not found. Available: ${availableCategories.join(", ")}` : void 0;
|
|
1008
|
+
} else if (!category && !search) {
|
|
1009
|
+
hint = `Use var(--token-name) in your CSS/styles. Filter by category or search to narrow results.`;
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
content: [{
|
|
1013
|
+
type: "text",
|
|
1014
|
+
text: JSON.stringify({
|
|
1015
|
+
prefix: tokenData.prefix,
|
|
1016
|
+
total: filteredTotal,
|
|
1017
|
+
totalAvailable: tokenData.total,
|
|
1018
|
+
categories: filteredCategories,
|
|
1019
|
+
...hint && { hint },
|
|
1020
|
+
...!category && !search && {
|
|
1021
|
+
availableCategories: Object.entries(tokenData.categories).map(
|
|
1022
|
+
([cat, tokens]) => ({ category: cat, count: tokens.length })
|
|
1023
|
+
)
|
|
1024
|
+
}
|
|
1025
|
+
}, null, 2)
|
|
1026
|
+
}]
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
// ================================================================
|
|
1030
|
+
// IMPLEMENT — one-shot discover + inspect + blocks + tokens
|
|
1031
|
+
// ================================================================
|
|
1032
|
+
case TOOL_NAMES.implement: {
|
|
1033
|
+
const data = await loadSegments();
|
|
1034
|
+
const useCase = args2?.useCase;
|
|
1035
|
+
if (!useCase) {
|
|
1036
|
+
throw new Error("useCase is required");
|
|
1037
|
+
}
|
|
1038
|
+
const useCaseLower = useCase.toLowerCase();
|
|
1039
|
+
const searchTerms = useCaseLower.split(/\s+/).filter(Boolean);
|
|
1040
|
+
const synonymMap = {
|
|
1041
|
+
"form": ["input", "field", "submit", "validation"],
|
|
1042
|
+
"input": ["form", "field", "text", "entry"],
|
|
1043
|
+
"button": ["action", "click", "submit", "trigger"],
|
|
1044
|
+
"alert": ["notification", "message", "warning", "error", "feedback"],
|
|
1045
|
+
"notification": ["alert", "message", "toast"],
|
|
1046
|
+
"card": ["container", "panel", "box", "content"],
|
|
1047
|
+
"toggle": ["switch", "checkbox", "boolean"],
|
|
1048
|
+
"badge": ["tag", "label", "status", "indicator"],
|
|
1049
|
+
"login": ["auth", "signin", "authentication", "form"],
|
|
1050
|
+
"chat": ["message", "conversation", "ai"],
|
|
1051
|
+
"table": ["data", "grid", "list", "rows"]
|
|
1052
|
+
};
|
|
1053
|
+
const expandedTerms = new Set(searchTerms);
|
|
1054
|
+
searchTerms.forEach((term) => {
|
|
1055
|
+
const synonyms = synonymMap[term];
|
|
1056
|
+
if (synonyms) synonyms.forEach((syn) => expandedTerms.add(syn));
|
|
1057
|
+
});
|
|
1058
|
+
const scored = Object.values(data.segments).map((s) => {
|
|
1059
|
+
let score = 0;
|
|
1060
|
+
const nameLower = s.meta.name.toLowerCase();
|
|
1061
|
+
if (searchTerms.some((t) => nameLower.includes(t))) score += 15;
|
|
1062
|
+
else if (Array.from(expandedTerms).some((t) => nameLower.includes(t))) score += 8;
|
|
1063
|
+
const desc = s.meta.description?.toLowerCase() ?? "";
|
|
1064
|
+
score += searchTerms.filter((t) => desc.includes(t)).length * 6;
|
|
1065
|
+
const tags = s.meta.tags?.map((t) => t.toLowerCase()) ?? [];
|
|
1066
|
+
score += searchTerms.filter((t) => tags.some((tag) => tag.includes(t))).length * 4;
|
|
1067
|
+
const whenUsed = s.usage?.when?.join(" ").toLowerCase() ?? "";
|
|
1068
|
+
score += searchTerms.filter((t) => whenUsed.includes(t)).length * 10;
|
|
1069
|
+
score += Array.from(expandedTerms).filter((t) => !searchTerms.includes(t) && whenUsed.includes(t)).length * 5;
|
|
1070
|
+
if (s.meta.category && searchTerms.some((t) => s.meta.category.toLowerCase().includes(t))) score += 8;
|
|
1071
|
+
if (s.meta.status === "stable") score += 5;
|
|
1072
|
+
if (s.meta.status === "deprecated") score -= 25;
|
|
1073
|
+
return { segment: s, score };
|
|
1074
|
+
});
|
|
1075
|
+
const topMatches = scored.filter((s) => s.score >= 8).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
1076
|
+
const components = await Promise.all(
|
|
1077
|
+
topMatches.map(async ({ segment: s, score }) => {
|
|
1078
|
+
const pkgName = await getPackageName(s.meta.name);
|
|
1079
|
+
const examples = s.variants.slice(0, 2).map((v) => ({
|
|
1080
|
+
variant: v.name,
|
|
1081
|
+
code: v.code ?? `<${s.meta.name} />`
|
|
1082
|
+
}));
|
|
1083
|
+
const propsSummary = Object.entries(s.props ?? {}).slice(0, 10).map(
|
|
1084
|
+
([name2, p]) => `${name2}${p.required ? " (required)" : ""}: ${p.type}${p.values ? ` = ${p.values.join("|")}` : ""}`
|
|
1085
|
+
);
|
|
1086
|
+
return {
|
|
1087
|
+
name: s.meta.name,
|
|
1088
|
+
category: s.meta.category,
|
|
1089
|
+
description: s.meta.description,
|
|
1090
|
+
confidence: score >= 25 ? "high" : score >= 15 ? "medium" : "low",
|
|
1091
|
+
import: `import { ${s.meta.name} } from '${pkgName}';`,
|
|
1092
|
+
props: propsSummary,
|
|
1093
|
+
examples,
|
|
1094
|
+
guidelines: filterPlaceholders(s.usage?.when).slice(0, 3),
|
|
1095
|
+
accessibility: s.usage?.accessibility?.slice(0, 2) ?? []
|
|
1096
|
+
};
|
|
1097
|
+
})
|
|
1098
|
+
);
|
|
1099
|
+
const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
|
|
1100
|
+
const matchingBlocks = allBlocks.filter((b) => {
|
|
1101
|
+
const haystack = [b.name, b.description, ...b.tags ?? [], ...b.components, b.category].join(" ").toLowerCase();
|
|
1102
|
+
return searchTerms.some((t) => haystack.includes(t)) || topMatches.some(({ segment }) => b.components.some((c) => c.toLowerCase() === segment.meta.name.toLowerCase()));
|
|
1103
|
+
}).slice(0, 2).map((b) => ({ name: b.name, description: b.description, components: b.components, code: b.code }));
|
|
1104
|
+
const tokenData = data.tokens;
|
|
1105
|
+
let relevantTokens;
|
|
1106
|
+
if (tokenData) {
|
|
1107
|
+
const STYLE_KEYWORDS = ["color", "spacing", "padding", "margin", "font", "border", "radius", "shadow", "background", "hover", "theme"];
|
|
1108
|
+
const styleTerms = searchTerms.filter((t) => STYLE_KEYWORDS.includes(t));
|
|
1109
|
+
if (styleTerms.length > 0) {
|
|
1110
|
+
relevantTokens = {};
|
|
1111
|
+
for (const [cat, tokens] of Object.entries(tokenData.categories)) {
|
|
1112
|
+
const matching = tokens.filter((t) => styleTerms.some((st) => t.name.includes(st) || cat.includes(st)));
|
|
1113
|
+
if (matching.length > 0) {
|
|
1114
|
+
relevantTokens[cat] = matching.map((t) => t.name);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (Object.keys(relevantTokens).length === 0) relevantTokens = void 0;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
content: [{
|
|
1122
|
+
type: "text",
|
|
1123
|
+
text: JSON.stringify({
|
|
1124
|
+
useCase,
|
|
1125
|
+
components,
|
|
1126
|
+
blocks: matchingBlocks.length > 0 ? matchingBlocks : void 0,
|
|
1127
|
+
tokens: relevantTokens,
|
|
1128
|
+
noMatch: components.length === 0,
|
|
1129
|
+
summary: components.length > 0 ? `Found ${components.length} component(s) for "${useCase}". ${matchingBlocks.length > 0 ? `Plus ${matchingBlocks.length} ready-to-use block(s).` : ""}` : `No components match "${useCase}". Try ${TOOL_NAMES.discover} with different terms${tokenData ? ` or ${TOOL_NAMES.tokens} for CSS variables` : ""}.`
|
|
900
1130
|
}, null, 2)
|
|
901
1131
|
}]
|
|
902
1132
|
};
|