openmagic 0.35.0 → 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 +301 -12
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +5 -5
- package/dist/toolbar/index.global.js.map +1 -1
- package/package.json +1 -1
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 === "
|
|
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);
|
|
@@ -2272,6 +2524,23 @@ function checkEnvPort(cwd = process.cwd()) {
|
|
|
2272
2524
|
}
|
|
2273
2525
|
return null;
|
|
2274
2526
|
}
|
|
2527
|
+
var LOCKFILE_NAMES = ["package-lock.json", "bun.lock", "bun.lockb", "yarn.lock", "pnpm-lock.yaml"];
|
|
2528
|
+
function scanParentLockfiles(projectDir) {
|
|
2529
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
2530
|
+
const project = resolve2(projectDir);
|
|
2531
|
+
const found = [];
|
|
2532
|
+
let dir = resolve2(project, "..");
|
|
2533
|
+
while (dir.length > 1 && dir.length >= home.length) {
|
|
2534
|
+
for (const lockfile of LOCKFILE_NAMES) {
|
|
2535
|
+
const p = join4(dir, lockfile);
|
|
2536
|
+
if (existsSync4(p)) found.push(p);
|
|
2537
|
+
}
|
|
2538
|
+
const parent = resolve2(dir, "..");
|
|
2539
|
+
if (parent === dir) break;
|
|
2540
|
+
dir = parent;
|
|
2541
|
+
}
|
|
2542
|
+
return found;
|
|
2543
|
+
}
|
|
2275
2544
|
function getProjectName(cwd = process.cwd()) {
|
|
2276
2545
|
const pkgPath = join4(cwd, "package.json");
|
|
2277
2546
|
if (!existsSync4(pkgPath)) return "this project";
|
|
@@ -2366,7 +2635,7 @@ function waitForPort(port, timeoutMs = 6e4, shouldAbort) {
|
|
|
2366
2635
|
function runCommand(cmd, args, cwd = process.cwd()) {
|
|
2367
2636
|
return new Promise((resolve4) => {
|
|
2368
2637
|
try {
|
|
2369
|
-
const child =
|
|
2638
|
+
const child = spawn4(cmd, args, {
|
|
2370
2639
|
cwd,
|
|
2371
2640
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2372
2641
|
shell: true
|
|
@@ -2433,13 +2702,20 @@ async function validateAppHealth(targetHost, targetPort) {
|
|
|
2433
2702
|
console.log(chalk.dim(" The dev server is running, but no page matched."));
|
|
2434
2703
|
console.log("");
|
|
2435
2704
|
if (detectedFramework === "Next.js") {
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2705
|
+
const strayLockfiles = scanParentLockfiles(process.cwd());
|
|
2706
|
+
if (strayLockfiles.length > 0) {
|
|
2707
|
+
console.log(chalk.yellow(" Found lockfiles in parent directories that confuse Turbopack:"));
|
|
2708
|
+
for (const f of strayLockfiles) {
|
|
2709
|
+
console.log(chalk.dim(` \u2022 ${f}`));
|
|
2710
|
+
}
|
|
2711
|
+
console.log("");
|
|
2712
|
+
console.log(chalk.dim(" Fix: remove them, or add to your next.config:"));
|
|
2713
|
+
console.log(chalk.cyan(" turbopack: { root: __dirname }"));
|
|
2714
|
+
} else {
|
|
2715
|
+
console.log(chalk.dim(" Common Next.js causes:"));
|
|
2716
|
+
console.log(chalk.dim(" \u2022 Missing src/app/page.tsx (App Router) or pages/index.tsx"));
|
|
2717
|
+
console.log(chalk.dim(" \u2022 Middleware redirecting all routes to an auth provider"));
|
|
2718
|
+
}
|
|
2443
2719
|
} else if (detectedFramework === "Angular") {
|
|
2444
2720
|
console.log(chalk.dim(" Angular hint: ensure the base href matches the proxy path."));
|
|
2445
2721
|
} else if (detectedFramework === "Vite") {
|
|
@@ -2540,6 +2816,19 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
|
|
|
2540
2816
|
console.log(
|
|
2541
2817
|
chalk.green(` \u2713 Dev server running at ${targetHost}:${targetPort}`)
|
|
2542
2818
|
);
|
|
2819
|
+
if (detectedFramework === "Next.js") {
|
|
2820
|
+
const strayLockfiles = scanParentLockfiles(process.cwd());
|
|
2821
|
+
if (strayLockfiles.length > 0) {
|
|
2822
|
+
console.log("");
|
|
2823
|
+
console.log(chalk.yellow(" \u26A0 Lockfiles found in parent directories:"));
|
|
2824
|
+
for (const f of strayLockfiles) {
|
|
2825
|
+
console.log(chalk.dim(` \u2022 ${f}`));
|
|
2826
|
+
}
|
|
2827
|
+
console.log(chalk.dim(" Next.js Turbopack may use the wrong workspace root, causing 404s."));
|
|
2828
|
+
console.log(chalk.dim(" Fix: remove them, or add to next.config:"));
|
|
2829
|
+
console.log(chalk.cyan(" turbopack: { root: __dirname }"));
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2543
2832
|
const roots = (opts.root || [process.cwd()]).map(
|
|
2544
2833
|
(r) => resolve3(r)
|
|
2545
2834
|
);
|
|
@@ -2604,7 +2893,7 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
2604
2893
|
}
|
|
2605
2894
|
const staticPort = expectedPort || 8080;
|
|
2606
2895
|
console.log(chalk.dim(` Starting static server on port ${staticPort}...`));
|
|
2607
|
-
const staticChild =
|
|
2896
|
+
const staticChild = spawn4("node", ["-e", `
|
|
2608
2897
|
const http = require("http");
|
|
2609
2898
|
const fs = require("fs");
|
|
2610
2899
|
const path = require("path");
|
|
@@ -2778,7 +3067,7 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
2778
3067
|
}
|
|
2779
3068
|
let child;
|
|
2780
3069
|
try {
|
|
2781
|
-
child =
|
|
3070
|
+
child = spawn4(runCmd, runArgs, {
|
|
2782
3071
|
cwd: process.cwd(),
|
|
2783
3072
|
stdio: "inherit",
|
|
2784
3073
|
env: {
|