cursor-feedback 2.4.1 → 2.5.0

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,9 @@
1
+ ---
2
+ description: 从对话反馈中积累的编码约定和偏好
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # 编码约定
7
+
8
+ ## 发布流程
9
+ - 发版(npm / Open VSX / VS Code Marketplace)前必须经用户明确确认,绝不能改完代码直接发。改动完成后先提交 + 跑冒烟,向用户汇报并等确认后再走发版流程。
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.5.0](https://github.com/jianger666/cursor-feedback-extension/compare/v2.4.1...v2.5.0) (2026-07-06)
6
+
7
+
8
+ ### Features
9
+
10
+ * /help 完整用法说明;全角斜杠/归一化;持久化「发版需用户确认」规则 ([d345b55](https://github.com/jianger666/cursor-feedback-extension/commit/d345b55da22cad9e1a838999f0d58dd36ffa10b2))
11
+ * slash commands case-insensitive + /help command, malformed /model //cwd args no longer fall through to feedback routing ([a4bceeb](https://github.com/jianger666/cursor-feedback-extension/commit/a4bceebb588898c82acb797e8f5abefb7a457550))
12
+
13
+
14
+ ### Code Refactoring
15
+
16
+ * **cli:** /new 工作目录简化为「显式指定>主目录」与 IDE 窗口解耦;移除 /cwd;新增 /projects 查项目路径、/new 支持项目名匹配 ([0752a32](https://github.com/jianger666/cursor-feedback-extension/commit/0752a3232de0e712b977f9f01ba988613cd7597c))
17
+
5
18
  ### [2.4.1](https://github.com/jianger666/cursor-feedback-extension/compare/v2.4.0...v2.4.1) (2026-07-06)
6
19
 
7
20
 
package/README.md CHANGED
@@ -270,11 +270,13 @@ Send these directly in the bot chat:
270
270
 
271
271
  | Command | What it does |
272
272
  |---------|--------------|
273
- | `/new task description` | Launch a headless CLI session for the task (in the active window's workspace, or the `/cwd` default) |
274
- | `/new /abs/path task description` | Launch with an explicit working directory |
273
+ | `/new task description` | Launch a headless CLI session for the task (in your home directory by default) |
274
+ | `/new /abs/path task description` | Launch in an explicit working directory |
275
+ | `/new project-name task description` | Launch in a project Cursor has opened before (unique folder-name match) |
276
+ | `/projects` | List project paths Cursor has opened (to look up / copy into `/new`) |
275
277
  | `/stop` | Terminate the running CLI session |
276
278
  | `/model [modelId]` | Show / set the session model (persisted) |
277
- | `/cwd [dir]` | Show / set the default working directory (persisted) |
279
+ | `/help` | Show command usage |
278
280
 
279
281
  - Sessions run in **non-interactive mode** with `maxMode=false` force-written before spawn — on request-based plans each session costs exactly 1 request, never Max billing.
280
282
  - The launched agent talks to you through this extension's feedback cards: confirmations and progress reports are pushed to Feishu; just reply to the cards.
package/README_CN.md CHANGED
@@ -274,11 +274,13 @@ npm install -g cursor-feedback
274
274
 
275
275
  | 命令 | 作用 |
276
276
  |------|------|
277
- | `/new 任务描述` | 拉起一个 headless CLI 会话跑这个任务(默认在活跃窗口工作区 / `/cwd` 设置的目录) |
277
+ | `/new 任务描述` | 拉起一个 headless CLI 会话跑这个任务(默认在主目录) |
278
278
  | `/new /绝对路径 任务描述` | 指定工作目录拉起 |
279
+ | `/new 项目名 任务描述` | 在 Cursor 打开过的项目里拉起(目录名唯一匹配) |
280
+ | `/projects` | 列出 Cursor 打开过的项目路径(供查路径 / 复制给 `/new`) |
279
281
  | `/stop` | 终止运行中的 CLI 会话 |
280
282
  | `/model [模型id]` | 查看 / 设置会话模型(持久化) |
281
- | `/cwd [目录]` | 查看 / 设置默认工作目录(持久化) |
283
+ | `/help` | 查看命令用法 |
282
284
 
283
285
  - 会话以**非交互模式**运行,拉起前强制写入 `maxMode=false`——按次计费套餐固定只扣 1 次请求,永远不会走 Max 计费。
284
286
  - 拉起的 Agent 也通过本插件的反馈卡片与你沟通:需要确认、汇报进展都会推到飞书,直接回复卡片即可。
@@ -194,12 +194,61 @@ class CliLauncher {
194
194
  this.readSettings().model ||
195
195
  CliLauncher.DEFAULT_MODEL);
196
196
  }
197
- /** /cwd 设置的默认工作目录(存在才返回) */
198
- defaultCwd() {
199
- const d = this.readSettings().defaultCwd;
200
- if (d && fs.existsSync(d))
201
- return d;
202
- return null;
197
+ /** Cursor IDE 的 storage.json 路径(平台相关) */
198
+ static storageJsonPath() {
199
+ if (process.platform === 'darwin') {
200
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'storage.json');
201
+ }
202
+ if (process.platform === 'win32') {
203
+ return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Cursor', 'User', 'globalStorage', 'storage.json');
204
+ }
205
+ return path.join(os.homedir(), '.config', 'Cursor', 'User', 'globalStorage', 'storage.json');
206
+ }
207
+ /**
208
+ * 用户在 Cursor IDE 里打开过的项目目录(供手机上查路径 / 按项目名拉起会话)。
209
+ * 数据源是 IDE 的 storage.json:backupWorkspaces.folders 是当前/最近打开的窗口(排前面),
210
+ * profileAssociations.workspaces 是历史打开过的全部工作区。只返回仍然存在的目录。
211
+ */
212
+ listProjects() {
213
+ let raw;
214
+ try {
215
+ raw = JSON.parse(fs.readFileSync(CliLauncher.storageJsonPath(), 'utf-8'));
216
+ }
217
+ catch {
218
+ return [];
219
+ }
220
+ const uris = [
221
+ ...(raw.backupWorkspaces?.folders || []).map((f) => f.folderUri || ''),
222
+ ...Object.keys(raw.profileAssociations?.workspaces || {}),
223
+ ];
224
+ const seen = new Set();
225
+ const result = [];
226
+ for (const uri of uris) {
227
+ if (!uri.startsWith('file://'))
228
+ continue;
229
+ let dir;
230
+ try {
231
+ dir = decodeURIComponent(uri.replace(/^file:\/\//, ''));
232
+ }
233
+ catch {
234
+ continue;
235
+ }
236
+ // Windows 的 URI 形如 file:///c%3A/... → 去掉开头多余的 /
237
+ if (process.platform === 'win32' && /^\/[a-zA-Z]:/.test(dir))
238
+ dir = dir.slice(1);
239
+ const key = dir.toLowerCase();
240
+ if (seen.has(key))
241
+ continue;
242
+ seen.add(key);
243
+ try {
244
+ if (fs.statSync(dir).isDirectory())
245
+ result.push(dir);
246
+ }
247
+ catch {
248
+ // 已删除的目录不展示
249
+ }
250
+ }
251
+ return result;
203
252
  }
204
253
  /**
205
254
  * 定位 cursor-agent 可执行文件。安装脚本默认放在 ~/.local/bin(mac/linux 是
@@ -396,7 +396,8 @@ class McpFeedbackServer {
396
396
  if (!text && images.length === 0 && files.length === 0)
397
397
  return;
398
398
  // 斜杠命令优先于反馈路由:用户显式输入命令时不应被当成对某张卡片的回复或排队消息。
399
- if (/^\/(new|stop|model|cwd)\b/.test(text) && this.handleCliCommand(text, chatId))
399
+ // 大小写不敏感:手机输入法常自动把句首字母大写(/New),必须容错。
400
+ if (/^\s*[//](new|stop|model|cwd|help|projects)\b/i.test(text) && this.handleCliCommand(text, chatId))
400
401
  return;
401
402
  if (parentId) {
402
403
  // 用户「回复」了某条卡片 → 用 parent_id 精确路由
@@ -494,13 +495,51 @@ class McpFeedbackServer {
494
495
  }
495
496
  /**
496
497
  * 处理飞书斜杠命令。返回是否已消费该消息。
497
- * - /new [工作目录] 任务描述:拉起一个 headless CLI 会话(非交互 + maxMode=false,扣 1 次请求)
498
+ * - /new [目录或项目名] 任务描述:拉起 headless CLI 会话(非交互 + maxMode=false,扣 1 次请求)。
499
+ * 工作目录刻意保持简单:显式指定 > 主目录,与当前开着哪些 IDE 窗口无关。
500
+ * - /projects:列出 Cursor 打开过的项目路径(供手机上查路径 / 复制给 /new)
498
501
  * - /stop:终止运行中的 CLI 会话
499
502
  * - /model [模型id]:查看 / 设置 CLI 会话模型(持久化;无论选什么模型都强制 maxMode=false)
500
- * - /cwd [目录]:查看 / 设置默认工作目录(持久化;IDE 不开时 /new 用它)
501
503
  */
502
- handleCliCommand(text, chatId) {
503
- if (/^\/stop\b/.test(text)) {
504
+ handleCliCommand(rawText, chatId) {
505
+ // 输入容错:首尾空格(飞书入站已 trim,这里兜底;trim 同样覆盖全角空格 U+3000);
506
+ // 全角斜杠「/」归一化(中文输入法常见);命令词大小写不敏感(手机输入法自动把句首
507
+ // 大写成 /New)。任务正文原样保留。
508
+ const text = rawText.trim().replace(/^//, '/');
509
+ if (/^\/help\b/i.test(text)) {
510
+ this.feishu.replyText(chatId, '📖 命令一览(大小写、首尾空格都不敏感)\n\n' +
511
+ '🚀 /new 任务描述\n' +
512
+ '拉起一个全新的 AI 会话跑任务,电脑不用开着 Cursor(需已开启常驻服务)。\n' +
513
+ '· 默认工作目录是主目录;可在任务前指定项目:\n' +
514
+ ' /new /Users/me/proj 帮我修下测试\n' +
515
+ ' /new crm-web 帮我修下测试(项目名唯一时自动匹配到完整路径)\n' +
516
+ '· 非交互模式 + 强制 maxMode=false,每次会话固定只扣 1 次请求\n' +
517
+ '· AI 需要沟通时会发反馈卡片,直接回复即可;同时只能跑一个会话\n\n' +
518
+ '📁 /projects\n' +
519
+ '列出 Cursor 打开过的项目路径(查路径 / 复制给 /new,也可直接用项目名)\n\n' +
520
+ '🛑 /stop\n' +
521
+ '终止运行中的 CLI 会话(任何窗口拉起的都能停)\n\n' +
522
+ '🧠 /model [模型id]\n' +
523
+ '查看 / 设置会话模型(持久化;无论选什么模型都不会走 Max 计费)\n\n' +
524
+ '❓ /help\n' +
525
+ '看这份帮助\n\n' +
526
+ '💬 不带斜杠的消息照常作为反馈:回复某张卡片就送达那个请求,直接发送则给当前等待中的 AI。');
527
+ return true;
528
+ }
529
+ if (/^\/projects\b/i.test(text)) {
530
+ const projects = this.cliLauncher.listProjects();
531
+ if (!projects.length) {
532
+ this.feishu.replyText(chatId, '没有找到 Cursor 打开过的项目记录。可以直接用绝对路径:/new /绝对路径 任务描述');
533
+ }
534
+ else {
535
+ const lines = projects.slice(0, 20).map((p) => `· ${path.basename(p)} — ${p}`);
536
+ this.feishu.replyText(chatId, `Cursor 打开过的项目(共 ${projects.length} 个${projects.length > 20 ? ',只列最近 20 个' : ''}):\n` +
537
+ lines.join('\n') +
538
+ '\n\n用法:/new 项目名 任务描述(项目名唯一时直接匹配),或 /new 完整路径 任务描述');
539
+ }
540
+ return true;
541
+ }
542
+ if (/^\/stop\b/i.test(text)) {
504
543
  if (this.cliLauncher.stop()) {
505
544
  this.feishu.replyText(chatId, '🛑 正在终止 CLI 会话…结束后我会再发一条收尾消息。');
506
545
  }
@@ -509,7 +548,7 @@ class McpFeedbackServer {
509
548
  }
510
549
  return true;
511
550
  }
512
- const mModel = text.match(/^\/model(?:\s+(\S+))?\s*$/);
551
+ const mModel = text.match(/^\/model(?:\s+(\S+))?\s*$/i);
513
552
  if (mModel) {
514
553
  if (mModel[1]) {
515
554
  this.cliLauncher.writeSettings({ model: mModel[1] });
@@ -520,52 +559,59 @@ class McpFeedbackServer {
520
559
  }
521
560
  return true;
522
561
  }
523
- const mCwd = text.match(/^\/cwd(?:\s+(\S+))?\s*$/);
524
- if (mCwd) {
525
- if (mCwd[1]) {
526
- const dir = this.expandDirToken(mCwd[1]);
527
- if (!dir) {
528
- this.feishu.replyText(chatId, `❌ 目录不存在或不是有效路径:${mCwd[1]}`);
529
- }
530
- else {
531
- this.cliLauncher.writeSettings({ defaultCwd: dir });
532
- this.feishu.replyText(chatId, `✅ 默认工作目录已设为 ${dir}\n之后 /new 不带路径时都用它。`);
533
- }
534
- }
535
- else {
536
- const cur = this.cliLauncher.defaultCwd();
537
- this.feishu.replyText(chatId, `当前默认工作目录:${cur || '(未设置,回退到活跃窗口工作区或主目录)'}\n设置:/cwd 绝对路径`);
538
- }
562
+ // /cwd 已移除(工作目录简化为「/new 里显式指定,否则主目录」),给老用户指个路
563
+ if (/^\/cwd\b/i.test(text)) {
564
+ this.feishu.replyText(chatId, '/cwd 已移除。现在直接在 /new 里指定:/new 项目名或/绝对路径 任务描述;不指定就在主目录跑。\n用 /projects 可以查项目路径。');
565
+ return true;
566
+ }
567
+ // /model 带了多余参数(如 /model a b):不落入反馈路由(用户明显在敲命令),回用法提示
568
+ if (/^\/model\b/i.test(text)) {
569
+ this.feishu.replyText(chatId, '参数不对。用法:/model 模型id(只接受一个参数,留空为查看当前模型)');
539
570
  return true;
540
571
  }
541
- const m = text.match(/^\/new\s*([\s\S]*)$/);
572
+ const m = text.match(/^\/new\b\s*([\s\S]*)$/i);
542
573
  if (!m)
543
574
  return false;
544
575
  let task = (m[1] || '').trim();
545
576
  if (!task) {
546
- this.feishu.replyText(chatId, '用法:/new 任务描述\n' +
547
- '可选在任务前带一个已存在的目录作为工作目录:\n' +
577
+ this.feishu.replyText(chatId, '用法:/new 任务描述(默认在主目录跑)\n' +
578
+ '可选在任务前带工作目录(绝对路径或项目名):\n' +
548
579
  '/new /Users/me/proj 帮我看下测试为什么挂了\n' +
549
- '相关命令:/cwd 设默认目录、/model 设模型、/stop 终止会话。');
580
+ '/new my-blog 帮我看下测试为什么挂了\n' +
581
+ '相关命令:/projects 查项目路径、/model 设模型、/stop 终止会话。');
550
582
  return true;
551
583
  }
552
584
  if (this.cliLauncher.isRunning()) {
553
585
  this.feishu.replyText(chatId, '已有一个 CLI 会话在运行:' + this.cliLauncher.describe() + '\n发 /stop 可先终止它。');
554
586
  return true;
555
587
  }
556
- // 工作目录优先级:命令里显式路径 > /cwd 持久化默认目录 > 本实例归属窗口 > 主目录。
557
- // 注意 ownerWorkspace 是小写归一化路径,macOS 默认大小写不敏感文件系统下可直接使用。
558
- let cwd = this.cliLauncher.defaultCwd() ||
559
- (this.ownerWorkspace && fs.existsSync(this.ownerWorkspace)
560
- ? this.ownerWorkspace
561
- : os.homedir());
562
- const explicitDir = this.expandDirToken(task.split(/\s+/)[0]);
588
+ // 工作目录刻意保持简单、与 IDE 窗口无关:命令里显式指定(路径或项目名)> 主目录。
589
+ let cwd = os.homedir();
590
+ const firstToken = task.split(/\s+/)[0];
591
+ const explicitDir = this.expandDirToken(firstToken);
563
592
  if (explicitDir) {
564
593
  cwd = explicitDir;
565
- task = task.slice(task.split(/\s+/)[0].length).trim();
594
+ task = task.slice(firstToken.length).trim();
595
+ }
596
+ else {
597
+ // 首词不是路径 → 试着当项目名匹配(Cursor 打开过的项目,按目录名精确匹配、忽略大小写)。
598
+ // 唯一命中才采用;多个重名让用户用完整路径消歧;没命中就当作任务正文的第一个词。
599
+ const matches = this.cliLauncher
600
+ .listProjects()
601
+ .filter((p) => path.basename(p).toLowerCase() === firstToken.toLowerCase());
602
+ if (matches.length === 1) {
603
+ cwd = matches[0];
604
+ task = task.slice(firstToken.length).trim();
605
+ }
606
+ else if (matches.length > 1) {
607
+ this.feishu.replyText(chatId, `有 ${matches.length} 个同名项目「${firstToken}」:\n` +
608
+ matches.map((p) => `· ${p}`).join('\n') +
609
+ '\n请用完整路径:/new 完整路径 任务描述');
610
+ return true;
611
+ }
566
612
  }
567
613
  if (!task) {
568
- this.feishu.replyText(chatId, '只给了目录没给任务。用法:/new [工作目录] 任务描述');
614
+ this.feishu.replyText(chatId, '只给了目录没给任务。用法:/new [目录或项目名] 任务描述');
569
615
  return true;
570
616
  }
571
617
  const err = this.cliLauncher.start(task, cwd, (result) => this.onCliSessionDone(result, chatId));
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "cursor-feedback",
3
3
  "displayName": "Cursor Feedback",
4
4
  "description": "One Cursor conversation, unlimited AI interactions - Save your monthly request quota! Interactive feedback loop for AI chat via MCP",
5
- "version": "2.4.1",
5
+ "version": "2.5.0",
6
6
  "icon": "icon.png",
7
7
  "author": "jianger666",
8
8
  "license": "MIT",