openmagic 0.11.0 → 0.13.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/dist/cli.js CHANGED
@@ -68,8 +68,9 @@ function saveConfig(updates) {
68
68
  const tmpFile = CONFIG_FILE + ".tmp";
69
69
  writeFileSync(tmpFile, JSON.stringify(merged, null, 2), { encoding: "utf-8", mode: 384 });
70
70
  renameSync(tmpFile, CONFIG_FILE);
71
+ return { ok: true };
71
72
  } catch (e) {
72
- console.warn(`[OpenMagic] Warning: Could not save config to ${CONFIG_FILE}: ${e.message}`);
73
+ return { ok: false, error: e.message };
73
74
  }
74
75
  }
75
76
 
@@ -78,10 +79,11 @@ import {
78
79
  readFileSync as readFileSync2,
79
80
  writeFileSync as writeFileSync2,
80
81
  existsSync as existsSync2,
81
- statSync,
82
+ lstatSync,
82
83
  readdirSync,
83
84
  copyFileSync,
84
- mkdirSync as mkdirSync2
85
+ mkdirSync as mkdirSync2,
86
+ realpathSync
85
87
  } from "fs";
86
88
  import { join as join2, resolve, relative, dirname, extname } from "path";
87
89
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -117,10 +119,17 @@ var IGNORED_EXTENSIONS = /* @__PURE__ */ new Set([
117
119
  ]);
118
120
  function isPathSafe(filePath, roots) {
119
121
  const resolved = resolve(filePath);
122
+ let real;
123
+ try {
124
+ real = realpathSync(resolved);
125
+ } catch {
126
+ real = resolved;
127
+ }
120
128
  return roots.some((root) => {
121
129
  const resolvedRoot = resolve(root);
122
130
  const rel = relative(resolvedRoot, resolved);
123
- return !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
131
+ const realRel = relative(resolvedRoot, real);
132
+ return !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\") && (!realRel.startsWith("..") && !realRel.startsWith("/") && !realRel.startsWith("\\"));
124
133
  });
125
134
  }
126
135
  function readFileSafe(filePath, roots) {
@@ -176,10 +185,11 @@ function listFiles(rootPath, roots, maxDepth = 4) {
176
185
  const fullPath = join2(dir, item);
177
186
  let stat;
178
187
  try {
179
- stat = statSync(fullPath);
188
+ stat = lstatSync(fullPath);
180
189
  } catch {
181
190
  continue;
182
191
  }
192
+ if (stat.isSymbolicLink()) continue;
183
193
  const relPath = relative(rootPath, fullPath);
184
194
  if (stat.isDirectory()) {
185
195
  entries.push({ path: relPath, type: "dir", name: item });
@@ -902,6 +912,18 @@ You MUST respond with valid JSON in this exact format:
902
912
  6. If the change involves multiple files, include all modifications in the array
903
913
  7. ALWAYS respond with the JSON format above, even for explanations (put them in the "explanation" field)
904
914
  8. If you cannot make the requested change, set modifications to an empty array and explain why`;
915
+ function buildContextParts(context) {
916
+ const parts = {};
917
+ if (context.selectedElement) parts.selectedElement = context.selectedElement.outerHTML;
918
+ if (context.files?.length) {
919
+ parts.filePath = context.files[0].path;
920
+ parts.fileContent = context.files[0].content;
921
+ }
922
+ if (context.projectTree) parts.projectTree = context.projectTree;
923
+ if (context.networkLogs) parts.networkLogs = context.networkLogs.map((l) => `${l.method} ${l.url} \u2192 ${l.status || "pending"}`).join("\n");
924
+ if (context.consoleLogs) parts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
925
+ return parts;
926
+ }
905
927
  function buildUserMessage(userPrompt, context) {
906
928
  const parts = [];
907
929
  if (context.projectTree) {
@@ -957,24 +979,7 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
957
979
  for (let i = 0; i < messages.length; i++) {
958
980
  const msg = messages[i];
959
981
  if (msg.role === "user" && typeof msg.content === "string" && i === lastUserIdx) {
960
- const contextParts = {};
961
- if (context.selectedElement) {
962
- contextParts.selectedElement = context.selectedElement.outerHTML;
963
- }
964
- if (context.files && context.files.length > 0) {
965
- contextParts.filePath = context.files[0].path;
966
- contextParts.fileContent = context.files[0].content;
967
- }
968
- if (context.projectTree) {
969
- contextParts.projectTree = context.projectTree;
970
- }
971
- if (context.networkLogs) {
972
- contextParts.networkLogs = context.networkLogs.map((l) => `${l.method} ${l.url} \u2192 ${l.status || "pending"}`).join("\n");
973
- }
974
- if (context.consoleLogs) {
975
- contextParts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
976
- }
977
- const enrichedContent = buildUserMessage(msg.content, contextParts);
982
+ const enrichedContent = buildUserMessage(msg.content, buildContextParts(context));
978
983
  const modelInfo2 = providerConfig.models.find((m) => m.id === model);
979
984
  if (context.screenshot && modelInfo2?.vision) {
980
985
  apiMessages.push({
@@ -1088,24 +1093,7 @@ async function chatAnthropic(model, apiKey, messages, context, onChunk, onDone,
1088
1093
  const msg = messages[i];
1089
1094
  if (msg.role === "system") continue;
1090
1095
  if (msg.role === "user" && typeof msg.content === "string" && i === lastUserIdx) {
1091
- const contextParts = {};
1092
- if (context.selectedElement) {
1093
- contextParts.selectedElement = context.selectedElement.outerHTML;
1094
- }
1095
- if (context.files && context.files.length > 0) {
1096
- contextParts.filePath = context.files[0].path;
1097
- contextParts.fileContent = context.files[0].content;
1098
- }
1099
- if (context.projectTree) {
1100
- contextParts.projectTree = context.projectTree;
1101
- }
1102
- if (context.networkLogs) {
1103
- contextParts.networkLogs = context.networkLogs.map((l) => `${l.method} ${l.url} \u2192 ${l.status || "pending"}`).join("\n");
1104
- }
1105
- if (context.consoleLogs) {
1106
- contextParts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
1107
- }
1108
- const enrichedContent = buildUserMessage(msg.content, contextParts);
1096
+ const enrichedContent = buildUserMessage(msg.content, buildContextParts(context));
1109
1097
  if (context.screenshot) {
1110
1098
  const base64Data = context.screenshot.replace(
1111
1099
  /^data:image\/\w+;base64,/,
@@ -1218,18 +1206,7 @@ async function chatGoogle(model, apiKey, messages, context, onChunk, onDone, onE
1218
1206
  if (msg.role === "system") continue;
1219
1207
  const role = msg.role === "assistant" ? "model" : "user";
1220
1208
  if (msg.role === "user" && typeof msg.content === "string" && i === lastUserIdx) {
1221
- const contextParts = {};
1222
- if (context.selectedElement) {
1223
- contextParts.selectedElement = context.selectedElement.outerHTML;
1224
- }
1225
- if (context.files && context.files.length > 0) {
1226
- contextParts.filePath = context.files[0].path;
1227
- contextParts.fileContent = context.files[0].content;
1228
- }
1229
- if (context.projectTree) {
1230
- contextParts.projectTree = context.projectTree;
1231
- }
1232
- const enrichedContent = buildUserMessage(msg.content, contextParts);
1209
+ const enrichedContent = buildUserMessage(msg.content, buildContextParts(context));
1233
1210
  const parts = [
1234
1211
  { text: enrichedContent }
1235
1212
  ];
@@ -1381,7 +1358,7 @@ async function handleLlmChat(params, onChunk, onDone, onError) {
1381
1358
  }
1382
1359
 
1383
1360
  // src/server.ts
1384
- var VERSION = "0.11.0";
1361
+ var VERSION = "0.13.0";
1385
1362
  var __dirname = dirname2(fileURLToPath(import.meta.url));
1386
1363
  function attachOpenMagic(httpServer, roots) {
1387
1364
  function handleRequest(req, res) {
@@ -1463,7 +1440,7 @@ async function handleMessage(ws, msg, state, roots) {
1463
1440
  provider: config.provider,
1464
1441
  model: config.model,
1465
1442
  hasApiKey: !!config.apiKey,
1466
- apiKeys: config.apiKeys || {}
1443
+ apiKeys: Object.fromEntries(Object.entries(config.apiKeys || {}).map(([k]) => [k, true]))
1467
1444
  }
1468
1445
  }
1469
1446
  });
@@ -1529,7 +1506,7 @@ async function handleMessage(ws, msg, state, roots) {
1529
1506
  await handleLlmChat(
1530
1507
  {
1531
1508
  provider,
1532
- model: payload.model || config.model || "gpt-4o",
1509
+ model: payload.model || config.model || MODEL_REGISTRY[provider]?.models[0]?.id || "gpt-4o",
1533
1510
  apiKey,
1534
1511
  messages: payload.messages,
1535
1512
  context: payload.context
@@ -1577,12 +1554,12 @@ async function handleMessage(ws, msg, state, roots) {
1577
1554
  } else if (payload.apiKey !== void 0) {
1578
1555
  updates.apiKey = payload.apiKey;
1579
1556
  }
1580
- saveConfig(updates);
1581
- send(ws, {
1582
- id: msg.id,
1583
- type: "config.saved",
1584
- payload: { ok: true }
1585
- });
1557
+ const result = saveConfig(updates);
1558
+ if (!result.ok) {
1559
+ sendError(ws, "config_error", result.error || "Failed to save", msg.id);
1560
+ } else {
1561
+ send(ws, { id: msg.id, type: "config.saved", payload: { ok: true } });
1562
+ }
1586
1563
  break;
1587
1564
  }
1588
1565
  default:
@@ -1662,6 +1639,7 @@ function createProxyServer(targetHost, targetPort, roots) {
1662
1639
  delete headers["x-content-security-policy"];
1663
1640
  delete headers["etag"];
1664
1641
  delete headers["last-modified"];
1642
+ headers["cache-control"] = "no-store";
1665
1643
  res.writeHead(proxyRes.statusCode || 200, headers);
1666
1644
  res.end(body);
1667
1645
  }).catch(() => {
@@ -1728,18 +1706,7 @@ async function collectBody(stream) {
1728
1706
  }
1729
1707
  }
1730
1708
  function buildInjectionScript(token) {
1731
- return `
1732
- <script data-openmagic="true">
1733
- (function() {
1734
- if (window.__OPENMAGIC_LOADED__) return;
1735
- window.__OPENMAGIC_LOADED__ = true;
1736
- window.__OPENMAGIC_TOKEN__ = "${token}";
1737
- var s = document.createElement("script");
1738
- s.src = "/__openmagic__/toolbar.js";
1739
- s.dataset.openmagic = "true";
1740
- document.body.appendChild(s);
1741
- })();
1742
- </script>`;
1709
+ return `<script src="/__openmagic__/toolbar.js?v=${Date.now()}" data-openmagic="true" data-openmagic-token="${token}" defer></script>`;
1743
1710
  }
1744
1711
 
1745
1712
  // src/detect.ts
@@ -1765,6 +1732,8 @@ var COMMON_DEV_PORTS = [
1765
1732
  // Common alternate
1766
1733
  4e3,
1767
1734
  // Phoenix, generic
1735
+ 5e3,
1736
+ // Flask
1768
1737
  1234,
1769
1738
  // Parcel
1770
1739
  4321,
@@ -1916,7 +1885,7 @@ process.on("uncaughtException", (err) => {
1916
1885
  process.exit(1);
1917
1886
  });
1918
1887
  var childProcesses = [];
1919
- var VERSION2 = "0.11.0";
1888
+ var VERSION2 = "0.13.0";
1920
1889
  function ask(question) {
1921
1890
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1922
1891
  return new Promise((resolve3) => {