openmagic 0.35.1 → 0.36.0

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
@@ -6,7 +6,7 @@ import chalk from "chalk";
6
6
  import open from "open";
7
7
  import { resolve as resolve3, join as join5 } from "path";
8
8
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
9
- import { spawn } from "child_process";
9
+ import { spawn as spawn4 } from "child_process";
10
10
  import { createInterface } from "readline";
11
11
 
12
12
  // src/proxy.ts
@@ -326,6 +326,57 @@ function getProjectTree(roots) {
326
326
 
327
327
  // src/llm/registry.ts
328
328
  var MODEL_REGISTRY = {
329
+ // ─── Claude Code (CLI) ────────────────────────────────────────
330
+ "claude-code": {
331
+ name: "Claude Code (CLI)",
332
+ models: [
333
+ {
334
+ id: "claude-code",
335
+ name: "Claude Code",
336
+ vision: false,
337
+ context: 2e5,
338
+ maxOutput: 64e3
339
+ }
340
+ ],
341
+ apiBase: "",
342
+ keyPrefix: "",
343
+ keyPlaceholder: "not required",
344
+ local: true
345
+ },
346
+ // ─── Codex CLI ────────────────────────────────────────────────
347
+ "codex-cli": {
348
+ name: "Codex CLI",
349
+ models: [
350
+ {
351
+ id: "codex-cli",
352
+ name: "Codex CLI",
353
+ vision: false,
354
+ context: 192e3,
355
+ maxOutput: 1e5
356
+ }
357
+ ],
358
+ apiBase: "",
359
+ keyPrefix: "",
360
+ keyPlaceholder: "not required",
361
+ local: true
362
+ },
363
+ // ─── Gemini CLI ───────────────────────────────────────────────
364
+ "gemini-cli": {
365
+ name: "Gemini CLI",
366
+ models: [
367
+ {
368
+ id: "gemini-cli",
369
+ name: "Gemini CLI",
370
+ vision: false,
371
+ context: 1048576,
372
+ maxOutput: 65536
373
+ }
374
+ ],
375
+ apiBase: "",
376
+ keyPrefix: "",
377
+ keyPlaceholder: "not required",
378
+ local: true
379
+ },
329
380
  // ─── OpenAI ───────────────────────────────────────────────────
330
381
  openai: {
331
382
  name: "OpenAI",
@@ -1467,6 +1518,201 @@ async function chatGoogle(model, apiKey, messages, context, onChunk, onDone, onE
1467
1518
  }
1468
1519
  }
1469
1520
 
1521
+ // src/llm/claude-code.ts
1522
+ import { spawn } from "child_process";
1523
+ async function chatClaudeCode(messages, context, onChunk, onDone, onError) {
1524
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1525
+ const userPrompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "Help me with this element.";
1526
+ const contextParts = buildContextParts(context);
1527
+ const fullPrompt = buildUserMessage(userPrompt, contextParts);
1528
+ const proc = spawn(
1529
+ "claude",
1530
+ [
1531
+ "-p",
1532
+ "--output-format",
1533
+ "stream-json",
1534
+ "--max-turns",
1535
+ "1"
1536
+ // Single turn — OpenMagic manages its own retry loop
1537
+ ],
1538
+ {
1539
+ stdio: ["pipe", "pipe", "pipe"],
1540
+ cwd: process.cwd()
1541
+ }
1542
+ );
1543
+ proc.stdin.write(`${SYSTEM_PROMPT}
1544
+
1545
+ ${fullPrompt}`);
1546
+ proc.stdin.end();
1547
+ let fullContent = "";
1548
+ let buffer = "";
1549
+ let errOutput = "";
1550
+ proc.stdout.on("data", (data) => {
1551
+ buffer += data.toString();
1552
+ const lines = buffer.split("\n");
1553
+ buffer = lines.pop() || "";
1554
+ for (const line of lines) {
1555
+ if (!line.trim()) continue;
1556
+ try {
1557
+ const event = JSON.parse(line);
1558
+ const text = extractText(event);
1559
+ if (text) {
1560
+ fullContent += text;
1561
+ onChunk(text);
1562
+ }
1563
+ } catch {
1564
+ }
1565
+ }
1566
+ });
1567
+ proc.stderr.on("data", (data) => {
1568
+ errOutput += data.toString();
1569
+ });
1570
+ proc.on("error", (err) => {
1571
+ if (err.message.includes("ENOENT")) {
1572
+ onError(
1573
+ "Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code"
1574
+ );
1575
+ } else {
1576
+ onError(`Claude CLI error: ${err.message}`);
1577
+ }
1578
+ });
1579
+ proc.on("close", (code) => {
1580
+ if (buffer.trim()) {
1581
+ try {
1582
+ 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;
1587
+ }
1588
+ } catch {
1589
+ }
1590
+ }
1591
+ if (code === 0 || fullContent) {
1592
+ onDone({ content: fullContent });
1593
+ } else {
1594
+ const err = errOutput.trim();
1595
+ if (err.includes("not authenticated") || err.includes("login")) {
1596
+ onError("Claude CLI is not authenticated. Run `claude login` in your terminal.");
1597
+ } else if (err.includes("ENOENT") || err.includes("not found")) {
1598
+ onError("Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code");
1599
+ } else {
1600
+ onError(err.slice(0, 500) || `Claude CLI exited with code ${code}`);
1601
+ }
1602
+ }
1603
+ });
1604
+ }
1605
+ function extractText(event) {
1606
+ if (event.type === "assistant") {
1607
+ const msg = event.message;
1608
+ if (msg?.content) {
1609
+ if (Array.isArray(msg.content)) {
1610
+ return msg.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("");
1611
+ }
1612
+ if (typeof msg.content === "string") return msg.content;
1613
+ }
1614
+ if (typeof event.text === "string") return event.text;
1615
+ }
1616
+ if (event.type === "content_block_delta") {
1617
+ const delta = event.delta;
1618
+ if (typeof delta?.text === "string") return delta.text;
1619
+ }
1620
+ return void 0;
1621
+ }
1622
+
1623
+ // src/llm/codex-cli.ts
1624
+ import { spawn as spawn2 } from "child_process";
1625
+ async function chatCodexCli(messages, context, onChunk, onDone, onError) {
1626
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1627
+ const userPrompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "Help me with this element.";
1628
+ const contextParts = buildContextParts(context);
1629
+ const fullPrompt = `${SYSTEM_PROMPT}
1630
+
1631
+ ${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();
1639
+ let fullContent = "";
1640
+ let errOutput = "";
1641
+ proc.stdout.on("data", (data) => {
1642
+ const text = data.toString();
1643
+ fullContent += text;
1644
+ onChunk(text);
1645
+ });
1646
+ proc.stderr.on("data", (data) => {
1647
+ errOutput += data.toString();
1648
+ });
1649
+ proc.on("error", (err) => {
1650
+ if (err.message.includes("ENOENT")) {
1651
+ onError("Codex CLI not found. Install it with: npm install -g @openai/codex");
1652
+ } else {
1653
+ onError(`Codex CLI error: ${err.message}`);
1654
+ }
1655
+ });
1656
+ proc.on("close", (code) => {
1657
+ if (code === 0 || fullContent) {
1658
+ onDone({ content: fullContent });
1659
+ } else {
1660
+ const err = errOutput.trim();
1661
+ if (err.includes("OPENAI_API_KEY") || err.includes("api key") || err.includes("unauthorized")) {
1662
+ onError("Codex CLI requires OPENAI_API_KEY in your environment. Set it with: export OPENAI_API_KEY=sk-...");
1663
+ } else {
1664
+ onError(err.slice(0, 500) || `Codex CLI exited with code ${code}`);
1665
+ }
1666
+ }
1667
+ });
1668
+ }
1669
+
1670
+ // src/llm/gemini-cli.ts
1671
+ import { spawn as spawn3 } from "child_process";
1672
+ async function chatGeminiCli(messages, context, onChunk, onDone, onError) {
1673
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1674
+ const userPrompt = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "Help me with this element.";
1675
+ const contextParts = buildContextParts(context);
1676
+ const fullPrompt = `${SYSTEM_PROMPT}
1677
+
1678
+ ${buildUserMessage(userPrompt, contextParts)}`;
1679
+ const proc = spawn3("gemini", [], {
1680
+ stdio: ["pipe", "pipe", "pipe"],
1681
+ cwd: process.cwd()
1682
+ });
1683
+ proc.stdin.write(fullPrompt);
1684
+ proc.stdin.end();
1685
+ let fullContent = "";
1686
+ let errOutput = "";
1687
+ proc.stdout.on("data", (data) => {
1688
+ const text = data.toString();
1689
+ fullContent += text;
1690
+ onChunk(text);
1691
+ });
1692
+ proc.stderr.on("data", (data) => {
1693
+ errOutput += data.toString();
1694
+ });
1695
+ proc.on("error", (err) => {
1696
+ if (err.message.includes("ENOENT")) {
1697
+ onError("Gemini CLI not found. Install it with: npm install -g @google/gemini-cli");
1698
+ } else {
1699
+ onError(`Gemini CLI error: ${err.message}`);
1700
+ }
1701
+ });
1702
+ proc.on("close", (code) => {
1703
+ if (code === 0 || fullContent) {
1704
+ onDone({ content: fullContent });
1705
+ } else {
1706
+ const err = errOutput.trim();
1707
+ if (err.includes("auth") || err.includes("login") || err.includes("credentials")) {
1708
+ onError("Gemini CLI requires Google authentication. Run `gemini auth login` in your terminal.");
1709
+ } else {
1710
+ onError(err.slice(0, 500) || `Gemini CLI exited with code ${code}`);
1711
+ }
1712
+ }
1713
+ });
1714
+ }
1715
+
1470
1716
  // src/llm/proxy.ts
1471
1717
  var OPENAI_COMPATIBLE_PROVIDERS = /* @__PURE__ */ new Set([
1472
1718
  "openai",
@@ -1543,7 +1789,13 @@ async function handleLlmChat(params, onChunk, onDone, onError) {
1543
1789
  onDone({ content: result.content, modifications });
1544
1790
  };
1545
1791
  try {
1546
- if (provider === "anthropic") {
1792
+ if (provider === "claude-code") {
1793
+ await chatClaudeCode(messages, context, onChunk, wrappedOnDone, onError);
1794
+ } else if (provider === "codex-cli") {
1795
+ await chatCodexCli(messages, context, onChunk, wrappedOnDone, onError);
1796
+ } else if (provider === "gemini-cli") {
1797
+ await chatGeminiCli(messages, context, onChunk, wrappedOnDone, onError);
1798
+ } else if (provider === "anthropic") {
1547
1799
  await chatAnthropic(model, apiKey, messages, context, onChunk, wrappedOnDone, onError);
1548
1800
  } else if (provider === "google") {
1549
1801
  await chatGoogle(model, apiKey, messages, context, onChunk, wrappedOnDone, onError);
@@ -2383,7 +2635,7 @@ function waitForPort(port, timeoutMs = 6e4, shouldAbort) {
2383
2635
  function runCommand(cmd, args, cwd = process.cwd()) {
2384
2636
  return new Promise((resolve4) => {
2385
2637
  try {
2386
- const child = spawn(cmd, args, {
2638
+ const child = spawn4(cmd, args, {
2387
2639
  cwd,
2388
2640
  stdio: ["ignore", "pipe", "pipe"],
2389
2641
  shell: true
@@ -2641,7 +2893,7 @@ async function offerToStartDevServer(expectedPort) {
2641
2893
  }
2642
2894
  const staticPort = expectedPort || 8080;
2643
2895
  console.log(chalk.dim(` Starting static server on port ${staticPort}...`));
2644
- const staticChild = spawn("node", ["-e", `
2896
+ const staticChild = spawn4("node", ["-e", `
2645
2897
  const http = require("http");
2646
2898
  const fs = require("fs");
2647
2899
  const path = require("path");
@@ -2815,7 +3067,7 @@ async function offerToStartDevServer(expectedPort) {
2815
3067
  }
2816
3068
  let child;
2817
3069
  try {
2818
- child = spawn(runCmd, runArgs, {
3070
+ child = spawn4(runCmd, runArgs, {
2819
3071
  cwd: process.cwd(),
2820
3072
  stdio: "inherit",
2821
3073
  env: {