openmagic 0.31.5 → 0.31.7
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 +87 -22
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +1 -1
- 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";
|
|
@@ -1849,7 +1849,8 @@ function buildInjectionScript(token) {
|
|
|
1849
1849
|
// src/detect.ts
|
|
1850
1850
|
import { createConnection } from "net";
|
|
1851
1851
|
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1852
|
-
import { join as join4 } from "path";
|
|
1852
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
1853
|
+
import { execSync } from "child_process";
|
|
1853
1854
|
var COMMON_DEV_PORTS = [
|
|
1854
1855
|
3e3,
|
|
1855
1856
|
// React (CRA), Next.js, Express
|
|
@@ -1883,19 +1884,19 @@ var COMMON_DEV_PORTS = [
|
|
|
1883
1884
|
// Flask (last — macOS AirPlay also uses 5000)
|
|
1884
1885
|
];
|
|
1885
1886
|
function checkPortSingle(port, host) {
|
|
1886
|
-
return new Promise((
|
|
1887
|
+
return new Promise((resolve4) => {
|
|
1887
1888
|
const socket = createConnection({ port, host, timeout: 500 });
|
|
1888
1889
|
socket.on("connect", () => {
|
|
1889
1890
|
socket.destroy();
|
|
1890
|
-
|
|
1891
|
+
resolve4(true);
|
|
1891
1892
|
});
|
|
1892
1893
|
socket.on("error", () => {
|
|
1893
1894
|
socket.destroy();
|
|
1894
|
-
|
|
1895
|
+
resolve4(false);
|
|
1895
1896
|
});
|
|
1896
1897
|
socket.on("timeout", () => {
|
|
1897
1898
|
socket.destroy();
|
|
1898
|
-
|
|
1899
|
+
resolve4(false);
|
|
1899
1900
|
});
|
|
1900
1901
|
});
|
|
1901
1902
|
}
|
|
@@ -1907,12 +1908,45 @@ async function checkPort(port, host = "127.0.0.1") {
|
|
|
1907
1908
|
]);
|
|
1908
1909
|
return results.some(Boolean);
|
|
1909
1910
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
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);
|
|
1912
1942
|
const scriptPorts = scripts.map((s) => s.defaultPort).filter((p, i, a) => a.indexOf(p) === i);
|
|
1913
1943
|
if (scriptPorts.length > 0) {
|
|
1914
1944
|
for (const port of scriptPorts) {
|
|
1915
1945
|
if (await checkPort(port)) {
|
|
1946
|
+
const owned = verifyPortOwnership(port, cwd);
|
|
1947
|
+
if (owned === false) {
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1916
1950
|
return { port, host: "localhost", fromScripts: true };
|
|
1917
1951
|
}
|
|
1918
1952
|
}
|
|
@@ -1923,8 +1957,10 @@ async function detectDevServer() {
|
|
|
1923
1957
|
return isOpen ? port : null;
|
|
1924
1958
|
});
|
|
1925
1959
|
const results = await Promise.all(checks);
|
|
1926
|
-
const foundPort
|
|
1927
|
-
|
|
1960
|
+
for (const foundPort of results) {
|
|
1961
|
+
if (foundPort === null) continue;
|
|
1962
|
+
const owned = verifyPortOwnership(foundPort, cwd);
|
|
1963
|
+
if (owned === false) continue;
|
|
1928
1964
|
return { port: foundPort, host: "localhost", fromScripts: false };
|
|
1929
1965
|
}
|
|
1930
1966
|
return null;
|
|
@@ -1932,6 +1968,16 @@ async function detectDevServer() {
|
|
|
1932
1968
|
async function isPortOpen(port) {
|
|
1933
1969
|
return checkPort(port);
|
|
1934
1970
|
}
|
|
1971
|
+
async function findAvailablePort(startPort) {
|
|
1972
|
+
let port = startPort;
|
|
1973
|
+
while (await isPortOpen(port)) {
|
|
1974
|
+
port++;
|
|
1975
|
+
if (port > startPort + 100) {
|
|
1976
|
+
throw new Error(`Could not find an available port near ${startPort}`);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return port;
|
|
1980
|
+
}
|
|
1935
1981
|
var FRAMEWORK_PATTERNS = [
|
|
1936
1982
|
{ match: /\bnext\b/, framework: "Next.js", defaultPort: 3e3 },
|
|
1937
1983
|
{ match: /\bvite\b/, framework: "Vite", defaultPort: 5173 },
|
|
@@ -2044,27 +2090,27 @@ var lastDetectedPort = null;
|
|
|
2044
2090
|
var VERSION2 = "0.31.1";
|
|
2045
2091
|
function ask(question) {
|
|
2046
2092
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2047
|
-
return new Promise((
|
|
2093
|
+
return new Promise((resolve4) => {
|
|
2048
2094
|
rl.question(question, (answer) => {
|
|
2049
2095
|
rl.close();
|
|
2050
|
-
|
|
2096
|
+
resolve4(answer.trim());
|
|
2051
2097
|
});
|
|
2052
2098
|
});
|
|
2053
2099
|
}
|
|
2054
2100
|
function waitForPort(port, timeoutMs = 3e4, shouldAbort) {
|
|
2055
2101
|
const start = Date.now();
|
|
2056
|
-
return new Promise((
|
|
2102
|
+
return new Promise((resolve4) => {
|
|
2057
2103
|
const check = async () => {
|
|
2058
2104
|
if (shouldAbort?.()) {
|
|
2059
|
-
|
|
2105
|
+
resolve4(false);
|
|
2060
2106
|
return;
|
|
2061
2107
|
}
|
|
2062
2108
|
if (await isPortOpen(port)) {
|
|
2063
|
-
|
|
2109
|
+
resolve4(true);
|
|
2064
2110
|
return;
|
|
2065
2111
|
}
|
|
2066
2112
|
if (Date.now() - start > timeoutMs) {
|
|
2067
|
-
|
|
2113
|
+
resolve4(false);
|
|
2068
2114
|
return;
|
|
2069
2115
|
}
|
|
2070
2116
|
setTimeout(check, 500);
|
|
@@ -2073,7 +2119,7 @@ function waitForPort(port, timeoutMs = 3e4, shouldAbort) {
|
|
|
2073
2119
|
});
|
|
2074
2120
|
}
|
|
2075
2121
|
function runCommand(cmd, args, cwd = process.cwd()) {
|
|
2076
|
-
return new Promise((
|
|
2122
|
+
return new Promise((resolve4) => {
|
|
2077
2123
|
try {
|
|
2078
2124
|
const child = spawn(cmd, args, {
|
|
2079
2125
|
cwd,
|
|
@@ -2094,10 +2140,10 @@ function runCommand(cmd, args, cwd = process.cwd()) {
|
|
|
2094
2140
|
`));
|
|
2095
2141
|
}
|
|
2096
2142
|
});
|
|
2097
|
-
child.on("error", () =>
|
|
2098
|
-
child.on("close", (code) =>
|
|
2143
|
+
child.on("error", () => resolve4(false));
|
|
2144
|
+
child.on("close", (code) => resolve4(code === 0));
|
|
2099
2145
|
} catch {
|
|
2100
|
-
|
|
2146
|
+
resolve4(false);
|
|
2101
2147
|
}
|
|
2102
2148
|
});
|
|
2103
2149
|
}
|
|
@@ -2220,7 +2266,7 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
2220
2266
|
chalk.green(` \u2713 Dev server running at ${targetHost}:${targetPort}`)
|
|
2221
2267
|
);
|
|
2222
2268
|
const roots = (opts.root || [process.cwd()]).map(
|
|
2223
|
-
(r) =>
|
|
2269
|
+
(r) => resolve3(r)
|
|
2224
2270
|
);
|
|
2225
2271
|
const config = loadConfig();
|
|
2226
2272
|
saveConfig({ ...config, roots, targetPort });
|
|
@@ -2406,7 +2452,24 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
2406
2452
|
chosen = scripts[idx];
|
|
2407
2453
|
}
|
|
2408
2454
|
}
|
|
2409
|
-
|
|
2455
|
+
let port = expectedPort || chosen.defaultPort;
|
|
2456
|
+
if (await isPortOpen(port)) {
|
|
2457
|
+
const owned = verifyPortOwnership(port, process.cwd());
|
|
2458
|
+
if (owned === true) {
|
|
2459
|
+
console.log(chalk.green(` \u2713 Dev server already running on port ${port}`));
|
|
2460
|
+
lastDetectedPort = port;
|
|
2461
|
+
return true;
|
|
2462
|
+
}
|
|
2463
|
+
const altPort = await findAvailablePort(port + 1);
|
|
2464
|
+
console.log("");
|
|
2465
|
+
console.log(
|
|
2466
|
+
chalk.yellow(` \u26A0 Port ${port} is already in use by another process.`)
|
|
2467
|
+
);
|
|
2468
|
+
console.log(
|
|
2469
|
+
chalk.dim(` Starting on port ${altPort} instead.`)
|
|
2470
|
+
);
|
|
2471
|
+
port = altPort;
|
|
2472
|
+
}
|
|
2410
2473
|
console.log("");
|
|
2411
2474
|
console.log(
|
|
2412
2475
|
chalk.dim(` Starting `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.dim("...")
|
|
@@ -2544,6 +2607,8 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
2544
2607
|
for (const scanPort of [3e3, 3001, 5173, 5174, 4200, 8080, 8e3, 4e3, 1234, 4321, 3333, 8081]) {
|
|
2545
2608
|
if (scanPort === port) continue;
|
|
2546
2609
|
if (await isPortOpen(scanPort)) {
|
|
2610
|
+
const owned = verifyPortOwnership(scanPort, process.cwd());
|
|
2611
|
+
if (owned === false) continue;
|
|
2547
2612
|
console.log(
|
|
2548
2613
|
chalk.green(` \u2713 Dev server found on port ${scanPort}.`)
|
|
2549
2614
|
);
|