openmagic 0.10.0 → 0.12.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
@@ -31,134 +31,7 @@ function validateToken(token) {
31
31
  return token === sessionToken;
32
32
  }
33
33
 
34
- // src/proxy.ts
35
- var gunzipAsync = promisify(gunzip);
36
- var inflateAsync = promisify(inflate);
37
- var brotliAsync = promisify(brotliDecompress);
38
- function createProxyServer(targetHost, targetPort, serverPort) {
39
- const proxy = httpProxy.createProxyServer({
40
- target: `http://${targetHost}:${targetPort}`,
41
- ws: true,
42
- selfHandleResponse: true
43
- });
44
- const token = getSessionToken();
45
- proxy.on("proxyRes", (proxyRes, req, res) => {
46
- const contentType = proxyRes.headers["content-type"] || "";
47
- const isHtml = contentType.includes("text/html");
48
- if (!isHtml) {
49
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
50
- proxyRes.pipe(res);
51
- return;
52
- }
53
- collectBody(proxyRes).then((body) => {
54
- const toolbarScript = buildInjectionScript(serverPort, token);
55
- if (body.includes("</body>")) {
56
- body = body.replace("</body>", `${toolbarScript}</body>`);
57
- } else if (body.includes("</html>")) {
58
- body = body.replace("</html>", `${toolbarScript}</html>`);
59
- } else {
60
- body += toolbarScript;
61
- }
62
- const headers = { ...proxyRes.headers };
63
- delete headers["content-length"];
64
- delete headers["content-encoding"];
65
- delete headers["transfer-encoding"];
66
- delete headers["content-security-policy"];
67
- delete headers["content-security-policy-report-only"];
68
- delete headers["x-content-security-policy"];
69
- delete headers["etag"];
70
- delete headers["last-modified"];
71
- res.writeHead(proxyRes.statusCode || 200, headers);
72
- res.end(body);
73
- }).catch(() => {
74
- try {
75
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
76
- res.end();
77
- } catch {
78
- }
79
- });
80
- });
81
- proxy.on("error", (err, _req, res) => {
82
- if (res instanceof http.ServerResponse && !res.headersSent) {
83
- const toolbarScript = buildInjectionScript(serverPort, token);
84
- res.writeHead(502, { "Content-Type": "text/html" });
85
- res.end(
86
- `<html><body style="font-family:system-ui;padding:40px;background:#1a1a2e;color:#e0e0e0;">
87
- <h2 style="color:#e94560;">OpenMagic \u2014 Cannot connect to dev server</h2>
88
- <p>Could not reach <code>${targetHost}:${targetPort}</code></p>
89
- <p style="color:#888;">Make sure your dev server is running, then refresh this page.</p>
90
- <p style="color:#666;font-size:13px;">${err.message}</p>
91
- ${toolbarScript}
92
- </body></html>`
93
- );
94
- }
95
- });
96
- const server = http.createServer((req, res) => {
97
- if (req.url?.startsWith("/__openmagic__/")) {
98
- handleToolbarAsset(req, res, serverPort);
99
- return;
100
- }
101
- proxy.web(req, res);
102
- });
103
- server.on("upgrade", (req, socket, head) => {
104
- if (req.url?.startsWith("/__openmagic__")) {
105
- return;
106
- }
107
- proxy.ws(req, socket, head);
108
- });
109
- return server;
110
- }
111
- async function collectBody(stream) {
112
- const rawBuffer = await new Promise((resolve3, reject) => {
113
- const chunks = [];
114
- stream.on("data", (chunk) => chunks.push(chunk));
115
- stream.on("end", () => resolve3(Buffer.concat(chunks)));
116
- stream.on("error", reject);
117
- });
118
- const encoding = (stream.headers["content-encoding"] || "").toLowerCase();
119
- if (!encoding || encoding === "identity") {
120
- return rawBuffer.toString("utf-8");
121
- }
122
- try {
123
- let decompressed;
124
- if (encoding === "gzip" || encoding === "x-gzip") {
125
- decompressed = await gunzipAsync(rawBuffer);
126
- } else if (encoding === "deflate") {
127
- decompressed = await inflateAsync(rawBuffer);
128
- } else if (encoding === "br") {
129
- decompressed = await brotliAsync(rawBuffer);
130
- } else {
131
- return rawBuffer.toString("utf-8");
132
- }
133
- return decompressed.toString("utf-8");
134
- } catch {
135
- return rawBuffer.toString("utf-8");
136
- }
137
- }
138
- function handleToolbarAsset(_req, res, _serverPort) {
139
- res.writeHead(404, { "Content-Type": "text/plain" });
140
- res.end("Not found");
141
- }
142
- function buildInjectionScript(serverPort, token) {
143
- return `
144
- <script data-openmagic="true">
145
- (function() {
146
- if (window.__OPENMAGIC_LOADED__) return;
147
- window.__OPENMAGIC_LOADED__ = true;
148
- window.__OPENMAGIC_CONFIG__ = {
149
- wsPort: ${serverPort},
150
- token: "${token}"
151
- };
152
- var script = document.createElement("script");
153
- script.src = "http://127.0.0.1:${serverPort}/__openmagic__/toolbar.js";
154
- script.dataset.openmagic = "true";
155
- document.body.appendChild(script);
156
- })();
157
- </script>`;
158
- }
159
-
160
34
  // src/server.ts
161
- import http2 from "http";
162
35
  import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
163
36
  import { join as join3, dirname as dirname2 } from "path";
164
37
  import { fileURLToPath } from "url";
@@ -208,7 +81,8 @@ import {
208
81
  statSync,
209
82
  readdirSync,
210
83
  copyFileSync,
211
- mkdirSync as mkdirSync2
84
+ mkdirSync as mkdirSync2,
85
+ realpathSync
212
86
  } from "fs";
213
87
  import { join as join2, resolve, relative, dirname, extname } from "path";
214
88
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -244,10 +118,17 @@ var IGNORED_EXTENSIONS = /* @__PURE__ */ new Set([
244
118
  ]);
245
119
  function isPathSafe(filePath, roots) {
246
120
  const resolved = resolve(filePath);
121
+ let real;
122
+ try {
123
+ real = realpathSync(resolved);
124
+ } catch {
125
+ real = resolved;
126
+ }
247
127
  return roots.some((root) => {
248
128
  const resolvedRoot = resolve(root);
249
129
  const rel = relative(resolvedRoot, resolved);
250
- return !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
130
+ const realRel = relative(resolvedRoot, real);
131
+ return !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\") && (!realRel.startsWith("..") && !realRel.startsWith("/") && !realRel.startsWith("\\"));
251
132
  });
252
133
  }
253
134
  function readFileSafe(filePath, roots) {
@@ -1029,6 +910,18 @@ You MUST respond with valid JSON in this exact format:
1029
910
  6. If the change involves multiple files, include all modifications in the array
1030
911
  7. ALWAYS respond with the JSON format above, even for explanations (put them in the "explanation" field)
1031
912
  8. If you cannot make the requested change, set modifications to an empty array and explain why`;
913
+ function buildContextParts(context) {
914
+ const parts = {};
915
+ if (context.selectedElement) parts.selectedElement = context.selectedElement.outerHTML;
916
+ if (context.files?.length) {
917
+ parts.filePath = context.files[0].path;
918
+ parts.fileContent = context.files[0].content;
919
+ }
920
+ if (context.projectTree) parts.projectTree = context.projectTree;
921
+ if (context.networkLogs) parts.networkLogs = context.networkLogs.map((l) => `${l.method} ${l.url} \u2192 ${l.status || "pending"}`).join("\n");
922
+ if (context.consoleLogs) parts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
923
+ return parts;
924
+ }
1032
925
  function buildUserMessage(userPrompt, context) {
1033
926
  const parts = [];
1034
927
  if (context.projectTree) {
@@ -1084,24 +977,7 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
1084
977
  for (let i = 0; i < messages.length; i++) {
1085
978
  const msg = messages[i];
1086
979
  if (msg.role === "user" && typeof msg.content === "string" && i === lastUserIdx) {
1087
- const contextParts = {};
1088
- if (context.selectedElement) {
1089
- contextParts.selectedElement = context.selectedElement.outerHTML;
1090
- }
1091
- if (context.files && context.files.length > 0) {
1092
- contextParts.filePath = context.files[0].path;
1093
- contextParts.fileContent = context.files[0].content;
1094
- }
1095
- if (context.projectTree) {
1096
- contextParts.projectTree = context.projectTree;
1097
- }
1098
- if (context.networkLogs) {
1099
- contextParts.networkLogs = context.networkLogs.map((l) => `${l.method} ${l.url} \u2192 ${l.status || "pending"}`).join("\n");
1100
- }
1101
- if (context.consoleLogs) {
1102
- contextParts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
1103
- }
1104
- const enrichedContent = buildUserMessage(msg.content, contextParts);
980
+ const enrichedContent = buildUserMessage(msg.content, buildContextParts(context));
1105
981
  const modelInfo2 = providerConfig.models.find((m) => m.id === model);
1106
982
  if (context.screenshot && modelInfo2?.vision) {
1107
983
  apiMessages.push({
@@ -1215,24 +1091,7 @@ async function chatAnthropic(model, apiKey, messages, context, onChunk, onDone,
1215
1091
  const msg = messages[i];
1216
1092
  if (msg.role === "system") continue;
1217
1093
  if (msg.role === "user" && typeof msg.content === "string" && i === lastUserIdx) {
1218
- const contextParts = {};
1219
- if (context.selectedElement) {
1220
- contextParts.selectedElement = context.selectedElement.outerHTML;
1221
- }
1222
- if (context.files && context.files.length > 0) {
1223
- contextParts.filePath = context.files[0].path;
1224
- contextParts.fileContent = context.files[0].content;
1225
- }
1226
- if (context.projectTree) {
1227
- contextParts.projectTree = context.projectTree;
1228
- }
1229
- if (context.networkLogs) {
1230
- contextParts.networkLogs = context.networkLogs.map((l) => `${l.method} ${l.url} \u2192 ${l.status || "pending"}`).join("\n");
1231
- }
1232
- if (context.consoleLogs) {
1233
- contextParts.consoleLogs = context.consoleLogs.map((l) => `[${l.level}] ${l.args.join(" ")}`).join("\n");
1234
- }
1235
- const enrichedContent = buildUserMessage(msg.content, contextParts);
1094
+ const enrichedContent = buildUserMessage(msg.content, buildContextParts(context));
1236
1095
  if (context.screenshot) {
1237
1096
  const base64Data = context.screenshot.replace(
1238
1097
  /^data:image\/\w+;base64,/,
@@ -1345,18 +1204,7 @@ async function chatGoogle(model, apiKey, messages, context, onChunk, onDone, onE
1345
1204
  if (msg.role === "system") continue;
1346
1205
  const role = msg.role === "assistant" ? "model" : "user";
1347
1206
  if (msg.role === "user" && typeof msg.content === "string" && i === lastUserIdx) {
1348
- const contextParts = {};
1349
- if (context.selectedElement) {
1350
- contextParts.selectedElement = context.selectedElement.outerHTML;
1351
- }
1352
- if (context.files && context.files.length > 0) {
1353
- contextParts.filePath = context.files[0].path;
1354
- contextParts.fileContent = context.files[0].content;
1355
- }
1356
- if (context.projectTree) {
1357
- contextParts.projectTree = context.projectTree;
1358
- }
1359
- const enrichedContent = buildUserMessage(msg.content, contextParts);
1207
+ const enrichedContent = buildUserMessage(msg.content, buildContextParts(context));
1360
1208
  const parts = [
1361
1209
  { text: enrichedContent }
1362
1210
  ];
@@ -1508,24 +1356,25 @@ async function handleLlmChat(params, onChunk, onDone, onError) {
1508
1356
  }
1509
1357
 
1510
1358
  // src/server.ts
1359
+ var VERSION = "0.11.0";
1511
1360
  var __dirname = dirname2(fileURLToPath(import.meta.url));
1512
- function createOpenMagicServer(proxyPort, roots) {
1513
- const httpServer = http2.createServer((req, res) => {
1361
+ function attachOpenMagic(httpServer, roots) {
1362
+ function handleRequest(req, res) {
1363
+ if (!req.url?.startsWith("/__openmagic__/")) return false;
1514
1364
  if (req.url === "/__openmagic__/toolbar.js") {
1515
1365
  serveToolbarBundle(res);
1516
- return;
1366
+ return true;
1517
1367
  }
1518
1368
  if (req.url === "/__openmagic__/health") {
1519
1369
  res.writeHead(200, {
1520
1370
  "Content-Type": "application/json",
1521
1371
  "Access-Control-Allow-Origin": "*"
1522
1372
  });
1523
- res.end(JSON.stringify({ status: "ok", version: "0.10.0" }));
1524
- return;
1373
+ res.end(JSON.stringify({ status: "ok", version: VERSION }));
1374
+ return true;
1525
1375
  }
1526
- res.writeHead(404);
1527
- res.end("Not found");
1528
- });
1376
+ return false;
1377
+ }
1529
1378
  const wss = new WebSocketServer({
1530
1379
  server: httpServer,
1531
1380
  path: "/__openmagic__/ws"
@@ -1552,7 +1401,7 @@ function createOpenMagicServer(proxyPort, roots) {
1552
1401
  return;
1553
1402
  }
1554
1403
  try {
1555
- await handleMessage(ws, msg, state, roots, proxyPort);
1404
+ await handleMessage(ws, msg, state, roots);
1556
1405
  } catch (e) {
1557
1406
  sendError(ws, "internal_error", e.message, msg.id);
1558
1407
  }
@@ -1561,9 +1410,9 @@ function createOpenMagicServer(proxyPort, roots) {
1561
1410
  clientStates.delete(ws);
1562
1411
  });
1563
1412
  });
1564
- return { httpServer, wss };
1413
+ return { wss, handleRequest };
1565
1414
  }
1566
- async function handleMessage(ws, msg, state, roots, _proxyPort) {
1415
+ async function handleMessage(ws, msg, state, roots) {
1567
1416
  switch (msg.type) {
1568
1417
  case "handshake": {
1569
1418
  const payload = msg.payload;
@@ -1583,12 +1432,13 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
1583
1432
  id: msg.id,
1584
1433
  type: "handshake.ok",
1585
1434
  payload: {
1586
- version: "0.10.0",
1435
+ version: VERSION,
1587
1436
  roots,
1588
1437
  config: {
1589
1438
  provider: config.provider,
1590
1439
  model: config.model,
1591
- hasApiKey: !!config.apiKey
1440
+ hasApiKey: !!config.apiKey,
1441
+ apiKeys: config.apiKeys || {}
1592
1442
  }
1593
1443
  }
1594
1444
  });
@@ -1614,12 +1464,20 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
1614
1464
  }
1615
1465
  case "fs.write": {
1616
1466
  const payload = msg.payload;
1617
- const result = writeFileSafe(payload.path, payload.content, roots);
1618
- send(ws, {
1619
- id: msg.id,
1620
- type: "fs.written",
1621
- payload: { path: payload.path, ok: result.ok, error: result.error }
1622
- });
1467
+ if (!payload?.path || payload.content === void 0) {
1468
+ sendError(ws, "invalid_payload", "Missing path or content", msg.id);
1469
+ break;
1470
+ }
1471
+ const writeResult = writeFileSafe(payload.path, payload.content, roots);
1472
+ if (!writeResult.ok) {
1473
+ sendError(ws, "fs_error", writeResult.error || "Write failed", msg.id);
1474
+ } else {
1475
+ send(ws, {
1476
+ id: msg.id,
1477
+ type: "fs.written",
1478
+ payload: { path: payload.path, ok: true }
1479
+ });
1480
+ }
1623
1481
  break;
1624
1482
  }
1625
1483
  case "fs.list": {
@@ -1636,16 +1494,18 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
1636
1494
  case "llm.chat": {
1637
1495
  const payload = msg.payload;
1638
1496
  const config = loadConfig();
1639
- const providerMeta = MODEL_REGISTRY?.[payload.provider || config.provider || ""];
1640
- if (!config.apiKey && !providerMeta?.local) {
1497
+ const provider = payload.provider || config.provider || "openai";
1498
+ const apiKey = config.apiKeys?.[provider] || config.apiKey || "";
1499
+ const providerMeta = MODEL_REGISTRY?.[provider];
1500
+ if (!apiKey && !providerMeta?.local) {
1641
1501
  sendError(ws, "config_error", "API key not configured", msg.id);
1642
1502
  return;
1643
1503
  }
1644
1504
  await handleLlmChat(
1645
1505
  {
1646
- provider: payload.provider || config.provider || "openai",
1506
+ provider,
1647
1507
  model: payload.model || config.model || "gpt-4o",
1648
- apiKey: config.apiKey,
1508
+ apiKey,
1649
1509
  messages: payload.messages,
1650
1510
  context: payload.context
1651
1511
  },
@@ -1669,8 +1529,11 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
1669
1529
  payload: {
1670
1530
  provider: config.provider,
1671
1531
  model: config.model,
1672
- hasApiKey: !!config.apiKey,
1673
- roots: config.roots || roots
1532
+ hasApiKey: !!(config.apiKeys?.[config.provider || ""] || config.apiKey),
1533
+ roots: config.roots || roots,
1534
+ apiKeys: Object.fromEntries(
1535
+ Object.entries(config.apiKeys || {}).map(([k]) => [k, true])
1536
+ )
1674
1537
  }
1675
1538
  });
1676
1539
  break;
@@ -1680,7 +1543,15 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
1680
1543
  const updates = {};
1681
1544
  if (payload.provider !== void 0) updates.provider = payload.provider;
1682
1545
  if (payload.model !== void 0) updates.model = payload.model;
1683
- if (payload.apiKey !== void 0) updates.apiKey = payload.apiKey;
1546
+ if (payload.apiKey !== void 0 && payload.provider) {
1547
+ const existing = loadConfig();
1548
+ const apiKeys = { ...existing.apiKeys || {} };
1549
+ apiKeys[payload.provider] = payload.apiKey;
1550
+ updates.apiKeys = apiKeys;
1551
+ updates.apiKey = payload.apiKey;
1552
+ } else if (payload.apiKey !== void 0) {
1553
+ updates.apiKey = payload.apiKey;
1554
+ }
1684
1555
  saveConfig(updates);
1685
1556
  send(ws, {
1686
1557
  id: msg.id,
@@ -1699,11 +1570,7 @@ function send(ws, msg) {
1699
1570
  }
1700
1571
  }
1701
1572
  function sendError(ws, code, message, id) {
1702
- send(ws, {
1703
- id: id || "error",
1704
- type: "error",
1705
- payload: { code, message }
1706
- });
1573
+ send(ws, { id: id || "error", type: "error", payload: { code, message } });
1707
1574
  }
1708
1575
  function serveToolbarBundle(res) {
1709
1576
  const bundlePaths = [
@@ -1730,14 +1597,124 @@ function serveToolbarBundle(res) {
1730
1597
  "Content-Type": "application/javascript",
1731
1598
  "Access-Control-Allow-Origin": "*"
1732
1599
  });
1733
- res.end(`
1734
- (function() {
1735
- var div = document.createElement("div");
1736
- div.style.cssText = "position:fixed;bottom:20px;right:20px;background:#1a1a2e;color:#e94560;padding:16px 24px;border-radius:12px;font-family:system-ui;font-size:14px;z-index:2147483647;box-shadow:0 4px 24px rgba(0,0,0,0.3);";
1737
- div.textContent = "OpenMagic: Toolbar bundle not found. Run 'npm run build:toolbar' first.";
1738
- document.body.appendChild(div);
1739
- })();
1740
- `);
1600
+ res.end(`(function(){var d=document.createElement("div");d.style.cssText="position:fixed;bottom:20px;right:20px;background:#1a1a2e;color:#e94560;padding:16px 24px;border-radius:12px;font-family:system-ui;font-size:14px;z-index:2147483647;box-shadow:0 4px 24px rgba(0,0,0,0.3);";d.textContent="OpenMagic: Toolbar bundle not found.";document.body.appendChild(d);})();`);
1601
+ }
1602
+
1603
+ // src/proxy.ts
1604
+ var gunzipAsync = promisify(gunzip);
1605
+ var inflateAsync = promisify(inflate);
1606
+ var brotliAsync = promisify(brotliDecompress);
1607
+ function createProxyServer(targetHost, targetPort, roots) {
1608
+ const proxy = httpProxy.createProxyServer({
1609
+ target: `http://${targetHost}:${targetPort}`,
1610
+ ws: true,
1611
+ selfHandleResponse: true
1612
+ });
1613
+ const token = getSessionToken();
1614
+ proxy.on("proxyRes", (proxyRes, req, res) => {
1615
+ const contentType = proxyRes.headers["content-type"] || "";
1616
+ const isHtml = contentType.includes("text/html");
1617
+ if (!isHtml) {
1618
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
1619
+ proxyRes.pipe(res);
1620
+ return;
1621
+ }
1622
+ collectBody(proxyRes).then((body) => {
1623
+ const toolbarScript = buildInjectionScript(token);
1624
+ if (body.includes("</body>")) {
1625
+ body = body.replace("</body>", `${toolbarScript}</body>`);
1626
+ } else if (body.includes("</html>")) {
1627
+ body = body.replace("</html>", `${toolbarScript}</html>`);
1628
+ } else {
1629
+ body += toolbarScript;
1630
+ }
1631
+ const headers = { ...proxyRes.headers };
1632
+ delete headers["content-length"];
1633
+ delete headers["content-encoding"];
1634
+ delete headers["transfer-encoding"];
1635
+ delete headers["content-security-policy"];
1636
+ delete headers["content-security-policy-report-only"];
1637
+ delete headers["x-content-security-policy"];
1638
+ delete headers["etag"];
1639
+ delete headers["last-modified"];
1640
+ res.writeHead(proxyRes.statusCode || 200, headers);
1641
+ res.end(body);
1642
+ }).catch(() => {
1643
+ try {
1644
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
1645
+ res.end();
1646
+ } catch {
1647
+ }
1648
+ });
1649
+ });
1650
+ proxy.on("error", (err, _req, res) => {
1651
+ if (res instanceof http.ServerResponse && !res.headersSent) {
1652
+ const toolbarScript = buildInjectionScript(token);
1653
+ res.writeHead(502, { "Content-Type": "text/html" });
1654
+ res.end(
1655
+ `<html><body style="font-family:system-ui;padding:40px;background:#1a1a2e;color:#e0e0e0;">
1656
+ <h2 style="color:#e94560;">OpenMagic \u2014 Cannot connect to dev server</h2>
1657
+ <p>Could not reach <code>${targetHost}:${targetPort}</code></p>
1658
+ <p style="color:#888;">Make sure your dev server is running, then refresh this page.</p>
1659
+ <p style="color:#666;font-size:13px;">${err.message}</p>
1660
+ ${toolbarScript}
1661
+ </body></html>`
1662
+ );
1663
+ }
1664
+ });
1665
+ let omHandle = null;
1666
+ const server = http.createServer((req, res) => {
1667
+ if (omHandle && omHandle(req, res)) return;
1668
+ proxy.web(req, res);
1669
+ });
1670
+ const om = attachOpenMagic(server, roots);
1671
+ omHandle = om.handleRequest;
1672
+ server.on("upgrade", (req, socket, head) => {
1673
+ if (req.url?.startsWith("/__openmagic__")) return;
1674
+ proxy.ws(req, socket, head);
1675
+ });
1676
+ return server;
1677
+ }
1678
+ async function collectBody(stream) {
1679
+ const rawBuffer = await new Promise((resolve3, reject) => {
1680
+ const chunks = [];
1681
+ stream.on("data", (chunk) => chunks.push(chunk));
1682
+ stream.on("end", () => resolve3(Buffer.concat(chunks)));
1683
+ stream.on("error", reject);
1684
+ });
1685
+ const encoding = (stream.headers["content-encoding"] || "").toLowerCase();
1686
+ if (!encoding || encoding === "identity") {
1687
+ return rawBuffer.toString("utf-8");
1688
+ }
1689
+ try {
1690
+ let decompressed;
1691
+ if (encoding === "gzip" || encoding === "x-gzip") {
1692
+ decompressed = await gunzipAsync(rawBuffer);
1693
+ } else if (encoding === "deflate") {
1694
+ decompressed = await inflateAsync(rawBuffer);
1695
+ } else if (encoding === "br") {
1696
+ decompressed = await brotliAsync(rawBuffer);
1697
+ } else {
1698
+ return rawBuffer.toString("utf-8");
1699
+ }
1700
+ return decompressed.toString("utf-8");
1701
+ } catch {
1702
+ return rawBuffer.toString("utf-8");
1703
+ }
1704
+ }
1705
+ function buildInjectionScript(token) {
1706
+ return `
1707
+ <script data-openmagic="true">
1708
+ (function() {
1709
+ if (window.__OPENMAGIC_LOADED__) return;
1710
+ window.__OPENMAGIC_LOADED__ = true;
1711
+ window.__OPENMAGIC_TOKEN__ = "${token}";
1712
+ var s = document.createElement("script");
1713
+ s.src = "/__openmagic__/toolbar.js";
1714
+ s.dataset.openmagic = "true";
1715
+ document.body.appendChild(s);
1716
+ })();
1717
+ </script>`;
1741
1718
  }
1742
1719
 
1743
1720
  // src/detect.ts
@@ -1914,7 +1891,7 @@ process.on("uncaughtException", (err) => {
1914
1891
  process.exit(1);
1915
1892
  });
1916
1893
  var childProcesses = [];
1917
- var VERSION = "0.10.0";
1894
+ var VERSION2 = "0.12.0";
1918
1895
  function ask(question) {
1919
1896
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1920
1897
  return new Promise((resolve3) => {
@@ -2026,7 +2003,7 @@ function formatDevServerLine(line) {
2026
2003
  return chalk.dim(` \u2502 ${trimmed}`);
2027
2004
  }
2028
2005
  var program = new Command();
2029
- program.name("openmagic").description("AI-powered coding toolbar for any web application").version(VERSION).option("-p, --port <port>", "Dev server port to proxy", "").option(
2006
+ program.name("openmagic").description("AI-powered coding toolbar for any web application").version(VERSION2).option("-p, --port <port>", "Dev server port to proxy", "").option(
2030
2007
  "-l, --listen <port>",
2031
2008
  "Port for the OpenMagic proxy",
2032
2009
  "4567"
@@ -2036,7 +2013,7 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
2036
2013
  ).option("--no-open", "Don't auto-open browser").option("--host <host>", "Dev server host", "127.0.0.1").action(async (opts) => {
2037
2014
  console.log("");
2038
2015
  console.log(
2039
- chalk.bold.magenta(" \u2728 OpenMagic") + chalk.dim(` v${VERSION}`)
2016
+ chalk.bold.magenta(" \u2728 OpenMagic") + chalk.dim(` v${VERSION2}`)
2040
2017
  );
2041
2018
  console.log("");
2042
2019
  let targetPort;
@@ -2085,28 +2062,20 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
2085
2062
  );
2086
2063
  const config = loadConfig();
2087
2064
  saveConfig({ ...config, roots, targetPort });
2088
- const token = generateSessionToken();
2065
+ generateSessionToken();
2089
2066
  let proxyPort = parseInt(opts.listen, 10);
2090
- while (await isPortOpen(proxyPort) || await isPortOpen(proxyPort + 1)) {
2067
+ while (await isPortOpen(proxyPort)) {
2091
2068
  proxyPort++;
2092
2069
  if (proxyPort > parseInt(opts.listen, 10) + 100) {
2093
- console.log(chalk.red(" Could not find two consecutive free ports."));
2070
+ console.log(chalk.red(" Could not find an available port."));
2094
2071
  process.exit(1);
2095
2072
  }
2096
2073
  }
2097
- const companionPort = proxyPort + 1;
2098
- const { httpServer: omServer } = createOpenMagicServer(companionPort, roots);
2099
- omServer.listen(companionPort, "127.0.0.1", () => {
2100
- });
2101
- const proxyServer = createProxyServer(
2102
- targetHost,
2103
- targetPort,
2104
- companionPort
2105
- );
2074
+ const proxyServer = createProxyServer(targetHost, targetPort, roots);
2106
2075
  proxyServer.listen(proxyPort, "127.0.0.1", async () => {
2107
2076
  console.log("");
2108
2077
  console.log(
2109
- chalk.bold.green(` \u{1F680} Proxy running at \u2192 `) + chalk.bold.underline.cyan(`http://localhost:${proxyPort}`)
2078
+ chalk.bold.green(` Proxy running at \u2192 `) + chalk.bold.underline.cyan(`http://localhost:${proxyPort}`)
2110
2079
  );
2111
2080
  console.log("");
2112
2081
  await healthCheck(proxyPort, targetPort);
@@ -2123,16 +2092,10 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
2123
2092
  });
2124
2093
  }
2125
2094
  });
2126
- proxyServer.on("upgrade", (req, socket, head) => {
2127
- if (req.url?.startsWith("/__openmagic__")) {
2128
- omServer.emit("upgrade", req, socket, head);
2129
- }
2130
- });
2131
2095
  const shutdown = () => {
2132
2096
  console.log("");
2133
2097
  console.log(chalk.dim(" Shutting down OpenMagic..."));
2134
2098
  proxyServer.close();
2135
- omServer.close();
2136
2099
  process.exit(0);
2137
2100
  };
2138
2101
  process.on("SIGINT", shutdown);