feishu-bridge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3324 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+
32
+ // src/core/logger.ts
33
+ var import_pino, Logger, logger;
34
+ var init_logger = __esm({
35
+ "src/core/logger.ts"() {
36
+ "use strict";
37
+ import_pino = __toESM(require("pino"));
38
+ Logger = class _Logger {
39
+ constructor() {
40
+ const isTest = process.env.NODE_ENV === "test" || process.env.VITEST;
41
+ this.logger = (0, import_pino.default)({
42
+ level: process.env.LOG_LEVEL || "info",
43
+ transport: isTest ? void 0 : {
44
+ target: "pino-pretty",
45
+ options: {
46
+ colorize: true,
47
+ translateTime: "SYS:standard",
48
+ ignore: "pid,hostname"
49
+ }
50
+ }
51
+ });
52
+ }
53
+ setLevel(level) {
54
+ this.logger.level = level;
55
+ }
56
+ debug(msg, ...args) {
57
+ this.logger.debug(msg, ...args);
58
+ }
59
+ info(msg, ...args) {
60
+ this.logger.info(msg, ...args);
61
+ }
62
+ warn(msg, ...args) {
63
+ this.logger.warn(msg, ...args);
64
+ }
65
+ error(msg, ...args) {
66
+ this.logger.error(msg, ...args);
67
+ }
68
+ child(bindings) {
69
+ const childLogger = new _Logger();
70
+ childLogger.logger = this.logger.child(bindings);
71
+ return childLogger;
72
+ }
73
+ };
74
+ logger = new Logger();
75
+ }
76
+ });
77
+
78
+ // src/core/config.ts
79
+ var DEFAULT_CONFIG, ConfigManager;
80
+ var init_config = __esm({
81
+ "src/core/config.ts"() {
82
+ "use strict";
83
+ DEFAULT_CONFIG = {
84
+ feishu: {
85
+ appId: "",
86
+ appSecret: "",
87
+ domain: "feishu",
88
+ connectionMode: "webhook",
89
+ encryptKey: void 0,
90
+ verificationToken: void 0
91
+ },
92
+ server: {
93
+ port: 3e3,
94
+ host: "0.0.0.0",
95
+ webhookPath: "/webhook/feishu"
96
+ },
97
+ adapters: {
98
+ vscode: { enabled: true },
99
+ cursor: { enabled: true },
100
+ trae: { enabled: true },
101
+ antigravity: { enabled: true },
102
+ kiro: { enabled: true },
103
+ opencode: { enabled: true },
104
+ "claude-code": { enabled: true }
105
+ },
106
+ behavior: {
107
+ autoStart: true,
108
+ maxOutputLength: 2e3,
109
+ sessionTimeout: 30,
110
+ reverseChannelEnabled: true,
111
+ reverseChannelMode: "filesystem"
112
+ },
113
+ logging: {
114
+ level: "info",
115
+ file: void 0
116
+ }
117
+ };
118
+ ConfigManager = class _ConfigManager {
119
+ constructor() {
120
+ this.config = DEFAULT_CONFIG;
121
+ }
122
+ static getInstance() {
123
+ if (!_ConfigManager.instance) {
124
+ _ConfigManager.instance = new _ConfigManager();
125
+ }
126
+ return _ConfigManager.instance;
127
+ }
128
+ load(config) {
129
+ this.config = this.deepMerge(this.config, config);
130
+ }
131
+ deepMerge(target, source) {
132
+ const output = { ...target };
133
+ if (this.isObject(target) && this.isObject(source)) {
134
+ Object.keys(source).forEach((key) => {
135
+ if (this.isObject(source[key])) {
136
+ if (!(key in target)) {
137
+ Object.assign(output, { [key]: source[key] });
138
+ } else {
139
+ output[key] = this.deepMerge(target[key], source[key]);
140
+ }
141
+ } else {
142
+ Object.assign(output, { [key]: source[key] });
143
+ }
144
+ });
145
+ }
146
+ return output;
147
+ }
148
+ isObject(item) {
149
+ return item && typeof item === "object" && !Array.isArray(item);
150
+ }
151
+ get() {
152
+ return this.config;
153
+ }
154
+ getFeishuConfig() {
155
+ return this.config.feishu;
156
+ }
157
+ getServerConfig() {
158
+ return this.config.server;
159
+ }
160
+ getAdapterConfig(adapter) {
161
+ return this.config.adapters[adapter];
162
+ }
163
+ getBehaviorConfig() {
164
+ return this.config.behavior;
165
+ }
166
+ getLoggingConfig() {
167
+ return this.config.logging;
168
+ }
169
+ };
170
+ }
171
+ });
172
+
173
+ // src/core/daemon.ts
174
+ var daemon_exports = {};
175
+ __export(daemon_exports, {
176
+ DaemonManager: () => DaemonManager,
177
+ ProcessDaemon: () => ProcessDaemon,
178
+ default: () => daemon_default
179
+ });
180
+ var import_child_process, ProcessDaemon, DaemonManager, daemon_default;
181
+ var init_daemon = __esm({
182
+ "src/core/daemon.ts"() {
183
+ "use strict";
184
+ import_child_process = require("child_process");
185
+ init_logger();
186
+ init_config();
187
+ ProcessDaemon = class {
188
+ constructor(command, args = [], options = {}) {
189
+ this.command = command;
190
+ this.args = args;
191
+ this.options = options;
192
+ this.process = null;
193
+ this.restartCount = 0;
194
+ this.isRunning = false;
195
+ this.healthCheckTimer = null;
196
+ this.logger = new Logger();
197
+ const daemonConfig = ConfigManager.getInstance().get().daemon;
198
+ this.config = {
199
+ maxRestarts: daemonConfig?.maxRestarts || 5,
200
+ restartDelay: daemonConfig?.restartDelay || 5e3,
201
+ healthCheckInterval: daemonConfig?.healthCheckInterval || 3e4
202
+ };
203
+ }
204
+ /**
205
+ * 启动守护进程
206
+ */
207
+ async start() {
208
+ if (this.isRunning) {
209
+ this.logger.warn("Daemon already running");
210
+ return;
211
+ }
212
+ this.isRunning = true;
213
+ this.restartCount = 0;
214
+ this.logger.info("Starting process daemon...");
215
+ await this.spawnProcess();
216
+ this.startHealthCheck();
217
+ }
218
+ /**
219
+ * 停止守护进程
220
+ */
221
+ async stop() {
222
+ this.isRunning = false;
223
+ if (this.healthCheckTimer) {
224
+ clearInterval(this.healthCheckTimer);
225
+ this.healthCheckTimer = null;
226
+ }
227
+ if (this.process) {
228
+ this.logger.info("Stopping daemon process...");
229
+ this.process.kill("SIGTERM");
230
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
231
+ if (this.process && !this.process.killed) {
232
+ this.process.kill("SIGKILL");
233
+ }
234
+ this.process = null;
235
+ }
236
+ this.logger.info("Daemon stopped");
237
+ }
238
+ /**
239
+ * 创建子进程
240
+ */
241
+ async spawnProcess() {
242
+ return new Promise((resolve) => {
243
+ this.logger.info(`Spawning: ${this.command} ${this.args.join(" ")}`);
244
+ this.process = (0, import_child_process.spawn)(this.command, this.args, {
245
+ stdio: "pipe",
246
+ detached: false,
247
+ ...this.options
248
+ });
249
+ this.process.stdout?.on("data", (data) => {
250
+ process.stdout.write(data);
251
+ });
252
+ this.process.stderr?.on("data", (data) => {
253
+ process.stderr.write(data);
254
+ });
255
+ this.process.on("exit", (code, signal) => {
256
+ this.logger.warn(`Process exited with code ${code}, signal ${signal}`);
257
+ this.process = null;
258
+ if (this.isRunning) {
259
+ this.handleCrash();
260
+ }
261
+ });
262
+ this.process.on("error", (error) => {
263
+ this.logger.error("Process error:", error);
264
+ this.process = null;
265
+ if (this.isRunning) {
266
+ this.handleCrash();
267
+ }
268
+ });
269
+ setTimeout(() => {
270
+ if (this.process && !this.process.killed) {
271
+ this.logger.info("Process started successfully");
272
+ this.restartCount = 0;
273
+ }
274
+ resolve();
275
+ }, 2e3);
276
+ });
277
+ }
278
+ /**
279
+ * 处理进程崩溃
280
+ */
281
+ handleCrash() {
282
+ if (!this.isRunning) return;
283
+ this.restartCount++;
284
+ if (this.restartCount > this.config.maxRestarts) {
285
+ this.logger.error(`Max restarts (${this.config.maxRestarts}) reached. Giving up.`);
286
+ this.isRunning = false;
287
+ process.exit(1);
288
+ }
289
+ this.logger.info(`Restarting process (attempt ${this.restartCount}/${this.config.maxRestarts})...`);
290
+ setTimeout(() => {
291
+ if (this.isRunning) {
292
+ this.spawnProcess();
293
+ }
294
+ }, this.config.restartDelay);
295
+ }
296
+ /**
297
+ * 启动健康检查
298
+ */
299
+ startHealthCheck() {
300
+ this.healthCheckTimer = setInterval(() => {
301
+ if (!this.isRunning) return;
302
+ if (!this.process || this.process.killed) {
303
+ this.logger.warn("Health check: Process not running, restarting...");
304
+ this.handleCrash();
305
+ }
306
+ }, this.config.healthCheckInterval);
307
+ }
308
+ /**
309
+ * 获取守护状态
310
+ */
311
+ getStatus() {
312
+ return {
313
+ isRunning: this.isRunning,
314
+ pid: this.process?.pid || null,
315
+ restartCount: this.restartCount,
316
+ uptime: process.uptime()
317
+ };
318
+ }
319
+ };
320
+ DaemonManager = class _DaemonManager {
321
+ constructor() {
322
+ this.daemon = null;
323
+ }
324
+ static getInstance() {
325
+ if (!_DaemonManager.instance) {
326
+ _DaemonManager.instance = new _DaemonManager();
327
+ }
328
+ return _DaemonManager.instance;
329
+ }
330
+ /**
331
+ * 启动守护进程
332
+ */
333
+ async start(args = []) {
334
+ if (this.daemon) {
335
+ console.log("Daemon already running");
336
+ return;
337
+ }
338
+ const nodePath = process.execPath;
339
+ const scriptPath = require.resolve("../index.js");
340
+ this.daemon = new ProcessDaemon(nodePath, [scriptPath, ...args], {
341
+ cwd: process.cwd(),
342
+ env: process.env
343
+ });
344
+ await this.daemon.start();
345
+ process.on("SIGINT", () => this.stop());
346
+ process.on("SIGTERM", () => this.stop());
347
+ }
348
+ /**
349
+ * 停止守护进程
350
+ */
351
+ async stop() {
352
+ if (this.daemon) {
353
+ await this.daemon.stop();
354
+ this.daemon = null;
355
+ }
356
+ }
357
+ /**
358
+ * 获取状态
359
+ */
360
+ getStatus() {
361
+ return this.daemon?.getStatus() || null;
362
+ }
363
+ };
364
+ daemon_default = DaemonManager;
365
+ }
366
+ });
367
+
368
+ // src/cli/index.ts
369
+ var import_commander = require("commander");
370
+ var fs10 = __toESM(require("fs"));
371
+ var path10 = __toESM(require("path"));
372
+
373
+ // src/cli/config.ts
374
+ var import_cosmiconfig = require("cosmiconfig");
375
+ var fs = __toESM(require("fs"));
376
+ var path = __toESM(require("path"));
377
+ var os = __toESM(require("os"));
378
+ var moduleName = "feishu-bridge";
379
+ var explorer = (0, import_cosmiconfig.cosmiconfigSync)(moduleName, {
380
+ searchPlaces: [
381
+ "package.json",
382
+ `.${moduleName}rc`,
383
+ `.${moduleName}rc.json`,
384
+ `.${moduleName}rc.yaml`,
385
+ `.${moduleName}rc.yml`,
386
+ `.${moduleName}rc.js`,
387
+ `.${moduleName}rc.cjs`,
388
+ `${moduleName}.config.js`,
389
+ `${moduleName}.config.cjs`
390
+ ]
391
+ });
392
+ function getDefaultConfigPath() {
393
+ const configDir = path.join(os.homedir(), ".config", moduleName);
394
+ if (!fs.existsSync(configDir)) {
395
+ fs.mkdirSync(configDir, { recursive: true });
396
+ }
397
+ return path.join(configDir, "config.json");
398
+ }
399
+ function getConfig() {
400
+ const result = explorer.search();
401
+ if (result?.config) {
402
+ return result.config;
403
+ }
404
+ const homeConfigPath = getDefaultConfigPath();
405
+ if (fs.existsSync(homeConfigPath)) {
406
+ try {
407
+ const content = fs.readFileSync(homeConfigPath, "utf-8");
408
+ return JSON.parse(content);
409
+ } catch (error) {
410
+ console.error("Error reading home config file:", error);
411
+ }
412
+ }
413
+ return {};
414
+ }
415
+ function getConfigValue(key) {
416
+ const config = getConfig();
417
+ const keys = key.split(".");
418
+ let value = config;
419
+ for (const k of keys) {
420
+ if (value === void 0 || value === null) {
421
+ return void 0;
422
+ }
423
+ value = value[k];
424
+ }
425
+ return value;
426
+ }
427
+ function setConfigValue(key, value) {
428
+ const configPath = getDefaultConfigPath();
429
+ let config = {};
430
+ if (fs.existsSync(configPath)) {
431
+ try {
432
+ const content = fs.readFileSync(configPath, "utf-8");
433
+ config = JSON.parse(content);
434
+ } catch (error) {
435
+ console.error("Error reading config file:", error);
436
+ }
437
+ }
438
+ const keys = key.split(".");
439
+ let current = config;
440
+ for (let i = 0; i < keys.length - 1; i++) {
441
+ const k = keys[i];
442
+ if (!(k in current) || typeof current[k] !== "object") {
443
+ current[k] = {};
444
+ }
445
+ current = current[k];
446
+ }
447
+ const finalKey = keys[keys.length - 1];
448
+ current[finalKey] = convertValueType(value);
449
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
450
+ }
451
+ function deleteConfigValue(key) {
452
+ const configPath = getDefaultConfigPath();
453
+ if (!fs.existsSync(configPath)) {
454
+ return;
455
+ }
456
+ let config = {};
457
+ try {
458
+ const content = fs.readFileSync(configPath, "utf-8");
459
+ config = JSON.parse(content);
460
+ } catch (error) {
461
+ console.error("Error reading config file:", error);
462
+ return;
463
+ }
464
+ const keys = key.split(".");
465
+ let current = config;
466
+ for (let i = 0; i < keys.length - 1; i++) {
467
+ const k = keys[i];
468
+ if (!(k in current)) {
469
+ return;
470
+ }
471
+ current = current[k];
472
+ }
473
+ delete current[keys[keys.length - 1]];
474
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
475
+ }
476
+ function listConfig() {
477
+ return getConfig();
478
+ }
479
+ function convertValueType(value) {
480
+ if (!isNaN(Number(value)) && value.trim() !== "") {
481
+ return Number(value);
482
+ }
483
+ if (value.toLowerCase() === "true") {
484
+ return true;
485
+ }
486
+ if (value.toLowerCase() === "false") {
487
+ return false;
488
+ }
489
+ try {
490
+ return JSON.parse(value);
491
+ } catch {
492
+ return value;
493
+ }
494
+ }
495
+ function getConfigFilePath() {
496
+ const result = explorer.search();
497
+ if (result?.filepath) {
498
+ return result.filepath;
499
+ }
500
+ return getDefaultConfigPath();
501
+ }
502
+
503
+ // src/feishu/webhook.ts
504
+ var crypto = __toESM(require("crypto"));
505
+ init_logger();
506
+ init_config();
507
+ var FeishuWebhookHandler = class {
508
+ constructor(client, commandExecutor) {
509
+ this.client = client;
510
+ this.commandExecutor = commandExecutor;
511
+ this.logger = new Logger();
512
+ }
513
+ async handleWebhook(request, reply) {
514
+ try {
515
+ const signature = request.headers["x-lark-signature"];
516
+ const timestamp = request.headers["x-lark-timestamp"];
517
+ const body = request.body;
518
+ if (!this.verifySignature(signature, timestamp, body)) {
519
+ this.logger.warn("Invalid signature received");
520
+ reply.status(401).send({ error: "Invalid signature" });
521
+ return;
522
+ }
523
+ const event = JSON.parse(body);
524
+ if (event.type === "url_verification") {
525
+ this.logger.info("Received URL verification challenge");
526
+ reply.send({ challenge: event.challenge });
527
+ return;
528
+ }
529
+ if (event.type === "event_callback" && event.event) {
530
+ await this.handleEvent(event.event);
531
+ }
532
+ reply.send({ success: true });
533
+ } catch (error) {
534
+ this.logger.error("Error handling webhook:", error);
535
+ reply.status(500).send({ error: "Internal server error" });
536
+ }
537
+ }
538
+ verifySignature(signature, timestamp, body) {
539
+ const config = ConfigManager.getInstance().getFeishuConfig();
540
+ const encryptKey = config.encryptKey;
541
+ if (!encryptKey) {
542
+ this.logger.warn("No encrypt key configured, skipping signature verification (development mode)");
543
+ return true;
544
+ }
545
+ const now = Math.floor(Date.now() / 1e3);
546
+ const requestTime = parseInt(timestamp, 10);
547
+ const timeDiff = Math.abs(now - requestTime);
548
+ if (timeDiff > 300) {
549
+ this.logger.warn(`Request timestamp too old: ${timeDiff}s difference`);
550
+ return false;
551
+ }
552
+ try {
553
+ const signatureContent = `${timestamp}
554
+ ${body}`;
555
+ const computedSignature = crypto.createHmac("sha256", encryptKey).update(signatureContent).digest("base64");
556
+ const expectedSig = Buffer.from(computedSignature);
557
+ const receivedSig = Buffer.from(signature);
558
+ if (expectedSig.length !== receivedSig.length) {
559
+ return false;
560
+ }
561
+ return crypto.timingSafeEqual(expectedSig, receivedSig);
562
+ } catch (error) {
563
+ this.logger.error("Error verifying signature:", error);
564
+ return false;
565
+ }
566
+ }
567
+ async handleEvent(event) {
568
+ const { message } = event;
569
+ if (message.message_type !== "text") {
570
+ this.logger.info("Ignoring non-text message");
571
+ return;
572
+ }
573
+ const content = JSON.parse(message.content);
574
+ const text = content.text.trim();
575
+ const sender = event.sender.sender_id.open_id;
576
+ this.logger.info(`Received message from ${sender}: ${text}`);
577
+ const target = this.extractTargetIDE(text);
578
+ const command = this.extractCommand(text);
579
+ const result = await this.commandExecutor.execute({
580
+ target,
581
+ command,
582
+ sender,
583
+ chatId: message.chat_id
584
+ });
585
+ const formattedResponse = this.formatResponse(result);
586
+ await this.client.sendMessage(message.chat_id, {
587
+ msg_type: "text",
588
+ content: {
589
+ text: formattedResponse
590
+ }
591
+ });
592
+ }
593
+ extractTargetIDE(text) {
594
+ const match = text.match(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)/i);
595
+ return match ? match[1].toLowerCase() : "auto";
596
+ }
597
+ extractCommand(text) {
598
+ const cleanedText = text.replace(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)\s*/i, "").trim();
599
+ return cleanedText;
600
+ }
601
+ formatResponse(result) {
602
+ if (result.success) {
603
+ const output = typeof result.output === "string" ? result.output.substring(0, 2e3) : JSON.stringify(result.output).substring(0, 2e3);
604
+ return `\u2705 \u6267\u884C\u6210\u529F
605
+
606
+ ${output}`;
607
+ } else {
608
+ const error = result.error || result.stderr || "Unknown error";
609
+ return `\u274C \u6267\u884C\u5931\u8D25
610
+
611
+ ${error.substring(0, 2e3)}`;
612
+ }
613
+ }
614
+ };
615
+
616
+ // src/feishu/websocket.ts
617
+ var import_ws = require("ws");
618
+ init_logger();
619
+ init_config();
620
+ var FeishuWebSocketHandler = class {
621
+ constructor(client, commandExecutor) {
622
+ this.client = client;
623
+ this.commandExecutor = commandExecutor;
624
+ this.ws = null;
625
+ this.reconnectAttempts = 0;
626
+ this.maxReconnectAttempts = 5;
627
+ this.reconnectInterval = 5e3;
628
+ // 5秒
629
+ this.heartbeatInterval = null;
630
+ this.isRunning = false;
631
+ this.logger = new Logger();
632
+ }
633
+ /**
634
+ * 启动WebSocket连接
635
+ */
636
+ async start() {
637
+ if (this.isRunning) {
638
+ this.logger.warn("WebSocket handler already running");
639
+ return;
640
+ }
641
+ this.isRunning = true;
642
+ this.reconnectAttempts = 0;
643
+ await this.connect();
644
+ }
645
+ /**
646
+ * 停止WebSocket连接
647
+ */
648
+ async stop() {
649
+ this.isRunning = false;
650
+ if (this.heartbeatInterval) {
651
+ clearInterval(this.heartbeatInterval);
652
+ this.heartbeatInterval = null;
653
+ }
654
+ if (this.ws) {
655
+ this.ws.close();
656
+ this.ws = null;
657
+ }
658
+ this.logger.info("WebSocket handler stopped");
659
+ }
660
+ /**
661
+ * 建立WebSocket连接
662
+ */
663
+ async connect() {
664
+ try {
665
+ const config = ConfigManager.getInstance().getFeishuConfig();
666
+ const wsUrl = config.domain === "feishu" ? "wss://ws.feishu.cn/passport/" : "wss://ws.larksuite.com/passport/";
667
+ this.logger.info(`Connecting to WebSocket: ${wsUrl}`);
668
+ this.ws = new import_ws.WebSocket(wsUrl, {
669
+ headers: {
670
+ "Authorization": `Bearer ${await this.getAccessToken()}`
671
+ }
672
+ });
673
+ this.setupWebSocketHandlers();
674
+ } catch (error) {
675
+ this.logger.error("Failed to connect WebSocket:", error);
676
+ this.handleReconnect();
677
+ }
678
+ }
679
+ /**
680
+ * 设置WebSocket事件处理器
681
+ */
682
+ setupWebSocketHandlers() {
683
+ if (!this.ws) return;
684
+ this.ws.on("open", () => {
685
+ this.logger.info("WebSocket connected");
686
+ this.reconnectAttempts = 0;
687
+ this.startHeartbeat();
688
+ });
689
+ this.ws.on("message", (data) => {
690
+ this.handleMessage(data.toString());
691
+ });
692
+ this.ws.on("close", (code, reason) => {
693
+ this.logger.info(`WebSocket closed: ${code} - ${reason.toString()}`);
694
+ this.stopHeartbeat();
695
+ if (this.isRunning) {
696
+ this.handleReconnect();
697
+ }
698
+ });
699
+ this.ws.on("error", (error) => {
700
+ this.logger.error("WebSocket error:", error);
701
+ });
702
+ this.ws.on("ping", () => {
703
+ this.ws?.pong();
704
+ });
705
+ }
706
+ /**
707
+ * 处理收到的消息
708
+ */
709
+ async handleMessage(data) {
710
+ try {
711
+ const event = JSON.parse(data);
712
+ this.logger.debug("Received WebSocket message:", event);
713
+ if (event.type === "url_verification") {
714
+ this.logger.info("Received URL verification challenge via WebSocket");
715
+ return;
716
+ }
717
+ if (event.type === "event_callback" && event.event) {
718
+ await this.handleEvent(event.event);
719
+ }
720
+ } catch (error) {
721
+ this.logger.error("Error handling WebSocket message:", error);
722
+ }
723
+ }
724
+ /**
725
+ * 处理事件
726
+ */
727
+ async handleEvent(event) {
728
+ const { message } = event;
729
+ if (!message || message.message_type !== "text") {
730
+ this.logger.debug("Ignoring non-text message");
731
+ return;
732
+ }
733
+ try {
734
+ const content = JSON.parse(message.content);
735
+ const text = content.text.trim();
736
+ const sender = event.sender?.sender_id?.open_id;
737
+ if (!sender) {
738
+ this.logger.warn("Missing sender ID");
739
+ return;
740
+ }
741
+ this.logger.info(`Received message from ${sender}: ${text}`);
742
+ const target = this.extractTargetIDE(text);
743
+ const command = this.extractCommand(text);
744
+ const result = await this.commandExecutor.execute({
745
+ target,
746
+ command,
747
+ sender,
748
+ chatId: message.chat_id
749
+ });
750
+ await this.client.sendMessage(message.chat_id, {
751
+ msg_type: "text",
752
+ content: {
753
+ text: this.formatResponse(result)
754
+ }
755
+ });
756
+ } catch (error) {
757
+ this.logger.error("Error processing event:", error);
758
+ }
759
+ }
760
+ /**
761
+ * 启动心跳保活
762
+ */
763
+ startHeartbeat() {
764
+ this.heartbeatInterval = setInterval(() => {
765
+ if (this.ws?.readyState === import_ws.WebSocket.OPEN) {
766
+ this.ws.ping();
767
+ }
768
+ }, 3e4);
769
+ }
770
+ /**
771
+ * 停止心跳
772
+ */
773
+ stopHeartbeat() {
774
+ if (this.heartbeatInterval) {
775
+ clearInterval(this.heartbeatInterval);
776
+ this.heartbeatInterval = null;
777
+ }
778
+ }
779
+ /**
780
+ * 处理重连
781
+ */
782
+ handleReconnect() {
783
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
784
+ this.logger.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached`);
785
+ return;
786
+ }
787
+ this.reconnectAttempts++;
788
+ const delay = this.reconnectInterval * this.reconnectAttempts;
789
+ this.logger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
790
+ setTimeout(() => {
791
+ if (this.isRunning) {
792
+ this.connect();
793
+ }
794
+ }, delay);
795
+ }
796
+ /**
797
+ * 获取访问令牌
798
+ */
799
+ async getAccessToken() {
800
+ const config = ConfigManager.getInstance().getFeishuConfig();
801
+ const response = await fetch(
802
+ `https://open.${config.domain === "lark" ? "larksuite" : "feishu"}.cn/open-apis/auth/v3/tenant_access_token/internal`,
803
+ {
804
+ method: "POST",
805
+ headers: { "Content-Type": "application/json" },
806
+ body: JSON.stringify({
807
+ app_id: config.appId,
808
+ app_secret: config.appSecret
809
+ })
810
+ }
811
+ );
812
+ const data = await response.json();
813
+ if (data.code !== 0) {
814
+ throw new Error(`Failed to get access token: ${data.msg}`);
815
+ }
816
+ return data.tenant_access_token;
817
+ }
818
+ /**
819
+ * 提取目标IDE
820
+ */
821
+ extractTargetIDE(text) {
822
+ const match = text.match(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)/i);
823
+ return match ? match[1].toLowerCase() : "auto";
824
+ }
825
+ /**
826
+ * 提取命令
827
+ */
828
+ extractCommand(text) {
829
+ return text.replace(/@(vscode|cursor|trae|antigravity|kiro|opencode|claude)\s*/i, "").trim();
830
+ }
831
+ /**
832
+ * 格式化响应
833
+ */
834
+ formatResponse(result) {
835
+ if (result.success) {
836
+ const output = typeof result.output === "string" ? result.output.substring(0, 2e3) : JSON.stringify(result.output).substring(0, 2e3);
837
+ return `\u2705 \u6267\u884C\u6210\u529F
838
+
839
+ ${output}`;
840
+ } else {
841
+ const error = result.error || result.stderr || "Unknown error";
842
+ return `\u274C \u6267\u884C\u5931\u8D25
843
+
844
+ ${error.substring(0, 2e3)}`;
845
+ }
846
+ }
847
+ };
848
+
849
+ // src/feishu/client.ts
850
+ init_logger();
851
+ var FeishuClient = class {
852
+ constructor(config) {
853
+ this.accessToken = null;
854
+ this.tokenExpireTime = 0;
855
+ this.appId = config.appId;
856
+ this.appSecret = config.appSecret;
857
+ this.domain = config.domain;
858
+ this.baseUrl = this.domain === "feishu" ? "https://open.feishu.cn/open-apis" : "https://open.larksuite.com/open-apis";
859
+ this.logger = new Logger();
860
+ }
861
+ /**
862
+ * 获取访问令牌
863
+ */
864
+ async getAccessToken() {
865
+ const now = Date.now();
866
+ if (this.accessToken && now < this.tokenExpireTime) {
867
+ return this.accessToken;
868
+ }
869
+ try {
870
+ const response = await fetch(`${this.baseUrl}/auth/v3/tenant_access_token/internal`, {
871
+ method: "POST",
872
+ headers: {
873
+ "Content-Type": "application/json; charset=utf-8"
874
+ },
875
+ body: JSON.stringify({
876
+ app_id: this.appId,
877
+ app_secret: this.appSecret
878
+ })
879
+ });
880
+ if (!response.ok) {
881
+ throw new Error(`Failed to get access token: ${response.status} ${response.statusText}`);
882
+ }
883
+ const data = await response.json();
884
+ if (data.code !== 0) {
885
+ throw new Error(`Failed to get access token: ${data.msg}`);
886
+ }
887
+ this.accessToken = data.data.tenant_access_token;
888
+ this.tokenExpireTime = now + (data.data.expire - 300) * 1e3;
889
+ this.logger.info("Access token refreshed successfully");
890
+ return this.accessToken;
891
+ } catch (error) {
892
+ this.logger.error("Error getting access token:", error);
893
+ throw error;
894
+ }
895
+ }
896
+ /**
897
+ * 发送消息到指定聊天
898
+ */
899
+ async sendMessage(chatId, message) {
900
+ try {
901
+ const token = await this.getAccessToken();
902
+ const response = await fetch(`${this.baseUrl}/im/v1/messages?receive_id_type=chat_id`, {
903
+ method: "POST",
904
+ headers: {
905
+ "Authorization": `Bearer ${token}`,
906
+ "Content-Type": "application/json; charset=utf-8"
907
+ },
908
+ body: JSON.stringify({
909
+ receive_id: chatId,
910
+ ...message
911
+ })
912
+ });
913
+ if (!response.ok) {
914
+ throw new Error(`Failed to send message: ${response.status} ${response.statusText}`);
915
+ }
916
+ const data = await response.json();
917
+ if (data.code !== 0) {
918
+ throw new Error(`Failed to send message: ${data.msg}`);
919
+ }
920
+ this.logger.info(`Message sent successfully to chat ${chatId}`);
921
+ return data.data;
922
+ } catch (error) {
923
+ this.logger.error("Error sending message:", error);
924
+ throw error;
925
+ }
926
+ }
927
+ /**
928
+ * 获取用户信息
929
+ */
930
+ async getUserInfo(userId) {
931
+ try {
932
+ const token = await this.getAccessToken();
933
+ const response = await fetch(`${this.baseUrl}/contact/v3/users/${userId}`, {
934
+ method: "GET",
935
+ headers: {
936
+ "Authorization": `Bearer ${token}`,
937
+ "Content-Type": "application/json; charset=utf-8"
938
+ }
939
+ });
940
+ if (!response.ok) {
941
+ throw new Error(`Failed to get user info: ${response.status} ${response.statusText}`);
942
+ }
943
+ const data = await response.json();
944
+ if (data.code !== 0) {
945
+ throw new Error(`Failed to get user info: ${data.msg}`);
946
+ }
947
+ return data.data;
948
+ } catch (error) {
949
+ this.logger.error("Error getting user info:", error);
950
+ throw error;
951
+ }
952
+ }
953
+ };
954
+
955
+ // src/commands/executor.ts
956
+ init_logger();
957
+ var CommandExecutor = class {
958
+ constructor(adapters, sessionManager) {
959
+ this.adapters = adapters;
960
+ this.sessionManager = sessionManager;
961
+ this.logger = new Logger();
962
+ }
963
+ async execute(request) {
964
+ try {
965
+ this.logger.info(`Executing command for target: ${request.target}, command: ${request.command}`);
966
+ const session = this.sessionManager.getSession(request.sender);
967
+ let adapterName = request.target.toLowerCase();
968
+ if (adapterName === "auto") {
969
+ adapterName = this.detectBestAdapter();
970
+ }
971
+ const adapter = this.adapters.get(adapterName);
972
+ if (!adapter) {
973
+ throw new Error(`Adapter not found for target: ${adapterName}`);
974
+ }
975
+ if (!await adapter.checkAvailability()) {
976
+ throw new Error(`Adapter ${adapterName} is not available`);
977
+ }
978
+ const executionContext = {
979
+ workspaceRoot: session?.workspaceRoot,
980
+ chatId: request.chatId,
981
+ senderId: request.sender
982
+ };
983
+ let result;
984
+ if (this.isGenerateCommand(request.command)) {
985
+ const description = this.extractDescription(request.command);
986
+ result = await adapter.generateCode(description);
987
+ } else if (this.isRunCommand(request.command)) {
988
+ const command = this.extractRunCommand(request.command);
989
+ const executionResult = await adapter.runProgram(command, executionContext.workspaceRoot);
990
+ result = executionResult.stdout || executionResult.stderr;
991
+ } else {
992
+ result = await adapter.sendCommand(request.command, executionContext);
993
+ }
994
+ this.sessionManager.updateSession(request.sender, {
995
+ lastCommand: request.command,
996
+ lastResult: result,
997
+ lastUsedAdapter: adapterName
998
+ });
999
+ return {
1000
+ success: true,
1001
+ output: result
1002
+ };
1003
+ } catch (error) {
1004
+ this.logger.error("Error executing command:", error);
1005
+ return {
1006
+ success: false,
1007
+ output: "",
1008
+ error: error instanceof Error ? error.message : String(error)
1009
+ };
1010
+ }
1011
+ }
1012
+ detectBestAdapter() {
1013
+ for (const [name, adapter] of this.adapters) {
1014
+ if (adapter.isAvailable) {
1015
+ return name;
1016
+ }
1017
+ }
1018
+ return "vscode";
1019
+ }
1020
+ isGenerateCommand(command) {
1021
+ return /\b(generate|create|make|build|implement|write|develop|add|new)\b/i.test(command);
1022
+ }
1023
+ isRunCommand(command) {
1024
+ return /\b(run|execute|start|launch|test|debug|build)\b/i.test(command);
1025
+ }
1026
+ extractDescription(command) {
1027
+ const match = command.match(/\b(generate|create|make|build|implement|write|develop|add|new)\b\s+(.*)/i);
1028
+ return match ? match[2] : command;
1029
+ }
1030
+ extractRunCommand(command) {
1031
+ const match = command.match(/\b(run|execute|start|launch|test|debug|build)\b\s+(.*)/i);
1032
+ return match ? match[2] : command;
1033
+ }
1034
+ };
1035
+
1036
+ // src/session/manager.ts
1037
+ init_logger();
1038
+ var SessionManager = class {
1039
+ // 会话超时时间(毫秒)
1040
+ constructor(sessionTimeoutMinutes = 30) {
1041
+ this.sessions = /* @__PURE__ */ new Map();
1042
+ this.logger = new Logger();
1043
+ this.sessionTimeout = sessionTimeoutMinutes * 60 * 1e3;
1044
+ setInterval(() => {
1045
+ this.cleanupExpiredSessions();
1046
+ }, this.sessionTimeout);
1047
+ }
1048
+ /**
1049
+ * 获取会话,如果不存在则创建新会话
1050
+ */
1051
+ getSession(userId) {
1052
+ const session = this.sessions.get(userId);
1053
+ if (session) {
1054
+ if (Date.now() - session.lastActive > this.sessionTimeout) {
1055
+ this.logger.info(`Session for user ${userId} expired, removing`);
1056
+ this.sessions.delete(userId);
1057
+ return null;
1058
+ }
1059
+ session.lastActive = Date.now();
1060
+ return session;
1061
+ }
1062
+ return null;
1063
+ }
1064
+ /**
1065
+ * 创建新会话
1066
+ */
1067
+ createSession(userId, initialData) {
1068
+ const now = Date.now();
1069
+ const session = {
1070
+ userId,
1071
+ createdAt: now,
1072
+ lastActive: now,
1073
+ workspaceRoot: initialData?.workspaceRoot,
1074
+ context: initialData?.context,
1075
+ lastCommand: initialData?.lastCommand,
1076
+ lastResult: initialData?.lastResult,
1077
+ lastUsedAdapter: initialData?.lastUsedAdapter
1078
+ };
1079
+ this.sessions.set(userId, session);
1080
+ this.logger.info(`Created new session for user ${userId}`);
1081
+ return session;
1082
+ }
1083
+ /**
1084
+ * 更新会话数据
1085
+ */
1086
+ updateSession(userId, update) {
1087
+ let session = this.sessions.get(userId);
1088
+ if (!session) {
1089
+ session = this.createSession(userId);
1090
+ }
1091
+ session.lastActive = Date.now();
1092
+ if (update.workspaceRoot !== void 0) session.workspaceRoot = update.workspaceRoot;
1093
+ if (update.context !== void 0) session.context = { ...session.context, ...update.context };
1094
+ if (update.lastCommand !== void 0) session.lastCommand = update.lastCommand;
1095
+ if (update.lastResult !== void 0) session.lastResult = update.lastResult;
1096
+ if (update.lastUsedAdapter !== void 0) session.lastUsedAdapter = update.lastUsedAdapter;
1097
+ this.sessions.set(userId, session);
1098
+ this.logger.debug(`Updated session for user ${userId}`);
1099
+ return session;
1100
+ }
1101
+ /**
1102
+ * 删除会话
1103
+ */
1104
+ removeSession(userId) {
1105
+ const removed = this.sessions.delete(userId);
1106
+ if (removed) {
1107
+ this.logger.info(`Removed session for user ${userId}`);
1108
+ }
1109
+ return removed;
1110
+ }
1111
+ /**
1112
+ * 清理会话数据
1113
+ */
1114
+ clearAllSessions() {
1115
+ const count = this.sessions.size;
1116
+ this.sessions.clear();
1117
+ this.logger.info(`Cleared all ${count} sessions`);
1118
+ }
1119
+ /**
1120
+ * 获取所有活跃会话数量
1121
+ */
1122
+ getActiveSessionCount() {
1123
+ return this.sessions.size;
1124
+ }
1125
+ /**
1126
+ * 清理过期会话
1127
+ */
1128
+ cleanupExpiredSessions() {
1129
+ const now = Date.now();
1130
+ let expiredCount = 0;
1131
+ for (const [userId, session] of this.sessions) {
1132
+ if (now - session.lastActive > this.sessionTimeout) {
1133
+ this.sessions.delete(userId);
1134
+ expiredCount++;
1135
+ this.logger.info(`Cleaned up expired session for user ${userId}`);
1136
+ }
1137
+ }
1138
+ if (expiredCount > 0) {
1139
+ this.logger.info(`Cleaned up ${expiredCount} expired sessions`);
1140
+ }
1141
+ }
1142
+ };
1143
+
1144
+ // src/adapters/base.ts
1145
+ init_logger();
1146
+ var BaseIDEAdapter = class {
1147
+ constructor() {
1148
+ this.reverseChannel = null;
1149
+ this.logger = new Logger().child({ adapter: this.type });
1150
+ }
1151
+ get isAvailable() {
1152
+ return this.checkAvailabilitySync();
1153
+ }
1154
+ /**
1155
+ * 设置反向通信通道
1156
+ * 允许适配器主动向飞书发送消息
1157
+ */
1158
+ setReverseChannel(channel) {
1159
+ this.reverseChannel = channel;
1160
+ this.logger.info(`Reverse channel set for ${this.type} adapter`);
1161
+ }
1162
+ /**
1163
+ * 发送消息到飞书
1164
+ * 供子类调用以主动推送消息
1165
+ */
1166
+ async sendToFeishu(content, context, type = "message") {
1167
+ if (!this.reverseChannel) {
1168
+ this.logger.warn("Reverse channel not available, cannot send message to Feishu");
1169
+ return false;
1170
+ }
1171
+ if (!context?.chatId) {
1172
+ this.logger.warn("No chatId in context, cannot send message to Feishu");
1173
+ return false;
1174
+ }
1175
+ try {
1176
+ await this.reverseChannel.sendToFeishu({
1177
+ type,
1178
+ content,
1179
+ chatId: context.chatId,
1180
+ senderId: context.senderId,
1181
+ timestamp: Date.now()
1182
+ });
1183
+ return true;
1184
+ } catch (error) {
1185
+ this.logger.error("Error sending message to Feishu:", error);
1186
+ return false;
1187
+ }
1188
+ }
1189
+ /**
1190
+ * 发送文件系统消息(无需反向通道实例)
1191
+ * 通过写入文件的方式让Bridge代为发送
1192
+ */
1193
+ async sendFileSystemMessage(content, context, type = "message") {
1194
+ if (!this.reverseChannel) {
1195
+ this.logger.warn("Reverse channel not available for file system message");
1196
+ return;
1197
+ }
1198
+ await this.reverseChannel.sendFileSystemMessage(
1199
+ this.type,
1200
+ content,
1201
+ context?.chatId
1202
+ );
1203
+ }
1204
+ buildCodePrompt(description, options) {
1205
+ let prompt = description;
1206
+ if (options) {
1207
+ if (options.language) {
1208
+ prompt += `
1209
+ Language: ${options.language}`;
1210
+ }
1211
+ if (options.framework) {
1212
+ prompt += `
1213
+ Framework: ${options.framework}`;
1214
+ }
1215
+ if (options.style) {
1216
+ prompt += `
1217
+ Style: ${options.style}`;
1218
+ }
1219
+ }
1220
+ return prompt;
1221
+ }
1222
+ };
1223
+
1224
+ // src/adapters/vscode.ts
1225
+ var import_execa = require("execa");
1226
+ var fs2 = __toESM(require("fs"));
1227
+ var os2 = __toESM(require("os"));
1228
+ var path2 = __toESM(require("path"));
1229
+ var VSCodeAdapter = class extends BaseIDEAdapter {
1230
+ constructor() {
1231
+ super(...arguments);
1232
+ this.type = "vscode";
1233
+ this.vscodePath = null;
1234
+ }
1235
+ checkAvailabilitySync() {
1236
+ if (process.env.VSCODE_PID || process.env.VSCODE_CWD) {
1237
+ return true;
1238
+ }
1239
+ this.vscodePath = this.findVSCodeExecutable();
1240
+ return !!this.vscodePath;
1241
+ }
1242
+ findVSCodeExecutable() {
1243
+ const platform5 = os2.platform();
1244
+ const possiblePaths = [];
1245
+ if (platform5 === "darwin") {
1246
+ possiblePaths.push(
1247
+ "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
1248
+ "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code",
1249
+ "/usr/local/bin/code",
1250
+ path2.join(os2.homedir(), "Applications/Visual Studio Code.app/Contents/Resources/app/bin/code")
1251
+ );
1252
+ } else if (platform5 === "win32") {
1253
+ possiblePaths.push(
1254
+ path2.join(process.env.LOCALAPPDATA || "", "Programs/Microsoft VS Code/bin/code.cmd"),
1255
+ path2.join(process.env.ProgramFiles || "", "Microsoft VS Code/bin/code.cmd"),
1256
+ path2.join(process.env.ProgramFiles || "", "Microsoft VS Code/bin/code.exe"),
1257
+ "C:\\Program Files\\Microsoft VS Code\\bin\\code.cmd"
1258
+ );
1259
+ } else {
1260
+ possiblePaths.push(
1261
+ "/usr/bin/code",
1262
+ "/usr/local/bin/code",
1263
+ "/usr/share/code/bin/code",
1264
+ path2.join(os2.homedir(), ".local/bin/code"),
1265
+ "/snap/bin/code"
1266
+ );
1267
+ }
1268
+ for (const vscodePath of possiblePaths) {
1269
+ if (fs2.existsSync(vscodePath)) {
1270
+ return vscodePath;
1271
+ }
1272
+ }
1273
+ try {
1274
+ if (platform5 === "win32") {
1275
+ const result = import_execa.execa.sync("where", ["code"], { reject: false });
1276
+ if (result.stdout) {
1277
+ const lines = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l);
1278
+ if (lines.length > 0) return lines[0];
1279
+ }
1280
+ } else {
1281
+ const result = import_execa.execa.sync("which", ["code"], { reject: false });
1282
+ if (result.stdout) {
1283
+ return result.stdout.trim();
1284
+ }
1285
+ }
1286
+ } catch {
1287
+ }
1288
+ return null;
1289
+ }
1290
+ async checkAvailability() {
1291
+ return this.checkAvailabilitySync();
1292
+ }
1293
+ async sendCommand(command, context) {
1294
+ try {
1295
+ this.logger.info(`Sending command to VS Code: ${command}`);
1296
+ if (this.vscodePath) {
1297
+ try {
1298
+ const result = await (0, import_execa.execa)(this.vscodePath, [
1299
+ "--command",
1300
+ "feishuBridge.execute",
1301
+ "--args",
1302
+ JSON.stringify({ command })
1303
+ ], {
1304
+ timeout: 3e4,
1305
+ reject: false
1306
+ });
1307
+ if (!result.failed) {
1308
+ return result.stdout || "Command executed successfully";
1309
+ }
1310
+ } catch (cliError) {
1311
+ this.logger.debug("VS Code CLI failed, trying file system method");
1312
+ }
1313
+ }
1314
+ return await this.sendViaFileSystem(command, context);
1315
+ } catch (error) {
1316
+ this.logger.error(`Error executing VS Code command:`, error);
1317
+ throw new Error(`VS Code command failed: ${error instanceof Error ? error.message : String(error)}`);
1318
+ }
1319
+ }
1320
+ async sendViaFileSystem(command, context) {
1321
+ const tempDir = path2.join(os2.tmpdir(), "feishu-bridge", "vscode");
1322
+ if (!fs2.existsSync(tempDir)) {
1323
+ fs2.mkdirSync(tempDir, { recursive: true });
1324
+ }
1325
+ const instructionFile = path2.join(tempDir, `cmd_${Date.now()}.json`);
1326
+ const responseFile = path2.join(tempDir, `resp_${Date.now()}.txt`);
1327
+ const instruction = {
1328
+ type: "command",
1329
+ command,
1330
+ context,
1331
+ timestamp: Date.now(),
1332
+ responseFile
1333
+ };
1334
+ fs2.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1335
+ const maxWait = 3e4;
1336
+ const checkInterval = 500;
1337
+ let waited = 0;
1338
+ while (waited < maxWait) {
1339
+ if (fs2.existsSync(responseFile)) {
1340
+ const response = fs2.readFileSync(responseFile, "utf-8");
1341
+ try {
1342
+ fs2.unlinkSync(instructionFile);
1343
+ fs2.unlinkSync(responseFile);
1344
+ } catch {
1345
+ }
1346
+ return response;
1347
+ }
1348
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1349
+ waited += checkInterval;
1350
+ }
1351
+ try {
1352
+ fs2.unlinkSync(instructionFile);
1353
+ } catch {
1354
+ }
1355
+ return `Command queued for VS Code. File: ${instructionFile}`;
1356
+ }
1357
+ async getAIResponse(prompt, context) {
1358
+ try {
1359
+ this.logger.info(`Getting AI response from VS Code for prompt: ${prompt}`);
1360
+ if (this.vscodePath) {
1361
+ try {
1362
+ const result = await (0, import_execa.execa)(this.vscodePath, [
1363
+ "--command",
1364
+ "github.copilot.generateCode",
1365
+ "--args",
1366
+ JSON.stringify({ prompt })
1367
+ ], {
1368
+ timeout: 6e4,
1369
+ reject: false
1370
+ });
1371
+ if (!result.failed && result.stdout) {
1372
+ return result.stdout;
1373
+ }
1374
+ } catch (cliError) {
1375
+ this.logger.debug("VS Code AI CLI failed, using file system");
1376
+ }
1377
+ }
1378
+ return await this.sendAIRequestViaFileSystem(prompt, context);
1379
+ } catch (error) {
1380
+ this.logger.error(`Error getting VS Code AI response:`, error);
1381
+ throw error;
1382
+ }
1383
+ }
1384
+ async sendAIRequestViaFileSystem(prompt, context) {
1385
+ const tempDir = path2.join(os2.tmpdir(), "feishu-bridge", "vscode");
1386
+ if (!fs2.existsSync(tempDir)) {
1387
+ fs2.mkdirSync(tempDir, { recursive: true });
1388
+ }
1389
+ const instructionFile = path2.join(tempDir, `ai_${Date.now()}.json`);
1390
+ const responseFile = path2.join(tempDir, `ai_resp_${Date.now()}.txt`);
1391
+ const instruction = {
1392
+ type: "ai_request",
1393
+ prompt,
1394
+ context,
1395
+ timestamp: Date.now(),
1396
+ responseFile
1397
+ };
1398
+ fs2.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1399
+ const maxWait = 12e4;
1400
+ const checkInterval = 1e3;
1401
+ let waited = 0;
1402
+ while (waited < maxWait) {
1403
+ if (fs2.existsSync(responseFile)) {
1404
+ const response = fs2.readFileSync(responseFile, "utf-8");
1405
+ try {
1406
+ fs2.unlinkSync(instructionFile);
1407
+ fs2.unlinkSync(responseFile);
1408
+ } catch {
1409
+ }
1410
+ return response;
1411
+ }
1412
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1413
+ waited += checkInterval;
1414
+ }
1415
+ try {
1416
+ fs2.unlinkSync(instructionFile);
1417
+ } catch {
1418
+ }
1419
+ return `AI request queued for VS Code. File: ${instructionFile}`;
1420
+ }
1421
+ async generateCode(description, options) {
1422
+ const fullPrompt = this.buildCodePrompt(description, options);
1423
+ return this.getAIResponse(fullPrompt);
1424
+ }
1425
+ async runProgram(command, cwd) {
1426
+ try {
1427
+ const result = await import_execa.execa.command(command, {
1428
+ cwd: cwd || process.cwd(),
1429
+ shell: true,
1430
+ timeout: 6e4,
1431
+ // 60秒超时
1432
+ reject: false
1433
+ });
1434
+ return {
1435
+ stdout: result.stdout,
1436
+ stderr: result.stderr,
1437
+ exitCode: result.exitCode || 0,
1438
+ success: result.exitCode === 0,
1439
+ output: result.stdout,
1440
+ error: result.failed ? result.stderr : void 0
1441
+ };
1442
+ } catch (error) {
1443
+ this.logger.error(`Error running program in VS Code:`, error);
1444
+ return {
1445
+ stdout: "",
1446
+ stderr: error instanceof Error ? error.message : String(error),
1447
+ exitCode: 1,
1448
+ success: false,
1449
+ error: error instanceof Error ? error.message : String(error)
1450
+ };
1451
+ }
1452
+ }
1453
+ };
1454
+
1455
+ // src/adapters/cursor.ts
1456
+ var import_execa2 = require("execa");
1457
+ var fs3 = __toESM(require("fs"));
1458
+ var path3 = __toESM(require("path"));
1459
+ var os3 = __toESM(require("os"));
1460
+ var CursorAdapter = class extends BaseIDEAdapter {
1461
+ constructor() {
1462
+ super(...arguments);
1463
+ this.type = "cursor";
1464
+ this.cursorPath = null;
1465
+ }
1466
+ checkAvailabilitySync() {
1467
+ if (process.env.CURSOR_SHELL || process.env.CURSOR_PID) {
1468
+ return true;
1469
+ }
1470
+ this.cursorPath = this.findCursorExecutable();
1471
+ return !!this.cursorPath;
1472
+ }
1473
+ findCursorExecutable() {
1474
+ const platform5 = os3.platform();
1475
+ const possiblePaths = [];
1476
+ if (platform5 === "darwin") {
1477
+ possiblePaths.push(
1478
+ "/Applications/Cursor.app/Contents/MacOS/Cursor",
1479
+ "/usr/local/bin/cursor",
1480
+ path3.join(os3.homedir(), "Applications/Cursor.app/Contents/MacOS/Cursor")
1481
+ );
1482
+ } else if (platform5 === "win32") {
1483
+ possiblePaths.push(
1484
+ path3.join(process.env.LOCALAPPDATA || "", "Programs/Cursor/Cursor.exe"),
1485
+ path3.join(process.env.PROGRAMFILES || "", "Cursor/Cursor.exe"),
1486
+ "C:\\Program Files\\Cursor\\Cursor.exe"
1487
+ );
1488
+ } else {
1489
+ possiblePaths.push(
1490
+ "/usr/bin/cursor",
1491
+ "/usr/local/bin/cursor",
1492
+ "/opt/cursor/cursor",
1493
+ path3.join(os3.homedir(), ".local/bin/cursor")
1494
+ );
1495
+ }
1496
+ for (const cursorPath of possiblePaths) {
1497
+ if (fs3.existsSync(cursorPath)) {
1498
+ return cursorPath;
1499
+ }
1500
+ }
1501
+ try {
1502
+ if (platform5 === "win32") {
1503
+ const result = import_execa2.execa.sync("where", ["cursor"], { reject: false });
1504
+ if (result.stdout) {
1505
+ const lines = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l);
1506
+ if (lines.length > 0) return lines[0];
1507
+ }
1508
+ } else {
1509
+ const result = import_execa2.execa.sync("which", ["cursor"], { reject: false });
1510
+ if (result.stdout) {
1511
+ return result.stdout.trim();
1512
+ }
1513
+ }
1514
+ } catch {
1515
+ }
1516
+ return null;
1517
+ }
1518
+ async checkAvailability() {
1519
+ return this.checkAvailabilitySync();
1520
+ }
1521
+ async sendCommand(command, context) {
1522
+ try {
1523
+ this.logger.info(`Sending command to Cursor: ${command}`);
1524
+ if (this.cursorPath) {
1525
+ try {
1526
+ const result = await (0, import_execa2.execa)(this.cursorPath, [
1527
+ "--command",
1528
+ "workbench.action.executeCommand",
1529
+ "--args",
1530
+ JSON.stringify({ command })
1531
+ ], {
1532
+ timeout: 3e4,
1533
+ reject: false
1534
+ });
1535
+ if (!result.failed) {
1536
+ return `Cursor executed: ${command}
1537
+ ${result.stdout}`;
1538
+ }
1539
+ } catch (cliError) {
1540
+ this.logger.debug("Cursor CLI failed, trying alternative methods");
1541
+ }
1542
+ }
1543
+ return await this.sendViaFileSystem(command, context);
1544
+ } catch (error) {
1545
+ this.logger.error(`Error executing Cursor command:`, error);
1546
+ throw new Error(`Cursor command failed: ${error instanceof Error ? error.message : String(error)}`);
1547
+ }
1548
+ }
1549
+ async sendViaFileSystem(command, context) {
1550
+ const tempDir = path3.join(os3.tmpdir(), "feishu-bridge", "cursor");
1551
+ if (!fs3.existsSync(tempDir)) {
1552
+ fs3.mkdirSync(tempDir, { recursive: true });
1553
+ }
1554
+ const instructionFile = path3.join(tempDir, `instruction_${Date.now()}.json`);
1555
+ const responseFile = path3.join(tempDir, `response_${Date.now()}.txt`);
1556
+ const instruction = {
1557
+ type: "command",
1558
+ command,
1559
+ context,
1560
+ timestamp: Date.now(),
1561
+ responseFile
1562
+ };
1563
+ fs3.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1564
+ const maxWait = 3e4;
1565
+ const checkInterval = 500;
1566
+ let waited = 0;
1567
+ while (waited < maxWait) {
1568
+ if (fs3.existsSync(responseFile)) {
1569
+ const response = fs3.readFileSync(responseFile, "utf-8");
1570
+ try {
1571
+ fs3.unlinkSync(instructionFile);
1572
+ fs3.unlinkSync(responseFile);
1573
+ } catch {
1574
+ }
1575
+ return response;
1576
+ }
1577
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1578
+ waited += checkInterval;
1579
+ }
1580
+ try {
1581
+ fs3.unlinkSync(instructionFile);
1582
+ } catch {
1583
+ }
1584
+ return `Command sent to Cursor workspace (file: ${instructionFile}). No response received within ${maxWait}ms. Make sure Cursor is running with Feishu Bridge extension.`;
1585
+ }
1586
+ async getAIResponse(prompt, context) {
1587
+ try {
1588
+ this.logger.info(`Getting AI response from Cursor for prompt: ${prompt}`);
1589
+ const tempDir = path3.join(os3.tmpdir(), "feishu-bridge", "cursor");
1590
+ if (!fs3.existsSync(tempDir)) {
1591
+ fs3.mkdirSync(tempDir, { recursive: true });
1592
+ }
1593
+ const instructionFile = path3.join(tempDir, `ai_request_${Date.now()}.json`);
1594
+ const responseFile = path3.join(tempDir, `ai_response_${Date.now()}.txt`);
1595
+ const instruction = {
1596
+ type: "ai_request",
1597
+ prompt,
1598
+ context,
1599
+ timestamp: Date.now(),
1600
+ responseFile
1601
+ };
1602
+ fs3.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1603
+ const maxWait = 6e4;
1604
+ const checkInterval = 500;
1605
+ let waited = 0;
1606
+ while (waited < maxWait) {
1607
+ if (fs3.existsSync(responseFile)) {
1608
+ const response = fs3.readFileSync(responseFile, "utf-8");
1609
+ try {
1610
+ fs3.unlinkSync(instructionFile);
1611
+ fs3.unlinkSync(responseFile);
1612
+ } catch {
1613
+ }
1614
+ return response;
1615
+ }
1616
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1617
+ waited += checkInterval;
1618
+ }
1619
+ try {
1620
+ fs3.unlinkSync(instructionFile);
1621
+ } catch {
1622
+ }
1623
+ return `AI request sent to Cursor (file: ${instructionFile}). No response received within ${maxWait}ms.`;
1624
+ } catch (error) {
1625
+ this.logger.error(`Error getting Cursor AI response:`, error);
1626
+ throw error;
1627
+ }
1628
+ }
1629
+ async generateCode(description, options) {
1630
+ const fullPrompt = this.buildCodePrompt(description, options);
1631
+ return this.getAIResponse(fullPrompt);
1632
+ }
1633
+ async runProgram(command, cwd) {
1634
+ try {
1635
+ const result = await import_execa2.execa.command(command, {
1636
+ cwd: cwd || process.cwd(),
1637
+ shell: true,
1638
+ timeout: 6e4,
1639
+ reject: false
1640
+ });
1641
+ return {
1642
+ stdout: result.stdout,
1643
+ stderr: result.stderr,
1644
+ exitCode: result.exitCode || 0,
1645
+ success: result.exitCode === 0,
1646
+ output: result.stdout,
1647
+ error: result.failed ? result.stderr : void 0
1648
+ };
1649
+ } catch (error) {
1650
+ this.logger.error(`Error running program in Cursor:`, error);
1651
+ return {
1652
+ stdout: "",
1653
+ stderr: error instanceof Error ? error.message : String(error),
1654
+ exitCode: 1,
1655
+ success: false,
1656
+ error: error instanceof Error ? error.message : String(error)
1657
+ };
1658
+ }
1659
+ }
1660
+ };
1661
+
1662
+ // src/adapters/trae.ts
1663
+ var import_execa3 = require("execa");
1664
+ var fs4 = __toESM(require("fs"));
1665
+ var path4 = __toESM(require("path"));
1666
+ var os4 = __toESM(require("os"));
1667
+ var TraeAdapter = class extends BaseIDEAdapter {
1668
+ constructor() {
1669
+ super(...arguments);
1670
+ this.type = "trae";
1671
+ this.traePath = null;
1672
+ }
1673
+ checkAvailabilitySync() {
1674
+ if (process.env.TRAE_SESSION || process.env.TRAE_PID) {
1675
+ return true;
1676
+ }
1677
+ this.traePath = this.findTraeExecutable();
1678
+ return !!this.traePath;
1679
+ }
1680
+ findTraeExecutable() {
1681
+ const platform5 = os4.platform();
1682
+ const possiblePaths = [];
1683
+ if (platform5 === "darwin") {
1684
+ possiblePaths.push(
1685
+ "/Applications/Trae.app/Contents/MacOS/Trae",
1686
+ "/usr/local/bin/trae",
1687
+ path4.join(os4.homedir(), "Applications/Trae.app/Contents/MacOS/Trae")
1688
+ );
1689
+ } else if (platform5 === "win32") {
1690
+ possiblePaths.push(
1691
+ path4.join(process.env.LOCALAPPDATA || "", "Programs", "Trae", "Trae.exe"),
1692
+ "C:\\Program Files\\Trae\\Trae.exe"
1693
+ );
1694
+ } else {
1695
+ possiblePaths.push(
1696
+ "/usr/bin/trae",
1697
+ "/usr/local/bin/trae",
1698
+ "/opt/trae/trae",
1699
+ path4.join(os4.homedir(), ".local/bin/trae")
1700
+ );
1701
+ }
1702
+ for (const p of possiblePaths) {
1703
+ if (fs4.existsSync(p)) {
1704
+ return p;
1705
+ }
1706
+ }
1707
+ try {
1708
+ if (platform5 === "win32") {
1709
+ const result = import_execa3.execa.sync("where", ["trae"], { reject: false });
1710
+ if (result.stdout) {
1711
+ const lines = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l);
1712
+ if (lines.length > 0) return lines[0];
1713
+ }
1714
+ } else {
1715
+ const result = import_execa3.execa.sync("which", ["trae"], { reject: false });
1716
+ if (result.stdout) {
1717
+ return result.stdout.trim();
1718
+ }
1719
+ }
1720
+ } catch {
1721
+ }
1722
+ return null;
1723
+ }
1724
+ async checkAvailability() {
1725
+ return this.checkAvailabilitySync();
1726
+ }
1727
+ async sendCommand(command, context) {
1728
+ try {
1729
+ this.logger.info(`Sending command to Trae: ${command}`);
1730
+ if (this.traePath) {
1731
+ try {
1732
+ const result = await (0, import_execa3.execa)(this.traePath, [
1733
+ "--executeCommand",
1734
+ command
1735
+ ], {
1736
+ timeout: 3e4,
1737
+ reject: false,
1738
+ cwd: context?.workspaceRoot
1739
+ });
1740
+ if (!result.failed) {
1741
+ return result.stdout || "Command executed";
1742
+ }
1743
+ } catch (cliError) {
1744
+ this.logger.debug("Trae CLI failed, using file system");
1745
+ }
1746
+ }
1747
+ return await this.sendViaFileSystem(command, context);
1748
+ } catch (error) {
1749
+ this.logger.error(`Error executing Trae command:`, error);
1750
+ throw new Error(`Trae command failed: ${error instanceof Error ? error.message : String(error)}`);
1751
+ }
1752
+ }
1753
+ async sendViaFileSystem(command, context) {
1754
+ const tempDir = path4.join(os4.tmpdir(), "feishu-bridge", "trae");
1755
+ if (!fs4.existsSync(tempDir)) {
1756
+ fs4.mkdirSync(tempDir, { recursive: true });
1757
+ }
1758
+ const instructionFile = path4.join(tempDir, `cmd_${Date.now()}.json`);
1759
+ const responseFile = path4.join(tempDir, `resp_${Date.now()}.txt`);
1760
+ const instruction = {
1761
+ type: "command",
1762
+ command,
1763
+ context,
1764
+ timestamp: Date.now(),
1765
+ responseFile
1766
+ };
1767
+ fs4.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1768
+ const maxWait = 3e4;
1769
+ const checkInterval = 500;
1770
+ let waited = 0;
1771
+ while (waited < maxWait) {
1772
+ if (fs4.existsSync(responseFile)) {
1773
+ const response = fs4.readFileSync(responseFile, "utf-8");
1774
+ try {
1775
+ fs4.unlinkSync(instructionFile);
1776
+ fs4.unlinkSync(responseFile);
1777
+ } catch {
1778
+ }
1779
+ return response;
1780
+ }
1781
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1782
+ waited += checkInterval;
1783
+ }
1784
+ try {
1785
+ fs4.unlinkSync(instructionFile);
1786
+ } catch {
1787
+ }
1788
+ return `Command queued for Trae. File: ${instructionFile}`;
1789
+ }
1790
+ async getAIResponse(prompt, context) {
1791
+ try {
1792
+ this.logger.info(`Getting AI response from Trae for prompt: ${prompt}`);
1793
+ if (this.traePath) {
1794
+ try {
1795
+ const result = await (0, import_execa3.execa)(this.traePath, [
1796
+ "--ai",
1797
+ prompt
1798
+ ], {
1799
+ timeout: 6e4,
1800
+ reject: false,
1801
+ cwd: context?.workspaceRoot
1802
+ });
1803
+ if (!result.failed && result.stdout) {
1804
+ return result.stdout;
1805
+ }
1806
+ } catch (cliError) {
1807
+ this.logger.debug("Trae AI CLI failed, using file system");
1808
+ }
1809
+ }
1810
+ return await this.sendAIRequestViaFileSystem(prompt, context);
1811
+ } catch (error) {
1812
+ this.logger.error(`Error getting Trae AI response:`, error);
1813
+ throw error;
1814
+ }
1815
+ }
1816
+ async sendAIRequestViaFileSystem(prompt, context) {
1817
+ const tempDir = path4.join(os4.tmpdir(), "feishu-bridge", "trae");
1818
+ if (!fs4.existsSync(tempDir)) {
1819
+ fs4.mkdirSync(tempDir, { recursive: true });
1820
+ }
1821
+ const instructionFile = path4.join(tempDir, `ai_${Date.now()}.json`);
1822
+ const responseFile = path4.join(tempDir, `ai_resp_${Date.now()}.txt`);
1823
+ const instruction = {
1824
+ type: "ai_request",
1825
+ prompt,
1826
+ context,
1827
+ timestamp: Date.now(),
1828
+ responseFile
1829
+ };
1830
+ fs4.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1831
+ const maxWait = 12e4;
1832
+ const checkInterval = 1e3;
1833
+ let waited = 0;
1834
+ while (waited < maxWait) {
1835
+ if (fs4.existsSync(responseFile)) {
1836
+ const response = fs4.readFileSync(responseFile, "utf-8");
1837
+ try {
1838
+ fs4.unlinkSync(instructionFile);
1839
+ fs4.unlinkSync(responseFile);
1840
+ } catch {
1841
+ }
1842
+ return response;
1843
+ }
1844
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1845
+ waited += checkInterval;
1846
+ }
1847
+ try {
1848
+ fs4.unlinkSync(instructionFile);
1849
+ } catch {
1850
+ }
1851
+ return `AI request queued for Trae. File: ${instructionFile}`;
1852
+ }
1853
+ async generateCode(description, options) {
1854
+ const fullPrompt = this.buildCodePrompt(description, options);
1855
+ return this.getAIResponse(fullPrompt);
1856
+ }
1857
+ async runProgram(command, cwd) {
1858
+ try {
1859
+ const result = await import_execa3.execa.command(command, {
1860
+ cwd: cwd || process.cwd(),
1861
+ shell: true,
1862
+ timeout: 6e4,
1863
+ reject: false
1864
+ });
1865
+ return {
1866
+ stdout: result.stdout,
1867
+ stderr: result.stderr,
1868
+ exitCode: result.exitCode || 0,
1869
+ success: result.exitCode === 0,
1870
+ output: result.stdout,
1871
+ error: result.failed ? result.stderr : void 0
1872
+ };
1873
+ } catch (error) {
1874
+ this.logger.error(`Error running program in Trae:`, error);
1875
+ return {
1876
+ stdout: "",
1877
+ stderr: error instanceof Error ? error.message : String(error),
1878
+ exitCode: 1,
1879
+ success: false,
1880
+ error: error instanceof Error ? error.message : String(error)
1881
+ };
1882
+ }
1883
+ }
1884
+ };
1885
+
1886
+ // src/adapters/antigravity.ts
1887
+ var import_execa4 = require("execa");
1888
+ var fs5 = __toESM(require("fs"));
1889
+ var path5 = __toESM(require("path"));
1890
+ var os5 = __toESM(require("os"));
1891
+ var AntigravityAdapter = class extends BaseIDEAdapter {
1892
+ constructor() {
1893
+ super(...arguments);
1894
+ this.type = "antigravity";
1895
+ this.antigravityPath = null;
1896
+ }
1897
+ checkAvailabilitySync() {
1898
+ if (process.env.ANTIGRAVITY_SESSION) {
1899
+ return true;
1900
+ }
1901
+ this.antigravityPath = this.findAntigravityExecutable();
1902
+ return !!this.antigravityPath;
1903
+ }
1904
+ findAntigravityExecutable() {
1905
+ try {
1906
+ const result = import_execa4.execa.sync("which", ["antigravity"], { reject: false });
1907
+ if (result.stdout) return result.stdout.trim();
1908
+ } catch {
1909
+ }
1910
+ const possiblePaths = [
1911
+ "/usr/local/bin/antigravity",
1912
+ "/usr/bin/antigravity",
1913
+ path5.join(os5.homedir(), ".local/bin/antigravity")
1914
+ ];
1915
+ for (const p of possiblePaths) {
1916
+ if (fs5.existsSync(p)) return p;
1917
+ }
1918
+ return null;
1919
+ }
1920
+ async checkAvailability() {
1921
+ return this.checkAvailabilitySync();
1922
+ }
1923
+ async sendCommand(command, context) {
1924
+ try {
1925
+ this.logger.info(`Sending command to Antigravity: ${command}`);
1926
+ if (this.antigravityPath) {
1927
+ try {
1928
+ const result = await (0, import_execa4.execa)(this.antigravityPath, ["execute", command], {
1929
+ timeout: 3e4,
1930
+ reject: false,
1931
+ cwd: context?.workspaceRoot
1932
+ });
1933
+ if (!result.failed) {
1934
+ return result.stdout || "Command executed";
1935
+ }
1936
+ } catch (cliError) {
1937
+ this.logger.debug("Antigravity CLI failed, using file system");
1938
+ }
1939
+ }
1940
+ return await this.sendViaFileSystem(command, context);
1941
+ } catch (error) {
1942
+ this.logger.error(`Error executing Antigravity command:`, error);
1943
+ throw new Error(`Antigravity command failed: ${error instanceof Error ? error.message : String(error)}`);
1944
+ }
1945
+ }
1946
+ async sendViaFileSystem(command, context) {
1947
+ const tempDir = path5.join(os5.tmpdir(), "feishu-bridge", "antigravity");
1948
+ if (!fs5.existsSync(tempDir)) {
1949
+ fs5.mkdirSync(tempDir, { recursive: true });
1950
+ }
1951
+ const instructionFile = path5.join(tempDir, `cmd_${Date.now()}.json`);
1952
+ const responseFile = path5.join(tempDir, `resp_${Date.now()}.txt`);
1953
+ const instruction = {
1954
+ type: "command",
1955
+ command,
1956
+ context,
1957
+ timestamp: Date.now(),
1958
+ responseFile
1959
+ };
1960
+ fs5.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
1961
+ const maxWait = 3e4;
1962
+ const checkInterval = 500;
1963
+ let waited = 0;
1964
+ while (waited < maxWait) {
1965
+ if (fs5.existsSync(responseFile)) {
1966
+ const response = fs5.readFileSync(responseFile, "utf-8");
1967
+ try {
1968
+ fs5.unlinkSync(instructionFile);
1969
+ fs5.unlinkSync(responseFile);
1970
+ } catch {
1971
+ }
1972
+ return response;
1973
+ }
1974
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
1975
+ waited += checkInterval;
1976
+ }
1977
+ try {
1978
+ fs5.unlinkSync(instructionFile);
1979
+ } catch {
1980
+ }
1981
+ return `Command queued for Antigravity. File: ${instructionFile}`;
1982
+ }
1983
+ async getAIResponse(prompt, context) {
1984
+ try {
1985
+ this.logger.info(`Getting AI response from Antigravity for prompt: ${prompt}`);
1986
+ if (this.antigravityPath) {
1987
+ try {
1988
+ const result = await (0, import_execa4.execa)(this.antigravityPath, ["ask", prompt], {
1989
+ timeout: 6e4,
1990
+ reject: false,
1991
+ cwd: context?.workspaceRoot
1992
+ });
1993
+ if (!result.failed && result.stdout) {
1994
+ return result.stdout;
1995
+ }
1996
+ } catch (cliError) {
1997
+ this.logger.debug("Antigravity AI CLI failed, using file system");
1998
+ }
1999
+ }
2000
+ return await this.sendAIRequestViaFileSystem(prompt, context);
2001
+ } catch (error) {
2002
+ this.logger.error(`Error getting Antigravity AI response:`, error);
2003
+ throw error;
2004
+ }
2005
+ }
2006
+ async sendAIRequestViaFileSystem(prompt, context) {
2007
+ const tempDir = path5.join(os5.tmpdir(), "feishu-bridge", "antigravity");
2008
+ if (!fs5.existsSync(tempDir)) {
2009
+ fs5.mkdirSync(tempDir, { recursive: true });
2010
+ }
2011
+ const instructionFile = path5.join(tempDir, `ai_${Date.now()}.json`);
2012
+ const responseFile = path5.join(tempDir, `ai_resp_${Date.now()}.txt`);
2013
+ const instruction = {
2014
+ type: "ai_request",
2015
+ prompt,
2016
+ context,
2017
+ timestamp: Date.now(),
2018
+ responseFile
2019
+ };
2020
+ fs5.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
2021
+ const maxWait = 12e4;
2022
+ const checkInterval = 1e3;
2023
+ let waited = 0;
2024
+ while (waited < maxWait) {
2025
+ if (fs5.existsSync(responseFile)) {
2026
+ const response = fs5.readFileSync(responseFile, "utf-8");
2027
+ try {
2028
+ fs5.unlinkSync(instructionFile);
2029
+ fs5.unlinkSync(responseFile);
2030
+ } catch {
2031
+ }
2032
+ return response;
2033
+ }
2034
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
2035
+ waited += checkInterval;
2036
+ }
2037
+ try {
2038
+ fs5.unlinkSync(instructionFile);
2039
+ } catch {
2040
+ }
2041
+ return `AI request queued for Antigravity. File: ${instructionFile}`;
2042
+ }
2043
+ async generateCode(description, options) {
2044
+ const fullPrompt = this.buildCodePrompt(description, options);
2045
+ return this.getAIResponse(fullPrompt);
2046
+ }
2047
+ async runProgram(command, cwd) {
2048
+ try {
2049
+ const result = await import_execa4.execa.command(command, {
2050
+ cwd: cwd || process.cwd(),
2051
+ shell: true,
2052
+ timeout: 6e4,
2053
+ reject: false
2054
+ });
2055
+ return {
2056
+ stdout: result.stdout,
2057
+ stderr: result.stderr,
2058
+ exitCode: result.exitCode || 0,
2059
+ success: result.exitCode === 0,
2060
+ output: result.stdout,
2061
+ error: result.failed ? result.stderr : void 0
2062
+ };
2063
+ } catch (error) {
2064
+ this.logger.error(`Error running program in Antigravity:`, error);
2065
+ return {
2066
+ stdout: "",
2067
+ stderr: error instanceof Error ? error.message : String(error),
2068
+ exitCode: 1,
2069
+ success: false,
2070
+ error: error instanceof Error ? error.message : String(error)
2071
+ };
2072
+ }
2073
+ }
2074
+ };
2075
+
2076
+ // src/adapters/kiro.ts
2077
+ var import_execa5 = require("execa");
2078
+ var fs6 = __toESM(require("fs"));
2079
+ var path6 = __toESM(require("path"));
2080
+ var os6 = __toESM(require("os"));
2081
+ var KiroAdapter = class extends BaseIDEAdapter {
2082
+ constructor() {
2083
+ super(...arguments);
2084
+ this.type = "kiro";
2085
+ this.kiroPath = null;
2086
+ }
2087
+ checkAvailabilitySync() {
2088
+ if (process.env.KIRO_SESSION) {
2089
+ return true;
2090
+ }
2091
+ this.kiroPath = this.findKiroExecutable();
2092
+ return !!this.kiroPath;
2093
+ }
2094
+ findKiroExecutable() {
2095
+ try {
2096
+ const result = import_execa5.execa.sync("which", ["kiro"], { reject: false });
2097
+ if (result.stdout) return result.stdout.trim();
2098
+ } catch {
2099
+ }
2100
+ const possiblePaths = [
2101
+ "/usr/local/bin/kiro",
2102
+ "/usr/bin/kiro",
2103
+ path6.join(os6.homedir(), ".local/bin/kiro")
2104
+ ];
2105
+ for (const p of possiblePaths) {
2106
+ if (fs6.existsSync(p)) return p;
2107
+ }
2108
+ return null;
2109
+ }
2110
+ async checkAvailability() {
2111
+ return this.checkAvailabilitySync();
2112
+ }
2113
+ async sendCommand(command, context) {
2114
+ try {
2115
+ this.logger.info(`Sending command to Kiro: ${command}`);
2116
+ if (this.kiroPath) {
2117
+ try {
2118
+ const result = await (0, import_execa5.execa)(this.kiroPath, ["execute", command], {
2119
+ timeout: 3e4,
2120
+ reject: false,
2121
+ cwd: context?.workspaceRoot
2122
+ });
2123
+ if (!result.failed) {
2124
+ return result.stdout || "Command executed";
2125
+ }
2126
+ } catch (cliError) {
2127
+ this.logger.debug("Kiro CLI failed, using file system");
2128
+ }
2129
+ }
2130
+ return await this.sendViaFileSystem(command, context);
2131
+ } catch (error) {
2132
+ this.logger.error(`Error executing Kiro command:`, error);
2133
+ throw new Error(`Kiro command failed: ${error instanceof Error ? error.message : String(error)}`);
2134
+ }
2135
+ }
2136
+ async sendViaFileSystem(command, context) {
2137
+ const tempDir = path6.join(os6.tmpdir(), "feishu-bridge", "kiro");
2138
+ if (!fs6.existsSync(tempDir)) {
2139
+ fs6.mkdirSync(tempDir, { recursive: true });
2140
+ }
2141
+ const instructionFile = path6.join(tempDir, `cmd_${Date.now()}.json`);
2142
+ const responseFile = path6.join(tempDir, `resp_${Date.now()}.txt`);
2143
+ const instruction = {
2144
+ type: "command",
2145
+ command,
2146
+ context,
2147
+ timestamp: Date.now(),
2148
+ responseFile
2149
+ };
2150
+ fs6.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
2151
+ const maxWait = 3e4;
2152
+ const checkInterval = 500;
2153
+ let waited = 0;
2154
+ while (waited < maxWait) {
2155
+ if (fs6.existsSync(responseFile)) {
2156
+ const response = fs6.readFileSync(responseFile, "utf-8");
2157
+ try {
2158
+ fs6.unlinkSync(instructionFile);
2159
+ fs6.unlinkSync(responseFile);
2160
+ } catch {
2161
+ }
2162
+ return response;
2163
+ }
2164
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
2165
+ waited += checkInterval;
2166
+ }
2167
+ try {
2168
+ fs6.unlinkSync(instructionFile);
2169
+ } catch {
2170
+ }
2171
+ return `Command queued for Kiro. File: ${instructionFile}`;
2172
+ }
2173
+ async getAIResponse(prompt, context) {
2174
+ try {
2175
+ this.logger.info(`Getting AI response from Kiro for prompt: ${prompt}`);
2176
+ if (this.kiroPath) {
2177
+ try {
2178
+ const result = await (0, import_execa5.execa)(this.kiroPath, ["ask", prompt], {
2179
+ timeout: 6e4,
2180
+ reject: false,
2181
+ cwd: context?.workspaceRoot
2182
+ });
2183
+ if (!result.failed && result.stdout) {
2184
+ return result.stdout;
2185
+ }
2186
+ } catch (cliError) {
2187
+ this.logger.debug("Kiro AI CLI failed, using file system");
2188
+ }
2189
+ }
2190
+ return await this.sendAIRequestViaFileSystem(prompt, context);
2191
+ } catch (error) {
2192
+ this.logger.error(`Error getting Kiro AI response:`, error);
2193
+ throw error;
2194
+ }
2195
+ }
2196
+ async sendAIRequestViaFileSystem(prompt, context) {
2197
+ const tempDir = path6.join(os6.tmpdir(), "feishu-bridge", "kiro");
2198
+ if (!fs6.existsSync(tempDir)) {
2199
+ fs6.mkdirSync(tempDir, { recursive: true });
2200
+ }
2201
+ const instructionFile = path6.join(tempDir, `ai_${Date.now()}.json`);
2202
+ const responseFile = path6.join(tempDir, `ai_resp_${Date.now()}.txt`);
2203
+ const instruction = {
2204
+ type: "ai_request",
2205
+ prompt,
2206
+ context,
2207
+ timestamp: Date.now(),
2208
+ responseFile
2209
+ };
2210
+ fs6.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
2211
+ const maxWait = 12e4;
2212
+ const checkInterval = 1e3;
2213
+ let waited = 0;
2214
+ while (waited < maxWait) {
2215
+ if (fs6.existsSync(responseFile)) {
2216
+ const response = fs6.readFileSync(responseFile, "utf-8");
2217
+ try {
2218
+ fs6.unlinkSync(instructionFile);
2219
+ fs6.unlinkSync(responseFile);
2220
+ } catch {
2221
+ }
2222
+ return response;
2223
+ }
2224
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
2225
+ waited += checkInterval;
2226
+ }
2227
+ try {
2228
+ fs6.unlinkSync(instructionFile);
2229
+ } catch {
2230
+ }
2231
+ return `AI request queued for Kiro. File: ${instructionFile}`;
2232
+ }
2233
+ async generateCode(description, options) {
2234
+ const fullPrompt = this.buildCodePrompt(description, options);
2235
+ return this.getAIResponse(fullPrompt);
2236
+ }
2237
+ async runProgram(command, cwd) {
2238
+ try {
2239
+ const result = await import_execa5.execa.command(command, {
2240
+ cwd: cwd || process.cwd(),
2241
+ shell: true,
2242
+ timeout: 6e4,
2243
+ reject: false
2244
+ });
2245
+ return {
2246
+ stdout: result.stdout,
2247
+ stderr: result.stderr,
2248
+ exitCode: result.exitCode || 0,
2249
+ success: result.exitCode === 0,
2250
+ output: result.stdout,
2251
+ error: result.failed ? result.stderr : void 0
2252
+ };
2253
+ } catch (error) {
2254
+ this.logger.error(`Error running program in Kiro:`, error);
2255
+ return {
2256
+ stdout: "",
2257
+ stderr: error instanceof Error ? error.message : String(error),
2258
+ exitCode: 1,
2259
+ success: false,
2260
+ error: error instanceof Error ? error.message : String(error)
2261
+ };
2262
+ }
2263
+ }
2264
+ };
2265
+
2266
+ // src/adapters/opencode.ts
2267
+ var net = __toESM(require("net"));
2268
+ var fs7 = __toESM(require("fs"));
2269
+ var os7 = __toESM(require("os"));
2270
+ var path7 = __toESM(require("path"));
2271
+ var OpenCodeAdapter = class extends BaseIDEAdapter {
2272
+ constructor() {
2273
+ super(...arguments);
2274
+ this.type = "opencode";
2275
+ }
2276
+ checkAvailabilitySync() {
2277
+ return !!process.env.OPENCODE_SESSION || fs7.existsSync(path7.join(os7.homedir(), ".opencode", "session")) || this.checkOpenCodeProcess();
2278
+ }
2279
+ checkOpenCodeProcess() {
2280
+ try {
2281
+ return true;
2282
+ } catch {
2283
+ return false;
2284
+ }
2285
+ }
2286
+ async checkAvailability() {
2287
+ return this.checkAvailabilitySync();
2288
+ }
2289
+ async sendCommand(command, context) {
2290
+ try {
2291
+ this.logger.info(`Sending command to OpenCode: ${command}`);
2292
+ const socketPath = this.getSocketPath();
2293
+ if (socketPath && fs7.existsSync(socketPath)) {
2294
+ return await this.sendViaSocket({
2295
+ type: "command",
2296
+ command,
2297
+ sessionId: this.sessionId,
2298
+ context
2299
+ });
2300
+ } else {
2301
+ return `OpenCode processed command: ${command}`;
2302
+ }
2303
+ } catch (error) {
2304
+ this.logger.error(`Error executing OpenCode command:`, error);
2305
+ throw error;
2306
+ }
2307
+ }
2308
+ getSocketPath() {
2309
+ const possiblePaths = [
2310
+ path7.join(os7.tmpdir(), "opencode.sock"),
2311
+ path7.join(os7.homedir(), ".opencode", "socket"),
2312
+ process.env.OPENCODE_SOCKET || ""
2313
+ ];
2314
+ for (const socketPath of possiblePaths) {
2315
+ if (socketPath && fs7.existsSync(socketPath)) {
2316
+ return socketPath;
2317
+ }
2318
+ }
2319
+ return null;
2320
+ }
2321
+ sendViaSocket(data) {
2322
+ return new Promise((resolve, reject) => {
2323
+ const socketPath = this.getSocketPath();
2324
+ if (!socketPath) {
2325
+ reject(new Error("No OpenCode socket available"));
2326
+ return;
2327
+ }
2328
+ const client = net.createConnection(socketPath);
2329
+ let response = "";
2330
+ client.on("data", (data2) => {
2331
+ response += data2.toString();
2332
+ });
2333
+ client.on("end", () => {
2334
+ try {
2335
+ const parsed = JSON.parse(response);
2336
+ resolve(parsed.result || response);
2337
+ } catch (e) {
2338
+ resolve(response);
2339
+ }
2340
+ });
2341
+ client.on("error", (err) => {
2342
+ reject(err);
2343
+ });
2344
+ client.write(JSON.stringify(data));
2345
+ setTimeout(() => {
2346
+ client.destroy();
2347
+ reject(new Error("OpenCode command timeout"));
2348
+ }, 3e4);
2349
+ });
2350
+ }
2351
+ async getAIResponse(prompt, context) {
2352
+ try {
2353
+ this.logger.info(`Getting AI response from OpenCode for prompt: ${prompt}`);
2354
+ return await this.sendCommand(`/ask ${prompt}`, context);
2355
+ } catch (error) {
2356
+ this.logger.error(`Error getting OpenCode AI response:`, error);
2357
+ throw error;
2358
+ }
2359
+ }
2360
+ async generateCode(description, options) {
2361
+ const fullPrompt = this.buildCodePrompt(description, options);
2362
+ return this.getAIResponse(fullPrompt);
2363
+ }
2364
+ async runProgram(command, cwd) {
2365
+ try {
2366
+ const result = await this.sendCommand(`/run ${command}`, { workspaceRoot: cwd });
2367
+ return {
2368
+ stdout: result,
2369
+ stderr: "",
2370
+ exitCode: 0,
2371
+ success: true,
2372
+ output: result
2373
+ };
2374
+ } catch (error) {
2375
+ this.logger.error(`Error running program in OpenCode:`, error);
2376
+ return {
2377
+ stdout: "",
2378
+ stderr: error instanceof Error ? error.message : String(error),
2379
+ exitCode: 1,
2380
+ success: false,
2381
+ error: error instanceof Error ? error.message : String(error)
2382
+ };
2383
+ }
2384
+ }
2385
+ };
2386
+
2387
+ // src/adapters/claude-code.ts
2388
+ var import_execa6 = require("execa");
2389
+ var fs8 = __toESM(require("fs"));
2390
+ var path8 = __toESM(require("path"));
2391
+ var os8 = __toESM(require("os"));
2392
+ var ClaudeCodeAdapter = class extends BaseIDEAdapter {
2393
+ constructor() {
2394
+ super(...arguments);
2395
+ this.type = "claude";
2396
+ this.claudePath = null;
2397
+ }
2398
+ checkAvailabilitySync() {
2399
+ if (process.env.CLAUDE_CODE_SESSION) {
2400
+ return true;
2401
+ }
2402
+ this.claudePath = this.findClaudeExecutable();
2403
+ return !!this.claudePath;
2404
+ }
2405
+ findClaudeExecutable() {
2406
+ const platform5 = os8.platform();
2407
+ try {
2408
+ const result = import_execa6.execa.sync("claude", ["--version"], { reject: false });
2409
+ if (result.exitCode === 0) {
2410
+ return "claude";
2411
+ }
2412
+ } catch {
2413
+ }
2414
+ const possiblePaths = [];
2415
+ if (platform5 === "darwin") {
2416
+ possiblePaths.push(
2417
+ "/usr/local/bin/claude",
2418
+ "/opt/homebrew/bin/claude",
2419
+ path8.join(os8.homedir(), ".local/bin/claude")
2420
+ );
2421
+ } else if (platform5 === "win32") {
2422
+ possiblePaths.push(
2423
+ path8.join(os8.homedir(), "AppData", "Local", "Programs", "claude", "claude.exe"),
2424
+ "C:\\Program Files\\Claude\\claude.exe"
2425
+ );
2426
+ } else {
2427
+ possiblePaths.push(
2428
+ "/usr/bin/claude",
2429
+ "/usr/local/bin/claude",
2430
+ path8.join(os8.homedir(), ".local/bin/claude"),
2431
+ path8.join(os8.homedir(), ".npm-global/bin/claude")
2432
+ );
2433
+ }
2434
+ for (const p of possiblePaths) {
2435
+ if (fs8.existsSync(p)) {
2436
+ return p;
2437
+ }
2438
+ }
2439
+ return null;
2440
+ }
2441
+ async checkAvailability() {
2442
+ return this.checkAvailabilitySync();
2443
+ }
2444
+ async sendCommand(command, context) {
2445
+ try {
2446
+ this.logger.info(`Sending command to Claude Code: ${command}`);
2447
+ if (this.claudePath) {
2448
+ try {
2449
+ const result = await (0, import_execa6.execa)(this.claudePath, [
2450
+ "execute",
2451
+ "--command",
2452
+ command
2453
+ ], {
2454
+ timeout: 3e4,
2455
+ reject: false,
2456
+ cwd: context?.workspaceRoot
2457
+ });
2458
+ if (!result.failed) {
2459
+ return result.stdout || "Command executed successfully";
2460
+ }
2461
+ } catch (cliError) {
2462
+ this.logger.debug("Claude CLI failed, trying file system method");
2463
+ }
2464
+ }
2465
+ return await this.sendViaFileSystem(command, context);
2466
+ } catch (error) {
2467
+ this.logger.error(`Error executing Claude Code command:`, error);
2468
+ throw new Error(`Claude Code command failed: ${error instanceof Error ? error.message : String(error)}`);
2469
+ }
2470
+ }
2471
+ async sendViaFileSystem(command, context) {
2472
+ const tempDir = path8.join(os8.tmpdir(), "feishu-bridge", "claude");
2473
+ if (!fs8.existsSync(tempDir)) {
2474
+ fs8.mkdirSync(tempDir, { recursive: true });
2475
+ }
2476
+ const instructionFile = path8.join(tempDir, `cmd_${Date.now()}.json`);
2477
+ const responseFile = path8.join(tempDir, `resp_${Date.now()}.txt`);
2478
+ const instruction = {
2479
+ type: "command",
2480
+ command,
2481
+ context,
2482
+ timestamp: Date.now(),
2483
+ responseFile
2484
+ };
2485
+ fs8.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
2486
+ const maxWait = 3e4;
2487
+ const checkInterval = 500;
2488
+ let waited = 0;
2489
+ while (waited < maxWait) {
2490
+ if (fs8.existsSync(responseFile)) {
2491
+ const response = fs8.readFileSync(responseFile, "utf-8");
2492
+ try {
2493
+ fs8.unlinkSync(instructionFile);
2494
+ fs8.unlinkSync(responseFile);
2495
+ } catch {
2496
+ }
2497
+ return response;
2498
+ }
2499
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
2500
+ waited += checkInterval;
2501
+ }
2502
+ try {
2503
+ fs8.unlinkSync(instructionFile);
2504
+ } catch {
2505
+ }
2506
+ return `Command queued for Claude Code. File: ${instructionFile}`;
2507
+ }
2508
+ async getAIResponse(prompt, context) {
2509
+ try {
2510
+ this.logger.info(`Getting AI response from Claude Code for prompt: ${prompt}`);
2511
+ if (this.claudePath) {
2512
+ try {
2513
+ const result = await (0, import_execa6.execa)(this.claudePath, [
2514
+ "ask",
2515
+ "--prompt",
2516
+ prompt
2517
+ ], {
2518
+ timeout: 6e4,
2519
+ reject: false,
2520
+ cwd: context?.workspaceRoot
2521
+ });
2522
+ if (!result.failed && result.stdout) {
2523
+ return result.stdout;
2524
+ }
2525
+ } catch (cliError) {
2526
+ this.logger.debug("Claude ask CLI failed, using file system");
2527
+ }
2528
+ }
2529
+ return await this.sendAIRequestViaFileSystem(prompt, context);
2530
+ } catch (error) {
2531
+ this.logger.error(`Error getting Claude Code AI response:`, error);
2532
+ throw error;
2533
+ }
2534
+ }
2535
+ async sendAIRequestViaFileSystem(prompt, context) {
2536
+ const tempDir = path8.join(os8.tmpdir(), "feishu-bridge", "claude");
2537
+ if (!fs8.existsSync(tempDir)) {
2538
+ fs8.mkdirSync(tempDir, { recursive: true });
2539
+ }
2540
+ const instructionFile = path8.join(tempDir, `ai_${Date.now()}.json`);
2541
+ const responseFile = path8.join(tempDir, `ai_resp_${Date.now()}.txt`);
2542
+ const instruction = {
2543
+ type: "ai_request",
2544
+ prompt,
2545
+ context,
2546
+ timestamp: Date.now(),
2547
+ responseFile
2548
+ };
2549
+ fs8.writeFileSync(instructionFile, JSON.stringify(instruction, null, 2));
2550
+ const maxWait = 12e4;
2551
+ const checkInterval = 1e3;
2552
+ let waited = 0;
2553
+ while (waited < maxWait) {
2554
+ if (fs8.existsSync(responseFile)) {
2555
+ const response = fs8.readFileSync(responseFile, "utf-8");
2556
+ try {
2557
+ fs8.unlinkSync(instructionFile);
2558
+ fs8.unlinkSync(responseFile);
2559
+ } catch {
2560
+ }
2561
+ return response;
2562
+ }
2563
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
2564
+ waited += checkInterval;
2565
+ }
2566
+ try {
2567
+ fs8.unlinkSync(instructionFile);
2568
+ } catch {
2569
+ }
2570
+ return `AI request queued. File: ${instructionFile}`;
2571
+ }
2572
+ async generateCode(description, options) {
2573
+ const fullPrompt = this.buildCodePrompt(description, options);
2574
+ return this.getAIResponse(fullPrompt);
2575
+ }
2576
+ async runProgram(command, cwd) {
2577
+ try {
2578
+ const result = await import_execa6.execa.command(command, {
2579
+ cwd: cwd || process.cwd(),
2580
+ shell: true,
2581
+ timeout: 6e4,
2582
+ reject: false
2583
+ });
2584
+ return {
2585
+ stdout: result.stdout,
2586
+ stderr: result.stderr,
2587
+ exitCode: result.exitCode || 0,
2588
+ success: result.exitCode === 0,
2589
+ output: result.stdout,
2590
+ error: result.failed ? result.stderr : void 0
2591
+ };
2592
+ } catch (error) {
2593
+ this.logger.error(`Error running program in Claude Code:`, error);
2594
+ return {
2595
+ stdout: "",
2596
+ stderr: error instanceof Error ? error.message : String(error),
2597
+ exitCode: 1,
2598
+ success: false,
2599
+ error: error instanceof Error ? error.message : String(error)
2600
+ };
2601
+ }
2602
+ }
2603
+ };
2604
+
2605
+ // src/core/bridge.ts
2606
+ init_config();
2607
+
2608
+ // src/core/health.ts
2609
+ init_config();
2610
+ var HealthMonitor = class {
2611
+ constructor(server, sessionManager, adapters) {
2612
+ this.server = server;
2613
+ this.sessionManager = sessionManager;
2614
+ this.adapters = adapters;
2615
+ this.requestCount = { total: 0, successful: 0, failed: 0 };
2616
+ this.startTime = Date.now();
2617
+ this.setupRoutes();
2618
+ }
2619
+ /**
2620
+ * 设置健康检查路由
2621
+ */
2622
+ setupRoutes() {
2623
+ this.server.get("/health", this.handleHealth.bind(this));
2624
+ this.server.get("/health/ready", this.handleReadiness.bind(this));
2625
+ this.server.get("/health/live", this.handleLiveness.bind(this));
2626
+ this.server.get("/metrics", this.handleMetrics.bind(this));
2627
+ }
2628
+ /**
2629
+ * 处理健康检查请求
2630
+ */
2631
+ async handleHealth(_request, reply) {
2632
+ const status = await this.getHealthStatus();
2633
+ const statusCode = status.status === "healthy" ? 200 : status.status === "degraded" ? 200 : 503;
2634
+ reply.status(statusCode).send(status);
2635
+ }
2636
+ /**
2637
+ * 处理就绪检查
2638
+ */
2639
+ async handleReadiness(_request, reply) {
2640
+ const config = ConfigManager.getInstance().get();
2641
+ const isReady = config.feishu.appId && config.feishu.appSecret;
2642
+ if (isReady) {
2643
+ reply.status(200).send({
2644
+ ready: true,
2645
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2646
+ });
2647
+ } else {
2648
+ reply.status(503).send({
2649
+ ready: false,
2650
+ message: "Configuration incomplete",
2651
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2652
+ });
2653
+ }
2654
+ }
2655
+ /**
2656
+ * 处理存活检查
2657
+ */
2658
+ async handleLiveness(_request, reply) {
2659
+ reply.status(200).send({
2660
+ alive: true,
2661
+ uptime: process.uptime(),
2662
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2663
+ });
2664
+ }
2665
+ /**
2666
+ * 处理指标请求
2667
+ */
2668
+ async handleMetrics(_request, reply) {
2669
+ const metrics = await this.getMetrics();
2670
+ const prometheusFormat = this.toPrometheusFormat(metrics);
2671
+ reply.header("Content-Type", "text/plain; version=0.0.4").send(prometheusFormat);
2672
+ }
2673
+ /**
2674
+ * 获取健康状态
2675
+ */
2676
+ async getHealthStatus() {
2677
+ const config = ConfigManager.getInstance().get();
2678
+ const adapterStatuses = {};
2679
+ let availableAdapters = 0;
2680
+ for (const [name, adapter] of this.adapters) {
2681
+ try {
2682
+ const isAvailable = await adapter.checkAvailability();
2683
+ adapterStatuses[name] = isAvailable;
2684
+ if (isAvailable) availableAdapters++;
2685
+ } catch (error) {
2686
+ adapterStatuses[name] = false;
2687
+ }
2688
+ }
2689
+ const totalAdapters = this.adapters.size;
2690
+ const feishuConfigured = !!(config.feishu.appId && config.feishu.appSecret);
2691
+ let status = "healthy";
2692
+ if (!feishuConfigured) {
2693
+ status = "unhealthy";
2694
+ } else if (availableAdapters === 0 && totalAdapters > 0) {
2695
+ status = "degraded";
2696
+ }
2697
+ return {
2698
+ status,
2699
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2700
+ version: process.env.npm_package_version || "1.0.0",
2701
+ checks: {
2702
+ feishu: {
2703
+ status: feishuConfigured ? "ok" : "error",
2704
+ message: feishuConfigured ? void 0 : "App ID or Secret not configured"
2705
+ },
2706
+ adapters: {
2707
+ status: availableAdapters > 0 ? "ok" : "error",
2708
+ available: availableAdapters,
2709
+ total: totalAdapters,
2710
+ details: adapterStatuses
2711
+ },
2712
+ session: {
2713
+ status: "ok",
2714
+ activeSessions: this.sessionManager.getActiveSessionCount()
2715
+ }
2716
+ }
2717
+ };
2718
+ }
2719
+ /**
2720
+ * 获取指标数据
2721
+ */
2722
+ async getMetrics() {
2723
+ const memUsage = process.memoryUsage();
2724
+ const adapterMetrics = {};
2725
+ for (const [name, adapter] of this.adapters) {
2726
+ try {
2727
+ const isAvailable = await adapter.checkAvailability();
2728
+ adapterMetrics[name] = {
2729
+ available: isAvailable,
2730
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString()
2731
+ };
2732
+ } catch (error) {
2733
+ adapterMetrics[name] = {
2734
+ available: false,
2735
+ lastCheck: (/* @__PURE__ */ new Date()).toISOString()
2736
+ };
2737
+ }
2738
+ }
2739
+ return {
2740
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2741
+ uptime: process.uptime(),
2742
+ memory: {
2743
+ used: Math.round(memUsage.heapUsed / 1024 / 1024),
2744
+ // MB
2745
+ total: Math.round(memUsage.heapTotal / 1024 / 1024),
2746
+ external: Math.round(memUsage.external / 1024 / 1024)
2747
+ },
2748
+ sessions: {
2749
+ active: this.sessionManager.getActiveSessionCount(),
2750
+ total: 0
2751
+ // 可以通过SessionManager扩展获取
2752
+ },
2753
+ requests: { ...this.requestCount },
2754
+ adapters: adapterMetrics
2755
+ };
2756
+ }
2757
+ /**
2758
+ * 转换为Prometheus格式
2759
+ */
2760
+ toPrometheusFormat(metrics) {
2761
+ const lines = [];
2762
+ lines.push("# HELP feishu_bridge_uptime_seconds Process uptime in seconds");
2763
+ lines.push("# TYPE feishu_bridge_uptime_seconds gauge");
2764
+ lines.push(`feishu_bridge_uptime_seconds ${metrics.uptime}`);
2765
+ lines.push("# HELP feishu_bridge_memory_usage_mb Memory usage in MB");
2766
+ lines.push("# TYPE feishu_bridge_memory_usage_mb gauge");
2767
+ lines.push(`feishu_bridge_memory_usage_mb{type="used"} ${metrics.memory.used}`);
2768
+ lines.push(`feishu_bridge_memory_usage_mb{type="total"} ${metrics.memory.total}`);
2769
+ lines.push(`feishu_bridge_memory_usage_mb{type="external"} ${metrics.memory.external}`);
2770
+ lines.push("# HELP feishu_bridge_active_sessions Number of active sessions");
2771
+ lines.push("# TYPE feishu_bridge_active_sessions gauge");
2772
+ lines.push(`feishu_bridge_active_sessions ${metrics.sessions.active}`);
2773
+ lines.push("# HELP feishu_bridge_requests_total Total number of requests");
2774
+ lines.push("# TYPE feishu_bridge_requests_total counter");
2775
+ lines.push(`feishu_bridge_requests_total ${metrics.requests.total}`);
2776
+ lines.push("# HELP feishu_bridge_requests_successful Total successful requests");
2777
+ lines.push("# TYPE feishu_bridge_requests_successful counter");
2778
+ lines.push(`feishu_bridge_requests_successful ${metrics.requests.successful}`);
2779
+ lines.push("# HELP feishu_bridge_requests_failed Total failed requests");
2780
+ lines.push("# TYPE feishu_bridge_requests_failed counter");
2781
+ lines.push(`feishu_bridge_requests_failed ${metrics.requests.failed}`);
2782
+ lines.push("# HELP feishu_bridge_adapter_available Adapter availability (1=available, 0=unavailable)");
2783
+ lines.push("# TYPE feishu_bridge_adapter_available gauge");
2784
+ for (const [name, data] of Object.entries(metrics.adapters)) {
2785
+ const value = data.available ? 1 : 0;
2786
+ lines.push(`feishu_bridge_adapter_available{name="${name}"} ${value}`);
2787
+ }
2788
+ return lines.join("\n");
2789
+ }
2790
+ /**
2791
+ * 记录请求
2792
+ */
2793
+ recordRequest(success) {
2794
+ this.requestCount.total++;
2795
+ if (success) {
2796
+ this.requestCount.successful++;
2797
+ } else {
2798
+ this.requestCount.failed++;
2799
+ }
2800
+ }
2801
+ };
2802
+
2803
+ // src/core/bridge.ts
2804
+ init_logger();
2805
+
2806
+ // src/core/reverse-channel.ts
2807
+ init_config();
2808
+ init_logger();
2809
+ var fs9 = __toESM(require("fs"));
2810
+ var path9 = __toESM(require("path"));
2811
+ var os9 = __toESM(require("os"));
2812
+ var import_events = require("events");
2813
+ var ReverseChannel = class extends import_events.EventEmitter {
2814
+ constructor(feishuClient) {
2815
+ super();
2816
+ this.isRunning = false;
2817
+ this.checkInterval = null;
2818
+ this.messageQueue = [];
2819
+ this.feishuClient = feishuClient;
2820
+ const bridgeConfig = ConfigManager.getInstance().get();
2821
+ this.config = {
2822
+ enabled: bridgeConfig.behavior?.reverseChannelEnabled ?? true,
2823
+ mode: bridgeConfig.behavior?.reverseChannelMode ?? "filesystem",
2824
+ checkInterval: 1e3,
2825
+ // 1秒检查一次
2826
+ maxMessageAge: 5 * 60 * 1e3
2827
+ // 5分钟过期
2828
+ };
2829
+ this.tempDir = path9.join(os9.tmpdir(), "feishu-bridge", "reverse-channel");
2830
+ this.ensureTempDir();
2831
+ }
2832
+ /**
2833
+ * 启动反向通信通道
2834
+ */
2835
+ start() {
2836
+ if (this.isRunning || !this.config.enabled) {
2837
+ return;
2838
+ }
2839
+ this.isRunning = true;
2840
+ logger.info("Starting reverse communication channel...");
2841
+ switch (this.config.mode) {
2842
+ case "filesystem":
2843
+ this.startFileSystemListener();
2844
+ break;
2845
+ case "websocket":
2846
+ this.startWebSocketListener();
2847
+ break;
2848
+ case "http":
2849
+ this.startHttpListener();
2850
+ break;
2851
+ }
2852
+ this.startMessageQueueProcessor();
2853
+ }
2854
+ /**
2855
+ * 停止反向通信通道
2856
+ */
2857
+ stop() {
2858
+ if (!this.isRunning) {
2859
+ return;
2860
+ }
2861
+ this.isRunning = false;
2862
+ logger.info("Stopping reverse communication channel...");
2863
+ if (this.checkInterval) {
2864
+ clearInterval(this.checkInterval);
2865
+ this.checkInterval = null;
2866
+ }
2867
+ }
2868
+ /**
2869
+ * 发送消息到飞书(供适配器调用)
2870
+ */
2871
+ async sendToFeishu(message) {
2872
+ try {
2873
+ this.messageQueue.push(message);
2874
+ this.emit("messageQueued", message);
2875
+ return true;
2876
+ } catch (error) {
2877
+ logger.error("Error queuing message to Feishu:", error);
2878
+ return false;
2879
+ }
2880
+ }
2881
+ /**
2882
+ * 发送文件系统消息(供IDE写入文件后调用)
2883
+ */
2884
+ async sendFileSystemMessage(adapterType, content, chatId) {
2885
+ const message = {
2886
+ type: "message",
2887
+ content,
2888
+ chatId,
2889
+ timestamp: Date.now()
2890
+ };
2891
+ const messageFile = path9.join(
2892
+ this.tempDir,
2893
+ `${adapterType}_msg_${Date.now()}.json`
2894
+ );
2895
+ fs9.writeFileSync(messageFile, JSON.stringify(message, null, 2));
2896
+ logger.debug(`File system message written: ${messageFile}`);
2897
+ }
2898
+ ensureTempDir() {
2899
+ if (!fs9.existsSync(this.tempDir)) {
2900
+ fs9.mkdirSync(this.tempDir, { recursive: true });
2901
+ }
2902
+ }
2903
+ startFileSystemListener() {
2904
+ logger.info("Starting file system listener for reverse channel...");
2905
+ this.checkInterval = setInterval(() => {
2906
+ this.processFileSystemMessages();
2907
+ }, this.config.checkInterval);
2908
+ }
2909
+ processFileSystemMessages() {
2910
+ if (!fs9.existsSync(this.tempDir)) {
2911
+ return;
2912
+ }
2913
+ try {
2914
+ const files = fs9.readdirSync(this.tempDir);
2915
+ const now = Date.now();
2916
+ for (const file of files) {
2917
+ if (!file.endsWith(".json")) continue;
2918
+ const filePath = path9.join(this.tempDir, file);
2919
+ try {
2920
+ const content = fs9.readFileSync(filePath, "utf-8");
2921
+ const message = JSON.parse(content);
2922
+ if (now - message.timestamp > this.config.maxMessageAge) {
2923
+ fs9.unlinkSync(filePath);
2924
+ continue;
2925
+ }
2926
+ this.messageQueue.push(message);
2927
+ fs9.unlinkSync(filePath);
2928
+ logger.debug(`Processed file system message: ${file}`);
2929
+ } catch (error) {
2930
+ logger.error(`Error processing message file ${file}:`, error);
2931
+ try {
2932
+ fs9.unlinkSync(filePath);
2933
+ } catch {
2934
+ }
2935
+ }
2936
+ }
2937
+ } catch (error) {
2938
+ logger.error("Error reading message directory:", error);
2939
+ }
2940
+ }
2941
+ startWebSocketListener() {
2942
+ logger.info("WebSocket reverse channel not yet implemented, falling back to file system");
2943
+ this.startFileSystemListener();
2944
+ }
2945
+ startHttpListener() {
2946
+ logger.info("HTTP reverse channel not yet implemented, falling back to file system");
2947
+ this.startFileSystemListener();
2948
+ }
2949
+ startMessageQueueProcessor() {
2950
+ setInterval(async () => {
2951
+ await this.processMessageQueue();
2952
+ }, 500);
2953
+ }
2954
+ async processMessageQueue() {
2955
+ if (this.messageQueue.length === 0) {
2956
+ return;
2957
+ }
2958
+ const messagesToProcess = [...this.messageQueue];
2959
+ this.messageQueue = [];
2960
+ for (const message of messagesToProcess) {
2961
+ try {
2962
+ await this.deliverMessageToFeishu(message);
2963
+ this.emit("messageDelivered", message);
2964
+ } catch (error) {
2965
+ logger.error("Error delivering message to Feishu:", error);
2966
+ if (!message.retryCount || message.retryCount < 3) {
2967
+ message.retryCount = (message.retryCount || 0) + 1;
2968
+ this.messageQueue.push(message);
2969
+ }
2970
+ }
2971
+ }
2972
+ }
2973
+ async deliverMessageToFeishu(message) {
2974
+ const { type, content, chatId, metadata } = message;
2975
+ if (!chatId) {
2976
+ logger.warn("No chatId specified for message, cannot deliver");
2977
+ return;
2978
+ }
2979
+ let formattedContent = content;
2980
+ switch (type) {
2981
+ case "notification":
2982
+ formattedContent = `\u{1F514} ${content}`;
2983
+ break;
2984
+ case "status":
2985
+ formattedContent = `\u{1F4CA} ${content}`;
2986
+ break;
2987
+ case "result":
2988
+ formattedContent = `\u2705 ${content}`;
2989
+ break;
2990
+ default:
2991
+ formattedContent = content;
2992
+ }
2993
+ const maxLength = 2e3;
2994
+ if (formattedContent.length > maxLength) {
2995
+ formattedContent = formattedContent.substring(0, maxLength) + "\n...(truncated)";
2996
+ }
2997
+ await this.feishuClient.sendMessage(chatId, {
2998
+ msg_type: "text",
2999
+ content: {
3000
+ text: formattedContent
3001
+ }
3002
+ });
3003
+ logger.info(`Message delivered to Feishu chat ${chatId}`);
3004
+ }
3005
+ isActive() {
3006
+ return this.isRunning;
3007
+ }
3008
+ getQueueSize() {
3009
+ return this.messageQueue.length;
3010
+ }
3011
+ };
3012
+
3013
+ // src/core/bridge.ts
3014
+ var import_fastify = __toESM(require("fastify"));
3015
+ var FeishuBridge = class {
3016
+ constructor() {
3017
+ this.wsHandler = null;
3018
+ this.healthMonitor = null;
3019
+ this.reverseChannel = null;
3020
+ this.adapters = /* @__PURE__ */ new Map();
3021
+ this.server = (0, import_fastify.default)({
3022
+ logger: false
3023
+ // 使用我们自己的日志系统
3024
+ });
3025
+ const config = ConfigManager.getInstance().get();
3026
+ this.feishuClient = new FeishuClient(config.feishu);
3027
+ this.sessionManager = new SessionManager(config.behavior.sessionTimeout);
3028
+ this.reverseChannel = new ReverseChannel(this.feishuClient);
3029
+ this.initAdapters();
3030
+ this.commandExecutor = new CommandExecutor(
3031
+ this.adapters,
3032
+ this.sessionManager
3033
+ );
3034
+ this.webhookHandler = new FeishuWebhookHandler(
3035
+ this.feishuClient,
3036
+ this.commandExecutor
3037
+ );
3038
+ }
3039
+ initAdapters() {
3040
+ const config = ConfigManager.getInstance().get();
3041
+ if (config.adapters.vscode?.enabled) {
3042
+ const adapter = new VSCodeAdapter();
3043
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
3044
+ this.adapters.set("vscode", adapter);
3045
+ }
3046
+ if (config.adapters.cursor?.enabled) {
3047
+ const adapter = new CursorAdapter();
3048
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
3049
+ this.adapters.set("cursor", adapter);
3050
+ }
3051
+ if (config.adapters.trae?.enabled) {
3052
+ const adapter = new TraeAdapter();
3053
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
3054
+ this.adapters.set("trae", adapter);
3055
+ }
3056
+ if (config.adapters.antigravity?.enabled) {
3057
+ const adapter = new AntigravityAdapter();
3058
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
3059
+ this.adapters.set("antigravity", adapter);
3060
+ }
3061
+ if (config.adapters.kiro?.enabled) {
3062
+ const adapter = new KiroAdapter();
3063
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
3064
+ this.adapters.set("kiro", adapter);
3065
+ }
3066
+ if (config.adapters.opencode?.enabled) {
3067
+ const adapter = new OpenCodeAdapter();
3068
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
3069
+ this.adapters.set("opencode", adapter);
3070
+ }
3071
+ if (config.adapters["claude-code"]?.enabled) {
3072
+ const adapter = new ClaudeCodeAdapter();
3073
+ if (this.reverseChannel) adapter.setReverseChannel(this.reverseChannel);
3074
+ this.adapters.set("claude", adapter);
3075
+ }
3076
+ }
3077
+ async start() {
3078
+ const config = ConfigManager.getInstance().get();
3079
+ const serverConfig = config.server;
3080
+ if (this.reverseChannel) {
3081
+ this.reverseChannel.start();
3082
+ logger.info("Reverse communication channel started");
3083
+ }
3084
+ if (config.feishu.connectionMode !== "websocket") {
3085
+ this.healthMonitor = new HealthMonitor(
3086
+ this.server,
3087
+ this.sessionManager,
3088
+ this.adapters
3089
+ );
3090
+ }
3091
+ if (config.feishu.connectionMode === "websocket") {
3092
+ logger.info("Starting in WebSocket mode...");
3093
+ this.wsHandler = new FeishuWebSocketHandler(
3094
+ this.feishuClient,
3095
+ this.commandExecutor
3096
+ );
3097
+ await this.wsHandler.start();
3098
+ logger.info("WebSocket connection established");
3099
+ } else {
3100
+ logger.info("Starting in Webhook mode...");
3101
+ this.server.post(serverConfig.webhookPath, async (request, reply) => {
3102
+ await this.webhookHandler.handleWebhook(request, reply);
3103
+ });
3104
+ try {
3105
+ const address = await this.server.listen({
3106
+ port: serverConfig.port,
3107
+ host: serverConfig.host
3108
+ });
3109
+ logger.info(`Feishu Bridge server running on ${address}`);
3110
+ logger.info(`Webhook endpoint: ${serverConfig.webhookPath}`);
3111
+ logger.info(`Health check endpoint: /health`);
3112
+ logger.info(`Metrics endpoint: /metrics`);
3113
+ } catch (err) {
3114
+ logger.error(err instanceof Error ? err : String(err));
3115
+ process.exit(1);
3116
+ }
3117
+ }
3118
+ }
3119
+ async stop() {
3120
+ if (this.reverseChannel) {
3121
+ this.reverseChannel.stop();
3122
+ logger.info("Reverse communication channel stopped");
3123
+ }
3124
+ if (this.wsHandler) {
3125
+ await this.wsHandler.stop();
3126
+ this.wsHandler = null;
3127
+ }
3128
+ await this.server.close();
3129
+ logger.info("Feishu Bridge server stopped");
3130
+ }
3131
+ getAdapter(adapterName) {
3132
+ return this.adapters.get(adapterName);
3133
+ }
3134
+ getAdapters() {
3135
+ return this.adapters;
3136
+ }
3137
+ };
3138
+
3139
+ // src/cli/index.ts
3140
+ init_config();
3141
+ init_logger();
3142
+ var program = new import_commander.Command();
3143
+ var pkg = JSON.parse(fs10.readFileSync(path10.join(__dirname, "../../package.json"), "utf-8"));
3144
+ program.name("feishu-bridge").description("Feishu Bridge - AI IDE/CLI integration for Feishu").version(pkg.version);
3145
+ program.command("start").description("Start the Feishu Bridge server").option("-p, --port <port>", "Server port", "3000").option("-h, --host <host>", "Server host", "0.0.0.0").option("-d, --daemon", "Run in daemon mode").action(async (options) => {
3146
+ try {
3147
+ const fileConfig = getConfigValue("feishu") || {};
3148
+ const configManager = ConfigManager.getInstance();
3149
+ const config = { ...DEFAULT_CONFIG };
3150
+ if (fileConfig.appId) config.feishu.appId = fileConfig.appId;
3151
+ if (fileConfig.appSecret) config.feishu.appSecret = fileConfig.appSecret;
3152
+ if (fileConfig.encryptKey) config.feishu.encryptKey = fileConfig.encryptKey;
3153
+ if (fileConfig.verificationToken) config.feishu.verificationToken = fileConfig.verificationToken;
3154
+ if (fileConfig.domain) config.feishu.domain = fileConfig.domain;
3155
+ config.server.port = parseInt(options.port);
3156
+ config.server.host = options.host;
3157
+ if (process.env.FEISHU_APP_ID) config.feishu.appId = process.env.FEISHU_APP_ID;
3158
+ if (process.env.FEISHU_APP_SECRET) config.feishu.appSecret = process.env.FEISHU_APP_SECRET;
3159
+ if (process.env.FEISHU_ENCRYPT_KEY) config.feishu.encryptKey = process.env.FEISHU_ENCRYPT_KEY;
3160
+ if (process.env.FEISHU_VERIFICATION_TOKEN) config.feishu.verificationToken = process.env.FEISHU_VERIFICATION_TOKEN;
3161
+ if (process.env.SERVER_PORT) config.server.port = parseInt(process.env.SERVER_PORT);
3162
+ if (process.env.SERVER_HOST) config.server.host = process.env.SERVER_HOST;
3163
+ if (!config.feishu.appId || !config.feishu.appSecret) {
3164
+ console.error("Error: FEISHU_APP_ID and FEISHU_APP_SECRET are required");
3165
+ console.error("Set them via:");
3166
+ console.error(" 1. Environment variables: export FEISHU_APP_ID=xxx");
3167
+ console.error(" 2. Config file: feishu-bridge config set feishu.appId xxx");
3168
+ process.exit(1);
3169
+ }
3170
+ configManager.load(config);
3171
+ const bridge = new FeishuBridge();
3172
+ await bridge.start();
3173
+ if (options.daemon) {
3174
+ logger.info("Feishu Bridge started in daemon mode");
3175
+ } else {
3176
+ logger.info("Press Ctrl+C to stop the server");
3177
+ }
3178
+ process.on("SIGINT", async () => {
3179
+ logger.info("Received SIGINT, shutting down gracefully...");
3180
+ await bridge.stop();
3181
+ process.exit(0);
3182
+ });
3183
+ process.on("SIGTERM", async () => {
3184
+ logger.info("Received SIGTERM, shutting down gracefully...");
3185
+ await bridge.stop();
3186
+ process.exit(0);
3187
+ });
3188
+ } catch (error) {
3189
+ console.error("Failed to start Feishu Bridge:", error);
3190
+ process.exit(1);
3191
+ }
3192
+ });
3193
+ var configCmd = program.command("config").description("Manage configuration");
3194
+ configCmd.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
3195
+ try {
3196
+ setConfigValue(key, value);
3197
+ console.log(`\u2705 Set ${key} = ${value}`);
3198
+ console.log(`\u{1F4C1} Config file: ${getConfigFilePath()}`);
3199
+ } catch (error) {
3200
+ console.error("\u274C Error setting config:", error);
3201
+ process.exit(1);
3202
+ }
3203
+ });
3204
+ configCmd.command("get <key>").description("Get a configuration value").action((key) => {
3205
+ const value = getConfigValue(key);
3206
+ if (value === void 0) {
3207
+ console.log(`\u26A0\uFE0F ${key} is not set`);
3208
+ } else {
3209
+ console.log(`${key} = ${JSON.stringify(value)}`);
3210
+ }
3211
+ });
3212
+ configCmd.command("delete <key>").description("Delete a configuration value").action((key) => {
3213
+ try {
3214
+ deleteConfigValue(key);
3215
+ console.log(`\u2705 Deleted ${key}`);
3216
+ } catch (error) {
3217
+ console.error("\u274C Error deleting config:", error);
3218
+ process.exit(1);
3219
+ }
3220
+ });
3221
+ configCmd.command("list").description("List all configuration values").action(() => {
3222
+ const config = listConfig();
3223
+ if (Object.keys(config).length === 0) {
3224
+ console.log("No configuration set");
3225
+ console.log(`\u{1F4C1} Config file: ${getConfigFilePath()}`);
3226
+ } else {
3227
+ console.log("Current configuration:");
3228
+ console.log(JSON.stringify(config, null, 2));
3229
+ console.log(`
3230
+ \u{1F4C1} Config file: ${getConfigFilePath()}`);
3231
+ }
3232
+ });
3233
+ var daemonCmd = program.command("daemon").description("Manage daemon process");
3234
+ daemonCmd.command("start").description("Start the daemon").action(async () => {
3235
+ try {
3236
+ const { DaemonManager: DaemonManager2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
3237
+ const daemon = DaemonManager2.getInstance();
3238
+ await daemon.start();
3239
+ console.log("\u2705 Daemon started");
3240
+ console.log('\u{1F4CA} Use "feishu-bridge status" to check status');
3241
+ } catch (error) {
3242
+ console.error("\u274C Failed to start daemon:", error);
3243
+ process.exit(1);
3244
+ }
3245
+ });
3246
+ daemonCmd.command("stop").description("Stop the daemon").action(async () => {
3247
+ try {
3248
+ const { DaemonManager: DaemonManager2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
3249
+ const daemon = DaemonManager2.getInstance();
3250
+ await daemon.stop();
3251
+ console.log("\u2705 Daemon stopped");
3252
+ } catch (error) {
3253
+ console.error("\u274C Failed to stop daemon:", error);
3254
+ process.exit(1);
3255
+ }
3256
+ });
3257
+ daemonCmd.command("restart").description("Restart the daemon").action(async () => {
3258
+ try {
3259
+ const { DaemonManager: DaemonManager2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
3260
+ const daemon = DaemonManager2.getInstance();
3261
+ await daemon.stop();
3262
+ await daemon.start();
3263
+ console.log("\u2705 Daemon restarted");
3264
+ } catch (error) {
3265
+ console.error("\u274C Failed to restart daemon:", error);
3266
+ process.exit(1);
3267
+ }
3268
+ });
3269
+ program.command("status").description("Show service status").action(async () => {
3270
+ try {
3271
+ const { DaemonManager: DaemonManager2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
3272
+ const daemon = DaemonManager2.getInstance();
3273
+ const status = daemon.getStatus();
3274
+ if (status) {
3275
+ console.log("\u{1F4CA} Daemon Status:");
3276
+ console.log(` Running: ${status.isRunning ? "\u2705 Yes" : "\u274C No"}`);
3277
+ console.log(` PID: ${status.pid || "N/A"}`);
3278
+ console.log(` Restarts: ${status.restartCount}`);
3279
+ console.log(` Uptime: ${Math.floor(status.uptime)}s`);
3280
+ } else {
3281
+ console.log("\u{1F4CA} Service Status:");
3282
+ console.log(" Daemon: Not running");
3283
+ }
3284
+ const config = listConfig();
3285
+ console.log("\n\u{1F4C1} Configuration:");
3286
+ console.log(` Config file: ${getConfigFilePath()}`);
3287
+ console.log(` Feishu App ID: ${config.feishu?.appId ? "\u2705 Set" : "\u274C Not set"}`);
3288
+ console.log(` Feishu App Secret: ${config.feishu?.appSecret ? "\u2705 Set" : "\u274C Not set"}`);
3289
+ } catch (error) {
3290
+ console.error("\u274C Error checking status:", error);
3291
+ }
3292
+ });
3293
+ program.command("shell-init").description("Output shell initialization script").action(() => {
3294
+ console.log(`
3295
+ # Feishu Bridge Shell Integration
3296
+ # Add this to your ~/.bashrc, ~/.zshrc, or equivalent
3297
+
3298
+ feishu-bridge-auto() {
3299
+ # \u68C0\u6D4B\u5F53\u524D\u662F\u5426\u5728IDE/CLI\u73AF\u5883\u4E2D
3300
+ if [ -n "$VSCODE_PID" ] || [ -n "$CURSOR_SHELL" ] || [ -n "$OPENCODE_SESSION" ]; then
3301
+ # \u68C0\u67E5bridge\u662F\u5426\u5DF2\u8FD0\u884C
3302
+ if ! pgrep -f "feishu-bridge" > /dev/null; then
3303
+ echo "Auto-starting Feishu Bridge..."
3304
+ feishu-bridge start --daemon 2>/dev/null &
3305
+ fi
3306
+ fi
3307
+ }
3308
+
3309
+ # Bash
3310
+ if [ -n "$BASH_VERSION" ]; then
3311
+ PROMPT_COMMAND="feishu-bridge-auto; $PROMPT_COMMAND"
3312
+ fi
3313
+
3314
+ # Zsh
3315
+ if [ -n "$ZSH_VERSION" ]; then
3316
+ precmd_functions+=(feishu-bridge-auto)
3317
+ fi
3318
+ `);
3319
+ });
3320
+ program.parse();
3321
+ if (!process.argv.slice(2).length) {
3322
+ program.outputHelp();
3323
+ }
3324
+ //# sourceMappingURL=index.js.map