jinzd-ai-cli 0.1.30 → 0.1.32

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.
Files changed (2) hide show
  1. package/dist/index.js +231 -89
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1663,8 +1663,8 @@ var SessionManager = class {
1663
1663
 
1664
1664
  // src/repl/repl.ts
1665
1665
  import * as readline from "readline";
1666
- import { existsSync as existsSync18, readFileSync as readFileSync13, readdirSync as readdirSync9, statSync as statSync6 } from "fs";
1667
- import { join as join13, resolve as resolve4, extname as extname4, dirname as dirname5, basename as basename5 } from "path";
1666
+ import { existsSync as existsSync19, readFileSync as readFileSync13, readdirSync as readdirSync9, statSync as statSync7 } from "fs";
1667
+ import { join as join14, resolve as resolve4, extname as extname4, dirname as dirname5, basename as basename5 } from "path";
1668
1668
  import chalk10 from "chalk";
1669
1669
 
1670
1670
  // src/repl/renderer.ts
@@ -1982,10 +1982,10 @@ Error: ${message}
1982
1982
  };
1983
1983
 
1984
1984
  // src/repl/commands/index.ts
1985
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync } from "fs";
1986
- import { execSync as execSync2 } from "child_process";
1985
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1986
+ import { execSync as execSync3 } from "child_process";
1987
1987
  import { platform } from "os";
1988
- import { resolve, dirname as dirname2, join as join4 } from "path";
1988
+ import { resolve, dirname as dirname2, join as join5, basename } from "path";
1989
1989
  import chalk2 from "chalk";
1990
1990
 
1991
1991
  // src/tools/git-context.ts
@@ -2148,6 +2148,93 @@ var UndoStack = class {
2148
2148
  };
2149
2149
  var undoStack = new UndoStack();
2150
2150
 
2151
+ // src/repl/clipboard.ts
2152
+ import { execSync as execSync2 } from "child_process";
2153
+ import { existsSync as existsSync5, statSync, unlinkSync as unlinkSync2 } from "fs";
2154
+ import { tmpdir } from "os";
2155
+ import { join as join4 } from "path";
2156
+ var CLIPBOARD_TIMEOUT = 5e3;
2157
+ function readClipboardImage() {
2158
+ const outPath = join4(tmpdir(), `aicli-paste-${Date.now()}.png`);
2159
+ try {
2160
+ if (process.platform === "win32") {
2161
+ _readWin32(outPath);
2162
+ } else if (process.platform === "darwin") {
2163
+ _readDarwin(outPath);
2164
+ } else {
2165
+ _readLinux(outPath);
2166
+ }
2167
+ } catch {
2168
+ _cleanup(outPath);
2169
+ return null;
2170
+ }
2171
+ if (!existsSync5(outPath) || statSync(outPath).size === 0) {
2172
+ _cleanup(outPath);
2173
+ return null;
2174
+ }
2175
+ return outPath;
2176
+ }
2177
+ function _readWin32(outPath) {
2178
+ const escapedPath = outPath.replace(/\\/g, "\\\\");
2179
+ const script = [
2180
+ "Add-Type -AssemblyName System.Windows.Forms",
2181
+ "Add-Type -AssemblyName System.Drawing",
2182
+ "$img = [System.Windows.Forms.Clipboard]::GetImage()",
2183
+ `if ($null -ne $img) { $img.Save('${escapedPath}', [System.Drawing.Imaging.ImageFormat]::Png); exit 0 } else { exit 1 }`
2184
+ ].join("; ");
2185
+ execSync2(`powershell -NoProfile -NonInteractive -Command "${script}"`, {
2186
+ stdio: "pipe",
2187
+ timeout: CLIPBOARD_TIMEOUT
2188
+ });
2189
+ }
2190
+ function _readDarwin(outPath) {
2191
+ try {
2192
+ execSync2(`pngpaste "${outPath}"`, { stdio: "pipe", timeout: CLIPBOARD_TIMEOUT });
2193
+ return;
2194
+ } catch {
2195
+ }
2196
+ const script = [
2197
+ "try",
2198
+ " set d to (the clipboard as \xABclass PNGf\xBB)",
2199
+ ` set fp to open for access POSIX file "${outPath}" with write permission`,
2200
+ " write d to fp",
2201
+ " close access fp",
2202
+ "end try"
2203
+ ].join("\n");
2204
+ execSync2(`osascript -e '${script}'`, { stdio: "pipe", timeout: CLIPBOARD_TIMEOUT });
2205
+ }
2206
+ function _readLinux(outPath) {
2207
+ try {
2208
+ execSync2(`xclip -selection clipboard -t image/png -o > "${outPath}"`, {
2209
+ shell: "/bin/sh",
2210
+ stdio: "pipe",
2211
+ timeout: CLIPBOARD_TIMEOUT
2212
+ });
2213
+ return;
2214
+ } catch {
2215
+ }
2216
+ execSync2(`wl-paste --type image/png > "${outPath}"`, {
2217
+ shell: "/bin/sh",
2218
+ stdio: "pipe",
2219
+ timeout: CLIPBOARD_TIMEOUT
2220
+ });
2221
+ }
2222
+ function _cleanup(path) {
2223
+ try {
2224
+ if (existsSync5(path)) unlinkSync2(path);
2225
+ } catch {
2226
+ }
2227
+ }
2228
+ function getClipboardHint() {
2229
+ if (process.platform === "darwin") {
2230
+ return "\u5982 pngpaste \u672A\u5B89\u88C5\uFF0C\u8BF7\u8FD0\u884C\uFF1Abrew install pngpaste";
2231
+ }
2232
+ if (process.platform === "linux") {
2233
+ return "\u8BF7\u5B89\u88C5 xclip\uFF08X11\uFF09\uFF1Asudo apt install xclip\n\u6216 wl-paste\uFF08Wayland\uFF09\uFF1Asudo apt install wl-clipboard";
2234
+ }
2235
+ return "";
2236
+ }
2237
+
2151
2238
  // src/repl/commands/index.ts
2152
2239
  function fmtCtx(tokens) {
2153
2240
  if (tokens >= 1e6) return `${Math.round(tokens / 1e5) / 10}M`;
@@ -2195,19 +2282,19 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
2195
2282
  }
2196
2283
  const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
2197
2284
  const sorted = filtered.sort((a, b) => {
2198
- const aIsDir = statSync(join4(d, a)).isDirectory();
2199
- const bIsDir = statSync(join4(d, b)).isDirectory();
2285
+ const aIsDir = statSync2(join5(d, a)).isDirectory();
2286
+ const bIsDir = statSync2(join5(d, b)).isDirectory();
2200
2287
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
2201
2288
  return a.localeCompare(b);
2202
2289
  });
2203
2290
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
2204
2291
  const name = sorted[i];
2205
- const fullPath = join4(d, name);
2292
+ const fullPath = join5(d, name);
2206
2293
  const isLast = i === sorted.length - 1;
2207
2294
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
2208
2295
  let isDir;
2209
2296
  try {
2210
- isDir = statSync(fullPath).isDirectory();
2297
+ isDir = statSync2(fullPath).isDirectory();
2211
2298
  } catch {
2212
2299
  continue;
2213
2300
  }
@@ -2229,7 +2316,7 @@ function scanProject(cwd) {
2229
2316
  configFiles: [],
2230
2317
  directoryStructure: ""
2231
2318
  };
2232
- const check = (file) => existsSync5(join4(cwd, file));
2319
+ const check = (file) => existsSync6(join5(cwd, file));
2233
2320
  const configCandidates = [
2234
2321
  "package.json",
2235
2322
  "tsconfig.json",
@@ -2256,7 +2343,7 @@ function scanProject(cwd) {
2256
2343
  info.type = "node";
2257
2344
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
2258
2345
  try {
2259
- const pkg = JSON.parse(readFileSync4(join4(cwd, "package.json"), "utf-8"));
2346
+ const pkg = JSON.parse(readFileSync4(join5(cwd, "package.json"), "utf-8"));
2260
2347
  const scripts = pkg.scripts ?? {};
2261
2348
  info.buildCommand = scripts.build ? `npm run build` : void 0;
2262
2349
  info.testCommand = scripts.test ? `npm test` : void 0;
@@ -2337,14 +2424,14 @@ ${info.directoryStructure}
2337
2424
  function copyToClipboard(text) {
2338
2425
  const plat = platform();
2339
2426
  if (plat === "win32") {
2340
- execSync2("clip", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2427
+ execSync3("clip", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2341
2428
  } else if (plat === "darwin") {
2342
- execSync2("pbcopy", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2429
+ execSync3("pbcopy", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2343
2430
  } else {
2344
2431
  try {
2345
- execSync2("xclip -selection clipboard", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2432
+ execSync3("xclip -selection clipboard", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2346
2433
  } catch {
2347
- execSync2("xsel --clipboard --input", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2434
+ execSync3("xsel --clipboard --input", { input: text, stdio: ["pipe", "ignore", "ignore"] });
2348
2435
  }
2349
2436
  }
2350
2437
  }
@@ -3045,9 +3132,9 @@ ${text}
3045
3132
  const cwd = process.cwd();
3046
3133
  const gitRoot = getGitRoot(cwd);
3047
3134
  const targetDir = gitRoot ?? cwd;
3048
- const targetPath = join4(targetDir, "AICLI.md");
3135
+ const targetPath = join5(targetDir, "AICLI.md");
3049
3136
  const force = args.includes("--force");
3050
- if (existsSync5(targetPath) && !force) {
3137
+ if (existsSync6(targetPath) && !force) {
3051
3138
  ctx.renderer.printInfo(`AICLI.md already exists at ${targetPath}`);
3052
3139
  ctx.renderer.printInfo("Use /init --force to overwrite, or edit it manually.");
3053
3140
  return;
@@ -3094,6 +3181,25 @@ ${text}
3094
3181
  }
3095
3182
  }
3096
3183
  },
3184
+ {
3185
+ name: "paste",
3186
+ description: "Read image from clipboard and send with optional description",
3187
+ usage: "/paste [description]",
3188
+ async execute(args, ctx) {
3189
+ const imgPath = readClipboardImage();
3190
+ if (!imgPath) {
3191
+ const hint = getClipboardHint();
3192
+ ctx.renderer.renderError(
3193
+ "\u526A\u8D34\u677F\u4E2D\u6CA1\u6709\u56FE\u7247\u3002\u8BF7\u5148\u5728\u5176\u4ED6\u7A0B\u5E8F\u4E2D\u590D\u5236\u4E00\u5F20\u56FE\u7247\uFF0C\u518D\u8FD0\u884C /paste\u3002" + (hint ? `
3194
+ ${hint}` : "")
3195
+ );
3196
+ return;
3197
+ }
3198
+ const description = args.join(" ").trim() || "\u8BF7\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247";
3199
+ ctx.renderer.printSuccess(`\u{1F4CB} \u56FE\u7247\u5DF2\u8BFB\u53D6\uFF1A${basename(imgPath)}`);
3200
+ await ctx.sendAsChat(`@${imgPath} ${description}`);
3201
+ }
3202
+ },
3097
3203
  {
3098
3204
  name: "cost",
3099
3205
  description: "Show session token usage summary",
@@ -3208,7 +3314,7 @@ ${text}
3208
3314
  let diff;
3209
3315
  try {
3210
3316
  const cmd = staged ? "git diff --staged" : "git diff";
3211
- diff = execSync2(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
3317
+ diff = execSync3(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
3212
3318
  } catch {
3213
3319
  ctx.renderer.renderError("Failed to run git diff.");
3214
3320
  return;
@@ -3498,8 +3604,8 @@ function selectFromList(prompt, items, initialIndex = 0) {
3498
3604
  }
3499
3605
 
3500
3606
  // src/tools/builtin/bash.ts
3501
- import { execSync as execSync3 } from "child_process";
3502
- import { existsSync as existsSync6 } from "fs";
3607
+ import { execSync as execSync4 } from "child_process";
3608
+ import { existsSync as existsSync7 } from "fs";
3503
3609
  import { platform as platform2 } from "os";
3504
3610
  import { resolve as resolve2 } from "path";
3505
3611
  var IS_WINDOWS = platform2() === "win32";
@@ -3548,7 +3654,7 @@ var bashTool = {
3548
3654
  let effectiveCwd = persistentCwd;
3549
3655
  if (cwdArg) {
3550
3656
  const resolved = resolve2(persistentCwd, cwdArg);
3551
- if (!existsSync6(resolved)) {
3657
+ if (!existsSync7(resolved)) {
3552
3658
  throw new Error(
3553
3659
  `cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
3554
3660
  );
@@ -3564,7 +3670,7 @@ var bashTool = {
3564
3670
  actualCommand = command;
3565
3671
  }
3566
3672
  try {
3567
- const output = execSync3(actualCommand, {
3673
+ const output = execSync4(actualCommand, {
3568
3674
  timeout,
3569
3675
  encoding: IS_WINDOWS ? "buffer" : "utf-8",
3570
3676
  stdio: ["pipe", "pipe", "pipe"],
@@ -3620,7 +3726,7 @@ function updateCwdFromCommand(command, baseCwd) {
3620
3726
  if (!target || target.startsWith("$") || target === "~") return;
3621
3727
  try {
3622
3728
  const newDir = resolve2(baseCwd, target);
3623
- if (existsSync6(newDir)) {
3729
+ if (existsSync7(newDir)) {
3624
3730
  persistentCwd = newDir;
3625
3731
  }
3626
3732
  } catch {
@@ -3628,7 +3734,7 @@ function updateCwdFromCommand(command, baseCwd) {
3628
3734
  }
3629
3735
 
3630
3736
  // src/tools/builtin/read-file.ts
3631
- import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync2 } from "fs";
3737
+ import { readFileSync as readFileSync5, existsSync as existsSync8, statSync as statSync3 } from "fs";
3632
3738
  import { extname, resolve as resolve3, basename as basename2, sep } from "path";
3633
3739
  import { homedir as homedir2 } from "os";
3634
3740
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
@@ -3721,8 +3827,8 @@ var readFileTool = {
3721
3827
  const encoding = args["encoding"] ?? "utf-8";
3722
3828
  if (!filePath) throw new Error("path is required");
3723
3829
  const normalizedPath = resolve3(filePath);
3724
- if (!existsSync7(normalizedPath)) throw new Error(`File not found: ${filePath}`);
3725
- const { size } = statSync2(normalizedPath);
3830
+ if (!existsSync8(normalizedPath)) throw new Error(`File not found: ${filePath}`);
3831
+ const { size } = statSync3(normalizedPath);
3726
3832
  if (size > MAX_FILE_BYTES) {
3727
3833
  const mb = (size / 1024 / 1024).toFixed(1);
3728
3834
  return `[File too large: ${filePath} (${mb} MB)]
@@ -3816,7 +3922,7 @@ var writeFileTool = {
3816
3922
  };
3817
3923
 
3818
3924
  // src/tools/builtin/edit-file.ts
3819
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
3925
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
3820
3926
  var editFileTool = {
3821
3927
  definition: {
3822
3928
  name: "edit_file",
@@ -3874,7 +3980,7 @@ var editFileTool = {
3874
3980
  const filePath = String(args["path"] ?? "");
3875
3981
  const encoding = args["encoding"] ?? "utf-8";
3876
3982
  if (!filePath) throw new Error("path is required");
3877
- if (!existsSync8(filePath)) throw new Error(`File not found: ${filePath}`);
3983
+ if (!existsSync9(filePath)) throw new Error(`File not found: ${filePath}`);
3878
3984
  const original = readFileSync6(filePath, encoding);
3879
3985
  if (args["old_str"] !== void 0) {
3880
3986
  const oldStr = String(args["old_str"]);
@@ -3940,8 +4046,8 @@ function truncatePreview(str, maxLen = 80) {
3940
4046
  }
3941
4047
 
3942
4048
  // src/tools/builtin/list-dir.ts
3943
- import { readdirSync as readdirSync3, statSync as statSync3, existsSync as existsSync9 } from "fs";
3944
- import { join as join5 } from "path";
4049
+ import { readdirSync as readdirSync3, statSync as statSync4, existsSync as existsSync10 } from "fs";
4050
+ import { join as join6 } from "path";
3945
4051
  var listDirTool = {
3946
4052
  definition: {
3947
4053
  name: "list_dir",
@@ -3963,7 +4069,7 @@ var listDirTool = {
3963
4069
  async execute(args) {
3964
4070
  const dirPath = String(args["path"] ?? process.cwd());
3965
4071
  const recursive = Boolean(args["recursive"] ?? false);
3966
- if (!existsSync9(dirPath)) throw new Error(`Directory not found: ${dirPath}`);
4072
+ if (!existsSync10(dirPath)) throw new Error(`Directory not found: ${dirPath}`);
3967
4073
  const lines = [`Directory: ${dirPath}
3968
4074
  `];
3969
4075
  listRecursive(dirPath, "", recursive, lines);
@@ -3992,11 +4098,11 @@ function listRecursive(basePath, indent, recursive, lines) {
3992
4098
  if (entry.isDirectory()) {
3993
4099
  lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
3994
4100
  if (recursive) {
3995
- listRecursive(join5(basePath, entry.name), indent + " ", true, lines);
4101
+ listRecursive(join6(basePath, entry.name), indent + " ", true, lines);
3996
4102
  }
3997
4103
  } else {
3998
4104
  try {
3999
- const stat = statSync3(join5(basePath, entry.name));
4105
+ const stat = statSync4(join6(basePath, entry.name));
4000
4106
  const size = formatSize(stat.size);
4001
4107
  lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
4002
4108
  } catch {
@@ -4012,8 +4118,8 @@ function formatSize(bytes) {
4012
4118
  }
4013
4119
 
4014
4120
  // src/tools/builtin/grep-files.ts
4015
- import { readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync4, existsSync as existsSync10 } from "fs";
4016
- import { join as join6, relative } from "path";
4121
+ import { readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync5, existsSync as existsSync11 } from "fs";
4122
+ import { join as join7, relative } from "path";
4017
4123
  var grepFilesTool = {
4018
4124
  definition: {
4019
4125
  name: "grep_files",
@@ -4064,7 +4170,7 @@ var grepFilesTool = {
4064
4170
  const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
4065
4171
  const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
4066
4172
  if (!pattern) throw new Error("pattern is required");
4067
- if (!existsSync10(rootPath)) throw new Error(`Path not found: ${rootPath}`);
4173
+ if (!existsSync11(rootPath)) throw new Error(`Path not found: ${rootPath}`);
4068
4174
  let regex;
4069
4175
  try {
4070
4176
  regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
@@ -4073,7 +4179,7 @@ var grepFilesTool = {
4073
4179
  regex = new RegExp(escaped, ignoreCase ? "gi" : "g");
4074
4180
  }
4075
4181
  const results = [];
4076
- const stat = statSync4(rootPath);
4182
+ const stat = statSync5(rootPath);
4077
4183
  if (stat.isFile()) {
4078
4184
  searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
4079
4185
  } else {
@@ -4137,11 +4243,11 @@ function collectFiles(dirPath, filePattern, results, regex, contextLines, maxRes
4137
4243
  if (results.length >= maxResults) return;
4138
4244
  if (entry.isDirectory()) {
4139
4245
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
4140
- collectFiles(join6(dirPath, entry.name), filePattern, results, regex, contextLines, maxResults, rootPath);
4246
+ collectFiles(join7(dirPath, entry.name), filePattern, results, regex, contextLines, maxResults, rootPath);
4141
4247
  } else if (entry.isFile()) {
4142
4248
  if (isBinary(entry.name)) continue;
4143
4249
  if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
4144
- const fullPath = join6(dirPath, entry.name);
4250
+ const fullPath = join7(dirPath, entry.name);
4145
4251
  const relPath = relative(rootPath, fullPath);
4146
4252
  searchInFile(fullPath, relPath, regex, contextLines, maxResults, results);
4147
4253
  }
@@ -4182,8 +4288,8 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
4182
4288
  }
4183
4289
 
4184
4290
  // src/tools/builtin/glob-files.ts
4185
- import { readdirSync as readdirSync5, statSync as statSync5, existsSync as existsSync11 } from "fs";
4186
- import { join as join7, relative as relative2, basename as basename3 } from "path";
4291
+ import { readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync12 } from "fs";
4292
+ import { join as join8, relative as relative2, basename as basename3 } from "path";
4187
4293
  var globFilesTool = {
4188
4294
  definition: {
4189
4295
  name: "glob_files",
@@ -4216,7 +4322,7 @@ var globFilesTool = {
4216
4322
  const rootPath = String(args["path"] ?? process.cwd());
4217
4323
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
4218
4324
  if (!pattern) throw new Error("pattern is required");
4219
- if (!existsSync11(rootPath)) throw new Error(`Path not found: ${rootPath}`);
4325
+ if (!existsSync12(rootPath)) throw new Error(`Path not found: ${rootPath}`);
4220
4326
  const regex = globToRegex(pattern);
4221
4327
  const matches = [];
4222
4328
  collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
@@ -4293,7 +4399,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
4293
4399
  }
4294
4400
  for (const entry of entries) {
4295
4401
  if (results.length >= maxResults) break;
4296
- const fullPath = join7(dirPath, entry.name);
4402
+ const fullPath = join8(dirPath, entry.name);
4297
4403
  if (entry.isDirectory()) {
4298
4404
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
4299
4405
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
@@ -4301,7 +4407,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
4301
4407
  const relPath = relative2(rootPath, fullPath).replace(/\\/g, "/");
4302
4408
  if (regex.test(relPath) || regex.test(basename3(relPath))) {
4303
4409
  try {
4304
- const stat = statSync5(fullPath);
4410
+ const stat = statSync6(fullPath);
4305
4411
  results.push({ relPath, absPath: fullPath, mtime: stat.mtimeMs });
4306
4412
  } catch {
4307
4413
  results.push({ relPath, absPath: fullPath, mtime: 0 });
@@ -4685,11 +4791,11 @@ var saveLastResponseTool = {
4685
4791
  };
4686
4792
 
4687
4793
  // src/tools/builtin/save-memory.ts
4688
- import { existsSync as existsSync12, readFileSync as readFileSync8, appendFileSync as appendFileSync2, mkdirSync as mkdirSync7 } from "fs";
4689
- import { join as join8 } from "path";
4794
+ import { existsSync as existsSync13, readFileSync as readFileSync8, appendFileSync as appendFileSync2, mkdirSync as mkdirSync7 } from "fs";
4795
+ import { join as join9 } from "path";
4690
4796
  import { homedir as homedir3 } from "os";
4691
4797
  function getMemoryFilePath() {
4692
- return join8(homedir3(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
4798
+ return join9(homedir3(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
4693
4799
  }
4694
4800
  function formatTimestamp() {
4695
4801
  const now = /* @__PURE__ */ new Date();
@@ -4713,8 +4819,8 @@ var saveMemoryTool = {
4713
4819
  const content = String(args["content"] ?? "").trim();
4714
4820
  if (!content) throw new Error("content is required");
4715
4821
  const memoryPath = getMemoryFilePath();
4716
- const configDir = join8(homedir3(), CONFIG_DIR_NAME);
4717
- if (!existsSync12(configDir)) {
4822
+ const configDir = join9(homedir3(), CONFIG_DIR_NAME);
4823
+ if (!existsSync13(configDir)) {
4718
4824
  mkdirSync7(configDir, { recursive: true });
4719
4825
  }
4720
4826
  const timestamp = formatTimestamp();
@@ -5304,8 +5410,8 @@ var spawnAgentTool = {
5304
5410
 
5305
5411
  // src/tools/registry.ts
5306
5412
  import { pathToFileURL } from "url";
5307
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync6 } from "fs";
5308
- import { join as join9 } from "path";
5413
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync6 } from "fs";
5414
+ import { join as join10 } from "path";
5309
5415
  var ToolRegistry = class {
5310
5416
  tools = /* @__PURE__ */ new Map();
5311
5417
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -5378,7 +5484,7 @@ var ToolRegistry = class {
5378
5484
  * Returns the number of successfully loaded plugins.
5379
5485
  */
5380
5486
  async loadPlugins(pluginsDir, allowPlugins = false) {
5381
- if (!existsSync13(pluginsDir)) {
5487
+ if (!existsSync14(pluginsDir)) {
5382
5488
  try {
5383
5489
  mkdirSync8(pluginsDir, { recursive: true });
5384
5490
  } catch {
@@ -5404,12 +5510,12 @@ var ToolRegistry = class {
5404
5510
  process.stderr.write(
5405
5511
  `
5406
5512
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
5407
- ` + files.map((f) => ` + ${join9(pluginsDir, f)}`).join("\n") + "\n\n"
5513
+ ` + files.map((f) => ` + ${join10(pluginsDir, f)}`).join("\n") + "\n\n"
5408
5514
  );
5409
5515
  let loaded = 0;
5410
5516
  for (const file of files) {
5411
5517
  try {
5412
- const fileUrl = pathToFileURL(join9(pluginsDir, file)).href;
5518
+ const fileUrl = pathToFileURL(join10(pluginsDir, file)).href;
5413
5519
  const mod = await import(fileUrl);
5414
5520
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
5415
5521
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -5438,7 +5544,7 @@ var ToolRegistry = class {
5438
5544
 
5439
5545
  // src/tools/executor.ts
5440
5546
  import chalk8 from "chalk";
5441
- import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
5547
+ import { existsSync as existsSync15, readFileSync as readFileSync9 } from "fs";
5442
5548
 
5443
5549
  // src/tools/diff-utils.ts
5444
5550
  import chalk7 from "chalk";
@@ -5594,7 +5700,7 @@ function simpleDiff(oldLines, newLines) {
5594
5700
  }
5595
5701
 
5596
5702
  // src/tools/hooks.ts
5597
- import { execSync as execSync4 } from "child_process";
5703
+ import { execSync as execSync5 } from "child_process";
5598
5704
  function runHook(template, vars) {
5599
5705
  if (!template) return;
5600
5706
  let cmd = template;
@@ -5603,7 +5709,7 @@ function runHook(template, vars) {
5603
5709
  cmd = cmd.replace(/\{args\}/g, vars.args ?? "");
5604
5710
  cmd = cmd.replace(/\{status\}/g, vars.status ?? "");
5605
5711
  try {
5606
- execSync4(cmd, {
5712
+ execSync5(cmd, {
5607
5713
  timeout: 5e3,
5608
5714
  stdio: ["pipe", "pipe", "pipe"],
5609
5715
  encoding: "utf-8"
@@ -5928,7 +6034,7 @@ var ToolExecutor = class {
5928
6034
  const filePath = String(call.arguments["path"] ?? "");
5929
6035
  const newContent = String(call.arguments["content"] ?? "");
5930
6036
  if (!filePath) return;
5931
- if (existsSync14(filePath)) {
6037
+ if (existsSync15(filePath)) {
5932
6038
  let oldContent;
5933
6039
  try {
5934
6040
  oldContent = readFileSync9(filePath, "utf-8");
@@ -5954,7 +6060,7 @@ var ToolExecutor = class {
5954
6060
  }
5955
6061
  } else if (call.name === "edit_file") {
5956
6062
  const filePath = String(call.arguments["path"] ?? "");
5957
- if (!filePath || !existsSync14(filePath)) return;
6063
+ if (!filePath || !existsSync15(filePath)) return;
5958
6064
  const oldStr = call.arguments["old_str"];
5959
6065
  const newStr = call.arguments["new_str"];
5960
6066
  if (oldStr !== void 0) {
@@ -6294,9 +6400,9 @@ Managing ${displayName} API Key`);
6294
6400
  };
6295
6401
 
6296
6402
  // src/repl/custom-commands.ts
6297
- import { existsSync as existsSync15, readFileSync as readFileSync10, readdirSync as readdirSync7, mkdirSync as mkdirSync9 } from "fs";
6298
- import { join as join10, extname as extname3 } from "path";
6299
- import { execSync as execSync5 } from "child_process";
6403
+ import { existsSync as existsSync16, readFileSync as readFileSync10, readdirSync as readdirSync7, mkdirSync as mkdirSync9 } from "fs";
6404
+ import { join as join11, extname as extname3 } from "path";
6405
+ import { execSync as execSync6 } from "child_process";
6300
6406
  function parseSimpleYaml(text) {
6301
6407
  const result = {};
6302
6408
  for (const line of text.split("\n")) {
@@ -6341,7 +6447,7 @@ function expandTemplate(template, args) {
6341
6447
  });
6342
6448
  result = result.replace(/\{\{git-diff\}\}/g, () => {
6343
6449
  try {
6344
- return execSync5("git diff", { encoding: "utf-8", timeout: 1e4 }).trim();
6450
+ return execSync6("git diff", { encoding: "utf-8", timeout: 1e4 }).trim();
6345
6451
  } catch {
6346
6452
  return "[No git diff available]";
6347
6453
  }
@@ -6359,14 +6465,14 @@ var CustomCommandManager = class {
6359
6465
  commands = /* @__PURE__ */ new Map();
6360
6466
  loadCommands() {
6361
6467
  this.commands.clear();
6362
- if (!existsSync15(this.commandsDir)) {
6468
+ if (!existsSync16(this.commandsDir)) {
6363
6469
  mkdirSync9(this.commandsDir, { recursive: true });
6364
6470
  return 0;
6365
6471
  }
6366
6472
  let count = 0;
6367
6473
  for (const file of readdirSync7(this.commandsDir)) {
6368
6474
  if (extname3(file) !== ".md") continue;
6369
- const cmd = parseCommandFile(join10(this.commandsDir, file));
6475
+ const cmd = parseCommandFile(join11(this.commandsDir, file));
6370
6476
  if (cmd) {
6371
6477
  this.commands.set(cmd.meta.name, cmd);
6372
6478
  count++;
@@ -6383,8 +6489,8 @@ var CustomCommandManager = class {
6383
6489
  };
6384
6490
 
6385
6491
  // src/repl/dev-state.ts
6386
- import { existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync2, mkdirSync as mkdirSync10 } from "fs";
6387
- import { join as join11 } from "path";
6492
+ import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync10 } from "fs";
6493
+ import { join as join12 } from "path";
6388
6494
  import { homedir as homedir4 } from "os";
6389
6495
  var DEV_STATE_MAX_CHARS = 4e3;
6390
6496
  var SNAPSHOT_PROMPT = `You are about to be replaced by a different AI model. Please generate a structured development state snapshot so the next model can continue seamlessly.
@@ -6423,11 +6529,11 @@ function sessionHasMeaningfulContent(messages) {
6423
6529
  return hasUser && hasAssistant;
6424
6530
  }
6425
6531
  function getDevStatePath() {
6426
- return join11(homedir4(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
6532
+ return join12(homedir4(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
6427
6533
  }
6428
6534
  function saveDevState(content) {
6429
- const configDir = join11(homedir4(), CONFIG_DIR_NAME);
6430
- if (!existsSync16(configDir)) {
6535
+ const configDir = join12(homedir4(), CONFIG_DIR_NAME);
6536
+ if (!existsSync17(configDir)) {
6431
6537
  mkdirSync10(configDir, { recursive: true });
6432
6538
  }
6433
6539
  let trimmed = content.trim();
@@ -6443,15 +6549,15 @@ function saveDevState(content) {
6443
6549
  }
6444
6550
  function loadDevState() {
6445
6551
  const path = getDevStatePath();
6446
- if (!existsSync16(path)) return null;
6552
+ if (!existsSync17(path)) return null;
6447
6553
  const content = readFileSync11(path, "utf-8").trim();
6448
6554
  return content || null;
6449
6555
  }
6450
6556
  function clearDevState() {
6451
6557
  const path = getDevStatePath();
6452
- if (existsSync16(path)) {
6558
+ if (existsSync17(path)) {
6453
6559
  try {
6454
- unlinkSync2(path);
6560
+ unlinkSync3(path);
6455
6561
  } catch {
6456
6562
  }
6457
6563
  }
@@ -6870,8 +6976,8 @@ var McpManager = class {
6870
6976
  };
6871
6977
 
6872
6978
  // src/skills/manager.ts
6873
- import { existsSync as existsSync17, readdirSync as readdirSync8, mkdirSync as mkdirSync11 } from "fs";
6874
- import { join as join12 } from "path";
6979
+ import { existsSync as existsSync18, readdirSync as readdirSync8, mkdirSync as mkdirSync11 } from "fs";
6980
+ import { join as join13 } from "path";
6875
6981
 
6876
6982
  // src/skills/types.ts
6877
6983
  import { readFileSync as readFileSync12 } from "fs";
@@ -6936,7 +7042,7 @@ var SkillManager = class {
6936
7042
  /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
6937
7043
  loadSkills() {
6938
7044
  this.skills.clear();
6939
- if (!existsSync17(this.skillsDir)) {
7045
+ if (!existsSync18(this.skillsDir)) {
6940
7046
  try {
6941
7047
  mkdirSync11(this.skillsDir, { recursive: true });
6942
7048
  } catch {
@@ -6951,7 +7057,7 @@ var SkillManager = class {
6951
7057
  }
6952
7058
  for (const entry of entries) {
6953
7059
  if (!entry.endsWith(".md")) continue;
6954
- const filePath = join12(this.skillsDir, entry);
7060
+ const filePath = join13(this.skillsDir, entry);
6955
7061
  const skill = parseSkillFile(filePath);
6956
7062
  if (skill) {
6957
7063
  this.skills.set(skill.meta.name, skill);
@@ -7019,12 +7125,12 @@ function parseAtReferences(input2, cwd) {
7019
7125
  const absPath = resolve4(cwd, rawPath);
7020
7126
  const ext = extname4(rawPath).toLowerCase();
7021
7127
  const mime = IMAGE_MIME[ext];
7022
- if (!existsSync18(absPath)) {
7128
+ if (!existsSync19(absPath)) {
7023
7129
  refs.push({ path: rawPath, type: "notfound" });
7024
7130
  continue;
7025
7131
  }
7026
7132
  if (mime) {
7027
- const fileSize = statSync6(absPath).size;
7133
+ const fileSize = statSync7(absPath).size;
7028
7134
  if (fileSize > MAX_IMAGE_BYTES) {
7029
7135
  refs.push({ path: rawPath, type: "toolarge" });
7030
7136
  continue;
@@ -7137,8 +7243,8 @@ var Repl = class {
7137
7243
  */
7138
7244
  findContextFile(dir, candidates = CONTEXT_FILE_CANDIDATES) {
7139
7245
  for (const candidate of candidates) {
7140
- const fullPath = join13(dir, candidate);
7141
- if (existsSync18(fullPath)) {
7246
+ const fullPath = join14(dir, candidate);
7247
+ if (existsSync19(fullPath)) {
7142
7248
  const content = readFileSync13(fullPath, "utf-8").trim();
7143
7249
  if (content) return { filePath: fullPath, content };
7144
7250
  }
@@ -7171,7 +7277,7 @@ var Repl = class {
7171
7277
  );
7172
7278
  return { layers: [], mergedContent: "" };
7173
7279
  }
7174
- if (existsSync18(fullPath)) {
7280
+ if (existsSync19(fullPath)) {
7175
7281
  const content = readFileSync13(fullPath, "utf-8").trim();
7176
7282
  if (content) {
7177
7283
  const layer = {
@@ -7229,8 +7335,8 @@ var Repl = class {
7229
7335
  * 超过 MEMORY_MAX_CHARS 时只取末尾最新部分。
7230
7336
  */
7231
7337
  loadMemoryContent() {
7232
- const memoryPath = join13(this.config.getConfigDir(), MEMORY_FILE_NAME);
7233
- if (!existsSync18(memoryPath)) return null;
7338
+ const memoryPath = join14(this.config.getConfigDir(), MEMORY_FILE_NAME);
7339
+ if (!existsSync19(memoryPath)) return null;
7234
7340
  let content = readFileSync13(memoryPath, "utf-8").trim();
7235
7341
  if (!content) return null;
7236
7342
  if (content.length > MEMORY_MAX_CHARS) {
@@ -7507,14 +7613,14 @@ ${response.content.trim()}
7507
7613
  process.stdout.write(chalk10.dim(` \u{1F50C} Plugins loaded: ${pluginCount} tool(s) from plugins/
7508
7614
  `));
7509
7615
  }
7510
- const skillsDir = join13(this.config.getConfigDir(), SKILLS_DIR_NAME);
7616
+ const skillsDir = join14(this.config.getConfigDir(), SKILLS_DIR_NAME);
7511
7617
  this.skillManager = new SkillManager(skillsDir);
7512
7618
  const skillCount = this.skillManager.loadSkills();
7513
7619
  if (skillCount > 0) {
7514
7620
  process.stdout.write(chalk10.dim(` \u{1F3AF} Skills: ${skillCount} available (use /skill to manage)
7515
7621
  `));
7516
7622
  }
7517
- const commandsDir = join13(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
7623
+ const commandsDir = join14(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
7518
7624
  this.customCommandManager = new CustomCommandManager(commandsDir);
7519
7625
  const customCmdCount = this.customCommandManager.loadCommands();
7520
7626
  if (customCmdCount > 0) {
@@ -7538,6 +7644,7 @@ ${response.content.trim()}
7538
7644
  );
7539
7645
  }
7540
7646
  }
7647
+ this.setupClipboardPaste();
7541
7648
  this.rl.on("SIGINT", () => {
7542
7649
  if (this.streamAbortController) {
7543
7650
  this.streamAbortController.abort();
@@ -7857,15 +7964,15 @@ ${response.content.trim()}
7857
7964
  const dir = normalized.includes("/") ? dirname5(normalized) : ".";
7858
7965
  const prefix = normalized.includes("/") ? basename5(normalized) : normalized;
7859
7966
  const absDir = resolve4(process.cwd(), dir);
7860
- if (!existsSync18(absDir)) return [];
7967
+ if (!existsSync19(absDir)) return [];
7861
7968
  const entries = readdirSync9(absDir);
7862
7969
  const results = [];
7863
7970
  for (const entry of entries) {
7864
7971
  if (entry.startsWith(".")) continue;
7865
7972
  if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
7866
7973
  try {
7867
- const fullPath = join13(absDir, entry);
7868
- const stat = statSync6(fullPath);
7974
+ const fullPath = join14(absDir, entry);
7975
+ const stat = statSync7(fullPath);
7869
7976
  const rel = dir === "." ? entry : `${dir}/${entry}`;
7870
7977
  results.push(stat.isDirectory() ? `${rel}/` : rel);
7871
7978
  } catch {
@@ -7908,6 +8015,41 @@ ${response.content.trim()}
7908
8015
  process.stdin.pause();
7909
8016
  this.streamAbortController = null;
7910
8017
  }
8018
+ /**
8019
+ * 注册 Ctrl+V 剪贴板图片粘贴快捷键。
8020
+ *
8021
+ * 使用 prependListener 先于 readline 的 quotedInsert 处理器运行:
8022
+ * 1. 我们的 handler 在 setImmediate 中调用 rl.write('@imgPath ')
8023
+ * 2. readline 的 quotedInsert 消费第一个字符('@'),字面插入 '@' — 结果相同
8024
+ * 3. 后续字符正常插入
8025
+ * 最终 readline 缓冲区包含完整的 @路径,用户可继续追加描述文字后回车发送。
8026
+ */
8027
+ setupClipboardPaste() {
8028
+ process.stdin.prependListener("keypress", (_ch, key) => {
8029
+ if (!key?.ctrl || key?.name !== "v") return;
8030
+ if (this.streamAbortController || this.toolExecutor.confirming || askUserContext.prompting || this.selecting) return;
8031
+ const imgPath = readClipboardImage();
8032
+ if (imgPath) {
8033
+ setImmediate(() => {
8034
+ this.rl.write(`@${imgPath} `);
8035
+ process.stdout.write(chalk10.dim(`
8036
+ \u{1F4CB} \u56FE\u7247\u5DF2\u7C98\u8D34\uFF0C\u8BF7\u6DFB\u52A0\u63CF\u8FF0\u540E\u56DE\u8F66\u53D1\u9001
8037
+ `));
8038
+ this.showPrompt();
8039
+ });
8040
+ } else {
8041
+ setImmediate(() => {
8042
+ const hint = getClipboardHint();
8043
+ process.stdout.write(
8044
+ chalk10.dim(`
8045
+ \u2139 \u526A\u8D34\u677F\u4E2D\u6CA1\u6709\u56FE\u7247\u3002\u53EF\u4F7F\u7528 @\u8DEF\u5F84 \u5F15\u7528\u672C\u5730\u56FE\u7247\u6587\u4EF6\u3002`) + (hint ? chalk10.dim(`
8046
+ ${hint}`) : "") + "\n"
8047
+ );
8048
+ this.showPrompt();
8049
+ });
8050
+ }
8051
+ });
8052
+ }
7911
8053
  async handleChatSimple(provider, messages) {
7912
8054
  const session = this.sessions.current;
7913
8055
  const useStreaming = this.config.get("ui").streaming;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",