@modelzen/feishu-codex-bridge 0.2.1 → 0.2.2-win

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 (2) hide show
  1. package/dist/cli.js +479 -95
  2. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -15,7 +15,6 @@ function bridgeVersion() {
15
15
  }
16
16
 
17
17
  // src/cli/commands/doctor.ts
18
- import { execFileSync as execFileSync2 } from "child_process";
19
18
  import { existsSync as existsSync3 } from "fs";
20
19
  import { homedir as homedir2 } from "os";
21
20
  import { join as join4 } from "path";
@@ -269,27 +268,56 @@ async function moveIfExists(src, dest) {
269
268
  }
270
269
 
271
270
  // src/agent/codex-appserver/locate.ts
272
- import { execFileSync } from "child_process";
273
271
  import { existsSync as existsSync2 } from "fs";
274
- import { join as join3 } from "path";
272
+ import { extname, join as join3 } from "path";
273
+
274
+ // src/platform/spawn.ts
275
+ import crossSpawn from "cross-spawn";
276
+ function spawnProcess(command, args = [], options = {}) {
277
+ return crossSpawn(command, [...args], options);
278
+ }
279
+ function spawnProcessSync(command, args = [], options = {}) {
280
+ return crossSpawn.sync(command, [...args], options);
281
+ }
282
+ function mergeProcessEnv(base = process.env, overrides = {}) {
283
+ const out = { ...base };
284
+ for (const [key, value] of Object.entries(overrides)) {
285
+ for (const existing of Object.keys(out)) {
286
+ if (existing.toLowerCase() === key.toLowerCase()) delete out[existing];
287
+ }
288
+ if (value !== void 0) out[key] = value;
289
+ }
290
+ return out;
291
+ }
292
+
293
+ // src/agent/codex-appserver/locate.ts
294
+ var IS_WIN = process.platform === "win32";
275
295
  function resolveCodexBin() {
276
296
  const env = process.env.CODEX_BIN;
277
297
  if (env && existsSync2(env)) return env;
278
298
  const onPath = which("codex");
279
299
  if (onPath) return onPath;
280
- const priv = join3(paths.codexCliBinDir, "codex");
281
- if (existsSync2(priv)) return priv;
300
+ for (const cand of execCandidates(paths.codexCliBinDir, "codex")) {
301
+ if (existsSync2(cand)) return cand;
302
+ }
282
303
  const appBundle = "/Applications/Codex.app/Contents/Resources/codex";
283
304
  if (process.platform === "darwin" && existsSync2(appBundle)) return appBundle;
284
305
  return null;
285
306
  }
307
+ function execCandidates(dir, base) {
308
+ const exact = join3(dir, base);
309
+ if (!IS_WIN || extname(base)) return [exact];
310
+ const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
311
+ return [exact, ...exts.map((e) => join3(dir, base + e.toLowerCase()))];
312
+ }
286
313
  function which(cmd) {
287
314
  try {
288
- const out = execFileSync(process.platform === "win32" ? "where" : "which", [cmd], {
315
+ const res = spawnProcessSync(IS_WIN ? "where" : "which", [cmd], {
289
316
  encoding: "utf8",
290
317
  stdio: ["ignore", "pipe", "ignore"]
291
318
  });
292
- const first = out.split("\n").map((l) => l.trim()).find(Boolean);
319
+ if (res.status !== 0 || typeof res.stdout !== "string") return null;
320
+ const first = res.stdout.split("\n").map((l) => l.trim()).find(Boolean);
293
321
  return first && existsSync2(first) ? first : null;
294
322
  } catch {
295
323
  return null;
@@ -297,7 +325,9 @@ function which(cmd) {
297
325
  }
298
326
  function codexVersion(bin) {
299
327
  try {
300
- return execFileSync(bin, ["--version"], { encoding: "utf8" }).trim();
328
+ const res = spawnProcessSync(bin, ["--version"], { encoding: "utf8" });
329
+ if (res.status !== 0 || typeof res.stdout !== "string") return null;
330
+ return res.stdout.trim();
301
331
  } catch {
302
332
  return null;
303
333
  }
@@ -355,7 +385,9 @@ ${failed === 0 ? "\u5168\u90E8\u901A\u8FC7 \u2713" : `${failed} \u9879\u9700\u59
355
385
  }
356
386
  function tryExec(cmd, args) {
357
387
  try {
358
- return execFileSync2(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
388
+ const res = spawnProcessSync(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
389
+ if (res.status !== 0 || typeof res.stdout !== "string") return null;
390
+ return res.stdout.trim();
359
391
  } catch {
360
392
  return null;
361
393
  }
@@ -538,7 +570,7 @@ async function spawnExecProvider(pc, ref) {
538
570
  const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
539
571
  const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
540
572
  const providerName = ref.provider ?? DEFAULT_PROVIDER;
541
- return new Promise((resolve6, reject) => {
573
+ return new Promise((resolve7, reject) => {
542
574
  const env = {};
543
575
  if (pc.passEnv) for (const k of pc.passEnv) {
544
576
  const v = process.env[k];
@@ -583,7 +615,7 @@ async function spawnExecProvider(pc, ref) {
583
615
  try {
584
616
  const parsed = JSON.parse(stdout);
585
617
  const value = parsed.values?.[ref.id];
586
- if (typeof value === "string") return resolve6(value);
618
+ if (typeof value === "string") return resolve7(value);
587
619
  const err = parsed.errors?.[ref.id]?.message;
588
620
  reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
589
621
  } catch (err) {
@@ -1131,7 +1163,6 @@ ${rule}`);
1131
1163
  import { createLarkChannel, Domain } from "@larksuiteoapi/node-sdk";
1132
1164
 
1133
1165
  // src/agent/codex-appserver/app-server-client.ts
1134
- import { spawn as spawn3 } from "child_process";
1135
1166
  var AsyncQueue = class {
1136
1167
  items = [];
1137
1168
  waiters = [];
@@ -1152,7 +1183,7 @@ var AsyncQueue = class {
1152
1183
  continue;
1153
1184
  }
1154
1185
  if (this.closed) return;
1155
- const next = await new Promise((resolve6) => this.waiters.push(resolve6));
1186
+ const next = await new Promise((resolve7) => this.waiters.push(resolve7));
1156
1187
  if (next.done) return;
1157
1188
  yield next.value;
1158
1189
  }
@@ -1174,9 +1205,9 @@ var AppServerClient = class {
1174
1205
  }
1175
1206
  /** spawn + initialize handshake. Throws if spawn/handshake fails. */
1176
1207
  async connect() {
1177
- const child = spawn3(this.opts.bin, ["app-server", "--listen", "stdio://"], {
1208
+ const child = spawnProcess(this.opts.bin, ["app-server", "--listen", "stdio://"], {
1178
1209
  cwd: this.opts.cwd,
1179
- env: { ...process.env, ...this.opts.env, FEISHU_CODEX_BRIDGE: "1" },
1210
+ env: mergeProcessEnv(process.env, { ...this.opts.env, FEISHU_CODEX_BRIDGE: "1" }),
1180
1211
  stdio: ["pipe", "pipe", "pipe"]
1181
1212
  });
1182
1213
  this.child = child;
@@ -1203,8 +1234,8 @@ var AppServerClient = class {
1203
1234
  const id = ++this.nextId;
1204
1235
  const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
1205
1236
  `;
1206
- return new Promise((resolve6, reject) => {
1207
- this.pending.set(id, { resolve: resolve6, reject });
1237
+ return new Promise((resolve7, reject) => {
1238
+ this.pending.set(id, { resolve: resolve7, reject });
1208
1239
  this.child.stdin.write(payload, (err) => {
1209
1240
  if (err) {
1210
1241
  this.pending.delete(id);
@@ -1227,15 +1258,36 @@ var AppServerClient = class {
1227
1258
  this.closed = true;
1228
1259
  const child = this.child;
1229
1260
  if (!child || child.exitCode !== null) return;
1261
+ if (process.platform === "win32" && child.pid) {
1262
+ await new Promise((resolve7) => {
1263
+ let settled = false;
1264
+ const done = () => {
1265
+ if (settled) return;
1266
+ settled = true;
1267
+ clearTimeout(t);
1268
+ resolve7();
1269
+ };
1270
+ const t = setTimeout(done, graceMs);
1271
+ child.once("exit", done);
1272
+ spawnProcess("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
1273
+ "error",
1274
+ () => {
1275
+ child.kill();
1276
+ done();
1277
+ }
1278
+ );
1279
+ });
1280
+ return;
1281
+ }
1230
1282
  child.kill("SIGTERM");
1231
- await new Promise((resolve6) => {
1283
+ await new Promise((resolve7) => {
1232
1284
  const t = setTimeout(() => {
1233
1285
  if (child.exitCode === null) child.kill("SIGKILL");
1234
- resolve6();
1286
+ resolve7();
1235
1287
  }, graceMs);
1236
1288
  child.once("exit", () => {
1237
1289
  clearTimeout(t);
1238
- resolve6();
1290
+ resolve7();
1239
1291
  });
1240
1292
  });
1241
1293
  }
@@ -1368,12 +1420,12 @@ var BRIDGE_DEVELOPER_INSTRUCTIONS = [
1368
1420
  ].join("\n");
1369
1421
  var READ_HISTORY_TIMEOUT_MS = 2e4;
1370
1422
  function withDeadline(p, ms, label) {
1371
- return new Promise((resolve6, reject) => {
1423
+ return new Promise((resolve7, reject) => {
1372
1424
  const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
1373
1425
  p.then(
1374
1426
  (v) => {
1375
1427
  clearTimeout(t);
1376
- resolve6(v);
1428
+ resolve7(v);
1377
1429
  },
1378
1430
  (e) => {
1379
1431
  clearTimeout(t);
@@ -1413,11 +1465,11 @@ var CodexThread = class {
1413
1465
  if (self.model) params.model = self.model;
1414
1466
  if (self.effort) params.effort = self.effort;
1415
1467
  let startError;
1416
- const startFailed = new Promise((resolve6) => {
1468
+ const startFailed = new Promise((resolve7) => {
1417
1469
  self.client.request("turn/start", params).then(void 0, (err) => {
1418
1470
  startError = err instanceof Error ? err : new Error(String(err));
1419
1471
  log.fail("agent", startError, { phase: "turn/start" });
1420
- resolve6("start-failed");
1472
+ resolve7("start-failed");
1421
1473
  });
1422
1474
  });
1423
1475
  const stream2 = self.client.stream()[Symbol.asyncIterator]();
@@ -2907,7 +2959,7 @@ function structureSig(card2, eid) {
2907
2959
 
2908
2960
  // src/card/outbound-images.ts
2909
2961
  import { readFile as readFile5, stat as stat2 } from "fs/promises";
2910
- import { extname, isAbsolute, resolve as resolve2, sep } from "path";
2962
+ import { extname as extname2, isAbsolute, resolve as resolve2, sep } from "path";
2911
2963
  var MAX_IMAGES = 9;
2912
2964
  var MAX_BYTES = 10 * 1024 * 1024;
2913
2965
  var DOWNLOAD_TIMEOUT_MS = 1e4;
@@ -2973,7 +3025,7 @@ async function loadLocal(src, cwd) {
2973
3025
  log.warn("outbound", "image-outside-cwd", { src: src.slice(0, 80) });
2974
3026
  return { cacheKey: `local:${abs}` };
2975
3027
  }
2976
- const ext = extname(abs).slice(1).toLowerCase();
3028
+ const ext = extname2(abs).slice(1).toLowerCase();
2977
3029
  if (!ALLOWED_EXT.has(ext)) {
2978
3030
  log.warn("outbound", "image-ext", { ext, src: src.slice(0, 80) });
2979
3031
  return { cacheKey: `local:${abs}` };
@@ -3504,39 +3556,106 @@ function buildGroupSettingsCard(project) {
3504
3556
  }
3505
3557
 
3506
3558
  // src/service/update.ts
3507
- import { execFile, spawn as spawn5 } from "child_process";
3508
- import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
3509
- import { dirname as dirname7, join as join8, resolve as resolve4 } from "path";
3510
- import { fileURLToPath as fileURLToPath3 } from "url";
3559
+ import { execFile, spawn as spawn4 } from "child_process";
3560
+ import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
3561
+ import { dirname as dirname10, join as join11, resolve as resolve5 } from "path";
3562
+ import { fileURLToPath as fileURLToPath4 } from "url";
3511
3563
  import { promisify } from "util";
3512
3564
 
3513
3565
  // src/service/launchd.ts
3514
- import { spawn as spawn4, spawnSync } from "child_process";
3566
+ import { spawn as spawn3, spawnSync } from "child_process";
3515
3567
  import { existsSync as existsSync4 } from "fs";
3516
- import { appendFile, mkdir as mkdir5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
3568
+ import { mkdir as mkdir6, rm as rm2, writeFile as writeFile5 } from "fs/promises";
3517
3569
  import { homedir as homedir3, userInfo as userInfo2 } from "os";
3570
+ import { dirname as dirname7, join as join8, resolve as resolve4 } from "path";
3571
+ import { fileURLToPath as fileURLToPath3 } from "url";
3572
+
3573
+ // src/service/common.ts
3574
+ import { createReadStream, statSync } from "fs";
3575
+ import { appendFile, mkdir as mkdir5, readFile as readFile7 } from "fs/promises";
3518
3576
  import { dirname as dirname6, join as join7, resolve as resolve3 } from "path";
3519
3577
  import { fileURLToPath as fileURLToPath2 } from "url";
3520
- var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
3521
- function launchAgentPlistPath() {
3522
- return join7(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
3523
- }
3524
3578
  function serviceStdoutPath() {
3525
3579
  return join7(paths.appDir, "service.log");
3526
3580
  }
3527
3581
  function serviceStderrPath() {
3528
3582
  return join7(paths.appDir, "service.err.log");
3529
3583
  }
3584
+ async function ensureLogFiles() {
3585
+ await mkdir5(paths.appDir, { recursive: true });
3586
+ await appendFile(serviceStdoutPath(), "");
3587
+ await appendFile(serviceStderrPath(), "");
3588
+ }
3530
3589
  function resolveCliBinPath() {
3531
3590
  const distDir = dirname6(fileURLToPath2(import.meta.url));
3532
3591
  return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
3533
3592
  }
3593
+ async function tailServiceLogs(follow) {
3594
+ await ensureLogFiles();
3595
+ const files = [serviceStdoutPath(), serviceStderrPath()];
3596
+ for (const f of files) {
3597
+ const tail = await lastLines(f, 100);
3598
+ if (tail) process.stdout.write(`
3599
+ ===== ${f} =====
3600
+ ${tail}
3601
+ `);
3602
+ }
3603
+ if (!follow) return;
3604
+ const offsets = new Map(files.map((f) => [f, fileSize(f)]));
3605
+ await new Promise((resolvePromise) => {
3606
+ const onSigint = () => {
3607
+ clearInterval(timer);
3608
+ process.off("SIGINT", onSigint);
3609
+ resolvePromise();
3610
+ };
3611
+ process.on("SIGINT", onSigint);
3612
+ const timer = setInterval(() => {
3613
+ for (const f of files) {
3614
+ const size = fileSize(f);
3615
+ const from = offsets.get(f) ?? 0;
3616
+ if (size > from) {
3617
+ offsets.set(f, size);
3618
+ createReadStream(f, { start: from, end: size - 1, encoding: "utf8" }).pipe(process.stdout, {
3619
+ end: false
3620
+ });
3621
+ } else if (size < from) {
3622
+ offsets.set(f, size);
3623
+ }
3624
+ }
3625
+ }, 700);
3626
+ });
3627
+ }
3628
+ function fileSize(file) {
3629
+ try {
3630
+ return statSync(file).size;
3631
+ } catch {
3632
+ return 0;
3633
+ }
3634
+ }
3635
+ async function lastLines(file, n) {
3636
+ try {
3637
+ const text = await readFile7(file, "utf8");
3638
+ return text.split("\n").slice(-n - 1).join("\n").trimEnd();
3639
+ } catch {
3640
+ return "";
3641
+ }
3642
+ }
3643
+
3644
+ // src/service/launchd.ts
3645
+ var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
3646
+ function launchAgentPlistPath() {
3647
+ return join8(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
3648
+ }
3649
+ function resolveCliBinPath2() {
3650
+ const distDir = dirname7(fileURLToPath3(import.meta.url));
3651
+ return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
3652
+ }
3534
3653
  function escapeXml(value) {
3535
3654
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3536
3655
  }
3537
3656
  function buildPlist() {
3538
3657
  const nodePath = process.execPath;
3539
- const cliBinPath = resolveCliBinPath();
3658
+ const cliBinPath = resolveCliBinPath2();
3540
3659
  const pathEnv = process.env.PATH ?? "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin";
3541
3660
  return `<?xml version="1.0" encoding="UTF-8"?>
3542
3661
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -3569,7 +3688,7 @@ function buildPlist() {
3569
3688
  }
3570
3689
  async function installLaunchd() {
3571
3690
  const plistPath = launchAgentPlistPath();
3572
- await mkdir5(dirname6(plistPath), { recursive: true });
3691
+ await mkdir6(dirname7(plistPath), { recursive: true });
3573
3692
  await ensureLogFiles();
3574
3693
  await writeFile5(plistPath, buildPlist(), "utf8");
3575
3694
  if (isLoaded()) {
@@ -3607,9 +3726,10 @@ function statusLaunchd() {
3607
3726
  const raw = result.stdout || result.stderr;
3608
3727
  const parsed = parseLaunchdStatus(raw);
3609
3728
  return {
3729
+ platformName: "launchd (macOS)",
3610
3730
  installed: existsSync4(launchAgentPlistPath()),
3611
- loaded: result.ok,
3612
- plistPath: launchAgentPlistPath(),
3731
+ running: result.ok,
3732
+ servicePath: launchAgentPlistPath(),
3613
3733
  stdoutPath: serviceStdoutPath(),
3614
3734
  stderrPath: serviceStderrPath(),
3615
3735
  pid: parsed.pid,
@@ -3621,7 +3741,7 @@ async function tailLaunchdLogs(follow) {
3621
3741
  await ensureLogFiles();
3622
3742
  const args = follow ? ["-f", serviceStdoutPath(), serviceStderrPath()] : ["-n", "100", serviceStdoutPath(), serviceStderrPath()];
3623
3743
  await new Promise((resolvePromise, reject) => {
3624
- const child = spawn4("tail", args, { stdio: "inherit" });
3744
+ const child = spawn3("tail", args, { stdio: "inherit" });
3625
3745
  child.on("error", reject);
3626
3746
  child.on("close", (code) => {
3627
3747
  if (code === 0 || follow && code === null) {
@@ -3652,11 +3772,6 @@ async function waitUntilUnloaded(timeoutMs = 5e3) {
3652
3772
  }
3653
3773
  throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
3654
3774
  }
3655
- async function ensureLogFiles() {
3656
- await mkdir5(paths.appDir, { recursive: true });
3657
- await appendFile(serviceStdoutPath(), "");
3658
- await appendFile(serviceStderrPath(), "");
3659
- }
3660
3775
  function userTarget() {
3661
3776
  return `gui/${userInfo2().uid}`;
3662
3777
  }
@@ -3677,29 +3792,291 @@ function launchctlError(command, result) {
3677
3792
  return new Error(`${command} \u5931\u8D25\uFF08exit ${result.status ?? "unknown"}\uFF09${output ? `\uFF1A${output}` : ""}`);
3678
3793
  }
3679
3794
 
3680
- // src/service/adapter.ts
3681
- function getServiceAdapter() {
3682
- if (process.platform !== "darwin") {
3683
- throw new Error("service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF0C\u540E\u7EED\u4F1A\u652F\u6301 Windows/systemd\u3002");
3795
+ // src/service/schtasks.ts
3796
+ import { spawnSync as spawnSync2 } from "child_process";
3797
+ import { existsSync as existsSync5 } from "fs";
3798
+ import { mkdir as mkdir7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
3799
+ import { dirname as dirname8, join as join9 } from "path";
3800
+ var WINDOWS_TASK_NAME = "feishu-codex-bridge";
3801
+ function launcherCmdPath() {
3802
+ return join9(paths.appDir, "service-launcher.cmd");
3803
+ }
3804
+ function buildLauncherCmd() {
3805
+ const nodePath = process.execPath;
3806
+ const cliBinPath = resolveCliBinPath();
3807
+ const pathEnv = process.env.PATH ?? "";
3808
+ return [
3809
+ "@echo off",
3810
+ `set "PATH=${pathEnv}"`,
3811
+ `"${nodePath}" "${cliBinPath}" run >> "${serviceStdoutPath()}" 2>> "${serviceStderrPath()}"`,
3812
+ ""
3813
+ ].join("\r\n");
3814
+ }
3815
+ function runSchtasks(args) {
3816
+ const r = spawnSync2("schtasks", args, { encoding: "utf8" });
3817
+ return {
3818
+ ok: r.status === 0,
3819
+ status: r.status,
3820
+ stdout: r.stdout ?? "",
3821
+ stderr: r.stderr ?? ""
3822
+ };
3823
+ }
3824
+ function schtasksError(command, r) {
3825
+ const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
3826
+ return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
3827
+ }
3828
+ async function writeLauncherCmd() {
3829
+ const cmdPath = launcherCmdPath();
3830
+ await mkdir7(dirname8(cmdPath), { recursive: true });
3831
+ await ensureLogFiles();
3832
+ await writeFile6(cmdPath, buildLauncherCmd(), "utf8");
3833
+ }
3834
+ async function installSchtask() {
3835
+ await writeLauncherCmd();
3836
+ const create = runSchtasks([
3837
+ "/Create",
3838
+ "/F",
3839
+ "/SC",
3840
+ "ONLOGON",
3841
+ "/RL",
3842
+ "LIMITED",
3843
+ "/TN",
3844
+ WINDOWS_TASK_NAME,
3845
+ "/TR",
3846
+ `"${launcherCmdPath()}"`
3847
+ ]);
3848
+ if (!create.ok) throw schtasksError("schtasks /Create", create);
3849
+ const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
3850
+ if (!run.ok) throw schtasksError("schtasks /Run", run);
3851
+ return statusSchtask();
3852
+ }
3853
+ async function uninstallSchtask() {
3854
+ runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
3855
+ const del = runSchtasks(["/Delete", "/F", "/TN", WINDOWS_TASK_NAME]);
3856
+ if (!del.ok && isTaskRegistered()) throw schtasksError("schtasks /Delete", del);
3857
+ if (existsSync5(launcherCmdPath())) await rm3(launcherCmdPath(), { force: true });
3858
+ }
3859
+ async function restartSchtask() {
3860
+ if (!isTaskRegistered()) {
3861
+ throw new Error(`\u8BA1\u5212\u4EFB\u52A1\u672A\u5B89\u88C5\uFF1A${WINDOWS_TASK_NAME}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
3862
+ }
3863
+ runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
3864
+ await waitUntilStopped();
3865
+ const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
3866
+ if (!run.ok) throw schtasksError("schtasks /Run", run);
3867
+ return statusSchtask();
3868
+ }
3869
+ function statusSchtask() {
3870
+ const installed = isTaskRegistered();
3871
+ const raw = installed ? describeTask() : "";
3872
+ return {
3873
+ platformName: "Task Scheduler (Windows)",
3874
+ installed,
3875
+ running: installed && /Status:\s+Running/i.test(raw),
3876
+ servicePath: WINDOWS_TASK_NAME,
3877
+ stdoutPath: serviceStdoutPath(),
3878
+ stderrPath: serviceStderrPath(),
3879
+ // `Process ID:` only appears in verbose output while the task is running.
3880
+ pid: raw.match(/Process ID:\s*(\d+)/i)?.[1],
3881
+ // `Last Result: 0` ⇒ last run succeeded. Surface it as the exit code.
3882
+ lastExit: raw.match(/Last Result:\s*(-?\d+)/i)?.[1],
3883
+ raw
3884
+ };
3885
+ }
3886
+ function isTaskRegistered() {
3887
+ const r = spawnSync2("schtasks", ["/Query", "/TN", WINDOWS_TASK_NAME], {
3888
+ stdio: ["ignore", "ignore", "ignore"]
3889
+ });
3890
+ return r.status === 0;
3891
+ }
3892
+ function schtaskRunning() {
3893
+ if (!isTaskRegistered()) return false;
3894
+ return /Status:\s+Running/i.test(describeTask());
3895
+ }
3896
+ function describeTask() {
3897
+ const r = runSchtasks(["/Query", "/V", "/FO", "LIST", "/TN", WINDOWS_TASK_NAME]);
3898
+ return r.stdout || r.stderr || "";
3899
+ }
3900
+ async function waitUntilStopped(timeoutMs = 5e3) {
3901
+ const deadline = Date.now() + timeoutMs;
3902
+ while (Date.now() < deadline) {
3903
+ if (!schtaskRunning()) return true;
3904
+ await new Promise((r) => setTimeout(r, 200));
3684
3905
  }
3906
+ return false;
3907
+ }
3908
+
3909
+ // src/service/systemd.ts
3910
+ import { spawnSync as spawnSync3 } from "child_process";
3911
+ import { existsSync as existsSync6 } from "fs";
3912
+ import { mkdir as mkdir8, rm as rm4, writeFile as writeFile7 } from "fs/promises";
3913
+ import { homedir as homedir4 } from "os";
3914
+ import { dirname as dirname9, join as join10 } from "path";
3915
+ var SYSTEMD_UNIT_NAME = "feishu-codex-bridge.service";
3916
+ function systemdUnitPath() {
3917
+ const base = process.env.XDG_CONFIG_HOME ?? join10(homedir4(), ".config");
3918
+ return join10(base, "systemd", "user", SYSTEMD_UNIT_NAME);
3919
+ }
3920
+ function buildUnit() {
3921
+ const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3922
+ const nodePath = process.execPath;
3923
+ const cliBinPath = resolveCliBinPath();
3924
+ const pathEnv = process.env.PATH ?? "";
3925
+ return `[Unit]
3926
+ Description=feishu-codex-bridge bot
3927
+ After=network-online.target
3928
+ Wants=network-online.target
3929
+
3930
+ [Service]
3931
+ Type=simple
3932
+ ExecStart="${esc(nodePath)}" "${esc(cliBinPath)}" run
3933
+ Restart=always
3934
+ RestartSec=5
3935
+ StandardOutput=append:${serviceStdoutPath()}
3936
+ StandardError=append:${serviceStderrPath()}
3937
+ Environment="PATH=${esc(pathEnv)}"
3938
+
3939
+ [Install]
3940
+ WantedBy=default.target
3941
+ `;
3942
+ }
3943
+ function runSystemctl(args) {
3944
+ const r = spawnSync3("systemctl", ["--user", ...args], { encoding: "utf8" });
3685
3945
  return {
3686
- install: installLaunchd,
3687
- uninstall: uninstallLaunchd,
3688
- status: async () => statusLaunchd(),
3689
- restart: restartLaunchd,
3690
- logs: tailLaunchdLogs
3946
+ ok: r.status === 0,
3947
+ status: r.status,
3948
+ stdout: r.stdout ?? "",
3949
+ stderr: (r.error ? `${r.error.message}
3950
+ ` : "") + (r.stderr ?? "")
3951
+ };
3952
+ }
3953
+ function systemctlError(command, r) {
3954
+ const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
3955
+ return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
3956
+ }
3957
+ function systemdAvailable() {
3958
+ const r = spawnSync3("systemctl", ["--user", "is-system-running"], { encoding: "utf8" });
3959
+ if (r.error) return false;
3960
+ const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
3961
+ return !/not been booted with systemd|Failed to connect to (the )?bus|Failed to (connect|get) D-?Bus/i.test(out);
3962
+ }
3963
+ function ensureSystemdOrThrow() {
3964
+ if (systemdAvailable()) return;
3965
+ throw new Error(
3966
+ "\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684\u7528\u6237\u7EA7 systemd\u3002WSL \u9700\u5728 /etc/wsl.conf \u5199\u5165 `[boot]\\nsystemd=true` \u540E\u6267\u884C `wsl --shutdown` \u91CD\u542F\uFF1B\u6216\u76F4\u63A5\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C\uFF08\u65E0\u9700\u540E\u53F0\u670D\u52A1\uFF09\u3002"
3967
+ );
3968
+ }
3969
+ async function installSystemd() {
3970
+ ensureSystemdOrThrow();
3971
+ const unitPath = systemdUnitPath();
3972
+ await mkdir8(dirname9(unitPath), { recursive: true });
3973
+ await ensureLogFiles();
3974
+ await writeFile7(unitPath, buildUnit(), "utf8");
3975
+ const reload = runSystemctl(["daemon-reload"]);
3976
+ if (!reload.ok) throw systemctlError("systemctl --user daemon-reload", reload);
3977
+ const enable = runSystemctl(["enable", "--now", SYSTEMD_UNIT_NAME]);
3978
+ if (!enable.ok) throw systemctlError("systemctl --user enable --now", enable);
3979
+ return statusSystemd();
3980
+ }
3981
+ async function uninstallSystemd() {
3982
+ if (systemdAvailable() && unitExists()) {
3983
+ runSystemctl(["disable", "--now", SYSTEMD_UNIT_NAME]);
3984
+ }
3985
+ await rm4(systemdUnitPath(), { force: true });
3986
+ if (systemdAvailable()) runSystemctl(["daemon-reload"]);
3987
+ }
3988
+ async function restartSystemd() {
3989
+ ensureSystemdOrThrow();
3990
+ if (!unitExists()) {
3991
+ throw new Error(`systemd unit \u672A\u5B89\u88C5\uFF1A${systemdUnitPath()}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
3992
+ }
3993
+ const restart = runSystemctl(["restart", SYSTEMD_UNIT_NAME]);
3994
+ if (!restart.ok) throw systemctlError("systemctl --user restart", restart);
3995
+ return statusSystemd();
3996
+ }
3997
+ function statusSystemd() {
3998
+ const installed = unitExists();
3999
+ const raw = installed && systemdAvailable() ? describeService() : "";
4000
+ return {
4001
+ platformName: "systemd (Linux user)",
4002
+ installed,
4003
+ running: systemdActive(),
4004
+ servicePath: systemdUnitPath(),
4005
+ stdoutPath: serviceStdoutPath(),
4006
+ stderrPath: serviceStderrPath(),
4007
+ pid: raw.match(/Main PID:\s*(\d+)/)?.[1],
4008
+ // On an inactive unit the "Process: <pid> ExecStart=... status=<n>" line
4009
+ // carries the last exit code.
4010
+ lastExit: raw.match(/Process:\s+\d+\s+ExecStart=.*status=(\d+)/)?.[1],
4011
+ raw
3691
4012
  };
3692
4013
  }
4014
+ function unitExists() {
4015
+ return existsSync6(systemdUnitPath());
4016
+ }
4017
+ function systemdActive() {
4018
+ const r = spawnSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT_NAME], {
4019
+ stdio: ["ignore", "ignore", "ignore"]
4020
+ });
4021
+ return r.status === 0;
4022
+ }
4023
+ function describeService() {
4024
+ const r = runSystemctl(["status", SYSTEMD_UNIT_NAME, "--no-pager"]);
4025
+ return r.stdout || r.stderr || "";
4026
+ }
4027
+
4028
+ // src/service/adapter.ts
4029
+ function getServiceAdapter() {
4030
+ if (process.platform === "darwin") {
4031
+ return {
4032
+ install: installLaunchd,
4033
+ uninstall: uninstallLaunchd,
4034
+ status: async () => statusLaunchd(),
4035
+ restart: restartLaunchd,
4036
+ logs: tailLaunchdLogs
4037
+ };
4038
+ }
4039
+ if (process.platform === "win32") {
4040
+ return {
4041
+ install: installSchtask,
4042
+ uninstall: uninstallSchtask,
4043
+ status: async () => statusSchtask(),
4044
+ restart: restartSchtask,
4045
+ logs: tailServiceLogs
4046
+ };
4047
+ }
4048
+ if (process.platform === "linux") {
4049
+ return {
4050
+ install: installSystemd,
4051
+ uninstall: uninstallSystemd,
4052
+ status: async () => statusSystemd(),
4053
+ restart: restartSystemd,
4054
+ logs: tailServiceLogs
4055
+ };
4056
+ }
4057
+ throw new Error(
4058
+ "service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u540E\u53F0\u670D\u52A1\uFF08\u4EC5 macOS launchd / Windows \u8BA1\u5212\u4EFB\u52A1 / Linux systemd\uFF09\u3002\u8BF7\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C\u3002"
4059
+ );
4060
+ }
4061
+ function isServiceRunning() {
4062
+ try {
4063
+ if (process.platform === "darwin") return isLoaded();
4064
+ if (process.platform === "win32") return schtaskRunning();
4065
+ if (process.platform === "linux") return systemdActive();
4066
+ } catch {
4067
+ }
4068
+ return false;
4069
+ }
3693
4070
 
3694
4071
  // src/service/update.ts
3695
4072
  var execFileP = promisify(execFile);
3696
4073
  var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
3697
4074
  function pkgRoot() {
3698
- return resolve4(dirname7(fileURLToPath3(import.meta.url)), "..");
4075
+ return resolve5(dirname10(fileURLToPath4(import.meta.url)), "..");
3699
4076
  }
3700
4077
  function pkgJson() {
3701
4078
  try {
3702
- return JSON.parse(readFileSync2(join8(pkgRoot(), "package.json"), "utf8"));
4079
+ return JSON.parse(readFileSync2(join11(pkgRoot(), "package.json"), "utf8"));
3703
4080
  } catch {
3704
4081
  return {};
3705
4082
  }
@@ -3711,7 +4088,7 @@ function packageName() {
3711
4088
  return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
3712
4089
  }
3713
4090
  function isDevSource() {
3714
- return existsSync5(join8(pkgRoot(), ".git"));
4091
+ return existsSync7(join11(pkgRoot(), ".git"));
3715
4092
  }
3716
4093
  function isNewer(a, b) {
3717
4094
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -3734,7 +4111,7 @@ async function latestVersion() {
3734
4111
  async function installLatest(opts = {}) {
3735
4112
  const target = `${packageName()}@latest`;
3736
4113
  return await new Promise((resolveP) => {
3737
- const child = spawn5(NPM, ["install", "-g", target], {
4114
+ const child = spawn4(NPM, ["install", "-g", target], {
3738
4115
  stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
3739
4116
  });
3740
4117
  let out = "";
@@ -3750,20 +4127,16 @@ async function installLatest(opts = {}) {
3750
4127
  });
3751
4128
  }
3752
4129
  function daemonRunning() {
3753
- try {
3754
- return statusLaunchd().loaded;
3755
- } catch {
3756
- return false;
3757
- }
4130
+ return isServiceRunning();
3758
4131
  }
3759
4132
  async function restartDaemon() {
3760
4133
  await getServiceAdapter().restart();
3761
4134
  }
3762
4135
 
3763
4136
  // src/project/lifecycle.ts
3764
- import { mkdir as mkdir6 } from "fs/promises";
3765
- import { existsSync as existsSync6 } from "fs";
3766
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
4137
+ import { mkdir as mkdir9 } from "fs/promises";
4138
+ import { existsSync as existsSync8 } from "fs";
4139
+ import { isAbsolute as isAbsolute2, join as join12, resolve as resolve6 } from "path";
3767
4140
 
3768
4141
  // src/project/git-info.ts
3769
4142
  import { execFile as execFile2 } from "child_process";
@@ -3898,12 +4271,12 @@ async function onboardGroup(channel, project) {
3898
4271
  // src/project/lifecycle.ts
3899
4272
  async function resolveCwd(name, existingPath) {
3900
4273
  if (existingPath) {
3901
- const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve5(existingPath);
3902
- if (!existsSync6(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
4274
+ const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
4275
+ if (!existsSync8(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
3903
4276
  return { cwd: cwd2, blank: false };
3904
4277
  }
3905
- const cwd = join9(paths.projectsRootDir, name);
3906
- await mkdir6(cwd, { recursive: true });
4278
+ const cwd = join12(paths.projectsRootDir, name);
4279
+ await mkdir9(cwd, { recursive: true });
3907
4280
  return { cwd, blank: true };
3908
4281
  }
3909
4282
  async function createProject(channel, input2) {
@@ -3970,12 +4343,12 @@ async function leaveChat(channel, chatId) {
3970
4343
  }
3971
4344
 
3972
4345
  // src/bot/session-store.ts
3973
- import { mkdir as mkdir7, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "fs/promises";
3974
- import { dirname as dirname8 } from "path";
4346
+ import { mkdir as mkdir10, readFile as readFile8, rename as rename5, writeFile as writeFile8 } from "fs/promises";
4347
+ import { dirname as dirname11 } from "path";
3975
4348
  var FILE_VERSION3 = 1;
3976
4349
  async function read2() {
3977
4350
  try {
3978
- const text = await readFile7(paths.sessionsFile, "utf8");
4351
+ const text = await readFile8(paths.sessionsFile, "utf8");
3979
4352
  const parsed = JSON.parse(text);
3980
4353
  return Array.isArray(parsed.sessions) ? parsed.sessions : [];
3981
4354
  } catch (err) {
@@ -3984,10 +4357,10 @@ async function read2() {
3984
4357
  }
3985
4358
  }
3986
4359
  async function write2(sessions) {
3987
- await mkdir7(dirname8(paths.sessionsFile), { recursive: true });
4360
+ await mkdir10(dirname11(paths.sessionsFile), { recursive: true });
3988
4361
  const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
3989
4362
  const body = { version: FILE_VERSION3, sessions };
3990
- await writeFile6(tmp, `${JSON.stringify(body, null, 2)}
4363
+ await writeFile8(tmp, `${JSON.stringify(body, null, 2)}
3991
4364
  `, "utf8");
3992
4365
  await rename5(tmp, paths.sessionsFile);
3993
4366
  }
@@ -4031,8 +4404,8 @@ async function handleDmConsole(channel, cfg, msg) {
4031
4404
  }
4032
4405
 
4033
4406
  // src/bot/media.ts
4034
- import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as stat3 } from "fs/promises";
4035
- import { join as join10 } from "path";
4407
+ import { mkdir as mkdir11, readdir as readdir2, rm as rm5, stat as stat3 } from "fs/promises";
4408
+ import { join as join13 } from "path";
4036
4409
  var MAX_IMAGES2 = 9;
4037
4410
  var MEDIA_TTL_MS = 60 * 6e4;
4038
4411
  var EXT_BY_CONTENT_TYPE = {
@@ -4061,7 +4434,7 @@ async function collectInboundImages(channel, msg) {
4061
4434
  if (refs.length === 0) return [];
4062
4435
  await pruneOldMedia();
4063
4436
  try {
4064
- await mkdir8(paths.mediaDir, { recursive: true });
4437
+ await mkdir11(paths.mediaDir, { recursive: true });
4065
4438
  } catch {
4066
4439
  }
4067
4440
  const out = [];
@@ -4137,7 +4510,7 @@ async function downloadOne(channel, ref, index) {
4137
4510
  params: { type: "image" }
4138
4511
  });
4139
4512
  const ext = extFromHeaders(res.headers);
4140
- const file = join10(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
4513
+ const file = join13(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
4141
4514
  await res.writeFile(file);
4142
4515
  return file;
4143
4516
  } catch (err) {
@@ -4171,10 +4544,10 @@ async function pruneOldMedia() {
4171
4544
  }
4172
4545
  const cutoff = Date.now() - MEDIA_TTL_MS;
4173
4546
  for (const name of entries) {
4174
- const file = join10(paths.mediaDir, name);
4547
+ const file = join13(paths.mediaDir, name);
4175
4548
  try {
4176
4549
  const st = await stat3(file);
4177
- if (st.mtimeMs < cutoff) await rm3(file, { force: true });
4550
+ if (st.mtimeMs < cutoff) await rm5(file, { force: true });
4178
4551
  } catch {
4179
4552
  }
4180
4553
  }
@@ -5546,7 +5919,7 @@ async function startBridge(opts) {
5546
5919
 
5547
5920
  // src/core/single-instance.ts
5548
5921
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "fs";
5549
- import { dirname as dirname9 } from "path";
5922
+ import { dirname as dirname12 } from "path";
5550
5923
  var BridgeAlreadyRunningError = class extends Error {
5551
5924
  constructor(pid) {
5552
5925
  super(
@@ -5575,7 +5948,7 @@ function acquireSingleInstanceLock(appId) {
5575
5948
  } catch (err) {
5576
5949
  if (err instanceof BridgeAlreadyRunningError) throw err;
5577
5950
  }
5578
- mkdirSync2(dirname9(file), { recursive: true });
5951
+ mkdirSync2(dirname12(file), { recursive: true });
5579
5952
  const record = { pid: process.pid, appId, startedAt: Date.now() };
5580
5953
  writeFileSync(file, `${JSON.stringify(record)}
5581
5954
  `, "utf8");
@@ -5644,12 +6017,12 @@ async function runStart() {
5644
6017
  return;
5645
6018
  }
5646
6019
  const status = await getServiceAdapter().install();
5647
- console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002");
6020
+ console.log(installedNote());
5648
6021
  printStatus(status);
5649
6022
  }
5650
6023
  async function runStop() {
5651
6024
  await getServiceAdapter().uninstall();
5652
- console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\u5F00\u673A\u81EA\u542F\uFF08\u5DF2\u79FB\u9664 launchd plist\uFF09\u3002");
6025
+ console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\u81EA\u542F\uFF08\u5DF2\u79FB\u9664\u670D\u52A1\u5B9A\u4E49\uFF09\u3002");
5653
6026
  }
5654
6027
  async function runRestart() {
5655
6028
  const status = await getServiceAdapter().restart();
@@ -5662,18 +6035,29 @@ async function runStatus() {
5662
6035
  async function runLogs(follow) {
5663
6036
  await getServiceAdapter().logs(follow);
5664
6037
  }
6038
+ function installedNote() {
6039
+ switch (process.platform) {
6040
+ case "win32":
6041
+ return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF1B\u6CE8\u610F\uFF1AWindows \u8BA1\u5212\u4EFB\u52A1\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002";
6042
+ case "linux":
6043
+ return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002\n \u63D0\u793A\uFF1A\u6CE8\u9500\u540E\u4ECD\u4FDD\u6301\u8FD0\u884C\u9700\u6267\u884C\u4E00\u6B21 `loginctl enable-linger $USER`\u3002";
6044
+ default:
6045
+ return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002";
6046
+ }
6047
+ }
5665
6048
  function printStatus(status) {
5666
- console.log(`plist: ${status.plistPath}`);
6049
+ console.log(`service: ${status.platformName}`);
6050
+ console.log(`path: ${status.servicePath}`);
5667
6051
  console.log(`installed: ${status.installed ? "yes" : "no"}`);
5668
- console.log(`loaded: ${status.loaded ? "yes" : "no"}`);
6052
+ console.log(`running: ${status.running ? "yes" : "no"}`);
5669
6053
  console.log(`pid: ${status.pid ?? "-"}`);
5670
6054
  console.log(`last exit: ${status.lastExit ?? "-"}`);
5671
6055
  console.log(`stdout: ${status.stdoutPath}`);
5672
6056
  console.log(`stderr: ${status.stderrPath}`);
5673
6057
  if (!status.installed) {
5674
6058
  console.log("\u63D0\u793A\uFF1A\u540E\u53F0\u670D\u52A1\u5C1A\u672A\u5B89\u88C5\uFF0C\u8FD0\u884C `feishu-codex-bridge start`\u3002");
5675
- } else if (!status.loaded) {
5676
- console.log("\u63D0\u793A\uFF1Aplist \u5DF2\u5B58\u5728\uFF0C\u4F46 launchd \u5F53\u524D\u672A\u52A0\u8F7D\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
6059
+ } else if (!status.running) {
6060
+ console.log("\u63D0\u793A\uFF1A\u670D\u52A1\u5DF2\u6CE8\u518C\u4F46\u5F53\u524D\u672A\u8FD0\u884C\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
5677
6061
  }
5678
6062
  }
5679
6063
 
@@ -5726,7 +6110,7 @@ async function runUpdate(opts = {}) {
5726
6110
  }
5727
6111
 
5728
6112
  // src/cli/commands/bot.ts
5729
- import { rm as rm4 } from "fs/promises";
6113
+ import { rm as rm6 } from "fs/promises";
5730
6114
  async function runBotInit(name) {
5731
6115
  if (!ensureCodex()) {
5732
6116
  process.exitCode = 1;
@@ -5781,7 +6165,7 @@ async function runBotRm(name) {
5781
6165
  }
5782
6166
  const after = await removeBot(bot2.appId);
5783
6167
  await removeSecret(secretKeyForApp(bot2.appId));
5784
- await rm4(botDir(bot2.appId), { recursive: true, force: true });
6168
+ await rm6(botDir(bot2.appId), { recursive: true, force: true });
5785
6169
  console.log(`\u2713 \u5DF2\u79FB\u9664\u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId})\uFF1A\u6CE8\u518C\u8868 + \u5BC6\u94A5 + \u72B6\u6001\u76EE\u5F55(projects/sessions)\u3002`);
5786
6170
  if (after.bots.length === 0) {
5787
6171
  console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
@@ -5839,15 +6223,15 @@ async function secretsRemove(id) {
5839
6223
  console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
5840
6224
  }
5841
6225
  function readStdin() {
5842
- return new Promise((resolve6) => {
6226
+ return new Promise((resolve7) => {
5843
6227
  let data = "";
5844
6228
  if (process.stdin.isTTY) {
5845
- resolve6("");
6229
+ resolve7("");
5846
6230
  return;
5847
6231
  }
5848
6232
  process.stdin.setEncoding("utf8");
5849
6233
  process.stdin.on("data", (c) => data += c);
5850
- process.stdin.on("end", () => resolve6(data));
6234
+ process.stdin.on("end", () => resolve7(data));
5851
6235
  });
5852
6236
  }
5853
6237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.2.1",
3
+ "version": "0.2.2-win",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,9 +30,11 @@
30
30
  "dependencies": {
31
31
  "@larksuiteoapi/node-sdk": "^1.65.0",
32
32
  "commander": "^12.1.0",
33
+ "cross-spawn": "^7.0.6",
33
34
  "qrcode-terminal": "^0.12.0"
34
35
  },
35
36
  "devDependencies": {
37
+ "@types/cross-spawn": "^6.0.6",
36
38
  "@types/node": "^22.10.0",
37
39
  "@types/qrcode-terminal": "^0.12.2",
38
40
  "tsup": "^8.3.5",