@modelzen/feishu-codex-bridge 0.3.0-win.1 → 0.3.1
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 +8 -5
- package/dist/cli.js +301 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
- **私聊控制台**:私聊机器人弹交互菜单 —— 新建项目、项目列表、设置、诊断、重连。
|
|
34
34
|
- **稳定隔离**:每会话独立 app-server 进程;卡死有 watchdog(默认 120s)→ 终止 → 回收,异常不波及其他群。
|
|
35
35
|
- **本地加密密钥库**:飞书应用密钥用 AES-256-GCM 存在 `~/.feishu-codex-bridge/`,不入仓库、不进环境变量。
|
|
36
|
-
-
|
|
36
|
+
- **跨平台常驻**:macOS / Windows / Linux·WSL 均可注册成后台服务、开机或登录自启(分别走 launchd / 登录自启免管理员 / systemd)。
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
| 依赖 | 说明 | 获取方式 |
|
|
43
43
|
|------|------|----------|
|
|
44
|
+
| **操作系统** | **macOS / Windows** 均支持;Linux·WSL 为 best-effort(已实现 systemd,未广泛实测) | — |
|
|
44
45
|
| **Node.js ≥ 20** | 运行时 | <https://nodejs.org> 或 `nvm install 20` |
|
|
45
46
|
| **Codex CLI** | 后端,bridge 会 spawn `codex app-server` | `npm i -g @openai/codex`,或装 Codex.app,或用 `CODEX_BIN` 指向已有二进制 |
|
|
46
47
|
| **Codex 已登录** | app-server 需要 `~/.codex/auth.json` | `codex login` |
|
|
@@ -78,7 +79,7 @@ feishu-codex-bridge run
|
|
|
78
79
|
### 3. 后台 daemon(`start` —— 日常这么跑)
|
|
79
80
|
|
|
80
81
|
```bash
|
|
81
|
-
feishu-codex-bridge start #
|
|
82
|
+
feishu-codex-bridge start # 装系统后台服务并启动:开机/登录自启、崩溃自动拉起、关终端照跑
|
|
82
83
|
feishu-codex-bridge status # 状态 / pid / 日志路径 / 上次退出码
|
|
83
84
|
feishu-codex-bridge logs -f # 跟踪日志
|
|
84
85
|
feishu-codex-bridge restart # 重启
|
|
@@ -90,7 +91,9 @@ feishu-codex-bridge update # 更新到最新版(npm i -g)并自动重启
|
|
|
90
91
|
|
|
91
92
|
`start` 会**先在当前终端完成 init**(没配置则扫码),并**阻塞到授权完成**——权限全部开通、且你确认已订阅事件/发布版本——才真正装服务,绝不会装一个收不到消息的空壳。daemon 体跑的就是 `run`。
|
|
92
93
|
|
|
93
|
-
>
|
|
94
|
+
> 🖥 **各平台后台机制**:macOS = launchd 用户服务;**Windows = 登录自启(写 `HKCU\…\Run`,隐藏启动,全程免管理员)**;Linux·WSL = systemd 用户单元(`systemctl --user`,需要 `loginctl enable-linger` 才能登出后续跑;WSL 还需在 `/etc/wsl.conf` 开 `[boot] systemd=true`,否则用前台 `run`)。三者命令一致(`start`/`status`/`stop`/`restart`/`logs`),状态/日志路径统一。
|
|
95
|
+
|
|
96
|
+
> ⚠️ **后台服务必须全局安装(`npm i -g`),不要用 npx**:服务里硬编码了 CLI 路径,而 npx 的临时缓存(`~/.npm/_npx/...`)会被清理,缓存一没服务就起不来。前台 `run` 用 npx 没问题(单次进程)。
|
|
94
97
|
|
|
95
98
|
### 4. 多飞书机器人(可选)
|
|
96
99
|
|
|
@@ -198,7 +201,7 @@ feishu-codex-bridge bot rm <名> # 移除一个机器人配置
|
|
|
198
201
|
|
|
199
202
|
```
|
|
200
203
|
feishu-codex-bridge run 前台启动(没配置先扫码 init;Ctrl+C 优雅退出)
|
|
201
|
-
feishu-codex-bridge start 后台 daemon
|
|
204
|
+
feishu-codex-bridge start 后台 daemon 启动(装系统后台服务、开机/登录自启;阻塞到授权完成)
|
|
202
205
|
feishu-codex-bridge stop|restart|status|logs 后台 daemon 生命周期
|
|
203
206
|
feishu-codex-bridge update 更新到最新版并自动重启 daemon(--check 只查不装)
|
|
204
207
|
feishu-codex-bridge bot init|list|use|rm 多飞书机器人:注册 / 列表 / 切当前 / 移除
|
|
@@ -229,7 +232,7 @@ src/
|
|
|
229
232
|
config/ 加密密钥库、密钥解析、配置存储、多机器人注册表、scope 清单、路径
|
|
230
233
|
core/ watchdog、单实例锁、日志
|
|
231
234
|
cli/ commander 命令(run / start / stop / restart / status / logs / update / bot / doctor / secrets)
|
|
232
|
-
service/ launchd
|
|
235
|
+
service/ 后台服务适配器(launchd / Windows 登录自启 / systemd)+ 跨平台 spawn
|
|
233
236
|
```
|
|
234
237
|
|
|
235
238
|
架构与实现细节见 [`docs/design/feishu-codex-bridge-design.md`](docs/design/feishu-codex-bridge-design.md) 与 [`docs/design/implementation-plan.md`](docs/design/implementation-plan.md)。
|
package/dist/cli.js
CHANGED
|
@@ -228,6 +228,21 @@ function findBot(reg, nameOrAppId) {
|
|
|
228
228
|
function currentBot(reg) {
|
|
229
229
|
return reg.current ? reg.bots.find((b) => b.appId === reg.current) : void 0;
|
|
230
230
|
}
|
|
231
|
+
function activeBots(reg) {
|
|
232
|
+
const configured = reg.bots.some((b) => b.active !== void 0);
|
|
233
|
+
if (configured) return reg.bots.filter((b) => b.active === true);
|
|
234
|
+
const cur = currentBot(reg);
|
|
235
|
+
return cur ? [cur] : [];
|
|
236
|
+
}
|
|
237
|
+
async function setActiveBots(appIds) {
|
|
238
|
+
const reg = await loadBots();
|
|
239
|
+
const want = new Set(appIds);
|
|
240
|
+
for (const b of reg.bots) b.active = want.has(b.appId);
|
|
241
|
+
const firstActive = reg.bots.find((b) => b.active);
|
|
242
|
+
if (firstActive) reg.current = firstActive.appId;
|
|
243
|
+
await saveBots(reg);
|
|
244
|
+
return reg;
|
|
245
|
+
}
|
|
231
246
|
async function addBot(entry) {
|
|
232
247
|
const reg = await loadBots();
|
|
233
248
|
reg.bots = reg.bots.filter((b) => b.appId !== entry.appId);
|
|
@@ -236,11 +251,6 @@ async function addBot(entry) {
|
|
|
236
251
|
await saveBots(reg);
|
|
237
252
|
return reg;
|
|
238
253
|
}
|
|
239
|
-
async function setCurrent(appId) {
|
|
240
|
-
const reg = await loadBots();
|
|
241
|
-
reg.current = appId;
|
|
242
|
-
await saveBots(reg);
|
|
243
|
-
}
|
|
244
254
|
async function removeBot(appId) {
|
|
245
255
|
const reg = await loadBots();
|
|
246
256
|
reg.bots = reg.bots.filter((b) => b.appId !== appId);
|
|
@@ -1026,8 +1036,14 @@ function ensureCodex() {
|
|
|
1026
1036
|
async function ensureOnboarded(opts = {}) {
|
|
1027
1037
|
if (!ensureCodex()) return null;
|
|
1028
1038
|
const reg = await ensureRegistry();
|
|
1029
|
-
const entry = currentBot(reg);
|
|
1039
|
+
const entry = opts.bot ? findBot(reg, opts.bot) : currentBot(reg);
|
|
1030
1040
|
if (!entry) {
|
|
1041
|
+
if (opts.bot) {
|
|
1042
|
+
console.error(
|
|
1043
|
+
`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\u300C${opts.bot}\u300D\u3002\u7528 \`feishu-codex-bridge bot list\` \u67E5\u770B\u5DF2\u6CE8\u518C\u7684\u673A\u5668\u4EBA\u3002`
|
|
1044
|
+
);
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1031
1047
|
if (!opts.allowCreate) {
|
|
1032
1048
|
console.error("\u2717 \u5C1A\u672A\u914D\u7F6E\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u8BF7\u5148\u8FD0\u884C `feishu-codex-bridge bot init`\uFF08\u6216\u524D\u53F0 `run`\uFF09\u626B\u7801\u521B\u5EFA\u3002");
|
|
1033
1049
|
return null;
|
|
@@ -5956,6 +5972,94 @@ async function startBridge(opts) {
|
|
|
5956
5972
|
return { channel, shutdown };
|
|
5957
5973
|
}
|
|
5958
5974
|
|
|
5975
|
+
// src/bot/supervisor.ts
|
|
5976
|
+
var BACKOFF_MIN_MS = 1e3;
|
|
5977
|
+
var BACKOFF_MAX_MS = 3e4;
|
|
5978
|
+
var HEALTHY_UPTIME_MS = 6e4;
|
|
5979
|
+
var SHUTDOWN_GRACE_MS = 8e3;
|
|
5980
|
+
async function runSupervisor(bots) {
|
|
5981
|
+
const cliEntry = process.argv[1];
|
|
5982
|
+
if (!cliEntry) throw new Error("supervisor: \u65E0\u6CD5\u89E3\u6790 CLI \u5165\u53E3\uFF08process.argv[1] \u4E3A\u7A7A\uFF09");
|
|
5983
|
+
const childEnv = { ...process.env };
|
|
5984
|
+
delete childEnv[SERVICE_ENV_FLAG];
|
|
5985
|
+
let shuttingDown = false;
|
|
5986
|
+
const children = bots.map((bot2) => ({ bot: bot2, backoffMs: BACKOFF_MIN_MS, startedAt: 0 }));
|
|
5987
|
+
console.log(`
|
|
5988
|
+
\u6B63\u5728\u542F\u52A8 ${bots.length} \u4E2A\u673A\u5668\u4EBA\uFF08\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF09\uFF1A`);
|
|
5989
|
+
for (const b of bots) console.log(` \u2022 ${b.name} (${b.appId}) [${b.tenant}]`);
|
|
5990
|
+
console.log("Ctrl+C \u9000\u51FA\uFF08\u5173\u95ED\u5168\u90E8\uFF09\u3002\n");
|
|
5991
|
+
const prefixPipe = (name, src, dst) => {
|
|
5992
|
+
if (!src) return;
|
|
5993
|
+
let buf = "";
|
|
5994
|
+
src.setEncoding("utf8");
|
|
5995
|
+
src.on("data", (chunk) => {
|
|
5996
|
+
buf += chunk;
|
|
5997
|
+
let nl;
|
|
5998
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
5999
|
+
const line = buf.slice(0, nl);
|
|
6000
|
+
buf = buf.slice(nl + 1);
|
|
6001
|
+
dst.write(`\x1B[2m[${name}]\x1B[0m ${line}
|
|
6002
|
+
`);
|
|
6003
|
+
}
|
|
6004
|
+
});
|
|
6005
|
+
src.on("end", () => {
|
|
6006
|
+
if (buf) dst.write(`\x1B[2m[${name}]\x1B[0m ${buf}
|
|
6007
|
+
`);
|
|
6008
|
+
});
|
|
6009
|
+
};
|
|
6010
|
+
const spawnChild = (c) => {
|
|
6011
|
+
c.startedAt = Date.now();
|
|
6012
|
+
const proc = spawnProcess(process.execPath, [cliEntry, "run", "--bot", c.bot.appId], {
|
|
6013
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6014
|
+
env: childEnv
|
|
6015
|
+
});
|
|
6016
|
+
c.proc = proc;
|
|
6017
|
+
log.info("supervisor", "child-start", { bot: c.bot.name, appId: c.bot.appId, pid: proc.pid ?? null });
|
|
6018
|
+
prefixPipe(c.bot.name, proc.stdout, process.stdout);
|
|
6019
|
+
prefixPipe(c.bot.name, proc.stderr, process.stderr);
|
|
6020
|
+
proc.on("exit", (code, signal) => {
|
|
6021
|
+
c.proc = void 0;
|
|
6022
|
+
if (shuttingDown) return;
|
|
6023
|
+
const uptime = Date.now() - c.startedAt;
|
|
6024
|
+
if (uptime >= HEALTHY_UPTIME_MS) c.backoffMs = BACKOFF_MIN_MS;
|
|
6025
|
+
const wait = c.backoffMs;
|
|
6026
|
+
c.backoffMs = Math.min(c.backoffMs * 2, BACKOFF_MAX_MS);
|
|
6027
|
+
log.warn("supervisor", "child-exit", { bot: c.bot.name, code, signal, restartInMs: wait });
|
|
6028
|
+
console.error(
|
|
6029
|
+
`\x1B[2m[${c.bot.name}]\x1B[0m \u8FDB\u7A0B\u9000\u51FA\uFF08code=${code ?? signal ?? "?"}\uFF09\uFF0C${Math.round(wait / 1e3)}s \u540E\u91CD\u542F\u2026`
|
|
6030
|
+
);
|
|
6031
|
+
c.restartTimer = setTimeout(() => spawnChild(c), wait);
|
|
6032
|
+
});
|
|
6033
|
+
proc.on("error", (err) => {
|
|
6034
|
+
log.fail("supervisor", err, { bot: c.bot.name, phase: "spawn" });
|
|
6035
|
+
});
|
|
6036
|
+
};
|
|
6037
|
+
for (const c of children) spawnChild(c);
|
|
6038
|
+
await new Promise((resolve7) => {
|
|
6039
|
+
const stop = (sig) => {
|
|
6040
|
+
if (shuttingDown) return;
|
|
6041
|
+
shuttingDown = true;
|
|
6042
|
+
console.log(`
|
|
6043
|
+
\u6536\u5230 ${sig}\uFF0C\u6B63\u5728\u5173\u95ED\u5168\u90E8\u673A\u5668\u4EBA\u2026`);
|
|
6044
|
+
for (const c of children) {
|
|
6045
|
+
if (c.restartTimer) clearTimeout(c.restartTimer);
|
|
6046
|
+
c.proc?.kill("SIGTERM");
|
|
6047
|
+
}
|
|
6048
|
+
const deadline = Date.now() + SHUTDOWN_GRACE_MS;
|
|
6049
|
+
const poll = setInterval(() => {
|
|
6050
|
+
const alive = children.filter((c) => c.proc && !c.proc.killed);
|
|
6051
|
+
if (alive.length === 0 || Date.now() >= deadline) {
|
|
6052
|
+
clearInterval(poll);
|
|
6053
|
+
for (const c of alive) c.proc?.kill("SIGKILL");
|
|
6054
|
+
resolve7();
|
|
6055
|
+
}
|
|
6056
|
+
}, 200);
|
|
6057
|
+
};
|
|
6058
|
+
for (const sig of ["SIGINT", "SIGTERM"]) process.once(sig, () => stop(sig));
|
|
6059
|
+
});
|
|
6060
|
+
process.exit(0);
|
|
6061
|
+
}
|
|
6062
|
+
|
|
5959
6063
|
// src/core/single-instance.ts
|
|
5960
6064
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5961
6065
|
import { dirname as dirname11 } from "path";
|
|
@@ -6003,8 +6107,20 @@ function acquireSingleInstanceLock(appId) {
|
|
|
6003
6107
|
}
|
|
6004
6108
|
|
|
6005
6109
|
// src/cli/commands/run.ts
|
|
6006
|
-
async function runRun() {
|
|
6007
|
-
|
|
6110
|
+
async function runRun(botName) {
|
|
6111
|
+
if (botName) {
|
|
6112
|
+
await runSingle(botName);
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6115
|
+
const active = activeBots(await loadBots());
|
|
6116
|
+
if (active.length > 1) {
|
|
6117
|
+
await runSupervisor(active);
|
|
6118
|
+
return;
|
|
6119
|
+
}
|
|
6120
|
+
await runSingle(active[0]?.name);
|
|
6121
|
+
}
|
|
6122
|
+
async function runSingle(botName) {
|
|
6123
|
+
const ready = await ensureOnboarded({ allowCreate: !botName, bot: botName });
|
|
6008
6124
|
if (!ready) {
|
|
6009
6125
|
process.exitCode = 1;
|
|
6010
6126
|
return;
|
|
@@ -6047,14 +6163,37 @@ async function runRun() {
|
|
|
6047
6163
|
|
|
6048
6164
|
// src/cli/commands/daemon.ts
|
|
6049
6165
|
async function runStart() {
|
|
6050
|
-
const
|
|
6051
|
-
if (
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6166
|
+
const active = activeBots(await loadBots());
|
|
6167
|
+
if (active.length === 0) {
|
|
6168
|
+
const ready = await ensureOnboarded({ allowCreate: true });
|
|
6169
|
+
if (!ready) {
|
|
6170
|
+
process.exitCode = 1;
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
6173
|
+
if (!await confirmReadyForDaemon(ready)) {
|
|
6174
|
+
process.exitCode = 1;
|
|
6175
|
+
return;
|
|
6176
|
+
}
|
|
6177
|
+
} else {
|
|
6178
|
+
if (active.length > 1) {
|
|
6179
|
+
console.log(`
|
|
6180
|
+
\u540E\u53F0\u670D\u52A1\u5C06\u6258\u7BA1 ${active.length} \u4E2A\u673A\u5668\u4EBA\uFF08supervisor \u591A\u8FDB\u7A0B\uFF0C\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF09\uFF1A`);
|
|
6181
|
+
for (const b of active) console.log(` \u2022 ${b.name} (${b.appId}) [${b.tenant}]`);
|
|
6182
|
+
console.log("");
|
|
6183
|
+
}
|
|
6184
|
+
for (const bot2 of active) {
|
|
6185
|
+
if (active.length > 1) console.log(`
|
|
6186
|
+
\u2500\u2500\u2500\u2500 \u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId}) \u2500\u2500\u2500\u2500`);
|
|
6187
|
+
const ready = await ensureOnboarded({ bot: bot2.appId });
|
|
6188
|
+
if (!ready) {
|
|
6189
|
+
process.exitCode = 1;
|
|
6190
|
+
return;
|
|
6191
|
+
}
|
|
6192
|
+
if (!await confirmReadyForDaemon(ready)) {
|
|
6193
|
+
process.exitCode = 1;
|
|
6194
|
+
return;
|
|
6195
|
+
}
|
|
6196
|
+
}
|
|
6058
6197
|
}
|
|
6059
6198
|
const status = await getServiceAdapter().install();
|
|
6060
6199
|
console.log(installedNote());
|
|
@@ -6151,6 +6290,98 @@ async function runUpdate(opts = {}) {
|
|
|
6151
6290
|
|
|
6152
6291
|
// src/cli/commands/bot.ts
|
|
6153
6292
|
import { rm as rm5 } from "fs/promises";
|
|
6293
|
+
|
|
6294
|
+
// src/cli/checkbox.ts
|
|
6295
|
+
import { emitKeypressEvents } from "readline";
|
|
6296
|
+
var ESC = "\x1B";
|
|
6297
|
+
var HIDE_CURSOR = `${ESC}[?25l`;
|
|
6298
|
+
var SHOW_CURSOR = `${ESC}[?25h`;
|
|
6299
|
+
var ALT_SCREEN_ON = `${ESC}[?1049h`;
|
|
6300
|
+
var ALT_SCREEN_OFF = `${ESC}[?1049l`;
|
|
6301
|
+
var HOME_AND_CLEAR = `${ESC}[H${ESC}[2J`;
|
|
6302
|
+
async function checkboxSelect(title, items) {
|
|
6303
|
+
const input2 = process.stdin;
|
|
6304
|
+
const output = process.stdout;
|
|
6305
|
+
if (!input2.isTTY) throw new Error("checkboxSelect requires an interactive TTY");
|
|
6306
|
+
const checked = items.map((it) => Boolean(it.checked));
|
|
6307
|
+
let cursor = 0;
|
|
6308
|
+
const frame = () => {
|
|
6309
|
+
const lines = [title, ""];
|
|
6310
|
+
items.forEach((it, i) => {
|
|
6311
|
+
const box = checked[i] ? "\x1B[32m[x]\x1B[0m" : "[ ]";
|
|
6312
|
+
const pointer = i === cursor ? "\x1B[36m>\x1B[0m" : " ";
|
|
6313
|
+
const label = i === cursor ? `\x1B[1m${it.label}\x1B[0m` : it.label;
|
|
6314
|
+
const hint = it.hint ? ` \x1B[2m${it.hint}\x1B[0m` : "";
|
|
6315
|
+
lines.push(`${pointer} ${box} ${label}${hint}`);
|
|
6316
|
+
});
|
|
6317
|
+
const n = checked.filter(Boolean).length;
|
|
6318
|
+
lines.push("");
|
|
6319
|
+
lines.push(`\x1B[2m${n} \u4E2A\u5DF2\u52FE\u9009 \xB7 \u2191\u2193 \u79FB\u52A8 \xB7 \u7A7A\u683C\u52FE\u9009 \xB7 a \u5168\u9009 \xB7 \u56DE\u8F66\u786E\u8BA4 \xB7 q \u53D6\u6D88\x1B[0m`);
|
|
6320
|
+
return lines.join("\r\n");
|
|
6321
|
+
};
|
|
6322
|
+
const redraw = () => {
|
|
6323
|
+
output.write(HOME_AND_CLEAR + frame());
|
|
6324
|
+
};
|
|
6325
|
+
emitKeypressEvents(input2);
|
|
6326
|
+
const wasRaw = Boolean(input2.isRaw);
|
|
6327
|
+
let restored = false;
|
|
6328
|
+
const restore = () => {
|
|
6329
|
+
if (restored) return;
|
|
6330
|
+
restored = true;
|
|
6331
|
+
output.write(`${SHOW_CURSOR}${ALT_SCREEN_OFF}`);
|
|
6332
|
+
};
|
|
6333
|
+
input2.setRawMode?.(true);
|
|
6334
|
+
input2.resume();
|
|
6335
|
+
output.write(`${ALT_SCREEN_ON}${HIDE_CURSOR}`);
|
|
6336
|
+
process.once("exit", restore);
|
|
6337
|
+
redraw();
|
|
6338
|
+
return await new Promise((resolve7) => {
|
|
6339
|
+
const cleanup = () => {
|
|
6340
|
+
input2.off("keypress", onKey);
|
|
6341
|
+
input2.setRawMode?.(wasRaw);
|
|
6342
|
+
input2.pause();
|
|
6343
|
+
process.off("exit", restore);
|
|
6344
|
+
restore();
|
|
6345
|
+
};
|
|
6346
|
+
const onKey = (_str, key) => {
|
|
6347
|
+
const name = key?.name;
|
|
6348
|
+
if (key?.ctrl && name === "c" || name === "escape" || name === "q") {
|
|
6349
|
+
cleanup();
|
|
6350
|
+
resolve7(null);
|
|
6351
|
+
return;
|
|
6352
|
+
}
|
|
6353
|
+
if (name === "return" || name === "enter") {
|
|
6354
|
+
cleanup();
|
|
6355
|
+
resolve7(checked.flatMap((on, i) => on ? [i] : []));
|
|
6356
|
+
return;
|
|
6357
|
+
}
|
|
6358
|
+
if (name === "up" || name === "k") {
|
|
6359
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
6360
|
+
redraw();
|
|
6361
|
+
return;
|
|
6362
|
+
}
|
|
6363
|
+
if (name === "down" || name === "j") {
|
|
6364
|
+
cursor = (cursor + 1) % items.length;
|
|
6365
|
+
redraw();
|
|
6366
|
+
return;
|
|
6367
|
+
}
|
|
6368
|
+
if (name === "space") {
|
|
6369
|
+
checked[cursor] = !checked[cursor];
|
|
6370
|
+
redraw();
|
|
6371
|
+
return;
|
|
6372
|
+
}
|
|
6373
|
+
if (name === "a") {
|
|
6374
|
+
const allOn = checked.every(Boolean);
|
|
6375
|
+
for (let i = 0; i < checked.length; i++) checked[i] = !allOn;
|
|
6376
|
+
redraw();
|
|
6377
|
+
return;
|
|
6378
|
+
}
|
|
6379
|
+
};
|
|
6380
|
+
input2.on("keypress", onKey);
|
|
6381
|
+
});
|
|
6382
|
+
}
|
|
6383
|
+
|
|
6384
|
+
// src/cli/commands/bot.ts
|
|
6154
6385
|
async function runBotInit(name) {
|
|
6155
6386
|
if (!ensureCodex()) {
|
|
6156
6387
|
process.exitCode = 1;
|
|
@@ -6165,7 +6396,7 @@ async function runBotInit(name) {
|
|
|
6165
6396
|
console.log(" 1) \u4E8B\u4EF6\u4E0E\u56DE\u8C03 \u2192 \u957F\u8FDE\u63A5 \u2192 \u8BA2\u9605\uFF1Aim.message.receive_v1 / card.action.trigger / application.bot.menu_v6");
|
|
6166
6397
|
console.log(" \uFF08\u53EF\u9009\uFF09\u300C\u52A0\u8FDB\u5DF2\u6709\u7FA4\u300D\u529F\u80FD\u518D\u8BA2\u9605\uFF1Aim.chat.member.bot.added_v1 / im.chat.member.bot.deleted_v1");
|
|
6167
6398
|
console.log(" 2) \u521B\u5EFA\u5E76\u53D1\u5E03\u5E94\u7528\u7248\u672C");
|
|
6168
|
-
console.log("\n`bot list` \u67E5\u770B\u5168\u90E8\uFF1B`bot use
|
|
6399
|
+
console.log("\n`bot list` \u67E5\u770B\u5168\u90E8\uFF1B`bot use` \u52FE\u9009\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF1B`run` \u524D\u53F0\u8DD1 / `start` \u540E\u53F0\u5E38\u9A7B\u3002\n");
|
|
6169
6400
|
}
|
|
6170
6401
|
async function runBotList() {
|
|
6171
6402
|
const reg = await loadBots();
|
|
@@ -6173,27 +6404,65 @@ async function runBotList() {
|
|
|
6173
6404
|
console.log("\uFF08\u8FD8\u6CA1\u6709\u6CE8\u518C\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u8FD0\u884C `feishu-codex-bridge bot init` \u521B\u5EFA\u3002\uFF09");
|
|
6174
6405
|
return;
|
|
6175
6406
|
}
|
|
6407
|
+
const active = new Set(activeBots(reg).map((b) => b.appId));
|
|
6176
6408
|
console.log("\n\u5DF2\u6CE8\u518C\u7684\u98DE\u4E66\u673A\u5668\u4EBA\uFF1A\n");
|
|
6177
6409
|
for (const b of reg.bots) {
|
|
6178
|
-
const
|
|
6179
|
-
console.log(`${
|
|
6410
|
+
const mark = active.has(b.appId) ? "\u2705" : "\u2B1C";
|
|
6411
|
+
console.log(`${mark} ${b.name.padEnd(16)} ${b.appId} [${b.tenant}]${b.botName ? ` ${b.botName}` : ""}`);
|
|
6180
6412
|
}
|
|
6181
|
-
console.log("\n\
|
|
6413
|
+
console.log("\n\x1B[2m`bot use` \u52FE\u9009\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF0C\u6216 `bot use <\u540D> [\u540D\u2026]` \u76F4\u63A5\u6307\u5B9A\u3002\x1B[0m\n");
|
|
6182
6414
|
}
|
|
6183
|
-
async function runBotUse(
|
|
6415
|
+
async function runBotUse(names) {
|
|
6184
6416
|
const reg = await loadBots();
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
console.error(`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\u300C${name}\u300D\u3002\u5DF2\u6CE8\u518C\uFF1A${botNames(reg.bots)}`);
|
|
6417
|
+
if (reg.bots.length === 0) {
|
|
6418
|
+
console.error("\u2717 \u8FD8\u6CA1\u6709\u6CE8\u518C\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u5148 `feishu-codex-bridge bot init` \u521B\u5EFA\u3002");
|
|
6188
6419
|
process.exitCode = 1;
|
|
6189
6420
|
return;
|
|
6190
6421
|
}
|
|
6191
|
-
|
|
6192
|
-
|
|
6422
|
+
let appIds;
|
|
6423
|
+
if (names.length > 0) {
|
|
6424
|
+
const resolved = [];
|
|
6425
|
+
const unknown = [];
|
|
6426
|
+
for (const n of names) {
|
|
6427
|
+
const b = findBot(reg, n);
|
|
6428
|
+
if (!b) unknown.push(n);
|
|
6429
|
+
else if (!resolved.includes(b.appId)) resolved.push(b.appId);
|
|
6430
|
+
}
|
|
6431
|
+
if (unknown.length) {
|
|
6432
|
+
console.error(`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\uFF1A${unknown.join(", ")}\u3002\u5DF2\u6CE8\u518C\uFF1A${botNames(reg.bots)}`);
|
|
6433
|
+
process.exitCode = 1;
|
|
6434
|
+
return;
|
|
6435
|
+
}
|
|
6436
|
+
appIds = resolved;
|
|
6437
|
+
} else {
|
|
6438
|
+
if (!process.stdin.isTTY) {
|
|
6439
|
+
const cur = activeBots(reg).map((b) => b.name).join(", ") || "\uFF08\u7A7A\uFF09";
|
|
6440
|
+
console.log(`\u5F53\u524D\u6D3B\u8DC3\u673A\u5668\u4EBA\uFF1A${cur}`);
|
|
6441
|
+
console.log("\uFF08\u975E\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\uFF0C\u65E0\u6CD5\u5F39\u52FE\u9009\u6846\u3002\u7528 `bot use <\u540D> [\u540D\u2026]` \u76F4\u63A5\u6307\u5B9A\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\u3002\uFF09");
|
|
6442
|
+
return;
|
|
6443
|
+
}
|
|
6444
|
+
const activeSet = new Set(activeBots(reg).map((b) => b.appId));
|
|
6445
|
+
const items = reg.bots.map((b) => ({
|
|
6446
|
+
label: b.name,
|
|
6447
|
+
hint: `${b.appId} [${b.tenant}]${b.botName ? ` ${b.botName}` : ""}`,
|
|
6448
|
+
checked: activeSet.has(b.appId)
|
|
6449
|
+
}));
|
|
6450
|
+
const picked = await checkboxSelect("\u9009\u62E9\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF08\u7A7A\u683C\u52FE\u9009\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A", items);
|
|
6451
|
+
if (picked === null) {
|
|
6452
|
+
console.log("\u5DF2\u53D6\u6D88\uFF0C\u672A\u6539\u52A8\u3002");
|
|
6453
|
+
return;
|
|
6454
|
+
}
|
|
6455
|
+
appIds = picked.map((i) => reg.bots[i]?.appId).filter((id) => Boolean(id));
|
|
6456
|
+
}
|
|
6457
|
+
await setActiveBots(appIds);
|
|
6458
|
+
const chosen = appIds.map((id) => reg.bots.find((b) => b.appId === id)?.name ?? id);
|
|
6459
|
+
if (chosen.length === 0) {
|
|
6460
|
+
console.log("\u2713 \u5DF2\u6E05\u7A7A\u6D3B\u8DC3\u673A\u5668\u4EBA\u2014\u2014`run` / `start` \u6682\u4E0D\u8FDE\u63A5\u4EFB\u4F55 bot\u3002`bot use` \u91CD\u65B0\u52FE\u9009\u3002");
|
|
6193
6461
|
return;
|
|
6194
6462
|
}
|
|
6195
|
-
|
|
6196
|
-
|
|
6463
|
+
console.log(
|
|
6464
|
+
`\u2713 \u6D3B\u8DC3\u673A\u5668\u4EBA\uFF08${chosen.length} \u4E2A\uFF09\u2192 ${chosen.join(", ")}\u3002\u524D\u53F0\u91CD\u8DD1 \`run\` \u751F\u6548${chosen.length > 1 ? "\uFF08\u591A\u8FDB\u7A0B\u6258\u7BA1\uFF09" : ""}\uFF1B\u540E\u53F0\u8BF7 \`restart\`\u3002`
|
|
6465
|
+
);
|
|
6197
6466
|
}
|
|
6198
6467
|
async function runBotRm(name) {
|
|
6199
6468
|
const reg = await loadBots();
|
|
@@ -6278,8 +6547,8 @@ function readStdin() {
|
|
|
6278
6547
|
// src/cli/index.ts
|
|
6279
6548
|
var program = new Command();
|
|
6280
6549
|
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());
|
|
6281
|
-
program.command("run").description("\u524D\u53F0\u542F\u52A8
|
|
6282
|
-
await runRun();
|
|
6550
|
+
program.command("run").description("\u524D\u53F0\u542F\u52A8\u6D3B\u8DC3\u673A\u5668\u4EBA\uFF08\u591A\u4E2A\u5219\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF1B\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF1BCtrl+C \u4F18\u96C5\u9000\u51FA\uFF09").option("--bot <name>", "\u53EA\u542F\u52A8\u6307\u5B9A\u7684\u4E00\u4E2A\u673A\u5668\u4EBA\uFF08\u540D\u5B57\u6216 appId\uFF09").action(async (options) => {
|
|
6551
|
+
await runRun(options.bot);
|
|
6283
6552
|
});
|
|
6284
6553
|
program.command("start").description("\u540E\u53F0 daemon \u542F\u52A8\uFF08\u88C5 launchd \u5F00\u673A\u81EA\u542F\uFF1B\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF09").action(async () => {
|
|
6285
6554
|
await runStart();
|
|
@@ -6306,8 +6575,8 @@ bot.command("init [name]").description("\u6CE8\u518C\u4E00\u4E2A\u98DE\u4E66\u67
|
|
|
6306
6575
|
bot.command("list").description("\u5217\u51FA\u5DF2\u6CE8\u518C\u7684\u98DE\u4E66\u673A\u5668\u4EBA").action(async () => {
|
|
6307
6576
|
await runBotList();
|
|
6308
6577
|
});
|
|
6309
|
-
bot.command("use
|
|
6310
|
-
await runBotUse(
|
|
6578
|
+
bot.command("use [names...]").description("\u52FE\u9009/\u6307\u5B9A\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF08\u591A\u9009\uFF09\uFF1B\u65E0\u53C2\u6570\u5F39\u4EA4\u4E92\u5F0F\u52FE\u9009\u6846").action(async (names) => {
|
|
6579
|
+
await runBotUse(names ?? []);
|
|
6311
6580
|
});
|
|
6312
6581
|
bot.command("rm <name>").description("\u79FB\u9664\u4E00\u4E2A\u673A\u5668\u4EBA\u914D\u7F6E").action(async (name) => {
|
|
6313
6582
|
await runBotRm(name);
|