review-mark 1.0.1 → 1.0.3

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 CHANGED
@@ -6,82 +6,25 @@ A powerful CLI tool for AI-powered code review using Git diffs.
6
6
 
7
7
  `review-mark` 旨在提供一个自动化 AI 代码审查解决方案。用户可以在自己的项目中初始化该工具后,通过简单的 CLI 命令自动获取 `git diff`,并将其发送给 AI 进行代码审查,从而帮助开发者发现潜在的 bug、逻辑问题、性能问题和代码风格问题,并获得优化建议。
8
8
 
9
- ## 特性
10
-
11
- - **TypeScript 支持**: 整个项目使用 TypeScript 编写,提供强大的类型安全和开发体验。
12
- - **ESM + CommonJS**: 支持现代 Node.js 环境下的 ESM 和 CommonJS 模块系统。
13
- - **CLI 工具**: 基于 `commander` 构建,提供友好的命令行接口。
14
- - **自动化 Git Diff**: 自动检测并获取 Git 仓库中的代码变更(包括暂存区和工作区)。
15
- - **AI 代码审查**: 将 Git diff 发送给 Cursor CLI 进行 AI 驱动的代码审查。
16
- - **Cursor CLI 自动检测与安装**: 自动检测 `agent` 命令是否存在,如果不存在则提示并提供安装指引。
17
- - **易于集成**: 通过简单的 `init` 方法即可在用户项目中配置 `review` 脚本。
18
- - **可发布到 npm**: 完整的项目结构和打包配置,方便发布到 npm。
19
-
20
- ## 项目结构
21
-
22
- ```
23
- review-mark
24
-
25
- ├─ src
26
- │ ├─ core
27
- │ │ ├─ BeLinkReview.ts
28
- │ │ ├─ git.ts
29
- │ │ └─ prompt.ts
30
- │ │
31
- │ ├─ cli
32
- │ │ └─ review.ts
33
- │ │
34
- │ ├─ utils
35
- │ │ └─ checkCli.ts
36
- │ │
37
- │ └─ index.ts
38
-
39
- ├─ package.json
40
- ├─ tsconfig.json
41
- ├─ tsup.config.ts
42
- └─ README.md
43
- ```
44
-
45
- ## 技术栈
46
-
47
- - **语言**: TypeScript
48
- - **运行时**: Node.js 18+
49
- - **CLI 框架**: [commander](https://www.npmjs.com/package/commander)
50
- - **打包工具**: [tsup](https://www.npmjs.com/package/tsup)
51
- - **AI 引擎**: [Cursor CLI](https://cursor.com/cn/docs/cli/headless)
52
-
53
9
  ## 安装
54
10
 
55
11
  首先,在你的项目根目录安装 `review-mark` 作为开发依赖:
56
12
 
57
13
  ```bash
58
14
  pnpm add -D review-mark
15
+ yarn add --dev review-mark
16
+ npm install --save-dev review-mark
59
17
  ```
60
18
 
61
19
  ## 使用
62
20
 
63
21
  ### 1. 初始化
64
22
 
65
- 在你的项目入口文件(例如 `src/main.ts` 或 `app.ts`)中,调用 `BeLinkReview.init()` 方法进行初始化。你需要提供一个 `apiKey`,这将用于 Cursor CLI 的认证。
66
-
67
- ```typescript
68
- // src/main.ts (或你的项目入口文件)
69
- import { BeLinkReview } from "review-mark";
70
-
71
- BeLinkReview.init({
72
- apiKey: process.env.CURSOR_API_KEY || "YOUR_CURSOR_API_KEY", // 建议从环境变量获取
73
- });
74
-
75
- // 你可以在这里继续你的应用逻辑
76
- ```
77
-
78
- `BeLinkReview.init()` 会自动检测并向你的 `package.json` 文件中添加一个 `review-mark` 脚本:
79
-
80
23
  ```json
81
24
  // package.json
82
25
  {
83
26
  "scripts": {
84
- "review-mark": "review-mark"
27
+ "review-mark": "CURSOR_API_KEY=your-api-key review-mark"
85
28
  }
86
29
  }
87
30
  ```
@@ -119,23 +62,6 @@ CLI 将会执行以下步骤:
119
62
  [review-mark] No code changes detected
120
63
  ```
121
64
 
122
- ## 配置
123
-
124
- ### 基础配置
125
-
126
- `BeLinkReview.init()` 接受一个配置对象:
127
-
128
- ```typescript
129
- interface BeLinkReviewOptions {
130
- apiKey?: string; // 你的 Cursor API Key
131
- agentPath?: string; // Cursor CLI agent 可执行文件路径
132
- ignore?: string[]; // 需要忽略的文件模式
133
- feishu?: FeiShuConfig; // 飞书机器人配置
134
- }
135
- ```
136
-
137
- 建议将 `apiKey` 作为环境变量 `CURSOR_API_KEY` 进行管理,以避免将其硬编码到代码中。
138
-
139
65
  ### 飞书机器人集成
140
66
 
141
67
  `review-mark` 已**完全内置**飞书机器人功能,所有飞书配置已写死在代码中,**无需任何配置即可使用**!
@@ -173,29 +99,6 @@ FEISHU_ENABLED=false pnpm run review
173
99
  pnpm run review --no-feishu
174
100
  ```
175
101
 
176
- **方式三:代码配置**
177
-
178
- ```typescript
179
- BeLinkReview.init({
180
- apiKey: process.env.CURSOR_API_KEY,
181
- enableFeishu: false,
182
- });
183
- ```
184
-
185
- #### 3. 修改内置配置
186
-
187
- 如果需要修改飞书配置(如更换群聊、修改消息格式等),可以直接编辑 `src/constants.ts` 文件:
188
-
189
- ```typescript
190
- // src/constants.ts
191
- export const appId = "cli_a93822da7238dbb5";
192
- export const appSecret = "ZQdcpLUHFb4gFa8cGfrlJfVfSSyGtyzF";
193
- export const receiveId = "oc_482b6a04f95f4206c4fa9bc61829fd17"; // 修改为你的群聊 ID
194
- export const receiveIdType = "chat_id";
195
- export const messageType = "interactive"; // text | post | interactive
196
- export const messageTitle = "🔍 Code Review 结果";
197
- ```
198
-
199
102
  #### 4. 常见问题
200
103
 
201
104
  **Q: 飞书通知会发送到哪里?**
@@ -214,34 +117,6 @@ A: 支持三种格式:
214
117
  **Q: 如何完全禁用飞书功能?**
215
118
  A: 使用 `--no-feishu` 参数或设置 `FEISHU_ENABLED=false` 环境变量。
216
119
 
217
- ## 开发
218
-
219
- ### 构建项目
220
-
221
- ```bash
222
- cd review-mark
223
- pnpm install
224
- pnpm run build
225
- ```
226
-
227
- 这将使用 `tsup` 将 TypeScript 代码编译为 `dist/index.js` (ESM) 和 `dist/index.cjs` (CommonJS)。
228
-
229
- ### 运行 CLI (开发模式)
230
-
231
- ```bash
232
- pnpm run start
233
- ```
234
-
235
- 这将直接运行 `dist/cli/review.js`。
236
-
237
120
  ## 贡献
238
121
 
239
122
  欢迎提交 Issue 或 Pull Request。
240
-
241
- ## 许可证
242
-
243
- ISC License
244
-
245
- ---
246
-
247
- **Manus AI** 生成
@@ -225,6 +225,7 @@ ${diff}
225
225
  var lark = __toESM(require("@larksuiteoapi/node-sdk"), 1);
226
226
 
227
227
  // src/constants.ts
228
+ var DEFAULT_MODEL = "composer-1";
228
229
  var appId = "cli_a93822da7238dbb5";
229
230
  var appSecret = "ZQdcpLUHFb4gFa8cGfrlJfVfSSyGtyzF";
230
231
  var receiveId = "oc_482b6a04f95f4206c4fa9bc61829fd17";
@@ -455,11 +456,13 @@ var BeLinkReview = class _BeLinkReview {
455
456
  #agentPath;
456
457
  #ignorePatterns;
457
458
  #enableFeishu;
458
- constructor(apiKey, agentPath, ignorePatterns, enableFeishu = true) {
459
+ #model;
460
+ constructor(apiKey, agentPath, ignorePatterns, enableFeishu = true, model) {
459
461
  this.#apiKey = apiKey;
460
462
  this.#agentPath = agentPath;
461
463
  this.#ignorePatterns = ignorePatterns;
462
464
  this.#enableFeishu = enableFeishu;
465
+ this.#model = model;
463
466
  }
464
467
  /**
465
468
  * 初始化单例并注入参数,在项目入口调用一次即可。
@@ -472,13 +475,15 @@ var BeLinkReview = class _BeLinkReview {
472
475
  options.apiKey,
473
476
  options.agentPath,
474
477
  options.ignore,
475
- options.enableFeishu ?? true
478
+ options.enableFeishu ?? true,
479
+ options.model
476
480
  );
477
481
  } else {
478
482
  _BeLinkReview.#instance.#apiKey = options.apiKey;
479
483
  _BeLinkReview.#instance.#agentPath = options.agentPath;
480
484
  _BeLinkReview.#instance.#ignorePatterns = options.ignore;
481
485
  _BeLinkReview.#instance.#enableFeishu = options.enableFeishu ?? true;
486
+ _BeLinkReview.#instance.#model = options.model;
482
487
  }
483
488
  _BeLinkReview.#instance.#setupProjectScript();
484
489
  return _BeLinkReview.#instance;
@@ -490,12 +495,13 @@ var BeLinkReview = class _BeLinkReview {
490
495
  * @param cliIgnorePatterns 可选的命令行传入的 ignorePatterns
491
496
  * @param cliEnableFeishu 可选的命令行传入的飞书开关
492
497
  */
493
- static getInstance(cliApiKey, cliAgentPath, cliIgnorePatterns, cliEnableFeishu) {
498
+ static getInstance(cliApiKey, cliAgentPath, cliIgnorePatterns, cliEnableFeishu, cliModel) {
494
499
  if (_BeLinkReview.#instance === null) {
495
500
  const apiKey = cliApiKey || process.env.CURSOR_API_KEY;
496
501
  const agentPath = cliAgentPath || process.env.CURSOR_AGENT_PATH;
497
502
  const ignorePatterns = cliIgnorePatterns || (process.env.BE_LINK_REVIEW_IGNORE ? process.env.BE_LINK_REVIEW_IGNORE.split(",") : void 0);
498
503
  const enableFeishu = cliEnableFeishu ?? process.env.FEISHU_ENABLED !== "false";
504
+ const model = cliModel || process.env.CURSOR_MODEL;
499
505
  if (!apiKey) {
500
506
  throw new Error(
501
507
  '[review-mark] \u8BF7\u5148\u8C03\u7528 BeLinkReview.init({ apiKey: "..." }) \u521D\u59CB\u5316\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF CURSOR_API_KEY\uFF0C\u6216\u901A\u8FC7\u547D\u4EE4\u884C\u53C2\u6570 --apiKey \u4F20\u5165\u3002'
@@ -505,7 +511,8 @@ var BeLinkReview = class _BeLinkReview {
505
511
  apiKey,
506
512
  agentPath,
507
513
  ignorePatterns,
508
- enableFeishu
514
+ enableFeishu,
515
+ model
509
516
  );
510
517
  } else {
511
518
  if (cliApiKey && !_BeLinkReview.#instance.#apiKey) {
@@ -520,6 +527,9 @@ var BeLinkReview = class _BeLinkReview {
520
527
  if (cliEnableFeishu !== void 0) {
521
528
  _BeLinkReview.#instance.#enableFeishu = cliEnableFeishu;
522
529
  }
530
+ if (cliModel) {
531
+ _BeLinkReview.#instance.#model = cliModel;
532
+ }
523
533
  }
524
534
  return _BeLinkReview.#instance;
525
535
  }
@@ -542,6 +552,9 @@ var BeLinkReview = class _BeLinkReview {
542
552
  #isFeishuEnabled() {
543
553
  return this.#enableFeishu;
544
554
  }
555
+ #getModel() {
556
+ return this.#model || process.env.CURSOR_MODEL || DEFAULT_MODEL;
557
+ }
545
558
  /**
546
559
  * 自动往用户项目 package.json 写入 script
547
560
  */
@@ -588,8 +601,10 @@ var BeLinkReview = class _BeLinkReview {
588
601
  console.log("[review-mark] Sending to AI...");
589
602
  const response = await this.chat(prompt, {
590
603
  agentPath: ensureResult.actualAgentPath,
591
- force: true
604
+ force: true,
605
+ model: this.#getModel()
592
606
  });
607
+ console.log(this.#getModel(), "model");
593
608
  console.log("===== AI Review =====");
594
609
  console.log(response);
595
610
  if (this.#isFeishuEnabled()) {
@@ -618,7 +633,9 @@ var BeLinkReview = class _BeLinkReview {
618
633
  */
619
634
  async chat(prompt, options = {}) {
620
635
  const actualAgentPath = options.agentPath || this.#getAgentPath() || "agent";
636
+ const model = options.model || this.#getModel();
621
637
  const args = ["--yolo", "-p", prompt];
638
+ if (model) args.unshift("--model", model);
622
639
  if (options.outputFormat === "json") args.push("--output-format", "json");
623
640
  return new Promise((resolve, reject) => {
624
641
  const env = { ...process.env, CURSOR_API_KEY: this.#getApiKey() };
@@ -660,89 +677,40 @@ var BeLinkReview = class _BeLinkReview {
660
677
 
661
678
  // src/cli/review.ts
662
679
  var program = new import_commander.Command();
663
- program.name("review-mark").description("AI-powered code review tool").version("1.0.0").option(
680
+ var sharedOptions = (cmd) => cmd.option(
664
681
  "--apiKey <key>",
665
682
  "Cursor API Key (overrides CURSOR_API_KEY environment variable)"
666
683
  ).option(
667
684
  "--agentPath <path>",
668
685
  "Path to the Cursor CLI agent executable (overrides CURSOR_AGENT_PATH environment variable)"
669
686
  ).option(
670
- "--ignore <patterns>",
671
- "Comma-separated list of glob patterns to ignore (e.g., *.lock,dist/**)"
672
- ).option(
673
- "--feishu-app-id <appId>",
674
- "Feishu App ID (overrides FEISHU_APP_ID environment variable)"
675
- ).option(
676
- "--feishu-app-secret <appSecret>",
677
- "Feishu App Secret (overrides FEISHU_APP_SECRET environment variable)"
678
- ).option(
679
- "--feishu-receive-id <receiveId>",
680
- "Feishu Receive ID - user open_id or chat_id (overrides FEISHU_RECEIVE_ID environment variable)"
681
- ).option(
682
- "--feishu-receive-id-type <type>",
683
- "Feishu Receive ID Type: open_id, user_id, chat_id, email, union_id (default: chat_id)"
684
- ).option(
685
- "--feishu-type <type>",
686
- "Feishu message type: text, post, or interactive (default: interactive)"
687
- ).option(
688
- "--feishu-title <title>",
689
- "Feishu message title (default: \u{1F50D} Code Review \u7ED3\u679C)"
690
- ).option("--no-feishu", "Disable Feishu notification").action(async (options) => {
691
- try {
692
- const ignorePatterns = options.ignore ? options.ignore.split(",") : void 0;
693
- const instance = BeLinkReview.getInstance(
694
- options.apiKey,
695
- options.agentPath,
696
- ignorePatterns,
697
- options.feishu !== false
698
- );
699
- await instance.goCli();
700
- } catch (error) {
701
- console.error(`[review-mark] Error: ${error.message}`);
702
- process.exit(1);
703
- }
704
- });
705
- program.command("review").description("Perform an AI code review on git diff").option(
706
- "--apiKey <key>",
707
- "Cursor API Key (overrides CURSOR_API_KEY environment variable)"
708
- ).option(
709
- "--agentPath <path>",
710
- "Path to the Cursor CLI agent executable (overrides CURSOR_AGENT_PATH environment variable)"
687
+ "--model <model>",
688
+ "AI model to use, e.g. claude-3-5-sonnet (overrides CURSOR_MODEL environment variable)"
711
689
  ).option(
712
690
  "--ignore <patterns>",
713
691
  "Comma-separated list of glob patterns to ignore (e.g., *.lock,dist/**)"
714
- ).option(
715
- "--feishu-app-id <appId>",
716
- "Feishu App ID (overrides FEISHU_APP_ID environment variable)"
717
- ).option(
718
- "--feishu-app-secret <appSecret>",
719
- "Feishu App Secret (overrides FEISHU_APP_SECRET environment variable)"
720
- ).option(
721
- "--feishu-receive-id <receiveId>",
722
- "Feishu Receive ID - user open_id or chat_id (overrides FEISHU_RECEIVE_ID environment variable)"
723
- ).option(
724
- "--feishu-receive-id-type <type>",
725
- "Feishu Receive ID Type: open_id, user_id, chat_id, email, union_id (default: chat_id)"
726
- ).option(
727
- "--feishu-type <type>",
728
- "Feishu message type: text, post, or interactive (default: interactive)"
729
- ).option(
730
- "--feishu-title <title>",
731
- "Feishu message title (default: \u{1F50D} Code Review \u7ED3\u679C)"
732
- ).option("--no-feishu", "Disable Feishu notification").action(async (options) => {
692
+ ).option("--no-feishu", "Disable Feishu notification");
693
+ var runAction = async (options) => {
733
694
  try {
734
695
  const ignorePatterns = options.ignore ? options.ignore.split(",") : void 0;
735
696
  const instance = BeLinkReview.getInstance(
736
697
  options.apiKey,
737
698
  options.agentPath,
738
699
  ignorePatterns,
739
- options.feishu !== false
700
+ options.feishu !== false,
701
+ options.model
740
702
  );
741
703
  await instance.goCli();
742
704
  } catch (error) {
743
705
  console.error(`[review-mark] Error: ${error.message}`);
744
706
  process.exit(1);
745
707
  }
746
- });
708
+ };
709
+ sharedOptions(
710
+ program.name("review-mark").description("AI-powered code review tool").version("1.0.0")
711
+ ).action(runAction);
712
+ sharedOptions(
713
+ program.command("review").description("Perform an AI code review on git diff")
714
+ ).action(runAction);
747
715
  program.parse(process.argv);
748
716
  //# sourceMappingURL=review.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/review.ts","../../src/core/BeLinkReview.ts","../../src/utils/checkCli.ts","../../src/core/git.ts","../../src/core/prompt.ts","../../src/core/feishu.ts","../../src/constants.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { BeLinkReview } from \"../core/BeLinkReview\";\n\nconst program = new Command();\n\nprogram\n .name(\"review-mark\")\n .description(\"AI-powered code review tool\")\n .version(\"1.0.0\")\n .option(\n \"--apiKey <key>\",\n \"Cursor API Key (overrides CURSOR_API_KEY environment variable)\"\n )\n .option(\n \"--agentPath <path>\",\n \"Path to the Cursor CLI agent executable (overrides CURSOR_AGENT_PATH environment variable)\"\n )\n .option(\n \"--ignore <patterns>\",\n \"Comma-separated list of glob patterns to ignore (e.g., *.lock,dist/**)\"\n )\n .option(\n \"--feishu-app-id <appId>\",\n \"Feishu App ID (overrides FEISHU_APP_ID environment variable)\"\n )\n .option(\n \"--feishu-app-secret <appSecret>\",\n \"Feishu App Secret (overrides FEISHU_APP_SECRET environment variable)\"\n )\n .option(\n \"--feishu-receive-id <receiveId>\",\n \"Feishu Receive ID - user open_id or chat_id (overrides FEISHU_RECEIVE_ID environment variable)\"\n )\n .option(\n \"--feishu-receive-id-type <type>\",\n \"Feishu Receive ID Type: open_id, user_id, chat_id, email, union_id (default: chat_id)\"\n )\n .option(\n \"--feishu-type <type>\",\n \"Feishu message type: text, post, or interactive (default: interactive)\"\n )\n .option(\n \"--feishu-title <title>\",\n \"Feishu message title (default: 🔍 Code Review 结果)\"\n )\n .option(\"--no-feishu\", \"Disable Feishu notification\")\n .action(async (options) => {\n try {\n const ignorePatterns = options.ignore\n ? options.ignore.split(\",\")\n : undefined;\n\n const instance = BeLinkReview.getInstance(\n options.apiKey,\n options.agentPath,\n ignorePatterns,\n options.feishu !== false\n );\n await instance.goCli();\n } catch (error: any) {\n console.error(`[review-mark] Error: ${error.message}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"review\")\n .description(\"Perform an AI code review on git diff\")\n .option(\n \"--apiKey <key>\",\n \"Cursor API Key (overrides CURSOR_API_KEY environment variable)\"\n )\n .option(\n \"--agentPath <path>\",\n \"Path to the Cursor CLI agent executable (overrides CURSOR_AGENT_PATH environment variable)\"\n )\n .option(\n \"--ignore <patterns>\",\n \"Comma-separated list of glob patterns to ignore (e.g., *.lock,dist/**)\"\n )\n .option(\n \"--feishu-app-id <appId>\",\n \"Feishu App ID (overrides FEISHU_APP_ID environment variable)\"\n )\n .option(\n \"--feishu-app-secret <appSecret>\",\n \"Feishu App Secret (overrides FEISHU_APP_SECRET environment variable)\"\n )\n .option(\n \"--feishu-receive-id <receiveId>\",\n \"Feishu Receive ID - user open_id or chat_id (overrides FEISHU_RECEIVE_ID environment variable)\"\n )\n .option(\n \"--feishu-receive-id-type <type>\",\n \"Feishu Receive ID Type: open_id, user_id, chat_id, email, union_id (default: chat_id)\"\n )\n .option(\n \"--feishu-type <type>\",\n \"Feishu message type: text, post, or interactive (default: interactive)\"\n )\n .option(\n \"--feishu-title <title>\",\n \"Feishu message title (default: 🔍 Code Review 结果)\"\n )\n .option(\"--no-feishu\", \"Disable Feishu notification\")\n .action(async (options) => {\n try {\n const ignorePatterns = options.ignore\n ? options.ignore.split(\",\")\n : undefined;\n\n const instance = BeLinkReview.getInstance(\n options.apiKey,\n options.agentPath,\n ignorePatterns,\n options.feishu !== false\n );\n await instance.goCli();\n } catch (error: any) {\n console.error(`[review-mark] Error: ${error.message}`);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n","import { spawn } from \"node:child_process\";\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type {\n BeLinkReviewOptions,\n BeLinkReviewChatOptions,\n BeLinkReviewEnsureResult,\n} from \"../types\";\nimport { isCheckCliInstall } from \"../utils/checkCli\";\nimport { getGitDiff } from \"./git\";\nimport { generateAIPrompt } from \"./prompt\";\nimport { sendReviewToFeishu } from \"./feishu\";\n\nexport class BeLinkReview {\n static #instance: BeLinkReview | null = null;\n #apiKey: string | undefined;\n #agentPath: string | undefined;\n #ignorePatterns: string[] | undefined;\n #enableFeishu: boolean;\n\n private constructor(\n apiKey?: string,\n agentPath?: string,\n ignorePatterns?: string[],\n enableFeishu: boolean = true\n ) {\n this.#apiKey = apiKey;\n this.#agentPath = agentPath;\n this.#ignorePatterns = ignorePatterns;\n this.#enableFeishu = enableFeishu;\n }\n\n /**\n * 初始化单例并注入参数,在项目入口调用一次即可。\n * 负责保存 apiKey 和自动写入 package.json 脚本。\n * 此方法主要用于在用户项目中设置 apiKey 和脚本,CLI 运行时会优先从环境变量或命令行参数获取。\n */\n static init(options: BeLinkReviewOptions = {}): BeLinkReview {\n if (BeLinkReview.#instance === null) {\n BeLinkReview.#instance = new BeLinkReview(\n options.apiKey,\n options.agentPath,\n options.ignore,\n options.enableFeishu ?? true\n );\n } else {\n BeLinkReview.#instance.#apiKey = options.apiKey; // 允许重新初始化时更新 apiKey\n BeLinkReview.#instance.#agentPath = options.agentPath; // 允许重新初始化时更新 agentPath\n BeLinkReview.#instance.#ignorePatterns = options.ignore; // 允许重新初始化时更新 ignorePatterns\n BeLinkReview.#instance.#enableFeishu = options.enableFeishu ?? true; // 允许重新初始化时更新飞书开关\n }\n BeLinkReview.#instance.#setupProjectScript();\n return BeLinkReview.#instance;\n }\n\n /**\n * 获取单例实例。如果未通过 init() 初始化,则尝试从环境变量 CURSOR_API_KEY 获取。\n * @param cliApiKey 可选的命令行传入的 apiKey\n * @param cliAgentPath 可选的命令行传入的 agentPath\n * @param cliIgnorePatterns 可选的命令行传入的 ignorePatterns\n * @param cliEnableFeishu 可选的命令行传入的飞书开关\n */\n static getInstance(\n cliApiKey?: string,\n cliAgentPath?: string,\n cliIgnorePatterns?: string[],\n cliEnableFeishu?: boolean\n ): BeLinkReview {\n if (BeLinkReview.#instance === null) {\n const apiKey = cliApiKey || process.env.CURSOR_API_KEY;\n const agentPath = cliAgentPath || process.env.CURSOR_AGENT_PATH;\n const ignorePatterns =\n cliIgnorePatterns ||\n (process.env.BE_LINK_REVIEW_IGNORE\n ? process.env.BE_LINK_REVIEW_IGNORE.split(\",\")\n : undefined);\n const enableFeishu =\n cliEnableFeishu ?? process.env.FEISHU_ENABLED !== \"false\";\n\n if (!apiKey) {\n throw new Error(\n '[review-mark] 请先调用 BeLinkReview.init({ apiKey: \"...\" }) 初始化,或设置环境变量 CURSOR_API_KEY,或通过命令行参数 --apiKey 传入。'\n );\n }\n BeLinkReview.#instance = new BeLinkReview(\n apiKey,\n agentPath,\n ignorePatterns,\n enableFeishu\n );\n } else {\n // 如果单例已存在,但参数未设置,且命令行传入了,则更新\n if (cliApiKey && !BeLinkReview.#instance.#apiKey) {\n BeLinkReview.#instance.#apiKey = cliApiKey;\n }\n if (cliAgentPath && !BeLinkReview.#instance.#agentPath) {\n BeLinkReview.#instance.#agentPath = cliAgentPath;\n }\n if (cliIgnorePatterns && !BeLinkReview.#instance.#ignorePatterns) {\n BeLinkReview.#instance.#ignorePatterns = cliIgnorePatterns;\n }\n if (cliEnableFeishu !== undefined) {\n BeLinkReview.#instance.#enableFeishu = cliEnableFeishu;\n }\n }\n return BeLinkReview.#instance;\n }\n\n #getApiKey(): string {\n const apiKey = this.#apiKey || process.env.CURSOR_API_KEY;\n if (!apiKey) {\n throw new Error(\n '[review-mark] 请先在 init({ apiKey: \"...\" }) 中传入 apiKey,或设置环境变量 CURSOR_API_KEY,或通过命令行参数 --apiKey 传入。'\n );\n }\n return apiKey;\n }\n\n #getAgentPath(): string | undefined {\n return this.#agentPath || process.env.CURSOR_AGENT_PATH;\n }\n\n #getIgnorePatterns(): string[] {\n const envIgnore = process.env.BE_LINK_REVIEW_IGNORE\n ? process.env.BE_LINK_REVIEW_IGNORE.split(\",\")\n : [];\n return this.#ignorePatterns || envIgnore;\n }\n\n #isFeishuEnabled(): boolean {\n return this.#enableFeishu;\n }\n\n /**\n * 自动往用户项目 package.json 写入 script\n */\n async #setupProjectScript() {\n try {\n const packageJsonPath = join(process.cwd(), \"package.json\");\n const packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\"));\n\n if (!packageJson.scripts) {\n packageJson.scripts = {};\n }\n\n if (!packageJson.scripts[\"review-mark\"]) {\n packageJson.scripts[\"review-mark\"] = \"review-mark\";\n writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));\n console.log(\n \"[review-mark] 已在 package.json 中添加 'review-mark' 脚本。\"\n );\n } else {\n console.log(\"[review-mark] 'review-mark' 脚本已存在,跳过添加。\");\n }\n } catch (error) {\n console.error(\"[review-mark] 无法更新 package.json: \", error);\n }\n }\n\n /**\n * 执行一次完整 CLI 流程:获取 git diff → 生成 prompt → 检查/安装 CLI → 与 Cursor 对话并打印结果。\n */\n async goCli(): Promise<string> {\n const apiKey = this.#getApiKey();\n const agentPath = this.#getAgentPath();\n const ignorePatterns = this.#getIgnorePatterns();\n\n const ensureResult = await this.ensureAgentInstalled(false, agentPath);\n\n if (!ensureResult.isInstalled) {\n throw new Error(\n \"[review-mark] Cursor CLI 未安装且自动安装失败,请手动安装。\"\n );\n }\n\n console.log(\"[review-mark] Getting git diff...\");\n const diff = await getGitDiff(ignorePatterns, process.cwd());\n\n if (!diff) {\n console.log(\"[review-mark] No code changes detected\");\n return \"No code changes detected\";\n }\n\n const prompt = generateAIPrompt(diff);\n console.log(\"[review-mark] Sending to AI...\");\n const response = await this.chat(prompt, {\n agentPath: ensureResult.actualAgentPath,\n force: true,\n });\n console.log(\"===== AI Review =====\");\n console.log(response);\n\n // 如果启用了飞书,发送到飞书\n if (this.#isFeishuEnabled()) {\n try {\n await sendReviewToFeishu(response);\n } catch (error: any) {\n console.error(\n `[review-mark] 飞书通知发送失败,但不影响 review 结果: ${error.message}`\n );\n }\n }\n\n return response;\n }\n\n /**\n * 检查 Cursor CLI 是否已安装;未安装则自动安装(需网络)。\n * @param silent 为 true 时不打印「已安装」等提示\n * @param agentPath 可选的 agent 可执行文件路径\n */\n async ensureAgentInstalled(\n silent = false,\n agentPath?: string\n ): Promise<BeLinkReviewEnsureResult> {\n return isCheckCliInstall({ apiKey: this.#getApiKey(), silent, agentPath });\n }\n\n /**\n * 通过 Cursor Headless CLI 与 Cursor 对话(发送 prompt,拿到回复)。\n * 默认会先执行 ensureAgentInstalled(检查/安装 CLI),再启动 agent 执行提问。\n * 参考:https://cursor.com/cn/docs/cli/headless\n */\n async chat(\n prompt: string,\n options: BeLinkReviewChatOptions = {}\n ): Promise<string> {\n const actualAgentPath =\n options.agentPath || this.#getAgentPath() || \"agent\";\n const args: string[] = [\"--yolo\", \"-p\", prompt];\n if (options.outputFormat === \"json\") args.push(\"--output-format\", \"json\");\n\n return new Promise((resolve, reject) => {\n const env = { ...process.env, CURSOR_API_KEY: this.#getApiKey() };\n const proc = spawn(actualAgentPath, args, {\n env,\n cwd: process.cwd(),\n shell: false,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n proc.on(\"error\", (err) => {\n reject(\n new Error(\n `[review-mark] 无法启动 Cursor CLI (${actualAgentPath}),请先安装:curl https://cursor.com/install -fsS | bash。原始错误: ${err.message}`\n )\n );\n });\n\n proc.on(\"close\", (code) => {\n if (code !== 0) {\n reject(\n new Error(\n `[review-mark] agent 退出码 ${code}${\n stderr ? `: ${stderr.trim()}` : \"\"\n }`\n )\n );\n return;\n }\n resolve(stdout.trim());\n });\n });\n }\n}\n","import { spawn, exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { existsSync, appendFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { BeLinkReviewEnsureResult } from \"@/types\";\n\nconst execAsync = promisify(exec);\n\ninterface CheckCliInstallOptions {\n apiKey: string;\n silent?: boolean;\n agentPath?: string;\n}\n\n// Cursor 官方安装脚本默认将 agent 放在 ~/.local/bin\nconst LOCAL_BIN = join(homedir(), \".local\", \"bin\");\n\n// 常见的 Cursor CLI 安装路径\nconst COMMON_AGENT_PATHS = [\n join(LOCAL_BIN, \"agent\"), // Cursor 官方安装脚本默认路径\n join(homedir(), \".cursor\", \"agent\"),\n \"/usr/local/bin/agent\",\n \"/opt/homebrew/bin/agent\",\n];\n\n/**\n * 将 ~/.local/bin 写入用户 shell 配置文件(.zshrc / .bashrc),\n * 并立即注入当前进程的 PATH,确保安装后无需重启终端即可使用。\n */\nfunction setupLocalBinPath(silent: boolean): void {\n // 立即让当前进程能找到 agent\n if (!process.env.PATH?.includes(LOCAL_BIN)) {\n process.env.PATH = `${LOCAL_BIN}:${process.env.PATH}`;\n }\n\n const exportLine = `\\nexport PATH=\"$HOME/.local/bin:$PATH\"`;\n const shell = process.env.SHELL ?? \"\";\n const rcFiles: string[] = [];\n\n if (shell.includes(\"zsh\")) {\n rcFiles.push(join(homedir(), \".zshrc\"));\n } else if (shell.includes(\"bash\")) {\n rcFiles.push(join(homedir(), \".bashrc\"));\n } else {\n // 两个都写,保证覆盖\n rcFiles.push(join(homedir(), \".zshrc\"), join(homedir(), \".bashrc\"));\n }\n\n for (const rc of rcFiles) {\n try {\n const content = existsSync(rc)\n ? require(\"node:fs\").readFileSync(rc, \"utf-8\")\n : \"\";\n if (!content.includes(\".local/bin\")) {\n appendFileSync(rc, exportLine);\n if (!silent) {\n console.log(`[review-mark] 已将 ~/.local/bin 写入 ${rc}`);\n }\n }\n } catch {\n // 写入失败不阻断流程\n }\n }\n}\n\nasync function findAgentExecutable(\n userAgentPath?: string\n): Promise<string | null> {\n // 1. 优先使用用户指定的路径\n if (userAgentPath && existsSync(userAgentPath)) {\n return userAgentPath;\n }\n\n // 2. 尝试通过 which 命令在 PATH 中查找\n try {\n const { stdout } = await execAsync(\"which agent\");\n const pathFromWhich = stdout.trim();\n if (pathFromWhich && existsSync(pathFromWhich)) {\n return pathFromWhich;\n }\n } catch (error) {\n // ignore error, continue to next method\n }\n\n // 3. 检查常见安装路径\n for (const path of COMMON_AGENT_PATHS) {\n if (existsSync(path)) {\n return path;\n }\n }\n\n return null;\n}\n\nexport async function isCheckCliInstall(\n options: CheckCliInstallOptions\n): Promise<BeLinkReviewEnsureResult> {\n const { silent = false, agentPath: userAgentPath } = options;\n\n let actualAgentPath = await findAgentExecutable(userAgentPath);\n\n if (actualAgentPath) {\n if (!silent) {\n console.log(\n `[review-mark] Cursor CLI (agent) 已在 ${actualAgentPath} 找到。`\n );\n }\n return { isInstalled: true, message: \"Cursor CLI 已安装\", actualAgentPath };\n }\n\n if (!silent) {\n console.log(\"[review-mark] Cursor CLI (agent) 未找到,正在尝试安装...\");\n console.log(\n \"[review-mark] 执行安装命令: curl https://cursor.com/install -fsS | bash\"\n );\n }\n\n return new Promise((resolve, reject) => {\n const installProcess = spawn(\n \"bash\",\n [\"-c\", \"curl https://cursor.com/install -fsS | bash\"],\n {\n stdio: \"inherit\", // 将安装过程的输出直接显示给用户\n }\n );\n\n installProcess.on(\"close\", async (code) => {\n if (code === 0) {\n // 安装完成后自动配置 PATH(写入 rc 文件 + 注入当前进程)\n setupLocalBinPath(silent);\n\n // 再次检查 agent 命令是否可用\n actualAgentPath = await findAgentExecutable(userAgentPath);\n if (actualAgentPath) {\n if (!silent) {\n console.log(\"[review-mark] Cursor CLI 安装成功。\");\n }\n resolve({\n isInstalled: true,\n message: \"Cursor CLI 安装成功\",\n actualAgentPath,\n });\n } else {\n reject(\n new Error(\n `[review-mark] Cursor CLI 安装命令执行成功,但未找到 agent 可执行文件。请手动检查安装:curl https://cursor.com/install -fsS | bash。`\n )\n );\n }\n } else {\n reject(\n new Error(\n `[review-mark] Cursor CLI 安装失败,退出码 ${code}。请手动安装:curl https://cursor.com/install -fsS | bash。`\n )\n );\n }\n });\n\n installProcess.on(\"error\", (err) => {\n reject(\n new Error(\n `[review-mark] 无法启动安装进程:${err.message}。请手动安装:curl https://cursor.com/install -fsS | bash`\n )\n );\n });\n });\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\n// 固定的忽略文件模式,这些文件将永远不会被审查\nconst FIXED_IGNORE_PATTERNS = [\"package.json\"];\n\nasync function runGit(args: string[], cwd: string): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", args, { cwd });\n return stdout.trim();\n}\n\nexport async function getGitDiff(\n userIgnorePatterns: string[] = [],\n cwd: string = process.cwd()\n): Promise<string> {\n // 检查是否在 git 仓库中\n try {\n await runGit([\"rev-parse\", \"--git-dir\"], cwd);\n } catch (error) {\n console.error(`[review-mark] 当前目录不是 git 仓库: ${cwd}`);\n return \"\";\n }\n\n // 合并固定忽略模式和用户提供的忽略模式\n const allIgnorePatterns = [\n ...new Set([...FIXED_IGNORE_PATTERNS, ...userIgnorePatterns]),\n ];\n // 使用 execFile 直接传参,完全绕过 shell,:(exclude) 不会被解析\n // git 参数顺序:git diff [options] [commit] -- [pathspec]\n // options/commit 必须在 -- 和 pathspec 之前\n const excludeArgs = allIgnorePatterns.map((p) => `:(exclude)${p}`);\n\n let diff = \"\";\n\n try {\n // 优先使用 git diff --cached 获取暂存区的改动\n diff = await runGit(\n [\"diff\", \"--no-color\", \"--relative\", \"--cached\", \"--\", ...excludeArgs],\n cwd\n );\n if (diff) {\n console.log(\"[review-mark] 检测到暂存区改动\");\n }\n } catch (error: any) {\n console.warn(`[review-mark] 获取暂存区 diff 失败: ${error.message}`);\n }\n\n if (!diff) {\n try {\n // 如果暂存区没有改动,则获取工作区和 HEAD 的改动\n diff = await runGit(\n [\"diff\", \"--no-color\", \"--relative\", \"HEAD\", \"--\", ...excludeArgs],\n cwd\n );\n if (diff) {\n console.log(\"[review-mark] 检测到工作区改动(相对于 HEAD)\");\n }\n } catch (error: any) {\n console.warn(`[review-mark] 获取工作区 diff 失败: ${error.message}`);\n }\n }\n\n if (!diff) {\n console.log(\"[review-mark] 未检测到代码改动(已检查暂存区和工作区)\");\n }\n\n return diff;\n}\n","\nexport function generateAIPrompt(diff: string): string {\n return `你是一名资深软件工程师,请 review 以下代码变更。\nGit diff:\n${diff}\n请分析:\n是否存在 bug\n是否存在潜在逻辑问题\n是否存在性能问题\n是否存在代码风格问题\n给出优化建议`;\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport {\n appId,\n appSecret,\n receiveId,\n receiveIdType,\n messageType,\n messageTitle,\n} from \"../constants\";\n\n/**\n * 发送 Code Review 结果到飞书\n * 使用飞书官方 Node.js SDK,所有配置从 constants.ts 读取\n */\nexport async function sendReviewToFeishu(reviewContent: string): Promise<void> {\n console.log(\"[review-mark] 正在发送消息到飞书...\");\n console.log(`[review-mark] 消息类型: ${messageType}`);\n\n try {\n // 初始化飞书客户端\n const client = new lark.Client({\n appId,\n appSecret,\n domain: lark.Domain.Feishu,\n });\n\n // 根据消息类型构造消息内容\n let msgContent: string;\n let msgType: string;\n\n if (messageType === \"interactive\") {\n // Interactive 卡片消息(支持 Markdown 渲染)\n msgContent = JSON.stringify({\n config: {\n wide_screen_mode: true,\n },\n header: {\n title: {\n tag: \"plain_text\",\n content: messageTitle,\n },\n template: \"blue\",\n },\n elements: [\n {\n tag: \"div\",\n text: {\n tag: \"lark_md\",\n content: reviewContent,\n },\n },\n {\n tag: \"hr\",\n },\n {\n tag: \"note\",\n elements: [\n {\n tag: \"plain_text\",\n content: `生成时间: ${new Date().toLocaleString(\"zh-CN\", {\n timeZone: \"Asia/Shanghai\",\n })}`,\n },\n ],\n },\n ],\n });\n msgType = \"interactive\";\n } else if (messageType === \"post\") {\n // Post 富文本消息\n msgContent = JSON.stringify({\n zh_cn: {\n title: messageTitle,\n content: convertMarkdownToFeishuPost(reviewContent),\n },\n });\n msgType = \"post\";\n } else if (messageType === \"text\") {\n // Text 纯文本消息\n msgContent = JSON.stringify({\n text: `${messageTitle}\\n\\n${reviewContent}`,\n });\n msgType = \"text\";\n } else {\n throw new Error(`[review-mark] 不支持的消息类型: ${messageType}`);\n }\n\n // 调试日志\n console.log(`[review-mark] 发送参数:`);\n console.log(` - receive_id_type: ${receiveIdType}`);\n console.log(` - receive_id: ${receiveId}`);\n console.log(` - msg_type: ${msgType}`);\n\n // 调用发送消息接口\n const response = await client.im.message.create({\n params: {\n receive_id_type: receiveIdType,\n },\n data: {\n receive_id: receiveId,\n msg_type: msgType,\n content: msgContent,\n },\n });\n\n if (response.code !== 0) {\n throw new Error(\n `[review-mark] 飞书 API 返回错误: ${response.msg || \"未知错误\"}`\n );\n }\n\n console.log(\"[review-mark] ✅ 飞书消息发送成功\");\n console.log(\n `[review-mark] 消息 ID: ${response.data?.message_id || \"未知\"}`\n );\n } catch (error: any) {\n console.error(`[review-mark] ❌ 飞书消息发送失败: ${error.message}`);\n throw error;\n }\n}\n\n/**\n * 将 Markdown 格式转换为飞书 post 格式\n * 飞书 post 格式支持富文本样式\n */\nfunction convertMarkdownToFeishuPost(markdown: string): any[][] {\n const lines = markdown.split(\"\\n\");\n const result: any[][] = [];\n\n let inCodeBlock = false;\n let codeBlockContent: string[] = [];\n\n for (const line of lines) {\n // 处理代码块\n if (line.startsWith(\"```\")) {\n if (inCodeBlock) {\n // 代码块结束\n if (codeBlockContent.length > 0) {\n result.push([\n {\n tag: \"text\",\n text: codeBlockContent.join(\"\\n\"),\n style: [\"code\"],\n },\n ]);\n codeBlockContent = [];\n }\n inCodeBlock = false;\n } else {\n // 代码块开始\n inCodeBlock = true;\n }\n continue;\n }\n\n if (inCodeBlock) {\n codeBlockContent.push(line);\n continue;\n }\n\n // 空行\n if (!line.trim()) {\n result.push([{ tag: \"text\", text: \"\" }]);\n continue;\n }\n\n // 标题(加粗 + 加大)\n if (line.startsWith(\"#\")) {\n const level = line.match(/^#+/)?.[0].length || 1;\n const text = line.replace(/^#+\\s*/, \"\");\n result.push([\n {\n tag: \"text\",\n text: text,\n style: level <= 2 ? [\"bold\", \"underline\"] : [\"bold\"],\n },\n ]);\n continue;\n }\n\n // 解析行内样式(加粗、链接等)\n const parsedLine = parseLineStyles(line);\n result.push(parsedLine);\n }\n\n // 如果还在代码块中,添加剩余内容\n if (codeBlockContent.length > 0) {\n result.push([\n {\n tag: \"text\",\n text: codeBlockContent.join(\"\\n\"),\n style: [\"code\"],\n },\n ]);\n }\n\n return result;\n}\n\n/**\n * 解析行内样式(加粗、链接、代码等)\n */\nfunction parseLineStyles(line: string): any[] {\n const elements: any[] = [];\n let currentText = \"\";\n let i = 0;\n\n while (i < line.length) {\n // 处理加粗 **text**\n if (line[i] === \"*\" && line[i + 1] === \"*\") {\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n currentText = \"\";\n }\n const endIndex = line.indexOf(\"**\", i + 2);\n if (endIndex !== -1) {\n const boldText = line.substring(i + 2, endIndex);\n elements.push({ tag: \"text\", text: boldText, style: [\"bold\"] });\n i = endIndex + 2;\n continue;\n }\n }\n\n // 处理行内代码 `code`\n if (line[i] === \"`\" && line[i + 1] !== \"`\") {\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n currentText = \"\";\n }\n const endIndex = line.indexOf(\"`\", i + 1);\n if (endIndex !== -1) {\n const codeText = line.substring(i + 1, endIndex);\n elements.push({\n tag: \"text\",\n text: codeText,\n style: [\"code\"],\n });\n i = endIndex + 1;\n continue;\n }\n }\n\n // 处理链接 [text](url)\n if (line[i] === \"[\") {\n const textEnd = line.indexOf(\"](\", i);\n const urlEnd = line.indexOf(\")\", textEnd + 2);\n if (textEnd !== -1 && urlEnd !== -1) {\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n currentText = \"\";\n }\n const linkText = line.substring(i + 1, textEnd);\n const url = line.substring(textEnd + 2, urlEnd);\n elements.push({\n tag: \"a\",\n text: linkText,\n href: url,\n });\n i = urlEnd + 1;\n continue;\n }\n }\n\n currentText += line[i];\n i++;\n }\n\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n }\n\n return elements.length > 0 ? elements : [{ tag: \"text\", text: line }];\n}\n","// 飞书机器人配置(内置,不需要外部配置)\nexport const appId = \"cli_a93822da7238dbb5\";\nexport const appSecret = \"ZQdcpLUHFb4gFa8cGfrlJfVfSSyGtyzF\";\nexport const receiveId = \"oc_482b6a04f95f4206c4fa9bc61829fd17\";\nexport const receiveIdType = \"chat_id\";\nexport const messageType = \"post\"; // text | post | interactive\nexport const messageTitle = \"🔍 Code Review 结果\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uBAAwB;;;ACAxB,IAAAA,6BAAsB;AACtB,IAAAC,kBAA4C;AAC5C,IAAAC,oBAAqB;;;ACFrB,gCAA4B;AAC5B,uBAA0B;AAC1B,qBAA2C;AAC3C,qBAAwB;AACxB,uBAAqB;AAGrB,IAAM,gBAAY,4BAAU,8BAAI;AAShC,IAAM,gBAAY,2BAAK,wBAAQ,GAAG,UAAU,KAAK;AAGjD,IAAM,qBAAqB;AAAA,MACzB,uBAAK,WAAW,OAAO;AAAA;AAAA,MACvB,2BAAK,wBAAQ,GAAG,WAAW,OAAO;AAAA,EAClC;AAAA,EACA;AACF;AAMA,SAAS,kBAAkB,QAAuB;AAEhD,MAAI,CAAC,QAAQ,IAAI,MAAM,SAAS,SAAS,GAAG;AAC1C,YAAQ,IAAI,OAAO,GAAG,SAAS,IAAI,QAAQ,IAAI,IAAI;AAAA,EACrD;AAEA,QAAM,aAAa;AAAA;AACnB,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,QAAM,UAAoB,CAAC;AAE3B,MAAI,MAAM,SAAS,KAAK,GAAG;AACzB,YAAQ,SAAK,2BAAK,wBAAQ,GAAG,QAAQ,CAAC;AAAA,EACxC,WAAW,MAAM,SAAS,MAAM,GAAG;AACjC,YAAQ,SAAK,2BAAK,wBAAQ,GAAG,SAAS,CAAC;AAAA,EACzC,OAAO;AAEL,YAAQ,SAAK,2BAAK,wBAAQ,GAAG,QAAQ,OAAG,2BAAK,wBAAQ,GAAG,SAAS,CAAC;AAAA,EACpE;AAEA,aAAW,MAAM,SAAS;AACxB,QAAI;AACF,YAAM,cAAU,2BAAW,EAAE,IACzB,QAAQ,IAAS,EAAE,aAAa,IAAI,OAAO,IAC3C;AACJ,UAAI,CAAC,QAAQ,SAAS,YAAY,GAAG;AACnC,2CAAe,IAAI,UAAU;AAC7B,YAAI,CAAC,QAAQ;AACX,kBAAQ,IAAI,wDAAoC,EAAE,EAAE;AAAA,QACtD;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,eAAe,oBACb,eACwB;AAExB,MAAI,qBAAiB,2BAAW,aAAa,GAAG;AAC9C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,UAAU,aAAa;AAChD,UAAM,gBAAgB,OAAO,KAAK;AAClC,QAAI,qBAAiB,2BAAW,aAAa,GAAG;AAC9C,aAAO;AAAA,IACT;AAAA,EACF,SAAS,OAAO;AAAA,EAEhB;AAGA,aAAW,QAAQ,oBAAoB;AACrC,YAAI,2BAAW,IAAI,GAAG;AACpB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,kBACpB,SACmC;AACnC,QAAM,EAAE,SAAS,OAAO,WAAW,cAAc,IAAI;AAErD,MAAI,kBAAkB,MAAM,oBAAoB,aAAa;AAE7D,MAAI,iBAAiB;AACnB,QAAI,CAAC,QAAQ;AACX,cAAQ;AAAA,QACN,iDAAuC,eAAe;AAAA,MACxD;AAAA,IACF;AACA,WAAO,EAAE,aAAa,MAAM,SAAS,iCAAkB,gBAAgB;AAAA,EACzE;AAEA,MAAI,CAAC,QAAQ;AACX,YAAQ,IAAI,kGAAgD;AAC5D,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,qBAAiB;AAAA,MACrB;AAAA,MACA,CAAC,MAAM,6CAA6C;AAAA,MACpD;AAAA,QACE,OAAO;AAAA;AAAA,MACT;AAAA,IACF;AAEA,mBAAe,GAAG,SAAS,OAAO,SAAS;AACzC,UAAI,SAAS,GAAG;AAEd,0BAAkB,MAAM;AAGxB,0BAAkB,MAAM,oBAAoB,aAAa;AACzD,YAAI,iBAAiB;AACnB,cAAI,CAAC,QAAQ;AACX,oBAAQ,IAAI,yDAAgC;AAAA,UAC9C;AACA,kBAAQ;AAAA,YACN,aAAa;AAAA,YACb,SAAS;AAAA,YACT;AAAA,UACF,CAAC;AAAA,QACH,OAAO;AACL;AAAA,YACE,IAAI;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,OAAO;AACL;AAAA,UACE,IAAI;AAAA,YACF,6EAAqC,IAAI;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,mBAAe,GAAG,SAAS,CAAC,QAAQ;AAClC;AAAA,QACE,IAAI;AAAA,UACF,uEAA0B,IAAI,OAAO;AAAA,QACvC;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;;;ACvKA,IAAAC,6BAAyB;AACzB,IAAAC,oBAA0B;AAE1B,IAAM,oBAAgB,6BAAU,mCAAQ;AAGxC,IAAM,wBAAwB,CAAC,cAAc;AAE7C,eAAe,OAAO,MAAgB,KAA8B;AAClE,QAAM,EAAE,OAAO,IAAI,MAAM,cAAc,OAAO,MAAM,EAAE,IAAI,CAAC;AAC3D,SAAO,OAAO,KAAK;AACrB;AAEA,eAAsB,WACpB,qBAA+B,CAAC,GAChC,MAAc,QAAQ,IAAI,GACT;AAEjB,MAAI;AACF,UAAM,OAAO,CAAC,aAAa,WAAW,GAAG,GAAG;AAAA,EAC9C,SAAS,OAAO;AACd,YAAQ,MAAM,wEAAgC,GAAG,EAAE;AACnD,WAAO;AAAA,EACT;AAGA,QAAM,oBAAoB;AAAA,IACxB,GAAG,oBAAI,IAAI,CAAC,GAAG,uBAAuB,GAAG,kBAAkB,CAAC;AAAA,EAC9D;AAIA,QAAM,cAAc,kBAAkB,IAAI,CAAC,MAAM,aAAa,CAAC,EAAE;AAEjE,MAAI,OAAO;AAEX,MAAI;AAEF,WAAO,MAAM;AAAA,MACX,CAAC,QAAQ,cAAc,cAAc,YAAY,MAAM,GAAG,WAAW;AAAA,MACrE;AAAA,IACF;AACA,QAAI,MAAM;AACR,cAAQ,IAAI,gEAAwB;AAAA,IACtC;AAAA,EACF,SAAS,OAAY;AACnB,YAAQ,KAAK,mEAAgC,MAAM,OAAO,EAAE;AAAA,EAC9D;AAEA,MAAI,CAAC,MAAM;AACT,QAAI;AAEF,aAAO,MAAM;AAAA,QACX,CAAC,QAAQ,cAAc,cAAc,QAAQ,MAAM,GAAG,WAAW;AAAA,QACjE;AAAA,MACF;AACA,UAAI,MAAM;AACR,gBAAQ,IAAI,mGAAkC;AAAA,MAChD;AAAA,IACF,SAAS,OAAY;AACnB,cAAQ,KAAK,mEAAgC,MAAM,OAAO,EAAE;AAAA,IAC9D;AAAA,EACF;AAEA,MAAI,CAAC,MAAM;AACT,YAAQ,IAAI,wIAAoC;AAAA,EAClD;AAEA,SAAO;AACT;;;ACpEO,SAAS,iBAAiB,MAAsB;AACrD,SAAO;AAAA;AAAA,EAEP,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAON;;;ACXA,WAAsB;;;ACCf,IAAM,QAAQ;AACd,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,eAAe;;;ADQ5B,eAAsB,mBAAmB,eAAsC;AAC7E,UAAQ,IAAI,yEAA4B;AACxC,UAAQ,IAAI,2CAAuB,WAAW,EAAE;AAEhD,MAAI;AAEF,UAAM,SAAS,IAAS,YAAO;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,QAAa,YAAO;AAAA,IACtB,CAAC;AAGD,QAAI;AACJ,QAAI;AAEJ,QAAI,gBAAgB,eAAe;AAEjC,mBAAa,KAAK,UAAU;AAAA,QAC1B,QAAQ;AAAA,UACN,kBAAkB;AAAA,QACpB;AAAA,QACA,QAAQ;AAAA,UACN,OAAO;AAAA,YACL,KAAK;AAAA,YACL,SAAS;AAAA,UACX;AAAA,UACA,UAAU;AAAA,QACZ;AAAA,QACA,UAAU;AAAA,UACR;AAAA,YACE,KAAK;AAAA,YACL,MAAM;AAAA,cACJ,KAAK;AAAA,cACL,SAAS;AAAA,YACX;AAAA,UACF;AAAA,UACA;AAAA,YACE,KAAK;AAAA,UACP;AAAA,UACA;AAAA,YACE,KAAK;AAAA,YACL,UAAU;AAAA,cACR;AAAA,gBACE,KAAK;AAAA,gBACL,SAAS,8BAAS,oBAAI,KAAK,GAAE,eAAe,SAAS;AAAA,kBACnD,UAAU;AAAA,gBACZ,CAAC,CAAC;AAAA,cACJ;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AACD,gBAAU;AAAA,IACZ,WAAW,gBAAgB,QAAQ;AAEjC,mBAAa,KAAK,UAAU;AAAA,QAC1B,OAAO;AAAA,UACL,OAAO;AAAA,UACP,SAAS,4BAA4B,aAAa;AAAA,QACpD;AAAA,MACF,CAAC;AACD,gBAAU;AAAA,IACZ,WAAW,gBAAgB,QAAQ;AAEjC,mBAAa,KAAK,UAAU;AAAA,QAC1B,MAAM,GAAG,YAAY;AAAA;AAAA,EAAO,aAAa;AAAA,MAC3C,CAAC;AACD,gBAAU;AAAA,IACZ,OAAO;AACL,YAAM,IAAI,MAAM,mEAA2B,WAAW,EAAE;AAAA,IAC1D;AAGA,YAAQ,IAAI,yCAAqB;AACjC,YAAQ,IAAI,wBAAwB,aAAa,EAAE;AACnD,YAAQ,IAAI,mBAAmB,SAAS,EAAE;AAC1C,YAAQ,IAAI,iBAAiB,OAAO,EAAE;AAGtC,UAAM,WAAW,MAAM,OAAO,GAAG,QAAQ,OAAO;AAAA,MAC9C,QAAQ;AAAA,QACN,iBAAiB;AAAA,MACnB;AAAA,MACA,MAAM;AAAA,QACJ,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAED,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,4DAA8B,SAAS,OAAO,0BAAM;AAAA,MACtD;AAAA,IACF;AAEA,YAAQ,IAAI,uEAA0B;AACtC,YAAQ;AAAA,MACN,kCAAwB,SAAS,MAAM,cAAc,cAAI;AAAA,IAC3D;AAAA,EACF,SAAS,OAAY;AACnB,YAAQ,MAAM,0EAA6B,MAAM,OAAO,EAAE;AAC1D,UAAM;AAAA,EACR;AACF;AAMA,SAAS,4BAA4B,UAA2B;AAC9D,QAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,QAAM,SAAkB,CAAC;AAEzB,MAAI,cAAc;AAClB,MAAI,mBAA6B,CAAC;AAElC,aAAW,QAAQ,OAAO;AAExB,QAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,UAAI,aAAa;AAEf,YAAI,iBAAiB,SAAS,GAAG;AAC/B,iBAAO,KAAK;AAAA,YACV;AAAA,cACE,KAAK;AAAA,cACL,MAAM,iBAAiB,KAAK,IAAI;AAAA,cAChC,OAAO,CAAC,MAAM;AAAA,YAChB;AAAA,UACF,CAAC;AACD,6BAAmB,CAAC;AAAA,QACtB;AACA,sBAAc;AAAA,MAChB,OAAO;AAEL,sBAAc;AAAA,MAChB;AACA;AAAA,IACF;AAEA,QAAI,aAAa;AACf,uBAAiB,KAAK,IAAI;AAC1B;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB,aAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,MAAM,GAAG,CAAC,CAAC;AACvC;AAAA,IACF;AAGA,QAAI,KAAK,WAAW,GAAG,GAAG;AACxB,YAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE,UAAU;AAC/C,YAAM,OAAO,KAAK,QAAQ,UAAU,EAAE;AACtC,aAAO,KAAK;AAAA,QACV;AAAA,UACE,KAAK;AAAA,UACL;AAAA,UACA,OAAO,SAAS,IAAI,CAAC,QAAQ,WAAW,IAAI,CAAC,MAAM;AAAA,QACrD;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAGA,UAAM,aAAa,gBAAgB,IAAI;AACvC,WAAO,KAAK,UAAU;AAAA,EACxB;AAGA,MAAI,iBAAiB,SAAS,GAAG;AAC/B,WAAO,KAAK;AAAA,MACV;AAAA,QACE,KAAK;AAAA,QACL,MAAM,iBAAiB,KAAK,IAAI;AAAA,QAChC,OAAO,CAAC,MAAM;AAAA,MAChB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAKA,SAAS,gBAAgB,MAAqB;AAC5C,QAAM,WAAkB,CAAC;AACzB,MAAI,cAAc;AAClB,MAAI,IAAI;AAER,SAAO,IAAI,KAAK,QAAQ;AAEtB,QAAI,KAAK,CAAC,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,KAAK;AAC1C,UAAI,aAAa;AACf,iBAAS,KAAK,EAAE,KAAK,QAAQ,MAAM,YAAY,CAAC;AAChD,sBAAc;AAAA,MAChB;AACA,YAAM,WAAW,KAAK,QAAQ,MAAM,IAAI,CAAC;AACzC,UAAI,aAAa,IAAI;AACnB,cAAM,WAAW,KAAK,UAAU,IAAI,GAAG,QAAQ;AAC/C,iBAAS,KAAK,EAAE,KAAK,QAAQ,MAAM,UAAU,OAAO,CAAC,MAAM,EAAE,CAAC;AAC9D,YAAI,WAAW;AACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,KAAK,CAAC,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,KAAK;AAC1C,UAAI,aAAa;AACf,iBAAS,KAAK,EAAE,KAAK,QAAQ,MAAM,YAAY,CAAC;AAChD,sBAAc;AAAA,MAChB;AACA,YAAM,WAAW,KAAK,QAAQ,KAAK,IAAI,CAAC;AACxC,UAAI,aAAa,IAAI;AACnB,cAAM,WAAW,KAAK,UAAU,IAAI,GAAG,QAAQ;AAC/C,iBAAS,KAAK;AAAA,UACZ,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO,CAAC,MAAM;AAAA,QAChB,CAAC;AACD,YAAI,WAAW;AACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,KAAK,CAAC,MAAM,KAAK;AACnB,YAAM,UAAU,KAAK,QAAQ,MAAM,CAAC;AACpC,YAAM,SAAS,KAAK,QAAQ,KAAK,UAAU,CAAC;AAC5C,UAAI,YAAY,MAAM,WAAW,IAAI;AACnC,YAAI,aAAa;AACf,mBAAS,KAAK,EAAE,KAAK,QAAQ,MAAM,YAAY,CAAC;AAChD,wBAAc;AAAA,QAChB;AACA,cAAM,WAAW,KAAK,UAAU,IAAI,GAAG,OAAO;AAC9C,cAAM,MAAM,KAAK,UAAU,UAAU,GAAG,MAAM;AAC9C,iBAAS,KAAK;AAAA,UACZ,KAAK;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,QACR,CAAC;AACD,YAAI,SAAS;AACb;AAAA,MACF;AAAA,IACF;AAEA,mBAAe,KAAK,CAAC;AACrB;AAAA,EACF;AAEA,MAAI,aAAa;AACf,aAAS,KAAK,EAAE,KAAK,QAAQ,MAAM,YAAY,CAAC;AAAA,EAClD;AAEA,SAAO,SAAS,SAAS,IAAI,WAAW,CAAC,EAAE,KAAK,QAAQ,MAAM,KAAK,CAAC;AACtE;;;AJnQO,IAAM,eAAN,MAAM,cAAa;AAAA,EACxB,OAAO,YAAiC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEQ,YACN,QACA,WACA,gBACA,eAAwB,MACxB;AACA,SAAK,UAAU;AACf,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,KAAK,UAA+B,CAAC,GAAiB;AAC3D,QAAI,cAAa,cAAc,MAAM;AACnC,oBAAa,YAAY,IAAI;AAAA,QAC3B,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ,gBAAgB;AAAA,MAC1B;AAAA,IACF,OAAO;AACL,oBAAa,UAAU,UAAU,QAAQ;AACzC,oBAAa,UAAU,aAAa,QAAQ;AAC5C,oBAAa,UAAU,kBAAkB,QAAQ;AACjD,oBAAa,UAAU,gBAAgB,QAAQ,gBAAgB;AAAA,IACjE;AACA,kBAAa,UAAU,oBAAoB;AAC3C,WAAO,cAAa;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,YACL,WACA,cACA,mBACA,iBACc;AACd,QAAI,cAAa,cAAc,MAAM;AACnC,YAAM,SAAS,aAAa,QAAQ,IAAI;AACxC,YAAM,YAAY,gBAAgB,QAAQ,IAAI;AAC9C,YAAM,iBACJ,sBACC,QAAQ,IAAI,wBACT,QAAQ,IAAI,sBAAsB,MAAM,GAAG,IAC3C;AACN,YAAM,eACJ,mBAAmB,QAAQ,IAAI,mBAAmB;AAEpD,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,oBAAa,YAAY,IAAI;AAAA,QAC3B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AAEL,UAAI,aAAa,CAAC,cAAa,UAAU,SAAS;AAChD,sBAAa,UAAU,UAAU;AAAA,MACnC;AACA,UAAI,gBAAgB,CAAC,cAAa,UAAU,YAAY;AACtD,sBAAa,UAAU,aAAa;AAAA,MACtC;AACA,UAAI,qBAAqB,CAAC,cAAa,UAAU,iBAAiB;AAChE,sBAAa,UAAU,kBAAkB;AAAA,MAC3C;AACA,UAAI,oBAAoB,QAAW;AACjC,sBAAa,UAAU,gBAAgB;AAAA,MACzC;AAAA,IACF;AACA,WAAO,cAAa;AAAA,EACtB;AAAA,EAEA,aAAqB;AACnB,UAAM,SAAS,KAAK,WAAW,QAAQ,IAAI;AAC3C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,gBAAoC;AAClC,WAAO,KAAK,cAAc,QAAQ,IAAI;AAAA,EACxC;AAAA,EAEA,qBAA+B;AAC7B,UAAM,YAAY,QAAQ,IAAI,wBAC1B,QAAQ,IAAI,sBAAsB,MAAM,GAAG,IAC3C,CAAC;AACL,WAAO,KAAK,mBAAmB;AAAA,EACjC;AAAA,EAEA,mBAA4B;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,sBAAsB;AAC1B,QAAI;AACF,YAAM,sBAAkB,wBAAK,QAAQ,IAAI,GAAG,cAAc;AAC1D,YAAM,cAAc,KAAK,UAAM,8BAAa,iBAAiB,OAAO,CAAC;AAErE,UAAI,CAAC,YAAY,SAAS;AACxB,oBAAY,UAAU,CAAC;AAAA,MACzB;AAEA,UAAI,CAAC,YAAY,QAAQ,aAAa,GAAG;AACvC,oBAAY,QAAQ,aAAa,IAAI;AACrC,2CAAc,iBAAiB,KAAK,UAAU,aAAa,MAAM,CAAC,CAAC;AACnE,gBAAQ;AAAA,UACN;AAAA,QACF;AAAA,MACF,OAAO;AACL,gBAAQ,IAAI,gGAAyC;AAAA,MACvD;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,yDAAqC,KAAK;AAAA,IAC1D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAyB;AAC7B,UAAM,SAAS,KAAK,WAAW;AAC/B,UAAM,YAAY,KAAK,cAAc;AACrC,UAAM,iBAAiB,KAAK,mBAAmB;AAE/C,UAAM,eAAe,MAAM,KAAK,qBAAqB,OAAO,SAAS;AAErE,QAAI,CAAC,aAAa,aAAa;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAI,mCAAmC;AAC/C,UAAM,OAAO,MAAM,WAAW,gBAAgB,QAAQ,IAAI,CAAC;AAE3D,QAAI,CAAC,MAAM;AACT,cAAQ,IAAI,wCAAwC;AACpD,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,iBAAiB,IAAI;AACpC,YAAQ,IAAI,gCAAgC;AAC5C,UAAM,WAAW,MAAM,KAAK,KAAK,QAAQ;AAAA,MACvC,WAAW,aAAa;AAAA,MACxB,OAAO;AAAA,IACT,CAAC;AACD,YAAQ,IAAI,uBAAuB;AACnC,YAAQ,IAAI,QAAQ;AAGpB,QAAI,KAAK,iBAAiB,GAAG;AAC3B,UAAI;AACF,cAAM,mBAAmB,QAAQ;AAAA,MACnC,SAAS,OAAY;AACnB,gBAAQ;AAAA,UACN,qHAA0C,MAAM,OAAO;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,qBACJ,SAAS,OACT,WACmC;AACnC,WAAO,kBAAkB,EAAE,QAAQ,KAAK,WAAW,GAAG,QAAQ,UAAU,CAAC;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KACJ,QACA,UAAmC,CAAC,GACnB;AACjB,UAAM,kBACJ,QAAQ,aAAa,KAAK,cAAc,KAAK;AAC/C,UAAM,OAAiB,CAAC,UAAU,MAAM,MAAM;AAC9C,QAAI,QAAQ,iBAAiB,OAAQ,MAAK,KAAK,mBAAmB,MAAM;AAExE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,MAAM,EAAE,GAAG,QAAQ,KAAK,gBAAgB,KAAK,WAAW,EAAE;AAChE,YAAM,WAAO,kCAAM,iBAAiB,MAAM;AAAA,QACxC;AAAA,QACA,KAAK,QAAQ,IAAI;AAAA,QACjB,OAAO;AAAA,QACP,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,MAClC,CAAC;AAED,UAAI,SAAS;AACb,UAAI,SAAS;AAEb,WAAK,QAAQ,GAAG,QAAQ,CAAC,UAAU;AACjC,kBAAU,MAAM,SAAS;AAAA,MAC3B,CAAC;AACD,WAAK,QAAQ,GAAG,QAAQ,CAAC,UAAU;AACjC,kBAAU,MAAM,SAAS;AAAA,MAC3B,CAAC;AAED,WAAK,GAAG,SAAS,CAAC,QAAQ;AACxB;AAAA,UACE,IAAI;AAAA,YACF,sDAAkC,eAAe,mHAA4D,IAAI,OAAO;AAAA,UAC1H;AAAA,QACF;AAAA,MACF,CAAC;AAED,WAAK,GAAG,SAAS,CAAC,SAAS;AACzB,YAAI,SAAS,GAAG;AACd;AAAA,YACE,IAAI;AAAA,cACF,0CAA2B,IAAI,GAC7B,SAAS,KAAK,OAAO,KAAK,CAAC,KAAK,EAClC;AAAA,YACF;AAAA,UACF;AACA;AAAA,QACF;AACA,gBAAQ,OAAO,KAAK,CAAC;AAAA,MACvB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;;;AD/QA,IAAM,UAAU,IAAI,yBAAQ;AAE5B,QACG,KAAK,aAAa,EAClB,YAAY,6BAA6B,EACzC,QAAQ,OAAO,EACf;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,eAAe,6BAA6B,EACnD,OAAO,OAAO,YAAY;AACzB,MAAI;AACF,UAAM,iBAAiB,QAAQ,SAC3B,QAAQ,OAAO,MAAM,GAAG,IACxB;AAEJ,UAAM,WAAW,aAAa;AAAA,MAC5B,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ,WAAW;AAAA,IACrB;AACA,UAAM,SAAS,MAAM;AAAA,EACvB,SAAS,OAAY;AACnB,YAAQ,MAAM,wBAAwB,MAAM,OAAO,EAAE;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,uCAAuC,EACnD;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,eAAe,6BAA6B,EACnD,OAAO,OAAO,YAAY;AACzB,MAAI;AACF,UAAM,iBAAiB,QAAQ,SAC3B,QAAQ,OAAO,MAAM,GAAG,IACxB;AAEJ,UAAM,WAAW,aAAa;AAAA,MAC5B,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ,WAAW;AAAA,IACrB;AACA,UAAM,SAAS,MAAM;AAAA,EACvB,SAAS,OAAY;AACnB,YAAQ,MAAM,wBAAwB,MAAM,OAAO,EAAE;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM,QAAQ,IAAI;","names":["import_node_child_process","import_node_fs","import_node_path","import_node_child_process","import_node_util"]}
1
+ {"version":3,"sources":["../../src/cli/review.ts","../../src/core/BeLinkReview.ts","../../src/utils/checkCli.ts","../../src/core/git.ts","../../src/core/prompt.ts","../../src/core/feishu.ts","../../src/constants.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { BeLinkReview } from \"../core/BeLinkReview\";\n\nconst program = new Command();\n\nconst sharedOptions = (cmd: Command) =>\n cmd\n .option(\n \"--apiKey <key>\",\n \"Cursor API Key (overrides CURSOR_API_KEY environment variable)\"\n )\n .option(\n \"--agentPath <path>\",\n \"Path to the Cursor CLI agent executable (overrides CURSOR_AGENT_PATH environment variable)\"\n )\n .option(\n \"--model <model>\",\n \"AI model to use, e.g. claude-3-5-sonnet (overrides CURSOR_MODEL environment variable)\"\n )\n .option(\n \"--ignore <patterns>\",\n \"Comma-separated list of glob patterns to ignore (e.g., *.lock,dist/**)\"\n )\n .option(\"--no-feishu\", \"Disable Feishu notification\");\n\nconst runAction = async (options: any) => {\n try {\n const ignorePatterns = options.ignore\n ? options.ignore.split(\",\")\n : undefined;\n\n const instance = BeLinkReview.getInstance(\n options.apiKey,\n options.agentPath,\n ignorePatterns,\n options.feishu !== false,\n options.model\n );\n await instance.goCli();\n } catch (error: any) {\n console.error(`[review-mark] Error: ${error.message}`);\n process.exit(1);\n }\n};\n\nsharedOptions(\n program\n .name(\"review-mark\")\n .description(\"AI-powered code review tool\")\n .version(\"1.0.0\")\n).action(runAction);\n\nsharedOptions(\n program.command(\"review\").description(\"Perform an AI code review on git diff\")\n).action(runAction);\n\nprogram.parse(process.argv);\n","import { spawn } from \"node:child_process\";\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type {\n BeLinkReviewOptions,\n BeLinkReviewChatOptions,\n BeLinkReviewEnsureResult,\n} from \"../types\";\nimport { isCheckCliInstall } from \"../utils/checkCli\";\nimport { getGitDiff } from \"./git\";\nimport { generateAIPrompt } from \"./prompt\";\nimport { sendReviewToFeishu } from \"./feishu\";\nimport { DEFAULT_MODEL } from \"../constants\";\n\nexport class BeLinkReview {\n static #instance: BeLinkReview | null = null;\n #apiKey: string | undefined;\n #agentPath: string | undefined;\n #ignorePatterns: string[] | undefined;\n #enableFeishu: boolean;\n #model: string | undefined;\n\n private constructor(\n apiKey?: string,\n agentPath?: string,\n ignorePatterns?: string[],\n enableFeishu: boolean = true,\n model?: string\n ) {\n this.#apiKey = apiKey;\n this.#agentPath = agentPath;\n this.#ignorePatterns = ignorePatterns;\n this.#enableFeishu = enableFeishu;\n this.#model = model;\n }\n\n /**\n * 初始化单例并注入参数,在项目入口调用一次即可。\n * 负责保存 apiKey 和自动写入 package.json 脚本。\n * 此方法主要用于在用户项目中设置 apiKey 和脚本,CLI 运行时会优先从环境变量或命令行参数获取。\n */\n static init(options: BeLinkReviewOptions = {}): BeLinkReview {\n if (BeLinkReview.#instance === null) {\n BeLinkReview.#instance = new BeLinkReview(\n options.apiKey,\n options.agentPath,\n options.ignore,\n options.enableFeishu ?? true,\n options.model\n );\n } else {\n BeLinkReview.#instance.#apiKey = options.apiKey;\n BeLinkReview.#instance.#agentPath = options.agentPath;\n BeLinkReview.#instance.#ignorePatterns = options.ignore;\n BeLinkReview.#instance.#enableFeishu = options.enableFeishu ?? true;\n BeLinkReview.#instance.#model = options.model;\n }\n BeLinkReview.#instance.#setupProjectScript();\n return BeLinkReview.#instance;\n }\n\n /**\n * 获取单例实例。如果未通过 init() 初始化,则尝试从环境变量 CURSOR_API_KEY 获取。\n * @param cliApiKey 可选的命令行传入的 apiKey\n * @param cliAgentPath 可选的命令行传入的 agentPath\n * @param cliIgnorePatterns 可选的命令行传入的 ignorePatterns\n * @param cliEnableFeishu 可选的命令行传入的飞书开关\n */\n static getInstance(\n cliApiKey?: string,\n cliAgentPath?: string,\n cliIgnorePatterns?: string[],\n cliEnableFeishu?: boolean,\n cliModel?: string\n ): BeLinkReview {\n if (BeLinkReview.#instance === null) {\n const apiKey = cliApiKey || process.env.CURSOR_API_KEY;\n const agentPath = cliAgentPath || process.env.CURSOR_AGENT_PATH;\n const ignorePatterns =\n cliIgnorePatterns ||\n (process.env.BE_LINK_REVIEW_IGNORE\n ? process.env.BE_LINK_REVIEW_IGNORE.split(\",\")\n : undefined);\n const enableFeishu =\n cliEnableFeishu ?? process.env.FEISHU_ENABLED !== \"false\";\n const model = cliModel || process.env.CURSOR_MODEL;\n\n if (!apiKey) {\n throw new Error(\n '[review-mark] 请先调用 BeLinkReview.init({ apiKey: \"...\" }) 初始化,或设置环境变量 CURSOR_API_KEY,或通过命令行参数 --apiKey 传入。'\n );\n }\n BeLinkReview.#instance = new BeLinkReview(\n apiKey,\n agentPath,\n ignorePatterns,\n enableFeishu,\n model\n );\n } else {\n if (cliApiKey && !BeLinkReview.#instance.#apiKey) {\n BeLinkReview.#instance.#apiKey = cliApiKey;\n }\n if (cliAgentPath && !BeLinkReview.#instance.#agentPath) {\n BeLinkReview.#instance.#agentPath = cliAgentPath;\n }\n if (cliIgnorePatterns && !BeLinkReview.#instance.#ignorePatterns) {\n BeLinkReview.#instance.#ignorePatterns = cliIgnorePatterns;\n }\n if (cliEnableFeishu !== undefined) {\n BeLinkReview.#instance.#enableFeishu = cliEnableFeishu;\n }\n if (cliModel) {\n BeLinkReview.#instance.#model = cliModel;\n }\n }\n return BeLinkReview.#instance;\n }\n\n #getApiKey(): string {\n const apiKey = this.#apiKey || process.env.CURSOR_API_KEY;\n if (!apiKey) {\n throw new Error(\n '[review-mark] 请先在 init({ apiKey: \"...\" }) 中传入 apiKey,或设置环境变量 CURSOR_API_KEY,或通过命令行参数 --apiKey 传入。'\n );\n }\n return apiKey;\n }\n\n #getAgentPath(): string | undefined {\n return this.#agentPath || process.env.CURSOR_AGENT_PATH;\n }\n\n #getIgnorePatterns(): string[] {\n const envIgnore = process.env.BE_LINK_REVIEW_IGNORE\n ? process.env.BE_LINK_REVIEW_IGNORE.split(\",\")\n : [];\n return this.#ignorePatterns || envIgnore;\n }\n\n #isFeishuEnabled(): boolean {\n return this.#enableFeishu;\n }\n\n #getModel(): string {\n return this.#model || process.env.CURSOR_MODEL || DEFAULT_MODEL;\n }\n\n /**\n * 自动往用户项目 package.json 写入 script\n */\n async #setupProjectScript() {\n try {\n const packageJsonPath = join(process.cwd(), \"package.json\");\n const packageJson = JSON.parse(readFileSync(packageJsonPath, \"utf-8\"));\n\n if (!packageJson.scripts) {\n packageJson.scripts = {};\n }\n\n if (!packageJson.scripts[\"review-mark\"]) {\n packageJson.scripts[\"review-mark\"] = \"review-mark\";\n writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));\n console.log(\n \"[review-mark] 已在 package.json 中添加 'review-mark' 脚本。\"\n );\n } else {\n console.log(\"[review-mark] 'review-mark' 脚本已存在,跳过添加。\");\n }\n } catch (error) {\n console.error(\"[review-mark] 无法更新 package.json: \", error);\n }\n }\n\n /**\n * 执行一次完整 CLI 流程:获取 git diff → 生成 prompt → 检查/安装 CLI → 与 Cursor 对话并打印结果。\n */\n async goCli(): Promise<string> {\n const apiKey = this.#getApiKey();\n const agentPath = this.#getAgentPath();\n const ignorePatterns = this.#getIgnorePatterns();\n\n const ensureResult = await this.ensureAgentInstalled(false, agentPath);\n\n if (!ensureResult.isInstalled) {\n throw new Error(\n \"[review-mark] Cursor CLI 未安装且自动安装失败,请手动安装。\"\n );\n }\n\n console.log(\"[review-mark] Getting git diff...\");\n const diff = await getGitDiff(ignorePatterns, process.cwd());\n\n if (!diff) {\n console.log(\"[review-mark] No code changes detected\");\n return \"No code changes detected\";\n }\n\n const prompt = generateAIPrompt(diff);\n console.log(\"[review-mark] Sending to AI...\");\n const response = await this.chat(prompt, {\n agentPath: ensureResult.actualAgentPath,\n force: true,\n model: this.#getModel(),\n });\n console.log(this.#getModel(), \"model\");\n console.log(\"===== AI Review =====\");\n console.log(response);\n\n // 如果启用了飞书,发送到飞书\n if (this.#isFeishuEnabled()) {\n try {\n await sendReviewToFeishu(response);\n } catch (error: any) {\n console.error(\n `[review-mark] 飞书通知发送失败,但不影响 review 结果: ${error.message}`\n );\n }\n }\n\n return response;\n }\n\n /**\n * 检查 Cursor CLI 是否已安装;未安装则自动安装(需网络)。\n * @param silent 为 true 时不打印「已安装」等提示\n * @param agentPath 可选的 agent 可执行文件路径\n */\n async ensureAgentInstalled(\n silent = false,\n agentPath?: string\n ): Promise<BeLinkReviewEnsureResult> {\n return isCheckCliInstall({ apiKey: this.#getApiKey(), silent, agentPath });\n }\n\n /**\n * 通过 Cursor Headless CLI 与 Cursor 对话(发送 prompt,拿到回复)。\n * 默认会先执行 ensureAgentInstalled(检查/安装 CLI),再启动 agent 执行提问。\n * 参考:https://cursor.com/cn/docs/cli/headless\n */\n async chat(\n prompt: string,\n options: BeLinkReviewChatOptions = {}\n ): Promise<string> {\n const actualAgentPath =\n options.agentPath || this.#getAgentPath() || \"agent\";\n const model = options.model || this.#getModel();\n const args: string[] = [\"--yolo\", \"-p\", prompt];\n if (model) args.unshift(\"--model\", model);\n if (options.outputFormat === \"json\") args.push(\"--output-format\", \"json\");\n\n return new Promise((resolve, reject) => {\n const env = { ...process.env, CURSOR_API_KEY: this.#getApiKey() };\n const proc = spawn(actualAgentPath, args, {\n env,\n cwd: process.cwd(),\n shell: false,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n proc.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n\n proc.on(\"error\", (err) => {\n reject(\n new Error(\n `[review-mark] 无法启动 Cursor CLI (${actualAgentPath}),请先安装:curl https://cursor.com/install -fsS | bash。原始错误: ${err.message}`\n )\n );\n });\n\n proc.on(\"close\", (code) => {\n if (code !== 0) {\n reject(\n new Error(\n `[review-mark] agent 退出码 ${code}${\n stderr ? `: ${stderr.trim()}` : \"\"\n }`\n )\n );\n return;\n }\n resolve(stdout.trim());\n });\n });\n }\n}\n","import { spawn, exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { existsSync, appendFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { BeLinkReviewEnsureResult } from \"@/types\";\n\nconst execAsync = promisify(exec);\n\ninterface CheckCliInstallOptions {\n apiKey: string;\n silent?: boolean;\n agentPath?: string;\n}\n\n// Cursor 官方安装脚本默认将 agent 放在 ~/.local/bin\nconst LOCAL_BIN = join(homedir(), \".local\", \"bin\");\n\n// 常见的 Cursor CLI 安装路径\nconst COMMON_AGENT_PATHS = [\n join(LOCAL_BIN, \"agent\"), // Cursor 官方安装脚本默认路径\n join(homedir(), \".cursor\", \"agent\"),\n \"/usr/local/bin/agent\",\n \"/opt/homebrew/bin/agent\",\n];\n\n/**\n * 将 ~/.local/bin 写入用户 shell 配置文件(.zshrc / .bashrc),\n * 并立即注入当前进程的 PATH,确保安装后无需重启终端即可使用。\n */\nfunction setupLocalBinPath(silent: boolean): void {\n // 立即让当前进程能找到 agent\n if (!process.env.PATH?.includes(LOCAL_BIN)) {\n process.env.PATH = `${LOCAL_BIN}:${process.env.PATH}`;\n }\n\n const exportLine = `\\nexport PATH=\"$HOME/.local/bin:$PATH\"`;\n const shell = process.env.SHELL ?? \"\";\n const rcFiles: string[] = [];\n\n if (shell.includes(\"zsh\")) {\n rcFiles.push(join(homedir(), \".zshrc\"));\n } else if (shell.includes(\"bash\")) {\n rcFiles.push(join(homedir(), \".bashrc\"));\n } else {\n // 两个都写,保证覆盖\n rcFiles.push(join(homedir(), \".zshrc\"), join(homedir(), \".bashrc\"));\n }\n\n for (const rc of rcFiles) {\n try {\n const content = existsSync(rc)\n ? require(\"node:fs\").readFileSync(rc, \"utf-8\")\n : \"\";\n if (!content.includes(\".local/bin\")) {\n appendFileSync(rc, exportLine);\n if (!silent) {\n console.log(`[review-mark] 已将 ~/.local/bin 写入 ${rc}`);\n }\n }\n } catch {\n // 写入失败不阻断流程\n }\n }\n}\n\nasync function findAgentExecutable(\n userAgentPath?: string\n): Promise<string | null> {\n // 1. 优先使用用户指定的路径\n if (userAgentPath && existsSync(userAgentPath)) {\n return userAgentPath;\n }\n\n // 2. 尝试通过 which 命令在 PATH 中查找\n try {\n const { stdout } = await execAsync(\"which agent\");\n const pathFromWhich = stdout.trim();\n if (pathFromWhich && existsSync(pathFromWhich)) {\n return pathFromWhich;\n }\n } catch (error) {\n // ignore error, continue to next method\n }\n\n // 3. 检查常见安装路径\n for (const path of COMMON_AGENT_PATHS) {\n if (existsSync(path)) {\n return path;\n }\n }\n\n return null;\n}\n\nexport async function isCheckCliInstall(\n options: CheckCliInstallOptions\n): Promise<BeLinkReviewEnsureResult> {\n const { silent = false, agentPath: userAgentPath } = options;\n\n let actualAgentPath = await findAgentExecutable(userAgentPath);\n\n if (actualAgentPath) {\n if (!silent) {\n console.log(\n `[review-mark] Cursor CLI (agent) 已在 ${actualAgentPath} 找到。`\n );\n }\n return { isInstalled: true, message: \"Cursor CLI 已安装\", actualAgentPath };\n }\n\n if (!silent) {\n console.log(\"[review-mark] Cursor CLI (agent) 未找到,正在尝试安装...\");\n console.log(\n \"[review-mark] 执行安装命令: curl https://cursor.com/install -fsS | bash\"\n );\n }\n\n return new Promise((resolve, reject) => {\n const installProcess = spawn(\n \"bash\",\n [\"-c\", \"curl https://cursor.com/install -fsS | bash\"],\n {\n stdio: \"inherit\", // 将安装过程的输出直接显示给用户\n }\n );\n\n installProcess.on(\"close\", async (code) => {\n if (code === 0) {\n // 安装完成后自动配置 PATH(写入 rc 文件 + 注入当前进程)\n setupLocalBinPath(silent);\n\n // 再次检查 agent 命令是否可用\n actualAgentPath = await findAgentExecutable(userAgentPath);\n if (actualAgentPath) {\n if (!silent) {\n console.log(\"[review-mark] Cursor CLI 安装成功。\");\n }\n resolve({\n isInstalled: true,\n message: \"Cursor CLI 安装成功\",\n actualAgentPath,\n });\n } else {\n reject(\n new Error(\n `[review-mark] Cursor CLI 安装命令执行成功,但未找到 agent 可执行文件。请手动检查安装:curl https://cursor.com/install -fsS | bash。`\n )\n );\n }\n } else {\n reject(\n new Error(\n `[review-mark] Cursor CLI 安装失败,退出码 ${code}。请手动安装:curl https://cursor.com/install -fsS | bash。`\n )\n );\n }\n });\n\n installProcess.on(\"error\", (err) => {\n reject(\n new Error(\n `[review-mark] 无法启动安装进程:${err.message}。请手动安装:curl https://cursor.com/install -fsS | bash`\n )\n );\n });\n });\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\n// 固定的忽略文件模式,这些文件将永远不会被审查\nconst FIXED_IGNORE_PATTERNS = [\"package.json\"];\n\nasync function runGit(args: string[], cwd: string): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", args, { cwd });\n return stdout.trim();\n}\n\nexport async function getGitDiff(\n userIgnorePatterns: string[] = [],\n cwd: string = process.cwd()\n): Promise<string> {\n // 检查是否在 git 仓库中\n try {\n await runGit([\"rev-parse\", \"--git-dir\"], cwd);\n } catch (error) {\n console.error(`[review-mark] 当前目录不是 git 仓库: ${cwd}`);\n return \"\";\n }\n\n // 合并固定忽略模式和用户提供的忽略模式\n const allIgnorePatterns = [\n ...new Set([...FIXED_IGNORE_PATTERNS, ...userIgnorePatterns]),\n ];\n // 使用 execFile 直接传参,完全绕过 shell,:(exclude) 不会被解析\n // git 参数顺序:git diff [options] [commit] -- [pathspec]\n // options/commit 必须在 -- 和 pathspec 之前\n const excludeArgs = allIgnorePatterns.map((p) => `:(exclude)${p}`);\n\n let diff = \"\";\n\n try {\n // 优先使用 git diff --cached 获取暂存区的改动\n diff = await runGit(\n [\"diff\", \"--no-color\", \"--relative\", \"--cached\", \"--\", ...excludeArgs],\n cwd\n );\n if (diff) {\n console.log(\"[review-mark] 检测到暂存区改动\");\n }\n } catch (error: any) {\n console.warn(`[review-mark] 获取暂存区 diff 失败: ${error.message}`);\n }\n\n if (!diff) {\n try {\n // 如果暂存区没有改动,则获取工作区和 HEAD 的改动\n diff = await runGit(\n [\"diff\", \"--no-color\", \"--relative\", \"HEAD\", \"--\", ...excludeArgs],\n cwd\n );\n if (diff) {\n console.log(\"[review-mark] 检测到工作区改动(相对于 HEAD)\");\n }\n } catch (error: any) {\n console.warn(`[review-mark] 获取工作区 diff 失败: ${error.message}`);\n }\n }\n\n if (!diff) {\n console.log(\"[review-mark] 未检测到代码改动(已检查暂存区和工作区)\");\n }\n\n return diff;\n}\n","\nexport function generateAIPrompt(diff: string): string {\n return `你是一名资深软件工程师,请 review 以下代码变更。\nGit diff:\n${diff}\n请分析:\n是否存在 bug\n是否存在潜在逻辑问题\n是否存在性能问题\n是否存在代码风格问题\n给出优化建议`;\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport {\n appId,\n appSecret,\n receiveId,\n receiveIdType,\n messageType,\n messageTitle,\n} from \"../constants\";\n\n/**\n * 发送 Code Review 结果到飞书\n * 使用飞书官方 Node.js SDK,所有配置从 constants.ts 读取\n */\nexport async function sendReviewToFeishu(reviewContent: string): Promise<void> {\n console.log(\"[review-mark] 正在发送消息到飞书...\");\n console.log(`[review-mark] 消息类型: ${messageType}`);\n\n try {\n // 初始化飞书客户端\n const client = new lark.Client({\n appId,\n appSecret,\n domain: lark.Domain.Feishu,\n });\n\n // 根据消息类型构造消息内容\n let msgContent: string;\n let msgType: string;\n\n if (messageType === \"interactive\") {\n // Interactive 卡片消息(支持 Markdown 渲染)\n msgContent = JSON.stringify({\n config: {\n wide_screen_mode: true,\n },\n header: {\n title: {\n tag: \"plain_text\",\n content: messageTitle,\n },\n template: \"blue\",\n },\n elements: [\n {\n tag: \"div\",\n text: {\n tag: \"lark_md\",\n content: reviewContent,\n },\n },\n {\n tag: \"hr\",\n },\n {\n tag: \"note\",\n elements: [\n {\n tag: \"plain_text\",\n content: `生成时间: ${new Date().toLocaleString(\"zh-CN\", {\n timeZone: \"Asia/Shanghai\",\n })}`,\n },\n ],\n },\n ],\n });\n msgType = \"interactive\";\n } else if (messageType === \"post\") {\n // Post 富文本消息\n msgContent = JSON.stringify({\n zh_cn: {\n title: messageTitle,\n content: convertMarkdownToFeishuPost(reviewContent),\n },\n });\n msgType = \"post\";\n } else if (messageType === \"text\") {\n // Text 纯文本消息\n msgContent = JSON.stringify({\n text: `${messageTitle}\\n\\n${reviewContent}`,\n });\n msgType = \"text\";\n } else {\n throw new Error(`[review-mark] 不支持的消息类型: ${messageType}`);\n }\n\n // 调试日志\n console.log(`[review-mark] 发送参数:`);\n console.log(` - receive_id_type: ${receiveIdType}`);\n console.log(` - receive_id: ${receiveId}`);\n console.log(` - msg_type: ${msgType}`);\n\n // 调用发送消息接口\n const response = await client.im.message.create({\n params: {\n receive_id_type: receiveIdType,\n },\n data: {\n receive_id: receiveId,\n msg_type: msgType,\n content: msgContent,\n },\n });\n\n if (response.code !== 0) {\n throw new Error(\n `[review-mark] 飞书 API 返回错误: ${response.msg || \"未知错误\"}`\n );\n }\n\n console.log(\"[review-mark] ✅ 飞书消息发送成功\");\n console.log(\n `[review-mark] 消息 ID: ${response.data?.message_id || \"未知\"}`\n );\n } catch (error: any) {\n console.error(`[review-mark] ❌ 飞书消息发送失败: ${error.message}`);\n throw error;\n }\n}\n\n/**\n * 将 Markdown 格式转换为飞书 post 格式\n * 飞书 post 格式支持富文本样式\n */\nfunction convertMarkdownToFeishuPost(markdown: string): any[][] {\n const lines = markdown.split(\"\\n\");\n const result: any[][] = [];\n\n let inCodeBlock = false;\n let codeBlockContent: string[] = [];\n\n for (const line of lines) {\n // 处理代码块\n if (line.startsWith(\"```\")) {\n if (inCodeBlock) {\n // 代码块结束\n if (codeBlockContent.length > 0) {\n result.push([\n {\n tag: \"text\",\n text: codeBlockContent.join(\"\\n\"),\n style: [\"code\"],\n },\n ]);\n codeBlockContent = [];\n }\n inCodeBlock = false;\n } else {\n // 代码块开始\n inCodeBlock = true;\n }\n continue;\n }\n\n if (inCodeBlock) {\n codeBlockContent.push(line);\n continue;\n }\n\n // 空行\n if (!line.trim()) {\n result.push([{ tag: \"text\", text: \"\" }]);\n continue;\n }\n\n // 标题(加粗 + 加大)\n if (line.startsWith(\"#\")) {\n const level = line.match(/^#+/)?.[0].length || 1;\n const text = line.replace(/^#+\\s*/, \"\");\n result.push([\n {\n tag: \"text\",\n text: text,\n style: level <= 2 ? [\"bold\", \"underline\"] : [\"bold\"],\n },\n ]);\n continue;\n }\n\n // 解析行内样式(加粗、链接等)\n const parsedLine = parseLineStyles(line);\n result.push(parsedLine);\n }\n\n // 如果还在代码块中,添加剩余内容\n if (codeBlockContent.length > 0) {\n result.push([\n {\n tag: \"text\",\n text: codeBlockContent.join(\"\\n\"),\n style: [\"code\"],\n },\n ]);\n }\n\n return result;\n}\n\n/**\n * 解析行内样式(加粗、链接、代码等)\n */\nfunction parseLineStyles(line: string): any[] {\n const elements: any[] = [];\n let currentText = \"\";\n let i = 0;\n\n while (i < line.length) {\n // 处理加粗 **text**\n if (line[i] === \"*\" && line[i + 1] === \"*\") {\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n currentText = \"\";\n }\n const endIndex = line.indexOf(\"**\", i + 2);\n if (endIndex !== -1) {\n const boldText = line.substring(i + 2, endIndex);\n elements.push({ tag: \"text\", text: boldText, style: [\"bold\"] });\n i = endIndex + 2;\n continue;\n }\n }\n\n // 处理行内代码 `code`\n if (line[i] === \"`\" && line[i + 1] !== \"`\") {\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n currentText = \"\";\n }\n const endIndex = line.indexOf(\"`\", i + 1);\n if (endIndex !== -1) {\n const codeText = line.substring(i + 1, endIndex);\n elements.push({\n tag: \"text\",\n text: codeText,\n style: [\"code\"],\n });\n i = endIndex + 1;\n continue;\n }\n }\n\n // 处理链接 [text](url)\n if (line[i] === \"[\") {\n const textEnd = line.indexOf(\"](\", i);\n const urlEnd = line.indexOf(\")\", textEnd + 2);\n if (textEnd !== -1 && urlEnd !== -1) {\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n currentText = \"\";\n }\n const linkText = line.substring(i + 1, textEnd);\n const url = line.substring(textEnd + 2, urlEnd);\n elements.push({\n tag: \"a\",\n text: linkText,\n href: url,\n });\n i = urlEnd + 1;\n continue;\n }\n }\n\n currentText += line[i];\n i++;\n }\n\n if (currentText) {\n elements.push({ tag: \"text\", text: currentText });\n }\n\n return elements.length > 0 ? elements : [{ tag: \"text\", text: line }];\n}\n","// 默认使用的 AI 模型\nexport const DEFAULT_MODEL = \"composer-1\";\n\n// 飞书机器人配置(内置,不需要外部配置)\nexport const appId = \"cli_a93822da7238dbb5\";\nexport const appSecret = \"ZQdcpLUHFb4gFa8cGfrlJfVfSSyGtyzF\";\nexport const receiveId = \"oc_482b6a04f95f4206c4fa9bc61829fd17\";\nexport const receiveIdType = \"chat_id\";\nexport const messageType = \"post\"; // text | post | interactive\nexport const messageTitle = \"🔍 Code Review 结果\";\n"],"mappings":"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","names":["import_node_child_process","import_node_fs","import_node_path","import_node_child_process","import_node_util"]}