openmagic 0.36.1 → 0.36.3

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
@@ -1532,13 +1532,21 @@ async function chatClaudeCode(messages, context, onChunk, onDone, onError) {
1532
1532
  "--output-format",
1533
1533
  "stream-json",
1534
1534
  "--verbose",
1535
+ "--include-partial-messages",
1535
1536
  "--max-turns",
1536
- "1"
1537
- // Single turn — OpenMagic manages its own retry loop
1537
+ "5"
1538
1538
  ],
1539
1539
  {
1540
1540
  stdio: ["pipe", "pipe", "pipe"],
1541
- cwd: process.cwd()
1541
+ cwd: process.cwd(),
1542
+ env: {
1543
+ ...process.env,
1544
+ // Generous timeouts — Claude may read files and think between turns
1545
+ CLAUDE_STREAM_IDLE_TIMEOUT_MS: "300000",
1546
+ // 5 min idle timeout between chunks
1547
+ API_TIMEOUT_MS: "600000"
1548
+ // 10 min overall API timeout
1549
+ }
1542
1550
  }
1543
1551
  );
1544
1552
  proc.stdin.write(`${SYSTEM_PROMPT}
@@ -1546,6 +1554,7 @@ async function chatClaudeCode(messages, context, onChunk, onDone, onError) {
1546
1554
  ${fullPrompt}`);
1547
1555
  proc.stdin.end();
1548
1556
  let fullContent = "";
1557
+ let resultContent = "";
1549
1558
  let buffer = "";
1550
1559
  let errOutput = "";
1551
1560
  proc.stdout.on("data", (data) => {
@@ -1556,6 +1565,12 @@ ${fullPrompt}`);
1556
1565
  if (!line.trim()) continue;
1557
1566
  try {
1558
1567
  const event = JSON.parse(line);
1568
+ if (event.type === "result") {
1569
+ if (typeof event.result === "string") {
1570
+ resultContent = event.result;
1571
+ }
1572
+ continue;
1573
+ }
1559
1574
  const text = extractText(event);
1560
1575
  if (text) {
1561
1576
  fullContent += text;
@@ -1581,20 +1596,22 @@ ${fullPrompt}`);
1581
1596
  if (buffer.trim()) {
1582
1597
  try {
1583
1598
  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;
1599
+ if (event.type === "result" && typeof event.result === "string") {
1600
+ resultContent = event.result;
1601
+ } else {
1602
+ const text = extractText(event);
1603
+ if (text) fullContent += text;
1588
1604
  }
1589
1605
  } catch {
1590
1606
  }
1591
1607
  }
1592
- if (code === 0 || fullContent) {
1593
- onDone({ content: fullContent });
1608
+ const finalContent = resultContent || fullContent;
1609
+ if (code === 0 || finalContent) {
1610
+ onDone({ content: finalContent });
1594
1611
  } else {
1595
1612
  const err = errOutput.trim();
1596
1613
  if (err.includes("not authenticated") || err.includes("login")) {
1597
- onError("Claude CLI is not authenticated. Run `claude login` in your terminal.");
1614
+ onError("Claude CLI is not authenticated. Run `claude` in your terminal to log in.");
1598
1615
  } else if (err.includes("ENOENT") || err.includes("not found")) {
1599
1616
  onError("Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code");
1600
1617
  } else {
@@ -1630,16 +1647,35 @@ async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1630
1647
  const fullPrompt = `${SYSTEM_PROMPT}
1631
1648
 
1632
1649
  ${buildUserMessage(userPrompt, contextParts)}`;
1633
- const proc = spawn2("codex", ["--full-auto", fullPrompt], {
1634
- stdio: ["ignore", "pipe", "pipe"],
1635
- cwd: process.cwd()
1636
- });
1650
+ const proc = spawn2(
1651
+ "codex",
1652
+ ["exec", "--full-auto", "--json", "--skip-git-repo-check", "-"],
1653
+ {
1654
+ stdio: ["pipe", "pipe", "pipe"],
1655
+ cwd: process.cwd()
1656
+ }
1657
+ );
1658
+ proc.stdin.write(fullPrompt);
1659
+ proc.stdin.end();
1637
1660
  let fullContent = "";
1661
+ let buffer = "";
1638
1662
  let errOutput = "";
1639
1663
  proc.stdout.on("data", (data) => {
1640
- const text = data.toString();
1641
- fullContent += text;
1642
- onChunk(text);
1664
+ buffer += data.toString();
1665
+ const lines = buffer.split("\n");
1666
+ buffer = lines.pop() || "";
1667
+ for (const line of lines) {
1668
+ if (!line.trim()) continue;
1669
+ try {
1670
+ const event = JSON.parse(line);
1671
+ const text = extractCodexText(event);
1672
+ if (text) {
1673
+ fullContent += text;
1674
+ onChunk(text);
1675
+ }
1676
+ } catch {
1677
+ }
1678
+ }
1643
1679
  });
1644
1680
  proc.stderr.on("data", (data) => {
1645
1681
  errOutput += data.toString();
@@ -1652,7 +1688,15 @@ ${buildUserMessage(userPrompt, contextParts)}`;
1652
1688
  }
1653
1689
  });
1654
1690
  proc.on("close", (code) => {
1655
- if (code === 0 || fullContent) {
1691
+ if (buffer.trim()) {
1692
+ try {
1693
+ const event = JSON.parse(buffer);
1694
+ const text = extractCodexText(event);
1695
+ if (text) fullContent += text;
1696
+ } catch {
1697
+ }
1698
+ }
1699
+ if (code === 0 || fullContent.trim()) {
1656
1700
  onDone({ content: fullContent });
1657
1701
  } else {
1658
1702
  const err = errOutput.trim();
@@ -1664,6 +1708,18 @@ ${buildUserMessage(userPrompt, contextParts)}`;
1664
1708
  }
1665
1709
  });
1666
1710
  }
1711
+ function extractCodexText(event) {
1712
+ if (event.type === "item.completed" || event.type === "item.updated" || event.type === "item.started") {
1713
+ const item = event.item;
1714
+ if (item?.type === "agent_message" && typeof item.text === "string") {
1715
+ return item.text;
1716
+ }
1717
+ }
1718
+ if (event.type === "error" && typeof event.message === "string") {
1719
+ return void 0;
1720
+ }
1721
+ return void 0;
1722
+ }
1667
1723
 
1668
1724
  // src/llm/gemini-cli.ts
1669
1725
  import { spawn as spawn3 } from "child_process";
@@ -1674,18 +1730,55 @@ async function chatGeminiCli(messages, context, onChunk, onDone, onError) {
1674
1730
  const fullPrompt = `${SYSTEM_PROMPT}
1675
1731
 
1676
1732
  ${buildUserMessage(userPrompt, contextParts)}`;
1677
- const proc = spawn3("gemini", [], {
1678
- stdio: ["pipe", "pipe", "pipe"],
1679
- cwd: process.cwd()
1680
- });
1681
- proc.stdin.write(fullPrompt);
1733
+ const proc = spawn3(
1734
+ "gemini",
1735
+ [
1736
+ "-p",
1737
+ userPrompt,
1738
+ "--output-format",
1739
+ "stream-json",
1740
+ "--yolo"
1741
+ ],
1742
+ {
1743
+ stdio: ["pipe", "pipe", "pipe"],
1744
+ cwd: process.cwd()
1745
+ }
1746
+ );
1747
+ proc.stdin.write(`${SYSTEM_PROMPT}
1748
+
1749
+ ${buildUserMessage("", contextParts)}`);
1682
1750
  proc.stdin.end();
1683
1751
  let fullContent = "";
1752
+ let resultContent = "";
1753
+ let buffer = "";
1684
1754
  let errOutput = "";
1685
1755
  proc.stdout.on("data", (data) => {
1686
- const text = data.toString();
1687
- fullContent += text;
1688
- onChunk(text);
1756
+ buffer += data.toString();
1757
+ const lines = buffer.split("\n");
1758
+ buffer = lines.pop() || "";
1759
+ for (const line of lines) {
1760
+ if (!line.trim()) continue;
1761
+ try {
1762
+ const event = JSON.parse(line);
1763
+ if (event.type === "result") {
1764
+ if (typeof event.result === "string") {
1765
+ resultContent = event.result;
1766
+ }
1767
+ continue;
1768
+ }
1769
+ const text = extractGeminiText(event);
1770
+ if (text) {
1771
+ fullContent += text;
1772
+ onChunk(text);
1773
+ }
1774
+ } catch {
1775
+ const trimmed = line.trim();
1776
+ if (trimmed) {
1777
+ fullContent += trimmed + "\n";
1778
+ onChunk(trimmed + "\n");
1779
+ }
1780
+ }
1781
+ }
1689
1782
  });
1690
1783
  proc.stderr.on("data", (data) => {
1691
1784
  errOutput += data.toString();
@@ -1698,18 +1791,48 @@ ${buildUserMessage(userPrompt, contextParts)}`;
1698
1791
  }
1699
1792
  });
1700
1793
  proc.on("close", (code) => {
1701
- if (code === 0 || fullContent) {
1702
- onDone({ content: fullContent });
1794
+ if (buffer.trim()) {
1795
+ try {
1796
+ const event = JSON.parse(buffer);
1797
+ if (event.type === "result" && typeof event.result === "string") {
1798
+ resultContent = event.result;
1799
+ } else {
1800
+ const text = extractGeminiText(event);
1801
+ if (text) fullContent += text;
1802
+ }
1803
+ } catch {
1804
+ const trimmed = buffer.trim();
1805
+ if (trimmed) fullContent += trimmed;
1806
+ }
1807
+ }
1808
+ const finalContent = resultContent || fullContent;
1809
+ if (code === 0 || finalContent.trim()) {
1810
+ onDone({ content: finalContent });
1703
1811
  } else {
1704
1812
  const err = errOutput.trim();
1705
- if (err.includes("auth") || err.includes("login") || err.includes("credentials")) {
1706
- onError("Gemini CLI requires Google authentication. Run `gemini auth login` in your terminal.");
1813
+ if (err.includes("auth") || err.includes("GEMINI_API_KEY") || err.includes("credentials") || err.includes("login")) {
1814
+ onError("Gemini CLI requires authentication. Set GEMINI_API_KEY in your environment, or run `gemini` interactively to log in with Google.");
1707
1815
  } else {
1708
1816
  onError(err.slice(0, 500) || `Gemini CLI exited with code ${code}`);
1709
1817
  }
1710
1818
  }
1711
1819
  });
1712
1820
  }
1821
+ function extractGeminiText(event) {
1822
+ if (event.type === "assistant" || event.type === "message") {
1823
+ const content = event.content ?? event.message?.content;
1824
+ if (Array.isArray(content)) {
1825
+ return content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("");
1826
+ }
1827
+ if (typeof content === "string") return content;
1828
+ if (typeof event.text === "string") return event.text;
1829
+ }
1830
+ if (event.type === "content_block_delta") {
1831
+ const delta = event.delta;
1832
+ if (typeof delta?.text === "string") return delta.text;
1833
+ }
1834
+ return void 0;
1835
+ }
1713
1836
 
1714
1837
  // src/llm/proxy.ts
1715
1838
  var OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set([
@@ -2863,12 +2986,49 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
2863
2986
  });
2864
2987
  }
2865
2988
  });
2989
+ let shuttingDown = false;
2866
2990
  const shutdown = () => {
2991
+ if (shuttingDown) return;
2992
+ shuttingDown = true;
2867
2993
  console.log("");
2868
2994
  console.log(chalk.dim(" Shutting down OpenMagic..."));
2869
2995
  cleanupBackups();
2870
2996
  proxyServer.close();
2871
- process.exit(0);
2997
+ for (const cp of childProcesses) {
2998
+ try {
2999
+ cp.kill("SIGTERM");
3000
+ } catch {
3001
+ }
3002
+ }
3003
+ const forceExit = setTimeout(() => {
3004
+ for (const cp of childProcesses) {
3005
+ try {
3006
+ cp.kill("SIGKILL");
3007
+ } catch {
3008
+ }
3009
+ }
3010
+ process.exit(0);
3011
+ }, 2e3);
3012
+ forceExit.unref();
3013
+ const allDead = childProcesses.every((cp) => cp.killed || cp.exitCode !== null);
3014
+ if (allDead) {
3015
+ clearTimeout(forceExit);
3016
+ process.exit(0);
3017
+ }
3018
+ let remaining = childProcesses.filter((cp) => !cp.killed && cp.exitCode === null).length;
3019
+ if (remaining === 0) {
3020
+ clearTimeout(forceExit);
3021
+ process.exit(0);
3022
+ }
3023
+ for (const cp of childProcesses) {
3024
+ cp.once("exit", () => {
3025
+ remaining--;
3026
+ if (remaining <= 0) {
3027
+ clearTimeout(forceExit);
3028
+ process.exit(0);
3029
+ }
3030
+ });
3031
+ }
2872
3032
  };
2873
3033
  process.on("SIGINT", shutdown);
2874
3034
  process.on("SIGTERM", shutdown);