openmagic 0.13.0 → 0.14.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/README.md CHANGED
@@ -191,6 +191,15 @@ This file is in your home directory, never in your project. It won't be committe
191
191
  - **Path sandboxing** — File operations are restricted to configured root directories. The server cannot read/write outside your project.
192
192
  - **API keys stay local** — Keys are stored in `~/.openmagic/config.json` on your machine. They are proxied through the local server and never exposed to the browser or any third party.
193
193
  - **Zero project modification** — OpenMagic never modifies your `package.json`, config files, or source code during installation. The toolbar exists only in the proxy layer.
194
+ - **Symlink protection** — File operations resolve symlinks and reject paths that escape the project root.
195
+ - **Diff preview** — AI-proposed code changes are shown as diffs with Apply/Reject buttons. Nothing is auto-applied.
196
+
197
+ ### Known Limitations
198
+
199
+ OpenMagic uses a reverse proxy which introduces these tradeoffs:
200
+
201
+ - **Origin change** — Your app runs on `localhost:3000` but is accessed via `localhost:4567`. This can affect OAuth redirects, `localStorage` isolation, and Service Worker scope. Most dev workflows are unaffected, but if your app relies on `window.location.origin`, you may need to adjust your dev config.
202
+ - **CSP meta tags** — OpenMagic strips CSP response headers to allow the toolbar script. However, CSP set via `<meta>` tags in your HTML cannot be modified and may block the toolbar on strict CSP pages.
194
203
 
195
204
  ## Comparison
196
205
 
package/dist/cli.js CHANGED
@@ -10,8 +10,6 @@ import { createInterface } from "readline";
10
10
 
11
11
  // src/proxy.ts
12
12
  import http from "http";
13
- import { gunzip, inflate, brotliDecompress } from "zlib";
14
- import { promisify } from "util";
15
13
  import httpProxy from "http-proxy";
16
14
 
17
15
  // src/security.ts
@@ -1358,7 +1356,7 @@ async function handleLlmChat(params, onChunk, onDone, onError) {
1358
1356
  }
1359
1357
 
1360
1358
  // src/server.ts
1361
- var VERSION = "0.13.0";
1359
+ var VERSION = "0.14.1";
1362
1360
  var __dirname = dirname2(fileURLToPath(import.meta.url));
1363
1361
  function attachOpenMagic(httpServer, roots) {
1364
1362
  function handleRequest(req, res) {
@@ -1603,9 +1601,6 @@ function serveToolbarBundle(res) {
1603
1601
  }
1604
1602
 
1605
1603
  // src/proxy.ts
1606
- var gunzipAsync = promisify(gunzip);
1607
- var inflateAsync = promisify(inflate);
1608
- var brotliAsync = promisify(brotliDecompress);
1609
1604
  function createProxyServer(targetHost, targetPort, roots) {
1610
1605
  const proxy = httpProxy.createProxyServer({
1611
1606
  target: `http://${targetHost}:${targetPort}`,
@@ -1613,23 +1608,19 @@ function createProxyServer(targetHost, targetPort, roots) {
1613
1608
  selfHandleResponse: true
1614
1609
  });
1615
1610
  const token = getSessionToken();
1611
+ proxy.on("proxyReq", (proxyReq) => {
1612
+ proxyReq.removeHeader("Accept-Encoding");
1613
+ });
1616
1614
  proxy.on("proxyRes", (proxyRes, req, res) => {
1617
1615
  const contentType = proxyRes.headers["content-type"] || "";
1618
1616
  const isHtml = contentType.includes("text/html");
1619
- if (!isHtml) {
1620
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
1617
+ const status = proxyRes.statusCode || 200;
1618
+ if (!isHtml && status < 400) {
1619
+ res.writeHead(status, proxyRes.headers);
1621
1620
  proxyRes.pipe(res);
1622
1621
  return;
1623
1622
  }
1624
- collectBody(proxyRes).then((body) => {
1625
- const toolbarScript = buildInjectionScript(token);
1626
- if (body.includes("</body>")) {
1627
- body = body.replace("</body>", `${toolbarScript}</body>`);
1628
- } else if (body.includes("</html>")) {
1629
- body = body.replace("</html>", `${toolbarScript}</html>`);
1630
- } else {
1631
- body += toolbarScript;
1632
- }
1623
+ if (isHtml) {
1633
1624
  const headers = { ...proxyRes.headers };
1634
1625
  delete headers["content-length"];
1635
1626
  delete headers["content-encoding"];
@@ -1640,14 +1631,26 @@ function createProxyServer(targetHost, targetPort, roots) {
1640
1631
  delete headers["etag"];
1641
1632
  delete headers["last-modified"];
1642
1633
  headers["cache-control"] = "no-store";
1643
- res.writeHead(proxyRes.statusCode || 200, headers);
1644
- res.end(body);
1645
- }).catch(() => {
1646
- try {
1647
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
1648
- res.end();
1649
- } catch {
1650
- }
1634
+ res.writeHead(status, headers);
1635
+ proxyRes.pipe(res, { end: false });
1636
+ proxyRes.on("end", () => {
1637
+ res.end(buildInjectionScript(token));
1638
+ });
1639
+ return;
1640
+ }
1641
+ const chunks = [];
1642
+ proxyRes.on("data", (c) => chunks.push(c));
1643
+ proxyRes.on("end", () => {
1644
+ const body = Buffer.concat(chunks).toString("utf-8").slice(0, 2e3);
1645
+ const toolbarScript = buildInjectionScript(token);
1646
+ res.writeHead(status, { "Content-Type": "text/html", "Cache-Control": "no-store" });
1647
+ res.end(`<html><head><meta charset="utf-8"><title>Error ${status}</title></head>
1648
+ <body style="font-family:system-ui;padding:40px;background:#0f0f1e;color:#e0e0e0;">
1649
+ <h2 style="color:#e94560;">Error ${status}</h2>
1650
+ <pre style="color:#888;white-space:pre-wrap;max-width:800px;overflow:auto;font-size:13px;">${body.replace(/</g, "&lt;")}</pre>
1651
+ <p style="color:#555;font-size:13px;">This error is from your dev server, not OpenMagic. The toolbar is available below.</p>
1652
+ ${toolbarScript}
1653
+ </body></html>`);
1651
1654
  });
1652
1655
  });
1653
1656
  proxy.on("error", (err, _req, res) => {
@@ -1678,33 +1681,6 @@ function createProxyServer(targetHost, targetPort, roots) {
1678
1681
  });
1679
1682
  return server;
1680
1683
  }
1681
- async function collectBody(stream) {
1682
- const rawBuffer = await new Promise((resolve3, reject) => {
1683
- const chunks = [];
1684
- stream.on("data", (chunk) => chunks.push(chunk));
1685
- stream.on("end", () => resolve3(Buffer.concat(chunks)));
1686
- stream.on("error", reject);
1687
- });
1688
- const encoding = (stream.headers["content-encoding"] || "").toLowerCase();
1689
- if (!encoding || encoding === "identity") {
1690
- return rawBuffer.toString("utf-8");
1691
- }
1692
- try {
1693
- let decompressed;
1694
- if (encoding === "gzip" || encoding === "x-gzip") {
1695
- decompressed = await gunzipAsync(rawBuffer);
1696
- } else if (encoding === "deflate") {
1697
- decompressed = await inflateAsync(rawBuffer);
1698
- } else if (encoding === "br") {
1699
- decompressed = await brotliAsync(rawBuffer);
1700
- } else {
1701
- return rawBuffer.toString("utf-8");
1702
- }
1703
- return decompressed.toString("utf-8");
1704
- } catch {
1705
- return rawBuffer.toString("utf-8");
1706
- }
1707
- }
1708
1684
  function buildInjectionScript(token) {
1709
1685
  return `<script src="/__openmagic__/toolbar.js?v=${Date.now()}" data-openmagic="true" data-openmagic-token="${token}" defer></script>`;
1710
1686
  }
@@ -1885,7 +1861,7 @@ process.on("uncaughtException", (err) => {
1885
1861
  process.exit(1);
1886
1862
  });
1887
1863
  var childProcesses = [];
1888
- var VERSION2 = "0.13.0";
1864
+ var VERSION2 = "0.14.1";
1889
1865
  function ask(question) {
1890
1866
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1891
1867
  return new Promise((resolve3) => {
@@ -1945,29 +1921,18 @@ function runCommand(cmd, args, cwd = process.cwd()) {
1945
1921
  }
1946
1922
  });
1947
1923
  }
1948
- async function healthCheck(proxyPort, targetPort) {
1924
+ async function healthCheck(proxyPort, _targetPort) {
1949
1925
  try {
1950
1926
  const controller = new AbortController();
1951
1927
  const timeout = setTimeout(() => controller.abort(), 5e3);
1952
- const res = await fetch(`http://127.0.0.1:${proxyPort}/`, {
1953
- signal: controller.signal,
1954
- headers: { Accept: "text/html" }
1928
+ const res = await fetch(`http://127.0.0.1:${proxyPort}/__openmagic__/health`, {
1929
+ signal: controller.signal
1955
1930
  });
1956
1931
  clearTimeout(timeout);
1957
1932
  if (res.ok) {
1958
- const text = await res.text();
1959
- if (text.includes("__OPENMAGIC_LOADED__")) {
1960
- console.log(chalk.green(" \u2713 Toolbar injection verified."));
1961
- } else {
1962
- console.log(chalk.yellow(" \u26A0 Page loaded but toolbar may not have injected (non-HTML response or CSP)."));
1963
- }
1933
+ console.log(chalk.green(" \u2713 Toolbar ready."));
1964
1934
  } else {
1965
- console.log(
1966
- chalk.yellow(` \u26A0 Dev server returned ${res.status}. Pages may have errors.`)
1967
- );
1968
- console.log(
1969
- chalk.dim(" The toolbar will still appear on pages that load successfully.")
1970
- );
1935
+ console.log(chalk.yellow(" \u26A0 Proxy started but toolbar health check failed."));
1971
1936
  }
1972
1937
  } catch {
1973
1938
  console.log(