yymaxapi 1.0.27 → 1.0.29

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 +108 -41
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -721,46 +721,87 @@ let _selectedDockerContainer = null;
721
721
 
722
722
  function isDockerAvailable() {
723
723
  if (_dockerAvailCache !== null) return _dockerAvailCache;
724
- const r = safeExec('docker info', { timeout: 5000 });
725
- _dockerAvailCache = r.ok;
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;
726
732
  return _dockerAvailCache;
727
733
  }
728
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
+
729
745
  // 查找包含 openclaw/clawdbot/moltbot 的运行中容器
730
746
  function findOpenclawDockerContainers() {
731
747
  if (_dockerContainerCache !== null) return _dockerContainerCache;
732
748
  if (!isDockerAvailable()) { _dockerContainerCache = []; return []; }
733
749
 
734
- const ps = safeExec('docker ps --format "{{.ID}}\\t{{.Names}}\\t{{.Image}}\\t{{.Status}}"', { timeout: 10000 });
750
+ const ps = safeExec(dockerCmd('ps --format "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"'), { timeout: 10000 });
735
751
  if (!ps.ok || !ps.output) { _dockerContainerCache = []; return []; }
736
752
 
737
753
  const containers = [];
738
754
  const lines = ps.output.split('\n').filter(Boolean);
739
755
 
740
756
  for (const line of lines) {
741
- const [id, name, image, ...statusParts] = line.split('\t');
742
- const status = statusParts.join('\t');
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(' ');
743
761
  if (!id) continue;
744
762
 
745
- // 检查容器内是否有 openclaw/clawdbot/moltbot
763
+ // 检查容器内是否有 openclaw/clawdbot/moltbot(用 command -v 代替 which,兼容精简镜像)
746
764
  for (const cli of ['openclaw', 'clawdbot', 'moltbot']) {
747
- const check = safeExec(`docker exec ${id} which ${cli} 2>/dev/null`, { timeout: 5000 });
748
- if (check.ok && check.output) {
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()) {
749
767
  containers.push({ id, name, image, status, cli, cliPath: check.output.trim() });
750
- break; // 一个容器只记录一次
768
+ break;
751
769
  }
752
770
  }
753
771
 
754
- // 也检查 /opt/moltbot 等常见路径
772
+ // 也检查常见路径(不依赖 which/command -v)
755
773
  if (!containers.find(c => c.id === id)) {
756
- for (const p of ['/opt/moltbot/moltbot.mjs', '/opt/moltbot/bin/moltbot.mjs']) {
757
- const check = safeExec(`docker exec ${id} test -f ${p} && echo found`, { timeout: 5000 });
758
- if (check.ok && check.output && check.output.includes('found')) {
759
- containers.push({ id, name, image, status, cli: 'node', cliPath: p });
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 });
760
789
  break;
761
790
  }
762
791
  }
763
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
+ }
764
805
  }
765
806
 
766
807
  _dockerContainerCache = containers;
@@ -800,16 +841,27 @@ async function selectDockerContainer() {
800
841
  // 在 Docker 容器内执行命令
801
842
  function execInDocker(containerId, cmd, options = {}) {
802
843
  const escaped = cmd.replace(/'/g, "'\\''");
803
- return safeExec(`docker exec ${containerId} bash -lc '${escaped}'`, { timeout: 30000, ...options });
844
+ // 尝试 sh -c,fallback bash -lc
845
+ const r = safeExec(dockerCmd(`exec ${containerId} sh -c '${escaped}'`), { timeout: 30000, ...options });
846
+ if (r.ok) return r;
847
+ return safeExec(dockerCmd(`exec ${containerId} bash -lc '${escaped}'`), { timeout: 30000, ...options });
804
848
  }
805
849
 
806
850
  // 在 Docker 容器内异步执行命令(后台启动 gateway 等)
807
- function spawnDetachedInDocker(containerId, cmd, env = {}) {
851
+ // shell 参数: 'sh -c' | 'bash -lc' | 'bash -c'
852
+ function spawnDetachedInDocker(containerId, cmd, shell = 'sh -c') {
808
853
  try {
809
- const child = spawn('docker', ['exec', '-d', containerId, 'bash', '-lc', cmd], {
854
+ const shellParts = shell.split(' ');
855
+ const shellBin = shellParts[0];
856
+ const shellFlag = shellParts.slice(1).join(' ');
857
+ const prefix = _dockerNeedsSudo ? 'sudo' : 'docker';
858
+ const args = _dockerNeedsSudo
859
+ ? ['-n', 'docker', 'exec', '-d', containerId, shellBin, shellFlag, cmd]
860
+ : ['exec', '-d', containerId, shellBin, shellFlag, cmd];
861
+ const child = spawn(prefix, args.filter(Boolean), {
810
862
  detached: true,
811
863
  stdio: 'ignore',
812
- env: { ...process.env, ...env }
864
+ env: { ...process.env }
813
865
  });
814
866
  child.unref();
815
867
  return true;
@@ -835,8 +887,8 @@ function syncConfigToDocker(localConfigPath, containerId) {
835
887
  // 先确定容器内哪个配置目录存在
836
888
  for (const target of targets) {
837
889
  const dir = path.posix.dirname(target);
838
- safeExec(`docker exec ${containerId} mkdir -p "${dir}"`, { timeout: 5000 });
839
- const cp = safeExec(`docker cp "${localConfigPath}" ${containerId}:${target}`, { timeout: 10000 });
890
+ safeExec(dockerCmd(`exec ${containerId} mkdir -p "${dir}"`), { timeout: 5000 });
891
+ const cp = safeExec(dockerCmd(`cp "${localConfigPath}" ${containerId}:${target}`), { timeout: 10000 });
840
892
  if (cp.ok) {
841
893
  console.log(chalk.gray(` 已同步配置到 Docker 容器: ${target}`));
842
894
  return;
@@ -966,7 +1018,7 @@ function execAsyncInGatewayEnv(cmd, options = {}) {
966
1018
  }
967
1019
  if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
968
1020
  const escaped = cmd.replace(/'/g, "'\\''");
969
- return { cmd: `docker exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`, options };
1021
+ return { cmd: dockerCmd(`exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`), options };
970
1022
  }
971
1023
  return { cmd, options };
972
1024
  }
@@ -1013,7 +1065,7 @@ function cleanupAgentProcesses() {
1013
1065
  if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
1014
1066
  try {
1015
1067
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
1016
- safeExec(`docker exec ${_selectedDockerContainer.id} bash -c "pkill -f '${name}.*agent' 2>/dev/null || true"`, { timeout: 10000 });
1068
+ safeExec(dockerCmd(`exec ${_selectedDockerContainer.id} sh -c "pkill -f '${name}.*agent' 2>/dev/null || true"`), { timeout: 10000 });
1017
1069
  }
1018
1070
  } catch { /* ignore */ }
1019
1071
  }
@@ -1658,6 +1710,7 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
1658
1710
  if (container.cli === 'node') {
1659
1711
  dockerCmds.push(`node ${container.cliPath} gateway`);
1660
1712
  } else {
1713
+ if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway`);
1661
1714
  dockerCmds.push(`${container.cli} gateway`);
1662
1715
  }
1663
1716
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
@@ -1666,10 +1719,13 @@ async function tryAutoStartGateway(port, allowAutoDaemon) {
1666
1719
 
1667
1720
  for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
1668
1721
  console.log(chalk.yellow(`⚠️ 尝试在容器内启动 Gateway: ${cmd}`));
1669
- if (spawnDetachedInDocker(container.id, cmd)) {
1670
- if (await waitForGateway(port, '127.0.0.1', 15000)) {
1671
- console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${container.name} 内启动`));
1672
- return { started: true, method: 'docker', container: container.name };
1722
+ // 尝试多种 shell
1723
+ for (const shell of ['sh -c', 'bash -lc', 'bash -c']) {
1724
+ if (spawnDetachedInDocker(container.id, cmd, shell)) {
1725
+ if (await waitForGateway(port, '127.0.0.1', 15000)) {
1726
+ console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${container.name} 内启动`));
1727
+ return { started: true, method: 'docker', container: container.name };
1728
+ }
1673
1729
  }
1674
1730
  }
1675
1731
  }
@@ -3010,26 +3066,34 @@ async function restartGateway() {
3010
3066
 
3011
3067
  const dockerCmds = [];
3012
3068
  if (container.cli === 'node') {
3013
- // 脚本路径模式(如 /opt/moltbot/moltbot.mjs)
3014
3069
  dockerCmds.push(`node ${container.cliPath} gateway restart`);
3015
3070
  } else {
3071
+ if (container.cliPath) dockerCmds.push(`${container.cliPath} gateway restart`);
3016
3072
  dockerCmds.push(`${container.cli} gateway restart`);
3017
3073
  }
3018
- // 通用回退
3019
3074
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3020
3075
  dockerCmds.push(`${name} gateway restart`);
3021
3076
  }
3022
3077
 
3078
+ // 每个命令尝试多种 shell(sh -c / bash -lc / bash -c)
3079
+ const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
3080
+
3023
3081
  return new Promise((resolve) => {
3082
+ // 展开为 [cmd1+sh, cmd1+bash-lc, cmd1+bash-c, cmd2+sh, ...]
3083
+ const allAttempts = [];
3084
+ for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
3085
+ for (const shell of shellVariants) {
3086
+ allAttempts.push(dockerCmd(`exec ${container.id} ${shell} "${cmd}"`));
3087
+ }
3088
+ }
3024
3089
  let tried = 0;
3025
3090
  const tryNext = () => {
3026
- if (tried >= dockerCmds.length) {
3091
+ if (tried >= allAttempts.length) {
3027
3092
  console.log(chalk.yellow('Docker 容器内 Gateway 重启失败,尝试本地重启...'));
3028
3093
  restartGatewayNative().then(resolve);
3029
3094
  return;
3030
3095
  }
3031
- const cmd = dockerCmds[tried++];
3032
- const fullCmd = `docker exec ${container.id} bash -lc "${cmd}"`;
3096
+ const fullCmd = allAttempts[tried++];
3033
3097
  exec(fullCmd, { timeout: 30000 }, (error) => {
3034
3098
  if (error) {
3035
3099
  tryNext();
@@ -3095,9 +3159,9 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
3095
3159
  // 1. 杀掉容器内旧 Gateway 进程
3096
3160
  try {
3097
3161
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3098
- safeExec(`docker exec ${cid} bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
3162
+ safeExec(dockerCmd(`exec ${cid} sh -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
3099
3163
  }
3100
- safeExec(`docker exec ${cid} bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
3164
+ safeExec(dockerCmd(`exec ${cid} sh -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
3101
3165
  console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
3102
3166
  } catch { /* ignore */ }
3103
3167
 
@@ -3108,6 +3172,7 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
3108
3172
  if (_selectedDockerContainer.cli === 'node') {
3109
3173
  dockerCmds.push(`node ${_selectedDockerContainer.cliPath} gateway`);
3110
3174
  } else {
3175
+ if (_selectedDockerContainer.cliPath) dockerCmds.push(`${_selectedDockerContainer.cliPath} gateway`);
3111
3176
  dockerCmds.push(`${_selectedDockerContainer.cli} gateway`);
3112
3177
  }
3113
3178
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
@@ -3116,10 +3181,12 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
3116
3181
 
3117
3182
  for (const cmd of [...new Set(dockerCmds)].filter(Boolean)) {
3118
3183
  console.log(chalk.gray(` 尝试在容器内启动: ${cmd}`));
3119
- if (spawnDetachedInDocker(cid, cmd)) {
3120
- if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
3121
- console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${cName} 内强制重启成功`));
3122
- return true;
3184
+ for (const shell of ['sh -c', 'bash -lc', 'bash -c']) {
3185
+ if (spawnDetachedInDocker(cid, cmd, shell)) {
3186
+ if (await waitForGateway(gatewayPort, '127.0.0.1', 12000)) {
3187
+ console.log(chalk.green(`✅ Gateway 已在 Docker 容器 ${cName} 内强制重启成功`));
3188
+ return true;
3189
+ }
3123
3190
  }
3124
3191
  }
3125
3192
  }
@@ -3259,12 +3326,12 @@ async function restartGatewayNative() {
3259
3326
  const dockerContainers = findOpenclawDockerContainers();
3260
3327
  for (const c of dockerContainers) {
3261
3328
  if (c.cli === 'node') {
3262
- commands.push(`docker exec ${c.id} bash -lc "node ${c.cliPath} gateway restart"`);
3263
- } else {
3264
- commands.push(`docker exec ${c.id} bash -lc "${c.cli} gateway restart"`);
3329
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath} gateway restart"`));
3330
+ } else if (c.cli !== 'unknown') {
3331
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} gateway restart"`));
3265
3332
  }
3266
3333
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3267
- commands.push(`docker exec ${c.id} bash -lc "${name} gateway restart"`);
3334
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} gateway restart"`));
3268
3335
  }
3269
3336
  }
3270
3337
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {