@fragments-sdk/mcp 0.5.5 → 0.5.7
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.
|
@@ -312,7 +312,7 @@ function estimateTokens(text) {
|
|
|
312
312
|
return Math.ceil(text.length / 4);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
// ../context/dist/chunk-
|
|
315
|
+
// ../context/dist/chunk-NECSN43I.js
|
|
316
316
|
var MCP_TOOL_DEFINITIONS = [
|
|
317
317
|
{
|
|
318
318
|
key: "discover",
|
|
@@ -352,9 +352,18 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
352
352
|
type: "boolean",
|
|
353
353
|
description: "If true, includes code examples for each variant"
|
|
354
354
|
},
|
|
355
|
+
limit: {
|
|
356
|
+
type: "number",
|
|
357
|
+
description: "Maximum number of results to return (default: 10 for useCase mode)"
|
|
358
|
+
},
|
|
355
359
|
includeRelations: {
|
|
356
360
|
type: "boolean",
|
|
357
361
|
description: "If true, includes component relationships"
|
|
362
|
+
},
|
|
363
|
+
verbosity: {
|
|
364
|
+
type: "string",
|
|
365
|
+
enum: ["compact", "standard", "full"],
|
|
366
|
+
description: 'Response detail level: "compact" (names only), "standard" (default), "full" (everything including code)'
|
|
358
367
|
}
|
|
359
368
|
}
|
|
360
369
|
},
|
|
@@ -369,7 +378,7 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
369
378
|
fields: {
|
|
370
379
|
type: "array",
|
|
371
380
|
items: { type: "string" },
|
|
372
|
-
description: 'Specific fields to return (e.g., ["meta", "
|
|
381
|
+
description: 'Specific fields to return (e.g., ["meta", "guidelines.when", "contract.propsSummary", "props", "examples"]). If omitted, returns everything. Supports dot notation. Aliases: "usage" \u2192 "guidelines".'
|
|
373
382
|
},
|
|
374
383
|
variant: {
|
|
375
384
|
type: "string",
|
|
@@ -382,6 +391,11 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
382
391
|
maxLines: {
|
|
383
392
|
type: "number",
|
|
384
393
|
description: "Maximum lines per code example (truncates longer examples)"
|
|
394
|
+
},
|
|
395
|
+
verbosity: {
|
|
396
|
+
type: "string",
|
|
397
|
+
enum: ["compact", "standard", "full"],
|
|
398
|
+
description: 'Response detail level: "compact" (meta + prop names), "standard" (default), "full" (everything)'
|
|
385
399
|
}
|
|
386
400
|
},
|
|
387
401
|
required: ["component"]
|
|
@@ -405,6 +419,15 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
405
419
|
category: {
|
|
406
420
|
type: "string",
|
|
407
421
|
description: 'Filter by category (e.g., "authentication", "marketing", "dashboard", "settings", "ecommerce", "ai")'
|
|
422
|
+
},
|
|
423
|
+
limit: {
|
|
424
|
+
type: "number",
|
|
425
|
+
description: "Maximum number of blocks to return (default: all matching)"
|
|
426
|
+
},
|
|
427
|
+
verbosity: {
|
|
428
|
+
type: "string",
|
|
429
|
+
enum: ["compact", "standard", "full"],
|
|
430
|
+
description: 'Response detail level: "compact" (no code), "standard" (default, code preview for long blocks), "full" (full code)'
|
|
408
431
|
}
|
|
409
432
|
}
|
|
410
433
|
},
|
|
@@ -419,6 +442,10 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
419
442
|
search: {
|
|
420
443
|
type: "string",
|
|
421
444
|
description: 'Search token names (e.g., "accent", "hover", "padding")'
|
|
445
|
+
},
|
|
446
|
+
limit: {
|
|
447
|
+
type: "number",
|
|
448
|
+
description: "Maximum number of tokens to return per category (default: 25 for search, unlimited for category browsing)"
|
|
422
449
|
}
|
|
423
450
|
}
|
|
424
451
|
},
|
|
@@ -429,6 +456,15 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
429
456
|
useCase: {
|
|
430
457
|
type: "string",
|
|
431
458
|
description: 'What you want to implement (e.g., "login form", "data table with sorting", "streaming chat messages")'
|
|
459
|
+
},
|
|
460
|
+
limit: {
|
|
461
|
+
type: "number",
|
|
462
|
+
description: "Maximum number of components to return (default: 5)"
|
|
463
|
+
},
|
|
464
|
+
verbosity: {
|
|
465
|
+
type: "string",
|
|
466
|
+
enum: ["compact", "standard", "full"],
|
|
467
|
+
description: 'Response detail level: "compact" (names only), "standard" (default), "full" (all props + examples + full block code)'
|
|
432
468
|
}
|
|
433
469
|
},
|
|
434
470
|
required: ["useCase"]
|
|
@@ -465,7 +501,7 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
465
501
|
},
|
|
466
502
|
variant: {
|
|
467
503
|
type: "string",
|
|
468
|
-
description: "Variant name
|
|
504
|
+
description: "Variant name to render (uses the variant's render function from the fragment definition). Works in both render and compare modes."
|
|
469
505
|
},
|
|
470
506
|
threshold: {
|
|
471
507
|
type: "number",
|
|
@@ -496,7 +532,7 @@ var MCP_TOOL_DEFINITIONS = [
|
|
|
496
532
|
},
|
|
497
533
|
{
|
|
498
534
|
key: "graph",
|
|
499
|
-
description: 'Query the component relationship graph. Understand dependencies, impact analysis, composition trees, alternatives, and design system health. Use "health" for an overview, "dependencies"/"dependents" for
|
|
535
|
+
description: 'Query the component relationship graph. Understand dependencies, impact analysis, composition trees, alternatives, and design system health. Use "health" for an overview, "dependencies"/"dependents" for composition and declared relationships (not code-level imports), "impact" for change analysis, "composition" for compound component trees.',
|
|
500
536
|
params: {
|
|
501
537
|
mode: {
|
|
502
538
|
type: "string",
|
|
@@ -737,9 +773,8 @@ function findFragmentsJson(startDir) {
|
|
|
737
773
|
return found;
|
|
738
774
|
}
|
|
739
775
|
|
|
740
|
-
// src/
|
|
741
|
-
|
|
742
|
-
var CONVEX_TIMEOUT_MS = 3e3;
|
|
776
|
+
// src/orama-index.ts
|
|
777
|
+
import { create, insertMultiple, search } from "@orama/orama";
|
|
743
778
|
var SYNONYM_MAP = {
|
|
744
779
|
"form": ["input", "field", "submit", "validation"],
|
|
745
780
|
"input": ["form", "field", "text", "entry"],
|
|
@@ -759,8 +794,259 @@ var SYNONYM_MAP = {
|
|
|
759
794
|
"chat": ["message", "conversation", "ai"],
|
|
760
795
|
"table": ["data", "grid", "list", "rows"],
|
|
761
796
|
"textarea": ["text", "input", "multiline", "area", "comment"],
|
|
762
|
-
"area": ["textarea", "multiline", "text"]
|
|
797
|
+
"area": ["textarea", "multiline", "text"],
|
|
798
|
+
"landing": ["page", "hero", "marketing", "section", "layout"],
|
|
799
|
+
"hero": ["landing", "marketing", "banner", "headline", "section"],
|
|
800
|
+
"marketing": ["landing", "hero", "pricing", "testimonial", "cta"],
|
|
801
|
+
"cta": ["marketing", "banner", "action", "button"],
|
|
802
|
+
"testimonial": ["marketing", "review", "quote", "feedback"],
|
|
803
|
+
"layout": ["stack", "grid", "box", "container", "page"],
|
|
804
|
+
"page": ["layout", "landing", "section", "container"],
|
|
805
|
+
"section": ["hero", "feature", "testimonial", "cta", "faq"],
|
|
806
|
+
"pricing": ["card", "plan", "tier", "marketing"],
|
|
807
|
+
"plan": ["pricing", "card", "tier", "subscription"],
|
|
808
|
+
"dashboard": ["metrics", "stats", "chart", "card", "grid"],
|
|
809
|
+
"metrics": ["dashboard", "stats", "progress", "number"],
|
|
810
|
+
"stats": ["metrics", "dashboard", "progress", "badge"],
|
|
811
|
+
"chart": ["dashboard", "metrics", "data", "graph"]
|
|
812
|
+
};
|
|
813
|
+
function expandQuery(query) {
|
|
814
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
815
|
+
const expanded = new Set(terms);
|
|
816
|
+
for (const term of terms) {
|
|
817
|
+
const synonyms = SYNONYM_MAP[term];
|
|
818
|
+
if (synonyms) {
|
|
819
|
+
for (const syn of synonyms) expanded.add(syn);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return Array.from(expanded).join(" ");
|
|
823
|
+
}
|
|
824
|
+
function twoPassSearch(config) {
|
|
825
|
+
const { index, query, properties, boost, limit, kind } = config;
|
|
826
|
+
const baseConfig = {
|
|
827
|
+
mode: "fulltext",
|
|
828
|
+
properties,
|
|
829
|
+
boost,
|
|
830
|
+
limit
|
|
831
|
+
};
|
|
832
|
+
const originalTermsQuery = query.toLowerCase().split(/\s+/).filter(Boolean).join(" ");
|
|
833
|
+
const expandedQuery = expandQuery(query);
|
|
834
|
+
const originalResults = search(index, { term: originalTermsQuery, ...baseConfig, threshold: 0.8 });
|
|
835
|
+
const expandedResults = search(index, { term: expandedQuery, ...baseConfig, threshold: 1 });
|
|
836
|
+
const origHits = originalResults.hits;
|
|
837
|
+
const expHits = expandedResults.hits;
|
|
838
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
839
|
+
for (const hit of origHits) {
|
|
840
|
+
scoreMap.set(hit.document.name, (hit.score || 0) * 2);
|
|
841
|
+
}
|
|
842
|
+
for (const hit of expHits) {
|
|
843
|
+
const name = hit.document.name;
|
|
844
|
+
const existing = scoreMap.get(name) ?? 0;
|
|
845
|
+
scoreMap.set(name, existing + (hit.score || 0));
|
|
846
|
+
}
|
|
847
|
+
const scored = [];
|
|
848
|
+
for (const [name, score] of scoreMap) {
|
|
849
|
+
if (score > 0) {
|
|
850
|
+
scored.push({ name, kind, rank: scored.length, score });
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
scored.sort((a, b) => b.score - a.score);
|
|
854
|
+
scored.forEach((s, i) => {
|
|
855
|
+
s.rank = i;
|
|
856
|
+
});
|
|
857
|
+
return scored;
|
|
858
|
+
}
|
|
859
|
+
var componentSchema = {
|
|
860
|
+
name: "string",
|
|
861
|
+
description: "string",
|
|
862
|
+
category: "string",
|
|
863
|
+
tags: "string",
|
|
864
|
+
whenUsed: "string",
|
|
865
|
+
variants: "string",
|
|
866
|
+
status: "string"
|
|
867
|
+
};
|
|
868
|
+
function buildComponentIndex(fragments) {
|
|
869
|
+
const db = create({ schema: componentSchema, language: "english" });
|
|
870
|
+
const docs = fragments.map((f) => ({
|
|
871
|
+
name: f.meta.name,
|
|
872
|
+
description: f.meta.description ?? "",
|
|
873
|
+
category: f.meta.category ?? "",
|
|
874
|
+
tags: (f.meta.tags ?? []).join(" "),
|
|
875
|
+
whenUsed: (f.usage?.when ?? []).join(" "),
|
|
876
|
+
variants: f.variants.map((v) => `${v.name} ${v.description || ""}`).join(" "),
|
|
877
|
+
status: f.meta.status ?? "stable"
|
|
878
|
+
}));
|
|
879
|
+
insertMultiple(db, docs);
|
|
880
|
+
return db;
|
|
881
|
+
}
|
|
882
|
+
function searchComponents(query, index, fragments, limit = 50) {
|
|
883
|
+
const boostConfig = {
|
|
884
|
+
mode: "fulltext",
|
|
885
|
+
properties: ["name", "whenUsed", "description", "category", "tags", "variants"],
|
|
886
|
+
boost: {
|
|
887
|
+
name: 3,
|
|
888
|
+
whenUsed: 2.5,
|
|
889
|
+
description: 2,
|
|
890
|
+
category: 1.5,
|
|
891
|
+
tags: 1.5,
|
|
892
|
+
variants: 1
|
|
893
|
+
},
|
|
894
|
+
limit
|
|
895
|
+
};
|
|
896
|
+
const originalTermsList = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
897
|
+
const originalTermsQuery = originalTermsList.join(" ");
|
|
898
|
+
const expandedQuery = expandQuery(query);
|
|
899
|
+
const originalResults = search(index, { term: originalTermsQuery, ...boostConfig, threshold: 0.8 });
|
|
900
|
+
const expandedResults = search(index, { term: expandedQuery, ...boostConfig, threshold: 1 });
|
|
901
|
+
const origHits = originalResults.hits;
|
|
902
|
+
const expHits = expandedResults.hits;
|
|
903
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
904
|
+
for (const hit of origHits) {
|
|
905
|
+
scoreMap.set(hit.document.name, (hit.score || 0) * 2);
|
|
906
|
+
}
|
|
907
|
+
for (const hit of expHits) {
|
|
908
|
+
const name = hit.document.name;
|
|
909
|
+
const existing = scoreMap.get(name) ?? 0;
|
|
910
|
+
scoreMap.set(name, existing + (hit.score || 0));
|
|
911
|
+
}
|
|
912
|
+
const fragmentMap = /* @__PURE__ */ new Map();
|
|
913
|
+
for (const f of fragments) {
|
|
914
|
+
fragmentMap.set(f.meta.name.toLowerCase(), f);
|
|
915
|
+
}
|
|
916
|
+
const originalTermsSet = new Set(originalTermsList);
|
|
917
|
+
const scored = [];
|
|
918
|
+
for (const [name, rawScore] of scoreMap) {
|
|
919
|
+
let score = rawScore;
|
|
920
|
+
const nameLower = name.toLowerCase();
|
|
921
|
+
const fragment = fragmentMap.get(nameLower);
|
|
922
|
+
if (originalTermsSet.has(nameLower)) {
|
|
923
|
+
score += 25;
|
|
924
|
+
}
|
|
925
|
+
if (fragment) {
|
|
926
|
+
if (fragment.meta.status === "stable") score += 5;
|
|
927
|
+
else if (fragment.meta.status === "beta") score += 2;
|
|
928
|
+
if (fragment.meta.status === "deprecated") score -= 25;
|
|
929
|
+
}
|
|
930
|
+
if (score > 0) {
|
|
931
|
+
scored.push({ name, kind: "component", rank: scored.length, score });
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
scored.sort((a, b) => b.score - a.score);
|
|
935
|
+
scored.forEach((s, i) => {
|
|
936
|
+
s.rank = i;
|
|
937
|
+
});
|
|
938
|
+
return scored;
|
|
939
|
+
}
|
|
940
|
+
var blockSchema = {
|
|
941
|
+
name: "string",
|
|
942
|
+
description: "string",
|
|
943
|
+
category: "string",
|
|
944
|
+
tags: "string",
|
|
945
|
+
components: "string"
|
|
946
|
+
};
|
|
947
|
+
function buildBlockIndex(blocks) {
|
|
948
|
+
const db = create({ schema: blockSchema, language: "english" });
|
|
949
|
+
const docs = blocks.map((b) => ({
|
|
950
|
+
name: b.name,
|
|
951
|
+
description: b.description ?? "",
|
|
952
|
+
category: b.category ?? "",
|
|
953
|
+
tags: (b.tags ?? []).join(" "),
|
|
954
|
+
components: b.components.join(" ")
|
|
955
|
+
}));
|
|
956
|
+
insertMultiple(db, docs);
|
|
957
|
+
return db;
|
|
958
|
+
}
|
|
959
|
+
function searchBlocks(query, index, limit = 50) {
|
|
960
|
+
return twoPassSearch({
|
|
961
|
+
index,
|
|
962
|
+
query,
|
|
963
|
+
properties: ["name", "description", "components", "tags", "category"],
|
|
964
|
+
boost: {
|
|
965
|
+
name: 3,
|
|
966
|
+
description: 2,
|
|
967
|
+
components: 1.5,
|
|
968
|
+
tags: 1.5,
|
|
969
|
+
category: 1.5
|
|
970
|
+
},
|
|
971
|
+
limit,
|
|
972
|
+
kind: "block"
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
var tokenSchema = {
|
|
976
|
+
name: "string",
|
|
977
|
+
category: "string",
|
|
978
|
+
description: "string"
|
|
763
979
|
};
|
|
980
|
+
function buildTokenIndex(tokenData) {
|
|
981
|
+
const db = create({ schema: tokenSchema, language: "english" });
|
|
982
|
+
const docs = [];
|
|
983
|
+
for (const [cat, tokens] of Object.entries(tokenData.categories)) {
|
|
984
|
+
for (const token of tokens) {
|
|
985
|
+
docs.push({
|
|
986
|
+
name: token.name,
|
|
987
|
+
category: cat,
|
|
988
|
+
description: token.description ?? ""
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
insertMultiple(db, docs);
|
|
993
|
+
return db;
|
|
994
|
+
}
|
|
995
|
+
function searchTokens(query, index, limit = 50) {
|
|
996
|
+
return twoPassSearch({
|
|
997
|
+
index,
|
|
998
|
+
query,
|
|
999
|
+
properties: ["name", "category", "description"],
|
|
1000
|
+
boost: {
|
|
1001
|
+
name: 2.5,
|
|
1002
|
+
category: 2,
|
|
1003
|
+
description: 1.5
|
|
1004
|
+
},
|
|
1005
|
+
limit,
|
|
1006
|
+
kind: "token"
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
var USE_CASE_TOKEN_CATEGORIES = {
|
|
1010
|
+
"table": ["spacing", "borders", "surfaces", "text"],
|
|
1011
|
+
"data": ["spacing", "borders", "surfaces"],
|
|
1012
|
+
"grid": ["spacing", "layout"],
|
|
1013
|
+
"form": ["spacing", "borders", "radius", "focus"],
|
|
1014
|
+
"input": ["spacing", "borders", "radius", "focus"],
|
|
1015
|
+
"card": ["surfaces", "shadows", "radius", "borders", "spacing"],
|
|
1016
|
+
"button": ["colors", "radius", "spacing", "focus"],
|
|
1017
|
+
"layout": ["spacing", "layout", "surfaces"],
|
|
1018
|
+
"dashboard": ["spacing", "surfaces", "borders", "shadows"],
|
|
1019
|
+
"chat": ["spacing", "surfaces", "radius", "shadows"],
|
|
1020
|
+
"modal": ["shadows", "surfaces", "radius", "spacing"],
|
|
1021
|
+
"dialog": ["shadows", "surfaces", "radius", "spacing"],
|
|
1022
|
+
"navigation": ["spacing", "surfaces", "borders"],
|
|
1023
|
+
"sidebar": ["spacing", "surfaces", "borders"],
|
|
1024
|
+
"hero": ["spacing", "typography", "colors"],
|
|
1025
|
+
"landing": ["spacing", "typography", "colors"],
|
|
1026
|
+
"pricing": ["spacing", "surfaces", "borders", "radius"],
|
|
1027
|
+
"auth": ["spacing", "borders", "radius", "focus"],
|
|
1028
|
+
"login": ["spacing", "borders", "radius", "focus"],
|
|
1029
|
+
"dark": ["colors", "surfaces"],
|
|
1030
|
+
"theme": ["colors", "surfaces", "text"]
|
|
1031
|
+
};
|
|
1032
|
+
function extractTokenCategories(query) {
|
|
1033
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1034
|
+
const categories = /* @__PURE__ */ new Set();
|
|
1035
|
+
for (const term of terms) {
|
|
1036
|
+
const cats = USE_CASE_TOKEN_CATEGORIES[term];
|
|
1037
|
+
if (cats) {
|
|
1038
|
+
for (const cat of cats) categories.add(cat);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (categories.size === 0) {
|
|
1042
|
+
return ["spacing", "colors", "surfaces"];
|
|
1043
|
+
}
|
|
1044
|
+
return Array.from(categories);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/search.ts
|
|
1048
|
+
var CONVEX_SEARCH_URL = "https://combative-jay-834.convex.site/search";
|
|
1049
|
+
var CONVEX_TIMEOUT_MS = 3e3;
|
|
764
1050
|
async function searchConvex(query, apiKey, limit = 10, kind) {
|
|
765
1051
|
try {
|
|
766
1052
|
const controller = new AbortController();
|
|
@@ -789,94 +1075,17 @@ async function searchConvex(query, apiKey, limit = 10, kind) {
|
|
|
789
1075
|
return [];
|
|
790
1076
|
}
|
|
791
1077
|
}
|
|
792
|
-
function keywordScoreComponents(query, fragments) {
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
for (const term of searchTerms) {
|
|
796
|
-
const synonyms = SYNONYM_MAP[term];
|
|
797
|
-
if (synonyms) {
|
|
798
|
-
for (const syn of synonyms) expandedTerms.add(syn);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
const scored = fragments.map((s) => {
|
|
802
|
-
let score = 0;
|
|
803
|
-
const nameLower = s.meta.name.toLowerCase();
|
|
804
|
-
if (searchTerms.some((term) => nameLower.includes(term))) {
|
|
805
|
-
score += 15;
|
|
806
|
-
} else if (Array.from(expandedTerms).some((term) => nameLower.includes(term))) {
|
|
807
|
-
score += 8;
|
|
808
|
-
}
|
|
809
|
-
const desc = s.meta.description?.toLowerCase() ?? "";
|
|
810
|
-
score += searchTerms.filter((term) => desc.includes(term)).length * 6;
|
|
811
|
-
const tags = s.meta.tags?.map((t) => t.toLowerCase()) ?? [];
|
|
812
|
-
score += searchTerms.filter((term) => tags.some((tag) => tag.includes(term))).length * 4;
|
|
813
|
-
const whenUsed = s.usage?.when?.join(" ").toLowerCase() ?? "";
|
|
814
|
-
score += searchTerms.filter((term) => whenUsed.includes(term)).length * 10;
|
|
815
|
-
score += Array.from(expandedTerms).filter((term) => !searchTerms.includes(term) && whenUsed.includes(term)).length * 5;
|
|
816
|
-
const cat = s.meta.category?.toLowerCase() ?? "";
|
|
817
|
-
if (searchTerms.some((term) => cat.includes(term))) {
|
|
818
|
-
score += 8;
|
|
819
|
-
}
|
|
820
|
-
const variantText = s.variants.map((v) => `${v.name} ${v.description || ""}`.toLowerCase()).join(" ");
|
|
821
|
-
score += searchTerms.filter((term) => variantText.includes(term)).length * 3;
|
|
822
|
-
if (s.meta.status === "stable") score += 5;
|
|
823
|
-
else if (s.meta.status === "beta") score += 2;
|
|
824
|
-
if (s.meta.status === "deprecated") score -= 25;
|
|
825
|
-
return { name: s.meta.name, kind: "component", score };
|
|
826
|
-
});
|
|
827
|
-
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).map((s, i) => ({ ...s, rank: i }));
|
|
1078
|
+
function keywordScoreComponents(query, fragments, componentIndex) {
|
|
1079
|
+
const index = componentIndex ?? buildComponentIndex(fragments);
|
|
1080
|
+
return searchComponents(query, index, fragments);
|
|
828
1081
|
}
|
|
829
|
-
function keywordScoreBlocks(query, blocks) {
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
for (const term of searchTerms) {
|
|
833
|
-
const synonyms = SYNONYM_MAP[term];
|
|
834
|
-
if (synonyms) {
|
|
835
|
-
for (const syn of synonyms) expandedTerms.add(syn);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
const scored = blocks.map((b) => {
|
|
839
|
-
let score = 0;
|
|
840
|
-
const nameLower = b.name.toLowerCase();
|
|
841
|
-
if (searchTerms.some((term) => nameLower.includes(term))) {
|
|
842
|
-
score += 15;
|
|
843
|
-
} else if (Array.from(expandedTerms).some((term) => nameLower.includes(term))) {
|
|
844
|
-
score += 8;
|
|
845
|
-
}
|
|
846
|
-
const desc = b.description.toLowerCase();
|
|
847
|
-
score += searchTerms.filter((term) => desc.includes(term)).length * 6;
|
|
848
|
-
const tags = b.tags?.map((t) => t.toLowerCase()) ?? [];
|
|
849
|
-
score += searchTerms.filter((term) => tags.some((tag) => tag.includes(term))).length * 4;
|
|
850
|
-
const componentText = b.components.join(" ").toLowerCase();
|
|
851
|
-
score += searchTerms.filter((term) => componentText.includes(term)).length * 5;
|
|
852
|
-
const cat = b.category.toLowerCase();
|
|
853
|
-
if (searchTerms.some((term) => cat.includes(term))) {
|
|
854
|
-
score += 8;
|
|
855
|
-
}
|
|
856
|
-
return { name: b.name, kind: "block", score };
|
|
857
|
-
});
|
|
858
|
-
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).map((s, i) => ({ ...s, rank: i }));
|
|
1082
|
+
function keywordScoreBlocks(query, blocks, blockIndex) {
|
|
1083
|
+
const index = blockIndex ?? buildBlockIndex(blocks);
|
|
1084
|
+
return searchBlocks(query, index);
|
|
859
1085
|
}
|
|
860
|
-
function keywordScoreTokens(query, tokenData) {
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
for (const [cat, tokens] of Object.entries(tokenData.categories)) {
|
|
864
|
-
const catLower = cat.toLowerCase();
|
|
865
|
-
const catBonus = searchTerms.some((term) => catLower.includes(term)) ? 8 : 0;
|
|
866
|
-
for (const token of tokens) {
|
|
867
|
-
let score = catBonus;
|
|
868
|
-
const nameLower = token.name.toLowerCase();
|
|
869
|
-
score += searchTerms.filter((term) => nameLower.includes(term)).length * 10;
|
|
870
|
-
if (token.description) {
|
|
871
|
-
const descLower = token.description.toLowerCase();
|
|
872
|
-
score += searchTerms.filter((term) => descLower.includes(term)).length * 6;
|
|
873
|
-
}
|
|
874
|
-
if (score > 0) {
|
|
875
|
-
scored.push({ name: token.name, kind: "token", score });
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
return scored.sort((a, b) => b.score - a.score).map((s, i) => ({ ...s, rank: i }));
|
|
1086
|
+
function keywordScoreTokens(query, tokenData, tokenIndex) {
|
|
1087
|
+
const index = tokenIndex ?? buildTokenIndex(tokenData);
|
|
1088
|
+
return searchTokens(query, index);
|
|
880
1089
|
}
|
|
881
1090
|
function reciprocalRankFusion(resultSets, k = 60) {
|
|
882
1091
|
const scoreMap = /* @__PURE__ */ new Map();
|
|
@@ -906,13 +1115,13 @@ function reciprocalRankFusion(resultSets, k = 60) {
|
|
|
906
1115
|
async function hybridSearch(query, data, limit = 10, kind, apiKey) {
|
|
907
1116
|
const keywordResults = [];
|
|
908
1117
|
if (!kind || kind === "component") {
|
|
909
|
-
keywordResults.push(...keywordScoreComponents(query, data.fragments));
|
|
1118
|
+
keywordResults.push(...keywordScoreComponents(query, data.fragments, data.componentIndex));
|
|
910
1119
|
}
|
|
911
1120
|
if ((!kind || kind === "block") && data.blocks) {
|
|
912
|
-
keywordResults.push(...keywordScoreBlocks(query, data.blocks));
|
|
1121
|
+
keywordResults.push(...keywordScoreBlocks(query, data.blocks, data.blockIndex));
|
|
913
1122
|
}
|
|
914
1123
|
if ((!kind || kind === "token") && data.tokenData) {
|
|
915
|
-
keywordResults.push(...keywordScoreTokens(query, data.tokenData));
|
|
1124
|
+
keywordResults.push(...keywordScoreTokens(query, data.tokenData, data.tokenIndex));
|
|
916
1125
|
}
|
|
917
1126
|
keywordResults.sort((a, b) => b.score - a.score);
|
|
918
1127
|
keywordResults.forEach((r, i) => {
|
|
@@ -962,6 +1171,78 @@ async function hybridSearch(query, data, limit = 10, kind, apiKey) {
|
|
|
962
1171
|
return fused.slice(0, limit);
|
|
963
1172
|
}
|
|
964
1173
|
|
|
1174
|
+
// src/scoring.ts
|
|
1175
|
+
var MINIMUM_SCORE_THRESHOLD = 5;
|
|
1176
|
+
function assignConfidence(score, maxScore) {
|
|
1177
|
+
if (maxScore <= 0) return "low";
|
|
1178
|
+
const ratio = score / maxScore;
|
|
1179
|
+
if (ratio >= 0.7) return "high";
|
|
1180
|
+
if (ratio >= 0.4) return "medium";
|
|
1181
|
+
return "low";
|
|
1182
|
+
}
|
|
1183
|
+
function meetsMinimumThreshold(maxScore) {
|
|
1184
|
+
return maxScore >= MINIMUM_SCORE_THRESHOLD;
|
|
1185
|
+
}
|
|
1186
|
+
function levenshtein(a, b) {
|
|
1187
|
+
const la = a.length;
|
|
1188
|
+
const lb = b.length;
|
|
1189
|
+
const dp = Array.from({ length: lb + 1 }, (_, i) => i);
|
|
1190
|
+
for (let i = 1; i <= la; i++) {
|
|
1191
|
+
let prev = i - 1;
|
|
1192
|
+
dp[0] = i;
|
|
1193
|
+
for (let j = 1; j <= lb; j++) {
|
|
1194
|
+
const temp = dp[j];
|
|
1195
|
+
dp[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
|
|
1196
|
+
prev = temp;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return dp[lb];
|
|
1200
|
+
}
|
|
1201
|
+
function findClosestMatch(input, candidates, maxDistance = 3) {
|
|
1202
|
+
const inputLower = input.toLowerCase();
|
|
1203
|
+
let bestMatch = null;
|
|
1204
|
+
let bestDist = maxDistance + 1;
|
|
1205
|
+
for (const candidate of candidates) {
|
|
1206
|
+
const candidateLower = candidate.toLowerCase();
|
|
1207
|
+
const dist = levenshtein(inputLower, candidateLower);
|
|
1208
|
+
if (dist < bestDist) {
|
|
1209
|
+
bestDist = dist;
|
|
1210
|
+
bestMatch = candidate;
|
|
1211
|
+
} else if (dist === bestDist && bestMatch) {
|
|
1212
|
+
const currentLenDiff = Math.abs(bestMatch.length - input.length);
|
|
1213
|
+
const newLenDiff = Math.abs(candidate.length - input.length);
|
|
1214
|
+
if (newLenDiff < currentLenDiff) {
|
|
1215
|
+
bestMatch = candidate;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return bestDist <= maxDistance ? bestMatch : null;
|
|
1220
|
+
}
|
|
1221
|
+
var BLOCK_BOOST_PER_OCCURRENCE = 5;
|
|
1222
|
+
function buildBlockComponentFrequency(blocks) {
|
|
1223
|
+
const freq = /* @__PURE__ */ new Map();
|
|
1224
|
+
for (const block of blocks) {
|
|
1225
|
+
for (const comp of block.components) {
|
|
1226
|
+
const key = comp.toLowerCase();
|
|
1227
|
+
freq.set(key, (freq.get(key) ?? 0) + 1);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return freq;
|
|
1231
|
+
}
|
|
1232
|
+
function boostByBlockFrequency(results, freq) {
|
|
1233
|
+
for (const result of results) {
|
|
1234
|
+
const count = freq.get(result.name.toLowerCase()) ?? 0;
|
|
1235
|
+
if (count > 0) {
|
|
1236
|
+
result.score += count * BLOCK_BOOST_PER_OCCURRENCE;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
results.sort((a, b) => b.score - a.score);
|
|
1240
|
+
results.forEach((r, i) => {
|
|
1241
|
+
r.rank = i;
|
|
1242
|
+
});
|
|
1243
|
+
return results;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
965
1246
|
// src/service.ts
|
|
966
1247
|
async function renderComponent(viewerUrl, request) {
|
|
967
1248
|
const renderUrl = `${viewerUrl}/fragments/render`;
|
|
@@ -971,6 +1252,7 @@ async function renderComponent(viewerUrl, request) {
|
|
|
971
1252
|
body: JSON.stringify({
|
|
972
1253
|
component: request.component,
|
|
973
1254
|
props: request.props ?? {},
|
|
1255
|
+
variant: request.variant,
|
|
974
1256
|
viewport: request.viewport ?? { width: 800, height: 600 }
|
|
975
1257
|
})
|
|
976
1258
|
});
|
|
@@ -1001,7 +1283,9 @@ async function auditComponent(viewerUrl, request) {
|
|
|
1001
1283
|
headers: { "Content-Type": "application/json" },
|
|
1002
1284
|
body: JSON.stringify({
|
|
1003
1285
|
component: request.component,
|
|
1004
|
-
variant: request.variant
|
|
1286
|
+
variant: request.variant,
|
|
1287
|
+
standard: request.standard,
|
|
1288
|
+
includeFixPatches: request.includeFixPatches
|
|
1005
1289
|
})
|
|
1006
1290
|
});
|
|
1007
1291
|
const raw = await response.json();
|
|
@@ -1031,18 +1315,31 @@ async function auditComponent(viewerUrl, request) {
|
|
|
1031
1315
|
}
|
|
1032
1316
|
const deductions = totalCritical * 10 + totalSerious * 5 + totalModerate * 2 + totalMinor * 1;
|
|
1033
1317
|
const score = Math.max(0, 100 - deductions);
|
|
1318
|
+
const variantCount = results.length;
|
|
1319
|
+
const aaPassCount = results.filter((r) => {
|
|
1320
|
+
const critical = r.summary.critical;
|
|
1321
|
+
const serious = r.summary.serious;
|
|
1322
|
+
return critical === 0 && serious === 0;
|
|
1323
|
+
}).length;
|
|
1324
|
+
const aaaPassCount = results.filter((r) => {
|
|
1325
|
+
const total = r.summary.critical + r.summary.serious + r.summary.moderate + r.summary.minor;
|
|
1326
|
+
return total === 0;
|
|
1327
|
+
}).length;
|
|
1328
|
+
const totalPasses = results.reduce((sum, r) => sum + r.passes, 0);
|
|
1034
1329
|
const totalViolations = totalCritical + totalSerious + totalModerate + totalMinor;
|
|
1035
|
-
const
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1038
|
-
const
|
|
1330
|
+
const emptyAudit = results.length > 0 && totalPasses === 0 && totalViolations === 0;
|
|
1331
|
+
const aaPercent = variantCount > 0 ? Math.round(aaPassCount / variantCount * 100) : 100;
|
|
1332
|
+
const aaaPercent = variantCount > 0 ? Math.round(aaaPassCount / variantCount * 100) : 100;
|
|
1333
|
+
const aaPass = !emptyAudit && totalCritical === 0 && totalSerious === 0;
|
|
1334
|
+
const aaaPass = !emptyAudit && totalViolations === 0;
|
|
1039
1335
|
const passed = standard === "AAA" ? aaaPass : aaPass;
|
|
1040
1336
|
return {
|
|
1041
1337
|
component: request.component,
|
|
1042
1338
|
results,
|
|
1043
|
-
score,
|
|
1044
|
-
aaPercent,
|
|
1045
|
-
aaaPercent,
|
|
1339
|
+
score: emptyAudit ? 0 : score,
|
|
1340
|
+
aaPercent: emptyAudit ? 0 : aaPercent,
|
|
1341
|
+
aaaPercent: emptyAudit ? 0 : aaaPercent,
|
|
1342
|
+
...emptyAudit && { emptyAudit },
|
|
1046
1343
|
passed,
|
|
1047
1344
|
standard
|
|
1048
1345
|
};
|
|
@@ -1081,7 +1378,7 @@ function projectFields(obj, fields) {
|
|
|
1081
1378
|
}
|
|
1082
1379
|
|
|
1083
1380
|
// src/graph-handler.ts
|
|
1084
|
-
function handleGraphTool(args, serializedGraph, blocks) {
|
|
1381
|
+
function handleGraphTool(args, serializedGraph, blocks, componentNames) {
|
|
1085
1382
|
if (!serializedGraph) {
|
|
1086
1383
|
return {
|
|
1087
1384
|
text: JSON.stringify({
|
|
@@ -1109,7 +1406,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1109
1406
|
compositionNote: "No composition blocks defined yet \u2014 compositionCoverage will increase as blocks are added"
|
|
1110
1407
|
},
|
|
1111
1408
|
summary: `${health.nodeCount} components, ${health.edgeCount} edges, ${health.connectedComponents.length} island(s), ${health.orphans.length} orphan(s), ${health.compositionCoverage}% in blocks`
|
|
1112
|
-
}
|
|
1409
|
+
})
|
|
1113
1410
|
};
|
|
1114
1411
|
}
|
|
1115
1412
|
case "dependencies": {
|
|
@@ -1117,7 +1414,9 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1117
1414
|
return { text: JSON.stringify({ error: "component is required for dependencies mode" }), isError: true };
|
|
1118
1415
|
}
|
|
1119
1416
|
if (!engine.hasNode(args.component)) {
|
|
1120
|
-
|
|
1417
|
+
const closest = componentNames ? findClosestMatch(args.component, componentNames) : null;
|
|
1418
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
1419
|
+
return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
|
|
1121
1420
|
}
|
|
1122
1421
|
const deps = engine.dependencies(args.component, edgeTypes);
|
|
1123
1422
|
return {
|
|
@@ -1132,7 +1431,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1132
1431
|
note: e.note,
|
|
1133
1432
|
provenance: e.provenance
|
|
1134
1433
|
}))
|
|
1135
|
-
}
|
|
1434
|
+
})
|
|
1136
1435
|
};
|
|
1137
1436
|
}
|
|
1138
1437
|
case "dependents": {
|
|
@@ -1140,7 +1439,9 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1140
1439
|
return { text: JSON.stringify({ error: "component is required for dependents mode" }), isError: true };
|
|
1141
1440
|
}
|
|
1142
1441
|
if (!engine.hasNode(args.component)) {
|
|
1143
|
-
|
|
1442
|
+
const closest = componentNames ? findClosestMatch(args.component, componentNames) : null;
|
|
1443
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
1444
|
+
return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
|
|
1144
1445
|
}
|
|
1145
1446
|
const deps = engine.dependents(args.component, edgeTypes);
|
|
1146
1447
|
return {
|
|
@@ -1155,7 +1456,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1155
1456
|
note: e.note,
|
|
1156
1457
|
provenance: e.provenance
|
|
1157
1458
|
}))
|
|
1158
|
-
}
|
|
1459
|
+
})
|
|
1159
1460
|
};
|
|
1160
1461
|
}
|
|
1161
1462
|
case "impact": {
|
|
@@ -1163,7 +1464,9 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1163
1464
|
return { text: JSON.stringify({ error: "component is required for impact mode" }), isError: true };
|
|
1164
1465
|
}
|
|
1165
1466
|
if (!engine.hasNode(args.component)) {
|
|
1166
|
-
|
|
1467
|
+
const closest = componentNames ? findClosestMatch(args.component, componentNames) : null;
|
|
1468
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
1469
|
+
return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
|
|
1167
1470
|
}
|
|
1168
1471
|
const result = engine.impact(args.component, args.maxDepth ?? 3);
|
|
1169
1472
|
return {
|
|
@@ -1171,7 +1474,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1171
1474
|
mode: "impact",
|
|
1172
1475
|
...result,
|
|
1173
1476
|
summary: `Changing ${args.component} affects ${result.totalAffected} component(s) and ${result.affectedBlocks.length} block(s)`
|
|
1174
|
-
}
|
|
1477
|
+
})
|
|
1175
1478
|
};
|
|
1176
1479
|
}
|
|
1177
1480
|
case "path": {
|
|
@@ -1190,7 +1493,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1190
1493
|
target: e.target,
|
|
1191
1494
|
type: e.type
|
|
1192
1495
|
}))
|
|
1193
|
-
}
|
|
1496
|
+
})
|
|
1194
1497
|
};
|
|
1195
1498
|
}
|
|
1196
1499
|
case "composition": {
|
|
@@ -1198,14 +1501,16 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1198
1501
|
return { text: JSON.stringify({ error: "component is required for composition mode" }), isError: true };
|
|
1199
1502
|
}
|
|
1200
1503
|
if (!engine.hasNode(args.component)) {
|
|
1201
|
-
|
|
1504
|
+
const closest = componentNames ? findClosestMatch(args.component, componentNames) : null;
|
|
1505
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
1506
|
+
return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
|
|
1202
1507
|
}
|
|
1203
1508
|
const tree = engine.composition(args.component);
|
|
1204
1509
|
return {
|
|
1205
1510
|
text: JSON.stringify({
|
|
1206
1511
|
mode: "composition",
|
|
1207
1512
|
...tree
|
|
1208
|
-
}
|
|
1513
|
+
})
|
|
1209
1514
|
};
|
|
1210
1515
|
}
|
|
1211
1516
|
case "alternatives": {
|
|
@@ -1213,7 +1518,9 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1213
1518
|
return { text: JSON.stringify({ error: "component is required for alternatives mode" }), isError: true };
|
|
1214
1519
|
}
|
|
1215
1520
|
if (!engine.hasNode(args.component)) {
|
|
1216
|
-
|
|
1521
|
+
const closest = componentNames ? findClosestMatch(args.component, componentNames) : null;
|
|
1522
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
1523
|
+
return { text: JSON.stringify({ error: `Component "${args.component}" not found in graph.${suggestion}` }), isError: true };
|
|
1217
1524
|
}
|
|
1218
1525
|
const alts = engine.alternatives(args.component);
|
|
1219
1526
|
return {
|
|
@@ -1222,7 +1529,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1222
1529
|
component: args.component,
|
|
1223
1530
|
count: alts.length,
|
|
1224
1531
|
alternatives: alts
|
|
1225
|
-
}
|
|
1532
|
+
})
|
|
1226
1533
|
};
|
|
1227
1534
|
}
|
|
1228
1535
|
case "islands": {
|
|
@@ -1236,7 +1543,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1236
1543
|
size: island.length,
|
|
1237
1544
|
components: island
|
|
1238
1545
|
}))
|
|
1239
|
-
}
|
|
1546
|
+
})
|
|
1240
1547
|
};
|
|
1241
1548
|
}
|
|
1242
1549
|
default:
|
|
@@ -1251,7 +1558,7 @@ function handleGraphTool(args, serializedGraph, blocks) {
|
|
|
1251
1558
|
|
|
1252
1559
|
// src/server.ts
|
|
1253
1560
|
var TOOL_NAMES = buildToolNames(BRAND.nameLower);
|
|
1254
|
-
var NO_VIEWER_MSG = "This tool requires a running dev server.
|
|
1561
|
+
var NO_VIEWER_MSG = "This tool requires a running dev server. Add --viewer-url http://localhost:PORT to this server's config args. Start the viewer with 'fragments dev' in your component library directory.";
|
|
1255
1562
|
var TOOLS = buildMcpTools(BRAND.nameLower);
|
|
1256
1563
|
function createMcpServer(config) {
|
|
1257
1564
|
const server = new Server(
|
|
@@ -1269,6 +1576,9 @@ function createMcpServer(config) {
|
|
|
1269
1576
|
const fragmentPackageMap = /* @__PURE__ */ new Map();
|
|
1270
1577
|
let defaultPackageName = null;
|
|
1271
1578
|
let resolvedRoot = null;
|
|
1579
|
+
let componentIndex = null;
|
|
1580
|
+
let blockIndex = null;
|
|
1581
|
+
let tokenIndex = null;
|
|
1272
1582
|
async function resolveProjectRoot() {
|
|
1273
1583
|
if (resolvedRoot) return resolvedRoot;
|
|
1274
1584
|
try {
|
|
@@ -1326,6 +1636,15 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1326
1636
|
fragmentsData.blocks = { ...fragmentsData.blocks, ...extraBlocks };
|
|
1327
1637
|
}
|
|
1328
1638
|
}
|
|
1639
|
+
const allFragments = Object.values(fragmentsData.fragments);
|
|
1640
|
+
const allBlocks = Object.values(fragmentsData.blocks ?? fragmentsData.recipes ?? {});
|
|
1641
|
+
componentIndex = buildComponentIndex(allFragments);
|
|
1642
|
+
if (allBlocks.length > 0) {
|
|
1643
|
+
blockIndex = buildBlockIndex(allBlocks);
|
|
1644
|
+
}
|
|
1645
|
+
if (fragmentsData.tokens && fragmentsData.tokens.total > 0) {
|
|
1646
|
+
tokenIndex = buildTokenIndex(fragmentsData.tokens);
|
|
1647
|
+
}
|
|
1329
1648
|
return fragmentsData;
|
|
1330
1649
|
}
|
|
1331
1650
|
async function getPackageName(fragmentName) {
|
|
@@ -1355,8 +1674,15 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1355
1674
|
defaultPackageName = "your-component-library";
|
|
1356
1675
|
return defaultPackageName;
|
|
1357
1676
|
}
|
|
1677
|
+
const VIEWER_TOOL_KEYS = /* @__PURE__ */ new Set(["render", "fix", "a11y"]);
|
|
1358
1678
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1359
|
-
|
|
1679
|
+
if (config.viewerUrl) {
|
|
1680
|
+
return { tools: TOOLS };
|
|
1681
|
+
}
|
|
1682
|
+
const availableTools = TOOLS.filter(
|
|
1683
|
+
(t) => !VIEWER_TOOL_KEYS.has(t.name.replace(`${BRAND.nameLower}_`, ""))
|
|
1684
|
+
);
|
|
1685
|
+
return { tools: availableTools };
|
|
1360
1686
|
});
|
|
1361
1687
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1362
1688
|
const { name, arguments: args } = request.params;
|
|
@@ -1370,15 +1696,28 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1370
1696
|
const useCase = args?.useCase ?? void 0;
|
|
1371
1697
|
const componentForAlts = args?.component ?? void 0;
|
|
1372
1698
|
const category = args?.category ?? void 0;
|
|
1373
|
-
const
|
|
1699
|
+
const search2 = args?.search?.toLowerCase() ?? void 0;
|
|
1374
1700
|
const status = args?.status ?? void 0;
|
|
1375
1701
|
const format = args?.format ?? "markdown";
|
|
1376
1702
|
const compact = args?.compact ?? false;
|
|
1377
1703
|
const includeCode = args?.includeCode ?? false;
|
|
1378
1704
|
const includeRelations = args?.includeRelations ?? false;
|
|
1379
|
-
|
|
1380
|
-
|
|
1705
|
+
const limit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 25) : 10;
|
|
1706
|
+
const verbosity = args?.verbosity ?? "standard";
|
|
1707
|
+
if (compact || args?.format && !useCase && !componentForAlts) {
|
|
1708
|
+
let fragments2 = Object.values(data.fragments);
|
|
1381
1709
|
const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
|
|
1710
|
+
if (category) {
|
|
1711
|
+
fragments2 = fragments2.filter((f) => f.meta.category?.toLowerCase() === category);
|
|
1712
|
+
}
|
|
1713
|
+
if (search2) {
|
|
1714
|
+
fragments2 = fragments2.filter(
|
|
1715
|
+
(f) => f.meta.name.toLowerCase().includes(search2) || f.meta.description?.toLowerCase().includes(search2) || f.meta.tags?.some((t) => t.toLowerCase().includes(search2))
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
if (status) {
|
|
1719
|
+
fragments2 = fragments2.filter((f) => f.meta.status === status);
|
|
1720
|
+
}
|
|
1382
1721
|
const { content: ctxContent, tokenEstimate } = generateContext(fragments2, {
|
|
1383
1722
|
format,
|
|
1384
1723
|
compact,
|
|
@@ -1400,9 +1739,19 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1400
1739
|
const localData = {
|
|
1401
1740
|
fragments: allFragments,
|
|
1402
1741
|
blocks: allBlocks,
|
|
1403
|
-
tokenData: data.tokens
|
|
1742
|
+
tokenData: data.tokens,
|
|
1743
|
+
componentIndex: componentIndex ?? void 0,
|
|
1744
|
+
blockIndex: blockIndex ?? void 0,
|
|
1745
|
+
tokenIndex: tokenIndex ?? void 0
|
|
1404
1746
|
};
|
|
1405
|
-
const searchResults = await hybridSearch(fullQuery, localData,
|
|
1747
|
+
const searchResults = await hybridSearch(fullQuery, localData, limit, "component", config.apiKey);
|
|
1748
|
+
const blockMatches = keywordScoreBlocks(fullQuery, allBlocks, blockIndex ?? void 0).slice(0, 5);
|
|
1749
|
+
if (blockMatches.length > 0) {
|
|
1750
|
+
const matchedBlocks = blockMatches.map((bm) => allBlocks.find((b) => b.name.toLowerCase() === bm.name.toLowerCase())).filter(Boolean);
|
|
1751
|
+
const blockFreq = buildBlockComponentFrequency(matchedBlocks);
|
|
1752
|
+
boostByBlockFrequency(searchResults, blockFreq);
|
|
1753
|
+
}
|
|
1754
|
+
const maxScore = searchResults.length > 0 ? searchResults[0].score : 0;
|
|
1406
1755
|
const scored = searchResults.map((result) => {
|
|
1407
1756
|
const fragment = allFragments.find(
|
|
1408
1757
|
(s) => s.meta.name.toLowerCase() === result.name.toLowerCase()
|
|
@@ -1410,15 +1759,11 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1410
1759
|
if (!fragment) return null;
|
|
1411
1760
|
const filteredWhen = filterPlaceholders(fragment.usage?.when).slice(0, 3);
|
|
1412
1761
|
const filteredWhenNot = filterPlaceholders(fragment.usage?.whenNot).slice(0, 2);
|
|
1413
|
-
let confidence;
|
|
1414
|
-
if (result.score >= 0.025) confidence = "high";
|
|
1415
|
-
else if (result.score >= 0.015) confidence = "medium";
|
|
1416
|
-
else confidence = "low";
|
|
1417
1762
|
return {
|
|
1418
1763
|
component: fragment.meta.name,
|
|
1419
1764
|
category: fragment.meta.category,
|
|
1420
1765
|
description: fragment.meta.description,
|
|
1421
|
-
confidence,
|
|
1766
|
+
confidence: assignConfidence(result.score, maxScore),
|
|
1422
1767
|
reasons: [`Matched via hybrid search (score: ${result.score.toFixed(4)})`],
|
|
1423
1768
|
usage: { when: filteredWhen, whenNot: filteredWhenNot },
|
|
1424
1769
|
variantCount: fragment.variants.length,
|
|
@@ -1434,7 +1779,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1434
1779
|
if (count < 2 || suggestions.length < 3) {
|
|
1435
1780
|
suggestions.push(item);
|
|
1436
1781
|
categoryCount[cat] = count + 1;
|
|
1437
|
-
if (suggestions.length >=
|
|
1782
|
+
if (suggestions.length >= limit) break;
|
|
1438
1783
|
}
|
|
1439
1784
|
}
|
|
1440
1785
|
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;
|
|
@@ -1442,7 +1787,8 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1442
1787
|
const STYLE_KEYWORDS = ["color", "spacing", "padding", "margin", "font", "border", "radius", "shadow", "variable", "token", "css", "theme", "dark mode", "background", "hover"];
|
|
1443
1788
|
const isStyleQuery = STYLE_KEYWORDS.some((kw) => useCaseLower.includes(kw));
|
|
1444
1789
|
const noMatch = suggestions.length === 0;
|
|
1445
|
-
const
|
|
1790
|
+
const belowThreshold = !noMatch && maxScore > 1 && !meetsMinimumThreshold(maxScore);
|
|
1791
|
+
const weakMatch = !noMatch && (belowThreshold || suggestions.every((s) => s.confidence === "low"));
|
|
1446
1792
|
let recommendation;
|
|
1447
1793
|
let nextStep;
|
|
1448
1794
|
if (noMatch) {
|
|
@@ -1455,19 +1801,33 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1455
1801
|
recommendation = `Best match: ${suggestions[0].component} (${suggestions[0].confidence} confidence) - ${suggestions[0].description}`;
|
|
1456
1802
|
nextStep = `Use ${TOOL_NAMES.inspect}("${suggestions[0].component}") for full details.`;
|
|
1457
1803
|
}
|
|
1804
|
+
const tokenHint = isStyleQuery && !noMatch ? `Your query includes styling terms. For CSS custom properties, also try ${TOOL_NAMES.tokens}(search: "${useCaseLower.split(/\s+/)[0]}").` : void 0;
|
|
1805
|
+
const blockNames = blockMatches.map((bm) => allBlocks.find((b) => b.name.toLowerCase() === bm.name.toLowerCase())).filter(Boolean).slice(0, 3).map((b) => b.name);
|
|
1806
|
+
const blockHint = blockNames.length > 0 ? `Related blocks: ${blockNames.join(", ")}. Use ${TOOL_NAMES.blocks}(search: "${useCase}") for ready-to-use patterns.` : void 0;
|
|
1807
|
+
const suggestResponse = verbosity === "compact" ? {
|
|
1808
|
+
useCase,
|
|
1809
|
+
suggestions: suggestions.map((s) => ({
|
|
1810
|
+
component: s.component,
|
|
1811
|
+
description: s.description,
|
|
1812
|
+
confidence: s.confidence
|
|
1813
|
+
})),
|
|
1814
|
+
recommendation
|
|
1815
|
+
} : {
|
|
1816
|
+
useCase,
|
|
1817
|
+
context: context || void 0,
|
|
1818
|
+
suggestions,
|
|
1819
|
+
noMatch,
|
|
1820
|
+
weakMatch,
|
|
1821
|
+
recommendation,
|
|
1822
|
+
compositionHint,
|
|
1823
|
+
...tokenHint && { tokenHint },
|
|
1824
|
+
...blockHint && { blockHint },
|
|
1825
|
+
nextStep
|
|
1826
|
+
};
|
|
1458
1827
|
return {
|
|
1459
1828
|
content: [{
|
|
1460
1829
|
type: "text",
|
|
1461
|
-
text: JSON.stringify(
|
|
1462
|
-
useCase,
|
|
1463
|
-
context: context || void 0,
|
|
1464
|
-
suggestions,
|
|
1465
|
-
noMatch,
|
|
1466
|
-
weakMatch,
|
|
1467
|
-
recommendation,
|
|
1468
|
-
compositionHint,
|
|
1469
|
-
nextStep
|
|
1470
|
-
}, null, 2)
|
|
1830
|
+
text: JSON.stringify(suggestResponse)
|
|
1471
1831
|
}]
|
|
1472
1832
|
};
|
|
1473
1833
|
}
|
|
@@ -1476,7 +1836,10 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1476
1836
|
(s) => s.meta.name.toLowerCase() === componentForAlts.toLowerCase()
|
|
1477
1837
|
);
|
|
1478
1838
|
if (!fragment) {
|
|
1479
|
-
|
|
1839
|
+
const allNames = Object.values(data.fragments).map((s) => s.meta.name);
|
|
1840
|
+
const closest = findClosestMatch(componentForAlts, allNames);
|
|
1841
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
1842
|
+
throw new Error(`Component "${componentForAlts}" not found.${suggestion} Use ${TOOL_NAMES.discover} to see available components.`);
|
|
1480
1843
|
}
|
|
1481
1844
|
const relations = fragment.relations ?? [];
|
|
1482
1845
|
const referencedBy = Object.values(data.fragments).filter(
|
|
@@ -1506,31 +1869,36 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1506
1869
|
referencedBy,
|
|
1507
1870
|
sameCategory,
|
|
1508
1871
|
suggestion: relations.find((r) => r.relationship === "alternative") ? `Consider ${relations.find((r) => r.relationship === "alternative")?.component}: ${relations.find((r) => r.relationship === "alternative")?.note}` : void 0
|
|
1509
|
-
}
|
|
1872
|
+
})
|
|
1510
1873
|
}]
|
|
1511
1874
|
};
|
|
1512
1875
|
}
|
|
1513
1876
|
const fragments = Object.values(data.fragments).filter((s) => {
|
|
1514
1877
|
if (category && s.meta.category !== category) return false;
|
|
1515
1878
|
if (status && (s.meta.status ?? "stable") !== status) return false;
|
|
1516
|
-
if (
|
|
1517
|
-
const nameMatch = s.meta.name.toLowerCase().includes(
|
|
1518
|
-
const descMatch = s.meta.description?.toLowerCase().includes(
|
|
1519
|
-
const tagMatch = s.meta.tags?.some((t) => t.toLowerCase().includes(
|
|
1879
|
+
if (search2) {
|
|
1880
|
+
const nameMatch = s.meta.name.toLowerCase().includes(search2);
|
|
1881
|
+
const descMatch = s.meta.description?.toLowerCase().includes(search2);
|
|
1882
|
+
const tagMatch = s.meta.tags?.some((t) => t.toLowerCase().includes(search2));
|
|
1520
1883
|
if (!nameMatch && !descMatch && !tagMatch) return false;
|
|
1521
1884
|
}
|
|
1522
1885
|
return true;
|
|
1523
|
-
}).map((s) =>
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
description: s.meta.description,
|
|
1527
|
-
status: s.meta.status ?? "stable",
|
|
1528
|
-
variantCount: s.variants.length,
|
|
1529
|
-
tags: s.meta.tags ?? [],
|
|
1530
|
-
...includeCode && s.variants[0]?.code && {
|
|
1531
|
-
example: s.variants[0].code
|
|
1886
|
+
}).map((s) => {
|
|
1887
|
+
if (verbosity === "compact") {
|
|
1888
|
+
return { name: s.meta.name, category: s.meta.category };
|
|
1532
1889
|
}
|
|
1533
|
-
|
|
1890
|
+
return {
|
|
1891
|
+
name: s.meta.name,
|
|
1892
|
+
category: s.meta.category,
|
|
1893
|
+
description: s.meta.description,
|
|
1894
|
+
status: s.meta.status ?? "stable",
|
|
1895
|
+
variantCount: s.variants.length,
|
|
1896
|
+
tags: s.meta.tags ?? [],
|
|
1897
|
+
...(includeCode || verbosity === "full") && s.variants[0]?.code && {
|
|
1898
|
+
example: s.variants[0].code
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
});
|
|
1534
1902
|
return {
|
|
1535
1903
|
content: [{
|
|
1536
1904
|
type: "text",
|
|
@@ -1539,7 +1907,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1539
1907
|
fragments,
|
|
1540
1908
|
categories: [...new Set(fragments.map((s) => s.category))],
|
|
1541
1909
|
hint: fragments.length === 0 ? "No components found. Try broader search terms or check available categories." : fragments.length > 5 ? "Use fragments_discover with useCase for recommendations, or fragments_inspect for details on a specific component." : void 0
|
|
1542
|
-
}
|
|
1910
|
+
})
|
|
1543
1911
|
}]
|
|
1544
1912
|
};
|
|
1545
1913
|
}
|
|
@@ -1553,6 +1921,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1553
1921
|
const variantName = args?.variant ?? void 0;
|
|
1554
1922
|
const maxExamples = args?.maxExamples;
|
|
1555
1923
|
const maxLines = args?.maxLines;
|
|
1924
|
+
const verbosity = args?.verbosity ?? "standard";
|
|
1556
1925
|
if (!componentName) {
|
|
1557
1926
|
throw new Error("component is required");
|
|
1558
1927
|
}
|
|
@@ -1560,7 +1929,10 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1560
1929
|
(s) => s.meta.name.toLowerCase() === componentName.toLowerCase()
|
|
1561
1930
|
);
|
|
1562
1931
|
if (!fragment) {
|
|
1563
|
-
|
|
1932
|
+
const allNames = Object.values(data.fragments).map((s) => s.meta.name);
|
|
1933
|
+
const closest = findClosestMatch(componentName, allNames);
|
|
1934
|
+
const suggestion = closest ? ` Did you mean "${closest}"? Use ${TOOL_NAMES.inspect}("${closest}") to inspect it.` : "";
|
|
1935
|
+
throw new Error(`Component "${componentName}" not found.${suggestion} Use ${TOOL_NAMES.discover} to see available components.`);
|
|
1564
1936
|
}
|
|
1565
1937
|
const pkgName = await getPackageName(fragment.meta.name);
|
|
1566
1938
|
let variants = fragment.variants;
|
|
@@ -1640,9 +2012,24 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1640
2012
|
propsReference
|
|
1641
2013
|
}
|
|
1642
2014
|
};
|
|
1643
|
-
const
|
|
2015
|
+
const aliasMap = { "usage": "guidelines" };
|
|
2016
|
+
const resolvedFields = fields?.map((f) => {
|
|
2017
|
+
const parts = f.split(".");
|
|
2018
|
+
if (aliasMap[parts[0]]) parts[0] = aliasMap[parts[0]];
|
|
2019
|
+
return parts.join(".");
|
|
2020
|
+
});
|
|
2021
|
+
let result;
|
|
2022
|
+
if (verbosity === "compact" && !resolvedFields?.length) {
|
|
2023
|
+
result = {
|
|
2024
|
+
meta: fullResult.meta,
|
|
2025
|
+
propNames: Object.keys(fragment.props ?? {}),
|
|
2026
|
+
variantNames: fragment.variants.map((v) => v.name)
|
|
2027
|
+
};
|
|
2028
|
+
} else {
|
|
2029
|
+
result = resolvedFields && resolvedFields.length > 0 ? projectFields(fullResult, resolvedFields) : fullResult;
|
|
2030
|
+
}
|
|
1644
2031
|
return {
|
|
1645
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
2032
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1646
2033
|
};
|
|
1647
2034
|
}
|
|
1648
2035
|
// ================================================================
|
|
@@ -1651,9 +2038,10 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1651
2038
|
case TOOL_NAMES.blocks: {
|
|
1652
2039
|
const data = await loadFragments();
|
|
1653
2040
|
const blockName = args?.name;
|
|
1654
|
-
const
|
|
2041
|
+
const search2 = args?.search?.toLowerCase() ?? void 0;
|
|
1655
2042
|
const component = args?.component?.toLowerCase() ?? void 0;
|
|
1656
2043
|
const category = args?.category?.toLowerCase() ?? void 0;
|
|
2044
|
+
const blocksLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 50) : search2 ? 10 : void 0;
|
|
1657
2045
|
const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
|
|
1658
2046
|
if (allBlocks.length === 0) {
|
|
1659
2047
|
return {
|
|
@@ -1663,7 +2051,7 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1663
2051
|
total: 0,
|
|
1664
2052
|
blocks: [],
|
|
1665
2053
|
hint: `No composition blocks found. Blocks are reusable patterns showing how components wire together (e.g., "Login Form", "Settings Page"). Create .block.ts files and run \`${BRAND.cliCommand} build\`.`
|
|
1666
|
-
}
|
|
2054
|
+
})
|
|
1667
2055
|
}]
|
|
1668
2056
|
};
|
|
1669
2057
|
}
|
|
@@ -1672,18 +2060,35 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1672
2060
|
filtered = filtered.filter(
|
|
1673
2061
|
(b) => b.name.toLowerCase() === blockName.toLowerCase()
|
|
1674
2062
|
);
|
|
2063
|
+
if (filtered.length === 0) {
|
|
2064
|
+
const allBlockNames = allBlocks.map((b) => b.name);
|
|
2065
|
+
const closest = findClosestMatch(blockName, allBlockNames);
|
|
2066
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
2067
|
+
throw new Error(`Block "${blockName}" not found.${suggestion} Use ${TOOL_NAMES.blocks} to see available blocks.`);
|
|
2068
|
+
}
|
|
1675
2069
|
}
|
|
1676
|
-
if (
|
|
1677
|
-
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
b.
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
}
|
|
2070
|
+
if (search2) {
|
|
2071
|
+
if (blockIndex) {
|
|
2072
|
+
const ranked = searchBlocks(search2, blockIndex, 50);
|
|
2073
|
+
const rankedNames = new Set(ranked.map((r) => r.name.toLowerCase()));
|
|
2074
|
+
filtered = filtered.filter((b) => rankedNames.has(b.name.toLowerCase()));
|
|
2075
|
+
filtered.sort((a, b) => {
|
|
2076
|
+
const aIdx = ranked.findIndex((r) => r.name.toLowerCase() === a.name.toLowerCase());
|
|
2077
|
+
const bIdx = ranked.findIndex((r) => r.name.toLowerCase() === b.name.toLowerCase());
|
|
2078
|
+
return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx);
|
|
2079
|
+
});
|
|
2080
|
+
} else {
|
|
2081
|
+
filtered = filtered.filter((b) => {
|
|
2082
|
+
const haystack = [
|
|
2083
|
+
b.name,
|
|
2084
|
+
b.description,
|
|
2085
|
+
...b.tags ?? [],
|
|
2086
|
+
...b.components,
|
|
2087
|
+
b.category
|
|
2088
|
+
].join(" ").toLowerCase();
|
|
2089
|
+
return haystack.includes(search2);
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
1687
2092
|
}
|
|
1688
2093
|
if (component) {
|
|
1689
2094
|
filtered = filtered.filter(
|
|
@@ -1695,16 +2100,41 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1695
2100
|
(b) => b.category.toLowerCase() === category
|
|
1696
2101
|
);
|
|
1697
2102
|
}
|
|
2103
|
+
const blocksUseIcons = filtered.some(
|
|
2104
|
+
(b) => b.components.some((c) => c === "Icon") || b.code && /\bIcon\b/.test(b.code)
|
|
2105
|
+
);
|
|
2106
|
+
const iconHint = blocksUseIcons ? "Icon components in block code are from @phosphor-icons/react. Import them as: import { IconName } from '@phosphor-icons/react';" : void 0;
|
|
2107
|
+
if (blocksLimit !== void 0) {
|
|
2108
|
+
filtered = filtered.slice(0, blocksLimit);
|
|
2109
|
+
}
|
|
2110
|
+
const pkgName = fragmentPackageMap.values().next().value ?? "your-component-library";
|
|
2111
|
+
const verbosity = args?.verbosity ?? "standard";
|
|
2112
|
+
const blocksWithImports = filtered.map((b) => {
|
|
2113
|
+
const base = {
|
|
2114
|
+
name: b.name,
|
|
2115
|
+
description: b.description,
|
|
2116
|
+
category: b.category,
|
|
2117
|
+
components: b.components,
|
|
2118
|
+
tags: b.tags,
|
|
2119
|
+
import: `import { ${b.components.join(", ")} } from '${pkgName}';`
|
|
2120
|
+
};
|
|
2121
|
+
if (verbosity === "compact") return base;
|
|
2122
|
+
if (verbosity === "full") return { ...base, code: b.code };
|
|
2123
|
+
const codeLines = b.code.split("\n");
|
|
2124
|
+
const code = codeLines.length > 30 ? codeLines.slice(0, 20).join("\n") + "\n// ... truncated (" + codeLines.length + " lines total)" : b.code;
|
|
2125
|
+
return { ...base, code };
|
|
2126
|
+
});
|
|
1698
2127
|
return {
|
|
1699
2128
|
content: [{
|
|
1700
2129
|
type: "text",
|
|
1701
2130
|
text: JSON.stringify({
|
|
1702
|
-
total:
|
|
1703
|
-
blocks:
|
|
1704
|
-
...
|
|
2131
|
+
total: blocksWithImports.length,
|
|
2132
|
+
blocks: blocksWithImports,
|
|
2133
|
+
...iconHint && { iconHint },
|
|
2134
|
+
...blocksWithImports.length === 0 && allBlocks.length > 0 && {
|
|
1705
2135
|
hint: "No blocks matching your query. Try broader search terms."
|
|
1706
2136
|
}
|
|
1707
|
-
}
|
|
2137
|
+
})
|
|
1708
2138
|
}]
|
|
1709
2139
|
};
|
|
1710
2140
|
}
|
|
@@ -1714,7 +2144,8 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1714
2144
|
case TOOL_NAMES.tokens: {
|
|
1715
2145
|
const data = await loadFragments();
|
|
1716
2146
|
const category = args?.category?.toLowerCase() ?? void 0;
|
|
1717
|
-
const
|
|
2147
|
+
const search2 = args?.search?.toLowerCase() ?? void 0;
|
|
2148
|
+
const tokensLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 100) : search2 ? 25 : void 0;
|
|
1718
2149
|
const tokenData = data.tokens;
|
|
1719
2150
|
if (!tokenData || tokenData.total === 0) {
|
|
1720
2151
|
return {
|
|
@@ -1724,30 +2155,53 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1724
2155
|
total: 0,
|
|
1725
2156
|
categories: {},
|
|
1726
2157
|
hint: `No design tokens found. Add a tokens.include pattern to your ${BRAND.configFile} and run \`${BRAND.cliCommand} build\`.`
|
|
1727
|
-
}
|
|
2158
|
+
})
|
|
1728
2159
|
}]
|
|
1729
2160
|
};
|
|
1730
2161
|
}
|
|
1731
2162
|
let filteredCategories = {};
|
|
1732
2163
|
let filteredTotal = 0;
|
|
2164
|
+
const searchMatchesCategory = search2 ? Object.keys(tokenData.categories).find((cat) => cat.toLowerCase() === search2) : void 0;
|
|
1733
2165
|
for (const [cat, tokens] of Object.entries(tokenData.categories)) {
|
|
1734
2166
|
if (category && cat !== category) continue;
|
|
1735
2167
|
let filtered = tokens;
|
|
1736
|
-
if (
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2168
|
+
if (search2) {
|
|
2169
|
+
if (searchMatchesCategory && cat.toLowerCase() === search2) {
|
|
2170
|
+
filtered = tokens;
|
|
2171
|
+
} else {
|
|
2172
|
+
filtered = tokens.filter(
|
|
2173
|
+
(t) => t.name.toLowerCase().includes(search2) || t.description && t.description.toLowerCase().includes(search2) || cat.toLowerCase().includes(search2)
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
1740
2176
|
}
|
|
1741
2177
|
if (filtered.length > 0) {
|
|
1742
2178
|
filteredCategories[cat] = filtered;
|
|
1743
2179
|
filteredTotal += filtered.length;
|
|
1744
2180
|
}
|
|
1745
2181
|
}
|
|
2182
|
+
if (tokensLimit !== void 0) {
|
|
2183
|
+
let remaining = tokensLimit;
|
|
2184
|
+
const limited = {};
|
|
2185
|
+
for (const [cat, tokens] of Object.entries(filteredCategories)) {
|
|
2186
|
+
if (remaining <= 0) break;
|
|
2187
|
+
limited[cat] = tokens.slice(0, remaining);
|
|
2188
|
+
remaining -= limited[cat].length;
|
|
2189
|
+
}
|
|
2190
|
+
filteredCategories = limited;
|
|
2191
|
+
filteredTotal = Math.min(filteredTotal, tokensLimit);
|
|
2192
|
+
}
|
|
1746
2193
|
let hint;
|
|
1747
2194
|
if (filteredTotal === 0) {
|
|
1748
2195
|
const availableCategories = Object.keys(tokenData.categories);
|
|
1749
|
-
|
|
1750
|
-
|
|
2196
|
+
if (category && search2) {
|
|
2197
|
+
const categoryTotal = tokenData.categories[category]?.length ?? 0;
|
|
2198
|
+
hint = categoryTotal > 0 ? `No tokens matching "${search2}" in category "${category}" (${categoryTotal} tokens in this category). Try a broader search or remove the search term.` : `Category "${category}" not found. Available: ${availableCategories.join(", ")}`;
|
|
2199
|
+
} else if (search2) {
|
|
2200
|
+
hint = `No tokens matching "${search2}". Available categories: ${availableCategories.join(", ")}`;
|
|
2201
|
+
} else if (category) {
|
|
2202
|
+
hint = `Category "${category}" not found. Available: ${availableCategories.join(", ")}`;
|
|
2203
|
+
}
|
|
2204
|
+
} else if (!category && !search2) {
|
|
1751
2205
|
hint = `Use var(--token-name) in your CSS/styles. Filter by category or search to narrow results.`;
|
|
1752
2206
|
}
|
|
1753
2207
|
return {
|
|
@@ -1759,12 +2213,12 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1759
2213
|
totalAvailable: tokenData.total,
|
|
1760
2214
|
categories: filteredCategories,
|
|
1761
2215
|
...hint && { hint },
|
|
1762
|
-
...!category && !
|
|
2216
|
+
...!category && !search2 && {
|
|
1763
2217
|
availableCategories: Object.entries(tokenData.categories).map(
|
|
1764
2218
|
([cat, tokens]) => ({ category: cat, count: tokens.length })
|
|
1765
2219
|
)
|
|
1766
2220
|
}
|
|
1767
|
-
}
|
|
2221
|
+
})
|
|
1768
2222
|
}]
|
|
1769
2223
|
};
|
|
1770
2224
|
}
|
|
@@ -1777,19 +2231,34 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1777
2231
|
if (!useCase) {
|
|
1778
2232
|
throw new Error("useCase is required");
|
|
1779
2233
|
}
|
|
2234
|
+
const verbosity = args?.verbosity ?? "standard";
|
|
1780
2235
|
const allFragments = Object.values(data.fragments);
|
|
1781
2236
|
const allBlocks = Object.values(data.blocks ?? data.recipes ?? {});
|
|
1782
2237
|
const tokenData = data.tokens;
|
|
2238
|
+
const implLimit = typeof args?.limit === "number" ? Math.min(Math.max(args.limit, 1), 15) : 5;
|
|
1783
2239
|
const localData = {
|
|
1784
2240
|
fragments: allFragments,
|
|
1785
2241
|
blocks: allBlocks,
|
|
1786
|
-
tokenData
|
|
2242
|
+
tokenData,
|
|
2243
|
+
componentIndex: componentIndex ?? void 0,
|
|
2244
|
+
blockIndex: blockIndex ?? void 0,
|
|
2245
|
+
tokenIndex: tokenIndex ?? void 0
|
|
1787
2246
|
};
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2247
|
+
const [componentResults, blockResults, tokenResults] = await Promise.all([
|
|
2248
|
+
hybridSearch(useCase, localData, implLimit * 3, "component", config.apiKey),
|
|
2249
|
+
hybridSearch(useCase, localData, implLimit, "block", config.apiKey),
|
|
2250
|
+
hybridSearch(useCase, localData, implLimit, "token", config.apiKey)
|
|
2251
|
+
]);
|
|
2252
|
+
const topBlockScore = blockResults.length > 0 ? blockResults[0].score : 0;
|
|
2253
|
+
const filteredBlockResults = blockResults.filter((r) => r.score >= topBlockScore * 0.3);
|
|
2254
|
+
if (filteredBlockResults.length > 0) {
|
|
2255
|
+
const matchedBlocks = filteredBlockResults.map((r) => allBlocks.find((b) => b.name.toLowerCase() === r.name.toLowerCase())).filter(Boolean);
|
|
2256
|
+
const blockFreq = buildBlockComponentFrequency(matchedBlocks);
|
|
2257
|
+
boostByBlockFrequency(componentResults, blockFreq);
|
|
2258
|
+
}
|
|
2259
|
+
const topComponentResults = componentResults.slice(0, implLimit);
|
|
2260
|
+
const maxCompScore = topComponentResults.length > 0 ? topComponentResults[0].score : 0;
|
|
2261
|
+
const topMatches = topComponentResults.map((result) => {
|
|
1793
2262
|
const fragment = allFragments.find(
|
|
1794
2263
|
(s) => s.meta.name.toLowerCase() === result.name.toLowerCase()
|
|
1795
2264
|
);
|
|
@@ -1798,18 +2267,28 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1798
2267
|
const components = await Promise.all(
|
|
1799
2268
|
topMatches.map(async ({ fragment: s, score }) => {
|
|
1800
2269
|
const pkgName = await getPackageName(s.meta.name);
|
|
1801
|
-
|
|
2270
|
+
if (verbosity === "compact") {
|
|
2271
|
+
return {
|
|
2272
|
+
name: s.meta.name,
|
|
2273
|
+
description: s.meta.description,
|
|
2274
|
+
confidence: assignConfidence(score, maxCompScore),
|
|
2275
|
+
import: `import { ${s.meta.name} } from '${pkgName}';`
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
const exampleLimit = verbosity === "full" ? s.variants.length : 2;
|
|
2279
|
+
const propsLimit = verbosity === "full" ? Object.keys(s.props ?? {}).length : 5;
|
|
2280
|
+
const examples = s.variants.slice(0, exampleLimit).map((v) => ({
|
|
1802
2281
|
variant: v.name,
|
|
1803
2282
|
code: v.code ?? `<${s.meta.name} />`
|
|
1804
2283
|
}));
|
|
1805
|
-
const propsSummary = Object.entries(s.props ?? {}).slice(0,
|
|
2284
|
+
const propsSummary = Object.entries(s.props ?? {}).slice(0, propsLimit).map(
|
|
1806
2285
|
([pName, p]) => `${pName}${p.required ? " (required)" : ""}: ${p.type}${p.values ? ` = ${p.values.join("|")}` : ""}`
|
|
1807
2286
|
);
|
|
1808
2287
|
return {
|
|
1809
2288
|
name: s.meta.name,
|
|
1810
2289
|
category: s.meta.category,
|
|
1811
2290
|
description: s.meta.description,
|
|
1812
|
-
confidence: score
|
|
2291
|
+
confidence: assignConfidence(score, maxCompScore),
|
|
1813
2292
|
import: `import { ${s.meta.name} } from '${pkgName}';`,
|
|
1814
2293
|
props: propsSummary,
|
|
1815
2294
|
examples,
|
|
@@ -1818,11 +2297,21 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1818
2297
|
};
|
|
1819
2298
|
})
|
|
1820
2299
|
);
|
|
1821
|
-
const matchingBlocks =
|
|
2300
|
+
const matchingBlocks = filteredBlockResults.slice(0, 5).map((result) => {
|
|
1822
2301
|
const block = allBlocks.find(
|
|
1823
2302
|
(b) => b.name.toLowerCase() === result.name.toLowerCase()
|
|
1824
2303
|
);
|
|
1825
|
-
|
|
2304
|
+
if (!block) return null;
|
|
2305
|
+
const pkgName = fragmentPackageMap.values().next().value ?? "your-component-library";
|
|
2306
|
+
const codeLines = block.code.split("\n");
|
|
2307
|
+
const code = codeLines.length > 30 ? codeLines.slice(0, 20).join("\n") + "\n// ... truncated (" + codeLines.length + " lines total)" : block.code;
|
|
2308
|
+
return {
|
|
2309
|
+
name: block.name,
|
|
2310
|
+
description: block.description,
|
|
2311
|
+
components: block.components,
|
|
2312
|
+
code,
|
|
2313
|
+
import: `import { ${block.components.join(", ")} } from '${pkgName}';`
|
|
2314
|
+
};
|
|
1826
2315
|
}).filter(Boolean);
|
|
1827
2316
|
let relevantTokens;
|
|
1828
2317
|
if (tokenResults.length > 0 && tokenData) {
|
|
@@ -1838,17 +2327,28 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1838
2327
|
}
|
|
1839
2328
|
if (Object.keys(relevantTokens).length === 0) relevantTokens = void 0;
|
|
1840
2329
|
}
|
|
2330
|
+
if (!relevantTokens && tokenData) {
|
|
2331
|
+
const categories = extractTokenCategories(useCase);
|
|
2332
|
+
relevantTokens = {};
|
|
2333
|
+
for (const cat of categories) {
|
|
2334
|
+
const tokens = tokenData.categories[cat];
|
|
2335
|
+
if (tokens && tokens.length > 0) {
|
|
2336
|
+
relevantTokens[cat] = tokens.slice(0, 5).map((t) => t.name);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (Object.keys(relevantTokens).length === 0) relevantTokens = void 0;
|
|
2340
|
+
}
|
|
1841
2341
|
return {
|
|
1842
2342
|
content: [{
|
|
1843
2343
|
type: "text",
|
|
1844
2344
|
text: JSON.stringify({
|
|
1845
2345
|
useCase,
|
|
1846
2346
|
components,
|
|
1847
|
-
blocks: matchingBlocks.length > 0 ? matchingBlocks : void 0,
|
|
1848
|
-
tokens: relevantTokens,
|
|
2347
|
+
blocks: verbosity !== "compact" && matchingBlocks.length > 0 ? matchingBlocks : void 0,
|
|
2348
|
+
tokens: verbosity !== "compact" ? relevantTokens : void 0,
|
|
1849
2349
|
noMatch: components.length === 0,
|
|
1850
2350
|
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` : ""}.`
|
|
1851
|
-
}
|
|
2351
|
+
})
|
|
1852
2352
|
}]
|
|
1853
2353
|
};
|
|
1854
2354
|
}
|
|
@@ -1868,6 +2368,18 @@ If you're a library author, run \`${BRAND.cliCommand} build\` first.`
|
|
|
1868
2368
|
isError: true
|
|
1869
2369
|
};
|
|
1870
2370
|
}
|
|
2371
|
+
{
|
|
2372
|
+
const data = await loadFragments();
|
|
2373
|
+
const fragment = Object.values(data.fragments).find(
|
|
2374
|
+
(s) => s.meta.name.toLowerCase() === componentName.toLowerCase()
|
|
2375
|
+
);
|
|
2376
|
+
if (!fragment) {
|
|
2377
|
+
const allNames = Object.values(data.fragments).map((s) => s.meta.name);
|
|
2378
|
+
const closest = findClosestMatch(componentName, allNames);
|
|
2379
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
2380
|
+
throw new Error(`Component "${componentName}" not found.${suggestion} Use ${TOOL_NAMES.discover} to see available components.`);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
1871
2383
|
const viewerUrl = config.viewerUrl;
|
|
1872
2384
|
if (!viewerUrl) {
|
|
1873
2385
|
return {
|
|
@@ -1919,7 +2431,7 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
1919
2431
|
threshold: result.threshold,
|
|
1920
2432
|
figmaUrl: result.figmaUrl,
|
|
1921
2433
|
changedRegions: result.changedRegions
|
|
1922
|
-
}
|
|
2434
|
+
})
|
|
1923
2435
|
});
|
|
1924
2436
|
return { content };
|
|
1925
2437
|
} catch (error) {
|
|
@@ -1936,6 +2448,7 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
1936
2448
|
const result = await renderComponent(viewerUrl, {
|
|
1937
2449
|
component: componentName,
|
|
1938
2450
|
props,
|
|
2451
|
+
variant: variantName,
|
|
1939
2452
|
viewport: viewport ?? { width: 800, height: 600 }
|
|
1940
2453
|
});
|
|
1941
2454
|
if (result.error) {
|
|
@@ -1953,7 +2466,7 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
1953
2466
|
},
|
|
1954
2467
|
{
|
|
1955
2468
|
type: "text",
|
|
1956
|
-
text: `Successfully rendered ${componentName} with props: ${JSON.stringify(props)}`
|
|
2469
|
+
text: `Successfully rendered ${componentName}${variantName ? ` (variant: "${variantName}")` : ""}${Object.keys(props).length > 0 ? ` with props: ${JSON.stringify(props)}` : ""}`
|
|
1957
2470
|
}
|
|
1958
2471
|
]
|
|
1959
2472
|
};
|
|
@@ -1982,7 +2495,10 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
1982
2495
|
(s) => s.meta.name.toLowerCase() === componentName.toLowerCase()
|
|
1983
2496
|
);
|
|
1984
2497
|
if (!fragment) {
|
|
1985
|
-
|
|
2498
|
+
const allNames = Object.values(data.fragments).map((s) => s.meta.name);
|
|
2499
|
+
const closest = findClosestMatch(componentName, allNames);
|
|
2500
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
2501
|
+
throw new Error(`Component "${componentName}" not found.${suggestion} Use ${TOOL_NAMES.discover} to see available components.`);
|
|
1986
2502
|
}
|
|
1987
2503
|
const viewerUrl = config.viewerUrl;
|
|
1988
2504
|
if (!viewerUrl) {
|
|
@@ -2020,7 +2536,7 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
2020
2536
|
summary: result.summary,
|
|
2021
2537
|
patchCount: result.patches.length,
|
|
2022
2538
|
nextStep: result.patches.length > 0 ? "Apply patches using your editor or `patch` command, then run fragments_render to confirm fixes." : void 0
|
|
2023
|
-
}
|
|
2539
|
+
})
|
|
2024
2540
|
}]
|
|
2025
2541
|
};
|
|
2026
2542
|
} catch (error) {
|
|
@@ -2044,6 +2560,18 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
2044
2560
|
if (!componentName) {
|
|
2045
2561
|
throw new Error("component is required");
|
|
2046
2562
|
}
|
|
2563
|
+
{
|
|
2564
|
+
const data = await loadFragments();
|
|
2565
|
+
const fragment = Object.values(data.fragments).find(
|
|
2566
|
+
(s) => s.meta.name.toLowerCase() === componentName.toLowerCase()
|
|
2567
|
+
);
|
|
2568
|
+
if (!fragment) {
|
|
2569
|
+
const allNames = Object.values(data.fragments).map((s) => s.meta.name);
|
|
2570
|
+
const closest = findClosestMatch(componentName, allNames);
|
|
2571
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : "";
|
|
2572
|
+
throw new Error(`Component "${componentName}" not found.${suggestion} Use ${TOOL_NAMES.discover} to see available components.`);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2047
2575
|
const viewerUrl = config.viewerUrl;
|
|
2048
2576
|
if (!viewerUrl) {
|
|
2049
2577
|
return {
|
|
@@ -2070,6 +2598,14 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
2070
2598
|
isError: true
|
|
2071
2599
|
};
|
|
2072
2600
|
}
|
|
2601
|
+
let nextStep;
|
|
2602
|
+
if (result.emptyAudit) {
|
|
2603
|
+
nextStep = `No testable elements found for ${variantName ? `variant "${variantName}"` : componentName}. The variant may not exist or renders no accessible content. Check available variants with ${TOOL_NAMES.inspect}("${componentName}").`;
|
|
2604
|
+
} else if (result.passed) {
|
|
2605
|
+
nextStep = 'All accessibility checks passed. Consider running with standard: "AAA" for enhanced compliance.';
|
|
2606
|
+
} else {
|
|
2607
|
+
nextStep = `Fix the violations above, then re-run ${TOOL_NAMES.a11y} to verify. Use ${TOOL_NAMES.fix} for automated fixes.`;
|
|
2608
|
+
}
|
|
2073
2609
|
return {
|
|
2074
2610
|
content: [{
|
|
2075
2611
|
type: "text",
|
|
@@ -2077,13 +2613,14 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
2077
2613
|
component: componentName,
|
|
2078
2614
|
variant: variantName ?? "all",
|
|
2079
2615
|
standard,
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2616
|
+
totalViolations: result.results.reduce((sum, r) => sum + r.summary.total, 0),
|
|
2617
|
+
variantsPassingAA: `${result.aaPercent}%`,
|
|
2618
|
+
variantsPassingAAA: `${result.aaaPercent}%`,
|
|
2083
2619
|
passed: result.passed,
|
|
2620
|
+
...result.emptyAudit && { emptyAudit: true },
|
|
2084
2621
|
results: result.results,
|
|
2085
|
-
nextStep
|
|
2086
|
-
}
|
|
2622
|
+
nextStep
|
|
2623
|
+
})
|
|
2087
2624
|
}]
|
|
2088
2625
|
};
|
|
2089
2626
|
} catch (error) {
|
|
@@ -2108,10 +2645,12 @@ Suggestion: ${result.suggestion}` : ""}`
|
|
|
2108
2645
|
edgeTypes: args?.edgeTypes,
|
|
2109
2646
|
maxDepth: args?.maxDepth
|
|
2110
2647
|
};
|
|
2648
|
+
const allNames = Object.values(data.fragments).map((s) => s.meta.name);
|
|
2111
2649
|
const result = handleGraphTool(
|
|
2112
2650
|
graphArgs,
|
|
2113
2651
|
data.graph,
|
|
2114
|
-
data.blocks ?? data.recipes
|
|
2652
|
+
data.blocks ?? data.recipes,
|
|
2653
|
+
allNames
|
|
2115
2654
|
);
|
|
2116
2655
|
if (result.isError) {
|
|
2117
2656
|
return {
|
|
@@ -2150,4 +2689,4 @@ export {
|
|
|
2150
2689
|
createMcpServer,
|
|
2151
2690
|
startMcpServer
|
|
2152
2691
|
};
|
|
2153
|
-
//# sourceMappingURL=chunk-
|
|
2692
|
+
//# sourceMappingURL=chunk-FEUUOMWK.js.map
|