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.
- package/.cursor/rules/learned-conventions.mdc +9 -0
- package/CHANGELOG.md +13 -0
- package/README.md +5 -3
- package/README_CN.md +4 -2
- package/dist/cli-launcher.js +55 -6
- package/dist/mcp-server.js +81 -35
- package/package.json +1 -1
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
|
|
274
|
-
| `/new /abs/path task description` | Launch
|
|
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
|
-
| `/
|
|
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
|
|
277
|
+
| `/new 任务描述` | 拉起一个 headless CLI 会话跑这个任务(默认在主目录) |
|
|
278
278
|
| `/new /绝对路径 任务描述` | 指定工作目录拉起 |
|
|
279
|
+
| `/new 项目名 任务描述` | 在 Cursor 打开过的项目里拉起(目录名唯一匹配) |
|
|
280
|
+
| `/projects` | 列出 Cursor 打开过的项目路径(供查路径 / 复制给 `/new`) |
|
|
279
281
|
| `/stop` | 终止运行中的 CLI 会话 |
|
|
280
282
|
| `/model [模型id]` | 查看 / 设置会话模型(持久化) |
|
|
281
|
-
| `/
|
|
283
|
+
| `/help` | 查看命令用法 |
|
|
282
284
|
|
|
283
285
|
- 会话以**非交互模式**运行,拉起前强制写入 `maxMode=false`——按次计费套餐固定只扣 1 次请求,永远不会走 Max 计费。
|
|
284
286
|
- 拉起的 Agent 也通过本插件的反馈卡片与你沟通:需要确认、汇报进展都会推到飞书,直接回复卡片即可。
|
package/dist/cli-launcher.js
CHANGED
|
@@ -194,12 +194,61 @@ class CliLauncher {
|
|
|
194
194
|
this.readSettings().model ||
|
|
195
195
|
CliLauncher.DEFAULT_MODEL);
|
|
196
196
|
}
|
|
197
|
-
/**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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 是
|
package/dist/mcp-server.js
CHANGED
|
@@ -396,7 +396,8 @@ class McpFeedbackServer {
|
|
|
396
396
|
if (!text && images.length === 0 && files.length === 0)
|
|
397
397
|
return;
|
|
398
398
|
// 斜杠命令优先于反馈路由:用户显式输入命令时不应被当成对某张卡片的回复或排队消息。
|
|
399
|
-
|
|
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 [
|
|
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(
|
|
503
|
-
|
|
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
|
-
|
|
524
|
-
if (
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
|
547
|
-
'
|
|
577
|
+
this.feishu.replyText(chatId, '用法:/new 任务描述(默认在主目录跑)\n' +
|
|
578
|
+
'可选在任务前带工作目录(绝对路径或项目名):\n' +
|
|
548
579
|
'/new /Users/me/proj 帮我看下测试为什么挂了\n' +
|
|
549
|
-
'
|
|
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
|
-
//
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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(
|
|
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.
|
|
5
|
+
"version": "2.5.0",
|
|
6
6
|
"icon": "icon.png",
|
|
7
7
|
"author": "jianger666",
|
|
8
8
|
"license": "MIT",
|