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 +9 -0
- package/dist/cli.js +34 -69
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +19 -19
- package/dist/toolbar/index.global.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
1620
|
-
|
|
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
|
-
|
|
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(
|
|
1644
|
-
res
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
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, "<")}</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.
|
|
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,
|
|
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
|
-
|
|
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(
|