@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.
Files changed (3) hide show
  1. package/README.md +11 -2
  2. package/dist/cli.js +590 -246
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # feishu-codex-bridge
2
2
 
3
+ [![npm version](https://badgen.net/npm/v/@modelzen/feishu-codex-bridge)](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
4
+ [![total downloads](https://badgen.net/npm/dt/@modelzen/feishu-codex-bridge)](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
5
+ [![downloads/month](https://badgen.net/npm/dm/@modelzen/feishu-codex-bridge)](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
6
+ [![license](https://badgen.net/npm/license/@modelzen/feishu-codex-bridge)](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 dirname2, join as join2 } from "path";
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(dirname(wrapperPath), { recursive: true });
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(dirname(path), { recursive: true });
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(dirname2(paths.botsFile), { recursive: true });
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 dirname3 } from "path";
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(dirname3(paths.secretsFile), { recursive: true });
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(dirname3(paths.keystoreSaltFile), { recursive: true });
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((resolve4, reject) => {
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 resolve4(value);
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((resolve4) => this.waiters.push(resolve4));
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((resolve4, reject) => {
1155
- this.pending.set(id, { resolve: resolve4, reject });
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((resolve4) => {
1191
+ await new Promise((resolve5) => {
1180
1192
  const t = setTimeout(() => {
1181
1193
  if (child.exitCode === null) child.kill("SIGKILL");
1182
- resolve4();
1194
+ resolve5();
1183
1195
  }, graceMs);
1184
1196
  child.once("exit", () => {
1185
1197
  clearTimeout(t);
1186
- resolve4();
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((resolve4, reject) => {
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
- resolve4(v);
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((resolve4) => {
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
- resolve4("start-failed");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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 mkdir4, readFile as readFile5, rename as rename4, writeFile as writeFile4 } from "fs/promises";
2793
- import { dirname as dirname4 } from "path";
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 mkdir4(dirname4(paths.projectsFile), { recursive: true });
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 writeFile4(tmp, `${JSON.stringify(body, null, 2)}
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 mkdir5 } from "fs/promises";
2851
- import { existsSync as existsSync4 } from "fs";
2852
- import { isAbsolute, join as join7, resolve } from "path";
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 = promisify(execFile);
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 : resolve(input2.existingPath);
2991
- if (!existsSync4(cwd)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd}`);
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 = join7(paths.projectsRootDir, name);
2995
- await mkdir5(cwd, { recursive: true });
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", set_bot_manager: true },
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 mkdir6, readFile as readFile6, rename as rename5, writeFile as writeFile5 } from "fs/promises";
3024
- import { dirname as dirname5 } from "path";
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 mkdir6(dirname5(paths.sessionsFile), { recursive: true });
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 writeFile5(tmp, `${JSON.stringify(body, null, 2)}
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 ok = await backend.isAvailable().catch(() => false);
3781
- const conn = channel.getConnectionStatus?.()?.state ?? "unknown";
3782
- await channel.send(evt.chatId, { markdown: `\u{1FA7A} **\u8BCA\u65AD**
3783
- - codex: ${ok ? "\u2705 \u53EF\u7528" : "\u274C \u4E0D\u53EF\u7528\uFF08\u68C0\u67E5 CODEX_BIN/PATH\uFF09"}
3784
- - \u957F\u8FDE\u63A5: ${conn}` }, { replyTo: evt.messageId }).catch(() => void 0);
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 dirname6 } from "path";
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(readFileSync(file, "utf8"));
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(dirname6(file), { recursive: true });
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(readFileSync(file, "utf8"));
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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((resolve4) => {
4970
+ return new Promise((resolve5) => {
4622
4971
  let data = "";
4623
4972
  if (process.stdin.isTTY) {
4624
- resolve4("");
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", () => resolve4(data));
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
- function readVersion() {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {