jinzd-ai-cli 0.1.0 → 0.1.2
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 +213 -84
- package/package.json +11 -7
package/dist/index.js
CHANGED
|
@@ -100,7 +100,7 @@ var EnvLoader = class {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
// src/core/constants.ts
|
|
103
|
-
var VERSION = "0.1.
|
|
103
|
+
var VERSION = "0.1.2";
|
|
104
104
|
var CONFIG_DIR_NAME = ".aicli";
|
|
105
105
|
var CONFIG_FILE_NAME = "config.json";
|
|
106
106
|
var HISTORY_DIR_NAME = "history";
|
|
@@ -1188,13 +1188,65 @@ var SessionManager = class {
|
|
|
1188
1188
|
}
|
|
1189
1189
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
1190
1190
|
}
|
|
1191
|
+
/**
|
|
1192
|
+
* 跨 session 全文搜索。
|
|
1193
|
+
* 遍历所有历史 JSON 文件,逐条匹配消息内容(不区分大小写),
|
|
1194
|
+
* 每个 session 最多返回 3 条匹配片段,全局最多 maxResults 个 session。
|
|
1195
|
+
*/
|
|
1196
|
+
searchMessages(query, maxResults = 20) {
|
|
1197
|
+
if (!existsSync2(this.historyDir)) return [];
|
|
1198
|
+
const q = query.toLowerCase();
|
|
1199
|
+
const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join2(this.historyDir, f));
|
|
1200
|
+
const results = [];
|
|
1201
|
+
for (const filePath of files) {
|
|
1202
|
+
if (results.length >= maxResults) break;
|
|
1203
|
+
try {
|
|
1204
|
+
const data = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
1205
|
+
const messages = data.messages ?? [];
|
|
1206
|
+
const matches = [];
|
|
1207
|
+
for (const msg of messages) {
|
|
1208
|
+
if (matches.length >= 3) break;
|
|
1209
|
+
let text = "";
|
|
1210
|
+
if (typeof msg.content === "string") {
|
|
1211
|
+
text = msg.content;
|
|
1212
|
+
} else if (Array.isArray(msg.content)) {
|
|
1213
|
+
text = msg.content.filter((p) => p.type === "text").map((p) => p.text ?? "").join("");
|
|
1214
|
+
}
|
|
1215
|
+
const lowerText = text.toLowerCase();
|
|
1216
|
+
const idx = lowerText.indexOf(q);
|
|
1217
|
+
if (idx !== -1) {
|
|
1218
|
+
const start = Math.max(0, idx - 30);
|
|
1219
|
+
const end = Math.min(text.length, idx + query.length + 60);
|
|
1220
|
+
const snippet = (start > 0 ? "\u2026" : "") + text.slice(start, end).replace(/\n/g, " ") + (end < text.length ? "\u2026" : "");
|
|
1221
|
+
matches.push({ role: msg.role, snippet });
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
if (matches.length > 0) {
|
|
1225
|
+
results.push({
|
|
1226
|
+
sessionMeta: {
|
|
1227
|
+
id: data.id,
|
|
1228
|
+
provider: data.provider,
|
|
1229
|
+
model: data.model,
|
|
1230
|
+
messageCount: messages.length,
|
|
1231
|
+
created: new Date(data.created),
|
|
1232
|
+
updated: new Date(data.updated),
|
|
1233
|
+
title: data.title
|
|
1234
|
+
},
|
|
1235
|
+
matches
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
} catch {
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return results.sort((a, b) => b.sessionMeta.updated.getTime() - a.sessionMeta.updated.getTime());
|
|
1242
|
+
}
|
|
1191
1243
|
};
|
|
1192
1244
|
|
|
1193
1245
|
// src/repl/repl.ts
|
|
1194
1246
|
import * as readline from "readline";
|
|
1195
1247
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
1196
1248
|
import { join as join7, resolve as resolve3, extname as extname2 } from "path";
|
|
1197
|
-
import
|
|
1249
|
+
import chalk6 from "chalk";
|
|
1198
1250
|
|
|
1199
1251
|
// src/repl/renderer.ts
|
|
1200
1252
|
import chalk from "chalk";
|
|
@@ -1258,21 +1310,49 @@ var Renderer = class {
|
|
|
1258
1310
|
console.log();
|
|
1259
1311
|
}
|
|
1260
1312
|
printAbout() {
|
|
1313
|
+
const HR = chalk.dim(" " + "\u2500".repeat(56));
|
|
1314
|
+
const label = (s) => chalk.gray(` ${s.padEnd(6)}`);
|
|
1315
|
+
const tool = (name, desc) => chalk.cyan(` ${name.padEnd(22)}`) + chalk.dim(desc);
|
|
1316
|
+
const feat = (s) => chalk.dim(" \u2726 ") + chalk.white(s);
|
|
1261
1317
|
console.log();
|
|
1262
|
-
console.log(
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
console.log(
|
|
1266
|
-
console.log(
|
|
1267
|
-
console.log(
|
|
1268
|
-
console.log(
|
|
1269
|
-
console.log(chalk.gray(" \u652F\u6301\u7684 Provider\uFF1A"));
|
|
1318
|
+
console.log(
|
|
1319
|
+
chalk.bold.cyan(" \u{1F916} ai-cli") + chalk.gray(` v${VERSION}`) + chalk.dim(" \u2014 \u8DE8\u5E73\u53F0 REPL \u98CE\u683C AI \u5BF9\u8BDD\u5DE5\u5177")
|
|
1320
|
+
);
|
|
1321
|
+
console.log(HR);
|
|
1322
|
+
console.log(label("\u63CF\u8FF0") + chalk.white(DESCRIPTION));
|
|
1323
|
+
console.log(label("\u4F5C\u8005") + chalk.yellow(AUTHOR) + chalk.dim(" <" + AUTHOR_EMAIL + ">"));
|
|
1324
|
+
console.log(HR);
|
|
1325
|
+
console.log(chalk.gray(" \u652F\u6301\u7684 Provider\uFF086\u4E2A\uFF09\uFF1A"));
|
|
1270
1326
|
console.log(chalk.dim(" DeepSeek \xB7 Kimi (Moonshot) \xB7 Claude (Anthropic)"));
|
|
1271
1327
|
console.log(chalk.dim(" Gemini (Google) \xB7 \u667A\u8C31\u6E05\u8A00 \xB7 \u81EA\u5B9A\u4E49 OpenAI \u517C\u5BB9"));
|
|
1272
|
-
console.log(
|
|
1273
|
-
console.log(chalk.gray(" \u5DE5\u5177\
|
|
1274
|
-
console.log(
|
|
1275
|
-
console.log(
|
|
1328
|
+
console.log(HR);
|
|
1329
|
+
console.log(chalk.gray(" Agentic \u5DE5\u5177\uFF0810\u4E2A\uFF09\uFF1A"));
|
|
1330
|
+
console.log(tool("bash", "\u6267\u884C Shell \u547D\u4EE4\uFF08PowerShell/bash\uFF0CWindows \u5F3A\u5236 UTF-8\uFF09"));
|
|
1331
|
+
console.log(tool("read_file", "\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9"));
|
|
1332
|
+
console.log(tool("write_file", "\u5199\u5165\u6587\u4EF6\uFF08\u5371\u9669\u7EA7 write\uFF0C\u9700\u786E\u8BA4\uFF09"));
|
|
1333
|
+
console.log(tool("edit_file", "\u7CBE\u786E\u5B57\u7B26\u4E32\u66FF\u6362\u7F16\u8F91\u6587\u4EF6\uFF08\u9700\u786E\u8BA4\uFF09"));
|
|
1334
|
+
console.log(tool("list_dir", "\u5217\u51FA\u76EE\u5F55\u5185\u5BB9"));
|
|
1335
|
+
console.log(tool("grep_files", "\u6B63\u5219\u641C\u7D22\u6587\u4EF6\u5185\u5BB9"));
|
|
1336
|
+
console.log(tool("glob_files", "\u6309 glob \u6A21\u5F0F\u5339\u914D\u6587\u4EF6\u8DEF\u5F84"));
|
|
1337
|
+
console.log(tool("run_interactive", "\u8FD0\u884C\u9700\u8981 stdin \u4EA4\u4E92\u7684\u7A0B\u5E8F\uFF08spawn \u76F4\u8FDE\uFF09"));
|
|
1338
|
+
console.log(tool("web_fetch", "\u6293\u53D6\u7F51\u9875\u5185\u5BB9\uFF08\u8F6C Markdown\uFF09"));
|
|
1339
|
+
console.log(tool("save_last_response", "\u4FDD\u5B58 AI \u56DE\u7B54\u5230\u6587\u4EF6\uFF08tee \u6D41\u5F0F\u5199\u76D8\uFF09"));
|
|
1340
|
+
console.log(HR);
|
|
1341
|
+
console.log(chalk.gray(" REPL \u547D\u4EE4\uFF0814\u4E2A\uFF09\uFF1A"));
|
|
1342
|
+
console.log(chalk.dim(" /help /about /provider /model /clear /session"));
|
|
1343
|
+
console.log(chalk.dim(" /system /context /status /search /undo /export"));
|
|
1344
|
+
console.log(chalk.dim(" /tools /exit"));
|
|
1345
|
+
console.log(HR);
|
|
1346
|
+
console.log(chalk.gray(" \u4E3B\u8981\u7279\u6027\uFF1A"));
|
|
1347
|
+
console.log(feat("Agentic \u5FAA\u73AF\uFF08\u6700\u591A 20 \u8F6E\u5DE5\u5177\u8C03\u7528\uFF0C\u6700\u7EC8\u56DE\u7B54\u6D41\u5F0F\u8F93\u51FA\uFF09"));
|
|
1348
|
+
console.log(feat("\u591A\u6A21\u6001\u8F93\u5165\uFF1A@\u6587\u4EF6\u8DEF\u5F84 \u5185\u8054\u56FE\u7247\uFF08base64\uFF09\u6216\u6587\u672C\u5230\u6D88\u606F"));
|
|
1349
|
+
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"));
|
|
1350
|
+
console.log(feat("\u9879\u76EE\u4E0A\u4E0B\u6587\u6587\u4EF6\uFF1A\u81EA\u52A8\u52A0\u8F7D AICLI.md / CLAUDE.md"));
|
|
1351
|
+
console.log(feat("\u8DE8 session \u5386\u53F2\u5168\u6587\u641C\u7D22\uFF08/search <\u5173\u952E\u8BCD>\uFF09"));
|
|
1352
|
+
console.log(feat("\u6587\u4EF6\u64CD\u4F5C\u64A4\u9500\uFF08/undo\uFF0C\u652F\u6301 write_file / edit_file\uFF09"));
|
|
1353
|
+
console.log(feat("Thinking \u6A21\u5F0F\u6298\u53E0\uFF08<think> \u5757\u81EA\u52A8\u6298\u53E0\uFF0CGLM-5 \u7B49\uFF09"));
|
|
1354
|
+
console.log(feat("Token \u7528\u91CF\u8FFD\u8E2A\uFF08\u6BCF\u6B21\u56DE\u590D + session \u7D2F\u8BA1\uFF09"));
|
|
1355
|
+
console.log(feat("\u72EC\u7ACB\u53EF\u6267\u884C\u6587\u4EF6\u6253\u5305\uFF08~56MB\uFF0C\u65E0\u9700 Node.js \u73AF\u5883\uFF09"));
|
|
1276
1356
|
console.log();
|
|
1277
1357
|
}
|
|
1278
1358
|
printPrompt(provider, _model) {
|
|
@@ -1416,6 +1496,7 @@ Error: ${message}
|
|
|
1416
1496
|
// src/repl/commands/index.ts
|
|
1417
1497
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
1418
1498
|
import { resolve, dirname as dirname2 } from "path";
|
|
1499
|
+
import chalk2 from "chalk";
|
|
1419
1500
|
|
|
1420
1501
|
// src/tools/undo-stack.ts
|
|
1421
1502
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync3 } from "fs";
|
|
@@ -1524,6 +1605,7 @@ function createDefaultCommands() {
|
|
|
1524
1605
|
" /system <prompt> - Set system prompt",
|
|
1525
1606
|
" /context - Show or reload project context file",
|
|
1526
1607
|
" /status - Show current status",
|
|
1608
|
+
" /search <keyword> - Search across all session histories",
|
|
1527
1609
|
" /undo - Undo the last file write/edit operation",
|
|
1528
1610
|
" /export [md|json] [file] - Export session to file (default: auto-named .md)",
|
|
1529
1611
|
" /tools - List all AI tools available",
|
|
@@ -1849,6 +1931,40 @@ ${text}
|
|
|
1849
1931
|
console.log();
|
|
1850
1932
|
}
|
|
1851
1933
|
},
|
|
1934
|
+
{
|
|
1935
|
+
name: "search",
|
|
1936
|
+
description: "Search across all session histories",
|
|
1937
|
+
usage: "/search <keyword>",
|
|
1938
|
+
execute(args, ctx) {
|
|
1939
|
+
const query = args.join(" ").trim();
|
|
1940
|
+
if (!query) {
|
|
1941
|
+
ctx.renderer.renderError("Usage: /search <keyword>");
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
const results = ctx.sessions.searchMessages(query);
|
|
1945
|
+
if (results.length === 0) {
|
|
1946
|
+
ctx.renderer.printInfo(`No sessions found containing "${query}".`);
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
console.log(`
|
|
1950
|
+
${chalk2.bold(`Found ${results.length} session(s) containing "${query}"`)}
|
|
1951
|
+
`);
|
|
1952
|
+
for (const r of results) {
|
|
1953
|
+
const { sessionMeta, matches } = r;
|
|
1954
|
+
const dateStr = sessionMeta.updated.toLocaleDateString();
|
|
1955
|
+
console.log(
|
|
1956
|
+
` ${chalk2.cyan(sessionMeta.id.slice(0, 8))}` + chalk2.dim(` [${dateStr}] ${sessionMeta.provider} / ${sessionMeta.model}`) + (sessionMeta.title ? `
|
|
1957
|
+
${chalk2.dim(" " + sessionMeta.title)}` : "")
|
|
1958
|
+
);
|
|
1959
|
+
for (const m of matches) {
|
|
1960
|
+
const icon = m.role === "user" ? "\u{1F464}" : "\u{1F916}";
|
|
1961
|
+
console.log(` ${icon} ${chalk2.yellow(m.snippet)}`);
|
|
1962
|
+
}
|
|
1963
|
+
console.log();
|
|
1964
|
+
}
|
|
1965
|
+
console.log(chalk2.dim(" Use /session load <id> to resume a session.\n"));
|
|
1966
|
+
}
|
|
1967
|
+
},
|
|
1852
1968
|
{
|
|
1853
1969
|
name: "undo",
|
|
1854
1970
|
description: "Undo the last file write or edit operation",
|
|
@@ -1883,7 +1999,7 @@ ${text}
|
|
|
1883
1999
|
}
|
|
1884
2000
|
|
|
1885
2001
|
// src/repl/select-list.ts
|
|
1886
|
-
import
|
|
2002
|
+
import chalk3 from "chalk";
|
|
1887
2003
|
var CLEAR_LINE = "\r\x1B[2K";
|
|
1888
2004
|
var MOVE_UP = (n) => n > 0 ? `\x1B[${n}A` : "";
|
|
1889
2005
|
var IGNORE_ENTER_MS = 80;
|
|
@@ -1908,10 +2024,10 @@ function selectFromList(prompt, items, initialIndex = 0) {
|
|
|
1908
2024
|
handleSequence(seq);
|
|
1909
2025
|
};
|
|
1910
2026
|
const renderLine = (item, active, absIdx) => {
|
|
1911
|
-
const cursor = active ?
|
|
1912
|
-
const label = active ?
|
|
1913
|
-
const hint = item.hint ?
|
|
1914
|
-
const num =
|
|
2027
|
+
const cursor = active ? chalk3.cyan("\u276F ") : " ";
|
|
2028
|
+
const label = active ? chalk3.cyan(item.label) : item.label;
|
|
2029
|
+
const hint = item.hint ? chalk3.dim(" " + item.hint) : "";
|
|
2030
|
+
const num = chalk3.dim(String(absIdx + 1).padStart(2) + " ");
|
|
1915
2031
|
return cursor + num + label + hint;
|
|
1916
2032
|
};
|
|
1917
2033
|
const render = () => {
|
|
@@ -1920,16 +2036,16 @@ function selectFromList(prompt, items, initialIndex = 0) {
|
|
|
1920
2036
|
const windowEnd = Math.min(windowStart + PAGE, items.length);
|
|
1921
2037
|
const visible = items.slice(windowStart, windowEnd);
|
|
1922
2038
|
const lines = [];
|
|
1923
|
-
lines.push(
|
|
2039
|
+
lines.push(chalk3.bold(prompt));
|
|
1924
2040
|
if (windowStart > 0)
|
|
1925
|
-
lines.push(
|
|
2041
|
+
lines.push(chalk3.dim(` \u2191 ${windowStart} more above`));
|
|
1926
2042
|
visible.forEach(
|
|
1927
2043
|
(item, i) => lines.push(renderLine(item, windowStart + i === selected, windowStart + i))
|
|
1928
2044
|
);
|
|
1929
2045
|
const below = items.length - windowEnd;
|
|
1930
2046
|
if (below > 0)
|
|
1931
|
-
lines.push(
|
|
1932
|
-
lines.push(
|
|
2047
|
+
lines.push(chalk3.dim(` \u2193 ${below} more below`));
|
|
2048
|
+
lines.push(chalk3.dim(" \u2191\u2193 move \xB7 Enter select \xB7 Esc cancel"));
|
|
1933
2049
|
if (lastRenderedLines > 0) {
|
|
1934
2050
|
process.stdout.write(MOVE_UP(lastRenderedLines) + CLEAR_LINE);
|
|
1935
2051
|
for (let i = 1; i < lastRenderedLines; i++)
|
|
@@ -1962,7 +2078,7 @@ function selectFromList(prompt, items, initialIndex = 0) {
|
|
|
1962
2078
|
process.stdout.write(MOVE_UP(lastRenderedLines - 1));
|
|
1963
2079
|
}
|
|
1964
2080
|
if (result !== null) {
|
|
1965
|
-
process.stdout.write(
|
|
2081
|
+
process.stdout.write(chalk3.dim(` \u2714 ${result}
|
|
1966
2082
|
`));
|
|
1967
2083
|
}
|
|
1968
2084
|
resolve4(result);
|
|
@@ -2794,9 +2910,21 @@ var runInteractiveTool = {
|
|
|
2794
2910
|
async execute(args) {
|
|
2795
2911
|
const executable = String(args["executable"] ?? "").trim();
|
|
2796
2912
|
const rawArgs = args["args"];
|
|
2797
|
-
|
|
2913
|
+
let argsTypeWarning = "";
|
|
2914
|
+
const cmdArgs = Array.isArray(rawArgs) ? rawArgs.map(String) : typeof rawArgs === "string" && rawArgs.trim() ? (() => {
|
|
2915
|
+
argsTypeWarning = `[Warning] "args" should be an array but got string: ${JSON.stringify(rawArgs)}. Applied fallback.
|
|
2916
|
+
`;
|
|
2917
|
+
process.stderr.write(argsTypeWarning);
|
|
2918
|
+
return [rawArgs.trim()];
|
|
2919
|
+
})() : [];
|
|
2798
2920
|
const rawStdin = args["stdin_lines"];
|
|
2799
|
-
|
|
2921
|
+
let stdinTypeWarning = "";
|
|
2922
|
+
const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String) : typeof rawStdin === "string" && rawStdin.trim() ? (() => {
|
|
2923
|
+
stdinTypeWarning = `[Warning] "stdin_lines" should be an array but got string: ${JSON.stringify(rawStdin.slice(0, 80))}. Applied fallback.
|
|
2924
|
+
`;
|
|
2925
|
+
process.stderr.write(stdinTypeWarning);
|
|
2926
|
+
return rawStdin.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2927
|
+
})() : [];
|
|
2800
2928
|
const timeout = Number(args["timeout"] ?? 2e4);
|
|
2801
2929
|
if (!executable) {
|
|
2802
2930
|
throw new Error("executable is required");
|
|
@@ -2808,6 +2936,7 @@ var runInteractiveTool = {
|
|
|
2808
2936
|
PYTHONIOENCODING: "utf-8",
|
|
2809
2937
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
2810
2938
|
};
|
|
2939
|
+
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
2811
2940
|
return new Promise((resolve4) => {
|
|
2812
2941
|
const child = spawn(executable, cmdArgs.map(String), {
|
|
2813
2942
|
cwd: process.cwd(),
|
|
@@ -2838,23 +2967,23 @@ var runInteractiveTool = {
|
|
|
2838
2967
|
setTimeout(writeNextLine, 400);
|
|
2839
2968
|
const timer = setTimeout(() => {
|
|
2840
2969
|
child.kill();
|
|
2841
|
-
resolve4(
|
|
2970
|
+
resolve4(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
2842
2971
|
${buildOutput(stdout, stderr)}`);
|
|
2843
2972
|
}, timeout);
|
|
2844
2973
|
child.on("close", (code) => {
|
|
2845
2974
|
clearTimeout(timer);
|
|
2846
2975
|
const output = buildOutput(stdout, stderr);
|
|
2847
2976
|
if (code !== 0 && code !== null) {
|
|
2848
|
-
resolve4(
|
|
2977
|
+
resolve4(`${prefixWarnings}Exit code ${code}:
|
|
2849
2978
|
${output}`);
|
|
2850
2979
|
} else {
|
|
2851
|
-
resolve4(output || "(no output)");
|
|
2980
|
+
resolve4(`${prefixWarnings}${output || "(no output)"}`);
|
|
2852
2981
|
}
|
|
2853
2982
|
});
|
|
2854
2983
|
child.on("error", (err) => {
|
|
2855
2984
|
clearTimeout(timer);
|
|
2856
2985
|
resolve4(
|
|
2857
|
-
|
|
2986
|
+
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
2858
2987
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
2859
2988
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
2860
2989
|
);
|
|
@@ -3075,7 +3204,7 @@ var ToolRegistry = class {
|
|
|
3075
3204
|
};
|
|
3076
3205
|
|
|
3077
3206
|
// src/tools/executor.ts
|
|
3078
|
-
import
|
|
3207
|
+
import chalk5 from "chalk";
|
|
3079
3208
|
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
3080
3209
|
|
|
3081
3210
|
// src/tools/types.ts
|
|
@@ -3096,7 +3225,7 @@ function getDangerLevel(toolName, args) {
|
|
|
3096
3225
|
}
|
|
3097
3226
|
|
|
3098
3227
|
// src/tools/diff-utils.ts
|
|
3099
|
-
import
|
|
3228
|
+
import chalk4 from "chalk";
|
|
3100
3229
|
function renderDiff(oldText, newText, opts = {}) {
|
|
3101
3230
|
const contextLines = opts.contextLines ?? 3;
|
|
3102
3231
|
const maxLines = opts.maxLines ?? 120;
|
|
@@ -3105,21 +3234,21 @@ function renderDiff(oldText, newText, opts = {}) {
|
|
|
3105
3234
|
const newLines = newText.split("\n");
|
|
3106
3235
|
const hunks = computeHunks(oldLines, newLines, contextLines);
|
|
3107
3236
|
if (hunks.length === 0) {
|
|
3108
|
-
return
|
|
3237
|
+
return chalk4.dim(" (no changes)");
|
|
3109
3238
|
}
|
|
3110
3239
|
const output = [];
|
|
3111
3240
|
if (filePath) {
|
|
3112
|
-
output.push(
|
|
3113
|
-
output.push(
|
|
3241
|
+
output.push(chalk4.bold.white(`--- ${filePath} (before)`));
|
|
3242
|
+
output.push(chalk4.bold.white(`+++ ${filePath} (after)`));
|
|
3114
3243
|
}
|
|
3115
3244
|
let totalDisplayed = 0;
|
|
3116
3245
|
for (const hunk of hunks) {
|
|
3117
3246
|
if (totalDisplayed >= maxLines) {
|
|
3118
|
-
output.push(
|
|
3247
|
+
output.push(chalk4.dim(` ... (diff truncated, too many changes)`));
|
|
3119
3248
|
break;
|
|
3120
3249
|
}
|
|
3121
3250
|
output.push(
|
|
3122
|
-
|
|
3251
|
+
chalk4.cyan(
|
|
3123
3252
|
`@@ -${hunk.oldStart + 1},${hunk.oldCount} +${hunk.newStart + 1},${hunk.newCount} @@`
|
|
3124
3253
|
)
|
|
3125
3254
|
);
|
|
@@ -3127,11 +3256,11 @@ function renderDiff(oldText, newText, opts = {}) {
|
|
|
3127
3256
|
if (totalDisplayed >= maxLines) break;
|
|
3128
3257
|
totalDisplayed++;
|
|
3129
3258
|
if (line.type === "context") {
|
|
3130
|
-
output.push(
|
|
3259
|
+
output.push(chalk4.dim(` ${line.text}`));
|
|
3131
3260
|
} else if (line.type === "remove") {
|
|
3132
|
-
output.push(
|
|
3261
|
+
output.push(chalk4.red(`- ${line.text}`));
|
|
3133
3262
|
} else {
|
|
3134
|
-
output.push(
|
|
3263
|
+
output.push(chalk4.green(`+ ${line.text}`));
|
|
3135
3264
|
}
|
|
3136
3265
|
}
|
|
3137
3266
|
}
|
|
@@ -3344,9 +3473,9 @@ var ToolExecutor = class {
|
|
|
3344
3473
|
printToolCall(call) {
|
|
3345
3474
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
3346
3475
|
console.log();
|
|
3347
|
-
const icon = dangerLevel === "write" ?
|
|
3348
|
-
const roundBadge = this.totalRounds > 0 ?
|
|
3349
|
-
console.log(icon +
|
|
3476
|
+
const icon = dangerLevel === "write" ? chalk5.yellow("\u270E Tool: ") : chalk5.bold.cyan("\u2699 Tool: ");
|
|
3477
|
+
const roundBadge = this.totalRounds > 0 ? chalk5.dim(` [${this.round}/${this.totalRounds}]`) : "";
|
|
3478
|
+
console.log(icon + chalk5.white(call.name) + roundBadge);
|
|
3350
3479
|
for (const [key, val] of Object.entries(call.arguments)) {
|
|
3351
3480
|
let valStr;
|
|
3352
3481
|
if (Array.isArray(val)) {
|
|
@@ -3357,7 +3486,7 @@ var ToolExecutor = class {
|
|
|
3357
3486
|
} else {
|
|
3358
3487
|
valStr = String(val);
|
|
3359
3488
|
}
|
|
3360
|
-
console.log(
|
|
3489
|
+
console.log(chalk5.gray(` ${key}: `) + chalk5.white(valStr));
|
|
3361
3490
|
}
|
|
3362
3491
|
}
|
|
3363
3492
|
/**
|
|
@@ -3379,19 +3508,19 @@ var ToolExecutor = class {
|
|
|
3379
3508
|
return;
|
|
3380
3509
|
}
|
|
3381
3510
|
if (oldContent === newContent) {
|
|
3382
|
-
console.log(
|
|
3511
|
+
console.log(chalk5.dim(" (file content unchanged)"));
|
|
3383
3512
|
return;
|
|
3384
3513
|
}
|
|
3385
3514
|
const diff = renderDiff(oldContent, newContent, { filePath, contextLines: 3 });
|
|
3386
|
-
console.log(
|
|
3515
|
+
console.log(chalk5.dim(" \u2500\u2500 diff preview \u2500\u2500"));
|
|
3387
3516
|
console.log(diff);
|
|
3388
3517
|
console.log();
|
|
3389
3518
|
} else {
|
|
3390
3519
|
const lines = newContent.split("\n");
|
|
3391
|
-
const preview = lines.slice(0, 20).map((l) =>
|
|
3392
|
-
const more = lines.length > 20 ?
|
|
3520
|
+
const preview = lines.slice(0, 20).map((l) => chalk5.green(`+ ${l}`)).join("\n");
|
|
3521
|
+
const more = lines.length > 20 ? chalk5.dim(`
|
|
3393
3522
|
... (+${lines.length - 20} more lines)`) : "";
|
|
3394
|
-
console.log(
|
|
3523
|
+
console.log(chalk5.dim(" \u2500\u2500 new file preview \u2500\u2500"));
|
|
3395
3524
|
console.log(preview + more);
|
|
3396
3525
|
console.log();
|
|
3397
3526
|
}
|
|
@@ -3406,17 +3535,17 @@ var ToolExecutor = class {
|
|
|
3406
3535
|
String(newStr ?? ""),
|
|
3407
3536
|
{ filePath, contextLines: 2 }
|
|
3408
3537
|
);
|
|
3409
|
-
console.log(
|
|
3538
|
+
console.log(chalk5.dim(" \u2500\u2500 diff preview \u2500\u2500"));
|
|
3410
3539
|
console.log(diff);
|
|
3411
3540
|
console.log();
|
|
3412
3541
|
} else if (call.arguments["insert_after_line"] !== void 0) {
|
|
3413
3542
|
const line = Number(call.arguments["insert_after_line"]);
|
|
3414
3543
|
const insertContent = String(call.arguments["insert_content"] ?? "");
|
|
3415
3544
|
const insertLines = insertContent.split("\n");
|
|
3416
|
-
const preview = insertLines.slice(0, 5).map((l) =>
|
|
3417
|
-
const more = insertLines.length > 5 ?
|
|
3545
|
+
const preview = insertLines.slice(0, 5).map((l) => chalk5.green(`+ ${l}`)).join("\n");
|
|
3546
|
+
const more = insertLines.length > 5 ? chalk5.dim(`
|
|
3418
3547
|
... (+${insertLines.length - 5} more lines)`) : "";
|
|
3419
|
-
console.log(
|
|
3548
|
+
console.log(chalk5.dim(` \u2500\u2500 insert after line ${line} \u2500\u2500`));
|
|
3420
3549
|
console.log(preview + more);
|
|
3421
3550
|
console.log();
|
|
3422
3551
|
} else if (call.arguments["delete_from_line"] !== void 0) {
|
|
@@ -3430,10 +3559,10 @@ var ToolExecutor = class {
|
|
|
3430
3559
|
}
|
|
3431
3560
|
const fileLines = fileContent.split("\n");
|
|
3432
3561
|
const deleted = fileLines.slice(from - 1, to);
|
|
3433
|
-
const preview = deleted.slice(0, 5).map((l) =>
|
|
3434
|
-
const more = deleted.length > 5 ?
|
|
3562
|
+
const preview = deleted.slice(0, 5).map((l) => chalk5.red(`- ${l}`)).join("\n");
|
|
3563
|
+
const more = deleted.length > 5 ? chalk5.dim(`
|
|
3435
3564
|
... (-${deleted.length - 5} more lines)`) : "";
|
|
3436
|
-
console.log(
|
|
3565
|
+
console.log(chalk5.dim(` \u2500\u2500 delete lines ${from}\u2013${to} \u2500\u2500`));
|
|
3437
3566
|
console.log(preview + more);
|
|
3438
3567
|
console.log();
|
|
3439
3568
|
}
|
|
@@ -3441,27 +3570,27 @@ var ToolExecutor = class {
|
|
|
3441
3570
|
}
|
|
3442
3571
|
printToolResult(name, content, isError, wasTruncated) {
|
|
3443
3572
|
if (isError) {
|
|
3444
|
-
console.log(
|
|
3573
|
+
console.log(chalk5.red(`\u26A0 ${name} error: `) + chalk5.gray(content.slice(0, 300)));
|
|
3445
3574
|
} else {
|
|
3446
3575
|
const lines = content.split("\n");
|
|
3447
3576
|
const maxLines = name === "run_interactive" ? 40 : 8;
|
|
3448
3577
|
const preview = lines.slice(0, maxLines).join("\n");
|
|
3449
|
-
const moreLines = lines.length > maxLines ?
|
|
3578
|
+
const moreLines = lines.length > maxLines ? chalk5.gray(`
|
|
3450
3579
|
... (${lines.length - maxLines} more lines)`) : "";
|
|
3451
|
-
const truncatedNote = wasTruncated ?
|
|
3580
|
+
const truncatedNote = wasTruncated ? chalk5.yellow(`
|
|
3452
3581
|
\u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS} \u5B57\u7B26\u540E\u8FD4\u56DE\u7ED9 AI`) : "";
|
|
3453
|
-
console.log(
|
|
3582
|
+
console.log(chalk5.green("\u2713 Result: ") + chalk5.gray(preview) + moreLines + truncatedNote);
|
|
3454
3583
|
}
|
|
3455
3584
|
console.log();
|
|
3456
3585
|
}
|
|
3457
3586
|
confirm(call, level) {
|
|
3458
|
-
const color = level === "destructive" ?
|
|
3587
|
+
const color = level === "destructive" ? chalk5.red : chalk5.yellow;
|
|
3459
3588
|
const label = level === "destructive" ? "\u26A0 DESTRUCTIVE" : "\u270E Write";
|
|
3460
3589
|
console.log();
|
|
3461
|
-
console.log(color(`${label} operation: `) +
|
|
3590
|
+
console.log(color(`${label} operation: `) + chalk5.bold(call.name));
|
|
3462
3591
|
for (const [key, val] of Object.entries(call.arguments)) {
|
|
3463
3592
|
const valStr = typeof val === "string" && val.length > 200 ? val.slice(0, 200) + "..." : String(val);
|
|
3464
|
-
console.log(
|
|
3593
|
+
console.log(chalk5.gray(` ${key}: `) + valStr);
|
|
3465
3594
|
}
|
|
3466
3595
|
if (!this.rl) {
|
|
3467
3596
|
process.stdout.write(color("No readline: auto-rejected.\n"));
|
|
@@ -3485,7 +3614,7 @@ var ToolExecutor = class {
|
|
|
3485
3614
|
};
|
|
3486
3615
|
const onLine = (line) => cleanup(line.trim().toLowerCase());
|
|
3487
3616
|
this.cancelConfirmFn = () => {
|
|
3488
|
-
process.stdout.write(
|
|
3617
|
+
process.stdout.write(chalk5.gray("\n(cancelled)\n"));
|
|
3489
3618
|
cleanup("n");
|
|
3490
3619
|
};
|
|
3491
3620
|
rl.once("line", onLine);
|
|
@@ -3740,7 +3869,7 @@ ${this.activeSystemPrompt}`;
|
|
|
3740
3869
|
return dateTimeInfo;
|
|
3741
3870
|
}
|
|
3742
3871
|
refreshPrompt() {
|
|
3743
|
-
const promptStr =
|
|
3872
|
+
const promptStr = chalk6.green(`[${this.currentProvider}]`) + chalk6.white(" > ");
|
|
3744
3873
|
this.rl.setPrompt(promptStr);
|
|
3745
3874
|
}
|
|
3746
3875
|
showPrompt() {
|
|
@@ -3771,14 +3900,14 @@ ${this.activeSystemPrompt}`;
|
|
|
3771
3900
|
this.renderer.printWelcome(this.currentProvider, this.currentModel, welcomeModelInfo?.contextWindow);
|
|
3772
3901
|
if (ctx) {
|
|
3773
3902
|
process.stdout.write(
|
|
3774
|
-
|
|
3903
|
+
chalk6.dim(` \u{1F4C4} Project context loaded: ${ctx.filePath} (${ctx.content.length} chars)
|
|
3775
3904
|
`)
|
|
3776
3905
|
);
|
|
3777
3906
|
}
|
|
3778
3907
|
if (gitCtx) {
|
|
3779
|
-
const statusSummary = gitCtx.stagedFiles.length + gitCtx.changedFiles.length > 0 ?
|
|
3908
|
+
const statusSummary = gitCtx.stagedFiles.length + gitCtx.changedFiles.length > 0 ? chalk6.yellow(` (${gitCtx.stagedFiles.length} staged, ${gitCtx.changedFiles.length} modified)`) : chalk6.dim(" (clean)");
|
|
3780
3909
|
process.stdout.write(
|
|
3781
|
-
|
|
3910
|
+
chalk6.dim(` \u{1F500} Git branch: `) + chalk6.cyan(gitCtx.branch) + statusSummary + "\n"
|
|
3782
3911
|
);
|
|
3783
3912
|
}
|
|
3784
3913
|
this.rl.on("SIGINT", () => {
|
|
@@ -3839,13 +3968,13 @@ ${this.activeSystemPrompt}`;
|
|
|
3839
3968
|
const { parts, hasImage, refs } = parseAtReferences(userInput, process.cwd());
|
|
3840
3969
|
for (const ref of refs) {
|
|
3841
3970
|
if (ref.type === "notfound") {
|
|
3842
|
-
process.stdout.write(
|
|
3971
|
+
process.stdout.write(chalk6.yellow(` \u26A0 File not found: ${ref.path}
|
|
3843
3972
|
`));
|
|
3844
3973
|
} else if (ref.type === "image") {
|
|
3845
|
-
process.stdout.write(
|
|
3974
|
+
process.stdout.write(chalk6.dim(` \u{1F4CE} Image: ${ref.path}
|
|
3846
3975
|
`));
|
|
3847
3976
|
} else {
|
|
3848
|
-
process.stdout.write(
|
|
3977
|
+
process.stdout.write(chalk6.dim(` \u{1F4C4} File: ${ref.path}
|
|
3849
3978
|
`));
|
|
3850
3979
|
}
|
|
3851
3980
|
}
|
|
@@ -3854,13 +3983,13 @@ ${this.activeSystemPrompt}`;
|
|
|
3854
3983
|
const visionHint = this.getVisionModelHint();
|
|
3855
3984
|
if (visionHint) {
|
|
3856
3985
|
process.stdout.write(
|
|
3857
|
-
|
|
3986
|
+
chalk6.yellow(` \u2716 Vision not supported \u2013 ${visionHint}
|
|
3858
3987
|
`)
|
|
3859
3988
|
);
|
|
3860
3989
|
return;
|
|
3861
3990
|
}
|
|
3862
3991
|
process.stdout.write(
|
|
3863
|
-
|
|
3992
|
+
chalk6.dim(` \u{1F5BC} Vision request \u2013 sending image to ${this.currentProvider}
|
|
3864
3993
|
`)
|
|
3865
3994
|
);
|
|
3866
3995
|
}
|
|
@@ -4171,14 +4300,14 @@ ${this.activeSystemPrompt}`;
|
|
|
4171
4300
|
this.events.emit("session.end", { sessionId });
|
|
4172
4301
|
}
|
|
4173
4302
|
this.rl.close();
|
|
4174
|
-
console.log(
|
|
4303
|
+
console.log(chalk6.gray("\nGoodbye!"));
|
|
4175
4304
|
process.exit(0);
|
|
4176
4305
|
}
|
|
4177
4306
|
};
|
|
4178
4307
|
|
|
4179
4308
|
// src/repl/setup-wizard.ts
|
|
4180
4309
|
import { password, select } from "@inquirer/prompts";
|
|
4181
|
-
import
|
|
4310
|
+
import chalk7 from "chalk";
|
|
4182
4311
|
var PROVIDERS = [
|
|
4183
4312
|
{ value: "claude", name: "Claude (Anthropic)" },
|
|
4184
4313
|
{ value: "gemini", name: "Gemini (Google)" },
|
|
@@ -4195,7 +4324,7 @@ var SetupWizard = class {
|
|
|
4195
4324
|
this.config = config;
|
|
4196
4325
|
}
|
|
4197
4326
|
async runFirstRun() {
|
|
4198
|
-
console.log(
|
|
4327
|
+
console.log(chalk7.bold.cyan("\nWelcome to ai-cli!\n"));
|
|
4199
4328
|
console.log("Let's set up your first AI provider.\n");
|
|
4200
4329
|
try {
|
|
4201
4330
|
const providerId = await select({
|
|
@@ -4205,14 +4334,14 @@ var SetupWizard = class {
|
|
|
4205
4334
|
await this.setupProvider(providerId);
|
|
4206
4335
|
this.config.set("defaultProvider", providerId);
|
|
4207
4336
|
this.config.save();
|
|
4208
|
-
console.log(
|
|
4337
|
+
console.log(chalk7.green("\nSetup complete! Starting ai-cli...\n"));
|
|
4209
4338
|
return true;
|
|
4210
4339
|
} catch {
|
|
4211
4340
|
return false;
|
|
4212
4341
|
}
|
|
4213
4342
|
}
|
|
4214
4343
|
async runFull() {
|
|
4215
|
-
console.log(
|
|
4344
|
+
console.log(chalk7.bold.cyan("\nai-cli Configuration\n"));
|
|
4216
4345
|
let running = true;
|
|
4217
4346
|
while (running) {
|
|
4218
4347
|
const action = await select({
|
|
@@ -4226,7 +4355,7 @@ var SetupWizard = class {
|
|
|
4226
4355
|
if (action === "apikey") {
|
|
4227
4356
|
const choicesWithStatus = PROVIDERS.map((p) => {
|
|
4228
4357
|
const existingKey = this.config.getApiKey(p.value);
|
|
4229
|
-
const status = existingKey ?
|
|
4358
|
+
const status = existingKey ? chalk7.green(`[${maskKey(existingKey)}]`) : chalk7.gray("[\u672A\u914D\u7F6E]");
|
|
4230
4359
|
return { value: p.value, name: `${p.name} ${status}` };
|
|
4231
4360
|
});
|
|
4232
4361
|
const providerId = await select({
|
|
@@ -4241,7 +4370,7 @@ var SetupWizard = class {
|
|
|
4241
4370
|
});
|
|
4242
4371
|
this.config.set("defaultProvider", providerId);
|
|
4243
4372
|
this.config.save();
|
|
4244
|
-
console.log(
|
|
4373
|
+
console.log(chalk7.green(`Default provider set to: ${providerId}
|
|
4245
4374
|
`));
|
|
4246
4375
|
} else {
|
|
4247
4376
|
running = false;
|
|
@@ -4255,9 +4384,9 @@ var SetupWizard = class {
|
|
|
4255
4384
|
console.log(`
|
|
4256
4385
|
Managing ${displayName} API Key`);
|
|
4257
4386
|
if (existingKey) {
|
|
4258
|
-
console.log(
|
|
4387
|
+
console.log(chalk7.gray(` Current: ${maskKey(existingKey)}`));
|
|
4259
4388
|
} else {
|
|
4260
|
-
console.log(
|
|
4389
|
+
console.log(chalk7.gray(" Current: (\u672A\u914D\u7F6E)"));
|
|
4261
4390
|
}
|
|
4262
4391
|
const choices = existingKey ? [
|
|
4263
4392
|
{ value: "keep", name: "\u4FDD\u6301\u4E0D\u53D8 (keep current key)" },
|
|
@@ -4269,7 +4398,7 @@ Managing ${displayName} API Key`);
|
|
|
4269
4398
|
choices
|
|
4270
4399
|
});
|
|
4271
4400
|
if (action === "show" && existingKey) {
|
|
4272
|
-
console.log(
|
|
4401
|
+
console.log(chalk7.yellow(`
|
|
4273
4402
|
\u5B8C\u6574 Key: ${existingKey}
|
|
4274
4403
|
`));
|
|
4275
4404
|
const updateAfterShow = await select({
|
|
@@ -4288,7 +4417,7 @@ Managing ${displayName} API Key`);
|
|
|
4288
4417
|
validate: (val) => val.length > 0 || "API key cannot be empty"
|
|
4289
4418
|
});
|
|
4290
4419
|
this.config.setApiKey(providerId, newKey);
|
|
4291
|
-
console.log(
|
|
4420
|
+
console.log(chalk7.green(`API key saved for ${displayName}: ${maskKey(newKey)}
|
|
4292
4421
|
`));
|
|
4293
4422
|
}
|
|
4294
4423
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jinzd-ai-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Cross-platform REPL-style AI CLI with multi-provider support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"aicli": "
|
|
8
|
+
"aicli": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public",
|
|
@@ -23,16 +23,20 @@
|
|
|
23
23
|
"test:watch": "vitest",
|
|
24
24
|
"lint": "eslint src",
|
|
25
25
|
"prepublishOnly": "npm run build",
|
|
26
|
-
"pack:win":
|
|
27
|
-
"pack:mac":
|
|
26
|
+
"pack:win": "npm run build && npx pkg dist-cjs/index.cjs --target node22-win-x64 --output release/ai-cli-win.exe --compress GZip --options no-deprecation",
|
|
27
|
+
"pack:mac": "npm run build && npx pkg dist-cjs/index.cjs --target node22-macos-arm64 --output release/ai-cli-mac --compress GZip --options no-deprecation",
|
|
28
28
|
"pack:mac-x64": "npm run build && npx pkg dist-cjs/index.cjs --target node22-macos-x64 --output release/ai-cli-mac-x64 --compress GZip --options no-deprecation",
|
|
29
|
-
"pack:linux":
|
|
30
|
-
"pack:all":
|
|
29
|
+
"pack:linux": "npm run build && npx pkg dist-cjs/index.cjs --target node22-linux-x64 --output release/ai-cli-linux --compress GZip --options no-deprecation",
|
|
30
|
+
"pack:all": "npm run build && npx pkg dist-cjs/index.cjs --target node22-win-x64,node22-macos-arm64,node22-linux-x64 --out-path release --compress GZip --options no-deprecation"
|
|
31
31
|
},
|
|
32
32
|
"pkg": {
|
|
33
33
|
"scripts": "dist-cjs/index.cjs",
|
|
34
34
|
"assets": [],
|
|
35
|
-
"targets": [
|
|
35
|
+
"targets": [
|
|
36
|
+
"node22-win-x64",
|
|
37
|
+
"node22-macos-arm64",
|
|
38
|
+
"node22-linux-x64"
|
|
39
|
+
],
|
|
36
40
|
"outputPath": "release",
|
|
37
41
|
"options": "no-deprecation"
|
|
38
42
|
},
|