@modelzen/feishu-codex-bridge 0.2.1 → 0.2.3-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 +506 -97
  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}` };
@@ -3505,38 +3557,105 @@ function buildGroupSettingsCard(project) {
3505
3557
 
3506
3558
  // src/service/update.ts
3507
3559
  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";
3560
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
3561
+ import { dirname as dirname9, 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,315 @@ 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/win-startup.ts
3796
+ import { spawn as spawn4, spawnSync as spawnSync2 } from "child_process";
3797
+ import { openSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
3798
+ import { mkdir as mkdir7, writeFile as writeFile6 } from "fs/promises";
3799
+ import { join as join9 } from "path";
3800
+ var RUN_KEY_PATH = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
3801
+ var RUN_KEY_NAME = "feishu-codex-bridge";
3802
+ var SERVICE_ENV_FLAG = "FEISHU_CODEX_BRIDGE_SERVICE";
3803
+ function launcherCmdPath() {
3804
+ return join9(paths.appDir, "service-launcher.cmd");
3805
+ }
3806
+ function launcherVbsPath() {
3807
+ return join9(paths.appDir, "service-launcher.vbs");
3808
+ }
3809
+ function servicePidFile() {
3810
+ return join9(paths.appDir, "service.pid");
3811
+ }
3812
+ function buildLauncherCmd() {
3813
+ return [
3814
+ "@echo off",
3815
+ `set "PATH=${process.env.PATH ?? ""}"`,
3816
+ `set "${SERVICE_ENV_FLAG}=1"`,
3817
+ `"${process.execPath}" "${resolveCliBinPath()}" run >> "${serviceStdoutPath()}" 2>> "${serviceStderrPath()}"`,
3818
+ ""
3819
+ ].join("\r\n");
3820
+ }
3821
+ function buildLauncherVbs() {
3822
+ return [
3823
+ "' Auto-generated by feishu-codex-bridge. Do not edit.",
3824
+ 'Set sh = CreateObject("WScript.Shell")',
3825
+ `sh.Run "cmd /c ""${launcherCmdPath()}""", 0, False`,
3826
+ ""
3827
+ ].join("\r\n");
3828
+ }
3829
+ function startNow() {
3830
+ const out = openSync(serviceStdoutPath(), "a");
3831
+ const err = openSync(serviceStderrPath(), "a");
3832
+ const child = spawn4(process.execPath, [resolveCliBinPath(), "run"], {
3833
+ detached: true,
3834
+ windowsHide: true,
3835
+ stdio: ["ignore", out, err],
3836
+ env: mergeProcessEnv(process.env, { [SERVICE_ENV_FLAG]: "1" })
3837
+ });
3838
+ if (child.pid) writeFileSync(servicePidFile(), String(child.pid), "utf8");
3839
+ child.unref();
3840
+ }
3841
+ async function installWinStartup() {
3842
+ await mkdir7(paths.appDir, { recursive: true });
3843
+ await ensureLogFiles();
3844
+ await writeFile6(launcherCmdPath(), buildLauncherCmd(), "utf8");
3845
+ await writeFile6(launcherVbsPath(), buildLauncherVbs(), "utf8");
3846
+ const reg = spawnSync2(
3847
+ "reg",
3848
+ ["add", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/t", "REG_SZ", "/d", `wscript.exe "${launcherVbsPath()}"`, "/f"],
3849
+ { encoding: "utf8" }
3850
+ );
3851
+ if (reg.status !== 0) {
3852
+ throw new Error(`\u5199\u5165\u767B\u5F55\u81EA\u542F\u6CE8\u518C\u8868\u9879\u5931\u8D25\uFF08exit ${reg.status ?? "unknown"}\uFF09\uFF1A${(reg.stderr || reg.stdout || "").trim()}`);
3853
+ }
3854
+ startNow();
3855
+ return statusWinStartup();
3856
+ }
3857
+ async function uninstallWinStartup() {
3858
+ spawnSync2("reg", ["delete", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/f"], { stdio: "ignore" });
3859
+ killService();
3860
+ rmSync(servicePidFile(), { force: true });
3861
+ }
3862
+ async function restartWinStartup() {
3863
+ if (!isInstalled()) {
3864
+ throw new Error("\u767B\u5F55\u81EA\u542F\u672A\u5B89\u88C5\uFF08\u5148\u8FD0\u884C `feishu-codex-bridge start`\uFF09\u3002");
3865
+ }
3866
+ await ensureLogFiles();
3867
+ killService();
3868
+ startNow();
3869
+ return statusWinStartup();
3870
+ }
3871
+ function statusWinStartup() {
3872
+ const installed = isInstalled();
3873
+ const pid = readServicePid();
3874
+ const running = pid !== null && pidAlive(pid);
3875
+ return {
3876
+ platformName: "Windows \u767B\u5F55\u81EA\u542F\uFF08HKCU Run\uFF0C\u514D\u7BA1\u7406\u5458\uFF09",
3877
+ installed,
3878
+ running,
3879
+ servicePath: `${RUN_KEY_PATH}\\${RUN_KEY_NAME}`,
3880
+ stdoutPath: serviceStdoutPath(),
3881
+ stderrPath: serviceStderrPath(),
3882
+ pid: running && pid !== null ? String(pid) : void 0,
3883
+ raw: ""
3884
+ };
3885
+ }
3886
+ function winStartupRunning() {
3887
+ const pid = readServicePid();
3888
+ return pid !== null && pidAlive(pid);
3889
+ }
3890
+ function recordServicePid() {
3891
+ if (process.platform !== "win32" || !process.env[SERVICE_ENV_FLAG]) return;
3892
+ try {
3893
+ writeFileSync(servicePidFile(), String(process.pid), "utf8");
3894
+ } catch {
3895
+ }
3896
+ process.once("exit", clearServicePid);
3897
+ }
3898
+ function clearServicePid() {
3899
+ if (process.platform !== "win32" || !process.env[SERVICE_ENV_FLAG]) return;
3900
+ try {
3901
+ if (readServicePid() === process.pid) rmSync(servicePidFile(), { force: true });
3902
+ } catch {
3903
+ }
3904
+ }
3905
+ function isInstalled() {
3906
+ const r = spawnSync2("reg", ["query", RUN_KEY_PATH, "/v", RUN_KEY_NAME], {
3907
+ stdio: ["ignore", "ignore", "ignore"]
3908
+ });
3909
+ return r.status === 0;
3910
+ }
3911
+ function readServicePid() {
3912
+ try {
3913
+ const pid = Number.parseInt(readFileSync2(servicePidFile(), "utf8").trim(), 10);
3914
+ return Number.isFinite(pid) ? pid : null;
3915
+ } catch {
3916
+ return null;
3917
+ }
3918
+ }
3919
+ function pidAlive(pid) {
3920
+ try {
3921
+ process.kill(pid, 0);
3922
+ return true;
3923
+ } catch (err) {
3924
+ return err.code === "EPERM";
3684
3925
  }
3926
+ }
3927
+ function killService() {
3928
+ const pid = readServicePid();
3929
+ if (pid === null || !pidAlive(pid)) return;
3930
+ spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
3931
+ }
3932
+
3933
+ // src/service/systemd.ts
3934
+ import { spawnSync as spawnSync3 } from "child_process";
3935
+ import { existsSync as existsSync5 } from "fs";
3936
+ import { mkdir as mkdir8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
3937
+ import { homedir as homedir4 } from "os";
3938
+ import { dirname as dirname8, join as join10 } from "path";
3939
+ var SYSTEMD_UNIT_NAME = "feishu-codex-bridge.service";
3940
+ function systemdUnitPath() {
3941
+ const base = process.env.XDG_CONFIG_HOME ?? join10(homedir4(), ".config");
3942
+ return join10(base, "systemd", "user", SYSTEMD_UNIT_NAME);
3943
+ }
3944
+ function buildUnit() {
3945
+ const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3946
+ const nodePath = process.execPath;
3947
+ const cliBinPath = resolveCliBinPath();
3948
+ const pathEnv = process.env.PATH ?? "";
3949
+ return `[Unit]
3950
+ Description=feishu-codex-bridge bot
3951
+ After=network-online.target
3952
+ Wants=network-online.target
3953
+
3954
+ [Service]
3955
+ Type=simple
3956
+ ExecStart="${esc(nodePath)}" "${esc(cliBinPath)}" run
3957
+ Restart=always
3958
+ RestartSec=5
3959
+ StandardOutput=append:${serviceStdoutPath()}
3960
+ StandardError=append:${serviceStderrPath()}
3961
+ Environment="PATH=${esc(pathEnv)}"
3962
+
3963
+ [Install]
3964
+ WantedBy=default.target
3965
+ `;
3966
+ }
3967
+ function runSystemctl(args) {
3968
+ const r = spawnSync3("systemctl", ["--user", ...args], { encoding: "utf8" });
3685
3969
  return {
3686
- install: installLaunchd,
3687
- uninstall: uninstallLaunchd,
3688
- status: async () => statusLaunchd(),
3689
- restart: restartLaunchd,
3690
- logs: tailLaunchdLogs
3970
+ ok: r.status === 0,
3971
+ status: r.status,
3972
+ stdout: r.stdout ?? "",
3973
+ stderr: (r.error ? `${r.error.message}
3974
+ ` : "") + (r.stderr ?? "")
3691
3975
  };
3692
3976
  }
3977
+ function systemctlError(command, r) {
3978
+ const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
3979
+ return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
3980
+ }
3981
+ function systemdAvailable() {
3982
+ const r = spawnSync3("systemctl", ["--user", "is-system-running"], { encoding: "utf8" });
3983
+ if (r.error) return false;
3984
+ const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
3985
+ return !/not been booted with systemd|Failed to connect to (the )?bus|Failed to (connect|get) D-?Bus/i.test(out);
3986
+ }
3987
+ function ensureSystemdOrThrow() {
3988
+ if (systemdAvailable()) return;
3989
+ throw new Error(
3990
+ "\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"
3991
+ );
3992
+ }
3993
+ async function installSystemd() {
3994
+ ensureSystemdOrThrow();
3995
+ const unitPath = systemdUnitPath();
3996
+ await mkdir8(dirname8(unitPath), { recursive: true });
3997
+ await ensureLogFiles();
3998
+ await writeFile7(unitPath, buildUnit(), "utf8");
3999
+ const reload = runSystemctl(["daemon-reload"]);
4000
+ if (!reload.ok) throw systemctlError("systemctl --user daemon-reload", reload);
4001
+ const enable = runSystemctl(["enable", "--now", SYSTEMD_UNIT_NAME]);
4002
+ if (!enable.ok) throw systemctlError("systemctl --user enable --now", enable);
4003
+ return statusSystemd();
4004
+ }
4005
+ async function uninstallSystemd() {
4006
+ if (systemdAvailable() && unitExists()) {
4007
+ runSystemctl(["disable", "--now", SYSTEMD_UNIT_NAME]);
4008
+ }
4009
+ await rm3(systemdUnitPath(), { force: true });
4010
+ if (systemdAvailable()) runSystemctl(["daemon-reload"]);
4011
+ }
4012
+ async function restartSystemd() {
4013
+ ensureSystemdOrThrow();
4014
+ if (!unitExists()) {
4015
+ throw new Error(`systemd unit \u672A\u5B89\u88C5\uFF1A${systemdUnitPath()}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
4016
+ }
4017
+ const restart = runSystemctl(["restart", SYSTEMD_UNIT_NAME]);
4018
+ if (!restart.ok) throw systemctlError("systemctl --user restart", restart);
4019
+ return statusSystemd();
4020
+ }
4021
+ function statusSystemd() {
4022
+ const installed = unitExists();
4023
+ const raw = installed && systemdAvailable() ? describeService() : "";
4024
+ return {
4025
+ platformName: "systemd (Linux user)",
4026
+ installed,
4027
+ running: systemdActive(),
4028
+ servicePath: systemdUnitPath(),
4029
+ stdoutPath: serviceStdoutPath(),
4030
+ stderrPath: serviceStderrPath(),
4031
+ pid: raw.match(/Main PID:\s*(\d+)/)?.[1],
4032
+ // On an inactive unit the "Process: <pid> ExecStart=... status=<n>" line
4033
+ // carries the last exit code.
4034
+ lastExit: raw.match(/Process:\s+\d+\s+ExecStart=.*status=(\d+)/)?.[1],
4035
+ raw
4036
+ };
4037
+ }
4038
+ function unitExists() {
4039
+ return existsSync5(systemdUnitPath());
4040
+ }
4041
+ function systemdActive() {
4042
+ const r = spawnSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT_NAME], {
4043
+ stdio: ["ignore", "ignore", "ignore"]
4044
+ });
4045
+ return r.status === 0;
4046
+ }
4047
+ function describeService() {
4048
+ const r = runSystemctl(["status", SYSTEMD_UNIT_NAME, "--no-pager"]);
4049
+ return r.stdout || r.stderr || "";
4050
+ }
4051
+
4052
+ // src/service/adapter.ts
4053
+ function getServiceAdapter() {
4054
+ if (process.platform === "darwin") {
4055
+ return {
4056
+ install: installLaunchd,
4057
+ uninstall: uninstallLaunchd,
4058
+ status: async () => statusLaunchd(),
4059
+ restart: restartLaunchd,
4060
+ logs: tailLaunchdLogs
4061
+ };
4062
+ }
4063
+ if (process.platform === "win32") {
4064
+ return {
4065
+ install: installWinStartup,
4066
+ uninstall: uninstallWinStartup,
4067
+ status: async () => statusWinStartup(),
4068
+ restart: restartWinStartup,
4069
+ logs: tailServiceLogs
4070
+ };
4071
+ }
4072
+ if (process.platform === "linux") {
4073
+ return {
4074
+ install: installSystemd,
4075
+ uninstall: uninstallSystemd,
4076
+ status: async () => statusSystemd(),
4077
+ restart: restartSystemd,
4078
+ logs: tailServiceLogs
4079
+ };
4080
+ }
4081
+ throw new Error(
4082
+ "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"
4083
+ );
4084
+ }
4085
+ function isServiceRunning() {
4086
+ try {
4087
+ if (process.platform === "darwin") return isLoaded();
4088
+ if (process.platform === "win32") return winStartupRunning();
4089
+ if (process.platform === "linux") return systemdActive();
4090
+ } catch {
4091
+ }
4092
+ return false;
4093
+ }
3693
4094
 
3694
4095
  // src/service/update.ts
3695
4096
  var execFileP = promisify(execFile);
3696
4097
  var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
3697
4098
  function pkgRoot() {
3698
- return resolve4(dirname7(fileURLToPath3(import.meta.url)), "..");
4099
+ return resolve5(dirname9(fileURLToPath4(import.meta.url)), "..");
3699
4100
  }
3700
4101
  function pkgJson() {
3701
4102
  try {
3702
- return JSON.parse(readFileSync2(join8(pkgRoot(), "package.json"), "utf8"));
4103
+ return JSON.parse(readFileSync3(join11(pkgRoot(), "package.json"), "utf8"));
3703
4104
  } catch {
3704
4105
  return {};
3705
4106
  }
@@ -3711,7 +4112,7 @@ function packageName() {
3711
4112
  return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
3712
4113
  }
3713
4114
  function isDevSource() {
3714
- return existsSync5(join8(pkgRoot(), ".git"));
4115
+ return existsSync6(join11(pkgRoot(), ".git"));
3715
4116
  }
3716
4117
  function isNewer(a, b) {
3717
4118
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -3750,20 +4151,16 @@ async function installLatest(opts = {}) {
3750
4151
  });
3751
4152
  }
3752
4153
  function daemonRunning() {
3753
- try {
3754
- return statusLaunchd().loaded;
3755
- } catch {
3756
- return false;
3757
- }
4154
+ return isServiceRunning();
3758
4155
  }
3759
4156
  async function restartDaemon() {
3760
4157
  await getServiceAdapter().restart();
3761
4158
  }
3762
4159
 
3763
4160
  // 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";
4161
+ import { mkdir as mkdir9 } from "fs/promises";
4162
+ import { existsSync as existsSync7 } from "fs";
4163
+ import { isAbsolute as isAbsolute2, join as join12, resolve as resolve6 } from "path";
3767
4164
 
3768
4165
  // src/project/git-info.ts
3769
4166
  import { execFile as execFile2 } from "child_process";
@@ -3898,12 +4295,12 @@ async function onboardGroup(channel, project) {
3898
4295
  // src/project/lifecycle.ts
3899
4296
  async function resolveCwd(name, existingPath) {
3900
4297
  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}`);
4298
+ const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
4299
+ if (!existsSync7(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
3903
4300
  return { cwd: cwd2, blank: false };
3904
4301
  }
3905
- const cwd = join9(paths.projectsRootDir, name);
3906
- await mkdir6(cwd, { recursive: true });
4302
+ const cwd = join12(paths.projectsRootDir, name);
4303
+ await mkdir9(cwd, { recursive: true });
3907
4304
  return { cwd, blank: true };
3908
4305
  }
3909
4306
  async function createProject(channel, input2) {
@@ -3970,12 +4367,12 @@ async function leaveChat(channel, chatId) {
3970
4367
  }
3971
4368
 
3972
4369
  // 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";
4370
+ import { mkdir as mkdir10, readFile as readFile8, rename as rename5, writeFile as writeFile8 } from "fs/promises";
4371
+ import { dirname as dirname10 } from "path";
3975
4372
  var FILE_VERSION3 = 1;
3976
4373
  async function read2() {
3977
4374
  try {
3978
- const text = await readFile7(paths.sessionsFile, "utf8");
4375
+ const text = await readFile8(paths.sessionsFile, "utf8");
3979
4376
  const parsed = JSON.parse(text);
3980
4377
  return Array.isArray(parsed.sessions) ? parsed.sessions : [];
3981
4378
  } catch (err) {
@@ -3984,10 +4381,10 @@ async function read2() {
3984
4381
  }
3985
4382
  }
3986
4383
  async function write2(sessions) {
3987
- await mkdir7(dirname8(paths.sessionsFile), { recursive: true });
4384
+ await mkdir10(dirname10(paths.sessionsFile), { recursive: true });
3988
4385
  const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
3989
4386
  const body = { version: FILE_VERSION3, sessions };
3990
- await writeFile6(tmp, `${JSON.stringify(body, null, 2)}
4387
+ await writeFile8(tmp, `${JSON.stringify(body, null, 2)}
3991
4388
  `, "utf8");
3992
4389
  await rename5(tmp, paths.sessionsFile);
3993
4390
  }
@@ -4031,8 +4428,8 @@ async function handleDmConsole(channel, cfg, msg) {
4031
4428
  }
4032
4429
 
4033
4430
  // 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";
4431
+ import { mkdir as mkdir11, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
4432
+ import { join as join13 } from "path";
4036
4433
  var MAX_IMAGES2 = 9;
4037
4434
  var MEDIA_TTL_MS = 60 * 6e4;
4038
4435
  var EXT_BY_CONTENT_TYPE = {
@@ -4061,7 +4458,7 @@ async function collectInboundImages(channel, msg) {
4061
4458
  if (refs.length === 0) return [];
4062
4459
  await pruneOldMedia();
4063
4460
  try {
4064
- await mkdir8(paths.mediaDir, { recursive: true });
4461
+ await mkdir11(paths.mediaDir, { recursive: true });
4065
4462
  } catch {
4066
4463
  }
4067
4464
  const out = [];
@@ -4137,7 +4534,7 @@ async function downloadOne(channel, ref, index) {
4137
4534
  params: { type: "image" }
4138
4535
  });
4139
4536
  const ext = extFromHeaders(res.headers);
4140
- const file = join10(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
4537
+ const file = join13(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
4141
4538
  await res.writeFile(file);
4142
4539
  return file;
4143
4540
  } catch (err) {
@@ -4171,10 +4568,10 @@ async function pruneOldMedia() {
4171
4568
  }
4172
4569
  const cutoff = Date.now() - MEDIA_TTL_MS;
4173
4570
  for (const name of entries) {
4174
- const file = join10(paths.mediaDir, name);
4571
+ const file = join13(paths.mediaDir, name);
4175
4572
  try {
4176
4573
  const st = await stat3(file);
4177
- if (st.mtimeMs < cutoff) await rm3(file, { force: true });
4574
+ if (st.mtimeMs < cutoff) await rm4(file, { force: true });
4178
4575
  } catch {
4179
4576
  }
4180
4577
  }
@@ -5545,8 +5942,8 @@ async function startBridge(opts) {
5545
5942
  }
5546
5943
 
5547
5944
  // src/core/single-instance.ts
5548
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "fs";
5549
- import { dirname as dirname9 } from "path";
5945
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
5946
+ import { dirname as dirname11 } from "path";
5550
5947
  var BridgeAlreadyRunningError = class extends Error {
5551
5948
  constructor(pid) {
5552
5949
  super(
@@ -5568,20 +5965,20 @@ function isAlive(pid) {
5568
5965
  function acquireSingleInstanceLock(appId) {
5569
5966
  const file = paths.processesFile;
5570
5967
  try {
5571
- const rec = JSON.parse(readFileSync3(file, "utf8"));
5968
+ const rec = JSON.parse(readFileSync4(file, "utf8"));
5572
5969
  if (rec.pid && rec.pid !== process.pid && rec.appId === appId && isAlive(rec.pid)) {
5573
5970
  throw new BridgeAlreadyRunningError(rec.pid);
5574
5971
  }
5575
5972
  } catch (err) {
5576
5973
  if (err instanceof BridgeAlreadyRunningError) throw err;
5577
5974
  }
5578
- mkdirSync2(dirname9(file), { recursive: true });
5975
+ mkdirSync2(dirname11(file), { recursive: true });
5579
5976
  const record = { pid: process.pid, appId, startedAt: Date.now() };
5580
- writeFileSync(file, `${JSON.stringify(record)}
5977
+ writeFileSync2(file, `${JSON.stringify(record)}
5581
5978
  `, "utf8");
5582
5979
  const release = () => {
5583
5980
  try {
5584
- const rec = JSON.parse(readFileSync3(file, "utf8"));
5981
+ const rec = JSON.parse(readFileSync4(file, "utf8"));
5585
5982
  if (rec.pid === process.pid) unlinkSync(file);
5586
5983
  } catch {
5587
5984
  }
@@ -5610,6 +6007,7 @@ async function runRun() {
5610
6007
  }
5611
6008
  throw err;
5612
6009
  }
6010
+ recordServicePid();
5613
6011
  const fallbackCwd = process.env.FEISHU_CODEX_CWD || process.cwd();
5614
6012
  console.log("\n\u6B63\u5728\u542F\u52A8\u957F\u8FDE\u63A5 bot\u2026");
5615
6013
  console.log("\u79C1\u804A\u6211 `/new <\u540D>` \u5EFA\u9879\u76EE\uFF1B\u5728\u9879\u76EE\u7FA4\u91CC @\u6211 \u5E72\u6D3B\u3002Ctrl+C \u9000\u51FA\u3002\n");
@@ -5644,12 +6042,12 @@ async function runStart() {
5644
6042
  return;
5645
6043
  }
5646
6044
  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");
6045
+ console.log(installedNote());
5648
6046
  printStatus(status);
5649
6047
  }
5650
6048
  async function runStop() {
5651
6049
  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");
6050
+ 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
6051
  }
5654
6052
  async function runRestart() {
5655
6053
  const status = await getServiceAdapter().restart();
@@ -5662,18 +6060,29 @@ async function runStatus() {
5662
6060
  async function runLogs(follow) {
5663
6061
  await getServiceAdapter().logs(follow);
5664
6062
  }
6063
+ function installedNote() {
6064
+ switch (process.platform) {
6065
+ case "win32":
6066
+ return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF0C\u514D\u7BA1\u7406\u5458\uFF09\u3002\n \u63D0\u793A\uFF1A\u767B\u5F55\u81EA\u542F\u5728\u4E0B\u6B21\u767B\u5F55\u751F\u6548\uFF1B\u5F53\u524D\u5DF2\u5728\u540E\u53F0\u9690\u85CF\u542F\u52A8\u3002\u6CE8\u610F Windows \u767B\u5F55\u81EA\u542F\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\u3002";
6067
+ case "linux":
6068
+ 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";
6069
+ default:
6070
+ 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";
6071
+ }
6072
+ }
5665
6073
  function printStatus(status) {
5666
- console.log(`plist: ${status.plistPath}`);
6074
+ console.log(`service: ${status.platformName}`);
6075
+ console.log(`path: ${status.servicePath}`);
5667
6076
  console.log(`installed: ${status.installed ? "yes" : "no"}`);
5668
- console.log(`loaded: ${status.loaded ? "yes" : "no"}`);
6077
+ console.log(`running: ${status.running ? "yes" : "no"}`);
5669
6078
  console.log(`pid: ${status.pid ?? "-"}`);
5670
6079
  console.log(`last exit: ${status.lastExit ?? "-"}`);
5671
6080
  console.log(`stdout: ${status.stdoutPath}`);
5672
6081
  console.log(`stderr: ${status.stderrPath}`);
5673
6082
  if (!status.installed) {
5674
6083
  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");
6084
+ } else if (!status.running) {
6085
+ console.log("\u63D0\u793A\uFF1A\u670D\u52A1\u5DF2\u6CE8\u518C\u4F46\u5F53\u524D\u672A\u8FD0\u884C\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
5677
6086
  }
5678
6087
  }
5679
6088
 
@@ -5726,7 +6135,7 @@ async function runUpdate(opts = {}) {
5726
6135
  }
5727
6136
 
5728
6137
  // src/cli/commands/bot.ts
5729
- import { rm as rm4 } from "fs/promises";
6138
+ import { rm as rm5 } from "fs/promises";
5730
6139
  async function runBotInit(name) {
5731
6140
  if (!ensureCodex()) {
5732
6141
  process.exitCode = 1;
@@ -5781,7 +6190,7 @@ async function runBotRm(name) {
5781
6190
  }
5782
6191
  const after = await removeBot(bot2.appId);
5783
6192
  await removeSecret(secretKeyForApp(bot2.appId));
5784
- await rm4(botDir(bot2.appId), { recursive: true, force: true });
6193
+ await rm5(botDir(bot2.appId), { recursive: true, force: true });
5785
6194
  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
6195
  if (after.bots.length === 0) {
5787
6196
  console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
@@ -5839,15 +6248,15 @@ async function secretsRemove(id) {
5839
6248
  console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
5840
6249
  }
5841
6250
  function readStdin() {
5842
- return new Promise((resolve6) => {
6251
+ return new Promise((resolve7) => {
5843
6252
  let data = "";
5844
6253
  if (process.stdin.isTTY) {
5845
- resolve6("");
6254
+ resolve7("");
5846
6255
  return;
5847
6256
  }
5848
6257
  process.stdin.setEncoding("utf8");
5849
6258
  process.stdin.on("data", (c) => data += c);
5850
- process.stdin.on("end", () => resolve6(data));
6259
+ process.stdin.on("end", () => resolve7(data));
5851
6260
  });
5852
6261
  }
5853
6262
 
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.3-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",