chatccc 0.2.161 → 0.2.162

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
@@ -313,6 +313,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
313
313
  | `/plan <内容>` | 只读计划模式:仅允许读文件和 stop-stuck-loop 请求,不执行任何写操作 |
314
314
  | `/ask <内容>` | 只读问答模式:与 /plan 相同,仅允许读文件和 stop-stuck-loop 请求 |
315
315
  | `/restart` | 重启机器人进程 |
316
+ | `/updateg` | 更新 npm 全局包并重启(仅限 `npm install -g chatccc` 安装的全局进程) |
316
317
 
317
318
  > **模型切换**:`/model` 查看可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。当前仅 Claude Code 支持模型切换(需同时填写 `claude.model` 和 `claude.subagentModel`),Cursor / Codex 切换正在开发中。
318
319
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.161",
3
+ "version": "0.2.162",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,6 +17,7 @@ describe("cardJsonToPlainText", () => {
17
17
  expect(text).toContain("/new cursor");
18
18
  expect(text).toContain("/new codex");
19
19
  expect(text).toContain("/restart");
20
+ expect(text).toContain("/updateg");
20
21
  expect(text).toContain("/cd");
21
22
  });
22
23
 
@@ -156,7 +156,7 @@ describe("buildHelpCard", () => {
156
156
  const parsed = JSON.parse(card);
157
157
  const action = parsed.elements[2];
158
158
  expect(action.tag).toBe("action");
159
- expect(action.actions).toHaveLength(6);
159
+ expect(action.actions).toHaveLength(7);
160
160
  });
161
161
  });
162
162
 
package/src/cards.ts CHANGED
@@ -135,6 +135,8 @@ export function buildHelpCard(
135
135
  "发送 **/newh** 重置当前会话(沿用当前工作目录,不切换)",
136
136
  "发送 **/plan** 以规划模式提问(只读,不执行写操作)",
137
137
  "发送 **/ask** 以问答模式提问(只读,不执行写操作)",
138
+ "发送 **/restart** 重启 ChatCCC 进程",
139
+ "发送 **/updateg** 更新并重启(仅 npm 全局安装可用)",
138
140
  ].join("\n");
139
141
  return JSON.stringify({
140
142
  config: { wide_screen_mode: true },
@@ -148,6 +150,7 @@ export function buildHelpCard(
148
150
  { text: "新建 Cursor 会话(/new cursor)", value: JSON.stringify({ cmd: "new cursor" }), type: "primary" },
149
151
  { text: "新建 Codex 会话(/new codex)", value: JSON.stringify({ cmd: "new codex" }), type: "primary" },
150
152
  { text: "重启 ChatCCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
153
+ { text: "更新并重启(/updateg)", value: JSON.stringify({ cmd: "updateg" }), type: "danger" },
151
154
  { text: "切换工作路径(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
152
155
  ]),
153
156
  ],
package/src/index.ts CHANGED
@@ -213,7 +213,7 @@ function parseCardAction(data: unknown): CardActionResult | null {
213
213
  }
214
214
  if (!cmd) return null;
215
215
 
216
- const CMD_MAP: Record<string, string> = { stop: "/stop", cancel: "/cancel", new: "/new", "new claude": "/new claude", "new cursor": "/new cursor", "new codex": "/new codex", restart: "/restart", state: "/state", cd: "/cd", sessions: "/sessions", newh: "/newh" };
216
+ const CMD_MAP: Record<string, string> = { stop: "/stop", cancel: "/cancel", new: "/new", "new claude": "/new claude", "new cursor": "/new cursor", "new codex": "/new codex", restart: "/restart", updateg: "/updateg", state: "/state", cd: "/cd", sessions: "/sessions", newh: "/newh" };
217
217
  let text = CMD_MAP[cmd] ?? "";
218
218
  if (cmd === "cd" && typeof action.value === "object" && action.value !== null) {
219
219
  const path = (action.value as Record<string, string>).path;
@@ -5,9 +5,9 @@
5
5
  * 所有 IM 平台操作通过 PlatformAdapter 接口注入,不直接依赖 feishu-platform.ts。
6
6
  */
7
7
 
8
- import { spawn } from "node:child_process";
8
+ import { execSync, spawn } from "node:child_process";
9
9
  import { readdir, stat } from "node:fs/promises";
10
- import { readFileSync, writeFileSync } from "node:fs";
10
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
11
11
  import { resolve, dirname } from "node:path";
12
12
 
13
13
  import { makeTraceId, logTrace } from "./trace.ts";
@@ -159,6 +159,45 @@ async function cleanupNonWechatP2pBinding(
159
159
  }
160
160
  }
161
161
 
162
+ /** 检测当前进程是否从 npm 全局安装启动 */
163
+ function isRunningFromGlobalNpm(): boolean {
164
+ try {
165
+ const globalRoot = execSync("npm root -g", { encoding: "utf8", timeout: 5000, windowsHide: true }).trim();
166
+ return resolve(PROJECT_ROOT).startsWith(resolve(globalRoot));
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+
172
+ /** 更新并重启:spawn detached Node.js 中间脚本,轮询父进程退出后执行 npm update + chatccc */
173
+ function scheduleUpdateRestart(): void {
174
+ const parentPid = process.pid;
175
+ const watcherScript = `
176
+ var spawn = require('child_process').spawn;
177
+ var execSync = require('child_process').execSync;
178
+ var parentPid = ${parentPid};
179
+ (function wait() {
180
+ try { process.kill(parentPid, 0); setTimeout(wait, 300); } catch (_) {
181
+ try {
182
+ var npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
183
+ execSync(npmCmd + ' update -g chatccc', { stdio: 'inherit' });
184
+ } catch (e) {
185
+ console.error('npm update failed:', e.message);
186
+ }
187
+ var c = spawn('chatccc', [], { detached: true, stdio: 'ignore', shell: true });
188
+ c.unref();
189
+ process.exit(0);
190
+ }
191
+ })();
192
+ `;
193
+ const child = spawn("node", ["-e", watcherScript], {
194
+ detached: true,
195
+ stdio: "ignore",
196
+ shell: true,
197
+ });
198
+ child.unref();
199
+ }
200
+
162
201
  // ---------------------------------------------------------------------------
163
202
  // handleCommand — 平台无关的命令分发
164
203
  // ---------------------------------------------------------------------------
@@ -212,6 +251,22 @@ export async function handleCommand(
212
251
  return;
213
252
  }
214
253
 
254
+ if (textLower === "/updateg") {
255
+ logTrace(tid, "BRANCH", { cmd: "/updateg" });
256
+ if (!isRunningFromGlobalNpm()) {
257
+ await platform.sendText(chatId, "当前进程非 npm 全局安装,无法使用 /updateg 更新。请通过 npm install -g chatccc 安装后使用。").catch(() => {});
258
+ logTrace(tid, "DONE", { outcome: "updateg_not_global" });
259
+ return;
260
+ }
261
+ await platform.sendText(chatId, "正在更新并重启,请稍候...").catch(() => {});
262
+ logTrace(tid, "DONE", { outcome: "updateg" });
263
+ appendStartupTrace("updateg: spawn watcher begin", { fromPid: process.pid });
264
+ scheduleUpdateRestart();
265
+ // 短暂延迟确保消息发送后再退出
266
+ setTimeout(() => process.exit(0), 500);
267
+ return;
268
+ }
269
+
215
270
  if (textLower === "/cd" || textLower.startsWith("/cd ")) {
216
271
  logTrace(tid, "BRANCH", {
217
272
  cmd: "/cd",