jinzd-ai-cli 0.4.70 → 0.4.71

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.
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.4.70";
11
+ var VERSION = "0.4.71";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.70";
9
+ var VERSION = "0.4.71";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -8,7 +8,7 @@ import {
8
8
  RateLimitError,
9
9
  schemaToJsonSchema,
10
10
  truncateForPersist
11
- } from "./chunk-IVTWWDWZ.js";
11
+ } from "./chunk-UHZ6YANH.js";
12
12
  import {
13
13
  APP_NAME,
14
14
  CONFIG_DIR_NAME,
@@ -21,7 +21,7 @@ import {
21
21
  MCP_TOOL_PREFIX,
22
22
  PLUGINS_DIR_NAME,
23
23
  VERSION
24
- } from "./chunk-ND3O5NQU.js";
24
+ } from "./chunk-6OKAZIY7.js";
25
25
 
26
26
  // src/config/config-manager.ts
27
27
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -10,7 +10,7 @@ import {
10
10
  SUBAGENT_DEFAULT_MAX_ROUNDS,
11
11
  SUBAGENT_MAX_ROUNDS_LIMIT,
12
12
  runTestsTool
13
- } from "./chunk-ND3O5NQU.js";
13
+ } from "./chunk-6OKAZIY7.js";
14
14
 
15
15
  // src/tools/builtin/bash.ts
16
16
  import { execSync } from "child_process";
@@ -385,7 +385,7 @@ ${content}`);
385
385
  }
386
386
  }
387
387
  async function runTaskMode(config, providers, configManager, topic) {
388
- const { TaskOrchestrator } = await import("./task-orchestrator-EB2XPY5S.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-WX5NZZJR.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  saveDevState,
32
32
  sessionHasMeaningfulContent,
33
33
  setupProxy
34
- } from "./chunk-3YT2DUUT.js";
34
+ } from "./chunk-IZK6GNT4.js";
35
35
  import {
36
36
  ToolExecutor,
37
37
  ToolRegistry,
@@ -47,7 +47,7 @@ import {
47
47
  spawnAgentContext,
48
48
  theme,
49
49
  undoStack
50
- } from "./chunk-IVTWWDWZ.js";
50
+ } from "./chunk-UHZ6YANH.js";
51
51
  import {
52
52
  fileCheckpoints
53
53
  } from "./chunk-4BKXL7SM.js";
@@ -72,7 +72,7 @@ import {
72
72
  SKILLS_DIR_NAME,
73
73
  VERSION,
74
74
  buildUserIdentityPrompt
75
- } from "./chunk-ND3O5NQU.js";
75
+ } from "./chunk-6OKAZIY7.js";
76
76
 
77
77
  // src/index.ts
78
78
  import { program } from "commander";
@@ -2267,7 +2267,7 @@ ${hint}` : "")
2267
2267
  usage: "/test [command|filter]",
2268
2268
  async execute(args, ctx) {
2269
2269
  try {
2270
- const { executeTests } = await import("./run-tests-NPRCZYN3.js");
2270
+ const { executeTests } = await import("./run-tests-A42NM2XQ.js");
2271
2271
  const argStr = args.join(" ").trim();
2272
2272
  let testArgs = {};
2273
2273
  if (argStr) {
@@ -6139,7 +6139,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6139
6139
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6140
6140
  process.exit(1);
6141
6141
  }
6142
- const { startWebServer } = await import("./server-WYL3OD5N.js");
6142
+ const { startWebServer } = await import("./server-BOAYC5O3.js");
6143
6143
  await startWebServer({ port, host: options.host });
6144
6144
  });
6145
6145
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6372,7 +6372,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
6372
6372
  }),
6373
6373
  config.get("customProviders")
6374
6374
  );
6375
- const { startHub } = await import("./hub-BGO4X72R.js");
6375
+ const { startHub } = await import("./hub-DUCBBK3Y.js");
6376
6376
  await startHub(
6377
6377
  {
6378
6378
  topic: topic ?? "",
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-ND3O5NQU.js";
5
+ } from "./chunk-6OKAZIY7.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-YJKPARSH.js";
4
+ } from "./chunk-6QRHSSJB.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -21,7 +21,7 @@ import {
21
21
  persistToolRound,
22
22
  rebuildExtraMessages,
23
23
  setupProxy
24
- } from "./chunk-3YT2DUUT.js";
24
+ } from "./chunk-IZK6GNT4.js";
25
25
  import {
26
26
  AuthManager
27
27
  } from "./chunk-BYNY5JPB.js";
@@ -42,7 +42,7 @@ import {
42
42
  spawnAgentContext,
43
43
  truncateOutput,
44
44
  undoStack
45
- } from "./chunk-IVTWWDWZ.js";
45
+ } from "./chunk-UHZ6YANH.js";
46
46
  import "./chunk-4BKXL7SM.js";
47
47
  import {
48
48
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -62,7 +62,7 @@ import {
62
62
  SKILLS_DIR_NAME,
63
63
  VERSION,
64
64
  buildUserIdentityPrompt
65
- } from "./chunk-ND3O5NQU.js";
65
+ } from "./chunk-6OKAZIY7.js";
66
66
 
67
67
  // src/web/server.ts
68
68
  import express from "express";
@@ -1948,7 +1948,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1948
1948
  case "test": {
1949
1949
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1950
1950
  try {
1951
- const { executeTests } = await import("./run-tests-NPRCZYN3.js");
1951
+ const { executeTests } = await import("./run-tests-A42NM2XQ.js");
1952
1952
  const argStr = args.join(" ").trim();
1953
1953
  let testArgs = {};
1954
1954
  if (argStr) {
@@ -3010,6 +3010,26 @@ async function startWebServer(options = {}) {
3010
3010
  res.json({ sessions: [] });
3011
3011
  }
3012
3012
  });
3013
+ app.get("/api/sessions/:id/replay", requireAuth, (req, res) => {
3014
+ const id = req.params.id;
3015
+ if (!/^[a-f0-9-]{36}$/i.test(id)) {
3016
+ res.status(400).json({ error: "Invalid session id" });
3017
+ return;
3018
+ }
3019
+ try {
3020
+ const authUser = req._authUser;
3021
+ const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
3022
+ const filePath = join3(histDir, `${id}.json`);
3023
+ if (!existsSync4(filePath)) {
3024
+ res.status(404).json({ error: "Session not found" });
3025
+ return;
3026
+ }
3027
+ const data = JSON.parse(readFileSync4(filePath, "utf-8"));
3028
+ res.json({ session: data });
3029
+ } catch (err) {
3030
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
3031
+ }
3032
+ });
3013
3033
  app.get("/api/file-content", requireAuth, (req, res) => {
3014
3034
  const filePath = req.query.path;
3015
3035
  if (!filePath) {
@@ -4,11 +4,11 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-IVTWWDWZ.js";
7
+ } from "./chunk-UHZ6YANH.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import {
10
10
  SUBAGENT_ALLOWED_TOOLS
11
- } from "./chunk-ND3O5NQU.js";
11
+ } from "./chunk-6OKAZIY7.js";
12
12
 
13
13
  // src/hub/task-orchestrator.ts
14
14
  import { createInterface } from "readline";
@@ -965,7 +965,7 @@ function renderFilteredSessions(filter) {
965
965
  <div class="flex items-center gap-1">
966
966
  ${checkbox}
967
967
  <div class="session-title flex-1">${escapeHtml(title)}</div>
968
- ${batchSelectMode ? '' : `<button class="session-delete-btn opacity-0 hover:opacity-100 text-error text-xs px-1 flex-shrink-0" data-delete-id="${s.id}" title="Delete session">&times;</button>`}
968
+ ${batchSelectMode ? '' : `<button class="session-replay-btn opacity-0 hover:opacity-100 text-xs px-1 flex-shrink-0" data-replay-id="${s.id}" title="Replay session">🎬</button><button class="session-delete-btn opacity-0 hover:opacity-100 text-error text-xs px-1 flex-shrink-0" data-delete-id="${s.id}" title="Delete session">&times;</button>`}
969
969
  </div>
970
970
  <div class="session-meta">${s.messageCount} msgs · ${timeStr}</div>
971
971
  </div>`;
@@ -976,7 +976,7 @@ function renderFilteredSessions(filter) {
976
976
  let clickTimer = null;
977
977
 
978
978
  el.addEventListener('click', (e) => {
979
- if (e.target.closest('.session-delete-btn') || e.target.closest('.session-batch-cb') || e.target.closest('.session-rename-input')) return;
979
+ if (e.target.closest('.session-delete-btn') || e.target.closest('.session-replay-btn') || e.target.closest('.session-batch-cb') || e.target.closest('.session-rename-input')) return;
980
980
  if (batchSelectMode) {
981
981
  const cb = el.querySelector('.session-batch-cb');
982
982
  if (cb) { cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); }
@@ -1032,9 +1032,121 @@ function renderFilteredSessions(filter) {
1032
1032
  }
1033
1033
  });
1034
1034
  });
1035
+
1036
+ sessionListEl.querySelectorAll('.session-replay-btn').forEach(btn => {
1037
+ btn.addEventListener('click', (e) => {
1038
+ e.stopPropagation();
1039
+ const id = btn.dataset.replayId;
1040
+ if (id) openReplay(id);
1041
+ });
1042
+ });
1035
1043
  }
1036
1044
  }
1037
1045
 
1046
+ // ── Session Replay (B1) ─────────────────────────────────
1047
+ async function openReplay(sessionId) {
1048
+ const modal = document.getElementById('replay-modal');
1049
+ const metaEl = document.getElementById('replay-meta');
1050
+ const usageEl = document.getElementById('replay-usage');
1051
+ const timelineEl = document.getElementById('replay-timeline');
1052
+ if (!modal || !metaEl || !usageEl || !timelineEl) return;
1053
+
1054
+ metaEl.textContent = 'Loading…';
1055
+ usageEl.textContent = '';
1056
+ timelineEl.innerHTML = '';
1057
+ modal.showModal();
1058
+
1059
+ try {
1060
+ const headers = {};
1061
+ if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
1062
+ const resp = await fetch('/api/sessions/' + encodeURIComponent(sessionId) + '/replay', { headers });
1063
+ if (!resp.ok) {
1064
+ const body = await resp.json().catch(() => ({}));
1065
+ metaEl.textContent = 'Failed: ' + (body.error || resp.status);
1066
+ return;
1067
+ }
1068
+ const { session } = await resp.json();
1069
+ renderReplay(session, metaEl, usageEl, timelineEl);
1070
+ } catch (err) {
1071
+ metaEl.textContent = 'Failed to load: ' + (err && err.message ? err.message : err);
1072
+ }
1073
+ }
1074
+
1075
+ function renderReplay(session, metaEl, usageEl, timelineEl) {
1076
+ const created = session.created ? new Date(session.created).toLocaleString() : '-';
1077
+ const updated = session.updated ? new Date(session.updated).toLocaleString() : '-';
1078
+ const title = session.title || 'Untitled';
1079
+ metaEl.innerHTML =
1080
+ `<div><b>${escapeHtml(title)}</b></div>` +
1081
+ `<div>${escapeHtml(session.provider || '?')} / ${escapeHtml(session.model || '?')} · ${escapeHtml(session.id || '')}</div>` +
1082
+ `<div>Created ${created} · Updated ${updated} · ${(session.messages || []).length} messages</div>`;
1083
+
1084
+ const tu = session.tokenUsage || {};
1085
+ const total = (tu.inputTokens || 0) + (tu.outputTokens || 0);
1086
+ usageEl.innerHTML =
1087
+ `<span class="badge badge-sm badge-ghost">total ${total}</span> ` +
1088
+ `<span class="badge badge-sm badge-ghost">in ${tu.inputTokens || 0}</span> ` +
1089
+ `<span class="badge badge-sm badge-ghost">out ${tu.outputTokens || 0}</span> ` +
1090
+ `<span class="badge badge-sm badge-ghost">cache-write ${tu.cacheCreationTokens || 0}</span> ` +
1091
+ `<span class="badge badge-sm badge-ghost">cache-read ${tu.cacheReadTokens || 0}</span>`;
1092
+
1093
+ const messages = Array.isArray(session.messages) ? session.messages : [];
1094
+ timelineEl.innerHTML = messages.map((m, i) => renderReplayStep(m, i)).join('');
1095
+ }
1096
+
1097
+ function renderReplayStep(m, idx) {
1098
+ const role = m.role || 'user';
1099
+ const ts = m.timestamp ? new Date(m.timestamp).toLocaleTimeString() : '';
1100
+ const isError = !!m.isError;
1101
+ const cls = 'replay-step role-' + role + (isError ? ' error' : '');
1102
+ const roleTag = role + (m.toolName ? ' · ' + m.toolName : '') + (isError ? ' · ERROR' : '');
1103
+
1104
+ // Extract text content
1105
+ let text = '';
1106
+ if (typeof m.content === 'string') {
1107
+ text = m.content;
1108
+ } else if (Array.isArray(m.content)) {
1109
+ text = m.content
1110
+ .map(p => p && p.type === 'text' ? (p.text || '') : (p && p.type === 'image_url' ? '[image]' : ''))
1111
+ .join('');
1112
+ }
1113
+
1114
+ let body = '';
1115
+ if (text) {
1116
+ const isTool = role === 'tool';
1117
+ const bodyCls = isTool ? 'replay-step-body' : 'replay-step-body text-body';
1118
+ body += `<div class="${bodyCls}">${escapeHtml(text)}</div>`;
1119
+ }
1120
+
1121
+ // Tool calls (assistant asking to invoke tools)
1122
+ if (Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
1123
+ body += m.toolCalls.map(tc => {
1124
+ const name = tc.name || tc.function?.name || '(unknown)';
1125
+ let args = tc.arguments ?? tc.function?.arguments ?? {};
1126
+ if (typeof args === 'string') {
1127
+ try { args = JSON.parse(args); } catch { /* keep as string */ }
1128
+ }
1129
+ const argsStr = typeof args === 'string' ? args : JSON.stringify(args, null, 2);
1130
+ return `<div class="replay-tool-block"><div><span class="tool-name">→ ${escapeHtml(name)}</span> <span class="opacity-60">${escapeHtml(tc.id || '')}</span></div><pre>${escapeHtml(argsStr)}</pre></div>`;
1131
+ }).join('');
1132
+ }
1133
+
1134
+ // Reasoning content (thinking models)
1135
+ if (m.reasoningContent) {
1136
+ body += `<details class="replay-tool-block"><summary class="opacity-70">💭 reasoning</summary><pre>${escapeHtml(m.reasoningContent)}</pre></details>`;
1137
+ }
1138
+
1139
+ return `<div class="${cls}">
1140
+ <div class="replay-step-header">
1141
+ <span class="opacity-50">#${idx + 1}</span>
1142
+ <span class="role-tag">${escapeHtml(roleTag)}</span>
1143
+ <span class="opacity-50">${ts}</span>
1144
+ ${m.toolCallId ? `<span class="opacity-40">↳ ${escapeHtml(m.toolCallId)}</span>` : ''}
1145
+ </div>
1146
+ ${body || '<div class="opacity-40 text-xs">(empty)</div>'}
1147
+ </div>`;
1148
+ }
1149
+
1038
1150
  function startSessionRename(itemEl, titleEl) {
1039
1151
  const sessionId = itemEl.dataset.sessionId;
1040
1152
  const currentTitle = titleEl.textContent.trim();
@@ -249,6 +249,20 @@
249
249
  <form method="dialog" class="modal-backdrop"><button>close</button></form>
250
250
  </dialog>
251
251
 
252
+ <!-- ── Session Replay Modal (B1) ───────────────────────── -->
253
+ <dialog id="replay-modal" class="modal">
254
+ <div class="modal-box max-w-5xl bg-base-200 w-11/12">
255
+ <div class="flex items-center justify-between mb-3">
256
+ <h3 class="font-bold text-lg">🎬 Session Replay</h3>
257
+ <form method="dialog"><button class="btn btn-sm btn-ghost">✕</button></form>
258
+ </div>
259
+ <div id="replay-meta" class="text-xs opacity-70 mb-2"></div>
260
+ <div id="replay-usage" class="text-xs mb-3"></div>
261
+ <div id="replay-timeline" class="flex flex-col gap-2 max-h-[70vh] overflow-y-auto pr-2"></div>
262
+ </div>
263
+ <form method="dialog" class="modal-backdrop"><button>close</button></form>
264
+ </dialog>
265
+
252
266
  <script src="app.js"></script>
253
267
  <script>
254
268
  if ('serviceWorker' in navigator) {
@@ -845,3 +845,61 @@ button, a, .session-item, .file-tree-row, .template-item, .tool-item, .mcp-serve
845
845
  @media (display-mode: standalone) {
846
846
  .navbar { padding-top: env(safe-area-inset-top, 0px); }
847
847
  }
848
+
849
+ /* ── Session Replay (B1) ───────────────────────────── */
850
+ .replay-step {
851
+ border-left: 3px solid hsl(var(--b3));
852
+ padding: 0.5rem 0.6rem;
853
+ background: hsl(var(--b1));
854
+ border-radius: 0 0.35rem 0.35rem 0;
855
+ font-size: 0.85rem;
856
+ }
857
+ .replay-step.role-user { border-left-color: #3b82f6; }
858
+ .replay-step.role-assistant { border-left-color: #10b981; }
859
+ .replay-step.role-tool { border-left-color: #f59e0b; }
860
+ .replay-step.role-tool.error { border-left-color: #ef4444; }
861
+ .replay-step-header {
862
+ display: flex;
863
+ gap: 0.5rem;
864
+ align-items: center;
865
+ font-size: 0.72rem;
866
+ opacity: 0.75;
867
+ margin-bottom: 0.25rem;
868
+ }
869
+ .replay-step-header .role-tag {
870
+ font-weight: 600;
871
+ padding: 0 0.35rem;
872
+ border-radius: 0.25rem;
873
+ background: hsl(var(--b3));
874
+ }
875
+ .replay-step-body {
876
+ white-space: pre-wrap;
877
+ word-break: break-word;
878
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
879
+ font-size: 0.78rem;
880
+ max-height: 18rem;
881
+ overflow-y: auto;
882
+ }
883
+ .replay-step-body.text-body {
884
+ font-family: inherit;
885
+ font-size: 0.85rem;
886
+ }
887
+ .replay-tool-block {
888
+ margin-top: 0.3rem;
889
+ padding: 0.4rem;
890
+ background: hsl(var(--b2));
891
+ border-radius: 0.3rem;
892
+ font-size: 0.78rem;
893
+ }
894
+ .replay-tool-block .tool-name {
895
+ font-weight: 600;
896
+ color: #f59e0b;
897
+ }
898
+ .replay-tool-block pre {
899
+ margin: 0.2rem 0 0;
900
+ white-space: pre-wrap;
901
+ word-break: break-word;
902
+ font-size: 0.72rem;
903
+ max-height: 12rem;
904
+ overflow-y: auto;
905
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.70",
3
+ "version": "0.4.71",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",