jinzd-ai-cli 0.4.103 → 0.4.105
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/{agent-client-6GX6QQDU.js → agent-client-25TIQ6AP.js} +1 -0
- package/dist/{auth-MSUWO6SE.js → auth-SC6KHHI3.js} +1 -0
- package/dist/{batch-LNTG2IRQ.js → batch-NPK4USGH.js} +3 -2
- package/dist/{chat-index-W2UZ34ZI.js → chat-index-7OHUKJY5.js} +1 -0
- package/dist/{chat-index-QKFH7ZP6.js → chat-index-ADG2GPCC.js} +1 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/{chunk-SN56X6RE.js → chunk-B6NUQVYK.js} +1 -1
- package/dist/{chunk-VOWVIR2U.js → chunk-F7XJ67XB.js} +30 -112
- package/dist/chunk-HOSJZMQS.js +97 -0
- package/dist/{chunk-OVYOYUP7.js → chunk-LVX667WL.js} +89 -36
- package/dist/{chunk-JHPSWYO3.js → chunk-LX5FXZVP.js} +7 -4
- package/dist/chunk-PDX44BCA.js +11 -0
- package/dist/{chunk-RZWWODW7.js → chunk-RFKT3T5S.js} +199 -74
- package/dist/{chunk-DGXUO7D4.js → chunk-VOF6OTZB.js} +89 -39
- package/dist/constants-HK5BB5EZ.js +78 -0
- package/dist/electron-server.js +289 -129
- package/dist/{file-checkpoint-CGH6OJVI.js → file-checkpoint-UHSMHCRU.js} +1 -0
- package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-ZN7KE3TN.js} +1 -0
- package/dist/git-context-7KIP4X2V.js +12 -0
- package/dist/{hub-4YGZ4XHN.js → hub-5VFGLTHY.js} +3 -2
- package/dist/{hub-server-BYXNQGDY.js → hub-server-AUMVPNU6.js} +1 -0
- package/dist/index.js +98 -47
- package/dist/{indexer-C7QYYHSZ.js → indexer-XGY7XGJM.js} +1 -0
- package/dist/{indexer-O5FCGFBJ.js → indexer-Z6AQTGBK.js} +1 -0
- package/dist/project-trust-EBGHD7LE.js +67 -0
- package/dist/project-trust-IFM7FXEV.js +68 -0
- package/dist/{run-tests-SN74WT4Z.js → run-tests-IMVI43CZ.js} +2 -1
- package/dist/{run-tests-3YOJEN2Q.js → run-tests-VQ3YZB75.js} +3 -2
- package/dist/{semantic-3KJPAUW6.js → semantic-FR2ZSQLY.js} +1 -0
- package/dist/{semantic-YDRPPVWK.js → semantic-UFKVYKFE.js} +3 -2
- package/dist/{server-BG4WR6RF.js → server-XDBIWNRW.js} +9 -8
- package/dist/{server-TNPDHGQT.js → server-ZVY3CKTJ.js} +65 -28
- package/dist/{store-S24SPPDZ.js → store-JDEW743P.js} +1 -0
- package/dist/{store-247B3TAU.js → store-Q7NMUCPP.js} +1 -0
- package/dist/{task-orchestrator-MUIH3XBY.js → task-orchestrator-UEZOFXQX.js} +9 -8
- package/dist/{vector-store-NDUFLNGN.js → vector-store-AK6J3RIA.js} +1 -0
- package/dist/{vector-store-QARQ2P6D.js → vector-store-MCQ77OOJ.js} +1 -0
- package/package.json +1 -1
- package/dist/{chunk-KJLJPUY2.js → chunk-3BICTI5M.js} +3 -3
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
hasSemanticIndex,
|
|
4
|
+
semanticSearch
|
|
5
|
+
} from "./chunk-3BICTI5M.js";
|
|
6
|
+
import {
|
|
7
|
+
runTestsTool
|
|
8
|
+
} from "./chunk-LVX667WL.js";
|
|
2
9
|
import {
|
|
3
10
|
EnvLoader,
|
|
4
11
|
NetworkError,
|
|
5
12
|
ToolError
|
|
6
13
|
} from "./chunk-2ZD3YTVM.js";
|
|
14
|
+
import {
|
|
15
|
+
CONFIG_DIR_NAME,
|
|
16
|
+
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
17
|
+
MEMORY_FILE_NAME,
|
|
18
|
+
SUBAGENT_ALLOWED_TOOLS,
|
|
19
|
+
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
20
|
+
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
21
|
+
} from "./chunk-LX5FXZVP.js";
|
|
7
22
|
import {
|
|
8
23
|
fileCheckpoints
|
|
9
24
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -14,24 +29,9 @@ import {
|
|
|
14
29
|
import {
|
|
15
30
|
indexProject
|
|
16
31
|
} from "./chunk-NHNWUBXB.js";
|
|
17
|
-
import {
|
|
18
|
-
hasSemanticIndex,
|
|
19
|
-
semanticSearch
|
|
20
|
-
} from "./chunk-KJLJPUY2.js";
|
|
21
32
|
import {
|
|
22
33
|
loadIndex
|
|
23
34
|
} from "./chunk-6VRJGH25.js";
|
|
24
|
-
import {
|
|
25
|
-
runTestsTool
|
|
26
|
-
} from "./chunk-OVYOYUP7.js";
|
|
27
|
-
import {
|
|
28
|
-
CONFIG_DIR_NAME,
|
|
29
|
-
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
30
|
-
MEMORY_FILE_NAME,
|
|
31
|
-
SUBAGENT_ALLOWED_TOOLS,
|
|
32
|
-
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
33
|
-
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
34
|
-
} from "./chunk-JHPSWYO3.js";
|
|
35
35
|
|
|
36
36
|
// src/tools/types.ts
|
|
37
37
|
function isFileWriteTool(name) {
|
|
@@ -43,11 +43,17 @@ function getDangerLevel(toolName, args) {
|
|
|
43
43
|
const cmd = String(args["command"] ?? "");
|
|
44
44
|
if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
|
|
45
45
|
if (/\brm\s+\S/.test(cmd)) return "destructive";
|
|
46
|
-
if (/\brmdir\b|\bformat\b|\bmkfs\b/.test(cmd)) return "destructive";
|
|
47
|
-
if (/\
|
|
46
|
+
if (/\brmdir\b|\bformat\b|\bmkfs\b|\bdd\s+if=|\bshred\b|\bfdisk\b|\bparted\b/.test(cmd)) return "destructive";
|
|
47
|
+
if (/\bshutdown\b|\breboot\b|\bhalt\b|\bpoweroff\b/.test(cmd)) return "destructive";
|
|
48
|
+
if (/\bkill\s+-9\b|\bkillall\b/.test(cmd)) return "destructive";
|
|
49
|
+
if (/\bRemove-Item\b|\bri\s+\S/i.test(cmd)) return "destructive";
|
|
50
|
+
if (/\brd\s+\/s\b|\brmdir\s+\/s\b/i.test(cmd)) return "destructive";
|
|
48
51
|
if (/\bdel\s+\S/.test(cmd)) return "destructive";
|
|
49
|
-
if (/\
|
|
50
|
-
if (
|
|
52
|
+
if (/\bShutdown(-Computer)?\b|\bRestart-Computer\b/i.test(cmd)) return "destructive";
|
|
53
|
+
if (/(?:^|[\s|;&])>>?\s*\S/.test(cmd)) return "write";
|
|
54
|
+
if (/\btee\b|\bcp\b|\bmv\b|\bln\s+-s/.test(cmd)) return "write";
|
|
55
|
+
if (/\bchmod\b|\bchown\b/.test(cmd)) return "write";
|
|
56
|
+
if (/\bSet-Content\b|\bOut-File\b|\bAdd-Content\b|\bCopy-Item\b|\bMove-Item\b|\bSet-ItemProperty\b|\bNew-ItemProperty\b/i.test(cmd)) return "write";
|
|
51
57
|
return "safe";
|
|
52
58
|
}
|
|
53
59
|
if (toolName === "write_file") return "write";
|
|
@@ -662,10 +668,10 @@ function createCell(cellType, content) {
|
|
|
662
668
|
// src/tools/builtin/read-file.ts
|
|
663
669
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
664
670
|
function getSensitiveWarning(normalizedPath) {
|
|
665
|
-
const
|
|
671
|
+
const home2 = homedir();
|
|
666
672
|
const p = normalizedPath.toLowerCase();
|
|
667
673
|
const base = basename(normalizedPath).toLowerCase();
|
|
668
|
-
if (normalizedPath.startsWith(
|
|
674
|
+
if (normalizedPath.startsWith(home2) && p.includes(".aicli") && base === "config.json") {
|
|
669
675
|
return "[\u26A0 Security Warning: This file contains API keys. Be careful not to share this content.]\n\n";
|
|
670
676
|
}
|
|
671
677
|
if (base === ".env" || base.startsWith(".env.") || base.endsWith(".env")) {
|
|
@@ -1159,8 +1165,14 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
|
|
|
1159
1165
|
if (rule.when) {
|
|
1160
1166
|
if (rule.when.dangerLevel && rule.when.dangerLevel !== dangerLevel) continue;
|
|
1161
1167
|
if (rule.when.pathPattern) {
|
|
1162
|
-
|
|
1163
|
-
|
|
1168
|
+
if (toolName === "bash" && rule.action === "auto-approve") {
|
|
1169
|
+
const cmd = String(args["command"] ?? "").trim();
|
|
1170
|
+
if (/[;&|`$<>]/.test(cmd) || /\$\(/.test(cmd)) continue;
|
|
1171
|
+
if (!cmd.startsWith(rule.when.pathPattern)) continue;
|
|
1172
|
+
} else {
|
|
1173
|
+
const path3 = String(args["path"] ?? args["command"] ?? "");
|
|
1174
|
+
if (!path3.includes(rule.when.pathPattern)) continue;
|
|
1175
|
+
}
|
|
1164
1176
|
}
|
|
1165
1177
|
}
|
|
1166
1178
|
return rule.action;
|
|
@@ -1606,7 +1618,7 @@ var ToolExecutor = class {
|
|
|
1606
1618
|
rl.resume();
|
|
1607
1619
|
process.stdout.write(prompt);
|
|
1608
1620
|
this.confirming = true;
|
|
1609
|
-
return new Promise((
|
|
1621
|
+
return new Promise((resolve5) => {
|
|
1610
1622
|
let completed = false;
|
|
1611
1623
|
const cleanup = (result) => {
|
|
1612
1624
|
if (completed) return;
|
|
@@ -1616,7 +1628,7 @@ var ToolExecutor = class {
|
|
|
1616
1628
|
rl.pause();
|
|
1617
1629
|
rlAny.output = savedOutput;
|
|
1618
1630
|
this.confirming = false;
|
|
1619
|
-
|
|
1631
|
+
resolve5(result);
|
|
1620
1632
|
};
|
|
1621
1633
|
const onLine = (line) => {
|
|
1622
1634
|
const trimmed = line.trim();
|
|
@@ -1786,7 +1798,7 @@ var ToolExecutor = class {
|
|
|
1786
1798
|
rl.resume();
|
|
1787
1799
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
1788
1800
|
this.confirming = true;
|
|
1789
|
-
return new Promise((
|
|
1801
|
+
return new Promise((resolve5) => {
|
|
1790
1802
|
let completed = false;
|
|
1791
1803
|
const cleanup = (answer) => {
|
|
1792
1804
|
if (completed) return;
|
|
@@ -1796,7 +1808,7 @@ var ToolExecutor = class {
|
|
|
1796
1808
|
rl.pause();
|
|
1797
1809
|
rlAny.output = savedOutput;
|
|
1798
1810
|
this.confirming = false;
|
|
1799
|
-
|
|
1811
|
+
resolve5(answer === "y");
|
|
1800
1812
|
};
|
|
1801
1813
|
const onLine = (line) => {
|
|
1802
1814
|
const trimmed = line.trim();
|
|
@@ -1823,6 +1835,99 @@ var ToolExecutor = class {
|
|
|
1823
1835
|
}
|
|
1824
1836
|
};
|
|
1825
1837
|
|
|
1838
|
+
// src/tools/sensitive-paths.ts
|
|
1839
|
+
import { resolve as resolve3, sep as sep2, basename as basename2 } from "path";
|
|
1840
|
+
import { homedir as homedir2 } from "os";
|
|
1841
|
+
var home = homedir2();
|
|
1842
|
+
function norm(p) {
|
|
1843
|
+
const abs = resolve3(p);
|
|
1844
|
+
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
1845
|
+
}
|
|
1846
|
+
function homeRel(p) {
|
|
1847
|
+
const abs = norm(p);
|
|
1848
|
+
const h = norm(home);
|
|
1849
|
+
if (abs === h) return ".";
|
|
1850
|
+
if (abs.startsWith(h + (process.platform === "win32" ? "\\" : "/"))) {
|
|
1851
|
+
return abs.slice(h.length + 1);
|
|
1852
|
+
}
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
1855
|
+
function classifyWritePath(path3) {
|
|
1856
|
+
if (!path3) return { sensitive: false };
|
|
1857
|
+
const abs = norm(path3);
|
|
1858
|
+
const base = basename2(abs);
|
|
1859
|
+
const rel = homeRel(path3);
|
|
1860
|
+
if (rel) {
|
|
1861
|
+
const shellRc = /* @__PURE__ */ new Set([
|
|
1862
|
+
".bashrc",
|
|
1863
|
+
".bash_profile",
|
|
1864
|
+
".bash_login",
|
|
1865
|
+
".profile",
|
|
1866
|
+
".zshrc",
|
|
1867
|
+
".zprofile",
|
|
1868
|
+
".zshenv",
|
|
1869
|
+
".zlogin",
|
|
1870
|
+
".kshrc",
|
|
1871
|
+
".cshrc",
|
|
1872
|
+
".tcshrc",
|
|
1873
|
+
".fish_config"
|
|
1874
|
+
]);
|
|
1875
|
+
if (shellRc.has(base)) {
|
|
1876
|
+
return { sensitive: true, reason: `shell startup file (${rel})` };
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const sshDir = `${sep2}.ssh${sep2}`;
|
|
1880
|
+
if (abs.includes(sshDir.toLowerCase()) || abs.includes(sshDir)) {
|
|
1881
|
+
return { sensitive: true, reason: `SSH directory (${rel ?? abs})` };
|
|
1882
|
+
}
|
|
1883
|
+
const credPaths = [
|
|
1884
|
+
`${sep2}.aws${sep2}credentials`,
|
|
1885
|
+
`${sep2}.aws${sep2}config`,
|
|
1886
|
+
`${sep2}.gcp${sep2}`,
|
|
1887
|
+
`${sep2}.azure${sep2}`,
|
|
1888
|
+
`${sep2}.config${sep2}gcloud${sep2}`,
|
|
1889
|
+
`${sep2}.kube${sep2}config`,
|
|
1890
|
+
`${sep2}.docker${sep2}config.json`,
|
|
1891
|
+
`${sep2}.netrc`
|
|
1892
|
+
];
|
|
1893
|
+
for (const c of credPaths) {
|
|
1894
|
+
const lc = process.platform === "win32" ? c.toLowerCase() : c;
|
|
1895
|
+
if (abs.includes(lc)) return { sensitive: true, reason: `cloud/auth credential file (${rel ?? abs})` };
|
|
1896
|
+
}
|
|
1897
|
+
if (rel && (rel === ".aicli" || rel.startsWith(".aicli" + (process.platform === "win32" ? "\\" : "/")))) {
|
|
1898
|
+
if (base === "config.json" || base === "users.json") {
|
|
1899
|
+
return { sensitive: true, reason: `ai-cli internal config (${rel})` };
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
if (abs.includes(`${sep2}.git${sep2}hooks${sep2}`) || abs.includes(`${sep2}.git${sep2}hooks${sep2}`.toLowerCase())) {
|
|
1903
|
+
return { sensitive: true, reason: ".git/hooks/* \u2014 runs on next git operation" };
|
|
1904
|
+
}
|
|
1905
|
+
if (abs.includes(`${sep2}.git${sep2}config`) || abs.includes(`${sep2}.git${sep2}config`.toLowerCase())) {
|
|
1906
|
+
return { sensitive: true, reason: ".git/config \u2014 repository identity" };
|
|
1907
|
+
}
|
|
1908
|
+
if (process.platform !== "win32") {
|
|
1909
|
+
const sysPrefixes = ["/etc/", "/usr/bin/", "/usr/sbin/", "/bin/", "/sbin/", "/boot/"];
|
|
1910
|
+
for (const pre of sysPrefixes) {
|
|
1911
|
+
if (abs.startsWith(pre)) return { sensitive: true, reason: `system path (${pre})` };
|
|
1912
|
+
}
|
|
1913
|
+
} else {
|
|
1914
|
+
const winSys = ["c:\\windows\\system32\\", "c:\\windows\\syswow64\\", "c:\\program files\\", "c:\\program files (x86)\\"];
|
|
1915
|
+
for (const pre of winSys) {
|
|
1916
|
+
if (abs.startsWith(pre)) return { sensitive: true, reason: `system path (${pre})` };
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (abs.includes(`${sep2}cron.d${sep2}`) || abs.startsWith("/etc/cron") || abs.startsWith("/var/spool/cron/")) {
|
|
1920
|
+
return { sensitive: true, reason: "cron scheduling" };
|
|
1921
|
+
}
|
|
1922
|
+
if (rel && (rel.startsWith("Library/LaunchAgents/") || rel.startsWith("Library/LaunchDaemons/"))) {
|
|
1923
|
+
return { sensitive: true, reason: "macOS launch agent" };
|
|
1924
|
+
}
|
|
1925
|
+
return { sensitive: false };
|
|
1926
|
+
}
|
|
1927
|
+
var subAgentGuard = {
|
|
1928
|
+
active: false
|
|
1929
|
+
};
|
|
1930
|
+
|
|
1826
1931
|
// src/tools/builtin/write-file.ts
|
|
1827
1932
|
var writeFileTool = {
|
|
1828
1933
|
definition: {
|
|
@@ -1865,6 +1970,13 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
1865
1970
|
const encoding = args["encoding"] ?? "utf-8";
|
|
1866
1971
|
const appendMode = String(args["append"] ?? "false").toLowerCase() === "true";
|
|
1867
1972
|
if (!filePath) throw new ToolError("write_file", "path is required");
|
|
1973
|
+
const verdict = classifyWritePath(filePath);
|
|
1974
|
+
if (verdict.sensitive && subAgentGuard.active) {
|
|
1975
|
+
throw new ToolError(
|
|
1976
|
+
"write_file",
|
|
1977
|
+
`Refused: sub-agents cannot write to sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the write so the user can approve it.`
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1868
1980
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
1869
1981
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
1870
1982
|
mkdirSync(dirname2(filePath), { recursive: true });
|
|
@@ -1877,7 +1989,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
1877
1989
|
const mode = appendMode ? "appended" : "written";
|
|
1878
1990
|
void (async () => {
|
|
1879
1991
|
try {
|
|
1880
|
-
const { updateFile } = await import("./indexer-
|
|
1992
|
+
const { updateFile } = await import("./indexer-XGY7XGJM.js");
|
|
1881
1993
|
await updateFile(process.cwd(), filePath);
|
|
1882
1994
|
} catch {
|
|
1883
1995
|
}
|
|
@@ -2249,6 +2361,13 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2249
2361
|
const encoding = args["encoding"] ?? "utf-8";
|
|
2250
2362
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
2251
2363
|
if (!existsSync5(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
2364
|
+
const verdict = classifyWritePath(filePath);
|
|
2365
|
+
if (verdict.sensitive && subAgentGuard.active) {
|
|
2366
|
+
throw new ToolError(
|
|
2367
|
+
"edit_file",
|
|
2368
|
+
`Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2252
2371
|
const original = readFileSync4(filePath, encoding);
|
|
2253
2372
|
if (args["patch"] !== void 0) {
|
|
2254
2373
|
const patchText = String(args["patch"] ?? "");
|
|
@@ -2408,7 +2527,7 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
2408
2527
|
|
|
2409
2528
|
// src/tools/builtin/list-dir.ts
|
|
2410
2529
|
import { readdirSync as readdirSync3, statSync as statSync3, existsSync as existsSync6 } from "fs";
|
|
2411
|
-
import { join, basename as
|
|
2530
|
+
import { join, basename as basename3 } from "path";
|
|
2412
2531
|
var listDirTool = {
|
|
2413
2532
|
definition: {
|
|
2414
2533
|
name: "list_dir",
|
|
@@ -2431,7 +2550,7 @@ var listDirTool = {
|
|
|
2431
2550
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
2432
2551
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
2433
2552
|
if (!existsSync6(dirPath)) {
|
|
2434
|
-
const targetName =
|
|
2553
|
+
const targetName = basename3(dirPath).toLowerCase();
|
|
2435
2554
|
const cwd = process.cwd();
|
|
2436
2555
|
const suggestions = [];
|
|
2437
2556
|
try {
|
|
@@ -2746,7 +2865,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
2746
2865
|
|
|
2747
2866
|
// src/tools/builtin/glob-files.ts
|
|
2748
2867
|
import { readdirSync as readdirSync5, statSync as statSync5, existsSync as existsSync8 } from "fs";
|
|
2749
|
-
import { join as join3, relative as relative2, basename as
|
|
2868
|
+
import { join as join3, relative as relative2, basename as basename4 } from "path";
|
|
2750
2869
|
var globFilesTool = {
|
|
2751
2870
|
definition: {
|
|
2752
2871
|
name: "glob_files",
|
|
@@ -2862,7 +2981,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
2862
2981
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
2863
2982
|
} else if (entry.isFile()) {
|
|
2864
2983
|
const relPath = relative2(rootPath, fullPath).replace(/\\/g, "/");
|
|
2865
|
-
if (regex.test(relPath) || regex.test(
|
|
2984
|
+
if (regex.test(relPath) || regex.test(basename4(relPath))) {
|
|
2866
2985
|
try {
|
|
2867
2986
|
const stat = statSync5(fullPath);
|
|
2868
2987
|
results.push({ relPath, absPath: fullPath, mtime: stat.mtimeMs });
|
|
@@ -2938,7 +3057,7 @@ var runInteractiveTool = {
|
|
|
2938
3057
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
2939
3058
|
};
|
|
2940
3059
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
2941
|
-
return new Promise((
|
|
3060
|
+
return new Promise((resolve5) => {
|
|
2942
3061
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
2943
3062
|
cwd: process.cwd(),
|
|
2944
3063
|
env,
|
|
@@ -2971,22 +3090,22 @@ var runInteractiveTool = {
|
|
|
2971
3090
|
setTimeout(writeNextLine, 400);
|
|
2972
3091
|
const timer = setTimeout(() => {
|
|
2973
3092
|
child.kill();
|
|
2974
|
-
|
|
3093
|
+
resolve5(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
2975
3094
|
${buildOutput(stdout, stderr)}`);
|
|
2976
3095
|
}, timeout);
|
|
2977
3096
|
child.on("close", (code) => {
|
|
2978
3097
|
clearTimeout(timer);
|
|
2979
3098
|
const output = buildOutput(stdout, stderr);
|
|
2980
3099
|
if (code !== 0 && code !== null) {
|
|
2981
|
-
|
|
3100
|
+
resolve5(`${prefixWarnings}Exit code ${code}:
|
|
2982
3101
|
${output}`);
|
|
2983
3102
|
} else {
|
|
2984
|
-
|
|
3103
|
+
resolve5(`${prefixWarnings}${output || "(no output)"}`);
|
|
2985
3104
|
}
|
|
2986
3105
|
});
|
|
2987
3106
|
child.on("error", (err) => {
|
|
2988
3107
|
clearTimeout(timer);
|
|
2989
|
-
|
|
3108
|
+
resolve5(
|
|
2990
3109
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
2991
3110
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
2992
3111
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -3256,9 +3375,9 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
3256
3375
|
// src/tools/builtin/save-memory.ts
|
|
3257
3376
|
import { existsSync as existsSync9, statSync as statSync6, appendFileSync as appendFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3258
3377
|
import { join as join4 } from "path";
|
|
3259
|
-
import { homedir as
|
|
3378
|
+
import { homedir as homedir3 } from "os";
|
|
3260
3379
|
function getMemoryFilePath() {
|
|
3261
|
-
return join4(
|
|
3380
|
+
return join4(homedir3(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
|
|
3262
3381
|
}
|
|
3263
3382
|
function formatTimestamp() {
|
|
3264
3383
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -3282,7 +3401,7 @@ var saveMemoryTool = {
|
|
|
3282
3401
|
const content = String(args["content"] ?? "").trim();
|
|
3283
3402
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
3284
3403
|
const memoryPath = getMemoryFilePath();
|
|
3285
|
-
const configDir = join4(
|
|
3404
|
+
const configDir = join4(homedir3(), CONFIG_DIR_NAME);
|
|
3286
3405
|
if (!existsSync9(configDir)) {
|
|
3287
3406
|
mkdirSync3(configDir, { recursive: true });
|
|
3288
3407
|
}
|
|
@@ -3337,7 +3456,7 @@ function promptUser(rl, question) {
|
|
|
3337
3456
|
console.log();
|
|
3338
3457
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
3339
3458
|
process.stdout.write(chalk4.cyan("> "));
|
|
3340
|
-
return new Promise((
|
|
3459
|
+
return new Promise((resolve5) => {
|
|
3341
3460
|
let completed = false;
|
|
3342
3461
|
const cleanup = (answer) => {
|
|
3343
3462
|
if (completed) return;
|
|
@@ -3347,7 +3466,7 @@ function promptUser(rl, question) {
|
|
|
3347
3466
|
rl.pause();
|
|
3348
3467
|
rlAny.output = savedOutput;
|
|
3349
3468
|
askUserContext.prompting = false;
|
|
3350
|
-
|
|
3469
|
+
resolve5(answer);
|
|
3351
3470
|
};
|
|
3352
3471
|
const onLine = (line) => {
|
|
3353
3472
|
cleanup(line);
|
|
@@ -3844,37 +3963,43 @@ var spawnAgentTool = {
|
|
|
3844
3963
|
if (!ctx.provider) {
|
|
3845
3964
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
3846
3965
|
}
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3966
|
+
const guardWasActive = subAgentGuard.active;
|
|
3967
|
+
subAgentGuard.active = true;
|
|
3968
|
+
try {
|
|
3969
|
+
if (singleTask) {
|
|
3970
|
+
const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
|
|
3971
|
+
return [
|
|
3972
|
+
"## Sub-Agent Result",
|
|
3973
|
+
"",
|
|
3974
|
+
content,
|
|
3975
|
+
"",
|
|
3976
|
+
"---",
|
|
3977
|
+
`Token usage: ${usage.inputTokens} input, ${usage.outputTokens} output`
|
|
3978
|
+
].join("\n");
|
|
3979
|
+
}
|
|
3980
|
+
console.log();
|
|
3981
|
+
console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel...`));
|
|
3982
|
+
const results = await Promise.all(
|
|
3983
|
+
tasksArr.map((t, i) => runSubAgent(t, maxRounds, i + 1, ctx))
|
|
3984
|
+
);
|
|
3985
|
+
const totalIn = results.reduce((s, r) => s + r.usage.inputTokens, 0);
|
|
3986
|
+
const totalOut = results.reduce((s, r) => s + r.usage.outputTokens, 0);
|
|
3987
|
+
const lines = [`## Parallel Sub-Agent Results (${results.length} agents)`, ""];
|
|
3988
|
+
results.forEach((r, i) => {
|
|
3989
|
+
lines.push(`### Sub-Agent #${i + 1}`);
|
|
3990
|
+
lines.push(`**Task**: ${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`);
|
|
3991
|
+
lines.push("");
|
|
3992
|
+
lines.push(r.content);
|
|
3993
|
+
lines.push("");
|
|
3994
|
+
lines.push(`*Tokens: ${r.usage.inputTokens} in / ${r.usage.outputTokens} out*`);
|
|
3995
|
+
lines.push("");
|
|
3996
|
+
});
|
|
3997
|
+
lines.push("---");
|
|
3998
|
+
lines.push(`Total token usage: ${totalIn} input, ${totalOut} output`);
|
|
3999
|
+
return lines.join("\n");
|
|
4000
|
+
} finally {
|
|
4001
|
+
subAgentGuard.active = guardWasActive;
|
|
3857
4002
|
}
|
|
3858
|
-
console.log();
|
|
3859
|
-
console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel...`));
|
|
3860
|
-
const results = await Promise.all(
|
|
3861
|
-
tasksArr.map((t, i) => runSubAgent(t, maxRounds, i + 1, ctx))
|
|
3862
|
-
);
|
|
3863
|
-
const totalIn = results.reduce((s, r) => s + r.usage.inputTokens, 0);
|
|
3864
|
-
const totalOut = results.reduce((s, r) => s + r.usage.outputTokens, 0);
|
|
3865
|
-
const lines = [`## Parallel Sub-Agent Results (${results.length} agents)`, ""];
|
|
3866
|
-
results.forEach((r, i) => {
|
|
3867
|
-
lines.push(`### Sub-Agent #${i + 1}`);
|
|
3868
|
-
lines.push(`**Task**: ${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`);
|
|
3869
|
-
lines.push("");
|
|
3870
|
-
lines.push(r.content);
|
|
3871
|
-
lines.push("");
|
|
3872
|
-
lines.push(`*Tokens: ${r.usage.inputTokens} in / ${r.usage.outputTokens} out*`);
|
|
3873
|
-
lines.push("");
|
|
3874
|
-
});
|
|
3875
|
-
lines.push("---");
|
|
3876
|
-
lines.push(`Total token usage: ${totalIn} input, ${totalOut} output`);
|
|
3877
|
-
return lines.join("\n");
|
|
3878
4003
|
}
|
|
3879
4004
|
};
|
|
3880
4005
|
|
|
@@ -4339,7 +4464,7 @@ ${commitOutput.trim()}`;
|
|
|
4339
4464
|
// src/tools/builtin/notebook-edit.ts
|
|
4340
4465
|
import { readFileSync as readFileSync6, existsSync as existsSync11 } from "fs";
|
|
4341
4466
|
import { writeFile } from "fs/promises";
|
|
4342
|
-
import { resolve as
|
|
4467
|
+
import { resolve as resolve4, extname as extname2 } from "path";
|
|
4343
4468
|
var notebookEditTool = {
|
|
4344
4469
|
definition: {
|
|
4345
4470
|
name: "notebook_edit",
|
|
@@ -4388,7 +4513,7 @@ var notebookEditTool = {
|
|
|
4388
4513
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
4389
4514
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
4390
4515
|
}
|
|
4391
|
-
const absPath =
|
|
4516
|
+
const absPath = resolve4(filePath);
|
|
4392
4517
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
4393
4518
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
4394
4519
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// src/tools/builtin/run-tests.ts
|
|
2
|
-
import { execSync } from "child_process";
|
|
2
|
+
import { execSync, spawnSync } from "child_process";
|
|
3
3
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { platform } from "os";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/core/constants.ts
|
|
9
|
-
var VERSION = "0.4.
|
|
9
|
+
var VERSION = "0.4.105";
|
|
10
10
|
var APP_NAME = "ai-cli";
|
|
11
11
|
var CONFIG_DIR_NAME = ".aicli";
|
|
12
12
|
var CONFIG_FILE_NAME = "config.json";
|
|
@@ -73,18 +73,15 @@ Once planning is complete, clearly inform the user: type \`/plan execute\` to be
|
|
|
73
73
|
var SUBAGENT_DEFAULT_MAX_ROUNDS = 15;
|
|
74
74
|
var SUBAGENT_MAX_ROUNDS_LIMIT = 30;
|
|
75
75
|
var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
|
|
76
|
-
"bash",
|
|
77
76
|
"read_file",
|
|
78
77
|
"write_file",
|
|
79
78
|
"edit_file",
|
|
80
79
|
"list_dir",
|
|
81
80
|
"grep_files",
|
|
82
81
|
"glob_files",
|
|
83
|
-
"run_interactive",
|
|
84
82
|
"web_fetch",
|
|
85
83
|
"google_search",
|
|
86
84
|
"write_todos",
|
|
87
|
-
"run_tests",
|
|
88
85
|
"find_symbol",
|
|
89
86
|
"get_outline",
|
|
90
87
|
"find_references",
|
|
@@ -136,6 +133,7 @@ var AUTHOR_EMAIL = "zhengdong.jin@gmail.com";
|
|
|
136
133
|
var DESCRIPTION = "Cross-platform REPL-style AI conversation tool with multi-provider and agentic tool calling support";
|
|
137
134
|
|
|
138
135
|
// src/tools/builtin/run-tests.ts
|
|
136
|
+
var FILTER_WHITELIST = /^[\w.\-/:#\s*?|^()\[\]@+]{1,200}$/;
|
|
139
137
|
var IS_WINDOWS = platform() === "win32";
|
|
140
138
|
function detectNodeTestFramework(cwd, pkg) {
|
|
141
139
|
const devDeps = pkg.devDependencies ?? {};
|
|
@@ -365,12 +363,62 @@ function renderColorReport(summary, framework) {
|
|
|
365
363
|
}
|
|
366
364
|
console.log();
|
|
367
365
|
}
|
|
366
|
+
function needsWindowsShell(file) {
|
|
367
|
+
if (!IS_WINDOWS) return false;
|
|
368
|
+
const lower = file.toLowerCase();
|
|
369
|
+
return lower.endsWith(".cmd") || lower.endsWith(".bat") || lower === "mvn" || lower === "gradle" || lower === "npm" || lower === "npx";
|
|
370
|
+
}
|
|
371
|
+
function buildArgv(detected, filter) {
|
|
372
|
+
switch (detected.type) {
|
|
373
|
+
case "java": {
|
|
374
|
+
const parts2 = detected.command.split(/\s+/);
|
|
375
|
+
const file = parts2[0];
|
|
376
|
+
const rest = parts2.slice(1);
|
|
377
|
+
const args = [...rest];
|
|
378
|
+
if (filter) {
|
|
379
|
+
args.push(detected.framework.startsWith("Maven") ? `-Dtest=${filter}` : `--tests=${filter}`);
|
|
380
|
+
}
|
|
381
|
+
return { file, args };
|
|
382
|
+
}
|
|
383
|
+
case "python":
|
|
384
|
+
return { file: "pytest", args: filter ? ["-v", "-k", filter] : ["-v"] };
|
|
385
|
+
case "rust":
|
|
386
|
+
return { file: "cargo", args: filter ? ["test", filter] : ["test"] };
|
|
387
|
+
case "go":
|
|
388
|
+
return filter ? { file: "go", args: ["test", "./...", "-run", filter] } : { file: "go", args: ["test", "./..."] };
|
|
389
|
+
case "node": {
|
|
390
|
+
if (detected.framework === "vitest") {
|
|
391
|
+
return { file: "npx", args: filter ? ["vitest", "run", "-t", filter] : ["vitest", "run"] };
|
|
392
|
+
}
|
|
393
|
+
if (detected.framework === "jest") {
|
|
394
|
+
return { file: "npx", args: filter ? ["jest", "-t", filter] : ["jest"] };
|
|
395
|
+
}
|
|
396
|
+
if (detected.framework === "mocha") {
|
|
397
|
+
return { file: "npx", args: filter ? ["mocha", "--grep", filter] : ["mocha"] };
|
|
398
|
+
}
|
|
399
|
+
if (detected.framework === "playwright") {
|
|
400
|
+
return { file: "npx", args: filter ? ["playwright", "test", "--grep", filter] : ["playwright", "test"] };
|
|
401
|
+
}
|
|
402
|
+
if (detected.framework === "ava") {
|
|
403
|
+
return { file: "npx", args: filter ? ["ava", "--match", filter] : ["ava"] };
|
|
404
|
+
}
|
|
405
|
+
return filter ? { file: "npm", args: ["test", "--", "--grep", filter] } : { file: "npm", args: ["test"] };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const parts = detected.command.split(/\s+/);
|
|
409
|
+
return { file: parts[0], args: parts.slice(1) };
|
|
410
|
+
}
|
|
368
411
|
async function executeTests(args) {
|
|
369
412
|
const cwd = process.cwd();
|
|
370
413
|
const customCmd = args["command"] ? String(args["command"]).trim() : "";
|
|
371
414
|
const filter = args["filter"] ? String(args["filter"]).trim() : "";
|
|
415
|
+
if (filter && !FILTER_WHITELIST.test(filter)) {
|
|
416
|
+
return `Error: filter contains characters that are not allowed in test names. Allowed: word chars, dots, dashes, slashes, colons, parens, spaces, and basic glob/regex meta (* ? | ^). Rejected: quotes, semicolons, backticks, redirects, and shell metacharacters.
|
|
417
|
+
Got: ${filter.slice(0, 60)}${filter.length > 60 ? "..." : ""}`;
|
|
418
|
+
}
|
|
372
419
|
let command;
|
|
373
420
|
let framework;
|
|
421
|
+
let argv = null;
|
|
374
422
|
if (customCmd) {
|
|
375
423
|
command = customCmd;
|
|
376
424
|
framework = "custom";
|
|
@@ -381,50 +429,52 @@ async function executeTests(args) {
|
|
|
381
429
|
}
|
|
382
430
|
command = detected.command;
|
|
383
431
|
framework = detected.framework;
|
|
384
|
-
|
|
385
|
-
if (detected.type === "java" && command.includes("mvn")) {
|
|
386
|
-
command += ` -Dtest="${filter}"`;
|
|
387
|
-
} else if (detected.type === "python") {
|
|
388
|
-
command += ` -k "${filter}"`;
|
|
389
|
-
} else if (detected.type === "rust") {
|
|
390
|
-
command += ` ${filter}`;
|
|
391
|
-
} else if (detected.type === "go") {
|
|
392
|
-
command = `go test ./... -run "${filter}"`;
|
|
393
|
-
} else if (detected.type === "node") {
|
|
394
|
-
if (detected.framework === "vitest") {
|
|
395
|
-
command += ` -t "${filter}"`;
|
|
396
|
-
} else if (detected.framework === "jest") {
|
|
397
|
-
command += ` -t "${filter}"`;
|
|
398
|
-
} else if (detected.framework === "mocha") {
|
|
399
|
-
command += ` --grep "${filter}"`;
|
|
400
|
-
} else if (detected.framework === "playwright") {
|
|
401
|
-
command += ` --grep "${filter}"`;
|
|
402
|
-
} else {
|
|
403
|
-
command += ` -- --grep "${filter}"`;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
432
|
+
argv = buildArgv(detected, filter);
|
|
407
433
|
}
|
|
408
434
|
let output;
|
|
409
435
|
let exitCode = 0;
|
|
410
|
-
|
|
411
|
-
const
|
|
436
|
+
if (argv) {
|
|
437
|
+
const r = spawnSync(argv.file, argv.args, {
|
|
412
438
|
cwd,
|
|
413
439
|
timeout: TEST_TIMEOUT,
|
|
414
|
-
encoding: "buffer",
|
|
415
440
|
stdio: ["pipe", "pipe", "pipe"],
|
|
441
|
+
// shell:false is the default for spawnSync; on Windows .cmd/.bat
|
|
442
|
+
// requires shell:true, so we opt-in only for known wrapper names.
|
|
443
|
+
shell: needsWindowsShell(argv.file),
|
|
416
444
|
env: {
|
|
417
445
|
...process.env,
|
|
418
446
|
...IS_WINDOWS ? {} : { FORCE_COLOR: "0" }
|
|
419
|
-
}
|
|
447
|
+
},
|
|
448
|
+
encoding: "utf-8",
|
|
449
|
+
maxBuffer: 64 * 1024 * 1024
|
|
420
450
|
});
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
451
|
+
if (r.error) {
|
|
452
|
+
output = `Failed to launch test runner: ${r.error.message}`;
|
|
453
|
+
exitCode = 1;
|
|
454
|
+
} else {
|
|
455
|
+
exitCode = r.status ?? 1;
|
|
456
|
+
output = (r.stdout ?? "") + (r.stderr ? "\n" + r.stderr : "");
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
try {
|
|
460
|
+
const buf = execSync(command, {
|
|
461
|
+
cwd,
|
|
462
|
+
timeout: TEST_TIMEOUT,
|
|
463
|
+
encoding: "buffer",
|
|
464
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
465
|
+
env: {
|
|
466
|
+
...process.env,
|
|
467
|
+
...IS_WINDOWS ? {} : { FORCE_COLOR: "0" }
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
output = buf.toString("utf-8");
|
|
471
|
+
} catch (err) {
|
|
472
|
+
const e = err;
|
|
473
|
+
exitCode = e.status ?? 1;
|
|
474
|
+
const stdout = e.stdout?.toString("utf-8") ?? "";
|
|
475
|
+
const stderr = e.stderr?.toString("utf-8") ?? "";
|
|
476
|
+
output = stdout + (stderr ? "\n" + stderr : "");
|
|
477
|
+
}
|
|
428
478
|
}
|
|
429
479
|
let summary = {
|
|
430
480
|
tests: 0,
|