@fragments-sdk/mcp 0.5.18 → 0.6.1

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/dist/bin.js CHANGED
@@ -1,13 +1,59 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AutoExtractionAdapter
4
+ } from "./chunk-NVHGG7GW.js";
5
+ import {
6
+ FragmentsJsonAdapter,
7
+ findFragmentsJson,
3
8
  startMcpServer
4
- } from "./chunk-ZOMUPGBG.js";
9
+ } from "./chunk-WBOVO43F.js";
10
+ import "./chunk-4SVS3AA3.js";
11
+ import "./chunk-FGIBLPSU.js";
5
12
 
6
13
  // src/bin.ts
14
+ import { existsSync } from "fs";
15
+ import { join } from "path";
7
16
  var args = process.argv.slice(2);
17
+ if (args[0] === "init") {
18
+ const { runInit } = await import("./init.js");
19
+ const initArgs = args.slice(1);
20
+ let projectRoot2 = process.cwd();
21
+ let clients;
22
+ for (let i = 0; i < initArgs.length; i++) {
23
+ const arg = initArgs[i];
24
+ if (arg === "--project-root" || arg === "-p") {
25
+ projectRoot2 = initArgs[++i] ?? projectRoot2;
26
+ } else if (arg === "--client" || arg === "-c") {
27
+ clients = (initArgs[++i] ?? "").split(",").map((s) => s.trim());
28
+ } else if (arg === "--help" || arg === "-h") {
29
+ console.log(`
30
+ Usage: fragments-mcp init [options]
31
+
32
+ Write MCP server config for your AI editor(s).
33
+ Auto-detects Claude Code, Cursor, VS Code, and Windsurf.
34
+
35
+ Options:
36
+ -p, --project-root <path> Project root directory (default: cwd)
37
+ -c, --client <list> Comma-separated clients: claude,cursor,vscode,windsurf
38
+ (default: auto-detect)
39
+ -h, --help Show this help message
40
+
41
+ Examples:
42
+ npx @fragments-sdk/mcp init
43
+ npx @fragments-sdk/mcp init --client cursor,claude
44
+ `);
45
+ process.exit(0);
46
+ }
47
+ }
48
+ runInit({ projectRoot: projectRoot2, clients });
49
+ process.exit(0);
50
+ }
8
51
  var projectRoot = process.cwd();
9
52
  var viewerUrl;
10
53
  var apiKey;
54
+ var generateRules = false;
55
+ var rulesFormats;
56
+ var force = false;
11
57
  for (let i = 0; i < args.length; i++) {
12
58
  const arg = args[i];
13
59
  if (arg === "--project-root" || arg === "-p") {
@@ -16,38 +62,93 @@ for (let i = 0; i < args.length; i++) {
16
62
  viewerUrl = args[++i];
17
63
  } else if (arg === "--api-key" || arg === "-k") {
18
64
  apiKey = args[++i];
65
+ } else if (arg === "--generate-rules") {
66
+ generateRules = true;
67
+ } else if (arg === "--formats") {
68
+ rulesFormats = args[++i];
69
+ } else if (arg === "--force") {
70
+ force = true;
19
71
  } else if (arg === "--help" || arg === "-h") {
20
72
  console.log(`
21
- Usage: fragments-mcp [options]
73
+ Usage: fragments-mcp [command] [options]
22
74
 
23
- Standalone MCP server for Fragments design system.
24
- Provides component discovery with keyword search, no CLI/Playwright needed.
75
+ Standalone MCP server for design system intelligence.
76
+ Provides component discovery, search, and governance \u2014 no CLI/Playwright needed.
77
+
78
+ Commands:
79
+ init Write MCP config for your AI editor(s)
25
80
 
26
81
  Options:
27
82
  -p, --project-root <path> Project root directory (default: cwd)
28
83
  -u, --viewer-url <url> Viewer URL for render/fix/a11y tools (optional)
29
84
  -k, --api-key <key> Premium API key for semantic vector search (optional)
85
+ --generate-rules Generate IDE rules files and exit
86
+ --formats <list> Comma-separated formats: cursor,claude,copilot (default: all)
87
+ --force Overwrite existing rules files
30
88
  -h, --help Show this help message
31
89
 
32
- Setup (Claude Code / Cursor):
33
- {
34
- "mcpServers": {
35
- "fragments": {
36
- "command": "npx",
37
- "args": ["@fragments-sdk/mcp"]
38
- }
39
- }
40
- }
90
+ Quick start:
91
+ npx @fragments-sdk/mcp init
41
92
  `);
42
93
  process.exit(0);
43
94
  }
44
95
  }
45
- startMcpServer({
46
- projectRoot,
47
- viewerUrl,
48
- apiKey
49
- }).catch((error) => {
50
- console.error("Failed to start MCP server:", error);
51
- process.exit(1);
52
- });
96
+ var adapter;
97
+ var fragmentsJsonPaths = findFragmentsJson(projectRoot);
98
+ if (fragmentsJsonPaths.length > 0) {
99
+ adapter = new FragmentsJsonAdapter();
100
+ } else if (existsSync(join(projectRoot, "tsconfig.json")) || existsSync(join(projectRoot, "tsconfig.app.json"))) {
101
+ adapter = new AutoExtractionAdapter();
102
+ console.error("[fragments-mcp] No fragments.json found \u2014 auto-extracting from source.");
103
+ } else {
104
+ adapter = new FragmentsJsonAdapter();
105
+ }
106
+ if (generateRules) {
107
+ (async () => {
108
+ const { generateRulesFiles } = await import("./rules-WGBCECAK.js");
109
+ const defaultFormats = ["cursor", "claude", "copilot"];
110
+ const formats = rulesFormats ? rulesFormats.split(",").map((f) => f.trim()) : defaultFormats;
111
+ const validFormats = /* @__PURE__ */ new Set(["cursor", "claude", "copilot"]);
112
+ for (const fmt of formats) {
113
+ if (!validFormats.has(fmt)) {
114
+ console.error(`Invalid format: "${fmt}". Valid formats: cursor, claude, copilot`);
115
+ process.exit(1);
116
+ }
117
+ }
118
+ const data = await adapter.load(projectRoot);
119
+ const { loadConfigFile } = await import("./config-TUFA5J2S.js");
120
+ const { BRAND } = await import("./constants-YXOTMY3I.js");
121
+ const fileConfig = loadConfigFile(projectRoot);
122
+ const brandName = fileConfig?.brand?.name ?? BRAND.name;
123
+ const files = generateRulesFiles({
124
+ data,
125
+ brandName,
126
+ outputDir: projectRoot,
127
+ formats,
128
+ force
129
+ });
130
+ if (files.length === 0) {
131
+ console.log("No rules files generated.");
132
+ } else {
133
+ console.log(`Generated ${files.length} rules file(s):`);
134
+ for (const file of files) {
135
+ console.log(` ${file}`);
136
+ }
137
+ }
138
+ process.exit(0);
139
+ })().catch((error) => {
140
+ console.error("Failed to generate rules:", error instanceof Error ? error.message : error);
141
+ process.exit(1);
142
+ });
143
+ } else {
144
+ startMcpServer({
145
+ projectRoot,
146
+ viewerUrl,
147
+ apiKey,
148
+ adapter
149
+ }).catch((error) => {
150
+ console.error("Failed to start MCP server:", error);
151
+ process.exit(1);
152
+ });
153
+ }
53
154
  //# sourceMappingURL=bin.js.map
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { startMcpServer } from './server.js';\n\n// Parse command line arguments\nconst args = process.argv.slice(2);\nlet projectRoot = process.cwd();\nlet viewerUrl: string | undefined;\nlet apiKey: string | undefined;\n\nfor (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === '--project-root' || arg === '-p') {\n projectRoot = args[++i] ?? projectRoot;\n } else if (arg === '--viewer-url' || arg === '-u') {\n viewerUrl = args[++i];\n } else if (arg === '--api-key' || arg === '-k') {\n apiKey = args[++i];\n } else if (arg === '--help' || arg === '-h') {\n console.log(`\nUsage: fragments-mcp [options]\n\nStandalone MCP server for Fragments design system.\nProvides component discovery with keyword search, no CLI/Playwright needed.\n\nOptions:\n -p, --project-root <path> Project root directory (default: cwd)\n -u, --viewer-url <url> Viewer URL for render/fix/a11y tools (optional)\n -k, --api-key <key> Premium API key for semantic vector search (optional)\n -h, --help Show this help message\n\nSetup (Claude Code / Cursor):\n {\n \"mcpServers\": {\n \"fragments\": {\n \"command\": \"npx\",\n \"args\": [\"@fragments-sdk/mcp\"]\n }\n }\n }\n`);\n process.exit(0);\n }\n}\n\n// Start server\nstartMcpServer({\n projectRoot,\n viewerUrl,\n apiKey,\n}).catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n});\n"],"mappings":";;;;;;AAIA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,IAAI,cAAc,QAAQ,IAAI;AAC9B,IAAI;AACJ,IAAI;AAEJ,SAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAM,MAAM,KAAK,CAAC;AAElB,MAAI,QAAQ,oBAAoB,QAAQ,MAAM;AAC5C,kBAAc,KAAK,EAAE,CAAC,KAAK;AAAA,EAC7B,WAAW,QAAQ,kBAAkB,QAAQ,MAAM;AACjD,gBAAY,KAAK,EAAE,CAAC;AAAA,EACtB,WAAW,QAAQ,eAAe,QAAQ,MAAM;AAC9C,aAAS,KAAK,EAAE,CAAC;AAAA,EACnB,WAAW,QAAQ,YAAY,QAAQ,MAAM;AAC3C,YAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAqBf;AACG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAGA,eAAe;AAAA,EACb;AAAA,EACA;AAAA,EACA;AACF,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,UAAQ,MAAM,+BAA+B,KAAK;AAClD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { startMcpServer } from './server.js';\nimport { FragmentsJsonAdapter } from './adapters/fragments-json.js';\nimport { AutoExtractionAdapter } from './adapters/auto-extract.js';\nimport { findFragmentsJson } from './discovery.js';\nimport type { DataAdapter } from './adapters/types.js';\n\n// Parse command line arguments\nconst args = process.argv.slice(2);\n\n// ---------------------------------------------------------------------------\n// Subcommand: init\n// ---------------------------------------------------------------------------\nif (args[0] === 'init') {\n const { runInit } = await import('./init.js');\n const initArgs = args.slice(1);\n\n let projectRoot = process.cwd();\n let clients: string[] | undefined;\n\n for (let i = 0; i < initArgs.length; i++) {\n const arg = initArgs[i];\n if (arg === '--project-root' || arg === '-p') {\n projectRoot = initArgs[++i] ?? projectRoot;\n } else if (arg === '--client' || arg === '-c') {\n clients = (initArgs[++i] ?? '').split(',').map((s) => s.trim());\n } else if (arg === '--help' || arg === '-h') {\n console.log(`\nUsage: fragments-mcp init [options]\n\nWrite MCP server config for your AI editor(s).\nAuto-detects Claude Code, Cursor, VS Code, and Windsurf.\n\nOptions:\n -p, --project-root <path> Project root directory (default: cwd)\n -c, --client <list> Comma-separated clients: claude,cursor,vscode,windsurf\n (default: auto-detect)\n -h, --help Show this help message\n\nExamples:\n npx @fragments-sdk/mcp init\n npx @fragments-sdk/mcp init --client cursor,claude\n`);\n process.exit(0);\n }\n }\n\n runInit({ projectRoot, clients: clients as import('./init.js').Client[] });\n process.exit(0);\n}\n\n// ---------------------------------------------------------------------------\n// Main: MCP server mode\n// ---------------------------------------------------------------------------\nlet projectRoot = process.cwd();\nlet viewerUrl: string | undefined;\nlet apiKey: string | undefined;\nlet generateRules = false;\nlet rulesFormats: string | undefined;\nlet force = false;\n\nfor (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === '--project-root' || arg === '-p') {\n projectRoot = args[++i] ?? projectRoot;\n } else if (arg === '--viewer-url' || arg === '-u') {\n viewerUrl = args[++i];\n } else if (arg === '--api-key' || arg === '-k') {\n apiKey = args[++i];\n } else if (arg === '--generate-rules') {\n generateRules = true;\n } else if (arg === '--formats') {\n rulesFormats = args[++i];\n } else if (arg === '--force') {\n force = true;\n } else if (arg === '--help' || arg === '-h') {\n console.log(`\nUsage: fragments-mcp [command] [options]\n\nStandalone MCP server for design system intelligence.\nProvides component discovery, search, and governance — no CLI/Playwright needed.\n\nCommands:\n init Write MCP config for your AI editor(s)\n\nOptions:\n -p, --project-root <path> Project root directory (default: cwd)\n -u, --viewer-url <url> Viewer URL for render/fix/a11y tools (optional)\n -k, --api-key <key> Premium API key for semantic vector search (optional)\n --generate-rules Generate IDE rules files and exit\n --formats <list> Comma-separated formats: cursor,claude,copilot (default: all)\n --force Overwrite existing rules files\n -h, --help Show this help message\n\nQuick start:\n npx @fragments-sdk/mcp init\n`);\n process.exit(0);\n }\n}\n\n// Adapter selection: fragments.json > auto-extract > error\nlet adapter: DataAdapter;\nconst fragmentsJsonPaths = findFragmentsJson(projectRoot);\n\nif (fragmentsJsonPaths.length > 0) {\n adapter = new FragmentsJsonAdapter();\n} else if (\n existsSync(join(projectRoot, 'tsconfig.json')) ||\n existsSync(join(projectRoot, 'tsconfig.app.json'))\n) {\n adapter = new AutoExtractionAdapter();\n console.error('[fragments-mcp] No fragments.json found — auto-extracting from source.');\n} else {\n // Let FragmentsJsonAdapter throw a helpful error\n adapter = new FragmentsJsonAdapter();\n}\n\n// --generate-rules: generate IDE rules and exit\nif (generateRules) {\n (async () => {\n const { generateRulesFiles } = await import('./rules.js');\n type RulesFormat = 'cursor' | 'claude' | 'copilot';\n\n const defaultFormats: RulesFormat[] = ['cursor', 'claude', 'copilot'];\n const formats: RulesFormat[] = rulesFormats\n ? (rulesFormats.split(',').map(f => f.trim()) as RulesFormat[])\n : defaultFormats;\n\n // Validate formats\n const validFormats = new Set(['cursor', 'claude', 'copilot']);\n for (const fmt of formats) {\n if (!validFormats.has(fmt)) {\n console.error(`Invalid format: \"${fmt}\". Valid formats: cursor, claude, copilot`);\n process.exit(1);\n }\n }\n\n // Load data using the same adapter\n const data = await adapter.load(projectRoot);\n\n // Resolve brand name from config\n const { loadConfigFile } = await import('./config.js');\n const { BRAND } = await import('./constants.js');\n const fileConfig = loadConfigFile(projectRoot);\n const brandName = fileConfig?.brand?.name ?? BRAND.name;\n\n // Generate rules\n const files = generateRulesFiles({\n data,\n brandName,\n outputDir: projectRoot,\n formats,\n force,\n });\n\n if (files.length === 0) {\n console.log('No rules files generated.');\n } else {\n console.log(`Generated ${files.length} rules file(s):`);\n for (const file of files) {\n console.log(` ${file}`);\n }\n }\n\n process.exit(0);\n })().catch((error) => {\n console.error('Failed to generate rules:', error instanceof Error ? error.message : error);\n process.exit(1);\n });\n} else {\n // Default: start MCP server\n startMcpServer({\n projectRoot,\n viewerUrl,\n apiKey,\n adapter,\n }).catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;;;;AACA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AAQrB,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAKjC,IAAI,KAAK,CAAC,MAAM,QAAQ;AACtB,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,WAAW;AAC5C,QAAM,WAAW,KAAK,MAAM,CAAC;AAE7B,MAAIA,eAAc,QAAQ,IAAI;AAC9B,MAAI;AAEJ,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,QAAQ,oBAAoB,QAAQ,MAAM;AAC5C,MAAAA,eAAc,SAAS,EAAE,CAAC,KAAKA;AAAA,IACjC,WAAW,QAAQ,cAAc,QAAQ,MAAM;AAC7C,iBAAW,SAAS,EAAE,CAAC,KAAK,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA,IAChE,WAAW,QAAQ,YAAY,QAAQ,MAAM;AAC3C,cAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAejB;AACK,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,UAAQ,EAAE,aAAAA,cAAa,QAAiD,CAAC;AACzE,UAAQ,KAAK,CAAC;AAChB;AAKA,IAAI,cAAc,QAAQ,IAAI;AAC9B,IAAI;AACJ,IAAI;AACJ,IAAI,gBAAgB;AACpB,IAAI;AACJ,IAAI,QAAQ;AAEZ,SAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAM,MAAM,KAAK,CAAC;AAElB,MAAI,QAAQ,oBAAoB,QAAQ,MAAM;AAC5C,kBAAc,KAAK,EAAE,CAAC,KAAK;AAAA,EAC7B,WAAW,QAAQ,kBAAkB,QAAQ,MAAM;AACjD,gBAAY,KAAK,EAAE,CAAC;AAAA,EACtB,WAAW,QAAQ,eAAe,QAAQ,MAAM;AAC9C,aAAS,KAAK,EAAE,CAAC;AAAA,EACnB,WAAW,QAAQ,oBAAoB;AACrC,oBAAgB;AAAA,EAClB,WAAW,QAAQ,aAAa;AAC9B,mBAAe,KAAK,EAAE,CAAC;AAAA,EACzB,WAAW,QAAQ,WAAW;AAC5B,YAAQ;AAAA,EACV,WAAW,QAAQ,YAAY,QAAQ,MAAM;AAC3C,YAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAoBf;AACG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAGA,IAAI;AACJ,IAAM,qBAAqB,kBAAkB,WAAW;AAExD,IAAI,mBAAmB,SAAS,GAAG;AACjC,YAAU,IAAI,qBAAqB;AACrC,WACE,WAAW,KAAK,aAAa,eAAe,CAAC,KAC7C,WAAW,KAAK,aAAa,mBAAmB,CAAC,GACjD;AACA,YAAU,IAAI,sBAAsB;AACpC,UAAQ,MAAM,6EAAwE;AACxF,OAAO;AAEL,YAAU,IAAI,qBAAqB;AACrC;AAGA,IAAI,eAAe;AACjB,GAAC,YAAY;AACX,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,qBAAY;AAGxD,UAAM,iBAAgC,CAAC,UAAU,UAAU,SAAS;AACpE,UAAM,UAAyB,eAC1B,aAAa,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC,IAC1C;AAGJ,UAAM,eAAe,oBAAI,IAAI,CAAC,UAAU,UAAU,SAAS,CAAC;AAC5D,eAAW,OAAO,SAAS;AACzB,UAAI,CAAC,aAAa,IAAI,GAAG,GAAG;AAC1B,gBAAQ,MAAM,oBAAoB,GAAG,2CAA2C;AAChF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,OAAO,MAAM,QAAQ,KAAK,WAAW;AAG3C,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,sBAAa;AACrD,UAAM,EAAE,MAAM,IAAI,MAAM,OAAO,yBAAgB;AAC/C,UAAM,aAAa,eAAe,WAAW;AAC7C,UAAM,YAAY,YAAY,OAAO,QAAQ,MAAM;AAGnD,UAAM,QAAQ,mBAAmB;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,MAAM,WAAW,GAAG;AACtB,cAAQ,IAAI,2BAA2B;AAAA,IACzC,OAAO;AACL,cAAQ,IAAI,aAAa,MAAM,MAAM,iBAAiB;AACtD,iBAAW,QAAQ,OAAO;AACxB,gBAAQ,IAAI,KAAK,IAAI,EAAE;AAAA,MACzB;AAAA,IACF;AAEA,YAAQ,KAAK,CAAC;AAAA,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU;AACpB,YAAQ,MAAM,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACzF,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH,OAAO;AAEL,iBAAe;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,YAAQ,MAAM,+BAA+B,KAAK;AAClD,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":["projectRoot"]}
@@ -0,0 +1,107 @@
1
+ // src/rules.ts
2
+ import { join } from "path";
3
+ import { writeFileSync, mkdirSync, existsSync } from "fs";
4
+ function generateRulesFiles(options) {
5
+ const { data, brandName, outputDir, formats, force = false } = options;
6
+ const content = buildRulesContent(data, brandName);
7
+ const files = [];
8
+ for (const format of formats) {
9
+ let filePath;
10
+ let fileContent;
11
+ switch (format) {
12
+ case "cursor":
13
+ filePath = join(outputDir, ".cursorrules");
14
+ fileContent = content;
15
+ break;
16
+ case "claude":
17
+ filePath = join(outputDir, "CLAUDE.md");
18
+ fileContent = `# CLAUDE.md
19
+
20
+ This file provides guidance to Claude Code when working with code in this repository.
21
+
22
+ ${content}`;
23
+ break;
24
+ case "copilot": {
25
+ const dir = join(outputDir, ".github");
26
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
27
+ filePath = join(dir, "copilot-instructions.md");
28
+ fileContent = content;
29
+ break;
30
+ }
31
+ }
32
+ if (!force && existsSync(filePath)) {
33
+ console.error(` Skipped ${filePath} (already exists, use --force to overwrite)`);
34
+ continue;
35
+ }
36
+ writeFileSync(filePath, fileContent, "utf-8");
37
+ files.push(filePath);
38
+ }
39
+ return files;
40
+ }
41
+ function buildRulesContent(data, brandName) {
42
+ const sections = [];
43
+ sections.push(`# ${brandName} Design System Rules
44
+ `);
45
+ sections.push(`Always use ${brandName} components and design tokens when building UI. Do not use raw HTML elements, Tailwind classes, or hardcoded CSS values when a design system component or token exists.
46
+ `);
47
+ const components = Object.values(data.components);
48
+ if (components.length > 0) {
49
+ sections.push("## Components\n");
50
+ const byCategory = /* @__PURE__ */ new Map();
51
+ for (const c of components) {
52
+ const cat = c.meta.category || "uncategorized";
53
+ if (!byCategory.has(cat)) byCategory.set(cat, []);
54
+ byCategory.get(cat).push(c.meta.name);
55
+ }
56
+ for (const [cat, names] of byCategory) {
57
+ sections.push(`### ${cat}
58
+ `);
59
+ sections.push(names.map((n) => `- ${n}`).join("\n"));
60
+ sections.push("");
61
+ }
62
+ }
63
+ if (data.defaultPackageName) {
64
+ sections.push("## Imports\n");
65
+ sections.push(`Import components from \`${data.defaultPackageName}\`:
66
+ `);
67
+ sections.push("```typescript");
68
+ const exampleNames = components.slice(0, 3).map((c) => c.meta.name);
69
+ sections.push(`import { ${exampleNames.join(", ")} } from '${data.defaultPackageName}';`);
70
+ sections.push("```\n");
71
+ }
72
+ if (data.tokens && data.tokens.total > 0) {
73
+ sections.push("## Design Tokens\n");
74
+ sections.push(`Prefix: \`${data.tokens.prefix}\`. Use \`var(--token-name)\` in CSS/styles.
75
+ `);
76
+ sections.push("Available categories:");
77
+ for (const [cat, tokens] of Object.entries(data.tokens.categories)) {
78
+ sections.push(`- **${cat}** (${tokens.length} tokens)`);
79
+ }
80
+ sections.push("");
81
+ }
82
+ const allDos = [];
83
+ const allDonts = [];
84
+ for (const c of components) {
85
+ if (c.usage?.when) allDos.push(...c.usage.when.slice(0, 1).map((w) => `${c.meta.name}: ${w}`));
86
+ if (c.usage?.whenNot) allDonts.push(...c.usage.whenNot.slice(0, 1).map((w) => `${c.meta.name}: ${w}`));
87
+ }
88
+ if (allDos.length > 0 || allDonts.length > 0) {
89
+ sections.push("## Usage Guidelines\n");
90
+ if (allDos.length > 0) {
91
+ sections.push("**Do:**");
92
+ sections.push(allDos.slice(0, 10).map((d) => `- ${d}`).join("\n"));
93
+ sections.push("");
94
+ }
95
+ if (allDonts.length > 0) {
96
+ sections.push("**Don't:**");
97
+ sections.push(allDonts.slice(0, 10).map((d) => `- ${d}`).join("\n"));
98
+ sections.push("");
99
+ }
100
+ }
101
+ return sections.join("\n");
102
+ }
103
+
104
+ export {
105
+ generateRulesFiles
106
+ };
107
+ //# sourceMappingURL=chunk-2W7DAUUS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/rules.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport type { DesignSystemData } from './types.js';\n\nexport type RulesFormat = 'cursor' | 'claude' | 'copilot';\n\nexport interface RulesGeneratorOptions {\n data: DesignSystemData;\n brandName: string;\n outputDir: string;\n formats: RulesFormat[];\n /** Overwrite existing files (default: false — skip and warn) */\n force?: boolean;\n}\n\n/**\n * Generate IDE rules files from design system data.\n * Returns list of files written. Skips existing files unless force is true.\n */\nexport function generateRulesFiles(options: RulesGeneratorOptions): string[] {\n const { data, brandName, outputDir, formats, force = false } = options;\n const content = buildRulesContent(data, brandName);\n const files: string[] = [];\n\n for (const format of formats) {\n let filePath: string;\n let fileContent: string;\n\n switch (format) {\n case 'cursor':\n filePath = join(outputDir, '.cursorrules');\n fileContent = content;\n break;\n case 'claude':\n filePath = join(outputDir, 'CLAUDE.md');\n fileContent = `# CLAUDE.md\\n\\nThis file provides guidance to Claude Code when working with code in this repository.\\n\\n${content}`;\n break;\n case 'copilot': {\n const dir = join(outputDir, '.github');\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n filePath = join(dir, 'copilot-instructions.md');\n fileContent = content;\n break;\n }\n }\n\n if (!force && existsSync(filePath)) {\n console.error(` Skipped ${filePath} (already exists, use --force to overwrite)`);\n continue;\n }\n\n writeFileSync(filePath, fileContent, 'utf-8');\n files.push(filePath);\n }\n\n return files;\n}\n\nfunction buildRulesContent(data: DesignSystemData, brandName: string): string {\n const sections: string[] = [];\n\n // Header\n sections.push(`# ${brandName} Design System Rules\\n`);\n sections.push(`Always use ${brandName} components and design tokens when building UI. Do not use raw HTML elements, Tailwind classes, or hardcoded CSS values when a design system component or token exists.\\n`);\n\n // Component inventory\n const components = Object.values(data.components);\n if (components.length > 0) {\n sections.push('## Components\\n');\n const byCategory = new Map<string, string[]>();\n for (const c of components) {\n const cat = c.meta.category || 'uncategorized';\n if (!byCategory.has(cat)) byCategory.set(cat, []);\n byCategory.get(cat)!.push(c.meta.name);\n }\n for (const [cat, names] of byCategory) {\n sections.push(`### ${cat}\\n`);\n sections.push(names.map(n => `- ${n}`).join('\\n'));\n sections.push('');\n }\n }\n\n // Import pattern\n if (data.defaultPackageName) {\n sections.push('## Imports\\n');\n sections.push(`Import components from \\`${data.defaultPackageName}\\`:\\n`);\n sections.push('```typescript');\n const exampleNames = components.slice(0, 3).map(c => c.meta.name);\n sections.push(`import { ${exampleNames.join(', ')} } from '${data.defaultPackageName}';`);\n sections.push('```\\n');\n }\n\n // Tokens\n if (data.tokens && data.tokens.total > 0) {\n sections.push('## Design Tokens\\n');\n sections.push(`Prefix: \\`${data.tokens.prefix}\\`. Use \\`var(--token-name)\\` in CSS/styles.\\n`);\n sections.push('Available categories:');\n for (const [cat, tokens] of Object.entries(data.tokens.categories)) {\n sections.push(`- **${cat}** (${tokens.length} tokens)`);\n }\n sections.push('');\n }\n\n // Usage guidelines (aggregated from all components)\n const allDos: string[] = [];\n const allDonts: string[] = [];\n for (const c of components) {\n if (c.usage?.when) allDos.push(...c.usage.when.slice(0, 1).map(w => `${c.meta.name}: ${w}`));\n if (c.usage?.whenNot) allDonts.push(...c.usage.whenNot.slice(0, 1).map(w => `${c.meta.name}: ${w}`));\n }\n if (allDos.length > 0 || allDonts.length > 0) {\n sections.push('## Usage Guidelines\\n');\n if (allDos.length > 0) {\n sections.push('**Do:**');\n sections.push(allDos.slice(0, 10).map(d => `- ${d}`).join('\\n'));\n sections.push('');\n }\n if (allDonts.length > 0) {\n sections.push('**Don\\'t:**');\n sections.push(allDonts.slice(0, 10).map(d => `- ${d}`).join('\\n'));\n sections.push('');\n }\n }\n\n return sections.join('\\n');\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,78 @@
1
+ // src/constants.ts
2
+ var BRAND = {
3
+ /** Display name (e.g., "Fragments") */
4
+ name: "Fragments",
5
+ /** Lowercase name for file paths and CLI (e.g., "fragments") */
6
+ nameLower: "fragments",
7
+ /** File extension for fragment definition files — V2 canonical format */
8
+ fileExtension: ".contract.json",
9
+ /** Legacy file extension for segments (still supported for migration) */
10
+ legacyFileExtension: ".segment.tsx",
11
+ /** JSON file extension for compiled output */
12
+ jsonExtension: ".fragment.json",
13
+ /** Default output file name (e.g., "fragments.json") */
14
+ outFile: "fragments.json",
15
+ /** Config file name (e.g., "fragments.config.ts") */
16
+ configFile: "fragments.config.ts",
17
+ /** Legacy config file name (still supported for migration) */
18
+ legacyConfigFile: "segments.config.ts",
19
+ /** CLI command name (e.g., "fragments") */
20
+ cliCommand: "fragments",
21
+ /** Package scope (e.g., "@fragments") */
22
+ packageScope: "@fragments",
23
+ /** Directory for storing fragments, registry, and cache */
24
+ dataDir: ".fragments",
25
+ /** Components subdirectory within .fragments/ */
26
+ componentsDir: "components",
27
+ /** Registry file name */
28
+ registryFile: "registry.json",
29
+ /** Context file name (AI-ready markdown) */
30
+ contextFile: "context.md",
31
+ /** Screenshots subdirectory */
32
+ screenshotsDir: "screenshots",
33
+ /** Cache subdirectory (gitignored) */
34
+ cacheDir: "cache",
35
+ /** Diff output subdirectory (gitignored) */
36
+ diffDir: "diff",
37
+ /** Manifest filename */
38
+ manifestFile: "manifest.json",
39
+ /** Prefix for localStorage keys (e.g., "fragments-") */
40
+ storagePrefix: "fragments-",
41
+ /** Static viewer HTML file name */
42
+ viewerHtmlFile: "fragments-viewer.html",
43
+ /** MCP tool name prefix (e.g., "fragments_") */
44
+ mcpToolPrefix: "fragments_",
45
+ /** File extension for block definition files */
46
+ blockFileExtension: ".block.ts",
47
+ /** @deprecated Use blockFileExtension instead */
48
+ recipeFileExtension: ".recipe.ts",
49
+ /** Vite plugin namespace */
50
+ vitePluginNamespace: "fragments-core-shim"
51
+ };
52
+ var DEFAULTS = {
53
+ /** Default viewport dimensions */
54
+ viewport: {
55
+ width: 1280,
56
+ height: 800
57
+ },
58
+ /** Default diff threshold (percentage) */
59
+ diffThreshold: 5,
60
+ /** Browser pool size */
61
+ poolSize: 3,
62
+ /** Idle timeout before browser shutdown (ms) - 5 minutes */
63
+ idleTimeoutMs: 5 * 60 * 1e3,
64
+ /** Delay after render before capture (ms) */
65
+ captureDelayMs: 100,
66
+ /** Font loading timeout (ms) */
67
+ fontTimeoutMs: 3e3,
68
+ /** Default theme */
69
+ theme: "light",
70
+ /** Dev server port */
71
+ port: 6006
72
+ };
73
+
74
+ export {
75
+ BRAND,
76
+ DEFAULTS
77
+ };
78
+ //# sourceMappingURL=chunk-4SVS3AA3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Brand constants for easy rebranding if domain availability requires it.\n * All naming throughout the codebase should reference these constants.\n *\n * Inlined from @fragments-sdk/cli to avoid pulling in the full CLI dependency.\n */\nexport const BRAND = {\n /** Display name (e.g., \"Fragments\") */\n name: \"Fragments\",\n\n /** Lowercase name for file paths and CLI (e.g., \"fragments\") */\n nameLower: \"fragments\",\n\n /** File extension for fragment definition files — V2 canonical format */\n fileExtension: \".contract.json\",\n\n /** Legacy file extension for segments (still supported for migration) */\n legacyFileExtension: \".segment.tsx\",\n\n /** JSON file extension for compiled output */\n jsonExtension: \".fragment.json\",\n\n /** Default output file name (e.g., \"fragments.json\") */\n outFile: \"fragments.json\",\n\n /** Config file name (e.g., \"fragments.config.ts\") */\n configFile: \"fragments.config.ts\",\n\n /** Legacy config file name (still supported for migration) */\n legacyConfigFile: \"segments.config.ts\",\n\n /** CLI command name (e.g., \"fragments\") */\n cliCommand: \"fragments\",\n\n /** Package scope (e.g., \"@fragments\") */\n packageScope: \"@fragments\",\n\n /** Directory for storing fragments, registry, and cache */\n dataDir: \".fragments\",\n\n /** Components subdirectory within .fragments/ */\n componentsDir: \"components\",\n\n /** Registry file name */\n registryFile: \"registry.json\",\n\n /** Context file name (AI-ready markdown) */\n contextFile: \"context.md\",\n\n /** Screenshots subdirectory */\n screenshotsDir: \"screenshots\",\n\n /** Cache subdirectory (gitignored) */\n cacheDir: \"cache\",\n\n /** Diff output subdirectory (gitignored) */\n diffDir: \"diff\",\n\n /** Manifest filename */\n manifestFile: \"manifest.json\",\n\n /** Prefix for localStorage keys (e.g., \"fragments-\") */\n storagePrefix: \"fragments-\",\n\n /** Static viewer HTML file name */\n viewerHtmlFile: \"fragments-viewer.html\",\n\n /** MCP tool name prefix (e.g., \"fragments_\") */\n mcpToolPrefix: \"fragments_\",\n\n /** File extension for block definition files */\n blockFileExtension: \".block.ts\",\n\n /** @deprecated Use blockFileExtension instead */\n recipeFileExtension: \".recipe.ts\",\n\n /** Vite plugin namespace */\n vitePluginNamespace: \"fragments-core-shim\",\n} as const;\n\nexport type Brand = typeof BRAND;\n\n/**\n * Default configuration values for the service.\n *\n * Inlined from @fragments-sdk/cli to avoid pulling in the full CLI dependency.\n */\nexport const DEFAULTS = {\n /** Default viewport dimensions */\n viewport: {\n width: 1280,\n height: 800,\n },\n\n /** Default diff threshold (percentage) */\n diffThreshold: 5,\n\n /** Browser pool size */\n poolSize: 3,\n\n /** Idle timeout before browser shutdown (ms) - 5 minutes */\n idleTimeoutMs: 5 * 60 * 1000,\n\n /** Delay after render before capture (ms) */\n captureDelayMs: 100,\n\n /** Font loading timeout (ms) */\n fontTimeoutMs: 3000,\n\n /** Default theme */\n theme: \"light\" as const,\n\n /** Dev server port */\n port: 6006,\n} as const;\n\nexport type Defaults = typeof DEFAULTS;\n"],"mappings":";AAMO,IAAM,QAAQ;AAAA;AAAA,EAEnB,MAAM;AAAA;AAAA,EAGN,WAAW;AAAA;AAAA,EAGX,eAAe;AAAA;AAAA,EAGf,qBAAqB;AAAA;AAAA,EAGrB,eAAe;AAAA;AAAA,EAGf,SAAS;AAAA;AAAA,EAGT,YAAY;AAAA;AAAA,EAGZ,kBAAkB;AAAA;AAAA,EAGlB,YAAY;AAAA;AAAA,EAGZ,cAAc;AAAA;AAAA,EAGd,SAAS;AAAA;AAAA,EAGT,eAAe;AAAA;AAAA,EAGf,cAAc;AAAA;AAAA,EAGd,aAAa;AAAA;AAAA,EAGb,gBAAgB;AAAA;AAAA,EAGhB,UAAU;AAAA;AAAA,EAGV,SAAS;AAAA;AAAA,EAGT,cAAc;AAAA;AAAA,EAGd,eAAe;AAAA;AAAA,EAGf,gBAAgB;AAAA;AAAA,EAGhB,eAAe;AAAA;AAAA,EAGf,oBAAoB;AAAA;AAAA,EAGpB,qBAAqB;AAAA;AAAA,EAGrB,qBAAqB;AACvB;AASO,IAAM,WAAW;AAAA;AAAA,EAEtB,UAAU;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA;AAAA,EAGA,eAAe;AAAA;AAAA,EAGf,UAAU;AAAA;AAAA,EAGV,eAAe,IAAI,KAAK;AAAA;AAAA,EAGxB,gBAAgB;AAAA;AAAA,EAGhB,eAAe;AAAA;AAAA,EAGf,OAAO;AAAA;AAAA,EAGP,MAAM;AACR;","names":[]}
@@ -0,0 +1,29 @@
1
+ // src/config.ts
2
+ import { readFileSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+ function loadConfigFile(projectRoot) {
5
+ const configPath = join(projectRoot, "ds-mcp.config.json");
6
+ if (existsSync(configPath)) {
7
+ try {
8
+ const content = readFileSync(configPath, "utf-8");
9
+ return JSON.parse(content);
10
+ } catch (e) {
11
+ throw new Error(`Failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
12
+ }
13
+ }
14
+ const pkgPath = join(projectRoot, "package.json");
15
+ if (existsSync(pkgPath)) {
16
+ try {
17
+ const content = readFileSync(pkgPath, "utf-8");
18
+ const pkg = JSON.parse(content);
19
+ if (pkg.dsMcp) return pkg.dsMcp;
20
+ } catch {
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+
26
+ export {
27
+ loadConfigFile
28
+ };
29
+ //# sourceMappingURL=chunk-FGIBLPSU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { SearchConfig } from './search-config.js';\nimport type { ServiceEndpoints } from './service.js';\nimport type { Brand } from './constants.js';\n\nexport interface DsMcpConfigFile {\n /** Override branding (name, prefix, etc.) */\n brand?: Partial<Brand>;\n /** Search configuration overrides */\n search?: SearchConfig;\n /** Service endpoint path overrides */\n endpoints?: Partial<ServiceEndpoints>;\n /** Tool configuration */\n tools?: {\n /** Tool keys to exclude from registration */\n exclude?: string[];\n };\n /** Vector search configuration */\n vectorSearch?: {\n url?: string;\n apiKey?: string;\n };\n /** Playground URL for generate_ui */\n playgroundUrl?: string;\n}\n\n/**\n * Load config from ds-mcp.config.json or package.json \"dsMcp\" field.\n * Returns null if no config file is found (pure defaults).\n */\nexport function loadConfigFile(projectRoot: string): DsMcpConfigFile | null {\n // 1. Check for ds-mcp.config.json\n const configPath = join(projectRoot, 'ds-mcp.config.json');\n if (existsSync(configPath)) {\n try {\n const content = readFileSync(configPath, 'utf-8');\n return JSON.parse(content) as DsMcpConfigFile;\n } catch (e) {\n throw new Error(`Failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n // 2. Check package.json \"dsMcp\" field\n const pkgPath = join(projectRoot, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const content = readFileSync(pkgPath, 'utf-8');\n const pkg = JSON.parse(content) as { dsMcp?: DsMcpConfigFile };\n if (pkg.dsMcp) return pkg.dsMcp;\n } catch {\n // Ignore parse errors in package.json\n }\n }\n\n return null;\n}\n"],"mappings":";AAAA,SAAS,cAAc,kBAAkB;AACzC,SAAS,YAAY;AA8Bd,SAAS,eAAe,aAA6C;AAE1E,QAAM,aAAa,KAAK,aAAa,oBAAoB;AACzD,MAAI,WAAW,UAAU,GAAG;AAC1B,QAAI;AACF,YAAM,UAAU,aAAa,YAAY,OAAO;AAChD,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,mBAAmB,UAAU,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,IAChG;AAAA,EACF;AAGA,QAAM,UAAU,KAAK,aAAa,cAAc;AAChD,MAAI,WAAW,OAAO,GAAG;AACvB,QAAI;AACF,YAAM,UAAU,aAAa,SAAS,OAAO;AAC7C,YAAM,MAAM,KAAK,MAAM,OAAO;AAC9B,UAAI,IAAI,MAAO,QAAO,IAAI;AAAA,IAC5B,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}