@napuketto/cli 0.1.27 → 0.2.0

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/dist/index.mjs CHANGED
@@ -1,51 +1,119 @@
1
1
  #!/usr/bin/env node
2
2
  import process from "node:process";
3
- import { ConfigBase, createLogger, kernelError, parseToml, resolveConfigPath, resolveDataRoot, stringifyToml } from "@napuketto/kernel";
3
+ import { ConfigBase, createLogger, kernelError, parseLogConfig, parseToml, resolveConfigPath, resolveDataRoot, stringifyToml } from "@napuketto/kernel";
4
4
  import { Command } from "commander";
5
- import path, { dirname, extname, join } from "node:path";
5
+ import path, { dirname, extname, isAbsolute, join } from "node:path";
6
6
  import { createInterface } from "node:readline";
7
7
  import { fileURLToPath } from "node:url";
8
- import { checkInstanceLock, defaultStubDir, isNativeNoiseLine, launchSelfHost, resolveQqFiles } from "@napuketto/loader";
8
+ import { checkInstanceLock, defaultStubDir, isNativeNoiseLine, launchSelfHost, removeStopSentinel, resolveQqFiles, watchStopSentinel, writeStopSentinel } from "@napuketto/loader";
9
9
  import QRCode from "qrcode";
10
- import { spawn } from "node:child_process";
11
10
  import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
11
+ import { spawn } from "node:child_process";
12
12
  import { access, readFile, writeFile } from "node:fs/promises";
13
13
  //#region src/logger.ts
14
14
  /**
15
- * cli 进程级 logger(ADR-007 日志规范,2026-08-07 统一改造)。
16
- *
17
- * 复用 kernel `createLogger`(console pretty + redact,格式与 kernel 子进程完全一致:
18
- * `[时间] LEVEL (name/pid): 消息` + 缩进元数据)。只打 console 不写文件——文件日志由
19
- * kernel 装配(NapukettoCore.create → `<数据根>/logs/napuketto.log`)负责。
15
+ * cli 进程级 logger(ADR-007 日志规范,2026-08-07 统一改造;2026-10-25 日志可配置化)。
20
16
  *
21
- * 无全局单例(ADR-015 推论):cli 每进程一份,模块级单例即可;`base.name` 用 pino
22
- * 保留字段 name(logger name)标注来源——pino-pretty 自动渲染为 `(cli/pid)` 元数据头,
23
- * 不占额外属性行,文件/JSON 侧仍保留 name 字段可过滤。
17
+ * 复用 kernel `createLogger`;输出格式/级别/时间戳/颜色由 napuketto.toml [log] 段 +
18
+ * 环境变量决定(kernel parseLogConfig 单一解析源)。懒构造:commander preAction
19
+ * 钩子先 resolveCliLogConfig(dataDir)(banner 决策 + 缓存),首个日志调用时按
20
+ * 缓存配置构造;未初始化时兜底用缺省数据根解析(-d 生效前的极早期日志)。
24
21
  *
25
- * 级别:`NAPKETTO_LOG_LEVEL` 环境变量可覆盖(缺省 info,非法值忽略)。
22
+ * 环境变量(优先级高于 toml,见 kernel parseLogConfig):NAPUKETTO_LOG_LEVEL /
23
+ * NAPUKETTO_LOG_FORMAT / NAPUKETTO_NO_BANNER / NAPUKETTO_LOG_COLORIZE,
24
+ * 兼容旧拼写 NAPKETTO_LOG_LEVEL。
26
25
  */
27
- /** 合法级别集合(pino 标准级别)。 */
28
- const LOG_LEVELS = [
29
- "trace",
30
- "debug",
31
- "info",
32
- "warn",
33
- "error",
34
- "fatal",
35
- "silent"
36
- ];
37
- /** 解析日志级别(env 覆盖,非法/缺失回退 info)。 */
38
- function resolveLevel() {
39
- const raw = process.env["NAPKETTO_LOG_LEVEL"];
40
- if (raw !== void 0 && LOG_LEVELS.includes(raw)) return raw;
41
- return "info";
26
+ /** 已解析日志配置缓存(preAction 首次解析后固定;-d 后续变化不影响已构造 logger)。 */
27
+ let cachedConfig = null;
28
+ /** 解析配置时用的数据根([log.file] 相对路径展开基准)。 */
29
+ let cachedDataRoot = resolveDataRoot();
30
+ /** 同步解析日志配置(toml [log] 段 + env 覆盖)。损坏/缺失配置回退缺省(不阻塞启动:
31
+ * 配置的严格校验由 loadCliConfig 主路径负责报错)。 */
32
+ function resolveCliLogConfig(dataDir) {
33
+ if (cachedConfig !== null) return cachedConfig;
34
+ cachedDataRoot = resolveDataRoot(dataDir);
35
+ let section;
36
+ try {
37
+ const configPath = resolveConfigPath({ dataRoot: cachedDataRoot });
38
+ section = parseToml(readFileSync(configPath, "utf8"))["log"];
39
+ } catch {
40
+ section = void 0;
41
+ }
42
+ try {
43
+ cachedConfig = parseLogConfig(section, process.env);
44
+ } catch {
45
+ cachedConfig = parseLogConfig(void 0, process.env);
46
+ }
47
+ return cachedConfig;
42
48
  }
43
- /** cli 进程日志(name 渲染为 (cli/pid) 元数据头,文件 JSON 保留 name 字段可过滤)。 */
44
- const logger = createLogger({
45
- level: resolveLevel(),
46
- console: true,
47
- base: { service: "cli" }
48
- });
49
+ /**
50
+ * 顶层进程的最终控制台格式([log] format 显式优先,否则按 TTY 自动:
51
+ * 终端 pretty / 管道 single-line)。supervisor 的前缀转发决策同源。
52
+ */
53
+ function effectiveConsoleFormat(cfg) {
54
+ const isTty = process.stdout.isTTY === true;
55
+ return cfg.format ?? (isTty ? "pretty" : "single-line");
56
+ }
57
+ /**
58
+ * 顶层进程的有效控制台渲染决策(传播给子进程):
59
+ * 子进程 stdout 恒为 pipe(TTY 判定失真),由本进程按最终控制台形态决定
60
+ * format/colorize 并经 env 注入(NAPUKETTO_LOG_FORMAT/NAPUKETTO_LOG_COLORIZE)。
61
+ */
62
+ function effectiveConsoleEnv(cfg) {
63
+ const isTty = process.stdout.isTTY === true;
64
+ const format = effectiveConsoleFormat(cfg);
65
+ return {
66
+ NAPUKETTO_LOG_FORMAT: format,
67
+ NAPUKETTO_LOG_COLORIZE: cfg.colorize ?? (format === "pretty" && isTty) ? "1" : "0"
68
+ };
69
+ }
70
+ /** [log.file] 相对路径按数据根展开。 */
71
+ function resolveLogFilePath(path) {
72
+ return isAbsolute(path) ? path : join(cachedDataRoot, path);
73
+ }
74
+ /** cli logger 构造参数([log] 段折叠;file/fileLevel 成对生效,其余直传 undefined)。 */
75
+ function cliLoggerOptions(cfg) {
76
+ return {
77
+ level: cfg.levels.cli ?? cfg.level,
78
+ format: cfg.format,
79
+ timestamp: cfg.timestamp,
80
+ colorize: cfg.colorize,
81
+ file: cfg.file !== void 0 ? resolveLogFilePath(cfg.file.path) : void 0,
82
+ fileLevel: cfg.file?.level,
83
+ base: {
84
+ name: "cli",
85
+ service: "cli"
86
+ }
87
+ };
88
+ }
89
+ /** 懒构造 cli 进程日志(name 渲染为 (cli/pid) 元数据头,文件 JSON 保留 name 字段可过滤)。 */
90
+ var LazyCliLogger = class {
91
+ #inner = null;
92
+ #ensure() {
93
+ if (this.#inner === null) this.#inner = createLogger(cliLoggerOptions(resolveCliLogConfig()));
94
+ return this.#inner;
95
+ }
96
+ info(obj, msg) {
97
+ this.#ensure().info(obj, msg);
98
+ }
99
+ warn(obj, msg) {
100
+ this.#ensure().warn(obj, msg);
101
+ }
102
+ error(obj, msg) {
103
+ this.#ensure().error(obj, msg);
104
+ }
105
+ debug(obj, msg) {
106
+ this.#ensure().debug(obj, msg);
107
+ }
108
+ trace(obj, msg) {
109
+ this.#ensure().trace(obj, msg);
110
+ }
111
+ fatal(obj, msg) {
112
+ this.#ensure().fatal(obj, msg);
113
+ }
114
+ };
115
+ /** cli 进程日志(懒构造,配置见 resolveCliLogConfig)。 */
116
+ const logger = new LazyCliLogger();
49
117
  //#endregion
50
118
  //#region src/runtime-state.ts
51
119
  /**
@@ -183,6 +251,8 @@ async function packageEntry(pkg) {
183
251
  }
184
252
  /** QR 透出标记行前缀(loader bootstrap-core 非 IPC 模式输出,cli 解析终端渲染)。 */
185
253
  const QR_LINE_PREFIX = "NAPUTO_QR ";
254
+ /** 优雅停等待上限(毫秒):超时 kill 兜底(Windows 下为硬杀)。 */
255
+ const GRACEFUL_STOP_TIMEOUT_MS = 1e4;
186
256
  /**
187
257
  * 逐行转发子进程输出到父进程,过滤原生噪音。
188
258
  * readline 按 UTF-8 解码 pipe 字节流,再经 process.stdout(TTY 路径,
@@ -247,13 +317,13 @@ async function runSingleAccount(opts = {}) {
247
317
  const adapterEntry = await packageEntry("@napuketto/adapter");
248
318
  const networkEntry = await packageEntry("@napuketto/network");
249
319
  const stubDir = opts.stubDir ?? process.env["NAPUTO_STUB_DIR"] ?? defaultStubDir();
250
- logger.info({
320
+ logger.debug({
251
321
  qqVersion: qq.version,
252
322
  qqPath: qq.qqPath,
253
323
  source: qq.source
254
324
  }, "QQ 文件来源");
255
- logger.info({ dataDir: cfgDir }, "数据目录");
256
- logger.info("自建宿主引导(标准 node + stub QQNT.dll)");
325
+ logger.debug({ dataDir: cfgDir }, "数据目录");
326
+ logger.debug("自建宿主引导(标准 node + stub QQNT.dll)");
257
327
  const { child } = await launchSelfHost({
258
328
  qq,
259
329
  kernelEntry,
@@ -268,6 +338,10 @@ async function runSingleAccount(opts = {}) {
268
338
  "pipe",
269
339
  "pipe"
270
340
  ],
341
+ extraEnv: {
342
+ ...effectiveConsoleEnv(resolveCliLogConfig(opts.dataDir)),
343
+ NAPUTO_DATA_ROOT: dataRoot
344
+ },
271
345
  ...opts.qq !== void 0 ? { quickUin: opts.qq } : {},
272
346
  ...stubDir !== void 0 ? { stubDir } : {}
273
347
  });
@@ -281,9 +355,40 @@ async function runSingleAccount(opts = {}) {
281
355
  kind: "single-boot",
282
356
  status: "running"
283
357
  });
358
+ const account = opts.qq ?? "default";
359
+ let stopArmed = false;
360
+ const armGracefulStop = (reason) => {
361
+ if (stopArmed) return;
362
+ stopArmed = true;
363
+ logger.info({
364
+ account,
365
+ reason
366
+ }, "收到停止请求,等待自建宿主退出");
367
+ setTimeout(() => {
368
+ if (child.exitCode === null && !child.killed) {
369
+ logger.warn({ account }, "自建宿主未在限时内退出,强制结束");
370
+ child.kill();
371
+ }
372
+ }, GRACEFUL_STOP_TIMEOUT_MS).unref?.();
373
+ };
374
+ watchStopSentinel({
375
+ dataRoot,
376
+ account,
377
+ onTrigger: () => armGracefulStop("停止哨兵")
378
+ });
379
+ const onSignal = (signal) => {
380
+ writeStopSentinel(dataRoot, account);
381
+ armGracefulStop(`信号 ${signal}`);
382
+ };
383
+ process.on("SIGINT", () => onSignal("SIGINT"));
384
+ process.on("SIGTERM", () => onSignal("SIGTERM"));
385
+ process.on("SIGBREAK", () => onSignal("SIGBREAK"));
284
386
  await new Promise((resolve) => {
285
387
  child.on("exit", (code) => {
286
- logger.info({ code }, "自建宿主进程退出");
388
+ logger.info({
389
+ code,
390
+ graceful: stopArmed
391
+ }, "自建宿主进程退出");
287
392
  writeAccountRuntime(dataRoot, {
288
393
  uin: opts.qq ?? "default",
289
394
  pid: process.pid,
@@ -339,6 +444,31 @@ autoRestart = true
339
444
  # 崩溃后自动重启的延迟(毫秒)
340
445
  restartDelayMs = 2000
341
446
 
447
+ # ------------------------------------------------------------
448
+ # 日志([log],可选——不配置即用缺省:终端 pretty 多行 / 管道 single-line)
449
+ # ------------------------------------------------------------
450
+ # 嵌入式宿主(把 napuketto 打进服务端等场景)推荐:
451
+ # [log]
452
+ # format = "single-line" # 或 "json"(NDJSON,宿主按 level/module 精确路由)
453
+ # level = "warn" # 控制台只留警告以上,细节进文件(配 [log.file])
454
+ # banner = false
455
+ #
456
+ # [log]
457
+ # format = "pretty" # pretty | single-line | json(缺省按 TTY 自动)
458
+ # level = "info" # trace|debug|info|warn|error|fatal(控制台级别)
459
+ # banner = true # 启动字符画
460
+ # timestamp = "local" # local | iso8601 | elapsed
461
+ # unboundReceive = true # false = 未绑定频道的接收行不打(kurobot 握手后生效)
462
+ # [log.levels] # 按模块覆盖级别:cli / kernel / loader / ob11
463
+ # # loader = "debug" # 例:终端上看接收行(默认已降 debug)
464
+ # # ob11 = "debug" # 例:协议校准数据(含原始 hex)
465
+ # [log.file] # 独立文件 sink(可选;级别可低于控制台)
466
+ # # path = "logs/napuketto-console.log"
467
+ # # level = "debug"
468
+ #
469
+ # 环境变量覆盖(优先级高于本文件):NAPUKETTO_LOG_LEVEL / NAPUKETTO_LOG_FORMAT /
470
+ # NAPUKETTO_NO_BANNER(兼容旧拼写 NAPKETTO_LOG_LEVEL)。
471
+
342
472
  # ------------------------------------------------------------
343
473
  # 账号(至少一个,qq 必填)
344
474
  # ------------------------------------------------------------
@@ -506,15 +636,32 @@ function parseAccount(item) {
506
636
  if (kurobot !== void 0) out.kurobot = kurobot;
507
637
  return out;
508
638
  }
639
+ /**
640
+ * [log] 段落库净化:format 为 undefined(= 按 TTY 自动)时剔除该键,
641
+ * 避免 toml 回写(config list/apply 的 stringifyToml)遇到 undefined 值。
642
+ */
643
+ function stripUndefinedFormat(log) {
644
+ const { format, ...rest } = log;
645
+ return format === void 0 ? rest : {
646
+ ...rest,
647
+ format
648
+ };
649
+ }
509
650
  /** 手写校验器:主配置 parse(不引入 zod)。 */
510
651
  function parseCliConfig(input) {
511
652
  if (typeof input !== "object" || input === null) throw kernelError("主配置必须是对象", "INVALID_PARAM");
512
653
  const raw = input;
654
+ const dataDir = parseDataDir(raw);
655
+ const autoRestart = parseAutoRestart(raw);
656
+ const restartDelayMs = parseRestartDelayMs(raw);
657
+ const accounts = parseAccounts(raw);
658
+ const log = raw["log"] !== void 0 ? stripUndefinedFormat(parseLogConfig(raw["log"])) : void 0;
513
659
  return {
514
- dataDir: parseDataDir(raw),
515
- autoRestart: parseAutoRestart(raw),
516
- restartDelayMs: parseRestartDelayMs(raw),
517
- accounts: parseAccounts(raw)
660
+ dataDir,
661
+ autoRestart,
662
+ restartDelayMs,
663
+ accounts,
664
+ ...log !== void 0 ? { log } : {}
518
665
  };
519
666
  }
520
667
  //#endregion
@@ -653,14 +800,27 @@ async function cmdConfigApply(file, opts) {
653
800
  * ops-commands.ts:status / stop / restart 运维命令实现(2026-09-08 T8)。
654
801
  *
655
802
  * 数据源:runtime-state 状态文件(supervisor/单账号 boot 写入)+ liveness 检测。
656
- * stop/restart 语义:
657
- * - stop -q <uin>:树杀该账号 boot 进程(连带 self-host)。⚠️ supervisor 在跑
658
- * 且 autoRestart 时账号会被自动拉起——提示用户先 `napuketto stop`(停 supervisor)。
659
- * - stop(无 -q):树杀 supervisor(子进程随 supervisor 的信号转发退出;若
660
- * 残留按各账号 runtime pid 逐个树杀兜底)。
803
+ * stop/restart 语义(2026-10-25 优雅化):
804
+ * - stop -q <uin>:写单账号停止哨兵 → 等该账号进程链优雅退出 → 超时树杀兜底。
805
+ * ⚠️ supervisor 在跑且 autoRestart 时账号会被自动拉起——提示用户先 `napuketto stop`。
806
+ * - stop(无 -q):写 all 哨兵(supervisor + 全部账号优雅退出)→ 超时树杀兜底。
661
807
  * - restart:stop 同目标 + detached 重新拉起(-q → 单账号 boot;无 -q →
662
808
  * supervisor autoStart)。
663
809
  */
810
+ /** 优雅停等待上限(毫秒):哨兵后子进程链自行退出,超时树杀兜底。 */
811
+ const GRACEFUL_STOP_WAIT_MS = 8e3;
812
+ /** 存活轮询间隔(毫秒)。 */
813
+ const LIVENESS_POLL_MS = 200;
814
+ /** 等待 pid 列表全部退出(返回超时仍未退的 pid)。 */
815
+ async function waitForPidsExit(pids) {
816
+ const deadline = Date.now() + GRACEFUL_STOP_WAIT_MS;
817
+ let alive = pids.filter((pid) => pid > 0 && isPidAlive(pid));
818
+ while (alive.length > 0 && Date.now() < deadline) {
819
+ await new Promise((resolve) => setTimeout(resolve, LIVENESS_POLL_MS));
820
+ alive = alive.filter((pid) => isPidAlive(pid));
821
+ }
822
+ return alive;
823
+ }
664
824
  /** status:聚合输出各账号运行状态(含 supervisor 自身)。 */
665
825
  function cmdStatus(dataRoot, opts) {
666
826
  const supervisor = readSupervisorRuntime(dataRoot);
@@ -694,7 +854,31 @@ function stopAccount(runtime) {
694
854
  pid: runtime.pid
695
855
  }, "已发送停止(树杀)");
696
856
  }
697
- /** stop:-q 停单账号;无 -q 停 supervisor + 全部账号兜底。 */
857
+ /** stop:-q 停单账号;无 -q 停 supervisor + 全部账号兜底。
858
+ *
859
+ * 2026-10-25 优雅化:先写停止哨兵(目标账号或 all)→ 等进程链自行有序退出
860
+ * (退出码 0)→ 超时残留树杀兜底 → 清理哨兵。单账号哨兵只命中对应 boot/self-host,
861
+ * 不干扰同数据根的其他账号。
862
+ */
863
+ /** stop -q:写单账号哨兵 → 等优雅退出 → 超时树杀兜底 → 清哨兵。 */
864
+ async function stopSingleAccount(dataRoot, target) {
865
+ logger.info({
866
+ uin: target.uin,
867
+ pid: target.pid
868
+ }, "已发送停止请求(哨兵),等待退出");
869
+ writeStopSentinel(dataRoot, target.uin);
870
+ const stragglers = await waitForPidsExit([target.pid]);
871
+ for (const pid of stragglers) {
872
+ logger.warn({
873
+ uin: target.uin,
874
+ pid
875
+ }, "限时内未退出,树杀兜底");
876
+ killProcessTree(pid);
877
+ }
878
+ removeStopSentinel(dataRoot);
879
+ const supervisor = readSupervisorRuntime(dataRoot);
880
+ if (supervisor !== null) logger.warn({ supervisorPid: supervisor.pid }, "supervisor 仍在运行且 autoRestart 开启时会自动拉起该账号;如需彻底停止请执行 napuketto stop(停 supervisor)");
881
+ }
698
882
  async function cmdStop(dataRoot, opts) {
699
883
  const runtimes = listAccountRuntimes(dataRoot);
700
884
  if (opts.qq !== void 0) {
@@ -704,17 +888,23 @@ async function cmdStop(dataRoot, opts) {
704
888
  process.exitCode = 1;
705
889
  return;
706
890
  }
707
- stopAccount(target);
708
- const supervisor = readSupervisorRuntime(dataRoot);
709
- if (supervisor !== null) logger.warn({ supervisorPid: supervisor.pid }, "supervisor 仍在运行且 autoRestart 开启时会自动拉起该账号;如需彻底停止请执行 napuketto stop(停 supervisor)");
891
+ await stopSingleAccount(dataRoot, target);
710
892
  return;
711
893
  }
712
894
  const supervisor = readSupervisorRuntime(dataRoot);
895
+ const supervisorPid = supervisor !== null && isPidAlive(supervisor.pid) ? supervisor.pid : 0;
713
896
  if (supervisor !== null) {
714
- killProcessTree(supervisor.pid);
715
- logger.info({ pid: supervisor.pid }, "已停止 supervisor(树杀,子进程随信号转发退出)");
897
+ logger.info({ pid: supervisor.pid }, "已发送停止请求(哨兵),等待 supervisor 退出");
898
+ writeStopSentinel(dataRoot, "all");
899
+ const alive = await waitForPidsExit([supervisorPid]);
900
+ for (const pid of alive) {
901
+ logger.warn({ pid }, "supervisor 限时内未退出,树杀兜底");
902
+ killProcessTree(pid);
903
+ }
716
904
  } else logger.warn("supervisor 未运行(无状态文件)");
717
- for (const runtime of runtimes) stopAccount(runtime);
905
+ const stragglers = await waitForPidsExit(runtimes.map((r) => r.pid));
906
+ for (const runtime of runtimes) if (stragglers.includes(runtime.pid)) stopAccount(runtime);
907
+ removeStopSentinel(dataRoot);
718
908
  }
719
909
  /** restart:stop 同目标 + detached 拉起。 */
720
910
  async function cmdRestart(dataRoot, opts) {
@@ -754,8 +944,8 @@ async function cmdRestart(dataRoot, opts) {
754
944
  const DEFAULT_RESTART_DELAY_MS = 2e3;
755
945
  /** 退出轮询间隔(毫秒)。 */
756
946
  const EXIT_POLL_MS = 100;
757
- /** 子进程强制退出时限(毫秒,超时未退则父进程强制退出)。 */
758
- const FORCE_EXIT_MS = 5e3;
947
+ /** 子进程优雅退出时限(毫秒):哨兵/信号后子进程自行停止,超时强杀兜底。 */
948
+ const FORCE_EXIT_MS = 8e3;
759
949
  /** 解析账号列表(显式 -q 优先,否则主配置 accounts 过滤启用项)。 */
760
950
  function resolveAccounts(opts, config) {
761
951
  if (opts.qqs !== void 0 && opts.qqs.length > 0) return opts.qqs.map((qq) => ({ qq }));
@@ -766,18 +956,22 @@ function resolveRestartDelay(opts, config) {
766
956
  if (opts.qqs === void 0) return config.restartDelayMs;
767
957
  return DEFAULT_RESTART_DELAY_MS;
768
958
  }
769
- /** 子进程输出前缀转发(stdout/stderr 逐行 `[uin] ` 前缀;pino 行结构化保留在行内)。 */
770
- function forwardPrefixed(stream, uin) {
959
+ /**
960
+ * 子进程输出前缀转发(stdout/stderr 逐行):pretty 模式加 `[uin] ` 前缀
961
+ * (pino 行结构化保留在行内);single-line/json 模式记录自身已带结构化
962
+ * account 字段(json 加前缀会破坏逐行 JSON.parse),原样透传。
963
+ */
964
+ function forwardPrefixed(stream, uin, prefixEnabled) {
771
965
  if (stream === null) return;
772
966
  createInterface({ input: stream }).on("line", (line) => {
773
- process.stdout.write(`[${uin}] ${line}\n`);
967
+ process.stdout.write(prefixEnabled ? `[${uin}] ${line}\n` : `${line}\n`);
774
968
  });
775
969
  }
776
970
  /** 启动一个账号子进程(exit 后按 autoRestart 守护重启)。 */
777
971
  function startAccount(ctx, acct) {
778
972
  if (ctx.isStopping()) return;
779
973
  const restartCount = ctx.restartCounts.get(acct.qq) ?? 0;
780
- logger.info({
974
+ logger.debug({
781
975
  qq: acct.qq,
782
976
  pid: process.pid,
783
977
  restartCount
@@ -790,13 +984,20 @@ function startAccount(ctx, acct) {
790
984
  ctx.dataRoot
791
985
  ];
792
986
  if (ctx.stubDir !== void 0) args.push("--stub-dir", ctx.stubDir);
793
- const child = spawn(process.execPath, args, { stdio: [
794
- "inherit",
795
- "pipe",
796
- "pipe"
797
- ] });
798
- forwardPrefixed(child.stdout, acct.qq);
799
- forwardPrefixed(child.stderr, acct.qq);
987
+ const child = spawn(process.execPath, args, {
988
+ stdio: [
989
+ "inherit",
990
+ "pipe",
991
+ "pipe"
992
+ ],
993
+ env: {
994
+ ...process.env,
995
+ NAPUKETTO_NO_BANNER: "1",
996
+ ...ctx.childEnv
997
+ }
998
+ });
999
+ forwardPrefixed(child.stdout, acct.qq, ctx.prefixEnabled);
1000
+ forwardPrefixed(child.stderr, acct.qq, ctx.prefixEnabled);
800
1001
  ctx.children.set(acct.qq, child);
801
1002
  writeAccountRuntime(ctx.dataRoot, {
802
1003
  uin: acct.qq,
@@ -837,14 +1038,21 @@ function startAccount(ctx, acct) {
837
1038
  ctx.children.delete(acct.qq);
838
1039
  });
839
1040
  }
840
- /** 停止全部子进程,全部退出后父进程退出(2026-08-07:超时强杀兜底)。 */
1041
+ /** 停止全部子进程,全部退出后父进程退出(2026-10-25 优雅化)。
1042
+ *
1043
+ * 优雅路径:写停止哨兵(all)→ 子进程链(boot / self-host)自行有序退出 →
1044
+ * 全部退出后 exit 0(退出码契约:0 = 请求式正常退出)。超时(FORCE_EXIT_MS)
1045
+ * 未退的子进程 kill 兜底(Windows 下为硬杀)后仍 exit 0——关停请求本身是正常路径,
1046
+ * 与崩溃(非 0)区分是契约的核心目标。
1047
+ */
841
1048
  function stopAll(ctx) {
842
1049
  logger.info("收到退出信号,停止全部账号");
843
- for (const child of ctx.children.values()) child.kill();
1050
+ writeStopSentinel(ctx.dataRoot, "all");
844
1051
  const forceTimer = setTimeout(() => {
845
- logger.warn("子进程未在限时内退出,强制退出");
846
- process.exit(1);
1052
+ for (const child of ctx.children.values()) child.kill();
1053
+ process.exit(0);
847
1054
  }, FORCE_EXIT_MS);
1055
+ forceTimer.unref?.();
848
1056
  const wait = setInterval(() => {
849
1057
  if (ctx.children.size === 0) {
850
1058
  clearInterval(wait);
@@ -867,6 +1075,7 @@ async function runSupervisor(opts = {}) {
867
1075
  const children = /* @__PURE__ */ new Map();
868
1076
  const restartCounts = /* @__PURE__ */ new Map();
869
1077
  let stopping = false;
1078
+ const logCfg = resolveCliLogConfig(opts.dataDir);
870
1079
  const ctx = {
871
1080
  dataRoot,
872
1081
  entry,
@@ -875,6 +1084,8 @@ async function runSupervisor(opts = {}) {
875
1084
  ...opts.stubDir !== void 0 ? { stubDir: opts.stubDir } : {},
876
1085
  children,
877
1086
  restartCounts,
1087
+ childEnv: effectiveConsoleEnv(logCfg),
1088
+ prefixEnabled: effectiveConsoleFormat(logCfg) === "pretty",
878
1089
  isStopping: () => stopping
879
1090
  };
880
1091
  writeSupervisorRuntime(dataRoot, {
@@ -890,6 +1101,11 @@ async function runSupervisor(opts = {}) {
890
1101
  };
891
1102
  process.on("SIGINT", shutdown);
892
1103
  process.on("SIGTERM", shutdown);
1104
+ process.on("SIGBREAK", shutdown);
1105
+ watchStopSentinel({
1106
+ dataRoot,
1107
+ onTrigger: shutdown
1108
+ });
893
1109
  await new Promise(() => {});
894
1110
  }
895
1111
  //#endregion
@@ -1047,10 +1263,12 @@ function registerMainAction(program) {
1047
1263
  }
1048
1264
  /** 入口:注册全部子命令与主命令后解析。 */
1049
1265
  function main() {
1050
- process.stdout.write(`${BANNER}\n`);
1051
1266
  const program = new Command();
1052
1267
  program.name("napuketto").version("0.0.1").description("NapukettoQQ 机器人框架(OneBot 11 兼容)");
1053
1268
  program.option("-q, --qq <uin>", "QQ 号(可重复;多个时 supervisor 拉起多账号子进程)", collectQq, []).option("-d, --data-dir <dir>", "数据根目录(缺省 <项目根>/.napuketto)").option("--qq-path <path>", "QQ 安装路径(联调覆盖)").option("--stub-dir <dir>", "stub QQNT.dll 目录(自建宿主 PATH 前置,缺省 loader/native/stub-test-env,Git Submodule)");
1269
+ program.hook("preAction", () => {
1270
+ if (resolveCliLogConfig(rootDataDir(program).dataDir).banner) process.stdout.write(`${BANNER}\n`);
1271
+ });
1054
1272
  registerConfigCommands(program);
1055
1273
  registerSupervisorCommand(program);
1056
1274
  registerOpsCommands(program);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["DEFAULT_RESTART_DELAY_MS"],"sources":["../src/logger.ts","../src/runtime-state.ts","../src/boot.ts","../src/config-template.ts","../src/config-parse.ts","../src/config-cmds.ts","../src/ops-commands.ts","../src/supervisor.ts","../src/index.ts"],"sourcesContent":["/**\n * cli 进程级 logger(ADR-007 日志规范,2026-08-07 统一改造)。\n *\n * 复用 kernel `createLogger`(console pretty + redact,格式与 kernel 子进程完全一致:\n * `[时间] LEVEL (name/pid): 消息` + 缩进元数据)。只打 console 不写文件——文件日志由\n * kernel 装配(NapukettoCore.create → `<数据根>/logs/napuketto.log`)负责。\n *\n * 无全局单例(ADR-015 推论):cli 每进程一份,模块级单例即可;`base.name` 用 pino\n * 保留字段 name(logger name)标注来源——pino-pretty 自动渲染为 `(cli/pid)` 元数据头,\n * 不占额外属性行,文件/JSON 侧仍保留 name 字段可过滤。\n *\n * 级别:`NAPKETTO_LOG_LEVEL` 环境变量可覆盖(缺省 info,非法值忽略)。\n */\nimport process from \"node:process\";\nimport { createLogger, type LogLevel } from \"@napuketto/kernel\";\n\n/** 合法级别集合(pino 标准级别)。 */\nconst LOG_LEVELS: readonly LogLevel[] = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\",\n \"fatal\",\n \"silent\",\n];\n\n/** 解析日志级别(env 覆盖,非法/缺失回退 info)。 */\nfunction resolveLevel(): LogLevel {\n const raw = process.env[\"NAPKETTO_LOG_LEVEL\"];\n if (raw !== undefined && (LOG_LEVELS as readonly string[]).includes(raw)) {\n return raw as LogLevel;\n }\n return \"info\";\n}\n\n/**\n * 窄接口注解(避免 TS2883:createLogger 返回 pino.Logger,cli 不直接依赖 pino,\n * declaration emit 无法命名该类型;pino.Logger 结构兼容本接口)。\n */\nexport interface CliLogger {\n info(obj: unknown, msg?: string): void;\n warn(obj: unknown, msg?: string): void;\n error(obj: unknown, msg?: string): void;\n debug(obj: unknown, msg?: string): void;\n trace(obj: unknown, msg?: string): void;\n fatal(obj: unknown, msg?: string): void;\n}\n\n/** cli 进程日志(name 渲染为 (cli/pid) 元数据头,文件 JSON 保留 name 字段可过滤)。 */\nexport const logger: CliLogger = createLogger({\n level: resolveLevel(),\n console: true,\n base: { service: \"cli\" },\n});\n","/**\n * runtime-state.ts:账号运行时状态文件 + 进程控制原语(2026-09-08 T8)。\n *\n * 跨进程通信选型(决策记录):现有 IPC 为 stdio JSON 行协议(NAPUTO_IPC=1,\n * koishi 插件专用),supervisor 子进程不在此模式;引入独立通道(socket/pipe)\n * 复杂度高。采用 **PID 状态文件**:supervisor/单账号 boot 启动时把进程信息写\n * `<数据根>/<uin>/runtime.json`,supervisor 自身写 `<数据根>/supervisor.json`;\n * `napuketto status/stop/restart` 读文件 + liveness 检测 + 树杀(win32\n * taskkill /T /F,杀 boot 进程树连带 self-host 孙进程)+ detached 重启。\n */\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\n\n/** 账号运行时状态(runtime.json)。 */\nexport interface AccountRuntime {\n /** 账号 QQ 号(= 数据目录名)。 */\n uin: string;\n /** 宿主引导进程 pid(boot 进程;树杀连带 self-host 孙进程)。 */\n pid: number;\n /** self-host 子进程 pid(诊断用,可能缺省)。 */\n selfHostPid?: number;\n /** 启动时间(ISO 字符串)。 */\n startedAt: string;\n /** 运行形态。 */\n kind: \"supervisor-child\" | \"single-boot\";\n /** 状态(写入时快照;读取方用 liveness 修正显示)。 */\n status: \"running\" | \"exited\";\n /** 退出码(status=exited 时)。 */\n exitCode?: number | null;\n /** supervisor 守护重启次数。 */\n restartCount?: number;\n}\n\n/** supervisor 运行时状态(supervisor.json)。 */\nexport interface SupervisorRuntime {\n pid: number;\n startedAt: string;\n /** 编排的账号列表。 */\n accounts: string[];\n}\n\n/** runtime.json 文件名。 */\nconst RUNTIME_FILE = \"runtime.json\";\n/** supervisor.json 文件名。 */\nconst SUPERVISOR_FILE = \"supervisor.json\";\n\n/** 账号状态文件路径。 */\nexport function runtimeStatePath(dataRoot: string, uin: string): string {\n return join(dataRoot, uin, RUNTIME_FILE);\n}\n\n/** supervisor 状态文件路径。 */\nexport function supervisorStatePath(dataRoot: string): string {\n return join(dataRoot, SUPERVISOR_FILE);\n}\n\n/** 写账号运行时状态(原子覆盖)。 */\nexport function writeAccountRuntime(dataRoot: string, state: AccountRuntime): void {\n const path = runtimeStatePath(dataRoot, state.uin);\n mkdirSync(join(path, \"..\"), { recursive: true });\n writeFileSync(path, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n}\n\n/** 读账号运行时状态(缺失/损坏返回 null)。 */\nexport function readAccountRuntime(dataRoot: string, uin: string): AccountRuntime | null {\n return readJson(runtimeStatePath(dataRoot, uin)) as AccountRuntime | null;\n}\n\n/** 写 supervisor 运行时状态。 */\nexport function writeSupervisorRuntime(dataRoot: string, state: SupervisorRuntime): void {\n writeFileSync(supervisorStatePath(dataRoot), `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n}\n\n/** 读 supervisor 运行时状态(缺失返回 null)。 */\nexport function readSupervisorRuntime(dataRoot: string): SupervisorRuntime | null {\n return readJson(supervisorStatePath(dataRoot)) as SupervisorRuntime | null;\n}\n\n/** 读 JSON 文件(缺失/解析失败返回 null)。 */\nfunction readJson(path: string): unknown {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n } catch {\n return null;\n }\n}\n\n/** 列出数据根下全部账号运行时状态(扫一级目录 runtime.json)。 */\nexport function listAccountRuntimes(dataRoot: string): AccountRuntime[] {\n let entries: string[] = [];\n try {\n entries = readdirSync(dataRoot, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n return [];\n }\n const out: AccountRuntime[] = [];\n for (const uin of entries) {\n const state = readAccountRuntime(dataRoot, uin);\n if (state !== null) {\n out.push(state);\n }\n }\n return out;\n}\n\n/** pid 存活检测(signal 0;EPERM 视为存活——进程存在但无权限)。 */\nexport function isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) {\n return false;\n }\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** 树杀命令构造(win32:taskkill /T /F 连孙进程;posix:kill 信号)。 */\nexport function killProcessTree(pid: number): void {\n if (process.platform === \"win32\") {\n spawn(\"taskkill\", [\"/PID\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\" });\n return;\n }\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // 已退出:无需处理\n }\n}\n\n/** detached 拉起 node 进程(restart 用;立即返回不等待)。 */\nexport function spawnDetached(args: string[], cwd?: string): ChildProcess {\n const child = spawn(process.execPath, args, {\n detached: true,\n stdio: \"ignore\",\n ...(cwd !== undefined ? { cwd } : {}),\n });\n child.unref();\n return child;\n}\n\n/** 状态聚合视图(status 命令输出)。 */\nexport interface AccountStatusRow {\n uin: string;\n pid: number;\n /** 文件状态 + liveness 修正后的实际状态。 */\n alive: boolean;\n kind: AccountRuntime[\"kind\"];\n startedAt: string;\n restartCount: number;\n exitCode: number | null | undefined;\n}\n\n/** 聚合各账号状态(liveness 修正:文件 running 但 pid 已死 → not running)。 */\nexport function aggregateStatus(\n runtimes: AccountRuntime[],\n aliveFn: (pid: number) => boolean = isPidAlive,\n): AccountStatusRow[] {\n return runtimes.map((r) => ({\n uin: r.uin,\n pid: r.pid,\n alive: r.status === \"running\" ? aliveFn(r.pid) : false,\n kind: r.kind,\n startedAt: r.startedAt,\n restartCount: r.restartCount ?? 0,\n exitCode: r.exitCode,\n }));\n}\n\n/** 数据根目录存在性(诊断输出用)。 */\nexport function dataRootExists(dataRoot: string): boolean {\n return existsSync(dataRoot);\n}\n","/**\n * cli boot:单账号启动序列(2026-08-07 用户拍板:只保留自建宿主)\n *\n * resolveQqFiles(多级来源定位 QQ 原生文件)→ 解析各包 dist 入口 → launchSelfHost\n * (标准 node + stub QQNT.dll 直接 dlopen,不拉起 QQ / 不注入)→ 常驻。\n *\n * QQ 原生文件来源(本机未装 QQ 也能跑,2026-08-16 接入):\n * L0 NAPUTO_QQ_FILES 显式文件根 → L1 本机 QQ 安装(--qq-path / NAPUTO_QQ_PATH /\n * 注册表 / 常见路径)→ L2 数据根缓存 <数据根>/qq-files/<版本> → 全部缺失时\n * 自动下载官方安装包(sha256 校验 + 7z 解包 + 提取缓存,幂等)。\n * cached 来源下 qq.qqPath 为语义占位(缓存目录无 QQ.exe)——launchSelfHost 只消费\n * qq.wrapperPath / qq.version(NAPUTO_QQ_PATH 仅注入、self-host 未读取),占位安全。\n *\n * 不写业务逻辑:kernel 装配 + 登录 + 协议装配全部在 self-host.cjs → boot-bootstrap.js 完成。\n * 路线 B(拉起 QQ + 注入)已淘汰(launchQqWithLoader 已移除,cli 只走自建宿主)。\n */\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { createInterface } from \"node:readline\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveConfigPath, resolveDataRoot } from \"@napuketto/kernel\";\nimport {\n checkInstanceLock,\n defaultStubDir,\n isNativeNoiseLine,\n launchSelfHost,\n type QqInstallInfo,\n resolveQqFiles,\n} from \"@napuketto/loader\";\nimport QRCode from \"qrcode\";\nimport { logger } from \"./logger.js\";\nimport { writeAccountRuntime } from \"./runtime-state.js\";\n\n/** 单账号启动选项。 */\nexport interface BootOptions {\n /** QQ 号(数据目录账号隔离,ADR-016)。 */\n qq?: string;\n /** 数据根目录(缺省环境变量/用户目录)。 */\n dataDir?: string;\n /** 覆盖 QQ 安装路径(联调)。 */\n qqPath?: string;\n /** stub QQNT.dll 目录(缺省 loader 包内闭源 submodule native/stub-test-env)。 */\n stubDir?: string;\n}\n\n/** 解析 workspace 包的 dist 入口(ESM 解析:包是 ESM-only,exports 无 require 条件)。 */\nasync function packageEntry(pkg: string): Promise<string> {\n const url = await import.meta.resolve(pkg);\n return fileURLToPath(url);\n}\n\n/** QR 透出标记行前缀(loader bootstrap-core 非 IPC 模式输出,cli 解析终端渲染)。 */\nconst QR_LINE_PREFIX = \"NAPUTO_QR \";\n\n/**\n * 逐行转发子进程输出到父进程,过滤原生噪音。\n * readline 按 UTF-8 解码 pipe 字节流,再经 process.stdout(TTY 路径,\n * WriteConsoleW UTF-16)输出——顺带修复 pino 中文在 cmd.exe/管道 936 转码\n * 链路下的乱码(原生 printf 字节流无法从 JS 侧改编码,转 pipe 后统一解码)。\n *\n * NAPUTO_QR 标记行(loader bootstrap-core 输出,QR 登录二维码数据)不转发,\n * 解析后用 qrcode 包渲染终端二维码;png 落盘与 URL 提示由 kernel 日志完成。\n * 噪音判定走 loader 共享单一来源(isNativeNoiseLine,与 koishi IPC 路径同源)。\n */\nfunction forwardFiltered(input: NodeJS.ReadableStream, out: NodeJS.WritableStream): void {\n const lines = createInterface({ input });\n lines.on(\"line\", (line) => {\n if (line.startsWith(QR_LINE_PREFIX)) {\n renderTerminalQr(line.slice(QR_LINE_PREFIX.length));\n return;\n }\n if (isNativeNoiseLine(line)) {\n return;\n }\n out.write(`${line}\\n`);\n });\n}\n\n/**\n * 渲染终端二维码(qrcode 包 terminal 模式,half-block 字符)。\n * 解析失败/无 url 时静默丢弃——标记行是内部协议,坏行不影响登录。\n */\nfunction renderTerminalQr(raw: string): void {\n try {\n const data = JSON.parse(raw) as { qrcodeUrl?: string };\n const { qrcodeUrl } = data;\n if (qrcodeUrl === undefined || qrcodeUrl === \"\") {\n return;\n }\n void QRCode.toString(qrcodeUrl, { type: \"terminal\", small: true })\n .then((str) => {\n process.stdout.write(`${str}\\n`);\n })\n .catch(() => {\n // 渲染失败静默(不影响登录,kernel 日志的 URL/png 路径仍可用)\n });\n } catch {\n // 坏行静默丢弃\n }\n}\n\n/** 启动单个账号(自建宿主 + 常驻)。 */\nexport async function runSingleAccount(opts: BootOptions = {}): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n // QQ 原生文件多级来源 + 自动下载(本机未装 QQ 时走下载管线;下载失败给可操作提示)\n let qq: QqInstallInfo;\n try {\n qq = await resolveQqFiles({\n dataRoot,\n ...(opts.qqPath !== undefined ? { qqPath: opts.qqPath } : {}),\n });\n } catch (err) {\n logger.error(\n { err },\n `定位/下载 QQ 原生文件失败:${err instanceof Error ? err.message : String(err)}\\n` +\n \"(本机未装 QQ 时自动下载官方安装包;若下载失败请检查网络,或手动安装 QQ、\" +\n \"设置 NAPUTO_QQ_PATH 指定已有安装、NAPUTO_QQ_FILES 指定文件根、\" +\n \"NAPUTO_QQ_URL 覆盖下载地址)\",\n );\n process.exitCode = 1;\n return;\n }\n const cfgDir = path.join(dataRoot, opts.qq ?? \"default\");\n\n // 单实例锁预检(2026-08-07 根治「多实例抢数据目录锁挂起」):同一账号数据\n // 目录只允许一个实例(QQ 原生层 MMKV/登录单例有锁,第二个实例抢不到锁会卡死)。\n // 占用 → 快速失败并提示占用 PID;残留(PID 已死 / PID 被复用,2026-09-10\n // cmdline 校验根治)→ self-host 会自动接管。真正的锁由 self-host 子进程\n // 获取/释放(它才是数据目录的实际持有者),cli 这里只做 spawn 前预检,\n // 给用户友好提示(supervisor 等批量拉起同用)。\n const { occupied, pid: holderPid } = checkInstanceLock(cfgDir);\n if (occupied) {\n logger.error(\n { pid: holderPid, dataDir: cfgDir },\n \"数据目录已被其他实例占用,拒绝启动(同一账号数据目录仅允许一个实例;\" +\n \"占用校验已含 pid 复用检测,如仍误报请删除该目录下的 instance.lock)\",\n );\n process.exitCode = 1;\n return;\n }\n\n const kernelEntry = await packageEntry(\"@napuketto/kernel\");\n const adapterEntry = await packageEntry(\"@napuketto/adapter\");\n const networkEntry = await packageEntry(\"@napuketto/network\");\n\n const stubDir = opts.stubDir ?? process.env[\"NAPUTO_STUB_DIR\"] ?? defaultStubDir();\n\n // 启动信息走结构化日志(时间戳 + 级别 + pid + 元数据,与 kernel 子进程格式一致)\n logger.info({ qqVersion: qq.version, qqPath: qq.qqPath, source: qq.source }, \"QQ 文件来源\");\n logger.info({ dataDir: cfgDir }, \"数据目录\");\n logger.info(\"自建宿主引导(标准 node + stub QQNT.dll)\");\n\n // 唯一启动路径:自建宿主(2026-08-07 用户拍板,路线 B 淘汰)\n // stdio 接管 stdout/stderr:过滤 MMKV / 符号查找失败等原生噪音,其余转发\n // configPath:全局配置文件(项目根 napuketto.toml),注入 NAPKETTO_CONFIG 供装配链读取\n // ⚠️ P2(2026-08-12):launchSelfHost 变 async(linux 场景需下载 win-node)\n const { child } = await launchSelfHost({\n qq,\n kernelEntry,\n adapterEntry,\n networkEntry,\n cfgDir,\n // 子进程 cwd 指向数据根:QQ 原生层 fallback 落盘(guild1.db 等)\n // 落在专门的数据目录,不污染项目根(实测 08-07)。\n cwd: dataRoot,\n configPath: resolveConfigPath({ dataRoot }),\n selfHost: true,\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n ...(opts.qq !== undefined ? { quickUin: opts.qq } : {}),\n ...(stubDir !== undefined ? { stubDir } : {}),\n });\n\n if (child.stdout !== null) {\n forwardFiltered(child.stdout, process.stdout);\n }\n if (child.stderr !== null) {\n forwardFiltered(child.stderr, process.stderr);\n }\n\n // 单账号运行时状态落盘(napuketto status/stop 消费,2026-09-08 T8;\n // pid 记 boot 进程——树杀连带 self-host 孙进程)\n writeAccountRuntime(dataRoot, {\n uin: opts.qq ?? \"default\",\n pid: process.pid,\n ...(child.pid !== undefined ? { selfHostPid: child.pid } : {}),\n startedAt: new Date().toISOString(),\n kind: \"single-boot\",\n status: \"running\",\n });\n\n // 常驻:等待自建宿主进程退出\n await new Promise<void>((resolve) => {\n child.on(\"exit\", (code) => {\n logger.info({ code }, \"自建宿主进程退出\");\n writeAccountRuntime(dataRoot, {\n uin: opts.qq ?? \"default\",\n pid: process.pid,\n ...(child.pid !== undefined ? { selfHostPid: child.pid } : {}),\n startedAt: new Date().toISOString(),\n kind: \"single-boot\",\n status: \"exited\",\n exitCode: code,\n });\n resolve();\n });\n child.on(\"error\", (err) => {\n logger.error({ err }, \"自建宿主进程启动失败\");\n resolve();\n });\n });\n}\n","/**\n * 配置模板(TOML 注释版,从 config-cmds.ts 拆分,2026-08-08 FTA 优化)。\n * `config init` 与首次启动(loadCliConfig)缺失时生成。\n *\n * 2026-08-08 结构拍板:**一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在\n * 账号内**([accounts.onebot11] / [accounts.satori]);账号必填(至少一个)。\n * dataDir 不再写死(缺省 = 项目根/.napuketto,跨平台),模板保持可移植。\n */\n\n/** 保留占位 QQ 号(模板默认值):必须替换为真实 QQ 号,否则启动校验报错。 */\nexport const RESERVED_PLACEHOLDER_QINS = [\"123456\", \"654321\"] as const;\n\nexport function configTemplate(): string {\n return `# ============================================================\n# NapukettoQQ 全局配置文件\n# ============================================================\n# 本文件由 \\`napuketto config init\\` 生成(或首次启动自动生成)。\n# 本机配置不入库(.gitignore);修改后重启生效。\n#\n# 配置组织:一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在账号内\n# ([accounts.onebot11] / [accounts.satori] / [accounts.kurobot])。账号没有写\n# 某协议段 = 该账号不启用该协议。账号必填(至少一个,qq 必填)。\n\n# ------------------------------------------------------------\n# 全局配置(跨账号)\n# ------------------------------------------------------------\n\n# 数据根目录:账号目录/日志/缓存/QQ 数据存放位置。\n# 缺省 = <项目根>/.napuketto(部署场景,跨平台,无需修改)。\n# 支持绝对路径、\"~/\" 前缀(用户主目录)、相对路径(相对启动目录)。\n# dataDir = \".napuketto\"\n\n# 多账号(supervisor)模式下,账号进程崩溃是否自动重启\nautoRestart = true\n\n# 崩溃后自动重启的延迟(毫秒)\nrestartDelayMs = 2000\n\n# ------------------------------------------------------------\n# 账号(至少一个,qq 必填)\n# ------------------------------------------------------------\n\n[[accounts]]\nqq = \"${RESERVED_PLACEHOLDER_QINS[0]}\" # 占位值,必须替换为你的 QQ 号,否则启动报错\nenabled = true # 是否启用(可选,缺省 true)\n\n[accounts.onebot11] # 该账号的 OneBot 11 配置(无此段 = 不启用 OB11)\n\n# 鉴权 token(全局默认):HTTP/WS 连接必须携带。\n# 校验方式:Authorization: Bearer <token> / 裸 token / URL 参数 ?access_token=<token>\n# 每个网络实例可单独覆盖(见下文各实例的 token 字段)。\n# 留空 = 不鉴权(仅本机调试推荐)。\n# token = \"\"\n\n# 心跳 meta 事件间隔(毫秒);0 = 关闭心跳事件\nheartbeatInterval = 3000\n\n# 是否上报机器人自己发的消息(OB11 规范默认不上报)\nreportSelfMessage = false\n\n# 消息内容格式:array = 消息段数组(标准 OneBot 11),string = CQ 码字符串\nmessagePostFormat = \"array\"\n\n# 群文件报形式:false(默认)= message 事件 + file 段(go-cqhttp 兼容);\n# true = 群文件消息改报 group_upload notice 事件(替代 message 事件)\ngroupUploadAsNotice = false\n\n# ---- HTTP 反向服务器(接收第三方 POST 指令),可配置多个 ----\n[[accounts.onebot11.httpServers]]\nenabled = true\nhost = \"127.0.0.1\" # 监听地址;0.0.0.0 = 监听所有网卡(局域网可连)\nport = 3000\n# token = \"abc\" # 可选:覆盖全局 token\n\n# ---- HTTP 正向上报(Webhook:把事件 POST 到第三方地址),可配置多个 ----\n# [[accounts.onebot11.httpPostUrls]]\n# enabled = true\n# url = \"http://127.0.0.1:8080/onebot\" # 第三方接收地址\n# token = \"abc\" # 可选:覆盖全局 token\n# timeoutMs = 5000 # 可选:上报超时(毫秒),缺省不超时\n\n# ---- WS 反向服务器(外部 WS 客户端主动连入),可配置多个 ----\n[[accounts.onebot11.wsServers]]\nenabled = true\nhost = \"127.0.0.1\"\nport = 3001\n# token = \"abc\" # 可选:覆盖全局 token\n# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)\n\n# ---- WS 正向客户端(主动连接外部 WS,双向),可配置多个 ----\n# [[accounts.onebot11.wsReverseUrls]]\n# enabled = true\n# url = \"ws://127.0.0.1:8081/ws\" # 第三方 WS 地址(ws:// 或 wss://)\n# token = \"abc\" # 可选:覆盖全局 token\n# reconnectDelayMs = 5000 # 可选:断线重连延迟(毫秒)\n# maxReconnectAttempts = 10 # 可选:最大重连次数(缺省无限)\n# rejectUnauthorized = true # 可选:wss:// 是否校验证书(自签证书设 false)\n# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)\n\n# 第二个账号示例(只开 Satori):\n# [[accounts]]\n# qq = \"${RESERVED_PLACEHOLDER_QINS[1]}\"\n# [accounts.satori]\n# # token = \"\" # 可选:Satori 鉴权 token\n# [[accounts.satori.httpServers]] # HTTP RPC(POST /v1/{resource}.{method})\n# enabled = true\n# host = \"127.0.0.1\"\n# port = 5500\n# [[accounts.satori.wsServers]] # WS 事件服务(/v1/events)\n# enabled = true\n# host = \"127.0.0.1\"\n# port = 5501\n\n# ------------------------------------------------------------\n# KuroBot 群服互通(kurobridge-ws 客户端,MVP-3)\n# ------------------------------------------------------------\n# 账号内写 [accounts.kurobot] 段即启用;不写 = 不启用。作为 WS 客户端\n# 主动连入 KuroBot 服务端(Minecraft 服务器 kurobot 插件)。\n# QQ 群文字 ↔ 游戏聊天双向互通;群命令(前缀开头)→ 游戏命令。\n# [accounts.kurobot]\n# url = \"ws://127.0.0.1:25580\" # 必填:KuroBot 服务端地址(ws:// 或 wss://)\n# # token = \"\" # 可选:鉴权 token(服务端配了则必填,错 token 拒连)\n# # client = \"napukettoqq\" # 可选:自报身份(缺省 napukettoqq/<版本>)\n# # commandPrefix = \"/\" # 可选:群命令前缀\n# # pingIntervalMs = 15000 # 可选:应用层心跳间隔(毫秒,须 < 30000)\n# # chatTemplate = \"[{player}] {content}\" # 游戏聊天渲染模板\n# # joinTemplate = \"{player} 加入了服务器\" # 玩家进服模板\n# # leaveTemplate = \"{player} 离开了服务器\" # 玩家退服模板\n# # deathTemplate = \"{player} {message}\" # 玩家死亡模板(空消息显示「死亡了」)\n`;\n}\n","/**\n * 主配置类型与手写校验器(从 config-cmds.ts 拆分,2026-08-08 FTA 优化)\n *\n * 主配置 parse(不引入 zod):数据根 / 自动重启 / 重启延迟 / 账号列表。\n * 2026-08-08 结构拍板:**一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在\n * 账号内**([accounts.onebot11] / [accounts.satori]);账号必填(至少一个)。\n * 协议段为宽松对象,装配时由对应协议包的 zod schema 严格校验。\n */\nimport { kernelError, resolveDataRoot } from \"@napuketto/kernel\";\nimport { RESERVED_PLACEHOLDER_QINS } from \"./config-template.js\";\n\n/** 默认自动重启。 */\nconst DEFAULT_AUTO_RESTART = true;\n/** 默认重启延迟(毫秒)。 */\nconst DEFAULT_RESTART_DELAY_MS = 2000;\n\n/** QQ 号格式:纯数字 5-11 位(QQ 号合法范围,勿写死 11 位)。 */\nconst QQ_IN_FORMAT_RE = /^\\d{5,11}$/;\n\n/** 协议段键(宽松对象,装配时 zod 严格校验)。 */\ntype ProtocolKey = \"onebot11\" | \"satori\" | \"kurobot\";\n\n/** 账号配置项(qq 必填;协议段缺省 = 该账号不启用对应协议)。 */\nexport interface CliAccountConfig {\n qq: string;\n enabled?: boolean;\n /** OneBot 11 协议段(与 ob11ConfigSchema 对应,宽松对象,装配时 zod 校验)。 */\n onebot11?: Record<string, unknown>;\n /** Satori 协议段(与 satoriConfigSchema 对应,宽松对象,装配时 zod 校验)。 */\n satori?: Record<string, unknown>;\n /** Kurobot 协议段(与 kurobotConfigSchema 对应,宽松对象,装配时 zod 校验)。 */\n kurobot?: Record<string, unknown>;\n}\n\n/** 主配置(跨账号,全局单文件 TOML)。 */\nexport interface CliConfig {\n /** 数据根目录(绝对路径)。 */\n dataDir: string;\n /** supervisor 是否自动重启崩溃账号。 */\n autoRestart: boolean;\n /** 崩溃后重启延迟(毫秒)。 */\n restartDelayMs: number;\n /** 账号列表(至少一个,qq 必填)。 */\n accounts: CliAccountConfig[];\n}\n\n/** 解析账号内协议段(宽松对象,装配时由 zod schema 严格校验)。 */\nfunction parseProtocolSection(\n raw: Record<string, unknown>,\n key: ProtocolKey,\n): Record<string, unknown> | undefined {\n const section = raw[key];\n if (section === undefined) {\n return;\n }\n if (typeof section !== \"object\" || section === null) {\n throw kernelError(`主配置账号 ${key} 段必须是对象`, \"INVALID_PARAM\");\n }\n return section as Record<string, unknown>;\n}\n\n/**\n * 解析 dataDir(缺省用当前解析的数据根 = 项目根/.napuketto)。\n * 支持绝对路径、\"~/\" 前缀(用户主目录)、相对路径(相对启动目录),\n * 消费端 resolveDataRoot 统一展开为绝对路径。\n */\nfunction parseDataDir(raw: Record<string, unknown>): string {\n if (raw[\"dataDir\"] === undefined) {\n return resolveDataRoot();\n }\n if (typeof raw[\"dataDir\"] !== \"string\" || raw[\"dataDir\"] === \"\") {\n throw kernelError(\"主配置 dataDir 必须是非空字符串\", \"INVALID_PARAM\");\n }\n return raw[\"dataDir\"];\n}\n\n/** 解析 autoRestart。 */\nfunction parseAutoRestart(raw: Record<string, unknown>): boolean {\n if (raw[\"autoRestart\"] === undefined) {\n return DEFAULT_AUTO_RESTART;\n }\n if (typeof raw[\"autoRestart\"] !== \"boolean\") {\n throw kernelError(\"主配置 autoRestart 必须是布尔值\", \"INVALID_PARAM\");\n }\n return raw[\"autoRestart\"];\n}\n\n/** 解析 restartDelayMs(毫秒)。 */\nfunction parseRestartDelayMs(raw: Record<string, unknown>): number {\n if (raw[\"restartDelayMs\"] === undefined) {\n return DEFAULT_RESTART_DELAY_MS;\n }\n if (\n typeof raw[\"restartDelayMs\"] !== \"number\" ||\n !Number.isInteger(raw[\"restartDelayMs\"]) ||\n raw[\"restartDelayMs\"] <= 0\n ) {\n throw kernelError(\"主配置 restartDelayMs 必须是正整数\", \"INVALID_PARAM\");\n }\n return raw[\"restartDelayMs\"];\n}\n\n/** 解析 accounts(必须至少一个账号,qq 必填)。 */\nfunction parseAccounts(raw: Record<string, unknown>): CliAccountConfig[] {\n if (raw[\"accounts\"] === undefined) {\n throw kernelError(\n \"主配置缺少 accounts(必须至少一个账号);首次启动已生成模板,请编辑后重试\",\n \"INVALID_PARAM\",\n );\n }\n if (!Array.isArray(raw[\"accounts\"]) || raw[\"accounts\"].length === 0) {\n throw kernelError(\n \"主配置 accounts 必须是非空数组(至少一个 QQ 账号);首次启动已生成模板,请编辑后重试\",\n \"INVALID_PARAM\",\n );\n }\n const accounts: CliAccountConfig[] = [];\n for (const item of raw[\"accounts\"]) {\n accounts.push(parseAccount(item));\n }\n return accounts;\n}\n\n/** 解析单个账号项(qq 必填;协议段缺省 = 不启用)。 */\nfunction parseAccount(item: unknown): CliAccountConfig {\n if (typeof item !== \"object\" || item === null) {\n throw kernelError(\"主配置 accounts 元素必须是对象\", \"INVALID_PARAM\");\n }\n const { qq, enabled } = item as { qq?: unknown; enabled?: unknown };\n if (typeof qq !== \"string\" || qq === \"\") {\n throw kernelError(\"主配置账号缺少非空 qq\", \"INVALID_PARAM\");\n }\n if (!QQ_IN_FORMAT_RE.test(qq)) {\n throw kernelError(\"主配置账号 qq 必须是 5-11 位纯数字 QQ 号\", \"INVALID_PARAM\");\n }\n if (RESERVED_PLACEHOLDER_QINS.some((placeholder) => placeholder === qq)) {\n throw kernelError(\n `主配置账号 qq \"${qq}\" 是模板占位值,请编辑配置文件填入真实 QQ 号后再启动`,\n \"INVALID_PARAM\",\n );\n }\n const out: CliAccountConfig = { qq };\n if (enabled !== undefined) {\n if (typeof enabled !== \"boolean\") {\n throw kernelError(\"主配置账号 enabled 必须是布尔值\", \"INVALID_PARAM\");\n }\n out.enabled = enabled;\n }\n const raw = item as Record<string, unknown>;\n const onebot11 = parseProtocolSection(raw, \"onebot11\");\n if (onebot11 !== undefined) {\n out.onebot11 = onebot11;\n }\n const satori = parseProtocolSection(raw, \"satori\");\n if (satori !== undefined) {\n out.satori = satori;\n }\n const kurobot = parseProtocolSection(raw, \"kurobot\");\n if (kurobot !== undefined) {\n out.kurobot = kurobot;\n }\n return out;\n}\n\n/** 手写校验器:主配置 parse(不引入 zod)。 */\nexport function parseCliConfig(input: unknown): CliConfig {\n if (typeof input !== \"object\" || input === null) {\n throw kernelError(\"主配置必须是对象\", \"INVALID_PARAM\");\n }\n const raw = input as Record<string, unknown>;\n const dataDir = parseDataDir(raw);\n const autoRestart = parseAutoRestart(raw);\n const restartDelayMs = parseRestartDelayMs(raw);\n const accounts = parseAccounts(raw);\n return { dataDir, autoRestart, restartDelayMs, accounts };\n}\n","/**\n * cli config 子命令:init / list / apply(P6,2026-08-05;2026-08-07 配置文件移到项目根;\n * 2026-08-08 结构拍板:账号内嵌协议段)\n *\n * **全局单配置文件**:`<项目根>/napuketto.toml`(2026-08-07 用户拍板:配置文件放项目根,\n * 数据仍按数据根组织),本项目所有配置都在里面管理(TOML + smol-toml,配合 zod 校验,\n * 用户 2026-08-05 拍板:JSON 门槛太高)。本机配置不入库(.gitignore)。\n *\n * 2026-08-08 结构(用户拍板):**一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在\n * 账号内**,账号必填(至少一个,qq 必填):\n *\n * # dataDir = \".napuketto\" # 可选;缺省 = 项目根/.napuketto(跨平台)\n * autoRestart = true\n * restartDelayMs = 2000\n * [[accounts]]\n * qq = \"123456\" # QQ 号(必填)\n * enabled = true\n * [accounts.onebot11] # 该账号的 OB11 段(无 = 不启用)\n * token = \"\"\n * [[accounts.onebot11.httpServers]]\n * enabled = true\n * port = 3000\n * [accounts.satori] # 该账号的 Satori 段(无 = 不启用)\n * [[accounts.satori.httpServers]]\n * enabled = true\n * port = 5500\n *\n * 配置文件路径解析见 kernel `resolveConfigPath`(NAPKETTO_CONFIG 显式 > 项目根探测 > 数据根兜底);\n * 校验器为手写 parse(适配 kernel ConfigBase 的 ConfigSchema 形状);\n * 协议段由对应协议包的 zod schema 校验(boot 装配时按登录账号 uin 从 accounts 取段作 seed)。\n */\nimport type { Dirent } from \"node:fs\";\nimport { mkdirSync, readdirSync } from \"node:fs\";\nimport { access, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, extname, join } from \"node:path\";\nimport process from \"node:process\";\nimport type { ConfigSchema } from \"@napuketto/kernel\";\nimport {\n ConfigBase,\n kernelError,\n parseToml,\n resolveConfigPath,\n resolveDataRoot,\n stringifyToml,\n} from \"@napuketto/kernel\";\nimport { type CliConfig, parseCliConfig } from \"./config-parse.js\";\nimport { configTemplate } from \"./config-template.js\";\nimport { logger } from \"./logger.js\";\n\n/** 主配置默认值(dataRoot 为当前解析的数据根 = 项目根/.napuketto)。 */\nfunction defaultCliConfig(dataRoot: string): CliConfig {\n return {\n dataDir: dataRoot,\n autoRestart: true,\n restartDelayMs: 2000,\n accounts: [],\n };\n}\n\n/** 配置文件缺失 → 生成带注释模板(首次启动自动生成默认配置)。 */\nasync function ensureConfigTemplate(path: string): Promise<void> {\n try {\n await access(path);\n } catch {\n mkdirSync(dirname(path), { recursive: true });\n await writeFile(path, `${configTemplate()}\\n`, \"utf8\");\n logger.info({ path }, \"配置文件不存在,已生成默认模板(请编辑 accounts 段填入 QQ 号)\");\n }\n}\n\n/** 主配置 store(读/写/校验;.toml 后缀 → smol-toml 序列化)。\n * 配置文件在项目根(resolveConfigPath),dataRoot 仅作兜底与 dataDir 默认值。 */\nclass CliConfigStore extends ConfigBase<CliConfig> {\n constructor(dataRoot: string) {\n super({\n path: resolveConfigPath({ dataRoot }),\n schema: { parse: parseCliConfig } satisfies ConfigSchema<CliConfig>,\n defaults: defaultCliConfig(dataRoot),\n });\n }\n}\n\n/** 读取主配置(缺失自动落模板)。 */\nexport async function loadCliConfig(dataRoot: string): Promise<CliConfig> {\n const store = new CliConfigStore(dataRoot);\n await ensureConfigTemplate(store.path);\n return await store.load();\n}\n\n/** CliConfig → 普通对象(smol-toml stringify 入参)。 */\nfunction toRecord(config: CliConfig): Record<string, unknown> {\n return config as unknown as Record<string, unknown>;\n}\n\n/** config init:生成全局 TOML 配置文件(项目根)+ 数据根目录。\n * 文件不存在 → 写带注释模板;已存在 → 跳过(不覆盖用户配置)。\n * 输出原始模板内容(账号未填时解析会校验失败,init 只负责生成不校验)。 */\nexport async function cmdConfigInit(opts: { dataDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n mkdirSync(dataRoot, { recursive: true });\n const store = new CliConfigStore(dataRoot);\n let created = false;\n try {\n await access(store.path);\n } catch {\n mkdirSync(dirname(store.path), { recursive: true });\n await writeFile(store.path, `${configTemplate()}\\n`, \"utf8\");\n created = true;\n }\n const raw = await readFile(store.path, \"utf8\");\n logger.info({ dataRoot }, \"数据根\");\n if (created) {\n logger.info({ path: store.path }, \"全局配置已生成(请编辑 accounts 段填入 QQ 号)\");\n } else {\n logger.info({ path: store.path }, \"全局配置已存在(跳过生成,避免覆盖用户配置)\");\n }\n // TOML 内容为功能输出(机器可读),保持原样 stdout,不做日志包装\n process.stdout.write(raw);\n}\n\n/** config list:列出全局配置与各账号目录。 */\nexport async function cmdConfigList(opts: { dataDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const store = new CliConfigStore(dataRoot);\n const config = await store.load();\n logger.info({ dataRoot }, \"数据根\");\n logger.info({ path: store.path }, \"全局配置\");\n process.stdout.write(`${stringifyToml(toRecord(config))}\\n`);\n // 账号目录(数据根下的子目录)\n let entries: Dirent[];\n try {\n entries = readdirSync(dataRoot, { withFileTypes: true });\n } catch {\n entries = [];\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n printAccountConfig(entry.name, dataRoot);\n }\n }\n}\n\n/** 打印账号 config 目录文件清单。 */\nfunction printAccountConfig(account: string, dataRoot: string): void {\n const configDir = join(dataRoot, account, \"config\");\n let files: string[] = [];\n try {\n files = readdirSync(configDir).filter((f) => f.endsWith(\".json\") || f.endsWith(\".toml\"));\n } catch {\n files = [];\n }\n let fileList = \"(未初始化)\";\n if (files.length > 0) {\n fileList = files.join(\", \");\n }\n // 注意:字段名用 account 而非 name——name 是 pino 保留键(logger name),\n // 会被 pino-pretty 渲染成 `(name/pid)` 元数据头而非普通字段。\n logger.info({ account, files: fileList }, \"账号配置\");\n}\n\n/** 解析外部配置文本(按扩展名推断 TOML/JSON)。 */\nfunction parseExternalConfig(file: string, rawText: string): unknown {\n if (extname(file).toLowerCase() === \".toml\") {\n return parseToml(rawText);\n }\n try {\n return JSON.parse(rawText) as unknown;\n } catch {\n throw kernelError(`外部配置 ${file} 不是合法 JSON`, \"INVALID_PARAM\");\n }\n}\n\n/** config apply:应用外部配置(顶层覆盖合并后写回全局配置)。 */\nexport async function cmdConfigApply(file: string, opts: { dataDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const store = new CliConfigStore(dataRoot);\n const existing = await store.load();\n let rawText: string;\n try {\n rawText = await readFile(file, \"utf8\");\n } catch (err) {\n let message = String(err);\n if (err instanceof Error) {\n const { message: errMessage } = err;\n message = errMessage;\n }\n throw kernelError(`读取外部配置失败 ${file}: ${message}`, \"INVALID_PARAM\", { cause: err });\n }\n const partial = parseExternalConfig(file, rawText);\n if (typeof partial !== \"object\" || partial === null) {\n throw kernelError(\"外部配置必须是对象\", \"INVALID_PARAM\");\n }\n const patch = partial as Record<string, unknown>;\n const merged: Record<string, unknown> = { ...existing, ...patch };\n const next = parseCliConfig(merged);\n await store.save(next);\n logger.info({ path: store.path }, \"全局配置已更新\");\n // TOML 内容为功能输出(机器可读),保持原样 stdout,不做日志包装\n process.stdout.write(`${stringifyToml(toRecord(next))}\\n`);\n}\n","/**\n * ops-commands.ts:status / stop / restart 运维命令实现(2026-09-08 T8)。\n *\n * 数据源:runtime-state 状态文件(supervisor/单账号 boot 写入)+ liveness 检测。\n * stop/restart 语义:\n * - stop -q <uin>:树杀该账号 boot 进程(连带 self-host)。⚠️ supervisor 在跑\n * 且 autoRestart 时账号会被自动拉起——提示用户先 `napuketto stop`(停 supervisor)。\n * - stop(无 -q):树杀 supervisor(子进程随 supervisor 的信号转发退出;若\n * 残留按各账号 runtime pid 逐个树杀兜底)。\n * - restart:stop 同目标 + detached 重新拉起(-q → 单账号 boot;无 -q →\n * supervisor autoStart)。\n */\nimport process from \"node:process\";\nimport { logger } from \"./logger.js\";\nimport {\n type AccountRuntime,\n aggregateStatus,\n isPidAlive,\n killProcessTree,\n listAccountRuntimes,\n readSupervisorRuntime,\n spawnDetached,\n} from \"./runtime-state.js\";\n\n/** 运维命令选项。 */\nexport interface OpsOptions {\n dataDir?: string;\n /** 目标账号(stop/restart 指定;status 过滤)。 */\n qq?: string;\n}\n\n/** status:聚合输出各账号运行状态(含 supervisor 自身)。 */\nexport function cmdStatus(dataRoot: string, opts: OpsOptions): void {\n const supervisor = readSupervisorRuntime(dataRoot);\n if (supervisor !== null) {\n logger.info(\n {\n pid: supervisor.pid,\n alive: isPidAlive(supervisor.pid),\n accounts: supervisor.accounts,\n startedAt: supervisor.startedAt,\n },\n \"supervisor\",\n );\n } else {\n logger.info(\"supervisor 未运行(无状态文件)\");\n }\n const runtimes = listAccountRuntimes(dataRoot).filter(\n (r) => opts.qq === undefined || r.uin === opts.qq,\n );\n if (runtimes.length === 0) {\n logger.warn({ dataRoot }, \"没有账号运行时状态(从未启动或数据根不含 runtime.json)\");\n return;\n }\n for (const row of aggregateStatus(runtimes)) {\n logger.info(\n {\n uin: row.uin,\n alive: row.alive,\n pid: row.pid,\n kind: row.kind,\n startedAt: row.startedAt,\n restartCount: row.restartCount,\n ...(row.exitCode !== undefined ? { exitCode: row.exitCode } : {}),\n },\n \"账号状态\",\n );\n }\n}\n\n/** 树杀目标账号(按 runtime pid)。 */\nfunction stopAccount(runtime: AccountRuntime): void {\n killProcessTree(runtime.pid);\n logger.info({ uin: runtime.uin, pid: runtime.pid }, \"已发送停止(树杀)\");\n}\n\n/** stop:-q 停单账号;无 -q 停 supervisor + 全部账号兜底。 */\nexport async function cmdStop(dataRoot: string, opts: OpsOptions): Promise<void> {\n const runtimes = listAccountRuntimes(dataRoot);\n if (opts.qq !== undefined) {\n const target = runtimes.find((r) => r.uin === opts.qq);\n if (target === undefined) {\n logger.error({ uin: opts.qq }, \"账号无运行时状态(未启动过)\");\n process.exitCode = 1;\n return;\n }\n stopAccount(target);\n const supervisor = readSupervisorRuntime(dataRoot);\n if (supervisor !== null) {\n logger.warn(\n { supervisorPid: supervisor.pid },\n \"supervisor 仍在运行且 autoRestart 开启时会自动拉起该账号;如需彻底停止请执行 napuketto stop(停 supervisor)\",\n );\n }\n return;\n }\n const supervisor = readSupervisorRuntime(dataRoot);\n if (supervisor !== null) {\n killProcessTree(supervisor.pid);\n logger.info({ pid: supervisor.pid }, \"已停止 supervisor(树杀,子进程随信号转发退出)\");\n } else {\n logger.warn(\"supervisor 未运行(无状态文件)\");\n }\n // 兜底:残留账号进程逐个树杀(supervisor 信号转发失败的场合)\n for (const runtime of runtimes) {\n stopAccount(runtime);\n }\n}\n\n/** restart:stop 同目标 + detached 拉起。 */\nexport async function cmdRestart(dataRoot: string, opts: OpsOptions): Promise<void> {\n await cmdStop(dataRoot, opts);\n // 等待进程树退出(实例锁:同账号数据目录单实例,立即拉起会抢锁失败)\n await new Promise((resolve) => setTimeout(resolve, 1500));\n const entry = process.argv[1] ?? \"\";\n const args =\n opts.qq !== undefined\n ? [entry, \"-q\", opts.qq, \"--data-dir\", dataRoot]\n : [entry, \"--data-dir\", dataRoot];\n spawnDetached(args);\n logger.info(\n { args: args.slice(1), qq: opts.qq ?? \"(supervisor 全量)\" },\n \"已 detached 拉起(日志不再接入当前终端,见数据目录 logs/)\",\n );\n}\n","/**\n * supervisor:多账号子进程编排(P6,2026-08-05;2026-08-07 自建宿主唯一)\n *\n * 多账号走多进程(ADR-015):每账号一个独立子进程(node <入口> -q <uin>\n * --data-dir <dataRoot> [--stub-dir <dir>],复用单账号 runSingleAccount 零改动),\n * 本模块作为父进程:\n * - 拉起:主配置 accounts(supervisor 子命令)或显式 -q 列表\n * - 守护:账号异常退出且 autoRestart → 延迟重启\n * - 信号转发:SIGINT/SIGTERM → 停止全部子进程 → 父进程退出\n */\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport process from \"node:process\";\nimport { createInterface } from \"node:readline\";\nimport { resolveDataRoot } from \"@napuketto/kernel\";\nimport { loadCliConfig } from \"./config-cmds.js\";\nimport type { CliAccountConfig, CliConfig } from \"./config-parse.js\";\nimport { logger } from \"./logger.js\";\nimport { writeAccountRuntime, writeSupervisorRuntime } from \"./runtime-state.js\";\n\n/** 默认重启延迟(毫秒)。 */\nconst DEFAULT_RESTART_DELAY_MS = 2000;\n/** 退出轮询间隔(毫秒)。 */\nconst EXIT_POLL_MS = 100;\n/** 子进程强制退出时限(毫秒,超时未退则父进程强制退出)。 */\nconst FORCE_EXIT_MS = 5000;\n\n/** supervisor 选项。 */\nexport interface SupervisorOptions {\n /** 数据根目录。 */\n dataDir?: string;\n /** 显式账号列表(-q A -q B),优先于主配置 accounts。 */\n qqs?: string[];\n /** stub QQNT.dll 目录(自建宿主 PATH 前置,透传给子进程 --stub-dir)。 */\n stubDir?: string;\n}\n\n/** 解析账号列表(显式 -q 优先,否则主配置 accounts 过滤启用项)。 */\nfunction resolveAccounts(opts: SupervisorOptions, config: CliConfig): CliAccountConfig[] {\n if (opts.qqs !== undefined && opts.qqs.length > 0) {\n return opts.qqs.map((qq) => ({ qq }));\n }\n return config.accounts.filter((acct) => acct.enabled !== false);\n}\n\n/** 解析重启延迟(显式 -q 用默认,主配置场景用配置值)。 */\nfunction resolveRestartDelay(opts: SupervisorOptions, config: CliConfig): number {\n if (opts.qqs === undefined) {\n return config.restartDelayMs;\n }\n return DEFAULT_RESTART_DELAY_MS;\n}\n\n/** 监督上下文(startAccount / stopAll 共享,避免闭包过长)。 */\ninterface SupervisorCtx {\n dataRoot: string;\n entry: string;\n restartDelayMs: number;\n autoRestart: boolean;\n stubDir?: string;\n children: Map<string, ChildProcess>;\n /** 每账号守护重启计数(写 runtime.json 用)。 */\n restartCounts: Map<string, number>;\n isStopping: () => boolean;\n}\n\n/** 子进程输出前缀转发(stdout/stderr 逐行 `[uin] ` 前缀;pino 行结构化保留在行内)。 */\nfunction forwardPrefixed(stream: NodeJS.ReadableStream | null, uin: string): void {\n if (stream === null) {\n return;\n }\n const rl = createInterface({ input: stream });\n rl.on(\"line\", (line) => {\n process.stdout.write(`[${uin}] ${line}\\n`);\n });\n}\n\n/** 启动一个账号子进程(exit 后按 autoRestart 守护重启)。 */\nfunction startAccount(ctx: SupervisorCtx, acct: CliAccountConfig): void {\n if (ctx.isStopping()) {\n return;\n }\n const restartCount = ctx.restartCounts.get(acct.qq) ?? 0;\n logger.info({ qq: acct.qq, pid: process.pid, restartCount }, \"启动账号\");\n const args = [ctx.entry, \"-q\", acct.qq, \"--data-dir\", ctx.dataRoot];\n if (ctx.stubDir !== undefined) {\n args.push(\"--stub-dir\", ctx.stubDir);\n }\n // stdio 管道化(2026-09-08 T8):逐行前缀转发(多账号输出可辨别),\n // 替代 inherit——结构化状态(runtime.json:pid/启动时间/重启计数)同步落盘\n const child = spawn(process.execPath, args, {\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n });\n forwardPrefixed(child.stdout, acct.qq);\n forwardPrefixed(child.stderr, acct.qq);\n ctx.children.set(acct.qq, child);\n writeAccountRuntime(ctx.dataRoot, {\n uin: acct.qq,\n pid: child.pid ?? 0,\n startedAt: new Date().toISOString(),\n kind: \"supervisor-child\",\n status: \"running\",\n restartCount,\n });\n child.on(\"exit\", (code, signal) => {\n ctx.children.delete(acct.qq);\n logger.info({ qq: acct.qq, code, signal: signal ?? \"none\" }, \"账号退出\");\n if (!ctx.isStopping() && ctx.autoRestart) {\n // 守护:延迟重启(关闭信号到达前无限重试,由 SIGINT/SIGTERM 终止)\n ctx.restartCounts.set(acct.qq, restartCount + 1);\n setTimeout(() => {\n startAccount(ctx, acct);\n }, ctx.restartDelayMs);\n } else {\n writeAccountRuntime(ctx.dataRoot, {\n uin: acct.qq,\n pid: child.pid ?? 0,\n startedAt: new Date().toISOString(),\n kind: \"supervisor-child\",\n status: \"exited\",\n exitCode: code,\n restartCount,\n });\n }\n });\n child.on(\"error\", (err) => {\n const { message } = err;\n logger.error({ qq: acct.qq, err }, `账号启动失败: ${message}`);\n ctx.children.delete(acct.qq);\n });\n}\n\n/** 停止全部子进程,全部退出后父进程退出(2026-08-07:超时强杀兜底)。 */\nfunction stopAll(ctx: SupervisorCtx): void {\n logger.info(\"收到退出信号,停止全部账号\");\n for (const child of ctx.children.values()) {\n child.kill();\n }\n // 超时强杀(2026-08-07 修复):子进程不响应 SIGTERM(自建宿主卡死等)时\n // 父进程死等 setInterval 永不退出——限时后强制退出。\n const forceTimer = setTimeout(() => {\n logger.warn(\"子进程未在限时内退出,强制退出\");\n process.exit(1);\n }, FORCE_EXIT_MS);\n const wait = setInterval(() => {\n if (ctx.children.size === 0) {\n clearInterval(wait);\n clearTimeout(forceTimer);\n process.exit(0);\n }\n }, EXIT_POLL_MS);\n}\n\n/** 运行 supervisor(多账号编排)。 */\nexport async function runSupervisor(opts: SupervisorOptions = {}): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const config = await loadCliConfig(dataRoot);\n const accounts = resolveAccounts(opts, config);\n if (accounts.length === 0) {\n logger.warn(\"supervisor:没有启用的账号(主配置 accounts 为空或全部禁用)\");\n return;\n }\n const restartDelayMs = resolveRestartDelay(opts, config);\n const [, entry = \"\"] = process.argv;\n const children = new Map<string, ChildProcess>();\n const restartCounts = new Map<string, number>();\n let stopping = false;\n const ctx: SupervisorCtx = {\n dataRoot,\n entry,\n restartDelayMs,\n autoRestart: config.autoRestart,\n ...(opts.stubDir !== undefined ? { stubDir: opts.stubDir } : {}),\n children,\n restartCounts,\n isStopping: () => stopping,\n };\n\n // supervisor 自身状态落盘(napuketto status/stop 消费)\n writeSupervisorRuntime(dataRoot, {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n accounts: accounts.map((a) => a.qq),\n });\n\n for (const acct of accounts) {\n startAccount(ctx, acct);\n }\n\n // 信号转发:停止全部子进程 → 父进程退出\n const shutdown = (): void => {\n if (stopping) {\n return;\n }\n stopping = true;\n stopAll(ctx);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // 常驻:直到收到退出信号(stopAll 内 process.exit)\n await new Promise<void>(() => {\n // 永不 resolve,由 stopAll 的 process.exit 结束进程\n });\n}\n","#!/usr/bin/env node\n\n/**\n * @napuketto/cli 入口(P2-6 + P6,2026-08-05;2026-08-07 用户拍板:只保留自建宿主;\n * 2026-08-08 配置结构拍板:账号必填,协议配置嵌在账号内)\n *\n * commander 参数解析:\n * - 单账号:-q <uin> → runSingleAccount(自建宿主:标准 node + stub QQNT.dll,不拉起 QQ)\n * - 多账号:-q A -q B 或 supervisor 子命令 → runSupervisor(子进程编排)\n * - 无 -q:读全局配置 accounts(至少一个,账号必填)→ supervisor 拉起全部启用账号\n * - config 子命令:init / list / apply(主配置管理)\n *\n * 路线 B(拉起 QQ + 注入)已淘汰(2026-08-07 用户拍板),相关入口已移除。\n */\n\nimport process from \"node:process\";\nimport { resolveDataRoot } from \"@napuketto/kernel\";\nimport { Command } from \"commander\";\nimport { runSingleAccount } from \"./boot.js\";\nimport { cmdConfigApply, cmdConfigInit, cmdConfigList, loadCliConfig } from \"./config-cmds.js\";\nimport { logger } from \"./logger.js\";\nimport { cmdRestart, cmdStatus, cmdStop } from \"./ops-commands.js\";\nimport { runSupervisor } from \"./supervisor.js\";\n\n/** commander collect:累积 -q 多值。 */\nfunction collectQq(value: string, prev: string[]): string[] {\n prev.push(value);\n return prev;\n}\n\n/** 启动字符画(NapukettoQQ 品牌标识,内嵌;art 含反引号/反斜杠,用数组逐行转义)。 */\nconst BANNER = [\n \" _ __ __ __ __ ____ ____ \",\n \" / | / /___ _____ __ __/ /_____ / /_/ /_____ / __ \\\\/ __ \\\\\",\n \" / |/ / __ `/ __ \\\\/ / / / //_/ _ \\\\/ __/ __/ __ \\\\/ / / / / / /\",\n \" / /| / /_/ / /_/ / /_/ / ,< / __/ /_/ /_/ /_/ / /_/ / /_/ / \",\n \"/_/ |_/\\\\__,_/ .___/\\\\__,_/_/|_|\\\\___/\\\\__/\\\\__/\\\\____/\\\\___\\\\_\\\\___\\\\_\\\\ \",\n \" /_/ \",\n].join(\"\\n\");\n\n/** 统一错误输出(结构化日志,错误对象经 pino serializer 带堆栈)。 */\nfunction reportError(err: unknown): void {\n let message = String(err);\n if (err instanceof Error) {\n const { message: errMessage } = err;\n message = errMessage;\n }\n logger.error({ err }, message);\n}\n\n/** 构造可选 dataDir 参数(exactOptionalPropertyTypes 不允许显式 undefined)。 */\nfunction withDataDir(dataDir: string | undefined): { dataDir?: string } {\n if (dataDir === undefined) {\n return {};\n }\n return { dataDir };\n}\n\n/** 读主命令已解析的 data-dir(-d 定义在主命令,子命令经此共享)。 */\nfunction rootDataDir(program: Command): { dataDir?: string } {\n return withDataDir(program.opts<{ dataDir?: string }>().dataDir);\n}\n\n/** 注册 config 子命令(init/list/apply,P6;-d 复用主命令 option)。 */\nfunction registerConfigCommands(program: Command): void {\n const configCmd = program.command(\"config\").description(\"全局配置管理(napuketto.toml)\");\n configCmd\n .command(\"init\")\n .description(\"生成默认全局配置\")\n .action(async () => {\n try {\n await cmdConfigInit(rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n configCmd\n .command(\"list\")\n .description(\"列出全局配置与账号配置\")\n .action(async () => {\n try {\n await cmdConfigList(rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n configCmd\n .command(\"apply <file>\")\n .description(\"应用外部配置(TOML/JSON,校验后写回全局配置)\")\n .action(async (file: string) => {\n try {\n await cmdConfigApply(file, rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n}\n\n/** 注册 supervisor 子命令(读主配置 accounts 批量拉起,P6;-d 复用主命令 option)。 */\nfunction registerSupervisorCommand(program: Command): void {\n program\n .command(\"supervisor\")\n .description(\"多账号编排(读全局配置 accounts 拉起子进程)\")\n .action(async () => {\n try {\n await runSupervisor(rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n}\n\n/** 注册运维子命令(status/stop/restart,2026-09-08 T8;-d 复用主命令 option)。 */\nfunction registerOpsCommands(program: Command): void {\n program\n .command(\"status\")\n .description(\"查看各账号运行状态(读 runtime.json + 进程存活检测)\")\n .option(\"-q, --qq <uin>\", \"只看指定账号\")\n .action(async (opts: { qq?: string }) => {\n try {\n cmdStatus(resolveDataRoot(rootDataDir(program).dataDir), {\n ...(opts.qq !== undefined ? { qq: opts.qq } : {}),\n });\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n program\n .command(\"stop\")\n .description(\n \"停止账号进程树(无 -q 停 supervisor 全部;-q 停单账号,supervisor 守护时会自动拉起)\",\n )\n .option(\"-q, --qq <uin>\", \"目标账号(缺省停 supervisor + 全部账号)\")\n .action(async (opts: { qq?: string }) => {\n try {\n await cmdStop(resolveDataRoot(rootDataDir(program).dataDir), {\n ...(opts.qq !== undefined ? { qq: opts.qq } : {}),\n });\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n program\n .command(\"restart\")\n .description(\"重启账号(无 -q 重启 supervisor 全量;-q 重启单账号;detached 拉起)\")\n .option(\"-q, --qq <uin>\", \"目标账号(缺省重启 supervisor 全量)\")\n .action(async (opts: { qq?: string }) => {\n try {\n await cmdRestart(resolveDataRoot(rootDataDir(program).dataDir), {\n ...(opts.qq !== undefined ? { qq: opts.qq } : {}),\n });\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n}\n\n/** 单账号启动分支(-q 单值;构造 bootOptions 后走 runSingleAccount)。 */\nasync function runSingleAccountBranch(\n opts: { dataDir?: string; qqPath?: string; stubDir?: string },\n qqs: string[],\n): Promise<void> {\n const [only] = qqs;\n if (only === undefined) {\n return;\n }\n const bootOptions: {\n qq?: string;\n dataDir?: string;\n qqPath?: string;\n stubDir?: string;\n } = { qq: only };\n if (opts.dataDir !== undefined) {\n bootOptions.dataDir = opts.dataDir;\n }\n if (opts.qqPath !== undefined) {\n bootOptions.qqPath = opts.qqPath;\n }\n if (opts.stubDir !== undefined) {\n bootOptions.stubDir = opts.stubDir;\n }\n await runSingleAccount(bootOptions);\n}\n\n/** 无 -q 时:读全局配置 accounts(账号必填,2026-08-08 拍板),supervisor 拉起全部启用账号。\n * 不再有「配置为空 → 交互式单账号登录」老路径:缺账号直接报错提示编辑模板。 */\nasync function autoStart(opts: { dataDir?: string; stubDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const config = await loadCliConfig(dataRoot);\n if (config.accounts.length === 0) {\n // parseCliConfig 已强制至少一个,此处防御\n throw new Error(\"主配置没有账号,请编辑 napuketto.toml 填写 [[accounts]] 段\");\n }\n await runSupervisor({\n ...(opts.dataDir !== undefined ? { dataDir: opts.dataDir } : {}),\n ...(opts.stubDir !== undefined ? { stubDir: opts.stubDir } : {}),\n });\n}\n\n/** 注册主命令 action(-q 单账号 / 多 -q supervisor / 无 -q 读配置 supervisor 拉起)。 */\nfunction registerMainAction(program: Command): void {\n program.action(\n async (opts: { qq?: string[]; dataDir?: string; qqPath?: string; stubDir?: string }) => {\n try {\n const qqs = opts.qq ?? [];\n if (qqs.length === 0) {\n await autoStart(opts);\n return;\n }\n if (qqs.length === 1) {\n await runSingleAccountBranch(opts, qqs);\n } else {\n const supOpts: { dataDir?: string; qqs: string[]; stubDir?: string } = { qqs };\n if (opts.dataDir !== undefined) {\n supOpts.dataDir = opts.dataDir;\n }\n if (opts.stubDir !== undefined) {\n supOpts.stubDir = opts.stubDir;\n }\n await runSupervisor(supOpts);\n }\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n },\n );\n}\n\n/** 入口:注册全部子命令与主命令后解析。 */\nfunction main(): void {\n // 启动字符画(每次 cli 启动打印)\n process.stdout.write(`${BANNER}\\n`);\n const program = new Command();\n program\n .name(\"napuketto\")\n .version(\"0.0.1\")\n .description(\"NapukettoQQ 机器人框架(OneBot 11 兼容)\");\n\n program\n .option(\n \"-q, --qq <uin>\",\n \"QQ 号(可重复;多个时 supervisor 拉起多账号子进程)\",\n collectQq,\n [],\n )\n .option(\"-d, --data-dir <dir>\", \"数据根目录(缺省 <项目根>/.napuketto)\")\n .option(\"--qq-path <path>\", \"QQ 安装路径(联调覆盖)\")\n .option(\n \"--stub-dir <dir>\",\n \"stub QQNT.dll 目录(自建宿主 PATH 前置,缺省 loader/native/stub-test-env,Git Submodule)\",\n );\n\n registerConfigCommands(program);\n registerSupervisorCommand(program);\n registerOpsCommands(program);\n registerMainAction(program);\n\n program.parse(process.argv);\n}\n\nmain();\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["DEFAULT_RESTART_DELAY_MS"],"sources":["../src/logger.ts","../src/runtime-state.ts","../src/boot.ts","../src/config-template.ts","../src/config-parse.ts","../src/config-cmds.ts","../src/ops-commands.ts","../src/supervisor.ts","../src/index.ts"],"sourcesContent":["/**\n * cli 进程级 logger(ADR-007 日志规范,2026-08-07 统一改造;2026-10-25 日志可配置化)。\n *\n * 复用 kernel `createLogger`;输出格式/级别/时间戳/颜色由 napuketto.toml [log] 段 +\n * 环境变量决定(kernel parseLogConfig 单一解析源)。懒构造:commander preAction\n * 钩子先 resolveCliLogConfig(dataDir)(banner 决策 + 缓存),首个日志调用时按\n * 缓存配置构造;未初始化时兜底用缺省数据根解析(-d 生效前的极早期日志)。\n *\n * 环境变量(优先级高于 toml,见 kernel parseLogConfig):NAPUKETTO_LOG_LEVEL /\n * NAPUKETTO_LOG_FORMAT / NAPUKETTO_NO_BANNER / NAPUKETTO_LOG_COLORIZE,\n * 兼容旧拼写 NAPKETTO_LOG_LEVEL。\n */\nimport { readFileSync } from \"node:fs\";\nimport { isAbsolute, join } from \"node:path\";\nimport process from \"node:process\";\nimport {\n createLogger,\n type LogConfig,\n type LogFormat,\n parseLogConfig,\n parseToml,\n resolveConfigPath,\n resolveDataRoot,\n} from \"@napuketto/kernel\";\n\n/** 已解析日志配置缓存(preAction 首次解析后固定;-d 后续变化不影响已构造 logger)。 */\nlet cachedConfig: LogConfig | null = null;\n/** 解析配置时用的数据根([log.file] 相对路径展开基准)。 */\nlet cachedDataRoot: string = resolveDataRoot();\n\n/** 同步解析日志配置(toml [log] 段 + env 覆盖)。损坏/缺失配置回退缺省(不阻塞启动:\n * 配置的严格校验由 loadCliConfig 主路径负责报错)。 */\nexport function resolveCliLogConfig(dataDir?: string): LogConfig {\n if (cachedConfig !== null) {\n return cachedConfig;\n }\n cachedDataRoot = resolveDataRoot(dataDir);\n let section: unknown;\n try {\n const configPath = resolveConfigPath({ dataRoot: cachedDataRoot });\n section = parseToml(readFileSync(configPath, \"utf8\"))[\"log\"];\n } catch {\n section = undefined;\n }\n try {\n cachedConfig = parseLogConfig(section, process.env);\n } catch {\n // [log] 段非法:env 覆盖仍生效,缺省兜底(主配置加载路径会给出确切报错)\n cachedConfig = parseLogConfig(undefined, process.env);\n }\n return cachedConfig;\n}\n\n/**\n * 顶层进程的最终控制台格式([log] format 显式优先,否则按 TTY 自动:\n * 终端 pretty / 管道 single-line)。supervisor 的前缀转发决策同源。\n */\nexport function effectiveConsoleFormat(cfg: LogConfig): LogFormat {\n const isTty = process.stdout.isTTY === true;\n return cfg.format ?? (isTty ? \"pretty\" : \"single-line\");\n}\n\n/**\n * 顶层进程的有效控制台渲染决策(传播给子进程):\n * 子进程 stdout 恒为 pipe(TTY 判定失真),由本进程按最终控制台形态决定\n * format/colorize 并经 env 注入(NAPUKETTO_LOG_FORMAT/NAPUKETTO_LOG_COLORIZE)。\n */\nexport function effectiveConsoleEnv(cfg: LogConfig): Record<string, string> {\n const isTty = process.stdout.isTTY === true;\n const format = effectiveConsoleFormat(cfg);\n const colorize = cfg.colorize ?? (format === \"pretty\" && isTty);\n return {\n NAPUKETTO_LOG_FORMAT: format,\n NAPUKETTO_LOG_COLORIZE: colorize ? \"1\" : \"0\",\n };\n}\n\n/** [log.file] 相对路径按数据根展开。 */\nfunction resolveLogFilePath(path: string): string {\n return isAbsolute(path) ? path : join(cachedDataRoot, path);\n}\n\n/**\n * 窄接口注解(避免 TS2883:createLogger 返回 pino.Logger,cli 不直接依赖 pino,\n * declaration emit 无法命名该类型;pino.Logger 结构兼容本接口)。\n */\nexport interface CliLogger {\n info(obj: unknown, msg?: string): void;\n warn(obj: unknown, msg?: string): void;\n error(obj: unknown, msg?: string): void;\n debug(obj: unknown, msg?: string): void;\n trace(obj: unknown, msg?: string): void;\n fatal(obj: unknown, msg?: string): void;\n}\n\n/** cli logger 构造参数([log] 段折叠;file/fileLevel 成对生效,其余直传 undefined)。 */\nfunction cliLoggerOptions(cfg: LogConfig): Parameters<typeof createLogger>[0] {\n return {\n level: cfg.levels.cli ?? cfg.level,\n format: cfg.format,\n timestamp: cfg.timestamp,\n colorize: cfg.colorize,\n file: cfg.file !== undefined ? resolveLogFilePath(cfg.file.path) : undefined,\n fileLevel: cfg.file?.level,\n // name=cli:与 kernel/loader 统一(pretty 渲染 (cli/pid) 头,\n // json 通道作 module 字段),service 保留作进程来源过滤\n base: { name: \"cli\", service: \"cli\" },\n };\n}\n\n/** 懒构造 cli 进程日志(name 渲染为 (cli/pid) 元数据头,文件 JSON 保留 name 字段可过滤)。 */\nclass LazyCliLogger implements CliLogger {\n #inner: CliLogger | null = null;\n\n #ensure(): CliLogger {\n if (this.#inner === null) {\n this.#inner = createLogger(cliLoggerOptions(resolveCliLogConfig()));\n }\n return this.#inner;\n }\n\n info(obj: unknown, msg?: string): void {\n this.#ensure().info(obj, msg);\n }\n warn(obj: unknown, msg?: string): void {\n this.#ensure().warn(obj, msg);\n }\n error(obj: unknown, msg?: string): void {\n this.#ensure().error(obj, msg);\n }\n debug(obj: unknown, msg?: string): void {\n this.#ensure().debug(obj, msg);\n }\n trace(obj: unknown, msg?: string): void {\n this.#ensure().trace(obj, msg);\n }\n fatal(obj: unknown, msg?: string): void {\n this.#ensure().fatal(obj, msg);\n }\n}\n\n/** cli 进程日志(懒构造,配置见 resolveCliLogConfig)。 */\nexport const logger: CliLogger = new LazyCliLogger();\n","/**\n * runtime-state.ts:账号运行时状态文件 + 进程控制原语(2026-09-08 T8)。\n *\n * 跨进程通信选型(决策记录):现有 IPC 为 stdio JSON 行协议(NAPUTO_IPC=1,\n * koishi 插件专用),supervisor 子进程不在此模式;引入独立通道(socket/pipe)\n * 复杂度高。采用 **PID 状态文件**:supervisor/单账号 boot 启动时把进程信息写\n * `<数据根>/<uin>/runtime.json`,supervisor 自身写 `<数据根>/supervisor.json`;\n * `napuketto status/stop/restart` 读文件 + liveness 检测 + 树杀(win32\n * taskkill /T /F,杀 boot 进程树连带 self-host 孙进程)+ detached 重启。\n */\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\n\n/** 账号运行时状态(runtime.json)。 */\nexport interface AccountRuntime {\n /** 账号 QQ 号(= 数据目录名)。 */\n uin: string;\n /** 宿主引导进程 pid(boot 进程;树杀连带 self-host 孙进程)。 */\n pid: number;\n /** self-host 子进程 pid(诊断用,可能缺省)。 */\n selfHostPid?: number;\n /** 启动时间(ISO 字符串)。 */\n startedAt: string;\n /** 运行形态。 */\n kind: \"supervisor-child\" | \"single-boot\";\n /** 状态(写入时快照;读取方用 liveness 修正显示)。 */\n status: \"running\" | \"exited\";\n /** 退出码(status=exited 时)。 */\n exitCode?: number | null;\n /** supervisor 守护重启次数。 */\n restartCount?: number;\n}\n\n/** supervisor 运行时状态(supervisor.json)。 */\nexport interface SupervisorRuntime {\n pid: number;\n startedAt: string;\n /** 编排的账号列表。 */\n accounts: string[];\n}\n\n/** runtime.json 文件名。 */\nconst RUNTIME_FILE = \"runtime.json\";\n/** supervisor.json 文件名。 */\nconst SUPERVISOR_FILE = \"supervisor.json\";\n\n/** 账号状态文件路径。 */\nexport function runtimeStatePath(dataRoot: string, uin: string): string {\n return join(dataRoot, uin, RUNTIME_FILE);\n}\n\n/** supervisor 状态文件路径。 */\nexport function supervisorStatePath(dataRoot: string): string {\n return join(dataRoot, SUPERVISOR_FILE);\n}\n\n/** 写账号运行时状态(原子覆盖)。 */\nexport function writeAccountRuntime(dataRoot: string, state: AccountRuntime): void {\n const path = runtimeStatePath(dataRoot, state.uin);\n mkdirSync(join(path, \"..\"), { recursive: true });\n writeFileSync(path, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n}\n\n/** 读账号运行时状态(缺失/损坏返回 null)。 */\nexport function readAccountRuntime(dataRoot: string, uin: string): AccountRuntime | null {\n return readJson(runtimeStatePath(dataRoot, uin)) as AccountRuntime | null;\n}\n\n/** 写 supervisor 运行时状态。 */\nexport function writeSupervisorRuntime(dataRoot: string, state: SupervisorRuntime): void {\n writeFileSync(supervisorStatePath(dataRoot), `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n}\n\n/** 读 supervisor 运行时状态(缺失返回 null)。 */\nexport function readSupervisorRuntime(dataRoot: string): SupervisorRuntime | null {\n return readJson(supervisorStatePath(dataRoot)) as SupervisorRuntime | null;\n}\n\n/** 读 JSON 文件(缺失/解析失败返回 null)。 */\nfunction readJson(path: string): unknown {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n } catch {\n return null;\n }\n}\n\n/** 列出数据根下全部账号运行时状态(扫一级目录 runtime.json)。 */\nexport function listAccountRuntimes(dataRoot: string): AccountRuntime[] {\n let entries: string[] = [];\n try {\n entries = readdirSync(dataRoot, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n return [];\n }\n const out: AccountRuntime[] = [];\n for (const uin of entries) {\n const state = readAccountRuntime(dataRoot, uin);\n if (state !== null) {\n out.push(state);\n }\n }\n return out;\n}\n\n/** pid 存活检测(signal 0;EPERM 视为存活——进程存在但无权限)。 */\nexport function isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) {\n return false;\n }\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** 树杀命令构造(win32:taskkill /T /F 连孙进程;posix:kill 信号)。 */\nexport function killProcessTree(pid: number): void {\n if (process.platform === \"win32\") {\n spawn(\"taskkill\", [\"/PID\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\" });\n return;\n }\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // 已退出:无需处理\n }\n}\n\n/** detached 拉起 node 进程(restart 用;立即返回不等待)。 */\nexport function spawnDetached(args: string[], cwd?: string): ChildProcess {\n const child = spawn(process.execPath, args, {\n detached: true,\n stdio: \"ignore\",\n ...(cwd !== undefined ? { cwd } : {}),\n });\n child.unref();\n return child;\n}\n\n/** 状态聚合视图(status 命令输出)。 */\nexport interface AccountStatusRow {\n uin: string;\n pid: number;\n /** 文件状态 + liveness 修正后的实际状态。 */\n alive: boolean;\n kind: AccountRuntime[\"kind\"];\n startedAt: string;\n restartCount: number;\n exitCode: number | null | undefined;\n}\n\n/** 聚合各账号状态(liveness 修正:文件 running 但 pid 已死 → not running)。 */\nexport function aggregateStatus(\n runtimes: AccountRuntime[],\n aliveFn: (pid: number) => boolean = isPidAlive,\n): AccountStatusRow[] {\n return runtimes.map((r) => ({\n uin: r.uin,\n pid: r.pid,\n alive: r.status === \"running\" ? aliveFn(r.pid) : false,\n kind: r.kind,\n startedAt: r.startedAt,\n restartCount: r.restartCount ?? 0,\n exitCode: r.exitCode,\n }));\n}\n\n/** 数据根目录存在性(诊断输出用)。 */\nexport function dataRootExists(dataRoot: string): boolean {\n return existsSync(dataRoot);\n}\n","/**\n * cli boot:单账号启动序列(2026-08-07 用户拍板:只保留自建宿主)\n *\n * resolveQqFiles(多级来源定位 QQ 原生文件)→ 解析各包 dist 入口 → launchSelfHost\n * (标准 node + stub QQNT.dll 直接 dlopen,不拉起 QQ / 不注入)→ 常驻。\n *\n * QQ 原生文件来源(本机未装 QQ 也能跑,2026-08-16 接入):\n * L0 NAPUTO_QQ_FILES 显式文件根 → L1 本机 QQ 安装(--qq-path / NAPUTO_QQ_PATH /\n * 注册表 / 常见路径)→ L2 数据根缓存 <数据根>/qq-files/<版本> → 全部缺失时\n * 自动下载官方安装包(sha256 校验 + 7z 解包 + 提取缓存,幂等)。\n * cached 来源下 qq.qqPath 为语义占位(缓存目录无 QQ.exe)——launchSelfHost 只消费\n * qq.wrapperPath / qq.version(NAPUTO_QQ_PATH 仅注入、self-host 未读取),占位安全。\n *\n * 不写业务逻辑:kernel 装配 + 登录 + 协议装配全部在 self-host.cjs → boot-bootstrap.js 完成。\n * 路线 B(拉起 QQ + 注入)已淘汰(launchQqWithLoader 已移除,cli 只走自建宿主)。\n */\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { createInterface } from \"node:readline\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveConfigPath, resolveDataRoot } from \"@napuketto/kernel\";\nimport {\n checkInstanceLock,\n defaultStubDir,\n isNativeNoiseLine,\n launchSelfHost,\n type QqInstallInfo,\n resolveQqFiles,\n watchStopSentinel,\n writeStopSentinel,\n} from \"@napuketto/loader\";\nimport QRCode from \"qrcode\";\nimport { effectiveConsoleEnv, logger, resolveCliLogConfig } from \"./logger.js\";\nimport { writeAccountRuntime } from \"./runtime-state.js\";\n\n/** 单账号启动选项。 */\nexport interface BootOptions {\n /** QQ 号(数据目录账号隔离,ADR-016)。 */\n qq?: string;\n /** 数据根目录(缺省环境变量/用户目录)。 */\n dataDir?: string;\n /** 覆盖 QQ 安装路径(联调)。 */\n qqPath?: string;\n /** stub QQNT.dll 目录(缺省 loader 包内闭源 submodule native/stub-test-env)。 */\n stubDir?: string;\n}\n\n/** 解析 workspace 包的 dist 入口(ESM 解析:包是 ESM-only,exports 无 require 条件)。 */\nasync function packageEntry(pkg: string): Promise<string> {\n const url = await import.meta.resolve(pkg);\n return fileURLToPath(url);\n}\n\n/** QR 透出标记行前缀(loader bootstrap-core 非 IPC 模式输出,cli 解析终端渲染)。 */\nconst QR_LINE_PREFIX = \"NAPUTO_QR \";\n\n/** 优雅停等待上限(毫秒):超时 kill 兜底(Windows 下为硬杀)。 */\nconst GRACEFUL_STOP_TIMEOUT_MS = 10_000;\n\n/**\n * 逐行转发子进程输出到父进程,过滤原生噪音。\n * readline 按 UTF-8 解码 pipe 字节流,再经 process.stdout(TTY 路径,\n * WriteConsoleW UTF-16)输出——顺带修复 pino 中文在 cmd.exe/管道 936 转码\n * 链路下的乱码(原生 printf 字节流无法从 JS 侧改编码,转 pipe 后统一解码)。\n *\n * NAPUTO_QR 标记行(loader bootstrap-core 输出,QR 登录二维码数据)不转发,\n * 解析后用 qrcode 包渲染终端二维码;png 落盘与 URL 提示由 kernel 日志完成。\n * 噪音判定走 loader 共享单一来源(isNativeNoiseLine,与 koishi IPC 路径同源)。\n */\nfunction forwardFiltered(input: NodeJS.ReadableStream, out: NodeJS.WritableStream): void {\n const lines = createInterface({ input });\n lines.on(\"line\", (line) => {\n if (line.startsWith(QR_LINE_PREFIX)) {\n renderTerminalQr(line.slice(QR_LINE_PREFIX.length));\n return;\n }\n if (isNativeNoiseLine(line)) {\n return;\n }\n out.write(`${line}\\n`);\n });\n}\n\n/**\n * 渲染终端二维码(qrcode 包 terminal 模式,half-block 字符)。\n * 解析失败/无 url 时静默丢弃——标记行是内部协议,坏行不影响登录。\n */\nfunction renderTerminalQr(raw: string): void {\n try {\n const data = JSON.parse(raw) as { qrcodeUrl?: string };\n const { qrcodeUrl } = data;\n if (qrcodeUrl === undefined || qrcodeUrl === \"\") {\n return;\n }\n void QRCode.toString(qrcodeUrl, { type: \"terminal\", small: true })\n .then((str) => {\n process.stdout.write(`${str}\\n`);\n })\n .catch(() => {\n // 渲染失败静默(不影响登录,kernel 日志的 URL/png 路径仍可用)\n });\n } catch {\n // 坏行静默丢弃\n }\n}\n\n/** 启动单个账号(自建宿主 + 常驻)。 */\nexport async function runSingleAccount(opts: BootOptions = {}): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n // QQ 原生文件多级来源 + 自动下载(本机未装 QQ 时走下载管线;下载失败给可操作提示)\n let qq: QqInstallInfo;\n try {\n qq = await resolveQqFiles({\n dataRoot,\n ...(opts.qqPath !== undefined ? { qqPath: opts.qqPath } : {}),\n });\n } catch (err) {\n logger.error(\n { err },\n `定位/下载 QQ 原生文件失败:${err instanceof Error ? err.message : String(err)}\\n` +\n \"(本机未装 QQ 时自动下载官方安装包;若下载失败请检查网络,或手动安装 QQ、\" +\n \"设置 NAPUTO_QQ_PATH 指定已有安装、NAPUTO_QQ_FILES 指定文件根、\" +\n \"NAPUTO_QQ_URL 覆盖下载地址)\",\n );\n process.exitCode = 1;\n return;\n }\n const cfgDir = path.join(dataRoot, opts.qq ?? \"default\");\n\n // 单实例锁预检(2026-08-07 根治「多实例抢数据目录锁挂起」):同一账号数据\n // 目录只允许一个实例(QQ 原生层 MMKV/登录单例有锁,第二个实例抢不到锁会卡死)。\n // 占用 → 快速失败并提示占用 PID;残留(PID 已死 / PID 被复用,2026-09-10\n // cmdline 校验根治)→ self-host 会自动接管。真正的锁由 self-host 子进程\n // 获取/释放(它才是数据目录的实际持有者),cli 这里只做 spawn 前预检,\n // 给用户友好提示(supervisor 等批量拉起同用)。\n const { occupied, pid: holderPid } = checkInstanceLock(cfgDir);\n if (occupied) {\n logger.error(\n { pid: holderPid, dataDir: cfgDir },\n \"数据目录已被其他实例占用,拒绝启动(同一账号数据目录仅允许一个实例;\" +\n \"占用校验已含 pid 复用检测,如仍误报请删除该目录下的 instance.lock)\",\n );\n process.exitCode = 1;\n return;\n }\n\n const kernelEntry = await packageEntry(\"@napuketto/kernel\");\n const adapterEntry = await packageEntry(\"@napuketto/adapter\");\n const networkEntry = await packageEntry(\"@napuketto/network\");\n\n const stubDir = opts.stubDir ?? process.env[\"NAPUTO_STUB_DIR\"] ?? defaultStubDir();\n\n // 启动信息走结构化日志(时间戳 + 级别 + pid + 元数据,与 kernel 子进程格式一致)。\n // 2026-10-25 级别重分配:引导细节降 debug(banner 之外的定位/路径细节对运行\n // 状态无信息量),致命错误与登录/退出生命周期保持 info 及以上。\n logger.debug({ qqVersion: qq.version, qqPath: qq.qqPath, source: qq.source }, \"QQ 文件来源\");\n logger.debug({ dataDir: cfgDir }, \"数据目录\");\n logger.debug(\"自建宿主引导(标准 node + stub QQNT.dll)\");\n\n // 唯一启动路径:自建宿主(2026-08-07 用户拍板,路线 B 淘汰)\n // stdio 接管 stdout/stderr:过滤 MMKV / 符号查找失败等原生噪音,其余转发\n // configPath:全局配置文件(项目根 napuketto.toml),注入 NAPKETTO_CONFIG 供装配链读取\n // extraEnv:传播有效控制台渲染决策(子进程 stdout 恒 pipe,TTY 判定失真)+\n // 数据根(NAPUTO_DATA_ROOT,self-host 停止哨兵目录)\n // ⚠️ P2(2026-08-12):launchSelfHost 变 async(linux 场景需下载 win-node)\n const { child } = await launchSelfHost({\n qq,\n kernelEntry,\n adapterEntry,\n networkEntry,\n cfgDir,\n // 子进程 cwd 指向数据根:QQ 原生层 fallback 落盘(guild1.db 等)\n // 落在专门的数据目录,不污染项目根(实测 08-07)。\n cwd: dataRoot,\n configPath: resolveConfigPath({ dataRoot }),\n selfHost: true,\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n extraEnv: {\n ...effectiveConsoleEnv(resolveCliLogConfig(opts.dataDir)),\n NAPUTO_DATA_ROOT: dataRoot,\n },\n ...(opts.qq !== undefined ? { quickUin: opts.qq } : {}),\n ...(stubDir !== undefined ? { stubDir } : {}),\n });\n\n if (child.stdout !== null) {\n forwardFiltered(child.stdout, process.stdout);\n }\n if (child.stderr !== null) {\n forwardFiltered(child.stderr, process.stderr);\n }\n\n // 单账号运行时状态落盘(napuketto status/stop 消费,2026-09-08 T8;\n // pid 记 boot 进程——树杀连带 self-host 孙进程)\n writeAccountRuntime(dataRoot, {\n uin: opts.qq ?? \"default\",\n pid: process.pid,\n ...(child.pid !== undefined ? { selfHostPid: child.pid } : {}),\n startedAt: new Date().toISOString(),\n kind: \"single-boot\",\n status: \"running\",\n });\n\n // 优雅关停编排(2026-10-25 生命周期契约):触发后等 self-host 经自身哨兵/信号\n // 处理自行退出(exit 0),10s 未退则 kill 兜底(Windows 下为硬杀)——\n // boot 进程随后随 child 退出自然返回(退出码 0 = 请求式正常退出)。\n const account = opts.qq ?? \"default\";\n let stopArmed = false;\n const armGracefulStop = (reason: string): void => {\n if (stopArmed) {\n return;\n }\n stopArmed = true;\n logger.info({ account, reason }, \"收到停止请求,等待自建宿主退出\");\n const fallback = setTimeout(() => {\n if (child.exitCode === null && !child.killed) {\n logger.warn({ account }, \"自建宿主未在限时内退出,强制结束\");\n child.kill();\n }\n }, GRACEFUL_STOP_TIMEOUT_MS);\n fallback.unref?.();\n };\n watchStopSentinel({ dataRoot, account, onTrigger: () => armGracefulStop(\"停止哨兵\") });\n const onSignal = (signal: string): void => {\n // 写哨兵覆盖非控制台信号场景(self-host 同目录观察同一文件);终端\n // Ctrl+C 下 self-host 也会直接收到 SIGINT,双路径收敛\n writeStopSentinel(dataRoot, account);\n armGracefulStop(`信号 ${signal}`);\n };\n process.on(\"SIGINT\", () => onSignal(\"SIGINT\"));\n process.on(\"SIGTERM\", () => onSignal(\"SIGTERM\"));\n process.on(\"SIGBREAK\", () => onSignal(\"SIGBREAK\"));\n\n // 常驻:等待自建宿主进程退出\n await new Promise<void>((resolve) => {\n child.on(\"exit\", (code) => {\n logger.info({ code, graceful: stopArmed }, \"自建宿主进程退出\");\n writeAccountRuntime(dataRoot, {\n uin: opts.qq ?? \"default\",\n pid: process.pid,\n ...(child.pid !== undefined ? { selfHostPid: child.pid } : {}),\n startedAt: new Date().toISOString(),\n kind: \"single-boot\",\n status: \"exited\",\n exitCode: code,\n });\n resolve();\n });\n child.on(\"error\", (err) => {\n logger.error({ err }, \"自建宿主进程启动失败\");\n resolve();\n });\n });\n}\n","/**\n * 配置模板(TOML 注释版,从 config-cmds.ts 拆分,2026-08-08 FTA 优化)。\n * `config init` 与首次启动(loadCliConfig)缺失时生成。\n *\n * 2026-08-08 结构拍板:**一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在\n * 账号内**([accounts.onebot11] / [accounts.satori]);账号必填(至少一个)。\n * dataDir 不再写死(缺省 = 项目根/.napuketto,跨平台),模板保持可移植。\n */\n\n/** 保留占位 QQ 号(模板默认值):必须替换为真实 QQ 号,否则启动校验报错。 */\nexport const RESERVED_PLACEHOLDER_QINS = [\"123456\", \"654321\"] as const;\n\nexport function configTemplate(): string {\n return `# ============================================================\n# NapukettoQQ 全局配置文件\n# ============================================================\n# 本文件由 \\`napuketto config init\\` 生成(或首次启动自动生成)。\n# 本机配置不入库(.gitignore);修改后重启生效。\n#\n# 配置组织:一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在账号内\n# ([accounts.onebot11] / [accounts.satori] / [accounts.kurobot])。账号没有写\n# 某协议段 = 该账号不启用该协议。账号必填(至少一个,qq 必填)。\n\n# ------------------------------------------------------------\n# 全局配置(跨账号)\n# ------------------------------------------------------------\n\n# 数据根目录:账号目录/日志/缓存/QQ 数据存放位置。\n# 缺省 = <项目根>/.napuketto(部署场景,跨平台,无需修改)。\n# 支持绝对路径、\"~/\" 前缀(用户主目录)、相对路径(相对启动目录)。\n# dataDir = \".napuketto\"\n\n# 多账号(supervisor)模式下,账号进程崩溃是否自动重启\nautoRestart = true\n\n# 崩溃后自动重启的延迟(毫秒)\nrestartDelayMs = 2000\n\n# ------------------------------------------------------------\n# 日志([log],可选——不配置即用缺省:终端 pretty 多行 / 管道 single-line)\n# ------------------------------------------------------------\n# 嵌入式宿主(把 napuketto 打进服务端等场景)推荐:\n# [log]\n# format = \"single-line\" # 或 \"json\"(NDJSON,宿主按 level/module 精确路由)\n# level = \"warn\" # 控制台只留警告以上,细节进文件(配 [log.file])\n# banner = false\n#\n# [log]\n# format = \"pretty\" # pretty | single-line | json(缺省按 TTY 自动)\n# level = \"info\" # trace|debug|info|warn|error|fatal(控制台级别)\n# banner = true # 启动字符画\n# timestamp = \"local\" # local | iso8601 | elapsed\n# unboundReceive = true # false = 未绑定频道的接收行不打(kurobot 握手后生效)\n# [log.levels] # 按模块覆盖级别:cli / kernel / loader / ob11\n# # loader = \"debug\" # 例:终端上看接收行(默认已降 debug)\n# # ob11 = \"debug\" # 例:协议校准数据(含原始 hex)\n# [log.file] # 独立文件 sink(可选;级别可低于控制台)\n# # path = \"logs/napuketto-console.log\"\n# # level = \"debug\"\n#\n# 环境变量覆盖(优先级高于本文件):NAPUKETTO_LOG_LEVEL / NAPUKETTO_LOG_FORMAT /\n# NAPUKETTO_NO_BANNER(兼容旧拼写 NAPKETTO_LOG_LEVEL)。\n\n# ------------------------------------------------------------\n# 账号(至少一个,qq 必填)\n# ------------------------------------------------------------\n\n[[accounts]]\nqq = \"${RESERVED_PLACEHOLDER_QINS[0]}\" # 占位值,必须替换为你的 QQ 号,否则启动报错\nenabled = true # 是否启用(可选,缺省 true)\n\n[accounts.onebot11] # 该账号的 OneBot 11 配置(无此段 = 不启用 OB11)\n\n# 鉴权 token(全局默认):HTTP/WS 连接必须携带。\n# 校验方式:Authorization: Bearer <token> / 裸 token / URL 参数 ?access_token=<token>\n# 每个网络实例可单独覆盖(见下文各实例的 token 字段)。\n# 留空 = 不鉴权(仅本机调试推荐)。\n# token = \"\"\n\n# 心跳 meta 事件间隔(毫秒);0 = 关闭心跳事件\nheartbeatInterval = 3000\n\n# 是否上报机器人自己发的消息(OB11 规范默认不上报)\nreportSelfMessage = false\n\n# 消息内容格式:array = 消息段数组(标准 OneBot 11),string = CQ 码字符串\nmessagePostFormat = \"array\"\n\n# 群文件报形式:false(默认)= message 事件 + file 段(go-cqhttp 兼容);\n# true = 群文件消息改报 group_upload notice 事件(替代 message 事件)\ngroupUploadAsNotice = false\n\n# ---- HTTP 反向服务器(接收第三方 POST 指令),可配置多个 ----\n[[accounts.onebot11.httpServers]]\nenabled = true\nhost = \"127.0.0.1\" # 监听地址;0.0.0.0 = 监听所有网卡(局域网可连)\nport = 3000\n# token = \"abc\" # 可选:覆盖全局 token\n\n# ---- HTTP 正向上报(Webhook:把事件 POST 到第三方地址),可配置多个 ----\n# [[accounts.onebot11.httpPostUrls]]\n# enabled = true\n# url = \"http://127.0.0.1:8080/onebot\" # 第三方接收地址\n# token = \"abc\" # 可选:覆盖全局 token\n# timeoutMs = 5000 # 可选:上报超时(毫秒),缺省不超时\n\n# ---- WS 反向服务器(外部 WS 客户端主动连入),可配置多个 ----\n[[accounts.onebot11.wsServers]]\nenabled = true\nhost = \"127.0.0.1\"\nport = 3001\n# token = \"abc\" # 可选:覆盖全局 token\n# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)\n\n# ---- WS 正向客户端(主动连接外部 WS,双向),可配置多个 ----\n# [[accounts.onebot11.wsReverseUrls]]\n# enabled = true\n# url = \"ws://127.0.0.1:8081/ws\" # 第三方 WS 地址(ws:// 或 wss://)\n# token = \"abc\" # 可选:覆盖全局 token\n# reconnectDelayMs = 5000 # 可选:断线重连延迟(毫秒)\n# maxReconnectAttempts = 10 # 可选:最大重连次数(缺省无限)\n# rejectUnauthorized = true # 可选:wss:// 是否校验证书(自签证书设 false)\n# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)\n\n# 第二个账号示例(只开 Satori):\n# [[accounts]]\n# qq = \"${RESERVED_PLACEHOLDER_QINS[1]}\"\n# [accounts.satori]\n# # token = \"\" # 可选:Satori 鉴权 token\n# [[accounts.satori.httpServers]] # HTTP RPC(POST /v1/{resource}.{method})\n# enabled = true\n# host = \"127.0.0.1\"\n# port = 5500\n# [[accounts.satori.wsServers]] # WS 事件服务(/v1/events)\n# enabled = true\n# host = \"127.0.0.1\"\n# port = 5501\n\n# ------------------------------------------------------------\n# KuroBot 群服互通(kurobridge-ws 客户端,MVP-3)\n# ------------------------------------------------------------\n# 账号内写 [accounts.kurobot] 段即启用;不写 = 不启用。作为 WS 客户端\n# 主动连入 KuroBot 服务端(Minecraft 服务器 kurobot 插件)。\n# QQ 群文字 ↔ 游戏聊天双向互通;群命令(前缀开头)→ 游戏命令。\n# [accounts.kurobot]\n# url = \"ws://127.0.0.1:25580\" # 必填:KuroBot 服务端地址(ws:// 或 wss://)\n# # token = \"\" # 可选:鉴权 token(服务端配了则必填,错 token 拒连)\n# # client = \"napukettoqq\" # 可选:自报身份(缺省 napukettoqq/<版本>)\n# # commandPrefix = \"/\" # 可选:群命令前缀\n# # pingIntervalMs = 15000 # 可选:应用层心跳间隔(毫秒,须 < 30000)\n# # chatTemplate = \"[{player}] {content}\" # 游戏聊天渲染模板\n# # joinTemplate = \"{player} 加入了服务器\" # 玩家进服模板\n# # leaveTemplate = \"{player} 离开了服务器\" # 玩家退服模板\n# # deathTemplate = \"{player} {message}\" # 玩家死亡模板(空消息显示「死亡了」)\n`;\n}\n","/**\n * 主配置类型与手写校验器(从 config-cmds.ts 拆分,2026-08-08 FTA 优化)\n *\n * 主配置 parse(不引入 zod):数据根 / 自动重启 / 重启延迟 / 账号列表。\n * 2026-08-08 结构拍板:**一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在\n * 账号内**([accounts.onebot11] / [accounts.satori]);账号必填(至少一个)。\n * 协议段为宽松对象,装配时由对应协议包的 zod schema 严格校验。\n */\nimport { kernelError, type LogConfig, parseLogConfig, resolveDataRoot } from \"@napuketto/kernel\";\nimport { RESERVED_PLACEHOLDER_QINS } from \"./config-template.js\";\n\n/** 默认自动重启。 */\nconst DEFAULT_AUTO_RESTART = true;\n/** 默认重启延迟(毫秒)。 */\nconst DEFAULT_RESTART_DELAY_MS = 2000;\n\n/** QQ 号格式:纯数字 5-11 位(QQ 号合法范围,勿写死 11 位)。 */\nconst QQ_IN_FORMAT_RE = /^\\d{5,11}$/;\n\n/** 协议段键(宽松对象,装配时 zod 严格校验)。 */\ntype ProtocolKey = \"onebot11\" | \"satori\" | \"kurobot\";\n\n/** 账号配置项(qq 必填;协议段缺省 = 该账号不启用对应协议)。 */\nexport interface CliAccountConfig {\n qq: string;\n enabled?: boolean;\n /** OneBot 11 协议段(与 ob11ConfigSchema 对应,宽松对象,装配时 zod 校验)。 */\n onebot11?: Record<string, unknown>;\n /** Satori 协议段(与 satoriConfigSchema 对应,宽松对象,装配时 zod 校验)。 */\n satori?: Record<string, unknown>;\n /** Kurobot 协议段(与 kurobotConfigSchema 对应,宽松对象,装配时 zod 校验)。 */\n kurobot?: Record<string, unknown>;\n}\n\n/** 主配置(跨账号,全局单文件 TOML)。 */\nexport interface CliConfig {\n /** 数据根目录(绝对路径)。 */\n dataDir: string;\n /** supervisor 是否自动重启崩溃账号。 */\n autoRestart: boolean;\n /** 崩溃后重启延迟(毫秒)。 */\n restartDelayMs: number;\n /** 账号列表(至少一个,qq 必填)。 */\n accounts: CliAccountConfig[];\n /** [log] 日志配置段(kernel parseLogConfig 解析;未配置段时 undefined = 全缺省)。 */\n log?: LogConfig;\n}\n\n/** 解析账号内协议段(宽松对象,装配时由 zod schema 严格校验)。 */\nfunction parseProtocolSection(\n raw: Record<string, unknown>,\n key: ProtocolKey,\n): Record<string, unknown> | undefined {\n const section = raw[key];\n if (section === undefined) {\n return;\n }\n if (typeof section !== \"object\" || section === null) {\n throw kernelError(`主配置账号 ${key} 段必须是对象`, \"INVALID_PARAM\");\n }\n return section as Record<string, unknown>;\n}\n\n/**\n * 解析 dataDir(缺省用当前解析的数据根 = 项目根/.napuketto)。\n * 支持绝对路径、\"~/\" 前缀(用户主目录)、相对路径(相对启动目录),\n * 消费端 resolveDataRoot 统一展开为绝对路径。\n */\nfunction parseDataDir(raw: Record<string, unknown>): string {\n if (raw[\"dataDir\"] === undefined) {\n return resolveDataRoot();\n }\n if (typeof raw[\"dataDir\"] !== \"string\" || raw[\"dataDir\"] === \"\") {\n throw kernelError(\"主配置 dataDir 必须是非空字符串\", \"INVALID_PARAM\");\n }\n return raw[\"dataDir\"];\n}\n\n/** 解析 autoRestart。 */\nfunction parseAutoRestart(raw: Record<string, unknown>): boolean {\n if (raw[\"autoRestart\"] === undefined) {\n return DEFAULT_AUTO_RESTART;\n }\n if (typeof raw[\"autoRestart\"] !== \"boolean\") {\n throw kernelError(\"主配置 autoRestart 必须是布尔值\", \"INVALID_PARAM\");\n }\n return raw[\"autoRestart\"];\n}\n\n/** 解析 restartDelayMs(毫秒)。 */\nfunction parseRestartDelayMs(raw: Record<string, unknown>): number {\n if (raw[\"restartDelayMs\"] === undefined) {\n return DEFAULT_RESTART_DELAY_MS;\n }\n if (\n typeof raw[\"restartDelayMs\"] !== \"number\" ||\n !Number.isInteger(raw[\"restartDelayMs\"]) ||\n raw[\"restartDelayMs\"] <= 0\n ) {\n throw kernelError(\"主配置 restartDelayMs 必须是正整数\", \"INVALID_PARAM\");\n }\n return raw[\"restartDelayMs\"];\n}\n\n/** 解析 accounts(必须至少一个账号,qq 必填)。 */\nfunction parseAccounts(raw: Record<string, unknown>): CliAccountConfig[] {\n if (raw[\"accounts\"] === undefined) {\n throw kernelError(\n \"主配置缺少 accounts(必须至少一个账号);首次启动已生成模板,请编辑后重试\",\n \"INVALID_PARAM\",\n );\n }\n if (!Array.isArray(raw[\"accounts\"]) || raw[\"accounts\"].length === 0) {\n throw kernelError(\n \"主配置 accounts 必须是非空数组(至少一个 QQ 账号);首次启动已生成模板,请编辑后重试\",\n \"INVALID_PARAM\",\n );\n }\n const accounts: CliAccountConfig[] = [];\n for (const item of raw[\"accounts\"]) {\n accounts.push(parseAccount(item));\n }\n return accounts;\n}\n\n/** 解析单个账号项(qq 必填;协议段缺省 = 不启用)。 */\nfunction parseAccount(item: unknown): CliAccountConfig {\n if (typeof item !== \"object\" || item === null) {\n throw kernelError(\"主配置 accounts 元素必须是对象\", \"INVALID_PARAM\");\n }\n const { qq, enabled } = item as { qq?: unknown; enabled?: unknown };\n if (typeof qq !== \"string\" || qq === \"\") {\n throw kernelError(\"主配置账号缺少非空 qq\", \"INVALID_PARAM\");\n }\n if (!QQ_IN_FORMAT_RE.test(qq)) {\n throw kernelError(\"主配置账号 qq 必须是 5-11 位纯数字 QQ 号\", \"INVALID_PARAM\");\n }\n if (RESERVED_PLACEHOLDER_QINS.some((placeholder) => placeholder === qq)) {\n throw kernelError(\n `主配置账号 qq \"${qq}\" 是模板占位值,请编辑配置文件填入真实 QQ 号后再启动`,\n \"INVALID_PARAM\",\n );\n }\n const out: CliAccountConfig = { qq };\n if (enabled !== undefined) {\n if (typeof enabled !== \"boolean\") {\n throw kernelError(\"主配置账号 enabled 必须是布尔值\", \"INVALID_PARAM\");\n }\n out.enabled = enabled;\n }\n const raw = item as Record<string, unknown>;\n const onebot11 = parseProtocolSection(raw, \"onebot11\");\n if (onebot11 !== undefined) {\n out.onebot11 = onebot11;\n }\n const satori = parseProtocolSection(raw, \"satori\");\n if (satori !== undefined) {\n out.satori = satori;\n }\n const kurobot = parseProtocolSection(raw, \"kurobot\");\n if (kurobot !== undefined) {\n out.kurobot = kurobot;\n }\n return out;\n}\n\n/**\n * [log] 段落库净化:format 为 undefined(= 按 TTY 自动)时剔除该键,\n * 避免 toml 回写(config list/apply 的 stringifyToml)遇到 undefined 值。\n */\nfunction stripUndefinedFormat(log: LogConfig): LogConfig {\n const { format, ...rest } = log;\n return format === undefined ? rest : { ...rest, format };\n}\n\n/** 手写校验器:主配置 parse(不引入 zod)。 */\nexport function parseCliConfig(input: unknown): CliConfig {\n if (typeof input !== \"object\" || input === null) {\n throw kernelError(\"主配置必须是对象\", \"INVALID_PARAM\");\n }\n const raw = input as Record<string, unknown>;\n const dataDir = parseDataDir(raw);\n const autoRestart = parseAutoRestart(raw);\n const restartDelayMs = parseRestartDelayMs(raw);\n const accounts = parseAccounts(raw);\n // [log] 段:kernel parseLogConfig 严格校验(非法值报错;未配置段 = undefined)\n const log =\n raw[\"log\"] !== undefined ? stripUndefinedFormat(parseLogConfig(raw[\"log\"])) : undefined;\n return {\n dataDir,\n autoRestart,\n restartDelayMs,\n accounts,\n ...(log !== undefined ? { log } : {}),\n };\n}\n","/**\n * cli config 子命令:init / list / apply(P6,2026-08-05;2026-08-07 配置文件移到项目根;\n * 2026-08-08 结构拍板:账号内嵌协议段)\n *\n * **全局单配置文件**:`<项目根>/napuketto.toml`(2026-08-07 用户拍板:配置文件放项目根,\n * 数据仍按数据根组织),本项目所有配置都在里面管理(TOML + smol-toml,配合 zod 校验,\n * 用户 2026-08-05 拍板:JSON 门槛太高)。本机配置不入库(.gitignore)。\n *\n * 2026-08-08 结构(用户拍板):**一个 QQ 账号一个 [[accounts]] 段,协议与通信配置嵌在\n * 账号内**,账号必填(至少一个,qq 必填):\n *\n * # dataDir = \".napuketto\" # 可选;缺省 = 项目根/.napuketto(跨平台)\n * autoRestart = true\n * restartDelayMs = 2000\n * [[accounts]]\n * qq = \"123456\" # QQ 号(必填)\n * enabled = true\n * [accounts.onebot11] # 该账号的 OB11 段(无 = 不启用)\n * token = \"\"\n * [[accounts.onebot11.httpServers]]\n * enabled = true\n * port = 3000\n * [accounts.satori] # 该账号的 Satori 段(无 = 不启用)\n * [[accounts.satori.httpServers]]\n * enabled = true\n * port = 5500\n *\n * 配置文件路径解析见 kernel `resolveConfigPath`(NAPKETTO_CONFIG 显式 > 项目根探测 > 数据根兜底);\n * 校验器为手写 parse(适配 kernel ConfigBase 的 ConfigSchema 形状);\n * 协议段由对应协议包的 zod schema 校验(boot 装配时按登录账号 uin 从 accounts 取段作 seed)。\n */\nimport type { Dirent } from \"node:fs\";\nimport { mkdirSync, readdirSync } from \"node:fs\";\nimport { access, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, extname, join } from \"node:path\";\nimport process from \"node:process\";\nimport type { ConfigSchema } from \"@napuketto/kernel\";\nimport {\n ConfigBase,\n kernelError,\n parseToml,\n resolveConfigPath,\n resolveDataRoot,\n stringifyToml,\n} from \"@napuketto/kernel\";\nimport { type CliConfig, parseCliConfig } from \"./config-parse.js\";\nimport { configTemplate } from \"./config-template.js\";\nimport { logger } from \"./logger.js\";\n\n/** 主配置默认值(dataRoot 为当前解析的数据根 = 项目根/.napuketto)。 */\nfunction defaultCliConfig(dataRoot: string): CliConfig {\n return {\n dataDir: dataRoot,\n autoRestart: true,\n restartDelayMs: 2000,\n accounts: [],\n };\n}\n\n/** 配置文件缺失 → 生成带注释模板(首次启动自动生成默认配置)。 */\nasync function ensureConfigTemplate(path: string): Promise<void> {\n try {\n await access(path);\n } catch {\n mkdirSync(dirname(path), { recursive: true });\n await writeFile(path, `${configTemplate()}\\n`, \"utf8\");\n logger.info({ path }, \"配置文件不存在,已生成默认模板(请编辑 accounts 段填入 QQ 号)\");\n }\n}\n\n/** 主配置 store(读/写/校验;.toml 后缀 → smol-toml 序列化)。\n * 配置文件在项目根(resolveConfigPath),dataRoot 仅作兜底与 dataDir 默认值。 */\nclass CliConfigStore extends ConfigBase<CliConfig> {\n constructor(dataRoot: string) {\n super({\n path: resolveConfigPath({ dataRoot }),\n schema: { parse: parseCliConfig } satisfies ConfigSchema<CliConfig>,\n defaults: defaultCliConfig(dataRoot),\n });\n }\n}\n\n/** 读取主配置(缺失自动落模板)。 */\nexport async function loadCliConfig(dataRoot: string): Promise<CliConfig> {\n const store = new CliConfigStore(dataRoot);\n await ensureConfigTemplate(store.path);\n return await store.load();\n}\n\n/** CliConfig → 普通对象(smol-toml stringify 入参)。 */\nfunction toRecord(config: CliConfig): Record<string, unknown> {\n return config as unknown as Record<string, unknown>;\n}\n\n/** config init:生成全局 TOML 配置文件(项目根)+ 数据根目录。\n * 文件不存在 → 写带注释模板;已存在 → 跳过(不覆盖用户配置)。\n * 输出原始模板内容(账号未填时解析会校验失败,init 只负责生成不校验)。 */\nexport async function cmdConfigInit(opts: { dataDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n mkdirSync(dataRoot, { recursive: true });\n const store = new CliConfigStore(dataRoot);\n let created = false;\n try {\n await access(store.path);\n } catch {\n mkdirSync(dirname(store.path), { recursive: true });\n await writeFile(store.path, `${configTemplate()}\\n`, \"utf8\");\n created = true;\n }\n const raw = await readFile(store.path, \"utf8\");\n logger.info({ dataRoot }, \"数据根\");\n if (created) {\n logger.info({ path: store.path }, \"全局配置已生成(请编辑 accounts 段填入 QQ 号)\");\n } else {\n logger.info({ path: store.path }, \"全局配置已存在(跳过生成,避免覆盖用户配置)\");\n }\n // TOML 内容为功能输出(机器可读),保持原样 stdout,不做日志包装\n process.stdout.write(raw);\n}\n\n/** config list:列出全局配置与各账号目录。 */\nexport async function cmdConfigList(opts: { dataDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const store = new CliConfigStore(dataRoot);\n const config = await store.load();\n logger.info({ dataRoot }, \"数据根\");\n logger.info({ path: store.path }, \"全局配置\");\n process.stdout.write(`${stringifyToml(toRecord(config))}\\n`);\n // 账号目录(数据根下的子目录)\n let entries: Dirent[];\n try {\n entries = readdirSync(dataRoot, { withFileTypes: true });\n } catch {\n entries = [];\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n printAccountConfig(entry.name, dataRoot);\n }\n }\n}\n\n/** 打印账号 config 目录文件清单。 */\nfunction printAccountConfig(account: string, dataRoot: string): void {\n const configDir = join(dataRoot, account, \"config\");\n let files: string[] = [];\n try {\n files = readdirSync(configDir).filter((f) => f.endsWith(\".json\") || f.endsWith(\".toml\"));\n } catch {\n files = [];\n }\n let fileList = \"(未初始化)\";\n if (files.length > 0) {\n fileList = files.join(\", \");\n }\n // 注意:字段名用 account 而非 name——name 是 pino 保留键(logger name),\n // 会被 pino-pretty 渲染成 `(name/pid)` 元数据头而非普通字段。\n logger.info({ account, files: fileList }, \"账号配置\");\n}\n\n/** 解析外部配置文本(按扩展名推断 TOML/JSON)。 */\nfunction parseExternalConfig(file: string, rawText: string): unknown {\n if (extname(file).toLowerCase() === \".toml\") {\n return parseToml(rawText);\n }\n try {\n return JSON.parse(rawText) as unknown;\n } catch {\n throw kernelError(`外部配置 ${file} 不是合法 JSON`, \"INVALID_PARAM\");\n }\n}\n\n/** config apply:应用外部配置(顶层覆盖合并后写回全局配置)。 */\nexport async function cmdConfigApply(file: string, opts: { dataDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const store = new CliConfigStore(dataRoot);\n const existing = await store.load();\n let rawText: string;\n try {\n rawText = await readFile(file, \"utf8\");\n } catch (err) {\n let message = String(err);\n if (err instanceof Error) {\n const { message: errMessage } = err;\n message = errMessage;\n }\n throw kernelError(`读取外部配置失败 ${file}: ${message}`, \"INVALID_PARAM\", { cause: err });\n }\n const partial = parseExternalConfig(file, rawText);\n if (typeof partial !== \"object\" || partial === null) {\n throw kernelError(\"外部配置必须是对象\", \"INVALID_PARAM\");\n }\n const patch = partial as Record<string, unknown>;\n const merged: Record<string, unknown> = { ...existing, ...patch };\n const next = parseCliConfig(merged);\n await store.save(next);\n logger.info({ path: store.path }, \"全局配置已更新\");\n // TOML 内容为功能输出(机器可读),保持原样 stdout,不做日志包装\n process.stdout.write(`${stringifyToml(toRecord(next))}\\n`);\n}\n","/**\n * ops-commands.ts:status / stop / restart 运维命令实现(2026-09-08 T8)。\n *\n * 数据源:runtime-state 状态文件(supervisor/单账号 boot 写入)+ liveness 检测。\n * stop/restart 语义(2026-10-25 优雅化):\n * - stop -q <uin>:写单账号停止哨兵 → 等该账号进程链优雅退出 → 超时树杀兜底。\n * ⚠️ supervisor 在跑且 autoRestart 时账号会被自动拉起——提示用户先 `napuketto stop`。\n * - stop(无 -q):写 all 哨兵(supervisor + 全部账号优雅退出)→ 超时树杀兜底。\n * - restart:stop 同目标 + detached 重新拉起(-q → 单账号 boot;无 -q →\n * supervisor autoStart)。\n */\nimport process from \"node:process\";\nimport { removeStopSentinel, writeStopSentinel } from \"@napuketto/loader\";\nimport { logger } from \"./logger.js\";\nimport {\n type AccountRuntime,\n aggregateStatus,\n isPidAlive,\n killProcessTree,\n listAccountRuntimes,\n readSupervisorRuntime,\n spawnDetached,\n} from \"./runtime-state.js\";\n\n/** 运维命令选项。 */\nexport interface OpsOptions {\n dataDir?: string;\n /** 目标账号(stop/restart 指定;status 过滤)。 */\n qq?: string;\n}\n\n/** 优雅停等待上限(毫秒):哨兵后子进程链自行退出,超时树杀兜底。 */\nconst GRACEFUL_STOP_WAIT_MS = 8000;\n/** 存活轮询间隔(毫秒)。 */\nconst LIVENESS_POLL_MS = 200;\n\n/** 等待 pid 列表全部退出(返回超时仍未退的 pid)。 */\nasync function waitForPidsExit(pids: number[]): Promise<number[]> {\n const deadline = Date.now() + GRACEFUL_STOP_WAIT_MS;\n let alive = pids.filter((pid) => pid > 0 && isPidAlive(pid));\n while (alive.length > 0 && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, LIVENESS_POLL_MS));\n alive = alive.filter((pid) => isPidAlive(pid));\n }\n return alive;\n}\n\n/** status:聚合输出各账号运行状态(含 supervisor 自身)。 */\nexport function cmdStatus(dataRoot: string, opts: OpsOptions): void {\n const supervisor = readSupervisorRuntime(dataRoot);\n if (supervisor !== null) {\n logger.info(\n {\n pid: supervisor.pid,\n alive: isPidAlive(supervisor.pid),\n accounts: supervisor.accounts,\n startedAt: supervisor.startedAt,\n },\n \"supervisor\",\n );\n } else {\n logger.info(\"supervisor 未运行(无状态文件)\");\n }\n const runtimes = listAccountRuntimes(dataRoot).filter(\n (r) => opts.qq === undefined || r.uin === opts.qq,\n );\n if (runtimes.length === 0) {\n logger.warn({ dataRoot }, \"没有账号运行时状态(从未启动或数据根不含 runtime.json)\");\n return;\n }\n for (const row of aggregateStatus(runtimes)) {\n logger.info(\n {\n uin: row.uin,\n alive: row.alive,\n pid: row.pid,\n kind: row.kind,\n startedAt: row.startedAt,\n restartCount: row.restartCount,\n ...(row.exitCode !== undefined ? { exitCode: row.exitCode } : {}),\n },\n \"账号状态\",\n );\n }\n}\n\n/** 树杀目标账号(按 runtime pid)。 */\nfunction stopAccount(runtime: AccountRuntime): void {\n killProcessTree(runtime.pid);\n logger.info({ uin: runtime.uin, pid: runtime.pid }, \"已发送停止(树杀)\");\n}\n\n/** stop:-q 停单账号;无 -q 停 supervisor + 全部账号兜底。\n *\n * 2026-10-25 优雅化:先写停止哨兵(目标账号或 all)→ 等进程链自行有序退出\n * (退出码 0)→ 超时残留树杀兜底 → 清理哨兵。单账号哨兵只命中对应 boot/self-host,\n * 不干扰同数据根的其他账号。\n */\n/** stop -q:写单账号哨兵 → 等优雅退出 → 超时树杀兜底 → 清哨兵。 */\nasync function stopSingleAccount(dataRoot: string, target: AccountRuntime): Promise<void> {\n logger.info({ uin: target.uin, pid: target.pid }, \"已发送停止请求(哨兵),等待退出\");\n writeStopSentinel(dataRoot, target.uin);\n const stragglers = await waitForPidsExit([target.pid]);\n for (const pid of stragglers) {\n logger.warn({ uin: target.uin, pid }, \"限时内未退出,树杀兜底\");\n killProcessTree(pid);\n }\n removeStopSentinel(dataRoot);\n const supervisor = readSupervisorRuntime(dataRoot);\n if (supervisor !== null) {\n logger.warn(\n { supervisorPid: supervisor.pid },\n \"supervisor 仍在运行且 autoRestart 开启时会自动拉起该账号;如需彻底停止请执行 napuketto stop(停 supervisor)\",\n );\n }\n}\n\nexport async function cmdStop(dataRoot: string, opts: OpsOptions): Promise<void> {\n const runtimes = listAccountRuntimes(dataRoot);\n if (opts.qq !== undefined) {\n const target = runtimes.find((r) => r.uin === opts.qq);\n if (target === undefined) {\n logger.error({ uin: opts.qq }, \"账号无运行时状态(未启动过)\");\n process.exitCode = 1;\n return;\n }\n await stopSingleAccount(dataRoot, target);\n return;\n }\n const supervisor = readSupervisorRuntime(dataRoot);\n const supervisorPid = supervisor !== null && isPidAlive(supervisor.pid) ? supervisor.pid : 0;\n if (supervisor !== null) {\n logger.info({ pid: supervisor.pid }, \"已发送停止请求(哨兵),等待 supervisor 退出\");\n writeStopSentinel(dataRoot, \"all\");\n const alive = await waitForPidsExit([supervisorPid]);\n for (const pid of alive) {\n logger.warn({ pid }, \"supervisor 限时内未退出,树杀兜底\");\n killProcessTree(pid);\n }\n } else {\n logger.warn(\"supervisor 未运行(无状态文件)\");\n }\n // 兜底:残留账号进程逐个树杀(哨兵转发失败的场合)\n const stragglers = await waitForPidsExit(runtimes.map((r) => r.pid));\n for (const runtime of runtimes) {\n if (stragglers.includes(runtime.pid)) {\n stopAccount(runtime);\n }\n }\n removeStopSentinel(dataRoot);\n}\n\n/** restart:stop 同目标 + detached 拉起。 */\nexport async function cmdRestart(dataRoot: string, opts: OpsOptions): Promise<void> {\n await cmdStop(dataRoot, opts);\n // 等待进程树退出(实例锁:同账号数据目录单实例,立即拉起会抢锁失败)\n await new Promise((resolve) => setTimeout(resolve, 1500));\n const entry = process.argv[1] ?? \"\";\n const args =\n opts.qq !== undefined\n ? [entry, \"-q\", opts.qq, \"--data-dir\", dataRoot]\n : [entry, \"--data-dir\", dataRoot];\n spawnDetached(args);\n logger.info(\n { args: args.slice(1), qq: opts.qq ?? \"(supervisor 全量)\" },\n \"已 detached 拉起(日志不再接入当前终端,见数据目录 logs/)\",\n );\n}\n","/**\n * supervisor:多账号子进程编排(P6,2026-08-05;2026-08-07 自建宿主唯一)\n *\n * 多账号走多进程(ADR-015):每账号一个独立子进程(node <入口> -q <uin>\n * --data-dir <dataRoot> [--stub-dir <dir>],复用单账号 runSingleAccount 零改动),\n * 本模块作为父进程:\n * - 拉起:主配置 accounts(supervisor 子命令)或显式 -q 列表\n * - 守护:账号异常退出且 autoRestart → 延迟重启\n * - 信号转发:SIGINT/SIGTERM → 停止全部子进程 → 父进程退出\n */\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport process from \"node:process\";\nimport { createInterface } from \"node:readline\";\nimport { resolveDataRoot } from \"@napuketto/kernel\";\nimport { watchStopSentinel, writeStopSentinel } from \"@napuketto/loader\";\nimport { loadCliConfig } from \"./config-cmds.js\";\nimport type { CliAccountConfig, CliConfig } from \"./config-parse.js\";\nimport {\n effectiveConsoleEnv,\n effectiveConsoleFormat,\n logger,\n resolveCliLogConfig,\n} from \"./logger.js\";\nimport { writeAccountRuntime, writeSupervisorRuntime } from \"./runtime-state.js\";\n\n/** 默认重启延迟(毫秒)。 */\nconst DEFAULT_RESTART_DELAY_MS = 2000;\n/** 退出轮询间隔(毫秒)。 */\nconst EXIT_POLL_MS = 100;\n/** 子进程优雅退出时限(毫秒):哨兵/信号后子进程自行停止,超时强杀兜底。 */\nconst FORCE_EXIT_MS = 8000;\n\n/** supervisor 选项。 */\nexport interface SupervisorOptions {\n /** 数据根目录。 */\n dataDir?: string;\n /** 显式账号列表(-q A -q B),优先于主配置 accounts。 */\n qqs?: string[];\n /** stub QQNT.dll 目录(自建宿主 PATH 前置,透传给子进程 --stub-dir)。 */\n stubDir?: string;\n}\n\n/** 解析账号列表(显式 -q 优先,否则主配置 accounts 过滤启用项)。 */\nfunction resolveAccounts(opts: SupervisorOptions, config: CliConfig): CliAccountConfig[] {\n if (opts.qqs !== undefined && opts.qqs.length > 0) {\n return opts.qqs.map((qq) => ({ qq }));\n }\n return config.accounts.filter((acct) => acct.enabled !== false);\n}\n\n/** 解析重启延迟(显式 -q 用默认,主配置场景用配置值)。 */\nfunction resolveRestartDelay(opts: SupervisorOptions, config: CliConfig): number {\n if (opts.qqs === undefined) {\n return config.restartDelayMs;\n }\n return DEFAULT_RESTART_DELAY_MS;\n}\n\n/** 监督上下文(startAccount / stopAll 共享,避免闭包过长)。 */\ninterface SupervisorCtx {\n dataRoot: string;\n entry: string;\n restartDelayMs: number;\n autoRestart: boolean;\n stubDir?: string;\n children: Map<string, ChildProcess>;\n /** 每账号守护重启计数(写 runtime.json 用)。 */\n restartCounts: Map<string, number>;\n /** 子进程追加 env(NO_BANNER + 渲染决策传播)。 */\n childEnv: Record<string, string>;\n /** pretty 模式才加 [uin] 前缀转发(single-line/json 记录自带 account 字段)。 */\n prefixEnabled: boolean;\n isStopping: () => boolean;\n}\n\n/**\n * 子进程输出前缀转发(stdout/stderr 逐行):pretty 模式加 `[uin] ` 前缀\n * (pino 行结构化保留在行内);single-line/json 模式记录自身已带结构化\n * account 字段(json 加前缀会破坏逐行 JSON.parse),原样透传。\n */\nfunction forwardPrefixed(\n stream: NodeJS.ReadableStream | null,\n uin: string,\n prefixEnabled: boolean,\n): void {\n if (stream === null) {\n return;\n }\n const rl = createInterface({ input: stream });\n rl.on(\"line\", (line) => {\n process.stdout.write(prefixEnabled ? `[${uin}] ${line}\\n` : `${line}\\n`);\n });\n}\n\n/** 启动一个账号子进程(exit 后按 autoRestart 守护重启)。 */\nfunction startAccount(ctx: SupervisorCtx, acct: CliAccountConfig): void {\n if (ctx.isStopping()) {\n return;\n }\n const restartCount = ctx.restartCounts.get(acct.qq) ?? 0;\n // 引导细节(2026-10-25 级别重分配):账号上线由子进程登录日志报告,此处降 debug\n logger.debug({ qq: acct.qq, pid: process.pid, restartCount }, \"启动账号\");\n const args = [ctx.entry, \"-q\", acct.qq, \"--data-dir\", ctx.dataRoot];\n if (ctx.stubDir !== undefined) {\n args.push(\"--stub-dir\", ctx.stubDir);\n }\n // stdio 管道化(2026-09-08 T8):逐行前缀转发(多账号输出可辨别),\n // 替代 inherit——结构化状态(runtime.json:pid/启动时间/重启计数)同步落盘。\n // env:banner 由 supervisor 统一打(每树至多一次),子进程注入 NO_BANNER;\n // 渲染决策(format/colorize)按本进程最终控制台形态传播(子进程 stdout 恒 pipe)。\n const child = spawn(process.execPath, args, {\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n env: {\n ...process.env,\n NAPUKETTO_NO_BANNER: \"1\",\n ...ctx.childEnv,\n },\n });\n forwardPrefixed(child.stdout, acct.qq, ctx.prefixEnabled);\n forwardPrefixed(child.stderr, acct.qq, ctx.prefixEnabled);\n ctx.children.set(acct.qq, child);\n writeAccountRuntime(ctx.dataRoot, {\n uin: acct.qq,\n pid: child.pid ?? 0,\n startedAt: new Date().toISOString(),\n kind: \"supervisor-child\",\n status: \"running\",\n restartCount,\n });\n child.on(\"exit\", (code, signal) => {\n ctx.children.delete(acct.qq);\n logger.info({ qq: acct.qq, code, signal: signal ?? \"none\" }, \"账号退出\");\n if (!ctx.isStopping() && ctx.autoRestart) {\n // 守护:延迟重启(关闭信号到达前无限重试,由 SIGINT/SIGTERM 终止)\n ctx.restartCounts.set(acct.qq, restartCount + 1);\n setTimeout(() => {\n startAccount(ctx, acct);\n }, ctx.restartDelayMs);\n } else {\n writeAccountRuntime(ctx.dataRoot, {\n uin: acct.qq,\n pid: child.pid ?? 0,\n startedAt: new Date().toISOString(),\n kind: \"supervisor-child\",\n status: \"exited\",\n exitCode: code,\n restartCount,\n });\n }\n });\n child.on(\"error\", (err) => {\n const { message } = err;\n logger.error({ qq: acct.qq, err }, `账号启动失败: ${message}`);\n ctx.children.delete(acct.qq);\n });\n}\n\n/** 停止全部子进程,全部退出后父进程退出(2026-10-25 优雅化)。\n *\n * 优雅路径:写停止哨兵(all)→ 子进程链(boot / self-host)自行有序退出 →\n * 全部退出后 exit 0(退出码契约:0 = 请求式正常退出)。超时(FORCE_EXIT_MS)\n * 未退的子进程 kill 兜底(Windows 下为硬杀)后仍 exit 0——关停请求本身是正常路径,\n * 与崩溃(非 0)区分是契约的核心目标。\n */\nfunction stopAll(ctx: SupervisorCtx): void {\n logger.info(\"收到退出信号,停止全部账号\");\n writeStopSentinel(ctx.dataRoot, \"all\");\n // 超时兜底:子进程不响应(自建宿主卡死等)时强杀残留,父进程不再死等\n const forceTimer = setTimeout(() => {\n for (const child of ctx.children.values()) {\n child.kill();\n }\n process.exit(0);\n }, FORCE_EXIT_MS);\n forceTimer.unref?.();\n const wait = setInterval(() => {\n if (ctx.children.size === 0) {\n clearInterval(wait);\n clearTimeout(forceTimer);\n process.exit(0);\n }\n }, EXIT_POLL_MS);\n}\n\n/** 运行 supervisor(多账号编排)。 */\nexport async function runSupervisor(opts: SupervisorOptions = {}): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const config = await loadCliConfig(dataRoot);\n const accounts = resolveAccounts(opts, config);\n if (accounts.length === 0) {\n logger.warn(\"supervisor:没有启用的账号(主配置 accounts 为空或全部禁用)\");\n return;\n }\n const restartDelayMs = resolveRestartDelay(opts, config);\n const [, entry = \"\"] = process.argv;\n const children = new Map<string, ChildProcess>();\n const restartCounts = new Map<string, number>();\n let stopping = false;\n // 渲染决策:按本进程最终控制台形态解析([log] 配置 + TTY),传播给子进程\n const logCfg = resolveCliLogConfig(opts.dataDir);\n const ctx: SupervisorCtx = {\n dataRoot,\n entry,\n restartDelayMs,\n autoRestart: config.autoRestart,\n ...(opts.stubDir !== undefined ? { stubDir: opts.stubDir } : {}),\n children,\n restartCounts,\n childEnv: effectiveConsoleEnv(logCfg),\n prefixEnabled: effectiveConsoleFormat(logCfg) === \"pretty\",\n isStopping: () => stopping,\n };\n\n // supervisor 自身状态落盘(napuketto status/stop 消费)\n writeSupervisorRuntime(dataRoot, {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n accounts: accounts.map((a) => a.qq),\n });\n\n for (const acct of accounts) {\n startAccount(ctx, acct);\n }\n\n // 信号转发:停止全部子进程 → 父进程退出。\n // SIGBREAK = Windows CTRL_BREAK_EVENT(嵌入式宿主 CREATE_NEW_PROCESS_GROUP +\n // GenerateConsoleCtrlEvent 可优雅停整树);另接停止哨兵(宿主/脚本写文件即可)。\n const shutdown = (): void => {\n if (stopping) {\n return;\n }\n stopping = true;\n stopAll(ctx);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGBREAK\", shutdown);\n // supervisor 不持有账号:仅响应 all/空内容哨兵(单账号哨兵由对应 boot/self-host 自行消费)\n watchStopSentinel({ dataRoot, onTrigger: shutdown });\n\n // 常驻:直到收到退出信号(stopAll 内 process.exit)\n await new Promise<void>(() => {\n // 永不 resolve,由 stopAll 的 process.exit 结束进程\n });\n}\n","#!/usr/bin/env node\n\n/**\n * @napuketto/cli 入口(P2-6 + P6,2026-08-05;2026-08-07 用户拍板:只保留自建宿主;\n * 2026-08-08 配置结构拍板:账号必填,协议配置嵌在账号内)\n *\n * commander 参数解析:\n * - 单账号:-q <uin> → runSingleAccount(自建宿主:标准 node + stub QQNT.dll,不拉起 QQ)\n * - 多账号:-q A -q B 或 supervisor 子命令 → runSupervisor(子进程编排)\n * - 无 -q:读全局配置 accounts(至少一个,账号必填)→ supervisor 拉起全部启用账号\n * - config 子命令:init / list / apply(主配置管理)\n *\n * 路线 B(拉起 QQ + 注入)已淘汰(2026-08-07 用户拍板),相关入口已移除。\n */\n\nimport process from \"node:process\";\nimport { resolveDataRoot } from \"@napuketto/kernel\";\nimport { Command } from \"commander\";\nimport { runSingleAccount } from \"./boot.js\";\nimport { cmdConfigApply, cmdConfigInit, cmdConfigList, loadCliConfig } from \"./config-cmds.js\";\nimport { logger, resolveCliLogConfig } from \"./logger.js\";\nimport { cmdRestart, cmdStatus, cmdStop } from \"./ops-commands.js\";\nimport { runSupervisor } from \"./supervisor.js\";\n\n/** commander collect:累积 -q 多值。 */\nfunction collectQq(value: string, prev: string[]): string[] {\n prev.push(value);\n return prev;\n}\n\n/** 启动字符画(NapukettoQQ 品牌标识,内嵌;art 含反引号/反斜杠,用数组逐行转义)。 */\nconst BANNER = [\n \" _ __ __ __ __ ____ ____ \",\n \" / | / /___ _____ __ __/ /_____ / /_/ /_____ / __ \\\\/ __ \\\\\",\n \" / |/ / __ `/ __ \\\\/ / / / //_/ _ \\\\/ __/ __/ __ \\\\/ / / / / / /\",\n \" / /| / /_/ / /_/ / /_/ / ,< / __/ /_/ /_/ /_/ / /_/ / /_/ / \",\n \"/_/ |_/\\\\__,_/ .___/\\\\__,_/_/|_|\\\\___/\\\\__/\\\\__/\\\\____/\\\\___\\\\_\\\\___\\\\_\\\\ \",\n \" /_/ \",\n].join(\"\\n\");\n\n/** 统一错误输出(结构化日志,错误对象经 pino serializer 带堆栈)。 */\nfunction reportError(err: unknown): void {\n let message = String(err);\n if (err instanceof Error) {\n const { message: errMessage } = err;\n message = errMessage;\n }\n logger.error({ err }, message);\n}\n\n/** 构造可选 dataDir 参数(exactOptionalPropertyTypes 不允许显式 undefined)。 */\nfunction withDataDir(dataDir: string | undefined): { dataDir?: string } {\n if (dataDir === undefined) {\n return {};\n }\n return { dataDir };\n}\n\n/** 读主命令已解析的 data-dir(-d 定义在主命令,子命令经此共享)。 */\nfunction rootDataDir(program: Command): { dataDir?: string } {\n return withDataDir(program.opts<{ dataDir?: string }>().dataDir);\n}\n\n/** 注册 config 子命令(init/list/apply,P6;-d 复用主命令 option)。 */\nfunction registerConfigCommands(program: Command): void {\n const configCmd = program.command(\"config\").description(\"全局配置管理(napuketto.toml)\");\n configCmd\n .command(\"init\")\n .description(\"生成默认全局配置\")\n .action(async () => {\n try {\n await cmdConfigInit(rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n configCmd\n .command(\"list\")\n .description(\"列出全局配置与账号配置\")\n .action(async () => {\n try {\n await cmdConfigList(rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n configCmd\n .command(\"apply <file>\")\n .description(\"应用外部配置(TOML/JSON,校验后写回全局配置)\")\n .action(async (file: string) => {\n try {\n await cmdConfigApply(file, rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n}\n\n/** 注册 supervisor 子命令(读主配置 accounts 批量拉起,P6;-d 复用主命令 option)。 */\nfunction registerSupervisorCommand(program: Command): void {\n program\n .command(\"supervisor\")\n .description(\"多账号编排(读全局配置 accounts 拉起子进程)\")\n .action(async () => {\n try {\n await runSupervisor(rootDataDir(program));\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n}\n\n/** 注册运维子命令(status/stop/restart,2026-09-08 T8;-d 复用主命令 option)。 */\nfunction registerOpsCommands(program: Command): void {\n program\n .command(\"status\")\n .description(\"查看各账号运行状态(读 runtime.json + 进程存活检测)\")\n .option(\"-q, --qq <uin>\", \"只看指定账号\")\n .action(async (opts: { qq?: string }) => {\n try {\n cmdStatus(resolveDataRoot(rootDataDir(program).dataDir), {\n ...(opts.qq !== undefined ? { qq: opts.qq } : {}),\n });\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n program\n .command(\"stop\")\n .description(\n \"停止账号进程树(无 -q 停 supervisor 全部;-q 停单账号,supervisor 守护时会自动拉起)\",\n )\n .option(\"-q, --qq <uin>\", \"目标账号(缺省停 supervisor + 全部账号)\")\n .action(async (opts: { qq?: string }) => {\n try {\n await cmdStop(resolveDataRoot(rootDataDir(program).dataDir), {\n ...(opts.qq !== undefined ? { qq: opts.qq } : {}),\n });\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n program\n .command(\"restart\")\n .description(\"重启账号(无 -q 重启 supervisor 全量;-q 重启单账号;detached 拉起)\")\n .option(\"-q, --qq <uin>\", \"目标账号(缺省重启 supervisor 全量)\")\n .action(async (opts: { qq?: string }) => {\n try {\n await cmdRestart(resolveDataRoot(rootDataDir(program).dataDir), {\n ...(opts.qq !== undefined ? { qq: opts.qq } : {}),\n });\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n });\n}\n\n/** 单账号启动分支(-q 单值;构造 bootOptions 后走 runSingleAccount)。 */\nasync function runSingleAccountBranch(\n opts: { dataDir?: string; qqPath?: string; stubDir?: string },\n qqs: string[],\n): Promise<void> {\n const [only] = qqs;\n if (only === undefined) {\n return;\n }\n const bootOptions: {\n qq?: string;\n dataDir?: string;\n qqPath?: string;\n stubDir?: string;\n } = { qq: only };\n if (opts.dataDir !== undefined) {\n bootOptions.dataDir = opts.dataDir;\n }\n if (opts.qqPath !== undefined) {\n bootOptions.qqPath = opts.qqPath;\n }\n if (opts.stubDir !== undefined) {\n bootOptions.stubDir = opts.stubDir;\n }\n await runSingleAccount(bootOptions);\n}\n\n/** 无 -q 时:读全局配置 accounts(账号必填,2026-08-08 拍板),supervisor 拉起全部启用账号。\n * 不再有「配置为空 → 交互式单账号登录」老路径:缺账号直接报错提示编辑模板。 */\nasync function autoStart(opts: { dataDir?: string; stubDir?: string }): Promise<void> {\n const dataRoot = resolveDataRoot(opts.dataDir);\n const config = await loadCliConfig(dataRoot);\n if (config.accounts.length === 0) {\n // parseCliConfig 已强制至少一个,此处防御\n throw new Error(\"主配置没有账号,请编辑 napuketto.toml 填写 [[accounts]] 段\");\n }\n await runSupervisor({\n ...(opts.dataDir !== undefined ? { dataDir: opts.dataDir } : {}),\n ...(opts.stubDir !== undefined ? { stubDir: opts.stubDir } : {}),\n });\n}\n\n/** 注册主命令 action(-q 单账号 / 多 -q supervisor / 无 -q 读配置 supervisor 拉起)。 */\nfunction registerMainAction(program: Command): void {\n program.action(\n async (opts: { qq?: string[]; dataDir?: string; qqPath?: string; stubDir?: string }) => {\n try {\n const qqs = opts.qq ?? [];\n if (qqs.length === 0) {\n await autoStart(opts);\n return;\n }\n if (qqs.length === 1) {\n await runSingleAccountBranch(opts, qqs);\n } else {\n const supOpts: { dataDir?: string; qqs: string[]; stubDir?: string } = { qqs };\n if (opts.dataDir !== undefined) {\n supOpts.dataDir = opts.dataDir;\n }\n if (opts.stubDir !== undefined) {\n supOpts.stubDir = opts.stubDir;\n }\n await runSupervisor(supOpts);\n }\n } catch (err) {\n reportError(err);\n process.exitCode = 1;\n }\n },\n );\n}\n\n/** 入口:注册全部子命令与主命令后解析。 */\nfunction main(): void {\n const program = new Command();\n program\n .name(\"napuketto\")\n .version(\"0.0.1\")\n .description(\"NapukettoQQ 机器人框架(OneBot 11 兼容)\");\n\n program\n .option(\n \"-q, --qq <uin>\",\n \"QQ 号(可重复;多个时 supervisor 拉起多账号子进程)\",\n collectQq,\n [],\n )\n .option(\"-d, --data-dir <dir>\", \"数据根目录(缺省 <项目根>/.napuketto)\")\n .option(\"--qq-path <path>\", \"QQ 安装路径(联调覆盖)\")\n .option(\n \"--stub-dir <dir>\",\n \"stub QQNT.dll 目录(自建宿主 PATH 前置,缺省 loader/native/stub-test-env,Git Submodule)\",\n );\n\n // 启动字符画(每次 cli 启动、首个 action 前打印;受 [log].banner / NAPUKETTO_NO_BANNER\n // 控制——supervisor 派生的子进程被注入 NO_BANNER,每树至多一次)。preAction 时机\n // 才能拿到已解析的 -d 数据根(banner 决策需读对配置文件)。\n program.hook(\"preAction\", () => {\n const cfg = resolveCliLogConfig(rootDataDir(program).dataDir);\n if (cfg.banner) {\n process.stdout.write(`${BANNER}\\n`);\n }\n });\n\n registerConfigCommands(program);\n registerSupervisorCommand(program);\n registerOpsCommands(program);\n registerMainAction(program);\n\n program.parse(process.argv);\n}\n\nmain();\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napuketto/cli",
3
- "version": "0.1.27",
3
+ "version": "0.2.0",
4
4
  "description": "Command line interface for NapukettoQQ",
5
5
  "type": "module",
6
6
  "files": [
@@ -33,10 +33,10 @@
33
33
  "directory": "apps/cli"
34
34
  },
35
35
  "dependencies": {
36
- "@napuketto/kernel": "^0.2.5",
36
+ "@napuketto/kernel": "^0.3.0",
37
37
  "@napuketto/network": "^0.0.3",
38
- "@napuketto/adapter": "^0.3.6",
39
- "@napuketto/loader": "^0.0.39",
38
+ "@napuketto/adapter": "^0.4.0",
39
+ "@napuketto/loader": "^0.1.0",
40
40
  "commander": "^15.0.0",
41
41
  "qrcode": "^1.5.4"
42
42
  }