@santi020k/lumen-mcp 0.1.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/LICENSE +21 -0
- package/README.md +216 -0
- package/bin/lumen-mcp-local.mjs +56 -0
- package/bin/lumen-mcp.mjs +8 -0
- package/data/lumen-data.json +21480 -0
- package/dist/data.d.ts +99 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +11 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +215 -0
- package/dist/tools.d.ts +136 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +346 -0
- package/package.json +82 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { loadLumenData } from './data.js';
|
|
2
|
+
const normalize = (value) => value.replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2').replaceAll(/[\s_]+/g, '-').toLowerCase();
|
|
3
|
+
const tokenize = (value) => normalize(value).split(/[^a-z0-9]+/).filter(Boolean);
|
|
4
|
+
const availableFrameworks = (component) => Object.entries(component.frameworks)
|
|
5
|
+
.filter(([, available]) => available)
|
|
6
|
+
.map(([framework]) => framework);
|
|
7
|
+
const componentSummary = (component) => ({
|
|
8
|
+
category: component.category,
|
|
9
|
+
collections: component.collections,
|
|
10
|
+
description: component.description,
|
|
11
|
+
frameworks: availableFrameworks(component),
|
|
12
|
+
kebab: component.kebab,
|
|
13
|
+
name: component.name,
|
|
14
|
+
recipes: component.recipes.map((recipe) => recipe.name)
|
|
15
|
+
});
|
|
16
|
+
export const resolveComponent = (name, data = loadLumenData()) => {
|
|
17
|
+
const target = normalize(name);
|
|
18
|
+
return data.components.find((component) => component.name === name) ??
|
|
19
|
+
data.components.find((component) => component.kebab === target) ??
|
|
20
|
+
data.components.find((component) => normalize(component.name) === target);
|
|
21
|
+
};
|
|
22
|
+
export const resolveRecipe = (name, data = loadLumenData()) => {
|
|
23
|
+
const target = normalize(name);
|
|
24
|
+
return data.recipes.find((recipe) => normalize(recipe.name) === target);
|
|
25
|
+
};
|
|
26
|
+
const frameworkSearchText = (component) => Object.values(component.frameworkDetails)
|
|
27
|
+
.flatMap((framework) => [
|
|
28
|
+
framework.packageName,
|
|
29
|
+
framework.propsExtends ?? '',
|
|
30
|
+
framework.props.map((prop) => `${prop.name} ${prop.type}`).join(' '),
|
|
31
|
+
framework.attributes?.join(' ') ?? '',
|
|
32
|
+
framework.tagName ?? ''
|
|
33
|
+
])
|
|
34
|
+
.join(' ');
|
|
35
|
+
const componentSearchText = (component) => [
|
|
36
|
+
component.name,
|
|
37
|
+
component.kebab,
|
|
38
|
+
'accessible accessibility',
|
|
39
|
+
component.description,
|
|
40
|
+
component.category,
|
|
41
|
+
component.collections.join(' '),
|
|
42
|
+
component.keywords.join(' '),
|
|
43
|
+
component.dependencies.join(' '),
|
|
44
|
+
component.guidance?.when ?? '',
|
|
45
|
+
component.guidance?.distinction ?? '',
|
|
46
|
+
component.apiReference
|
|
47
|
+
.map((row) => `${row.attribute} ${row.values} ${row.description}`)
|
|
48
|
+
.join(' '),
|
|
49
|
+
frameworkSearchText(component)
|
|
50
|
+
].join(' ');
|
|
51
|
+
const scoreCandidate = (query, terms, searchableText, primaryName = '') => {
|
|
52
|
+
const normalizedText = normalize(searchableText);
|
|
53
|
+
const matchedTerms = terms.filter((term) => normalizedText.includes(term));
|
|
54
|
+
if (matchedTerms.length !== terms.length)
|
|
55
|
+
return { matchedTerms, score: 0 };
|
|
56
|
+
const normalizedQuery = normalize(query);
|
|
57
|
+
const normalizedName = normalize(primaryName);
|
|
58
|
+
let score = terms.reduce((total, term) => total + (normalizedName.includes(term) ? 20 : 5), 0);
|
|
59
|
+
if (normalizedText.includes(normalizedQuery))
|
|
60
|
+
score += 100;
|
|
61
|
+
if (normalizedName === normalizedQuery)
|
|
62
|
+
score += 500;
|
|
63
|
+
return { matchedTerms, score };
|
|
64
|
+
};
|
|
65
|
+
const appendSearchResult = (results, candidate, query, terms, searchableText) => {
|
|
66
|
+
const match = scoreCandidate(query, terms, searchableText, candidate.name);
|
|
67
|
+
if (match.score > 0)
|
|
68
|
+
results.push({ ...candidate, ...match });
|
|
69
|
+
};
|
|
70
|
+
const collectSearchResults = (query, terms, data) => {
|
|
71
|
+
const results = [];
|
|
72
|
+
for (const component of data.components) {
|
|
73
|
+
appendSearchResult(results, {
|
|
74
|
+
category: component.category,
|
|
75
|
+
description: component.description,
|
|
76
|
+
frameworks: availableFrameworks(component),
|
|
77
|
+
kind: 'component',
|
|
78
|
+
name: component.name
|
|
79
|
+
}, query, terms, componentSearchText(component));
|
|
80
|
+
}
|
|
81
|
+
for (const recipe of data.recipes) {
|
|
82
|
+
appendSearchResult(results, {
|
|
83
|
+
category: recipe.categories.join(', '),
|
|
84
|
+
description: recipe.description,
|
|
85
|
+
kind: 'recipe',
|
|
86
|
+
name: recipe.name
|
|
87
|
+
}, query, terms, `${recipe.name} ${recipe.description} ${recipe.categories.join(' ')} ${(recipe.components ?? []).join(' ')}`);
|
|
88
|
+
}
|
|
89
|
+
for (const token of data.tokens.semantic) {
|
|
90
|
+
appendSearchResult(results, {
|
|
91
|
+
description: 'Lumen semantic design token.',
|
|
92
|
+
kind: 'token',
|
|
93
|
+
name: token
|
|
94
|
+
}, query, terms, token);
|
|
95
|
+
}
|
|
96
|
+
for (const line of data.rules.split('\n').filter((rule) => rule.trim())) {
|
|
97
|
+
appendSearchResult(results, {
|
|
98
|
+
description: line.trim(),
|
|
99
|
+
kind: 'rule',
|
|
100
|
+
name: line.trim()
|
|
101
|
+
}, query, terms, line);
|
|
102
|
+
}
|
|
103
|
+
return results;
|
|
104
|
+
};
|
|
105
|
+
export const listComponents = (args = {}, data = loadLumenData()) => {
|
|
106
|
+
const query = args.query?.trim();
|
|
107
|
+
const queryTerms = query ? tokenize(query) : [];
|
|
108
|
+
const matches = data.components.filter((component) => {
|
|
109
|
+
if (args.framework && !component.frameworks[args.framework])
|
|
110
|
+
return false;
|
|
111
|
+
if (args.recipe && !component.recipes.some((recipe) => recipe.name === args.recipe))
|
|
112
|
+
return false;
|
|
113
|
+
if (query &&
|
|
114
|
+
scoreCandidate(query, queryTerms, componentSearchText(component), component.name).score === 0)
|
|
115
|
+
return false;
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
const summaries = matches.map(componentSummary);
|
|
119
|
+
if (summaries.length === 0) {
|
|
120
|
+
return {
|
|
121
|
+
data: { components: [], count: 0 },
|
|
122
|
+
text: 'No components matched the given filters.'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const lines = summaries.map((component) => {
|
|
126
|
+
const recipeNote = component.recipes.length > 0
|
|
127
|
+
? ` [recipes: ${component.recipes.join(', ')}]`
|
|
128
|
+
: '';
|
|
129
|
+
return `${component.name} (${component.kebab}) — ${component.description} — ${component.frameworks.join(', ')}${recipeNote}`;
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
data: { components: summaries, count: summaries.length },
|
|
133
|
+
text: `${summaries.length} component(s):\n\n${lines.join('\n')}`
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
const frameworkWithoutSource = (framework) => {
|
|
137
|
+
const { source: _source, ...usage } = framework;
|
|
138
|
+
return usage;
|
|
139
|
+
};
|
|
140
|
+
const getFrameworkDetail = (details, framework) => {
|
|
141
|
+
if (framework === 'astro')
|
|
142
|
+
return details.astro;
|
|
143
|
+
if (framework === 'elements')
|
|
144
|
+
return details.elements;
|
|
145
|
+
return details.react;
|
|
146
|
+
};
|
|
147
|
+
const getRecipeInstall = (install, framework) => {
|
|
148
|
+
if (framework === 'astro')
|
|
149
|
+
return install.astro;
|
|
150
|
+
if (framework === 'elements')
|
|
151
|
+
return install.elements;
|
|
152
|
+
return install.react;
|
|
153
|
+
};
|
|
154
|
+
const componentDetail = (component, framework, detail) => {
|
|
155
|
+
const selectedFramework = getFrameworkDetail(component.frameworkDetails, framework);
|
|
156
|
+
const summary = componentSummary(component);
|
|
157
|
+
if (detail === 'summary') {
|
|
158
|
+
return {
|
|
159
|
+
...summary,
|
|
160
|
+
dependencies: component.dependencies,
|
|
161
|
+
framework: {
|
|
162
|
+
available: selectedFramework.available,
|
|
163
|
+
packageName: selectedFramework.packageName,
|
|
164
|
+
tagName: selectedFramework.tagName
|
|
165
|
+
},
|
|
166
|
+
guidance: component.guidance
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const usage = {
|
|
170
|
+
...summary,
|
|
171
|
+
apiReference: component.apiReference,
|
|
172
|
+
dependencies: component.dependencies,
|
|
173
|
+
files: component.files,
|
|
174
|
+
framework: frameworkWithoutSource(selectedFramework),
|
|
175
|
+
guidance: component.guidance,
|
|
176
|
+
keyboardInteractions: component.keyboardInteractions,
|
|
177
|
+
runtimeEvents: component.runtimeEvents
|
|
178
|
+
};
|
|
179
|
+
return detail === 'source'
|
|
180
|
+
? { ...usage, framework: selectedFramework }
|
|
181
|
+
: usage;
|
|
182
|
+
};
|
|
183
|
+
const formatProps = (framework) => {
|
|
184
|
+
if (framework.props.length === 0) {
|
|
185
|
+
return framework.propsExtends
|
|
186
|
+
? `Props extend ${framework.propsExtends}; no additional local props were detected.`
|
|
187
|
+
: 'No typed props detected.';
|
|
188
|
+
}
|
|
189
|
+
const lines = framework.props.map((prop) => `- ${prop.name}${prop.optional ? '?' : ''}: ${prop.type}`);
|
|
190
|
+
const header = framework.propsExtends
|
|
191
|
+
? `Props (extends ${framework.propsExtends}):`
|
|
192
|
+
: 'Props:';
|
|
193
|
+
return `${header}\n${lines.join('\n')}`;
|
|
194
|
+
};
|
|
195
|
+
const formatComponentUsage = (component, frameworkName, detail) => {
|
|
196
|
+
const framework = getFrameworkDetail(component.frameworkDetails, frameworkName);
|
|
197
|
+
const sections = [
|
|
198
|
+
`# ${component.name}`,
|
|
199
|
+
component.description,
|
|
200
|
+
`Category: ${component.category}`,
|
|
201
|
+
`Framework: ${frameworkName}`,
|
|
202
|
+
`Package: ${framework.packageName}`,
|
|
203
|
+
`Import: ${framework.importStatement}`,
|
|
204
|
+
`Styles: ${framework.styleImport}`,
|
|
205
|
+
framework.tagName ? `Custom element: <${framework.tagName}>` : '',
|
|
206
|
+
component.recipes.length > 0
|
|
207
|
+
? `Recipes: ${component.recipes.map((recipe) => recipe.name).join(', ')}`
|
|
208
|
+
: 'Recipes: none'
|
|
209
|
+
].filter(Boolean);
|
|
210
|
+
if (detail === 'summary')
|
|
211
|
+
return sections.join('\n');
|
|
212
|
+
sections.push('', formatProps(framework), '', '## Example', `\`\`\`${framework.language}`);
|
|
213
|
+
sections.push(framework.example, '```');
|
|
214
|
+
if (component.guidance) {
|
|
215
|
+
sections.push('', '## Guidance', component.guidance.when);
|
|
216
|
+
if (component.guidance.distinction)
|
|
217
|
+
sections.push(component.guidance.distinction);
|
|
218
|
+
}
|
|
219
|
+
if (component.keyboardInteractions.length > 0) {
|
|
220
|
+
sections.push('', '## Keyboard interactions');
|
|
221
|
+
sections.push(...component.keyboardInteractions.map((row) => `- ${row.key}: ${row.action}`));
|
|
222
|
+
}
|
|
223
|
+
if (component.runtimeEvents.length > 0) {
|
|
224
|
+
sections.push('', '## Runtime events');
|
|
225
|
+
sections.push(...component.runtimeEvents.map((event) => `- ${event.name}: ${event.when}`));
|
|
226
|
+
}
|
|
227
|
+
if (detail === 'source') {
|
|
228
|
+
sections.push('', `## ${frameworkName} reference source`);
|
|
229
|
+
sections.push(`\`\`\`${framework.language}`, framework.source.trimEnd(), '```');
|
|
230
|
+
}
|
|
231
|
+
return sections.join('\n');
|
|
232
|
+
};
|
|
233
|
+
export const getComponent = (args, data = loadLumenData()) => {
|
|
234
|
+
const component = resolveComponent(args.name, data);
|
|
235
|
+
if (!component) {
|
|
236
|
+
const message = `Unknown component "${args.name}". Use lumen_list_components to see available names.`;
|
|
237
|
+
return {
|
|
238
|
+
data: { found: false, message },
|
|
239
|
+
isError: true,
|
|
240
|
+
text: message
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const framework = args.framework ?? 'astro';
|
|
244
|
+
const detail = args.detail ?? (args.includeSource ? 'source' : 'usage');
|
|
245
|
+
const selectedFramework = getFrameworkDetail(component.frameworkDetails, framework);
|
|
246
|
+
if (!selectedFramework.available) {
|
|
247
|
+
const message = `${component.name} is not available for ${framework}.`;
|
|
248
|
+
return {
|
|
249
|
+
data: { found: false, message },
|
|
250
|
+
isError: true,
|
|
251
|
+
text: message
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
data: {
|
|
256
|
+
component: componentDetail(component, framework, detail),
|
|
257
|
+
found: true
|
|
258
|
+
},
|
|
259
|
+
text: formatComponentUsage(component, framework, detail)
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
export const getRecipe = (args, data = loadLumenData()) => {
|
|
263
|
+
const recipe = resolveRecipe(args.name, data);
|
|
264
|
+
if (!recipe) {
|
|
265
|
+
const message = `Unknown recipe "${args.name}". Use lumen_search or lumen_list_components to discover recipes.`;
|
|
266
|
+
return {
|
|
267
|
+
data: { found: false, message },
|
|
268
|
+
isError: true,
|
|
269
|
+
text: message
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const framework = args.framework ?? 'astro';
|
|
273
|
+
const componentLines = (recipe.components ?? []).map((name) => {
|
|
274
|
+
const component = resolveComponent(name, data);
|
|
275
|
+
return component ? `- ${component.name}: ${component.description}` : `- ${name}`;
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
data: { found: true, recipe },
|
|
279
|
+
text: [
|
|
280
|
+
`# ${recipe.name}`,
|
|
281
|
+
recipe.description,
|
|
282
|
+
`Type: ${recipe.type}`,
|
|
283
|
+
`Categories: ${recipe.categories.join(', ') || 'all'}`,
|
|
284
|
+
`Install for ${framework}: ${getRecipeInstall(recipe.install, framework)}`,
|
|
285
|
+
'',
|
|
286
|
+
'## Components',
|
|
287
|
+
...componentLines
|
|
288
|
+
].join('\n')
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
export const search = (args, data = loadLumenData()) => {
|
|
292
|
+
const query = args.query.trim();
|
|
293
|
+
if (!query) {
|
|
294
|
+
return {
|
|
295
|
+
data: { query, results: [], total: 0 },
|
|
296
|
+
isError: true,
|
|
297
|
+
text: 'Provide a non-empty query.'
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const limit = Math.max(1, Math.min(args.limit ?? 20, 100));
|
|
301
|
+
const results = collectSearchResults(query, tokenize(query), data);
|
|
302
|
+
results.sort((left, right) => right.score - left.score || left.name.localeCompare(right.name));
|
|
303
|
+
const shown = results.slice(0, limit);
|
|
304
|
+
if (results.length === 0) {
|
|
305
|
+
return {
|
|
306
|
+
data: { query, results: [], total: 0 },
|
|
307
|
+
text: `No matches for "${args.query}".`
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
data: { query, results: shown, total: results.length },
|
|
312
|
+
text: `${results.length} match(es) for "${args.query}"${results.length > shown.length ? ` (showing ${shown.length})` : ''}:\n\n${shown.map((result) => `${result.kind}: ${result.name} — ${result.description} [matched: ${result.matchedTerms.join(', ')}]`).join('\n')}`
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
export const getTokens = (data = loadLumenData()) => {
|
|
316
|
+
const { tokens } = data;
|
|
317
|
+
const colorLines = Object.entries(tokens.colors).map(([name, value]) => `- ${name}: hsl(${value})`);
|
|
318
|
+
const glassLines = Object.entries(tokens.glass).map(([name, value]) => `- ${name}: ${value}`);
|
|
319
|
+
const text = [
|
|
320
|
+
'# Lumen design tokens',
|
|
321
|
+
`Theme attribute: ${tokens.themeAttribute} (values: light, dark)`,
|
|
322
|
+
'',
|
|
323
|
+
'## Semantic token names (use as `text-ink`, `bg-surface`, `border-line`, etc.)',
|
|
324
|
+
tokens.semantic.map((token) => `- ${token}`).join('\n'),
|
|
325
|
+
'',
|
|
326
|
+
'## Base color values',
|
|
327
|
+
colorLines.join('\n'),
|
|
328
|
+
'',
|
|
329
|
+
'## Glass surface tokens',
|
|
330
|
+
glassLines.join('\n')
|
|
331
|
+
].join('\n');
|
|
332
|
+
return { data: { tokens }, text };
|
|
333
|
+
};
|
|
334
|
+
export const getRules = (data = loadLumenData()) => {
|
|
335
|
+
if (!data.rules) {
|
|
336
|
+
return {
|
|
337
|
+
data: { rules: '' },
|
|
338
|
+
isError: true,
|
|
339
|
+
text: 'No agent rules (llms.txt) were bundled.'
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
data: { rules: data.rules.trimEnd() },
|
|
344
|
+
text: data.rules.trimEnd()
|
|
345
|
+
};
|
|
346
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@santi020k/lumen-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Model Context Protocol server for the Lumen multi-framework primitive UI system. Lets AI agents list components, read real component source, and follow Lumen tokens and rules.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./tools": {
|
|
19
|
+
"types": "./dist/tools.d.ts",
|
|
20
|
+
"import": "./dist/tools.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"dist",
|
|
27
|
+
"data",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"bin": {
|
|
32
|
+
"lumen-mcp": "./bin/lumen-mcp.mjs"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36
|
+
"zod": "^4.4.3"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^26.1.1",
|
|
40
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
41
|
+
"typescript": "^6.0.3",
|
|
42
|
+
"vitest": "^4.1.10"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22.18.0"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/santi020k/lumen.git",
|
|
50
|
+
"directory": "packages/mcp"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://lumen.santi020k.com",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/santi020k/lumen/issues"
|
|
55
|
+
},
|
|
56
|
+
"keywords": [
|
|
57
|
+
"lumen",
|
|
58
|
+
"mcp",
|
|
59
|
+
"model-context-protocol",
|
|
60
|
+
"ai",
|
|
61
|
+
"ui",
|
|
62
|
+
"design-system",
|
|
63
|
+
"astro"
|
|
64
|
+
],
|
|
65
|
+
"author": {
|
|
66
|
+
"name": "Santiago Molina",
|
|
67
|
+
"url": "https://santi020k.com"
|
|
68
|
+
},
|
|
69
|
+
"license": "MIT",
|
|
70
|
+
"scripts": {
|
|
71
|
+
"generate": "node scripts/generate-data.mjs",
|
|
72
|
+
"build": "pnpm run generate && tsc -p tsconfig.build.json",
|
|
73
|
+
"dev": "pnpm run generate && tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
74
|
+
"local": "node bin/lumen-mcp-local.mjs",
|
|
75
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
76
|
+
"test": "vitest run --config ../../vitest.config.ts --project mcp",
|
|
77
|
+
"test:coverage": "vitest run --config ../../vitest.config.ts --project mcp --coverage",
|
|
78
|
+
"lint": "eslint . --no-warn-ignored",
|
|
79
|
+
"lint:fix": "eslint . --fix --no-warn-ignored",
|
|
80
|
+
"clean": "rm -rf dist coverage"
|
|
81
|
+
}
|
|
82
|
+
}
|