jinzd-ai-cli 0.4.209 → 0.4.211
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 +104 -6
- package/README.zh-CN.md +45 -5
- package/dist/{batch-45WLJUVP.js → batch-DE4RXKZD.js} +2 -2
- package/dist/{chunk-EN63JA7N.js → chunk-6NS6643Y.js} +1 -1
- package/dist/{chunk-D6M4E7SH.js → chunk-BE6ERF7M.js} +1 -1
- package/dist/{chunk-ATNYI6JL.js → chunk-C3OU2OPF.js} +374 -22
- package/dist/{chunk-UPMBIS4T.js → chunk-E5XCM4A6.js} +5 -3
- package/dist/{chunk-STIEOOOL.js → chunk-JBWA73GK.js} +1 -1
- package/dist/{chunk-DUZRQXIP.js → chunk-UOROWTGG.js} +45 -6
- package/dist/{chunk-JVLRMIHA.js → chunk-ZOPYREL5.js} +312 -38
- package/dist/{chunk-EYID2AIR.js → chunk-ZY2N2N6T.js} +5 -3
- package/dist/{ci-PUE3MQ7J.js → ci-XMUEX526.js} +2 -2
- package/dist/{constants-VTZLKDAG.js → constants-AWTIQIWG.js} +3 -1
- package/dist/{doctor-cli-IU6FSVLF.js → doctor-cli-SSI6ETFT.js} +4 -4
- package/dist/electron-server.js +1006 -471
- package/dist/{hub-4ETK4R2J.js → hub-INUJND2G.js} +1 -1
- package/dist/index.js +137 -367
- package/dist/{run-tests-NDS2IEMK.js → run-tests-3MHWUF43.js} +2 -2
- package/dist/{run-tests-UAS5NJBM.js → run-tests-PACN4UYX.js} +1 -1
- package/dist/{server-72SAPLPS.js → server-7USZJJAH.js} +4 -4
- package/dist/{server-4URLNC7P.js → server-BTSKOPQI.js} +139 -311
- package/dist/{task-orchestrator-6PZTB35T.js → task-orchestrator-OV4O25MX.js} +4 -4
- package/dist/{usage-ZNCKGF6U.js → usage-T2P6FTE7.js} +2 -2
- package/dist/web/client/app.js +1 -1
- package/package.json +1 -1
|
@@ -1,21 +1,180 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
truncateForPersist
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZOPYREL5.js";
|
|
5
5
|
import {
|
|
6
6
|
APP_NAME,
|
|
7
7
|
CONFIG_DIR_NAME,
|
|
8
|
+
CONTEXT_FILE_CANDIDATES,
|
|
9
|
+
CONTEXT_FILE_MAX_BYTES,
|
|
8
10
|
DEV_STATE_FILE_NAME,
|
|
9
11
|
MCP_CALL_TIMEOUT,
|
|
10
12
|
MCP_CONNECT_TIMEOUT,
|
|
11
13
|
MCP_PROTOCOL_VERSION,
|
|
12
14
|
MCP_TOOL_PREFIX,
|
|
13
15
|
VERSION
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-E5XCM4A6.js";
|
|
15
17
|
import {
|
|
16
18
|
atomicWriteFileSync
|
|
17
19
|
} from "./chunk-IW3Q7AE5.js";
|
|
18
20
|
|
|
21
|
+
// src/core/context-files.ts
|
|
22
|
+
import { existsSync, readFileSync } from "fs";
|
|
23
|
+
import { join, relative, resolve } from "path";
|
|
24
|
+
function uniqueNonEmpty(values) {
|
|
25
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const value of values) {
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
30
|
+
seen.add(trimmed);
|
|
31
|
+
out.push(trimmed);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function buildContextFileCandidates(fallbackFilenames = []) {
|
|
36
|
+
return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
|
|
37
|
+
}
|
|
38
|
+
function displayPath(filePath, cwd) {
|
|
39
|
+
const rel = relative(cwd, filePath);
|
|
40
|
+
if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
|
|
41
|
+
return rel;
|
|
42
|
+
}
|
|
43
|
+
return filePath;
|
|
44
|
+
}
|
|
45
|
+
function isInsideOrEqual(parent, child) {
|
|
46
|
+
const rel = relative(resolve(parent), resolve(child));
|
|
47
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
48
|
+
}
|
|
49
|
+
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
50
|
+
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
51
|
+
const shown = displayPath(filePath, cwd);
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(filePath);
|
|
54
|
+
const byteCount = raw.byteLength;
|
|
55
|
+
if (byteCount === 0) {
|
|
56
|
+
return {
|
|
57
|
+
layer: null,
|
|
58
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const truncated = byteCount > maxBytes;
|
|
62
|
+
const bytes = truncated ? raw.subarray(0, maxBytes) : raw;
|
|
63
|
+
const content = bytes.toString("utf-8").trim();
|
|
64
|
+
if (!content) {
|
|
65
|
+
return {
|
|
66
|
+
layer: null,
|
|
67
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
layer: {
|
|
72
|
+
level: level === "single" ? "project" : level,
|
|
73
|
+
filePath,
|
|
74
|
+
displayPath: shown,
|
|
75
|
+
fileName: name,
|
|
76
|
+
content,
|
|
77
|
+
charCount: content.length,
|
|
78
|
+
byteCount,
|
|
79
|
+
truncated
|
|
80
|
+
},
|
|
81
|
+
skipped: truncated ? { level, filePath, displayPath: shown, fileName: name, reason: "too-large", message: `truncated to ${maxBytes} bytes` } : null
|
|
82
|
+
};
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return {
|
|
85
|
+
layer: null,
|
|
86
|
+
skipped: {
|
|
87
|
+
level,
|
|
88
|
+
filePath,
|
|
89
|
+
displayPath: shown,
|
|
90
|
+
fileName: name,
|
|
91
|
+
reason: "read-error",
|
|
92
|
+
message: err instanceof Error ? err.message : String(err)
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
98
|
+
const skipped = [];
|
|
99
|
+
for (const candidate of candidates) {
|
|
100
|
+
const filePath = join(dir, candidate);
|
|
101
|
+
if (!existsSync(filePath)) continue;
|
|
102
|
+
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
103
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
104
|
+
if (result.layer) return { layer: result.layer, skipped };
|
|
105
|
+
}
|
|
106
|
+
return { layer: null, skipped };
|
|
107
|
+
}
|
|
108
|
+
function loadContextFiles(options) {
|
|
109
|
+
const cwd = resolve(options.cwd);
|
|
110
|
+
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
111
|
+
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
112
|
+
const skipped = [];
|
|
113
|
+
if (options.setting === false) {
|
|
114
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
115
|
+
}
|
|
116
|
+
if (options.setting && options.setting !== "auto") {
|
|
117
|
+
const filePath = resolve(cwd, String(options.setting));
|
|
118
|
+
if (!isInsideOrEqual(cwd, filePath)) {
|
|
119
|
+
skipped.push({
|
|
120
|
+
level: "single",
|
|
121
|
+
filePath,
|
|
122
|
+
displayPath: filePath,
|
|
123
|
+
fileName: String(options.setting),
|
|
124
|
+
reason: "outside-cwd"
|
|
125
|
+
});
|
|
126
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
127
|
+
}
|
|
128
|
+
if (!existsSync(filePath)) {
|
|
129
|
+
skipped.push({
|
|
130
|
+
level: "single",
|
|
131
|
+
filePath,
|
|
132
|
+
displayPath: displayPath(filePath, cwd),
|
|
133
|
+
fileName: String(options.setting),
|
|
134
|
+
reason: "missing"
|
|
135
|
+
});
|
|
136
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
137
|
+
}
|
|
138
|
+
const result = readContextFile("single", filePath, cwd, maxBytes);
|
|
139
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
140
|
+
const layers2 = result.layer ? [result.layer] : [];
|
|
141
|
+
const mergedContent2 = layers2.map((l) => l.content).join("\n\n---\n\n");
|
|
142
|
+
return {
|
|
143
|
+
layers: layers2,
|
|
144
|
+
mergedContent: mergedContent2,
|
|
145
|
+
skipped,
|
|
146
|
+
totalChars: layers2.reduce((sum, l) => sum + l.charCount, 0),
|
|
147
|
+
totalBytes: layers2.reduce((sum, l) => sum + l.byteCount, 0),
|
|
148
|
+
maxBytes,
|
|
149
|
+
candidates
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const layers = [];
|
|
153
|
+
for (const [level, dir] of [
|
|
154
|
+
["global", options.configDir],
|
|
155
|
+
["project", options.projectRoot]
|
|
156
|
+
]) {
|
|
157
|
+
const result = findFirstContextFile(level, dir, cwd, candidates, maxBytes);
|
|
158
|
+
skipped.push(...result.skipped);
|
|
159
|
+
if (result.layer) layers.push(result.layer);
|
|
160
|
+
}
|
|
161
|
+
if (resolve(options.cwd) !== resolve(options.projectRoot)) {
|
|
162
|
+
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
163
|
+
skipped.push(...result.skipped);
|
|
164
|
+
if (result.layer) layers.push(result.layer);
|
|
165
|
+
}
|
|
166
|
+
const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
|
|
167
|
+
return {
|
|
168
|
+
layers,
|
|
169
|
+
mergedContent,
|
|
170
|
+
skipped,
|
|
171
|
+
totalChars: layers.reduce((sum, l) => sum + l.charCount, 0),
|
|
172
|
+
totalBytes: layers.reduce((sum, l) => sum + l.byteCount, 0),
|
|
173
|
+
maxBytes,
|
|
174
|
+
candidates
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
19
178
|
// src/mcp/client.ts
|
|
20
179
|
import { spawn } from "child_process";
|
|
21
180
|
var McpClient = class {
|
|
@@ -151,7 +310,7 @@ var McpClient = class {
|
|
|
151
310
|
// 内部方法:JSON-RPC 通信
|
|
152
311
|
// ══════════════════════════════════════════════════════════════════
|
|
153
312
|
sendRequest(method, params) {
|
|
154
|
-
return new Promise((
|
|
313
|
+
return new Promise((resolve2, reject) => {
|
|
155
314
|
if (!this.process?.stdin?.writable) {
|
|
156
315
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
157
316
|
}
|
|
@@ -175,7 +334,7 @@ var McpClient = class {
|
|
|
175
334
|
this.pendingRequests.set(id, {
|
|
176
335
|
resolve: (result) => {
|
|
177
336
|
cleanup();
|
|
178
|
-
|
|
337
|
+
resolve2(result);
|
|
179
338
|
},
|
|
180
339
|
reject: (error) => {
|
|
181
340
|
cleanup();
|
|
@@ -252,13 +411,13 @@ var McpClient = class {
|
|
|
252
411
|
}
|
|
253
412
|
/** Promise 超时包装 */
|
|
254
413
|
withTimeout(promise, ms, label) {
|
|
255
|
-
return new Promise((
|
|
414
|
+
return new Promise((resolve2, reject) => {
|
|
256
415
|
const timer = setTimeout(() => {
|
|
257
416
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
258
417
|
}, ms);
|
|
259
418
|
promise.then((val) => {
|
|
260
419
|
clearTimeout(timer);
|
|
261
|
-
|
|
420
|
+
resolve2(val);
|
|
262
421
|
}).catch((err) => {
|
|
263
422
|
clearTimeout(timer);
|
|
264
423
|
reject(err);
|
|
@@ -535,11 +694,11 @@ var McpManager = class {
|
|
|
535
694
|
};
|
|
536
695
|
|
|
537
696
|
// src/skills/manager.ts
|
|
538
|
-
import { existsSync, readdirSync, mkdirSync, statSync } from "fs";
|
|
539
|
-
import { join } from "path";
|
|
697
|
+
import { existsSync as existsSync2, readdirSync, mkdirSync, statSync } from "fs";
|
|
698
|
+
import { join as join2 } from "path";
|
|
540
699
|
|
|
541
700
|
// src/skills/types.ts
|
|
542
|
-
import { readFileSync } from "fs";
|
|
701
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
543
702
|
import { basename } from "path";
|
|
544
703
|
function parseSimpleYaml(yaml) {
|
|
545
704
|
const result = {};
|
|
@@ -561,7 +720,7 @@ function parseYamlArray(value) {
|
|
|
561
720
|
function parseSkillFile(filePath) {
|
|
562
721
|
let raw;
|
|
563
722
|
try {
|
|
564
|
-
raw =
|
|
723
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
565
724
|
} catch {
|
|
566
725
|
return null;
|
|
567
726
|
}
|
|
@@ -604,7 +763,7 @@ var SkillManager = class {
|
|
|
604
763
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
605
764
|
loadSkills() {
|
|
606
765
|
this.skills.clear();
|
|
607
|
-
if (!
|
|
766
|
+
if (!existsSync2(this.skillsDir)) {
|
|
608
767
|
try {
|
|
609
768
|
mkdirSync(this.skillsDir, { recursive: true });
|
|
610
769
|
} catch {
|
|
@@ -619,14 +778,14 @@ var SkillManager = class {
|
|
|
619
778
|
}
|
|
620
779
|
for (const entry of entries) {
|
|
621
780
|
let filePath;
|
|
622
|
-
const fullPath =
|
|
781
|
+
const fullPath = join2(this.skillsDir, entry);
|
|
623
782
|
if (entry.endsWith(".md")) {
|
|
624
783
|
filePath = fullPath;
|
|
625
784
|
} else {
|
|
626
785
|
try {
|
|
627
786
|
if (statSync(fullPath).isDirectory()) {
|
|
628
|
-
const skillMd =
|
|
629
|
-
if (
|
|
787
|
+
const skillMd = join2(fullPath, "SKILL.md");
|
|
788
|
+
if (existsSync2(skillMd)) {
|
|
630
789
|
filePath = skillMd;
|
|
631
790
|
} else {
|
|
632
791
|
continue;
|
|
@@ -699,6 +858,195 @@ async function setupProxy(configProxy) {
|
|
|
699
858
|
}
|
|
700
859
|
}
|
|
701
860
|
|
|
861
|
+
// src/repl/commands/project-init.ts
|
|
862
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
863
|
+
import { join as join3 } from "path";
|
|
864
|
+
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
865
|
+
"node_modules",
|
|
866
|
+
".git",
|
|
867
|
+
"dist",
|
|
868
|
+
"build",
|
|
869
|
+
"out",
|
|
870
|
+
"target",
|
|
871
|
+
".next",
|
|
872
|
+
".nuxt",
|
|
873
|
+
"__pycache__",
|
|
874
|
+
".venv",
|
|
875
|
+
"venv",
|
|
876
|
+
".tox",
|
|
877
|
+
".mypy_cache",
|
|
878
|
+
".pytest_cache",
|
|
879
|
+
".gradle",
|
|
880
|
+
".idea",
|
|
881
|
+
".vscode",
|
|
882
|
+
".vs",
|
|
883
|
+
"coverage",
|
|
884
|
+
".cache",
|
|
885
|
+
".parcel-cache",
|
|
886
|
+
"dist-cjs",
|
|
887
|
+
"release",
|
|
888
|
+
".output",
|
|
889
|
+
".turbo",
|
|
890
|
+
"vendor"
|
|
891
|
+
]);
|
|
892
|
+
function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
893
|
+
const lines = [];
|
|
894
|
+
let count = 0;
|
|
895
|
+
const walk = (d, prefix, depth) => {
|
|
896
|
+
if (depth > maxDepth || count >= maxEntries) return;
|
|
897
|
+
let entries;
|
|
898
|
+
try {
|
|
899
|
+
entries = readdirSync2(d);
|
|
900
|
+
} catch {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
|
|
904
|
+
const sorted = filtered.sort((a, b) => {
|
|
905
|
+
let aIsDir = false, bIsDir = false;
|
|
906
|
+
try {
|
|
907
|
+
aIsDir = statSync2(join3(d, a)).isDirectory();
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
try {
|
|
911
|
+
bIsDir = statSync2(join3(d, b)).isDirectory();
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
915
|
+
return a.localeCompare(b);
|
|
916
|
+
});
|
|
917
|
+
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
918
|
+
const name = sorted[i];
|
|
919
|
+
const fullPath = join3(d, name);
|
|
920
|
+
const isLast = i === sorted.length - 1;
|
|
921
|
+
const connector = isLast ? "+-- " : "|-- ";
|
|
922
|
+
let isDir;
|
|
923
|
+
try {
|
|
924
|
+
isDir = statSync2(fullPath).isDirectory();
|
|
925
|
+
} catch {
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
lines.push(prefix + connector + name + (isDir ? "/" : ""));
|
|
929
|
+
count++;
|
|
930
|
+
if (isDir) {
|
|
931
|
+
walk(fullPath, prefix + (isLast ? " " : "| "), depth + 1);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
walk(dir, "", 0);
|
|
936
|
+
if (count >= maxEntries) lines.push("... (truncated)");
|
|
937
|
+
return lines.join("\n");
|
|
938
|
+
}
|
|
939
|
+
function scanProject(cwd) {
|
|
940
|
+
const info = {
|
|
941
|
+
type: "unknown",
|
|
942
|
+
language: "unknown",
|
|
943
|
+
configFiles: [],
|
|
944
|
+
directoryStructure: ""
|
|
945
|
+
};
|
|
946
|
+
const check = (file) => existsSync3(join3(cwd, file));
|
|
947
|
+
const configCandidates = [
|
|
948
|
+
"package.json",
|
|
949
|
+
"tsconfig.json",
|
|
950
|
+
"Cargo.toml",
|
|
951
|
+
"pyproject.toml",
|
|
952
|
+
"setup.py",
|
|
953
|
+
"requirements.txt",
|
|
954
|
+
"go.mod",
|
|
955
|
+
"pom.xml",
|
|
956
|
+
"build.gradle",
|
|
957
|
+
"build.gradle.kts",
|
|
958
|
+
"CMakeLists.txt",
|
|
959
|
+
"Makefile",
|
|
960
|
+
".csproj",
|
|
961
|
+
".sln",
|
|
962
|
+
"composer.json",
|
|
963
|
+
"Gemfile",
|
|
964
|
+
"mix.exs",
|
|
965
|
+
"deno.json",
|
|
966
|
+
"bun.lockb"
|
|
967
|
+
];
|
|
968
|
+
info.configFiles = configCandidates.filter(check);
|
|
969
|
+
if (check("package.json")) {
|
|
970
|
+
info.type = "node";
|
|
971
|
+
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
972
|
+
try {
|
|
973
|
+
const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf-8"));
|
|
974
|
+
const scripts = pkg.scripts ?? {};
|
|
975
|
+
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
976
|
+
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
977
|
+
info.devCommand = scripts.dev ? "npm run dev" : void 0;
|
|
978
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
979
|
+
if (allDeps["react"]) info.framework = "React";
|
|
980
|
+
else if (allDeps["vue"]) info.framework = "Vue";
|
|
981
|
+
else if (allDeps["@angular/core"]) info.framework = "Angular";
|
|
982
|
+
else if (allDeps["next"]) info.framework = "Next.js";
|
|
983
|
+
else if (allDeps["nuxt"]) info.framework = "Nuxt";
|
|
984
|
+
else if (allDeps["express"]) info.framework = "Express";
|
|
985
|
+
else if (allDeps["fastify"]) info.framework = "Fastify";
|
|
986
|
+
else if (allDeps["svelte"]) info.framework = "Svelte";
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
989
|
+
} else if (check("Cargo.toml")) {
|
|
990
|
+
info.type = "rust";
|
|
991
|
+
info.language = "Rust";
|
|
992
|
+
info.buildCommand = "cargo build";
|
|
993
|
+
info.testCommand = "cargo test";
|
|
994
|
+
} else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
|
|
995
|
+
info.type = "python";
|
|
996
|
+
info.language = "Python";
|
|
997
|
+
info.testCommand = check("pytest.ini") || check("pyproject.toml") ? "pytest" : "python -m unittest";
|
|
998
|
+
} else if (check("go.mod")) {
|
|
999
|
+
info.type = "go";
|
|
1000
|
+
info.language = "Go";
|
|
1001
|
+
info.buildCommand = "go build ./...";
|
|
1002
|
+
info.testCommand = "go test ./...";
|
|
1003
|
+
} else if (check("pom.xml")) {
|
|
1004
|
+
info.type = "java";
|
|
1005
|
+
info.language = "Java";
|
|
1006
|
+
info.buildCommand = "mvn package";
|
|
1007
|
+
info.testCommand = "mvn test";
|
|
1008
|
+
} else if (check("build.gradle") || check("build.gradle.kts")) {
|
|
1009
|
+
info.type = "java";
|
|
1010
|
+
info.language = "Java/Kotlin";
|
|
1011
|
+
info.buildCommand = "./gradlew build";
|
|
1012
|
+
info.testCommand = "./gradlew test";
|
|
1013
|
+
}
|
|
1014
|
+
info.directoryStructure = scanDirTree(cwd);
|
|
1015
|
+
return info;
|
|
1016
|
+
}
|
|
1017
|
+
function buildInitPrompt(info, cwd) {
|
|
1018
|
+
const parts = [
|
|
1019
|
+
"Please generate an AICLI.md context file (Markdown format) for the following project. This file will be injected into the AI conversation system prompt to help the AI understand the project structure and coding conventions.",
|
|
1020
|
+
"\n## Project Info\n",
|
|
1021
|
+
`- Working directory: ${cwd}`,
|
|
1022
|
+
`- Type: ${info.type}`,
|
|
1023
|
+
`- Language: ${info.language}`
|
|
1024
|
+
];
|
|
1025
|
+
if (info.framework) parts.push(`- Framework: ${info.framework}`);
|
|
1026
|
+
if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
|
|
1027
|
+
if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
|
|
1028
|
+
if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
|
|
1029
|
+
parts.push(`
|
|
1030
|
+
## Detected Config Files
|
|
1031
|
+
${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
|
|
1032
|
+
parts.push(`
|
|
1033
|
+
## Directory Structure
|
|
1034
|
+
\`\`\`
|
|
1035
|
+
${info.directoryStructure}
|
|
1036
|
+
\`\`\``);
|
|
1037
|
+
parts.push(`
|
|
1038
|
+
## Requirements
|
|
1039
|
+
Please generate a structured Markdown file containing:
|
|
1040
|
+
1. Project overview (one-sentence summary)
|
|
1041
|
+
2. Tech stack
|
|
1042
|
+
3. Project structure description (based on the directory structure above)
|
|
1043
|
+
4. Common commands (build, test, dev, etc.)
|
|
1044
|
+
5. Code style and conventions (inferred from config files)
|
|
1045
|
+
|
|
1046
|
+
Output the Markdown content directly, do not wrap the entire file in a code block. Keep it concise, within 200 lines.`);
|
|
1047
|
+
return parts.join("\n");
|
|
1048
|
+
}
|
|
1049
|
+
|
|
702
1050
|
// src/core/git-diff.ts
|
|
703
1051
|
import { execFileSync } from "child_process";
|
|
704
1052
|
function readGitDiff(options = {}) {
|
|
@@ -798,8 +1146,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
798
1146
|
}
|
|
799
1147
|
|
|
800
1148
|
// src/repl/dev-state.ts
|
|
801
|
-
import { existsSync as
|
|
802
|
-
import { join as
|
|
1149
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1150
|
+
import { join as join4 } from "path";
|
|
803
1151
|
import { homedir } from "os";
|
|
804
1152
|
var DEV_STATE_MAX_CHARS = 6e3;
|
|
805
1153
|
var SNAPSHOT_PROMPT = `You are about to be replaced by a different AI model. Please generate a structured development state snapshot so the next model can continue seamlessly.
|
|
@@ -853,11 +1201,11 @@ function sessionHasMeaningfulContent(messages) {
|
|
|
853
1201
|
return hasUser && hasAssistant;
|
|
854
1202
|
}
|
|
855
1203
|
function getDevStatePath() {
|
|
856
|
-
return
|
|
1204
|
+
return join4(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
857
1205
|
}
|
|
858
1206
|
function saveDevState(content) {
|
|
859
|
-
const configDir =
|
|
860
|
-
if (!
|
|
1207
|
+
const configDir = join4(homedir(), CONFIG_DIR_NAME);
|
|
1208
|
+
if (!existsSync4(configDir)) {
|
|
861
1209
|
mkdirSync2(configDir, { recursive: true });
|
|
862
1210
|
}
|
|
863
1211
|
let trimmed = content.trim();
|
|
@@ -873,13 +1221,13 @@ function saveDevState(content) {
|
|
|
873
1221
|
}
|
|
874
1222
|
function loadDevState() {
|
|
875
1223
|
const path = getDevStatePath();
|
|
876
|
-
if (!
|
|
877
|
-
const content =
|
|
1224
|
+
if (!existsSync4(path)) return null;
|
|
1225
|
+
const content = readFileSync4(path, "utf-8").trim();
|
|
878
1226
|
return content || null;
|
|
879
1227
|
}
|
|
880
1228
|
function clearDevState() {
|
|
881
1229
|
const path = getDevStatePath();
|
|
882
|
-
if (
|
|
1230
|
+
if (existsSync4(path)) {
|
|
883
1231
|
try {
|
|
884
1232
|
unlinkSync(path);
|
|
885
1233
|
} catch {
|
|
@@ -888,10 +1236,14 @@ function clearDevState() {
|
|
|
888
1236
|
}
|
|
889
1237
|
|
|
890
1238
|
export {
|
|
1239
|
+
scanDirTree,
|
|
1240
|
+
scanProject,
|
|
1241
|
+
buildInitPrompt,
|
|
891
1242
|
readGitDiff,
|
|
892
1243
|
parseSimpleYaml,
|
|
893
1244
|
persistToolRound,
|
|
894
1245
|
autoTrimSessionIfNeeded,
|
|
1246
|
+
loadContextFiles,
|
|
895
1247
|
SNAPSHOT_PROMPT,
|
|
896
1248
|
sessionHasMeaningfulContent,
|
|
897
1249
|
saveDevState,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/core/constants.ts
|
|
4
|
-
var VERSION = "0.4.
|
|
4
|
+
var VERSION = "0.4.211";
|
|
5
5
|
var APP_NAME = "ai-cli";
|
|
6
6
|
var CONFIG_DIR_NAME = ".aicli";
|
|
7
7
|
var CONFIG_FILE_NAME = "config.json";
|
|
@@ -9,7 +9,8 @@ var HISTORY_DIR_NAME = "history";
|
|
|
9
9
|
var PLUGINS_DIR_NAME = "plugins";
|
|
10
10
|
var SKILLS_DIR_NAME = "skills";
|
|
11
11
|
var CUSTOM_COMMANDS_DIR_NAME = "commands";
|
|
12
|
-
var CONTEXT_FILE_CANDIDATES = ["AICLI.md", "CLAUDE.md"];
|
|
12
|
+
var CONTEXT_FILE_CANDIDATES = ["AICLI.override.md", "AGENTS.override.md", "AICLI.md", "CLAUDE.md", "AGENTS.md"];
|
|
13
|
+
var CONTEXT_FILE_MAX_BYTES = 32 * 1024;
|
|
13
14
|
var MEMORY_FILE_NAME = "memory.md";
|
|
14
15
|
var MEMORY_MAX_CHARS = 1e4;
|
|
15
16
|
var DEV_STATE_FILE_NAME = "dev-state.md";
|
|
@@ -95,7 +96,7 @@ var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
|
|
|
95
96
|
- Only begin using write/execute tools when the user **explicitly requests** an action (e.g., "generate", "create", "modify", "run", "start", etc.).
|
|
96
97
|
- **Stop when the explicit task is done \u2014 do NOT autonomously test, verify, or run what you just created.** If the user asks "write/create/save file X", finish writing the file and STOP. Do not run it, do not test it, do not "verify it works" with bash/run_interactive. The user will run it themselves and tell you if anything is wrong. Only run/test when the user explicitly says "run", "test", "execute", "try it", "verify", "make sure it works", etc.
|
|
97
98
|
- **Do NOT retry the same failing command with variants.** If a command fails (exit code non-zero), report the failure to the user with a brief diagnosis and STOP. Do not loop through alternate shells, alternate quoting, alternate paths, or alternate Python invocations. The user can decide whether to retry.
|
|
98
|
-
- Project context files (CLAUDE.md,
|
|
99
|
+
- Project context files (AICLI.md, CLAUDE.md, AGENTS.md, and override variants) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
|
|
99
100
|
- If you are unsure about the user's intent, use the ask_user tool to confirm with the user, rather than assuming and executing on your own.
|
|
100
101
|
- **Do NOT abuse ask_user for redundant confirmations**: When the user has already given a clear, explicit instruction (e.g., "write lesson 142", "generate file X", "create the report"), execute it immediately. Do NOT ask "are you sure?" or request details that can be found in project documents. Repeatedly asking the user to confirm wastes their time and is extremely frustrating. Only use ask_user when critical information is genuinely missing and cannot be inferred from context files.`;
|
|
101
102
|
function buildUserIdentityPrompt(profile) {
|
|
@@ -142,6 +143,7 @@ export {
|
|
|
142
143
|
SKILLS_DIR_NAME,
|
|
143
144
|
CUSTOM_COMMANDS_DIR_NAME,
|
|
144
145
|
CONTEXT_FILE_CANDIDATES,
|
|
146
|
+
CONTEXT_FILE_MAX_BYTES,
|
|
145
147
|
MEMORY_FILE_NAME,
|
|
146
148
|
MEMORY_MAX_CHARS,
|
|
147
149
|
DEV_STATE_FILE_NAME,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
CONFIG_FILE_NAME,
|
|
9
9
|
HISTORY_DIR_NAME,
|
|
10
10
|
PLUGINS_DIR_NAME
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-E5XCM4A6.js";
|
|
12
12
|
import {
|
|
13
13
|
atomicWriteFileSync
|
|
14
14
|
} from "./chunk-IW3Q7AE5.js";
|
|
@@ -122,10 +122,15 @@ var ConfigSchema = z.object({
|
|
|
122
122
|
systemPrompt: z.string().optional()
|
|
123
123
|
}).default({}),
|
|
124
124
|
// 项目上下文文件配置
|
|
125
|
-
// 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md 机制
|
|
126
|
-
// 默认按顺序查找:AICLI.md →
|
|
127
|
-
// 设为 false
|
|
125
|
+
// 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md / Codex AGENTS.md 机制
|
|
126
|
+
// 默认按顺序查找:AICLI.override.md → AGENTS.override.md → AICLI.md → CLAUDE.md → AGENTS.md
|
|
127
|
+
// 设为 false 可禁用此功能;指定字符串时仅加载 cwd 下的该文件
|
|
128
128
|
contextFile: z.union([z.string(), z.literal(false)]).default("auto"),
|
|
129
|
+
context: z.object({
|
|
130
|
+
projectDocFallbackFilenames: z.array(z.string()).default([]),
|
|
131
|
+
projectDocMaxBytes: z.number().int().positive().default(32 * 1024),
|
|
132
|
+
showLoadedContextSources: z.boolean().default(false)
|
|
133
|
+
}).default({}),
|
|
129
134
|
// Google Custom Search API 的 Search Engine ID (cx 参数)
|
|
130
135
|
// API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
|
|
131
136
|
// CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
|
|
@@ -157,7 +162,41 @@ var ConfigSchema = z.object({
|
|
|
157
162
|
preToolExecution: z.string().optional(),
|
|
158
163
|
postToolExecution: z.string().optional()
|
|
159
164
|
}).optional(),
|
|
160
|
-
//
|
|
165
|
+
// 网络访问治理(v0.4.211+):默认关闭以保持兼容;开启后统一治理 web/search/MCP/shell 网络出口。
|
|
166
|
+
networkPolicy: z.object({
|
|
167
|
+
enabled: z.boolean().default(false),
|
|
168
|
+
defaultAction: z.enum(["allow", "confirm", "deny"]).default("confirm"),
|
|
169
|
+
allowDomains: z.array(z.string()).default([]),
|
|
170
|
+
denyDomains: z.array(z.string()).default([]),
|
|
171
|
+
allowPorts: z.array(z.number().int().min(1).max(65535)).default([]),
|
|
172
|
+
denyPorts: z.array(z.number().int().min(1).max(65535)).default([]),
|
|
173
|
+
allowPrivateNetwork: z.boolean().default(false),
|
|
174
|
+
tools: z.object({
|
|
175
|
+
web_fetch: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
176
|
+
web_search: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
177
|
+
google_search: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
178
|
+
shell: z.enum(["allow", "confirm", "deny"]).optional(),
|
|
179
|
+
mcp: z.enum(["allow", "confirm", "deny"]).optional()
|
|
180
|
+
}).default({})
|
|
181
|
+
}).default({ enabled: false, defaultAction: "confirm", allowDomains: [], denyDomains: [], allowPorts: [], denyPorts: [], allowPrivateNetwork: false, tools: {} }),
|
|
182
|
+
// 权限 Profile(v0.4.211+):legacy 保持旧行为;其余 profile 提供更清晰的安全边界。
|
|
183
|
+
defaultPermissionProfile: z.enum(["legacy", "read-only", "workspace-write", "danger-full-access"]).default("legacy"),
|
|
184
|
+
allowedPermissionProfiles: z.array(z.string()).default(["legacy", "read-only", "workspace-write", "danger-full-access"]),
|
|
185
|
+
permissionProfiles: z.record(z.object({
|
|
186
|
+
extends: z.enum(["legacy", "read-only", "workspace-write", "danger-full-access"]).optional(),
|
|
187
|
+
defaultAction: z.enum(["auto-approve", "deny", "confirm"]).optional(),
|
|
188
|
+
workspaceRoots: z.array(z.string()).optional(),
|
|
189
|
+
allowTemp: z.boolean().optional(),
|
|
190
|
+
rules: z.array(z.object({
|
|
191
|
+
tool: z.string(),
|
|
192
|
+
action: z.enum(["auto-approve", "deny", "confirm"]),
|
|
193
|
+
when: z.object({
|
|
194
|
+
dangerLevel: z.enum(["safe", "write", "destructive"]).optional(),
|
|
195
|
+
pathPattern: z.string().optional()
|
|
196
|
+
}).optional()
|
|
197
|
+
})).optional()
|
|
198
|
+
})).default({}),
|
|
199
|
+
// 工具权限规则(按顺序匹配第一个生效;作为当前权限 Profile 内的细粒度规则)
|
|
161
200
|
permissionRules: z.array(z.object({
|
|
162
201
|
tool: z.string(),
|
|
163
202
|
action: z.enum(["auto-approve", "deny", "confirm"]),
|
|
@@ -166,7 +205,7 @@ var ConfigSchema = z.object({
|
|
|
166
205
|
pathPattern: z.string().optional()
|
|
167
206
|
}).optional()
|
|
168
207
|
})).default([]),
|
|
169
|
-
//
|
|
208
|
+
// 无规则匹配时的默认权限动作(legacy profile 兼容旧行为)
|
|
170
209
|
defaultPermission: z.enum(["auto-approve", "deny", "confirm"]).default("confirm"),
|
|
171
210
|
// 自动上下文压缩开关
|
|
172
211
|
// 当对话估算 token 数超过模型 contextWindow 的 80% 时,自动触发 compact 压缩旧消息
|