jinzd-ai-cli 0.4.278 → 0.4.279

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 (34) hide show
  1. package/dist/{batch-7Y4NAA6N.js → batch-6QZSWCBL.js} +2 -2
  2. package/dist/{chunk-X3722RFA.js → chunk-2BKRWTEQ.js} +1 -1
  3. package/dist/{chunk-SJCOE4C7.js → chunk-5XHEXRMN.js} +1 -1
  4. package/dist/{chunk-RFSKPCYC.js → chunk-6WMT7OQG.js} +1 -1
  5. package/dist/{chunk-JJCNNQTU.js → chunk-7L4TEK7Q.js} +239 -3
  6. package/dist/{chunk-LXXQTWT2.js → chunk-CMNXAJLH.js} +1 -1
  7. package/dist/{chunk-WOXIKQRI.js → chunk-FQJFGGKN.js} +4 -4
  8. package/dist/{chunk-L3CPM472.js → chunk-IIXZ7IPN.js} +1 -1
  9. package/dist/{chunk-LTZFUYBT.js → chunk-JLOQBYU4.js} +3 -3
  10. package/dist/{chunk-4Z6GAYB5.js → chunk-JS6VAPWM.js} +3 -3
  11. package/dist/{chunk-SB4XONS4.js → chunk-NZPHEUJM.js} +1 -1
  12. package/dist/{chunk-H7QC5VMQ.js → chunk-RJY3HJS4.js} +1 -1
  13. package/dist/{chunk-LOXEYWC3.js → chunk-SIVM2YQQ.js} +1 -1
  14. package/dist/{chunk-2BYQP6JX.js → chunk-UD3BVNC5.js} +1 -1
  15. package/dist/{ci-WDF2CYR4.js → ci-F4ESOYI5.js} +4 -4
  16. package/dist/{ci-format-7B5VAZGA.js → ci-format-AIRW3TVQ.js} +2 -2
  17. package/dist/{constants-DC3TNLTL.js → constants-3VMMZJDS.js} +1 -1
  18. package/dist/{doctor-cli-CDDG5XOY.js → doctor-cli-WVMEWMYE.js} +4 -4
  19. package/dist/electron-server.js +398 -58
  20. package/dist/{hub-TO7ODOIA.js → hub-JJCCCU3O.js} +1 -1
  21. package/dist/index.js +112 -323
  22. package/dist/{persistent-memory-BU63CSGK.js → persistent-memory-KSZKM4JG.js} +2 -2
  23. package/dist/{persistent-memory-JCENBMCJ.js → persistent-memory-QZN4Q3W4.js} +2 -2
  24. package/dist/{pr-GVSZYX6I.js → pr-GFB5JC26.js} +4 -4
  25. package/dist/{run-tests-R4IQS6IF.js → run-tests-EL54INOI.js} +2 -2
  26. package/dist/{run-tests-2Q5MJHXB.js → run-tests-HMHHIG7Y.js} +2 -2
  27. package/dist/{server-WXLE53HG.js → server-6C7GITHY.js} +5 -5
  28. package/dist/{server-KAURBKFI.js → server-WUSJWC3A.js} +187 -63
  29. package/dist/{task-orchestrator-RJ6FXYY5.js → task-orchestrator-C7RUUA2Q.js} +5 -5
  30. package/dist/{usage-V3R7DA7M.js → usage-CW6BT2UV.js} +2 -2
  31. package/dist/web/client/app.js +14 -2
  32. package/dist/web/client/app.js.br +0 -0
  33. package/dist/web/client/app.js.gz +0 -0
  34. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19,24 +19,33 @@ import {
19
19
  estimateConversationTokens,
20
20
  evaluateContextPressure,
21
21
  filterMemoryEntries,
22
+ formatSessionSummaryLine,
22
23
  loadDevState,
24
+ parseBatchDeleteArgs,
25
+ parseCleanArgs,
23
26
  parseMemoryShowFilters,
24
27
  parseModelCommand,
25
28
  parseRouteCommand,
29
+ parseSessionListArgs,
26
30
  parseSimpleYaml,
27
31
  persistToolRound,
28
32
  readGitDiff,
29
33
  resolveModelSwitch,
30
34
  resolveProviderSwitch,
35
+ resolveSessionRef,
31
36
  routeDecisionRows,
32
37
  routingEmptyRulesNote,
33
38
  saveDevState,
34
39
  scanProject,
40
+ selectSessions,
41
+ selectStaleSessions,
35
42
  sessionHasMeaningfulContent,
43
+ sessionListJsonRow,
44
+ sessionListSummary,
36
45
  setupProxy,
37
46
  snapshotFile,
38
47
  stripRoutingTags
39
- } from "./chunk-JJCNNQTU.js";
48
+ } from "./chunk-7L4TEK7Q.js";
40
49
  import {
41
50
  CostTracker,
42
51
  computeCost,
@@ -66,7 +75,7 @@ import {
66
75
  setMaxOutputCap,
67
76
  spawnAgentContext,
68
77
  undoStack
69
- } from "./chunk-WOXIKQRI.js";
78
+ } from "./chunk-FQJFGGKN.js";
70
79
  import {
71
80
  buildReviewPrompt,
72
81
  buildSecurityReviewPrompt
@@ -107,7 +116,7 @@ import {
107
116
  touchMemoryReferences,
108
117
  updateMemoryApproval,
109
118
  updateMemoryEntry
110
- } from "./chunk-RFSKPCYC.js";
119
+ } from "./chunk-6WMT7OQG.js";
111
120
  import "./chunk-4DEMGCK4.js";
112
121
  import "./chunk-UUSRWSSX.js";
113
122
  import "./chunk-CKH4KQ4E.js";
@@ -131,7 +140,7 @@ import {
131
140
  formatDoctorReport,
132
141
  loadContextFiles,
133
142
  writeCrashLog
134
- } from "./chunk-LTZFUYBT.js";
143
+ } from "./chunk-JLOQBYU4.js";
135
144
  import {
136
145
  describePlugin,
137
146
  getActivePluginAssets,
@@ -148,14 +157,14 @@ import {
148
157
  trustHook,
149
158
  trustPlugin,
150
159
  untrustHook
151
- } from "./chunk-L3CPM472.js";
160
+ } from "./chunk-IIXZ7IPN.js";
152
161
  import {
153
162
  ProviderRegistry,
154
163
  listBuiltInProviderMetadata
155
164
  } from "./chunk-UGY5SVNX.js";
156
165
  import {
157
166
  ConfigManager
158
- } from "./chunk-2BYQP6JX.js";
167
+ } from "./chunk-UD3BVNC5.js";
159
168
  import {
160
169
  CONTENT_ONLY_STREAM_REMINDER,
161
170
  TEE_FINAL_USER_NUDGE,
@@ -193,7 +202,7 @@ import {
193
202
  import {
194
203
  fileCheckpoints
195
204
  } from "./chunk-4BKXL7SM.js";
196
- import "./chunk-SB4XONS4.js";
205
+ import "./chunk-NZPHEUJM.js";
197
206
  import {
198
207
  AGENTIC_BEHAVIOR_GUIDELINE,
199
208
  AUTHOR,
@@ -211,7 +220,7 @@ import {
211
220
  SKILLS_DIR_NAME,
212
221
  VERSION,
213
222
  buildUserIdentityPrompt
214
- } from "./chunk-X3722RFA.js";
223
+ } from "./chunk-2BKRWTEQ.js";
215
224
 
216
225
  // src/index.ts
217
226
  import { program } from "commander";
@@ -730,7 +739,6 @@ import { platform } from "os";
730
739
 
731
740
  // src/repl/commands/constants.ts
732
741
  var SESSION_PAGE_SIZE = 20;
733
- var DEFAULT_SESSION_LIST_LIMIT = 50;
734
742
  var MAX_TITLE_WIDTH = 40;
735
743
 
736
744
  // src/repl/commands/utils.ts
@@ -809,73 +817,6 @@ function formatSessionDate(date) {
809
817
  if (diffDays < 7) return `${abs} (${diffDays} days ago)`;
810
818
  return abs;
811
819
  }
812
- function parseSessionListArgs(args) {
813
- let limit = DEFAULT_SESSION_LIST_LIMIT;
814
- let all = false;
815
- const filter = {};
816
- const warnings = [];
817
- let search = null;
818
- let days = null;
819
- let since = null;
820
- let sort = { field: "updated", order: "desc" };
821
- let json = false;
822
- let stale = null;
823
- let verbose = false;
824
- for (let i = 1; i < args.length; i++) {
825
- const a = args[i];
826
- if (a === "--all" || a === "-a") {
827
- all = true;
828
- } else if ((a === "--limit" || a === "-n") && i + 1 < args.length) {
829
- const n = Number(args[i + 1]);
830
- if (Number.isFinite(n) && n > 0) {
831
- limit = n;
832
- i++;
833
- }
834
- } else if (a === "--filter" && i + 1 < args.length) {
835
- const val = args[++i];
836
- const eq = val.indexOf("=");
837
- if (eq > 0) {
838
- const key = val.slice(0, eq);
839
- const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
840
- if (key === "provider") filter.provider = vals;
841
- else if (key === "model") filter.model = vals;
842
- else if (key === "messages") {
843
- const n = Number(vals[0]);
844
- if (Number.isFinite(n) && n >= 0) filter.messages = n;
845
- else warnings.push(`Invalid filter value for "messages": expected a non-negative integer, got "${vals[0]}"`);
846
- } else {
847
- warnings.push(`Unknown filter key: "${key}". Supported keys: provider, model, messages`);
848
- }
849
- } else {
850
- warnings.push(`Invalid --filter syntax: "${val}". Expected key=value`);
851
- }
852
- } else if ((a === "--search" || a === "-s") && i + 1 < args.length) {
853
- search = args[++i];
854
- } else if (a === "--days" && i + 1 < args.length) {
855
- const n = Number(args[++i]);
856
- if (Number.isFinite(n) && n > 0) days = n;
857
- } else if (a === "--since" && i + 1 < args.length) {
858
- since = args[++i];
859
- } else if (a === "--sort" && i + 1 < args.length) {
860
- const sv = args[++i];
861
- const colon = sv.indexOf(":");
862
- const field = colon > 0 ? sv.slice(0, colon) : sv;
863
- const order = colon > 0 ? sv.slice(colon + 1) : "desc";
864
- if (field === "updated" || field === "created" || field === "messages" || field === "size") {
865
- sort = { field, order: order === "asc" ? "asc" : "desc" };
866
- }
867
- } else if (a === "--json") {
868
- json = true;
869
- } else if (a === "--verbose" || a === "-v") {
870
- verbose = true;
871
- } else if (a === "--stale" && i + 1 < args.length) {
872
- const n = Number(args[++i]);
873
- if (Number.isFinite(n) && n > 0) stale = n;
874
- }
875
- }
876
- if (all) limit = -1;
877
- return { limit, all, filter, search, days, since, sort, json, stale, verbose, warnings };
878
- }
879
820
  function renderTranscriptMessage(msg, renderer) {
880
821
  if (msg.role === "user") {
881
822
  const text = getContentText(msg.content);
@@ -1397,7 +1338,7 @@ No tools match "${filter}".
1397
1338
  const { join: join6 } = await import("path");
1398
1339
  const { existsSync: existsSync6 } = await import("fs");
1399
1340
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1400
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-DC3TNLTL.js");
1341
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-3VMMZJDS.js");
1401
1342
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1402
1343
  const cwd = process.cwd();
1403
1344
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2317,7 +2258,7 @@ function createReviewCommands() {
2317
2258
  usage: "/test [command|filter]",
2318
2259
  async execute(args, ctx) {
2319
2260
  try {
2320
- const { executeTests } = await import("./run-tests-R4IQS6IF.js");
2261
+ const { executeTests } = await import("./run-tests-EL54INOI.js");
2321
2262
  const argStr = args.join(" ").trim();
2322
2263
  let testArgs = {};
2323
2264
  if (argStr) {
@@ -3081,7 +3022,7 @@ function createSessionCommands() {
3081
3022
  {
3082
3023
  name: "session",
3083
3024
  description: "Session management",
3084
- usage: "/session new|list [--limit N|--all] [--filter provider=|model=|messages=N] [--search kw] [--days N|--stale N] [--sort f[:o]] [--json] [--verbose]|load <id>|show <id> [page]|page <n>|remove <id>|clean --stale N [--dry-run]",
3025
+ usage: "/session new|list [--limit N|--all] [--filter provider=|model=|messages=N] [--search kw] [--days N|--stale N] [--sort f[:o]] [--json] [--verbose]|load <id>|show <id> [page]|page <n>|rename <id> <title>|remove <id>|clean --stale N [--dry-run]",
3085
3026
  async execute(args, ctx) {
3086
3027
  const sub = args[0];
3087
3028
  if (sub === "new") {
@@ -3093,139 +3034,44 @@ function createSessionCommands() {
3093
3034
  ctx.renderer.printSuccess("New session started.");
3094
3035
  } else if (sub === "list") {
3095
3036
  const opts = parseSessionListArgs(args);
3096
- for (const w of opts.warnings) {
3097
- ctx.renderer.printInfo(`\u26A0 ${w}`);
3098
- }
3099
- const needFileSize = opts.verbose || opts.sort.field === "size";
3100
- let allSessions = ctx.sessions.listSessions({ includeFileSize: needFileSize });
3101
- if (allSessions.length === 0) {
3037
+ for (const w of opts.warnings) ctx.renderer.printInfo(`\u26A0 ${w}`);
3038
+ const all = ctx.sessions.listSessions({ includeFileSize: opts.verbose || opts.sort.field === "size" });
3039
+ if (all.length === 0) {
3102
3040
  ctx.renderer.printInfo("No saved sessions.");
3103
3041
  return;
3104
3042
  }
3105
- const hasFilters = opts.filter.provider || opts.filter.model || opts.filter.messages !== void 0;
3106
- const hasSearch = !!opts.search;
3107
- const hasTimeFilter = opts.days !== null || opts.since !== null;
3108
- const hasStale = opts.stale !== null;
3109
- if (hasFilters) {
3110
- const p = opts.filter.provider;
3111
- const m = opts.filter.model;
3112
- const msg = opts.filter.messages;
3113
- allSessions = allSessions.filter((s) => {
3114
- if (p && !p.includes(s.provider.toLowerCase())) return false;
3115
- if (m && !m.some((kw) => s.model.toLowerCase().includes(kw))) return false;
3116
- if (msg !== void 0 && s.messageCount !== msg) return false;
3117
- return true;
3118
- });
3119
- }
3120
- if (hasSearch) {
3121
- const q = opts.search.toLowerCase();
3122
- allSessions = allSessions.filter(
3123
- (s) => (s.title ?? "").toLowerCase().includes(q) || s.id.toLowerCase().includes(q)
3124
- );
3125
- }
3126
- if (hasTimeFilter) {
3127
- const now = Date.now();
3128
- let cutoff = 0;
3129
- if (opts.since) {
3130
- cutoff = new Date(opts.since).getTime();
3131
- } else if (opts.days !== null) {
3132
- cutoff = now - opts.days * 24 * 60 * 60 * 1e3;
3133
- }
3134
- if (!isNaN(cutoff)) {
3135
- allSessions = allSessions.filter((s) => s.updated.getTime() >= cutoff);
3136
- }
3137
- }
3138
- const staleCutoff = hasStale ? Date.now() - opts.stale * 24 * 60 * 60 * 1e3 : 0;
3139
- if (hasStale) {
3140
- allSessions = allSessions.filter((s) => s.updated.getTime() < staleCutoff);
3141
- }
3142
- const defaultSort = opts.sort.field === "updated" && opts.sort.order === "desc";
3143
- if (!defaultSort) {
3144
- const mul = opts.sort.order === "asc" ? 1 : -1;
3145
- allSessions = [...allSessions].sort((a, b) => {
3146
- switch (opts.sort.field) {
3147
- case "created":
3148
- return (a.created.getTime() - b.created.getTime()) * mul;
3149
- case "messages":
3150
- return (a.messageCount - b.messageCount) * mul;
3151
- case "size":
3152
- return ((a.fileSize ?? 0) - (b.fileSize ?? 0)) * mul;
3153
- default:
3154
- return (a.updated.getTime() - b.updated.getTime()) * mul;
3155
- }
3156
- });
3157
- }
3158
- const totalCount = allSessions.length;
3159
- if (totalCount === 0) {
3043
+ const view = selectSessions(all, opts);
3044
+ if (view.totalCount === 0) {
3160
3045
  ctx.renderer.printInfo("No sessions match the filter criteria.");
3161
3046
  return;
3162
3047
  }
3163
3048
  if (opts.json) {
3164
- const sliced2 = opts.limit > 0 ? allSessions.slice(0, opts.limit) : allSessions;
3165
- const payload = sliced2.map((s) => ({
3166
- id: s.id,
3167
- title: s.title ?? null,
3168
- provider: s.provider,
3169
- model: s.model,
3170
- messageCount: s.messageCount,
3171
- created: s.created.toISOString(),
3172
- updated: s.updated.toISOString(),
3173
- ...needFileSize ? { fileSize: s.fileSize } : {},
3174
- ...s.branchCount !== void 0 ? { branchCount: s.branchCount, activeBranch: s.activeBranch } : {}
3175
- }));
3176
- console.log(JSON.stringify(payload, null, 2));
3177
- if (opts.limit > 0 && totalCount > opts.limit) {
3049
+ console.log(JSON.stringify(view.shown.map((s) => sessionListJsonRow(s, view.needsFileSize)), null, 2));
3050
+ if (view.shown.length < view.totalCount) {
3178
3051
  console.error(
3179
3052
  `
3180
- (truncated: ${opts.limit} of ${totalCount} sessions shown; use --all for full list)`
3053
+ (truncated: ${view.shown.length} of ${view.totalCount} sessions shown; use --all for full list)`
3181
3054
  );
3182
3055
  }
3183
3056
  return;
3184
3057
  }
3185
- const sliced = opts.limit > 0 ? allSessions.slice(0, opts.limit) : allSessions;
3186
3058
  const headers = ["ID (short)", "Provider", "Model", "Messages"];
3187
- if (opts.verbose) {
3188
- headers.push("Size");
3189
- headers.push("Br");
3190
- }
3059
+ if (opts.verbose) headers.push("Size", "Br");
3191
3060
  headers.push("Date", "Title");
3192
- const rows = sliced.map((s) => {
3193
- const isStale = hasStale ? s.updated.getTime() < staleCutoff : false;
3061
+ const rows = view.shown.map((s) => {
3062
+ const isStale = view.staleCutoff !== null && s.updated.getTime() < view.staleCutoff;
3194
3063
  const dim = isStale ? theme.dim : (x) => String(x);
3195
- const row = [
3196
- dim(s.id.slice(0, 8)),
3197
- dim(s.provider),
3198
- dim(s.model),
3199
- s.messageCount
3200
- ];
3064
+ const row = [dim(s.id.slice(0, 8)), dim(s.provider), dim(s.model), s.messageCount];
3201
3065
  if (opts.verbose) {
3202
- row.push(
3203
- s.fileSize !== void 0 ? formatFileSize(s.fileSize) : "\u2014",
3204
- s.branchCount ?? 1
3205
- );
3066
+ row.push(s.fileSize !== void 0 ? formatFileSize(s.fileSize) : "\u2014", s.branchCount ?? 1);
3206
3067
  }
3207
- row.push(
3208
- formatSessionDate(s.updated),
3209
- dim(truncateTitle(s.title ?? "(untitled)", MAX_TITLE_WIDTH))
3210
- );
3068
+ row.push(formatSessionDate(s.updated), dim(truncateTitle(s.title ?? "(untitled)", MAX_TITLE_WIDTH)));
3211
3069
  return row;
3212
3070
  });
3213
3071
  console.log();
3214
3072
  ctx.renderer.printTable(headers, rows);
3215
- const totalMessages = allSessions.reduce((sum, s) => sum + s.messageCount, 0);
3216
- const shown = sliced.length;
3217
- const filterHints = [];
3218
- if (hasFilters) filterHints.push("filtered");
3219
- if (hasSearch) filterHints.push("searched");
3220
- if (hasTimeFilter) filterHints.push("time-limited");
3221
- if (hasStale) filterHints.push(`stale >${opts.stale}d`);
3222
- const filterTag = filterHints.length > 0 ? ` (${filterHints.join(", ")})` : "";
3223
- console.log(
3224
- theme.dim(
3225
- ` \u2500\u2500 ${shown} of ${totalCount} sessions${filterTag} \xB7 ${totalMessages} messages total`
3226
- )
3227
- );
3228
- if (shown < totalCount) {
3073
+ console.log(theme.dim(` \u2500\u2500 ${sessionListSummary(view)}`));
3074
+ if (view.shown.length < view.totalCount) {
3229
3075
  console.log(theme.dim(` Use --all to show all, or --limit N to change count.`));
3230
3076
  }
3231
3077
  } else if (sub === "load" || sub === "show") {
@@ -3234,19 +3080,18 @@ function createSessionCommands() {
3234
3080
  ctx.renderer.renderError(`Usage: /session ${sub} <id>${sub === "show" ? " [page]" : ""}`);
3235
3081
  return;
3236
3082
  }
3237
- const sessions = ctx.sessions.listSessions();
3238
- const matches = sessions.filter((s) => s.id.startsWith(id));
3239
- if (matches.length === 0) {
3240
- ctx.renderer.renderError(`Session '${id}' not found.`);
3083
+ const ref = resolveSessionRef(ctx.sessions.listSessions(), id, "most-recent");
3084
+ if (ref.status !== "ok") {
3085
+ ctx.renderer.renderError(ref.message);
3241
3086
  return;
3242
3087
  }
3243
- if (matches.length > 1) {
3244
- console.log(theme.warning(` \u26A0 Ambiguous prefix '${id}' matches ${matches.length} sessions \u2014 loading most recent:`));
3245
- for (const m of matches.slice(0, 5)) {
3088
+ if (ref.message) {
3089
+ console.log(theme.warning(` ${ref.message}`));
3090
+ for (const m of ref.ambiguousWith.slice(0, 5)) {
3246
3091
  console.log(theme.dim(` ${m.id.slice(0, 12)} ${m.title ?? "(untitled)"}`));
3247
3092
  }
3248
3093
  }
3249
- const match = matches[0];
3094
+ const match = ref.match;
3250
3095
  ctx.sessions.loadSession(match.id);
3251
3096
  ctx.setProvider(match.provider, match.model);
3252
3097
  ctx.resetSessionTokenUsage();
@@ -3270,70 +3115,54 @@ function createSessionCommands() {
3270
3115
  return;
3271
3116
  }
3272
3117
  renderTranscriptPage(cur.messages, ctx.renderer, n);
3118
+ } else if (sub === "rename") {
3119
+ const id = args[1];
3120
+ const newTitle = args.slice(2).join(" ").trim();
3121
+ if (!id || !newTitle) {
3122
+ ctx.renderer.renderError("Usage: /session rename <id> <new title>");
3123
+ return;
3124
+ }
3125
+ const ref = resolveSessionRef(ctx.sessions.listSessions(), id, "strict");
3126
+ if (ref.status !== "ok") {
3127
+ ctx.renderer.renderError(ref.message);
3128
+ if (ref.status === "ambiguous") {
3129
+ for (const m of ref.matches.slice(0, 10)) console.log(theme.dim(` ${formatSessionSummaryLine(m)}`));
3130
+ }
3131
+ return;
3132
+ }
3133
+ const priorId = ctx.sessions.current?.id ?? null;
3134
+ const isCurrent = priorId === ref.match.id;
3135
+ const session = isCurrent ? ctx.sessions.current : ctx.sessions.loadSession(ref.match.id);
3136
+ session.title = newTitle;
3137
+ await ctx.sessions.save();
3138
+ if (!isCurrent && priorId) {
3139
+ ctx.sessions.loadSession(priorId);
3140
+ }
3141
+ ctx.renderer.printSuccess(`Renamed session ${ref.match.id.slice(0, 8)}: "${newTitle}"`);
3273
3142
  } else if (sub === "remove" || sub === "delete") {
3274
3143
  if (args[1] === "--filter") {
3275
- let filterMessages;
3276
- let dryRun = false;
3277
- for (let i = 1; i < args.length; i++) {
3278
- if (args[i] === "--filter" && i + 1 < args.length) {
3279
- const val = args[++i];
3280
- const eq = val.indexOf("=");
3281
- if (eq > 0) {
3282
- const key = val.slice(0, eq);
3283
- const vals = val.slice(eq + 1).split(",").map((s) => s.trim().toLowerCase());
3284
- if (key === "messages") {
3285
- const n = Number(vals[0]);
3286
- if (Number.isFinite(n) && n >= 0) filterMessages = n;
3287
- else {
3288
- ctx.renderer.renderError(`Invalid filter value for "messages": expected a non-negative integer, got "${vals[0]}"`);
3289
- return;
3290
- }
3291
- } else {
3292
- ctx.renderer.renderError(`Unsupported filter key for remove: "${key}". Only "messages" is supported.`);
3293
- return;
3294
- }
3295
- } else {
3296
- ctx.renderer.renderError(`Invalid --filter syntax: "${val}". Expected key=value (e.g., messages=0)`);
3297
- return;
3298
- }
3299
- } else if (args[i] === "--dry-run") {
3300
- dryRun = true;
3301
- }
3302
- }
3303
- if (filterMessages === void 0) {
3304
- ctx.renderer.renderError("Usage: /session remove --filter messages=N [--dry-run]");
3144
+ const opts = parseBatchDeleteArgs(args);
3145
+ if (opts.error) {
3146
+ ctx.renderer.renderError(opts.error);
3305
3147
  return;
3306
3148
  }
3307
- const allSessions = ctx.sessions.listSessions();
3308
- const matches2 = allSessions.filter((s) => s.messageCount === filterMessages);
3309
- if (matches2.length === 0) {
3310
- ctx.renderer.printInfo(`No sessions with ${filterMessages} message(s) found.`);
3149
+ const matches = ctx.sessions.listSessions().filter((s) => s.messageCount === opts.messages);
3150
+ if (matches.length === 0) {
3151
+ ctx.renderer.printInfo(`No sessions with ${opts.messages} message(s) found.`);
3311
3152
  return;
3312
3153
  }
3313
3154
  console.log();
3314
- console.log(
3315
- theme.warning(` Found ${matches2.length} session(s) with ${filterMessages} message(s):`)
3316
- );
3317
- for (const s of matches2.slice(0, 20)) {
3318
- console.log(
3319
- theme.dim(
3320
- ` ${s.id.slice(0, 12)} ${s.provider}/${s.model} ${String(s.messageCount).padStart(4)} msgs ${s.updated.toISOString().slice(0, 10)} ${truncateTitle(s.title ?? "(untitled)", 60)}`
3321
- )
3322
- );
3323
- }
3324
- if (matches2.length > 20) {
3325
- console.log(theme.dim(` ... and ${matches2.length - 20} more`));
3326
- }
3327
- if (dryRun) {
3328
- console.log(
3329
- theme.info(`
3330
- --dry-run: no sessions were deleted. Remove --dry-run to actually delete.`)
3331
- );
3155
+ console.log(theme.warning(` Found ${matches.length} session(s) with ${opts.messages} message(s):`));
3156
+ for (const s of matches.slice(0, 20)) console.log(theme.dim(` ${formatSessionSummaryLine(s)}`));
3157
+ if (matches.length > 20) console.log(theme.dim(` ... and ${matches.length - 20} more`));
3158
+ if (opts.dryRun) {
3159
+ console.log(theme.info(`
3160
+ --dry-run: no sessions were deleted. Remove --dry-run to actually delete.`));
3332
3161
  return;
3333
3162
  }
3334
3163
  console.log();
3335
3164
  const choice2 = await ctx.select(
3336
- `Delete all ${matches2.length} matching sessions?`,
3165
+ `Delete all ${matches.length} matching sessions?`,
3337
3166
  [
3338
3167
  { label: "No \u2014 keep them", value: "no" },
3339
3168
  { label: "Yes \u2014 delete permanently", value: "yes", hint: "unlinks all matching history files" }
@@ -3345,17 +3174,12 @@ function createSessionCommands() {
3345
3174
  }
3346
3175
  let deleted = 0;
3347
3176
  let failed = 0;
3348
- for (const s of matches2) {
3349
- if (ctx.sessions.deleteSession(s.id)) {
3350
- deleted++;
3351
- } else {
3352
- failed++;
3353
- }
3177
+ for (const s of matches) {
3178
+ if (ctx.sessions.deleteSession(s.id)) deleted++;
3179
+ else failed++;
3354
3180
  }
3355
3181
  const currentStillExists = ctx.sessions.current !== null;
3356
- ctx.renderer.printSuccess(
3357
- `Removed: ${deleted} deleted${failed > 0 ? `, ${failed} failed` : ""}.`
3358
- );
3182
+ ctx.renderer.printSuccess(`Removed: ${deleted} deleted${failed > 0 ? `, ${failed} failed` : ""}.`);
3359
3183
  if (!currentStillExists) {
3360
3184
  ctx.sessions.createSession(ctx.getCurrentProvider(), ctx.getCurrentModel());
3361
3185
  ctx.resetSessionTokenUsage();
@@ -3369,20 +3193,15 @@ function createSessionCommands() {
3369
3193
  ctx.renderer.renderError("Usage: /session remove <id> | /session remove --filter messages=N [--dry-run]");
3370
3194
  return;
3371
3195
  }
3372
- const sessions = ctx.sessions.listSessions();
3373
- const matches = sessions.filter((s) => s.id.startsWith(id));
3374
- if (matches.length === 0) {
3375
- ctx.renderer.renderError(`Session '${id}' not found.`);
3376
- return;
3377
- }
3378
- if (matches.length > 1) {
3379
- ctx.renderer.renderError(`Ambiguous prefix '${id}' matches ${matches.length} sessions \u2014 be more specific:`);
3380
- for (const m of matches.slice(0, 10)) {
3381
- console.log(theme.dim(` ${m.id.slice(0, 12)} ${String(m.messageCount).padStart(4)} msgs ${m.updated.toLocaleDateString()} ${m.title ?? "(untitled)"}`));
3196
+ const ref = resolveSessionRef(ctx.sessions.listSessions(), id, "strict");
3197
+ if (ref.status !== "ok") {
3198
+ ctx.renderer.renderError(ref.message);
3199
+ if (ref.status === "ambiguous") {
3200
+ for (const m of ref.matches.slice(0, 10)) console.log(theme.dim(` ${formatSessionSummaryLine(m)}`));
3382
3201
  }
3383
3202
  return;
3384
3203
  }
3385
- const match = matches[0];
3204
+ const match = ref.match;
3386
3205
  const isCurrent = ctx.sessions.current?.id === match.id;
3387
3206
  console.log();
3388
3207
  console.log(theme.warning(" About to permanently delete session (no undo):"));
@@ -3409,44 +3228,19 @@ function createSessionCommands() {
3409
3228
  ctx.renderer.printInfo("Current session was deleted \u2014 started a fresh session.");
3410
3229
  }
3411
3230
  } else if (sub === "clean") {
3412
- let staleDays = 90;
3413
- let dryRun = false;
3414
- for (let i = 1; i < args.length; i++) {
3415
- if (args[i] === "--stale" && i + 1 < args.length) {
3416
- const n = Number(args[++i]);
3417
- if (Number.isFinite(n) && n > 0) staleDays = n;
3418
- } else if (args[i] === "--dry-run") {
3419
- dryRun = true;
3420
- }
3421
- }
3422
- const cutoff = Date.now() - staleDays * 24 * 60 * 60 * 1e3;
3423
- const allSessions = ctx.sessions.listSessions();
3424
- const stale = allSessions.filter((s) => s.updated.getTime() < cutoff);
3231
+ const opts = parseCleanArgs(args);
3232
+ const stale = selectStaleSessions(ctx.sessions.listSessions(), opts.staleDays);
3425
3233
  if (stale.length === 0) {
3426
- ctx.renderer.printInfo(`No sessions older than ${staleDays} days found.`);
3234
+ ctx.renderer.printInfo(`No sessions older than ${opts.staleDays} days found.`);
3427
3235
  return;
3428
3236
  }
3429
3237
  console.log();
3430
- console.log(
3431
- theme.warning(
3432
- ` Found ${stale.length} session(s) not updated in ${staleDays}+ days:`
3433
- )
3434
- );
3435
- for (const s of stale.slice(0, 20)) {
3436
- console.log(
3437
- theme.dim(
3438
- ` ${s.id.slice(0, 12)} ${String(s.messageCount).padStart(4)} msgs ${s.updated.toISOString().slice(0, 10)} ${truncateTitle(s.title ?? "(untitled)", 60)}`
3439
- )
3440
- );
3441
- }
3442
- if (stale.length > 20) {
3443
- console.log(theme.dim(` ... and ${stale.length - 20} more`));
3444
- }
3445
- if (dryRun) {
3446
- console.log(
3447
- theme.info(`
3448
- --dry-run: no sessions were deleted. Remove --dry-run to actually delete.`)
3449
- );
3238
+ console.log(theme.warning(` Found ${stale.length} session(s) not updated in ${opts.staleDays}+ days:`));
3239
+ for (const s of stale.slice(0, 20)) console.log(theme.dim(` ${formatSessionSummaryLine(s)}`));
3240
+ if (stale.length > 20) console.log(theme.dim(` ... and ${stale.length - 20} more`));
3241
+ if (opts.dryRun) {
3242
+ console.log(theme.info(`
3243
+ --dry-run: no sessions were deleted. Remove --dry-run to actually delete.`));
3450
3244
  return;
3451
3245
  }
3452
3246
  console.log();
@@ -3464,17 +3258,12 @@ function createSessionCommands() {
3464
3258
  let deleted = 0;
3465
3259
  let failed = 0;
3466
3260
  for (const s of stale) {
3467
- if (ctx.sessions.deleteSession(s.id)) {
3468
- deleted++;
3469
- } else {
3470
- failed++;
3471
- }
3261
+ if (ctx.sessions.deleteSession(s.id)) deleted++;
3262
+ else failed++;
3472
3263
  }
3473
- ctx.renderer.printSuccess(
3474
- `Cleaned up: ${deleted} deleted${failed > 0 ? `, ${failed} failed` : ""}.`
3475
- );
3264
+ ctx.renderer.printSuccess(`Cleaned up: ${deleted} deleted${failed > 0 ? `, ${failed} failed` : ""}.`);
3476
3265
  } else {
3477
- ctx.renderer.renderError("Usage: /session new|list|load <id>|show <id> [page]|page <n>|remove <id>|remove --filter messages=N [--dry-run]|clean --stale N [--dry-run]");
3266
+ ctx.renderer.renderError("Usage: /session new|list|load <id>|show <id> [page]|page <n>|rename <id> <title>|remove <id>|remove --filter messages=N [--dry-run]|clean --stale N [--dry-run]");
3478
3267
  }
3479
3268
  }
3480
3269
  },
@@ -7547,7 +7336,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7547
7336
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7548
7337
  process.exit(1);
7549
7338
  }
7550
- const { startWebServer } = await import("./server-KAURBKFI.js");
7339
+ const { startWebServer } = await import("./server-WUSJWC3A.js");
7551
7340
  await startWebServer({ port, host: options.host });
7552
7341
  });
7553
7342
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7714,11 +7503,11 @@ program.command("sessions").description("List recent conversation sessions").opt
7714
7503
  console.log(footer + "\n");
7715
7504
  });
7716
7505
  program.command("usage").description("Show token + cost usage grouped by provider/model (cross-session)").option("--days <n>", "Only the last N days (inclusive of today)").option("--month <ym>", "Only a specific month, format YYYY-MM (e.g. 2026-06)").option("--json", "Output as JSON (for scripting)").action(async (options) => {
7717
- const { runUsageCli } = await import("./usage-V3R7DA7M.js");
7506
+ const { runUsageCli } = await import("./usage-CW6BT2UV.js");
7718
7507
  await runUsageCli(options);
7719
7508
  });
7720
7509
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7721
- const { runDoctorCli } = await import("./doctor-cli-CDDG5XOY.js");
7510
+ const { runDoctorCli } = await import("./doctor-cli-WVMEWMYE.js");
7722
7511
  const argv = process.argv.slice(2);
7723
7512
  await runDoctorCli({
7724
7513
  json: !!options.json || argv.includes("--json"),
@@ -7727,7 +7516,7 @@ program.command("doctor").description("Health check: API keys, config, MCP, rece
7727
7516
  });
7728
7517
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7729
7518
  try {
7730
- const batch = await import("./batch-7Y4NAA6N.js");
7519
+ const batch = await import("./batch-6QZSWCBL.js");
7731
7520
  switch (action) {
7732
7521
  case "submit":
7733
7522
  if (!arg) {
@@ -7770,7 +7559,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7770
7559
  }
7771
7560
  });
7772
7561
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7773
- const { startMcpServer } = await import("./server-WXLE53HG.js");
7562
+ const { startMcpServer } = await import("./server-6C7GITHY.js");
7774
7563
  await startMcpServer({
7775
7564
  allowDestructive: !!options.allowDestructive,
7776
7565
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7783,7 +7572,7 @@ program.command("pr <action> [target]").description("PR review helpers: review |
7783
7572
  console.error(`Unknown pr action: ${action}. Use review | security-review | summarize.`);
7784
7573
  process.exit(1);
7785
7574
  }
7786
- const { runPrCli } = await import("./pr-GVSZYX6I.js");
7575
+ const { runPrCli } = await import("./pr-GFB5JC26.js");
7787
7576
  const result = await runPrCli({
7788
7577
  action,
7789
7578
  target,
@@ -7803,8 +7592,8 @@ program.command("pr <action> [target]").description("PR review helpers: review |
7803
7592
  process.exit(result.exitCode);
7804
7593
  });
7805
7594
  program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number or URL; diff fetched via gh pr diff <value>", (v) => /^#?\d+$/.test(v) ? parseInt(v.replace(/^#/, ""), 10) : v).option("--base <ref>", "Base ref for git diff <ref>...HEAD (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").option("--json", "Output a structured JSON report").option("--markdown", "Output Markdown report (default)").option("--sarif", "Output SARIF 2.1.0 for code scanning artifacts").option("--fail-on-security-high", "Fail when the security review contains HIGH/CRITICAL findings (default)").option("--no-fail-on-security-high", "Do not fail on security HIGH/CRITICAL findings").option("--fail-on-test-failure", "Fail when the report mentions test failures (default)").option("--no-fail-on-test-failure", "Do not fail on test failure findings").option("--fail-on-lint-failure", "Fail when the report mentions lint/typecheck failures (default)").option("--no-fail-on-lint-failure", "Do not fail on lint/typecheck failure findings").action(async (options) => {
7806
- const { runCi } = await import("./ci-WDF2CYR4.js");
7807
- const { formatCiResult } = await import("./ci-format-7B5VAZGA.js");
7595
+ const { runCi } = await import("./ci-F4ESOYI5.js");
7596
+ const { formatCiResult } = await import("./ci-format-AIRW3TVQ.js");
7808
7597
  const formats = [options.json, options.markdown, options.sarif].filter(Boolean).length;
7809
7598
  if (formats > 1) {
7810
7599
  console.error("Choose only one output format: --json, --markdown, or --sarif.");
@@ -7962,7 +7751,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7962
7751
  config.get("customProviders"),
7963
7752
  config.getConfigDir()
7964
7753
  );
7965
- const { startHub } = await import("./hub-TO7ODOIA.js");
7754
+ const { startHub } = await import("./hub-JJCCCU3O.js");
7966
7755
  await startHub(
7967
7756
  {
7968
7757
  topic: topic ?? "",