@gemini-designer/mcp-server 0.1.2 → 0.1.29
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/components/catalog.d.ts.map +1 -1
- package/dist/components/catalog.js +10 -4
- package/dist/components/catalog.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +11 -6
- package/dist/config/index.js.map +1 -1
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/context/builder.js.map +1 -1
- package/dist/context/filter.d.ts.map +1 -1
- package/dist/context/filter.js +5 -1
- package/dist/context/filter.js.map +1 -1
- package/dist/context/grounding.d.ts.map +1 -1
- package/dist/context/grounding.js +7 -3
- package/dist/context/grounding.js.map +1 -1
- package/dist/context/guards.d.ts.map +1 -1
- package/dist/context/guards.js +53 -0
- package/dist/context/guards.js.map +1 -1
- package/dist/context/repo-hints.js.map +1 -1
- package/dist/context/styling-detector.d.ts +24 -0
- package/dist/context/styling-detector.d.ts.map +1 -0
- package/dist/context/styling-detector.js +337 -0
- package/dist/context/styling-detector.js.map +1 -0
- package/dist/design/principles.js.map +1 -1
- package/dist/generation/gemini-client.d.ts.map +1 -1
- package/dist/generation/gemini-client.js.map +1 -1
- package/dist/generation/litellm-client.d.ts.map +1 -1
- package/dist/generation/litellm-client.js +14 -7
- package/dist/generation/litellm-client.js.map +1 -1
- package/dist/generation/remote-client.d.ts +10 -5
- package/dist/generation/remote-client.d.ts.map +1 -1
- package/dist/generation/remote-client.js +13 -2
- package/dist/generation/remote-client.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/output/file-writer.d.ts.map +1 -1
- package/dist/output/file-writer.js +4 -4
- package/dist/output/file-writer.js.map +1 -1
- package/dist/output/formatter.d.ts.map +1 -1
- package/dist/output/formatter.js +5 -2
- package/dist/output/formatter.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/stack/detect.d.ts.map +1 -1
- package/dist/stack/detect.js +42 -9
- package/dist/stack/detect.js.map +1 -1
- package/dist/tokens/sync.d.ts.map +1 -1
- package/dist/tokens/sync.js +22 -5
- package/dist/tokens/sync.js.map +1 -1
- package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -1
- package/dist/tools/analyze-screenshot-ui.js +5 -5
- package/dist/tools/analyze-screenshot-ui.js.map +1 -1
- package/dist/tools/analyze-tokens.d.ts.map +1 -1
- package/dist/tools/analyze-tokens.js +3 -1
- package/dist/tools/analyze-tokens.js.map +1 -1
- package/dist/tools/catalog-components.d.ts.map +1 -1
- package/dist/tools/catalog-components.js +1 -4
- package/dist/tools/catalog-components.js.map +1 -1
- package/dist/tools/create-ui.d.ts +3 -0
- package/dist/tools/create-ui.d.ts.map +1 -1
- package/dist/tools/create-ui.js +203 -75
- package/dist/tools/create-ui.js.map +1 -1
- package/dist/tools/detect-ui-stack.js.map +1 -1
- package/dist/tools/generate-component-variants.d.ts.map +1 -1
- package/dist/tools/generate-component-variants.js +15 -4
- package/dist/tools/generate-component-variants.js.map +1 -1
- package/dist/tools/generate-vibes.d.ts.map +1 -1
- package/dist/tools/generate-vibes.js +7 -3
- package/dist/tools/generate-vibes.js.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/modify-ui.d.ts.map +1 -1
- package/dist/tools/modify-ui.js +7 -2
- package/dist/tools/modify-ui.js.map +1 -1
- package/dist/tools/scaffold-project.d.ts.map +1 -1
- package/dist/tools/scaffold-project.js +3 -1
- package/dist/tools/scaffold-project.js.map +1 -1
- package/dist/tools/snippet-ui.d.ts +3 -1
- package/dist/tools/snippet-ui.d.ts.map +1 -1
- package/dist/tools/snippet-ui.js +219 -88
- package/dist/tools/snippet-ui.js.map +1 -1
- package/dist/tools/sync-design-tokens.d.ts.map +1 -1
- package/dist/tools/sync-design-tokens.js +26 -11
- package/dist/tools/sync-design-tokens.js.map +1 -1
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +55 -55
- package/src/__tests__/builder.test.ts +19 -19
- package/src/__tests__/config.test.ts +63 -31
- package/src/__tests__/filter.test.ts +98 -92
- package/src/__tests__/remote-client.test.ts +179 -0
- package/src/components/catalog.ts +170 -166
- package/src/config/index.ts +185 -177
- package/src/context/builder.ts +157 -157
- package/src/context/filter.ts +110 -104
- package/src/context/grounding.ts +143 -129
- package/src/context/guards.ts +97 -38
- package/src/context/repo-hints.ts +24 -24
- package/src/context/styling-detector.ts +460 -0
- package/src/design/principles.ts +14 -14
- package/src/generation/gemini-client.ts +53 -56
- package/src/generation/litellm-client.ts +102 -86
- package/src/generation/remote-client.ts +100 -77
- package/src/index.ts +16 -16
- package/src/output/file-writer.ts +123 -123
- package/src/output/formatter.ts +139 -132
- package/src/server.ts +12 -11
- package/src/stack/detect.ts +226 -175
- package/src/tokens/sync.ts +189 -155
- package/src/tools/analyze-screenshot-ui.ts +89 -88
- package/src/tools/analyze-tokens.ts +80 -78
- package/src/tools/catalog-components.ts +68 -68
- package/src/tools/create-ui.ts +295 -142
- package/src/tools/detect-ui-stack.ts +36 -36
- package/src/tools/generate-component-variants.ts +155 -135
- package/src/tools/generate-vibes.ts +121 -117
- package/src/tools/index.ts +14 -14
- package/src/tools/modify-ui.ts +170 -165
- package/src/tools/scaffold-project.ts +68 -66
- package/src/tools/snippet-ui.ts +323 -172
- package/src/tools/sync-design-tokens.ts +217 -195
- package/src/utils/walk.ts +47 -45
- package/src/version.ts +6 -0
- package/tsconfig.json +23 -33
- package/vitest.config.ts +10 -10
- package/.prettierrc +0 -9
- package/eslint.config.js +0 -37
|
@@ -17,41 +17,59 @@ import { assertReadablePath, assertWritablePath } from '../context/guards.js';
|
|
|
17
17
|
import { sanitizeContent } from '../context/filter.js';
|
|
18
18
|
import { writeFile } from '../output/file-writer.js';
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
parseCssVars,
|
|
21
|
+
tokensToCssVars,
|
|
22
|
+
tokensToTailwindTheme,
|
|
23
|
+
tokensToTokensStudio,
|
|
24
|
+
tokensToStyleDictionary,
|
|
25
|
+
type TokenType,
|
|
26
|
+
type Token,
|
|
26
27
|
} from '../tokens/sync.js';
|
|
27
28
|
import { generateWithGemini } from '../generation/gemini-client.js';
|
|
28
29
|
|
|
30
|
+
const TOKEN_TYPES: ReadonlySet<TokenType> = new Set([
|
|
31
|
+
'color',
|
|
32
|
+
'dimension',
|
|
33
|
+
'fontFamily',
|
|
34
|
+
'fontWeight',
|
|
35
|
+
'number',
|
|
36
|
+
'string',
|
|
37
|
+
'shadow',
|
|
38
|
+
'duration',
|
|
39
|
+
'easing',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function coerceTokenType(value: unknown): TokenType {
|
|
43
|
+
if (typeof value === 'string' && TOKEN_TYPES.has(value as TokenType)) return value as TokenType;
|
|
44
|
+
return 'string';
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
const inputSchema = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
sourcePath: z.string().optional().describe('Path to a token source file (CSS vars or JSON).'),
|
|
49
|
+
sourceContent: z.string().optional().describe('Inline token content (if not using sourcePath).'),
|
|
50
|
+
sourceType: z
|
|
51
|
+
.enum(['css-vars', 'tokens-json', 'style-dictionary'])
|
|
52
|
+
.default('css-vars')
|
|
53
|
+
.describe('How to interpret the source.'),
|
|
54
|
+
targets: z
|
|
55
|
+
.array(z.enum(['css-vars', 'tailwind', 'tokens-studio', 'style-dictionary']))
|
|
56
|
+
.default(['css-vars', 'tailwind', 'tokens-studio'])
|
|
57
|
+
.describe('Target formats to generate.'),
|
|
58
|
+
aiNormalize: z
|
|
59
|
+
.boolean()
|
|
60
|
+
.default(false)
|
|
61
|
+
.describe('If true, use the model to improve token naming & grouping (uses tokens).'),
|
|
62
|
+
writeToFile: z.boolean().default(false).describe('If true, write generated outputs to files.'),
|
|
63
|
+
outputPaths: z
|
|
64
|
+
.object({
|
|
65
|
+
cssVars: z.string().optional(),
|
|
66
|
+
tailwind: z.string().optional(),
|
|
67
|
+
tokensStudio: z.string().optional(),
|
|
68
|
+
styleDictionary: z.string().optional(),
|
|
69
|
+
})
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('Where to write outputs if writeToFile=true.'),
|
|
72
|
+
backup: z.boolean().default(true).describe('Create .bak backups for overwritten files.'),
|
|
55
73
|
};
|
|
56
74
|
|
|
57
75
|
const NORMALIZE_SYSTEM_PROMPT = `You are a design systems engineer. You will normalize and improve a design token set.
|
|
@@ -67,190 +85,194 @@ Output rules:
|
|
|
67
85
|
`;
|
|
68
86
|
|
|
69
87
|
function cleanJsonArray(response: string): string {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
let out = response.trim();
|
|
89
|
+
const fenced = out.match(/```(?:json)?\n([\s\S]*?)```/);
|
|
90
|
+
if (fenced) out = fenced[1].trim();
|
|
91
|
+
return out;
|
|
74
92
|
}
|
|
75
93
|
|
|
76
94
|
export function registerSyncDesignTokens(server: McpServer, config: Config): void {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const backup = args.backup !== false;
|
|
95
|
+
server.registerTool(
|
|
96
|
+
'sync_design_tokens',
|
|
97
|
+
{
|
|
98
|
+
title: 'Sync Design Tokens',
|
|
99
|
+
description:
|
|
100
|
+
'Convert tokens between CSS vars, Tailwind theme, Tokens Studio JSON, and Style Dictionary. Optionally writes outputs to files.',
|
|
101
|
+
inputSchema,
|
|
102
|
+
},
|
|
103
|
+
async (args) => {
|
|
104
|
+
const sourcePath = args.sourcePath as string | undefined;
|
|
105
|
+
const sourceContent = args.sourceContent as string | undefined;
|
|
106
|
+
const sourceType =
|
|
107
|
+
(args.sourceType as 'css-vars' | 'tokens-json' | 'style-dictionary') || 'css-vars';
|
|
108
|
+
const targets = (args.targets as Array<
|
|
109
|
+
'css-vars' | 'tailwind' | 'tokens-studio' | 'style-dictionary'
|
|
110
|
+
>) || ['css-vars', 'tailwind', 'tokens-studio'];
|
|
111
|
+
const aiNormalize = args.aiNormalize === true;
|
|
112
|
+
const writeToFile = args.writeToFile === true;
|
|
113
|
+
const outputPaths = args.outputPaths as
|
|
114
|
+
| {
|
|
115
|
+
cssVars?: string;
|
|
116
|
+
tailwind?: string;
|
|
117
|
+
tokensStudio?: string;
|
|
118
|
+
styleDictionary?: string;
|
|
119
|
+
}
|
|
120
|
+
| undefined;
|
|
121
|
+
const backup = args.backup !== false;
|
|
105
122
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
if (!sourcePath && !sourceContent) {
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{ type: 'text' as const, text: 'Error: Provide either sourcePath or sourceContent.' },
|
|
127
|
+
],
|
|
128
|
+
isError: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
112
131
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
let raw: string;
|
|
133
|
+
let safeSourcePath: string | undefined;
|
|
134
|
+
try {
|
|
135
|
+
if (sourcePath) {
|
|
136
|
+
safeSourcePath = assertReadablePath(sourcePath, config);
|
|
137
|
+
raw = fs.readFileSync(safeSourcePath, 'utf-8');
|
|
138
|
+
} else {
|
|
139
|
+
raw = sourceContent as string;
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : 'Could not read source';
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
145
|
+
isError: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
129
148
|
|
|
130
|
-
|
|
149
|
+
raw = sanitizeContent(raw);
|
|
131
150
|
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
let tokens: Token[] = [];
|
|
152
|
+
const warnings: string[] = [];
|
|
134
153
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
try {
|
|
155
|
+
if (sourceType === 'css-vars') {
|
|
156
|
+
const parsed = parseCssVars(raw, safeSourcePath);
|
|
157
|
+
tokens = parsed.tokens;
|
|
158
|
+
warnings.push(...parsed.warnings);
|
|
159
|
+
} else {
|
|
160
|
+
// Very lightweight: accept either Tokens Studio or Style Dictionary style
|
|
161
|
+
const json = JSON.parse(raw);
|
|
162
|
+
const collected: Token[] = [];
|
|
144
163
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (typeof obj.value === 'string' && typeof obj.type === 'string') {
|
|
148
|
-
collected.push({
|
|
149
|
-
name: [...prefix].join('.'),
|
|
150
|
-
value: obj.value,
|
|
151
|
-
type: obj.type,
|
|
152
|
-
description: typeof obj.description === 'string' ? obj.description : undefined,
|
|
153
|
-
source: { file: safeSourcePath ? safeSourcePath.split('/').pop() : undefined },
|
|
154
|
-
});
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
158
|
-
walk(v, [...prefix, k]);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
164
|
+
const walk = (obj: unknown, prefix: string[]) => {
|
|
165
|
+
if (!obj || typeof obj !== 'object') return;
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
const rec = obj as Record<string, unknown>;
|
|
168
|
+
if (typeof rec.value === 'string' && typeof rec.type === 'string') {
|
|
169
|
+
collected.push({
|
|
170
|
+
name: [...prefix].join('.'),
|
|
171
|
+
value: rec.value,
|
|
172
|
+
type: coerceTokenType(rec.type),
|
|
173
|
+
description: typeof rec.description === 'string' ? rec.description : undefined,
|
|
174
|
+
source: { file: safeSourcePath ? safeSourcePath.split('/').pop() : undefined },
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
179
|
+
walk(v, [...prefix, k]);
|
|
172
180
|
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
walk(json, []);
|
|
184
|
+
tokens = collected;
|
|
185
|
+
if (tokens.length === 0)
|
|
186
|
+
warnings.push('No tokens found in JSON (expected nested {value,type} nodes).');
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const message = error instanceof Error ? error.message : 'Failed to parse tokens';
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
192
|
+
isError: true,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
173
195
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
196
|
+
// Optional AI normalization
|
|
197
|
+
if (aiNormalize && tokens.length > 0) {
|
|
198
|
+
const userPrompt = `Normalize these tokens. Remember: never change values.
|
|
177
199
|
|
|
178
200
|
TOKENS (JSON array):
|
|
179
201
|
${JSON.stringify(tokens, null, 2)}`;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
202
|
+
try {
|
|
203
|
+
const resp = await generateWithGemini(config, NORMALIZE_SYSTEM_PROMPT, userPrompt, {
|
|
204
|
+
toolName: 'sync_design_tokens',
|
|
205
|
+
});
|
|
206
|
+
const cleaned = cleanJsonArray(resp);
|
|
207
|
+
const normalized = JSON.parse(cleaned);
|
|
208
|
+
if (Array.isArray(normalized)) {
|
|
209
|
+
tokens = normalized as Token[];
|
|
210
|
+
} else {
|
|
211
|
+
warnings.push('AI normalization returned non-array output; keeping original tokens.');
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const message = error instanceof Error ? error.message : 'AI normalization failed';
|
|
215
|
+
warnings.push(`AI normalization failed: ${message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
196
218
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
219
|
+
const outputs: Record<string, string> = {};
|
|
220
|
+
for (const t of targets) {
|
|
221
|
+
if (t === 'css-vars') outputs.cssVars = tokensToCssVars(tokens);
|
|
222
|
+
if (t === 'tailwind') outputs.tailwind = tokensToTailwindTheme(tokens);
|
|
223
|
+
if (t === 'tokens-studio') outputs.tokensStudio = tokensToTokensStudio(tokens);
|
|
224
|
+
if (t === 'style-dictionary') outputs.styleDictionary = tokensToStyleDictionary(tokens);
|
|
225
|
+
}
|
|
204
226
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
227
|
+
// Optional writing
|
|
228
|
+
const written: Array<{ path: string; backupPath?: string }> = [];
|
|
229
|
+
if (writeToFile) {
|
|
230
|
+
const mapping = [
|
|
231
|
+
{ outKey: 'cssVars', pathKey: 'cssVars' },
|
|
232
|
+
{ outKey: 'tailwind', pathKey: 'tailwind' },
|
|
233
|
+
{ outKey: 'tokensStudio', pathKey: 'tokensStudio' },
|
|
234
|
+
{ outKey: 'styleDictionary', pathKey: 'styleDictionary' },
|
|
235
|
+
] as const;
|
|
214
236
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
written.push({ path: result.filePath, backupPath: result.backupPath || undefined });
|
|
234
|
-
} catch (error) {
|
|
235
|
-
const message = error instanceof Error ? error.message : 'Write failed';
|
|
236
|
-
return {
|
|
237
|
-
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
238
|
-
isError: true,
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
}
|
|
237
|
+
for (const m of mapping) {
|
|
238
|
+
const content = outputs[m.outKey];
|
|
239
|
+
const outPath = outputPaths?.[m.pathKey];
|
|
240
|
+
if (!content || !outPath) continue;
|
|
241
|
+
try {
|
|
242
|
+
const safeOut = assertWritablePath(outPath, config);
|
|
243
|
+
const result = await writeFile(safeOut, content, { format: false, backup });
|
|
244
|
+
if (!result.success) {
|
|
245
|
+
return {
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: 'text' as const,
|
|
249
|
+
text: `Error: Failed writing ${outPath}: ${result.error || 'Unknown error'}`,
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
isError: true,
|
|
253
|
+
};
|
|
242
254
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
outputs,
|
|
247
|
-
warnings,
|
|
248
|
-
written,
|
|
249
|
-
};
|
|
250
|
-
|
|
255
|
+
written.push({ path: result.filePath, backupPath: result.backupPath || undefined });
|
|
256
|
+
} catch (error) {
|
|
257
|
+
const message = error instanceof Error ? error.message : 'Write failed';
|
|
251
258
|
return {
|
|
252
|
-
|
|
259
|
+
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
260
|
+
isError: true,
|
|
253
261
|
};
|
|
262
|
+
}
|
|
254
263
|
}
|
|
255
|
-
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const response = {
|
|
267
|
+
tokens,
|
|
268
|
+
outputs,
|
|
269
|
+
warnings,
|
|
270
|
+
written,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: 'text' as const, text: JSON.stringify(response, null, 2) }],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
);
|
|
256
278
|
}
|
package/src/utils/walk.ts
CHANGED
|
@@ -10,66 +10,68 @@ import * as fs from 'node:fs';
|
|
|
10
10
|
import * as path from 'node:path';
|
|
11
11
|
|
|
12
12
|
export interface WalkOptions {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
includeExtensions?: string[];
|
|
14
|
+
excludeDirNames?: string[];
|
|
15
|
+
maxFiles?: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const DEFAULT_EXCLUDE_DIRS = [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
'node_modules',
|
|
20
|
+
'.git',
|
|
21
|
+
'.next',
|
|
22
|
+
'.nuxt',
|
|
23
|
+
'dist',
|
|
24
|
+
'build',
|
|
25
|
+
'.turbo',
|
|
26
|
+
'.cache',
|
|
27
|
+
'coverage',
|
|
28
28
|
];
|
|
29
29
|
|
|
30
30
|
export function walkFiles(rootDir: string, options: WalkOptions = {}): string[] {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const includeExtensions = (options.includeExtensions || []).map((e) => e.toLowerCase());
|
|
32
|
+
const exclude = new Set(
|
|
33
|
+
(options.excludeDirNames || DEFAULT_EXCLUDE_DIRS).map((d) => d.toLowerCase())
|
|
34
|
+
);
|
|
35
|
+
const maxFiles = typeof options.maxFiles === 'number' ? options.maxFiles : 10_000;
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const results: string[] = [];
|
|
38
|
+
const queue: string[] = [rootDir];
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
while (queue.length > 0) {
|
|
41
|
+
const current = queue.pop()!;
|
|
42
|
+
let entries: fs.Dirent[];
|
|
43
|
+
try {
|
|
44
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
45
|
+
} catch {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
for (const ent of entries) {
|
|
50
|
+
if (results.length >= maxFiles) return results;
|
|
51
|
+
const abs = path.join(current, ent.name);
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (ent.isDirectory()) {
|
|
54
|
+
if (exclude.has(ent.name.toLowerCase())) continue;
|
|
55
|
+
queue.push(abs);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
if (!ent.isFile()) continue;
|
|
60
|
+
if (includeExtensions.length === 0) {
|
|
61
|
+
results.push(abs);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
65
|
+
const ext = path.extname(ent.name).toLowerCase();
|
|
66
|
+
if (includeExtensions.includes(ext)) {
|
|
67
|
+
results.push(abs);
|
|
68
|
+
}
|
|
68
69
|
}
|
|
70
|
+
}
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
return results;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export function toPosixPath(p: string): string {
|
|
74
|
-
|
|
76
|
+
return p.replace(/\\/g, '/');
|
|
75
77
|
}
|
package/src/version.ts
ADDED
package/tsconfig.json
CHANGED
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
"include": [
|
|
27
|
-
"src/**/*"
|
|
28
|
-
],
|
|
29
|
-
"exclude": [
|
|
30
|
-
"node_modules",
|
|
31
|
-
"dist",
|
|
32
|
-
"**/*.test.ts"
|
|
33
|
-
]
|
|
34
|
-
}
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"baseUrl": ".",
|
|
18
|
+
"paths": {
|
|
19
|
+
"@/*": ["src/*"]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"include": ["src/**/*"],
|
|
23
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
24
|
+
}
|