chatccc 0.2.178 → 0.2.179

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/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", updateg: "/updateg", 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", update: "/update", 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;
@@ -78,10 +78,11 @@ import {
78
78
  enqueueMessage,
79
79
  cancelQueuedMessage,
80
80
  } from "./session-chat-binding.ts";
81
- import { getCodexUsageSummary, getTenantAccessToken, sendPostMessage } from "./feishu-platform.ts";
82
- export { type PlatformAdapter } from "./platform-adapter.ts";
83
- import type { PlatformAdapter } from "./platform-adapter.ts";
84
- import type { CodexUsageSummary } from "./feishu-api.ts";
81
+ import { getCodexUsageSummary, getTenantAccessToken, sendPostMessage } from "./feishu-platform.ts";
82
+ import { getCursorUsageSummary, type CursorUsageSummary } from "./cursor-usage.ts";
83
+ export { type PlatformAdapter } from "./platform-adapter.ts";
84
+ import type { PlatformAdapter } from "./platform-adapter.ts";
85
+ import type { CodexUsageSummary } from "./feishu-api.ts";
85
86
 
86
87
  // ---------------------------------------------------------------------------
87
88
  // 辅助函数
@@ -97,7 +98,7 @@ export function sessionChatName(left: string, cwd: string): string {
97
98
  }
98
99
 
99
100
  /** 模型模糊匹配:精确匹配优先,否则找子串匹配(模型名越短越优先) */
100
- function findModelMatch(input: string, models: string[]): string | null {
101
+ function findModelMatch(input: string, models: string[]): string | null {
101
102
  if (models.length === 0) return null;
102
103
  const inputLower = input.toLowerCase();
103
104
  // 1) 精确匹配(忽略大小写)
@@ -109,72 +110,166 @@ function findModelMatch(input: string, models: string[]): string | null {
109
110
  .filter(m => m.toLowerCase().includes(inputLower))
110
111
  .sort((a, b) => a.length - b.length);
111
112
  return candidates[0] ?? null;
112
- }
113
-
114
- function formatCodexUsageSummary(usage: CodexUsageSummary): string {
115
- const progressBar = (usedPercent: number) => {
116
- const width = 20;
117
- const usedBlocks = Math.max(0, Math.min(width, Math.round((usedPercent / 100) * width)));
118
- return `[${"█".repeat(usedBlocks)}${"░".repeat(width - usedBlocks)}]`;
119
- };
120
-
121
- const formatDuration = (seconds: number | null) => {
122
- if (seconds === null) return "";
123
- if (seconds <= 0) return "(已到重置时间)";
124
- const totalMinutes = Math.max(1, Math.floor(seconds / 60));
125
- const days = Math.floor(totalMinutes / 1440);
126
- const hours = Math.floor((totalMinutes % 1440) / 60);
127
- const minutes = totalMinutes % 60;
128
- const parts: string[] = [];
129
- if (days > 0) parts.push(`${days}天`);
130
- if (hours > 0) parts.push(`${hours}小时`);
131
- if (minutes > 0 || parts.length === 0) parts.push(`${minutes}分钟`);
132
- return `(约 ${parts.join("")}后)`;
133
- };
134
-
135
- const formatResetTime = (balance: CodexUsageSummary["fiveHour"]) => {
136
- if (balance.resetAtEpochSeconds === null) return "暂无数据";
137
- const date = new Date(balance.resetAtEpochSeconds * 1000);
138
- const pad = (value: number) => String(value).padStart(2, "0");
139
- const absolute = [
140
- date.getFullYear(),
141
- "-",
142
- pad(date.getMonth() + 1),
143
- "-",
144
- pad(date.getDate()),
145
- " ",
146
- pad(date.getHours()),
147
- ":",
148
- pad(date.getMinutes()),
149
- ].join("");
150
- return `${absolute}${formatDuration(balance.resetAfterSeconds)}`;
151
- };
152
-
153
- const formatWindow = (label: string, balance: CodexUsageSummary["fiveHour"] | null) => {
154
- if (!balance) return `**${label}:** 暂无数据`;
155
- return [
156
- `**${label}:** 已用 ${balance.usedPercent}%,剩余 ${balance.remainingPercent}%,重置: ${formatResetTime(balance)}`,
157
- progressBar(balance.usedPercent),
158
- ].join("\n");
159
- };
160
-
161
- return [
162
- "Codex 用量:",
163
- "",
164
- formatWindow("5h", usage.fiveHour),
165
- formatWindow("周", usage.weekly),
166
- ].join("\n");
167
- }
168
-
169
- function codexUsageHelpLine(tool: string): string {
170
- return tool === "codex"
171
- ? "\n发送 **/usage** 查看 Codex 5h 和周用量。"
172
- : "";
173
- }
174
-
175
- function isUntitledSessionChatName(name: string): boolean {
176
- return name === "新会话" || name.startsWith("新会话-");
177
- }
113
+ }
114
+
115
+ function formatCodexUsageSummary(usage: CodexUsageSummary): string {
116
+ const progressBar = (usedPercent: number) => {
117
+ const width = 20;
118
+ const usedBlocks = Math.max(0, Math.min(width, Math.round((usedPercent / 100) * width)));
119
+ return `[${"█".repeat(usedBlocks)}${"░".repeat(width - usedBlocks)}]`;
120
+ };
121
+
122
+ const formatDuration = (seconds: number | null) => {
123
+ if (seconds === null) return "";
124
+ if (seconds <= 0) return "(已到重置时间)";
125
+ const totalMinutes = Math.max(1, Math.floor(seconds / 60));
126
+ const days = Math.floor(totalMinutes / 1440);
127
+ const hours = Math.floor((totalMinutes % 1440) / 60);
128
+ const minutes = totalMinutes % 60;
129
+ const parts: string[] = [];
130
+ if (days > 0) parts.push(`${days}天`);
131
+ if (hours > 0) parts.push(`${hours}小时`);
132
+ if (minutes > 0 || parts.length === 0) parts.push(`${minutes}分钟`);
133
+ return `(约 ${parts.join("")}后)`;
134
+ };
135
+
136
+ const formatResetTime = (balance: CodexUsageSummary["fiveHour"]) => {
137
+ if (balance.resetAtEpochSeconds === null) return "暂无数据";
138
+ const date = new Date(balance.resetAtEpochSeconds * 1000);
139
+ const pad = (value: number) => String(value).padStart(2, "0");
140
+ const absolute = [
141
+ date.getFullYear(),
142
+ "-",
143
+ pad(date.getMonth() + 1),
144
+ "-",
145
+ pad(date.getDate()),
146
+ " ",
147
+ pad(date.getHours()),
148
+ ":",
149
+ pad(date.getMinutes()),
150
+ ].join("");
151
+ return `${absolute}${formatDuration(balance.resetAfterSeconds)}`;
152
+ };
153
+
154
+ const formatWindow = (label: string, balance: CodexUsageSummary["fiveHour"] | null) => {
155
+ if (!balance) return `**${label}:** 暂无数据`;
156
+ return [
157
+ `**${label}:** 已用 ${balance.usedPercent}%,剩余 ${balance.remainingPercent}%,重置: ${formatResetTime(balance)}`,
158
+ progressBar(balance.usedPercent),
159
+ ].join("\n");
160
+ };
161
+
162
+ return [
163
+ "Codex 用量:",
164
+ "",
165
+ formatWindow("5h", usage.fiveHour),
166
+ formatWindow("周", usage.weekly),
167
+ ].join("\n");
168
+ }
169
+
170
+ function formatCursorUsageSummary(usage: CursorUsageSummary): string {
171
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
172
+ const dateFormatter = new Intl.DateTimeFormat("zh-CN", {
173
+ timeZone,
174
+ year: "numeric",
175
+ month: "2-digit",
176
+ day: "2-digit",
177
+ hour: "2-digit",
178
+ minute: "2-digit",
179
+ second: "2-digit",
180
+ hour12: false,
181
+ timeZoneName: "shortOffset",
182
+ });
183
+ const formatDate = (value: string | undefined) => {
184
+ const timestamp = Number(value);
185
+ if (!Number.isFinite(timestamp)) return "暂无数据";
186
+ return dateFormatter.format(new Date(timestamp));
187
+ };
188
+ const formatMoney = (value: number | undefined) => {
189
+ if (typeof value !== "number" || !Number.isFinite(value)) return "暂无数据";
190
+ return `$${(value / 100).toFixed(2)}`;
191
+ };
192
+ const formatPercent = (value: number | undefined) => {
193
+ if (typeof value !== "number" || !Number.isFinite(value)) return "暂无数据";
194
+ return `${value}%`;
195
+ };
196
+ const plan = usage.planUsage;
197
+ const spendLimit = usage.spendLimitUsage;
198
+
199
+ return [
200
+ "Cursor 用量:",
201
+ "",
202
+ `**计费周期:** ${formatDate(usage.billingCycleStart)} - ${formatDate(usage.billingCycleEnd)}`,
203
+ "",
204
+ "**Included usage:**",
205
+ `- Total: ${formatMoney(plan?.totalSpend)} / ${formatMoney(plan?.limit)} (${formatPercent(plan?.totalPercentUsed)})`,
206
+ `- Included: ${formatMoney(plan?.includedSpend)}`,
207
+ `- Bonus: ${formatMoney(plan?.bonusSpend)}`,
208
+ `- Auto: ${formatPercent(plan?.autoPercentUsed)}`,
209
+ `- API: ${formatPercent(plan?.apiPercentUsed)}`,
210
+ "",
211
+ "**On-Demand / Spend limit:**",
212
+ `- Individual used: ${formatMoney(spendLimit?.individualUsed)}`,
213
+ `- Pool used: ${formatMoney(spendLimit?.pooledUsed)} / ${formatMoney(spendLimit?.pooledLimit)}`,
214
+ `- Pool remaining: ${formatMoney(spendLimit?.pooledRemaining)}`,
215
+ `- Limit type: ${spendLimit?.limitType ?? "暂无数据"}`,
216
+ `- Display threshold: ${formatMoney(usage.displayThreshold)}`,
217
+ "",
218
+ `**Enabled:** ${usage.enabled === undefined ? "暂无数据" : String(usage.enabled)}`,
219
+ usage.displayMessage ? `**Message:** ${usage.displayMessage}` : "",
220
+ usage.autoModelSelectedDisplayMessage ? `**Auto model:** ${usage.autoModelSelectedDisplayMessage}` : "",
221
+ usage.namedModelSelectedDisplayMessage ? `**Named model:** ${usage.namedModelSelectedDisplayMessage}` : "",
222
+ usage.autoBucketModels?.length ? `**Auto bucket models:** ${usage.autoBucketModels.join(", ")}` : "",
223
+ ].filter(Boolean).join("\n");
224
+ }
225
+
226
+ function usageHelpLine(tool: string): string {
227
+ if (tool === "codex") return "\n发送 **/usage** 查看 Codex 5h 和周用量。";
228
+ if (tool === "cursor") return "\n发送 **/usage** 查看 Cursor 用量。";
229
+ return "";
230
+ }
231
+
232
+ async function resolveUsageTool(chatId: string): Promise<"codex" | "cursor"> {
233
+ try {
234
+ const registry = await loadSessionRegistryForBinding();
235
+ return registry[chatId]?.tool === "cursor" ? "cursor" : "codex";
236
+ } catch {
237
+ return "codex";
238
+ }
239
+ }
240
+
241
+ async function sendUsageSummary(platform: PlatformAdapter, chatId: string, tool: "codex" | "cursor"): Promise<void> {
242
+ if (tool === "cursor") {
243
+ const content = formatCursorUsageSummary(await getCursorUsageSummary());
244
+ if (platform.kind === "wechat") {
245
+ await platform.sendText(chatId, content).catch(() => {});
246
+ } else {
247
+ await platform.sendCard(chatId, "Cursor Usage", content, "blue");
248
+ }
249
+ return;
250
+ }
251
+
252
+ const content = formatCodexUsageSummary(await getCodexUsageSummary());
253
+ if (platform.kind === "wechat") {
254
+ await platform.sendText(chatId, content).catch(() => {});
255
+ } else {
256
+ await platform.sendCard(chatId, "Codex Usage", content, "blue");
257
+ }
258
+ }
259
+
260
+ async function sendUsageError(platform: PlatformAdapter, chatId: string, tool: "codex" | "cursor", err: unknown): Promise<void> {
261
+ const toolLabel = tool === "cursor" ? "Cursor" : "Codex";
262
+ const message = `${toolLabel} 用量获取失败:${(err as Error).message}`;
263
+ if (platform.kind === "wechat") {
264
+ await platform.sendText(chatId, message).catch(() => {});
265
+ } else {
266
+ await platform.sendCard(chatId, `${toolLabel} Usage`, message, "red");
267
+ }
268
+ }
269
+
270
+ function isUntitledSessionChatName(name: string): boolean {
271
+ return name === "新会话" || name.startsWith("新会话-");
272
+ }
178
273
 
179
274
  function shouldSendWechatProcessingAck(
180
275
  platform: PlatformAdapter,
@@ -232,49 +327,49 @@ function isRunningFromGlobalNpm(): boolean {
232
327
  }
233
328
  }
234
329
 
235
- const UPDATEG_LOG = join(homedir(), ".chatccc", "logs", "updateg-watcher.log");
330
+ const UPDATE_LOG = join(homedir(), ".chatccc", "logs", "update-watcher.log");
236
331
 
237
332
  function updLog(msg: string): void {
238
333
  const ts = new Date().toISOString();
239
- try { appendFileSync(UPDATEG_LOG, `${ts} [UPG-SYNC] ${msg}\n`, "utf-8"); } catch {}
334
+ try { appendFileSync(UPDATE_LOG, `${ts} [UPDATE-SYNC] ${msg}\n`, "utf-8"); } catch {}
240
335
  }
241
336
 
242
337
  /** 同步更新 npm 全局包并 spawn 新进程重启。不依赖 systemd 或任何服务管理器。 */
243
338
  function syncUpdateAndRestart(): void {
244
339
  updLog(`sync update start, pid=${process.pid}`);
245
- appendStartupTrace("updateg: sync update start", { pid: process.pid });
340
+ appendStartupTrace("update: sync update start", { pid: process.pid });
246
341
 
247
342
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
248
343
 
249
344
  // 1. npm update
250
345
  updLog(`running: ${npmCmd} update -g chatccc`);
251
- appendStartupTrace("updateg: npm update begin", { npmCmd });
346
+ appendStartupTrace("update: npm update begin", { npmCmd });
252
347
  const t0 = Date.now();
253
348
  try {
254
349
  const out = execSync(`${npmCmd} update -g chatccc 2>&1`, { encoding: "utf8", timeout: 120000, windowsHide: true });
255
350
  const elapsed = Date.now() - t0;
256
351
  updLog(`npm update OK (${elapsed}ms): ${out.slice(0, 500)}`);
257
- appendStartupTrace("updateg: npm update OK", { elapsedMs: elapsed, outputLen: out.length });
352
+ appendStartupTrace("update: npm update OK", { elapsedMs: elapsed, outputLen: out.length });
258
353
  } catch (e) {
259
354
  const elapsed = Date.now() - t0;
260
355
  const err = e as Error & { stderr?: string; stdout?: string; status?: number };
261
356
  updLog(`npm update failed (${elapsed}ms): message=${err.message}, stderr=${(err.stderr || "").slice(0, 500)}, stdout=${(err.stdout || "").slice(0, 200)}`);
262
- appendStartupTrace("updateg: npm update failed", { elapsedMs: elapsed, message: err.message, stderrLen: (err.stderr || "").length });
357
+ appendStartupTrace("update: npm update failed", { elapsedMs: elapsed, message: err.message, stderrLen: (err.stderr || "").length });
263
358
 
264
359
  // fallback
265
360
  updLog(`fallback: ${npmCmd} install -g chatccc@latest`);
266
- appendStartupTrace("updateg: npm install fallback begin", { npmCmd });
361
+ appendStartupTrace("update: npm install fallback begin", { npmCmd });
267
362
  const t1 = Date.now();
268
363
  try {
269
364
  const out2 = execSync(`${npmCmd} install -g chatccc@latest 2>&1`, { encoding: "utf8", timeout: 120000, windowsHide: true });
270
365
  const elapsed2 = Date.now() - t1;
271
366
  updLog(`npm install fallback OK (${elapsed2}ms): ${out2.slice(0, 500)}`);
272
- appendStartupTrace("updateg: npm install fallback OK", { elapsedMs: elapsed2, outputLen: out2.length });
367
+ appendStartupTrace("update: npm install fallback OK", { elapsedMs: elapsed2, outputLen: out2.length });
273
368
  } catch (e2) {
274
369
  const elapsed2 = Date.now() - t1;
275
370
  const err2 = e2 as Error & { stderr?: string; stdout?: string };
276
371
  updLog(`npm install fallback also failed (${elapsed2}ms): message=${err2.message}, stderr=${(err2.stderr || "").slice(0, 500)}`);
277
- appendStartupTrace("updateg: npm install fallback failed", { elapsedMs: elapsed2, message: err2.message });
372
+ appendStartupTrace("update: npm install fallback failed", { elapsedMs: elapsed2, message: err2.message });
278
373
  }
279
374
  }
280
375
 
@@ -283,22 +378,22 @@ function syncUpdateAndRestart(): void {
283
378
  const binName = process.platform === "win32" ? "chatccc.cmd" : "chatccc";
284
379
  const binPath = npmPrefix ? join(npmPrefix, binName) : "chatccc";
285
380
  updLog(`bin path: npmPrefix=${npmPrefix || "(empty)"}, binPath=${binPath}`);
286
- appendStartupTrace("updateg: spawn begin", { npmPrefix: npmPrefix || "(empty)", binPath });
381
+ appendStartupTrace("update: spawn begin", { npmPrefix: npmPrefix || "(empty)", binPath });
287
382
 
288
383
  // 3. spawn new chatccc
289
384
  try {
290
385
  const child = spawn(binPath, [], { detached: true, stdio: "ignore", shell: true });
291
386
  child.unref();
292
387
  updLog(`spawn new chatccc OK, childPid=${child.pid}, bin=${binPath}`);
293
- appendStartupTrace("updateg: spawn OK", { childPid: child.pid, binPath });
388
+ appendStartupTrace("update: spawn OK", { childPid: child.pid, binPath });
294
389
  } catch (e) {
295
390
  const errMsg = (e as Error).message;
296
391
  updLog(`spawn new chatccc failed: ${errMsg}`);
297
- appendStartupTrace("updateg: spawn failed", { error: errMsg });
392
+ appendStartupTrace("update: spawn failed", { error: errMsg });
298
393
  }
299
394
 
300
395
  updLog("sync update done, parent exiting in 2s");
301
- appendStartupTrace("updateg: sync update done, exiting", { pid: process.pid });
396
+ appendStartupTrace("update: sync update done, exiting", { pid: process.pid });
302
397
  }
303
398
 
304
399
  // ---------------------------------------------------------------------------
@@ -354,46 +449,37 @@ export async function handleCommand(
354
449
  return;
355
450
  }
356
451
 
357
- if (textLower === "/updateg") {
358
- logTrace(tid, "BRANCH", { cmd: "/updateg" });
452
+ if (textLower === "/update") {
453
+ logTrace(tid, "BRANCH", { cmd: "/update" });
359
454
  const isGlobal = isRunningFromGlobalNpm();
360
- appendStartupTrace("updateg: command received", { isGlobal, chatId });
455
+ appendStartupTrace("update: command received", { isGlobal, chatId });
361
456
  if (!isGlobal) {
362
- await platform.sendText(chatId, "当前进程非 npm 全局安装,无法使用 /updateg 更新。请通过 npm install -g chatccc 安装后使用。").catch(() => {});
363
- logTrace(tid, "DONE", { outcome: "updateg_not_global" });
457
+ await platform.sendText(chatId, "当前进程非 npm 全局安装,无法使用 /update 更新。请通过 npm install -g chatccc 安装后使用。").catch(() => {});
458
+ logTrace(tid, "DONE", { outcome: "update_not_global" });
364
459
  return;
365
460
  }
366
461
  await platform.sendText(chatId, "正在更新并重启,请稍候...").catch(() => {});
367
- logTrace(tid, "DONE", { outcome: "updateg" });
368
- appendStartupTrace("updateg: sync update begin", { fromPid: process.pid });
462
+ logTrace(tid, "DONE", { outcome: "update" });
463
+ appendStartupTrace("update: sync update begin", { fromPid: process.pid });
369
464
  syncUpdateAndRestart();
370
465
  setTimeout(() => process.exit(0), 2000);
371
- return;
372
- }
373
-
374
- if (textLower === "/usage") {
375
- logTrace(tid, "BRANCH", { cmd: "/usage", tool: "codex" });
376
- try {
377
- const content = formatCodexUsageSummary(await getCodexUsageSummary());
378
- if (platform.kind === "wechat") {
379
- await platform.sendText(chatId, content).catch(() => {});
380
- } else {
381
- await platform.sendCard(chatId, "Codex Usage", content, "blue");
382
- }
383
- logTrace(tid, "DONE", { outcome: "usage" });
384
- } catch (err) {
385
- const message = `无法获取 Codex 用量:${(err as Error).message}`;
386
- if (platform.kind === "wechat") {
387
- await platform.sendText(chatId, message).catch(() => {});
388
- } else {
389
- await platform.sendCard(chatId, "Codex Usage", message, "red");
390
- }
391
- logTrace(tid, "DONE", { outcome: "usage_fail", error: (err as Error).message });
392
- }
393
- return;
394
- }
395
-
396
- if (textLower === "/cd" || textLower.startsWith("/cd ")) {
466
+ return;
467
+ }
468
+
469
+ if (textLower === "/usage") {
470
+ const usageTool = await resolveUsageTool(chatId);
471
+ logTrace(tid, "BRANCH", { cmd: "/usage", tool: usageTool });
472
+ try {
473
+ await sendUsageSummary(platform, chatId, usageTool);
474
+ logTrace(tid, "DONE", { outcome: "usage", tool: usageTool });
475
+ } catch (err) {
476
+ await sendUsageError(platform, chatId, usageTool, err);
477
+ logTrace(tid, "DONE", { outcome: "usage_fail", tool: usageTool, error: (err as Error).message });
478
+ }
479
+ return;
480
+ }
481
+
482
+ if (textLower === "/cd" || textLower.startsWith("/cd ")) {
397
483
  logTrace(tid, "BRANCH", {
398
484
  cmd: "/cd",
399
485
  arg: text.slice(3).trim() || "(none)",
@@ -612,13 +698,13 @@ export async function handleCommand(
612
698
  `**工作目录:** \`${cwd}\`\n\n` +
613
699
  `直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
614
700
  `发送 **/cd** 切换新建会话的默认目录。\n` +
615
- `发送 **/model** 查看或切换当前会话的模型。\n` +
616
- `发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
617
- `发送 **/sessions** 查看所有会话状态。\n` +
618
- `发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
619
- codexUsageHelpLine(tool),
620
- "green",
621
- );
701
+ `发送 **/model** 查看或切换当前会话的模型。\n` +
702
+ `发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
703
+ `发送 **/sessions** 查看所有会话状态。\n` +
704
+ `发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
705
+ usageHelpLine(tool),
706
+ "green",
707
+ );
622
708
  console.log(
623
709
  `[${ts()}] [NEW] P2P session created: ${sessionId} (${toolLabel})`,
624
710
  );
@@ -699,13 +785,13 @@ export async function handleCommand(
699
785
  `**工作目录:** \`${cwd}\`\n\n` +
700
786
  `直接在这里发消息即可与 ${toolLabel} 对话。\n\n` +
701
787
  `发送 **/cd** 切换新建会话的默认目录。\n` +
702
- `发送 **/model** 查看或切换当前会话的模型。\n` +
703
- `发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
704
- `发送 **/sessions** 查看所有会话状态。\n` +
705
- `发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
706
- codexUsageHelpLine(tool),
707
- "green",
708
- );
788
+ `发送 **/model** 查看或切换当前会话的模型。\n` +
789
+ `发送 **/new** 创建新会话,**/newh** 重置当前会话(沿用工作目录)。\n` +
790
+ `发送 **/sessions** 查看所有会话状态。\n` +
791
+ `发送 \`/git <子命令>\` 在本会话工作目录执行 git,例如 \`/git status\`、\`/git log --oneline -n 5\`。` +
792
+ usageHelpLine(tool),
793
+ "green",
794
+ );
709
795
 
710
796
  console.log(`[${ts()}] [STEP 4/4] Replied to new group → OK`);
711
797
  logTrace(tid, "DONE", {