@schemyx/mcp 0.1.0 → 0.1.2
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 +79 -2
- package/dist/backend-client.d.ts +1 -17
- package/dist/backend-client.js +15 -91
- package/dist/backend-client.js.map +1 -1
- package/dist/client/backend-client.d.ts +17 -0
- package/dist/client/backend-client.js +97 -0
- package/dist/client/backend-client.js.map +1 -0
- package/dist/client/local-theme-source.d.ts +19 -0
- package/dist/client/local-theme-source.js +737 -0
- package/dist/client/local-theme-source.js.map +1 -0
- package/dist/client/mcp-client.d.ts +15 -0
- package/dist/client/mcp-client.js +46 -0
- package/dist/client/mcp-client.js.map +1 -0
- package/dist/codebase-scanner/backend.d.ts +12 -0
- package/dist/codebase-scanner/backend.js +814 -0
- package/dist/codebase-scanner/backend.js.map +1 -0
- package/dist/codebase-scanner/bundle.d.ts +315 -0
- package/dist/codebase-scanner/bundle.js +5195 -0
- package/dist/codebase-scanner/bundle.js.map +1 -0
- package/dist/codebase-scanner/constants.d.ts +26 -0
- package/dist/codebase-scanner/constants.js +231 -0
- package/dist/codebase-scanner/constants.js.map +1 -0
- package/dist/codebase-scanner/database.d.ts +8 -0
- package/dist/codebase-scanner/database.js +1252 -0
- package/dist/codebase-scanner/database.js.map +1 -0
- package/dist/codebase-scanner/extractors.d.ts +241 -0
- package/dist/codebase-scanner/extractors.js +3513 -0
- package/dist/codebase-scanner/extractors.js.map +1 -0
- package/dist/codebase-scanner/files.d.ts +16 -0
- package/dist/codebase-scanner/files.js +250 -0
- package/dist/codebase-scanner/files.js.map +1 -0
- package/dist/codebase-scanner/index.d.ts +217 -0
- package/dist/codebase-scanner/index.js +387 -0
- package/dist/codebase-scanner/index.js.map +1 -0
- package/dist/codebase-scanner/recipes.d.ts +74 -0
- package/dist/codebase-scanner/recipes.js +743 -0
- package/dist/codebase-scanner/recipes.js.map +1 -0
- package/dist/codebase-scanner/storage.d.ts +19 -0
- package/dist/codebase-scanner/storage.js +103 -0
- package/dist/codebase-scanner/storage.js.map +1 -0
- package/dist/codebase-scanner/types.d.ts +743 -0
- package/dist/codebase-scanner/types.js +3 -0
- package/dist/codebase-scanner/types.js.map +1 -0
- package/dist/codebase-scanner/utils.d.ts +37 -0
- package/dist/codebase-scanner/utils.js +259 -0
- package/dist/codebase-scanner/utils.js.map +1 -0
- package/dist/codebase-scanner.d.ts +1 -0
- package/dist/codebase-scanner.js +18 -0
- package/dist/codebase-scanner.js.map +1 -0
- package/dist/config.d.ts +1 -2
- package/dist/config.js +15 -37
- package/dist/config.js.map +1 -1
- package/dist/local-theme-source.d.ts +1 -0
- package/dist/local-theme-source.js +18 -0
- package/dist/local-theme-source.js.map +1 -0
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/mcp-client.d.ts +1 -0
- package/dist/mcp-client.js +18 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/prompts.d.ts +1 -7
- package/dist/prompts.js +15 -52
- package/dist/prompts.js.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +163 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/prompts.d.ts +7 -0
- package/dist/server/prompts.js +55 -0
- package/dist/server/prompts.js.map +1 -0
- package/dist/server/tool-definitions.d.ts +22 -0
- package/dist/server/tool-definitions.js +531 -0
- package/dist/server/tool-definitions.js.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +33 -0
- package/dist/server.js.map +1 -1
- package/dist/shared/config.d.ts +2 -0
- package/dist/shared/config.js +54 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/text.d.ts +14 -0
- package/dist/shared/text.js +33 -0
- package/dist/shared/text.js.map +1 -0
- package/dist/shared/types.d.ts +118 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/uri.d.ts +6 -0
- package/dist/shared/uri.js +24 -0
- package/dist/shared/uri.js.map +1 -0
- package/dist/style-recipes.d.ts +1 -0
- package/dist/style-recipes.js +18 -0
- package/dist/style-recipes.js.map +1 -0
- package/dist/text.d.ts +1 -14
- package/dist/text.js +15 -30
- package/dist/text.js.map +1 -1
- package/dist/theme/style-recipes.d.ts +26 -0
- package/dist/theme/style-recipes.js +129 -0
- package/dist/theme/style-recipes.js.map +1 -0
- package/dist/tool-definitions.d.ts +1 -11
- package/dist/tool-definitions.js +15 -127
- package/dist/tool-definitions.js.map +1 -1
- package/dist/types.d.ts +1 -106
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/uri.d.ts +1 -6
- package/dist/uri.js +15 -21
- package/dist/uri.js.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalThemeSource = void 0;
|
|
4
|
+
exports.createLocalThemeSourceIfAvailable = createLocalThemeSourceIfAvailable;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const node_url_1 = require("node:url");
|
|
9
|
+
const style_recipes_1 = require("../theme/style-recipes");
|
|
10
|
+
const localConfigId = 'local-theme';
|
|
11
|
+
const localProjectId = 'local-project';
|
|
12
|
+
const themeForgeResourceIds = {
|
|
13
|
+
bundle: 'theme-forge.bundle',
|
|
14
|
+
source: 'theme-forge.source',
|
|
15
|
+
files: 'theme-forge.files',
|
|
16
|
+
implementationGuide: 'theme-forge.implementation-guide',
|
|
17
|
+
};
|
|
18
|
+
const themeForgeToolNames = {
|
|
19
|
+
getBundle: 'theme-forge.get-bundle',
|
|
20
|
+
listFiles: 'theme-forge.list-files',
|
|
21
|
+
getFile: 'theme-forge.get-file',
|
|
22
|
+
listStyleRecipes: 'theme-forge.list-style-recipes',
|
|
23
|
+
getStyleRecipe: 'theme-forge.get-style-recipe',
|
|
24
|
+
getImplementationGuide: 'theme-forge.get-implementation-guide',
|
|
25
|
+
};
|
|
26
|
+
const defaultFileOrder = [
|
|
27
|
+
'agent-style-index.json',
|
|
28
|
+
'agent-style-contract.json',
|
|
29
|
+
'themeforge.json',
|
|
30
|
+
'theme.css',
|
|
31
|
+
'tailwind-theme.css',
|
|
32
|
+
'shadcn-globals.css',
|
|
33
|
+
'mui-theme.ts',
|
|
34
|
+
'chakra-system.ts',
|
|
35
|
+
'mantine-theme.ts',
|
|
36
|
+
'styled-theme.ts',
|
|
37
|
+
'theme.scss',
|
|
38
|
+
'tokens.json',
|
|
39
|
+
'react-native-theme.ts',
|
|
40
|
+
'tailwind.config.tokens.ts',
|
|
41
|
+
'implementation-guide.md',
|
|
42
|
+
'schemyx-theme-bundle.manifest.json',
|
|
43
|
+
];
|
|
44
|
+
const discoverableFileExtensions = new Set([
|
|
45
|
+
'.css',
|
|
46
|
+
'.scss',
|
|
47
|
+
'.json',
|
|
48
|
+
'.ts',
|
|
49
|
+
'.tsx',
|
|
50
|
+
'.js',
|
|
51
|
+
'.jsx',
|
|
52
|
+
'.mjs',
|
|
53
|
+
'.cjs',
|
|
54
|
+
'.md',
|
|
55
|
+
'.mdx',
|
|
56
|
+
]);
|
|
57
|
+
const ignoredDiscoveredFileNames = new Set([
|
|
58
|
+
'package.json',
|
|
59
|
+
'package-lock.json',
|
|
60
|
+
'pnpm-lock.yaml',
|
|
61
|
+
'yarn.lock',
|
|
62
|
+
'bun.lockb',
|
|
63
|
+
'tsconfig.json',
|
|
64
|
+
'tsconfig.tsbuildinfo',
|
|
65
|
+
'next-env.d.ts',
|
|
66
|
+
]);
|
|
67
|
+
const maxDiscoveredFileBytes = 512_000;
|
|
68
|
+
const fileDescriptors = new Map([
|
|
69
|
+
[
|
|
70
|
+
'agent-style-index.json',
|
|
71
|
+
{
|
|
72
|
+
label: 'Agent style index',
|
|
73
|
+
description: 'Compact list of Schemyx style recipe keys for targeted MCP lookup.',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
'agent-style-contract.json',
|
|
78
|
+
{
|
|
79
|
+
label: 'Agent style contract',
|
|
80
|
+
description: 'Compact machine-readable Schemyx style recipes for AI UI generation.',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
'themeforge.json',
|
|
85
|
+
{
|
|
86
|
+
label: 'Theme contract',
|
|
87
|
+
description: 'The source-of-truth Theme Forge token contract.',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
'theme.css',
|
|
92
|
+
{
|
|
93
|
+
label: 'Theme CSS',
|
|
94
|
+
description: 'Framework-agnostic CSS variables and theme primitives.',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
'tailwind-theme.css',
|
|
99
|
+
{
|
|
100
|
+
label: 'Tailwind Theme',
|
|
101
|
+
description: 'Tailwind v4 CSS theme variables mapped to the generated tokens.',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
[
|
|
105
|
+
'shadcn-globals.css',
|
|
106
|
+
{
|
|
107
|
+
label: 'shadcn globals',
|
|
108
|
+
description: 'Tailwind v4 and shadcn/ui globals bridge.',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
[
|
|
112
|
+
'mui-theme.ts',
|
|
113
|
+
{
|
|
114
|
+
label: 'MUI Theme',
|
|
115
|
+
description: 'Material UI theme object synchronized to the generated spec.',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
'chakra-system.ts',
|
|
120
|
+
{
|
|
121
|
+
label: 'Chakra System',
|
|
122
|
+
description: 'Chakra UI system config synchronized to the theme tokens.',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
'mantine-theme.ts',
|
|
127
|
+
{
|
|
128
|
+
label: 'Mantine Theme',
|
|
129
|
+
description: 'Mantine createTheme adapter for the generated visual system.',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
'styled-theme.ts',
|
|
134
|
+
{
|
|
135
|
+
label: 'Styled Theme',
|
|
136
|
+
description: 'Typed theme object for styled-components and Emotion providers.',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
'theme.scss',
|
|
141
|
+
{
|
|
142
|
+
label: 'SCSS Tokens',
|
|
143
|
+
description: 'SCSS variables and mixins for Angular component styles.',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
'tokens.json',
|
|
148
|
+
{
|
|
149
|
+
label: 'Design Tokens',
|
|
150
|
+
description: 'Portable token JSON for Style Dictionary or custom pipelines.',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
'react-native-theme.ts',
|
|
155
|
+
{
|
|
156
|
+
label: 'React Native Theme',
|
|
157
|
+
description: 'Native token object for React Native component systems.',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
[
|
|
161
|
+
'tailwind.config.tokens.ts',
|
|
162
|
+
{
|
|
163
|
+
label: 'Tailwind tokens',
|
|
164
|
+
description: 'Tailwind v3 token adapter for projects using tailwind.config.ts.',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
[
|
|
168
|
+
'implementation-guide.md',
|
|
169
|
+
{
|
|
170
|
+
label: 'Implementation guide',
|
|
171
|
+
description: 'AI-facing instructions for applying the theme without drift.',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
[
|
|
175
|
+
'schemyx-theme-bundle.manifest.json',
|
|
176
|
+
{
|
|
177
|
+
label: 'Bundle manifest',
|
|
178
|
+
description: 'Machine-readable file list for local tooling.',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
]);
|
|
182
|
+
async function createLocalThemeSourceIfAvailable(config) {
|
|
183
|
+
if (config.sourceMode === 'remote') {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const themeDir = await findLocalThemeDir(config);
|
|
187
|
+
if (!themeDir) {
|
|
188
|
+
if (config.sourceMode === 'local') {
|
|
189
|
+
throw new Error('SCHEMYX_MCP_SOURCE=local was set, but no local themeforge.json file was found.');
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return new LocalThemeSource(themeDir);
|
|
194
|
+
}
|
|
195
|
+
class LocalThemeSource {
|
|
196
|
+
themeDir;
|
|
197
|
+
bundleCache = null;
|
|
198
|
+
configCache = null;
|
|
199
|
+
constructor(themeDir) {
|
|
200
|
+
this.themeDir = themeDir;
|
|
201
|
+
}
|
|
202
|
+
async listConfigs() {
|
|
203
|
+
return [await this.getLocalConfig()];
|
|
204
|
+
}
|
|
205
|
+
async discoverConfigs() {
|
|
206
|
+
const config = await this.getLocalConfig();
|
|
207
|
+
return [
|
|
208
|
+
{
|
|
209
|
+
config,
|
|
210
|
+
manifest: await this.getManifest(config.id),
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
async getManifest(configId) {
|
|
215
|
+
this.assertLocalConfig(configId);
|
|
216
|
+
const bundle = await this.getBundle();
|
|
217
|
+
const resources = [
|
|
218
|
+
createResource(themeForgeResourceIds.bundle, 'Theme Bundle', 'The complete local theme bundle.'),
|
|
219
|
+
createResource(themeForgeResourceIds.source, bundle.sourceOfTruth, 'The Theme Forge source-of-truth theme JSON.'),
|
|
220
|
+
createResource(themeForgeResourceIds.files, 'Theme Files Manifest', 'Generated file metadata for the selected target stack.'),
|
|
221
|
+
...bundle.files.map((file) => createFileResource(this.themeDir, file)),
|
|
222
|
+
createResource(themeForgeResourceIds.implementationGuide, 'Implementation Guide', 'AI-facing instructions for applying this saved theme.', 'text/markdown', 'text'),
|
|
223
|
+
];
|
|
224
|
+
return {
|
|
225
|
+
configId: localConfigId,
|
|
226
|
+
providers: [{ id: 'theme-forge.local', name: 'Local Theme Forge Files' }],
|
|
227
|
+
resources,
|
|
228
|
+
tools: createTools(bundle),
|
|
229
|
+
prompts: [
|
|
230
|
+
{
|
|
231
|
+
id: 'theme-forge.apply-theme',
|
|
232
|
+
name: 'Apply Schemyx Theme',
|
|
233
|
+
description: 'Use the local Schemyx theme contract before building or restyling UI.',
|
|
234
|
+
arguments: [
|
|
235
|
+
{
|
|
236
|
+
name: 'task',
|
|
237
|
+
description: 'The UI or product workflow to build.',
|
|
238
|
+
required: true,
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async getLatestConfig(configId) {
|
|
246
|
+
this.assertLocalConfig(configId);
|
|
247
|
+
return this.getLocalConfig();
|
|
248
|
+
}
|
|
249
|
+
async readResource(configId, resourceId) {
|
|
250
|
+
this.assertLocalConfig(configId);
|
|
251
|
+
const bundle = await this.getBundle();
|
|
252
|
+
if (resourceId === themeForgeResourceIds.bundle) {
|
|
253
|
+
return {
|
|
254
|
+
resource: createResource(themeForgeResourceIds.bundle, 'Theme Bundle', 'The complete local theme bundle.'),
|
|
255
|
+
content: bundle,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (resourceId === themeForgeResourceIds.source) {
|
|
259
|
+
return {
|
|
260
|
+
resource: createResource(themeForgeResourceIds.source, bundle.sourceOfTruth, 'The Theme Forge source-of-truth theme JSON.'),
|
|
261
|
+
content: bundle.themeforge,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (resourceId === themeForgeResourceIds.files) {
|
|
265
|
+
return {
|
|
266
|
+
resource: createResource(themeForgeResourceIds.files, 'Theme Files Manifest', 'Generated file metadata for the selected target stack.'),
|
|
267
|
+
content: createFilesManifest(bundle),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (resourceId === themeForgeResourceIds.implementationGuide) {
|
|
271
|
+
return {
|
|
272
|
+
resource: createResource(themeForgeResourceIds.implementationGuide, 'Implementation Guide', 'AI-facing instructions for applying this saved theme.', 'text/markdown', 'text'),
|
|
273
|
+
content: getImplementationGuide(bundle),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const file = findFileByResourceId(bundle, resourceId);
|
|
277
|
+
if (!file) {
|
|
278
|
+
throw new Error(`Local Schemyx resource not found: ${resourceId}`);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
resource: createFileResource(this.themeDir, file),
|
|
282
|
+
content: file.content,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async executeTool(configId, toolName, args) {
|
|
286
|
+
this.assertLocalConfig(configId);
|
|
287
|
+
const bundle = await this.getBundle();
|
|
288
|
+
if (toolName === themeForgeToolNames.getBundle) {
|
|
289
|
+
return {
|
|
290
|
+
tool: createTool(themeForgeToolNames.getBundle, 'Get Theme Bundle', 'Return the full local Theme Forge bundle.'),
|
|
291
|
+
contentType: 'json',
|
|
292
|
+
content: bundle,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (toolName === themeForgeToolNames.listFiles) {
|
|
296
|
+
return {
|
|
297
|
+
tool: createTool(themeForgeToolNames.listFiles, 'List Theme Files', 'List local theme files.'),
|
|
298
|
+
contentType: 'json',
|
|
299
|
+
content: createFilesManifest(bundle),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (toolName === themeForgeToolNames.getImplementationGuide) {
|
|
303
|
+
return {
|
|
304
|
+
tool: createTool(themeForgeToolNames.getImplementationGuide, 'Get Implementation Guide', 'Return AI-facing instructions for applying this local theme.'),
|
|
305
|
+
contentType: 'text',
|
|
306
|
+
content: getImplementationGuide(bundle),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (toolName === themeForgeToolNames.listStyleRecipes) {
|
|
310
|
+
return {
|
|
311
|
+
tool: createListStyleRecipesTool(),
|
|
312
|
+
contentType: 'json',
|
|
313
|
+
content: (0, style_recipes_1.listStyleRecipes)(bundle, args),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (toolName === themeForgeToolNames.getStyleRecipe) {
|
|
317
|
+
const recipe = (0, style_recipes_1.getStyleRecipe)(bundle, args);
|
|
318
|
+
if (!recipe) {
|
|
319
|
+
throw new Error('Local Theme Forge style recipe not found.');
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
tool: createGetStyleRecipeTool(bundle),
|
|
323
|
+
contentType: 'json',
|
|
324
|
+
content: recipe,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (toolName !== themeForgeToolNames.getFile) {
|
|
328
|
+
throw new Error(`Local Schemyx tool not found: ${toolName}`);
|
|
329
|
+
}
|
|
330
|
+
const file = findFile(bundle, args);
|
|
331
|
+
if (!file) {
|
|
332
|
+
throw new Error('Local Theme Forge file not found.');
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
tool: createGetFileTool(bundle),
|
|
336
|
+
contentType: 'text',
|
|
337
|
+
content: file.content,
|
|
338
|
+
metadata: {
|
|
339
|
+
id: file.id,
|
|
340
|
+
fileName: file.fileName,
|
|
341
|
+
label: file.label,
|
|
342
|
+
description: file.description,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
async resolveConfigId(configId) {
|
|
347
|
+
if (!configId || configId === localConfigId) {
|
|
348
|
+
return localConfigId;
|
|
349
|
+
}
|
|
350
|
+
throw new Error(`Local Schemyx theme files are active, but configId "${configId}" is not available.`);
|
|
351
|
+
}
|
|
352
|
+
async getBundle() {
|
|
353
|
+
this.bundleCache ??= loadLocalThemeBundle(this.themeDir);
|
|
354
|
+
return this.bundleCache;
|
|
355
|
+
}
|
|
356
|
+
async getLocalConfig() {
|
|
357
|
+
this.configCache ??= this.createLocalConfig();
|
|
358
|
+
return this.configCache;
|
|
359
|
+
}
|
|
360
|
+
async createLocalConfig() {
|
|
361
|
+
const bundle = await this.getBundle();
|
|
362
|
+
return {
|
|
363
|
+
id: localConfigId,
|
|
364
|
+
projectId: localProjectId,
|
|
365
|
+
name: bundle.themeName,
|
|
366
|
+
key: 'local-theme',
|
|
367
|
+
type: 'THEME',
|
|
368
|
+
status: 'ACTIVE',
|
|
369
|
+
source: 'LOCAL_FILES',
|
|
370
|
+
description: `Local Theme Forge files from ${this.themeDir}`,
|
|
371
|
+
targetPath: path.join(this.themeDir, bundle.sourceOfTruth),
|
|
372
|
+
schema: {
|
|
373
|
+
name: 'Local Theme Forge theme config',
|
|
374
|
+
version: 'schemyx.theme-bundle.v1',
|
|
375
|
+
},
|
|
376
|
+
validation: {
|
|
377
|
+
status: 'local',
|
|
378
|
+
checks: ['themeforge-json-present', 'local-files-readable'],
|
|
379
|
+
},
|
|
380
|
+
metadata: {
|
|
381
|
+
source: 'local-files',
|
|
382
|
+
themeDir: this.themeDir,
|
|
383
|
+
},
|
|
384
|
+
createdAt: bundle.generatedAt,
|
|
385
|
+
updatedAt: bundle.generatedAt,
|
|
386
|
+
latestVersion: {
|
|
387
|
+
id: 'local-theme-version',
|
|
388
|
+
version: 1,
|
|
389
|
+
content: bundle,
|
|
390
|
+
createdAt: bundle.generatedAt,
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
assertLocalConfig(configId) {
|
|
395
|
+
if (configId !== localConfigId) {
|
|
396
|
+
throw new Error(`Local Schemyx config not found: ${configId}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
exports.LocalThemeSource = LocalThemeSource;
|
|
401
|
+
async function findLocalThemeDir(config) {
|
|
402
|
+
if (config.themeDir) {
|
|
403
|
+
const explicitDir = path.resolve(config.cwd, config.themeDir);
|
|
404
|
+
if (!(await hasThemeForgeJson(explicitDir))) {
|
|
405
|
+
throw new Error(`SCHEMYX_THEME_DIR does not contain themeforge.json: ${explicitDir}`);
|
|
406
|
+
}
|
|
407
|
+
return explicitDir;
|
|
408
|
+
}
|
|
409
|
+
for (const root of collectSearchRoots(config.cwd)) {
|
|
410
|
+
for (const candidate of [
|
|
411
|
+
path.join(root, 'schemyx-ui-theme'),
|
|
412
|
+
path.join(root, '.schemyx', 'theme'),
|
|
413
|
+
root,
|
|
414
|
+
]) {
|
|
415
|
+
if (await hasThemeForgeJson(candidate)) {
|
|
416
|
+
return candidate;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
function collectSearchRoots(cwd) {
|
|
423
|
+
const roots = [];
|
|
424
|
+
const seen = new Set();
|
|
425
|
+
let current = path.resolve(cwd);
|
|
426
|
+
const home = os.homedir();
|
|
427
|
+
for (let index = 0; index < 12; index += 1) {
|
|
428
|
+
if (!seen.has(current)) {
|
|
429
|
+
seen.add(current);
|
|
430
|
+
roots.push(current);
|
|
431
|
+
}
|
|
432
|
+
if (current === home || current === path.dirname(current)) {
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
current = path.dirname(current);
|
|
436
|
+
}
|
|
437
|
+
return roots;
|
|
438
|
+
}
|
|
439
|
+
async function hasThemeForgeJson(candidateDir) {
|
|
440
|
+
try {
|
|
441
|
+
const stat = await node_fs_1.promises.stat(path.join(candidateDir, 'themeforge.json'));
|
|
442
|
+
return stat.isFile();
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async function loadLocalThemeBundle(themeDir) {
|
|
449
|
+
const themeforgePath = path.join(themeDir, 'themeforge.json');
|
|
450
|
+
const [themeforgeText, themeforgeStat] = await Promise.all([
|
|
451
|
+
node_fs_1.promises.readFile(themeforgePath, 'utf8'),
|
|
452
|
+
node_fs_1.promises.stat(themeforgePath),
|
|
453
|
+
]);
|
|
454
|
+
const themeforge = JSON.parse(themeforgeText);
|
|
455
|
+
const manifest = await readJsonIfExists(path.join(themeDir, 'schemyx-theme-bundle.manifest.json'));
|
|
456
|
+
const files = await loadThemeFiles(themeDir, themeforgeText, manifest);
|
|
457
|
+
return {
|
|
458
|
+
schemaVersion: 'schemyx.theme-bundle.v1',
|
|
459
|
+
generatedAt: themeforgeStat.mtime.toISOString(),
|
|
460
|
+
themeName: stringValue(themeforge.themeName) ?? stringValue(manifest?.themeName) ?? 'Local Theme',
|
|
461
|
+
targetStack: stringValue(themeforge.stack) ?? stringValue(manifest?.targetStack) ?? 'local',
|
|
462
|
+
targetStackLabel: stringValue(themeforge.targetStackLabel) ?? 'Local Theme Files',
|
|
463
|
+
sourceOfTruth: 'themeforge.json',
|
|
464
|
+
themeforge,
|
|
465
|
+
files,
|
|
466
|
+
...(themeforge.intelligence ? { intelligence: themeforge.intelligence } : {}),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async function loadThemeFiles(themeDir, themeforgeText, manifest) {
|
|
470
|
+
const fileNames = new Set(defaultFileOrder);
|
|
471
|
+
for (const fileName of await discoverLocalThemeFiles(themeDir)) {
|
|
472
|
+
fileNames.add(fileName);
|
|
473
|
+
}
|
|
474
|
+
for (const file of manifest?.files ?? []) {
|
|
475
|
+
if (typeof file.path === 'string' && !path.isAbsolute(file.path) && !file.path.includes('..')) {
|
|
476
|
+
fileNames.add(file.path);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const files = [];
|
|
480
|
+
for (const fileName of fileNames) {
|
|
481
|
+
const fullPath = path.join(themeDir, fileName);
|
|
482
|
+
const content = fileName === 'themeforge.json' ? themeforgeText : await readTextIfExists(fullPath);
|
|
483
|
+
if (content === null) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const manifestEntry = manifest?.files?.find((file) => file.path === fileName);
|
|
487
|
+
const descriptor = fileDescriptors.get(fileName);
|
|
488
|
+
files.push({
|
|
489
|
+
id: toFileId(fileName),
|
|
490
|
+
fileName,
|
|
491
|
+
label: descriptor?.label ?? titleFromFileName(fileName),
|
|
492
|
+
description: stringValue(manifestEntry?.description) ??
|
|
493
|
+
descriptor?.description ??
|
|
494
|
+
`Local theme file: ${fileName}.`,
|
|
495
|
+
content,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return files;
|
|
499
|
+
}
|
|
500
|
+
async function discoverLocalThemeFiles(themeDir) {
|
|
501
|
+
const entries = await node_fs_1.promises.readdir(themeDir, { withFileTypes: true });
|
|
502
|
+
const fileNames = [];
|
|
503
|
+
for (const entry of entries) {
|
|
504
|
+
if (!entry.isFile() || !isDiscoverableThemeFile(entry.name)) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const fullPath = path.join(themeDir, entry.name);
|
|
508
|
+
const stat = await node_fs_1.promises.stat(fullPath);
|
|
509
|
+
if (stat.size > maxDiscoveredFileBytes) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
fileNames.push(entry.name);
|
|
513
|
+
}
|
|
514
|
+
return fileNames.sort((a, b) => a.localeCompare(b));
|
|
515
|
+
}
|
|
516
|
+
function isDiscoverableThemeFile(fileName) {
|
|
517
|
+
if (fileName.startsWith('.') || ignoredDiscoveredFileNames.has(fileName)) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
return discoverableFileExtensions.has(path.extname(fileName));
|
|
521
|
+
}
|
|
522
|
+
async function readTextIfExists(filePath) {
|
|
523
|
+
try {
|
|
524
|
+
const stat = await node_fs_1.promises.stat(filePath);
|
|
525
|
+
if (!stat.isFile()) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
return await node_fs_1.promises.readFile(filePath, 'utf8');
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function readJsonIfExists(filePath) {
|
|
535
|
+
const text = await readTextIfExists(filePath);
|
|
536
|
+
if (!text) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
return JSON.parse(text);
|
|
540
|
+
}
|
|
541
|
+
function createFilesManifest(bundle) {
|
|
542
|
+
return {
|
|
543
|
+
themeName: bundle.themeName,
|
|
544
|
+
targetStack: bundle.targetStack,
|
|
545
|
+
targetStackLabel: bundle.targetStackLabel,
|
|
546
|
+
sourceOfTruth: bundle.sourceOfTruth,
|
|
547
|
+
files: bundle.files.map((file) => ({
|
|
548
|
+
id: file.id,
|
|
549
|
+
fileName: file.fileName,
|
|
550
|
+
label: file.label,
|
|
551
|
+
description: file.description,
|
|
552
|
+
resourceId: createFileResourceId(file),
|
|
553
|
+
})),
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function getImplementationGuide(bundle) {
|
|
557
|
+
const guide = bundle.files.find((file) => file.fileName === 'implementation-guide.md');
|
|
558
|
+
const agentContract = bundle.files.find((file) => file.fileName === 'agent-style-contract.json');
|
|
559
|
+
if (guide) {
|
|
560
|
+
return guide.content;
|
|
561
|
+
}
|
|
562
|
+
return [
|
|
563
|
+
`# Apply ${bundle.themeName}`,
|
|
564
|
+
'',
|
|
565
|
+
`Use ${agentContract?.fileName ?? bundle.sourceOfTruth} first before writing UI code.`,
|
|
566
|
+
'If agent-style-contract.json is present, treat it as the compact element recipe source and use markdown guides only as fallback detail.',
|
|
567
|
+
`Target stack: ${bundle.targetStackLabel}.`,
|
|
568
|
+
'',
|
|
569
|
+
'Read the generated files from this MCP server instead of inventing colors, radius, spacing, shadows, or typography.',
|
|
570
|
+
].join('\n');
|
|
571
|
+
}
|
|
572
|
+
function findFile(bundle, args) {
|
|
573
|
+
const fileName = typeof args.fileName === 'string' ? args.fileName : null;
|
|
574
|
+
const fileId = typeof args.fileId === 'string' ? args.fileId : null;
|
|
575
|
+
return bundle.files.find((file) => file.fileName === fileName || file.id === fileId) ?? null;
|
|
576
|
+
}
|
|
577
|
+
function findFileByResourceId(bundle, resourceId) {
|
|
578
|
+
return bundle.files.find((file) => createFileResourceId(file) === resourceId) ?? null;
|
|
579
|
+
}
|
|
580
|
+
function createFileResourceId(file) {
|
|
581
|
+
return `theme-forge.file.${file.id}`;
|
|
582
|
+
}
|
|
583
|
+
function createResource(id, name, description, mimeType = 'application/json', kind = 'json') {
|
|
584
|
+
return {
|
|
585
|
+
id,
|
|
586
|
+
uri: `schemyx-local://configs/${localConfigId}/theme-forge/${id}`,
|
|
587
|
+
name,
|
|
588
|
+
description,
|
|
589
|
+
mimeType,
|
|
590
|
+
kind,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function createFileResource(themeDir, file) {
|
|
594
|
+
return {
|
|
595
|
+
id: createFileResourceId(file),
|
|
596
|
+
uri: (0, node_url_1.pathToFileURL)(path.join(themeDir, file.fileName)).toString(),
|
|
597
|
+
name: file.fileName,
|
|
598
|
+
description: file.description,
|
|
599
|
+
mimeType: getMimeType(file.fileName),
|
|
600
|
+
kind: 'text',
|
|
601
|
+
metadata: {
|
|
602
|
+
artifactId: file.id,
|
|
603
|
+
label: file.label,
|
|
604
|
+
source: 'local-files',
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function createTools(bundle) {
|
|
609
|
+
return [
|
|
610
|
+
createTool(themeForgeToolNames.getBundle, 'Get Theme Bundle', 'Return the full local Theme Forge bundle.'),
|
|
611
|
+
createTool(themeForgeToolNames.listFiles, 'List Theme Files', 'List local theme files.'),
|
|
612
|
+
createGetFileTool(bundle),
|
|
613
|
+
...((0, style_recipes_1.hasStyleRecipes)(bundle)
|
|
614
|
+
? [createListStyleRecipesTool(), createGetStyleRecipeTool(bundle)]
|
|
615
|
+
: []),
|
|
616
|
+
createTool(themeForgeToolNames.getImplementationGuide, 'Get Implementation Guide', 'Return AI-facing instructions for applying this local theme.'),
|
|
617
|
+
];
|
|
618
|
+
}
|
|
619
|
+
function createTool(name, title, description) {
|
|
620
|
+
return {
|
|
621
|
+
name,
|
|
622
|
+
title,
|
|
623
|
+
description,
|
|
624
|
+
inputSchema: {
|
|
625
|
+
type: 'object',
|
|
626
|
+
properties: {},
|
|
627
|
+
additionalProperties: false,
|
|
628
|
+
},
|
|
629
|
+
readOnly: true,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function createListStyleRecipesTool() {
|
|
633
|
+
return {
|
|
634
|
+
name: themeForgeToolNames.listStyleRecipes,
|
|
635
|
+
title: 'List Style Recipes',
|
|
636
|
+
description: 'List compact Schemyx style recipe keys for targeted agent lookup.',
|
|
637
|
+
inputSchema: {
|
|
638
|
+
type: 'object',
|
|
639
|
+
properties: {
|
|
640
|
+
query: {
|
|
641
|
+
type: 'string',
|
|
642
|
+
description: 'Optional search across recipe key, group, summary, and tags.',
|
|
643
|
+
},
|
|
644
|
+
group: {
|
|
645
|
+
type: 'string',
|
|
646
|
+
description: 'Optional exact recipe group filter, such as ui, nav, studio, or tokens.',
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
additionalProperties: false,
|
|
650
|
+
},
|
|
651
|
+
readOnly: true,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function createGetStyleRecipeTool(bundle) {
|
|
655
|
+
return {
|
|
656
|
+
name: themeForgeToolNames.getStyleRecipe,
|
|
657
|
+
title: 'Get Style Recipe',
|
|
658
|
+
description: 'Return one compact Schemyx style recipe by key, optionally with dependencies.',
|
|
659
|
+
inputSchema: {
|
|
660
|
+
type: 'object',
|
|
661
|
+
properties: {
|
|
662
|
+
key: {
|
|
663
|
+
type: 'string',
|
|
664
|
+
enum: (0, style_recipes_1.getStyleRecipeKeys)(bundle),
|
|
665
|
+
},
|
|
666
|
+
includeDeps: {
|
|
667
|
+
type: 'boolean',
|
|
668
|
+
description: 'Include direct token/component dependencies. Defaults to true.',
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
required: ['key'],
|
|
672
|
+
additionalProperties: false,
|
|
673
|
+
},
|
|
674
|
+
readOnly: true,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function createGetFileTool(bundle) {
|
|
678
|
+
return {
|
|
679
|
+
name: themeForgeToolNames.getFile,
|
|
680
|
+
title: 'Get Theme File',
|
|
681
|
+
description: 'Return one local Theme Forge artifact by file name or artifact id.',
|
|
682
|
+
inputSchema: {
|
|
683
|
+
type: 'object',
|
|
684
|
+
properties: {
|
|
685
|
+
fileName: {
|
|
686
|
+
type: 'string',
|
|
687
|
+
enum: bundle.files.map((file) => file.fileName),
|
|
688
|
+
},
|
|
689
|
+
fileId: {
|
|
690
|
+
type: 'string',
|
|
691
|
+
enum: bundle.files.map((file) => file.id),
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
additionalProperties: false,
|
|
695
|
+
},
|
|
696
|
+
readOnly: true,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function getMimeType(fileName) {
|
|
700
|
+
if (fileName.endsWith('.json')) {
|
|
701
|
+
return 'application/json';
|
|
702
|
+
}
|
|
703
|
+
if (fileName.endsWith('.css')) {
|
|
704
|
+
return 'text/css';
|
|
705
|
+
}
|
|
706
|
+
if (fileName.endsWith('.ts') || fileName.endsWith('.tsx')) {
|
|
707
|
+
return 'text/typescript';
|
|
708
|
+
}
|
|
709
|
+
if (fileName.endsWith('.js') ||
|
|
710
|
+
fileName.endsWith('.jsx') ||
|
|
711
|
+
fileName.endsWith('.mjs') ||
|
|
712
|
+
fileName.endsWith('.cjs')) {
|
|
713
|
+
return 'text/javascript';
|
|
714
|
+
}
|
|
715
|
+
if (fileName.endsWith('.md') || fileName.endsWith('.mdx')) {
|
|
716
|
+
return 'text/markdown';
|
|
717
|
+
}
|
|
718
|
+
return 'text/plain';
|
|
719
|
+
}
|
|
720
|
+
function toFileId(fileName) {
|
|
721
|
+
return fileName
|
|
722
|
+
.replace(/[^a-z0-9]+/gi, '-')
|
|
723
|
+
.replace(/^-+|-+$/g, '')
|
|
724
|
+
.toLowerCase();
|
|
725
|
+
}
|
|
726
|
+
function titleFromFileName(fileName) {
|
|
727
|
+
return fileName
|
|
728
|
+
.replace(/\.[^.]+$/, '')
|
|
729
|
+
.split(/[-_.]+/)
|
|
730
|
+
.filter(Boolean)
|
|
731
|
+
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
732
|
+
.join(' ');
|
|
733
|
+
}
|
|
734
|
+
function stringValue(value) {
|
|
735
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
736
|
+
}
|
|
737
|
+
//# sourceMappingURL=local-theme-source.js.map
|