@modelzen/feishu-codex-bridge 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/cli.js +590 -246
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# feishu-codex-bridge
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
|
|
4
|
+
[](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
|
|
5
|
+
[](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
|
|
6
|
+
[](https://github.com/modelzen/feishu-codex-bridge/blob/main/LICENSE)
|
|
7
|
+
|
|
3
8
|
> 把飞书 / Lark 桥接到你本机的 [Codex](https://github.com/openai/codex),在群里 @ 机器人就能让 Codex 在指定项目目录里干活,结果以流式 Markdown 卡片实时回到群里。
|
|
4
9
|
>
|
|
5
10
|
> **项目 = 群 = 固定工作目录(cwd)**,**话题(thread)= 一个 Codex 会话(session)**。
|
|
@@ -78,8 +83,11 @@ feishu-codex-bridge status # 状态 / pid / 日志路径 / 上次退出码
|
|
|
78
83
|
feishu-codex-bridge logs -f # 跟踪日志
|
|
79
84
|
feishu-codex-bridge restart # 重启
|
|
80
85
|
feishu-codex-bridge stop # 停止并关闭开机自启
|
|
86
|
+
feishu-codex-bridge update # 更新到最新版(npm i -g)并自动重启 daemon(--check 只查不装)
|
|
81
87
|
```
|
|
82
88
|
|
|
89
|
+
> 💡 升级很省事:装了后台 daemon 的,直接 `feishu-codex-bridge update` 一条命令 = 拉最新版 + 自动 `restart`;也可在**私聊管理台**点 **⬆️ 版本更新** 按钮,机器人自更新后重启服务。
|
|
90
|
+
|
|
83
91
|
`start` 会**先在当前终端完成 init**(没配置则扫码),并**阻塞到授权完成**——权限全部开通、且你确认已订阅事件/发布版本——才真正装服务,绝不会装一个收不到消息的空壳。daemon 体跑的就是 `run`。
|
|
84
92
|
|
|
85
93
|
> ⚠️ **后台 daemon 必须全局安装(`npm i -g`),不要用 npx**:launchd plist 里硬编码了 CLI 路径,而 npx 的临时缓存(`~/.npm/_npx/...`)会被清理,缓存一没服务就起不来。前台 `run` 用 npx 没问题(单次进程)。
|
|
@@ -109,7 +117,7 @@ feishu-codex-bridge bot rm <名> # 移除一个机器人配置
|
|
|
109
117
|
|
|
110
118
|
启动时若有缺失权限,会**自动打开浏览器**到形如 `https://open.feishu.cn/app/<app_id>/auth?q=...` 的页面(同时在终端打印链接),**一次性勾选全部 → 确认**即可(即时生效、无需重启)。`start`(后台 daemon)会阻塞到这步开通完成才装服务。
|
|
111
119
|
|
|
112
|
-
本桥需要的全部权限以 [`src/config/scopes.ts`](src/config/scopes.ts) 的 `REQUIRED_SCOPES` 为权威清单,包含:收群 @ 消息 / 全量群消息(免 @)/ 私聊消息、以机器人身份发消息与回话题、消息置顶、表情回复、上传下载资源、建群 /
|
|
120
|
+
本桥需要的全部权限以 [`src/config/scopes.ts`](src/config/scopes.ts) 的 `REQUIRED_SCOPES` 为权威清单,包含:收群 @ 消息 / 全量群消息(免 @)/ 私聊消息、以机器人身份发消息与回话题、消息置顶、表情回复、上传下载资源、建群 / 转让群主 / 设群管理员、群公告读写、置顶横幅、群标签页、交互卡片。**这些都在首次开通链接里一并申请,正常用不会再遇到「权限不足」。**
|
|
113
121
|
|
|
114
122
|
> 「**文档评论回复**」功能另需 `docs:document.comment:read`、`docs:document.comment:create`、`wiki:wiki:readonly` 三项(见 `COMMENT_SCOPES`)。它们**已预勾选进同一个开通链接**,但**不属于** `REQUIRED_SCOPES` —— 不开通也不会卡住后台服务安装,只是该功能静默关闭。
|
|
115
123
|
|
|
@@ -188,6 +196,7 @@ feishu-codex-bridge bot rm <名> # 移除一个机器人配置
|
|
|
188
196
|
feishu-codex-bridge run 前台启动(没配置先扫码 init;Ctrl+C 优雅退出)
|
|
189
197
|
feishu-codex-bridge start 后台 daemon 启动(装 launchd 开机自启;阻塞到授权完成)
|
|
190
198
|
feishu-codex-bridge stop|restart|status|logs 后台 daemon 生命周期
|
|
199
|
+
feishu-codex-bridge update 更新到最新版并自动重启 daemon(--check 只查不装)
|
|
191
200
|
feishu-codex-bridge bot init|list|use|rm 多飞书机器人:注册 / 列表 / 切当前 / 移除
|
|
192
201
|
feishu-codex-bridge doctor 本地自检:codex / 登录 / lark-cli / 当前机器人
|
|
193
202
|
```
|
|
@@ -215,7 +224,7 @@ src/
|
|
|
215
224
|
project/ 项目注册表、建群/公告/标签页 onboarding、生命周期
|
|
216
225
|
config/ 加密密钥库、密钥解析、配置存储、多机器人注册表、scope 清单、路径
|
|
217
226
|
core/ watchdog、单实例锁、日志
|
|
218
|
-
cli/ commander 命令(run / start / stop / restart / status / logs / bot / doctor / secrets)
|
|
227
|
+
cli/ commander 命令(run / start / stop / restart / status / logs / update / bot / doctor / secrets)
|
|
219
228
|
service/ launchd 后台服务
|
|
220
229
|
```
|
|
221
230
|
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
// src/cli/index.ts
|
|
2
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
3
|
-
import { dirname as dirname8, resolve as resolve3 } from "path";
|
|
4
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5
2
|
import { Command } from "commander";
|
|
6
3
|
|
|
4
|
+
// src/core/version.ts
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { dirname, resolve } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
function bridgeVersion() {
|
|
9
|
+
try {
|
|
10
|
+
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
11
|
+
return JSON.parse(readFileSync(pkgPath, "utf8")).version ?? "0.0.0";
|
|
12
|
+
} catch {
|
|
13
|
+
return "0.0.0";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
// src/cli/commands/doctor.ts
|
|
8
18
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9
19
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -65,11 +75,11 @@ var paths = {
|
|
|
65
75
|
// src/config/bots.ts
|
|
66
76
|
import { existsSync } from "fs";
|
|
67
77
|
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
68
|
-
import { dirname as
|
|
78
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
69
79
|
|
|
70
80
|
// src/config/store.ts
|
|
71
81
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
72
|
-
import { dirname } from "path";
|
|
82
|
+
import { dirname as dirname2 } from "path";
|
|
73
83
|
|
|
74
84
|
// src/config/schema.ts
|
|
75
85
|
function isComplete(cfg) {
|
|
@@ -158,7 +168,7 @@ async function ensureSecretsGetterWrapper() {
|
|
|
158
168
|
# Auto-generated by feishu-codex-bridge. Do not edit.
|
|
159
169
|
exec ${sq(node)} ${sq(bridgeEntry)} secrets get "$@"
|
|
160
170
|
`;
|
|
161
|
-
await mkdir(
|
|
171
|
+
await mkdir(dirname2(wrapperPath), { recursive: true });
|
|
162
172
|
const tmp = `${wrapperPath}.tmp-${process.pid}`;
|
|
163
173
|
await writeFile(tmp, content, "utf8");
|
|
164
174
|
await chmod(tmp, 448);
|
|
@@ -166,7 +176,7 @@ exec ${sq(node)} ${sq(bridgeEntry)} secrets get "$@"
|
|
|
166
176
|
return wrapperPath;
|
|
167
177
|
}
|
|
168
178
|
async function saveConfig(cfg, path = paths.configFile) {
|
|
169
|
-
await mkdir(
|
|
179
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
170
180
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
171
181
|
await writeFile(tmp, `${JSON.stringify(cfg, null, 2)}
|
|
172
182
|
`, "utf8");
|
|
@@ -187,7 +197,7 @@ async function loadBots() {
|
|
|
187
197
|
}
|
|
188
198
|
}
|
|
189
199
|
async function saveBots(reg) {
|
|
190
|
-
await mkdir2(
|
|
200
|
+
await mkdir2(dirname3(paths.botsFile), { recursive: true });
|
|
191
201
|
const tmp = `${paths.botsFile}.tmp-${process.pid}`;
|
|
192
202
|
await writeFile2(tmp, `${JSON.stringify(reg, null, 2)}
|
|
193
203
|
`, "utf8");
|
|
@@ -358,7 +368,7 @@ import { createInterface } from "readline/promises";
|
|
|
358
368
|
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
359
369
|
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
|
|
360
370
|
import { hostname, userInfo } from "os";
|
|
361
|
-
import { dirname as
|
|
371
|
+
import { dirname as dirname4 } from "path";
|
|
362
372
|
var KEY_LEN = 32;
|
|
363
373
|
var IV_LEN = 12;
|
|
364
374
|
var TAG_LEN = 16;
|
|
@@ -377,7 +387,7 @@ async function readStore() {
|
|
|
377
387
|
}
|
|
378
388
|
}
|
|
379
389
|
async function writeStore(store) {
|
|
380
|
-
await mkdir3(
|
|
390
|
+
await mkdir3(dirname4(paths.secretsFile), { recursive: true });
|
|
381
391
|
const tmp = `${paths.secretsFile}.tmp-${process.pid}`;
|
|
382
392
|
await writeFile3(tmp, `${JSON.stringify(store, null, 2)}
|
|
383
393
|
`, "utf8");
|
|
@@ -392,7 +402,7 @@ async function loadOrCreateSalt() {
|
|
|
392
402
|
if (err.code !== "ENOENT") throw err;
|
|
393
403
|
}
|
|
394
404
|
const salt = randomBytes(KEY_LEN);
|
|
395
|
-
await mkdir3(
|
|
405
|
+
await mkdir3(dirname4(paths.keystoreSaltFile), { recursive: true });
|
|
396
406
|
const tmp = `${paths.keystoreSaltFile}.tmp-${process.pid}`;
|
|
397
407
|
await writeFile3(tmp, salt);
|
|
398
408
|
await chmod3(tmp, 384);
|
|
@@ -528,7 +538,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
528
538
|
const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
529
539
|
const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
|
|
530
540
|
const providerName = ref.provider ?? DEFAULT_PROVIDER;
|
|
531
|
-
return new Promise((
|
|
541
|
+
return new Promise((resolve5, reject) => {
|
|
532
542
|
const env = {};
|
|
533
543
|
if (pc.passEnv) for (const k of pc.passEnv) {
|
|
534
544
|
const v = process.env[k];
|
|
@@ -573,7 +583,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
573
583
|
try {
|
|
574
584
|
const parsed = JSON.parse(stdout);
|
|
575
585
|
const value = parsed.values?.[ref.id];
|
|
576
|
-
if (typeof value === "string") return
|
|
586
|
+
if (typeof value === "string") return resolve5(value);
|
|
577
587
|
const err = parsed.errors?.[ref.id]?.message;
|
|
578
588
|
reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
|
|
579
589
|
} catch (err) {
|
|
@@ -651,6 +661,8 @@ var REQUIRED_SCOPES = [
|
|
|
651
661
|
// create the project group
|
|
652
662
|
"im:chat:update",
|
|
653
663
|
// transfer ownership on unbind
|
|
664
|
+
"im:chat.managers:write_only",
|
|
665
|
+
// promote the project creator to group admin (im.v1.chat.managers.add_managers)
|
|
654
666
|
"im:chat.announcement:read",
|
|
655
667
|
// read group announcement blocks (list)
|
|
656
668
|
"im:chat.announcement:write_only",
|
|
@@ -1100,7 +1112,7 @@ var AsyncQueue = class {
|
|
|
1100
1112
|
continue;
|
|
1101
1113
|
}
|
|
1102
1114
|
if (this.closed) return;
|
|
1103
|
-
const next = await new Promise((
|
|
1115
|
+
const next = await new Promise((resolve5) => this.waiters.push(resolve5));
|
|
1104
1116
|
if (next.done) return;
|
|
1105
1117
|
yield next.value;
|
|
1106
1118
|
}
|
|
@@ -1151,8 +1163,8 @@ var AppServerClient = class {
|
|
|
1151
1163
|
const id = ++this.nextId;
|
|
1152
1164
|
const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
|
|
1153
1165
|
`;
|
|
1154
|
-
return new Promise((
|
|
1155
|
-
this.pending.set(id, { resolve:
|
|
1166
|
+
return new Promise((resolve5, reject) => {
|
|
1167
|
+
this.pending.set(id, { resolve: resolve5, reject });
|
|
1156
1168
|
this.child.stdin.write(payload, (err) => {
|
|
1157
1169
|
if (err) {
|
|
1158
1170
|
this.pending.delete(id);
|
|
@@ -1176,14 +1188,14 @@ var AppServerClient = class {
|
|
|
1176
1188
|
const child = this.child;
|
|
1177
1189
|
if (!child || child.exitCode !== null) return;
|
|
1178
1190
|
child.kill("SIGTERM");
|
|
1179
|
-
await new Promise((
|
|
1191
|
+
await new Promise((resolve5) => {
|
|
1180
1192
|
const t = setTimeout(() => {
|
|
1181
1193
|
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1182
|
-
|
|
1194
|
+
resolve5();
|
|
1183
1195
|
}, graceMs);
|
|
1184
1196
|
child.once("exit", () => {
|
|
1185
1197
|
clearTimeout(t);
|
|
1186
|
-
|
|
1198
|
+
resolve5();
|
|
1187
1199
|
});
|
|
1188
1200
|
});
|
|
1189
1201
|
}
|
|
@@ -1302,12 +1314,12 @@ var APPROVAL_POLICY = "never";
|
|
|
1302
1314
|
var SANDBOX = "danger-full-access";
|
|
1303
1315
|
var READ_HISTORY_TIMEOUT_MS = 2e4;
|
|
1304
1316
|
function withDeadline(p, ms, label) {
|
|
1305
|
-
return new Promise((
|
|
1317
|
+
return new Promise((resolve5, reject) => {
|
|
1306
1318
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
1307
1319
|
p.then(
|
|
1308
1320
|
(v) => {
|
|
1309
1321
|
clearTimeout(t);
|
|
1310
|
-
|
|
1322
|
+
resolve5(v);
|
|
1311
1323
|
},
|
|
1312
1324
|
(e) => {
|
|
1313
1325
|
clearTimeout(t);
|
|
@@ -1347,11 +1359,11 @@ var CodexThread = class {
|
|
|
1347
1359
|
if (self.model) params.model = self.model;
|
|
1348
1360
|
if (self.effort) params.effort = self.effort;
|
|
1349
1361
|
let startError;
|
|
1350
|
-
const startFailed = new Promise((
|
|
1362
|
+
const startFailed = new Promise((resolve5) => {
|
|
1351
1363
|
self.client.request("turn/start", params).then(void 0, (err) => {
|
|
1352
1364
|
startError = err instanceof Error ? err : new Error(String(err));
|
|
1353
1365
|
log.fail("agent", startError, { phase: "turn/start" });
|
|
1354
|
-
|
|
1366
|
+
resolve5("start-failed");
|
|
1355
1367
|
});
|
|
1356
1368
|
});
|
|
1357
1369
|
const stream2 = self.client.stream()[Symbol.asyncIterator]();
|
|
@@ -2616,6 +2628,7 @@ var RunCardStream = class {
|
|
|
2616
2628
|
function openChatUrl(chatId) {
|
|
2617
2629
|
return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
|
|
2618
2630
|
}
|
|
2631
|
+
var REPO = "https://github.com/modelzen/feishu-codex-bridge";
|
|
2619
2632
|
var DM = {
|
|
2620
2633
|
menu: "dm.menu",
|
|
2621
2634
|
newProject: "dm.newProject",
|
|
@@ -2624,6 +2637,8 @@ var DM = {
|
|
|
2624
2637
|
settings: "dm.settings",
|
|
2625
2638
|
doctor: "dm.doctor",
|
|
2626
2639
|
reconnect: "dm.reconnect",
|
|
2640
|
+
update: "dm.update",
|
|
2641
|
+
updateDo: "dm.update.do",
|
|
2627
2642
|
rmConfirm: "dm.rmConfirm",
|
|
2628
2643
|
rmDo: "dm.rmDo",
|
|
2629
2644
|
rmCancel: "dm.rmCancel",
|
|
@@ -2650,12 +2665,159 @@ function buildDmMenuCard() {
|
|
|
2650
2665
|
]),
|
|
2651
2666
|
actions([
|
|
2652
2667
|
button("\u{1FA7A} \u8BCA\u65AD", { a: DM.doctor }),
|
|
2653
|
-
button("\u{1F504} \u91CD\u8FDE", { a: DM.reconnect })
|
|
2668
|
+
button("\u{1F504} \u91CD\u8FDE", { a: DM.reconnect }),
|
|
2669
|
+
button("\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", { a: DM.update })
|
|
2654
2670
|
])
|
|
2655
2671
|
],
|
|
2656
2672
|
{ header: { title: "\u{1F916} Codex Bridge \u7BA1\u7406\u53F0", template: "blue" } }
|
|
2657
2673
|
);
|
|
2658
2674
|
}
|
|
2675
|
+
var backToMenu = () => actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]);
|
|
2676
|
+
function buildUpdateCard(state) {
|
|
2677
|
+
switch (state.phase) {
|
|
2678
|
+
case "checking":
|
|
2679
|
+
return card([md("\u23F3 \u6B63\u5728\u67E5\u8BE2\u6700\u65B0\u7248\u672C\u2026"), note("\u4ECE npm registry \u62C9\u53D6\u7248\u672C\u4FE1\u606F\uFF0C\u8BF7\u7A0D\u5019\u3002")], {
|
|
2680
|
+
header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "turquoise" }
|
|
2681
|
+
});
|
|
2682
|
+
case "checked": {
|
|
2683
|
+
const cur = state.current ?? "?";
|
|
2684
|
+
if (!state.latest) {
|
|
2685
|
+
return card(
|
|
2686
|
+
[
|
|
2687
|
+
md(`\u5F53\u524D\u7248\u672C\uFF1A**v${cur}**`),
|
|
2688
|
+
md("\u26A0\uFE0F \u67E5\u4E0D\u5230\u6700\u65B0\u7248\u672C\uFF08\u7F51\u7EDC\u6216 npm registry \u95EE\u9898\uFF09\u3002"),
|
|
2689
|
+
actions([button("\u{1F504} \u91CD\u8BD5", { a: DM.update }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
|
|
2690
|
+
],
|
|
2691
|
+
{ header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "red" } }
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
if (!state.hasUpdate) {
|
|
2695
|
+
return card(
|
|
2696
|
+
[md(`\u2705 \u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF1A**v${cur}**`), backToMenu()],
|
|
2697
|
+
{ header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "green" } }
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
const head = [
|
|
2701
|
+
md(`\u53D1\u73B0\u65B0\u7248\u672C \u{1F389}`),
|
|
2702
|
+
note(`\u5F53\u524D v${cur} \u2192 \u6700\u65B0 v${state.latest}`)
|
|
2703
|
+
];
|
|
2704
|
+
if (state.dev) {
|
|
2705
|
+
return card(
|
|
2706
|
+
[
|
|
2707
|
+
...head,
|
|
2708
|
+
md("\u68C0\u6D4B\u5230**\u6E90\u7801\u5F00\u53D1\u6A21\u5F0F**\uFF08\u4ED3\u5E93\u5185\u6709 .git\uFF09\u3002\u8BF7\u5728\u7EC8\u7AEF\u7528 `git pull && npm i` \u66F4\u65B0\uFF0C\u800C\u4E0D\u662F\u5168\u5C40\u5B89\u88C5\u3002"),
|
|
2709
|
+
backToMenu()
|
|
2710
|
+
],
|
|
2711
|
+
{ header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "orange" } }
|
|
2712
|
+
);
|
|
2713
|
+
}
|
|
2714
|
+
return card(
|
|
2715
|
+
[
|
|
2716
|
+
...head,
|
|
2717
|
+
note("\u70B9\u300C\u7ACB\u5373\u66F4\u65B0\u300D\u4F1A\u6267\u884C `npm i -g` \u5E76\u81EA\u52A8\u91CD\u542F\u540E\u53F0\u670D\u52A1\uFF08\u7EA6\u6570\u5341\u79D2\uFF09\u3002"),
|
|
2718
|
+
actions([
|
|
2719
|
+
button("\u2B06\uFE0F \u7ACB\u5373\u66F4\u65B0", { a: DM.updateDo }, "primary"),
|
|
2720
|
+
button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })
|
|
2721
|
+
])
|
|
2722
|
+
],
|
|
2723
|
+
{ header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "blue" } }
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
case "updating":
|
|
2727
|
+
return card(
|
|
2728
|
+
[
|
|
2729
|
+
md(`\u23F3 \u6B63\u5728\u66F4\u65B0\u5230\u6700\u65B0\u7248\u2026`),
|
|
2730
|
+
note(`\u4ECE v${state.from ?? "?"} \u5347\u7EA7\u4E2D\uFF0C\u4E0B\u8F7D\u5B89\u88C5\u7EA6\u6570\u5341\u79D2\uFF0C\u8BF7\u52FF\u91CD\u590D\u70B9\u51FB\u3002`)
|
|
2731
|
+
],
|
|
2732
|
+
{ header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "turquoise" } }
|
|
2733
|
+
);
|
|
2734
|
+
case "done": {
|
|
2735
|
+
const tail = state.willRestart ? note("\u6B63\u5728\u91CD\u542F\u540E\u53F0\u670D\u52A1\u4EE5\u751F\u6548 \u2014\u2014 \u91CD\u542F\u671F\u95F4\u672C\u5361\u7247\u505C\u6B62\u66F4\u65B0\uFF1B\u7A0D\u540E\u53D1\u6211\u4EFB\u610F\u6D88\u606F\u53EF\u91CD\u5F00\u7BA1\u7406\u53F0\u3002") : note("\u524D\u53F0\u6A21\u5F0F\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u624B\u52A8\u91CD\u542F `run` \u8FDB\u7A0B\u4F7F\u65B0\u7248\u672C\u751F\u6548\u3002");
|
|
2736
|
+
return card(
|
|
2737
|
+
[md(`\u2705 \u5DF2\u66F4\u65B0 **v${state.from ?? "?"} \u2192 v${state.to ?? "?"}**`), tail],
|
|
2738
|
+
{ header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "green" } }
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
case "error":
|
|
2742
|
+
return card(
|
|
2743
|
+
[
|
|
2744
|
+
md("\u274C **\u66F4\u65B0\u5931\u8D25**"),
|
|
2745
|
+
state.message ? note(state.message) : note("npm \u5B89\u88C5\u672A\u6210\u529F\u3002"),
|
|
2746
|
+
md("\u53EF\u5728\u7EC8\u7AEF\u624B\u52A8\u6267\u884C\uFF1A`npm i -g @modelzen/feishu-codex-bridge@latest`\uFF08\u5FC5\u8981\u65F6\u52A0 sudo\uFF09\u3002"),
|
|
2747
|
+
actions([button("\u{1F504} \u91CD\u8BD5", { a: DM.update }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
|
|
2748
|
+
],
|
|
2749
|
+
{ header: { title: "\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", template: "red" } }
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
function connLabel(state) {
|
|
2754
|
+
switch (state) {
|
|
2755
|
+
case "connected":
|
|
2756
|
+
return "\u2705 \u5DF2\u8FDE\u63A5";
|
|
2757
|
+
case "connecting":
|
|
2758
|
+
return "\u23F3 \u8FDE\u63A5\u4E2D";
|
|
2759
|
+
case "reconnecting":
|
|
2760
|
+
return "\u21BB \u91CD\u8FDE\u4E2D";
|
|
2761
|
+
case "disconnected":
|
|
2762
|
+
return "\u274C \u5DF2\u65AD\u5F00";
|
|
2763
|
+
default:
|
|
2764
|
+
return state;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
function codexDiagnosePrompt(i) {
|
|
2768
|
+
return [
|
|
2769
|
+
"\u6211\u5728\u7528 feishu-codex-bridge\uFF08\u98DE\u4E66 \u2194 \u672C\u5730 Codex \u6865\u63A5\uFF09\u9047\u5230\u95EE\u9898\uFF0C\u8BF7\u5E2E\u6211\u5B9A\u4F4D\u539F\u56E0\u5E76\u7ED9\u51FA\u4FEE\u590D\u6B65\u9AA4\u3002",
|
|
2770
|
+
"",
|
|
2771
|
+
"\u3010\u73AF\u5883\u3011",
|
|
2772
|
+
`- bridge \u7248\u672C\uFF1Av${i.bridgeVer}`,
|
|
2773
|
+
`- codex \u7248\u672C\uFF1A${i.codexVer ?? "\u672A\u627E\u5230\uFF08PATH / CODEX_BIN \u91CC\u90FD\u6CA1\u6709 codex\uFF09"}`,
|
|
2774
|
+
`- Node\uFF1A${i.node}`,
|
|
2775
|
+
`- \u5E73\u53F0\uFF1A${i.platform}`,
|
|
2776
|
+
`- \u9879\u76EE\u4ED3\u5E93\uFF1A${REPO}`,
|
|
2777
|
+
"",
|
|
2778
|
+
"\u3010\u8FD0\u884C\u5FEB\u7167\u3011",
|
|
2779
|
+
`- codex \u53EF\u7528\uFF1A${i.codexOk ? "\u662F" : "\u5426"}`,
|
|
2780
|
+
`- \u98DE\u4E66\u957F\u8FDE\u63A5\uFF1A${i.conn}`,
|
|
2781
|
+
"",
|
|
2782
|
+
"\u3010\u8BF7\u4F60\u505A\u7684\u4E8B\u3011",
|
|
2783
|
+
"1. \u8BFB\u53D6\u5E76\u5206\u6790\u65E5\u5FD7\uFF0C\u627E\u51FA\u6700\u8FD1\u7684\u62A5\u9519\u6216\u5F02\u5E38\u5806\u6808\uFF1A",
|
|
2784
|
+
` - \u540E\u53F0\u5B88\u62A4\u8F93\u51FA\u65E5\u5FD7\uFF1A${i.logStdout}`,
|
|
2785
|
+
` - \u540E\u53F0\u5B88\u62A4\u9519\u8BEF\u65E5\u5FD7\uFF1A${i.logStderr}`,
|
|
2786
|
+
" \uFF08\u82E5\u662F\u524D\u53F0 feishu-codex-bridge run \u6A21\u5F0F\uFF0C\u65E5\u5FD7\u5728\u542F\u52A8\u5B83\u7684\u7EC8\u7AEF\u7A97\u53E3\uFF0C\u8BF7\u628A\u7EC8\u7AEF\u91CC\u7684\u62A5\u9519\u4E00\u8D77\u53D1\u6211\uFF09",
|
|
2787
|
+
`2. \u5224\u65AD\u95EE\u9898\u5C5E\u4E8E\u54EA\u7C7B\uFF1Acodex \u542F\u52A8 / \u767B\u5F55\u3001\u98DE\u4E66\u9274\u6743\u6216\u6743\u9650\u4E0D\u8DB3\u3001\u957F\u8FDE\u63A5\u65AD\u5F00\u3001\u8FD8\u662F\u914D\u7F6E\u7F3A\u5931\uFF08\u914D\u7F6E\u6587\u4EF6\uFF1A${i.configFile}\uFF09\u3002`,
|
|
2788
|
+
`3. \u5FC5\u8981\u65F6\u5BF9\u7167\u4ED3\u5E93 README \u4E0E issues \u7ED9\u65B9\u6848\uFF1A${REPO}/issues`,
|
|
2789
|
+
"4. \u7ED9\u51FA\u53EF\u76F4\u63A5\u6267\u884C\u7684\u4FEE\u590D\u6B65\u9AA4\u3002",
|
|
2790
|
+
"",
|
|
2791
|
+
"\u3010\u6211\u9047\u5230\u7684\u73B0\u8C61\u3011",
|
|
2792
|
+
"\uFF08\u5728\u8FD9\u91CC\u8865\u5145\uFF1A\u6BD4\u5982 @\u673A\u5668\u4EBA\u4E0D\u56DE\u590D / \u5361\u7247\u6309\u94AE\u70B9\u4E86\u6CA1\u53CD\u5E94 / \u542F\u52A8\u5C31\u62A5\u9519\u2026\u2026\uFF09"
|
|
2793
|
+
].join("\n");
|
|
2794
|
+
}
|
|
2795
|
+
function buildDoctorCard(i) {
|
|
2796
|
+
const prompt = codexDiagnosePrompt(i);
|
|
2797
|
+
return card(
|
|
2798
|
+
[
|
|
2799
|
+
md("**\u521D\u6B65\u8BCA\u65AD**"),
|
|
2800
|
+
md(
|
|
2801
|
+
`- Codex\uFF1A${i.codexOk ? `\u2705 \u53EF\u7528${i.codexVer ? `\uFF08${i.codexVer}\uFF09` : ""}` : "\u274C \u4E0D\u53EF\u7528\uFF08\u68C0\u67E5 CODEX_BIN / PATH\uFF09"}`
|
|
2802
|
+
),
|
|
2803
|
+
md(`- \u98DE\u4E66\u957F\u8FDE\u63A5\uFF1A${connLabel(i.conn)}`),
|
|
2804
|
+
note(`bridge v${i.bridgeVer}\u3000\xB7\u3000Node ${i.node}\u3000\xB7\u3000${i.platform}`),
|
|
2805
|
+
hr(),
|
|
2806
|
+
md("**\u65E5\u5FD7\u8DEF\u5F84**"),
|
|
2807
|
+
note(`\u540E\u53F0\u5B88\u62A4\u8F93\u51FA\uFF1A\`${i.logStdout}\``),
|
|
2808
|
+
note(`\u540E\u53F0\u5B88\u62A4\u9519\u8BEF\uFF1A\`${i.logStderr}\``),
|
|
2809
|
+
note("\u524D\u53F0 `run` \u6A21\u5F0F\uFF1A\u65E5\u5FD7\u5728\u542F\u52A8\u5B83\u7684\u7EC8\u7AEF\u7A97\u53E3\u91CC"),
|
|
2810
|
+
hr(),
|
|
2811
|
+
md("**\u8BA9 Codex \u5E2E\u4F60\u6DF1\u5EA6\u8BCA\u65AD** \u2014 \u590D\u5236\u4E0B\u9762\u6574\u6BB5\uFF0C\u5230\u4EFB\u610F\u9879\u76EE\u7FA4\u91CC **@\u6211** \u7C98\u8D34\u53D1\u9001\uFF1A"),
|
|
2812
|
+
md("```\n" + prompt + "\n```"),
|
|
2813
|
+
actions([
|
|
2814
|
+
linkButton("\u{1F4E6} \u9879\u76EE\u4ED3\u5E93", REPO),
|
|
2815
|
+
linkButton("\u{1F41E} \u63D0 Issue", `${REPO}/issues`)
|
|
2816
|
+
])
|
|
2817
|
+
],
|
|
2818
|
+
{ header: { title: "\u{1FA7A} \u8BCA\u65AD", template: i.codexOk ? "blue" : "orange" } }
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2659
2821
|
function buildNewProjectFormCard(opts = {}) {
|
|
2660
2822
|
const elements = [];
|
|
2661
2823
|
if (opts.error) elements.push(md(`\u274C **\u521B\u5EFA\u5931\u8D25**\uFF1A${opts.error}`));
|
|
@@ -2788,9 +2950,266 @@ function buildGroupSettingsCard(project) {
|
|
|
2788
2950
|
);
|
|
2789
2951
|
}
|
|
2790
2952
|
|
|
2953
|
+
// src/service/update.ts
|
|
2954
|
+
import { execFile, spawn as spawn5 } from "child_process";
|
|
2955
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
2956
|
+
import { dirname as dirname6, join as join8, resolve as resolve3 } from "path";
|
|
2957
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2958
|
+
import { promisify } from "util";
|
|
2959
|
+
|
|
2960
|
+
// src/service/launchd.ts
|
|
2961
|
+
import { spawn as spawn4, spawnSync } from "child_process";
|
|
2962
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2963
|
+
import { appendFile, mkdir as mkdir4, rm as rm2, writeFile as writeFile4 } from "fs/promises";
|
|
2964
|
+
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
2965
|
+
import { dirname as dirname5, join as join7, resolve as resolve2 } from "path";
|
|
2966
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2967
|
+
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
2968
|
+
function launchAgentPlistPath() {
|
|
2969
|
+
return join7(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
2970
|
+
}
|
|
2971
|
+
function serviceStdoutPath() {
|
|
2972
|
+
return join7(paths.appDir, "service.log");
|
|
2973
|
+
}
|
|
2974
|
+
function serviceStderrPath() {
|
|
2975
|
+
return join7(paths.appDir, "service.err.log");
|
|
2976
|
+
}
|
|
2977
|
+
function resolveCliBinPath() {
|
|
2978
|
+
const distDir = dirname5(fileURLToPath2(import.meta.url));
|
|
2979
|
+
return resolve2(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
2980
|
+
}
|
|
2981
|
+
function escapeXml(value) {
|
|
2982
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2983
|
+
}
|
|
2984
|
+
function buildPlist() {
|
|
2985
|
+
const nodePath = process.execPath;
|
|
2986
|
+
const cliBinPath = resolveCliBinPath();
|
|
2987
|
+
const pathEnv = process.env.PATH ?? "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
2988
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2989
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2990
|
+
<plist version="1.0">
|
|
2991
|
+
<dict>
|
|
2992
|
+
<key>Label</key>
|
|
2993
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
2994
|
+
<key>ProgramArguments</key>
|
|
2995
|
+
<array>
|
|
2996
|
+
<string>${escapeXml(nodePath)}</string>
|
|
2997
|
+
<string>${escapeXml(cliBinPath)}</string>
|
|
2998
|
+
<string>run</string>
|
|
2999
|
+
</array>
|
|
3000
|
+
<key>RunAtLoad</key>
|
|
3001
|
+
<true/>
|
|
3002
|
+
<key>KeepAlive</key>
|
|
3003
|
+
<true/>
|
|
3004
|
+
<key>StandardOutPath</key>
|
|
3005
|
+
<string>${escapeXml(serviceStdoutPath())}</string>
|
|
3006
|
+
<key>StandardErrorPath</key>
|
|
3007
|
+
<string>${escapeXml(serviceStderrPath())}</string>
|
|
3008
|
+
<key>EnvironmentVariables</key>
|
|
3009
|
+
<dict>
|
|
3010
|
+
<key>PATH</key>
|
|
3011
|
+
<string>${escapeXml(pathEnv)}</string>
|
|
3012
|
+
</dict>
|
|
3013
|
+
</dict>
|
|
3014
|
+
</plist>
|
|
3015
|
+
`;
|
|
3016
|
+
}
|
|
3017
|
+
async function installLaunchd() {
|
|
3018
|
+
const plistPath = launchAgentPlistPath();
|
|
3019
|
+
await mkdir4(dirname5(plistPath), { recursive: true });
|
|
3020
|
+
await ensureLogFiles();
|
|
3021
|
+
await writeFile4(plistPath, buildPlist(), "utf8");
|
|
3022
|
+
if (isLoaded()) {
|
|
3023
|
+
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
3024
|
+
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
3025
|
+
await waitUntilUnloaded();
|
|
3026
|
+
}
|
|
3027
|
+
const bootstrap = runLaunchctl(["bootstrap", userTarget(), plistPath]);
|
|
3028
|
+
if (!bootstrap.ok) throw launchctlError("launchctl bootstrap", bootstrap);
|
|
3029
|
+
return statusLaunchd();
|
|
3030
|
+
}
|
|
3031
|
+
async function uninstallLaunchd() {
|
|
3032
|
+
if (isLoaded()) {
|
|
3033
|
+
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
3034
|
+
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
3035
|
+
await waitUntilUnloaded();
|
|
3036
|
+
}
|
|
3037
|
+
await rm2(launchAgentPlistPath(), { force: true });
|
|
3038
|
+
}
|
|
3039
|
+
async function restartLaunchd() {
|
|
3040
|
+
if (!existsSync4(launchAgentPlistPath())) {
|
|
3041
|
+
throw new Error(`launchd service \u672A\u5B89\u88C5\uFF1A${launchAgentPlistPath()}`);
|
|
3042
|
+
}
|
|
3043
|
+
if (isLoaded()) {
|
|
3044
|
+
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
3045
|
+
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
3046
|
+
await waitUntilUnloaded();
|
|
3047
|
+
}
|
|
3048
|
+
const bootstrap = runLaunchctl(["bootstrap", userTarget(), launchAgentPlistPath()]);
|
|
3049
|
+
if (!bootstrap.ok) throw launchctlError("launchctl bootstrap", bootstrap);
|
|
3050
|
+
return statusLaunchd();
|
|
3051
|
+
}
|
|
3052
|
+
function statusLaunchd() {
|
|
3053
|
+
const result = runLaunchctl(["print", serviceTarget()]);
|
|
3054
|
+
const raw = result.stdout || result.stderr;
|
|
3055
|
+
const parsed = parseLaunchdStatus(raw);
|
|
3056
|
+
return {
|
|
3057
|
+
installed: existsSync4(launchAgentPlistPath()),
|
|
3058
|
+
loaded: result.ok,
|
|
3059
|
+
plistPath: launchAgentPlistPath(),
|
|
3060
|
+
stdoutPath: serviceStdoutPath(),
|
|
3061
|
+
stderrPath: serviceStderrPath(),
|
|
3062
|
+
pid: parsed.pid,
|
|
3063
|
+
lastExit: parsed.lastExit,
|
|
3064
|
+
raw
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
async function tailLaunchdLogs(follow) {
|
|
3068
|
+
await ensureLogFiles();
|
|
3069
|
+
const args = follow ? ["-f", serviceStdoutPath(), serviceStderrPath()] : ["-n", "100", serviceStdoutPath(), serviceStderrPath()];
|
|
3070
|
+
await new Promise((resolvePromise, reject) => {
|
|
3071
|
+
const child = spawn4("tail", args, { stdio: "inherit" });
|
|
3072
|
+
child.on("error", reject);
|
|
3073
|
+
child.on("close", (code) => {
|
|
3074
|
+
if (code === 0 || follow && code === null) {
|
|
3075
|
+
resolvePromise();
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
reject(new Error(`tail \u9000\u51FA\u7801 ${code ?? "unknown"}`));
|
|
3079
|
+
});
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
function parseLaunchdStatus(text) {
|
|
3083
|
+
return {
|
|
3084
|
+
pid: text.match(/\bpid\s*=\s*(\d+)/)?.[1],
|
|
3085
|
+
lastExit: text.match(/last exit code\s*=\s*(-?\d+)/i)?.[1]
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
function isLoaded() {
|
|
3089
|
+
const result = spawnSync("launchctl", ["print", serviceTarget()], {
|
|
3090
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3091
|
+
});
|
|
3092
|
+
return result.status === 0;
|
|
3093
|
+
}
|
|
3094
|
+
async function waitUntilUnloaded(timeoutMs = 5e3) {
|
|
3095
|
+
const deadline = Date.now() + timeoutMs;
|
|
3096
|
+
while (Date.now() < deadline) {
|
|
3097
|
+
if (!isLoaded()) return;
|
|
3098
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 200));
|
|
3099
|
+
}
|
|
3100
|
+
throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
|
|
3101
|
+
}
|
|
3102
|
+
async function ensureLogFiles() {
|
|
3103
|
+
await mkdir4(paths.appDir, { recursive: true });
|
|
3104
|
+
await appendFile(serviceStdoutPath(), "");
|
|
3105
|
+
await appendFile(serviceStderrPath(), "");
|
|
3106
|
+
}
|
|
3107
|
+
function userTarget() {
|
|
3108
|
+
return `gui/${userInfo2().uid}`;
|
|
3109
|
+
}
|
|
3110
|
+
function serviceTarget() {
|
|
3111
|
+
return `${userTarget()}/${LAUNCHD_LABEL}`;
|
|
3112
|
+
}
|
|
3113
|
+
function runLaunchctl(args) {
|
|
3114
|
+
const result = spawnSync("launchctl", args, { encoding: "utf8" });
|
|
3115
|
+
return {
|
|
3116
|
+
ok: result.status === 0,
|
|
3117
|
+
status: result.status,
|
|
3118
|
+
stdout: result.stdout ?? "",
|
|
3119
|
+
stderr: result.stderr ?? ""
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
function launchctlError(command, result) {
|
|
3123
|
+
const output = [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join("\n");
|
|
3124
|
+
return new Error(`${command} \u5931\u8D25\uFF08exit ${result.status ?? "unknown"}\uFF09${output ? `\uFF1A${output}` : ""}`);
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
// src/service/adapter.ts
|
|
3128
|
+
function getServiceAdapter() {
|
|
3129
|
+
if (process.platform !== "darwin") {
|
|
3130
|
+
throw new Error("service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF0C\u540E\u7EED\u4F1A\u652F\u6301 Windows/systemd\u3002");
|
|
3131
|
+
}
|
|
3132
|
+
return {
|
|
3133
|
+
install: installLaunchd,
|
|
3134
|
+
uninstall: uninstallLaunchd,
|
|
3135
|
+
status: async () => statusLaunchd(),
|
|
3136
|
+
restart: restartLaunchd,
|
|
3137
|
+
logs: tailLaunchdLogs
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// src/service/update.ts
|
|
3142
|
+
var execFileP = promisify(execFile);
|
|
3143
|
+
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3144
|
+
function pkgRoot() {
|
|
3145
|
+
return resolve3(dirname6(fileURLToPath3(import.meta.url)), "..");
|
|
3146
|
+
}
|
|
3147
|
+
function pkgJson() {
|
|
3148
|
+
try {
|
|
3149
|
+
return JSON.parse(readFileSync2(join8(pkgRoot(), "package.json"), "utf8"));
|
|
3150
|
+
} catch {
|
|
3151
|
+
return {};
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
function currentVersion() {
|
|
3155
|
+
return pkgJson().version ?? "0.0.0";
|
|
3156
|
+
}
|
|
3157
|
+
function packageName() {
|
|
3158
|
+
return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
|
|
3159
|
+
}
|
|
3160
|
+
function isDevSource() {
|
|
3161
|
+
return existsSync5(join8(pkgRoot(), ".git"));
|
|
3162
|
+
}
|
|
3163
|
+
function isNewer(a, b) {
|
|
3164
|
+
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
3165
|
+
const pb = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
3166
|
+
for (let i = 0; i < 3; i++) {
|
|
3167
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
3168
|
+
if (d !== 0) return d > 0;
|
|
3169
|
+
}
|
|
3170
|
+
return false;
|
|
3171
|
+
}
|
|
3172
|
+
async function latestVersion() {
|
|
3173
|
+
try {
|
|
3174
|
+
const { stdout } = await execFileP(NPM, ["view", packageName(), "version"], { timeout: 2e4 });
|
|
3175
|
+
const v = stdout.trim();
|
|
3176
|
+
return /^\d+\.\d+\.\d+/.test(v) ? v : null;
|
|
3177
|
+
} catch {
|
|
3178
|
+
return null;
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
async function installLatest(opts = {}) {
|
|
3182
|
+
const target = `${packageName()}@latest`;
|
|
3183
|
+
return await new Promise((resolveP) => {
|
|
3184
|
+
const child = spawn5(NPM, ["install", "-g", target], {
|
|
3185
|
+
stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
3186
|
+
});
|
|
3187
|
+
let out = "";
|
|
3188
|
+
if (!opts.inherit) {
|
|
3189
|
+
child.stdout?.on("data", (d) => out += d);
|
|
3190
|
+
child.stderr?.on("data", (d) => out += d);
|
|
3191
|
+
}
|
|
3192
|
+
child.on("error", (e) => resolveP({ ok: false, message: e.message }));
|
|
3193
|
+
child.on("close", (code) => {
|
|
3194
|
+
const tail = out.trim().slice(-600);
|
|
3195
|
+
resolveP({ ok: code === 0, message: opts.inherit ? `\u9000\u51FA\u7801 ${code}` : tail || `\u9000\u51FA\u7801 ${code}` });
|
|
3196
|
+
});
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
function daemonRunning() {
|
|
3200
|
+
try {
|
|
3201
|
+
return statusLaunchd().loaded;
|
|
3202
|
+
} catch {
|
|
3203
|
+
return false;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
async function restartDaemon() {
|
|
3207
|
+
await getServiceAdapter().restart();
|
|
3208
|
+
}
|
|
3209
|
+
|
|
2791
3210
|
// src/project/registry.ts
|
|
2792
|
-
import { mkdir as
|
|
2793
|
-
import { dirname as
|
|
3211
|
+
import { mkdir as mkdir5, readFile as readFile5, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
3212
|
+
import { dirname as dirname7 } from "path";
|
|
2794
3213
|
var FILE_VERSION2 = 1;
|
|
2795
3214
|
async function read() {
|
|
2796
3215
|
try {
|
|
@@ -2803,10 +3222,10 @@ async function read() {
|
|
|
2803
3222
|
}
|
|
2804
3223
|
}
|
|
2805
3224
|
async function write(projects) {
|
|
2806
|
-
await
|
|
3225
|
+
await mkdir5(dirname7(paths.projectsFile), { recursive: true });
|
|
2807
3226
|
const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
|
|
2808
3227
|
const body = { version: FILE_VERSION2, projects };
|
|
2809
|
-
await
|
|
3228
|
+
await writeFile5(tmp, `${JSON.stringify(body, null, 2)}
|
|
2810
3229
|
`, "utf8");
|
|
2811
3230
|
await rename4(tmp, paths.projectsFile);
|
|
2812
3231
|
}
|
|
@@ -2847,14 +3266,14 @@ async function removeProject(name) {
|
|
|
2847
3266
|
}
|
|
2848
3267
|
|
|
2849
3268
|
// src/project/lifecycle.ts
|
|
2850
|
-
import { mkdir as
|
|
2851
|
-
import { existsSync as
|
|
2852
|
-
import { isAbsolute, join as
|
|
3269
|
+
import { mkdir as mkdir6 } from "fs/promises";
|
|
3270
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3271
|
+
import { isAbsolute, join as join9, resolve as resolve4 } from "path";
|
|
2853
3272
|
|
|
2854
3273
|
// src/project/git-info.ts
|
|
2855
|
-
import { execFile } from "child_process";
|
|
2856
|
-
import { promisify } from "util";
|
|
2857
|
-
var execFileAsync =
|
|
3274
|
+
import { execFile as execFile2 } from "child_process";
|
|
3275
|
+
import { promisify as promisify2 } from "util";
|
|
3276
|
+
var execFileAsync = promisify2(execFile2);
|
|
2858
3277
|
async function currentBranch(cwd) {
|
|
2859
3278
|
try {
|
|
2860
3279
|
const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
@@ -2987,20 +3406,25 @@ async function createProject(channel, input2) {
|
|
|
2987
3406
|
let cwd;
|
|
2988
3407
|
let blank;
|
|
2989
3408
|
if (input2.existingPath) {
|
|
2990
|
-
cwd = isAbsolute(input2.existingPath) ? input2.existingPath :
|
|
2991
|
-
if (!
|
|
3409
|
+
cwd = isAbsolute(input2.existingPath) ? input2.existingPath : resolve4(input2.existingPath);
|
|
3410
|
+
if (!existsSync6(cwd)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd}`);
|
|
2992
3411
|
blank = false;
|
|
2993
3412
|
} else {
|
|
2994
|
-
cwd =
|
|
2995
|
-
await
|
|
3413
|
+
cwd = join9(paths.projectsRootDir, name);
|
|
3414
|
+
await mkdir6(cwd, { recursive: true });
|
|
2996
3415
|
blank = true;
|
|
2997
3416
|
}
|
|
2998
3417
|
const res = await channel.rawClient.im.v1.chat.create({
|
|
2999
|
-
params: { user_id_type: "open_id"
|
|
3418
|
+
params: { user_id_type: "open_id" },
|
|
3000
3419
|
data: { name, user_id_list: [input2.ownerOpenId] }
|
|
3001
3420
|
});
|
|
3002
3421
|
const chatId = res.data?.chat_id;
|
|
3003
3422
|
if (!chatId) throw new Error(`\u5EFA\u7FA4\u5931\u8D25\uFF1A${JSON.stringify(res).slice(0, 200)}`);
|
|
3423
|
+
await channel.rawClient.im.v1.chatManagers.addManagers({
|
|
3424
|
+
path: { chat_id: chatId },
|
|
3425
|
+
params: { member_id_type: "open_id" },
|
|
3426
|
+
data: { manager_ids: [input2.ownerOpenId] }
|
|
3427
|
+
}).catch((err) => log.fail("project", err, { phase: "add-manager" }));
|
|
3004
3428
|
const project = { name, chatId, cwd, blank, createdAt: Date.now(), kind: input2.kind ?? "multi" };
|
|
3005
3429
|
await addProject(project);
|
|
3006
3430
|
log.info("project", "create", { name, chatId, cwd, blank });
|
|
@@ -3020,8 +3444,8 @@ async function transferOwnership(channel, chatId, toOpenId) {
|
|
|
3020
3444
|
}
|
|
3021
3445
|
|
|
3022
3446
|
// src/bot/session-store.ts
|
|
3023
|
-
import { mkdir as
|
|
3024
|
-
import { dirname as
|
|
3447
|
+
import { mkdir as mkdir7, readFile as readFile6, rename as rename5, writeFile as writeFile6 } from "fs/promises";
|
|
3448
|
+
import { dirname as dirname8 } from "path";
|
|
3025
3449
|
var FILE_VERSION3 = 1;
|
|
3026
3450
|
async function read2() {
|
|
3027
3451
|
try {
|
|
@@ -3034,10 +3458,10 @@ async function read2() {
|
|
|
3034
3458
|
}
|
|
3035
3459
|
}
|
|
3036
3460
|
async function write2(sessions) {
|
|
3037
|
-
await
|
|
3461
|
+
await mkdir7(dirname8(paths.sessionsFile), { recursive: true });
|
|
3038
3462
|
const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
|
|
3039
3463
|
const body = { version: FILE_VERSION3, sessions };
|
|
3040
|
-
await
|
|
3464
|
+
await writeFile6(tmp, `${JSON.stringify(body, null, 2)}
|
|
3041
3465
|
`, "utf8");
|
|
3042
3466
|
await rename5(tmp, paths.sessionsFile);
|
|
3043
3467
|
}
|
|
@@ -3777,16 +4201,74 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3777
4201
|
if (dmAdmin(evt.operator?.openId)) await patch(evt, buildSettingsCard(cfg));
|
|
3778
4202
|
}).on(DM.doctor, async ({ evt }) => {
|
|
3779
4203
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
3780
|
-
const
|
|
3781
|
-
const
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
4204
|
+
const codexBin = resolveCodexBin();
|
|
4205
|
+
const info = {
|
|
4206
|
+
codexOk: await backend.isAvailable().catch(() => false),
|
|
4207
|
+
codexVer: codexBin ? codexVersion(codexBin) : null,
|
|
4208
|
+
conn: channel.getConnectionStatus?.()?.state ?? "unknown",
|
|
4209
|
+
bridgeVer: bridgeVersion(),
|
|
4210
|
+
node: process.version,
|
|
4211
|
+
platform: `${process.platform}-${process.arch}`,
|
|
4212
|
+
logStdout: serviceStdoutPath(),
|
|
4213
|
+
logStderr: serviceStderrPath(),
|
|
4214
|
+
configFile: paths.configFile
|
|
4215
|
+
};
|
|
4216
|
+
await sendManagedCard(channel, evt.chatId, buildDoctorCard(info), evt.messageId).catch(
|
|
4217
|
+
(err) => log.fail("console", err, { cmd: "doctor" })
|
|
4218
|
+
);
|
|
3785
4219
|
}).on(DM.reconnect, async ({ evt }) => {
|
|
3786
4220
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
3787
4221
|
const conn = channel.getConnectionStatus?.()?.state ?? "unknown";
|
|
3788
4222
|
await channel.send(evt.chatId, { markdown: `\u{1F504} \u957F\u8FDE\u63A5\u72B6\u6001\uFF1A**${conn}**
|
|
3789
4223
|
SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u91CD\u8DD1 \`feishu-codex-bridge run\`\uFF08\u524D\u53F0\uFF09\u6216 \`feishu-codex-bridge restart\`\uFF08\u540E\u53F0\u5B88\u62A4\uFF09\u3002` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
4224
|
+
}).on(DM.update, ({ evt }) => {
|
|
4225
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4226
|
+
void (async () => {
|
|
4227
|
+
await new Promise((r) => setTimeout(r, CARD_SETTLE_MS));
|
|
4228
|
+
await updateManagedCard(channel, evt.messageId, buildUpdateCard({ phase: "checking" })).catch(
|
|
4229
|
+
() => void 0
|
|
4230
|
+
);
|
|
4231
|
+
const current = currentVersion();
|
|
4232
|
+
const latest = await latestVersion().catch(() => null);
|
|
4233
|
+
const hasUpdate = !!latest && isNewer(latest, current);
|
|
4234
|
+
log.info("console", "update-check", { current, latest, hasUpdate });
|
|
4235
|
+
await updateManagedCard(
|
|
4236
|
+
channel,
|
|
4237
|
+
evt.messageId,
|
|
4238
|
+
buildUpdateCard({ phase: "checked", current, latest, hasUpdate, dev: isDevSource() })
|
|
4239
|
+
).catch((e) => log.fail("console", e, { phase: "update-check" }));
|
|
4240
|
+
})();
|
|
4241
|
+
}).on(DM.updateDo, ({ evt }) => {
|
|
4242
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
4243
|
+
void (async () => {
|
|
4244
|
+
await new Promise((r) => setTimeout(r, CARD_SETTLE_MS));
|
|
4245
|
+
const from = currentVersion();
|
|
4246
|
+
await updateManagedCard(channel, evt.messageId, buildUpdateCard({ phase: "updating", from })).catch(
|
|
4247
|
+
() => void 0
|
|
4248
|
+
);
|
|
4249
|
+
const res = await installLatest();
|
|
4250
|
+
if (!res.ok) {
|
|
4251
|
+
log.info("console", "update-failed", { from });
|
|
4252
|
+
await updateManagedCard(
|
|
4253
|
+
channel,
|
|
4254
|
+
evt.messageId,
|
|
4255
|
+
buildUpdateCard({ phase: "error", from, message: res.message })
|
|
4256
|
+
).catch((e) => log.fail("console", e, { phase: "update-error" }));
|
|
4257
|
+
return;
|
|
4258
|
+
}
|
|
4259
|
+
const to = currentVersion();
|
|
4260
|
+
const willRestart = daemonRunning();
|
|
4261
|
+
log.info("console", "update-done", { from, to, willRestart });
|
|
4262
|
+
await updateManagedCard(
|
|
4263
|
+
channel,
|
|
4264
|
+
evt.messageId,
|
|
4265
|
+
buildUpdateCard({ phase: "done", from, to, willRestart })
|
|
4266
|
+
).catch((e) => log.fail("console", e, { phase: "update-done" }));
|
|
4267
|
+
if (willRestart) {
|
|
4268
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
4269
|
+
await restartDaemon().catch((e) => log.fail("console", e, { phase: "update-restart" }));
|
|
4270
|
+
}
|
|
4271
|
+
})();
|
|
3790
4272
|
}).on(DM.rmConfirm, async ({ evt, value }) => {
|
|
3791
4273
|
const name = typeof value.n === "string" ? value.n : void 0;
|
|
3792
4274
|
if (!dmAdmin(evt.operator?.openId) || !name) return;
|
|
@@ -4192,8 +4674,8 @@ async function startBridge(opts) {
|
|
|
4192
4674
|
}
|
|
4193
4675
|
|
|
4194
4676
|
// src/core/single-instance.ts
|
|
4195
|
-
import { mkdirSync as mkdirSync2, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
4196
|
-
import { dirname as
|
|
4677
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "fs";
|
|
4678
|
+
import { dirname as dirname9 } from "path";
|
|
4197
4679
|
var BridgeAlreadyRunningError = class extends Error {
|
|
4198
4680
|
constructor(pid) {
|
|
4199
4681
|
super(
|
|
@@ -4215,20 +4697,20 @@ function isAlive(pid) {
|
|
|
4215
4697
|
function acquireSingleInstanceLock(appId) {
|
|
4216
4698
|
const file = paths.processesFile;
|
|
4217
4699
|
try {
|
|
4218
|
-
const rec = JSON.parse(
|
|
4700
|
+
const rec = JSON.parse(readFileSync3(file, "utf8"));
|
|
4219
4701
|
if (rec.pid && rec.pid !== process.pid && rec.appId === appId && isAlive(rec.pid)) {
|
|
4220
4702
|
throw new BridgeAlreadyRunningError(rec.pid);
|
|
4221
4703
|
}
|
|
4222
4704
|
} catch (err) {
|
|
4223
4705
|
if (err instanceof BridgeAlreadyRunningError) throw err;
|
|
4224
4706
|
}
|
|
4225
|
-
mkdirSync2(
|
|
4707
|
+
mkdirSync2(dirname9(file), { recursive: true });
|
|
4226
4708
|
const record = { pid: process.pid, appId, startedAt: Date.now() };
|
|
4227
4709
|
writeFileSync(file, `${JSON.stringify(record)}
|
|
4228
4710
|
`, "utf8");
|
|
4229
4711
|
const release = () => {
|
|
4230
4712
|
try {
|
|
4231
|
-
const rec = JSON.parse(
|
|
4713
|
+
const rec = JSON.parse(readFileSync3(file, "utf8"));
|
|
4232
4714
|
if (rec.pid === process.pid) unlinkSync(file);
|
|
4233
4715
|
} catch {
|
|
4234
4716
|
}
|
|
@@ -4279,187 +4761,6 @@ async function runRun() {
|
|
|
4279
4761
|
});
|
|
4280
4762
|
}
|
|
4281
4763
|
|
|
4282
|
-
// src/service/launchd.ts
|
|
4283
|
-
import { spawn as spawn4, spawnSync } from "child_process";
|
|
4284
|
-
import { existsSync as existsSync5 } from "fs";
|
|
4285
|
-
import { appendFile, mkdir as mkdir7, rm as rm2, writeFile as writeFile6 } from "fs/promises";
|
|
4286
|
-
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
4287
|
-
import { dirname as dirname7, join as join8, resolve as resolve2 } from "path";
|
|
4288
|
-
import { fileURLToPath } from "url";
|
|
4289
|
-
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
4290
|
-
function launchAgentPlistPath() {
|
|
4291
|
-
return join8(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
4292
|
-
}
|
|
4293
|
-
function serviceStdoutPath() {
|
|
4294
|
-
return join8(paths.appDir, "service.log");
|
|
4295
|
-
}
|
|
4296
|
-
function serviceStderrPath() {
|
|
4297
|
-
return join8(paths.appDir, "service.err.log");
|
|
4298
|
-
}
|
|
4299
|
-
function resolveCliBinPath() {
|
|
4300
|
-
const distDir = dirname7(fileURLToPath(import.meta.url));
|
|
4301
|
-
return resolve2(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
4302
|
-
}
|
|
4303
|
-
function escapeXml(value) {
|
|
4304
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4305
|
-
}
|
|
4306
|
-
function buildPlist() {
|
|
4307
|
-
const nodePath = process.execPath;
|
|
4308
|
-
const cliBinPath = resolveCliBinPath();
|
|
4309
|
-
const pathEnv = process.env.PATH ?? "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
4310
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4311
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4312
|
-
<plist version="1.0">
|
|
4313
|
-
<dict>
|
|
4314
|
-
<key>Label</key>
|
|
4315
|
-
<string>${LAUNCHD_LABEL}</string>
|
|
4316
|
-
<key>ProgramArguments</key>
|
|
4317
|
-
<array>
|
|
4318
|
-
<string>${escapeXml(nodePath)}</string>
|
|
4319
|
-
<string>${escapeXml(cliBinPath)}</string>
|
|
4320
|
-
<string>run</string>
|
|
4321
|
-
</array>
|
|
4322
|
-
<key>RunAtLoad</key>
|
|
4323
|
-
<true/>
|
|
4324
|
-
<key>KeepAlive</key>
|
|
4325
|
-
<true/>
|
|
4326
|
-
<key>StandardOutPath</key>
|
|
4327
|
-
<string>${escapeXml(serviceStdoutPath())}</string>
|
|
4328
|
-
<key>StandardErrorPath</key>
|
|
4329
|
-
<string>${escapeXml(serviceStderrPath())}</string>
|
|
4330
|
-
<key>EnvironmentVariables</key>
|
|
4331
|
-
<dict>
|
|
4332
|
-
<key>PATH</key>
|
|
4333
|
-
<string>${escapeXml(pathEnv)}</string>
|
|
4334
|
-
</dict>
|
|
4335
|
-
</dict>
|
|
4336
|
-
</plist>
|
|
4337
|
-
`;
|
|
4338
|
-
}
|
|
4339
|
-
async function installLaunchd() {
|
|
4340
|
-
const plistPath = launchAgentPlistPath();
|
|
4341
|
-
await mkdir7(dirname7(plistPath), { recursive: true });
|
|
4342
|
-
await ensureLogFiles();
|
|
4343
|
-
await writeFile6(plistPath, buildPlist(), "utf8");
|
|
4344
|
-
if (isLoaded()) {
|
|
4345
|
-
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
4346
|
-
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
4347
|
-
await waitUntilUnloaded();
|
|
4348
|
-
}
|
|
4349
|
-
const bootstrap = runLaunchctl(["bootstrap", userTarget(), plistPath]);
|
|
4350
|
-
if (!bootstrap.ok) throw launchctlError("launchctl bootstrap", bootstrap);
|
|
4351
|
-
return statusLaunchd();
|
|
4352
|
-
}
|
|
4353
|
-
async function uninstallLaunchd() {
|
|
4354
|
-
if (isLoaded()) {
|
|
4355
|
-
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
4356
|
-
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
4357
|
-
await waitUntilUnloaded();
|
|
4358
|
-
}
|
|
4359
|
-
await rm2(launchAgentPlistPath(), { force: true });
|
|
4360
|
-
}
|
|
4361
|
-
async function restartLaunchd() {
|
|
4362
|
-
if (!existsSync5(launchAgentPlistPath())) {
|
|
4363
|
-
throw new Error(`launchd service \u672A\u5B89\u88C5\uFF1A${launchAgentPlistPath()}`);
|
|
4364
|
-
}
|
|
4365
|
-
if (isLoaded()) {
|
|
4366
|
-
const bootout = runLaunchctl(["bootout", serviceTarget()]);
|
|
4367
|
-
if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
|
|
4368
|
-
await waitUntilUnloaded();
|
|
4369
|
-
}
|
|
4370
|
-
const bootstrap = runLaunchctl(["bootstrap", userTarget(), launchAgentPlistPath()]);
|
|
4371
|
-
if (!bootstrap.ok) throw launchctlError("launchctl bootstrap", bootstrap);
|
|
4372
|
-
return statusLaunchd();
|
|
4373
|
-
}
|
|
4374
|
-
function statusLaunchd() {
|
|
4375
|
-
const result = runLaunchctl(["print", serviceTarget()]);
|
|
4376
|
-
const raw = result.stdout || result.stderr;
|
|
4377
|
-
const parsed = parseLaunchdStatus(raw);
|
|
4378
|
-
return {
|
|
4379
|
-
installed: existsSync5(launchAgentPlistPath()),
|
|
4380
|
-
loaded: result.ok,
|
|
4381
|
-
plistPath: launchAgentPlistPath(),
|
|
4382
|
-
stdoutPath: serviceStdoutPath(),
|
|
4383
|
-
stderrPath: serviceStderrPath(),
|
|
4384
|
-
pid: parsed.pid,
|
|
4385
|
-
lastExit: parsed.lastExit,
|
|
4386
|
-
raw
|
|
4387
|
-
};
|
|
4388
|
-
}
|
|
4389
|
-
async function tailLaunchdLogs(follow) {
|
|
4390
|
-
await ensureLogFiles();
|
|
4391
|
-
const args = follow ? ["-f", serviceStdoutPath(), serviceStderrPath()] : ["-n", "100", serviceStdoutPath(), serviceStderrPath()];
|
|
4392
|
-
await new Promise((resolvePromise, reject) => {
|
|
4393
|
-
const child = spawn4("tail", args, { stdio: "inherit" });
|
|
4394
|
-
child.on("error", reject);
|
|
4395
|
-
child.on("close", (code) => {
|
|
4396
|
-
if (code === 0 || follow && code === null) {
|
|
4397
|
-
resolvePromise();
|
|
4398
|
-
return;
|
|
4399
|
-
}
|
|
4400
|
-
reject(new Error(`tail \u9000\u51FA\u7801 ${code ?? "unknown"}`));
|
|
4401
|
-
});
|
|
4402
|
-
});
|
|
4403
|
-
}
|
|
4404
|
-
function parseLaunchdStatus(text) {
|
|
4405
|
-
return {
|
|
4406
|
-
pid: text.match(/\bpid\s*=\s*(\d+)/)?.[1],
|
|
4407
|
-
lastExit: text.match(/last exit code\s*=\s*(-?\d+)/i)?.[1]
|
|
4408
|
-
};
|
|
4409
|
-
}
|
|
4410
|
-
function isLoaded() {
|
|
4411
|
-
const result = spawnSync("launchctl", ["print", serviceTarget()], {
|
|
4412
|
-
stdio: ["ignore", "ignore", "ignore"]
|
|
4413
|
-
});
|
|
4414
|
-
return result.status === 0;
|
|
4415
|
-
}
|
|
4416
|
-
async function waitUntilUnloaded(timeoutMs = 5e3) {
|
|
4417
|
-
const deadline = Date.now() + timeoutMs;
|
|
4418
|
-
while (Date.now() < deadline) {
|
|
4419
|
-
if (!isLoaded()) return;
|
|
4420
|
-
await new Promise((resolvePromise) => setTimeout(resolvePromise, 200));
|
|
4421
|
-
}
|
|
4422
|
-
throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
|
|
4423
|
-
}
|
|
4424
|
-
async function ensureLogFiles() {
|
|
4425
|
-
await mkdir7(paths.appDir, { recursive: true });
|
|
4426
|
-
await appendFile(serviceStdoutPath(), "");
|
|
4427
|
-
await appendFile(serviceStderrPath(), "");
|
|
4428
|
-
}
|
|
4429
|
-
function userTarget() {
|
|
4430
|
-
return `gui/${userInfo2().uid}`;
|
|
4431
|
-
}
|
|
4432
|
-
function serviceTarget() {
|
|
4433
|
-
return `${userTarget()}/${LAUNCHD_LABEL}`;
|
|
4434
|
-
}
|
|
4435
|
-
function runLaunchctl(args) {
|
|
4436
|
-
const result = spawnSync("launchctl", args, { encoding: "utf8" });
|
|
4437
|
-
return {
|
|
4438
|
-
ok: result.status === 0,
|
|
4439
|
-
status: result.status,
|
|
4440
|
-
stdout: result.stdout ?? "",
|
|
4441
|
-
stderr: result.stderr ?? ""
|
|
4442
|
-
};
|
|
4443
|
-
}
|
|
4444
|
-
function launchctlError(command, result) {
|
|
4445
|
-
const output = [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join("\n");
|
|
4446
|
-
return new Error(`${command} \u5931\u8D25\uFF08exit ${result.status ?? "unknown"}\uFF09${output ? `\uFF1A${output}` : ""}`);
|
|
4447
|
-
}
|
|
4448
|
-
|
|
4449
|
-
// src/service/adapter.ts
|
|
4450
|
-
function getServiceAdapter() {
|
|
4451
|
-
if (process.platform !== "darwin") {
|
|
4452
|
-
throw new Error("service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF0C\u540E\u7EED\u4F1A\u652F\u6301 Windows/systemd\u3002");
|
|
4453
|
-
}
|
|
4454
|
-
return {
|
|
4455
|
-
install: installLaunchd,
|
|
4456
|
-
uninstall: uninstallLaunchd,
|
|
4457
|
-
status: async () => statusLaunchd(),
|
|
4458
|
-
restart: restartLaunchd,
|
|
4459
|
-
logs: tailLaunchdLogs
|
|
4460
|
-
};
|
|
4461
|
-
}
|
|
4462
|
-
|
|
4463
4764
|
// src/cli/commands/daemon.ts
|
|
4464
4765
|
async function runStart() {
|
|
4465
4766
|
const ready = await ensureOnboarded({ allowCreate: true });
|
|
@@ -4505,6 +4806,54 @@ function printStatus(status) {
|
|
|
4505
4806
|
}
|
|
4506
4807
|
}
|
|
4507
4808
|
|
|
4809
|
+
// src/cli/commands/update.ts
|
|
4810
|
+
async function runUpdate(opts = {}) {
|
|
4811
|
+
const pkg = packageName();
|
|
4812
|
+
const current = currentVersion();
|
|
4813
|
+
console.log(`\u5F53\u524D\u7248\u672C\uFF1Av${current}`);
|
|
4814
|
+
console.log("\u67E5\u8BE2\u6700\u65B0\u7248\u672C\u2026");
|
|
4815
|
+
const latest = await latestVersion();
|
|
4816
|
+
if (!latest) {
|
|
4817
|
+
console.log("\u26A0\uFE0F \u67E5\u4E0D\u5230\u6700\u65B0\u7248\u672C\uFF08\u7F51\u7EDC\u6216 npm registry \u95EE\u9898\uFF09\u3002");
|
|
4818
|
+
process.exitCode = 1;
|
|
4819
|
+
return;
|
|
4820
|
+
}
|
|
4821
|
+
if (!isNewer(latest, current)) {
|
|
4822
|
+
console.log(`\u2713 \u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF08v${current}\uFF09\u3002`);
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
console.log(`\u53D1\u73B0\u65B0\u7248\u672C\uFF1Av${current} \u2192 v${latest}`);
|
|
4826
|
+
if (opts.check) {
|
|
4827
|
+
console.log("\u8FD0\u884C `feishu-codex-bridge update` \u5B89\u88C5\u66F4\u65B0\u3002");
|
|
4828
|
+
return;
|
|
4829
|
+
}
|
|
4830
|
+
if (isDevSource()) {
|
|
4831
|
+
console.log("\u68C0\u6D4B\u5230\u6E90\u7801\u5F00\u53D1\u6A21\u5F0F\uFF08\u4ED3\u5E93\u5185\u6709 .git\uFF09\u3002\u8BF7\u7528\uFF1Agit pull && npm i \u2014\u2014 \u8DF3\u8FC7\u5168\u5C40\u5B89\u88C5\u3002");
|
|
4832
|
+
return;
|
|
4833
|
+
}
|
|
4834
|
+
console.log(`\u5F00\u59CB\u5168\u5C40\u5B89\u88C5\u6700\u65B0\u7248\uFF08npm i -g ${pkg}@latest\uFF09\u2026`);
|
|
4835
|
+
const res = await installLatest({ inherit: true });
|
|
4836
|
+
if (!res.ok) {
|
|
4837
|
+
console.log(`\u274C \u5B89\u88C5\u5931\u8D25\uFF1A${res.message}`);
|
|
4838
|
+
console.log(`\u53EF\u5728\u7EC8\u7AEF\u624B\u52A8\u6267\u884C\uFF1Anpm i -g ${pkg}@latest`);
|
|
4839
|
+
process.exitCode = 1;
|
|
4840
|
+
return;
|
|
4841
|
+
}
|
|
4842
|
+
console.log(`\u2713 \u5DF2\u66F4\u65B0\u5230 v${latest}`);
|
|
4843
|
+
if (daemonRunning()) {
|
|
4844
|
+
console.log("\u91CD\u542F\u540E\u53F0\u670D\u52A1\u4EE5\u52A0\u8F7D\u65B0\u7248\u672C\u2026");
|
|
4845
|
+
try {
|
|
4846
|
+
await restartDaemon();
|
|
4847
|
+
console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u91CD\u542F\uFF0C\u65B0\u7248\u672C\u5DF2\u751F\u6548\u3002");
|
|
4848
|
+
} catch (err) {
|
|
4849
|
+
console.log(`\u26A0\uFE0F \u81EA\u52A8\u91CD\u542F\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
4850
|
+
console.log("\u8BF7\u624B\u52A8\u6267\u884C\uFF1Afeishu-codex-bridge restart");
|
|
4851
|
+
}
|
|
4852
|
+
} else {
|
|
4853
|
+
console.log("\u672A\u68C0\u6D4B\u5230\u8FD0\u884C\u4E2D\u7684\u540E\u53F0 daemon\uFF1B\u4E0B\u6B21 `start` / `run` \u5373\u7528\u65B0\u7248\u672C\u3002");
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4508
4857
|
// src/cli/commands/bot.ts
|
|
4509
4858
|
import { rm as rm3 } from "fs/promises";
|
|
4510
4859
|
async function runBotInit(name) {
|
|
@@ -4618,29 +4967,21 @@ async function secretsRemove(id) {
|
|
|
4618
4967
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
4619
4968
|
}
|
|
4620
4969
|
function readStdin() {
|
|
4621
|
-
return new Promise((
|
|
4970
|
+
return new Promise((resolve5) => {
|
|
4622
4971
|
let data = "";
|
|
4623
4972
|
if (process.stdin.isTTY) {
|
|
4624
|
-
|
|
4973
|
+
resolve5("");
|
|
4625
4974
|
return;
|
|
4626
4975
|
}
|
|
4627
4976
|
process.stdin.setEncoding("utf8");
|
|
4628
4977
|
process.stdin.on("data", (c) => data += c);
|
|
4629
|
-
process.stdin.on("end", () =>
|
|
4978
|
+
process.stdin.on("end", () => resolve5(data));
|
|
4630
4979
|
});
|
|
4631
4980
|
}
|
|
4632
4981
|
|
|
4633
4982
|
// src/cli/index.ts
|
|
4634
4983
|
var program = new Command();
|
|
4635
|
-
|
|
4636
|
-
try {
|
|
4637
|
-
const pkgPath = resolve3(dirname8(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
4638
|
-
return JSON.parse(readFileSync2(pkgPath, "utf8")).version ?? "0.0.0";
|
|
4639
|
-
} catch {
|
|
4640
|
-
return "0.0.0";
|
|
4641
|
-
}
|
|
4642
|
-
}
|
|
4643
|
-
program.name("feishu-codex-bridge").description("\u628A\u98DE\u4E66/Lark \u6865\u63A5\u5230\u672C\u673A Codex\uFF08\u9879\u76EE=\u7FA4, \u8BDD\u9898=\u4F1A\u8BDD\uFF09").version(readVersion());
|
|
4984
|
+
program.name("feishu-codex-bridge").description("\u628A\u98DE\u4E66/Lark \u6865\u63A5\u5230\u672C\u673A Codex\uFF08\u9879\u76EE=\u7FA4, \u8BDD\u9898=\u4F1A\u8BDD\uFF09").version(bridgeVersion());
|
|
4644
4985
|
program.command("run").description("\u524D\u53F0\u542F\u52A8 bot\uFF08\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF1BCtrl+C \u4F18\u96C5\u9000\u51FA\uFF09").action(async () => {
|
|
4645
4986
|
await runRun();
|
|
4646
4987
|
});
|
|
@@ -4659,6 +5000,9 @@ program.command("status").description("\u540E\u53F0 daemon \u72B6\u6001\uFF08pid
|
|
|
4659
5000
|
program.command("logs").description("\u67E5\u770B\u540E\u53F0 daemon \u65E5\u5FD7").option("-f, --follow", "\u6301\u7EED\u8DDF\u968F\u65E5\u5FD7").action(async (options) => {
|
|
4660
5001
|
await runLogs(Boolean(options.follow));
|
|
4661
5002
|
});
|
|
5003
|
+
program.command("update").description("\u66F4\u65B0\u5230\u6700\u65B0\u7248\uFF08npm i -g\uFF09\uFF0C\u5E76\u81EA\u52A8\u91CD\u542F\u540E\u53F0 daemon").option("--check", "\u53EA\u68C0\u67E5\u6709\u65E0\u65B0\u7248\uFF0C\u4E0D\u5B89\u88C5").action(async (options) => {
|
|
5004
|
+
await runUpdate({ check: Boolean(options.check) });
|
|
5005
|
+
});
|
|
4662
5006
|
var bot = program.command("bot").description("\u98DE\u4E66\u673A\u5668\u4EBA\u7BA1\u7406\uFF08\u591A\u673A\u5668\u4EBA\uFF09");
|
|
4663
5007
|
bot.command("init [name]").description("\u6CE8\u518C\u4E00\u4E2A\u98DE\u4E66\u673A\u5668\u4EBA\u5E76\u6388\u6743\uFF08\u53EF\u9009\u77ED\u540D\uFF09").action(async (name) => {
|
|
4664
5008
|
await runBotInit(name);
|