jinzd-ai-cli 0.4.16 → 0.4.18

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.
@@ -594,14 +594,8 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
594
594
  return `[Binary file: ${filePath} (${ext})]
595
595
  This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
596
596
  }
597
- try {
598
- const fstat = statSync2(normalizedPath);
599
- if (fstat.size > MAX_FILE_BYTES) {
600
- return `[File too large: ${filePath} | ${(fstat.size / 1024 / 1024).toFixed(1)} MB exceeds ${MAX_FILE_BYTES / 1024 / 1024} MB limit]`;
601
- }
602
- } catch {
603
- }
604
- const buf = readFileSync2(normalizedPath);
597
+ const { readFile: readFile2 } = await import("fs/promises");
598
+ const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync2(normalizedPath);
605
599
  if (encoding === "base64") {
606
600
  return `[File: ${filePath} | base64]
607
601
 
@@ -1021,6 +1015,7 @@ function formatSize(bytes) {
1021
1015
 
1022
1016
  // src/tools/builtin/grep-files.ts
1023
1017
  import { readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4, existsSync as existsSync6 } from "fs";
1018
+ import { readFile } from "fs/promises";
1024
1019
  import { join as join2, relative } from "path";
1025
1020
  var grepFilesTool = {
1026
1021
  definition: {
@@ -1089,7 +1084,22 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
1089
1084
  if (stat.isFile()) {
1090
1085
  searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
1091
1086
  } else {
1092
- collectFiles(rootPath, filePattern, results, regex, contextLines, maxResults, rootPath);
1087
+ const filePaths = collectFilePaths(rootPath, filePattern, maxResults);
1088
+ const BATCH_SIZE = 16;
1089
+ for (let i = 0; i < filePaths.length && results.length < maxResults; i += BATCH_SIZE) {
1090
+ const batch = filePaths.slice(i, i + BATCH_SIZE);
1091
+ const batchResults = await Promise.all(
1092
+ batch.map(
1093
+ ({ fullPath, relPath }) => searchInFileAsync(fullPath, relPath, regex, contextLines, maxResults - results.length)
1094
+ )
1095
+ );
1096
+ for (const br of batchResults) {
1097
+ for (const r of br) {
1098
+ if (results.length >= maxResults) break;
1099
+ results.push(r);
1100
+ }
1101
+ }
1102
+ }
1093
1103
  }
1094
1104
  if (results.length === 0) {
1095
1105
  return `No matches found for pattern: ${pattern}
@@ -1137,27 +1147,69 @@ function isBinary(filename) {
1137
1147
  const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
1138
1148
  return BINARY_EXTS.has(ext);
1139
1149
  }
1140
- function collectFiles(dirPath, filePattern, results, regex, contextLines, maxResults, rootPath) {
1141
- if (results.length >= maxResults) return;
1142
- let entries;
1150
+ function collectFilePaths(rootPath, filePattern, maxFiles) {
1151
+ const paths = [];
1152
+ function walk(dirPath) {
1153
+ if (paths.length >= maxFiles) return;
1154
+ let entries;
1155
+ try {
1156
+ entries = readdirSync4(dirPath, { withFileTypes: true });
1157
+ } catch {
1158
+ return;
1159
+ }
1160
+ for (const entry of entries) {
1161
+ if (paths.length >= maxFiles) return;
1162
+ if (entry.isDirectory()) {
1163
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
1164
+ walk(join2(dirPath, entry.name));
1165
+ } else if (entry.isFile()) {
1166
+ if (isBinary(entry.name)) continue;
1167
+ if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
1168
+ const fullPath = join2(dirPath, entry.name);
1169
+ try {
1170
+ if (statSync4(fullPath).size > 1e6) continue;
1171
+ } catch {
1172
+ continue;
1173
+ }
1174
+ paths.push({ fullPath, relPath: relative(rootPath, fullPath) });
1175
+ }
1176
+ }
1177
+ }
1178
+ walk(rootPath);
1179
+ return paths;
1180
+ }
1181
+ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, maxResults) {
1182
+ let content;
1143
1183
  try {
1144
- entries = readdirSync4(dirPath, { withFileTypes: true });
1184
+ content = await readFile(fullPath, "utf-8");
1145
1185
  } catch {
1146
- return;
1186
+ return [];
1147
1187
  }
1148
- for (const entry of entries) {
1149
- if (results.length >= maxResults) return;
1150
- if (entry.isDirectory()) {
1151
- if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
1152
- collectFiles(join2(dirPath, entry.name), filePattern, results, regex, contextLines, maxResults, rootPath);
1153
- } else if (entry.isFile()) {
1154
- if (isBinary(entry.name)) continue;
1155
- if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
1156
- const fullPath = join2(dirPath, entry.name);
1157
- const relPath = relative(rootPath, fullPath);
1158
- searchInFile(fullPath, relPath, regex, contextLines, maxResults, results);
1188
+ const results = [];
1189
+ const lines = content.split("\n");
1190
+ regex.lastIndex = 0;
1191
+ for (let i = 0; i < lines.length && results.length < maxResults; i++) {
1192
+ regex.lastIndex = 0;
1193
+ if (regex.test(lines[i])) {
1194
+ const match = {
1195
+ file: displayPath.replace(/\\/g, "/"),
1196
+ lineNumber: i + 1,
1197
+ lineText: lines[i]
1198
+ };
1199
+ if (contextLines > 0) {
1200
+ match.contextBefore = [];
1201
+ for (let j = Math.max(0, i - contextLines); j < i; j++) {
1202
+ match.contextBefore.push([j + 1, lines[j]]);
1203
+ }
1204
+ match.contextAfter = [];
1205
+ for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
1206
+ match.contextAfter.push([j + 1, lines[j]]);
1207
+ }
1208
+ }
1209
+ results.push(match);
1159
1210
  }
1160
1211
  }
1212
+ return results;
1161
1213
  }
1162
1214
  function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, results) {
1163
1215
  try {
@@ -7,7 +7,7 @@ import {
7
7
  ProviderNotFoundError,
8
8
  RateLimitError,
9
9
  schemaToJsonSchema
10
- } from "./chunk-DMM3XL26.js";
10
+ } from "./chunk-NLT5FT2W.js";
11
11
  import {
12
12
  APP_NAME,
13
13
  CONFIG_DIR_NAME,
@@ -2050,7 +2050,7 @@ var ProviderRegistry = class {
2050
2050
  };
2051
2051
 
2052
2052
  // src/session/session-manager.ts
2053
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, unlinkSync, renameSync } from "fs";
2053
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, unlinkSync, renameSync, openSync, readSync, closeSync } from "fs";
2054
2054
  import { join as join2 } from "path";
2055
2055
  import { v4 as uuidv4 } from "uuid";
2056
2056
 
@@ -2256,6 +2256,11 @@ function safeDate(value) {
2256
2256
  const d = new Date(value);
2257
2257
  return isNaN(d.getTime()) ? /* @__PURE__ */ new Date(0) : d;
2258
2258
  }
2259
+ function extractJsonField(header, field) {
2260
+ const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`, "i");
2261
+ const m = header.match(re);
2262
+ return m ? m[1] : void 0;
2263
+ }
2259
2264
  var SessionManager = class {
2260
2265
  constructor(config) {
2261
2266
  this.config = config;
@@ -2300,18 +2305,8 @@ var SessionManager = class {
2300
2305
  const metas = [];
2301
2306
  for (const file of files) {
2302
2307
  try {
2303
- const data = JSON.parse(
2304
- readFileSync2(join2(this.historyDir, file), "utf-8")
2305
- );
2306
- metas.push({
2307
- id: data.id,
2308
- provider: data.provider,
2309
- model: data.model,
2310
- messageCount: data.messages?.length ?? 0,
2311
- created: safeDate(data.created),
2312
- updated: safeDate(data.updated),
2313
- title: data.title
2314
- });
2308
+ const meta = this.readSessionMeta(join2(this.historyDir, file));
2309
+ if (meta) metas.push(meta);
2315
2310
  } catch (err) {
2316
2311
  process.stderr.write(
2317
2312
  `[Warning] Skipping corrupted session file "${file}": ${err instanceof Error ? err.message : String(err)}
@@ -2321,6 +2316,59 @@ var SessionManager = class {
2321
2316
  }
2322
2317
  return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
2323
2318
  }
2319
+ /**
2320
+ * P1-B: Read only the first ~1KB of a session file to extract metadata fields.
2321
+ * Session JSON format puts id/provider/model/created/updated/title before the
2322
+ * large "messages" array, so a small header read suffices for metadata extraction.
2323
+ * Falls back to full file read if header parsing fails.
2324
+ */
2325
+ readSessionMeta(filePath) {
2326
+ const HEADER_SIZE = 1024;
2327
+ let header;
2328
+ try {
2329
+ const fd = openSync(filePath, "r");
2330
+ const buf = Buffer.alloc(HEADER_SIZE);
2331
+ const bytesRead = readSync(fd, buf, 0, HEADER_SIZE, 0);
2332
+ closeSync(fd);
2333
+ header = buf.toString("utf-8", 0, bytesRead);
2334
+ } catch {
2335
+ return null;
2336
+ }
2337
+ const id = extractJsonField(header, "id");
2338
+ const provider = extractJsonField(header, "provider");
2339
+ const model = extractJsonField(header, "model");
2340
+ const created = extractJsonField(header, "created");
2341
+ const updated = extractJsonField(header, "updated");
2342
+ const title = extractJsonField(header, "title");
2343
+ if (id && provider && model) {
2344
+ let messageCount = 0;
2345
+ try {
2346
+ const full = readFileSync2(filePath, "utf-8");
2347
+ const matches = full.match(/"role"\s*:/g);
2348
+ messageCount = matches ? matches.length : 0;
2349
+ } catch {
2350
+ }
2351
+ return {
2352
+ id,
2353
+ provider,
2354
+ model,
2355
+ messageCount,
2356
+ created: safeDate(created),
2357
+ updated: safeDate(updated),
2358
+ title: title || void 0
2359
+ };
2360
+ }
2361
+ const data = JSON.parse(readFileSync2(filePath, "utf-8"));
2362
+ return {
2363
+ id: data.id,
2364
+ provider: data.provider,
2365
+ model: data.model,
2366
+ messageCount: data.messages?.length ?? 0,
2367
+ created: safeDate(data.created),
2368
+ updated: safeDate(data.updated),
2369
+ title: data.title
2370
+ };
2371
+ }
2324
2372
  deleteSession(id) {
2325
2373
  const filePath = join2(this.historyDir, `${id}.json`);
2326
2374
  if (!existsSync2(filePath)) return false;
@@ -381,7 +381,7 @@ ${content}`);
381
381
  }
382
382
  }
383
383
  async function runTaskMode(config, providers, configManager, topic) {
384
- const { TaskOrchestrator } = await import("./task-orchestrator-USDQ6J6V.js");
384
+ const { TaskOrchestrator } = await import("./task-orchestrator-FVBUXFLC.js");
385
385
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
386
386
  let interrupted = false;
387
387
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  saveDevState,
24
24
  sessionHasMeaningfulContent,
25
25
  setupProxy
26
- } from "./chunk-2OKRGXVU.js";
26
+ } from "./chunk-OBFFL5DJ.js";
27
27
  import {
28
28
  ToolRegistry,
29
29
  askUserContext,
@@ -38,7 +38,7 @@ import {
38
38
  theme,
39
39
  truncateOutput,
40
40
  undoStack
41
- } from "./chunk-DMM3XL26.js";
41
+ } from "./chunk-NLT5FT2W.js";
42
42
  import {
43
43
  AGENTIC_BEHAVIOR_GUIDELINE,
44
44
  AUTHOR,
@@ -4170,21 +4170,27 @@ Session '${this.resumeSessionId}' not found.
4170
4170
  }
4171
4171
  if (Object.keys(mergedMcpServers).length > 0) {
4172
4172
  this.mcpManager = new McpManager();
4173
- await this.mcpManager.connectAll(mergedMcpServers);
4174
- const mcpTools = this.mcpManager.getAllTools();
4175
- for (const tool of mcpTools) {
4176
- this.toolRegistry.registerMcpTool(tool);
4177
- }
4178
- const connectedCount = this.mcpManager.getConnectedCount();
4179
- const totalTools = this.mcpManager.getTotalToolCount();
4180
- if (connectedCount > 0) {
4181
- const projectCount = Object.keys(projectMcpServers).length;
4182
- const sourceInfo = projectCount > 0 ? ` (${projectCount} from .mcp.json)` : "";
4183
- process.stdout.write(
4184
- theme.dim(` \u{1F50C} MCP: ${connectedCount} server(s), ${totalTools} tool(s)${sourceInfo}
4173
+ const mcpProjectCount = Object.keys(projectMcpServers).length;
4174
+ this.mcpManager.connectAll(mergedMcpServers).then(() => {
4175
+ const mcpTools = this.mcpManager.getAllTools();
4176
+ for (const tool of mcpTools) {
4177
+ this.toolRegistry.registerMcpTool(tool);
4178
+ }
4179
+ const connectedCount = this.mcpManager.getConnectedCount();
4180
+ const totalTools = this.mcpManager.getTotalToolCount();
4181
+ if (connectedCount > 0) {
4182
+ const sourceInfo = mcpProjectCount > 0 ? ` (${mcpProjectCount} from .mcp.json)` : "";
4183
+ process.stdout.write(
4184
+ theme.dim(`
4185
+ \u{1F50C} MCP: ${connectedCount} server(s), ${totalTools} tool(s)${sourceInfo}
4185
4186
  `)
4186
- );
4187
- }
4187
+ );
4188
+ this.showPrompt();
4189
+ }
4190
+ }).catch((err) => {
4191
+ process.stderr.write(`[mcp] connectAll error: ${err instanceof Error ? err.message : err}
4192
+ `);
4193
+ });
4188
4194
  }
4189
4195
  this.setupClipboardPaste();
4190
4196
  this.rl.on("SIGINT", () => {
@@ -5542,7 +5548,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5542
5548
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5543
5549
  process.exit(1);
5544
5550
  }
5545
- const { startWebServer } = await import("./server-4JOL6AJL.js");
5551
+ const { startWebServer } = await import("./server-KSH5U7QY.js");
5546
5552
  await startWebServer({ port, host: options.host });
5547
5553
  });
5548
5554
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5775,7 +5781,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5775
5781
  }),
5776
5782
  config.get("customProviders")
5777
5783
  );
5778
- const { startHub } = await import("./hub-2MUUWFAZ.js");
5784
+ const { startHub } = await import("./hub-WF6CNNUT.js");
5779
5785
  await startHub(
5780
5786
  {
5781
5787
  topic: topic ?? "",
@@ -18,7 +18,7 @@ import {
18
18
  renderDiff,
19
19
  runHook,
20
20
  setupProxy
21
- } from "./chunk-2OKRGXVU.js";
21
+ } from "./chunk-OBFFL5DJ.js";
22
22
  import {
23
23
  AuthManager
24
24
  } from "./chunk-BYNY5JPB.js";
@@ -32,7 +32,7 @@ import {
32
32
  spawnAgentContext,
33
33
  truncateOutput,
34
34
  undoStack
35
- } from "./chunk-DMM3XL26.js";
35
+ } from "./chunk-NLT5FT2W.js";
36
36
  import {
37
37
  AGENTIC_BEHAVIOR_GUIDELINE,
38
38
  AUTHOR,
@@ -4,7 +4,7 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-DMM3XL26.js";
7
+ } from "./chunk-NLT5FT2W.js";
8
8
  import {
9
9
  SUBAGENT_ALLOWED_TOOLS
10
10
  } from "./chunk-KOD3C2CU.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",