review-mark 1.0.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/README.md +249 -0
- package/dist/cli/review.cjs +710 -0
- package/dist/cli/review.cjs.map +1 -0
- package/dist/cli/review.d.cts +2 -0
- package/dist/cli/review.d.ts +2 -0
- package/dist/cli/review.js +687 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/index.cjs +634 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +597 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
- package/src/cli/review.ts +125 -0
- package/src/constants.ts +7 -0
- package/src/core/BeLinkReview.ts +274 -0
- package/src/core/feishu.ts +271 -0
- package/src/core/git.ts +63 -0
- package/src/core/prompt.ts +12 -0
- package/src/index.ts +2 -0
- package/src/types.ts +18 -0
- package/src/utils/checkCli.ts +127 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +30 -0
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "review-mark",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A CLI tool for AI-powered code review using git diff.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"belink-review": "dist/cli/review.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"start": "node dist/cli/review.js",
|
|
22
|
+
"prepublishOnly": "pnpm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"ai",
|
|
26
|
+
"code-review",
|
|
27
|
+
"cli",
|
|
28
|
+
"git",
|
|
29
|
+
"diff",
|
|
30
|
+
"cursor",
|
|
31
|
+
"feishu"
|
|
32
|
+
],
|
|
33
|
+
"author": "snowmountain-top",
|
|
34
|
+
"license": "ISC",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/snowmountain-top/review-mark.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/snowmountain-top/review-mark#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/snowmountain-top/review-mark/issues"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^18.0.0",
|
|
45
|
+
"tsup": "^8.0.2",
|
|
46
|
+
"typescript": "^5.4.5"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
50
|
+
"commander": "^12.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { BeLinkReview } from "../core/BeLinkReview";
|
|
3
|
+
|
|
4
|
+
const program = new Command();
|
|
5
|
+
|
|
6
|
+
program
|
|
7
|
+
.name("belink-review")
|
|
8
|
+
.description("AI-powered code review tool")
|
|
9
|
+
.version("1.0.0")
|
|
10
|
+
.option(
|
|
11
|
+
"--apiKey <key>",
|
|
12
|
+
"Cursor API Key (overrides CURSOR_API_KEY environment variable)"
|
|
13
|
+
)
|
|
14
|
+
.option(
|
|
15
|
+
"--agentPath <path>",
|
|
16
|
+
"Path to the Cursor CLI agent executable (overrides CURSOR_AGENT_PATH environment variable)"
|
|
17
|
+
)
|
|
18
|
+
.option(
|
|
19
|
+
"--ignore <patterns>",
|
|
20
|
+
"Comma-separated list of glob patterns to ignore (e.g., *.lock,dist/**)"
|
|
21
|
+
)
|
|
22
|
+
.option(
|
|
23
|
+
"--feishu-app-id <appId>",
|
|
24
|
+
"Feishu App ID (overrides FEISHU_APP_ID environment variable)"
|
|
25
|
+
)
|
|
26
|
+
.option(
|
|
27
|
+
"--feishu-app-secret <appSecret>",
|
|
28
|
+
"Feishu App Secret (overrides FEISHU_APP_SECRET environment variable)"
|
|
29
|
+
)
|
|
30
|
+
.option(
|
|
31
|
+
"--feishu-receive-id <receiveId>",
|
|
32
|
+
"Feishu Receive ID - user open_id or chat_id (overrides FEISHU_RECEIVE_ID environment variable)"
|
|
33
|
+
)
|
|
34
|
+
.option(
|
|
35
|
+
"--feishu-receive-id-type <type>",
|
|
36
|
+
"Feishu Receive ID Type: open_id, user_id, chat_id, email, union_id (default: chat_id)"
|
|
37
|
+
)
|
|
38
|
+
.option(
|
|
39
|
+
"--feishu-type <type>",
|
|
40
|
+
"Feishu message type: text, post, or interactive (default: interactive)"
|
|
41
|
+
)
|
|
42
|
+
.option(
|
|
43
|
+
"--feishu-title <title>",
|
|
44
|
+
"Feishu message title (default: 🔍 Code Review 结果)"
|
|
45
|
+
)
|
|
46
|
+
.option("--no-feishu", "Disable Feishu notification")
|
|
47
|
+
.action(async (options) => {
|
|
48
|
+
try {
|
|
49
|
+
const ignorePatterns = options.ignore
|
|
50
|
+
? options.ignore.split(",")
|
|
51
|
+
: undefined;
|
|
52
|
+
|
|
53
|
+
const instance = BeLinkReview.getInstance(
|
|
54
|
+
options.apiKey,
|
|
55
|
+
options.agentPath,
|
|
56
|
+
ignorePatterns,
|
|
57
|
+
options.feishu !== false
|
|
58
|
+
);
|
|
59
|
+
await instance.goCli();
|
|
60
|
+
} catch (error: any) {
|
|
61
|
+
console.error(`[be-link-review] Error: ${error.message}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command("review")
|
|
68
|
+
.description("Perform an AI code review on git diff")
|
|
69
|
+
.option(
|
|
70
|
+
"--apiKey <key>",
|
|
71
|
+
"Cursor API Key (overrides CURSOR_API_KEY environment variable)"
|
|
72
|
+
)
|
|
73
|
+
.option(
|
|
74
|
+
"--agentPath <path>",
|
|
75
|
+
"Path to the Cursor CLI agent executable (overrides CURSOR_AGENT_PATH environment variable)"
|
|
76
|
+
)
|
|
77
|
+
.option(
|
|
78
|
+
"--ignore <patterns>",
|
|
79
|
+
"Comma-separated list of glob patterns to ignore (e.g., *.lock,dist/**)"
|
|
80
|
+
)
|
|
81
|
+
.option(
|
|
82
|
+
"--feishu-app-id <appId>",
|
|
83
|
+
"Feishu App ID (overrides FEISHU_APP_ID environment variable)"
|
|
84
|
+
)
|
|
85
|
+
.option(
|
|
86
|
+
"--feishu-app-secret <appSecret>",
|
|
87
|
+
"Feishu App Secret (overrides FEISHU_APP_SECRET environment variable)"
|
|
88
|
+
)
|
|
89
|
+
.option(
|
|
90
|
+
"--feishu-receive-id <receiveId>",
|
|
91
|
+
"Feishu Receive ID - user open_id or chat_id (overrides FEISHU_RECEIVE_ID environment variable)"
|
|
92
|
+
)
|
|
93
|
+
.option(
|
|
94
|
+
"--feishu-receive-id-type <type>",
|
|
95
|
+
"Feishu Receive ID Type: open_id, user_id, chat_id, email, union_id (default: chat_id)"
|
|
96
|
+
)
|
|
97
|
+
.option(
|
|
98
|
+
"--feishu-type <type>",
|
|
99
|
+
"Feishu message type: text, post, or interactive (default: interactive)"
|
|
100
|
+
)
|
|
101
|
+
.option(
|
|
102
|
+
"--feishu-title <title>",
|
|
103
|
+
"Feishu message title (default: 🔍 Code Review 结果)"
|
|
104
|
+
)
|
|
105
|
+
.option("--no-feishu", "Disable Feishu notification")
|
|
106
|
+
.action(async (options) => {
|
|
107
|
+
try {
|
|
108
|
+
const ignorePatterns = options.ignore
|
|
109
|
+
? options.ignore.split(",")
|
|
110
|
+
: undefined;
|
|
111
|
+
|
|
112
|
+
const instance = BeLinkReview.getInstance(
|
|
113
|
+
options.apiKey,
|
|
114
|
+
options.agentPath,
|
|
115
|
+
ignorePatterns,
|
|
116
|
+
options.feishu !== false
|
|
117
|
+
);
|
|
118
|
+
await instance.goCli();
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
console.error(`[be-link-review] Error: ${error.message}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
program.parse(process.argv);
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// 飞书机器人配置(内置,不需要外部配置)
|
|
2
|
+
export const appId = "cli_a93822da7238dbb5";
|
|
3
|
+
export const appSecret = "ZQdcpLUHFb4gFa8cGfrlJfVfSSyGtyzF";
|
|
4
|
+
export const receiveId = "oc_482b6a04f95f4206c4fa9bc61829fd17";
|
|
5
|
+
export const receiveIdType = "chat_id";
|
|
6
|
+
export const messageType = "post"; // text | post | interactive
|
|
7
|
+
export const messageTitle = "🔍 Code Review 结果";
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type {
|
|
5
|
+
BeLinkReviewOptions,
|
|
6
|
+
BeLinkReviewChatOptions,
|
|
7
|
+
BeLinkReviewEnsureResult,
|
|
8
|
+
} from "../types";
|
|
9
|
+
import { isCheckCliInstall } from "../utils/checkCli";
|
|
10
|
+
import { getGitDiff } from "./git";
|
|
11
|
+
import { generateAIPrompt } from "./prompt";
|
|
12
|
+
import { sendReviewToFeishu } from "./feishu";
|
|
13
|
+
|
|
14
|
+
export class BeLinkReview {
|
|
15
|
+
static #instance: BeLinkReview | null = null;
|
|
16
|
+
#apiKey: string | undefined;
|
|
17
|
+
#agentPath: string | undefined;
|
|
18
|
+
#ignorePatterns: string[] | undefined;
|
|
19
|
+
#enableFeishu: boolean;
|
|
20
|
+
|
|
21
|
+
private constructor(
|
|
22
|
+
apiKey?: string,
|
|
23
|
+
agentPath?: string,
|
|
24
|
+
ignorePatterns?: string[],
|
|
25
|
+
enableFeishu: boolean = true
|
|
26
|
+
) {
|
|
27
|
+
this.#apiKey = apiKey;
|
|
28
|
+
this.#agentPath = agentPath;
|
|
29
|
+
this.#ignorePatterns = ignorePatterns;
|
|
30
|
+
this.#enableFeishu = enableFeishu;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 初始化单例并注入参数,在项目入口调用一次即可。
|
|
35
|
+
* 负责保存 apiKey 和自动写入 package.json 脚本。
|
|
36
|
+
* 此方法主要用于在用户项目中设置 apiKey 和脚本,CLI 运行时会优先从环境变量或命令行参数获取。
|
|
37
|
+
*/
|
|
38
|
+
static init(options: BeLinkReviewOptions = {}): BeLinkReview {
|
|
39
|
+
if (BeLinkReview.#instance === null) {
|
|
40
|
+
BeLinkReview.#instance = new BeLinkReview(
|
|
41
|
+
options.apiKey,
|
|
42
|
+
options.agentPath,
|
|
43
|
+
options.ignore,
|
|
44
|
+
options.enableFeishu ?? true
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
BeLinkReview.#instance.#apiKey = options.apiKey; // 允许重新初始化时更新 apiKey
|
|
48
|
+
BeLinkReview.#instance.#agentPath = options.agentPath; // 允许重新初始化时更新 agentPath
|
|
49
|
+
BeLinkReview.#instance.#ignorePatterns = options.ignore; // 允许重新初始化时更新 ignorePatterns
|
|
50
|
+
BeLinkReview.#instance.#enableFeishu = options.enableFeishu ?? true; // 允许重新初始化时更新飞书开关
|
|
51
|
+
}
|
|
52
|
+
BeLinkReview.#instance.#setupProjectScript();
|
|
53
|
+
return BeLinkReview.#instance;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 获取单例实例。如果未通过 init() 初始化,则尝试从环境变量 CURSOR_API_KEY 获取。
|
|
58
|
+
* @param cliApiKey 可选的命令行传入的 apiKey
|
|
59
|
+
* @param cliAgentPath 可选的命令行传入的 agentPath
|
|
60
|
+
* @param cliIgnorePatterns 可选的命令行传入的 ignorePatterns
|
|
61
|
+
* @param cliEnableFeishu 可选的命令行传入的飞书开关
|
|
62
|
+
*/
|
|
63
|
+
static getInstance(
|
|
64
|
+
cliApiKey?: string,
|
|
65
|
+
cliAgentPath?: string,
|
|
66
|
+
cliIgnorePatterns?: string[],
|
|
67
|
+
cliEnableFeishu?: boolean
|
|
68
|
+
): BeLinkReview {
|
|
69
|
+
if (BeLinkReview.#instance === null) {
|
|
70
|
+
const apiKey = cliApiKey || process.env.CURSOR_API_KEY;
|
|
71
|
+
const agentPath = cliAgentPath || process.env.CURSOR_AGENT_PATH;
|
|
72
|
+
const ignorePatterns =
|
|
73
|
+
cliIgnorePatterns ||
|
|
74
|
+
(process.env.BE_LINK_REVIEW_IGNORE
|
|
75
|
+
? process.env.BE_LINK_REVIEW_IGNORE.split(",")
|
|
76
|
+
: undefined);
|
|
77
|
+
const enableFeishu = cliEnableFeishu ?? (process.env.FEISHU_ENABLED !== "false");
|
|
78
|
+
|
|
79
|
+
if (!apiKey) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
'[be-link-review] 请先调用 BeLinkReview.init({ apiKey: "..." }) 初始化,或设置环境变量 CURSOR_API_KEY,或通过命令行参数 --apiKey 传入。'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
BeLinkReview.#instance = new BeLinkReview(
|
|
85
|
+
apiKey,
|
|
86
|
+
agentPath,
|
|
87
|
+
ignorePatterns,
|
|
88
|
+
enableFeishu
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
// 如果单例已存在,但参数未设置,且命令行传入了,则更新
|
|
92
|
+
if (cliApiKey && !BeLinkReview.#instance.#apiKey) {
|
|
93
|
+
BeLinkReview.#instance.#apiKey = cliApiKey;
|
|
94
|
+
}
|
|
95
|
+
if (cliAgentPath && !BeLinkReview.#instance.#agentPath) {
|
|
96
|
+
BeLinkReview.#instance.#agentPath = cliAgentPath;
|
|
97
|
+
}
|
|
98
|
+
if (cliIgnorePatterns && !BeLinkReview.#instance.#ignorePatterns) {
|
|
99
|
+
BeLinkReview.#instance.#ignorePatterns = cliIgnorePatterns;
|
|
100
|
+
}
|
|
101
|
+
if (cliEnableFeishu !== undefined) {
|
|
102
|
+
BeLinkReview.#instance.#enableFeishu = cliEnableFeishu;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return BeLinkReview.#instance;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#getApiKey(): string {
|
|
109
|
+
const apiKey = this.#apiKey || process.env.CURSOR_API_KEY;
|
|
110
|
+
if (!apiKey) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'[be-link-review] 请先在 init({ apiKey: "..." }) 中传入 apiKey,或设置环境变量 CURSOR_API_KEY,或通过命令行参数 --apiKey 传入。'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return apiKey;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#getAgentPath(): string | undefined {
|
|
119
|
+
return this.#agentPath || process.env.CURSOR_AGENT_PATH;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#getIgnorePatterns(): string[] {
|
|
123
|
+
const envIgnore = process.env.BE_LINK_REVIEW_IGNORE
|
|
124
|
+
? process.env.BE_LINK_REVIEW_IGNORE.split(",")
|
|
125
|
+
: [];
|
|
126
|
+
return this.#ignorePatterns || envIgnore;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#isFeishuEnabled(): boolean {
|
|
130
|
+
return this.#enableFeishu;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 自动往用户项目 package.json 写入 script
|
|
135
|
+
*/
|
|
136
|
+
async #setupProjectScript() {
|
|
137
|
+
try {
|
|
138
|
+
const packageJsonPath = join(process.cwd(), "package.json");
|
|
139
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
140
|
+
|
|
141
|
+
if (!packageJson.scripts) {
|
|
142
|
+
packageJson.scripts = {};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!packageJson.scripts.review) {
|
|
146
|
+
packageJson.scripts.review = "belink-review";
|
|
147
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
148
|
+
console.log(
|
|
149
|
+
"[be-link-review] 已在 package.json 中添加 'review' 脚本。"
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
console.log("[be-link-review] 'review' 脚本已存在,跳过添加。");
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error("[be-link-review] 无法更新 package.json: ", error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 执行一次完整 CLI 流程:获取 git diff → 生成 prompt → 检查/安装 CLI → 与 Cursor 对话并打印结果。
|
|
161
|
+
*/
|
|
162
|
+
async goCli(): Promise<string> {
|
|
163
|
+
const apiKey = this.#getApiKey();
|
|
164
|
+
const agentPath = this.#getAgentPath();
|
|
165
|
+
const ignorePatterns = this.#getIgnorePatterns();
|
|
166
|
+
|
|
167
|
+
const ensureResult = await this.ensureAgentInstalled(false, agentPath);
|
|
168
|
+
|
|
169
|
+
if (!ensureResult.isInstalled) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"[be-link-review] Cursor CLI 未安装且自动安装失败,请手动安装。"
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log("[be-link-review] Getting git diff...");
|
|
176
|
+
const diff = await getGitDiff(ignorePatterns, process.cwd());
|
|
177
|
+
|
|
178
|
+
if (!diff) {
|
|
179
|
+
console.log("[be-link-review] No code changes detected");
|
|
180
|
+
return "No code changes detected";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const prompt = generateAIPrompt(diff);
|
|
184
|
+
console.log("[be-link-review] Sending to AI...");
|
|
185
|
+
const response = await this.chat(prompt, {
|
|
186
|
+
agentPath: ensureResult.actualAgentPath,
|
|
187
|
+
force: true,
|
|
188
|
+
});
|
|
189
|
+
console.log("===== AI Review =====");
|
|
190
|
+
console.log(response);
|
|
191
|
+
|
|
192
|
+
// 如果启用了飞书,发送到飞书
|
|
193
|
+
if (this.#isFeishuEnabled()) {
|
|
194
|
+
try {
|
|
195
|
+
await sendReviewToFeishu(response);
|
|
196
|
+
} catch (error: any) {
|
|
197
|
+
console.error(
|
|
198
|
+
`[be-link-review] 飞书通知发送失败,但不影响 review 结果: ${error.message}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return response;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 检查 Cursor CLI 是否已安装;未安装则自动安装(需网络)。
|
|
208
|
+
* @param silent 为 true 时不打印「已安装」等提示
|
|
209
|
+
* @param agentPath 可选的 agent 可执行文件路径
|
|
210
|
+
*/
|
|
211
|
+
async ensureAgentInstalled(
|
|
212
|
+
silent = false,
|
|
213
|
+
agentPath?: string
|
|
214
|
+
): Promise<BeLinkReviewEnsureResult> {
|
|
215
|
+
return isCheckCliInstall({ apiKey: this.#getApiKey(), silent, agentPath });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 通过 Cursor Headless CLI 与 Cursor 对话(发送 prompt,拿到回复)。
|
|
220
|
+
* 默认会先执行 ensureAgentInstalled(检查/安装 CLI),再启动 agent 执行提问。
|
|
221
|
+
* 参考:https://cursor.com/cn/docs/cli/headless
|
|
222
|
+
*/
|
|
223
|
+
async chat(
|
|
224
|
+
prompt: string,
|
|
225
|
+
options: BeLinkReviewChatOptions = {}
|
|
226
|
+
): Promise<string> {
|
|
227
|
+
const actualAgentPath =
|
|
228
|
+
options.agentPath || this.#getAgentPath() || "agent";
|
|
229
|
+
const args: string[] = ["--yolo", "-p", prompt];
|
|
230
|
+
if (options.outputFormat === "json") args.push("--output-format", "json");
|
|
231
|
+
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
const env = { ...process.env, CURSOR_API_KEY: this.#getApiKey() };
|
|
234
|
+
const proc = spawn(actualAgentPath, args, {
|
|
235
|
+
env,
|
|
236
|
+
cwd: process.cwd(),
|
|
237
|
+
shell: false,
|
|
238
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
let stdout = "";
|
|
242
|
+
let stderr = "";
|
|
243
|
+
|
|
244
|
+
proc.stdout?.on("data", (chunk) => {
|
|
245
|
+
stdout += chunk.toString();
|
|
246
|
+
});
|
|
247
|
+
proc.stderr?.on("data", (chunk) => {
|
|
248
|
+
stderr += chunk.toString();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
proc.on("error", (err) => {
|
|
252
|
+
reject(
|
|
253
|
+
new Error(
|
|
254
|
+
`[be-link-review] 无法启动 Cursor CLI (${actualAgentPath}),请先安装:curl https://cursor.com/install -fsS | bash。原始错误: ${err.message}`
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
proc.on("close", (code) => {
|
|
260
|
+
if (code !== 0) {
|
|
261
|
+
reject(
|
|
262
|
+
new Error(
|
|
263
|
+
`[be-link-review] agent 退出码 ${code}${
|
|
264
|
+
stderr ? `: ${stderr.trim()}` : ""
|
|
265
|
+
}`
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
resolve(stdout.trim());
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|