@gemdoq/codi 0.1.4 → 0.1.6

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
@@ -778,6 +778,19 @@ function completer(line) {
778
778
  }
779
779
 
780
780
  // src/repl.ts
781
+ import { readFileSync as readFileSync3 } from "fs";
782
+ import { fileURLToPath as fileURLToPath2 } from "url";
783
+ import * as path5 from "path";
784
+ var __filename2 = fileURLToPath2(import.meta.url);
785
+ var __dirname2 = path5.dirname(__filename2);
786
+ function getVersion() {
787
+ try {
788
+ const pkg = JSON.parse(readFileSync3(path5.join(__dirname2, "..", "package.json"), "utf-8"));
789
+ return `v${pkg.version}`;
790
+ } catch {
791
+ return "v0.1.4";
792
+ }
793
+ }
781
794
  var Repl = class {
782
795
  rl = null;
783
796
  keyBindings = new KeyBindingManager();
@@ -810,9 +823,25 @@ var Repl = class {
810
823
  completer: (line) => completer(line),
811
824
  terminal: true
812
825
  });
813
- if (process.stdin.isTTY) {
826
+ if (process.stdin.isTTY && os2.platform() !== "win32") {
814
827
  process.stdout.write("\x1B[?2004h");
815
828
  }
829
+ if (os2.platform() === "win32" && process.stdin.isTTY) {
830
+ process.stdin.on("keypress", (_str, key) => {
831
+ if (key && key.sequence === "") {
832
+ try {
833
+ const clip = execSync("powershell -command Get-Clipboard", {
834
+ encoding: "utf-8",
835
+ timeout: 3e3
836
+ }).replace(/\r\n$/, "");
837
+ if (clip && this.rl) {
838
+ this.rl.write(clip);
839
+ }
840
+ } catch {
841
+ }
842
+ }
843
+ });
844
+ }
816
845
  this.printWelcome();
817
846
  while (this.running) {
818
847
  try {
@@ -877,7 +906,7 @@ var Repl = class {
877
906
  }
878
907
  }
879
908
  }
880
- if (process.stdin.isTTY) {
909
+ if (process.stdin.isTTY && os2.platform() !== "win32") {
881
910
  process.stdout.write("\x1B[?2004l");
882
911
  }
883
912
  }
@@ -916,19 +945,38 @@ var Repl = class {
916
945
  return;
917
946
  }
918
947
  let message = input3;
919
- const atMatches = input3.match(/@([\w./-]+)/g);
948
+ const IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"]);
949
+ const atMatches = input3.match(/@([\w.\/\\:~-]+)/g);
920
950
  if (atMatches) {
921
951
  for (const match of atMatches) {
922
952
  const filePath = match.slice(1);
923
953
  try {
924
- const { readFileSync: readFileSync13 } = await import("fs");
925
- const content = readFileSync13(filePath, "utf-8");
926
- message = message.replace(match, `
954
+ const ext = path5.extname(filePath).toLowerCase();
955
+ if (IMAGE_EXTS.has(ext)) {
956
+ const data = readFileSync3(filePath);
957
+ const base64 = data.toString("base64");
958
+ const mimeMap = {
959
+ ".png": "image/png",
960
+ ".jpg": "image/jpeg",
961
+ ".jpeg": "image/jpeg",
962
+ ".gif": "image/gif",
963
+ ".webp": "image/webp",
964
+ ".bmp": "image/bmp",
965
+ ".svg": "image/svg+xml"
966
+ };
967
+ const mime = mimeMap[ext] || "image/png";
968
+ message = message.replace(match, `
969
+ [Image: ${filePath}](data:${mime};base64,${base64})
970
+ `);
971
+ } else {
972
+ const content = readFileSync3(filePath, "utf-8");
973
+ message = message.replace(match, `
927
974
  [File: ${filePath}]
928
975
  \`\`\`
929
976
  ${content}
930
977
  \`\`\`
931
978
  `);
979
+ }
932
980
  } catch {
933
981
  }
934
982
  }
@@ -961,7 +1009,8 @@ ${content}
961
1009
  printWelcome() {
962
1010
  console.log("");
963
1011
  console.log(chalk4.cyan.bold(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
964
- console.log(chalk4.cyan.bold(" \u2502") + chalk4.white.bold(" Codi (\uCF54\uB514) v0.1.0 ") + chalk4.cyan.bold("\u2502"));
1012
+ const versionPad = ` Codi (\uCF54\uB514) ${getVersion()}`.padEnd(29);
1013
+ console.log(chalk4.cyan.bold(" \u2502") + chalk4.white.bold(versionPad) + chalk4.cyan.bold("\u2502"));
965
1014
  console.log(chalk4.cyan.bold(" \u2502") + chalk4.dim(" AI Code Agent for Terminal ") + chalk4.cyan.bold("\u2502"));
966
1015
  console.log(chalk4.cyan.bold(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
967
1016
  console.log("");
@@ -1456,7 +1505,56 @@ function sleep(ms) {
1456
1505
  init_esm_shims();
1457
1506
  import * as os3 from "os";
1458
1507
  import { execSync as execSync2 } from "child_process";
1459
- var ROLE_DEFINITION = `You are Codi (\uCF54\uB514), a terminal-based AI coding agent. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have access to tools for file manipulation, code search, shell execution, and more.`;
1508
+ var ROLE_DEFINITION = `You are Codi (\uCF54\uB514), a terminal-based AI coding agent. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have access to tools for file manipulation, code search, shell execution, and more.
1509
+
1510
+ # How Users Interact with You
1511
+ - Users type natural language messages to you. They do NOT type tool calls directly.
1512
+ - Tools (read_file, bash, grep, etc.) are for YOU to use internally. NEVER tell users to type tool calls like "read_file(path)" or "bash(command)".
1513
+ - When users ask "how should I do X?" or "what should I type?", give them natural language prompts they can type to you, NOT tool call syntax.
1514
+ - When users ask a QUESTION about how to do something, ANSWER with an explanation. Do NOT immediately execute actions.
1515
+ - Only execute actions when the user clearly REQUESTS you to do something (e.g., "clone this repo", "analyze this code", "fix this bug").
1516
+
1517
+ # Codi CLI Features (you must know these)
1518
+ Users can start Codi with these command-line options:
1519
+ - codi --yolo : Skip ALL permission checks (like Claude Code's --dangerously-skip-permissions)
1520
+ - codi --plan : Start in read-only plan mode (analysis only, no changes)
1521
+ - codi -p "prompt" : Run a single prompt and exit
1522
+ - codi -c / --continue : Continue the last session
1523
+ - codi -r <id> / --resume <id> : Resume a specific session
1524
+ - codi -m <model> : Switch to a different model
1525
+ - codi --provider <name> : Switch provider (openai, anthropic, ollama)
1526
+
1527
+ # Slash Commands (available inside Codi)
1528
+ Users can type these commands while using Codi:
1529
+ - /help : Show all available commands
1530
+ - /quit or /exit : Exit Codi
1531
+ - /clear : Clear conversation history
1532
+ - /model <name> : Switch model (e.g., /model gpt-4o)
1533
+ - /compact : Compress conversation to save context
1534
+ - /cost : Show token usage and cost
1535
+ - /plan : Toggle plan mode (read-only analysis)
1536
+ - /commit : Generate commit message from git diff and commit
1537
+ - /review : AI code review of current changes
1538
+ - /fix <command> : Run command, auto-fix if it fails
1539
+ - /search <keyword> : Search past sessions
1540
+ - /save : Save current session
1541
+ - /resume : Resume a saved session
1542
+ - /memory : Show auto memory
1543
+ - /tasks : Show task list
1544
+ - /context : Show context window usage
1545
+ - /rewind : Undo to previous checkpoint
1546
+ - /diff : Show git diff
1547
+
1548
+ # Input Prefixes
1549
+ - ! command : Execute a shell command directly (e.g., ! git status)
1550
+ - @file.ts : Attach file content to your message
1551
+ - \\ at end of line : Continue typing on next line (multiline input)`;
1552
+ var CONVERSATION_RULES = `# Conversation Rules
1553
+ - When a user asks "how do I..." or "what should I type...", give a clear EXPLANATION with example prompts they can type.
1554
+ - Do NOT execute commands or use tools when the user is just asking for information.
1555
+ - When giving examples of what to type, show them as natural language, e.g.: "You can type: \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uAD6C\uC870\uB97C \uBD84\uC11D\uD574\uC918"
1556
+ - Only use tools when the user explicitly requests an action.
1557
+ - If the user's intent is ambiguous, ASK for clarification before acting.`;
1460
1558
  var TOOL_HIERARCHY = `# Tool Usage Rules
1461
1559
  - Use read_file instead of bash cat/head/tail
1462
1560
  - Use edit_file instead of bash sed/awk
@@ -1515,6 +1613,7 @@ function buildSystemPrompt(context) {
1515
1613
  const fragments = [];
1516
1614
  fragments.push(ROLE_DEFINITION);
1517
1615
  fragments.push(buildEnvironmentInfo(context));
1616
+ fragments.push(CONVERSATION_RULES);
1518
1617
  fragments.push(TOOL_HIERARCHY);
1519
1618
  if (os3.platform() === "win32") {
1520
1619
  fragments.push(WINDOWS_RULES);
@@ -1619,15 +1718,15 @@ ${summaryContent}`;
1619
1718
  // src/agent/memory.ts
1620
1719
  init_esm_shims();
1621
1720
  import * as fs4 from "fs";
1622
- import * as path5 from "path";
1721
+ import * as path6 from "path";
1623
1722
  import * as crypto from "crypto";
1624
1723
  var MemoryManager = class {
1625
1724
  memoryDir;
1626
1725
  constructor() {
1627
1726
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1628
1727
  const projectHash = crypto.createHash("md5").update(process.cwd()).digest("hex").slice(0, 8);
1629
- const projectName = path5.basename(process.cwd());
1630
- this.memoryDir = path5.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
1728
+ const projectName = path6.basename(process.cwd());
1729
+ this.memoryDir = path6.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
1631
1730
  }
1632
1731
  ensureDir() {
1633
1732
  if (!fs4.existsSync(this.memoryDir)) {
@@ -1638,7 +1737,7 @@ var MemoryManager = class {
1638
1737
  return this.memoryDir;
1639
1738
  }
1640
1739
  loadIndex() {
1641
- const indexPath = path5.join(this.memoryDir, "MEMORY.md");
1740
+ const indexPath = path6.join(this.memoryDir, "MEMORY.md");
1642
1741
  if (!fs4.existsSync(indexPath)) return "";
1643
1742
  const content = fs4.readFileSync(indexPath, "utf-8");
1644
1743
  const lines = content.split("\n");
@@ -1646,17 +1745,17 @@ var MemoryManager = class {
1646
1745
  }
1647
1746
  saveIndex(content) {
1648
1747
  this.ensureDir();
1649
- const indexPath = path5.join(this.memoryDir, "MEMORY.md");
1748
+ const indexPath = path6.join(this.memoryDir, "MEMORY.md");
1650
1749
  fs4.writeFileSync(indexPath, content, "utf-8");
1651
1750
  }
1652
1751
  loadTopic(name) {
1653
- const topicPath = path5.join(this.memoryDir, `${name}.md`);
1752
+ const topicPath = path6.join(this.memoryDir, `${name}.md`);
1654
1753
  if (!fs4.existsSync(topicPath)) return null;
1655
1754
  return fs4.readFileSync(topicPath, "utf-8");
1656
1755
  }
1657
1756
  saveTopic(name, content) {
1658
1757
  this.ensureDir();
1659
- const topicPath = path5.join(this.memoryDir, `${name}.md`);
1758
+ const topicPath = path6.join(this.memoryDir, `${name}.md`);
1660
1759
  fs4.writeFileSync(topicPath, content, "utf-8");
1661
1760
  }
1662
1761
  listTopics() {
@@ -1681,13 +1780,13 @@ var memoryManager = new MemoryManager();
1681
1780
  // src/agent/session.ts
1682
1781
  init_esm_shims();
1683
1782
  import * as fs5 from "fs";
1684
- import * as path6 from "path";
1783
+ import * as path7 from "path";
1685
1784
  import * as crypto2 from "crypto";
1686
1785
  var SessionManager = class {
1687
1786
  sessionsDir;
1688
1787
  constructor() {
1689
1788
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1690
- this.sessionsDir = path6.join(home, ".codi", "sessions");
1789
+ this.sessionsDir = path7.join(home, ".codi", "sessions");
1691
1790
  }
1692
1791
  ensureDir() {
1693
1792
  if (!fs5.existsSync(this.sessionsDir)) {
@@ -1697,7 +1796,7 @@ var SessionManager = class {
1697
1796
  save(conversation, name, model) {
1698
1797
  this.ensureDir();
1699
1798
  const id = name || crypto2.randomUUID().slice(0, 8);
1700
- const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1799
+ const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
1701
1800
  const data = conversation.serialize();
1702
1801
  const meta = {
1703
1802
  id,
@@ -1717,7 +1816,7 @@ var SessionManager = class {
1717
1816
  return id;
1718
1817
  }
1719
1818
  load(id) {
1720
- const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1819
+ const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
1721
1820
  if (!fs5.existsSync(filePath)) return null;
1722
1821
  const content = fs5.readFileSync(filePath, "utf-8");
1723
1822
  const lines = content.trim().split("\n").filter(Boolean);
@@ -1747,7 +1846,7 @@ var SessionManager = class {
1747
1846
  const files = fs5.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl"));
1748
1847
  const sessions = [];
1749
1848
  for (const file of files) {
1750
- const filePath = path6.join(this.sessionsDir, file);
1849
+ const filePath = path7.join(this.sessionsDir, file);
1751
1850
  try {
1752
1851
  const firstLine = fs5.readFileSync(filePath, "utf-8").split("\n")[0];
1753
1852
  if (firstLine) {
@@ -1767,7 +1866,7 @@ var SessionManager = class {
1767
1866
  return sessions[0] ?? null;
1768
1867
  }
1769
1868
  delete(id) {
1770
- const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1869
+ const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
1771
1870
  if (fs5.existsSync(filePath)) {
1772
1871
  fs5.unlinkSync(filePath);
1773
1872
  return true;
@@ -1872,21 +1971,21 @@ var checkpointManager = new CheckpointManager();
1872
1971
  // src/agent/codi-md.ts
1873
1972
  init_esm_shims();
1874
1973
  import * as fs6 from "fs";
1875
- import * as path7 from "path";
1974
+ import * as path8 from "path";
1876
1975
  function loadCodiMd() {
1877
1976
  const fragments = [];
1878
1977
  let dir = process.cwd();
1879
- const root = path7.parse(dir).root;
1978
+ const root = path8.parse(dir).root;
1880
1979
  while (dir !== root) {
1881
1980
  loadFromDir(dir, fragments);
1882
- const parent = path7.dirname(dir);
1981
+ const parent = path8.dirname(dir);
1883
1982
  if (parent === dir) break;
1884
1983
  dir = parent;
1885
1984
  }
1886
1985
  return fragments.join("\n\n---\n\n");
1887
1986
  }
1888
1987
  function loadFromDir(dir, fragments) {
1889
- const codiPath = path7.join(dir, "CODI.md");
1988
+ const codiPath = path8.join(dir, "CODI.md");
1890
1989
  if (fs6.existsSync(codiPath)) {
1891
1990
  try {
1892
1991
  let content = fs6.readFileSync(codiPath, "utf-8");
@@ -1896,7 +1995,7 @@ ${content}`);
1896
1995
  } catch {
1897
1996
  }
1898
1997
  }
1899
- const localPath = path7.join(dir, "CODI.local.md");
1998
+ const localPath = path8.join(dir, "CODI.local.md");
1900
1999
  if (fs6.existsSync(localPath)) {
1901
2000
  try {
1902
2001
  const content = fs6.readFileSync(localPath, "utf-8");
@@ -1905,12 +2004,12 @@ ${content}`);
1905
2004
  } catch {
1906
2005
  }
1907
2006
  }
1908
- const rulesDir = path7.join(dir, ".codi", "rules");
2007
+ const rulesDir = path8.join(dir, ".codi", "rules");
1909
2008
  if (fs6.existsSync(rulesDir)) {
1910
2009
  try {
1911
2010
  const files = fs6.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort();
1912
2011
  for (const file of files) {
1913
- const content = fs6.readFileSync(path7.join(rulesDir, file), "utf-8");
2012
+ const content = fs6.readFileSync(path8.join(rulesDir, file), "utf-8");
1914
2013
  fragments.push(`[Rule: ${file}]
1915
2014
  ${content}`);
1916
2015
  }
@@ -1920,7 +2019,7 @@ ${content}`);
1920
2019
  }
1921
2020
  function processImports(content, baseDir) {
1922
2021
  return content.replace(/@([\w./-]+)/g, (match, importPath) => {
1923
- const resolved = path7.resolve(baseDir, importPath);
2022
+ const resolved = path8.resolve(baseDir, importPath);
1924
2023
  if (fs6.existsSync(resolved)) {
1925
2024
  try {
1926
2025
  return fs6.readFileSync(resolved, "utf-8");
@@ -2189,7 +2288,7 @@ var hookManager = new HookManager();
2189
2288
  init_esm_shims();
2190
2289
  init_tool();
2191
2290
  import * as fs7 from "fs";
2192
- import * as path8 from "path";
2291
+ import * as path9 from "path";
2193
2292
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2194
2293
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2195
2294
  import chalk9 from "chalk";
@@ -2211,8 +2310,8 @@ var McpManager = class {
2211
2310
  const configs = {};
2212
2311
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2213
2312
  const paths = [
2214
- path8.join(home, ".codi", "mcp.json"),
2215
- path8.join(process.cwd(), ".codi", "mcp.json")
2313
+ path9.join(home, ".codi", "mcp.json"),
2314
+ path9.join(process.cwd(), ".codi", "mcp.json")
2216
2315
  ];
2217
2316
  for (const p of paths) {
2218
2317
  try {
@@ -2414,7 +2513,7 @@ var subAgentTool = {
2414
2513
  init_esm_shims();
2415
2514
  import * as fs8 from "fs";
2416
2515
  import * as os5 from "os";
2417
- import * as path9 from "path";
2516
+ import * as path10 from "path";
2418
2517
  import chalk11 from "chalk";
2419
2518
  function createBuiltinCommands() {
2420
2519
  return [
@@ -2608,12 +2707,12 @@ Topics: ${topics.join(", ")}`));
2608
2707
  name: "/init",
2609
2708
  description: "Initialize CODI.md in the current project",
2610
2709
  handler: async () => {
2611
- const codiPath = path9.join(process.cwd(), "CODI.md");
2710
+ const codiPath = path10.join(process.cwd(), "CODI.md");
2612
2711
  if (fs8.existsSync(codiPath)) {
2613
2712
  console.log(chalk11.yellow("CODI.md already exists"));
2614
2713
  return true;
2615
2714
  }
2616
- const content = `# Project: ${path9.basename(process.cwd())}
2715
+ const content = `# Project: ${path10.basename(process.cwd())}
2617
2716
 
2618
2717
  ## Overview
2619
2718
  <!-- Describe your project here -->
@@ -2825,7 +2924,7 @@ ${diff}
2825
2924
  return true;
2826
2925
  }
2827
2926
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2828
- const sessionsDir = path9.join(home, ".codi", "sessions");
2927
+ const sessionsDir = path10.join(home, ".codi", "sessions");
2829
2928
  if (!fs8.existsSync(sessionsDir)) {
2830
2929
  console.log(chalk11.dim("\nNo sessions found.\n"));
2831
2930
  return true;
@@ -2835,7 +2934,7 @@ ${diff}
2835
2934
  const keyword = args.toLowerCase();
2836
2935
  for (const file of files) {
2837
2936
  if (results.length >= 10) break;
2838
- const filePath = path9.join(sessionsDir, file);
2937
+ const filePath = path10.join(sessionsDir, file);
2839
2938
  const lines = fs8.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
2840
2939
  for (const line of lines) {
2841
2940
  if (results.length >= 10) break;
@@ -2929,18 +3028,18 @@ function loadCustomCommands() {
2929
3028
  const commands = [];
2930
3029
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2931
3030
  const dirs = [
2932
- path9.join(home, ".codi", "commands"),
2933
- path9.join(process.cwd(), ".codi", "commands")
3031
+ path10.join(home, ".codi", "commands"),
3032
+ path10.join(process.cwd(), ".codi", "commands")
2934
3033
  ];
2935
3034
  for (const dir of dirs) {
2936
3035
  if (!fs8.existsSync(dir)) continue;
2937
3036
  const files = fs8.readdirSync(dir).filter((f) => f.endsWith(".md"));
2938
3037
  for (const file of files) {
2939
3038
  const name = "/" + file.replace(".md", "");
2940
- const filePath = path9.join(dir, file);
3039
+ const filePath = path10.join(dir, file);
2941
3040
  commands.push({
2942
3041
  name,
2943
- description: `Custom command from ${path9.relative(process.cwd(), filePath)}`,
3042
+ description: `Custom command from ${path10.relative(process.cwd(), filePath)}`,
2944
3043
  handler: async (_args, ctx) => {
2945
3044
  let content = fs8.readFileSync(filePath, "utf-8");
2946
3045
  content = content.replace(/\{\{cwd\}\}/g, process.cwd()).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]).replace(/\{\{file_path\}\}/g, _args || "");
@@ -2957,7 +3056,7 @@ function loadCustomCommands() {
2957
3056
  init_esm_shims();
2958
3057
  init_tool();
2959
3058
  import * as fs9 from "fs";
2960
- import * as path10 from "path";
3059
+ import * as path11 from "path";
2961
3060
  var fileReadTool = {
2962
3061
  name: "read_file",
2963
3062
  description: `Read a file from the filesystem. Supports text files with line numbers (cat -n format), PDF files, images (returns base64 for multimodal), and Jupyter notebooks (.ipynb). Use offset/limit for large files.`,
@@ -2977,7 +3076,7 @@ var fileReadTool = {
2977
3076
  const filePath = String(input3["file_path"]);
2978
3077
  const offset = input3["offset"];
2979
3078
  const limit = input3["limit"];
2980
- const resolved = path10.resolve(filePath);
3079
+ const resolved = path11.resolve(filePath);
2981
3080
  if (!fs9.existsSync(resolved)) {
2982
3081
  return makeToolError(`File not found: ${resolved}`);
2983
3082
  }
@@ -2985,7 +3084,7 @@ var fileReadTool = {
2985
3084
  if (stat.isDirectory()) {
2986
3085
  return makeToolError(`Path is a directory, not a file: ${resolved}. Use list_dir instead.`);
2987
3086
  }
2988
- const ext = path10.extname(resolved).toLowerCase();
3087
+ const ext = path11.extname(resolved).toLowerCase();
2989
3088
  if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"].includes(ext)) {
2990
3089
  const data = fs9.readFileSync(resolved);
2991
3090
  const base64 = data.toString("base64");
@@ -2998,7 +3097,7 @@ var fileReadTool = {
2998
3097
  ".bmp": "image/bmp",
2999
3098
  ".svg": "image/svg+xml"
3000
3099
  };
3001
- return makeToolResult(`[Image: ${path10.basename(resolved)}]`, {
3100
+ return makeToolResult(`[Image: ${path11.basename(resolved)}]`, {
3002
3101
  filePath: resolved,
3003
3102
  isImage: true,
3004
3103
  imageData: base64,
@@ -3084,7 +3183,7 @@ var fileReadTool = {
3084
3183
  init_esm_shims();
3085
3184
  init_tool();
3086
3185
  import * as fs10 from "fs";
3087
- import * as path11 from "path";
3186
+ import * as path12 from "path";
3088
3187
  var fileWriteTool = {
3089
3188
  name: "write_file",
3090
3189
  description: `Create a new file or overwrite an existing file. Creates parent directories if needed. For modifying existing files, prefer edit_file instead.`,
@@ -3101,9 +3200,9 @@ var fileWriteTool = {
3101
3200
  async execute(input3) {
3102
3201
  const filePath = String(input3["file_path"]);
3103
3202
  const content = String(input3["content"]);
3104
- const resolved = path11.resolve(filePath);
3203
+ const resolved = path12.resolve(filePath);
3105
3204
  try {
3106
- const dir = path11.dirname(resolved);
3205
+ const dir = path12.dirname(resolved);
3107
3206
  if (!fs10.existsSync(dir)) {
3108
3207
  fs10.mkdirSync(dir, { recursive: true });
3109
3208
  }
@@ -3125,7 +3224,7 @@ var fileWriteTool = {
3125
3224
  init_esm_shims();
3126
3225
  init_tool();
3127
3226
  import * as fs11 from "fs";
3128
- import * as path12 from "path";
3227
+ import * as path13 from "path";
3129
3228
  var fileEditTool = {
3130
3229
  name: "edit_file",
3131
3230
  description: `Perform exact string replacement in a file. The old_string must be unique in the file unless replace_all is true. Preserves indentation exactly.`,
@@ -3146,7 +3245,7 @@ var fileEditTool = {
3146
3245
  const oldString = String(input3["old_string"]);
3147
3246
  const newString = String(input3["new_string"]);
3148
3247
  const replaceAll = input3["replace_all"] === true;
3149
- const resolved = path12.resolve(filePath);
3248
+ const resolved = path13.resolve(filePath);
3150
3249
  if (!fs11.existsSync(resolved)) {
3151
3250
  return makeToolError(`File not found: ${resolved}`);
3152
3251
  }
@@ -3205,7 +3304,7 @@ Did you mean this line?
3205
3304
  init_esm_shims();
3206
3305
  init_tool();
3207
3306
  import * as fs12 from "fs";
3208
- import * as path13 from "path";
3307
+ import * as path14 from "path";
3209
3308
  var fileMultiEditTool = {
3210
3309
  name: "multi_edit",
3211
3310
  description: `Apply multiple edits to a single file atomically. Each edit is an old_string \u2192 new_string replacement. All edits are validated before any are applied.`,
@@ -3233,7 +3332,7 @@ var fileMultiEditTool = {
3233
3332
  async execute(input3) {
3234
3333
  const filePath = String(input3["file_path"]);
3235
3334
  const edits = input3["edits"];
3236
- const resolved = path13.resolve(filePath);
3335
+ const resolved = path14.resolve(filePath);
3237
3336
  if (!fs12.existsSync(resolved)) {
3238
3337
  return makeToolError(`File not found: ${resolved}`);
3239
3338
  }
@@ -3281,7 +3380,7 @@ Searching for: ${edit2.old_string.slice(0, 100)}...`
3281
3380
  init_esm_shims();
3282
3381
  init_tool();
3283
3382
  import * as fs13 from "fs";
3284
- import * as path14 from "path";
3383
+ import * as path15 from "path";
3285
3384
  import { globby } from "globby";
3286
3385
  var globTool = {
3287
3386
  name: "glob",
@@ -3299,7 +3398,7 @@ var globTool = {
3299
3398
  async execute(input3) {
3300
3399
  const pattern = String(input3["pattern"]);
3301
3400
  const searchPath = input3["path"] ? String(input3["path"]) : process.cwd();
3302
- const resolved = path14.resolve(searchPath);
3401
+ const resolved = path15.resolve(searchPath);
3303
3402
  try {
3304
3403
  const files = await globby(pattern, {
3305
3404
  cwd: resolved,
@@ -3336,7 +3435,7 @@ init_esm_shims();
3336
3435
  init_tool();
3337
3436
  import { execFile } from "child_process";
3338
3437
  import * as fs14 from "fs";
3339
- import * as path15 from "path";
3438
+ import * as path16 from "path";
3340
3439
  var grepTool = {
3341
3440
  name: "grep",
3342
3441
  description: `Search file contents using regex patterns. Uses ripgrep (rg) if available, falls back to grep, then to a built-in Node.js search. Supports context lines, file type filters, and multiple output modes.`,
@@ -3366,7 +3465,7 @@ var grepTool = {
3366
3465
  readOnly: true,
3367
3466
  async execute(input3) {
3368
3467
  const pattern = String(input3["pattern"]);
3369
- const searchPath = path15.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3468
+ const searchPath = path16.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3370
3469
  const outputMode = input3["output_mode"] || "files_with_matches";
3371
3470
  const headLimit = input3["head_limit"] || 0;
3372
3471
  const hasRg = await hasCommand("rg");
@@ -3556,11 +3655,11 @@ function collectFiles(dirPath, typeFilter, globFilter) {
3556
3655
  for (const entry of entries) {
3557
3656
  if (IGNORE_DIRS.has(entry.name)) continue;
3558
3657
  if (entry.name.startsWith(".") && entry.name !== ".") continue;
3559
- const fullPath = path15.join(dir, entry.name);
3658
+ const fullPath = path16.join(dir, entry.name);
3560
3659
  if (entry.isDirectory()) {
3561
3660
  walk(fullPath);
3562
3661
  } else if (entry.isFile()) {
3563
- const ext = path15.extname(entry.name).toLowerCase();
3662
+ const ext = path16.extname(entry.name).toLowerCase();
3564
3663
  if (BINARY_EXTENSIONS.has(ext)) continue;
3565
3664
  if (allowedExtensions && !allowedExtensions.has(ext)) continue;
3566
3665
  if (globRegex && !globRegex.test(entry.name)) continue;
@@ -3687,7 +3786,7 @@ Use task_output tool to check results.`);
3687
3786
  init_esm_shims();
3688
3787
  init_tool();
3689
3788
  import * as fs15 from "fs";
3690
- import * as path16 from "path";
3789
+ import * as path17 from "path";
3691
3790
  var listDirTool = {
3692
3791
  name: "list_dir",
3693
3792
  description: `List directory contents with file/folder distinction and basic metadata.`,
@@ -3701,7 +3800,7 @@ var listDirTool = {
3701
3800
  dangerous: false,
3702
3801
  readOnly: true,
3703
3802
  async execute(input3) {
3704
- const dirPath = path16.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3803
+ const dirPath = path17.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3705
3804
  if (!fs15.existsSync(dirPath)) {
3706
3805
  return makeToolError(`Directory not found: ${dirPath}`);
3707
3806
  }
@@ -3721,7 +3820,7 @@ var listDirTool = {
3721
3820
  dirs.push(`${entry.name}/`);
3722
3821
  } else if (entry.isSymbolicLink()) {
3723
3822
  try {
3724
- const target = fs15.readlinkSync(path16.join(dirPath, entry.name));
3823
+ const target = fs15.readlinkSync(path17.join(dirPath, entry.name));
3725
3824
  files.push(`${entry.name} -> ${target}`);
3726
3825
  } catch {
3727
3826
  files.push(`${entry.name} -> (broken link)`);
@@ -3934,7 +4033,7 @@ ${formatted}`);
3934
4033
  init_esm_shims();
3935
4034
  init_tool();
3936
4035
  import * as fs16 from "fs";
3937
- import * as path17 from "path";
4036
+ import * as path18 from "path";
3938
4037
  var notebookEditTool = {
3939
4038
  name: "notebook_edit",
3940
4039
  description: `Edit Jupyter notebook (.ipynb) cells. Supports replacing, inserting, and deleting cells.`,
@@ -3952,7 +4051,7 @@ var notebookEditTool = {
3952
4051
  dangerous: true,
3953
4052
  readOnly: false,
3954
4053
  async execute(input3) {
3955
- const nbPath = path17.resolve(String(input3["notebook_path"]));
4054
+ const nbPath = path18.resolve(String(input3["notebook_path"]));
3956
4055
  const cellNumber = input3["cell_number"];
3957
4056
  const newSource = String(input3["new_source"]);
3958
4057
  const cellType = input3["cell_type"] || "code";