@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.
- package/README.md +1 -1
- package/dist/bin.js +12 -12
- package/dist/{chunk-NOTYONHY.js → chunk-2DJH4F4P.js} +2 -2
- package/dist/{chunk-5CKYLCJH.js → chunk-2H2JAA3U.js} +35 -7
- package/dist/chunk-2H2JAA3U.js.map +1 -0
- package/dist/{chunk-G3M3MPQ6.js → chunk-B2TQKOLW.js} +157 -30
- package/dist/chunk-B2TQKOLW.js.map +1 -0
- package/dist/{chunk-AW7MWOUH.js → chunk-ICAIQ57V.js} +9 -5
- package/dist/chunk-ICAIQ57V.js.map +1 -0
- package/dist/{chunk-5ZYEOHYK.js → chunk-IOJE35DZ.js} +2 -2
- package/dist/{chunk-ZFKGX3QK.js → chunk-UXRGD3DM.js} +47 -14
- package/dist/chunk-UXRGD3DM.js.map +1 -0
- package/dist/{chunk-J4SI5RIH.js → chunk-XNWDI6UT.js} +4 -4
- package/dist/{core-LNXDLXDP.js → core-NJVKKLJ4.js} +11 -3
- package/dist/{generate-OIXXHOWR.js → generate-OVGMDKCJ.js} +4 -4
- package/dist/index.d.ts +30 -4
- package/dist/index.js +6 -6
- package/dist/{init-EVPXIDW4.js → init-EOA7TTOR.js} +4 -4
- package/dist/mcp-bin.js +308 -48
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-YN4LUDKY.js +12 -0
- package/dist/{service-K52ORLCJ.js → service-2T26CBWE.js} +4 -4
- package/dist/{static-viewer-JNQIHA4B.js → static-viewer-CLJJRYHK.js} +4 -4
- package/dist/{test-USARUEFW.js → test-ECPEXFDN.js} +3 -3
- package/dist/{tokens-C6YHBOQE.js → tokens-FHA2DO22.js} +5 -5
- package/dist/{viewer-H7TVFT4E.js → viewer-XDPD52L7.js} +13 -13
- package/package.json +1 -1
- package/src/build.ts +53 -13
- package/src/core/constants.ts +4 -1
- package/src/core/context.ts +28 -28
- package/src/core/defineSegment.ts +21 -11
- package/src/core/discovery.ts +52 -4
- package/src/core/index.ts +14 -4
- package/src/core/loader.ts +3 -0
- package/src/core/node.ts +3 -1
- package/src/core/parser.ts +1 -1
- package/src/core/schema.ts +7 -2
- package/src/core/token-parser.ts +211 -0
- package/src/core/types.ts +46 -6
- package/src/mcp/server.ts +378 -56
- package/dist/chunk-5CKYLCJH.js.map +0 -1
- package/dist/chunk-AW7MWOUH.js.map +0 -1
- package/dist/chunk-G3M3MPQ6.js.map +0 -1
- package/dist/chunk-ZFKGX3QK.js.map +0 -1
- package/dist/scan-YVYD64GD.js +0 -12
- /package/dist/{chunk-NOTYONHY.js.map → chunk-2DJH4F4P.js.map} +0 -0
- /package/dist/{chunk-5ZYEOHYK.js.map → chunk-IOJE35DZ.js.map} +0 -0
- /package/dist/{chunk-J4SI5RIH.js.map → chunk-XNWDI6UT.js.map} +0 -0
- /package/dist/{core-LNXDLXDP.js.map → core-NJVKKLJ4.js.map} +0 -0
- /package/dist/{generate-OIXXHOWR.js.map → generate-OVGMDKCJ.js.map} +0 -0
- /package/dist/{init-EVPXIDW4.js.map → init-EOA7TTOR.js.map} +0 -0
- /package/dist/{scan-YVYD64GD.js.map → scan-YN4LUDKY.js.map} +0 -0
- /package/dist/{service-K52ORLCJ.js.map → service-2T26CBWE.js.map} +0 -0
- /package/dist/{static-viewer-JNQIHA4B.js.map → static-viewer-CLJJRYHK.js.map} +0 -0
- /package/dist/{test-USARUEFW.js.map → test-ECPEXFDN.js.map} +0 -0
- /package/dist/{tokens-C6YHBOQE.js.map → tokens-FHA2DO22.js.map} +0 -0
- /package/dist/{viewer-H7TVFT4E.js.map → viewer-XDPD52L7.js.map} +0 -0
package/dist/mcp-bin.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
generateContext
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-B2TQKOLW.js";
|
|
6
6
|
import {
|
|
7
7
|
BRAND,
|
|
8
8
|
DEFAULTS
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ICAIQ57V.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp/server.ts
|
|
12
12
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -57,7 +57,7 @@ var _service = null;
|
|
|
57
57
|
async function getService() {
|
|
58
58
|
if (!_service) {
|
|
59
59
|
try {
|
|
60
|
-
_service = await import("./service-
|
|
60
|
+
_service = await import("./service-2T26CBWE.js");
|
|
61
61
|
} catch {
|
|
62
62
|
throw new Error(
|
|
63
63
|
"Visual tools require playwright. Install it with: npm install playwright"
|
|
@@ -69,7 +69,9 @@ async function getService() {
|
|
|
69
69
|
var TOOL_NAMES = {
|
|
70
70
|
discover: `${BRAND.nameLower}_discover`,
|
|
71
71
|
inspect: `${BRAND.nameLower}_inspect`,
|
|
72
|
-
|
|
72
|
+
blocks: `${BRAND.nameLower}_blocks`,
|
|
73
|
+
tokens: `${BRAND.nameLower}_tokens`,
|
|
74
|
+
implement: `${BRAND.nameLower}_implement`,
|
|
73
75
|
render: `${BRAND.nameLower}_render`,
|
|
74
76
|
fix: `${BRAND.nameLower}_fix`
|
|
75
77
|
};
|
|
@@ -279,26 +281,61 @@ var TOOLS = [
|
|
|
279
281
|
}
|
|
280
282
|
},
|
|
281
283
|
{
|
|
282
|
-
name: TOOL_NAMES.
|
|
283
|
-
description: `Search and retrieve composition
|
|
284
|
+
name: TOOL_NAMES.blocks,
|
|
285
|
+
description: `Search and retrieve composition blocks \u2014 named patterns showing how design system components wire together for common use cases (e.g., "Login Form", "Settings Page"). Returns the block with its code pattern.`,
|
|
284
286
|
inputSchema: {
|
|
285
287
|
type: "object",
|
|
286
288
|
properties: {
|
|
287
289
|
name: {
|
|
288
290
|
type: "string",
|
|
289
|
-
description: 'Exact
|
|
291
|
+
description: 'Exact block name to retrieve (e.g., "Login Form")'
|
|
290
292
|
},
|
|
291
293
|
search: {
|
|
292
294
|
type: "string",
|
|
293
|
-
description: "Free-text search across
|
|
295
|
+
description: "Free-text search across block names, descriptions, tags, and components"
|
|
294
296
|
},
|
|
295
297
|
component: {
|
|
296
298
|
type: "string",
|
|
297
|
-
description: 'Filter
|
|
299
|
+
description: 'Filter blocks that use a specific component (e.g., "Button")'
|
|
300
|
+
},
|
|
301
|
+
category: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: 'Filter by category (e.g., "authentication", "marketing", "dashboard", "settings", "ecommerce", "ai")'
|
|
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
|
-
|
|
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
|
-
|
|
407
|
-
|
|
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
|
-
|
|
414
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
428
|
-
return
|
|
483
|
+
defaultPackageName = pkg.name;
|
|
484
|
+
return defaultPackageName;
|
|
429
485
|
}
|
|
430
486
|
} catch {
|
|
431
487
|
}
|
|
432
488
|
}
|
|
433
|
-
|
|
434
|
-
return
|
|
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
|
|
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
|
-
},
|
|
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
|
-
|
|
717
|
+
noMatch,
|
|
718
|
+
weakMatch,
|
|
719
|
+
recommendation,
|
|
646
720
|
compositionHint,
|
|
647
|
-
nextStep
|
|
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
|
|
744
|
-
|
|
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
|
-
//
|
|
910
|
+
// BLOCKS — composition patterns
|
|
822
911
|
// ================================================================
|
|
823
|
-
case TOOL_NAMES.
|
|
912
|
+
case TOOL_NAMES.blocks: {
|
|
824
913
|
const data = await loadSegments();
|
|
825
|
-
const
|
|
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
|
|
829
|
-
|
|
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
|
-
|
|
836
|
-
hint: `No
|
|
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 =
|
|
842
|
-
if (
|
|
931
|
+
let filtered = allBlocks;
|
|
932
|
+
if (blockName) {
|
|
843
933
|
filtered = filtered.filter(
|
|
844
|
-
(
|
|
934
|
+
(b) => b.name.toLowerCase() === blockName.toLowerCase()
|
|
845
935
|
);
|
|
846
936
|
}
|
|
847
937
|
if (search) {
|
|
848
|
-
filtered = filtered.filter((
|
|
938
|
+
filtered = filtered.filter((b) => {
|
|
849
939
|
const haystack = [
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
...
|
|
853
|
-
...
|
|
854
|
-
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
};
|