openmagic 0.36.0 → 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
 
@@ -1531,9 +1531,9 @@ async function chatClaudeCode(messages, context, onChunk, onDone, onError) {
1531
1531
  "-p",
1532
1532
  "--output-format",
1533
1533
  "stream-json",
1534
+ "--verbose",
1534
1535
  "--max-turns",
1535
- "1"
1536
- // Single turn — OpenMagic manages its own retry loop
1536
+ "5"
1537
1537
  ],
1538
1538
  {
1539
1539
  stdio: ["pipe", "pipe", "pipe"],
@@ -1545,6 +1545,7 @@ async function chatClaudeCode(messages, context, onChunk, onDone, onError) {
1545
1545
  ${fullPrompt}`);
1546
1546
  proc.stdin.end();
1547
1547
  let fullContent = "";
1548
+ let resultContent = "";
1548
1549
  let buffer = "";
1549
1550
  let errOutput = "";
1550
1551
  proc.stdout.on("data", (data) => {
@@ -1555,6 +1556,12 @@ ${fullPrompt}`);
1555
1556
  if (!line.trim()) continue;
1556
1557
  try {
1557
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
+ }
1558
1565
  const text = extractText(event);
1559
1566
  if (text) {
1560
1567
  fullContent += text;
@@ -1580,16 +1587,18 @@ ${fullPrompt}`);
1580
1587
  if (buffer.trim()) {
1581
1588
  try {
1582
1589
  const event = JSON.parse(buffer);
1583
- const text = extractText(event);
1584
- if (text) fullContent += text;
1585
- if (event.result && typeof event.result === "string") {
1586
- 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;
1587
1595
  }
1588
1596
  } catch {
1589
1597
  }
1590
1598
  }
1591
- if (code === 0 || fullContent) {
1592
- onDone({ content: fullContent });
1599
+ const finalContent = resultContent || fullContent;
1600
+ if (code === 0 || finalContent) {
1601
+ onDone({ content: finalContent });
1593
1602
  } else {
1594
1603
  const err = errOutput.trim();
1595
1604
  if (err.includes("not authenticated") || err.includes("login")) {
@@ -1622,6 +1631,13 @@ function extractText(event) {
1622
1631
 
1623
1632
  // src/llm/codex-cli.ts
1624
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
+ }
1625
1641
  async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1626
1642
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1627
1643
  const userPrompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "Help me with this element.";
@@ -1629,24 +1645,48 @@ async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1629
1645
  const fullPrompt = `${SYSTEM_PROMPT}
1630
1646
 
1631
1647
  ${buildUserMessage(userPrompt, contextParts)}`;
1632
- const proc = spawn2("codex", ["-q"], {
1633
- stdio: ["pipe", "pipe", "pipe"],
1634
- cwd: process.cwd(),
1635
- env: { ...process.env, CODEX_QUIET_MODE: "1" }
1636
- });
1637
- proc.stdin.write(fullPrompt);
1638
- proc.stdin.end();
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
+ }
1639
1673
  let fullContent = "";
1640
1674
  let errOutput = "";
1641
1675
  proc.stdout.on("data", (data) => {
1642
- const text = data.toString();
1643
- fullContent += text;
1644
- onChunk(text);
1676
+ const text = stripAnsi(data.toString());
1677
+ if (text) {
1678
+ fullContent += text;
1679
+ onChunk(text);
1680
+ }
1645
1681
  });
1646
1682
  proc.stderr.on("data", (data) => {
1647
1683
  errOutput += data.toString();
1648
1684
  });
1649
1685
  proc.on("error", (err) => {
1686
+ try {
1687
+ unlinkSync2(tmpFile);
1688
+ } catch {
1689
+ }
1650
1690
  if (err.message.includes("ENOENT")) {
1651
1691
  onError("Codex CLI not found. Install it with: npm install -g @openai/codex");
1652
1692
  } else {
@@ -1654,12 +1694,18 @@ ${buildUserMessage(userPrompt, contextParts)}`;
1654
1694
  }
1655
1695
  });
1656
1696
  proc.on("close", (code) => {
1657
- if (code === 0 || fullContent) {
1697
+ try {
1698
+ unlinkSync2(tmpFile);
1699
+ } catch {
1700
+ }
1701
+ if (code === 0 || fullContent.trim()) {
1658
1702
  onDone({ content: fullContent });
1659
1703
  } else {
1660
- const err = errOutput.trim();
1704
+ const err = stripAnsi(errOutput.trim());
1661
1705
  if (err.includes("OPENAI_API_KEY") || err.includes("api key") || err.includes("unauthorized")) {
1662
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.");
1663
1709
  } else {
1664
1710
  onError(err.slice(0, 500) || `Codex CLI exited with code ${code}`);
1665
1711
  }
@@ -2101,7 +2147,7 @@ async function handleMessage(ws, msg, state, roots) {
2101
2147
  sendError(ws, "invalid_payload", "Missing pattern", msg.id);
2102
2148
  break;
2103
2149
  }
2104
- 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();
2105
2151
  const results = grepFiles(payload.pattern, searchRoot, roots);
2106
2152
  send(ws, { id: msg.id, type: "fs.grep.result", payload: { results } });
2107
2153
  break;
@@ -2137,8 +2183,8 @@ function sendError(ws, code, message, id) {
2137
2183
  }
2138
2184
  function serveToolbarBundle(res) {
2139
2185
  const bundlePaths = [
2140
- join3(__dirname, "toolbar", "index.global.js"),
2141
- join3(__dirname, "..", "dist", "toolbar", "index.global.js")
2186
+ join4(__dirname, "toolbar", "index.global.js"),
2187
+ join4(__dirname, "..", "dist", "toolbar", "index.global.js")
2142
2188
  ];
2143
2189
  for (const bundlePath of bundlePaths) {
2144
2190
  try {
@@ -2287,7 +2333,7 @@ function buildInjectionScript(token) {
2287
2333
  // src/detect.ts
2288
2334
  import { createConnection } from "net";
2289
2335
  import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
2290
- import { join as join4, resolve as resolve2 } from "path";
2336
+ import { join as join5, resolve as resolve2 } from "path";
2291
2337
  import { execSync } from "child_process";
2292
2338
  var COMMON_DEV_PORTS = [
2293
2339
  3e3,
@@ -2444,7 +2490,7 @@ var FRAMEWORK_PATTERNS = [
2444
2490
  ];
2445
2491
  var DEV_SCRIPT_NAMES = ["dev", "start", "serve", "develop", "dev:start", "start:dev", "server", "dev:server", "web", "frontend"];
2446
2492
  function detectDevScripts(cwd = process.cwd()) {
2447
- const pkgPath = join4(cwd, "package.json");
2493
+ const pkgPath = join5(cwd, "package.json");
2448
2494
  if (!existsSync4(pkgPath)) return [];
2449
2495
  let pkg;
2450
2496
  try {
@@ -2512,7 +2558,7 @@ function checkNodeCompatibility(framework) {
2512
2558
  function checkEnvPort(cwd = process.cwd()) {
2513
2559
  const envFiles = [".env.local", ".env.development.local", ".env.development", ".env"];
2514
2560
  for (const envFile of envFiles) {
2515
- const envPath = join4(cwd, envFile);
2561
+ const envPath = join5(cwd, envFile);
2516
2562
  if (!existsSync4(envPath)) continue;
2517
2563
  try {
2518
2564
  const content = readFileSync4(envPath, "utf-8");
@@ -2532,7 +2578,7 @@ function scanParentLockfiles(projectDir) {
2532
2578
  let dir = resolve2(project, "..");
2533
2579
  while (dir.length > 1 && dir.length >= home.length) {
2534
2580
  for (const lockfile of LOCKFILE_NAMES) {
2535
- const p = join4(dir, lockfile);
2581
+ const p = join5(dir, lockfile);
2536
2582
  if (existsSync4(p)) found.push(p);
2537
2583
  }
2538
2584
  const parent = resolve2(dir, "..");
@@ -2542,7 +2588,7 @@ function scanParentLockfiles(projectDir) {
2542
2588
  return found;
2543
2589
  }
2544
2590
  function getProjectName(cwd = process.cwd()) {
2545
- const pkgPath = join4(cwd, "package.json");
2591
+ const pkgPath = join5(cwd, "package.json");
2546
2592
  if (!existsSync4(pkgPath)) return "this project";
2547
2593
  try {
2548
2594
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
@@ -2565,10 +2611,10 @@ var INSTALL_COMMANDS = {
2565
2611
  bun: "bun install"
2566
2612
  };
2567
2613
  function checkDependenciesInstalled(cwd = process.cwd()) {
2568
- const hasNodeModules = existsSync4(join4(cwd, "node_modules"));
2614
+ const hasNodeModules = existsSync4(join5(cwd, "node_modules"));
2569
2615
  let pm = "npm";
2570
2616
  for (const { file, pm: detectedPm } of LOCK_FILES) {
2571
- if (existsSync4(join4(cwd, file))) {
2617
+ if (existsSync4(join5(cwd, file))) {
2572
2618
  pm = detectedPm;
2573
2619
  break;
2574
2620
  }
@@ -2865,12 +2911,49 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
2865
2911
  });
2866
2912
  }
2867
2913
  });
2914
+ let shuttingDown = false;
2868
2915
  const shutdown = () => {
2916
+ if (shuttingDown) return;
2917
+ shuttingDown = true;
2869
2918
  console.log("");
2870
2919
  console.log(chalk.dim(" Shutting down OpenMagic..."));
2871
2920
  cleanupBackups();
2872
2921
  proxyServer.close();
2873
- 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
+ }
2874
2957
  };
2875
2958
  process.on("SIGINT", shutdown);
2876
2959
  process.on("SIGTERM", shutdown);
@@ -2879,7 +2962,7 @@ async function offerToStartDevServer(expectedPort) {
2879
2962
  const projectName = getProjectName();
2880
2963
  const scripts = detectDevScripts();
2881
2964
  if (scripts.length === 0) {
2882
- const htmlPath = join5(process.cwd(), "index.html");
2965
+ const htmlPath = join6(process.cwd(), "index.html");
2883
2966
  if (existsSync5(htmlPath)) {
2884
2967
  console.log(
2885
2968
  chalk.dim(" No dev scripts found, but index.html detected.")
@@ -3144,7 +3227,7 @@ async function offerToStartDevServer(expectedPort) {
3144
3227
  );
3145
3228
  console.log("");
3146
3229
  try {
3147
- const pkgPath = join5(process.cwd(), "package.json");
3230
+ const pkgPath = join6(process.cwd(), "package.json");
3148
3231
  if (existsSync5(pkgPath)) {
3149
3232
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
3150
3233
  if (pkg.engines?.node) {