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.
- package/README.md +28 -3
- package/README.zh-CN.md +28 -3
- package/dist/{chunk-OKNKH5D7.js → chunk-265H5S5H.js} +1 -1
- package/dist/chunk-2ZD3YTVM.js +114 -0
- package/dist/chunk-BT2TCINO.js +155 -0
- package/dist/{chunk-XH65H3BT.js → chunk-D5ZDVEJJ.js} +10 -111
- package/dist/{chunk-73UI5AH7.js → chunk-FKVJRBPO.js} +3 -152
- package/dist/{chunk-57HHY5ZX.js → chunk-PLJUAA3J.js} +242 -368
- package/dist/chunk-VG3MFZYG.js +318 -0
- package/dist/{hub-NQADIYTS.js → hub-ABCHM2OR.js} +1 -1
- package/dist/index.js +182 -7
- package/dist/{run-tests-X2P5BOWV.js → run-tests-FMEFXUGO.js} +1 -1
- package/dist/{run-tests-4565WOUK.js → run-tests-HBLD2R6B.js} +2 -1
- package/dist/{server-Y4CT5IJJ.js → server-NMMRIWT2.js} +106 -5
- package/dist/{task-orchestrator-R33SWTHO.js → task-orchestrator-24UUKJW5.js} +4 -2
- package/dist/web/client/app.js +32 -0
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
6550
|
+
const { startHub } = await import("./hub-ABCHM2OR.js");
|
|
6376
6551
|
await startHub(
|
|
6377
6552
|
{
|
|
6378
6553
|
topic: topic ?? "",
|