@fragments-sdk/cli 0.4.3 → 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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +12 -12
  3. package/dist/{chunk-NOTYONHY.js → chunk-2DJH4F4P.js} +2 -2
  4. package/dist/{chunk-5CKYLCJH.js → chunk-2H2JAA3U.js} +35 -7
  5. package/dist/chunk-2H2JAA3U.js.map +1 -0
  6. package/dist/{chunk-G3M3MPQ6.js → chunk-B2TQKOLW.js} +157 -30
  7. package/dist/chunk-B2TQKOLW.js.map +1 -0
  8. package/dist/{chunk-AW7MWOUH.js → chunk-ICAIQ57V.js} +9 -5
  9. package/dist/chunk-ICAIQ57V.js.map +1 -0
  10. package/dist/{chunk-5ZYEOHYK.js → chunk-IOJE35DZ.js} +2 -2
  11. package/dist/{chunk-ZFKGX3QK.js → chunk-UXRGD3DM.js} +47 -14
  12. package/dist/chunk-UXRGD3DM.js.map +1 -0
  13. package/dist/{chunk-J4SI5RIH.js → chunk-XNWDI6UT.js} +4 -4
  14. package/dist/{core-LNXDLXDP.js → core-NJVKKLJ4.js} +11 -3
  15. package/dist/{generate-OIXXHOWR.js → generate-OVGMDKCJ.js} +4 -4
  16. package/dist/index.d.ts +30 -4
  17. package/dist/index.js +6 -6
  18. package/dist/{init-EVPXIDW4.js → init-EOA7TTOR.js} +4 -4
  19. package/dist/mcp-bin.js +308 -48
  20. package/dist/mcp-bin.js.map +1 -1
  21. package/dist/scan-YN4LUDKY.js +12 -0
  22. package/dist/{service-K52ORLCJ.js → service-2T26CBWE.js} +4 -4
  23. package/dist/{static-viewer-JNQIHA4B.js → static-viewer-CLJJRYHK.js} +4 -4
  24. package/dist/{test-USARUEFW.js → test-ECPEXFDN.js} +3 -3
  25. package/dist/{tokens-C6YHBOQE.js → tokens-FHA2DO22.js} +5 -5
  26. package/dist/{viewer-H7TVFT4E.js → viewer-XDPD52L7.js} +13 -13
  27. package/package.json +1 -1
  28. package/src/build.ts +53 -13
  29. package/src/core/constants.ts +4 -1
  30. package/src/core/context.ts +28 -28
  31. package/src/core/defineSegment.ts +21 -11
  32. package/src/core/discovery.ts +52 -4
  33. package/src/core/index.ts +14 -4
  34. package/src/core/loader.ts +3 -0
  35. package/src/core/node.ts +3 -1
  36. package/src/core/parser.ts +1 -1
  37. package/src/core/schema.ts +7 -2
  38. package/src/core/token-parser.ts +211 -0
  39. package/src/core/types.ts +46 -6
  40. package/src/mcp/server.ts +378 -56
  41. package/dist/chunk-5CKYLCJH.js.map +0 -1
  42. package/dist/chunk-AW7MWOUH.js.map +0 -1
  43. package/dist/chunk-G3M3MPQ6.js.map +0 -1
  44. package/dist/chunk-ZFKGX3QK.js.map +0 -1
  45. package/dist/scan-YVYD64GD.js +0 -12
  46. /package/dist/{chunk-NOTYONHY.js.map → chunk-2DJH4F4P.js.map} +0 -0
  47. /package/dist/{chunk-5ZYEOHYK.js.map → chunk-IOJE35DZ.js.map} +0 -0
  48. /package/dist/{chunk-J4SI5RIH.js.map → chunk-XNWDI6UT.js.map} +0 -0
  49. /package/dist/{core-LNXDLXDP.js.map → core-NJVKKLJ4.js.map} +0 -0
  50. /package/dist/{generate-OIXXHOWR.js.map → generate-OVGMDKCJ.js.map} +0 -0
  51. /package/dist/{init-EVPXIDW4.js.map → init-EOA7TTOR.js.map} +0 -0
  52. /package/dist/{scan-YVYD64GD.js.map → scan-YN4LUDKY.js.map} +0 -0
  53. /package/dist/{service-K52ORLCJ.js.map → service-2T26CBWE.js.map} +0 -0
  54. /package/dist/{static-viewer-JNQIHA4B.js.map → static-viewer-CLJJRYHK.js.map} +0 -0
  55. /package/dist/{test-USARUEFW.js.map → test-ECPEXFDN.js.map} +0 -0
  56. /package/dist/{tokens-C6YHBOQE.js.map → tokens-FHA2DO22.js.map} +0 -0
  57. /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-G3M3MPQ6.js";
5
+ } from "./chunk-B2TQKOLW.js";
6
6
  import {
7
7
  BRAND,
8
8
  DEFAULTS
9
- } from "./chunk-AW7MWOUH.js";
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-K52ORLCJ.js");
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
- recipe: `${BRAND.nameLower}_recipe`,
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.recipe,
283
- description: `Search and retrieve composition recipes \u2014 named patterns showing how design system components wire together for common use cases (e.g., "Login Form", "Settings Page"). Returns the recipe with its code pattern.`,
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 recipe name to retrieve (e.g., "Login Form")'
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 recipe names, descriptions, tags, and components"
295
+ description: "Free-text search across block names, descriptions, tags, and components"
294
296
  },
295
297
  component: {
296
298
  type: "string",
297
- description: 'Filter recipes that use a specific component (e.g., "Button")'
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")'
298
304
  }
299
305
  }
300
306
  }
301
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")'
321
+ }
322
+ }
323
+ }
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.`,
@@ -383,7 +420,8 @@ function createMcpServer(config) {
383
420
  }
384
421
  );
385
422
  let segmentsData = null;
386
- let packageName = null;
423
+ const segmentPackageMap = /* @__PURE__ */ new Map();
424
+ let defaultPackageName = null;
387
425
  let browserPool = null;
388
426
  let storageManager = null;
389
427
  let diffEngine = null;
@@ -400,23 +438,41 @@ function createMcpServer(config) {
400
438
  }
401
439
  const content = await readFile(paths[0], "utf-8");
402
440
  segmentsData = JSON.parse(content);
441
+ if (!segmentsData.blocks && segmentsData.recipes) {
442
+ segmentsData.blocks = segmentsData.recipes;
443
+ }
444
+ if (segmentsData.packageName) {
445
+ for (const name of Object.keys(segmentsData.segments)) {
446
+ segmentPackageMap.set(name, segmentsData.packageName);
447
+ }
448
+ }
403
449
  for (let i = 1; i < paths.length; i++) {
404
450
  const extra = JSON.parse(await readFile(paths[i], "utf-8"));
451
+ if (extra.packageName) {
452
+ for (const name of Object.keys(extra.segments)) {
453
+ segmentPackageMap.set(name, extra.packageName);
454
+ }
455
+ }
405
456
  Object.assign(segmentsData.segments, extra.segments);
406
- if (extra.recipes) {
407
- segmentsData.recipes = { ...segmentsData.recipes, ...extra.recipes };
457
+ const extraBlocks = extra.blocks ?? extra.recipes;
458
+ if (extraBlocks) {
459
+ segmentsData.blocks = { ...segmentsData.blocks, ...extraBlocks };
408
460
  }
409
461
  }
410
462
  return segmentsData;
411
463
  }
412
- async function getPackageName() {
413
- if (packageName) {
414
- return packageName;
464
+ async function getPackageName(segmentName) {
465
+ await loadSegments();
466
+ if (segmentName) {
467
+ const segPkg = segmentPackageMap.get(segmentName);
468
+ if (segPkg) return segPkg;
415
469
  }
416
- const data = await loadSegments();
417
- if (data.packageName) {
418
- packageName = data.packageName;
419
- return packageName;
470
+ if (defaultPackageName) {
471
+ return defaultPackageName;
472
+ }
473
+ if (segmentsData?.packageName) {
474
+ defaultPackageName = segmentsData.packageName;
475
+ return defaultPackageName;
420
476
  }
421
477
  const packageJsonPath = join(config.projectRoot, "package.json");
422
478
  if (existsSync(packageJsonPath)) {
@@ -424,14 +480,14 @@ function createMcpServer(config) {
424
480
  const content = await readFile(packageJsonPath, "utf-8");
425
481
  const pkg = JSON.parse(content);
426
482
  if (pkg.name) {
427
- packageName = pkg.name;
428
- return packageName;
483
+ defaultPackageName = pkg.name;
484
+ return defaultPackageName;
429
485
  }
430
486
  } catch {
431
487
  }
432
488
  }
433
- packageName = "your-component-library";
434
- return packageName;
489
+ defaultPackageName = "your-component-library";
490
+ return defaultPackageName;
435
491
  }
436
492
  async function getBrowserPool() {
437
493
  if (!browserPool) {
@@ -501,7 +557,7 @@ function createMcpServer(config) {
501
557
  const includeRelations = args2?.includeRelations ?? false;
502
558
  if (compact || args2?.format && !useCase && !componentForAlts && !category && !search && !status) {
503
559
  const segments2 = Object.values(data.segments);
504
- const recipes = Object.values(data.recipes ?? {});
560
+ const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
505
561
  const { content: ctxContent, tokenEstimate } = generateContext(segments2, {
506
562
  format,
507
563
  compact,
@@ -509,7 +565,7 @@ function createMcpServer(config) {
509
565
  code: includeCode,
510
566
  relations: includeRelations
511
567
  }
512
- }, recipes);
568
+ }, allBlocks);
513
569
  return {
514
570
  content: [{
515
571
  type: "text",
@@ -635,6 +691,22 @@ function createMcpServer(config) {
635
691
  }
636
692
  }
637
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
+ }
638
710
  return {
639
711
  content: [{
640
712
  type: "text",
@@ -642,9 +714,11 @@ function createMcpServer(config) {
642
714
  useCase,
643
715
  context: context || void 0,
644
716
  suggestions: suggestions.map(({ score, ...rest }) => rest),
645
- recommendation: suggestions.length > 0 ? `Best match: ${suggestions[0].component} (${suggestions[0].confidence} confidence) - ${suggestions[0].description}` : "No matching components found. Try different keywords or browse with fragments_discover.",
717
+ noMatch,
718
+ weakMatch,
719
+ recommendation,
646
720
  compositionHint,
647
- nextStep: suggestions.length > 0 ? `Use fragments_inspect("${suggestions[0].component}") for full details.` : void 0
721
+ nextStep
648
722
  }, null, 2)
649
723
  }]
650
724
  };
@@ -737,14 +811,29 @@ function createMcpServer(config) {
737
811
  if (!segment) {
738
812
  throw new Error(`Component "${componentName}" not found. Use fragments_discover to see available components.`);
739
813
  }
740
- const pkgName = await getPackageName();
814
+ const pkgName = await getPackageName(segment.meta.name);
741
815
  let variants = segment.variants;
742
816
  if (variantName) {
743
- const filtered = variants.filter(
744
- (v) => v.name.toLowerCase() === variantName.toLowerCase()
817
+ const query = variantName.toLowerCase();
818
+ let filtered = variants.filter(
819
+ (v) => v.name.toLowerCase() === query
745
820
  );
821
+ if (filtered.length === 0) {
822
+ filtered = variants.filter(
823
+ (v) => v.name.toLowerCase().startsWith(query)
824
+ );
825
+ }
826
+ if (filtered.length === 0) {
827
+ filtered = variants.filter(
828
+ (v) => v.name.toLowerCase().includes(query)
829
+ );
830
+ }
746
831
  if (filtered.length > 0) {
747
832
  variants = filtered;
833
+ } else {
834
+ throw new Error(
835
+ `Variant "${variantName}" not found for ${componentName}. Available: ${segment.variants.map((v) => v.name).join(", ")}`
836
+ );
748
837
  }
749
838
  }
750
839
  if (maxExamples && maxExamples > 0) {
@@ -818,47 +907,53 @@ function createMcpServer(config) {
818
907
  };
819
908
  }
820
909
  // ================================================================
821
- // RECIPEunchanged
910
+ // BLOCKScomposition patterns
822
911
  // ================================================================
823
- case TOOL_NAMES.recipe: {
912
+ case TOOL_NAMES.blocks: {
824
913
  const data = await loadSegments();
825
- const recipeName = args2?.name;
914
+ const blockName = args2?.name;
826
915
  const search = args2?.search?.toLowerCase() ?? void 0;
827
916
  const component = args2?.component?.toLowerCase() ?? void 0;
828
- const allRecipes = Object.values(data.recipes ?? {});
829
- if (allRecipes.length === 0) {
917
+ const category = args2?.category?.toLowerCase() ?? void 0;
918
+ const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
919
+ if (allBlocks.length === 0) {
830
920
  return {
831
921
  content: [{
832
922
  type: "text",
833
923
  text: JSON.stringify({
834
924
  total: 0,
835
- recipes: [],
836
- hint: `No recipes found. Run \`${BRAND.cliCommand} build\` after adding .recipe.ts files.`
925
+ blocks: [],
926
+ hint: `No blocks found. Run \`${BRAND.cliCommand} build\` after adding .block.ts files.`
837
927
  }, null, 2)
838
928
  }]
839
929
  };
840
930
  }
841
- let filtered = allRecipes;
842
- if (recipeName) {
931
+ let filtered = allBlocks;
932
+ if (blockName) {
843
933
  filtered = filtered.filter(
844
- (r) => r.name.toLowerCase() === recipeName.toLowerCase()
934
+ (b) => b.name.toLowerCase() === blockName.toLowerCase()
845
935
  );
846
936
  }
847
937
  if (search) {
848
- filtered = filtered.filter((r) => {
938
+ filtered = filtered.filter((b) => {
849
939
  const haystack = [
850
- r.name,
851
- r.description,
852
- ...r.tags ?? [],
853
- ...r.components,
854
- r.category
940
+ b.name,
941
+ b.description,
942
+ ...b.tags ?? [],
943
+ ...b.components,
944
+ b.category
855
945
  ].join(" ").toLowerCase();
856
946
  return haystack.includes(search);
857
947
  });
858
948
  }
859
949
  if (component) {
860
950
  filtered = filtered.filter(
861
- (r) => r.components.some((c) => c.toLowerCase() === component)
951
+ (b) => b.components.some((c) => c.toLowerCase() === component)
952
+ );
953
+ }
954
+ if (category) {
955
+ filtered = filtered.filter(
956
+ (b) => b.category.toLowerCase() === category
862
957
  );
863
958
  }
864
959
  return {
@@ -866,7 +961,172 @@ function createMcpServer(config) {
866
961
  type: "text",
867
962
  text: JSON.stringify({
868
963
  total: filtered.length,
869
- recipes: filtered
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` : ""}.`
870
1130
  }, null, 2)
871
1131
  }]
872
1132
  };