draply-dev 1.3.4 → 1.3.6
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/bin/cli.js +73 -24
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -262,47 +262,96 @@ const server = http.createServer((req, res) => {
|
|
|
262
262
|
for (const [filePath, { found, items }] of fileChanges) {
|
|
263
263
|
console.log(` \x1b[36m🤖\x1b[0m AI applying ${items.length} changes → ${found.relativePath}`);
|
|
264
264
|
|
|
265
|
-
//
|
|
265
|
+
// Find the relevant context snippet (~50 lines around the class usage)
|
|
266
|
+
const lines = found.content.split('\n');
|
|
267
|
+
let contextStart = 0, contextEnd = lines.length - 1;
|
|
268
|
+
for (const item of items) {
|
|
269
|
+
for (let i = 0; i < lines.length; i++) {
|
|
270
|
+
if (lines[i].includes(item.className)) {
|
|
271
|
+
contextStart = Math.min(contextStart || i, Math.max(0, i - 25));
|
|
272
|
+
contextEnd = Math.min(lines.length - 1, i + 25);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const snippet = lines.slice(contextStart, contextEnd + 1).join('\n');
|
|
277
|
+
|
|
278
|
+
// Build changes description
|
|
266
279
|
let changesBlock = '';
|
|
267
280
|
items.forEach(item => {
|
|
268
281
|
const propsStr = Object.entries(item.props).map(([k, v]) => ` ${k}: ${v}`).join('\n');
|
|
269
282
|
changesBlock += `\nElement .${item.className}:\n${propsStr}\n`;
|
|
270
283
|
});
|
|
271
284
|
|
|
272
|
-
const prompt = `You are a code editor.
|
|
285
|
+
const prompt = `You are a code editor. I need to apply CSS style changes to a file.
|
|
273
286
|
|
|
274
|
-
FILE: ${found.relativePath}
|
|
287
|
+
FILE: ${found.relativePath} (lines ${contextStart + 1}-${contextEnd + 1})
|
|
275
288
|
\`\`\`
|
|
276
|
-
${
|
|
289
|
+
${snippet}
|
|
277
290
|
\`\`\`
|
|
278
291
|
|
|
279
|
-
CHANGES
|
|
292
|
+
CHANGES:
|
|
280
293
|
${changesBlock}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
-
|
|
289
|
-
-
|
|
290
|
-
|
|
291
|
-
|
|
294
|
+
IMPORTANT: Return ONLY a JSON array of search-and-replace pairs. Each pair is:
|
|
295
|
+
{"search": "exact existing code to find", "replace": "modified code to replace it with"}
|
|
296
|
+
|
|
297
|
+
Rules:
|
|
298
|
+
- "search" must be an EXACT substring from the file above (copy it precisely)
|
|
299
|
+
- "search" should be minimal — just the line(s) that need changing
|
|
300
|
+
- For CSS: modify the class rule properties. If rule doesn't exist, set search to the closing bracket of the nearest rule and add the new rule after it.
|
|
301
|
+
- For JSX with className: add/update the style prop on that element
|
|
302
|
+
- For JSX with imported CSS: add CSS rules (search for the import line, replace with import + note)
|
|
303
|
+
- Keep changes minimal
|
|
304
|
+
|
|
305
|
+
Example response:
|
|
306
|
+
[{"search": "className=\\"hero\\"", "replace": "className=\\"hero\\" style={{color: 'red', fontSize: '20px'}}"}]
|
|
307
|
+
|
|
308
|
+
Return ONLY valid JSON array. No markdown, no explanation.`;
|
|
292
309
|
|
|
293
310
|
try {
|
|
294
311
|
const result = await callAI(cfg.apiKey, prompt, cfg.provider || 'groq');
|
|
295
|
-
let
|
|
296
|
-
if
|
|
297
|
-
|
|
312
|
+
let jsonStr = result.trim();
|
|
313
|
+
// Clean markdown fences if present
|
|
314
|
+
if (jsonStr.startsWith('```')) {
|
|
315
|
+
jsonStr = jsonStr.replace(/^```[a-z]*\n?/, '').replace(/\n?```$/, '');
|
|
298
316
|
}
|
|
299
|
-
|
|
300
|
-
|
|
317
|
+
|
|
318
|
+
let patches;
|
|
319
|
+
try {
|
|
320
|
+
patches = JSON.parse(jsonStr);
|
|
321
|
+
} catch {
|
|
322
|
+
console.log(` \x1b[31m✗\x1b[0m AI returned invalid JSON`);
|
|
323
|
+
console.log(` Response: ${jsonStr.substring(0, 200)}`);
|
|
324
|
+
items.forEach(item => results.push({ selector: item.selector, ok: false, reason: 'AI returned invalid JSON' }));
|
|
301
325
|
continue;
|
|
302
326
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
327
|
+
|
|
328
|
+
if (!Array.isArray(patches) || patches.length === 0) {
|
|
329
|
+
items.forEach(item => results.push({ selector: item.selector, ok: false, reason: 'AI returned no changes' }));
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Apply patches
|
|
334
|
+
let content = found.content;
|
|
335
|
+
let applied = 0;
|
|
336
|
+
for (const patch of patches) {
|
|
337
|
+
if (!patch.search || patch.replace === undefined) continue;
|
|
338
|
+
if (content.includes(patch.search)) {
|
|
339
|
+
content = content.replace(patch.search, patch.replace);
|
|
340
|
+
applied++;
|
|
341
|
+
console.log(` \x1b[32m ✓\x1b[0m Patch applied`);
|
|
342
|
+
} else {
|
|
343
|
+
console.log(` \x1b[33m âš \x1b[0m Search string not found: "${patch.search.substring(0, 60)}..."`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (applied > 0) {
|
|
348
|
+
fs.writeFileSync(found.file, content, 'utf8');
|
|
349
|
+
console.log(` \x1b[32m✓\x1b[0m Applied ${applied} patches to ${found.relativePath}`);
|
|
350
|
+
items.forEach(item => results.push({ selector: item.selector, ok: true, file: found.relativePath }));
|
|
351
|
+
} else {
|
|
352
|
+
items.forEach(item => results.push({ selector: item.selector, ok: false, reason: 'No patches matched' }));
|
|
353
|
+
}
|
|
354
|
+
|
|
306
355
|
} catch (aiErr) {
|
|
307
356
|
console.log(` \x1b[31m✗\x1b[0m AI error: ${aiErr.message}`);
|
|
308
357
|
items.forEach(item => results.push({ selector: item.selector, ok: false, reason: aiErr.message }));
|