openmagic 0.36.1 → 0.36.2

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
@@ -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 resolve3, join as join5 } from "path";
7
+ import { resolve as resolve3, join as join6 } from "path";
8
8
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
9
9
  import { spawn as spawn4 } from "child_process";
10
10
  import { createInterface } from "readline";
@@ -32,7 +32,7 @@ function validateToken(token) {
32
32
 
33
33
  // src/server.ts
34
34
  import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
35
- import { join as join3, dirname as dirname2 } from "path";
35
+ import { join as join4, dirname as dirname2 } from "path";
36
36
  import { fileURLToPath } from "url";
37
37
  import { WebSocketServer, WebSocket } from "ws";
38
38
 
@@ -1533,8 +1533,7 @@ async function chatClaudeCode(messages, context, onChunk, onDone, onError) {
1533
1533
  "stream-json",
1534
1534
  "--verbose",
1535
1535
  "--max-turns",
1536
- "1"
1537
- // Single turn — OpenMagic manages its own retry loop
1536
+ "5"
1538
1537
  ],
1539
1538
  {
1540
1539
  stdio: ["pipe", "pipe", "pipe"],
@@ -1546,6 +1545,7 @@ async function chatClaudeCode(messages, context, onChunk, onDone, onError) {
1546
1545
  ${fullPrompt}`);
1547
1546
  proc.stdin.end();
1548
1547
  let fullContent = "";
1548
+ let resultContent = "";
1549
1549
  let buffer = "";
1550
1550
  let errOutput = "";
1551
1551
  proc.stdout.on("data", (data) => {
@@ -1556,6 +1556,12 @@ ${fullPrompt}`);
1556
1556
  if (!line.trim()) continue;
1557
1557
  try {
1558
1558
  const event = JSON.parse(line);
1559
+ if (event.type === "result") {
1560
+ if (typeof event.result === "string") {
1561
+ resultContent = event.result;
1562
+ }
1563
+ continue;
1564
+ }
1559
1565
  const text = extractText(event);
1560
1566
  if (text) {
1561
1567
  fullContent += text;
@@ -1581,16 +1587,18 @@ ${fullPrompt}`);
1581
1587
  if (buffer.trim()) {
1582
1588
  try {
1583
1589
  const event = JSON.parse(buffer);
1584
- const text = extractText(event);
1585
- if (text) fullContent += text;
1586
- if (event.result && typeof event.result === "string") {
1587
- fullContent = event.result;
1590
+ if (event.type === "result" && typeof event.result === "string") {
1591
+ resultContent = event.result;
1592
+ } else {
1593
+ const text = extractText(event);
1594
+ if (text) fullContent += text;
1588
1595
  }
1589
1596
  } catch {
1590
1597
  }
1591
1598
  }
1592
- if (code === 0 || fullContent) {
1593
- onDone({ content: fullContent });
1599
+ const finalContent = resultContent || fullContent;
1600
+ if (code === 0 || finalContent) {
1601
+ onDone({ content: finalContent });
1594
1602
  } else {
1595
1603
  const err = errOutput.trim();
1596
1604
  if (err.includes("not authenticated") || err.includes("login")) {
@@ -1623,6 +1631,13 @@ function extractText(event) {
1623
1631
 
1624
1632
  // src/llm/codex-cli.ts
1625
1633
  import { spawn as spawn2 } from "child_process";
1634
+ import { writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
1635
+ import { join as join3 } from "path";
1636
+ import { tmpdir as tmpdir2 } from "os";
1637
+ import { randomBytes as randomBytes2 } from "crypto";
1638
+ function stripAnsi(text) {
1639
+ return text.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "");
1640
+ }
1626
1641
  async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1627
1642
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1628
1643
  const userPrompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "Help me with this element.";
@@ -1630,21 +1645,48 @@ async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1630
1645
  const fullPrompt = `${SYSTEM_PROMPT}
1631
1646
 
1632
1647
  ${buildUserMessage(userPrompt, contextParts)}`;
1633
- const proc = spawn2("codex", ["--full-auto", fullPrompt], {
1634
- stdio: ["ignore", "pipe", "pipe"],
1635
- cwd: process.cwd()
1636
- });
1648
+ const tmpFile = join3(tmpdir2(), `openmagic-codex-${randomBytes2(8).toString("hex")}.txt`);
1649
+ writeFileSync3(tmpFile, fullPrompt);
1650
+ let proc;
1651
+ if (process.platform === "darwin") {
1652
+ proc = spawn2("script", ["-q", "/dev/null", "codex", "--full-auto", "--quiet", "-"], {
1653
+ stdio: ["pipe", "pipe", "pipe"],
1654
+ cwd: process.cwd()
1655
+ });
1656
+ proc.stdin.write(fullPrompt);
1657
+ proc.stdin.end();
1658
+ } else if (process.platform === "linux") {
1659
+ proc = spawn2("script", ["-qc", `codex --full-auto --quiet -`, "/dev/null"], {
1660
+ stdio: ["pipe", "pipe", "pipe"],
1661
+ cwd: process.cwd()
1662
+ });
1663
+ proc.stdin.write(fullPrompt);
1664
+ proc.stdin.end();
1665
+ } else {
1666
+ proc = spawn2("codex", ["--full-auto", "--quiet", "-"], {
1667
+ stdio: ["pipe", "pipe", "pipe"],
1668
+ cwd: process.cwd()
1669
+ });
1670
+ proc.stdin.write(fullPrompt);
1671
+ proc.stdin.end();
1672
+ }
1637
1673
  let fullContent = "";
1638
1674
  let errOutput = "";
1639
1675
  proc.stdout.on("data", (data) => {
1640
- const text = data.toString();
1641
- fullContent += text;
1642
- onChunk(text);
1676
+ const text = stripAnsi(data.toString());
1677
+ if (text) {
1678
+ fullContent += text;
1679
+ onChunk(text);
1680
+ }
1643
1681
  });
1644
1682
  proc.stderr.on("data", (data) => {
1645
1683
  errOutput += data.toString();
1646
1684
  });
1647
1685
  proc.on("error", (err) => {
1686
+ try {
1687
+ unlinkSync2(tmpFile);
1688
+ } catch {
1689
+ }
1648
1690
  if (err.message.includes("ENOENT")) {
1649
1691
  onError("Codex CLI not found. Install it with: npm install -g @openai/codex");
1650
1692
  } else {
@@ -1652,12 +1694,18 @@ ${buildUserMessage(userPrompt, contextParts)}`;
1652
1694
  }
1653
1695
  });
1654
1696
  proc.on("close", (code) => {
1655
- if (code === 0 || fullContent) {
1697
+ try {
1698
+ unlinkSync2(tmpFile);
1699
+ } catch {
1700
+ }
1701
+ if (code === 0 || fullContent.trim()) {
1656
1702
  onDone({ content: fullContent });
1657
1703
  } else {
1658
- const err = errOutput.trim();
1704
+ const err = stripAnsi(errOutput.trim());
1659
1705
  if (err.includes("OPENAI_API_KEY") || err.includes("api key") || err.includes("unauthorized")) {
1660
1706
  onError("Codex CLI requires OPENAI_API_KEY in your environment. Set it with: export OPENAI_API_KEY=sk-...");
1707
+ } else if (err.includes("stdin is not a terminal") || err.includes("not a tty")) {
1708
+ onError("Codex CLI requires a terminal. This platform may not support the PTY wrapper. Try using OpenAI provider instead.");
1661
1709
  } else {
1662
1710
  onError(err.slice(0, 500) || `Codex CLI exited with code ${code}`);
1663
1711
  }
@@ -2099,7 +2147,7 @@ async function handleMessage(ws, msg, state, roots) {
2099
2147
  sendError(ws, "invalid_payload", "Missing pattern", msg.id);
2100
2148
  break;
2101
2149
  }
2102
- const searchRoot = payload.path ? join3(roots[0] || process.cwd(), payload.path) : roots[0] || process.cwd();
2150
+ const searchRoot = payload.path ? join4(roots[0] || process.cwd(), payload.path) : roots[0] || process.cwd();
2103
2151
  const results = grepFiles(payload.pattern, searchRoot, roots);
2104
2152
  send(ws, { id: msg.id, type: "fs.grep.result", payload: { results } });
2105
2153
  break;
@@ -2135,8 +2183,8 @@ function sendError(ws, code, message, id) {
2135
2183
  }
2136
2184
  function serveToolbarBundle(res) {
2137
2185
  const bundlePaths = [
2138
- join3(__dirname, "toolbar", "index.global.js"),
2139
- join3(__dirname, "..", "dist", "toolbar", "index.global.js")
2186
+ join4(__dirname, "toolbar", "index.global.js"),
2187
+ join4(__dirname, "..", "dist", "toolbar", "index.global.js")
2140
2188
  ];
2141
2189
  for (const bundlePath of bundlePaths) {
2142
2190
  try {
@@ -2285,7 +2333,7 @@ function buildInjectionScript(token) {
2285
2333
  // src/detect.ts
2286
2334
  import { createConnection } from "net";
2287
2335
  import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
2288
- import { join as join4, resolve as resolve2 } from "path";
2336
+ import { join as join5, resolve as resolve2 } from "path";
2289
2337
  import { execSync } from "child_process";
2290
2338
  var COMMON_DEV_PORTS = [
2291
2339
  3e3,
@@ -2442,7 +2490,7 @@ var FRAMEWORK_PATTERNS = [
2442
2490
  ];
2443
2491
  var DEV_SCRIPT_NAMES = ["dev", "start", "serve", "develop", "dev:start", "start:dev", "server", "dev:server", "web", "frontend"];
2444
2492
  function detectDevScripts(cwd = process.cwd()) {
2445
- const pkgPath = join4(cwd, "package.json");
2493
+ const pkgPath = join5(cwd, "package.json");
2446
2494
  if (!existsSync4(pkgPath)) return [];
2447
2495
  let pkg;
2448
2496
  try {
@@ -2510,7 +2558,7 @@ function checkNodeCompatibility(framework) {
2510
2558
  function checkEnvPort(cwd = process.cwd()) {
2511
2559
  const envFiles = [".env.local", ".env.development.local", ".env.development", ".env"];
2512
2560
  for (const envFile of envFiles) {
2513
- const envPath = join4(cwd, envFile);
2561
+ const envPath = join5(cwd, envFile);
2514
2562
  if (!existsSync4(envPath)) continue;
2515
2563
  try {
2516
2564
  const content = readFileSync4(envPath, "utf-8");
@@ -2530,7 +2578,7 @@ function scanParentLockfiles(projectDir) {
2530
2578
  let dir = resolve2(project, "..");
2531
2579
  while (dir.length > 1 && dir.length >= home.length) {
2532
2580
  for (const lockfile of LOCKFILE_NAMES) {
2533
- const p = join4(dir, lockfile);
2581
+ const p = join5(dir, lockfile);
2534
2582
  if (existsSync4(p)) found.push(p);
2535
2583
  }
2536
2584
  const parent = resolve2(dir, "..");
@@ -2540,7 +2588,7 @@ function scanParentLockfiles(projectDir) {
2540
2588
  return found;
2541
2589
  }
2542
2590
  function getProjectName(cwd = process.cwd()) {
2543
- const pkgPath = join4(cwd, "package.json");
2591
+ const pkgPath = join5(cwd, "package.json");
2544
2592
  if (!existsSync4(pkgPath)) return "this project";
2545
2593
  try {
2546
2594
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
@@ -2563,10 +2611,10 @@ var INSTALL_COMMANDS = {
2563
2611
  bun: "bun install"
2564
2612
  };
2565
2613
  function checkDependenciesInstalled(cwd = process.cwd()) {
2566
- const hasNodeModules = existsSync4(join4(cwd, "node_modules"));
2614
+ const hasNodeModules = existsSync4(join5(cwd, "node_modules"));
2567
2615
  let pm = "npm";
2568
2616
  for (const { file, pm: detectedPm } of LOCK_FILES) {
2569
- if (existsSync4(join4(cwd, file))) {
2617
+ if (existsSync4(join5(cwd, file))) {
2570
2618
  pm = detectedPm;
2571
2619
  break;
2572
2620
  }
@@ -2863,12 +2911,49 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
2863
2911
  });
2864
2912
  }
2865
2913
  });
2914
+ let shuttingDown = false;
2866
2915
  const shutdown = () => {
2916
+ if (shuttingDown) return;
2917
+ shuttingDown = true;
2867
2918
  console.log("");
2868
2919
  console.log(chalk.dim(" Shutting down OpenMagic..."));
2869
2920
  cleanupBackups();
2870
2921
  proxyServer.close();
2871
- process.exit(0);
2922
+ for (const cp of childProcesses) {
2923
+ try {
2924
+ cp.kill("SIGTERM");
2925
+ } catch {
2926
+ }
2927
+ }
2928
+ const forceExit = setTimeout(() => {
2929
+ for (const cp of childProcesses) {
2930
+ try {
2931
+ cp.kill("SIGKILL");
2932
+ } catch {
2933
+ }
2934
+ }
2935
+ process.exit(0);
2936
+ }, 2e3);
2937
+ forceExit.unref();
2938
+ const allDead = childProcesses.every((cp) => cp.killed || cp.exitCode !== null);
2939
+ if (allDead) {
2940
+ clearTimeout(forceExit);
2941
+ process.exit(0);
2942
+ }
2943
+ let remaining = childProcesses.filter((cp) => !cp.killed && cp.exitCode === null).length;
2944
+ if (remaining === 0) {
2945
+ clearTimeout(forceExit);
2946
+ process.exit(0);
2947
+ }
2948
+ for (const cp of childProcesses) {
2949
+ cp.once("exit", () => {
2950
+ remaining--;
2951
+ if (remaining <= 0) {
2952
+ clearTimeout(forceExit);
2953
+ process.exit(0);
2954
+ }
2955
+ });
2956
+ }
2872
2957
  };
2873
2958
  process.on("SIGINT", shutdown);
2874
2959
  process.on("SIGTERM", shutdown);
@@ -2877,7 +2962,7 @@ async function offerToStartDevServer(expectedPort) {
2877
2962
  const projectName = getProjectName();
2878
2963
  const scripts = detectDevScripts();
2879
2964
  if (scripts.length === 0) {
2880
- const htmlPath = join5(process.cwd(), "index.html");
2965
+ const htmlPath = join6(process.cwd(), "index.html");
2881
2966
  if (existsSync5(htmlPath)) {
2882
2967
  console.log(
2883
2968
  chalk.dim(" No dev scripts found, but index.html detected.")
@@ -3142,7 +3227,7 @@ async function offerToStartDevServer(expectedPort) {
3142
3227
  );
3143
3228
  console.log("");
3144
3229
  try {
3145
- const pkgPath = join5(process.cwd(), "package.json");
3230
+ const pkgPath = join6(process.cwd(), "package.json");
3146
3231
  if (existsSync5(pkgPath)) {
3147
3232
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
3148
3233
  if (pkg.engines?.node) {