@gemdoq/codi 0.1.4 → 0.1.5
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 +146 -63
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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,7 +823,7 @@ 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
|
}
|
|
816
829
|
this.printWelcome();
|
|
@@ -877,7 +890,7 @@ var Repl = class {
|
|
|
877
890
|
}
|
|
878
891
|
}
|
|
879
892
|
}
|
|
880
|
-
if (process.stdin.isTTY) {
|
|
893
|
+
if (process.stdin.isTTY && os2.platform() !== "win32") {
|
|
881
894
|
process.stdout.write("\x1B[?2004l");
|
|
882
895
|
}
|
|
883
896
|
}
|
|
@@ -916,19 +929,38 @@ var Repl = class {
|
|
|
916
929
|
return;
|
|
917
930
|
}
|
|
918
931
|
let message = input3;
|
|
919
|
-
const
|
|
932
|
+
const IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"]);
|
|
933
|
+
const atMatches = input3.match(/@([\w.\/\\:~-]+)/g);
|
|
920
934
|
if (atMatches) {
|
|
921
935
|
for (const match of atMatches) {
|
|
922
936
|
const filePath = match.slice(1);
|
|
923
937
|
try {
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
938
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
939
|
+
if (IMAGE_EXTS.has(ext)) {
|
|
940
|
+
const data = readFileSync3(filePath);
|
|
941
|
+
const base64 = data.toString("base64");
|
|
942
|
+
const mimeMap = {
|
|
943
|
+
".png": "image/png",
|
|
944
|
+
".jpg": "image/jpeg",
|
|
945
|
+
".jpeg": "image/jpeg",
|
|
946
|
+
".gif": "image/gif",
|
|
947
|
+
".webp": "image/webp",
|
|
948
|
+
".bmp": "image/bmp",
|
|
949
|
+
".svg": "image/svg+xml"
|
|
950
|
+
};
|
|
951
|
+
const mime = mimeMap[ext] || "image/png";
|
|
952
|
+
message = message.replace(match, `
|
|
953
|
+
[Image: ${filePath}](data:${mime};base64,${base64})
|
|
954
|
+
`);
|
|
955
|
+
} else {
|
|
956
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
957
|
+
message = message.replace(match, `
|
|
927
958
|
[File: ${filePath}]
|
|
928
959
|
\`\`\`
|
|
929
960
|
${content}
|
|
930
961
|
\`\`\`
|
|
931
962
|
`);
|
|
963
|
+
}
|
|
932
964
|
} catch {
|
|
933
965
|
}
|
|
934
966
|
}
|
|
@@ -961,7 +993,8 @@ ${content}
|
|
|
961
993
|
printWelcome() {
|
|
962
994
|
console.log("");
|
|
963
995
|
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
|
-
|
|
996
|
+
const versionPad = ` Codi (\uCF54\uB514) ${getVersion()}`.padEnd(29);
|
|
997
|
+
console.log(chalk4.cyan.bold(" \u2502") + chalk4.white.bold(versionPad) + chalk4.cyan.bold("\u2502"));
|
|
965
998
|
console.log(chalk4.cyan.bold(" \u2502") + chalk4.dim(" AI Code Agent for Terminal ") + chalk4.cyan.bold("\u2502"));
|
|
966
999
|
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
1000
|
console.log("");
|
|
@@ -1456,7 +1489,56 @@ function sleep(ms) {
|
|
|
1456
1489
|
init_esm_shims();
|
|
1457
1490
|
import * as os3 from "os";
|
|
1458
1491
|
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
|
|
1492
|
+
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.
|
|
1493
|
+
|
|
1494
|
+
# How Users Interact with You
|
|
1495
|
+
- Users type natural language messages to you. They do NOT type tool calls directly.
|
|
1496
|
+
- 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)".
|
|
1497
|
+
- 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.
|
|
1498
|
+
- When users ask a QUESTION about how to do something, ANSWER with an explanation. Do NOT immediately execute actions.
|
|
1499
|
+
- Only execute actions when the user clearly REQUESTS you to do something (e.g., "clone this repo", "analyze this code", "fix this bug").
|
|
1500
|
+
|
|
1501
|
+
# Codi CLI Features (you must know these)
|
|
1502
|
+
Users can start Codi with these command-line options:
|
|
1503
|
+
- codi --yolo : Skip ALL permission checks (like Claude Code's --dangerously-skip-permissions)
|
|
1504
|
+
- codi --plan : Start in read-only plan mode (analysis only, no changes)
|
|
1505
|
+
- codi -p "prompt" : Run a single prompt and exit
|
|
1506
|
+
- codi -c / --continue : Continue the last session
|
|
1507
|
+
- codi -r <id> / --resume <id> : Resume a specific session
|
|
1508
|
+
- codi -m <model> : Switch to a different model
|
|
1509
|
+
- codi --provider <name> : Switch provider (openai, anthropic, ollama)
|
|
1510
|
+
|
|
1511
|
+
# Slash Commands (available inside Codi)
|
|
1512
|
+
Users can type these commands while using Codi:
|
|
1513
|
+
- /help : Show all available commands
|
|
1514
|
+
- /quit or /exit : Exit Codi
|
|
1515
|
+
- /clear : Clear conversation history
|
|
1516
|
+
- /model <name> : Switch model (e.g., /model gpt-4o)
|
|
1517
|
+
- /compact : Compress conversation to save context
|
|
1518
|
+
- /cost : Show token usage and cost
|
|
1519
|
+
- /plan : Toggle plan mode (read-only analysis)
|
|
1520
|
+
- /commit : Generate commit message from git diff and commit
|
|
1521
|
+
- /review : AI code review of current changes
|
|
1522
|
+
- /fix <command> : Run command, auto-fix if it fails
|
|
1523
|
+
- /search <keyword> : Search past sessions
|
|
1524
|
+
- /save : Save current session
|
|
1525
|
+
- /resume : Resume a saved session
|
|
1526
|
+
- /memory : Show auto memory
|
|
1527
|
+
- /tasks : Show task list
|
|
1528
|
+
- /context : Show context window usage
|
|
1529
|
+
- /rewind : Undo to previous checkpoint
|
|
1530
|
+
- /diff : Show git diff
|
|
1531
|
+
|
|
1532
|
+
# Input Prefixes
|
|
1533
|
+
- ! command : Execute a shell command directly (e.g., ! git status)
|
|
1534
|
+
- @file.ts : Attach file content to your message
|
|
1535
|
+
- \\ at end of line : Continue typing on next line (multiline input)`;
|
|
1536
|
+
var CONVERSATION_RULES = `# Conversation Rules
|
|
1537
|
+
- When a user asks "how do I..." or "what should I type...", give a clear EXPLANATION with example prompts they can type.
|
|
1538
|
+
- Do NOT execute commands or use tools when the user is just asking for information.
|
|
1539
|
+
- 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"
|
|
1540
|
+
- Only use tools when the user explicitly requests an action.
|
|
1541
|
+
- If the user's intent is ambiguous, ASK for clarification before acting.`;
|
|
1460
1542
|
var TOOL_HIERARCHY = `# Tool Usage Rules
|
|
1461
1543
|
- Use read_file instead of bash cat/head/tail
|
|
1462
1544
|
- Use edit_file instead of bash sed/awk
|
|
@@ -1515,6 +1597,7 @@ function buildSystemPrompt(context) {
|
|
|
1515
1597
|
const fragments = [];
|
|
1516
1598
|
fragments.push(ROLE_DEFINITION);
|
|
1517
1599
|
fragments.push(buildEnvironmentInfo(context));
|
|
1600
|
+
fragments.push(CONVERSATION_RULES);
|
|
1518
1601
|
fragments.push(TOOL_HIERARCHY);
|
|
1519
1602
|
if (os3.platform() === "win32") {
|
|
1520
1603
|
fragments.push(WINDOWS_RULES);
|
|
@@ -1619,15 +1702,15 @@ ${summaryContent}`;
|
|
|
1619
1702
|
// src/agent/memory.ts
|
|
1620
1703
|
init_esm_shims();
|
|
1621
1704
|
import * as fs4 from "fs";
|
|
1622
|
-
import * as
|
|
1705
|
+
import * as path6 from "path";
|
|
1623
1706
|
import * as crypto from "crypto";
|
|
1624
1707
|
var MemoryManager = class {
|
|
1625
1708
|
memoryDir;
|
|
1626
1709
|
constructor() {
|
|
1627
1710
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
1628
1711
|
const projectHash = crypto.createHash("md5").update(process.cwd()).digest("hex").slice(0, 8);
|
|
1629
|
-
const projectName =
|
|
1630
|
-
this.memoryDir =
|
|
1712
|
+
const projectName = path6.basename(process.cwd());
|
|
1713
|
+
this.memoryDir = path6.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
|
|
1631
1714
|
}
|
|
1632
1715
|
ensureDir() {
|
|
1633
1716
|
if (!fs4.existsSync(this.memoryDir)) {
|
|
@@ -1638,7 +1721,7 @@ var MemoryManager = class {
|
|
|
1638
1721
|
return this.memoryDir;
|
|
1639
1722
|
}
|
|
1640
1723
|
loadIndex() {
|
|
1641
|
-
const indexPath =
|
|
1724
|
+
const indexPath = path6.join(this.memoryDir, "MEMORY.md");
|
|
1642
1725
|
if (!fs4.existsSync(indexPath)) return "";
|
|
1643
1726
|
const content = fs4.readFileSync(indexPath, "utf-8");
|
|
1644
1727
|
const lines = content.split("\n");
|
|
@@ -1646,17 +1729,17 @@ var MemoryManager = class {
|
|
|
1646
1729
|
}
|
|
1647
1730
|
saveIndex(content) {
|
|
1648
1731
|
this.ensureDir();
|
|
1649
|
-
const indexPath =
|
|
1732
|
+
const indexPath = path6.join(this.memoryDir, "MEMORY.md");
|
|
1650
1733
|
fs4.writeFileSync(indexPath, content, "utf-8");
|
|
1651
1734
|
}
|
|
1652
1735
|
loadTopic(name) {
|
|
1653
|
-
const topicPath =
|
|
1736
|
+
const topicPath = path6.join(this.memoryDir, `${name}.md`);
|
|
1654
1737
|
if (!fs4.existsSync(topicPath)) return null;
|
|
1655
1738
|
return fs4.readFileSync(topicPath, "utf-8");
|
|
1656
1739
|
}
|
|
1657
1740
|
saveTopic(name, content) {
|
|
1658
1741
|
this.ensureDir();
|
|
1659
|
-
const topicPath =
|
|
1742
|
+
const topicPath = path6.join(this.memoryDir, `${name}.md`);
|
|
1660
1743
|
fs4.writeFileSync(topicPath, content, "utf-8");
|
|
1661
1744
|
}
|
|
1662
1745
|
listTopics() {
|
|
@@ -1681,13 +1764,13 @@ var memoryManager = new MemoryManager();
|
|
|
1681
1764
|
// src/agent/session.ts
|
|
1682
1765
|
init_esm_shims();
|
|
1683
1766
|
import * as fs5 from "fs";
|
|
1684
|
-
import * as
|
|
1767
|
+
import * as path7 from "path";
|
|
1685
1768
|
import * as crypto2 from "crypto";
|
|
1686
1769
|
var SessionManager = class {
|
|
1687
1770
|
sessionsDir;
|
|
1688
1771
|
constructor() {
|
|
1689
1772
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
1690
|
-
this.sessionsDir =
|
|
1773
|
+
this.sessionsDir = path7.join(home, ".codi", "sessions");
|
|
1691
1774
|
}
|
|
1692
1775
|
ensureDir() {
|
|
1693
1776
|
if (!fs5.existsSync(this.sessionsDir)) {
|
|
@@ -1697,7 +1780,7 @@ var SessionManager = class {
|
|
|
1697
1780
|
save(conversation, name, model) {
|
|
1698
1781
|
this.ensureDir();
|
|
1699
1782
|
const id = name || crypto2.randomUUID().slice(0, 8);
|
|
1700
|
-
const filePath =
|
|
1783
|
+
const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
|
|
1701
1784
|
const data = conversation.serialize();
|
|
1702
1785
|
const meta = {
|
|
1703
1786
|
id,
|
|
@@ -1717,7 +1800,7 @@ var SessionManager = class {
|
|
|
1717
1800
|
return id;
|
|
1718
1801
|
}
|
|
1719
1802
|
load(id) {
|
|
1720
|
-
const filePath =
|
|
1803
|
+
const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
|
|
1721
1804
|
if (!fs5.existsSync(filePath)) return null;
|
|
1722
1805
|
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1723
1806
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
@@ -1747,7 +1830,7 @@ var SessionManager = class {
|
|
|
1747
1830
|
const files = fs5.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
1748
1831
|
const sessions = [];
|
|
1749
1832
|
for (const file of files) {
|
|
1750
|
-
const filePath =
|
|
1833
|
+
const filePath = path7.join(this.sessionsDir, file);
|
|
1751
1834
|
try {
|
|
1752
1835
|
const firstLine = fs5.readFileSync(filePath, "utf-8").split("\n")[0];
|
|
1753
1836
|
if (firstLine) {
|
|
@@ -1767,7 +1850,7 @@ var SessionManager = class {
|
|
|
1767
1850
|
return sessions[0] ?? null;
|
|
1768
1851
|
}
|
|
1769
1852
|
delete(id) {
|
|
1770
|
-
const filePath =
|
|
1853
|
+
const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
|
|
1771
1854
|
if (fs5.existsSync(filePath)) {
|
|
1772
1855
|
fs5.unlinkSync(filePath);
|
|
1773
1856
|
return true;
|
|
@@ -1872,21 +1955,21 @@ var checkpointManager = new CheckpointManager();
|
|
|
1872
1955
|
// src/agent/codi-md.ts
|
|
1873
1956
|
init_esm_shims();
|
|
1874
1957
|
import * as fs6 from "fs";
|
|
1875
|
-
import * as
|
|
1958
|
+
import * as path8 from "path";
|
|
1876
1959
|
function loadCodiMd() {
|
|
1877
1960
|
const fragments = [];
|
|
1878
1961
|
let dir = process.cwd();
|
|
1879
|
-
const root =
|
|
1962
|
+
const root = path8.parse(dir).root;
|
|
1880
1963
|
while (dir !== root) {
|
|
1881
1964
|
loadFromDir(dir, fragments);
|
|
1882
|
-
const parent =
|
|
1965
|
+
const parent = path8.dirname(dir);
|
|
1883
1966
|
if (parent === dir) break;
|
|
1884
1967
|
dir = parent;
|
|
1885
1968
|
}
|
|
1886
1969
|
return fragments.join("\n\n---\n\n");
|
|
1887
1970
|
}
|
|
1888
1971
|
function loadFromDir(dir, fragments) {
|
|
1889
|
-
const codiPath =
|
|
1972
|
+
const codiPath = path8.join(dir, "CODI.md");
|
|
1890
1973
|
if (fs6.existsSync(codiPath)) {
|
|
1891
1974
|
try {
|
|
1892
1975
|
let content = fs6.readFileSync(codiPath, "utf-8");
|
|
@@ -1896,7 +1979,7 @@ ${content}`);
|
|
|
1896
1979
|
} catch {
|
|
1897
1980
|
}
|
|
1898
1981
|
}
|
|
1899
|
-
const localPath =
|
|
1982
|
+
const localPath = path8.join(dir, "CODI.local.md");
|
|
1900
1983
|
if (fs6.existsSync(localPath)) {
|
|
1901
1984
|
try {
|
|
1902
1985
|
const content = fs6.readFileSync(localPath, "utf-8");
|
|
@@ -1905,12 +1988,12 @@ ${content}`);
|
|
|
1905
1988
|
} catch {
|
|
1906
1989
|
}
|
|
1907
1990
|
}
|
|
1908
|
-
const rulesDir =
|
|
1991
|
+
const rulesDir = path8.join(dir, ".codi", "rules");
|
|
1909
1992
|
if (fs6.existsSync(rulesDir)) {
|
|
1910
1993
|
try {
|
|
1911
1994
|
const files = fs6.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort();
|
|
1912
1995
|
for (const file of files) {
|
|
1913
|
-
const content = fs6.readFileSync(
|
|
1996
|
+
const content = fs6.readFileSync(path8.join(rulesDir, file), "utf-8");
|
|
1914
1997
|
fragments.push(`[Rule: ${file}]
|
|
1915
1998
|
${content}`);
|
|
1916
1999
|
}
|
|
@@ -1920,7 +2003,7 @@ ${content}`);
|
|
|
1920
2003
|
}
|
|
1921
2004
|
function processImports(content, baseDir) {
|
|
1922
2005
|
return content.replace(/@([\w./-]+)/g, (match, importPath) => {
|
|
1923
|
-
const resolved =
|
|
2006
|
+
const resolved = path8.resolve(baseDir, importPath);
|
|
1924
2007
|
if (fs6.existsSync(resolved)) {
|
|
1925
2008
|
try {
|
|
1926
2009
|
return fs6.readFileSync(resolved, "utf-8");
|
|
@@ -2189,7 +2272,7 @@ var hookManager = new HookManager();
|
|
|
2189
2272
|
init_esm_shims();
|
|
2190
2273
|
init_tool();
|
|
2191
2274
|
import * as fs7 from "fs";
|
|
2192
|
-
import * as
|
|
2275
|
+
import * as path9 from "path";
|
|
2193
2276
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2194
2277
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2195
2278
|
import chalk9 from "chalk";
|
|
@@ -2211,8 +2294,8 @@ var McpManager = class {
|
|
|
2211
2294
|
const configs = {};
|
|
2212
2295
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2213
2296
|
const paths = [
|
|
2214
|
-
|
|
2215
|
-
|
|
2297
|
+
path9.join(home, ".codi", "mcp.json"),
|
|
2298
|
+
path9.join(process.cwd(), ".codi", "mcp.json")
|
|
2216
2299
|
];
|
|
2217
2300
|
for (const p of paths) {
|
|
2218
2301
|
try {
|
|
@@ -2414,7 +2497,7 @@ var subAgentTool = {
|
|
|
2414
2497
|
init_esm_shims();
|
|
2415
2498
|
import * as fs8 from "fs";
|
|
2416
2499
|
import * as os5 from "os";
|
|
2417
|
-
import * as
|
|
2500
|
+
import * as path10 from "path";
|
|
2418
2501
|
import chalk11 from "chalk";
|
|
2419
2502
|
function createBuiltinCommands() {
|
|
2420
2503
|
return [
|
|
@@ -2608,12 +2691,12 @@ Topics: ${topics.join(", ")}`));
|
|
|
2608
2691
|
name: "/init",
|
|
2609
2692
|
description: "Initialize CODI.md in the current project",
|
|
2610
2693
|
handler: async () => {
|
|
2611
|
-
const codiPath =
|
|
2694
|
+
const codiPath = path10.join(process.cwd(), "CODI.md");
|
|
2612
2695
|
if (fs8.existsSync(codiPath)) {
|
|
2613
2696
|
console.log(chalk11.yellow("CODI.md already exists"));
|
|
2614
2697
|
return true;
|
|
2615
2698
|
}
|
|
2616
|
-
const content = `# Project: ${
|
|
2699
|
+
const content = `# Project: ${path10.basename(process.cwd())}
|
|
2617
2700
|
|
|
2618
2701
|
## Overview
|
|
2619
2702
|
<!-- Describe your project here -->
|
|
@@ -2825,7 +2908,7 @@ ${diff}
|
|
|
2825
2908
|
return true;
|
|
2826
2909
|
}
|
|
2827
2910
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2828
|
-
const sessionsDir =
|
|
2911
|
+
const sessionsDir = path10.join(home, ".codi", "sessions");
|
|
2829
2912
|
if (!fs8.existsSync(sessionsDir)) {
|
|
2830
2913
|
console.log(chalk11.dim("\nNo sessions found.\n"));
|
|
2831
2914
|
return true;
|
|
@@ -2835,7 +2918,7 @@ ${diff}
|
|
|
2835
2918
|
const keyword = args.toLowerCase();
|
|
2836
2919
|
for (const file of files) {
|
|
2837
2920
|
if (results.length >= 10) break;
|
|
2838
|
-
const filePath =
|
|
2921
|
+
const filePath = path10.join(sessionsDir, file);
|
|
2839
2922
|
const lines = fs8.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
2840
2923
|
for (const line of lines) {
|
|
2841
2924
|
if (results.length >= 10) break;
|
|
@@ -2929,18 +3012,18 @@ function loadCustomCommands() {
|
|
|
2929
3012
|
const commands = [];
|
|
2930
3013
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2931
3014
|
const dirs = [
|
|
2932
|
-
|
|
2933
|
-
|
|
3015
|
+
path10.join(home, ".codi", "commands"),
|
|
3016
|
+
path10.join(process.cwd(), ".codi", "commands")
|
|
2934
3017
|
];
|
|
2935
3018
|
for (const dir of dirs) {
|
|
2936
3019
|
if (!fs8.existsSync(dir)) continue;
|
|
2937
3020
|
const files = fs8.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
2938
3021
|
for (const file of files) {
|
|
2939
3022
|
const name = "/" + file.replace(".md", "");
|
|
2940
|
-
const filePath =
|
|
3023
|
+
const filePath = path10.join(dir, file);
|
|
2941
3024
|
commands.push({
|
|
2942
3025
|
name,
|
|
2943
|
-
description: `Custom command from ${
|
|
3026
|
+
description: `Custom command from ${path10.relative(process.cwd(), filePath)}`,
|
|
2944
3027
|
handler: async (_args, ctx) => {
|
|
2945
3028
|
let content = fs8.readFileSync(filePath, "utf-8");
|
|
2946
3029
|
content = content.replace(/\{\{cwd\}\}/g, process.cwd()).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]).replace(/\{\{file_path\}\}/g, _args || "");
|
|
@@ -2957,7 +3040,7 @@ function loadCustomCommands() {
|
|
|
2957
3040
|
init_esm_shims();
|
|
2958
3041
|
init_tool();
|
|
2959
3042
|
import * as fs9 from "fs";
|
|
2960
|
-
import * as
|
|
3043
|
+
import * as path11 from "path";
|
|
2961
3044
|
var fileReadTool = {
|
|
2962
3045
|
name: "read_file",
|
|
2963
3046
|
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 +3060,7 @@ var fileReadTool = {
|
|
|
2977
3060
|
const filePath = String(input3["file_path"]);
|
|
2978
3061
|
const offset = input3["offset"];
|
|
2979
3062
|
const limit = input3["limit"];
|
|
2980
|
-
const resolved =
|
|
3063
|
+
const resolved = path11.resolve(filePath);
|
|
2981
3064
|
if (!fs9.existsSync(resolved)) {
|
|
2982
3065
|
return makeToolError(`File not found: ${resolved}`);
|
|
2983
3066
|
}
|
|
@@ -2985,7 +3068,7 @@ var fileReadTool = {
|
|
|
2985
3068
|
if (stat.isDirectory()) {
|
|
2986
3069
|
return makeToolError(`Path is a directory, not a file: ${resolved}. Use list_dir instead.`);
|
|
2987
3070
|
}
|
|
2988
|
-
const ext =
|
|
3071
|
+
const ext = path11.extname(resolved).toLowerCase();
|
|
2989
3072
|
if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"].includes(ext)) {
|
|
2990
3073
|
const data = fs9.readFileSync(resolved);
|
|
2991
3074
|
const base64 = data.toString("base64");
|
|
@@ -2998,7 +3081,7 @@ var fileReadTool = {
|
|
|
2998
3081
|
".bmp": "image/bmp",
|
|
2999
3082
|
".svg": "image/svg+xml"
|
|
3000
3083
|
};
|
|
3001
|
-
return makeToolResult(`[Image: ${
|
|
3084
|
+
return makeToolResult(`[Image: ${path11.basename(resolved)}]`, {
|
|
3002
3085
|
filePath: resolved,
|
|
3003
3086
|
isImage: true,
|
|
3004
3087
|
imageData: base64,
|
|
@@ -3084,7 +3167,7 @@ var fileReadTool = {
|
|
|
3084
3167
|
init_esm_shims();
|
|
3085
3168
|
init_tool();
|
|
3086
3169
|
import * as fs10 from "fs";
|
|
3087
|
-
import * as
|
|
3170
|
+
import * as path12 from "path";
|
|
3088
3171
|
var fileWriteTool = {
|
|
3089
3172
|
name: "write_file",
|
|
3090
3173
|
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 +3184,9 @@ var fileWriteTool = {
|
|
|
3101
3184
|
async execute(input3) {
|
|
3102
3185
|
const filePath = String(input3["file_path"]);
|
|
3103
3186
|
const content = String(input3["content"]);
|
|
3104
|
-
const resolved =
|
|
3187
|
+
const resolved = path12.resolve(filePath);
|
|
3105
3188
|
try {
|
|
3106
|
-
const dir =
|
|
3189
|
+
const dir = path12.dirname(resolved);
|
|
3107
3190
|
if (!fs10.existsSync(dir)) {
|
|
3108
3191
|
fs10.mkdirSync(dir, { recursive: true });
|
|
3109
3192
|
}
|
|
@@ -3125,7 +3208,7 @@ var fileWriteTool = {
|
|
|
3125
3208
|
init_esm_shims();
|
|
3126
3209
|
init_tool();
|
|
3127
3210
|
import * as fs11 from "fs";
|
|
3128
|
-
import * as
|
|
3211
|
+
import * as path13 from "path";
|
|
3129
3212
|
var fileEditTool = {
|
|
3130
3213
|
name: "edit_file",
|
|
3131
3214
|
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 +3229,7 @@ var fileEditTool = {
|
|
|
3146
3229
|
const oldString = String(input3["old_string"]);
|
|
3147
3230
|
const newString = String(input3["new_string"]);
|
|
3148
3231
|
const replaceAll = input3["replace_all"] === true;
|
|
3149
|
-
const resolved =
|
|
3232
|
+
const resolved = path13.resolve(filePath);
|
|
3150
3233
|
if (!fs11.existsSync(resolved)) {
|
|
3151
3234
|
return makeToolError(`File not found: ${resolved}`);
|
|
3152
3235
|
}
|
|
@@ -3205,7 +3288,7 @@ Did you mean this line?
|
|
|
3205
3288
|
init_esm_shims();
|
|
3206
3289
|
init_tool();
|
|
3207
3290
|
import * as fs12 from "fs";
|
|
3208
|
-
import * as
|
|
3291
|
+
import * as path14 from "path";
|
|
3209
3292
|
var fileMultiEditTool = {
|
|
3210
3293
|
name: "multi_edit",
|
|
3211
3294
|
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 +3316,7 @@ var fileMultiEditTool = {
|
|
|
3233
3316
|
async execute(input3) {
|
|
3234
3317
|
const filePath = String(input3["file_path"]);
|
|
3235
3318
|
const edits = input3["edits"];
|
|
3236
|
-
const resolved =
|
|
3319
|
+
const resolved = path14.resolve(filePath);
|
|
3237
3320
|
if (!fs12.existsSync(resolved)) {
|
|
3238
3321
|
return makeToolError(`File not found: ${resolved}`);
|
|
3239
3322
|
}
|
|
@@ -3281,7 +3364,7 @@ Searching for: ${edit2.old_string.slice(0, 100)}...`
|
|
|
3281
3364
|
init_esm_shims();
|
|
3282
3365
|
init_tool();
|
|
3283
3366
|
import * as fs13 from "fs";
|
|
3284
|
-
import * as
|
|
3367
|
+
import * as path15 from "path";
|
|
3285
3368
|
import { globby } from "globby";
|
|
3286
3369
|
var globTool = {
|
|
3287
3370
|
name: "glob",
|
|
@@ -3299,7 +3382,7 @@ var globTool = {
|
|
|
3299
3382
|
async execute(input3) {
|
|
3300
3383
|
const pattern = String(input3["pattern"]);
|
|
3301
3384
|
const searchPath = input3["path"] ? String(input3["path"]) : process.cwd();
|
|
3302
|
-
const resolved =
|
|
3385
|
+
const resolved = path15.resolve(searchPath);
|
|
3303
3386
|
try {
|
|
3304
3387
|
const files = await globby(pattern, {
|
|
3305
3388
|
cwd: resolved,
|
|
@@ -3336,7 +3419,7 @@ init_esm_shims();
|
|
|
3336
3419
|
init_tool();
|
|
3337
3420
|
import { execFile } from "child_process";
|
|
3338
3421
|
import * as fs14 from "fs";
|
|
3339
|
-
import * as
|
|
3422
|
+
import * as path16 from "path";
|
|
3340
3423
|
var grepTool = {
|
|
3341
3424
|
name: "grep",
|
|
3342
3425
|
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 +3449,7 @@ var grepTool = {
|
|
|
3366
3449
|
readOnly: true,
|
|
3367
3450
|
async execute(input3) {
|
|
3368
3451
|
const pattern = String(input3["pattern"]);
|
|
3369
|
-
const searchPath =
|
|
3452
|
+
const searchPath = path16.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
|
|
3370
3453
|
const outputMode = input3["output_mode"] || "files_with_matches";
|
|
3371
3454
|
const headLimit = input3["head_limit"] || 0;
|
|
3372
3455
|
const hasRg = await hasCommand("rg");
|
|
@@ -3556,11 +3639,11 @@ function collectFiles(dirPath, typeFilter, globFilter) {
|
|
|
3556
3639
|
for (const entry of entries) {
|
|
3557
3640
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
3558
3641
|
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
3559
|
-
const fullPath =
|
|
3642
|
+
const fullPath = path16.join(dir, entry.name);
|
|
3560
3643
|
if (entry.isDirectory()) {
|
|
3561
3644
|
walk(fullPath);
|
|
3562
3645
|
} else if (entry.isFile()) {
|
|
3563
|
-
const ext =
|
|
3646
|
+
const ext = path16.extname(entry.name).toLowerCase();
|
|
3564
3647
|
if (BINARY_EXTENSIONS.has(ext)) continue;
|
|
3565
3648
|
if (allowedExtensions && !allowedExtensions.has(ext)) continue;
|
|
3566
3649
|
if (globRegex && !globRegex.test(entry.name)) continue;
|
|
@@ -3687,7 +3770,7 @@ Use task_output tool to check results.`);
|
|
|
3687
3770
|
init_esm_shims();
|
|
3688
3771
|
init_tool();
|
|
3689
3772
|
import * as fs15 from "fs";
|
|
3690
|
-
import * as
|
|
3773
|
+
import * as path17 from "path";
|
|
3691
3774
|
var listDirTool = {
|
|
3692
3775
|
name: "list_dir",
|
|
3693
3776
|
description: `List directory contents with file/folder distinction and basic metadata.`,
|
|
@@ -3701,7 +3784,7 @@ var listDirTool = {
|
|
|
3701
3784
|
dangerous: false,
|
|
3702
3785
|
readOnly: true,
|
|
3703
3786
|
async execute(input3) {
|
|
3704
|
-
const dirPath =
|
|
3787
|
+
const dirPath = path17.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
|
|
3705
3788
|
if (!fs15.existsSync(dirPath)) {
|
|
3706
3789
|
return makeToolError(`Directory not found: ${dirPath}`);
|
|
3707
3790
|
}
|
|
@@ -3721,7 +3804,7 @@ var listDirTool = {
|
|
|
3721
3804
|
dirs.push(`${entry.name}/`);
|
|
3722
3805
|
} else if (entry.isSymbolicLink()) {
|
|
3723
3806
|
try {
|
|
3724
|
-
const target = fs15.readlinkSync(
|
|
3807
|
+
const target = fs15.readlinkSync(path17.join(dirPath, entry.name));
|
|
3725
3808
|
files.push(`${entry.name} -> ${target}`);
|
|
3726
3809
|
} catch {
|
|
3727
3810
|
files.push(`${entry.name} -> (broken link)`);
|
|
@@ -3934,7 +4017,7 @@ ${formatted}`);
|
|
|
3934
4017
|
init_esm_shims();
|
|
3935
4018
|
init_tool();
|
|
3936
4019
|
import * as fs16 from "fs";
|
|
3937
|
-
import * as
|
|
4020
|
+
import * as path18 from "path";
|
|
3938
4021
|
var notebookEditTool = {
|
|
3939
4022
|
name: "notebook_edit",
|
|
3940
4023
|
description: `Edit Jupyter notebook (.ipynb) cells. Supports replacing, inserting, and deleting cells.`,
|
|
@@ -3952,7 +4035,7 @@ var notebookEditTool = {
|
|
|
3952
4035
|
dangerous: true,
|
|
3953
4036
|
readOnly: false,
|
|
3954
4037
|
async execute(input3) {
|
|
3955
|
-
const nbPath =
|
|
4038
|
+
const nbPath = path18.resolve(String(input3["notebook_path"]));
|
|
3956
4039
|
const cellNumber = input3["cell_number"];
|
|
3957
4040
|
const newSource = String(input3["new_source"]);
|
|
3958
4041
|
const cellType = input3["cell_type"] || "code";
|