chatccc 0.2.178 → 0.2.180

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 CHANGED
@@ -144,7 +144,12 @@ npm install -g chatccc
144
144
  chatccc
145
145
  ```
146
146
 
147
- 要求 Node.js >= 20。安装完成后,在任意目录执行 `chatccc` 即可启动。配置、日志和状态文件会保存在包目录下的 `config.json`、`logs/`、`state/`。
147
+ 要求 Node.js >= 20。安装完成后,在任意目录执行 `chatccc` 即可启动。配置、日志和状态文件会保存在用户目录的 `.chatccc` 下:
148
+
149
+ - Windows:`C:\Users\<用户名>\.chatccc\`
150
+ - macOS / Linux:`~/.chatccc/`
151
+
152
+ 旧版本留在仓库或包目录下的 `config.json`、`logs/`、`state/` 会在首次启动时自动迁移到用户目录。
148
153
 
149
154
  首次启动时,如果还没有有效配置,ChatCCC 会自动打开本地 Web 配置向导(默认 `http://127.0.0.1:18080`)。
150
155
 
@@ -197,7 +202,7 @@ chatccc
197
202
  # 在微信里找到机器人,发送 /new 开始对话
198
203
  ```
199
204
 
200
- 微信登录信息会保存到 `state/ilink-auth.json`。token 过期后重新扫码即可。
205
+ 微信登录信息会保存到 `~/.chatccc/state/ilink-auth.json`。token 过期后重新扫码即可。
201
206
 
202
207
  ### 3. AI 工具配置
203
208
 
@@ -234,7 +239,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
234
239
 
235
240
  ### 4. `config.json`
236
241
 
237
- `config.json` 不存在时,ChatCCC 会从 `config.sample.json` 复制一份。常用结构如下:
242
+ `~/.chatccc/config.json` 不存在时,ChatCCC 会从 `config.sample.json` 复制一份。常用结构如下:
238
243
 
239
244
  ```json
240
245
  {
@@ -242,28 +247,32 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
242
247
  "appId": "cli_xxxxxxxxxxxx",
243
248
  "appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
244
249
  },
245
- "platforms": {
246
- "feishu": { "enabled": true },
247
- "ilink": { "enabled": true }
248
- },
249
- "port": 18080,
250
- "gitTimeoutSeconds": 180,
251
- "claude": {
252
- "enabled": false,
253
- "defaultAgent": true,
254
- "model": "claude-sonnet-4-6",
255
- "subagentModel": "",
256
- "effort": "",
257
- "apiKey": "",
258
- "baseUrl": ""
259
- },
260
- "cursor": {
261
- "enabled": false,
262
- "defaultAgent": false,
263
- "path": "",
264
- "model": ""
265
- },
266
- "codex": {
250
+ "platforms": {
251
+ "feishu": { "enabled": true, "platformType": "feishu" },
252
+ "ilink": { "enabled": true, "reuseTokenOnStart": true }
253
+ },
254
+ "port": 18080,
255
+ "gitTimeoutSeconds": 180,
256
+ "allowInterrupt": false,
257
+ "claude": {
258
+ "enabled": false,
259
+ "defaultAgent": true,
260
+ "model": "claude-sonnet-4-6",
261
+ "subagentModel": "",
262
+ "effort": "",
263
+ "apiKey": "",
264
+ "baseUrl": "",
265
+ "maxTurn": 0
266
+ },
267
+ "cursor": {
268
+ "enabled": false,
269
+ "defaultAgent": false,
270
+ "path": "",
271
+ "model": "",
272
+ "avatarBatteryMode": "apiPercent",
273
+ "onDemandMonthlyBudget": 1000
274
+ },
275
+ "codex": {
267
276
  "enabled": false,
268
277
  "defaultAgent": false,
269
278
  "path": "",
@@ -276,16 +285,21 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
276
285
  | 字段 | 说明 |
277
286
  | --- | --- |
278
287
  | `feishu.appId` / `feishu.appSecret` | 飞书应用凭证 |
279
- | `platforms.feishu.enabled` | 是否启用飞书 |
280
- | `platforms.ilink.enabled` | 是否启用微信 iLink |
281
- | `port` | 本地 Web 配置面板和中继服务端口 |
282
- | `gitTimeoutSeconds` | `/git` 命令超时时间,默认 180 秒 |
283
- | `*.enabled` | 是否启用对应 AI Agent |
284
- | `*.defaultAgent` | `/new` 未指定 Agent 时使用哪个工具 |
285
- | `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
286
- | `claude.model` / `claude.subagentModel` / `claude.effort` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
287
- | `claude.apiKey` / `claude.baseUrl` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
288
- | `claude.maxTurn` | 选填;Claude 最大对话轮数,默认 0(无限制),可在 Web UI 编辑 |
288
+ | `platforms.feishu.enabled` | 是否启用飞书 |
289
+ | `platforms.feishu.platformType` | 飞书平台类型,默认 `feishu` |
290
+ | `platforms.ilink.enabled` | 是否启用微信 iLink |
291
+ | `platforms.ilink.reuseTokenOnStart` | 启动时是否复用已有微信登录 token |
292
+ | `port` | 本地 Web 配置面板和中继服务端口 |
293
+ | `gitTimeoutSeconds` | `/git` 命令超时时间,默认 180 |
294
+ | `allowInterrupt` | 是否允许新消息中断正在运行的任务;默认 false |
295
+ | `*.enabled` | 是否启用对应 AI Agent |
296
+ | `*.defaultAgent` | `/new` 未指定 Agent 时使用哪个工具 |
297
+ | `cursor.path` / `codex.path` | CLI 可执行文件路径;留空时自动探测或使用 PATH |
298
+ | `cursor.avatarBatteryMode` | Cursor 头像电量显示来源:`apiPercent` 或 `onDemandUse` |
299
+ | `cursor.onDemandMonthlyBudget` | `avatarBatteryMode=onDemandUse` 时用于计算电量的月预算 |
300
+ | `claude.model` / `claude.subagentModel` / `claude.effort` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
301
+ | `claude.apiKey` / `claude.baseUrl` | 选填;设置后传给 Claude Agent SDK,留空以 `~/.claude/settings.json` 为准 |
302
+ | `claude.maxTurn` | 选填;Claude 最大对话轮数,默认 0(无限制),可在 Web UI 编辑 |
289
303
 
290
304
  > **权限控制**:普通消息以 `bypassPermissions` 模式运行,跳过 Agent 操作确认。使用 `/plan` 或 `/ask` 前缀时,ChatCCC 自动切换为只读模式:Claude SDK 仅放行 Read + stop-stuck-loop 网络请求,Codex 使用 `--sandbox read-only`,Cursor 使用 `--mode plan/ask`。请只在可信环境中使用。
291
305
 
@@ -301,22 +315,26 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
301
315
  | --- | --- |
302
316
  | `/new` | 使用默认 Agent 创建新会话 |
303
317
  | `/new claude` | 创建 Claude Code 会话 |
304
- | `/new cursor` | 创建 Cursor 会话 |
305
- | `/new codex` | 创建 Codex 会话 |
306
- | `/newh` | 重置当前会话,保留工作目录 |
307
- | `/model` | 查看或切换当前会话的模型 |
308
- | `/stop` | 停止当前回复 |
309
- | `/state` | 查看当前会话状态 |
310
- | `/cd` | 查看或设置当前会话工作目录 |
318
+ | `/new cursor` | 创建 Cursor 会话 |
319
+ | `/new codex` | 创建 Codex 会话 |
320
+ | `/newh` | 重置当前会话,保留工作目录 |
321
+ | `/model` | 查看或切换当前会话的模型 |
322
+ | `/stop` | 停止当前回复 |
323
+ | `/cancel` | 取消当前会话里排队等待处理的消息 |
324
+ | `/state` | 查看当前会话状态 |
325
+ | `/cd` | 查看或设置当前会话工作目录 |
311
326
  | `/sessions` | 查看所有会话状态 |
312
- | `/usage` | 查看 Codex 5h 用量和周用量 |
327
+ | `/session <数字>` | 将当前群聊切换到 `/sessions` 列表中的指定会话 |
328
+ | `/usage` | 查看当前会话对应 Agent 的用量;Codex 显示 5h/周用量,Cursor 显示当前周期用量 |
313
329
  | `/git <子命令>` | 在当前会话工作目录执行 `git ...` 并回传输出 |
314
- | `/plan <内容>` | 只读计划模式:仅允许读文件和 stop-stuck-loop 请求,不执行任何写操作 |
315
- | `/ask <内容>` | 只读问答模式:与 /plan 相同,仅允许读文件和 stop-stuck-loop 请求 |
316
- | `/restart` | 重启机器人进程 |
317
- | `/updateg` | 更新 npm 全局包并重启(仅限 `npm install -g chatccc` 安装的全局进程) |
318
-
319
- > **模型切换**:`/model` 查看可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。当前仅 Claude Code 支持模型切换(需同时填写 `claude.model` 和 `claude.subagentModel`),Cursor / Codex 切换正在开发中。
330
+ | `/abd<内容>` | 去掉 `/abd` 前缀后把内容发给 Agent,并在消息末尾追加第一性原理需求澄清提示 |
331
+ | `/plan <内容>` | 只读计划模式:仅允许读文件和 stop-stuck-loop 请求,不执行任何写操作 |
332
+ | `/ask <内容>` | 只读问答模式:与 /plan 相同,仅允许读文件和 stop-stuck-loop 请求 |
333
+ | `/restart` | 重启机器人进程 |
334
+ | `/update` | 更新 npm 全局包并重启(仅限 `npm install -g chatccc` 安装的全局进程) |
335
+ | `/deleteg` | 解散当前飞书会话群;Agent 会话记录保留 |
336
+
337
+ > **模型切换**:`/model` 查看当前会话 Agent 的可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。可选模型来自当前 Agent 的配置:Claude 使用 `claude.model` / `claude.subagentModel`,Cursor 使用 `cursor.model`,Codex 使用 `codex.model`。
320
338
 
321
339
  ---
322
340
 
@@ -24,7 +24,9 @@
24
24
  "enabled": false,
25
25
  "defaultAgent": false,
26
26
  "path": "",
27
- "model": ""
27
+ "model": "",
28
+ "avatarBatteryMode": "apiPercent",
29
+ "onDemandMonthlyBudget": 1000
28
30
  },
29
31
  "codex": {
30
32
  "enabled": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.178",
3
+ "version": "0.2.180",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -16,10 +16,11 @@ describe("cardJsonToPlainText", () => {
16
16
  expect(text).toContain("/new");
17
17
  expect(text).toContain("/new cursor");
18
18
  expect(text).toContain("/new codex");
19
- expect(text).toContain("/restart");
20
- expect(text).toContain("/updateg");
21
- expect(text).toContain("/cd");
22
- });
19
+ expect(text).toContain("/restart");
20
+ expect(text).toContain("/update");
21
+ expect(text).toContain("/cd");
22
+ expect(text).toContain("/abd");
23
+ });
23
24
 
24
25
  it("converts status cards from v1 card format", () => {
25
26
  const text = cardJsonToPlainText(buildStatusCard("status body", "green"));
@@ -1,15 +1,16 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import {
3
- buildProgressCard,
4
- buildHelpCard,
2
+ import {
3
+ buildProgressCard,
4
+ buildHelpCard,
5
5
  buildCdContent,
6
6
  buildCdCard,
7
7
  buildSessionsCard,
8
8
  buildStatusCard,
9
9
  buildButtons,
10
10
  truncateContent,
11
- getToolEmoji,
12
- } from "../cards.ts";
11
+ getToolEmoji,
12
+ } from "../cards.ts";
13
+ import { ABD_HELP_LINE } from "../shared-prefix.ts";
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // truncateContent
@@ -151,14 +152,22 @@ describe("buildHelpCard", () => {
151
152
  expect(parsed.elements[0].text.content).toContain("你好");
152
153
  });
153
154
 
154
- it("includes action buttons", () => {
155
- const card = buildHelpCard("test");
156
- const parsed = JSON.parse(card);
157
- const action = parsed.elements[2];
158
- expect(action.tag).toBe("action");
159
- expect(action.actions).toHaveLength(7);
160
- });
161
- });
155
+ it("includes action buttons", () => {
156
+ const card = buildHelpCard("test");
157
+ const parsed = JSON.parse(card);
158
+ const action = parsed.elements[2];
159
+ expect(action.tag).toBe("action");
160
+ expect(action.actions).toHaveLength(7);
161
+ });
162
+
163
+ it("adds ABD prefix help as the final help line", () => {
164
+ const card = buildHelpCard("test");
165
+ const parsed = JSON.parse(card);
166
+ const lines = parsed.elements[1].text.content.split("\n");
167
+
168
+ expect(lines.at(-1)).toBe(ABD_HELP_LINE);
169
+ });
170
+ });
162
171
 
163
172
  // ---------------------------------------------------------------------------
164
173
  // buildCdContent
@@ -8,11 +8,11 @@ import {
8
8
  APP_ID,
9
9
  APP_SECRET,
10
10
  CHATCCC_PORT,
11
- CLAUDE_API_KEY,
12
- CLAUDE_BASE_URL,
13
- CLAUDE_EFFORT,
14
- CLAUDE_MAX_TURN,
15
- CLAUDE_MODEL,
11
+ CLAUDE_API_KEY,
12
+ CLAUDE_BASE_URL,
13
+ CLAUDE_EFFORT,
14
+ CLAUDE_MAX_TURN,
15
+ CLAUDE_MODEL,
16
16
  CLAUDE_SUBAGENT_MODEL,
17
17
  CURSOR_AGENT_ARGS,
18
18
  CURSOR_AGENT_COMMAND,
@@ -53,7 +53,14 @@ const baseAppConfig: AppConfig = {
53
53
  baseUrl: "",
54
54
  maxTurn: 0,
55
55
  },
56
- cursor: { enabled: true, defaultAgent: false, path: "/initial/cursor", model: "initial-cursor-model" },
56
+ cursor: {
57
+ enabled: true,
58
+ defaultAgent: false,
59
+ path: "/initial/cursor",
60
+ model: "initial-cursor-model",
61
+ avatarBatteryMode: "apiPercent",
62
+ onDemandMonthlyBudget: 1000,
63
+ },
57
64
  codex: { enabled: true, defaultAgent: false, path: "/initial/codex", model: "initial-codex-model", effort: "initial-codex-effort" },
58
65
  };
59
66
 
@@ -82,7 +89,7 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
82
89
  expect(APP_SECRET).toBe("NEW_APP_SECRET");
83
90
  });
84
91
 
85
- it("更新 Claude 配置(model / subagentModel / effort / maxTurn / apiKey / baseUrl)", () => {
92
+ it("更新 Claude 配置(model / subagentModel / effort / maxTurn / apiKey / baseUrl)", () => {
86
93
  applyLoadedConfig({
87
94
  ...structuredClone(baseAppConfig),
88
95
  claude: {
@@ -90,18 +97,18 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
90
97
  defaultAgent: true,
91
98
  model: "claude-sonnet-4-6",
92
99
  subagentModel: "claude-haiku-4-5-20251001",
93
- effort: "high",
94
- apiKey: "sk-test",
95
- baseUrl: "https://api.example.com",
96
- maxTurn: 7,
97
- },
98
- });
99
-
100
- expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
101
- expect(CLAUDE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
102
- expect(CLAUDE_EFFORT).toBe("high");
103
- expect(CLAUDE_MAX_TURN).toBe(7);
104
- expect(CLAUDE_API_KEY).toBe("sk-test");
100
+ effort: "high",
101
+ apiKey: "sk-test",
102
+ baseUrl: "https://api.example.com",
103
+ maxTurn: 7,
104
+ },
105
+ });
106
+
107
+ expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
108
+ expect(CLAUDE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
109
+ expect(CLAUDE_EFFORT).toBe("high");
110
+ expect(CLAUDE_MAX_TURN).toBe(7);
111
+ expect(CLAUDE_API_KEY).toBe("sk-test");
105
112
  expect(CLAUDE_BASE_URL).toBe("https://api.example.com");
106
113
  });
107
114
 
@@ -132,7 +139,7 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
132
139
  it("CURSOR_AGENT_ARGS 跟随 cursor.model 重新解析", () => {
133
140
  applyLoadedConfig({
134
141
  ...structuredClone(baseAppConfig),
135
- cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "claude-3.7-sonnet" },
142
+ cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "claude-3.7-sonnet", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
136
143
  });
137
144
 
138
145
  // CURSOR_AGENT_ARGS 是 ['-p', '--force', '--approve-mcps', ..., '--model', 'claude-3.7-sonnet']
@@ -144,7 +151,7 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
144
151
  it("cursor.model 留空时 CURSOR_AGENT_ARGS 不含 --model", () => {
145
152
  applyLoadedConfig({
146
153
  ...structuredClone(baseAppConfig),
147
- cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "" },
154
+ cursor: { enabled: true, defaultAgent: false, path: "/x/cursor", model: "", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
148
155
  });
149
156
 
150
157
  expect(CURSOR_AGENT_ARGS).not.toContain("--model");
@@ -153,7 +160,7 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
153
160
  it("CURSOR_AGENT_COMMAND 优先取 config.cursor.path", () => {
154
161
  applyLoadedConfig({
155
162
  ...structuredClone(baseAppConfig),
156
- cursor: { enabled: true, defaultAgent: false, path: "C:/custom/cursor.exe", model: "" },
163
+ cursor: { enabled: true, defaultAgent: false, path: "C:/custom/cursor.exe", model: "", avatarBatteryMode: "apiPercent", onDemandMonthlyBudget: 1000 },
157
164
  });
158
165
 
159
166
  expect(CURSOR_AGENT_COMMAND).toBe("C:/custom/cursor.exe");