jinzd-ai-cli 0.1.46 → 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 崩溃。
|
|
@@ -1924,7 +1944,12 @@ var SessionManager = class {
|
|
|
1924
1944
|
if (!existsSync2(filePath)) {
|
|
1925
1945
|
throw new Error(`Session ${id} not found`);
|
|
1926
1946
|
}
|
|
1927
|
-
|
|
1947
|
+
let data;
|
|
1948
|
+
try {
|
|
1949
|
+
data = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
throw new Error(`Session ${id} is corrupted: ${err instanceof Error ? err.message : String(err)}`);
|
|
1952
|
+
}
|
|
1928
1953
|
const session = Session.fromJSON(data);
|
|
1929
1954
|
this._current = session;
|
|
1930
1955
|
return session;
|
|
@@ -1956,6 +1981,26 @@ var SessionManager = class {
|
|
|
1956
1981
|
}
|
|
1957
1982
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
1958
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
|
+
}
|
|
1959
2004
|
/**
|
|
1960
2005
|
* 跨 session 全文搜索。
|
|
1961
2006
|
* 遍历所有历史 JSON 文件,逐条匹配消息内容(不区分大小写),
|
|
@@ -2016,7 +2061,7 @@ var SessionManager = class {
|
|
|
2016
2061
|
|
|
2017
2062
|
// src/repl/repl.ts
|
|
2018
2063
|
import * as readline from "readline";
|
|
2019
|
-
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";
|
|
2020
2065
|
import { join as join14, resolve as resolve4, extname as extname4, dirname as dirname5, basename as basename5 } from "path";
|
|
2021
2066
|
import chalk10 from "chalk";
|
|
2022
2067
|
|
|
@@ -2226,12 +2271,12 @@ var Renderer = class {
|
|
|
2226
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"));
|
|
2227
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"));
|
|
2228
2273
|
console.log(HR);
|
|
2229
|
-
console.log(theme.dim(" REPL \u547D\u4EE4\
|
|
2274
|
+
console.log(theme.dim(" REPL \u547D\u4EE4\uFF0835\u4E2A\uFF09\uFF1A"));
|
|
2230
2275
|
console.log(theme.dim(" /help /about /provider /model /clear /compact /plan /session"));
|
|
2231
2276
|
console.log(theme.dim(" /system /context /status /search /undo /export /copy /cost"));
|
|
2232
2277
|
console.log(theme.dim(" /init /skill /tools /plugins /mcp /config /checkpoint /review"));
|
|
2233
2278
|
console.log(theme.dim(" /commands /test /scaffold /add-dir /memory /doctor /bug /think"));
|
|
2234
|
-
console.log(theme.dim(" /diff /exit"));
|
|
2279
|
+
console.log(theme.dim(" /diff /fork /exit"));
|
|
2235
2280
|
console.log(HR);
|
|
2236
2281
|
console.log(theme.dim(" \u4E3B\u8981\u7279\u6027\uFF1A"));
|
|
2237
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"));
|
|
@@ -2239,7 +2284,7 @@ var Renderer = class {
|
|
|
2239
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"));
|
|
2240
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"));
|
|
2241
2286
|
console.log(feat("\u8DE8 session \u5386\u53F2\u5168\u6587\u641C\u7D22\uFF08/search <\u5173\u952E\u8BCD>\uFF09"));
|
|
2242
|
-
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"));
|
|
2243
2288
|
console.log(feat("Thinking \u6A21\u5F0F\u6298\u53E0\uFF08<think> \u5757\u81EA\u52A8\u6298\u53E0\uFF0CGLM-5 \u7B49\uFF09"));
|
|
2244
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"));
|
|
2245
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"));
|
|
@@ -2281,6 +2326,7 @@ var Renderer = class {
|
|
|
2281
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"));
|
|
2282
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"));
|
|
2283
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"));
|
|
2284
2330
|
console.log();
|
|
2285
2331
|
}
|
|
2286
2332
|
printPrompt(provider, _model) {
|
|
@@ -2590,7 +2636,7 @@ function formatGitContextForPrompt(ctx) {
|
|
|
2590
2636
|
}
|
|
2591
2637
|
|
|
2592
2638
|
// src/tools/undo-stack.ts
|
|
2593
|
-
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";
|
|
2594
2640
|
var MAX_UNDO_DEPTH = 20;
|
|
2595
2641
|
var UndoStack = class {
|
|
2596
2642
|
stack = [];
|
|
@@ -2618,6 +2664,37 @@ var UndoStack = class {
|
|
|
2618
2664
|
this.stack.shift();
|
|
2619
2665
|
}
|
|
2620
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
|
+
}
|
|
2621
2698
|
/**
|
|
2622
2699
|
* 弹出并执行最近一次撤销操作。
|
|
2623
2700
|
* @returns 撤销结果描述,或 null(栈为空时)
|
|
@@ -2627,10 +2704,22 @@ var UndoStack = class {
|
|
|
2627
2704
|
if (!entry) return null;
|
|
2628
2705
|
try {
|
|
2629
2706
|
if (entry.previousContent === null) {
|
|
2630
|
-
if (
|
|
2631
|
-
|
|
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}` };
|
|
2632
2722
|
}
|
|
2633
|
-
return { entry, result: `Deleted newly created file: ${entry.filePath}` };
|
|
2634
2723
|
} else {
|
|
2635
2724
|
writeFileSync3(entry.filePath, entry.previousContent, "utf-8");
|
|
2636
2725
|
const lines = entry.previousContent.split("\n").length;
|
|
@@ -2950,8 +3039,15 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
2950
3039
|
}
|
|
2951
3040
|
const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
|
|
2952
3041
|
const sorted = filtered.sort((a, b) => {
|
|
2953
|
-
|
|
2954
|
-
|
|
3042
|
+
let aIsDir = false, bIsDir = false;
|
|
3043
|
+
try {
|
|
3044
|
+
aIsDir = statSync2(join5(d, a)).isDirectory();
|
|
3045
|
+
} catch {
|
|
3046
|
+
}
|
|
3047
|
+
try {
|
|
3048
|
+
bIsDir = statSync2(join5(d, b)).isDirectory();
|
|
3049
|
+
} catch {
|
|
3050
|
+
}
|
|
2955
3051
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
2956
3052
|
return a.localeCompare(b);
|
|
2957
3053
|
});
|
|
@@ -3161,7 +3257,7 @@ function createDefaultCommands() {
|
|
|
3161
3257
|
" /context - Show or reload hierarchical context layers",
|
|
3162
3258
|
" /status - Show current status",
|
|
3163
3259
|
" /search <keyword> - Search across all session histories",
|
|
3164
|
-
" /undo
|
|
3260
|
+
" /undo [list|<n>] - Undo file ops (list stack, undo N times)",
|
|
3165
3261
|
" /export [md|json] [file] - Export session to file (default: auto-named .md)",
|
|
3166
3262
|
" /tools - List all AI tools available",
|
|
3167
3263
|
" /plugins - Show plugin directory and loaded plugins",
|
|
@@ -3181,6 +3277,7 @@ function createDefaultCommands() {
|
|
|
3181
3277
|
" /doctor - Health check (API keys, config, MCP status)",
|
|
3182
3278
|
" /bug [--copy] - Generate bug report template (--copy to clipboard)",
|
|
3183
3279
|
" /diff [--stats] - Show all file modifications in this session",
|
|
3280
|
+
" /fork [checkpoint] - Fork session from checkpoint or current position",
|
|
3184
3281
|
" /exit - Exit"
|
|
3185
3282
|
] : [];
|
|
3186
3283
|
console.log("\nAvailable commands:");
|
|
@@ -3569,23 +3666,53 @@ ${text}
|
|
|
3569
3666
|
},
|
|
3570
3667
|
{
|
|
3571
3668
|
name: "undo",
|
|
3572
|
-
description: "Undo
|
|
3573
|
-
usage: "/undo",
|
|
3574
|
-
execute(
|
|
3575
|
-
const
|
|
3576
|
-
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) {
|
|
3577
3694
|
ctx.renderer.printInfo("Nothing to undo.");
|
|
3578
3695
|
return;
|
|
3579
3696
|
}
|
|
3580
|
-
const
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
ctx.renderer.
|
|
3587
|
-
|
|
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) {
|
|
3588
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
|
+
`));
|
|
3589
3716
|
}
|
|
3590
3717
|
}
|
|
3591
3718
|
},
|
|
@@ -4161,7 +4288,7 @@ ${hint}` : "")
|
|
|
4161
4288
|
description: "Run project tests and show structured report",
|
|
4162
4289
|
usage: "/test [command|filter]",
|
|
4163
4290
|
async execute(args, _ctx) {
|
|
4164
|
-
const { executeTests } = await import("./run-tests-
|
|
4291
|
+
const { executeTests } = await import("./run-tests-2TCRQBHR.js");
|
|
4165
4292
|
const argStr = args.join(" ").trim();
|
|
4166
4293
|
let testArgs = {};
|
|
4167
4294
|
if (argStr) {
|
|
@@ -4488,6 +4615,57 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
|
|
|
4488
4615
|
console.log();
|
|
4489
4616
|
}
|
|
4490
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
|
+
},
|
|
4491
4669
|
{
|
|
4492
4670
|
name: "exit",
|
|
4493
4671
|
description: "Exit the REPL",
|
|
@@ -4665,7 +4843,7 @@ function selectFromList(prompt, items, initialIndex = 0) {
|
|
|
4665
4843
|
|
|
4666
4844
|
// src/tools/builtin/bash.ts
|
|
4667
4845
|
import { execSync as execSync4 } from "child_process";
|
|
4668
|
-
import { existsSync as existsSync7 } from "fs";
|
|
4846
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
4669
4847
|
import { platform as platform2 } from "os";
|
|
4670
4848
|
import { resolve as resolve2 } from "path";
|
|
4671
4849
|
var IS_WINDOWS = platform2() === "win32";
|
|
@@ -4737,6 +4915,12 @@ var bashTool = {
|
|
|
4737
4915
|
} else {
|
|
4738
4916
|
actualCommand = command;
|
|
4739
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
|
+
}
|
|
4740
4924
|
try {
|
|
4741
4925
|
const output = execSync4(actualCommand, {
|
|
4742
4926
|
timeout,
|
|
@@ -4751,9 +4935,11 @@ var bashTool = {
|
|
|
4751
4935
|
}
|
|
4752
4936
|
});
|
|
4753
4937
|
updateCwdFromCommand(command, effectiveCwd);
|
|
4938
|
+
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
4754
4939
|
const result = IS_WINDOWS && Buffer.isBuffer(output) ? output.toString("utf-8") : output;
|
|
4755
4940
|
return result || "(command completed with no output)";
|
|
4756
4941
|
} catch (err) {
|
|
4942
|
+
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
4757
4943
|
if (err && typeof err === "object" && "status" in err) {
|
|
4758
4944
|
const execErr = err;
|
|
4759
4945
|
const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? execErr.stderr.toString("utf-8").trim() : execErr.stderr?.toString().trim() ?? "";
|
|
@@ -4786,6 +4972,69 @@ function fixWindowsDeleteCommand(command) {
|
|
|
4786
4972
|
}
|
|
4787
4973
|
);
|
|
4788
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
|
+
}
|
|
4789
5038
|
function updateCwdFromCommand(command, baseCwd) {
|
|
4790
5039
|
const cdMatches = [...command.matchAll(/(?:^|[;&|])\s*cd\s+(['"]?)([^\s;&|'"]+)\1/g)];
|
|
4791
5040
|
if (cdMatches.length === 0) return;
|
|
@@ -4802,7 +5051,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
4802
5051
|
}
|
|
4803
5052
|
|
|
4804
5053
|
// src/tools/builtin/read-file.ts
|
|
4805
|
-
import { readFileSync as readFileSync5, existsSync as existsSync8, statSync as
|
|
5054
|
+
import { readFileSync as readFileSync5, existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
4806
5055
|
import { extname, resolve as resolve3, basename as basename2, sep } from "path";
|
|
4807
5056
|
import { homedir as homedir2 } from "os";
|
|
4808
5057
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
@@ -4896,7 +5145,7 @@ var readFileTool = {
|
|
|
4896
5145
|
if (!filePath) throw new Error("path is required");
|
|
4897
5146
|
const normalizedPath = resolve3(filePath);
|
|
4898
5147
|
if (!existsSync8(normalizedPath)) throw new Error(`File not found: ${filePath}`);
|
|
4899
|
-
const { size } =
|
|
5148
|
+
const { size } = statSync4(normalizedPath);
|
|
4900
5149
|
if (size > MAX_FILE_BYTES) {
|
|
4901
5150
|
const mb = (size / 1024 / 1024).toFixed(1);
|
|
4902
5151
|
return `[File too large: ${filePath} (${mb} MB)]
|
|
@@ -5209,7 +5458,7 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
5209
5458
|
}
|
|
5210
5459
|
|
|
5211
5460
|
// src/tools/builtin/list-dir.ts
|
|
5212
|
-
import { readdirSync as
|
|
5461
|
+
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync10 } from "fs";
|
|
5213
5462
|
import { join as join6 } from "path";
|
|
5214
5463
|
var listDirTool = {
|
|
5215
5464
|
definition: {
|
|
@@ -5242,7 +5491,7 @@ var listDirTool = {
|
|
|
5242
5491
|
function listRecursive(basePath, indent, recursive, lines) {
|
|
5243
5492
|
let entries;
|
|
5244
5493
|
try {
|
|
5245
|
-
entries =
|
|
5494
|
+
entries = readdirSync4(basePath, { withFileTypes: true });
|
|
5246
5495
|
} catch {
|
|
5247
5496
|
lines.push(`${indent}(permission denied)`);
|
|
5248
5497
|
return;
|
|
@@ -5265,7 +5514,7 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
5265
5514
|
}
|
|
5266
5515
|
} else {
|
|
5267
5516
|
try {
|
|
5268
|
-
const stat =
|
|
5517
|
+
const stat = statSync5(join6(basePath, entry.name));
|
|
5269
5518
|
const size = formatSize(stat.size);
|
|
5270
5519
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
5271
5520
|
} catch {
|
|
@@ -5281,7 +5530,7 @@ function formatSize(bytes) {
|
|
|
5281
5530
|
}
|
|
5282
5531
|
|
|
5283
5532
|
// src/tools/builtin/grep-files.ts
|
|
5284
|
-
import { readdirSync as
|
|
5533
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync6, existsSync as existsSync11 } from "fs";
|
|
5285
5534
|
import { join as join7, relative } from "path";
|
|
5286
5535
|
var grepFilesTool = {
|
|
5287
5536
|
definition: {
|
|
@@ -5342,7 +5591,7 @@ var grepFilesTool = {
|
|
|
5342
5591
|
regex = new RegExp(escaped, ignoreCase ? "gi" : "g");
|
|
5343
5592
|
}
|
|
5344
5593
|
const results = [];
|
|
5345
|
-
const stat =
|
|
5594
|
+
const stat = statSync6(rootPath);
|
|
5346
5595
|
if (stat.isFile()) {
|
|
5347
5596
|
searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
|
|
5348
5597
|
} else {
|
|
@@ -5398,7 +5647,7 @@ function collectFiles(dirPath, filePattern, results, regex, contextLines, maxRes
|
|
|
5398
5647
|
if (results.length >= maxResults) return;
|
|
5399
5648
|
let entries;
|
|
5400
5649
|
try {
|
|
5401
|
-
entries =
|
|
5650
|
+
entries = readdirSync5(dirPath, { withFileTypes: true });
|
|
5402
5651
|
} catch {
|
|
5403
5652
|
return;
|
|
5404
5653
|
}
|
|
@@ -5451,7 +5700,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
|
|
|
5451
5700
|
}
|
|
5452
5701
|
|
|
5453
5702
|
// src/tools/builtin/glob-files.ts
|
|
5454
|
-
import { readdirSync as
|
|
5703
|
+
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync12 } from "fs";
|
|
5455
5704
|
import { join as join8, relative as relative2, basename as basename3 } from "path";
|
|
5456
5705
|
var globFilesTool = {
|
|
5457
5706
|
definition: {
|
|
@@ -5556,7 +5805,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
5556
5805
|
if (results.length >= maxResults) return;
|
|
5557
5806
|
let entries;
|
|
5558
5807
|
try {
|
|
5559
|
-
entries =
|
|
5808
|
+
entries = readdirSync6(dirPath, { withFileTypes: true });
|
|
5560
5809
|
} catch {
|
|
5561
5810
|
return;
|
|
5562
5811
|
}
|
|
@@ -5570,7 +5819,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
5570
5819
|
const relPath = relative2(rootPath, fullPath).replace(/\\/g, "/");
|
|
5571
5820
|
if (regex.test(relPath) || regex.test(basename3(relPath))) {
|
|
5572
5821
|
try {
|
|
5573
|
-
const stat =
|
|
5822
|
+
const stat = statSync7(fullPath);
|
|
5574
5823
|
results.push({ relPath, absPath: fullPath, mtime: stat.mtimeMs });
|
|
5575
5824
|
} catch {
|
|
5576
5825
|
results.push({ relPath, absPath: fullPath, mtime: 0 });
|
|
@@ -6541,7 +6790,7 @@ var spawnAgentTool = {
|
|
|
6541
6790
|
|
|
6542
6791
|
// src/tools/registry.ts
|
|
6543
6792
|
import { pathToFileURL } from "url";
|
|
6544
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as
|
|
6793
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
|
|
6545
6794
|
import { join as join10 } from "path";
|
|
6546
6795
|
var ToolRegistry = class {
|
|
6547
6796
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -6624,7 +6873,7 @@ var ToolRegistry = class {
|
|
|
6624
6873
|
}
|
|
6625
6874
|
let files;
|
|
6626
6875
|
try {
|
|
6627
|
-
files =
|
|
6876
|
+
files = readdirSync7(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
6628
6877
|
} catch {
|
|
6629
6878
|
return 0;
|
|
6630
6879
|
}
|
|
@@ -7384,7 +7633,7 @@ Managing ${displayName} API Key`);
|
|
|
7384
7633
|
};
|
|
7385
7634
|
|
|
7386
7635
|
// src/repl/custom-commands.ts
|
|
7387
|
-
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";
|
|
7388
7637
|
import { join as join11, extname as extname3 } from "path";
|
|
7389
7638
|
import { execSync as execSync6 } from "child_process";
|
|
7390
7639
|
function parseSimpleYaml(text) {
|
|
@@ -7454,7 +7703,7 @@ var CustomCommandManager = class {
|
|
|
7454
7703
|
return 0;
|
|
7455
7704
|
}
|
|
7456
7705
|
let count = 0;
|
|
7457
|
-
for (const file of
|
|
7706
|
+
for (const file of readdirSync8(this.commandsDir)) {
|
|
7458
7707
|
if (extname3(file) !== ".md") continue;
|
|
7459
7708
|
const cmd = parseCommandFile(join11(this.commandsDir, file));
|
|
7460
7709
|
if (cmd) {
|
|
@@ -8046,7 +8295,7 @@ var McpManager = class {
|
|
|
8046
8295
|
};
|
|
8047
8296
|
|
|
8048
8297
|
// src/skills/manager.ts
|
|
8049
|
-
import { existsSync as existsSync18, readdirSync as
|
|
8298
|
+
import { existsSync as existsSync18, readdirSync as readdirSync9, mkdirSync as mkdirSync11 } from "fs";
|
|
8050
8299
|
import { join as join13 } from "path";
|
|
8051
8300
|
|
|
8052
8301
|
// src/skills/types.ts
|
|
@@ -8121,7 +8370,7 @@ var SkillManager = class {
|
|
|
8121
8370
|
}
|
|
8122
8371
|
let entries;
|
|
8123
8372
|
try {
|
|
8124
|
-
entries =
|
|
8373
|
+
entries = readdirSync9(this.skillsDir);
|
|
8125
8374
|
} catch {
|
|
8126
8375
|
return 0;
|
|
8127
8376
|
}
|
|
@@ -8240,7 +8489,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
8240
8489
|
continue;
|
|
8241
8490
|
}
|
|
8242
8491
|
if (mime) {
|
|
8243
|
-
const fileSize =
|
|
8492
|
+
const fileSize = statSync8(absPath).size;
|
|
8244
8493
|
if (fileSize > MAX_IMAGE_BYTES) {
|
|
8245
8494
|
refs.push({ path: rawPath, type: "toolarge" });
|
|
8246
8495
|
continue;
|
|
@@ -8438,7 +8687,7 @@ var Repl = class {
|
|
|
8438
8687
|
if (depth > 2 || entryCount >= MAX_TREE_ENTRIES) return;
|
|
8439
8688
|
let entries;
|
|
8440
8689
|
try {
|
|
8441
|
-
entries =
|
|
8690
|
+
entries = readdirSync10(dir);
|
|
8442
8691
|
} catch {
|
|
8443
8692
|
return;
|
|
8444
8693
|
}
|
|
@@ -8450,7 +8699,7 @@ var Repl = class {
|
|
|
8450
8699
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
8451
8700
|
let isDir;
|
|
8452
8701
|
try {
|
|
8453
|
-
isDir =
|
|
8702
|
+
isDir = statSync8(fullPath).isDirectory();
|
|
8454
8703
|
} catch {
|
|
8455
8704
|
continue;
|
|
8456
8705
|
}
|
|
@@ -8472,7 +8721,7 @@ ${treeLines.join("\n")}`
|
|
|
8472
8721
|
if (totalChars >= MAX_TOTAL_CHARS) return;
|
|
8473
8722
|
let entries;
|
|
8474
8723
|
try {
|
|
8475
|
-
entries =
|
|
8724
|
+
entries = readdirSync10(dir);
|
|
8476
8725
|
} catch {
|
|
8477
8726
|
return;
|
|
8478
8727
|
}
|
|
@@ -8482,7 +8731,7 @@ ${treeLines.join("\n")}`
|
|
|
8482
8731
|
const fullPath = join14(dir, name);
|
|
8483
8732
|
let st;
|
|
8484
8733
|
try {
|
|
8485
|
-
st =
|
|
8734
|
+
st = statSync8(fullPath);
|
|
8486
8735
|
} catch {
|
|
8487
8736
|
continue;
|
|
8488
8737
|
}
|
|
@@ -8529,7 +8778,7 @@ ${content}
|
|
|
8529
8778
|
}
|
|
8530
8779
|
let isDir;
|
|
8531
8780
|
try {
|
|
8532
|
-
isDir =
|
|
8781
|
+
isDir = statSync8(absPath).isDirectory();
|
|
8533
8782
|
} catch {
|
|
8534
8783
|
return { success: false, charCount: 0, added: false, error: `Cannot access: ${dirPath}` };
|
|
8535
8784
|
}
|
|
@@ -9106,17 +9355,17 @@ Session '${this.resumeSessionId}' not found.
|
|
|
9106
9355
|
this.renderer.renderError(err);
|
|
9107
9356
|
} finally {
|
|
9108
9357
|
processing = false;
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9358
|
+
if (this.running) {
|
|
9359
|
+
rlAny.output = savedOutput;
|
|
9360
|
+
const rlInternal = this.rl;
|
|
9361
|
+
rlInternal.line = "";
|
|
9362
|
+
rlInternal.cursor = 0;
|
|
9363
|
+
rlInternal.paused = false;
|
|
9364
|
+
process.stdin.resume();
|
|
9365
|
+
this.showPrompt();
|
|
9366
|
+
} else {
|
|
9367
|
+
resolve5();
|
|
9368
|
+
}
|
|
9120
9369
|
}
|
|
9121
9370
|
});
|
|
9122
9371
|
this.rl.on("close", () => {
|
|
@@ -9374,6 +9623,12 @@ Session '${this.resumeSessionId}' not found.
|
|
|
9374
9623
|
return ["reload"];
|
|
9375
9624
|
case "mcp":
|
|
9376
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
|
+
}
|
|
9377
9632
|
case "compact":
|
|
9378
9633
|
case "review":
|
|
9379
9634
|
case "init":
|
|
@@ -9394,14 +9649,14 @@ Session '${this.resumeSessionId}' not found.
|
|
|
9394
9649
|
const prefix = normalized.includes("/") ? basename5(normalized) : normalized;
|
|
9395
9650
|
const absDir = resolve4(process.cwd(), dir);
|
|
9396
9651
|
if (!existsSync19(absDir)) return [];
|
|
9397
|
-
const entries =
|
|
9652
|
+
const entries = readdirSync10(absDir);
|
|
9398
9653
|
const results = [];
|
|
9399
9654
|
for (const entry of entries) {
|
|
9400
9655
|
if (entry.startsWith(".")) continue;
|
|
9401
9656
|
if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
|
|
9402
9657
|
try {
|
|
9403
9658
|
const fullPath = join14(absDir, entry);
|
|
9404
|
-
const stat =
|
|
9659
|
+
const stat = statSync8(fullPath);
|
|
9405
9660
|
const rel = dir === "." ? entry : `${dir}/${entry}`;
|
|
9406
9661
|
results.push(stat.isDirectory() ? `${rel}/` : rel);
|
|
9407
9662
|
} catch {
|
|
@@ -9931,6 +10186,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
9931
10186
|
addContextDir: (dirPath) => this.addExtraContextDir(dirPath),
|
|
9932
10187
|
removeContextDir: (dirPath) => this.removeExtraContextDir(dirPath),
|
|
9933
10188
|
listContextDirs: () => [...this.extraContextDirs],
|
|
10189
|
+
forkSession: (messageCount, title) => this.sessions.forkSession(messageCount, title),
|
|
9934
10190
|
exit: () => this.handleExit()
|
|
9935
10191
|
};
|
|
9936
10192
|
await cmd.execute(args, ctx);
|