openmagic 0.32.0 → 0.33.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/cli.js +154 -2
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +56 -54
- package/dist/toolbar/index.global.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -82,9 +82,12 @@ import {
|
|
|
82
82
|
readdirSync,
|
|
83
83
|
copyFileSync,
|
|
84
84
|
mkdirSync as mkdirSync2,
|
|
85
|
-
realpathSync
|
|
85
|
+
realpathSync,
|
|
86
|
+
rmSync
|
|
86
87
|
} from "fs";
|
|
87
88
|
import { join as join2, resolve, relative, dirname, extname } from "path";
|
|
89
|
+
import { tmpdir } from "os";
|
|
90
|
+
import { createHash } from "crypto";
|
|
88
91
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
89
92
|
"node_modules",
|
|
90
93
|
".git",
|
|
@@ -145,6 +148,25 @@ function readFileSafe(filePath, roots) {
|
|
|
145
148
|
return { error: `Failed to read file: ${e.message}` };
|
|
146
149
|
}
|
|
147
150
|
}
|
|
151
|
+
var BACKUP_DIR = join2(tmpdir(), "openmagic-backups");
|
|
152
|
+
var backupMap = /* @__PURE__ */ new Map();
|
|
153
|
+
function getBackupPath(filePath) {
|
|
154
|
+
const hash = createHash("md5").update(resolve(filePath)).digest("hex").slice(0, 12);
|
|
155
|
+
const name = filePath.split(/[/\\]/).pop() || "file";
|
|
156
|
+
return join2(BACKUP_DIR, `${hash}_${name}`);
|
|
157
|
+
}
|
|
158
|
+
function getBackupForFile(filePath) {
|
|
159
|
+
return backupMap.get(resolve(filePath));
|
|
160
|
+
}
|
|
161
|
+
function cleanupBackups() {
|
|
162
|
+
try {
|
|
163
|
+
if (existsSync2(BACKUP_DIR)) {
|
|
164
|
+
rmSync(BACKUP_DIR, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
backupMap.clear();
|
|
169
|
+
}
|
|
148
170
|
function writeFileSafe(filePath, content, roots) {
|
|
149
171
|
if (!isPathSafe(filePath, roots)) {
|
|
150
172
|
return { ok: false, error: "Path is outside allowed roots" };
|
|
@@ -152,8 +174,10 @@ function writeFileSafe(filePath, content, roots) {
|
|
|
152
174
|
try {
|
|
153
175
|
let backupPath;
|
|
154
176
|
if (existsSync2(filePath)) {
|
|
155
|
-
|
|
177
|
+
if (!existsSync2(BACKUP_DIR)) mkdirSync2(BACKUP_DIR, { recursive: true });
|
|
178
|
+
backupPath = getBackupPath(filePath);
|
|
156
179
|
copyFileSync(filePath, backupPath);
|
|
180
|
+
backupMap.set(resolve(filePath), backupPath);
|
|
157
181
|
}
|
|
158
182
|
const dir = dirname(filePath);
|
|
159
183
|
if (!existsSync2(dir)) {
|
|
@@ -204,6 +228,79 @@ function listFiles(rootPath, roots, maxDepth = 4) {
|
|
|
204
228
|
walk(rootPath, 0);
|
|
205
229
|
return entries;
|
|
206
230
|
}
|
|
231
|
+
var GREP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
232
|
+
".js",
|
|
233
|
+
".jsx",
|
|
234
|
+
".ts",
|
|
235
|
+
".tsx",
|
|
236
|
+
".mjs",
|
|
237
|
+
".cjs",
|
|
238
|
+
".vue",
|
|
239
|
+
".svelte",
|
|
240
|
+
".astro",
|
|
241
|
+
".html",
|
|
242
|
+
".htm",
|
|
243
|
+
".css",
|
|
244
|
+
".scss",
|
|
245
|
+
".less",
|
|
246
|
+
".json",
|
|
247
|
+
".md",
|
|
248
|
+
".yaml",
|
|
249
|
+
".yml",
|
|
250
|
+
".php",
|
|
251
|
+
".py",
|
|
252
|
+
".rb"
|
|
253
|
+
]);
|
|
254
|
+
function grepFiles(pattern, searchRoot, roots, maxResults = 30) {
|
|
255
|
+
if (!isPathSafe(searchRoot, roots)) return [];
|
|
256
|
+
const results = [];
|
|
257
|
+
const lowerPattern = pattern.toLowerCase();
|
|
258
|
+
function walk(dir, depth) {
|
|
259
|
+
if (depth > 5 || results.length >= maxResults) return;
|
|
260
|
+
let items;
|
|
261
|
+
try {
|
|
262
|
+
items = readdirSync(dir);
|
|
263
|
+
} catch {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
if (results.length >= maxResults) return;
|
|
268
|
+
if (IGNORED_DIRS.has(item) || item.startsWith(".") && item !== ".env.example") continue;
|
|
269
|
+
const fullPath = join2(dir, item);
|
|
270
|
+
let stat;
|
|
271
|
+
try {
|
|
272
|
+
stat = lstatSync(fullPath);
|
|
273
|
+
} catch {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (stat.isSymbolicLink()) continue;
|
|
277
|
+
if (stat.isDirectory()) {
|
|
278
|
+
walk(fullPath, depth + 1);
|
|
279
|
+
} else if (stat.isFile()) {
|
|
280
|
+
const ext = extname(item).toLowerCase();
|
|
281
|
+
if (!GREP_EXTENSIONS.has(ext)) continue;
|
|
282
|
+
try {
|
|
283
|
+
const content = readFileSync2(fullPath, "utf-8");
|
|
284
|
+
const lines = content.split("\n");
|
|
285
|
+
let fileMatches = 0;
|
|
286
|
+
for (let i = 0; i < lines.length && fileMatches < 5; i++) {
|
|
287
|
+
if (lines[i].toLowerCase().includes(lowerPattern)) {
|
|
288
|
+
results.push({
|
|
289
|
+
file: relative(searchRoot, fullPath),
|
|
290
|
+
lineNum: i + 1,
|
|
291
|
+
line: lines[i].trim().slice(0, 200)
|
|
292
|
+
});
|
|
293
|
+
fileMatches++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
walk(searchRoot, 0);
|
|
302
|
+
return results;
|
|
303
|
+
}
|
|
207
304
|
function getProjectTree(roots) {
|
|
208
305
|
const lines = [];
|
|
209
306
|
for (const root of roots) {
|
|
@@ -909,6 +1006,7 @@ You MUST respond with valid JSON in this exact format:
|
|
|
909
1006
|
4. Include 3-5 lines of surrounding context in the search field to ensure uniqueness
|
|
910
1007
|
5. Keep modifications minimal \u2014 change only what's needed. Do NOT rewrite entire files.
|
|
911
1008
|
6. If the grounded files don't contain the code you need to modify, return: {"modifications":[],"explanation":"NEED_FILE: exact/relative/path/to/file.ext"}
|
|
1009
|
+
7. To search for a pattern across the codebase, return: {"modifications":[],"explanation":"SEARCH_FILES: \\"pattern\\" in optional/path/"}
|
|
912
1010
|
7. For style changes: check the dependencies (package.json) to know if the project uses Tailwind, MUI, etc. Use the project's styling approach, not raw CSS
|
|
913
1011
|
8. Use the selected element's cssSelector, className, parentContainerStyles, and siblings to understand the full layout context
|
|
914
1012
|
9. Use the page URL route and componentHint to identify the correct source file to modify
|
|
@@ -949,6 +1047,12 @@ function buildContextParts(context) {
|
|
|
949
1047
|
if (el.reactProps) {
|
|
950
1048
|
elementData.reactProps = el.reactProps;
|
|
951
1049
|
}
|
|
1050
|
+
if (el.childrenLayout?.length) {
|
|
1051
|
+
elementData.childrenLayout = el.childrenLayout;
|
|
1052
|
+
}
|
|
1053
|
+
if (el.resolvedClasses?.length) {
|
|
1054
|
+
elementData.resolvedClasses = el.resolvedClasses;
|
|
1055
|
+
}
|
|
952
1056
|
parts.selectedElement = JSON.stringify(elementData, null, 2);
|
|
953
1057
|
}
|
|
954
1058
|
if (context.files?.length) {
|
|
@@ -959,6 +1063,12 @@ function buildContextParts(context) {
|
|
|
959
1063
|
if (context.pageTitle) parts.pageTitle = context.pageTitle;
|
|
960
1064
|
if (context.networkLogs) parts.networkLogs = context.networkLogs.map((l) => `${l.method} ${l.url} \u2192 ${l.status || "pending"}`).join("\n");
|
|
961
1065
|
if (context.consoleLogs) parts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
|
|
1066
|
+
if (context.searchResults?.length) {
|
|
1067
|
+
parts.searchResults = context.searchResults.map(
|
|
1068
|
+
(s) => `Search: "${s.query}"
|
|
1069
|
+
${s.matches.map((m) => ` ${m.file}:${m.lineNum}: ${m.line}`).join("\n")}`
|
|
1070
|
+
).join("\n\n");
|
|
1071
|
+
}
|
|
962
1072
|
return parts;
|
|
963
1073
|
}
|
|
964
1074
|
function buildUserMessage(userPrompt, context) {
|
|
@@ -1002,6 +1112,12 @@ ${context.networkLogs}
|
|
|
1002
1112
|
parts.push(`## Console Output
|
|
1003
1113
|
\`\`\`
|
|
1004
1114
|
${context.consoleLogs}
|
|
1115
|
+
\`\`\``);
|
|
1116
|
+
}
|
|
1117
|
+
if (context.searchResults) {
|
|
1118
|
+
parts.push(`## Search Results
|
|
1119
|
+
\`\`\`
|
|
1120
|
+
${context.searchResults}
|
|
1005
1121
|
\`\`\``);
|
|
1006
1122
|
}
|
|
1007
1123
|
parts.push(`## User Request
|
|
@@ -1614,6 +1730,30 @@ async function handleMessage(ws, msg, state, roots) {
|
|
|
1614
1730
|
}
|
|
1615
1731
|
break;
|
|
1616
1732
|
}
|
|
1733
|
+
case "fs.undo": {
|
|
1734
|
+
const payload = msg.payload;
|
|
1735
|
+
if (!payload?.path) {
|
|
1736
|
+
sendError(ws, "invalid_payload", "Missing path", msg.id);
|
|
1737
|
+
break;
|
|
1738
|
+
}
|
|
1739
|
+
const backupPath = getBackupForFile(payload.path);
|
|
1740
|
+
if (!backupPath) {
|
|
1741
|
+
sendError(ws, "fs_error", "No backup found", msg.id);
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
try {
|
|
1745
|
+
const backupContent = readFileSync3(backupPath, "utf-8");
|
|
1746
|
+
const writeResult = writeFileSafe(payload.path, backupContent, roots);
|
|
1747
|
+
if (!writeResult.ok) {
|
|
1748
|
+
sendError(ws, "fs_error", writeResult.error || "Undo failed", msg.id);
|
|
1749
|
+
break;
|
|
1750
|
+
}
|
|
1751
|
+
send(ws, { id: msg.id, type: "fs.undone", payload: { path: payload.path, ok: true } });
|
|
1752
|
+
} catch (e) {
|
|
1753
|
+
sendError(ws, "fs_error", `Backup read failed: ${e.message}`, msg.id);
|
|
1754
|
+
}
|
|
1755
|
+
break;
|
|
1756
|
+
}
|
|
1617
1757
|
case "fs.list": {
|
|
1618
1758
|
const payload = msg.payload;
|
|
1619
1759
|
const root = payload?.root || roots[0];
|
|
@@ -1694,6 +1834,17 @@ async function handleMessage(ws, msg, state, roots) {
|
|
|
1694
1834
|
}
|
|
1695
1835
|
break;
|
|
1696
1836
|
}
|
|
1837
|
+
case "fs.grep": {
|
|
1838
|
+
const payload = msg.payload;
|
|
1839
|
+
if (!payload?.pattern) {
|
|
1840
|
+
sendError(ws, "invalid_payload", "Missing pattern", msg.id);
|
|
1841
|
+
break;
|
|
1842
|
+
}
|
|
1843
|
+
const searchRoot = payload.path ? join3(roots[0] || process.cwd(), payload.path) : roots[0] || process.cwd();
|
|
1844
|
+
const results = grepFiles(payload.pattern, searchRoot, roots);
|
|
1845
|
+
send(ws, { id: msg.id, type: "fs.grep.result", payload: { results } });
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1697
1848
|
case "debug.logs": {
|
|
1698
1849
|
send(ws, {
|
|
1699
1850
|
id: msg.id,
|
|
@@ -2289,6 +2440,7 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
2289
2440
|
const shutdown = () => {
|
|
2290
2441
|
console.log("");
|
|
2291
2442
|
console.log(chalk.dim(" Shutting down OpenMagic..."));
|
|
2443
|
+
cleanupBackups();
|
|
2292
2444
|
proxyServer.close();
|
|
2293
2445
|
process.exit(0);
|
|
2294
2446
|
};
|