jinzd-ai-cli 0.4.209 → 0.4.210
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 +5 -5
- package/README.zh-CN.md +5 -5
- package/dist/{batch-45WLJUVP.js → batch-MFPYHOIL.js} +2 -2
- package/dist/{chunk-ATNYI6JL.js → chunk-7JWT3KXO.js} +374 -22
- package/dist/{chunk-STIEOOOL.js → chunk-DJ35G5VO.js} +1 -1
- package/dist/{chunk-EN63JA7N.js → chunk-E2ZWWE6Q.js} +1 -1
- package/dist/{chunk-UPMBIS4T.js → chunk-GRJNQJA5.js} +5 -3
- package/dist/{chunk-EYID2AIR.js → chunk-HZQVX7VF.js} +5 -3
- package/dist/{chunk-JVLRMIHA.js → chunk-NEPFADHX.js} +3 -3
- package/dist/{chunk-D6M4E7SH.js → chunk-NWP7C6CC.js} +1 -1
- package/dist/{chunk-DUZRQXIP.js → chunk-P6MTFCXB.js} +9 -4
- package/dist/{ci-PUE3MQ7J.js → ci-QLJUDLMY.js} +2 -2
- package/dist/{constants-VTZLKDAG.js → constants-47OR72MV.js} +3 -1
- package/dist/{doctor-cli-IU6FSVLF.js → doctor-cli-32COMA5K.js} +4 -4
- package/dist/electron-server.js +589 -427
- package/dist/{hub-4ETK4R2J.js → hub-3NWJUCLK.js} +1 -1
- package/dist/index.js +119 -366
- package/dist/{run-tests-UAS5NJBM.js → run-tests-NT2UIUVB.js} +1 -1
- package/dist/{run-tests-NDS2IEMK.js → run-tests-UT7RZ7Y3.js} +2 -2
- package/dist/{server-72SAPLPS.js → server-AIHVMLNU.js} +4 -4
- package/dist/{server-4URLNC7P.js → server-E5D54DIZ.js} +50 -291
- package/dist/{task-orchestrator-6PZTB35T.js → task-orchestrator-BFDSTSOH.js} +4 -4
- package/dist/{usage-ZNCKGF6U.js → usage-P4B2RZKL.js} +2 -2
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
CONFIG_DIR_NAME,
|
|
13
13
|
CONFIG_FILE_NAME,
|
|
14
14
|
CONTEXT_FILE_CANDIDATES,
|
|
15
|
+
CONTEXT_FILE_MAX_BYTES,
|
|
15
16
|
CUSTOM_COMMANDS_DIR_NAME,
|
|
16
17
|
DEFAULT_MAX_TOKENS,
|
|
17
18
|
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
VERSION,
|
|
37
38
|
buildUserIdentityPrompt,
|
|
38
39
|
runTestsTool
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-HZQVX7VF.js";
|
|
40
41
|
import {
|
|
41
42
|
hasSemanticIndex,
|
|
42
43
|
semanticSearch
|
|
@@ -59,8 +60,8 @@ import {
|
|
|
59
60
|
import express from "express";
|
|
60
61
|
import { createServer } from "http";
|
|
61
62
|
import { WebSocketServer } from "ws";
|
|
62
|
-
import { join as
|
|
63
|
-
import { existsSync as
|
|
63
|
+
import { join as join19, dirname as dirname6, resolve as resolve7, relative as relative4, sep as sep3 } from "path";
|
|
64
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
|
|
64
65
|
import { networkInterfaces } from "os";
|
|
65
66
|
|
|
66
67
|
// src/config/config-manager.ts
|
|
@@ -172,10 +173,15 @@ var ConfigSchema = z.object({
|
|
|
172
173
|
systemPrompt: z.string().optional()
|
|
173
174
|
}).default({}),
|
|
174
175
|
// 项目上下文文件配置
|
|
175
|
-
// 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md 机制
|
|
176
|
-
// 默认按顺序查找:AICLI.md →
|
|
177
|
-
// 设为 false
|
|
176
|
+
// 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md / Codex AGENTS.md 机制
|
|
177
|
+
// 默认按顺序查找:AICLI.override.md → AGENTS.override.md → AICLI.md → CLAUDE.md → AGENTS.md
|
|
178
|
+
// 设为 false 可禁用此功能;指定字符串时仅加载 cwd 下的该文件
|
|
178
179
|
contextFile: z.union([z.string(), z.literal(false)]).default("auto"),
|
|
180
|
+
context: z.object({
|
|
181
|
+
projectDocFallbackFilenames: z.array(z.string()).default([]),
|
|
182
|
+
projectDocMaxBytes: z.number().int().positive().default(32 * 1024),
|
|
183
|
+
showLoadedContextSources: z.boolean().default(false)
|
|
184
|
+
}).default({}),
|
|
179
185
|
// Google Custom Search API 的 Search Engine ID (cx 参数)
|
|
180
186
|
// API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
|
|
181
187
|
// CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
|
|
@@ -6489,7 +6495,7 @@ var ToolExecutor = class {
|
|
|
6489
6495
|
rl.resume();
|
|
6490
6496
|
process.stdout.write(prompt);
|
|
6491
6497
|
this.confirming = true;
|
|
6492
|
-
return new Promise((
|
|
6498
|
+
return new Promise((resolve8) => {
|
|
6493
6499
|
let completed = false;
|
|
6494
6500
|
const cleanup = (result) => {
|
|
6495
6501
|
if (completed) return;
|
|
@@ -6499,7 +6505,7 @@ var ToolExecutor = class {
|
|
|
6499
6505
|
rl.pause();
|
|
6500
6506
|
rlAny.output = savedOutput;
|
|
6501
6507
|
this.confirming = false;
|
|
6502
|
-
|
|
6508
|
+
resolve8(result);
|
|
6503
6509
|
};
|
|
6504
6510
|
const onLine = (line) => {
|
|
6505
6511
|
const trimmed = line.trim();
|
|
@@ -6673,7 +6679,7 @@ var ToolExecutor = class {
|
|
|
6673
6679
|
rl.resume();
|
|
6674
6680
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
6675
6681
|
this.confirming = true;
|
|
6676
|
-
return new Promise((
|
|
6682
|
+
return new Promise((resolve8) => {
|
|
6677
6683
|
let completed = false;
|
|
6678
6684
|
const cleanup = (answer) => {
|
|
6679
6685
|
if (completed) return;
|
|
@@ -6683,7 +6689,7 @@ var ToolExecutor = class {
|
|
|
6683
6689
|
rl.pause();
|
|
6684
6690
|
rlAny.output = savedOutput;
|
|
6685
6691
|
this.confirming = false;
|
|
6686
|
-
|
|
6692
|
+
resolve8(answer === "y");
|
|
6687
6693
|
};
|
|
6688
6694
|
const onLine = (line) => {
|
|
6689
6695
|
const trimmed = line.trim();
|
|
@@ -7666,7 +7672,7 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
7666
7672
|
walk(rootPath);
|
|
7667
7673
|
return paths;
|
|
7668
7674
|
}
|
|
7669
|
-
async function searchInFileAsync(fullPath,
|
|
7675
|
+
async function searchInFileAsync(fullPath, displayPath2, regex, contextLines, maxResults) {
|
|
7670
7676
|
let content;
|
|
7671
7677
|
try {
|
|
7672
7678
|
content = await readFile(fullPath, "utf-8");
|
|
@@ -7680,7 +7686,7 @@ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, max
|
|
|
7680
7686
|
regex.lastIndex = 0;
|
|
7681
7687
|
if (regex.test(lines[i])) {
|
|
7682
7688
|
const match = {
|
|
7683
|
-
file:
|
|
7689
|
+
file: displayPath2.replace(/\\/g, "/"),
|
|
7684
7690
|
lineNumber: i + 1,
|
|
7685
7691
|
lineText: lines[i]
|
|
7686
7692
|
};
|
|
@@ -7699,7 +7705,7 @@ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, max
|
|
|
7699
7705
|
}
|
|
7700
7706
|
return results;
|
|
7701
7707
|
}
|
|
7702
|
-
function searchInFile(fullPath,
|
|
7708
|
+
function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, results) {
|
|
7703
7709
|
try {
|
|
7704
7710
|
const stat = statSync4(fullPath);
|
|
7705
7711
|
if (stat.size > 1e6) return;
|
|
@@ -7719,7 +7725,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
7719
7725
|
regex.lastIndex = 0;
|
|
7720
7726
|
if (regex.test(lines[i])) {
|
|
7721
7727
|
const result = {
|
|
7722
|
-
file:
|
|
7728
|
+
file: displayPath2,
|
|
7723
7729
|
lineNumber: i + 1,
|
|
7724
7730
|
lineText: lines[i].trimEnd()
|
|
7725
7731
|
};
|
|
@@ -7932,7 +7938,7 @@ var runInteractiveTool = {
|
|
|
7932
7938
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
7933
7939
|
};
|
|
7934
7940
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
7935
|
-
return new Promise((
|
|
7941
|
+
return new Promise((resolve8) => {
|
|
7936
7942
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
7937
7943
|
cwd: process.cwd(),
|
|
7938
7944
|
env,
|
|
@@ -7965,22 +7971,22 @@ var runInteractiveTool = {
|
|
|
7965
7971
|
setTimeout(writeNextLine, 400);
|
|
7966
7972
|
const timer = setTimeout(() => {
|
|
7967
7973
|
child.kill();
|
|
7968
|
-
|
|
7974
|
+
resolve8(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
7969
7975
|
${buildOutput(stdout, stderr)}`);
|
|
7970
7976
|
}, timeout);
|
|
7971
7977
|
child.on("close", (code) => {
|
|
7972
7978
|
clearTimeout(timer);
|
|
7973
7979
|
const output = buildOutput(stdout, stderr);
|
|
7974
7980
|
if (code !== 0 && code !== null) {
|
|
7975
|
-
|
|
7981
|
+
resolve8(`${prefixWarnings}Exit code ${code}:
|
|
7976
7982
|
${output}`);
|
|
7977
7983
|
} else {
|
|
7978
|
-
|
|
7984
|
+
resolve8(`${prefixWarnings}${output || "(no output)"}`);
|
|
7979
7985
|
}
|
|
7980
7986
|
});
|
|
7981
7987
|
child.on("error", (err) => {
|
|
7982
7988
|
clearTimeout(timer);
|
|
7983
|
-
|
|
7989
|
+
resolve8(
|
|
7984
7990
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
7985
7991
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
7986
7992
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -8732,7 +8738,7 @@ function promptUser(rl, question) {
|
|
|
8732
8738
|
console.log();
|
|
8733
8739
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
8734
8740
|
process.stdout.write(chalk4.cyan("> "));
|
|
8735
|
-
return new Promise((
|
|
8741
|
+
return new Promise((resolve8) => {
|
|
8736
8742
|
let completed = false;
|
|
8737
8743
|
const cleanup = (answer) => {
|
|
8738
8744
|
if (completed) return;
|
|
@@ -8742,7 +8748,7 @@ function promptUser(rl, question) {
|
|
|
8742
8748
|
rl.pause();
|
|
8743
8749
|
rlAny.output = savedOutput;
|
|
8744
8750
|
askUserContext.prompting = false;
|
|
8745
|
-
|
|
8751
|
+
resolve8(answer);
|
|
8746
8752
|
};
|
|
8747
8753
|
const onLine = (line) => {
|
|
8748
8754
|
cleanup(line);
|
|
@@ -10581,7 +10587,7 @@ var McpClient = class {
|
|
|
10581
10587
|
// 内部方法:JSON-RPC 通信
|
|
10582
10588
|
// ══════════════════════════════════════════════════════════════════
|
|
10583
10589
|
sendRequest(method, params) {
|
|
10584
|
-
return new Promise((
|
|
10590
|
+
return new Promise((resolve8, reject) => {
|
|
10585
10591
|
if (!this.process?.stdin?.writable) {
|
|
10586
10592
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
10587
10593
|
}
|
|
@@ -10605,7 +10611,7 @@ var McpClient = class {
|
|
|
10605
10611
|
this.pendingRequests.set(id, {
|
|
10606
10612
|
resolve: (result) => {
|
|
10607
10613
|
cleanup();
|
|
10608
|
-
|
|
10614
|
+
resolve8(result);
|
|
10609
10615
|
},
|
|
10610
10616
|
reject: (error) => {
|
|
10611
10617
|
cleanup();
|
|
@@ -10682,13 +10688,13 @@ var McpClient = class {
|
|
|
10682
10688
|
}
|
|
10683
10689
|
/** Promise 超时包装 */
|
|
10684
10690
|
withTimeout(promise, ms, label) {
|
|
10685
|
-
return new Promise((
|
|
10691
|
+
return new Promise((resolve8, reject) => {
|
|
10686
10692
|
const timer = setTimeout(() => {
|
|
10687
10693
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
10688
10694
|
}, ms);
|
|
10689
10695
|
promise.then((val) => {
|
|
10690
10696
|
clearTimeout(timer);
|
|
10691
|
-
|
|
10697
|
+
resolve8(val);
|
|
10692
10698
|
}).catch((err) => {
|
|
10693
10699
|
clearTimeout(timer);
|
|
10694
10700
|
reject(err);
|
|
@@ -11176,33 +11182,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11176
11182
|
}
|
|
11177
11183
|
/** Resolve a pending confirm from client response */
|
|
11178
11184
|
resolveConfirm(requestId, approved) {
|
|
11179
|
-
const
|
|
11180
|
-
if (
|
|
11185
|
+
const resolve8 = this.pendingConfirms.get(requestId);
|
|
11186
|
+
if (resolve8) {
|
|
11181
11187
|
this.clearPendingTimer(requestId);
|
|
11182
11188
|
this.pendingConfirms.delete(requestId);
|
|
11183
11189
|
this.confirming = false;
|
|
11184
|
-
|
|
11190
|
+
resolve8(approved);
|
|
11185
11191
|
}
|
|
11186
11192
|
}
|
|
11187
11193
|
/** Resolve a pending batch confirm from client response */
|
|
11188
11194
|
resolveBatchConfirm(requestId, decision) {
|
|
11189
|
-
const
|
|
11190
|
-
if (
|
|
11195
|
+
const resolve8 = this.pendingBatchConfirms.get(requestId);
|
|
11196
|
+
if (resolve8) {
|
|
11191
11197
|
this.clearPendingTimer(requestId);
|
|
11192
11198
|
this.pendingBatchConfirms.delete(requestId);
|
|
11193
11199
|
this.confirming = false;
|
|
11194
11200
|
if (decision === "all" || decision === "none") {
|
|
11195
|
-
|
|
11201
|
+
resolve8(decision);
|
|
11196
11202
|
} else {
|
|
11197
|
-
|
|
11203
|
+
resolve8(new Set(decision));
|
|
11198
11204
|
}
|
|
11199
11205
|
}
|
|
11200
11206
|
}
|
|
11201
11207
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
11202
11208
|
cancelAll() {
|
|
11203
|
-
for (const
|
|
11209
|
+
for (const resolve8 of this.pendingConfirms.values()) resolve8(false);
|
|
11204
11210
|
this.pendingConfirms.clear();
|
|
11205
|
-
for (const
|
|
11211
|
+
for (const resolve8 of this.pendingBatchConfirms.values()) resolve8("none");
|
|
11206
11212
|
this.pendingBatchConfirms.clear();
|
|
11207
11213
|
this.confirming = false;
|
|
11208
11214
|
}
|
|
@@ -11278,8 +11284,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11278
11284
|
diff: this.getDiffPreview(call)
|
|
11279
11285
|
};
|
|
11280
11286
|
this.send(msg);
|
|
11281
|
-
return new Promise((
|
|
11282
|
-
this.pendingConfirms.set(requestId,
|
|
11287
|
+
return new Promise((resolve8) => {
|
|
11288
|
+
this.pendingConfirms.set(requestId, resolve8);
|
|
11283
11289
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11284
11290
|
if (this.pendingConfirms.has(requestId)) {
|
|
11285
11291
|
this.resolveConfirm(requestId, false);
|
|
@@ -11303,8 +11309,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11303
11309
|
files
|
|
11304
11310
|
};
|
|
11305
11311
|
this.send(msg);
|
|
11306
|
-
return new Promise((
|
|
11307
|
-
this.pendingBatchConfirms.set(requestId,
|
|
11312
|
+
return new Promise((resolve8) => {
|
|
11313
|
+
this.pendingBatchConfirms.set(requestId, resolve8);
|
|
11308
11314
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11309
11315
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
11310
11316
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -11729,14 +11735,171 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
11729
11735
|
return committedSize < originalSize;
|
|
11730
11736
|
}
|
|
11731
11737
|
|
|
11738
|
+
// src/core/context-files.ts
|
|
11739
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
11740
|
+
import { join as join13, relative as relative3, resolve as resolve5 } from "path";
|
|
11741
|
+
function uniqueNonEmpty(values) {
|
|
11742
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11743
|
+
const out = [];
|
|
11744
|
+
for (const value of values) {
|
|
11745
|
+
const trimmed = value.trim();
|
|
11746
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
11747
|
+
seen.add(trimmed);
|
|
11748
|
+
out.push(trimmed);
|
|
11749
|
+
}
|
|
11750
|
+
return out;
|
|
11751
|
+
}
|
|
11752
|
+
function buildContextFileCandidates(fallbackFilenames = []) {
|
|
11753
|
+
return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
|
|
11754
|
+
}
|
|
11755
|
+
function displayPath(filePath, cwd) {
|
|
11756
|
+
const rel = relative3(cwd, filePath);
|
|
11757
|
+
if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
|
|
11758
|
+
return rel;
|
|
11759
|
+
}
|
|
11760
|
+
return filePath;
|
|
11761
|
+
}
|
|
11762
|
+
function isInsideOrEqual(parent, child) {
|
|
11763
|
+
const rel = relative3(resolve5(parent), resolve5(child));
|
|
11764
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
11765
|
+
}
|
|
11766
|
+
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
11767
|
+
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
11768
|
+
const shown = displayPath(filePath, cwd);
|
|
11769
|
+
try {
|
|
11770
|
+
const raw = readFileSync15(filePath);
|
|
11771
|
+
const byteCount = raw.byteLength;
|
|
11772
|
+
if (byteCount === 0) {
|
|
11773
|
+
return {
|
|
11774
|
+
layer: null,
|
|
11775
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
11776
|
+
};
|
|
11777
|
+
}
|
|
11778
|
+
const truncated = byteCount > maxBytes;
|
|
11779
|
+
const bytes = truncated ? raw.subarray(0, maxBytes) : raw;
|
|
11780
|
+
const content = bytes.toString("utf-8").trim();
|
|
11781
|
+
if (!content) {
|
|
11782
|
+
return {
|
|
11783
|
+
layer: null,
|
|
11784
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
11785
|
+
};
|
|
11786
|
+
}
|
|
11787
|
+
return {
|
|
11788
|
+
layer: {
|
|
11789
|
+
level: level === "single" ? "project" : level,
|
|
11790
|
+
filePath,
|
|
11791
|
+
displayPath: shown,
|
|
11792
|
+
fileName: name,
|
|
11793
|
+
content,
|
|
11794
|
+
charCount: content.length,
|
|
11795
|
+
byteCount,
|
|
11796
|
+
truncated
|
|
11797
|
+
},
|
|
11798
|
+
skipped: truncated ? { level, filePath, displayPath: shown, fileName: name, reason: "too-large", message: `truncated to ${maxBytes} bytes` } : null
|
|
11799
|
+
};
|
|
11800
|
+
} catch (err) {
|
|
11801
|
+
return {
|
|
11802
|
+
layer: null,
|
|
11803
|
+
skipped: {
|
|
11804
|
+
level,
|
|
11805
|
+
filePath,
|
|
11806
|
+
displayPath: shown,
|
|
11807
|
+
fileName: name,
|
|
11808
|
+
reason: "read-error",
|
|
11809
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11810
|
+
}
|
|
11811
|
+
};
|
|
11812
|
+
}
|
|
11813
|
+
}
|
|
11814
|
+
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
11815
|
+
const skipped = [];
|
|
11816
|
+
for (const candidate of candidates) {
|
|
11817
|
+
const filePath = join13(dir, candidate);
|
|
11818
|
+
if (!existsSync20(filePath)) continue;
|
|
11819
|
+
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
11820
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
11821
|
+
if (result.layer) return { layer: result.layer, skipped };
|
|
11822
|
+
}
|
|
11823
|
+
return { layer: null, skipped };
|
|
11824
|
+
}
|
|
11825
|
+
function loadContextFiles(options) {
|
|
11826
|
+
const cwd = resolve5(options.cwd);
|
|
11827
|
+
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
11828
|
+
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
11829
|
+
const skipped = [];
|
|
11830
|
+
if (options.setting === false) {
|
|
11831
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
11832
|
+
}
|
|
11833
|
+
if (options.setting && options.setting !== "auto") {
|
|
11834
|
+
const filePath = resolve5(cwd, String(options.setting));
|
|
11835
|
+
if (!isInsideOrEqual(cwd, filePath)) {
|
|
11836
|
+
skipped.push({
|
|
11837
|
+
level: "single",
|
|
11838
|
+
filePath,
|
|
11839
|
+
displayPath: filePath,
|
|
11840
|
+
fileName: String(options.setting),
|
|
11841
|
+
reason: "outside-cwd"
|
|
11842
|
+
});
|
|
11843
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
11844
|
+
}
|
|
11845
|
+
if (!existsSync20(filePath)) {
|
|
11846
|
+
skipped.push({
|
|
11847
|
+
level: "single",
|
|
11848
|
+
filePath,
|
|
11849
|
+
displayPath: displayPath(filePath, cwd),
|
|
11850
|
+
fileName: String(options.setting),
|
|
11851
|
+
reason: "missing"
|
|
11852
|
+
});
|
|
11853
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
11854
|
+
}
|
|
11855
|
+
const result = readContextFile("single", filePath, cwd, maxBytes);
|
|
11856
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
11857
|
+
const layers2 = result.layer ? [result.layer] : [];
|
|
11858
|
+
const mergedContent2 = layers2.map((l) => l.content).join("\n\n---\n\n");
|
|
11859
|
+
return {
|
|
11860
|
+
layers: layers2,
|
|
11861
|
+
mergedContent: mergedContent2,
|
|
11862
|
+
skipped,
|
|
11863
|
+
totalChars: layers2.reduce((sum, l) => sum + l.charCount, 0),
|
|
11864
|
+
totalBytes: layers2.reduce((sum, l) => sum + l.byteCount, 0),
|
|
11865
|
+
maxBytes,
|
|
11866
|
+
candidates
|
|
11867
|
+
};
|
|
11868
|
+
}
|
|
11869
|
+
const layers = [];
|
|
11870
|
+
for (const [level, dir] of [
|
|
11871
|
+
["global", options.configDir],
|
|
11872
|
+
["project", options.projectRoot]
|
|
11873
|
+
]) {
|
|
11874
|
+
const result = findFirstContextFile(level, dir, cwd, candidates, maxBytes);
|
|
11875
|
+
skipped.push(...result.skipped);
|
|
11876
|
+
if (result.layer) layers.push(result.layer);
|
|
11877
|
+
}
|
|
11878
|
+
if (resolve5(options.cwd) !== resolve5(options.projectRoot)) {
|
|
11879
|
+
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
11880
|
+
skipped.push(...result.skipped);
|
|
11881
|
+
if (result.layer) layers.push(result.layer);
|
|
11882
|
+
}
|
|
11883
|
+
const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
|
|
11884
|
+
return {
|
|
11885
|
+
layers,
|
|
11886
|
+
mergedContent,
|
|
11887
|
+
skipped,
|
|
11888
|
+
totalChars: layers.reduce((sum, l) => sum + l.charCount, 0),
|
|
11889
|
+
totalBytes: layers.reduce((sum, l) => sum + l.byteCount, 0),
|
|
11890
|
+
maxBytes,
|
|
11891
|
+
candidates
|
|
11892
|
+
};
|
|
11893
|
+
}
|
|
11894
|
+
|
|
11732
11895
|
// src/web/session-handler.ts
|
|
11733
|
-
import { existsSync as
|
|
11734
|
-
import { join as
|
|
11896
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
|
|
11897
|
+
import { join as join17, resolve as resolve6, dirname as dirname5 } from "path";
|
|
11735
11898
|
|
|
11736
11899
|
// src/tools/git-context.ts
|
|
11737
11900
|
import { execSync as execSync2 } from "child_process";
|
|
11738
|
-
import { existsSync as
|
|
11739
|
-
import { join as
|
|
11901
|
+
import { existsSync as existsSync21 } from "fs";
|
|
11902
|
+
import { join as join14 } from "path";
|
|
11740
11903
|
function runGit2(cmd, cwd) {
|
|
11741
11904
|
try {
|
|
11742
11905
|
return execSync2(`git ${cmd}`, {
|
|
@@ -11753,7 +11916,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
11753
11916
|
return runGit2("rev-parse --show-toplevel", cwd);
|
|
11754
11917
|
}
|
|
11755
11918
|
function getGitContext(cwd = process.cwd()) {
|
|
11756
|
-
if (!
|
|
11919
|
+
if (!existsSync21(join14(cwd, ".git"))) {
|
|
11757
11920
|
const result = runGit2("rev-parse --git-dir", cwd);
|
|
11758
11921
|
if (!result) return null;
|
|
11759
11922
|
}
|
|
@@ -11823,6 +11986,253 @@ function formatGitContextForPrompt(ctx) {
|
|
|
11823
11986
|
return lines.join("\n");
|
|
11824
11987
|
}
|
|
11825
11988
|
|
|
11989
|
+
// src/cli/review-prompts.ts
|
|
11990
|
+
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
11991
|
+
const level = detailed ? "Please perform a detailed in-depth review covering: security, performance, maintainability, error handling, naming conventions, and code duplication." : "Please perform a concise code review focusing on bugs, security issues, and key improvement suggestions.";
|
|
11992
|
+
return `# Code Review Request
|
|
11993
|
+
|
|
11994
|
+
${level}
|
|
11995
|
+
|
|
11996
|
+
## Git Status
|
|
11997
|
+
${gitContextStr}
|
|
11998
|
+
|
|
11999
|
+
## Code Changes (diff)
|
|
12000
|
+
\`\`\`diff
|
|
12001
|
+
${diff}
|
|
12002
|
+
\`\`\`
|
|
12003
|
+
|
|
12004
|
+
## Output Format
|
|
12005
|
+
Please structure your review as follows:
|
|
12006
|
+
1. **Overall Assessment**: One-sentence summary of the change quality
|
|
12007
|
+
2. **Issues** (if any): Each issue with [Severity] file:line \u2014 description + suggested fix
|
|
12008
|
+
3. **Improvement Suggestions** (if any): Non-critical but recommended optimizations
|
|
12009
|
+
4. **Highlights** (if any): Good practices worth acknowledging
|
|
12010
|
+
|
|
12011
|
+
Severity levels: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
|
|
12012
|
+
}
|
|
12013
|
+
function buildSecurityReviewPrompt(diff, gitContextStr) {
|
|
12014
|
+
return `# Security Vulnerability Review
|
|
12015
|
+
|
|
12016
|
+
Analyze the following code changes **exclusively for security vulnerabilities**.
|
|
12017
|
+
|
|
12018
|
+
## Categories to check:
|
|
12019
|
+
1. **Injection** \u2014 SQL, command, path traversal, XSS, template injection
|
|
12020
|
+
2. **Authentication & Authorization** \u2014 hardcoded credentials, missing auth checks, privilege escalation
|
|
12021
|
+
3. **Secrets & Sensitive Data** \u2014 API keys, tokens, passwords in code, logging sensitive data
|
|
12022
|
+
4. **Input Validation** \u2014 missing validation, unsafe deserialization, buffer issues
|
|
12023
|
+
5. **Cryptography** \u2014 weak algorithms, improper random, hardcoded IVs/salts
|
|
12024
|
+
6. **Dependencies** \u2014 known vulnerable packages, unsafe dynamic imports
|
|
12025
|
+
7. **File System** \u2014 path traversal, unsafe file permissions, symlink attacks
|
|
12026
|
+
8. **Network** \u2014 SSRF, insecure protocols, missing TLS validation
|
|
12027
|
+
|
|
12028
|
+
## Git Status
|
|
12029
|
+
${gitContextStr}
|
|
12030
|
+
|
|
12031
|
+
## Code Changes (diff)
|
|
12032
|
+
\`\`\`diff
|
|
12033
|
+
${diff}
|
|
12034
|
+
\`\`\`
|
|
12035
|
+
|
|
12036
|
+
## Output Format
|
|
12037
|
+
For each finding:
|
|
12038
|
+
- **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW / \u2139\uFE0F INFO
|
|
12039
|
+
- **Category**: (from list above)
|
|
12040
|
+
- **File & location**: file:line
|
|
12041
|
+
- **Description**: what the vulnerability is and how it could be exploited
|
|
12042
|
+
- **Recommended fix**: specific code change to resolve
|
|
12043
|
+
|
|
12044
|
+
If no security issues found, state "\u2705 No security vulnerabilities detected" with a brief explanation of what was checked.`;
|
|
12045
|
+
}
|
|
12046
|
+
|
|
12047
|
+
// src/repl/commands/project-init.ts
|
|
12048
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
12049
|
+
import { join as join15 } from "path";
|
|
12050
|
+
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
12051
|
+
"node_modules",
|
|
12052
|
+
".git",
|
|
12053
|
+
"dist",
|
|
12054
|
+
"build",
|
|
12055
|
+
"out",
|
|
12056
|
+
"target",
|
|
12057
|
+
".next",
|
|
12058
|
+
".nuxt",
|
|
12059
|
+
"__pycache__",
|
|
12060
|
+
".venv",
|
|
12061
|
+
"venv",
|
|
12062
|
+
".tox",
|
|
12063
|
+
".mypy_cache",
|
|
12064
|
+
".pytest_cache",
|
|
12065
|
+
".gradle",
|
|
12066
|
+
".idea",
|
|
12067
|
+
".vscode",
|
|
12068
|
+
".vs",
|
|
12069
|
+
"coverage",
|
|
12070
|
+
".cache",
|
|
12071
|
+
".parcel-cache",
|
|
12072
|
+
"dist-cjs",
|
|
12073
|
+
"release",
|
|
12074
|
+
".output",
|
|
12075
|
+
".turbo",
|
|
12076
|
+
"vendor"
|
|
12077
|
+
]);
|
|
12078
|
+
function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
12079
|
+
const lines = [];
|
|
12080
|
+
let count = 0;
|
|
12081
|
+
const walk = (d, prefix, depth) => {
|
|
12082
|
+
if (depth > maxDepth || count >= maxEntries) return;
|
|
12083
|
+
let entries;
|
|
12084
|
+
try {
|
|
12085
|
+
entries = readdirSync9(d);
|
|
12086
|
+
} catch {
|
|
12087
|
+
return;
|
|
12088
|
+
}
|
|
12089
|
+
const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
|
|
12090
|
+
const sorted = filtered.sort((a, b) => {
|
|
12091
|
+
let aIsDir = false, bIsDir = false;
|
|
12092
|
+
try {
|
|
12093
|
+
aIsDir = statSync8(join15(d, a)).isDirectory();
|
|
12094
|
+
} catch {
|
|
12095
|
+
}
|
|
12096
|
+
try {
|
|
12097
|
+
bIsDir = statSync8(join15(d, b)).isDirectory();
|
|
12098
|
+
} catch {
|
|
12099
|
+
}
|
|
12100
|
+
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
12101
|
+
return a.localeCompare(b);
|
|
12102
|
+
});
|
|
12103
|
+
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
12104
|
+
const name = sorted[i];
|
|
12105
|
+
const fullPath = join15(d, name);
|
|
12106
|
+
const isLast = i === sorted.length - 1;
|
|
12107
|
+
const connector = isLast ? "+-- " : "|-- ";
|
|
12108
|
+
let isDir;
|
|
12109
|
+
try {
|
|
12110
|
+
isDir = statSync8(fullPath).isDirectory();
|
|
12111
|
+
} catch {
|
|
12112
|
+
continue;
|
|
12113
|
+
}
|
|
12114
|
+
lines.push(prefix + connector + name + (isDir ? "/" : ""));
|
|
12115
|
+
count++;
|
|
12116
|
+
if (isDir) {
|
|
12117
|
+
walk(fullPath, prefix + (isLast ? " " : "| "), depth + 1);
|
|
12118
|
+
}
|
|
12119
|
+
}
|
|
12120
|
+
};
|
|
12121
|
+
walk(dir, "", 0);
|
|
12122
|
+
if (count >= maxEntries) lines.push("... (truncated)");
|
|
12123
|
+
return lines.join("\n");
|
|
12124
|
+
}
|
|
12125
|
+
function scanProject(cwd) {
|
|
12126
|
+
const info = {
|
|
12127
|
+
type: "unknown",
|
|
12128
|
+
language: "unknown",
|
|
12129
|
+
configFiles: [],
|
|
12130
|
+
directoryStructure: ""
|
|
12131
|
+
};
|
|
12132
|
+
const check = (file) => existsSync22(join15(cwd, file));
|
|
12133
|
+
const configCandidates = [
|
|
12134
|
+
"package.json",
|
|
12135
|
+
"tsconfig.json",
|
|
12136
|
+
"Cargo.toml",
|
|
12137
|
+
"pyproject.toml",
|
|
12138
|
+
"setup.py",
|
|
12139
|
+
"requirements.txt",
|
|
12140
|
+
"go.mod",
|
|
12141
|
+
"pom.xml",
|
|
12142
|
+
"build.gradle",
|
|
12143
|
+
"build.gradle.kts",
|
|
12144
|
+
"CMakeLists.txt",
|
|
12145
|
+
"Makefile",
|
|
12146
|
+
".csproj",
|
|
12147
|
+
".sln",
|
|
12148
|
+
"composer.json",
|
|
12149
|
+
"Gemfile",
|
|
12150
|
+
"mix.exs",
|
|
12151
|
+
"deno.json",
|
|
12152
|
+
"bun.lockb"
|
|
12153
|
+
];
|
|
12154
|
+
info.configFiles = configCandidates.filter(check);
|
|
12155
|
+
if (check("package.json")) {
|
|
12156
|
+
info.type = "node";
|
|
12157
|
+
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
12158
|
+
try {
|
|
12159
|
+
const pkg = JSON.parse(readFileSync16(join15(cwd, "package.json"), "utf-8"));
|
|
12160
|
+
const scripts = pkg.scripts ?? {};
|
|
12161
|
+
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
12162
|
+
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
12163
|
+
info.devCommand = scripts.dev ? "npm run dev" : void 0;
|
|
12164
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12165
|
+
if (allDeps["react"]) info.framework = "React";
|
|
12166
|
+
else if (allDeps["vue"]) info.framework = "Vue";
|
|
12167
|
+
else if (allDeps["@angular/core"]) info.framework = "Angular";
|
|
12168
|
+
else if (allDeps["next"]) info.framework = "Next.js";
|
|
12169
|
+
else if (allDeps["nuxt"]) info.framework = "Nuxt";
|
|
12170
|
+
else if (allDeps["express"]) info.framework = "Express";
|
|
12171
|
+
else if (allDeps["fastify"]) info.framework = "Fastify";
|
|
12172
|
+
else if (allDeps["svelte"]) info.framework = "Svelte";
|
|
12173
|
+
} catch {
|
|
12174
|
+
}
|
|
12175
|
+
} else if (check("Cargo.toml")) {
|
|
12176
|
+
info.type = "rust";
|
|
12177
|
+
info.language = "Rust";
|
|
12178
|
+
info.buildCommand = "cargo build";
|
|
12179
|
+
info.testCommand = "cargo test";
|
|
12180
|
+
} else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
|
|
12181
|
+
info.type = "python";
|
|
12182
|
+
info.language = "Python";
|
|
12183
|
+
info.testCommand = check("pytest.ini") || check("pyproject.toml") ? "pytest" : "python -m unittest";
|
|
12184
|
+
} else if (check("go.mod")) {
|
|
12185
|
+
info.type = "go";
|
|
12186
|
+
info.language = "Go";
|
|
12187
|
+
info.buildCommand = "go build ./...";
|
|
12188
|
+
info.testCommand = "go test ./...";
|
|
12189
|
+
} else if (check("pom.xml")) {
|
|
12190
|
+
info.type = "java";
|
|
12191
|
+
info.language = "Java";
|
|
12192
|
+
info.buildCommand = "mvn package";
|
|
12193
|
+
info.testCommand = "mvn test";
|
|
12194
|
+
} else if (check("build.gradle") || check("build.gradle.kts")) {
|
|
12195
|
+
info.type = "java";
|
|
12196
|
+
info.language = "Java/Kotlin";
|
|
12197
|
+
info.buildCommand = "./gradlew build";
|
|
12198
|
+
info.testCommand = "./gradlew test";
|
|
12199
|
+
}
|
|
12200
|
+
info.directoryStructure = scanDirTree(cwd);
|
|
12201
|
+
return info;
|
|
12202
|
+
}
|
|
12203
|
+
function buildInitPrompt(info, cwd) {
|
|
12204
|
+
const parts = [
|
|
12205
|
+
"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.",
|
|
12206
|
+
"\n## Project Info\n",
|
|
12207
|
+
`- Working directory: ${cwd}`,
|
|
12208
|
+
`- Type: ${info.type}`,
|
|
12209
|
+
`- Language: ${info.language}`
|
|
12210
|
+
];
|
|
12211
|
+
if (info.framework) parts.push(`- Framework: ${info.framework}`);
|
|
12212
|
+
if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
|
|
12213
|
+
if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
|
|
12214
|
+
if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
|
|
12215
|
+
parts.push(`
|
|
12216
|
+
## Detected Config Files
|
|
12217
|
+
${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
|
|
12218
|
+
parts.push(`
|
|
12219
|
+
## Directory Structure
|
|
12220
|
+
\`\`\`
|
|
12221
|
+
${info.directoryStructure}
|
|
12222
|
+
\`\`\``);
|
|
12223
|
+
parts.push(`
|
|
12224
|
+
## Requirements
|
|
12225
|
+
Please generate a structured Markdown file containing:
|
|
12226
|
+
1. Project overview (one-sentence summary)
|
|
12227
|
+
2. Tech stack
|
|
12228
|
+
3. Project structure description (based on the directory structure above)
|
|
12229
|
+
4. Common commands (build, test, dev, etc.)
|
|
12230
|
+
5. Code style and conventions (inferred from config files)
|
|
12231
|
+
|
|
12232
|
+
Output the Markdown content directly, do not wrap the entire file in a code block. Keep it concise, within 200 lines.`);
|
|
12233
|
+
return parts.join("\n");
|
|
12234
|
+
}
|
|
12235
|
+
|
|
11826
12236
|
// src/core/git-diff.ts
|
|
11827
12237
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
11828
12238
|
function readGitDiff(options = {}) {
|
|
@@ -12406,7 +12816,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
12406
12816
|
}
|
|
12407
12817
|
|
|
12408
12818
|
// src/hub/persist.ts
|
|
12409
|
-
import { join as
|
|
12819
|
+
import { join as join16 } from "path";
|
|
12410
12820
|
function discussionToMessages(state2) {
|
|
12411
12821
|
const out = [];
|
|
12412
12822
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -12444,7 +12854,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
12444
12854
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
12445
12855
|
session.titleAiGenerated = true;
|
|
12446
12856
|
await sm.save();
|
|
12447
|
-
return { id: session.id, path:
|
|
12857
|
+
return { id: session.id, path: join16(config.getHistoryDir(), `${session.id}.json`) };
|
|
12448
12858
|
}
|
|
12449
12859
|
|
|
12450
12860
|
// src/web/session-handler.ts
|
|
@@ -12462,7 +12872,7 @@ function lastAssistantText(messages) {
|
|
|
12462
12872
|
}
|
|
12463
12873
|
return void 0;
|
|
12464
12874
|
}
|
|
12465
|
-
var SessionHandler = class
|
|
12875
|
+
var SessionHandler = class {
|
|
12466
12876
|
ws;
|
|
12467
12877
|
config;
|
|
12468
12878
|
providers;
|
|
@@ -12499,6 +12909,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
12499
12909
|
pendingAutoPause = /* @__PURE__ */ new Map();
|
|
12500
12910
|
/** Active system prompt from context files */
|
|
12501
12911
|
activeSystemPrompt;
|
|
12912
|
+
contextLoadResult = null;
|
|
12502
12913
|
/** Directories added via /add-dir */
|
|
12503
12914
|
addedDirs = /* @__PURE__ */ new Set();
|
|
12504
12915
|
/** Track MCP tool names used across this session (for MCP budget prioritization) */
|
|
@@ -12605,10 +13016,10 @@ var SessionHandler = class _SessionHandler {
|
|
|
12605
13016
|
return;
|
|
12606
13017
|
}
|
|
12607
13018
|
case "ask_user_response": {
|
|
12608
|
-
const
|
|
12609
|
-
if (
|
|
13019
|
+
const resolve8 = this.pendingAskUser.get(msg.requestId);
|
|
13020
|
+
if (resolve8) {
|
|
12610
13021
|
this.pendingAskUser.delete(msg.requestId);
|
|
12611
|
-
|
|
13022
|
+
resolve8(msg.answer);
|
|
12612
13023
|
}
|
|
12613
13024
|
return;
|
|
12614
13025
|
}
|
|
@@ -12619,10 +13030,10 @@ var SessionHandler = class _SessionHandler {
|
|
|
12619
13030
|
case "memory_rebuild":
|
|
12620
13031
|
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
12621
13032
|
case "auto_pause_response": {
|
|
12622
|
-
const
|
|
12623
|
-
if (
|
|
13033
|
+
const resolve8 = this.pendingAutoPause.get(msg.requestId);
|
|
13034
|
+
if (resolve8) {
|
|
12624
13035
|
this.pendingAutoPause.delete(msg.requestId);
|
|
12625
|
-
|
|
13036
|
+
resolve8({ action: msg.action, message: msg.message });
|
|
12626
13037
|
}
|
|
12627
13038
|
return;
|
|
12628
13039
|
}
|
|
@@ -12637,10 +13048,10 @@ var SessionHandler = class _SessionHandler {
|
|
|
12637
13048
|
this.hubOrchestrator?.abort();
|
|
12638
13049
|
return;
|
|
12639
13050
|
case "hub_steer": {
|
|
12640
|
-
const
|
|
12641
|
-
if (
|
|
13051
|
+
const resolve8 = this.pendingHubReview.get(msg.requestId);
|
|
13052
|
+
if (resolve8) {
|
|
12642
13053
|
this.pendingHubReview.delete(msg.requestId);
|
|
12643
|
-
|
|
13054
|
+
resolve8({ action: msg.action, message: msg.message });
|
|
12644
13055
|
}
|
|
12645
13056
|
return;
|
|
12646
13057
|
}
|
|
@@ -12652,9 +13063,9 @@ var SessionHandler = class _SessionHandler {
|
|
|
12652
13063
|
onDisconnect() {
|
|
12653
13064
|
this.toolExecutor.cancelAll();
|
|
12654
13065
|
if (this.abortController) this.abortController.abort();
|
|
12655
|
-
for (const
|
|
13066
|
+
for (const resolve8 of this.pendingAskUser.values()) resolve8(null);
|
|
12656
13067
|
this.pendingAskUser.clear();
|
|
12657
|
-
for (const
|
|
13068
|
+
for (const resolve8 of this.pendingAutoPause.values()) resolve8({ action: "stop" });
|
|
12658
13069
|
this.pendingAutoPause.clear();
|
|
12659
13070
|
this.saveIfNeeded();
|
|
12660
13071
|
}
|
|
@@ -12788,9 +13199,9 @@ var SessionHandler = class _SessionHandler {
|
|
|
12788
13199
|
this.hubOrchestrator = orchestrator;
|
|
12789
13200
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
12790
13201
|
if (config.humanSteer) {
|
|
12791
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
13202
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve8) => {
|
|
12792
13203
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
12793
|
-
this.pendingHubReview.set(requestId,
|
|
13204
|
+
this.pendingHubReview.set(requestId, resolve8);
|
|
12794
13205
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
12795
13206
|
});
|
|
12796
13207
|
}
|
|
@@ -13127,8 +13538,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
13127
13538
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
13128
13539
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
13129
13540
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13130
|
-
const pauseResp = await new Promise((
|
|
13131
|
-
this.pendingAutoPause.set(requestId,
|
|
13541
|
+
const pauseResp = await new Promise((resolve8) => {
|
|
13542
|
+
this.pendingAutoPause.set(requestId, resolve8);
|
|
13132
13543
|
this.send({
|
|
13133
13544
|
type: "auto_pause_request",
|
|
13134
13545
|
requestId,
|
|
@@ -13307,8 +13718,8 @@ ${summaryContent}`,
|
|
|
13307
13718
|
}
|
|
13308
13719
|
if (chunk.done) break;
|
|
13309
13720
|
}
|
|
13310
|
-
await new Promise((
|
|
13311
|
-
fileStream.end((err) => err ? reject(err) :
|
|
13721
|
+
await new Promise((resolve8, reject) => {
|
|
13722
|
+
fileStream.end((err) => err ? reject(err) : resolve8());
|
|
13312
13723
|
});
|
|
13313
13724
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
13314
13725
|
if (verdict.kind === "reject") {
|
|
@@ -13334,7 +13745,7 @@ ${summaryContent}`,
|
|
|
13334
13745
|
} catch (err) {
|
|
13335
13746
|
if (fileStream) {
|
|
13336
13747
|
try {
|
|
13337
|
-
await new Promise((
|
|
13748
|
+
await new Promise((resolve8) => fileStream.end(() => resolve8()));
|
|
13338
13749
|
} catch {
|
|
13339
13750
|
}
|
|
13340
13751
|
}
|
|
@@ -13623,7 +14034,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
13623
14034
|
" /plan [enter|exit] \u2014 Toggle read-only planning mode",
|
|
13624
14035
|
" /session new|list|load|delete <id> \u2014 Session management",
|
|
13625
14036
|
" /system [prompt|clear] \u2014 Set/view/reset system prompt",
|
|
13626
|
-
" /context [reload]
|
|
14037
|
+
" /context [status|reload] \u2014 Show/reload context layers",
|
|
13627
14038
|
" /status \u2014 Show session info & token usage",
|
|
13628
14039
|
" /cost \u2014 Show cumulative token usage",
|
|
13629
14040
|
" /config [show|get|set] \u2014 View/modify configuration",
|
|
@@ -13864,9 +14275,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
13864
14275
|
let modifiedFiles = 0;
|
|
13865
14276
|
const diffLines2 = [];
|
|
13866
14277
|
for (const [filePath, { earliest }] of fileMap) {
|
|
13867
|
-
const currentContent =
|
|
14278
|
+
const currentContent = existsSync23(filePath) ? (() => {
|
|
13868
14279
|
try {
|
|
13869
|
-
return
|
|
14280
|
+
return readFileSync17(filePath, "utf-8");
|
|
13870
14281
|
} catch {
|
|
13871
14282
|
return null;
|
|
13872
14283
|
}
|
|
@@ -13967,7 +14378,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
13967
14378
|
break;
|
|
13968
14379
|
}
|
|
13969
14380
|
const sub = args[0]?.toLowerCase();
|
|
13970
|
-
const
|
|
14381
|
+
const resolve8 = (ref) => {
|
|
13971
14382
|
const r = session.resolveBranchRef(ref);
|
|
13972
14383
|
if (r.ok) return r.id;
|
|
13973
14384
|
if (r.reason === "ambiguous") {
|
|
@@ -14021,7 +14432,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14021
14432
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
14022
14433
|
break;
|
|
14023
14434
|
}
|
|
14024
|
-
const id =
|
|
14435
|
+
const id = resolve8(ref);
|
|
14025
14436
|
if (!id) break;
|
|
14026
14437
|
const ok = session.switchBranch(id);
|
|
14027
14438
|
if (ok) {
|
|
@@ -14041,7 +14452,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14041
14452
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
14042
14453
|
break;
|
|
14043
14454
|
}
|
|
14044
|
-
const id =
|
|
14455
|
+
const id = resolve8(ref);
|
|
14045
14456
|
if (!id) break;
|
|
14046
14457
|
const ok = session.deleteBranch(id);
|
|
14047
14458
|
if (ok) {
|
|
@@ -14060,7 +14471,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14060
14471
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
14061
14472
|
break;
|
|
14062
14473
|
}
|
|
14063
|
-
const id =
|
|
14474
|
+
const id = resolve8(ref);
|
|
14064
14475
|
if (!id) break;
|
|
14065
14476
|
const ok = session.renameBranch(id, title);
|
|
14066
14477
|
if (ok) {
|
|
@@ -14078,7 +14489,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14078
14489
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
14079
14490
|
break;
|
|
14080
14491
|
}
|
|
14081
|
-
const id =
|
|
14492
|
+
const id = resolve8(ref);
|
|
14082
14493
|
if (!id) break;
|
|
14083
14494
|
const d = session.diffBranches(id);
|
|
14084
14495
|
if (!d) {
|
|
@@ -14120,7 +14531,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14120
14531
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
14121
14532
|
break;
|
|
14122
14533
|
}
|
|
14123
|
-
const id =
|
|
14534
|
+
const id = resolve8(ref);
|
|
14124
14535
|
if (!id) break;
|
|
14125
14536
|
const idx = parseInt(idxStr, 10);
|
|
14126
14537
|
if (Number.isNaN(idx)) {
|
|
@@ -14283,7 +14694,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14283
14694
|
diff = head + "\n\n... [diff truncated, " + diff.length + " chars total] ...\n\n" + tail;
|
|
14284
14695
|
truncated = true;
|
|
14285
14696
|
}
|
|
14286
|
-
const reviewPrompt =
|
|
14697
|
+
const reviewPrompt = buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
|
|
14287
14698
|
this.send({ type: "info", message: "\u{1F50D} Analyzing changes..." });
|
|
14288
14699
|
try {
|
|
14289
14700
|
const review = await this.chatOnce(reviewPrompt, { temperature: 0.3, maxTokens: 8192 });
|
|
@@ -14321,7 +14732,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14321
14732
|
secDiff = head + "\n\n... [diff truncated, " + secDiff.length + " chars total] ...\n\n" + tail;
|
|
14322
14733
|
secTruncated = true;
|
|
14323
14734
|
}
|
|
14324
|
-
const secPrompt =
|
|
14735
|
+
const secPrompt = buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
|
|
14325
14736
|
this.send({ type: "info", message: "\u{1F512} Scanning for security vulnerabilities..." });
|
|
14326
14737
|
try {
|
|
14327
14738
|
const secReview = await this.chatOnce(secPrompt, { temperature: 0.2, maxTokens: 8192 });
|
|
@@ -14382,7 +14793,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14382
14793
|
case "test": {
|
|
14383
14794
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
14384
14795
|
try {
|
|
14385
|
-
const { executeTests } = await import("./run-tests-
|
|
14796
|
+
const { executeTests } = await import("./run-tests-NT2UIUVB.js");
|
|
14386
14797
|
const argStr = args.join(" ").trim();
|
|
14387
14798
|
let testArgs = {};
|
|
14388
14799
|
if (argStr) {
|
|
@@ -14399,17 +14810,17 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14399
14810
|
// ── /init ───────────────────────────────────────────────────────
|
|
14400
14811
|
case "init": {
|
|
14401
14812
|
const cwd = process.cwd();
|
|
14402
|
-
const targetPath =
|
|
14813
|
+
const targetPath = join17(cwd, "AICLI.md");
|
|
14403
14814
|
const force = args.includes("--force");
|
|
14404
|
-
if (
|
|
14815
|
+
if (existsSync23(targetPath) && !force) {
|
|
14405
14816
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
14406
14817
|
Use /init --force to overwrite.` });
|
|
14407
14818
|
break;
|
|
14408
14819
|
}
|
|
14409
14820
|
this.send({ type: "info", message: "\u{1F4DD} Scanning project structure..." });
|
|
14410
14821
|
try {
|
|
14411
|
-
const projectInfo =
|
|
14412
|
-
const prompt =
|
|
14822
|
+
const projectInfo = scanProject(cwd);
|
|
14823
|
+
const prompt = buildInitPrompt(projectInfo, cwd);
|
|
14413
14824
|
const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
|
|
14414
14825
|
writeFileSync2(targetPath, content, "utf-8");
|
|
14415
14826
|
this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
|
|
@@ -14434,19 +14845,19 @@ Use /context reload to load it.` });
|
|
|
14434
14845
|
lines.push("**Config Files:**");
|
|
14435
14846
|
lines.push(` Dir: ${configDir}`);
|
|
14436
14847
|
const checkFile = (label, filePath) => {
|
|
14437
|
-
const exists =
|
|
14848
|
+
const exists = existsSync23(filePath);
|
|
14438
14849
|
let extra = "";
|
|
14439
14850
|
if (exists) {
|
|
14440
14851
|
try {
|
|
14441
|
-
extra = ` (${
|
|
14852
|
+
extra = ` (${statSync9(filePath).size} bytes)`;
|
|
14442
14853
|
} catch {
|
|
14443
14854
|
}
|
|
14444
14855
|
}
|
|
14445
14856
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
14446
14857
|
};
|
|
14447
|
-
checkFile("config.json",
|
|
14448
|
-
checkFile("memory.md",
|
|
14449
|
-
checkFile("dev-state.md",
|
|
14858
|
+
checkFile("config.json", join17(configDir, "config.json"));
|
|
14859
|
+
checkFile("memory.md", join17(configDir, MEMORY_FILE_NAME));
|
|
14860
|
+
checkFile("dev-state.md", join17(configDir, "dev-state.md"));
|
|
14450
14861
|
lines.push("");
|
|
14451
14862
|
if (this.mcpManager) {
|
|
14452
14863
|
lines.push("**MCP Servers:**");
|
|
@@ -14546,29 +14957,24 @@ ${this.config.toFormattedJSON()}
|
|
|
14546
14957
|
this.send({ type: "info", message: this.activeSystemPrompt ? `\u2713 Context reloaded (${this.activeSystemPrompt.length} chars).` : "\u2713 No context files found." });
|
|
14547
14958
|
break;
|
|
14548
14959
|
}
|
|
14549
|
-
const
|
|
14550
|
-
const cwd = process.cwd();
|
|
14551
|
-
const gitRoot = getGitRoot(cwd);
|
|
14552
|
-
const projectRoot = gitRoot ?? cwd;
|
|
14960
|
+
const result = this.contextLoadResult;
|
|
14553
14961
|
const layers = ["\u{1F4DA} **Context Layers:**", ""];
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14962
|
+
if (result && result.layers.length > 0) {
|
|
14963
|
+
const labels = { global: "Global ", project: "Project", local: "Local " };
|
|
14964
|
+
for (const layer of result.layers) {
|
|
14965
|
+
layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
|
|
14966
|
+
}
|
|
14967
|
+
layers.push(` Total: ${result.totalChars} chars (${result.layers.length} layer${result.layers.length > 1 ? "s" : ""})`);
|
|
14968
|
+
} else {
|
|
14969
|
+
layers.push(" No context files loaded.");
|
|
14970
|
+
layers.push(" Search order per layer: AICLI.override.md \u2192 AGENTS.override.md \u2192 AICLI.md \u2192 CLAUDE.md \u2192 AGENTS.md");
|
|
14971
|
+
}
|
|
14972
|
+
if (result && result.skipped.length > 0) {
|
|
14973
|
+
layers.push("");
|
|
14974
|
+
layers.push("Skipped:");
|
|
14975
|
+
for (const skipped of result.skipped.slice(0, 5)) {
|
|
14976
|
+
layers.push(` - ${skipped.displayPath}: ${skipped.reason}${skipped.message ? ` (${skipped.message})` : ""}`);
|
|
14565
14977
|
}
|
|
14566
|
-
layers.push(` \u2013 ${label}: (none)`);
|
|
14567
|
-
};
|
|
14568
|
-
checkLayer("Global", configDir);
|
|
14569
|
-
checkLayer("Project", projectRoot);
|
|
14570
|
-
if (resolve5(cwd) !== resolve5(projectRoot)) {
|
|
14571
|
-
checkLayer("Subdir", cwd);
|
|
14572
14978
|
}
|
|
14573
14979
|
layers.push("");
|
|
14574
14980
|
layers.push(`Total prompt length: ${this.activeSystemPrompt?.length ?? 0} chars`);
|
|
@@ -14638,12 +15044,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
14638
15044
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
14639
15045
|
break;
|
|
14640
15046
|
}
|
|
14641
|
-
const dirPath =
|
|
14642
|
-
if (!
|
|
15047
|
+
const dirPath = resolve6(sub);
|
|
15048
|
+
if (!existsSync23(dirPath)) {
|
|
14643
15049
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
14644
15050
|
break;
|
|
14645
15051
|
}
|
|
14646
|
-
if (!
|
|
15052
|
+
if (!statSync9(dirPath).isDirectory()) {
|
|
14647
15053
|
this.send({ type: "error", message: `Not a directory: ${dirPath}` });
|
|
14648
15054
|
break;
|
|
14649
15055
|
}
|
|
@@ -14655,14 +15061,14 @@ It will be included in AI context for subsequent messages.` });
|
|
|
14655
15061
|
// ── /commands ───────────────────────────────────────────────────
|
|
14656
15062
|
case "commands": {
|
|
14657
15063
|
const configDir = this.config.getConfigDir();
|
|
14658
|
-
const commandsDir =
|
|
14659
|
-
if (!
|
|
15064
|
+
const commandsDir = join17(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
15065
|
+
if (!existsSync23(commandsDir)) {
|
|
14660
15066
|
this.send({ type: "info", message: `No custom commands directory.
|
|
14661
15067
|
Create: ${commandsDir}/ with .md files.` });
|
|
14662
15068
|
break;
|
|
14663
15069
|
}
|
|
14664
15070
|
try {
|
|
14665
|
-
const files =
|
|
15071
|
+
const files = readdirSync10(commandsDir).filter((f) => f.endsWith(".md"));
|
|
14666
15072
|
if (files.length === 0) {
|
|
14667
15073
|
this.send({ type: "info", message: `No custom commands found in ${commandsDir}
|
|
14668
15074
|
Add .md files to create commands.` });
|
|
@@ -14682,7 +15088,7 @@ Add .md files to create commands.` });
|
|
|
14682
15088
|
// ── /plugins ────────────────────────────────────────────────────
|
|
14683
15089
|
case "plugins": {
|
|
14684
15090
|
const configDir = this.config.getConfigDir();
|
|
14685
|
-
const pluginsDir =
|
|
15091
|
+
const pluginsDir = join17(configDir, PLUGINS_DIR_NAME);
|
|
14686
15092
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
14687
15093
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
14688
15094
|
if (pluginTools.length === 0) {
|
|
@@ -14856,11 +15262,11 @@ Add .md files to create commands.` });
|
|
|
14856
15262
|
}
|
|
14857
15263
|
memoryShow() {
|
|
14858
15264
|
const configDir = this.config.getConfigDir();
|
|
14859
|
-
const memPath =
|
|
15265
|
+
const memPath = join17(configDir, MEMORY_FILE_NAME);
|
|
14860
15266
|
let content = "";
|
|
14861
15267
|
try {
|
|
14862
|
-
if (
|
|
14863
|
-
content =
|
|
15268
|
+
if (existsSync23(memPath)) {
|
|
15269
|
+
content = readFileSync17(memPath, "utf-8");
|
|
14864
15270
|
}
|
|
14865
15271
|
} catch (err) {
|
|
14866
15272
|
process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
|
|
@@ -14874,11 +15280,11 @@ Add .md files to create commands.` });
|
|
|
14874
15280
|
}
|
|
14875
15281
|
memoryAdd(text) {
|
|
14876
15282
|
const configDir = this.config.getConfigDir();
|
|
14877
|
-
const memPath =
|
|
15283
|
+
const memPath = join17(configDir, MEMORY_FILE_NAME);
|
|
14878
15284
|
try {
|
|
14879
15285
|
mkdirSync10(configDir, { recursive: true });
|
|
14880
15286
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
14881
|
-
const previous =
|
|
15287
|
+
const previous = existsSync23(memPath) ? readFileSync17(memPath, "utf-8") : "";
|
|
14882
15288
|
const entry = `
|
|
14883
15289
|
- [${timestamp}] ${text}
|
|
14884
15290
|
`;
|
|
@@ -14890,7 +15296,7 @@ Add .md files to create commands.` });
|
|
|
14890
15296
|
}
|
|
14891
15297
|
memoryClear() {
|
|
14892
15298
|
const configDir = this.config.getConfigDir();
|
|
14893
|
-
const memPath =
|
|
15299
|
+
const memPath = join17(configDir, MEMORY_FILE_NAME);
|
|
14894
15300
|
try {
|
|
14895
15301
|
atomicWriteFileSync(memPath, "");
|
|
14896
15302
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
@@ -15055,7 +15461,7 @@ Add .md files to create commands.` });
|
|
|
15055
15461
|
dirContext += `
|
|
15056
15462
|
[Directory: ${dir}]
|
|
15057
15463
|
`;
|
|
15058
|
-
dirContext +=
|
|
15464
|
+
dirContext += scanDirTree(dir, 2, 40) + "\n";
|
|
15059
15465
|
totalLen = dirContext.length;
|
|
15060
15466
|
}
|
|
15061
15467
|
stable += dirContext;
|
|
@@ -15094,265 +15500,21 @@ Add .md files to create commands.` });
|
|
|
15094
15500
|
}
|
|
15095
15501
|
return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
|
|
15096
15502
|
}
|
|
15097
|
-
/**
|
|
15098
|
-
* Find first matching context file in a directory.
|
|
15099
|
-
*/
|
|
15100
|
-
findContextFile(dir) {
|
|
15101
|
-
for (const name of CONTEXT_FILE_CANDIDATES) {
|
|
15102
|
-
const fullPath = join15(dir, name);
|
|
15103
|
-
try {
|
|
15104
|
-
if (existsSync21(fullPath)) {
|
|
15105
|
-
const content = readFileSync15(fullPath, "utf-8").trim();
|
|
15106
|
-
if (content) return content;
|
|
15107
|
-
}
|
|
15108
|
-
} catch {
|
|
15109
|
-
}
|
|
15110
|
-
}
|
|
15111
|
-
return null;
|
|
15112
|
-
}
|
|
15113
|
-
/**
|
|
15114
|
-
* Load hierarchical context files (same as CLI):
|
|
15115
|
-
* 1. Global: ~/.aicli/AICLI.md or CLAUDE.md
|
|
15116
|
-
* 2. Project: <git-root>/AICLI.md or CLAUDE.md
|
|
15117
|
-
* 3. Subdir: <cwd>/AICLI.md or CLAUDE.md (only if cwd ≠ project root)
|
|
15118
|
-
*/
|
|
15119
|
-
// ── /init helpers ─────────────────────────────────────────────────
|
|
15120
|
-
static SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
15121
|
-
"node_modules",
|
|
15122
|
-
".git",
|
|
15123
|
-
"dist",
|
|
15124
|
-
"build",
|
|
15125
|
-
"out",
|
|
15126
|
-
"target",
|
|
15127
|
-
".next",
|
|
15128
|
-
".nuxt",
|
|
15129
|
-
"__pycache__",
|
|
15130
|
-
".venv",
|
|
15131
|
-
"venv",
|
|
15132
|
-
".tox",
|
|
15133
|
-
".mypy_cache",
|
|
15134
|
-
".pytest_cache",
|
|
15135
|
-
".gradle",
|
|
15136
|
-
".idea",
|
|
15137
|
-
".vscode",
|
|
15138
|
-
".vs",
|
|
15139
|
-
"coverage",
|
|
15140
|
-
".cache",
|
|
15141
|
-
".parcel-cache",
|
|
15142
|
-
"dist-cjs",
|
|
15143
|
-
"release",
|
|
15144
|
-
".output",
|
|
15145
|
-
".turbo",
|
|
15146
|
-
"vendor"
|
|
15147
|
-
]);
|
|
15148
|
-
scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
15149
|
-
const lines = [];
|
|
15150
|
-
let count = 0;
|
|
15151
|
-
const walk = (d, prefix, depth) => {
|
|
15152
|
-
if (depth > maxDepth || count >= maxEntries) return;
|
|
15153
|
-
let entries;
|
|
15154
|
-
try {
|
|
15155
|
-
entries = readdirSync9(d);
|
|
15156
|
-
} catch {
|
|
15157
|
-
return;
|
|
15158
|
-
}
|
|
15159
|
-
const filtered = entries.filter((e) => !e.startsWith(".") && !_SessionHandler.SCAN_SKIP_DIRS.has(e));
|
|
15160
|
-
const sorted = filtered.sort((a, b) => {
|
|
15161
|
-
let aIsDir = false, bIsDir = false;
|
|
15162
|
-
try {
|
|
15163
|
-
aIsDir = statSync8(join15(d, a)).isDirectory();
|
|
15164
|
-
} catch {
|
|
15165
|
-
}
|
|
15166
|
-
try {
|
|
15167
|
-
bIsDir = statSync8(join15(d, b)).isDirectory();
|
|
15168
|
-
} catch {
|
|
15169
|
-
}
|
|
15170
|
-
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
15171
|
-
return a.localeCompare(b);
|
|
15172
|
-
});
|
|
15173
|
-
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
15174
|
-
const name = sorted[i];
|
|
15175
|
-
const fullPath = join15(d, name);
|
|
15176
|
-
const isLast = i === sorted.length - 1;
|
|
15177
|
-
let isDir;
|
|
15178
|
-
try {
|
|
15179
|
-
isDir = statSync8(fullPath).isDirectory();
|
|
15180
|
-
} catch {
|
|
15181
|
-
continue;
|
|
15182
|
-
}
|
|
15183
|
-
lines.push(prefix + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ") + name + (isDir ? "/" : ""));
|
|
15184
|
-
count++;
|
|
15185
|
-
if (isDir) walk(fullPath, prefix + (isLast ? " " : "\u2502 "), depth + 1);
|
|
15186
|
-
}
|
|
15187
|
-
};
|
|
15188
|
-
walk(dir, "", 0);
|
|
15189
|
-
if (count >= maxEntries) lines.push("... (truncated)");
|
|
15190
|
-
return lines.join("\n");
|
|
15191
|
-
}
|
|
15192
|
-
scanProject(cwd) {
|
|
15193
|
-
const info = { type: "unknown", language: "unknown", configFiles: [], directoryStructure: "" };
|
|
15194
|
-
const check = (file) => existsSync21(join15(cwd, file));
|
|
15195
|
-
const configCandidates = [
|
|
15196
|
-
"package.json",
|
|
15197
|
-
"tsconfig.json",
|
|
15198
|
-
"Cargo.toml",
|
|
15199
|
-
"pyproject.toml",
|
|
15200
|
-
"setup.py",
|
|
15201
|
-
"requirements.txt",
|
|
15202
|
-
"go.mod",
|
|
15203
|
-
"pom.xml",
|
|
15204
|
-
"build.gradle",
|
|
15205
|
-
"build.gradle.kts",
|
|
15206
|
-
"CMakeLists.txt",
|
|
15207
|
-
"Makefile",
|
|
15208
|
-
".csproj",
|
|
15209
|
-
".sln",
|
|
15210
|
-
"composer.json",
|
|
15211
|
-
"Gemfile",
|
|
15212
|
-
"mix.exs",
|
|
15213
|
-
"deno.json",
|
|
15214
|
-
"bun.lockb"
|
|
15215
|
-
];
|
|
15216
|
-
info.configFiles = configCandidates.filter(check);
|
|
15217
|
-
if (check("package.json")) {
|
|
15218
|
-
info.type = "node";
|
|
15219
|
-
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
15220
|
-
try {
|
|
15221
|
-
const pkg = JSON.parse(readFileSync15(join15(cwd, "package.json"), "utf-8"));
|
|
15222
|
-
const scripts = pkg.scripts ?? {};
|
|
15223
|
-
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
15224
|
-
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
15225
|
-
info.devCommand = scripts.dev ? "npm run dev" : void 0;
|
|
15226
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
15227
|
-
if (allDeps["react"]) info.framework = "React";
|
|
15228
|
-
else if (allDeps["vue"]) info.framework = "Vue";
|
|
15229
|
-
else if (allDeps["@angular/core"]) info.framework = "Angular";
|
|
15230
|
-
else if (allDeps["next"]) info.framework = "Next.js";
|
|
15231
|
-
else if (allDeps["express"]) info.framework = "Express";
|
|
15232
|
-
} catch {
|
|
15233
|
-
}
|
|
15234
|
-
} else if (check("Cargo.toml")) {
|
|
15235
|
-
info.type = "rust";
|
|
15236
|
-
info.language = "Rust";
|
|
15237
|
-
info.buildCommand = "cargo build";
|
|
15238
|
-
info.testCommand = "cargo test";
|
|
15239
|
-
} else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
|
|
15240
|
-
info.type = "python";
|
|
15241
|
-
info.language = "Python";
|
|
15242
|
-
info.testCommand = "pytest";
|
|
15243
|
-
} else if (check("go.mod")) {
|
|
15244
|
-
info.type = "go";
|
|
15245
|
-
info.language = "Go";
|
|
15246
|
-
info.buildCommand = "go build ./...";
|
|
15247
|
-
info.testCommand = "go test ./...";
|
|
15248
|
-
} else if (check("pom.xml")) {
|
|
15249
|
-
info.type = "java";
|
|
15250
|
-
info.language = "Java";
|
|
15251
|
-
info.buildCommand = "mvn package";
|
|
15252
|
-
info.testCommand = "mvn test";
|
|
15253
|
-
} else if (check("build.gradle") || check("build.gradle.kts")) {
|
|
15254
|
-
info.type = "java";
|
|
15255
|
-
info.language = "Java/Kotlin";
|
|
15256
|
-
info.buildCommand = "./gradlew build";
|
|
15257
|
-
info.testCommand = "./gradlew test";
|
|
15258
|
-
}
|
|
15259
|
-
info.directoryStructure = this.scanDirTree(cwd);
|
|
15260
|
-
return info;
|
|
15261
|
-
}
|
|
15262
|
-
buildInitPrompt(info, cwd) {
|
|
15263
|
-
const parts = [
|
|
15264
|
-
"Please generate an AICLI.md context file (Markdown format) for the following project.",
|
|
15265
|
-
"\n## Project Info\n",
|
|
15266
|
-
`- Working directory: ${cwd}`,
|
|
15267
|
-
`- Type: ${info.type}`,
|
|
15268
|
-
`- Language: ${info.language}`
|
|
15269
|
-
];
|
|
15270
|
-
if (info.framework) parts.push(`- Framework: ${info.framework}`);
|
|
15271
|
-
if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
|
|
15272
|
-
if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
|
|
15273
|
-
if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
|
|
15274
|
-
parts.push(`
|
|
15275
|
-
## Detected Config Files
|
|
15276
|
-
${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
|
|
15277
|
-
parts.push(`
|
|
15278
|
-
## Directory Structure
|
|
15279
|
-
\`\`\`
|
|
15280
|
-
${info.directoryStructure}
|
|
15281
|
-
\`\`\``);
|
|
15282
|
-
parts.push(`
|
|
15283
|
-
## Requirements
|
|
15284
|
-
Generate a structured Markdown file with: project overview, tech stack, project structure, common commands, code style. Keep it concise, within 200 lines.`);
|
|
15285
|
-
return parts.join("\n");
|
|
15286
|
-
}
|
|
15287
|
-
// ── /review helper ──────────────────────────────────────────────────
|
|
15288
|
-
buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
15289
|
-
const level = detailed ? "Please perform a detailed in-depth review covering: security, performance, maintainability, error handling, naming conventions, and code duplication." : "Please perform a concise code review focusing on bugs, security issues, and key improvement suggestions.";
|
|
15290
|
-
return `# Code Review Request
|
|
15291
|
-
|
|
15292
|
-
${level}
|
|
15293
|
-
|
|
15294
|
-
## Git Status
|
|
15295
|
-
${gitContextStr}
|
|
15296
|
-
|
|
15297
|
-
## Code Changes (diff)
|
|
15298
|
-
\`\`\`diff
|
|
15299
|
-
${diff}
|
|
15300
|
-
\`\`\`
|
|
15301
|
-
|
|
15302
|
-
## Output Format
|
|
15303
|
-
1. **Overall Assessment**: One-sentence summary
|
|
15304
|
-
2. **Issues** (if any): [Severity] file:line \u2014 description + fix
|
|
15305
|
-
3. **Improvement Suggestions** (if any)
|
|
15306
|
-
4. **Highlights** (if any)
|
|
15307
|
-
|
|
15308
|
-
Severity: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
|
|
15309
|
-
}
|
|
15310
|
-
buildSecurityReviewPrompt(diff, gitContextStr) {
|
|
15311
|
-
return `# Security Vulnerability Review
|
|
15312
|
-
|
|
15313
|
-
Analyze the following code changes **exclusively for security vulnerabilities**.
|
|
15314
|
-
|
|
15315
|
-
## Categories
|
|
15316
|
-
1. Injection (SQL, command, path traversal, XSS)
|
|
15317
|
-
2. Auth & Authorization (hardcoded creds, missing checks)
|
|
15318
|
-
3. Secrets & Sensitive Data (API keys, tokens in code)
|
|
15319
|
-
4. Input Validation (missing validation, unsafe deserialization)
|
|
15320
|
-
5. Cryptography (weak algorithms, hardcoded IVs)
|
|
15321
|
-
6. File System (path traversal, symlink attacks)
|
|
15322
|
-
7. Network (SSRF, insecure protocols)
|
|
15323
|
-
|
|
15324
|
-
## Git Status
|
|
15325
|
-
${gitContextStr}
|
|
15326
|
-
|
|
15327
|
-
## Code Changes
|
|
15328
|
-
\`\`\`diff
|
|
15329
|
-
${diff}
|
|
15330
|
-
\`\`\`
|
|
15331
|
-
|
|
15332
|
-
## Output Format
|
|
15333
|
-
For each finding:
|
|
15334
|
-
- **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW
|
|
15335
|
-
- **Category** + **File:line**
|
|
15336
|
-
- **Description** + exploitation scenario
|
|
15337
|
-
- **Recommended fix**
|
|
15338
|
-
|
|
15339
|
-
If none found: "\u2705 No security vulnerabilities detected"`;
|
|
15340
|
-
}
|
|
15341
15503
|
loadContextFiles() {
|
|
15342
|
-
const
|
|
15504
|
+
const contextConfig = this.config.get("context");
|
|
15343
15505
|
const cwd = process.cwd();
|
|
15344
|
-
const configDir = this.config.getConfigDir();
|
|
15345
|
-
const globalCtx = this.findContextFile(configDir);
|
|
15346
|
-
if (globalCtx) parts.push(globalCtx);
|
|
15347
15506
|
const gitRoot = getGitRoot(cwd);
|
|
15348
15507
|
const projectRoot = gitRoot ?? cwd;
|
|
15349
|
-
const
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15508
|
+
const result = loadContextFiles({
|
|
15509
|
+
cwd,
|
|
15510
|
+
configDir: this.config.getConfigDir(),
|
|
15511
|
+
projectRoot,
|
|
15512
|
+
setting: this.config.get("contextFile"),
|
|
15513
|
+
fallbackFilenames: contextConfig.projectDocFallbackFilenames,
|
|
15514
|
+
maxBytes: contextConfig.projectDocMaxBytes
|
|
15515
|
+
});
|
|
15516
|
+
this.contextLoadResult = result;
|
|
15517
|
+
return result.mergedContent || void 0;
|
|
15356
15518
|
}
|
|
15357
15519
|
};
|
|
15358
15520
|
|
|
@@ -15368,8 +15530,8 @@ async function setupProxy(configProxy) {
|
|
|
15368
15530
|
}
|
|
15369
15531
|
|
|
15370
15532
|
// src/web/auth.ts
|
|
15371
|
-
import { existsSync as
|
|
15372
|
-
import { join as
|
|
15533
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18, writeFileSync as writeFileSync3, mkdirSync as mkdirSync11, readdirSync as readdirSync11, copyFileSync } from "fs";
|
|
15534
|
+
import { join as join18 } from "path";
|
|
15373
15535
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
15374
15536
|
var USERS_FILE = "users.json";
|
|
15375
15537
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -15384,7 +15546,7 @@ var AuthManager = class {
|
|
|
15384
15546
|
db;
|
|
15385
15547
|
constructor(baseDir) {
|
|
15386
15548
|
this.baseDir = baseDir;
|
|
15387
|
-
this.usersFile =
|
|
15549
|
+
this.usersFile = join18(baseDir, USERS_FILE);
|
|
15388
15550
|
this.db = this.loadOrCreate();
|
|
15389
15551
|
}
|
|
15390
15552
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -15413,9 +15575,9 @@ var AuthManager = class {
|
|
|
15413
15575
|
}
|
|
15414
15576
|
const salt = randomBytes(16).toString("hex");
|
|
15415
15577
|
const passwordHash = this.hashPassword(password, salt);
|
|
15416
|
-
const dataDir =
|
|
15417
|
-
const fullDataDir =
|
|
15418
|
-
mkdirSync11(
|
|
15578
|
+
const dataDir = join18(USERS_DIR, username);
|
|
15579
|
+
const fullDataDir = join18(this.baseDir, dataDir);
|
|
15580
|
+
mkdirSync11(join18(fullDataDir, "history"), { recursive: true });
|
|
15419
15581
|
const user = {
|
|
15420
15582
|
username,
|
|
15421
15583
|
passwordHash,
|
|
@@ -15530,7 +15692,7 @@ var AuthManager = class {
|
|
|
15530
15692
|
getUserDataDir(username) {
|
|
15531
15693
|
const user = this.db.users.find((u) => u.username === username);
|
|
15532
15694
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
15533
|
-
return
|
|
15695
|
+
return join18(this.baseDir, user.dataDir);
|
|
15534
15696
|
}
|
|
15535
15697
|
/** List all usernames */
|
|
15536
15698
|
listUsers() {
|
|
@@ -15566,30 +15728,30 @@ var AuthManager = class {
|
|
|
15566
15728
|
const err = this.register(username, password);
|
|
15567
15729
|
if (err) return err;
|
|
15568
15730
|
const userDir = this.getUserDataDir(username);
|
|
15569
|
-
const globalConfig =
|
|
15570
|
-
if (
|
|
15731
|
+
const globalConfig = join18(this.baseDir, "config.json");
|
|
15732
|
+
if (existsSync24(globalConfig)) {
|
|
15571
15733
|
try {
|
|
15572
|
-
const content =
|
|
15573
|
-
writeFileSync3(
|
|
15734
|
+
const content = readFileSync18(globalConfig, "utf-8");
|
|
15735
|
+
writeFileSync3(join18(userDir, "config.json"), content, "utf-8");
|
|
15574
15736
|
} catch {
|
|
15575
15737
|
}
|
|
15576
15738
|
}
|
|
15577
|
-
const globalMemory =
|
|
15578
|
-
if (
|
|
15739
|
+
const globalMemory = join18(this.baseDir, "memory.md");
|
|
15740
|
+
if (existsSync24(globalMemory)) {
|
|
15579
15741
|
try {
|
|
15580
|
-
const content =
|
|
15581
|
-
writeFileSync3(
|
|
15742
|
+
const content = readFileSync18(globalMemory, "utf-8");
|
|
15743
|
+
writeFileSync3(join18(userDir, "memory.md"), content, "utf-8");
|
|
15582
15744
|
} catch {
|
|
15583
15745
|
}
|
|
15584
15746
|
}
|
|
15585
|
-
const globalHistory =
|
|
15586
|
-
if (
|
|
15747
|
+
const globalHistory = join18(this.baseDir, "history");
|
|
15748
|
+
if (existsSync24(globalHistory)) {
|
|
15587
15749
|
try {
|
|
15588
|
-
const files =
|
|
15589
|
-
const userHistory =
|
|
15750
|
+
const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
|
|
15751
|
+
const userHistory = join18(userDir, "history");
|
|
15590
15752
|
for (const f of files) {
|
|
15591
15753
|
try {
|
|
15592
|
-
copyFileSync(
|
|
15754
|
+
copyFileSync(join18(globalHistory, f), join18(userHistory, f));
|
|
15593
15755
|
} catch {
|
|
15594
15756
|
}
|
|
15595
15757
|
}
|
|
@@ -15600,9 +15762,9 @@ var AuthManager = class {
|
|
|
15600
15762
|
}
|
|
15601
15763
|
// ── Private methods ────────────────────────────────────────────
|
|
15602
15764
|
loadOrCreate() {
|
|
15603
|
-
if (
|
|
15765
|
+
if (existsSync24(this.usersFile)) {
|
|
15604
15766
|
try {
|
|
15605
|
-
return JSON.parse(
|
|
15767
|
+
return JSON.parse(readFileSync18(this.usersFile, "utf-8"));
|
|
15606
15768
|
} catch {
|
|
15607
15769
|
}
|
|
15608
15770
|
}
|
|
@@ -15654,7 +15816,7 @@ function getModuleDir() {
|
|
|
15654
15816
|
}
|
|
15655
15817
|
} catch {
|
|
15656
15818
|
}
|
|
15657
|
-
return
|
|
15819
|
+
return resolve7(".");
|
|
15658
15820
|
}
|
|
15659
15821
|
async function startWebServer(options = {}) {
|
|
15660
15822
|
const port = options.port ?? 3e3;
|
|
@@ -15719,8 +15881,8 @@ async function startWebServer(options = {}) {
|
|
|
15719
15881
|
}
|
|
15720
15882
|
}
|
|
15721
15883
|
let skillManager = null;
|
|
15722
|
-
const skillsDir =
|
|
15723
|
-
if (
|
|
15884
|
+
const skillsDir = join19(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
15885
|
+
if (existsSync25(skillsDir)) {
|
|
15724
15886
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
15725
15887
|
skillManager.loadSkills();
|
|
15726
15888
|
const count = skillManager.listSkills().length;
|
|
@@ -15791,18 +15953,18 @@ async function startWebServer(options = {}) {
|
|
|
15791
15953
|
next();
|
|
15792
15954
|
};
|
|
15793
15955
|
const moduleDir = getModuleDir();
|
|
15794
|
-
let clientDir =
|
|
15795
|
-
if (!
|
|
15796
|
-
clientDir =
|
|
15956
|
+
let clientDir = join19(moduleDir, "web", "client");
|
|
15957
|
+
if (!existsSync25(clientDir)) {
|
|
15958
|
+
clientDir = join19(moduleDir, "client");
|
|
15797
15959
|
}
|
|
15798
|
-
if (!
|
|
15799
|
-
clientDir =
|
|
15960
|
+
if (!existsSync25(clientDir)) {
|
|
15961
|
+
clientDir = join19(moduleDir, "..", "..", "src", "web", "client");
|
|
15800
15962
|
}
|
|
15801
|
-
if (!
|
|
15802
|
-
clientDir =
|
|
15963
|
+
if (!existsSync25(clientDir)) {
|
|
15964
|
+
clientDir = join19(process.cwd(), "src", "web", "client");
|
|
15803
15965
|
}
|
|
15804
15966
|
console.log(` Static files: ${clientDir}`);
|
|
15805
|
-
app.use("/vendor", express.static(
|
|
15967
|
+
app.use("/vendor", express.static(join19(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
15806
15968
|
app.use(express.static(clientDir));
|
|
15807
15969
|
app.get("/api/status", (_req, res) => {
|
|
15808
15970
|
res.json({
|
|
@@ -15869,10 +16031,10 @@ async function startWebServer(options = {}) {
|
|
|
15869
16031
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
15870
16032
|
const cwd = process.cwd();
|
|
15871
16033
|
const prefix = req.query.prefix || "";
|
|
15872
|
-
const targetDir =
|
|
16034
|
+
const targetDir = join19(cwd, prefix);
|
|
15873
16035
|
try {
|
|
15874
|
-
const canonicalTarget = realpathSync(
|
|
15875
|
-
const canonicalCwd = realpathSync(
|
|
16036
|
+
const canonicalTarget = realpathSync(resolve7(targetDir));
|
|
16037
|
+
const canonicalCwd = realpathSync(resolve7(cwd));
|
|
15876
16038
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
15877
16039
|
res.json({ files: [] });
|
|
15878
16040
|
return;
|
|
@@ -15883,10 +16045,10 @@ async function startWebServer(options = {}) {
|
|
|
15883
16045
|
}
|
|
15884
16046
|
try {
|
|
15885
16047
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
|
|
15886
|
-
const entries =
|
|
16048
|
+
const entries = readdirSync12(targetDir, { withFileTypes: true });
|
|
15887
16049
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
15888
16050
|
name: e.name,
|
|
15889
|
-
path:
|
|
16051
|
+
path: relative4(cwd, join19(targetDir, e.name)).replace(/\\/g, "/"),
|
|
15890
16052
|
isDir: e.isDirectory()
|
|
15891
16053
|
}));
|
|
15892
16054
|
res.json({ files });
|
|
@@ -15922,8 +16084,8 @@ async function startWebServer(options = {}) {
|
|
|
15922
16084
|
try {
|
|
15923
16085
|
const authUser = req._authUser;
|
|
15924
16086
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
15925
|
-
const filePath =
|
|
15926
|
-
if (!
|
|
16087
|
+
const filePath = join19(histDir, `${id}.json`);
|
|
16088
|
+
if (!existsSync25(filePath)) {
|
|
15927
16089
|
res.status(404).json({ error: "Session not found" });
|
|
15928
16090
|
return;
|
|
15929
16091
|
}
|
|
@@ -15938,7 +16100,7 @@ async function startWebServer(options = {}) {
|
|
|
15938
16100
|
res.status(404).json({ error: "Session not found" });
|
|
15939
16101
|
return;
|
|
15940
16102
|
}
|
|
15941
|
-
const data = JSON.parse(
|
|
16103
|
+
const data = JSON.parse(readFileSync19(filePath, "utf-8"));
|
|
15942
16104
|
res.json({ session: data });
|
|
15943
16105
|
} catch (err) {
|
|
15944
16106
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -15951,10 +16113,10 @@ async function startWebServer(options = {}) {
|
|
|
15951
16113
|
return;
|
|
15952
16114
|
}
|
|
15953
16115
|
const cwd = process.cwd();
|
|
15954
|
-
const fullPath =
|
|
16116
|
+
const fullPath = resolve7(join19(cwd, filePath));
|
|
15955
16117
|
try {
|
|
15956
16118
|
const canonicalFull = realpathSync(fullPath);
|
|
15957
|
-
const canonicalCwd = realpathSync(
|
|
16119
|
+
const canonicalCwd = realpathSync(resolve7(cwd));
|
|
15958
16120
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
15959
16121
|
res.json({ error: "Access denied" });
|
|
15960
16122
|
return;
|
|
@@ -15964,12 +16126,12 @@ async function startWebServer(options = {}) {
|
|
|
15964
16126
|
return;
|
|
15965
16127
|
}
|
|
15966
16128
|
try {
|
|
15967
|
-
const stat =
|
|
16129
|
+
const stat = statSync10(fullPath);
|
|
15968
16130
|
if (stat.size > 512 * 1024) {
|
|
15969
16131
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
15970
16132
|
return;
|
|
15971
16133
|
}
|
|
15972
|
-
const content =
|
|
16134
|
+
const content = readFileSync19(fullPath, "utf-8");
|
|
15973
16135
|
res.json({ content, size: stat.size });
|
|
15974
16136
|
} catch {
|
|
15975
16137
|
res.json({ error: "Cannot read file" });
|
|
@@ -16173,7 +16335,7 @@ async function startWebServer(options = {}) {
|
|
|
16173
16335
|
});
|
|
16174
16336
|
const MAX_PORT_ATTEMPTS = 10;
|
|
16175
16337
|
let actualPort = port;
|
|
16176
|
-
const result = await new Promise((
|
|
16338
|
+
const result = await new Promise((resolve8, reject) => {
|
|
16177
16339
|
const tryListen = (attempt) => {
|
|
16178
16340
|
server.once("error", (err) => {
|
|
16179
16341
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -16204,7 +16366,7 @@ async function startWebServer(options = {}) {
|
|
|
16204
16366
|
}
|
|
16205
16367
|
console.log(` Press Ctrl+C to stop
|
|
16206
16368
|
`);
|
|
16207
|
-
|
|
16369
|
+
resolve8({ port: actualPort, host, url });
|
|
16208
16370
|
});
|
|
16209
16371
|
};
|
|
16210
16372
|
tryListen(1);
|
|
@@ -16224,17 +16386,17 @@ function resolveProjectMcpPath() {
|
|
|
16224
16386
|
const cwd = process.cwd();
|
|
16225
16387
|
const gitRoot = getGitRoot(cwd);
|
|
16226
16388
|
const projectRoot = gitRoot ?? cwd;
|
|
16227
|
-
const configPath =
|
|
16228
|
-
return
|
|
16389
|
+
const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
16390
|
+
return existsSync25(configPath) ? configPath : null;
|
|
16229
16391
|
}
|
|
16230
16392
|
function loadProjectMcpConfig() {
|
|
16231
16393
|
const cwd = process.cwd();
|
|
16232
16394
|
const gitRoot = getGitRoot(cwd);
|
|
16233
16395
|
const projectRoot = gitRoot ?? cwd;
|
|
16234
|
-
const configPath =
|
|
16235
|
-
if (!
|
|
16396
|
+
const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
16397
|
+
if (!existsSync25(configPath)) return null;
|
|
16236
16398
|
try {
|
|
16237
|
-
const raw = JSON.parse(
|
|
16399
|
+
const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
16238
16400
|
return raw.mcpServers ?? raw;
|
|
16239
16401
|
} catch {
|
|
16240
16402
|
return null;
|