jinzd-ai-cli 0.4.72 → 0.4.74

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.
@@ -0,0 +1,318 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ConfigError,
4
+ EnvLoader
5
+ } from "./chunk-2ZD3YTVM.js";
6
+ import {
7
+ CONFIG_DIR_NAME,
8
+ CONFIG_FILE_NAME,
9
+ HISTORY_DIR_NAME,
10
+ PLUGINS_DIR_NAME
11
+ } from "./chunk-BT2TCINO.js";
12
+
13
+ // src/config/config-manager.ts
14
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
15
+ import { join } from "path";
16
+ import { homedir } from "os";
17
+
18
+ // src/config/schema.ts
19
+ import { z } from "zod";
20
+ var CustomModelSchema = z.object({
21
+ id: z.string(),
22
+ displayName: z.string().optional(),
23
+ contextWindow: z.number().optional()
24
+ });
25
+ var CustomProviderConfigSchema = z.object({
26
+ id: z.string(),
27
+ // 唯一 ID,不能与内置 provider 重名
28
+ displayName: z.string(),
29
+ // 显示名称
30
+ apiKey: z.string().optional(),
31
+ // 可选:直接在此写 key(也可通过 apiKeys 字段或环境变量提供)
32
+ baseUrl: z.string(),
33
+ // OpenAI 兼容 API 的 base URL(必填)
34
+ defaultModel: z.string(),
35
+ // 默认使用的模型 ID
36
+ models: z.array(CustomModelSchema).default([]),
37
+ timeout: z.number().optional()
38
+ // 请求超时(ms),覆盖全局默认值
39
+ });
40
+ var ModelParamsSchema = z.object({
41
+ temperature: z.number().min(0).max(2).optional(),
42
+ maxTokens: z.number().int().positive().optional(),
43
+ timeout: z.number().int().positive().optional(),
44
+ /** 是否启用深度思考(thinking)模式,Claude Sonnet/Opus、GLM-5 等 */
45
+ thinking: z.boolean().optional(),
46
+ /** thinking 模式的 token 预算(最小 1024,仅 Claude Extended Thinking 使用) */
47
+ thinkingBudget: z.number().int().min(1024).optional()
48
+ });
49
+ var UserProfileSchema = z.object({
50
+ /** 真实姓名(如 "Jin Zhengdong") */
51
+ name: z.string().optional(),
52
+ /** 昵称/称呼偏好(如 "东叔"),AI 会以此称呼你 */
53
+ nickname: z.string().optional(),
54
+ /** 职业角色(如 "Full-stack developer"、"Data scientist") */
55
+ role: z.string().optional(),
56
+ /** 个人简介 / 专长描述(1-3 句话) */
57
+ bio: z.string().optional(),
58
+ /** 兴趣领域或技术栈(如 ["TypeScript", "Rust", "AI/ML"]) */
59
+ interests: z.array(z.string()).default([]),
60
+ /** 语言偏好(如 "zh-CN"、"en"),AI 将据此选择交流语言 */
61
+ locale: z.string().optional(),
62
+ /** 自定义人设补充(自由格式,直接注入 system prompt) */
63
+ extra: z.string().optional()
64
+ }).default({});
65
+ var ConfigSchema = z.object({
66
+ version: z.string().default("1.0.0"),
67
+ // 用户身份档案 — 跨 Provider 的 "灵魂"
68
+ userProfile: UserProfileSchema,
69
+ defaultProvider: z.string().default("claude"),
70
+ // 每个 provider 的默认模型(key 为 provider ID)
71
+ defaultModels: z.record(z.string()).default({}),
72
+ // API Keys:放宽为任意 record,支持自定义 provider ID
73
+ apiKeys: z.record(z.string()).default({}),
74
+ customBaseUrls: z.record(z.string()).default({}),
75
+ // Per-provider timeout in ms (e.g. { deepseek: 60000 })
76
+ // ⚠️ Timeout 优先级说明(L5):
77
+ // 1. modelParams[modelId].timeout — 最高优先级,精确到具体模型(如 deepseek-reasoner 需要更长超时)
78
+ // 2. timeouts[providerId] — Provider 级默认,覆盖内置默认值
79
+ // 3. 内置 Provider 的硬编码默认值 — 最低兜底(如 OpenAI 兼容系列默认 60000ms)
80
+ // 实现位置:src/providers/registry.ts initialize(),将 timeouts[id] 注入 provider
81
+ timeouts: z.record(z.number()).default({}),
82
+ // HTTP/HTTPS 代理地址(Node.js 不自动使用系统代理,需显式配置)
83
+ // 例:http://127.0.0.1:10809
84
+ // 也可通过环境变量 HTTPS_PROXY / HTTP_PROXY 覆盖
85
+ proxy: z.string().optional(),
86
+ // 自定义 Provider 列表(OpenAI 兼容接口,无需改代码)
87
+ customProviders: z.array(CustomProviderConfigSchema).default([]),
88
+ // 按模型 ID 存储的推理参数(key 为模型 ID,如 "deepseek-chat")
89
+ modelParams: z.record(ModelParamsSchema).default({}),
90
+ ui: z.object({
91
+ streaming: z.boolean().default(true),
92
+ markdownRendering: z.boolean().default(true),
93
+ showTokenCount: z.boolean().default(true),
94
+ /** 桌面通知阈值(毫秒):AI 任务耗时超过此值时发送系统通知。0 = 禁用。默认 10000 (10s) */
95
+ notificationThreshold: z.number().default(1e4),
96
+ /** 终端输出折行宽度。0 = 自动(使用终端宽度),>0 = 固定列宽。默认 0 */
97
+ wordWrap: z.number().int().min(0).default(0),
98
+ /** 颜色主题:'dark'(默认)| 'light' | 'custom' */
99
+ theme: z.enum(["dark", "light", "custom"]).default("dark"),
100
+ /** 自定义颜色覆盖(仅在 theme='custom' 时生效)。值为 chalk 颜色名或 '#hex',支持 'bold.cyan' 组合 */
101
+ colors: z.object({
102
+ prompt: z.string().optional(),
103
+ info: z.string().optional(),
104
+ warning: z.string().optional(),
105
+ error: z.string().optional(),
106
+ success: z.string().optional(),
107
+ dim: z.string().optional(),
108
+ accent: z.string().optional(),
109
+ toolCall: z.string().optional(),
110
+ toolResult: z.string().optional(),
111
+ heading: z.string().optional()
112
+ }).optional()
113
+ }).default({}),
114
+ session: z.object({
115
+ autoSave: z.boolean().default(true),
116
+ maxHistoryDays: z.number().default(30),
117
+ systemPrompt: z.string().optional()
118
+ }).default({}),
119
+ // 项目上下文文件配置
120
+ // 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md 机制
121
+ // 默认按顺序查找:AICLI.md → CLAUDE.md → .aicli/context.md
122
+ // 设为 false 可禁用此功能
123
+ contextFile: z.union([z.string(), z.literal(false)]).default("auto"),
124
+ // Google Custom Search API 的 Search Engine ID (cx 参数)
125
+ // API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
126
+ // CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
127
+ googleSearchEngineId: z.string().optional(),
128
+ // MCP (Model Context Protocol) 服务器配置
129
+ // 声明外部 MCP 服务器,启动时自动连接、发现工具并注册
130
+ // 配置格式兼容 Claude Desktop(command + args + env)
131
+ // 示例:{ "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"] } }
132
+ mcpServers: z.record(z.object({
133
+ command: z.string(),
134
+ args: z.array(z.string()).default([]),
135
+ env: z.record(z.string()).optional(),
136
+ timeout: z.number().default(3e4)
137
+ })).default({}),
138
+ // 工具执行钩子(shell 命令,模板变量:{tool} {dangerLevel} {args} {status})
139
+ hooks: z.object({
140
+ preToolExecution: z.string().optional(),
141
+ postToolExecution: z.string().optional()
142
+ }).optional(),
143
+ // 工具权限规则(按顺序匹配第一个生效)
144
+ permissionRules: z.array(z.object({
145
+ tool: z.string(),
146
+ action: z.enum(["auto-approve", "deny", "confirm"]),
147
+ when: z.object({
148
+ dangerLevel: z.enum(["safe", "write", "destructive"]).optional(),
149
+ pathPattern: z.string().optional()
150
+ }).optional()
151
+ })).default([]),
152
+ // 无规则匹配时的默认权限动作
153
+ defaultPermission: z.enum(["auto-approve", "deny", "confirm"]).default("confirm"),
154
+ // 自动上下文压缩开关
155
+ // 当对话估算 token 数超过模型 contextWindow 的 80% 时,自动触发 compact 压缩旧消息
156
+ // 默认开启。设为 false 则仅在手动 /compact 时压缩
157
+ autoCompact: z.boolean().default(true),
158
+ // Agentic 工具调用循环单次对话最大轮次(默认 200)。
159
+ // 超过此值后 AI 被强制停止调用工具并生成总结。
160
+ // 建议范围:25(保守)~ 1000(宽松,接近 Claude Code)。
161
+ // CLI `--max-tool-rounds <n>` 可覆盖此值。
162
+ maxToolRounds: z.number().int().min(1).max(1e4).default(200),
163
+ // Auto-pause checkpoint:Agentic 循环每隔多少轮暂停一次,让用户确认方向或中途介入。
164
+ // 默认 50;设为 0 禁用(完全自动执行到完成或 maxToolRounds 用尽)。
165
+ // CLI 与 Web UI 行为一致:CLI 用 readline question 提示,Web UI 弹出对话框。
166
+ autoPauseInterval: z.number().int().min(0).max(1e4).default(50),
167
+ // 单次工具输出(如 read_file、bash、grep_files)返回给 AI 的最大字符数上限。
168
+ // 默认 500_000 (~500K chars ≈ 6000-8000 行代码)。
169
+ // 实际上限还会受模型 contextWindow 动态约束(取 contextWindow/4 作为下限)。
170
+ // 设置为 0 或未配置时使用默认值;不建议设为小于 12_000 或大于模型 contextWindow/2。
171
+ maxToolOutputChars: z.number().int().min(0).default(5e5),
172
+ // 月度成本预算(USD)。
173
+ // 设置后,每次 AI 回复后会跟踪成本,接近或超过预算时在 /status 和 /cost 中显示警告。
174
+ // 默认 0 = 不限制。例:50 表示每月最多花 $50。
175
+ monthlyBudget: z.number().min(0).default(0),
176
+ // 插件加载开关(安全控制)
177
+ // 默认 false:不自动加载 ~/.aicli/plugins/ 中的插件文件。
178
+ // 插件以完整 Node.js 权限在主进程中执行(可读写文件、访问网络、执行命令),
179
+ // 必须确认插件来源可信后,再设为 true 启用。
180
+ // 可通过 /config 命令或直接编辑 ~/.aicli/config.json 开启。
181
+ allowPlugins: z.boolean().default(false),
182
+ // 智能模型路由(v0.4.68+)
183
+ // 按用户每轮输入的内容/标签/长度动态选择模型,在同一 provider 内切换,
184
+ // 例:短问题走 haiku(省钱),planning 走 opus(质量)。
185
+ // enabled=false 时永远返回当前模型。rules 按顺序匹配,首个命中的规则生效。
186
+ // 每个 rule 的 match 必须至少有一个条件(tag/contains/maxLength/minLength)。
187
+ // 详见 src/core/model-router.ts。
188
+ routing: z.object({
189
+ enabled: z.boolean().default(false),
190
+ rules: z.array(z.object({
191
+ match: z.object({
192
+ contains: z.array(z.string()).optional(),
193
+ maxLength: z.number().int().positive().optional(),
194
+ minLength: z.number().int().positive().optional(),
195
+ tag: z.string().optional()
196
+ }),
197
+ model: z.string(),
198
+ name: z.string().optional()
199
+ })).default([]),
200
+ fallback: z.string().optional()
201
+ }).default({ enabled: false, rules: [] })
202
+ });
203
+
204
+ // src/config/config-manager.ts
205
+ var ConfigManager = class {
206
+ configDir;
207
+ configPath;
208
+ config;
209
+ constructor(configDir) {
210
+ this.configDir = configDir ?? join(homedir(), CONFIG_DIR_NAME);
211
+ this.configPath = join(this.configDir, CONFIG_FILE_NAME);
212
+ this.config = this.load();
213
+ }
214
+ load() {
215
+ if (!existsSync(this.configPath)) {
216
+ return ConfigSchema.parse({});
217
+ }
218
+ try {
219
+ const raw = JSON.parse(readFileSync(this.configPath, "utf-8"));
220
+ return ConfigSchema.parse(raw);
221
+ } catch (err) {
222
+ throw new ConfigError(
223
+ `Config file at ${this.configPath} is invalid. Delete it and run 'ai-cli config' to recreate.
224
+ ${err}`
225
+ );
226
+ }
227
+ }
228
+ save() {
229
+ mkdirSync(this.configDir, { recursive: true });
230
+ writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), "utf-8");
231
+ }
232
+ getApiKey(providerId) {
233
+ const envKey = EnvLoader.getApiKey(providerId);
234
+ if (envKey) return envKey;
235
+ return this.config.apiKeys[providerId];
236
+ }
237
+ setApiKey(providerId, key) {
238
+ this.config.apiKeys[providerId] = key;
239
+ this.save();
240
+ }
241
+ get(key) {
242
+ return this.config[key];
243
+ }
244
+ set(key, value) {
245
+ this.config[key] = value;
246
+ this.save();
247
+ }
248
+ /**
249
+ * 仅修改内存中的配置值,不持久化到磁盘。
250
+ * 用于 CLI 命令行参数覆盖(-p, -m, --no-stream),使其只对当前进程生效。
251
+ */
252
+ setTransient(key, value) {
253
+ this.config[key] = value;
254
+ }
255
+ isFirstRun() {
256
+ return !existsSync(this.configPath);
257
+ }
258
+ getConfigDir() {
259
+ return this.configDir;
260
+ }
261
+ getHistoryDir() {
262
+ return join(this.configDir, HISTORY_DIR_NAME);
263
+ }
264
+ getPluginsDir() {
265
+ return join(this.configDir, PLUGINS_DIR_NAME);
266
+ }
267
+ getDefaultProvider() {
268
+ return EnvLoader.getDefaultProvider() ?? this.config.defaultProvider;
269
+ }
270
+ /** 点分路径读取配置值,如 `ui.theme` → config.ui.theme */
271
+ getByPath(path) {
272
+ const keys = path.split(".");
273
+ let current = this.config;
274
+ for (const key of keys) {
275
+ if (current == null || typeof current !== "object") return void 0;
276
+ current = current[key];
277
+ }
278
+ return current;
279
+ }
280
+ /** 点分路径写入配置值,自动类型转换(boolean/number/string)并持久化 */
281
+ setByPath(path, rawValue) {
282
+ const keys = path.split(".");
283
+ if (keys.length === 0) return;
284
+ let value = rawValue;
285
+ if (rawValue === "true") value = true;
286
+ else if (rawValue === "false") value = false;
287
+ else if (rawValue !== "" && !isNaN(Number(rawValue))) value = Number(rawValue);
288
+ const draft = JSON.parse(JSON.stringify(this.config));
289
+ let current = draft;
290
+ for (let i = 0; i < keys.length - 1; i++) {
291
+ const key = keys[i];
292
+ if (current[key] == null || typeof current[key] !== "object") {
293
+ current[key] = {};
294
+ }
295
+ current = current[key];
296
+ }
297
+ current[keys[keys.length - 1]] = value;
298
+ const result = ConfigSchema.safeParse(draft);
299
+ if (!result.success) {
300
+ const firstErr = result.error.errors[0];
301
+ throw new ConfigError(`Invalid config value for "${path}": ${firstErr?.message ?? "validation failed"}`);
302
+ }
303
+ this.config = result.data;
304
+ this.save();
305
+ }
306
+ /** 获取完整配置对象的格式化 JSON 字符串(用于 /config show 等展示) */
307
+ toFormattedJSON() {
308
+ return JSON.stringify(this.config, null, 2);
309
+ }
310
+ /** 获取完整配置对象(JSON 兼容的原始对象) */
311
+ toJSON() {
312
+ return structuredClone(this.config);
313
+ }
314
+ };
315
+
316
+ export {
317
+ ConfigManager
318
+ };
@@ -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-R33SWTHO.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-24UUKJW5.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ConfigManager,
4
3
  HALLUCINATION_CORRECTION_MESSAGE,
5
4
  McpManager,
6
5
  ProviderRegistry,
@@ -31,7 +30,10 @@ import {
31
30
  saveDevState,
32
31
  sessionHasMeaningfulContent,
33
32
  setupProxy
34
- } from "./chunk-57HHY5ZX.js";
33
+ } from "./chunk-PLJUAA3J.js";
34
+ import {
35
+ ConfigManager
36
+ } from "./chunk-VG3MFZYG.js";
35
37
  import {
36
38
  ToolExecutor,
37
39
  ToolRegistry,
@@ -47,10 +49,12 @@ import {
47
49
  spawnAgentContext,
48
50
  theme,
49
51
  undoStack
50
- } from "./chunk-XH65H3BT.js";
52
+ } from "./chunk-D5ZDVEJJ.js";
51
53
  import {
52
54
  fileCheckpoints
53
55
  } from "./chunk-4BKXL7SM.js";
56
+ import "./chunk-FKVJRBPO.js";
57
+ import "./chunk-2ZD3YTVM.js";
54
58
  import {
55
59
  AGENTIC_BEHAVIOR_GUIDELINE,
56
60
  AUTHOR,
@@ -72,7 +76,7 @@ import {
72
76
  SKILLS_DIR_NAME,
73
77
  VERSION,
74
78
  buildUserIdentityPrompt
75
- } from "./chunk-73UI5AH7.js";
79
+ } from "./chunk-BT2TCINO.js";
76
80
 
77
81
  // src/index.ts
78
82
  import { program } from "commander";
@@ -312,6 +316,11 @@ var Renderer = class {
312
316
  console.log(feat("Session size control: auto-trim old tool output when session exceeds 2MB, keep recent rounds intact"));
313
317
  console.log(feat("Crash recovery: detect incomplete agentic loops on /resume, warn and offer continuation"));
314
318
  console.log(feat("Cost dashboard: /cost history shows cross-session daily/weekly/monthly spend with budget progress bar"));
319
+ console.log(feat("Prompt caching (A1, v0.4.70+): system prompt split into stable/volatile \u2014 Claude caches stable half, 10% cost on hits"));
320
+ console.log(feat("edit_file patch mode (A2, v0.4.72+): accepts unified diff (@@ hunks) \u2014 most compact for scattered small changes in large files"));
321
+ console.log(feat("Anthropic Batches API (A3, v0.4.73+): aicli batch submit/list/status/results/cancel \u2014 50% off, 24h window"));
322
+ console.log(feat("Session Replay (B1, v0.4.71+): Web UI \u{1F3AC} button \u2014 timeline view of every message, tool call, reasoning, and cache-aware token usage"));
323
+ console.log(feat("Conversation Branching (B2, v0.4.74+): /branch list/new/switch/delete/rename \u2014 fork the conversation at any message; Web UI replay \u{1F33F} fork-here button"));
315
324
  console.log();
316
325
  }
317
326
  printPrompt(provider, _model) {
@@ -2231,6 +2240,122 @@ ${hint}` : "")
2231
2240
  console.log();
2232
2241
  }
2233
2242
  },
2243
+ // ── /branch ───────────────────────────────────────────────────
2244
+ {
2245
+ name: "branch",
2246
+ description: "Manage conversation branches (fork/switch/list/delete/rename)",
2247
+ usage: "/branch [list | new <msgIndex> [title] | switch <id> | delete <id> | rename <id> <title>]",
2248
+ async execute(args, ctx) {
2249
+ const session = ctx.sessions.current;
2250
+ if (!session) {
2251
+ ctx.renderer.renderError("No active session.");
2252
+ return;
2253
+ }
2254
+ const sub = args[0]?.toLowerCase();
2255
+ if (!sub || sub === "list") {
2256
+ const branches = session.listBranches();
2257
+ console.log(theme.heading(`
2258
+ Branches (${branches.length}):
2259
+ `));
2260
+ for (const b of branches) {
2261
+ const active = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
2262
+ const parent = b.parentBranchId ? theme.dim(` \u2190 ${b.parentBranchId}@${b.parentMessageIndex}`) : "";
2263
+ const count = b.id === session.activeBranchId ? session.messages.length : session.getBranchMessages(b.id)?.length ?? 0;
2264
+ console.log(
2265
+ ` ${active}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
2266
+ );
2267
+ }
2268
+ console.log();
2269
+ console.log(theme.dim(" /branch new <msgIndex> [title] \u2014 fork active branch at message N"));
2270
+ console.log(theme.dim(" /branch switch <id> \u2014 switch active branch"));
2271
+ console.log(theme.dim(" /branch delete <id> \u2014 delete inactive branch"));
2272
+ console.log(theme.dim(" /branch rename <id> <title> \u2014 rename a branch"));
2273
+ console.log();
2274
+ return;
2275
+ }
2276
+ if (sub === "new") {
2277
+ const idxArg = args[1];
2278
+ if (!idxArg) {
2279
+ ctx.renderer.renderError("Usage: /branch new <msgIndex> [title]");
2280
+ return;
2281
+ }
2282
+ const fromIndex = parseInt(idxArg, 10);
2283
+ if (isNaN(fromIndex) || fromIndex < 0 || fromIndex > session.messages.length) {
2284
+ ctx.renderer.renderError(`Invalid msgIndex: ${idxArg}. Range: 0-${session.messages.length}`);
2285
+ return;
2286
+ }
2287
+ const title = args.slice(2).join(" ").trim() || void 0;
2288
+ try {
2289
+ const newId = session.createBranch(fromIndex, title);
2290
+ await ctx.sessions.save();
2291
+ console.log(theme.success(`
2292
+ \u2713 Created branch "${newId}" from message #${fromIndex}`));
2293
+ console.log(theme.dim(` Now on branch "${newId}" (${session.messages.length} messages)
2294
+ `));
2295
+ } catch (err) {
2296
+ ctx.renderer.renderError(err.message);
2297
+ }
2298
+ return;
2299
+ }
2300
+ if (sub === "switch") {
2301
+ const id = args[1];
2302
+ if (!id) {
2303
+ ctx.renderer.renderError("Usage: /branch switch <id>");
2304
+ return;
2305
+ }
2306
+ const ok = session.switchBranch(id);
2307
+ if (ok) {
2308
+ await ctx.sessions.save();
2309
+ const b = session.getActiveBranch();
2310
+ console.log(theme.success(`
2311
+ \u2713 Switched to branch "${b.id}" \u2014 ${b.title}`));
2312
+ console.log(theme.dim(` ${session.messages.length} messages on this branch
2313
+ `));
2314
+ } else {
2315
+ ctx.renderer.renderError(`Cannot switch to "${id}" (not found or already active).`);
2316
+ }
2317
+ return;
2318
+ }
2319
+ if (sub === "delete") {
2320
+ const id = args[1];
2321
+ if (!id) {
2322
+ ctx.renderer.renderError("Usage: /branch delete <id>");
2323
+ return;
2324
+ }
2325
+ const ok = session.deleteBranch(id);
2326
+ if (ok) {
2327
+ await ctx.sessions.save();
2328
+ console.log(theme.success(`
2329
+ \u2713 Deleted branch "${id}"
2330
+ `));
2331
+ } else {
2332
+ ctx.renderer.renderError(
2333
+ `Cannot delete "${id}" (not found, active, or last remaining branch).`
2334
+ );
2335
+ }
2336
+ return;
2337
+ }
2338
+ if (sub === "rename") {
2339
+ const id = args[1];
2340
+ const title = args.slice(2).join(" ").trim();
2341
+ if (!id || !title) {
2342
+ ctx.renderer.renderError("Usage: /branch rename <id> <new title>");
2343
+ return;
2344
+ }
2345
+ const ok = session.renameBranch(id, title);
2346
+ if (ok) {
2347
+ await ctx.sessions.save();
2348
+ console.log(theme.success(`
2349
+ \u2713 Renamed branch "${id}" \u2192 "${title}"
2350
+ `));
2351
+ } else {
2352
+ ctx.renderer.renderError(`Branch "${id}" not found.`);
2353
+ }
2354
+ return;
2355
+ }
2356
+ ctx.renderer.renderError(`Unknown subcommand: ${sub}. Use list/new/switch/delete/rename.`);
2357
+ }
2358
+ },
2234
2359
  // ── /commands ─────────────────────────────────────────────────
2235
2360
  {
2236
2361
  name: "commands",
@@ -2267,7 +2392,7 @@ ${hint}` : "")
2267
2392
  usage: "/test [command|filter]",
2268
2393
  async execute(args, ctx) {
2269
2394
  try {
2270
- const { executeTests } = await import("./run-tests-4565WOUK.js");
2395
+ const { executeTests } = await import("./run-tests-HBLD2R6B.js");
2271
2396
  const argStr = args.join(" ").trim();
2272
2397
  let testArgs = {};
2273
2398
  if (argStr) {
@@ -6139,7 +6264,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6139
6264
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6140
6265
  process.exit(1);
6141
6266
  }
6142
- const { startWebServer } = await import("./server-Y4CT5IJJ.js");
6267
+ const { startWebServer } = await import("./server-NMMRIWT2.js");
6143
6268
  await startWebServer({ port, host: options.host });
6144
6269
  });
6145
6270
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6260,6 +6385,50 @@ program.command("sessions").description("List recent conversation sessions").act
6260
6385
  }
6261
6386
  console.log();
6262
6387
  });
6388
+ 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) => {
6389
+ try {
6390
+ const batch = await import("./batch-SEO6BLMQ.js");
6391
+ switch (action) {
6392
+ case "submit":
6393
+ if (!arg) {
6394
+ console.error("Usage: aicli batch submit <input.jsonl>");
6395
+ process.exit(1);
6396
+ }
6397
+ await batch.cmdSubmit(arg, { dryRun: options?.dryRun });
6398
+ break;
6399
+ case "list":
6400
+ await batch.cmdList();
6401
+ break;
6402
+ case "status":
6403
+ if (!arg) {
6404
+ console.error("Usage: aicli batch status <id>");
6405
+ process.exit(1);
6406
+ }
6407
+ await batch.cmdStatus(arg);
6408
+ break;
6409
+ case "results":
6410
+ if (!arg) {
6411
+ console.error("Usage: aicli batch results <id> [out.jsonl]");
6412
+ process.exit(1);
6413
+ }
6414
+ await batch.cmdResults(arg, arg2);
6415
+ break;
6416
+ case "cancel":
6417
+ if (!arg) {
6418
+ console.error("Usage: aicli batch cancel <id>");
6419
+ process.exit(1);
6420
+ }
6421
+ await batch.cmdCancel(arg);
6422
+ break;
6423
+ default:
6424
+ console.error(`Unknown batch action: ${action}. Use submit | list | status | results | cancel.`);
6425
+ process.exit(1);
6426
+ }
6427
+ } catch (err) {
6428
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
6429
+ process.exit(1);
6430
+ }
6431
+ });
6263
6432
  program.command("help").description("Show a comprehensive guide to all aicli features and commands").action(() => {
6264
6433
  const B = "\x1B[1m";
6265
6434
  const D = "\x1B[2m";
@@ -6285,6 +6454,7 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
6285
6454
  ` ${G}aicli providers${R} List all providers and status`,
6286
6455
  ` ${G}aicli sessions${R} List recent conversation sessions`,
6287
6456
  ` ${G}aicli user <action>${R} User management (list/create/delete)`,
6457
+ ` ${G}aicli batch <action>${R} Anthropic Batches API (50% off, 24h): submit/list/status/results/cancel`,
6288
6458
  "",
6289
6459
  `${B}${C} \u25A0 STARTUP OPTIONS${R}`,
6290
6460
  ` ${Y}--provider <name>${R} Set AI provider`,
@@ -6304,6 +6474,7 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
6304
6474
  `${B}${C} \u25A0 WEB UI OPTIONS${R}`,
6305
6475
  ` ${Y}aicli web --port 8080${R} Custom port (default: 3000)`,
6306
6476
  ` ${Y}aicli web --host 0.0.0.0${R} LAN access (mobile/tablet)`,
6477
+ ` ${D} Features: \u{1F3AC} Session Replay + \u{1F33F} Conversation Branching (fork from any message)${R}`,
6307
6478
  "",
6308
6479
  `${B}${C} \u25A0 MULTI-AGENT HUB${R}`,
6309
6480
  ` ${Y}--preset <name>${R} Role preset (tech-review/brainstorm/code-review/debate)`,
@@ -6326,6 +6497,7 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
6326
6497
  ` ${M}/search${R} Search across all session histories`,
6327
6498
  ` ${M}/compact${R} Compress conversation to save context`,
6328
6499
  ` ${M}/checkpoint${R} ${M}/fork${R} Save/restore/fork session state`,
6500
+ ` ${M}/branch${R} List/create/switch/delete conversation branches (B2)`,
6329
6501
  ` ${M}/undo${R} Undo file operations`,
6330
6502
  ` ${M}/yolo${R} Skip all tool confirmations`,
6331
6503
  ` ${M}/mcp${R} Manage MCP server connections`,
@@ -6337,6 +6509,7 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
6337
6509
  ` ${D}bash read_file write_file edit_file list_dir grep_files glob_files${R}`,
6338
6510
  ` ${D}web_fetch google_search run_interactive run_tests spawn_agent${R}`,
6339
6511
  ` ${D}ask_user write_todos save_memory save_last_response + MCP tools${R}`,
6512
+ ` ${D}edit_file has 5 modes: string replace, line insert/delete, batch edits, unified-diff patch${R}`,
6340
6513
  "",
6341
6514
  `${B}${C} \u25A0 CONFIGURATION${R}`,
6342
6515
  ` ${D}Config: ~/.aicli/config.json${R}`,
@@ -6354,6 +6527,8 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
6354
6527
  ` ${G}aicli hub --preset brainstorm "idea"${R} ${D}# multi-agent brainstorm${R}`,
6355
6528
  ` ${G}aicli hub --task --roles team.json "build app"${R} ${D}# task mode: agents write code${R}`,
6356
6529
  ` ${G}aicli hub -c spec.md -c api.md "build it"${R} ${D}# hub + docs context${R}`,
6530
+ ` ${G}aicli batch submit prompts.jsonl${R} ${D}# Anthropic batch: 50% off, 24h window${R}`,
6531
+ ` ${G}aicli batch status <id>${R} ${G}/ results <id> out.jsonl${R} ${D}# poll / download${R}`,
6357
6532
  "",
6358
6533
  `${D} Docs: https://github.com/jinzhengdong/ai-cli${R}`,
6359
6534
  `${D} Issues: https://github.com/jinzhengdong/ai-cli/issues${R}`,
@@ -6372,7 +6547,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
6372
6547
  }),
6373
6548
  config.get("customProviders")
6374
6549
  );
6375
- const { startHub } = await import("./hub-NQADIYTS.js");
6550
+ const { startHub } = await import("./hub-ABCHM2OR.js");
6376
6551
  await startHub(
6377
6552
  {
6378
6553
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-OKNKH5D7.js";
4
+ } from "./chunk-265H5S5H.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,7 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-73UI5AH7.js";
5
+ } from "./chunk-FKVJRBPO.js";
6
+ import "./chunk-BT2TCINO.js";
6
7
  export {
7
8
  executeTests,
8
9
  runTestsTool