openmagic 0.31.4 → 0.31.6
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 +10 -9
- package/dist/cli.js +108 -22
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +44 -44
- package/dist/toolbar/index.global.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
[](https://www.typescriptlang.org/)
|
|
15
15
|
[](https://github.com/Kalmuraee/OpenMagic/pulls)
|
|
16
16
|
[](https://nodejs.org/)
|
|
17
|
+
[](https://hits.sh/github.com/Kalmuraee/OpenMagic/)
|
|
17
18
|
|
|
18
19
|
OpenMagic injects a floating AI toolbar into your running web app via reverse proxy.
|
|
19
20
|
Select any element, describe what you want, review the diff, approve — your code updates and HMR refreshes the page.
|
|
@@ -43,7 +44,7 @@ npm run dev
|
|
|
43
44
|
npx openmagic@latest
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
Run
|
|
47
|
+
Run this from your project folder so OpenMagic can find your source files and dev server. A proxied version of your app opens with the toolbar overlaid.
|
|
47
48
|
|
|
48
49
|
---
|
|
49
50
|
|
|
@@ -90,7 +91,7 @@ OpenMagic is a single-port reverse proxy. It sits between your browser and your
|
|
|
90
91
|
3. **Server** -- Local Node.js process handling file I/O and proxying LLM calls. API keys never leave your machine.
|
|
91
92
|
4. **HMR** -- When the AI modifies source files, your dev server's hot module replacement picks up changes automatically.
|
|
92
93
|
|
|
93
|
-
Stop with `Ctrl+C`.
|
|
94
|
+
Stop with `Ctrl+C`. Nothing stays behind.
|
|
94
95
|
|
|
95
96
|
---
|
|
96
97
|
|
|
@@ -181,11 +182,9 @@ npx openmagic --port 3000
|
|
|
181
182
|
|
|
182
183
|
## Known Limitations
|
|
183
184
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
- **
|
|
187
|
-
- **CSP via meta tags** -- OpenMagic strips CSP response headers to allow the toolbar script, but CSP defined in `<meta>` tags cannot be modified at the proxy level and may block the toolbar on strict pages.
|
|
188
|
-
- **Not for production** -- OpenMagic is a development tool. Do not deploy the proxy to production.
|
|
185
|
+
- **Origin change** -- Your app runs on `:3000` but you access it via `:4567`. This can break OAuth redirect URIs, `localStorage` isolation, and Service Worker scope. Most dev setups work fine, but if your app checks `window.location.origin`, you may need to adjust your dev config.
|
|
186
|
+
- **CSP via meta tags** -- OpenMagic strips CSP response headers so the toolbar script can load, but `<meta>` tag CSP can't be modified at the proxy level and may still block it.
|
|
187
|
+
- **Not for production** -- This is a dev tool. Don't deploy the proxy to production.
|
|
189
188
|
|
|
190
189
|
---
|
|
191
190
|
|
|
@@ -220,15 +219,17 @@ OpenMagic works via reverse proxy, so it supports any framework that serves HTML
|
|
|
220
219
|
|
|
221
220
|
## Contributing
|
|
222
221
|
|
|
222
|
+
PRs are welcome. Bug fixes, new providers, UI improvements, docs.
|
|
223
|
+
|
|
223
224
|
```bash
|
|
224
225
|
git clone https://github.com/Kalmuraee/OpenMagic.git
|
|
225
226
|
cd OpenMagic
|
|
226
227
|
npm install
|
|
227
228
|
npm run build
|
|
228
|
-
node dist/cli.js --port 3000
|
|
229
|
+
node dist/cli.js --port 3000 # Test with your dev server
|
|
229
230
|
```
|
|
230
231
|
|
|
231
|
-
See the
|
|
232
|
+
See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for the architecture overview, how to add providers, and PR process.
|
|
232
233
|
|
|
233
234
|
---
|
|
234
235
|
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import open from "open";
|
|
7
|
-
import { resolve as
|
|
7
|
+
import { resolve as resolve3, join as join5 } from "path";
|
|
8
8
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
9
9
|
import { spawn } from "child_process";
|
|
10
10
|
import { createInterface } from "readline";
|
|
@@ -1449,8 +1449,41 @@ async function handleLlmChat(params, onChunk, onDone, onError) {
|
|
|
1449
1449
|
}
|
|
1450
1450
|
|
|
1451
1451
|
// src/server.ts
|
|
1452
|
-
var VERSION = "0.31.
|
|
1452
|
+
var VERSION = "0.31.4";
|
|
1453
1453
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
1454
|
+
var MAX_SERVER_LOGS = 200;
|
|
1455
|
+
var serverLogs = [];
|
|
1456
|
+
function captureServerLog(level, ...args) {
|
|
1457
|
+
const msg = args.map((a) => {
|
|
1458
|
+
try {
|
|
1459
|
+
return typeof a === "object" ? JSON.stringify(a).slice(0, 500) : String(a);
|
|
1460
|
+
} catch {
|
|
1461
|
+
return String(a);
|
|
1462
|
+
}
|
|
1463
|
+
}).join(" ");
|
|
1464
|
+
serverLogs.push({ level, msg, ts: Date.now() });
|
|
1465
|
+
if (serverLogs.length > MAX_SERVER_LOGS) serverLogs.shift();
|
|
1466
|
+
}
|
|
1467
|
+
var _origLog = console.log;
|
|
1468
|
+
var _origWarn = console.warn;
|
|
1469
|
+
var _origErr = console.error;
|
|
1470
|
+
var _origInfo = console.info;
|
|
1471
|
+
console.log = (...a) => {
|
|
1472
|
+
captureServerLog("log", ...a);
|
|
1473
|
+
_origLog(...a);
|
|
1474
|
+
};
|
|
1475
|
+
console.warn = (...a) => {
|
|
1476
|
+
captureServerLog("warn", ...a);
|
|
1477
|
+
_origWarn(...a);
|
|
1478
|
+
};
|
|
1479
|
+
console.error = (...a) => {
|
|
1480
|
+
captureServerLog("error", ...a);
|
|
1481
|
+
_origErr(...a);
|
|
1482
|
+
};
|
|
1483
|
+
console.info = (...a) => {
|
|
1484
|
+
captureServerLog("info", ...a);
|
|
1485
|
+
_origInfo(...a);
|
|
1486
|
+
};
|
|
1454
1487
|
function attachOpenMagic(httpServer, roots) {
|
|
1455
1488
|
function handleRequest(req, res) {
|
|
1456
1489
|
if (!req.url?.startsWith("/__openmagic__/")) return false;
|
|
@@ -1661,6 +1694,23 @@ async function handleMessage(ws, msg, state, roots) {
|
|
|
1661
1694
|
}
|
|
1662
1695
|
break;
|
|
1663
1696
|
}
|
|
1697
|
+
case "debug.logs": {
|
|
1698
|
+
send(ws, {
|
|
1699
|
+
id: msg.id,
|
|
1700
|
+
type: "debug.logs",
|
|
1701
|
+
payload: {
|
|
1702
|
+
logs: serverLogs.slice(-100),
|
|
1703
|
+
nodeVersion: process.version,
|
|
1704
|
+
platform: process.platform,
|
|
1705
|
+
arch: process.arch,
|
|
1706
|
+
uptime: Math.round(process.uptime()),
|
|
1707
|
+
memoryMB: Math.round(process.memoryUsage().rss / 1024 / 1024),
|
|
1708
|
+
pid: process.pid,
|
|
1709
|
+
cwd: process.cwd()
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
break;
|
|
1713
|
+
}
|
|
1664
1714
|
default:
|
|
1665
1715
|
sendError(ws, "unknown_type", `Unknown message type: ${msg.type}`, msg.id);
|
|
1666
1716
|
}
|
|
@@ -1799,7 +1849,8 @@ function buildInjectionScript(token) {
|
|
|
1799
1849
|
// src/detect.ts
|
|
1800
1850
|
import { createConnection } from "net";
|
|
1801
1851
|
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1802
|
-
import { join as join4 } from "path";
|
|
1852
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
1853
|
+
import { execSync } from "child_process";
|
|
1803
1854
|
var COMMON_DEV_PORTS = [
|
|
1804
1855
|
3e3,
|
|
1805
1856
|
// React (CRA), Next.js, Express
|
|
@@ -1833,19 +1884,19 @@ var COMMON_DEV_PORTS = [
|
|
|
1833
1884
|
// Flask (last — macOS AirPlay also uses 5000)
|
|
1834
1885
|
];
|
|
1835
1886
|
function checkPortSingle(port, host) {
|
|
1836
|
-
return new Promise((
|
|
1887
|
+
return new Promise((resolve4) => {
|
|
1837
1888
|
const socket = createConnection({ port, host, timeout: 500 });
|
|
1838
1889
|
socket.on("connect", () => {
|
|
1839
1890
|
socket.destroy();
|
|
1840
|
-
|
|
1891
|
+
resolve4(true);
|
|
1841
1892
|
});
|
|
1842
1893
|
socket.on("error", () => {
|
|
1843
1894
|
socket.destroy();
|
|
1844
|
-
|
|
1895
|
+
resolve4(false);
|
|
1845
1896
|
});
|
|
1846
1897
|
socket.on("timeout", () => {
|
|
1847
1898
|
socket.destroy();
|
|
1848
|
-
|
|
1899
|
+
resolve4(false);
|
|
1849
1900
|
});
|
|
1850
1901
|
});
|
|
1851
1902
|
}
|
|
@@ -1857,12 +1908,45 @@ async function checkPort(port, host = "127.0.0.1") {
|
|
|
1857
1908
|
]);
|
|
1858
1909
|
return results.some(Boolean);
|
|
1859
1910
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1911
|
+
function verifyPortOwnership(port, expectedDir) {
|
|
1912
|
+
try {
|
|
1913
|
+
const pidOutput = execSync(`lsof -i :${port} -sTCP:LISTEN -t 2>/dev/null`, {
|
|
1914
|
+
encoding: "utf-8",
|
|
1915
|
+
timeout: 3e3
|
|
1916
|
+
}).trim();
|
|
1917
|
+
if (!pidOutput) return null;
|
|
1918
|
+
const pids = pidOutput.split("\n").map((p) => p.trim()).filter(Boolean);
|
|
1919
|
+
const expected = resolve2(expectedDir);
|
|
1920
|
+
for (const pid of pids) {
|
|
1921
|
+
try {
|
|
1922
|
+
const cwdOutput = execSync(
|
|
1923
|
+
`lsof -a -p ${pid} -d cwd -Fn 2>/dev/null | grep ^n | head -1`,
|
|
1924
|
+
{ encoding: "utf-8", timeout: 3e3 }
|
|
1925
|
+
).trim();
|
|
1926
|
+
if (!cwdOutput) continue;
|
|
1927
|
+
const processCwd = resolve2(cwdOutput.slice(1));
|
|
1928
|
+
if (processCwd === expected || expected.startsWith(processCwd) || processCwd.startsWith(expected)) {
|
|
1929
|
+
return true;
|
|
1930
|
+
}
|
|
1931
|
+
} catch {
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return false;
|
|
1936
|
+
} catch {
|
|
1937
|
+
return null;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
async function detectDevServer(cwd = process.cwd()) {
|
|
1941
|
+
const scripts = detectDevScripts(cwd);
|
|
1862
1942
|
const scriptPorts = scripts.map((s) => s.defaultPort).filter((p, i, a) => a.indexOf(p) === i);
|
|
1863
1943
|
if (scriptPorts.length > 0) {
|
|
1864
1944
|
for (const port of scriptPorts) {
|
|
1865
1945
|
if (await checkPort(port)) {
|
|
1946
|
+
const owned = verifyPortOwnership(port, cwd);
|
|
1947
|
+
if (owned === false) {
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1866
1950
|
return { port, host: "localhost", fromScripts: true };
|
|
1867
1951
|
}
|
|
1868
1952
|
}
|
|
@@ -1873,8 +1957,10 @@ async function detectDevServer() {
|
|
|
1873
1957
|
return isOpen ? port : null;
|
|
1874
1958
|
});
|
|
1875
1959
|
const results = await Promise.all(checks);
|
|
1876
|
-
const foundPort
|
|
1877
|
-
|
|
1960
|
+
for (const foundPort of results) {
|
|
1961
|
+
if (foundPort === null) continue;
|
|
1962
|
+
const owned = verifyPortOwnership(foundPort, cwd);
|
|
1963
|
+
if (owned === false) continue;
|
|
1878
1964
|
return { port: foundPort, host: "localhost", fromScripts: false };
|
|
1879
1965
|
}
|
|
1880
1966
|
return null;
|
|
@@ -1994,27 +2080,27 @@ var lastDetectedPort = null;
|
|
|
1994
2080
|
var VERSION2 = "0.31.1";
|
|
1995
2081
|
function ask(question) {
|
|
1996
2082
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1997
|
-
return new Promise((
|
|
2083
|
+
return new Promise((resolve4) => {
|
|
1998
2084
|
rl.question(question, (answer) => {
|
|
1999
2085
|
rl.close();
|
|
2000
|
-
|
|
2086
|
+
resolve4(answer.trim());
|
|
2001
2087
|
});
|
|
2002
2088
|
});
|
|
2003
2089
|
}
|
|
2004
2090
|
function waitForPort(port, timeoutMs = 3e4, shouldAbort) {
|
|
2005
2091
|
const start = Date.now();
|
|
2006
|
-
return new Promise((
|
|
2092
|
+
return new Promise((resolve4) => {
|
|
2007
2093
|
const check = async () => {
|
|
2008
2094
|
if (shouldAbort?.()) {
|
|
2009
|
-
|
|
2095
|
+
resolve4(false);
|
|
2010
2096
|
return;
|
|
2011
2097
|
}
|
|
2012
2098
|
if (await isPortOpen(port)) {
|
|
2013
|
-
|
|
2099
|
+
resolve4(true);
|
|
2014
2100
|
return;
|
|
2015
2101
|
}
|
|
2016
2102
|
if (Date.now() - start > timeoutMs) {
|
|
2017
|
-
|
|
2103
|
+
resolve4(false);
|
|
2018
2104
|
return;
|
|
2019
2105
|
}
|
|
2020
2106
|
setTimeout(check, 500);
|
|
@@ -2023,7 +2109,7 @@ function waitForPort(port, timeoutMs = 3e4, shouldAbort) {
|
|
|
2023
2109
|
});
|
|
2024
2110
|
}
|
|
2025
2111
|
function runCommand(cmd, args, cwd = process.cwd()) {
|
|
2026
|
-
return new Promise((
|
|
2112
|
+
return new Promise((resolve4) => {
|
|
2027
2113
|
try {
|
|
2028
2114
|
const child = spawn(cmd, args, {
|
|
2029
2115
|
cwd,
|
|
@@ -2044,10 +2130,10 @@ function runCommand(cmd, args, cwd = process.cwd()) {
|
|
|
2044
2130
|
`));
|
|
2045
2131
|
}
|
|
2046
2132
|
});
|
|
2047
|
-
child.on("error", () =>
|
|
2048
|
-
child.on("close", (code) =>
|
|
2133
|
+
child.on("error", () => resolve4(false));
|
|
2134
|
+
child.on("close", (code) => resolve4(code === 0));
|
|
2049
2135
|
} catch {
|
|
2050
|
-
|
|
2136
|
+
resolve4(false);
|
|
2051
2137
|
}
|
|
2052
2138
|
});
|
|
2053
2139
|
}
|
|
@@ -2170,7 +2256,7 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
2170
2256
|
chalk.green(` \u2713 Dev server running at ${targetHost}:${targetPort}`)
|
|
2171
2257
|
);
|
|
2172
2258
|
const roots = (opts.root || [process.cwd()]).map(
|
|
2173
|
-
(r) =>
|
|
2259
|
+
(r) => resolve3(r)
|
|
2174
2260
|
);
|
|
2175
2261
|
const config = loadConfig();
|
|
2176
2262
|
saveConfig({ ...config, roots, targetPort });
|