openmagic 0.36.2 → 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
@@ -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 join6 } from "path";
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 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 join4, dirname as dirname2 } from "path";
35
+ import { join as join3, dirname as dirname2 } from "path";
36
36
  import { fileURLToPath } from "url";
37
37
  import { WebSocketServer, WebSocket } from "ws";
38
38
 
@@ -1532,12 +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
1537
  "5"
1537
1538
  ],
1538
1539
  {
1539
1540
  stdio: ["pipe", "pipe", "pipe"],
1540
- 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
+ }
1541
1550
  }
1542
1551
  );
1543
1552
  proc.stdin.write(`${SYSTEM_PROMPT}
@@ -1602,7 +1611,7 @@ ${fullPrompt}`);
1602
1611
  } else {
1603
1612
  const err = errOutput.trim();
1604
1613
  if (err.includes("not authenticated") || err.includes("login")) {
1605
- 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.");
1606
1615
  } else if (err.includes("ENOENT") || err.includes("not found")) {
1607
1616
  onError("Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code");
1608
1617
  } else {
@@ -1631,13 +1640,6 @@ function extractText(event) {
1631
1640
 
1632
1641
  // src/llm/codex-cli.ts
1633
1642
  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
- }
1641
1643
  async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1642
1644
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1643
1645
  const userPrompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "Help me with this element.";
@@ -1645,48 +1647,40 @@ async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1645
1647
  const fullPrompt = `${SYSTEM_PROMPT}
1646
1648
 
1647
1649
  ${buildUserMessage(userPrompt, contextParts)}`;
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", "-"], {
1650
+ const proc = spawn2(
1651
+ "codex",
1652
+ ["exec", "--full-auto", "--json", "--skip-git-repo-check", "-"],
1653
+ {
1667
1654
  stdio: ["pipe", "pipe", "pipe"],
1668
1655
  cwd: process.cwd()
1669
- });
1670
- proc.stdin.write(fullPrompt);
1671
- proc.stdin.end();
1672
- }
1656
+ }
1657
+ );
1658
+ proc.stdin.write(fullPrompt);
1659
+ proc.stdin.end();
1673
1660
  let fullContent = "";
1661
+ let buffer = "";
1674
1662
  let errOutput = "";
1675
1663
  proc.stdout.on("data", (data) => {
1676
- const text = stripAnsi(data.toString());
1677
- if (text) {
1678
- fullContent += text;
1679
- 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
+ }
1680
1678
  }
1681
1679
  });
1682
1680
  proc.stderr.on("data", (data) => {
1683
1681
  errOutput += data.toString();
1684
1682
  });
1685
1683
  proc.on("error", (err) => {
1686
- try {
1687
- unlinkSync2(tmpFile);
1688
- } catch {
1689
- }
1690
1684
  if (err.message.includes("ENOENT")) {
1691
1685
  onError("Codex CLI not found. Install it with: npm install -g @openai/codex");
1692
1686
  } else {
@@ -1694,24 +1688,38 @@ ${buildUserMessage(userPrompt, contextParts)}`;
1694
1688
  }
1695
1689
  });
1696
1690
  proc.on("close", (code) => {
1697
- try {
1698
- unlinkSync2(tmpFile);
1699
- } catch {
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
+ }
1700
1698
  }
1701
1699
  if (code === 0 || fullContent.trim()) {
1702
1700
  onDone({ content: fullContent });
1703
1701
  } else {
1704
- const err = stripAnsi(errOutput.trim());
1702
+ const err = errOutput.trim();
1705
1703
  if (err.includes("OPENAI_API_KEY") || err.includes("api key") || err.includes("unauthorized")) {
1706
1704
  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.");
1709
1705
  } else {
1710
1706
  onError(err.slice(0, 500) || `Codex CLI exited with code ${code}`);
1711
1707
  }
1712
1708
  }
1713
1709
  });
1714
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
+ }
1715
1723
 
1716
1724
  // src/llm/gemini-cli.ts
1717
1725
  import { spawn as spawn3 } from "child_process";
@@ -1722,18 +1730,55 @@ async function chatGeminiCli(messages, context, onChunk, onDone, onError) {
1722
1730
  const fullPrompt = `${SYSTEM_PROMPT}
1723
1731
 
1724
1732
  ${buildUserMessage(userPrompt, contextParts)}`;
1725
- const proc = spawn3("gemini", [], {
1726
- stdio: ["pipe", "pipe", "pipe"],
1727
- cwd: process.cwd()
1728
- });
1729
- 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)}`);
1730
1750
  proc.stdin.end();
1731
1751
  let fullContent = "";
1752
+ let resultContent = "";
1753
+ let buffer = "";
1732
1754
  let errOutput = "";
1733
1755
  proc.stdout.on("data", (data) => {
1734
- const text = data.toString();
1735
- fullContent += text;
1736
- 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
+ }
1737
1782
  });
1738
1783
  proc.stderr.on("data", (data) => {
1739
1784
  errOutput += data.toString();
@@ -1746,18 +1791,48 @@ ${buildUserMessage(userPrompt, contextParts)}`;
1746
1791
  }
1747
1792
  });
1748
1793
  proc.on("close", (code) => {
1749
- if (code === 0 || fullContent) {
1750
- 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 });
1751
1811
  } else {
1752
1812
  const err = errOutput.trim();
1753
- if (err.includes("auth") || err.includes("login") || err.includes("credentials")) {
1754
- 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.");
1755
1815
  } else {
1756
1816
  onError(err.slice(0, 500) || `Gemini CLI exited with code ${code}`);
1757
1817
  }
1758
1818
  }
1759
1819
  });
1760
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
+ }
1761
1836
 
1762
1837
  // src/llm/proxy.ts
1763
1838
  var OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set([
@@ -2147,7 +2222,7 @@ async function handleMessage(ws, msg, state, roots) {
2147
2222
  sendError(ws, "invalid_payload", "Missing pattern", msg.id);
2148
2223
  break;
2149
2224
  }
2150
- const searchRoot = payload.path ? join4(roots[0] || process.cwd(), payload.path) : roots[0] || process.cwd();
2225
+ const searchRoot = payload.path ? join3(roots[0] || process.cwd(), payload.path) : roots[0] || process.cwd();
2151
2226
  const results = grepFiles(payload.pattern, searchRoot, roots);
2152
2227
  send(ws, { id: msg.id, type: "fs.grep.result", payload: { results } });
2153
2228
  break;
@@ -2183,8 +2258,8 @@ function sendError(ws, code, message, id) {
2183
2258
  }
2184
2259
  function serveToolbarBundle(res) {
2185
2260
  const bundlePaths = [
2186
- join4(__dirname, "toolbar", "index.global.js"),
2187
- join4(__dirname, "..", "dist", "toolbar", "index.global.js")
2261
+ join3(__dirname, "toolbar", "index.global.js"),
2262
+ join3(__dirname, "..", "dist", "toolbar", "index.global.js")
2188
2263
  ];
2189
2264
  for (const bundlePath of bundlePaths) {
2190
2265
  try {
@@ -2333,7 +2408,7 @@ function buildInjectionScript(token) {
2333
2408
  // src/detect.ts
2334
2409
  import { createConnection } from "net";
2335
2410
  import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
2336
- import { join as join5, resolve as resolve2 } from "path";
2411
+ import { join as join4, resolve as resolve2 } from "path";
2337
2412
  import { execSync } from "child_process";
2338
2413
  var COMMON_DEV_PORTS = [
2339
2414
  3e3,
@@ -2490,7 +2565,7 @@ var FRAMEWORK_PATTERNS = [
2490
2565
  ];
2491
2566
  var DEV_SCRIPT_NAMES = ["dev", "start", "serve", "develop", "dev:start", "start:dev", "server", "dev:server", "web", "frontend"];
2492
2567
  function detectDevScripts(cwd = process.cwd()) {
2493
- const pkgPath = join5(cwd, "package.json");
2568
+ const pkgPath = join4(cwd, "package.json");
2494
2569
  if (!existsSync4(pkgPath)) return [];
2495
2570
  let pkg;
2496
2571
  try {
@@ -2558,7 +2633,7 @@ function checkNodeCompatibility(framework) {
2558
2633
  function checkEnvPort(cwd = process.cwd()) {
2559
2634
  const envFiles = [".env.local", ".env.development.local", ".env.development", ".env"];
2560
2635
  for (const envFile of envFiles) {
2561
- const envPath = join5(cwd, envFile);
2636
+ const envPath = join4(cwd, envFile);
2562
2637
  if (!existsSync4(envPath)) continue;
2563
2638
  try {
2564
2639
  const content = readFileSync4(envPath, "utf-8");
@@ -2578,7 +2653,7 @@ function scanParentLockfiles(projectDir) {
2578
2653
  let dir = resolve2(project, "..");
2579
2654
  while (dir.length > 1 && dir.length >= home.length) {
2580
2655
  for (const lockfile of LOCKFILE_NAMES) {
2581
- const p = join5(dir, lockfile);
2656
+ const p = join4(dir, lockfile);
2582
2657
  if (existsSync4(p)) found.push(p);
2583
2658
  }
2584
2659
  const parent = resolve2(dir, "..");
@@ -2588,7 +2663,7 @@ function scanParentLockfiles(projectDir) {
2588
2663
  return found;
2589
2664
  }
2590
2665
  function getProjectName(cwd = process.cwd()) {
2591
- const pkgPath = join5(cwd, "package.json");
2666
+ const pkgPath = join4(cwd, "package.json");
2592
2667
  if (!existsSync4(pkgPath)) return "this project";
2593
2668
  try {
2594
2669
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
@@ -2611,10 +2686,10 @@ var INSTALL_COMMANDS = {
2611
2686
  bun: "bun install"
2612
2687
  };
2613
2688
  function checkDependenciesInstalled(cwd = process.cwd()) {
2614
- const hasNodeModules = existsSync4(join5(cwd, "node_modules"));
2689
+ const hasNodeModules = existsSync4(join4(cwd, "node_modules"));
2615
2690
  let pm = "npm";
2616
2691
  for (const { file, pm: detectedPm } of LOCK_FILES) {
2617
- if (existsSync4(join5(cwd, file))) {
2692
+ if (existsSync4(join4(cwd, file))) {
2618
2693
  pm = detectedPm;
2619
2694
  break;
2620
2695
  }
@@ -2962,7 +3037,7 @@ async function offerToStartDevServer(expectedPort) {
2962
3037
  const projectName = getProjectName();
2963
3038
  const scripts = detectDevScripts();
2964
3039
  if (scripts.length === 0) {
2965
- const htmlPath = join6(process.cwd(), "index.html");
3040
+ const htmlPath = join5(process.cwd(), "index.html");
2966
3041
  if (existsSync5(htmlPath)) {
2967
3042
  console.log(
2968
3043
  chalk.dim(" No dev scripts found, but index.html detected.")
@@ -3227,7 +3302,7 @@ async function offerToStartDevServer(expectedPort) {
3227
3302
  );
3228
3303
  console.log("");
3229
3304
  try {
3230
- const pkgPath = join6(process.cwd(), "package.json");
3305
+ const pkgPath = join5(process.cwd(), "package.json");
3231
3306
  if (existsSync5(pkgPath)) {
3232
3307
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
3233
3308
  if (pkg.engines?.node) {