jinzd-ai-cli 0.4.23 → 0.4.25

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-XMTMCMAP.js";
4
+ } from "./chunk-ETMUP3CY.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-UA4BVWKV.js";
5
+ } from "./chunk-AHH5I2U6.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -7,7 +7,6 @@ import {
7
7
  SessionManager,
8
8
  SkillManager,
9
9
  TOOL_CALL_REMINDER,
10
- checkPermission,
11
10
  detectsHallucinatedFileOp,
12
11
  formatGitContextForPrompt,
13
12
  getContentText,
@@ -15,24 +14,27 @@ import {
15
14
  getGitRoot,
16
15
  hadPreviousWriteToolCalls,
17
16
  loadDevState,
18
- renderDiff,
19
- runHook,
20
17
  setupProxy
21
- } from "./chunk-GBMVHLPA.js";
18
+ } from "./chunk-SS7BQZ5R.js";
22
19
  import {
23
20
  AuthManager
24
21
  } from "./chunk-BYNY5JPB.js";
25
22
  import {
23
+ ToolExecutor,
26
24
  ToolRegistry,
27
25
  askUserContext,
26
+ checkPermission,
28
27
  getDangerLevel,
29
28
  googleSearchContext,
30
29
  isFileWriteTool,
30
+ renderDiff,
31
+ runHook,
31
32
  setContextWindow,
32
33
  spawnAgentContext,
33
34
  truncateOutput,
34
35
  undoStack
35
- } from "./chunk-PDVX5QJA.js";
36
+ } from "./chunk-5GZQLJAY.js";
37
+ import "./chunk-4BKXL7SM.js";
36
38
  import {
37
39
  AGENTIC_BEHAVIOR_GUIDELINE,
38
40
  AUTHOR,
@@ -50,7 +52,7 @@ import {
50
52
  SKILLS_DIR_NAME,
51
53
  VERSION,
52
54
  buildUserIdentityPrompt
53
- } from "./chunk-UA4BVWKV.js";
55
+ } from "./chunk-AHH5I2U6.js";
54
56
 
55
57
  // src/web/server.ts
56
58
  import express from "express";
@@ -815,6 +817,7 @@ You have a maximum of ${MAX_TOOL_ROUNDS} tool call rounds for this task. Plan ef
815
817
  spawnAgentContext.systemPrompt = systemPrompt;
816
818
  spawnAgentContext.modelParams = modelParams;
817
819
  spawnAgentContext.configManager = this.config;
820
+ ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
818
821
  const toolResults = await this.toolExecutor.executeAll(result.toolCalls);
819
822
  const reasoningContent = result.reasoningContent;
820
823
  const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
@@ -1512,11 +1515,96 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1512
1515
  }
1513
1516
  break;
1514
1517
  }
1518
+ // ── /security-review ──────────────────────────────────────────────
1519
+ case "security-review": {
1520
+ const gitCtx = getGitContext();
1521
+ if (!gitCtx) {
1522
+ this.send({ type: "error", message: "Not a git repository." });
1523
+ break;
1524
+ }
1525
+ const secStaged = args.includes("--staged");
1526
+ let secDiff;
1527
+ try {
1528
+ const cmd = secStaged ? "git diff --staged" : "git diff";
1529
+ secDiff = execSync(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
1530
+ } catch {
1531
+ this.send({ type: "error", message: "Failed to run git diff." });
1532
+ break;
1533
+ }
1534
+ if (!secDiff) {
1535
+ this.send({ type: "info", message: "No changes to review." + (secStaged ? "" : " Try --staged for staged changes.") });
1536
+ break;
1537
+ }
1538
+ const SEC_MAX_DIFF = 8e3;
1539
+ let secTruncated = false;
1540
+ if (secDiff.length > SEC_MAX_DIFF) {
1541
+ const head = secDiff.slice(0, Math.floor(SEC_MAX_DIFF * 0.7));
1542
+ const tail = secDiff.slice(secDiff.length - Math.floor(SEC_MAX_DIFF * 0.2));
1543
+ secDiff = head + "\n\n... [diff truncated, " + secDiff.length + " chars total] ...\n\n" + tail;
1544
+ secTruncated = true;
1545
+ }
1546
+ const secPrompt = this.buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
1547
+ this.send({ type: "info", message: "\u{1F512} Scanning for security vulnerabilities..." });
1548
+ try {
1549
+ const secReview = await this.chatOnce(secPrompt, { temperature: 0.2, maxTokens: 8192 });
1550
+ const secMsg = secTruncated ? secReview + "\n\n\u26A0 Diff was truncated. Consider reviewing smaller changesets." : secReview;
1551
+ this.send({ type: "info", message: secMsg });
1552
+ } catch (err) {
1553
+ this.send({ type: "error", message: `Security review failed: ${err.message}` });
1554
+ }
1555
+ break;
1556
+ }
1557
+ // ── /rewind ────────────────────────────────────────────────────────
1558
+ case "rewind": {
1559
+ const session = this.sessions.current;
1560
+ if (!session || session.messages.length === 0) {
1561
+ this.send({ type: "info", message: "No messages to rewind." });
1562
+ break;
1563
+ }
1564
+ const rewindSub = args[0];
1565
+ if (rewindSub === "list" || !rewindSub) {
1566
+ const lines = [`Conversation messages (${session.messages.length} total):
1567
+ `];
1568
+ const cpIndices = (await import("./file-checkpoint-NKBHGC7L.js")).fileCheckpoints.getMessageIndices();
1569
+ for (let i = 0; i < session.messages.length; i++) {
1570
+ const m = session.messages[i];
1571
+ const text = getContentText(m.content).replace(/\n/g, " ").slice(0, 60);
1572
+ const pin = cpIndices.includes(i) ? " \u{1F4CC}" : "";
1573
+ lines.push(` [${i + 1}] ${m.role.padEnd(10)} ${text}${pin}`);
1574
+ }
1575
+ lines.push("", "Usage: /rewind <n> \u2014 rewind to message N", "\u{1F4CC} = file checkpoint");
1576
+ this.send({ type: "info", message: lines.join("\n") });
1577
+ break;
1578
+ }
1579
+ const rewindN = parseInt(rewindSub, 10);
1580
+ if (isNaN(rewindN) || rewindN < 1 || rewindN > session.messages.length) {
1581
+ this.send({ type: "error", message: `Invalid message number: ${rewindSub}. Range: 1-${session.messages.length}` });
1582
+ break;
1583
+ }
1584
+ const { fileCheckpoints: fc } = await import("./file-checkpoint-NKBHGC7L.js");
1585
+ const rewindRemoved = session.messages.length - rewindN;
1586
+ const rewindResult = fc.restoreToMessageIndex(rewindN);
1587
+ session.messages = session.messages.slice(0, rewindN);
1588
+ session.checkpoints = session.checkpoints.filter((c) => c.messageIndex <= rewindN);
1589
+ session.updated = /* @__PURE__ */ new Date();
1590
+ this.sessions.save();
1591
+ const rewindLines = [`\u2713 Rewound to message ${rewindN}`, ` Messages removed: ${rewindRemoved}`];
1592
+ if (rewindResult.restored > 0 || rewindResult.deleted > 0) {
1593
+ rewindLines.push(` Files restored: ${rewindResult.restored}, deleted: ${rewindResult.deleted}`);
1594
+ for (const f of rewindResult.files) rewindLines.push(` ${f}`);
1595
+ } else {
1596
+ rewindLines.push(" No file changes to revert.");
1597
+ }
1598
+ this.send({ type: "info", message: rewindLines.join("\n") });
1599
+ this.sendSessionMessages();
1600
+ this.sendStatus();
1601
+ break;
1602
+ }
1515
1603
  // ── /test ───────────────────────────────────────────────────────
1516
1604
  case "test": {
1517
1605
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1518
1606
  try {
1519
- const { executeTests } = await import("./run-tests-7ZBI4ZTU.js");
1607
+ const { executeTests } = await import("./run-tests-L3JNRB6X.js");
1520
1608
  const argStr = args.join(" ").trim();
1521
1609
  let testArgs = {};
1522
1610
  if (argStr) {
@@ -2320,6 +2408,37 @@ ${diff}
2320
2408
  4. **Highlights** (if any)
2321
2409
 
2322
2410
  Severity: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
2411
+ }
2412
+ buildSecurityReviewPrompt(diff, gitContextStr) {
2413
+ return `# Security Vulnerability Review
2414
+
2415
+ Analyze the following code changes **exclusively for security vulnerabilities**.
2416
+
2417
+ ## Categories
2418
+ 1. Injection (SQL, command, path traversal, XSS)
2419
+ 2. Auth & Authorization (hardcoded creds, missing checks)
2420
+ 3. Secrets & Sensitive Data (API keys, tokens in code)
2421
+ 4. Input Validation (missing validation, unsafe deserialization)
2422
+ 5. Cryptography (weak algorithms, hardcoded IVs)
2423
+ 6. File System (path traversal, symlink attacks)
2424
+ 7. Network (SSRF, insecure protocols)
2425
+
2426
+ ## Git Status
2427
+ ${gitContextStr}
2428
+
2429
+ ## Code Changes
2430
+ \`\`\`diff
2431
+ ${diff}
2432
+ \`\`\`
2433
+
2434
+ ## Output Format
2435
+ For each finding:
2436
+ - **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW
2437
+ - **Category** + **File:line**
2438
+ - **Description** + exploitation scenario
2439
+ - **Recommended fix**
2440
+
2441
+ If none found: "\u2705 No security vulnerabilities detected"`;
2323
2442
  }
2324
2443
  loadContextFiles() {
2325
2444
  const parts = [];
@@ -4,10 +4,11 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-PDVX5QJA.js";
7
+ } from "./chunk-5GZQLJAY.js";
8
+ import "./chunk-4BKXL7SM.js";
8
9
  import {
9
10
  SUBAGENT_ALLOWED_TOOLS
10
- } from "./chunk-UA4BVWKV.js";
11
+ } from "./chunk-AHH5I2U6.js";
11
12
 
12
13
  // src/hub/task-orchestrator.ts
13
14
  import { createInterface } from "readline";
@@ -782,27 +782,40 @@ function renderFilteredSessions(filter) {
782
782
  </div>`;
783
783
  }).join('');
784
784
 
785
- // Click to load session (only in normal mode)
785
+ // Click to load session / double-click title to rename
786
786
  sessionListEl.querySelectorAll('.session-item').forEach(el => {
787
+ let clickTimer = null;
788
+
787
789
  el.addEventListener('click', (e) => {
788
790
  if (e.target.closest('.session-delete-btn') || e.target.closest('.session-batch-cb') || e.target.closest('.session-rename-input')) return;
789
791
  if (batchSelectMode) {
790
- // In batch mode, clicking the row toggles the checkbox
791
792
  const cb = el.querySelector('.session-batch-cb');
792
793
  if (cb) { cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); }
793
794
  return;
794
795
  }
796
+ // If clicking on the title text, delay to allow dblclick detection
797
+ if (!batchSelectMode && e.target.closest('.session-title')) {
798
+ if (clickTimer) return; // Already waiting
799
+ clickTimer = setTimeout(() => {
800
+ clickTimer = null;
801
+ const id = el.dataset.sessionId;
802
+ if (id) send({ type: 'command', name: 'session', args: ['load', id] });
803
+ }, 300);
804
+ return;
805
+ }
806
+ // Clicking elsewhere on the item — load immediately
795
807
  const id = el.dataset.sessionId;
796
808
  if (!id) return;
797
809
  send({ type: 'command', name: 'session', args: ['load', id] });
798
810
  });
799
811
 
800
- // Double-click session title to rename
801
812
  if (!batchSelectMode) {
802
813
  const titleEl = el.querySelector('.session-title');
803
814
  if (titleEl) {
804
815
  titleEl.addEventListener('dblclick', (e) => {
805
816
  e.stopPropagation();
817
+ e.preventDefault();
818
+ if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; }
806
819
  startSessionRename(el, titleEl);
807
820
  });
808
821
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",