jinzd-ai-cli 0.1.47 → 0.1.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
30
30
|
VERSION,
|
|
31
31
|
runTestsTool
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-XDSNK7WW.js";
|
|
33
33
|
|
|
34
34
|
// src/index.ts
|
|
35
35
|
import { program } from "commander";
|
|
@@ -1846,6 +1846,26 @@ var Session = class _Session {
|
|
|
1846
1846
|
}))
|
|
1847
1847
|
};
|
|
1848
1848
|
}
|
|
1849
|
+
/**
|
|
1850
|
+
* 从现有 session 分叉创建新 session。
|
|
1851
|
+
*
|
|
1852
|
+
* 复制 messages[0..messageCount],保留范围内的 checkpoints。
|
|
1853
|
+
* 新 session 拥有独立 UUID,title 默认 "Fork of <原标题>"。
|
|
1854
|
+
*
|
|
1855
|
+
* @param original 原始 session
|
|
1856
|
+
* @param newId 新 session 的 UUID
|
|
1857
|
+
* @param messageCount 复制的消息数量(0 = 空 session,> messages.length 则取全部)
|
|
1858
|
+
* @param newTitle 可选的新标题
|
|
1859
|
+
*/
|
|
1860
|
+
static fork(original, newId, messageCount, newTitle) {
|
|
1861
|
+
const forked = new _Session(newId, original.provider, original.model);
|
|
1862
|
+
forked.title = newTitle ?? (original.title ? `Fork of ${original.title}` : void 0);
|
|
1863
|
+
const clampedCount = Math.min(Math.max(messageCount, 0), original.messages.length);
|
|
1864
|
+
forked.messages = original.messages.slice(0, clampedCount).map((m) => ({ ...m }));
|
|
1865
|
+
forked.checkpoints = original.checkpoints.filter((c) => c.messageIndex <= clampedCount).map((c) => ({ ...c, timestamp: new Date(c.timestamp.getTime()) }));
|
|
1866
|
+
forked.updated = /* @__PURE__ */ new Date();
|
|
1867
|
+
return forked;
|
|
1868
|
+
}
|
|
1849
1869
|
/**
|
|
1850
1870
|
* 从磁盘 JSON 数据恢复 Session 实例。
|
|
1851
1871
|
* 添加运行时校验:损坏或不兼容的历史文件会抛出明确错误,而非 TypeError 崩溃。
|
|
@@ -1961,6 +1981,26 @@ var SessionManager = class {
|
|
|
1961
1981
|
}
|
|
1962
1982
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
1963
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* 从当前 session 分叉创建新 session。
|
|
1986
|
+
*
|
|
1987
|
+
* 先保存原始 session(保留完整历史),然后创建分叉(截取到 messageCount),
|
|
1988
|
+
* 将分叉设为当前 session 并保存。
|
|
1989
|
+
*
|
|
1990
|
+
* @param messageCount 复制的消息数量
|
|
1991
|
+
* @param title 可选的新标题
|
|
1992
|
+
* @returns 新的分叉 session
|
|
1993
|
+
*/
|
|
1994
|
+
async forkSession(messageCount, title) {
|
|
1995
|
+
if (!this._current) {
|
|
1996
|
+
throw new Error("No active session to fork");
|
|
1997
|
+
}
|
|
1998
|
+
await this.save();
|
|
1999
|
+
const forked = Session.fork(this._current, uuidv4(), messageCount, title);
|
|
2000
|
+
this._current = forked;
|
|
2001
|
+
await this.save();
|
|
2002
|
+
return forked;
|
|
2003
|
+
}
|
|
1964
2004
|
/**
|
|
1965
2005
|
* 跨 session 全文搜索。
|
|
1966
2006
|
* 遍历所有历史 JSON 文件,逐条匹配消息内容(不区分大小写),
|
|
@@ -2021,7 +2061,7 @@ var SessionManager = class {
|
|
|
2021
2061
|
|
|
2022
2062
|
// src/repl/repl.ts
|
|
2023
2063
|
import * as readline from "readline";
|
|
2024
|
-
import { existsSync as existsSync19, readFileSync as readFileSync13, readdirSync as
|
|
2064
|
+
import { existsSync as existsSync19, readFileSync as readFileSync13, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
|
|
2025
2065
|
import { join as join14, resolve as resolve4, extname as extname4, dirname as dirname5, basename as basename5 } from "path";
|
|
2026
2066
|
import chalk10 from "chalk";
|
|
2027
2067
|
|
|
@@ -2231,12 +2271,12 @@ var Renderer = class {
|
|
|
2231
2271
|
console.log(tool("spawn_agent", "\u59D4\u6D3E\u72EC\u7ACB\u5B50\u4EE3\u7406\u6267\u884C\u7279\u5B9A\u4EFB\u52A1\uFF08\u9694\u79BB\u5BF9\u8BDD + \u81EA\u52A8\u5DE5\u5177\u8C03\u7528\u5FAA\u73AF\uFF09"));
|
|
2232
2272
|
console.log(tool("run_tests", "\u8FD0\u884C\u9879\u76EE\u6D4B\u8BD5\u5E76\u8FD4\u56DE\u7ED3\u6784\u5316\u62A5\u544A\uFF08\u81EA\u52A8\u68C0\u6D4B Maven/npm/pytest \u7B49\uFF09"));
|
|
2233
2273
|
console.log(HR);
|
|
2234
|
-
console.log(theme.dim(" REPL \u547D\u4EE4\
|
|
2274
|
+
console.log(theme.dim(" REPL \u547D\u4EE4\uFF0835\u4E2A\uFF09\uFF1A"));
|
|
2235
2275
|
console.log(theme.dim(" /help /about /provider /model /clear /compact /plan /session"));
|
|
2236
2276
|
console.log(theme.dim(" /system /context /status /search /undo /export /copy /cost"));
|
|
2237
2277
|
console.log(theme.dim(" /init /skill /tools /plugins /mcp /config /checkpoint /review"));
|
|
2238
2278
|
console.log(theme.dim(" /commands /test /scaffold /add-dir /memory /doctor /bug /think"));
|
|
2239
|
-
console.log(theme.dim(" /diff /exit"));
|
|
2279
|
+
console.log(theme.dim(" /diff /fork /exit"));
|
|
2240
2280
|
console.log(HR);
|
|
2241
2281
|
console.log(theme.dim(" \u4E3B\u8981\u7279\u6027\uFF1A"));
|
|
2242
2282
|
console.log(feat("Agentic \u5FAA\u73AF\uFF08\u6700\u591A 25 \u8F6E\u5DE5\u5177\u8C03\u7528\uFF0C\u6700\u7EC8\u56DE\u7B54\u6D41\u5F0F\u8F93\u51FA\uFF09"));
|
|
@@ -2244,7 +2284,7 @@ var Renderer = class {
|
|
|
2244
2284
|
console.log(feat("Git \u4E0A\u4E0B\u6587\u611F\u77E5\uFF1A\u542F\u52A8\u81EA\u52A8\u6CE8\u5165\u5206\u652F\u540D\u4E0E\u6587\u4EF6\u53D8\u66F4\u72B6\u6001"));
|
|
2245
2285
|
console.log(feat("\u9879\u76EE\u4E0A\u4E0B\u6587\u6587\u4EF6\uFF1A\u81EA\u52A8\u52A0\u8F7D AICLI.md / CLAUDE.md\uFF08\u4E09\u5C42\u7EA7\uFF1A\u5168\u5C40/\u9879\u76EE/\u5B50\u76EE\u5F55\uFF09"));
|
|
2246
2286
|
console.log(feat("\u8DE8 session \u5386\u53F2\u5168\u6587\u641C\u7D22\uFF08/search <\u5173\u952E\u8BCD>\uFF09"));
|
|
2247
|
-
console.log(feat("\u6587\u4EF6\u64CD\u4F5C\u64A4\u9500\uFF08/undo\uFF0C\u652F\u6301 write_file / edit_file\uFF09"));
|
|
2287
|
+
console.log(feat("\u6587\u4EF6\u64CD\u4F5C\u64A4\u9500\uFF08/undo [list|<n>]\uFF0C\u652F\u6301 write_file / edit_file / bash \u521B\u5EFA\u7684\u6587\u4EF6/\u76EE\u5F55\uFF09"));
|
|
2248
2288
|
console.log(feat("Thinking \u6A21\u5F0F\u6298\u53E0\uFF08<think> \u5757\u81EA\u52A8\u6298\u53E0\uFF0CGLM-5 \u7B49\uFF09"));
|
|
2249
2289
|
console.log(feat("Token \u7528\u91CF\u8FFD\u8E2A\uFF08\u6BCF\u6B21\u56DE\u590D + session \u7D2F\u8BA1\uFF0C\u652F\u6301 Gemini/Claude/DeepSeek \u7B49\uFF09"));
|
|
2250
2290
|
console.log(feat("MCP \u534F\u8BAE\u652F\u6301\uFF1A\u63A5\u5165\u5916\u90E8 MCP \u670D\u52A1\u5668\u5DE5\u5177\uFF08config.json mcpServers \u914D\u7F6E\uFF09"));
|
|
@@ -2286,6 +2326,7 @@ var Renderer = class {
|
|
|
2286
2326
|
console.log(feat("/config set|get|show\uFF1AREPL \u5185\u5FEB\u6377\u8BFB\u5199\u914D\u7F6E\uFF0C\u65E0\u9700\u8FDB\u5165\u5411\u5BFC\uFF08\u70B9\u5206\u8DEF\u5F84 + \u81EA\u52A8\u7C7B\u578B\u8F6C\u6362\uFF09"));
|
|
2287
2327
|
console.log(feat("\u5DE5\u5177\u8C03\u7528\u6700\u7EC8\u56DE\u7B54\u6D41\u5F0F\u5316\uFF1A\u6A21\u62DF\u6253\u5B57\u673A\u6548\u679C\u9010\u5757\u8F93\u51FA\uFF0C\u96F6\u989D\u5916 API \u8C03\u7528\uFF0C\u652F\u6301 Escape \u4E2D\u65AD"));
|
|
2288
2328
|
console.log(feat("/diff \u547D\u4EE4\uFF1A\u663E\u793A\u5F53\u524D session \u5185\u6240\u6709\u6587\u4EF6\u4FEE\u6539\u7684\u6C47\u603B diff\uFF08\u5408\u5E76\u540C\u6587\u4EF6\u591A\u6B21\u4FEE\u6539\uFF09"));
|
|
2329
|
+
console.log(feat("/fork \u5BF9\u8BDD\u5206\u652F\uFF1A\u4ECE\u5F53\u524D\u4F4D\u7F6E\u6216\u6307\u5B9A checkpoint \u5206\u53C9\u4E3A\u65B0 session\uFF0C\u63A2\u7D22\u4E0D\u540C\u65B9\u6848"));
|
|
2289
2330
|
console.log();
|
|
2290
2331
|
}
|
|
2291
2332
|
printPrompt(provider, _model) {
|
|
@@ -2595,7 +2636,7 @@ function formatGitContextForPrompt(ctx) {
|
|
|
2595
2636
|
}
|
|
2596
2637
|
|
|
2597
2638
|
// src/tools/undo-stack.ts
|
|
2598
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync4 } from "fs";
|
|
2639
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, rmdirSync, existsSync as existsSync4 } from "fs";
|
|
2599
2640
|
var MAX_UNDO_DEPTH = 20;
|
|
2600
2641
|
var UndoStack = class {
|
|
2601
2642
|
stack = [];
|
|
@@ -2623,6 +2664,37 @@ var UndoStack = class {
|
|
|
2623
2664
|
this.stack.shift();
|
|
2624
2665
|
}
|
|
2625
2666
|
}
|
|
2667
|
+
/**
|
|
2668
|
+
* 推入一个新建文件的条目(previousContent=null),undo 时删除该文件。
|
|
2669
|
+
* 用于 bash 工具执行后检测到的新建文件。
|
|
2670
|
+
*/
|
|
2671
|
+
pushNewFile(filePath, description) {
|
|
2672
|
+
this.stack.push({
|
|
2673
|
+
filePath,
|
|
2674
|
+
previousContent: null,
|
|
2675
|
+
description,
|
|
2676
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
2677
|
+
});
|
|
2678
|
+
if (this.stack.length > MAX_UNDO_DEPTH) {
|
|
2679
|
+
this.stack.shift();
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* 推入一个新建目录的条目(previousContent=null, isDirectory=true),
|
|
2684
|
+
* undo 时尝试 rmdir(仅空目录可删)。
|
|
2685
|
+
*/
|
|
2686
|
+
pushNewDir(dirPath, description) {
|
|
2687
|
+
this.stack.push({
|
|
2688
|
+
filePath: dirPath,
|
|
2689
|
+
previousContent: null,
|
|
2690
|
+
description,
|
|
2691
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
2692
|
+
isDirectory: true
|
|
2693
|
+
});
|
|
2694
|
+
if (this.stack.length > MAX_UNDO_DEPTH) {
|
|
2695
|
+
this.stack.shift();
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2626
2698
|
/**
|
|
2627
2699
|
* 弹出并执行最近一次撤销操作。
|
|
2628
2700
|
* @returns 撤销结果描述,或 null(栈为空时)
|
|
@@ -2632,10 +2704,22 @@ var UndoStack = class {
|
|
|
2632
2704
|
if (!entry) return null;
|
|
2633
2705
|
try {
|
|
2634
2706
|
if (entry.previousContent === null) {
|
|
2635
|
-
if (
|
|
2636
|
-
|
|
2707
|
+
if (entry.isDirectory) {
|
|
2708
|
+
if (existsSync4(entry.filePath)) {
|
|
2709
|
+
try {
|
|
2710
|
+
rmdirSync(entry.filePath);
|
|
2711
|
+
return { entry, result: `Removed newly created directory: ${entry.filePath}` };
|
|
2712
|
+
} catch {
|
|
2713
|
+
return { entry, result: `Cannot remove directory (not empty): ${entry.filePath}` };
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
return { entry, result: `Directory already removed: ${entry.filePath}` };
|
|
2717
|
+
} else {
|
|
2718
|
+
if (existsSync4(entry.filePath)) {
|
|
2719
|
+
unlinkSync(entry.filePath);
|
|
2720
|
+
}
|
|
2721
|
+
return { entry, result: `Deleted newly created file: ${entry.filePath}` };
|
|
2637
2722
|
}
|
|
2638
|
-
return { entry, result: `Deleted newly created file: ${entry.filePath}` };
|
|
2639
2723
|
} else {
|
|
2640
2724
|
writeFileSync3(entry.filePath, entry.previousContent, "utf-8");
|
|
2641
2725
|
const lines = entry.previousContent.split("\n").length;
|
|
@@ -3173,7 +3257,7 @@ function createDefaultCommands() {
|
|
|
3173
3257
|
" /context - Show or reload hierarchical context layers",
|
|
3174
3258
|
" /status - Show current status",
|
|
3175
3259
|
" /search <keyword> - Search across all session histories",
|
|
3176
|
-
" /undo
|
|
3260
|
+
" /undo [list|<n>] - Undo file ops (list stack, undo N times)",
|
|
3177
3261
|
" /export [md|json] [file] - Export session to file (default: auto-named .md)",
|
|
3178
3262
|
" /tools - List all AI tools available",
|
|
3179
3263
|
" /plugins - Show plugin directory and loaded plugins",
|
|
@@ -3193,6 +3277,7 @@ function createDefaultCommands() {
|
|
|
3193
3277
|
" /doctor - Health check (API keys, config, MCP status)",
|
|
3194
3278
|
" /bug [--copy] - Generate bug report template (--copy to clipboard)",
|
|
3195
3279
|
" /diff [--stats] - Show all file modifications in this session",
|
|
3280
|
+
" /fork [checkpoint] - Fork session from checkpoint or current position",
|
|
3196
3281
|
" /exit - Exit"
|
|
3197
3282
|
] : [];
|
|
3198
3283
|
console.log("\nAvailable commands:");
|
|
@@ -3581,23 +3666,53 @@ ${text}
|
|
|
3581
3666
|
},
|
|
3582
3667
|
{
|
|
3583
3668
|
name: "undo",
|
|
3584
|
-
description: "Undo
|
|
3585
|
-
usage: "/undo",
|
|
3586
|
-
execute(
|
|
3587
|
-
const
|
|
3588
|
-
if (
|
|
3669
|
+
description: "Undo file operations (supports: /undo, /undo list, /undo <n>)",
|
|
3670
|
+
usage: "/undo [list | <n>]",
|
|
3671
|
+
execute(args, ctx) {
|
|
3672
|
+
const sub = args.trim();
|
|
3673
|
+
if (sub === "list") {
|
|
3674
|
+
const history = undoStack.getHistory();
|
|
3675
|
+
if (history.length === 0) {
|
|
3676
|
+
ctx.renderer.printInfo("Undo stack is empty.");
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
console.log(theme.heading("\n Undo Stack (" + history.length + " entries, newest last):\n"));
|
|
3680
|
+
history.forEach((entry, i) => {
|
|
3681
|
+
const timeStr = entry.timestamp.toLocaleTimeString();
|
|
3682
|
+
const typeTag = entry.isDirectory ? theme.accent("[dir]") : entry.previousContent === null ? theme.accent("[new]") : theme.dim("[mod]");
|
|
3683
|
+
console.log(` ${theme.dim(String(i + 1).padStart(3))} ${typeTag} ${entry.description} ${theme.dim(timeStr)}`);
|
|
3684
|
+
});
|
|
3685
|
+
console.log("");
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
const n = sub ? parseInt(sub, 10) : 1;
|
|
3689
|
+
if (isNaN(n) || n < 1) {
|
|
3690
|
+
ctx.renderer.printInfo("Usage: /undo [list | <n>] \u2014 n must be a positive integer.");
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
if (undoStack.depth === 0) {
|
|
3589
3694
|
ctx.renderer.printInfo("Nothing to undo.");
|
|
3590
3695
|
return;
|
|
3591
3696
|
}
|
|
3592
|
-
const
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
ctx.renderer.
|
|
3599
|
-
|
|
3697
|
+
const count = Math.min(n, undoStack.depth);
|
|
3698
|
+
const results = [];
|
|
3699
|
+
for (let i = 0; i < count; i++) {
|
|
3700
|
+
const top = undoStack.peek();
|
|
3701
|
+
if (!top) break;
|
|
3702
|
+
const timeStr = top.timestamp.toLocaleTimeString();
|
|
3703
|
+
ctx.renderer.printInfo(`Undoing [${i + 1}/${count}]: ${top.description} (${timeStr})`);
|
|
3704
|
+
const undoResult = undoStack.undo();
|
|
3705
|
+
if (undoResult) {
|
|
3706
|
+
results.push(undoResult.result);
|
|
3707
|
+
ctx.renderer.printSuccess(" " + undoResult.result);
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
if (results.length === 0) {
|
|
3600
3711
|
ctx.renderer.printInfo("Nothing to undo.");
|
|
3712
|
+
} else if (results.length > 1) {
|
|
3713
|
+
console.log(theme.success(`
|
|
3714
|
+
\u2713 ${results.length} operations undone.
|
|
3715
|
+
`));
|
|
3601
3716
|
}
|
|
3602
3717
|
}
|
|
3603
3718
|
},
|
|
@@ -4173,7 +4288,7 @@ ${hint}` : "")
|
|
|
4173
4288
|
description: "Run project tests and show structured report",
|
|
4174
4289
|
usage: "/test [command|filter]",
|
|
4175
4290
|
async execute(args, _ctx) {
|
|
4176
|
-
const { executeTests } = await import("./run-tests-
|
|
4291
|
+
const { executeTests } = await import("./run-tests-2TCRQBHR.js");
|
|
4177
4292
|
const argStr = args.join(" ").trim();
|
|
4178
4293
|
let testArgs = {};
|
|
4179
4294
|
if (argStr) {
|
|
@@ -4500,6 +4615,57 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
|
|
|
4500
4615
|
console.log();
|
|
4501
4616
|
}
|
|
4502
4617
|
},
|
|
4618
|
+
{
|
|
4619
|
+
name: "fork",
|
|
4620
|
+
description: "Fork current session from a checkpoint or current position",
|
|
4621
|
+
usage: "/fork [checkpoint-name]",
|
|
4622
|
+
async execute(args, ctx) {
|
|
4623
|
+
const session = ctx.sessions.current;
|
|
4624
|
+
if (!session) {
|
|
4625
|
+
ctx.renderer.printInfo("No active session to fork.");
|
|
4626
|
+
return;
|
|
4627
|
+
}
|
|
4628
|
+
const sub = args.join(" ").trim();
|
|
4629
|
+
let messageCount = session.messages.length;
|
|
4630
|
+
let fromLabel = "current position";
|
|
4631
|
+
if (sub) {
|
|
4632
|
+
const cp = session.checkpoints.find((c) => c.name === sub);
|
|
4633
|
+
if (!cp) {
|
|
4634
|
+
const available = session.checkpoints.map((c) => c.name);
|
|
4635
|
+
if (available.length > 0) {
|
|
4636
|
+
ctx.renderer.printInfo(
|
|
4637
|
+
`Checkpoint "${sub}" not found. Available: ${available.join(", ")}`
|
|
4638
|
+
);
|
|
4639
|
+
} else {
|
|
4640
|
+
ctx.renderer.printInfo(
|
|
4641
|
+
`Checkpoint "${sub}" not found. No checkpoints saved. Use /checkpoint save <name> first.`
|
|
4642
|
+
);
|
|
4643
|
+
}
|
|
4644
|
+
return;
|
|
4645
|
+
}
|
|
4646
|
+
messageCount = cp.messageIndex;
|
|
4647
|
+
fromLabel = `checkpoint "${cp.name}" (${cp.messageIndex} messages)`;
|
|
4648
|
+
}
|
|
4649
|
+
try {
|
|
4650
|
+
const originalId = session.id.slice(0, 8);
|
|
4651
|
+
const originalTitle = session.title ?? "(untitled)";
|
|
4652
|
+
const forked = await ctx.forkSession(messageCount);
|
|
4653
|
+
console.log(theme.heading("\n Session Forked\n"));
|
|
4654
|
+
console.log(` ${theme.dim("Original:")} ${theme.accent(originalId)} "${originalTitle}"`);
|
|
4655
|
+
console.log(` ${theme.dim("Forked: ")} ${theme.accent(forked.id.slice(0, 8))} "${forked.title ?? "(untitled)"}"`);
|
|
4656
|
+
console.log(` ${theme.dim("From: ")} ${fromLabel}`);
|
|
4657
|
+
console.log(` ${theme.dim("Messages:")} ${forked.messages.length} copied, ${forked.checkpoints.length} checkpoint(s) preserved`);
|
|
4658
|
+
console.log("");
|
|
4659
|
+
console.log(theme.dim(` Use /session load ${originalId} to switch back to original.`));
|
|
4660
|
+
console.log("");
|
|
4661
|
+
ctx.refreshPrompt();
|
|
4662
|
+
} catch (err) {
|
|
4663
|
+
ctx.renderer.printError(
|
|
4664
|
+
`Fork failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4665
|
+
);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
},
|
|
4503
4669
|
{
|
|
4504
4670
|
name: "exit",
|
|
4505
4671
|
description: "Exit the REPL",
|
|
@@ -4677,7 +4843,7 @@ function selectFromList(prompt, items, initialIndex = 0) {
|
|
|
4677
4843
|
|
|
4678
4844
|
// src/tools/builtin/bash.ts
|
|
4679
4845
|
import { execSync as execSync4 } from "child_process";
|
|
4680
|
-
import { existsSync as existsSync7 } from "fs";
|
|
4846
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
4681
4847
|
import { platform as platform2 } from "os";
|
|
4682
4848
|
import { resolve as resolve2 } from "path";
|
|
4683
4849
|
var IS_WINDOWS = platform2() === "win32";
|
|
@@ -4749,6 +4915,12 @@ var bashTool = {
|
|
|
4749
4915
|
} else {
|
|
4750
4916
|
actualCommand = command;
|
|
4751
4917
|
}
|
|
4918
|
+
const beforeSnapshot = snapshotDir(effectiveCwd);
|
|
4919
|
+
const parsedTargets = parseCreationTargets(command, effectiveCwd);
|
|
4920
|
+
const parsedTargetsBefore = /* @__PURE__ */ new Map();
|
|
4921
|
+
for (const t of parsedTargets) {
|
|
4922
|
+
parsedTargetsBefore.set(t, existsSync7(t));
|
|
4923
|
+
}
|
|
4752
4924
|
try {
|
|
4753
4925
|
const output = execSync4(actualCommand, {
|
|
4754
4926
|
timeout,
|
|
@@ -4763,9 +4935,11 @@ var bashTool = {
|
|
|
4763
4935
|
}
|
|
4764
4936
|
});
|
|
4765
4937
|
updateCwdFromCommand(command, effectiveCwd);
|
|
4938
|
+
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
4766
4939
|
const result = IS_WINDOWS && Buffer.isBuffer(output) ? output.toString("utf-8") : output;
|
|
4767
4940
|
return result || "(command completed with no output)";
|
|
4768
4941
|
} catch (err) {
|
|
4942
|
+
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
4769
4943
|
if (err && typeof err === "object" && "status" in err) {
|
|
4770
4944
|
const execErr = err;
|
|
4771
4945
|
const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? execErr.stderr.toString("utf-8").trim() : execErr.stderr?.toString().trim() ?? "";
|
|
@@ -4798,6 +4972,69 @@ function fixWindowsDeleteCommand(command) {
|
|
|
4798
4972
|
}
|
|
4799
4973
|
);
|
|
4800
4974
|
}
|
|
4975
|
+
function snapshotDir(dir) {
|
|
4976
|
+
try {
|
|
4977
|
+
return new Set(readdirSync3(dir).map((name) => resolve2(dir, name)));
|
|
4978
|
+
} catch {
|
|
4979
|
+
return /* @__PURE__ */ new Set();
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
function parseCreationTargets(command, cwd) {
|
|
4983
|
+
const targets = [];
|
|
4984
|
+
for (const m of command.matchAll(/(?:echo|cat|printf)\s+[^>]*>\s*(['"]?)([^\s;&|'"]+)\1/g)) {
|
|
4985
|
+
if (m[2]) targets.push(resolve2(cwd, m[2]));
|
|
4986
|
+
}
|
|
4987
|
+
for (const m of command.matchAll(/\btouch\s+((?:['"]?[^\s;&|'"]+['"]?\s*)+)/g)) {
|
|
4988
|
+
for (const f of m[1].trim().split(/\s+/)) {
|
|
4989
|
+
const clean = f.replace(/^['"]|['"]$/g, "");
|
|
4990
|
+
if (clean && !clean.startsWith("-")) targets.push(resolve2(cwd, clean));
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
for (const m of command.matchAll(/\bmkdir\s+(?:-\w+\s+)*((?:['"]?[^\s;&|'"]+['"]?\s*)+)/g)) {
|
|
4994
|
+
for (const d of m[1].trim().split(/\s+/)) {
|
|
4995
|
+
const clean = d.replace(/^['"]|['"]$/g, "");
|
|
4996
|
+
if (clean && !clean.startsWith("-")) targets.push(resolve2(cwd, clean));
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
for (const m of command.matchAll(/\bcp\s+(?:-\w+\s+)*['"]?[^\s;&|'"]+['"]?\s+(['"]?)([^\s;&|'"]+)\1/g)) {
|
|
5000
|
+
if (m[2]) targets.push(resolve2(cwd, m[2]));
|
|
5001
|
+
}
|
|
5002
|
+
for (const m of command.matchAll(/\bNew-Item\s+(?:-(?:Path|ItemType)\s+\w+\s+)*['"]?([^\s;&|'"]+)['"]?/gi)) {
|
|
5003
|
+
if (m[1] && !m[1].startsWith("-")) targets.push(resolve2(cwd, m[1]));
|
|
5004
|
+
}
|
|
5005
|
+
return [...new Set(targets)];
|
|
5006
|
+
}
|
|
5007
|
+
function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
5008
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
5009
|
+
const afterSnapshot = snapshotDir(cwd);
|
|
5010
|
+
for (const absPath of afterSnapshot) {
|
|
5011
|
+
if (!beforeSnapshot.has(absPath)) {
|
|
5012
|
+
try {
|
|
5013
|
+
const st = statSync3(absPath);
|
|
5014
|
+
if (st.isDirectory()) {
|
|
5015
|
+
undoStack.pushNewDir(absPath, `bash (new dir): ${absPath}`);
|
|
5016
|
+
} else {
|
|
5017
|
+
undoStack.pushNewFile(absPath, `bash (new file): ${absPath}`);
|
|
5018
|
+
}
|
|
5019
|
+
tracked.add(absPath);
|
|
5020
|
+
} catch {
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
for (const [target, existedBefore] of parsedTargetsBefore) {
|
|
5025
|
+
if (!existedBefore && !tracked.has(target) && existsSync7(target)) {
|
|
5026
|
+
try {
|
|
5027
|
+
const st = statSync3(target);
|
|
5028
|
+
if (st.isDirectory()) {
|
|
5029
|
+
undoStack.pushNewDir(target, `bash (new dir): ${target}`);
|
|
5030
|
+
} else {
|
|
5031
|
+
undoStack.pushNewFile(target, `bash (new file): ${target}`);
|
|
5032
|
+
}
|
|
5033
|
+
} catch {
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
4801
5038
|
function updateCwdFromCommand(command, baseCwd) {
|
|
4802
5039
|
const cdMatches = [...command.matchAll(/(?:^|[;&|])\s*cd\s+(['"]?)([^\s;&|'"]+)\1/g)];
|
|
4803
5040
|
if (cdMatches.length === 0) return;
|
|
@@ -4814,7 +5051,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
4814
5051
|
}
|
|
4815
5052
|
|
|
4816
5053
|
// src/tools/builtin/read-file.ts
|
|
4817
|
-
import { readFileSync as readFileSync5, existsSync as existsSync8, statSync as
|
|
5054
|
+
import { readFileSync as readFileSync5, existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
4818
5055
|
import { extname, resolve as resolve3, basename as basename2, sep } from "path";
|
|
4819
5056
|
import { homedir as homedir2 } from "os";
|
|
4820
5057
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
@@ -4908,7 +5145,7 @@ var readFileTool = {
|
|
|
4908
5145
|
if (!filePath) throw new Error("path is required");
|
|
4909
5146
|
const normalizedPath = resolve3(filePath);
|
|
4910
5147
|
if (!existsSync8(normalizedPath)) throw new Error(`File not found: ${filePath}`);
|
|
4911
|
-
const { size } =
|
|
5148
|
+
const { size } = statSync4(normalizedPath);
|
|
4912
5149
|
if (size > MAX_FILE_BYTES) {
|
|
4913
5150
|
const mb = (size / 1024 / 1024).toFixed(1);
|
|
4914
5151
|
return `[File too large: ${filePath} (${mb} MB)]
|
|
@@ -5221,7 +5458,7 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
5221
5458
|
}
|
|
5222
5459
|
|
|
5223
5460
|
// src/tools/builtin/list-dir.ts
|
|
5224
|
-
import { readdirSync as
|
|
5461
|
+
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync10 } from "fs";
|
|
5225
5462
|
import { join as join6 } from "path";
|
|
5226
5463
|
var listDirTool = {
|
|
5227
5464
|
definition: {
|
|
@@ -5254,7 +5491,7 @@ var listDirTool = {
|
|
|
5254
5491
|
function listRecursive(basePath, indent, recursive, lines) {
|
|
5255
5492
|
let entries;
|
|
5256
5493
|
try {
|
|
5257
|
-
entries =
|
|
5494
|
+
entries = readdirSync4(basePath, { withFileTypes: true });
|
|
5258
5495
|
} catch {
|
|
5259
5496
|
lines.push(`${indent}(permission denied)`);
|
|
5260
5497
|
return;
|
|
@@ -5277,7 +5514,7 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
5277
5514
|
}
|
|
5278
5515
|
} else {
|
|
5279
5516
|
try {
|
|
5280
|
-
const stat =
|
|
5517
|
+
const stat = statSync5(join6(basePath, entry.name));
|
|
5281
5518
|
const size = formatSize(stat.size);
|
|
5282
5519
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
5283
5520
|
} catch {
|
|
@@ -5293,7 +5530,7 @@ function formatSize(bytes) {
|
|
|
5293
5530
|
}
|
|
5294
5531
|
|
|
5295
5532
|
// src/tools/builtin/grep-files.ts
|
|
5296
|
-
import { readdirSync as
|
|
5533
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync6, existsSync as existsSync11 } from "fs";
|
|
5297
5534
|
import { join as join7, relative } from "path";
|
|
5298
5535
|
var grepFilesTool = {
|
|
5299
5536
|
definition: {
|
|
@@ -5354,7 +5591,7 @@ var grepFilesTool = {
|
|
|
5354
5591
|
regex = new RegExp(escaped, ignoreCase ? "gi" : "g");
|
|
5355
5592
|
}
|
|
5356
5593
|
const results = [];
|
|
5357
|
-
const stat =
|
|
5594
|
+
const stat = statSync6(rootPath);
|
|
5358
5595
|
if (stat.isFile()) {
|
|
5359
5596
|
searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
|
|
5360
5597
|
} else {
|
|
@@ -5410,7 +5647,7 @@ function collectFiles(dirPath, filePattern, results, regex, contextLines, maxRes
|
|
|
5410
5647
|
if (results.length >= maxResults) return;
|
|
5411
5648
|
let entries;
|
|
5412
5649
|
try {
|
|
5413
|
-
entries =
|
|
5650
|
+
entries = readdirSync5(dirPath, { withFileTypes: true });
|
|
5414
5651
|
} catch {
|
|
5415
5652
|
return;
|
|
5416
5653
|
}
|
|
@@ -5463,7 +5700,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
5463
5700
|
}
|
|
5464
5701
|
|
|
5465
5702
|
// src/tools/builtin/glob-files.ts
|
|
5466
|
-
import { readdirSync as
|
|
5703
|
+
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync12 } from "fs";
|
|
5467
5704
|
import { join as join8, relative as relative2, basename as basename3 } from "path";
|
|
5468
5705
|
var globFilesTool = {
|
|
5469
5706
|
definition: {
|
|
@@ -5568,7 +5805,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
5568
5805
|
if (results.length >= maxResults) return;
|
|
5569
5806
|
let entries;
|
|
5570
5807
|
try {
|
|
5571
|
-
entries =
|
|
5808
|
+
entries = readdirSync6(dirPath, { withFileTypes: true });
|
|
5572
5809
|
} catch {
|
|
5573
5810
|
return;
|
|
5574
5811
|
}
|
|
@@ -5582,7 +5819,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
5582
5819
|
const relPath = relative2(rootPath, fullPath).replace(/\\/g, "/");
|
|
5583
5820
|
if (regex.test(relPath) || regex.test(basename3(relPath))) {
|
|
5584
5821
|
try {
|
|
5585
|
-
const stat =
|
|
5822
|
+
const stat = statSync7(fullPath);
|
|
5586
5823
|
results.push({ relPath, absPath: fullPath, mtime: stat.mtimeMs });
|
|
5587
5824
|
} catch {
|
|
5588
5825
|
results.push({ relPath, absPath: fullPath, mtime: 0 });
|
|
@@ -6553,7 +6790,7 @@ var spawnAgentTool = {
|
|
|
6553
6790
|
|
|
6554
6791
|
// src/tools/registry.ts
|
|
6555
6792
|
import { pathToFileURL } from "url";
|
|
6556
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as
|
|
6793
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
|
|
6557
6794
|
import { join as join10 } from "path";
|
|
6558
6795
|
var ToolRegistry = class {
|
|
6559
6796
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -6636,7 +6873,7 @@ var ToolRegistry = class {
|
|
|
6636
6873
|
}
|
|
6637
6874
|
let files;
|
|
6638
6875
|
try {
|
|
6639
|
-
files =
|
|
6876
|
+
files = readdirSync7(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
6640
6877
|
} catch {
|
|
6641
6878
|
return 0;
|
|
6642
6879
|
}
|
|
@@ -7396,7 +7633,7 @@ Managing ${displayName} API Key`);
|
|
|
7396
7633
|
};
|
|
7397
7634
|
|
|
7398
7635
|
// src/repl/custom-commands.ts
|
|
7399
|
-
import { existsSync as existsSync16, readFileSync as readFileSync10, readdirSync as
|
|
7636
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10, readdirSync as readdirSync8, mkdirSync as mkdirSync9 } from "fs";
|
|
7400
7637
|
import { join as join11, extname as extname3 } from "path";
|
|
7401
7638
|
import { execSync as execSync6 } from "child_process";
|
|
7402
7639
|
function parseSimpleYaml(text) {
|
|
@@ -7466,7 +7703,7 @@ var CustomCommandManager = class {
|
|
|
7466
7703
|
return 0;
|
|
7467
7704
|
}
|
|
7468
7705
|
let count = 0;
|
|
7469
|
-
for (const file of
|
|
7706
|
+
for (const file of readdirSync8(this.commandsDir)) {
|
|
7470
7707
|
if (extname3(file) !== ".md") continue;
|
|
7471
7708
|
const cmd = parseCommandFile(join11(this.commandsDir, file));
|
|
7472
7709
|
if (cmd) {
|
|
@@ -8058,7 +8295,7 @@ var McpManager = class {
|
|
|
8058
8295
|
};
|
|
8059
8296
|
|
|
8060
8297
|
// src/skills/manager.ts
|
|
8061
|
-
import { existsSync as existsSync18, readdirSync as
|
|
8298
|
+
import { existsSync as existsSync18, readdirSync as readdirSync9, mkdirSync as mkdirSync11 } from "fs";
|
|
8062
8299
|
import { join as join13 } from "path";
|
|
8063
8300
|
|
|
8064
8301
|
// src/skills/types.ts
|
|
@@ -8133,7 +8370,7 @@ var SkillManager = class {
|
|
|
8133
8370
|
}
|
|
8134
8371
|
let entries;
|
|
8135
8372
|
try {
|
|
8136
|
-
entries =
|
|
8373
|
+
entries = readdirSync9(this.skillsDir);
|
|
8137
8374
|
} catch {
|
|
8138
8375
|
return 0;
|
|
8139
8376
|
}
|
|
@@ -8252,7 +8489,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
8252
8489
|
continue;
|
|
8253
8490
|
}
|
|
8254
8491
|
if (mime) {
|
|
8255
|
-
const fileSize =
|
|
8492
|
+
const fileSize = statSync8(absPath).size;
|
|
8256
8493
|
if (fileSize > MAX_IMAGE_BYTES) {
|
|
8257
8494
|
refs.push({ path: rawPath, type: "toolarge" });
|
|
8258
8495
|
continue;
|
|
@@ -8450,7 +8687,7 @@ var Repl = class {
|
|
|
8450
8687
|
if (depth > 2 || entryCount >= MAX_TREE_ENTRIES) return;
|
|
8451
8688
|
let entries;
|
|
8452
8689
|
try {
|
|
8453
|
-
entries =
|
|
8690
|
+
entries = readdirSync10(dir);
|
|
8454
8691
|
} catch {
|
|
8455
8692
|
return;
|
|
8456
8693
|
}
|
|
@@ -8462,7 +8699,7 @@ var Repl = class {
|
|
|
8462
8699
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
8463
8700
|
let isDir;
|
|
8464
8701
|
try {
|
|
8465
|
-
isDir =
|
|
8702
|
+
isDir = statSync8(fullPath).isDirectory();
|
|
8466
8703
|
} catch {
|
|
8467
8704
|
continue;
|
|
8468
8705
|
}
|
|
@@ -8484,7 +8721,7 @@ ${treeLines.join("\n")}`
|
|
|
8484
8721
|
if (totalChars >= MAX_TOTAL_CHARS) return;
|
|
8485
8722
|
let entries;
|
|
8486
8723
|
try {
|
|
8487
|
-
entries =
|
|
8724
|
+
entries = readdirSync10(dir);
|
|
8488
8725
|
} catch {
|
|
8489
8726
|
return;
|
|
8490
8727
|
}
|
|
@@ -8494,7 +8731,7 @@ ${treeLines.join("\n")}`
|
|
|
8494
8731
|
const fullPath = join14(dir, name);
|
|
8495
8732
|
let st;
|
|
8496
8733
|
try {
|
|
8497
|
-
st =
|
|
8734
|
+
st = statSync8(fullPath);
|
|
8498
8735
|
} catch {
|
|
8499
8736
|
continue;
|
|
8500
8737
|
}
|
|
@@ -8541,7 +8778,7 @@ ${content}
|
|
|
8541
8778
|
}
|
|
8542
8779
|
let isDir;
|
|
8543
8780
|
try {
|
|
8544
|
-
isDir =
|
|
8781
|
+
isDir = statSync8(absPath).isDirectory();
|
|
8545
8782
|
} catch {
|
|
8546
8783
|
return { success: false, charCount: 0, added: false, error: `Cannot access: ${dirPath}` };
|
|
8547
8784
|
}
|
|
@@ -9386,6 +9623,12 @@ Session '${this.resumeSessionId}' not found.
|
|
|
9386
9623
|
return ["reload"];
|
|
9387
9624
|
case "mcp":
|
|
9388
9625
|
return ["reconnect"];
|
|
9626
|
+
case "undo":
|
|
9627
|
+
return ["list"];
|
|
9628
|
+
case "fork": {
|
|
9629
|
+
const cps = this.sessions.current?.checkpoints.map((c) => c.name) ?? [];
|
|
9630
|
+
return cps;
|
|
9631
|
+
}
|
|
9389
9632
|
case "compact":
|
|
9390
9633
|
case "review":
|
|
9391
9634
|
case "init":
|
|
@@ -9406,14 +9649,14 @@ Session '${this.resumeSessionId}' not found.
|
|
|
9406
9649
|
const prefix = normalized.includes("/") ? basename5(normalized) : normalized;
|
|
9407
9650
|
const absDir = resolve4(process.cwd(), dir);
|
|
9408
9651
|
if (!existsSync19(absDir)) return [];
|
|
9409
|
-
const entries =
|
|
9652
|
+
const entries = readdirSync10(absDir);
|
|
9410
9653
|
const results = [];
|
|
9411
9654
|
for (const entry of entries) {
|
|
9412
9655
|
if (entry.startsWith(".")) continue;
|
|
9413
9656
|
if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
|
|
9414
9657
|
try {
|
|
9415
9658
|
const fullPath = join14(absDir, entry);
|
|
9416
|
-
const stat =
|
|
9659
|
+
const stat = statSync8(fullPath);
|
|
9417
9660
|
const rel = dir === "." ? entry : `${dir}/${entry}`;
|
|
9418
9661
|
results.push(stat.isDirectory() ? `${rel}/` : rel);
|
|
9419
9662
|
} catch {
|
|
@@ -9943,6 +10186,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
9943
10186
|
addContextDir: (dirPath) => this.addExtraContextDir(dirPath),
|
|
9944
10187
|
removeContextDir: (dirPath) => this.removeExtraContextDir(dirPath),
|
|
9945
10188
|
listContextDirs: () => [...this.extraContextDirs],
|
|
10189
|
+
forkSession: (messageCount, title) => this.sessions.forkSession(messageCount, title),
|
|
9946
10190
|
exit: () => this.handleExit()
|
|
9947
10191
|
};
|
|
9948
10192
|
await cmd.execute(args, ctx);
|