yymaxapi 1.0.27 → 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 +72 -27
  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,13 +841,17 @@ 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
+ return safeExec(dockerCmd(`exec ${containerId} bash -lc '${escaped}'`), { timeout: 30000, ...options });
804
845
  }
805
846
 
806
847
  // 在 Docker 容器内异步执行命令(后台启动 gateway 等)
807
848
  function spawnDetachedInDocker(containerId, cmd, env = {}) {
808
849
  try {
809
- const child = spawn('docker', ['exec', '-d', containerId, 'bash', '-lc', cmd], {
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, {
810
855
  detached: true,
811
856
  stdio: 'ignore',
812
857
  env: { ...process.env, ...env }
@@ -835,8 +880,8 @@ function syncConfigToDocker(localConfigPath, containerId) {
835
880
  // 先确定容器内哪个配置目录存在
836
881
  for (const target of targets) {
837
882
  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 });
883
+ safeExec(dockerCmd(`exec ${containerId} mkdir -p "${dir}"`), { timeout: 5000 });
884
+ const cp = safeExec(dockerCmd(`cp "${localConfigPath}" ${containerId}:${target}`), { timeout: 10000 });
840
885
  if (cp.ok) {
841
886
  console.log(chalk.gray(` 已同步配置到 Docker 容器: ${target}`));
842
887
  return;
@@ -966,7 +1011,7 @@ function execAsyncInGatewayEnv(cmd, options = {}) {
966
1011
  }
967
1012
  if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
968
1013
  const escaped = cmd.replace(/'/g, "'\\''");
969
- return { cmd: `docker exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`, options };
1014
+ return { cmd: dockerCmd(`exec ${_selectedDockerContainer.id} bash -lc '${escaped}'`), options };
970
1015
  }
971
1016
  return { cmd, options };
972
1017
  }
@@ -1013,7 +1058,7 @@ function cleanupAgentProcesses() {
1013
1058
  if (detectGatewayEnv() === 'docker' && _selectedDockerContainer) {
1014
1059
  try {
1015
1060
  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 });
1061
+ safeExec(dockerCmd(`exec ${_selectedDockerContainer.id} bash -c "pkill -f '${name}.*agent' 2>/dev/null || true"`), { timeout: 10000 });
1017
1062
  }
1018
1063
  } catch { /* ignore */ }
1019
1064
  }
@@ -3029,7 +3074,7 @@ async function restartGateway() {
3029
3074
  return;
3030
3075
  }
3031
3076
  const cmd = dockerCmds[tried++];
3032
- const fullCmd = `docker exec ${container.id} bash -lc "${cmd}"`;
3077
+ const fullCmd = dockerCmd(`exec ${container.id} bash -lc "${cmd}"`);
3033
3078
  exec(fullCmd, { timeout: 30000 }, (error) => {
3034
3079
  if (error) {
3035
3080
  tryNext();
@@ -3095,9 +3140,9 @@ async function forceRestartGateway(resolved, nodeInfo, useNode, env, gatewayPort
3095
3140
  // 1. 杀掉容器内旧 Gateway 进程
3096
3141
  try {
3097
3142
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3098
- safeExec(`docker exec ${cid} bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`, { timeout: 5000 });
3143
+ safeExec(dockerCmd(`exec ${cid} bash -c "pkill -f '${name}.*gateway' 2>/dev/null || true"`), { timeout: 5000 });
3099
3144
  }
3100
- safeExec(`docker exec ${cid} bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`, { timeout: 5000 });
3145
+ safeExec(dockerCmd(`exec ${cid} bash -c "lsof -ti :${gatewayPort} 2>/dev/null | xargs -r kill -9 2>/dev/null || true"`), { timeout: 5000 });
3101
3146
  console.log(chalk.gray(' 已尝试清理容器内旧 Gateway 进程'));
3102
3147
  } catch { /* ignore */ }
3103
3148
 
@@ -3259,12 +3304,12 @@ async function restartGatewayNative() {
3259
3304
  const dockerContainers = findOpenclawDockerContainers();
3260
3305
  for (const c of dockerContainers) {
3261
3306
  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"`);
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"`));
3265
3310
  }
3266
3311
  for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
3267
- commands.push(`docker exec ${c.id} bash -lc "${name} gateway restart"`);
3312
+ commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} gateway restart"`));
3268
3313
  }
3269
3314
  }
3270
3315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {