chatccc 0.2.5 → 0.2.6

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,229 @@
1
+ // =============================================================================
2
+ // cursor-adapter.ts — Cursor Agent CLI 适配器
3
+ // =============================================================================
4
+ // 通过 agent -p --output-format stream-json 与 Cursor agent 交互。
5
+ // 命令行可通过 CHATCCC_CURSOR_COMMAND / CHATCCC_CURSOR_ARGS 环境变量自定义。
6
+ // =============================================================================
7
+
8
+ import { spawn, type ChildProcess } from "node:child_process";
9
+ import { createInterface } from "node:readline";
10
+
11
+ import type {
12
+ ToolAdapter,
13
+ UnifiedBlock,
14
+ UnifiedStreamMessage,
15
+ CreateSessionResult,
16
+ SessionInfo,
17
+ } from "./adapter-interface.ts";
18
+ import { CURSOR_AGENT_COMMAND, CURSOR_AGENT_ARGS } from "../config.ts";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // 类型:Cursor JSONL 消息行
22
+ // ---------------------------------------------------------------------------
23
+
24
+ interface CursorMessageLine {
25
+ type?: string;
26
+ subtype?: string;
27
+ session_id?: string;
28
+ cwd?: string;
29
+ model?: string;
30
+ message?: {
31
+ role?: string;
32
+ content?: CursorContentBlock[];
33
+ };
34
+ result?: string;
35
+ duration_ms?: number;
36
+ usage?: {
37
+ inputTokens?: number;
38
+ outputTokens?: number;
39
+ };
40
+ }
41
+
42
+ interface CursorContentBlock {
43
+ type?: string;
44
+ text?: string;
45
+ thinking?: string;
46
+ name?: string;
47
+ input?: unknown;
48
+ tool_use_id?: string;
49
+ content?: unknown;
50
+ is_error?: boolean;
51
+ query?: string;
52
+ [key: string]: unknown;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // normalizeCursorMessage — Cursor 消息 → UnifiedStreamMessage | null
57
+ // ---------------------------------------------------------------------------
58
+
59
+ export function normalizeCursorMessage(
60
+ msg: CursorMessageLine,
61
+ ): UnifiedStreamMessage | null {
62
+ if (msg.type === "assistant" && msg.message?.content) {
63
+ const blocks: UnifiedBlock[] = [];
64
+ for (const block of msg.message.content) {
65
+ if (block.type === "thinking" && block.thinking) {
66
+ blocks.push({ type: "thinking", thinking: block.thinking });
67
+ } else if (block.type === "tool_use") {
68
+ blocks.push({
69
+ type: "tool_use",
70
+ name: block.name ?? "unknown",
71
+ input: block.input,
72
+ });
73
+ } else if (block.type === "tool_result") {
74
+ blocks.push({
75
+ type: "tool_result",
76
+ tool_use_id: block.tool_use_id ?? "",
77
+ content: block.content,
78
+ is_error: block.is_error,
79
+ });
80
+ } else if (block.type === "redacted_thinking") {
81
+ blocks.push({ type: "redacted_thinking" });
82
+ } else if (block.type === "search_result") {
83
+ blocks.push({
84
+ type: "search_result",
85
+ query: block.query ?? "",
86
+ });
87
+ } else if (block.type === "text" && block.text) {
88
+ blocks.push({ type: "text", text: block.text });
89
+ }
90
+ }
91
+ return { type: "assistant", blocks };
92
+ }
93
+
94
+ if (msg.type === "user" && msg.message?.content) {
95
+ const blocks: UnifiedBlock[] = [];
96
+ for (const block of msg.message.content) {
97
+ if (block.type === "tool_result") {
98
+ blocks.push({
99
+ type: "tool_result",
100
+ tool_use_id: block.tool_use_id ?? "",
101
+ content: block.content,
102
+ is_error: block.is_error,
103
+ });
104
+ } else if (block.type === "text" && block.text) {
105
+ blocks.push({ type: "text", text: block.text });
106
+ }
107
+ }
108
+ return { type: "user", blocks };
109
+ }
110
+
111
+ if (msg.type === "system" && msg.subtype === "compact_boundary") {
112
+ const meta = (msg as Record<string, unknown>).compact_metadata as
113
+ | { trigger?: "manual" | "auto"; pre_tokens?: number; post_tokens?: number }
114
+ | undefined;
115
+ if (!meta) return null;
116
+ return {
117
+ type: "system",
118
+ blocks: [
119
+ {
120
+ type: "compact_boundary",
121
+ trigger: meta.trigger ?? "auto",
122
+ pre_tokens: meta.pre_tokens ?? 0,
123
+ post_tokens: meta.post_tokens,
124
+ },
125
+ ],
126
+ };
127
+ }
128
+
129
+ return null;
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // 子进程辅助函数
134
+ // ---------------------------------------------------------------------------
135
+
136
+ function spawnAgent(
137
+ extraArgs: string[],
138
+ cwd?: string,
139
+ ): ChildProcess {
140
+ const allArgs = [...CURSOR_AGENT_ARGS, ...extraArgs];
141
+ return spawn(CURSOR_AGENT_COMMAND, allArgs, {
142
+ cwd,
143
+ stdio: ["ignore", "pipe", "pipe"],
144
+ windowsHide: true,
145
+ shell: true,
146
+ });
147
+ }
148
+
149
+ async function* readJsonLines(
150
+ proc: ChildProcess,
151
+ signal?: AbortSignal,
152
+ ): AsyncGenerator<CursorMessageLine> {
153
+ const rl = createInterface({ input: proc.stdout!, crlfDelay: Infinity });
154
+ for await (const line of rl) {
155
+ if (signal?.aborted) break;
156
+ const trimmed = line.trim();
157
+ if (!trimmed) continue;
158
+ try {
159
+ yield JSON.parse(trimmed) as CursorMessageLine;
160
+ } catch { /* 非 JSON 行静默跳过 */ }
161
+ }
162
+ }
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // 适配器实现
166
+ // ---------------------------------------------------------------------------
167
+
168
+ class CursorAdapter implements ToolAdapter {
169
+ readonly displayName = "Cursor";
170
+ readonly sessionDescPrefix = "Cursor Session:";
171
+ private activeProcs = new Set<ChildProcess>();
172
+
173
+ async createSession(cwd: string): Promise<CreateSessionResult> {
174
+ const proc = spawnAgent(["ok"], cwd);
175
+ this.activeProcs.add(proc);
176
+
177
+ for await (const msg of readJsonLines(proc)) {
178
+ if (msg.type === "system" && msg.subtype === "init" && msg.session_id) {
179
+ const sessionId = msg.session_id;
180
+ this.activeProcs.delete(proc);
181
+ proc.kill();
182
+ return { sessionId };
183
+ }
184
+ }
185
+
186
+ proc.kill();
187
+ this.activeProcs.delete(proc);
188
+ throw new Error("No session ID in Cursor init event");
189
+ }
190
+
191
+ async *prompt(
192
+ sessionId: string,
193
+ userText: string,
194
+ cwd: string,
195
+ signal?: AbortSignal,
196
+ ): AsyncIterable<UnifiedStreamMessage> {
197
+ const proc = spawnAgent(["--resume", sessionId, userText], cwd);
198
+ this.activeProcs.add(proc);
199
+
200
+ try {
201
+ for await (const raw of readJsonLines(proc, signal)) {
202
+ if (signal?.aborted) break;
203
+ const normalized = normalizeCursorMessage(raw);
204
+ if (normalized) yield normalized;
205
+ }
206
+ } finally {
207
+ proc.kill();
208
+ this.activeProcs.delete(proc);
209
+ }
210
+ }
211
+
212
+ async getSessionInfo(
213
+ _sessionId: string,
214
+ ): Promise<SessionInfo | undefined> {
215
+ return { sessionId: _sessionId };
216
+ }
217
+
218
+ async closeSession(_sessionId: string): Promise<void> {
219
+ // 子进程由 prompt 的 finally 自动 kill
220
+ }
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // 工厂函数
225
+ // ---------------------------------------------------------------------------
226
+
227
+ export function createCursorAdapter(): ToolAdapter {
228
+ return new CursorAdapter();
229
+ }
package/src/cards.ts CHANGED
@@ -114,10 +114,12 @@ export function buildHelpCard(userText: string): string {
114
114
  header: { template: "blue", title: { content: "ChatCCC", tag: "plain_text" } },
115
115
  elements: [
116
116
  { tag: "div", text: { tag: "lark_md", content: `你发送了: ${userText}` } },
117
+ { tag: "div", text: { tag: "lark_md", content: "使用 **/new**(默认 Claude Code)或 **/new claude** / **/new cursor** 创建新会话" } },
117
118
  buildButtons([
118
- { text: "新建会话(/new)", value: JSON.stringify({ cmd: "new" }), type: "primary" },
119
- { text: "重启Chat CCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
120
- { text: "查看/切换工作路径(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
119
+ { text: "新建 Claude Code 会话(/new claude)", value: JSON.stringify({ cmd: "new" }), type: "primary" },
120
+ { text: "新建 Cursor 会话(/new cursor)", value: JSON.stringify({ cmd: "new cursor" }), type: "primary" },
121
+ { text: "重启 ChatCCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
122
+ { text: "查看/切换工作路径及最近使用(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
121
123
  ]),
122
124
  ],
123
125
  });
@@ -159,37 +161,134 @@ export function buildCdContent(
159
161
  return lines.join("\n");
160
162
  }
161
163
 
162
- // 所有会话列表卡片
164
+ // 工作路径卡片(/cd 无参数时使用,含最近使用路径按钮)
165
+ export function buildCdCard(
166
+ dirPath: string,
167
+ entries: { name: string; isDir: boolean }[],
168
+ recentDirs: string[],
169
+ sessionCwd?: string,
170
+ maxFiles = 100,
171
+ ): string {
172
+ const display = entries.slice(0, maxFiles);
173
+ const overflow = entries.length > maxFiles ? `\n...(共 ${entries.length} 个条目,仅显示前 ${maxFiles} 个)` : "";
174
+ const listing = display.map(e => e.isDir ? `📁 ${e.name}/` : `📄 ${e.name}`).join("\n");
175
+
176
+ const currentLine = sessionCwd
177
+ ? `**当前会话工作路径:** \`${sessionCwd}\``
178
+ : "";
179
+
180
+ const elements: object[] = [];
181
+
182
+ if (currentLine) {
183
+ elements.push({ tag: "markdown", content: currentLine });
184
+ }
185
+ elements.push({ tag: "markdown", content: `**新会话默认工作路径:** \`${dirPath}\`` });
186
+
187
+ if (recentDirs.length > 0) {
188
+ elements.push({ tag: "hr" });
189
+ elements.push({ tag: "markdown", content: "**最近使用过的路径(点击切换):**" });
190
+ elements.push({
191
+ tag: "action",
192
+ actions: recentDirs.map(d => {
193
+ const name = d.split(/[\\/]/).filter(Boolean).pop() ?? d;
194
+ const label = d.length > 36 ? `...${d.slice(-33)}` : d;
195
+ return {
196
+ tag: "button",
197
+ text: { tag: "plain_text", content: label },
198
+ type: "default",
199
+ value: { action: "cd", path: d },
200
+ };
201
+ }),
202
+ });
203
+ }
204
+
205
+ elements.push({ tag: "hr" });
206
+ elements.push({
207
+ tag: "markdown",
208
+ content: [
209
+ `**目录内容** (最多 ${maxFiles} 个):`,
210
+ listing,
211
+ overflow,
212
+ ].join("\n"),
213
+ });
214
+
215
+ return JSON.stringify({
216
+ schema: "2.0",
217
+ config: { wide_screen_mode: true },
218
+ header: {
219
+ template: "blue",
220
+ title: { tag: "plain_text", content: "工作路径" },
221
+ },
222
+ body: {
223
+ direction: "vertical",
224
+ elements,
225
+ },
226
+ });
227
+ }
228
+
229
+ // 所有会话列表卡片(Claude Code 优先,然后 Cursor)
163
230
  export function buildSessionsCard(sessions: Array<{
164
231
  sessionId: string;
165
232
  active: boolean;
166
233
  turnCount: number;
167
234
  elapsedSeconds: number | null;
168
235
  model: string;
236
+ tool: string;
169
237
  }>): string {
170
- const text = sessions.length === 0
171
- ? "当前没有会话记录。"
172
- : [
173
- `共 **${sessions.length}** 个会话:`,
174
- ``,
175
- ...sessions.map((s, i) => {
176
- const status = s.active ? "🟢 活跃" : "⚪ 空闲";
177
- const shortId = s.sessionId.length > 16 ? s.sessionId.slice(0, 16) + "..." : s.sessionId;
178
- let extra = "";
179
- if (s.active && s.elapsedSeconds !== null) {
180
- const mins = Math.floor(s.elapsedSeconds / 60);
181
- const secs = s.elapsedSeconds % 60;
182
- extra = ` | 本轮: ${mins}分${secs}秒`;
183
- }
184
- return `**${i + 1}.** \`${shortId}\` ${status} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
185
- }),
186
- ].join("\n");
238
+ // tool 分组排序:Claude Code 在前,Cursor 在后
239
+ const claudeCodeSessions = sessions.filter(s => s.tool !== "cursor");
240
+ const cursorSessions = sessions.filter(s => s.tool === "cursor");
241
+ const hasClaudeCode = claudeCodeSessions.length > 0;
242
+ const hasCursor = cursorSessions.length > 0;
243
+
244
+ if (sessions.length === 0) {
245
+ return JSON.stringify({
246
+ config: { wide_screen_mode: true },
247
+ header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
248
+ elements: [
249
+ { tag: "div", text: { tag: "lark_md", content: "当前没有会话记录。\n\n使用 **/new**(默认 Claude Code)、**/new claude** 或 **/new cursor** 创建新会话。" } },
250
+ { tag: "hr" },
251
+ { tag: "action", actions: [{ tag: "button", text: { tag: "plain_text", content: "收起" }, type: "default", value: { action: "close" } }] },
252
+ ],
253
+ });
254
+ }
255
+
256
+ const formatSession = (s: typeof sessions[0], i: number) => {
257
+ const status = s.active ? "🟢 活跃" : "⚪ 空闲";
258
+ const shortId = s.sessionId.length > 16 ? s.sessionId.slice(0, 16) + "..." : s.sessionId;
259
+ let extra = "";
260
+ if (s.active && s.elapsedSeconds !== null) {
261
+ const mins = Math.floor(s.elapsedSeconds / 60);
262
+ const secs = s.elapsedSeconds % 60;
263
+ extra = ` | 本轮: ${mins}分${secs}秒`;
264
+ }
265
+ const toolLabel = s.tool === "cursor" ? "Cursor" : "Claude Code";
266
+ return `**${i + 1}.** \`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
267
+ };
268
+
269
+ const lines: string[] = [`共 **${sessions.length}** 个会话:`, ""];
270
+ let idx = 0;
271
+
272
+ if (hasClaudeCode) {
273
+ lines.push("**Claude Code 会话:**", "");
274
+ for (const s of claudeCodeSessions) {
275
+ lines.push(formatSession(s, idx++));
276
+ }
277
+ }
278
+
279
+ if (hasCursor) {
280
+ if (hasClaudeCode) lines.push("", "**Cursor 会话:**", "");
281
+ else lines.push("**Cursor 会话:**", "");
282
+ for (const s of cursorSessions) {
283
+ lines.push(formatSession(s, idx++));
284
+ }
285
+ }
187
286
 
188
287
  return JSON.stringify({
189
288
  config: { wide_screen_mode: true },
190
289
  header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
191
290
  elements: [
192
- { tag: "div", text: { tag: "lark_md", content: text } },
291
+ { tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
193
292
  { tag: "hr" },
194
293
  {
195
294
  tag: "action",
package/src/config.ts CHANGED
@@ -62,13 +62,56 @@ export const CLAUDE_MODEL = process.env.CHATCCC_ANTHROPIC_MODEL?.trim() || "defa
62
62
 
63
63
  export const CLAUDE_EFFORT = process.env.CHATCCC_ANTHROPIC_EFFORT?.trim() || "default";
64
64
 
65
- /** AI 工具选择:claude | cursor | codex(未设置时默认 claude) */
66
- export const AI_TOOL = process.env.CHATCCC_AI_TOOL?.trim().toLowerCase() || "claude";
65
+ /** 探测 cursor-agent 安装路径(优先环境变量,其次 LocalAppData,最后默认 agent) */
66
+ function detectCursorAgent(): string {
67
+ if (process.env.CHATCCC_CURSOR_COMMAND?.trim()) return process.env.CHATCCC_CURSOR_COMMAND.trim();
68
+ const localAppData = process.env.LOCALAPPDATA;
69
+ if (localAppData) {
70
+ const defaultPath = join(localAppData, "cursor-agent", "agent.cmd");
71
+ if (existsSync(defaultPath)) return defaultPath;
72
+ }
73
+ return "agent";
74
+ }
75
+ export const CURSOR_AGENT_COMMAND = detectCursorAgent();
76
+ /** Cursor agent 参数:-p 非交互模式,stream-json 流式 JSONL 输出 */
77
+ export const CURSOR_AGENT_ARGS = (process.env.CHATCCC_CURSOR_ARGS?.trim() || "-p --output-format stream-json --stream-partial-output").split(/\s+/).filter(Boolean);
67
78
 
68
79
  // 新建会话的默认工作路径(/cd 命令设置,持久化到本地文件)
69
- // 该路径仅影响通过 /new 新建的 Claude 会话,不影响已有会话的 resume。
80
+ // 该路径仅影响通过 /new 新建的会话,不影响已有会话的 resume。
70
81
  export const DEFAULT_CWD_FILE = join(PROJECT_ROOT, ".claude", "working_dir.txt");
71
82
 
83
+ /** 会话工具类型持久化文件 */
84
+ export const SESSIONS_FILE = join(PROJECT_ROOT, ".claude", "sessions.json");
85
+
86
+ /** 最近成功新建会话的工作路径记录(最多 10 条) */
87
+ export const RECENT_DIRS_FILE = join(PROJECT_ROOT, ".claude", "recent_dirs.json");
88
+ export const MAX_RECENT_DIRS = 10;
89
+
90
+ /** 读取最近使用过的工作路径列表(最新的在前) */
91
+ export async function getRecentDirs(): Promise<string[]> {
92
+ try {
93
+ const raw = await readFile(RECENT_DIRS_FILE, "utf-8");
94
+ const arr = JSON.parse(raw);
95
+ if (Array.isArray(arr)) return arr.filter((d: unknown) => typeof d === "string");
96
+ } catch { /* file doesn't exist or corrupted */ }
97
+ return [];
98
+ }
99
+
100
+ /** 添加一个路径到最近使用列表(去重、限制数量、最新的在前) */
101
+ export async function addRecentDir(dir: string): Promise<void> {
102
+ const dirs = await getRecentDirs();
103
+ const filtered = dirs.filter(d => d !== dir);
104
+ filtered.unshift(dir);
105
+ const trimmed = filtered.slice(0, MAX_RECENT_DIRS);
106
+ try {
107
+ await mkdir(dirname(RECENT_DIRS_FILE), { recursive: true });
108
+ await writeFile(RECENT_DIRS_FILE, JSON.stringify(trimmed, null, 2), "utf-8");
109
+ } catch (err) {
110
+ console.error(`[${ts()}] Failed to save recent_dirs.json: ${(err as Error).message}`);
111
+ fileLog.flush();
112
+ }
113
+ }
114
+
72
115
  /** 读取 /cd 设置的默认工作路径。若文件不存在或路径已失效,回退到用户主目录。 */
73
116
  export async function getDefaultCwd(): Promise<string> {
74
117
  try {
@@ -227,4 +270,19 @@ export function explainMissingFeishuCredentialsAndExit(): never {
227
270
  process.exit(1);
228
271
  }
229
272
 
230
- export let SESSION_DESC_PREFIX: string = "Claude Session:";
273
+ /** 群描述中用于识别 Claude Code 会话的前缀 */
274
+ export const CLAUDE_SESSION_PREFIX = "Claude Code Session:";
275
+ /** 群描述中用于识别 Cursor 会话的前缀 */
276
+ export const CURSOR_SESSION_PREFIX = "Cursor Session:";
277
+
278
+ /** 根据 tool 名称返回对应的群描述前缀 */
279
+ export function sessionPrefixForTool(tool: string): string {
280
+ if (tool === "cursor") return CURSOR_SESSION_PREFIX;
281
+ return CLAUDE_SESSION_PREFIX;
282
+ }
283
+
284
+ /** 根据 tool 名称返回用于状态展示的标签 */
285
+ export function toolDisplayName(tool: string): string {
286
+ if (tool === "cursor") return "Cursor";
287
+ return "Claude Code";
288
+ }
package/src/feishu-api.ts CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  BASE_URL,
8
8
  CHAT_LOGS_DIR,
9
9
  PROJECT_ROOT,
10
- SESSION_DESC_PREFIX,
10
+ CLAUDE_SESSION_PREFIX,
11
+ CURSOR_SESSION_PREFIX,
11
12
  ts,
12
13
  } from "./config.ts";
13
14
  import { buildButtons } from "./cards.ts";
@@ -219,12 +220,24 @@ export async function getChatInfo(
219
220
  };
220
221
  }
221
222
 
223
+ export function extractSessionInfo(description: string): { sessionId: string; tool: string } | null {
224
+ const PREFIXES: Array<{ prefix: string; tool: string }> = [
225
+ { prefix: CLAUDE_SESSION_PREFIX, tool: "claude" },
226
+ { prefix: CURSOR_SESSION_PREFIX, tool: "cursor" },
227
+ ];
228
+ for (const { prefix, tool } of PREFIXES) {
229
+ const idx = description.indexOf(prefix);
230
+ if (idx === -1) continue;
231
+ const after = description.slice(idx + prefix.length).trim();
232
+ const match = after.match(/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
233
+ if (match) return { sessionId: match[1], tool };
234
+ }
235
+ return null;
236
+ }
237
+
238
+ /** @deprecated 使用 extractSessionInfo 代替 */
222
239
  export function extractSessionId(description: string): string | null {
223
- const idx = description.indexOf(SESSION_DESC_PREFIX);
224
- if (idx === -1) return null;
225
- const after = description.slice(idx + SESSION_DESC_PREFIX.length).trim();
226
- const match = after.match(/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
227
- return match ? match[1] : null;
240
+ return extractSessionInfo(description)?.sessionId ?? null;
228
241
  }
229
242
 
230
243
  // ---------------------------------------------------------------------------
@@ -325,9 +338,10 @@ export async function sendRestartCard(token: string): Promise<void> {
325
338
  config: { wide_screen_mode: true },
326
339
  header: { template: "green", title: { content: "ChatCCC Started", tag: "plain_text" } },
327
340
  elements: [
328
- { tag: "div", text: { tag: "lark_md", content: "Bot 已启动完成,可以继续使用。\n\n发送 **/new** 创建新会话,或直接在已有会话群中发消息。" } },
341
+ { tag: "div", text: { tag: "lark_md", content: "Bot 已启动完成,可以继续使用。\n\n使用 **/new**(默认 Claude Code)或 **/new claude** / **/new cursor** 创建新会话" } },
329
342
  buildButtons([
330
- { text: "新建会话(/new)", value: JSON.stringify({ cmd: "new" }), type: "primary" },
343
+ { text: "新建 Claude Code 会话(/new claude)", value: JSON.stringify({ cmd: "new" }), type: "primary" },
344
+ { text: "新建 Cursor 会话(/new cursor)", value: JSON.stringify({ cmd: "new cursor" }), type: "primary" },
331
345
  { text: "重启Chat CCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
332
346
  { text: "查看/切换工作路径(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
333
347
  ]),