cursor-feishu 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NeverMore93
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # cursor-feishu
2
+
3
+ [![npm](https://img.shields.io/npm/v/cursor-feishu)](https://www.npmjs.com/package/cursor-feishu)
4
+
5
+ **Cursor 飞书集成** — 通过飞书 WebSocket 长连接将飞书消息接入 Cursor Headless CLI。
6
+
7
+ ## 特性
8
+
9
+ - 🚀 **WebSocket 长连接** — 实时接收飞书消息,无需 Webhook 配置
10
+ - 🤖 **多媒体支持** — 图片、文件、音频、富文本消息自动处理
11
+ - 👥 **智能群聊** — 仅 @提及时回复,其他消息静默监听作为上下文
12
+ - 💬 **流式响应** — 支持实时更新消息(流式输出)
13
+ - 🔧 **灵活配置** — 支持环境变量注入和配置文件加载
14
+ - 📝 **完整类型** — TypeScript 类型定义,开发友好
15
+
16
+ ## 快速开始
17
+
18
+ ### 1. 安装依赖
19
+
20
+ ```bash
21
+ npm install cursor-feishu
22
+ ```
23
+
24
+ ### 2. 创建飞书配置文件
25
+
26
+ 创建 `~/.config/cursor/plugins/feishu.json`:
27
+
28
+ ```json
29
+ {
30
+ "appId": "cli_xxxxxxxxxxxx",
31
+ "appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
32
+ }
33
+ ```
34
+
35
+ 也支持通过环境变量注入敏感值(适合容器部署):
36
+
37
+ ```json
38
+ {
39
+ "appId": "${FEISHU_APP_ID}",
40
+ "appSecret": "${FEISHU_APP_SECRET}"
41
+ }
42
+ ```
43
+
44
+ ### 3. 配置飞书应用
45
+
46
+ 在 [飞书开放平台](https://open.feishu.cn/app) 创建自建应用,然后:
47
+
48
+ 1. **添加机器人能力**
49
+ 2. **事件订阅** — 添加 `im.message.receive_v1` 和 `im.chat.member.bot.added_v1`
50
+ 3. **订阅方式** — 选择「使用长连接接收事件/回调」
51
+ 4. **权限** — 开通 `im:message`、`im:message:send_as_bot`、`im:chat`
52
+ 5. **发布应用**
53
+
54
+ ### 4. 使用
55
+
56
+ ```typescript
57
+ import { createFeishuService } from 'cursor-feishu'
58
+
59
+ const service = await createFeishuService({
60
+ onMessage: async (msgCtx) => {
61
+ console.log(`收到消息: ${msgCtx.content}`)
62
+ // 调用 cursor-agent 处理消息
63
+ // const result = await execCursor(msgCtx.content)
64
+ // 发送结果回飞书
65
+ // await service.getSender().sendText(msgCtx.chatId, result)
66
+ },
67
+ onBotAdded: async (chatId) => {
68
+ console.log(`Bot 已加入群聊: ${chatId}`)
69
+ }
70
+ })
71
+
72
+ await service.run()
73
+ ```
74
+
75
+ ## API 文档
76
+
77
+ ### `createFeishuService(options)`
78
+
79
+ 创建飞书服务实例。
80
+
81
+ **参数**:
82
+
83
+ ```typescript
84
+ interface FeishuServiceOptions {
85
+ /** 飞书配置,可从 ~/.config/cursor/plugins/feishu.json 自动加载 */
86
+ config?: Partial<ResolvedConfig>
87
+ /** 消息处理回调 */
88
+ onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>
89
+ /** Bot 入群回调 */
90
+ onBotAdded?: (chatId: string) => Promise<void>
91
+ /** 卡片交互回调 */
92
+ onCardAction?: (action: any) => Promise<void>
93
+ /** 自定义日志函数 */
94
+ log?: LogFn
95
+ }
96
+ ```
97
+
98
+ **返回**:
99
+
100
+ ```typescript
101
+ interface FeishuService {
102
+ run: () => Promise<void> // 启动并运行服务
103
+ shutdown: () => Promise<void> // 关闭服务
104
+ getSender: () => FeishuSender // 获取消息发送器
105
+ getClient: () => LarkClient // 获取 Lark SDK 客户端
106
+ }
107
+ ```
108
+
109
+ ### `FeishuMessageContext`
110
+
111
+ 收到的消息上下文:
112
+
113
+ ```typescript
114
+ interface FeishuMessageContext {
115
+ chatId: string // 聊天 ID
116
+ messageId: string // 消息 ID
117
+ messageType: string // 消息类型(text, image, file 等)
118
+ content: string // 提取的文本内容
119
+ rawContent: string // 原始 JSON content
120
+ chatType: "p2p" | "group" // 聊天类型
121
+ senderId: string // 发送者 ID
122
+ rootId?: string // 回复的消息 ID
123
+ createTime?: string // 消息创建时间
124
+ shouldReply: boolean // 是否需要回复
125
+ }
126
+ ```
127
+
128
+ ### `FeishuSender`
129
+
130
+ 消息发送器:
131
+
132
+ ```typescript
133
+ class FeishuSender {
134
+ // 发送文本消息
135
+ sendText(chatId: string, text: string): Promise<boolean>
136
+
137
+ // 发送富文本卡片
138
+ sendCard(chatId: string, card: any): Promise<boolean>
139
+
140
+ // 更新消息(用于流式响应)
141
+ updateMessage(messageId: string, text: string): Promise<boolean>
142
+ }
143
+ ```
144
+
145
+ ## 配置说明
146
+
147
+ 完整配置字段(`~/.config/cursor/plugins/feishu.json`):
148
+
149
+ | 字段 | 类型 | 必填 | 默认值 | 说明 |
150
+ |------|------|:----:|--------|------|
151
+ | `appId` | string | ✅ | — | 飞书应用 App ID |
152
+ | `appSecret` | string | ✅ | — | 飞书应用 App Secret |
153
+ | `timeout` | number | ❌ | `120000` | AI 响应超时(毫秒) |
154
+ | `logLevel` | string | ❌ | `"info"` | 日志级别:fatal/error/warn/info/debug/trace |
155
+ | `maxHistoryMessages` | number | ❌ | `200` | 入群时拉取历史消息的最大条数 |
156
+ | `pollInterval` | number | ❌ | `1000` | 轮询响应的间隔(毫秒) |
157
+ | `stablePolls` | number | ❌ | `3` | 连续几次轮询内容不变视为回复完成 |
158
+ | `dedupTtl` | number | ❌ | `600000` | 消息去重缓存过期时间(毫秒) |
159
+ | `directory` | string | ❌ | `""` | 默认工作目录,支持 `~` 和 `${ENV_VAR}` 展开 |
160
+
161
+ ## 群聊行为
162
+
163
+ | 场景 | 接收消息 | 回复 |
164
+ |------|:---:|:---:|
165
+ | 单聊 | ✅ | ✅ |
166
+ | 群聊 + @bot | ✅ | ✅ |
167
+ | 群聊未 @bot | ✅ (静默) | ❌ |
168
+ | bot 入群 | ✅ (历史) | ❌ |
169
+
170
+ ## 环境变量
171
+
172
+ ```bash
173
+ # 启用调试日志(结构化 JSON 输出到 stderr)
174
+ FEISHU_DEBUG=1
175
+
176
+ # Feishu 应用凭证(如配置文件中使用 ${VAR} 占位符)
177
+ FEISHU_APP_ID=cli_xxxxxxxxxxxx
178
+ FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
179
+
180
+ # HTTP/HTTPS 代理支持
181
+ HTTP_PROXY=http://proxy.company.com:8080
182
+ HTTPS_PROXY=http://proxy.company.com:8080
183
+ ```
184
+
185
+ ## 开发
186
+
187
+ ```bash
188
+ npm install # 安装依赖
189
+ npm run build # 构建
190
+ npm run dev # 开发模式(监听变更)
191
+ npm run typecheck # 类型检查
192
+ npm run release # 交互式版本发布
193
+ npm publish # 发布到 npm
194
+ ```
195
+
196
+ ## 项目结构
197
+
198
+ ```
199
+ src/
200
+ ├── index.ts # 主入口,导出 createFeishuService
201
+ ├── types.ts # 类型定义
202
+ ├── feishu/
203
+ │ ├── gateway.ts # WebSocket 网关,连接飞书
204
+ │ └── sender.ts # 消息发送器
205
+ ```
206
+
207
+ ## 常见问题
208
+
209
+ ### Q: 消息无法接收?
210
+
211
+ 检查以下几点:
212
+
213
+ 1. 飞书应用凭证是否正确(在飞书开放平台验证)
214
+ 2. 是否订阅了 `im.message.receive_v1` 事件
215
+ 3. 是否使用「长连接」方式(不是 Webhook)
216
+ 4. 查看调试日志:`FEISHU_DEBUG=1 node app.js 2>&1 | grep error`
217
+
218
+ ### Q: 群聊消息无法回复?
219
+
220
+ 群聊中必须 @bot,否则消息只会静默监听。这是设计行为,可以避免 bot 过度回复。
221
+
222
+ ### Q: 如何支持代理?
223
+
224
+ 设置环境变量即可:
225
+
226
+ ```bash
227
+ HTTP_PROXY=http://proxy:8080 npm start
228
+ ```
229
+
230
+ ### Q: 如何与 cursor-agent 容器集成?
231
+
232
+ 在 Dockerfile 中安装 cursor-feishu,然后启动一个长期运行的 Node.js 服务来处理飞书消息,再调用 cursor-agent 容器执行任务。
233
+
234
+ 示例见 `cursor-agent` 仓库的 `docs/feishu/` 目录。
235
+
236
+ ## 许可证
237
+
238
+ MIT
239
+
240
+ ## 相关项目
241
+
242
+ - [opencode-feishu](https://github.com/NeverMore93/opencode-feishu) — OpenCode 飞书插件(原型)
243
+ - [cursor-agent](https://github.com/NeverMore93/cursor-agent) — Cursor Headless CLI 容器化解决方案
244
+ - [Feishu SDK](https://open.feishu.cn/document) — 飞书官方文档
@@ -0,0 +1,164 @@
1
+ import * as Lark from '@larksuiteoapi/node-sdk';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * 飞书消息上下文
6
+ */
7
+ interface FeishuMessageContext {
8
+ chatId: string;
9
+ messageId: string;
10
+ messageType: string;
11
+ /** 提取后的文本内容 */
12
+ content: string;
13
+ /** 原始 JSON content 字符串 */
14
+ rawContent: string;
15
+ chatType: "p2p" | "group";
16
+ senderId: string;
17
+ rootId?: string;
18
+ /** 消息创建时间 */
19
+ createTime?: string;
20
+ /** 是否需要回复 */
21
+ shouldReply: boolean;
22
+ }
23
+ declare const FeishuConfigSchema: z.ZodObject<{
24
+ appId: z.ZodString;
25
+ appSecret: z.ZodString;
26
+ timeout: z.ZodDefault<z.ZodNumber>;
27
+ logLevel: z.ZodDefault<z.ZodEnum<{
28
+ error: "error";
29
+ fatal: "fatal";
30
+ warn: "warn";
31
+ info: "info";
32
+ debug: "debug";
33
+ trace: "trace";
34
+ }>>;
35
+ maxHistoryMessages: z.ZodDefault<z.ZodNumber>;
36
+ pollInterval: z.ZodDefault<z.ZodNumber>;
37
+ stablePolls: z.ZodDefault<z.ZodNumber>;
38
+ dedupTtl: z.ZodDefault<z.ZodNumber>;
39
+ directory: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strip>;
41
+ /**
42
+ * 合并默认值后的完整配置
43
+ */
44
+ type ResolvedConfig = z.infer<typeof FeishuConfigSchema> & {
45
+ directory: string;
46
+ };
47
+ /**
48
+ * 日志函数签名
49
+ */
50
+ type LogFn = (level: "info" | "warn" | "error", message: string, extra?: Record<string, unknown>) => void;
51
+ /**
52
+ * 网关回调处理器
53
+ */
54
+ interface GatewayHandlers {
55
+ onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>;
56
+ onBotAdded?: (chatId: string) => Promise<void>;
57
+ onCardAction?: (action: CardAction) => Promise<void>;
58
+ }
59
+ /**
60
+ * 卡片交互动作
61
+ */
62
+ interface CardAction {
63
+ actionId: string;
64
+ messageId: string;
65
+ chatId: string;
66
+ senderId: string;
67
+ actionValue: Record<string, unknown>;
68
+ }
69
+
70
+ /**
71
+ * 飞书消息发送 — 向飞书用户/群组发送消息
72
+ */
73
+
74
+ declare class FeishuSender {
75
+ private larkClient;
76
+ private log;
77
+ constructor(larkClient: InstanceType<typeof Lark.Client>, log: LogFn);
78
+ /**
79
+ * 发送文本消息
80
+ */
81
+ sendText(chatId: string, text: string): Promise<boolean>;
82
+ /**
83
+ * 发送富文本卡片
84
+ */
85
+ sendCard(chatId: string, card: any): Promise<boolean>;
86
+ /**
87
+ * 更新消息(用于流式响应)
88
+ */
89
+ updateMessage(messageId: string, text: string): Promise<boolean>;
90
+ }
91
+ declare function createSender(larkClient: InstanceType<typeof Lark.Client>, log: LogFn): FeishuSender;
92
+
93
+ /**
94
+ * Feishu WebSocket 网关 — 与飞书建立长连接,接收事件
95
+ */
96
+
97
+ /**
98
+ * 类型增强:为 Lark SDK 添加缺失的类型定义
99
+ */
100
+ declare module "@larksuiteoapi/node-sdk" {
101
+ const im: any;
102
+ }
103
+ interface FeishuGatewayResult {
104
+ shutdown: () => Promise<void>;
105
+ }
106
+ interface GatewayOptions {
107
+ config: ResolvedConfig;
108
+ larkClient: InstanceType<typeof Lark.Client>;
109
+ botOpenId: string;
110
+ handlers: GatewayHandlers;
111
+ log: LogFn;
112
+ }
113
+ /**
114
+ * 启动飞书 WebSocket 网关
115
+ */
116
+ declare function startFeishuGateway(options: GatewayOptions): Promise<FeishuGatewayResult>;
117
+
118
+ /**
119
+ * Cursor 飞书集成主模块
120
+ *
121
+ * 使用方式:
122
+ * ```typescript
123
+ * import { createFeishuService } from 'cursor-feishu'
124
+ *
125
+ * const service = await createFeishuService({
126
+ * config: { appId: '...', appSecret: '...' },
127
+ * onMessage: async (msg) => {
128
+ * // 处理消息
129
+ * }
130
+ * })
131
+ *
132
+ * // 等待服务运行
133
+ * await service.run()
134
+ * ```
135
+ */
136
+
137
+ interface FeishuServiceOptions {
138
+ /** 飞书配置,可从 ~/.config/cursor/plugins/feishu.json 自动加载 */
139
+ config?: Partial<ResolvedConfig>;
140
+ /** 消息处理回调 */
141
+ onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>;
142
+ /** Bot 入群回调 */
143
+ onBotAdded?: (chatId: string) => Promise<void>;
144
+ /** 卡片交互回调 */
145
+ onCardAction?: (action: any) => Promise<void>;
146
+ /** 自定义日志函数 */
147
+ log?: LogFn;
148
+ }
149
+ interface FeishuService {
150
+ /** 启动并运行服务 */
151
+ run: () => Promise<void>;
152
+ /** 关闭服务 */
153
+ shutdown: () => Promise<void>;
154
+ /** 获取 Feishu 消息发送器 */
155
+ getSender: () => FeishuSender;
156
+ /** 获取 Lark SDK 客户端 */
157
+ getClient: () => InstanceType<typeof Lark.Client>;
158
+ }
159
+ /**
160
+ * 创建 Cursor 飞书集成服务
161
+ */
162
+ declare function createFeishuService(options: FeishuServiceOptions): Promise<FeishuService>;
163
+
164
+ export { type FeishuMessageContext, type FeishuService, type FeishuServiceOptions, type LogFn, type ResolvedConfig, createFeishuService, createSender, startFeishuGateway };
package/dist/index.js ADDED
@@ -0,0 +1,368 @@
1
+ // src/index.ts
2
+ import { readFileSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ import * as Lark2 from "@larksuiteoapi/node-sdk";
6
+ import { z as z2 } from "zod";
7
+
8
+ // src/types.ts
9
+ import { z } from "zod";
10
+ var AutoPromptSchema = z.object({
11
+ enabled: z.boolean().default(false),
12
+ intervalSeconds: z.number().int().positive().max(300).default(30),
13
+ maxIterations: z.number().int().positive().max(100).default(10),
14
+ message: z.string().min(1).default("\u8BF7\u540C\u6B65\u5F53\u524D\u8FDB\u5EA6\uFF0C\u5982\u9700\u5E2E\u52A9\u8BF7\u8BF4\u660E")
15
+ });
16
+ var FeishuConfigSchema = z.object({
17
+ appId: z.string().min(1, "appId \u4E0D\u80FD\u4E3A\u7A7A"),
18
+ appSecret: z.string().min(1, "appSecret \u4E0D\u80FD\u4E3A\u7A7A"),
19
+ timeout: z.number().int().positive().max(6e5).default(12e4),
20
+ logLevel: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"),
21
+ maxHistoryMessages: z.number().int().positive().max(500).default(200),
22
+ pollInterval: z.number().int().positive().default(1e3),
23
+ stablePolls: z.number().int().positive().default(3),
24
+ dedupTtl: z.number().int().positive().default(10 * 60 * 1e3),
25
+ directory: z.string().optional()
26
+ });
27
+
28
+ // src/feishu/gateway.ts
29
+ import * as Lark from "@larksuiteoapi/node-sdk";
30
+ async function startFeishuGateway(options) {
31
+ const { config, larkClient, botOpenId, handlers, log } = options;
32
+ log("info", "\u542F\u52A8\u98DE\u4E66 WebSocket \u7F51\u5173");
33
+ const ws = new Lark.EventDispatcher({});
34
+ ws.addEventListener(Lark.im.MessageReceive.v1, async (data) => {
35
+ try {
36
+ const event = data.detail.event;
37
+ const message = event.message;
38
+ const msgCtx = {
39
+ chatId: event.chat_id,
40
+ messageId: message.message_id,
41
+ messageType: message.message_type,
42
+ content: extractTextContent(message),
43
+ rawContent: message.content || "{}",
44
+ chatType: event.chat_type,
45
+ senderId: event.sender.sender_id.user_id || event.sender.sender_id.open_id || "",
46
+ rootId: message.root_id,
47
+ createTime: message.create_time,
48
+ shouldReply: shouldReply(event, botOpenId)
49
+ };
50
+ if (handlers.onMessage) {
51
+ await handlers.onMessage(msgCtx);
52
+ }
53
+ } catch (err) {
54
+ log("error", "\u5904\u7406\u6D88\u606F\u4E8B\u4EF6\u5931\u8D25", {
55
+ error: err instanceof Error ? err.message : String(err)
56
+ });
57
+ }
58
+ });
59
+ ws.addEventListener(Lark.im.ChatMemberBotAdded.v1, async (data) => {
60
+ try {
61
+ const chatId = data.detail.event.chat_id;
62
+ if (handlers.onBotAdded) {
63
+ await handlers.onBotAdded(chatId);
64
+ }
65
+ } catch (err) {
66
+ log("error", "\u5904\u7406 bot \u5165\u7FA4\u4E8B\u4EF6\u5931\u8D25", {
67
+ error: err instanceof Error ? err.message : String(err)
68
+ });
69
+ }
70
+ });
71
+ await larkClient.startEventDispatcher(ws);
72
+ log("info", "\u98DE\u4E66 WebSocket \u7F51\u5173\u5DF2\u542F\u52A8");
73
+ return {
74
+ shutdown: async () => {
75
+ log("info", "\u5173\u95ED\u98DE\u4E66 WebSocket \u7F51\u5173");
76
+ await larkClient.stopEventDispatcher(ws);
77
+ }
78
+ };
79
+ }
80
+ function extractTextContent(message) {
81
+ try {
82
+ if (message.message_type === "text") {
83
+ const content = JSON.parse(message.content || "{}");
84
+ return content.text || "";
85
+ }
86
+ } catch {
87
+ }
88
+ return "";
89
+ }
90
+ function shouldReply(event, botOpenId) {
91
+ if (event.chat_type === "p2p") {
92
+ return true;
93
+ }
94
+ const message = event.message;
95
+ if (message.message_type === "text") {
96
+ try {
97
+ const content = JSON.parse(message.content || "{}");
98
+ const text = content.text || "";
99
+ return text.includes(`<at user_id="${botOpenId}">`) || text.includes(`<at open_id="${botOpenId}">`);
100
+ } catch {
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+
106
+ // src/feishu/sender.ts
107
+ var FeishuSender = class {
108
+ constructor(larkClient, log) {
109
+ this.larkClient = larkClient;
110
+ this.log = log;
111
+ }
112
+ /**
113
+ * 发送文本消息
114
+ */
115
+ async sendText(chatId, text) {
116
+ try {
117
+ const res = await this.larkClient.im.message.create({
118
+ params: {
119
+ receive_id_type: "chat_id"
120
+ },
121
+ data: {
122
+ receive_id: chatId,
123
+ msg_type: "text",
124
+ content: JSON.stringify({ text })
125
+ }
126
+ });
127
+ const messageId = res?.data?.message_id;
128
+ if (messageId) {
129
+ this.log("info", "\u6D88\u606F\u5DF2\u53D1\u9001", { chatId, messageId });
130
+ return true;
131
+ } else {
132
+ this.log("error", "\u53D1\u9001\u6D88\u606F\u5931\u8D25", { chatId, response: res });
133
+ return false;
134
+ }
135
+ } catch (err) {
136
+ this.log("error", "\u53D1\u9001\u6D88\u606F\u5F02\u5E38", {
137
+ chatId,
138
+ error: err instanceof Error ? err.message : String(err)
139
+ });
140
+ return false;
141
+ }
142
+ }
143
+ /**
144
+ * 发送富文本卡片
145
+ */
146
+ async sendCard(chatId, card) {
147
+ try {
148
+ const res = await this.larkClient.im.message.create({
149
+ params: {
150
+ receive_id_type: "chat_id"
151
+ },
152
+ data: {
153
+ receive_id: chatId,
154
+ msg_type: "interactive",
155
+ card
156
+ }
157
+ });
158
+ const messageId = res?.data?.message_id;
159
+ if (messageId) {
160
+ this.log("info", "\u5361\u7247\u5DF2\u53D1\u9001", { chatId, messageId });
161
+ return true;
162
+ } else {
163
+ this.log("error", "\u53D1\u9001\u5361\u7247\u5931\u8D25", { chatId, response: res });
164
+ return false;
165
+ }
166
+ } catch (err) {
167
+ this.log("error", "\u53D1\u9001\u5361\u7247\u5F02\u5E38", {
168
+ chatId,
169
+ error: err instanceof Error ? err.message : String(err)
170
+ });
171
+ return false;
172
+ }
173
+ }
174
+ /**
175
+ * 更新消息(用于流式响应)
176
+ */
177
+ async updateMessage(messageId, text) {
178
+ try {
179
+ await this.larkClient.im.message.patch({
180
+ path: {
181
+ message_id: messageId
182
+ },
183
+ data: {
184
+ content: JSON.stringify({ text })
185
+ }
186
+ });
187
+ this.log("info", "\u6D88\u606F\u5DF2\u66F4\u65B0", { messageId });
188
+ return true;
189
+ } catch (err) {
190
+ this.log("error", "\u66F4\u65B0\u6D88\u606F\u5F02\u5E38", {
191
+ messageId,
192
+ error: err instanceof Error ? err.message : String(err)
193
+ });
194
+ return false;
195
+ }
196
+ }
197
+ };
198
+ function createSender(larkClient, log) {
199
+ return new FeishuSender(larkClient, log);
200
+ }
201
+
202
+ // src/index.ts
203
+ var SERVICE_NAME = "cursor-feishu";
204
+ var LOG_PREFIX = "[feishu]";
205
+ var isDebug = !!process.env.FEISHU_DEBUG;
206
+ async function createFeishuService(options) {
207
+ const { config: partialConfig, onMessage, onBotAdded, onCardAction, log: customLog } = options;
208
+ const log = customLog || ((level, message, extra) => {
209
+ const prefixed = `${LOG_PREFIX} ${message}`;
210
+ if (isDebug) {
211
+ console.error(JSON.stringify({
212
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
213
+ service: SERVICE_NAME,
214
+ level,
215
+ message: prefixed,
216
+ ...extra
217
+ }));
218
+ } else if (level === "error" || level === "warn") {
219
+ console.error(prefixed, extra || "");
220
+ } else {
221
+ console.log(prefixed, extra || "");
222
+ }
223
+ });
224
+ const config = await loadConfig(partialConfig, log);
225
+ const larkClient = new Lark2.Client({
226
+ appId: config.appId,
227
+ appSecret: config.appSecret,
228
+ domain: Lark2.Domain.Feishu,
229
+ appType: Lark2.AppType.SelfBuild
230
+ });
231
+ const botOpenId = await fetchBotOpenId(larkClient, log);
232
+ const sender = createSender(larkClient, log);
233
+ let gateway = null;
234
+ const handlers = {
235
+ onMessage,
236
+ onBotAdded,
237
+ onCardAction
238
+ };
239
+ log("info", "\u98DE\u4E66\u670D\u52A1\u5DF2\u521D\u59CB\u5316", {
240
+ appId: config.appId.slice(0, 8) + "...",
241
+ botOpenId
242
+ });
243
+ return {
244
+ async run() {
245
+ try {
246
+ gateway = await startFeishuGateway({
247
+ config,
248
+ larkClient,
249
+ botOpenId,
250
+ handlers,
251
+ log
252
+ });
253
+ log("info", "\u98DE\u4E66\u670D\u52A1\u5DF2\u542F\u52A8");
254
+ await new Promise(() => {
255
+ });
256
+ } catch (err) {
257
+ log("error", "\u542F\u52A8\u98DE\u4E66\u670D\u52A1\u5931\u8D25", {
258
+ error: err instanceof Error ? err.message : String(err)
259
+ });
260
+ throw err;
261
+ }
262
+ },
263
+ async shutdown() {
264
+ if (gateway) {
265
+ await gateway.shutdown();
266
+ }
267
+ log("info", "\u98DE\u4E66\u670D\u52A1\u5DF2\u5173\u95ED");
268
+ },
269
+ getSender() {
270
+ return sender;
271
+ },
272
+ getClient() {
273
+ return larkClient;
274
+ }
275
+ };
276
+ }
277
+ async function loadConfig(partialConfig, log) {
278
+ if (partialConfig && partialConfig.appId && partialConfig.appSecret) {
279
+ const config = FeishuConfigSchema.parse({
280
+ appId: partialConfig.appId,
281
+ appSecret: partialConfig.appSecret,
282
+ ...partialConfig
283
+ });
284
+ return {
285
+ ...config,
286
+ directory: expandDirectoryPath(partialConfig.directory || "")
287
+ };
288
+ }
289
+ const configPath = join(homedir(), ".config", "cursor", "plugins", "feishu.json");
290
+ if (!existsSync(configPath)) {
291
+ throw new Error(
292
+ `\u7F3A\u5C11\u98DE\u4E66\u914D\u7F6E\u6587\u4EF6\uFF1A\u8BF7\u521B\u5EFA ${configPath}\uFF0C\u5185\u5BB9\u4E3A {"appId":"cli_xxx","appSecret":"xxx"}`
293
+ );
294
+ }
295
+ try {
296
+ const raw = resolveEnvPlaceholders(JSON.parse(readFileSync(configPath, "utf-8")));
297
+ const config = FeishuConfigSchema.parse(raw);
298
+ return {
299
+ ...config,
300
+ directory: expandDirectoryPath(config.directory || "")
301
+ };
302
+ } catch (err) {
303
+ if (err instanceof z2.ZodError) {
304
+ const details = err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
305
+ throw new Error(`${LOG_PREFIX} \u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25:
306
+ ${details}`);
307
+ }
308
+ if (err instanceof SyntaxError) {
309
+ throw new Error(`\u98DE\u4E66\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u9519\u8BEF\uFF1A${configPath} \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON (${err.message})`);
310
+ }
311
+ throw err;
312
+ }
313
+ }
314
+ function expandDirectoryPath(dir) {
315
+ if (!dir) return dir;
316
+ if (dir.startsWith("~")) {
317
+ dir = join(homedir(), dir.slice(1));
318
+ }
319
+ dir = dir.replace(/\$\{(\w+)\}/g, (_match, name) => {
320
+ const val = process.env[name];
321
+ if (val === void 0) {
322
+ throw new Error(`\u73AF\u5883\u53D8\u91CF ${name} \u672A\u8BBE\u7F6E\uFF08directory \u5F15\u7528\u4E86 \${${name}}\uFF09`);
323
+ }
324
+ return val;
325
+ });
326
+ return dir;
327
+ }
328
+ function resolveEnvPlaceholders(obj) {
329
+ if (typeof obj === "string") {
330
+ if (!obj.includes("${")) return obj;
331
+ return obj.replace(/\$\{(\w+)\}/g, (_match, name) => {
332
+ const val = process.env[name];
333
+ if (val === void 0) {
334
+ throw new Error(`\u73AF\u5883\u53D8\u91CF ${name} \u672A\u8BBE\u7F6E\uFF08\u914D\u7F6E\u503C\u5F15\u7528\u4E86 \${${name}}\uFF09`);
335
+ }
336
+ return val;
337
+ });
338
+ }
339
+ if (Array.isArray(obj)) {
340
+ return obj.map(resolveEnvPlaceholders);
341
+ }
342
+ if (obj !== null && typeof obj === "object") {
343
+ const result = {};
344
+ for (const [key, value] of Object.entries(obj)) {
345
+ result[key] = resolveEnvPlaceholders(value);
346
+ }
347
+ return result;
348
+ }
349
+ return obj;
350
+ }
351
+ async function fetchBotOpenId(larkClient, log) {
352
+ const res = await larkClient.request({
353
+ url: "https://open.feishu.cn/open-apis/bot/v3/info",
354
+ method: "GET"
355
+ });
356
+ const openId = res?.bot?.open_id;
357
+ if (!openId) {
358
+ throw new Error("Bot open_id \u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u542F\u52A8\u98DE\u4E66\u670D\u52A1");
359
+ }
360
+ log("info", "Bot open_id \u83B7\u53D6\u6210\u529F", { openId });
361
+ return openId;
362
+ }
363
+ export {
364
+ createFeishuService,
365
+ createSender,
366
+ startFeishuGateway
367
+ };
368
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/feishu/gateway.ts","../src/feishu/sender.ts"],"sourcesContent":["/**\n * Cursor 飞书集成主模块\n *\n * 使用方式:\n * ```typescript\n * import { createFeishuService } from 'cursor-feishu'\n *\n * const service = await createFeishuService({\n * config: { appId: '...', appSecret: '...' },\n * onMessage: async (msg) => {\n * // 处理消息\n * }\n * })\n *\n * // 等待服务运行\n * await service.run()\n * ```\n */\n\nimport { readFileSync, existsSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport { z } from \"zod\"\nimport type { ResolvedConfig, LogFn, FeishuMessageContext, GatewayHandlers } from \"./types.js\"\nimport { FeishuConfigSchema } from \"./types.js\"\nimport { startFeishuGateway, type FeishuGatewayResult } from \"./feishu/gateway.js\"\nimport { createSender, type FeishuSender } from \"./feishu/sender.js\"\n\nconst SERVICE_NAME = \"cursor-feishu\"\nconst LOG_PREFIX = \"[feishu]\"\nconst isDebug = !!process.env.FEISHU_DEBUG\n\nexport interface FeishuServiceOptions {\n /** 飞书配置,可从 ~/.config/cursor/plugins/feishu.json 自动加载 */\n config?: Partial<ResolvedConfig>\n /** 消息处理回调 */\n onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>\n /** Bot 入群回调 */\n onBotAdded?: (chatId: string) => Promise<void>\n /** 卡片交互回调 */\n onCardAction?: (action: any) => Promise<void>\n /** 自定义日志函数 */\n log?: LogFn\n}\n\nexport interface FeishuService {\n /** 启动并运行服务 */\n run: () => Promise<void>\n /** 关闭服务 */\n shutdown: () => Promise<void>\n /** 获取 Feishu 消息发送器 */\n getSender: () => FeishuSender\n /** 获取 Lark SDK 客户端 */\n getClient: () => InstanceType<typeof Lark.Client>\n}\n\n/**\n * 创建 Cursor 飞书集成服务\n */\nexport async function createFeishuService(options: FeishuServiceOptions): Promise<FeishuService> {\n const { config: partialConfig, onMessage, onBotAdded, onCardAction, log: customLog } = options\n\n // 日志函数\n const log: LogFn = customLog || ((level, message, extra) => {\n const prefixed = `${LOG_PREFIX} ${message}`\n if (isDebug) {\n console.error(JSON.stringify({\n ts: new Date().toISOString(),\n service: SERVICE_NAME,\n level,\n message: prefixed,\n ...extra,\n }))\n } else if (level === \"error\" || level === \"warn\") {\n console.error(prefixed, extra || \"\")\n } else {\n console.log(prefixed, extra || \"\")\n }\n })\n\n // 加载配置\n const config = await loadConfig(partialConfig, log)\n\n // 创建 Lark 客户端\n const larkClient = new Lark.Client({\n appId: config.appId,\n appSecret: config.appSecret,\n domain: Lark.Domain.Feishu,\n appType: Lark.AppType.SelfBuild,\n })\n\n // 获取 bot open_id\n const botOpenId = await fetchBotOpenId(larkClient, log)\n\n // 创建消息发送器\n const sender = createSender(larkClient, log)\n\n // 网关\n let gateway: FeishuGatewayResult | null = null\n\n // 创建处理器\n const handlers: GatewayHandlers = {\n onMessage,\n onBotAdded,\n onCardAction,\n }\n\n log(\"info\", \"飞书服务已初始化\", {\n appId: config.appId.slice(0, 8) + \"...\",\n botOpenId,\n })\n\n return {\n async run() {\n try {\n gateway = await startFeishuGateway({\n config,\n larkClient,\n botOpenId,\n handlers,\n log,\n })\n log(\"info\", \"飞书服务已启动\")\n // 保持运行\n await new Promise(() => {})\n } catch (err) {\n log(\"error\", \"启动飞书服务失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n throw err\n }\n },\n\n async shutdown() {\n if (gateway) {\n await gateway.shutdown()\n }\n log(\"info\", \"飞书服务已关闭\")\n },\n\n getSender() {\n return sender\n },\n\n getClient() {\n return larkClient\n },\n }\n}\n\n/**\n * 加载配置\n */\nasync function loadConfig(\n partialConfig: Partial<ResolvedConfig> | undefined,\n log: LogFn,\n): Promise<ResolvedConfig> {\n if (partialConfig && partialConfig.appId && partialConfig.appSecret) {\n // 使用传入的配置\n const config = FeishuConfigSchema.parse({\n appId: partialConfig.appId,\n appSecret: partialConfig.appSecret,\n ...partialConfig,\n })\n return {\n ...config,\n directory: expandDirectoryPath(partialConfig.directory || \"\"),\n }\n }\n\n // 从配置文件加载\n const configPath = join(homedir(), \".config\", \"cursor\", \"plugins\", \"feishu.json\")\n if (!existsSync(configPath)) {\n throw new Error(\n `缺少飞书配置文件:请创建 ${configPath},内容为 {\"appId\":\"cli_xxx\",\"appSecret\":\"xxx\"}`,\n )\n }\n\n try {\n const raw = resolveEnvPlaceholders(JSON.parse(readFileSync(configPath, \"utf-8\")))\n const config = FeishuConfigSchema.parse(raw)\n return {\n ...config,\n directory: expandDirectoryPath(config.directory || \"\"),\n }\n } catch (err) {\n if (err instanceof z.ZodError) {\n const details = err.issues.map((i) => ` - ${i.path.join(\".\")}: ${i.message}`).join(\"\\n\")\n throw new Error(`${LOG_PREFIX} 配置验证失败:\\n${details}`)\n }\n if (err instanceof SyntaxError) {\n throw new Error(`飞书配置文件格式错误:${configPath} 必须是合法的 JSON (${err.message})`)\n }\n throw err\n }\n}\n\n/**\n * 展开目录路径中的环境变量和 ~ 前缀\n */\nfunction expandDirectoryPath(dir: string): string {\n if (!dir) return dir\n if (dir.startsWith(\"~\")) {\n dir = join(homedir(), dir.slice(1))\n }\n dir = dir.replace(/\\$\\{(\\w+)\\}/g, (_match, name: string) => {\n const val = process.env[name]\n if (val === undefined) {\n throw new Error(`环境变量 ${name} 未设置(directory 引用了 \\${${name}})`)\n }\n return val\n })\n return dir\n}\n\n/**\n * 递归替换对象中字符串值里的 ${ENV_VAR} 占位符\n */\nfunction resolveEnvPlaceholders(obj: unknown): unknown {\n if (typeof obj === \"string\") {\n if (!obj.includes(\"${\")) return obj\n return obj.replace(/\\$\\{(\\w+)\\}/g, (_match, name: string) => {\n const val = process.env[name]\n if (val === undefined) {\n throw new Error(`环境变量 ${name} 未设置(配置值引用了 \\${${name}})`)\n }\n return val\n })\n }\n if (Array.isArray(obj)) {\n return obj.map(resolveEnvPlaceholders)\n }\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = resolveEnvPlaceholders(value)\n }\n return result\n }\n return obj\n}\n\n/**\n * 获取 bot 自身的 open_id\n */\nasync function fetchBotOpenId(\n larkClient: InstanceType<typeof Lark.Client>,\n log: LogFn,\n): Promise<string> {\n const res = await larkClient.request<{ bot?: { open_id?: string } }>({\n url: \"https://open.feishu.cn/open-apis/bot/v3/info\",\n method: \"GET\",\n })\n const openId = res?.bot?.open_id\n if (!openId) {\n throw new Error(\"Bot open_id 为空,无法启动飞书服务\")\n }\n log(\"info\", \"Bot open_id 获取成功\", { openId })\n return openId\n}\n\nexport type { ResolvedConfig, LogFn, FeishuMessageContext } from \"./types.js\"\nexport { startFeishuGateway } from \"./feishu/gateway.js\"\nexport { createSender } from \"./feishu/sender.js\"\n","import { z } from \"zod\"\n\n/**\n * 飞书消息上下文\n */\nexport interface FeishuMessageContext {\n chatId: string\n messageId: string\n messageType: string\n /** 提取后的文本内容 */\n content: string\n /** 原始 JSON content 字符串 */\n rawContent: string\n chatType: \"p2p\" | \"group\"\n senderId: string\n rootId?: string\n /** 消息创建时间 */\n createTime?: string\n /** 是否需要回复 */\n shouldReply: boolean\n}\n\n/**\n * 插件配置(从 ~/.config/cursor/plugins/feishu.json 读取)\n */\nexport interface FeishuPluginConfig {\n appId: string\n appSecret: string\n timeout?: number\n logLevel?: \"fatal\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\"\n /** 入群时拉取历史消息的最大条数 */\n maxHistoryMessages?: number\n /** 轮询响应的间隔毫秒数 */\n pollInterval?: number\n /** 连续几次轮询内容不变视为回复完成 */\n stablePolls?: number\n /** 消息去重缓存过期毫秒数 */\n dedupTtl?: number\n /** 默认工作目录 */\n directory?: string\n}\n\nconst AutoPromptSchema = z.object({\n enabled: z.boolean().default(false),\n intervalSeconds: z.number().int().positive().max(300).default(30),\n maxIterations: z.number().int().positive().max(100).default(10),\n message: z.string().min(1).default(\"请同步当前进度,如需帮助请说明\"),\n})\n\nexport const FeishuConfigSchema = z.object({\n appId: z.string().min(1, \"appId 不能为空\"),\n appSecret: z.string().min(1, \"appSecret 不能为空\"),\n timeout: z.number().int().positive().max(600_000).default(120_000),\n logLevel: z.enum([\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"]).default(\"info\"),\n maxHistoryMessages: z.number().int().positive().max(500).default(200),\n pollInterval: z.number().int().positive().default(1_000),\n stablePolls: z.number().int().positive().default(3),\n dedupTtl: z.number().int().positive().default(10 * 60 * 1_000),\n directory: z.string().optional(),\n})\n\n/**\n * 合并默认值后的完整配置\n */\nexport type ResolvedConfig = z.infer<typeof FeishuConfigSchema> & { directory: string }\n\n/**\n * 日志函数签名\n */\nexport type LogFn = (\n level: \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>,\n) => void\n\n/**\n * 网关回调处理器\n */\nexport interface GatewayHandlers {\n onMessage?: (msgCtx: FeishuMessageContext) => Promise<void>\n onBotAdded?: (chatId: string) => Promise<void>\n onCardAction?: (action: CardAction) => Promise<void>\n}\n\n/**\n * 卡片交互动作\n */\nexport interface CardAction {\n actionId: string\n messageId: string\n chatId: string\n senderId: string\n actionValue: Record<string, unknown>\n}\n","/**\n * Feishu WebSocket 网关 — 与飞书建立长连接,接收事件\n */\n\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport type { ResolvedConfig, FeishuMessageContext, LogFn, CardAction, GatewayHandlers } from \"../types.js\"\n\n/**\n * 类型增强:为 Lark SDK 添加缺失的类型定义\n */\ndeclare module \"@larksuiteoapi/node-sdk\" {\n export const im: any\n}\n\nexport interface FeishuGatewayResult {\n shutdown: () => Promise<void>\n}\n\nexport interface GatewayOptions {\n config: ResolvedConfig\n larkClient: InstanceType<typeof Lark.Client>\n botOpenId: string\n handlers: GatewayHandlers\n log: LogFn\n}\n\n/**\n * 启动飞书 WebSocket 网关\n */\nexport async function startFeishuGateway(options: GatewayOptions): Promise<FeishuGatewayResult> {\n const { config, larkClient, botOpenId, handlers, log } = options\n\n log(\"info\", \"启动飞书 WebSocket 网关\")\n\n // 创建 WebSocket 客户端连接到飞书\n const ws = new Lark.EventDispatcher({}) as any\n\n // 注册事件处理器\n ws.addEventListener((Lark.im as any).MessageReceive.v1, async (data: any) => {\n try {\n const event = data.detail.event\n const message = event.message\n\n const msgCtx: FeishuMessageContext = {\n chatId: event.chat_id,\n messageId: message.message_id,\n messageType: message.message_type,\n content: extractTextContent(message),\n rawContent: message.content || \"{}\",\n chatType: event.chat_type as \"p2p\" | \"group\",\n senderId: event.sender.sender_id.user_id || event.sender.sender_id.open_id || \"\",\n rootId: message.root_id,\n createTime: message.create_time,\n shouldReply: shouldReply(event, botOpenId),\n }\n\n if (handlers.onMessage) {\n await handlers.onMessage(msgCtx)\n }\n } catch (err) {\n log(\"error\", \"处理消息事件失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n })\n\n ws.addEventListener((Lark.im as any).ChatMemberBotAdded.v1, async (data: any) => {\n try {\n const chatId = data.detail.event.chat_id\n if (handlers.onBotAdded) {\n await handlers.onBotAdded(chatId)\n }\n } catch (err) {\n log(\"error\", \"处理 bot 入群事件失败\", {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n })\n\n // 启动连接\n await (larkClient as any).startEventDispatcher(ws)\n\n log(\"info\", \"飞书 WebSocket 网关已启动\")\n\n return {\n shutdown: async () => {\n log(\"info\", \"关闭飞书 WebSocket 网关\")\n await (larkClient as any).stopEventDispatcher(ws)\n },\n }\n}\n\n/**\n * 从飞书消息中提取文本内容\n */\nfunction extractTextContent(message: any): string {\n try {\n if (message.message_type === \"text\") {\n const content = JSON.parse(message.content || \"{}\")\n return content.text || \"\"\n }\n } catch {\n // ignore\n }\n return \"\"\n}\n\n/**\n * 判断是否应该回复\n */\nfunction shouldReply(event: any, botOpenId: string): boolean {\n // 单聊总是回复\n if (event.chat_type === \"p2p\") {\n return true\n }\n\n // 群聊只有被 @提及 才回复\n const message = event.message\n if (message.message_type === \"text\") {\n try {\n const content = JSON.parse(message.content || \"{}\")\n const text = content.text || \"\"\n // 检查是否包含 @bot\n return text.includes(`<at user_id=\"${botOpenId}\">`) || text.includes(`<at open_id=\"${botOpenId}\">`)\n } catch {\n // ignore\n }\n }\n\n return false\n}\n","/**\n * 飞书消息发送 — 向飞书用户/群组发送消息\n */\n\nimport * as Lark from \"@larksuiteoapi/node-sdk\"\nimport type { LogFn } from \"../types.js\"\n\nexport interface SenderOptions {\n larkClient: InstanceType<typeof Lark.Client>\n log: LogFn\n}\n\nexport class FeishuSender {\n constructor(private larkClient: InstanceType<typeof Lark.Client>, private log: LogFn) {}\n\n /**\n * 发送文本消息\n */\n async sendText(chatId: string, text: string): Promise<boolean> {\n try {\n const res = await this.larkClient.im.message.create({\n params: {\n receive_id_type: \"chat_id\",\n },\n data: {\n receive_id: chatId,\n msg_type: \"text\",\n content: JSON.stringify({ text }),\n },\n })\n\n const messageId = (res as any)?.data?.message_id\n if (messageId) {\n this.log(\"info\", \"消息已发送\", { chatId, messageId })\n return true\n } else {\n this.log(\"error\", \"发送消息失败\", { chatId, response: res })\n return false\n }\n } catch (err) {\n this.log(\"error\", \"发送消息异常\", {\n chatId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n /**\n * 发送富文本卡片\n */\n async sendCard(chatId: string, card: any): Promise<boolean> {\n try {\n const res = await (this.larkClient.im.message.create as any)({\n params: {\n receive_id_type: \"chat_id\",\n },\n data: {\n receive_id: chatId,\n msg_type: \"interactive\",\n card,\n },\n })\n\n const messageId = (res as any)?.data?.message_id\n if (messageId) {\n this.log(\"info\", \"卡片已发送\", { chatId, messageId })\n return true\n } else {\n this.log(\"error\", \"发送卡片失败\", { chatId, response: res })\n return false\n }\n } catch (err) {\n this.log(\"error\", \"发送卡片异常\", {\n chatId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n /**\n * 更新消息(用于流式响应)\n */\n async updateMessage(messageId: string, text: string): Promise<boolean> {\n try {\n await this.larkClient.im.message.patch({\n path: {\n message_id: messageId,\n },\n data: {\n content: JSON.stringify({ text }),\n },\n })\n\n this.log(\"info\", \"消息已更新\", { messageId })\n return true\n } catch (err) {\n this.log(\"error\", \"更新消息异常\", {\n messageId,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n}\n\nexport function createSender(\n larkClient: InstanceType<typeof Lark.Client>,\n log: LogFn,\n): FeishuSender {\n return new FeishuSender(larkClient, log)\n}\n"],"mappings":"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","names":["Lark","z","z"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "cursor-feishu",
3
+ "version": "1.0.3",
4
+ "description": "Cursor 飞书集成 — 通过飞书 WebSocket 长连接与 Cursor Headless CLI 交互",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./gateway": {
14
+ "types": "./dist/feishu/gateway.d.ts",
15
+ "default": "./dist/feishu/gateway.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "typecheck": "tsc --noEmit",
27
+ "prepublishOnly": "npm run build && npm run typecheck",
28
+ "release": "bumpp"
29
+ },
30
+ "author": "NeverMore93",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/NeverMore93/cursor-feishu"
34
+ },
35
+ "homepage": "https://github.com/NeverMore93/cursor-feishu#readme",
36
+ "dependencies": {
37
+ "@larksuiteoapi/node-sdk": "^1.56.1",
38
+ "https-proxy-agent": "^7.0.6",
39
+ "zod": "^4.3.6"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.0.0",
43
+ "bumpp": "^10.4.1",
44
+ "tsup": "^8.0.0",
45
+ "typescript": "^5.5.0"
46
+ },
47
+ "engines": {
48
+ "node": ">=20.0.0"
49
+ },
50
+ "keywords": [
51
+ "cursor",
52
+ "cursor-cli",
53
+ "feishu",
54
+ "lark",
55
+ "飞书",
56
+ "飞书机器人",
57
+ "ai",
58
+ "chatbot"
59
+ ],
60
+ "license": "MIT"
61
+ }