yymaxapi 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/yymaxapi.js +380 -6
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -708,11 +708,191 @@ function writeConfigWithSync(paths, config) {
708
708
  if (gwEnv === 'wsl') {
709
709
  syncConfigToWsl(paths.openclawConfig);
710
710
  }
711
+ // 如果 Gateway 在 Docker 容器内,自动同步配置过去
712
+ if (gwEnv === 'docker' && _selectedDockerContainer) {
713
+ syncConfigToDocker(paths.openclawConfig, _selectedDockerContainer.id);
714
+ }
715
+ }
716
+
717
+ // ============ Docker 容器发现与操作 ============
718
+ let _dockerAvailCache = null;
719
+ let _dockerContainerCache = null;
720
+ let _selectedDockerContainer = null;
721
+
722
+ function isDockerAvailable() {
723
+ if (_dockerAvailCache !== null) return _dockerAvailCache;
724
+ // 优先用 docker ps(权限要求更低),fallback 到 docker info
725
+ const r = safeExec('docker ps --no-trunc -q', { timeout: 8000 });
726
+ if (r.ok) { _dockerAvailCache = true; return true; }
727
+ const r2 = safeExec('docker info', { timeout: 8000 });
728
+ if (r2.ok) { _dockerAvailCache = true; return true; }
729
+ // 可能需要 sudo
730
+ const r3 = safeExec('sudo -n docker ps -q', { timeout: 8000 });
731
+ _dockerAvailCache = r3.ok;
732
+ return _dockerAvailCache;
733
+ }
734
+
735
+ // 封装 docker 命令(自动处理 sudo)
736
+ let _dockerNeedsSudo = null;
737
+ function dockerCmd(cmd) {
738
+ if (_dockerNeedsSudo === null) {
739
+ const test = safeExec('docker ps -q', { timeout: 5000 });
740
+ _dockerNeedsSudo = !test.ok;
741
+ }
742
+ return _dockerNeedsSudo ? `sudo -n docker ${cmd}` : `docker ${cmd}`;
743
+ }
744
+
745
+ // 查找包含 openclaw/clawdbot/moltbot 的运行中容器
746
+ function findOpenclawDockerContainers() {
747
+ if (_dockerContainerCache !== null) return _dockerContainerCache;
748
+ if (!isDockerAvailable()) { _dockerContainerCache = []; return []; }
749
+
750
+ const ps = safeExec(dockerCmd('ps --format "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"'), { timeout: 10000 });
751
+ if (!ps.ok || !ps.output) { _dockerContainerCache = []; return []; }
752
+
753
+ const containers = [];
754
+ const lines = ps.output.split('\n').filter(Boolean);
755
+
756
+ for (const line of lines) {
757
+ // 兼容 tab 分隔和空格分隔
758
+ const parts = line.includes('\t') ? line.split('\t') : line.split(/\s{2,}/);
759
+ const [id, name, image, ...statusParts] = parts;
760
+ const status = statusParts.join(' ');
761
+ if (!id) continue;
762
+
763
+ // 检查容器内是否有 openclaw/clawdbot/moltbot(用 command -v 代替 which,兼容精简镜像)
764
+ for (const cli of ['openclaw', 'clawdbot', 'moltbot']) {
765
+ const check = safeExec(dockerCmd(`exec ${id} sh -c "command -v ${cli} 2>/dev/null || which ${cli} 2>/dev/null"`), { timeout: 8000 });
766
+ if (check.ok && check.output && check.output.trim()) {
767
+ containers.push({ id, name, image, status, cli, cliPath: check.output.trim() });
768
+ break;
769
+ }
770
+ }
771
+
772
+ // 也检查常见路径(不依赖 which/command -v)
773
+ if (!containers.find(c => c.id === id)) {
774
+ const knownPaths = [
775
+ '/opt/moltbot/moltbot.mjs',
776
+ '/opt/moltbot/bin/moltbot.mjs',
777
+ '/opt/moltbot/bin/moltbot.js',
778
+ '/opt/moltbot/src/moltbot.mjs',
779
+ '/usr/local/bin/openclaw',
780
+ '/usr/local/bin/clawdbot',
781
+ '/usr/local/bin/moltbot',
782
+ ];
783
+ for (const p of knownPaths) {
784
+ const check = safeExec(dockerCmd(`exec ${id} sh -c "test -f '${p}' && echo '${p}'"`), { timeout: 5000 });
785
+ if (check.ok && check.output && check.output.trim()) {
786
+ const isMjs = p.endsWith('.mjs') || p.endsWith('.js');
787
+ const cliName = isMjs ? 'node' : path.basename(p);
788
+ containers.push({ id, name, image, status, cli: cliName, cliPath: p });
789
+ break;
790
+ }
791
+ }
792
+ }
793
+
794
+ // 最后检查容器镜像名/容器名是否包含关键字(即使内部 CLI 路径未知)
795
+ if (!containers.find(c => c.id === id)) {
796
+ const combined = `${name} ${image}`.toLowerCase();
797
+ if (/openclaw|clawdbot|moltbot/.test(combined)) {
798
+ // 尝试在容器内找任何 node 进程带 gateway 关键字
799
+ const findProc = safeExec(dockerCmd(`exec ${id} sh -c "ps aux 2>/dev/null | grep -E 'openclaw|clawdbot|moltbot' | grep -v grep | head -1"`), { timeout: 5000 });
800
+ if (findProc.ok && findProc.output && findProc.output.trim()) {
801
+ containers.push({ id, name, image, status, cli: 'unknown', cliPath: '' });
802
+ }
803
+ }
804
+ }
805
+ }
806
+
807
+ _dockerContainerCache = containers;
808
+ return containers;
809
+ }
810
+
811
+ // 交互式选择 Docker 容器
812
+ async function selectDockerContainer() {
813
+ if (_selectedDockerContainer) return _selectedDockerContainer;
814
+
815
+ const containers = findOpenclawDockerContainers();
816
+ if (containers.length === 0) return null;
817
+
818
+ if (containers.length === 1) {
819
+ _selectedDockerContainer = containers[0];
820
+ console.log(chalk.green(`✅ 自动选择 Docker 容器: ${containers[0].name} (${containers[0].image})`));
821
+ return _selectedDockerContainer;
822
+ }
823
+
824
+ // 多个容器,让用户选择
825
+ console.log(chalk.cyan('\n检测到多个包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器:'));
826
+ const { selected } = await inquirer.prompt([{
827
+ type: 'list',
828
+ name: 'selected',
829
+ message: '请选择要操作的容器:',
830
+ choices: containers.map((c, i) => ({
831
+ name: `${c.name} (${c.image}) [${c.cli}] - ${c.status}`,
832
+ value: i
833
+ }))
834
+ }]);
835
+
836
+ _selectedDockerContainer = containers[selected];
837
+ console.log(chalk.green(`✅ 已选择容器: ${_selectedDockerContainer.name}`));
838
+ return _selectedDockerContainer;
839
+ }
840
+
841
+ // 在 Docker 容器内执行命令
842
+ function execInDocker(containerId, cmd, options = {}) {
843
+ const escaped = cmd.replace(/'/g, "'\\''");
844
+ return safeExec(dockerCmd(`exec ${containerId} bash -lc '${escaped}'`), { timeout: 30000, ...options });
845
+ }
846
+
847
+ // 在 Docker 容器内异步执行命令(后台启动 gateway 等)
848
+ function spawnDetachedInDocker(containerId, cmd, env = {}) {
849
+ try {
850
+ const prefix = _dockerNeedsSudo ? 'sudo' : 'docker';
851
+ const args = _dockerNeedsSudo
852
+ ? ['-n', 'docker', 'exec', '-d', containerId, 'bash', '-lc', cmd]
853
+ : ['exec', '-d', containerId, 'bash', '-lc', cmd];
854
+ const child = spawn(prefix, args, {
855
+ detached: true,
856
+ stdio: 'ignore',
857
+ env: { ...process.env, ...env }
858
+ });
859
+ child.unref();
860
+ return true;
861
+ } catch { return false; }
862
+ }
863
+
864
+ // 获取 Docker 容器内的 CLI 二进制路径
865
+ function getDockerCliBinary(container) {
866
+ if (!container) return null;
867
+ return container.cliPath || null;
868
+ }
869
+
870
+ // 同步配置到 Docker 容器(将本地 openclaw.json 复制到容器内)
871
+ function syncConfigToDocker(localConfigPath, containerId) {
872
+ try {
873
+ // 尝试多个目标路径
874
+ const targets = [
875
+ '/root/.openclaw/openclaw.json',
876
+ '/root/.clawdbot/openclaw.json',
877
+ '/root/.moltbot/moltbot.json',
878
+ '/home/.openclaw/openclaw.json',
879
+ ];
880
+ // 先确定容器内哪个配置目录存在
881
+ for (const target of targets) {
882
+ const dir = path.posix.dirname(target);
883
+ safeExec(dockerCmd(`exec ${containerId} mkdir -p "${dir}"`), { timeout: 5000 });
884
+ const cp = safeExec(dockerCmd(`cp "${localConfigPath}" ${containerId}:${target}`), { timeout: 10000 });
885
+ if (cp.ok) {
886
+ console.log(chalk.gray(` 已同步配置到 Docker 容器: ${target}`));
887
+ return;
888
+ }
889
+ }
890
+ } catch { /* best-effort */ }
711
891
  }
712
892
 
713
893
  // ============ Gateway 环境检测(集中化) ============
714
- // 所有 WSL 相关逻辑统一通过 detectGatewayEnv() 路由
715
- // 返回 'native' | 'wsl',结果缓存,整个进程生命周期只检测一次
894
+ // 所有 WSL/Docker 相关逻辑统一通过 detectGatewayEnv() 路由
895
+ // 返回 'native' | 'wsl' | 'docker',结果缓存,整个进程生命周期只检测一次
716
896
 
717
897
  let _gwEnvCache = null;
718
898
  let _wslAvailCache = null;
@@ -758,7 +938,20 @@ function getWslCliBinary() {
758
938
 
759
939
  function detectGatewayEnv(port = 18789) {
760
940
  if (_gwEnvCache !== null) return _gwEnvCache;
941
+
942
+ // 非 Windows 平台:先检查本地 CLI,找不到再检查 Docker
761
943
  if (process.platform !== 'win32' || !isWslAvailable()) {
944
+ const nativeCli = resolveCliBinary();
945
+ if (nativeCli) {
946
+ _gwEnvCache = 'native';
947
+ return 'native';
948
+ }
949
+ // 本地没有 CLI,检查 Docker 容器
950
+ const dockerContainers = findOpenclawDockerContainers();
951
+ if (dockerContainers.length > 0) {
952
+ _gwEnvCache = 'docker';
953
+ return 'docker';
954
+ }
762
955
  _gwEnvCache = 'native';
763
956
  return 'native';
764
957
  }
@@ -787,17 +980,26 @@ function detectGatewayEnv(port = 18789) {
787
980
  _gwEnvCache = 'wsl';
788
981
  return 'wsl';
789
982
  }
983
+ // Fallback: 检查 Docker 容器
984
+ const dockerContainers = findOpenclawDockerContainers();
985
+ if (dockerContainers.length > 0) {
986
+ _gwEnvCache = 'docker';
987
+ return 'docker';
988
+ }
790
989
  }
791
990
  _gwEnvCache = 'native';
792
991
  return 'native';
793
992
  }
794
993
 
795
- // 在 Gateway 环境中执行命令(WSL 自动包裹 wsl -- bash -lc)
994
+ // 在 Gateway 环境中执行命令(WSL/Docker 自动包裹)
796
995
  function execInGatewayEnv(cmd, options = {}) {
797
996
  if (detectGatewayEnv() === 'wsl') {
798
997
  const escaped = cmd.replace(/'/g, "'\\''");
799
998
  return safeExec(`wsl -- bash -lc '${escaped}'`, options);
800
999
  }
1000
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
1001
+ return execInDocker(_selectedDockerContainer.id, cmd, options);
1002
+ }
801
1003
  return safeExec(cmd, options);
802
1004
  }
803
1005
 
@@ -807,6 +1009,10 @@ function execAsyncInGatewayEnv(cmd, options = {}) {
807
1009
  const escaped = cmd.replace(/'/g, "'\\''");
808
1010
  return { cmd: `wsl -- bash -lc '${escaped}'`, options };
809
1011
  }
1012
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
1013
+ const escaped = cmd.replace(/'/g, "'\\''");
1014
+ return { cmd: dockerCmd(`exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`), options };
1015
+ }
810
1016
  return { cmd, options };
811
1017
  }
812
1018
 
@@ -848,6 +1054,14 @@ function cleanupAgentProcesses() {
848
1054
  execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
849
1055
  }
850
1056
  }
1057
+ // Docker 容器内的 agent 也要清理
1058
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
1059
+ try {
1060
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
1061
+ safeExec(dockerCmd(`exec ${_selectedDockerContainer.id} bash -c "pkill -f '${name}.*agent' 2>/dev/null || true"`), { timeout: 10000 });
1062
+ }
1063
+ } catch { /* ignore */ }
1064
+ }
851
1065
  console.log(chalk.gray('已清理残留 agent 进程'));
852
1066
  } catch { /* ignore */ }
853
1067
  }
@@ -1478,6 +1692,35 @@ function installMacGatewayDaemon() {
1478
1692
  async function tryAutoStartGateway(port, allowAutoDaemon) {
1479
1693
  const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
1480
1694
 
1695
+ // Docker 容器内启动 Gateway
1696
+ if (detectGatewayEnv() === 'docker') {
1697
+ const container = await selectDockerContainer();
1698
+ if (!container) return { started: false };
1699
+
1700
+ console.log(chalk.yellow(`⚠️ Gateway 未检测到运行,尝试在 Docker 容器 ${container.name} 内启动...`));
1701
+
1702
+ const dockerCmds = [];
1703
+ if (container.cli === 'node') {
1704
+ dockerCmds.push(`node ${container.cliPath} gateway`);
1705
+ } else {
1706
+ dockerCmds.push(`${container.cli} gateway`);
1707
+ }
1708
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
1709
+ dockerCmds.push(`${name} gateway`);
1710
+ }
1711
+
1712
+ for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
1713
+ console.log(chalk.yellow(`⚠️ 尝试在容器内启动 Gateway: ${cmd}`));
1714
+ if (spawnDetachedInDocker(container.id, cmd)) {
1715
+ if (await waitForGateway(port, '127.0.0.1', 15000)) {
1716
+ console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${container.name} 内启动`));
1717
+ return { started: true, method: 'docker', container: container.name };
1718
+ }
1719
+ }
1720
+ }
1721
+ return { started: false };
1722
+ }
1723
+
1481
1724
  if (process.platform === 'darwin' && allowAutoDaemon) {
1482
1725
  console.log(chalk.yellow('⚠️ Gateway 未检测到运行,尝试在 macOS 后台启动 (LaunchAgent)...'));
1483
1726
  const daemonResult = installMacGatewayDaemon();
@@ -2630,6 +2873,16 @@ async function testConnection(paths, args = {}) {
2630
2873
 
2631
2874
  const allowAutoDaemon = !(args['no-daemon'] || args.noDaemon);
2632
2875
 
2876
+ // 如果检测到 Docker 环境,先让用户选择容器
2877
+ if (detectGatewayEnv() === 'docker') {
2878
+ const container = await selectDockerContainer();
2879
+ if (!container) {
2880
+ console.log(chalk.red('❌ 未找到包含 OpenClaw/Clawdbot/Moltbot 的 Docker 容器'));
2881
+ console.log(chalk.gray(' 请确保容器正在运行且已安装 openclaw/clawdbot/moltbot'));
2882
+ return;
2883
+ }
2884
+ }
2885
+
2633
2886
  // 步骤0: 清理所有挂死的 agent 进程
2634
2887
  cleanupAgentProcesses();
2635
2888
 
@@ -2650,9 +2903,15 @@ async function testConnection(paths, args = {}) {
2650
2903
 
2651
2904
  if (!gatewayRunning) {
2652
2905
  gwSpinner.fail('Gateway 未运行');
2653
- console.log(chalk.gray(' 请在新的终端执行: openclaw gateway'));
2654
- console.log(chalk.gray(' 或: clawdbot gateway'));
2655
- console.log(chalk.gray(' 或: moltbot gateway'));
2906
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
2907
+ console.log(chalk.gray(` 请在 Docker 容器内启动 Gateway:`));
2908
+ console.log(chalk.gray(` docker exec -it ${_selectedDockerContainer.id} bash`));
2909
+ console.log(chalk.gray(` 然后执行: ${_selectedDockerContainer.cli === 'node' ? `node ${_selectedDockerContainer.cliPath}` : _selectedDockerContainer.cli} gateway`));
2910
+ } else {
2911
+ console.log(chalk.gray(' 请在新的终端执行: openclaw gateway'));
2912
+ console.log(chalk.gray(' 或: clawdbot gateway'));
2913
+ console.log(chalk.gray(' 或: moltbot gateway'));
2914
+ }
2656
2915
  return;
2657
2916
  }
2658
2917
 
@@ -2785,6 +3044,50 @@ async function restartGateway() {
2785
3044
 
2786
3045
  const gwEnv = detectGatewayEnv();
2787
3046
 
3047
+ // 如果 Gateway 在 Docker 容器里,通过 docker exec 重启
3048
+ if (gwEnv === 'docker') {
3049
+ const container = await selectDockerContainer();
3050
+ if (!container) {
3051
+ console.log(chalk.red('❌ 未找到可用的 Docker 容器'));
3052
+ return restartGatewayNative();
3053
+ }
3054
+ console.log(chalk.gray(` [检测] Gateway 运行在 Docker 容器: ${container.name} (${container.image})`));
3055
+
3056
+ const dockerCmds = [];
3057
+ if (container.cli === 'node') {
3058
+ // 脚本路径模式(如 /opt/moltbot/moltbot.mjs)
3059
+ dockerCmds.push(`node ${container.cliPath} gateway restart`);
3060
+ } else {
3061
+ dockerCmds.push(`${container.cli} gateway restart`);
3062
+ }
3063
+ // 通用回退
3064
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3065
+ dockerCmds.push(`${name} gateway restart`);
3066
+ }
3067
+
3068
+ return new Promise((resolve) => {
3069
+ let tried = 0;
3070
+ const tryNext = () => {
3071
+ if (tried >= dockerCmds.length) {
3072
+ console.log(chalk.yellow('Docker 容器内 Gateway 重启失败,尝试本地重启...'));
3073
+ restartGatewayNative().then(resolve);
3074
+ return;
3075
+ }
3076
+ const cmd = dockerCmds[tried++];
3077
+ const fullCmd = dockerCmd(`exec ${container.id} bash -lc "${cmd}"`);
3078
+ exec(fullCmd, { timeout: 30000 }, (error) => {
3079
+ if (error) {
3080
+ tryNext();
3081
+ } else {
3082
+ console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
3083
+ resolve(true);
3084
+ }
3085
+ });
3086
+ };
3087
+ tryNext();
3088
+ });
3089
+ }
3090
+
2788
3091
  // 如果 Gateway 在 WSL 里,优先用 wsl -- 重启
2789
3092
  if (gwEnv === 'wsl') {
2790
3093
  console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
@@ -2828,6 +3131,50 @@ async function restartGateway() {
2828
3131
  async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort = 18789) {
2829
3132
  console.log(chalk.yellow('\n🔄 尝试强制重启 Gateway(杀旧进程 → 启动新进程)...'));
2830
3133
 
3134
+ // Docker 容器内强制重启
3135
+ if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
3136
+ const cid = _selectedDockerContainer.id;
3137
+ const cName = _selectedDockerContainer.name;
3138
+ console.log(chalk.gray(` [Docker] 在容器 ${cName} 内强制重启...`));
3139
+
3140
+ // 1. 杀掉容器内旧 Gateway 进程
3141
+ try {
3142
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3143
+ safeExec(dockerCmd(`exec ${cid} bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
3144
+ }
3145
+ safeExec(dockerCmd(`exec ${cid} bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
3146
+ console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
3147
+ } catch { /* ignore */ }
3148
+
3149
+ await new Promise(resolve => setTimeout(resolve, 2000));
3150
+
3151
+ // 2. 启动新 Gateway
3152
+ const dockerCmds = [];
3153
+ if (_selectedDockerContainer.cli === 'node') {
3154
+ dockerCmds.push(`node ${_selectedDockerContainer.cliPath} gateway`);
3155
+ } else {
3156
+ dockerCmds.push(`${_selectedDockerContainer.cli} gateway`);
3157
+ }
3158
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3159
+ dockerCmds.push(`${name} gateway`);
3160
+ }
3161
+
3162
+ for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
3163
+ console.log(chalk.gray(` 尝试在容器内启动: ${cmd}`));
3164
+ if (spawnDetachedInDocker(cid, cmd)) {
3165
+ if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
3166
+ console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${cName} 内强制重启成功`));
3167
+ return true;
3168
+ }
3169
+ }
3170
+ }
3171
+
3172
+ console.log(chalk.red('❌ Docker 容器内强制重启也失败了'));
3173
+ console.log(chalk.gray(` 请手动进入容器重启: docker exec -it ${cid} bash`));
3174
+ console.log(chalk.gray(` 然后执行: openclaw gateway / clawdbot gateway / moltbot gateway`));
3175
+ return false;
3176
+ }
3177
+
2831
3178
  // 1. 杀掉旧 Gateway 进程
2832
3179
  try {
2833
3180
  if (process.platform === 'win32') {
@@ -2952,6 +3299,21 @@ async function restartGatewayNative() {
2952
3299
  }
2953
3300
  }
2954
3301
 
3302
+ // Docker: 追加 Docker 容器命令作为额外回退
3303
+ if (isDockerAvailable()) {
3304
+ const dockerContainers = findOpenclawDockerContainers();
3305
+ for (const c of dockerContainers) {
3306
+ if (c.cli === 'node') {
3307
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath} gateway restart"`));
3308
+ } else if (c.cli !== 'unknown') {
3309
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} gateway restart"`));
3310
+ }
3311
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3312
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} gateway restart"`));
3313
+ }
3314
+ }
3315
+ }
3316
+
2955
3317
  return new Promise((resolve) => {
2956
3318
  let tried = 0;
2957
3319
 
@@ -2969,6 +3331,18 @@ async function restartGatewayNative() {
2969
3331
  const which = safeExec(process.platform === 'win32' ? `where ${name} 2>nul` : `/bin/zsh -lc "command -v ${name}" 2>/dev/null || /bin/bash -lc "command -v ${name}" 2>/dev/null`);
2970
3332
  if (which.ok && which.output) console.log(chalk.gray(` [诊断] ${name} -> ${which.output.split('\n')[0].trim()}`));
2971
3333
  }
3334
+ // Docker 诊断
3335
+ if (isDockerAvailable()) {
3336
+ const dContainers = findOpenclawDockerContainers();
3337
+ if (dContainers.length > 0) {
3338
+ console.log(chalk.gray(` [诊断] Docker 容器 (含 openclaw/clawdbot/moltbot):`));
3339
+ for (const c of dContainers) {
3340
+ console.log(chalk.gray(` - ${c.name} (${c.image}) [${c.cli}: ${c.cliPath}]`));
3341
+ }
3342
+ } else {
3343
+ console.log(chalk.gray(` [诊断] Docker 可用,但未找到含 openclaw/clawdbot/moltbot 的容器`));
3344
+ }
3345
+ }
2972
3346
  // 尝试强制重启
2973
3347
  forceRestartGateway(resolved, nodeInfo, useNode, env).then(resolve);
2974
3348
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {