@xcanwin/manyoyo 3.7.0 → 3.7.5

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/README.md CHANGED
@@ -53,7 +53,7 @@ podman pull ubuntu:24.04
53
53
 
54
54
  ```bash
55
55
  # 使用 manyoyo 构建镜像(推荐,自动使用缓存加速)
56
- manyoyo --ib # 默认构建 full 版本(推荐)
56
+ manyoyo --ib --iv 1.6.4 # 默认构建 full 版本(推荐,建议指定版本号)
57
57
  manyoyo --ib --iba TOOL=common # 构建常见组件版本(python,nodejs,claude)
58
58
  manyoyo --ib --iba TOOL=go,codex,java,gemini # 构建自定义组件版本
59
59
  manyoyo --ib --iba GIT_SSL_NO_VERIFY=true # 构建 full 版本且跳过git的ssl验证
@@ -85,7 +85,10 @@ manyoyo -l
85
85
  manyoyo -n test --ef .env -y c
86
86
 
87
87
  # 恢复现有会话
88
- manyoyo -n test -- -c
88
+ manyoyo -n test -- -c # Claude Code
89
+ manyoyo -n test -- resume --last # Codex
90
+ manyoyo -n test -- -r # Gemini
91
+ manyoyo -n test -- -c # OpenCode
89
92
 
90
93
  # 在交互式 shell 中执行命令
91
94
  manyoyo -n test -x /bin/bash
@@ -120,7 +123,7 @@ manyoyo -e "ANTHROPIC_BASE_URL=https://xxxx" -e "ANTHROPIC_AUTH_TOKEN=your-key"
120
123
 
121
124
  ```bash
122
125
  export ANTHROPIC_BASE_URL="https://xxxx"
123
- AUTH_TOANTHROPIC_AUTH_TOKENKEN=your-key
126
+ AUTH_TOANTHROPIC_AUTH_TOKEN=your-key
124
127
  # MESSAGE="Hello World" # 注释会被忽略
125
128
  TESTPATH='/usr/local/bin'
126
129
  ```
@@ -171,7 +174,7 @@ manyoyo --ef claude -x claude
171
174
  "hostPath": "/path/to/project", // 默认宿主机工作目录
172
175
  "containerPath": "/path/to/project", // 默认容器工作目录
173
176
  "imageName": "localhost/xcanwin/manyoyo", // 默认镜像名称
174
- "imageVersion": "1.6.3-full", // 默认镜像版本
177
+ "imageVersion": "1.6.4-full", // 默认镜像版本
175
178
  "containerMode": "common", // 容器嵌套模式 (common, dind, sock)
176
179
 
177
180
  // 环境变量配置
@@ -199,18 +202,26 @@ manyoyo --ef claude -x claude
199
202
 
200
203
  合并型参数包括:`envFile`, `env`, `volumes`, `imageBuildArgs`
201
204
 
202
- #### 常用样例
205
+ #### 常用样例-全局
206
+
207
+ ```bash
208
+ mkdir -p ~/.manyoyo/
209
+
210
+ cat > ~/.manyoyo/manyoyo.json << 'EOF'
211
+ {
212
+ "imageName": "localhost/xcanwin/manyoyo",
213
+ "imageVersion": "1.6.4-full"
214
+ }
215
+ EOF
216
+ ```
217
+
218
+ #### 常用样例-Claude Code
203
219
 
204
220
  ```bash
205
- # 创建运行配置目录
206
221
  mkdir -p ~/.manyoyo/run/
207
222
 
208
- # 创建 Claude 运行配置
209
223
  cat > ~/.manyoyo/run/c.json << 'EOF'
210
224
  {
211
- // Claude Code 快捷配置
212
- "imageName": "localhost/xcanwin/manyoyo",
213
- "imageVersion": "1.6.3-full",
214
225
  "envFile": [
215
226
  "claude" // 自动加载 ~/.manyoyo/env/claude.env
216
227
  ],
@@ -222,6 +233,24 @@ EOF
222
233
  manyoyo -r c
223
234
  ```
224
235
 
236
+ #### 常用样例-Codex
237
+
238
+ ```bash
239
+ mkdir -p ~/.manyoyo/run/
240
+
241
+ cat > ~/.manyoyo/run/codex.json << 'EOF'
242
+ {
243
+ "volumes": [
244
+ "/Users/mac_user/.codex/auth.json:/root/.codex/auth.json"
245
+ ],
246
+ "yolo": "cx"
247
+ }
248
+ EOF
249
+
250
+ # 在任意目录下使用运行配置
251
+ manyoyo -r codex
252
+ ```
253
+
225
254
  ### AI CLI 快捷方式(跳过权限确认)
226
255
 
227
256
  ```bash
@@ -280,11 +309,11 @@ docker ps -a # 现在可以在容器内使用 docker 命令
280
309
  | 选项 | 别名 | 描述 |
281
310
  |------|------|------|
282
311
  | `--hp PATH` | `--host-path` | 设置宿主机工作目录(默认:当前路径) |
283
- | `-n NAME` | `--cn`, `--cont-name` | 设置容器名称 |
312
+ | `-n NAME` | `--cont-name` | 设置容器名称 |
284
313
  | `--cp PATH` | `--cont-path` | 设置容器工作目录 |
285
- | `-l` | `--cl`, `--cont-list` | 列出所有 manyoyo 容器 |
314
+ | `-l` | `--cont-list` | 列出所有 manyoyo 容器 |
286
315
  | `--crm` | `--cont-remove` | 删除容器 |
287
- | `-m MODE` | `--cm`, `--cont-mode` | 设置容器模式(common, dind, sock) |
316
+ | `-m MODE` | `--cont-mode` | 设置容器模式(common, dind, sock) |
288
317
  | `--in NAME` | `--image-name` | 指定镜像名称 |
289
318
  | `--iv VERSION` | `--image-ver` | 指定镜像版本 |
290
319
  | `--ib` | `--image-build` | 构建镜像 |
@@ -298,6 +327,8 @@ docker ps -a # 现在可以在容器内使用 docker 命令
298
327
  | `--` | `--ss`, `--shell-suffix` | 命令参数(作为 -s 的后缀) |
299
328
  | `-x CMD` | `--sf`, `--shell-full` | 完整命令(替代 --sp, -s 和 --) |
300
329
  | `-y CLI` | `--yolo` | 无需确认运行 AI 智能体 |
330
+ | `--show-config` | | 显示最终生效配置并退出 |
331
+ | `--show-command` | | 显示将执行的命令并退出(存在容器时为 docker exec,不存在时为 docker run) |
301
332
  | `--install NAME` | | 安装 manyoyo 命令 |
302
333
  | `-q LIST` | `--quiet` | 静默显示 |
303
334
  | `-r NAME` | `--run` | 加载运行配置(支持 `name` 或 `./path.json`) |
package/bin/manyoyo.js CHANGED
@@ -41,6 +41,7 @@ let CONTAINER_VOLUMES = [];
41
41
  let MANYOYO_NAME = "manyoyo";
42
42
  let CONT_MODE = "";
43
43
  let QUIET = {};
44
+ let SHOW_COMMAND = false;
44
45
 
45
46
  // Color definitions using ANSI codes
46
47
  const RED = '\x1b[0;31m';
@@ -160,6 +161,14 @@ function setQuiet(actions) {
160
161
  });
161
162
  }
162
163
 
164
+ function validateName(label, value, pattern) {
165
+ if (!value) return;
166
+ if (!pattern.test(value)) {
167
+ console.error(`${RED}⚠️ 错误: ${label} 非法: ${value}${NC}`);
168
+ process.exit(1);
169
+ }
170
+ }
171
+
163
172
  async function askQuestion(prompt) {
164
173
  const rl = readline.createInterface({
165
174
  input: process.stdin,
@@ -179,6 +188,21 @@ async function askQuestion(prompt) {
179
188
  // ==============================================================================
180
189
 
181
190
  function addEnv(env) {
191
+ const idx = env.indexOf('=');
192
+ if (idx <= 0) {
193
+ console.error(`${RED}⚠️ 错误: env 格式应为 KEY=VALUE: ${env}${NC}`);
194
+ process.exit(1);
195
+ }
196
+ const key = env.slice(0, idx);
197
+ const value = env.slice(idx + 1);
198
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
199
+ console.error(`${RED}⚠️ 错误: env key 非法: ${key}${NC}`);
200
+ process.exit(1);
201
+ }
202
+ if (/[\r\n\0]/.test(value) || /[;&|`$<>]/.test(value)) {
203
+ console.error(`${RED}⚠️ 错误: env value 含非法字符: ${key}${NC}`);
204
+ process.exit(1);
205
+ }
182
206
  CONTAINER_ENVS.push("--env", env);
183
207
  }
184
208
 
@@ -208,7 +232,8 @@ function addEnvFile(envFile) {
208
232
  let value = match[2].trim();
209
233
 
210
234
  // Filter malicious characters
211
- if (/[\$\(\)\`\|\&\*\{\}]/.test(value)) continue;
235
+ if (/[\r\n\0]/.test(value)) continue;
236
+ if (/[\$\(\)\`\|\&\*\{\};<>]/.test(value)) continue;
212
237
  if (/^\(/.test(value)) continue;
213
238
 
214
239
  // Remove quotes
@@ -247,11 +272,11 @@ function setYolo(cli) {
247
272
  break;
248
273
  case 'codex':
249
274
  case 'cx':
250
- EXEC_COMMAND = "codex";
275
+ EXEC_COMMAND = "codex --dangerously-bypass-approvals-and-sandbox";
251
276
  break;
252
277
  case 'opencode':
253
278
  case 'oc':
254
- EXEC_COMMAND = "opencode";
279
+ EXEC_COMMAND = "OPENCODE_PERMISSION=allow opencode";
255
280
  break;
256
281
  default:
257
282
  console.log(`${RED}⚠️ 未知LLM CLI: ${cli}${NC}`);
@@ -297,18 +322,40 @@ function dockerExec(cmd, options = {}) {
297
322
  }
298
323
  }
299
324
 
325
+ function runCmd(cmd, args, options = {}) {
326
+ const result = spawnSync(cmd, args, { encoding: 'utf-8', ...options });
327
+ if (result.error) {
328
+ throw result.error;
329
+ }
330
+ if (result.status !== 0) {
331
+ if (options.ignoreError) {
332
+ return result.stdout || '';
333
+ }
334
+ const err = new Error(`Command failed: ${cmd} ${args.join(' ')}`);
335
+ err.stdout = result.stdout;
336
+ err.stderr = result.stderr;
337
+ err.status = result.status;
338
+ throw err;
339
+ }
340
+ return result.stdout || '';
341
+ }
342
+
343
+ function dockerExecArgs(args, options = {}) {
344
+ return runCmd(DOCKER_CMD, args, options);
345
+ }
346
+
300
347
  function containerExists(name) {
301
- const containers = dockerExec(`${DOCKER_CMD} ps -a --format '{{.Names}}'`);
348
+ const containers = dockerExecArgs(['ps', '-a', '--format', '{{.Names}}']);
302
349
  return containers.split('\n').some(n => n.trim() === name);
303
350
  }
304
351
 
305
352
  function getContainerStatus(name) {
306
- return dockerExec(`${DOCKER_CMD} inspect -f '{{.State.Status}}' "${name}"`).trim();
353
+ return dockerExecArgs(['inspect', '-f', '{{.State.Status}}', name]).trim();
307
354
  }
308
355
 
309
356
  function removeContainer(name) {
310
357
  if ( !(QUIET.crm || QUIET.full) ) console.log(`${YELLOW}🗑️ 正在删除容器: ${name}...${NC}`);
311
- dockerExec(`${DOCKER_CMD} rm -f "${name}"`, { stdio: 'pipe' });
358
+ dockerExecArgs(['rm', '-f', name], { stdio: 'pipe' });
312
359
  if ( !(QUIET.crm || QUIET.full) ) console.log(`${GREEN}✅ 已彻底删除。${NC}`);
313
360
  }
314
361
 
@@ -320,7 +367,7 @@ function ensureDocker() {
320
367
  const commands = ['docker', 'podman'];
321
368
  for (const cmd of commands) {
322
369
  try {
323
- execSync(`${cmd} --version`, { stdio: 'pipe' });
370
+ runCmd(cmd, ['--version'], { stdio: 'pipe' });
324
371
  DOCKER_CMD = cmd;
325
372
  return true;
326
373
  } catch (e) {
@@ -361,11 +408,11 @@ function getContList() {
361
408
 
362
409
  function pruneDanglingImages() {
363
410
  console.log(`\n${YELLOW}清理悬空镜像...${NC}`);
364
- execSync(`${DOCKER_CMD} image prune -f`, { stdio: 'inherit' });
411
+ dockerExecArgs(['image', 'prune', '-f'], { stdio: 'inherit' });
365
412
 
366
413
  // Remove remaining <none> images
367
414
  try {
368
- const imagesOutput = execSync(`${DOCKER_CMD} images -a --format "{{.ID}} {{.Repository}}"`, { encoding: 'utf-8' });
415
+ const imagesOutput = dockerExecArgs(['images', '-a', '--format', '{{.ID}} {{.Repository}}']);
369
416
  const noneImages = imagesOutput
370
417
  .split('\n')
371
418
  .filter(line => line.includes('<none>'))
@@ -374,7 +421,7 @@ function pruneDanglingImages() {
374
421
 
375
422
  if (noneImages.length > 0) {
376
423
  console.log(`${YELLOW}清理剩余的 <none> 镜像 (${noneImages.length} 个)...${NC}`);
377
- execSync(`${DOCKER_CMD} rmi -f ${noneImages.join(' ')}`, { stdio: 'inherit' });
424
+ dockerExecArgs(['rmi', '-f', ...noneImages], { stdio: 'inherit' });
378
425
  }
379
426
  } catch (e) {
380
427
  // Ignore errors if no <none> images found
@@ -434,7 +481,7 @@ async function prepareBuildCache(imageTool) {
434
481
  const shasum = execSync(`curl -sL ${mirror}/latest-v${nodeVersion}.x/SHASUMS256.txt | grep linux-${archNode}.tar.gz | awk '{print $2}'`, { encoding: 'utf-8' }).trim();
435
482
  const nodeUrl = `${mirror}/latest-v${nodeVersion}.x/${shasum}`;
436
483
  const nodeTargetPath = path.join(nodeCacheDir, shasum);
437
- execSync(`curl -fsSL "${nodeUrl}" -o "${nodeTargetPath}"`, { stdio: 'inherit' });
484
+ runCmd('curl', ['-fsSL', nodeUrl, '-o', nodeTargetPath], { stdio: 'inherit' });
438
485
  timestamps[nodeKey] = now.toISOString();
439
486
  fs.writeFileSync(timestampFile, JSON.stringify(timestamps, null, 2));
440
487
  console.log(`${GREEN}✓ Node.js 下载完成${NC}`);
@@ -458,15 +505,23 @@ async function prepareBuildCache(imageTool) {
458
505
 
459
506
  if (!fs.existsSync(jdtlsPath) || isExpired(jdtlsKey)) {
460
507
  console.log(`${YELLOW}下载 JDT Language Server...${NC}`);
461
- const jdtUrl = 'https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz';
508
+ const apkUrl = 'https://mirrors.tencent.com/alpine/latest-stable/community/x86_64/jdtls-1.53.0-r0.apk';
509
+ const tmpDir = path.join(jdtlsCacheDir, '.tmp-apk');
510
+ const apkPath = path.join(tmpDir, 'jdtls.apk');
462
511
  try {
463
- execSync(`curl -fsSL "${jdtUrl}" -o "${jdtlsPath}"`, { stdio: 'inherit' });
512
+ fs.mkdirSync(tmpDir, { recursive: true });
513
+ runCmd('curl', ['-fsSL', apkUrl, '-o', apkPath], { stdio: 'inherit' });
514
+ runCmd('tar', ['-xzf', apkPath, '-C', tmpDir], { stdio: 'inherit' });
515
+ const srcDir = path.join(tmpDir, 'usr', 'share', 'jdtls');
516
+ runCmd('tar', ['-czf', jdtlsPath, '-C', srcDir, '.'], { stdio: 'inherit' });
464
517
  timestamps[jdtlsKey] = now.toISOString();
465
518
  fs.writeFileSync(timestampFile, JSON.stringify(timestamps, null, 2));
466
519
  console.log(`${GREEN}✓ JDT LSP 下载完成${NC}`);
467
520
  } catch (e) {
468
521
  console.error(`${RED}错误: JDT LSP 下载失败${NC}`);
469
522
  throw e;
523
+ } finally {
524
+ try { runCmd('rm', ['-rf', tmpDir], { stdio: 'inherit', ignoreError: true }); } catch {}
470
525
  }
471
526
  } else {
472
527
  console.log(`${GREEN}✓ JDT LSP 缓存已存在${NC}`);
@@ -501,9 +556,12 @@ async function prepareBuildCache(imageTool) {
501
556
  }
502
557
  fs.mkdirSync(tmpGoPath, { recursive: true });
503
558
 
504
- execSync(`GOPATH="${tmpGoPath}" GOOS=linux GOARCH=${arch} go install golang.org/x/tools/gopls@latest`, { stdio: 'inherit' });
559
+ runCmd('go', ['install', 'golang.org/x/tools/gopls@latest'], {
560
+ stdio: 'inherit',
561
+ env: { ...process.env, GOPATH: tmpGoPath, GOOS: 'linux', GOARCH: arch }
562
+ });
505
563
  execSync(`cp "${tmpGoPath}/bin/linux_${arch}/gopls" "${goplsPath}" || cp "${tmpGoPath}/bin/gopls" "${goplsPath}"`, { stdio: 'inherit' });
506
- execSync(`chmod +x "${goplsPath}"`, { stdio: 'inherit' });
564
+ runCmd('chmod', ['+x', goplsPath], { stdio: 'inherit' });
507
565
 
508
566
  // Save timestamp immediately after successful download
509
567
  timestamps[goplsKey] = now.toISOString();
@@ -642,6 +700,8 @@ function setupCommander() {
642
700
  .option('-x, --shell-full <command...>', '指定完整命令执行 (代替--sp和-s和--命令)')
643
701
  .option('-y, --yolo <cli>', '使AGENT无需确认 (claude/c, gemini/gm, codex/cx, opencode/oc)')
644
702
  .option('--install <name>', '安装manyoyo命令 (docker-cli-plugin)')
703
+ .option('--show-config', '显示最终生效配置并退出')
704
+ .option('--show-command', '显示将执行的 docker run 命令并退出')
645
705
  .option('-q, --quiet <item>', '静默显示 (可多次使用: cnew,crm,tip,cmd,full)', (value, previous) => [...(previous || []), value], []);
646
706
 
647
707
  // Docker CLI plugin metadata check
@@ -693,6 +753,11 @@ function setupCommander() {
693
753
  EXEC_COMMAND = options.shell || runConfig.shell || config.shell;
694
754
  }
695
755
 
756
+ // Basic name validation to reduce injection risk
757
+ validateName('containerName', CONTAINER_NAME, /^[A-Za-z0-9][A-Za-z0-9_.-]*$/);
758
+ validateName('imageName', IMAGE_NAME, /^[A-Za-z0-9][A-Za-z0-9._/:-]*$/);
759
+ validateName('imageVersion', IMAGE_VERSION, /^[A-Za-z0-9][A-Za-z0-9_.-]*$/);
760
+
696
761
  // Merge mode (array values): concatenate all sources
697
762
  const toArray = (val) => Array.isArray(val) ? val : (val ? [val] : []);
698
763
  const envFileList = [
@@ -721,23 +786,58 @@ function setupCommander() {
721
786
  const quietValue = options.quiet || runConfig.quiet || config.quiet;
722
787
  if (quietValue) setQuiet(quietValue);
723
788
 
724
- if (options.contList) { getContList(); process.exit(0); }
725
- if (options.contRemove) SHOULD_REMOVE = true;
726
- if (options.imageBuild) IMAGE_BUILD_NEED = true;
727
- if (options.imageRemove) { pruneDanglingImages(); process.exit(0); }
728
- if (options.install) { installManyoyo(options.install); process.exit(0); }
729
-
730
789
  // Handle shell-full (variadic arguments)
731
790
  if (options.shellFull) {
732
791
  EXEC_COMMAND = options.shellFull.join(' ');
792
+ EXEC_COMMAND_PREFIX = "";
793
+ EXEC_COMMAND_SUFFIX = "";
733
794
  }
734
795
 
735
796
  // Handle -- suffix arguments
736
- const doubleDashIndex = process.argv.indexOf('--');
737
- if (doubleDashIndex !== -1 && doubleDashIndex < process.argv.length - 1) {
738
- EXEC_COMMAND_SUFFIX = " " + process.argv.slice(doubleDashIndex + 1).join(' ');
797
+ if (!options.shellFull) {
798
+ const doubleDashIndex = process.argv.indexOf('--');
799
+ if (doubleDashIndex !== -1 && doubleDashIndex < process.argv.length - 1) {
800
+ EXEC_COMMAND_SUFFIX = " " + process.argv.slice(doubleDashIndex + 1).join(' ');
801
+ }
802
+ }
803
+
804
+ if (options.showConfig) {
805
+ const finalConfig = {
806
+ hostPath: HOST_PATH,
807
+ containerName: CONTAINER_NAME,
808
+ containerPath: CONTAINER_PATH,
809
+ imageName: IMAGE_NAME,
810
+ imageVersion: IMAGE_VERSION,
811
+ envFile: envFileList,
812
+ env: envList,
813
+ volumes: volumeList,
814
+ imageBuildArgs: buildArgList,
815
+ containerMode: contModeValue || "",
816
+ shellPrefix: EXEC_COMMAND_PREFIX.trim(),
817
+ shell: EXEC_COMMAND || "",
818
+ shellSuffix: EXEC_COMMAND_SUFFIX || "",
819
+ yolo: yoloValue || "",
820
+ quiet: quietValue || [],
821
+ exec: {
822
+ prefix: EXEC_COMMAND_PREFIX,
823
+ shell: EXEC_COMMAND,
824
+ suffix: EXEC_COMMAND_SUFFIX
825
+ }
826
+ };
827
+ console.log(JSON.stringify(finalConfig, null, 2));
828
+ process.exit(0);
739
829
  }
740
830
 
831
+ if (options.showCommand) {
832
+ SHOW_COMMAND = true;
833
+ }
834
+
835
+ if (options.contList) { getContList(); process.exit(0); }
836
+ if (options.contRemove) SHOULD_REMOVE = true;
837
+ if (options.imageBuild) IMAGE_BUILD_NEED = true;
838
+ if (options.imageRemove) { pruneDanglingImages(); process.exit(0); }
839
+ if (options.install) { installManyoyo(options.install); process.exit(0); }
840
+
741
841
  return program;
742
842
  }
743
843
 
@@ -757,6 +857,10 @@ function handleRemoveContainer() {
757
857
  }
758
858
 
759
859
  function validateHostPath() {
860
+ if (!fs.existsSync(HOST_PATH)) {
861
+ console.log(`${RED}⚠️ 错误: 宿主机路径不存在: ${HOST_PATH}${NC}`);
862
+ process.exit(1);
863
+ }
760
864
  const realHostPath = fs.realpathSync(HOST_PATH);
761
865
  const homeDir = process.env.HOME || '/home';
762
866
  if (realHostPath === '/' || realHostPath === '/home' || realHostPath === homeDir) {
@@ -778,7 +882,7 @@ async function waitForContainerReady(containerName) {
778
882
 
779
883
  if (status === 'exited') {
780
884
  console.log(`${RED}⚠️ 错误: 容器启动后立即退出。${NC}`);
781
- dockerExec(`${DOCKER_CMD} logs "${containerName}"`, { stdio: 'inherit' });
885
+ dockerExecArgs(['logs', containerName], { stdio: 'inherit' });
782
886
  process.exit(1);
783
887
  }
784
888
 
@@ -787,7 +891,7 @@ async function waitForContainerReady(containerName) {
787
891
 
788
892
  if (count >= MAX_RETRIES) {
789
893
  console.log(`${RED}⚠️ 错误: 容器启动超时(当前状态: ${status})。${NC}`);
790
- dockerExec(`${DOCKER_CMD} logs "${containerName}"`, { stdio: 'inherit' });
894
+ dockerExecArgs(['logs', containerName], { stdio: 'inherit' });
791
895
  process.exit(1);
792
896
  }
793
897
  } catch (e) {
@@ -807,13 +911,12 @@ async function createNewContainer() {
807
911
  EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`;
808
912
  const defaultCommand = EXEC_COMMAND;
809
913
 
810
- // Build docker run command
811
- const fullImage = `${IMAGE_NAME}:${IMAGE_VERSION}`;
812
- const envArgs = CONTAINER_ENVS.join(' ');
813
- const volumeArgs = CONTAINER_VOLUMES.join(' ');
814
- const contModeArg = CONT_MODE || '';
914
+ const dockerRunCmd = buildDockerRunCmd();
815
915
 
816
- const dockerRunCmd = `${DOCKER_CMD} run -d --name "${CONTAINER_NAME}" --entrypoint "" ${contModeArg} ${envArgs} ${volumeArgs} --volume "${HOST_PATH}:${CONTAINER_PATH}" --workdir "${CONTAINER_PATH}" --label "manyoyo.default_cmd=${EXEC_COMMAND}" "${fullImage}" tail -f /dev/null`;
916
+ if (SHOW_COMMAND) {
917
+ console.log(dockerRunCmd);
918
+ process.exit(0);
919
+ }
817
920
 
818
921
  dockerExec(dockerRunCmd, { stdio: 'pipe' });
819
922
 
@@ -823,17 +926,30 @@ async function createNewContainer() {
823
926
  return defaultCommand;
824
927
  }
825
928
 
929
+ function buildDockerRunCmd() {
930
+ // Build docker run command
931
+ const fullImage = `${IMAGE_NAME}:${IMAGE_VERSION}`;
932
+ const envArgs = CONTAINER_ENVS.join(' ');
933
+ const volumeArgs = CONTAINER_VOLUMES.join(' ');
934
+ const contModeArg = CONT_MODE || '';
935
+
936
+ const safeLabelCmd = EXEC_COMMAND.replace(/[\r\n]/g, ' ').replace(/"/g, '\\"');
937
+ const dockerRunCmd = `${DOCKER_CMD} run -d --name "${CONTAINER_NAME}" --entrypoint "" ${contModeArg} ${envArgs} ${volumeArgs} --volume "${HOST_PATH}:${CONTAINER_PATH}" --workdir "${CONTAINER_PATH}" --label "manyoyo.default_cmd=${safeLabelCmd}" "${fullImage}" tail -f /dev/null`;
938
+
939
+ return dockerRunCmd;
940
+ }
941
+
826
942
  async function connectExistingContainer() {
827
943
  if ( !(QUIET.cnew || QUIET.full) ) console.log(`${CYAN}🔄 manyoyo by xcanwin 正在连接到现有容器: ${YELLOW}${CONTAINER_NAME}${NC}`);
828
944
 
829
945
  // Start container if stopped
830
946
  const status = getContainerStatus(CONTAINER_NAME);
831
947
  if (status !== 'running') {
832
- dockerExec(`${DOCKER_CMD} start "${CONTAINER_NAME}"`, { stdio: 'pipe' });
948
+ dockerExecArgs(['start', CONTAINER_NAME], { stdio: 'pipe' });
833
949
  }
834
950
 
835
951
  // Get default command from label
836
- const defaultCommand = dockerExec(`${DOCKER_CMD} inspect -f '{{index .Config.Labels "manyoyo.default_cmd"}}' "${CONTAINER_NAME}"`).trim();
952
+ const defaultCommand = dockerExecArgs(['inspect', '-f', '{{index .Config.Labels "manyoyo.default_cmd"}}', CONTAINER_NAME]).trim();
837
953
 
838
954
  if (!EXEC_COMMAND) {
839
955
  EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${defaultCommand}${EXEC_COMMAND_SUFFIX}`;
@@ -845,6 +961,19 @@ async function connectExistingContainer() {
845
961
  }
846
962
 
847
963
  async function setupContainer() {
964
+ if (SHOW_COMMAND) {
965
+ if (containerExists(CONTAINER_NAME)) {
966
+ const defaultCommand = dockerExecArgs(['inspect', '-f', '{{index .Config.Labels "manyoyo.default_cmd"}}', CONTAINER_NAME]).trim();
967
+ const execCmd = EXEC_COMMAND
968
+ ? `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`
969
+ : `${EXEC_COMMAND_PREFIX}${defaultCommand}${EXEC_COMMAND_SUFFIX}`;
970
+ console.log(`${DOCKER_CMD} exec -it ${CONTAINER_NAME} /bin/bash -c "${execCmd.replace(/"/g, '\\"')}"`);
971
+ process.exit(0);
972
+ }
973
+ EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`;
974
+ console.log(buildDockerRunCmd());
975
+ process.exit(0);
976
+ }
848
977
  if (!containerExists(CONTAINER_NAME)) {
849
978
  return await createNewContainer();
850
979
  } else {
@@ -169,6 +169,10 @@ EOF
169
169
  # 安装 Codex CLI
170
170
  case ",$TOOL," in *,full,*|*,codex,*)
171
171
  npm install -g @openai/codex
172
+ mkdir -p ~/.codex
173
+ cat > ~/.codex/config.toml <<EOF
174
+ check_for_update_on_startup = false
175
+ EOF
172
176
  ;; esac
173
177
 
174
178
  # 安装 Copilot CLI
@@ -194,7 +198,6 @@ EOF
194
198
  {
195
199
  "\$schema": "https://opencode.ai/config.json",
196
200
  "autoupdate": false,
197
- "permission": "allow",
198
201
  "model": "myprovider/{env:ANTHROPIC_MODEL}",
199
202
  "provider": {
200
203
  "myprovider": {
package/docs/README_EN.md CHANGED
@@ -53,7 +53,7 @@ Only one of the following commands needs to be executed:
53
53
 
54
54
  ```bash
55
55
  # Build using manyoyo (Recommended, auto-cache enabled)
56
- manyoyo --ib # Build full version by default (Recommended)
56
+ manyoyo --ib --iv 1.6.4 # Build full version by default (Recommended, specify version)
57
57
  manyoyo --ib --iba TOOL=common # Build common version (python,nodejs,claude)
58
58
  manyoyo --ib --iba TOOL=go,codex,java,gemini # Build custom combination
59
59
  manyoyo --ib --iba GIT_SSL_NO_VERIFY=true # Build the full version and skip Git SSL verification
@@ -85,7 +85,10 @@ manyoyo -l
85
85
  manyoyo -n test --ef .env -y c
86
86
 
87
87
  # Resume existing session
88
- manyoyo -n test -- -c
88
+ manyoyo -n test -- -c # Claude Code
89
+ manyoyo -n test -- resume --last # Codex
90
+ manyoyo -n test -- -r # Gemini
91
+ manyoyo -n test -- -c # OpenCode
89
92
 
90
93
  # Execute command in interactive shell
91
94
  manyoyo -n test -x /bin/bash
@@ -171,7 +174,7 @@ Refer to `config.example.json` for all available options:
171
174
  "hostPath": "/path/to/project", // Default host working directory
172
175
  "containerPath": "/path/to/project", // Default container working directory
173
176
  "imageName": "localhost/xcanwin/manyoyo", // Default image name
174
- "imageVersion": "1.6.3-full", // Default image version
177
+ "imageVersion": "1.6.4-full", // Default image version
175
178
  "containerMode": "common", // Container nesting mode (common, dind, sock)
176
179
 
177
180
  // Environment variable configuration
@@ -199,18 +202,26 @@ Override parameters include: `containerName`, `hostPath`, `containerPath`, `imag
199
202
 
200
203
  Merge parameters include: `envFile`, `env`, `volumes`, `imageBuildArgs`
201
204
 
202
- #### Common Examples
205
+ #### Common Examples-Global
206
+
207
+ ```bash
208
+ mkdir -p ~/.manyoyo/
209
+
210
+ cat > ~/.manyoyo/manyoyo.json << 'EOF'
211
+ {
212
+ "imageName": "localhost/xcanwin/manyoyo",
213
+ "imageVersion": "1.6.4-full"
214
+ }
215
+ EOF
216
+ ```
217
+
218
+ #### Common Examples-Claude Code
203
219
 
204
220
  ```bash
205
- # Create run configuration directory
206
221
  mkdir -p ~/.manyoyo/run/
207
222
 
208
- # Create Claude run configuration
209
223
  cat > ~/.manyoyo/run/c.json << 'EOF'
210
224
  {
211
- // Claude Code quick configuration
212
- "imageName": "localhost/xcanwin/manyoyo",
213
- "imageVersion": "1.6.3-full",
214
225
  "envFile": [
215
226
  "claude" // Automatically loads ~/.manyoyo/env/claude.env
216
227
  ],
@@ -222,6 +233,24 @@ EOF
222
233
  manyoyo -r c
223
234
  ```
224
235
 
236
+ #### Common Examples-Codex
237
+
238
+ ```bash
239
+ mkdir -p ~/.manyoyo/run/
240
+
241
+ cat > ~/.manyoyo/run/codex.json << 'EOF'
242
+ {
243
+ "volumes": [
244
+ "/Users/mac_user/.codex/auth.json:/root/.codex/auth.json"
245
+ ],
246
+ "yolo": "cx"
247
+ }
248
+ EOF
249
+
250
+ # Use run configuration from any directory
251
+ manyoyo -r codex
252
+ ```
253
+
225
254
  ### AI CLI Shortcuts (skip permissions)
226
255
 
227
256
  ```bash
@@ -280,11 +309,11 @@ docker ps -a # Now you can use docker commands inside the container
280
309
  | Option | Aliases | Description |
281
310
  |--------|---------|-------------|
282
311
  | `--hp PATH` | `--host-path` | Set host working directory (default: current path) |
283
- | `-n NAME` | `--cn`, `--cont-name` | Set container name |
312
+ | `-n NAME` | `--cont-name` | Set container name |
284
313
  | `--cp PATH` | `--cont-path` | Set container working directory |
285
- | `-l` | `--cl`, `--cont-list` | List all manyoyo containers |
314
+ | `-l` | `--cont-list` | List all manyoyo containers |
286
315
  | `--crm` | `--cont-remove` | Remove container |
287
- | `-m MODE` | `--cm`, `--cont-mode` | Set container mode (common, dind, sock) |
316
+ | `-m MODE` | `--cont-mode` | Set container mode (common, dind, sock) |
288
317
  | `--in NAME` | `--image-name` | Specify image name |
289
318
  | `--iv VERSION` | `--image-ver` | Specify image version |
290
319
  | `--ib` | `--image-build` | Build image |
@@ -298,6 +327,8 @@ docker ps -a # Now you can use docker commands inside the container
298
327
  | `--` | `--ss`, `--shell-suffix` | Command arguments (suffix for -s) |
299
328
  | `-x CMD` | `--sf`, `--shell-full` | Full command (replaces --sp, -s, and --) |
300
329
  | `-y CLI` | `--yolo` | Run AI agent without confirmation |
330
+ | `--show-config` | | Print final effective config and exit |
331
+ | `--show-command` | | Print the command to be executed and exit (docker exec if container exists, otherwise docker run) |
301
332
  | `--install NAME` | | Install manyoyo command |
302
333
  | `-q LIST` | `--quiet` | Quiet output |
303
334
  | `-r NAME` | `--run` | Load run configuration (supports `name` or `./path.json`) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "3.7.0",
3
+ "version": "3.7.5",
4
4
  "imageVersion": "1.6.4",
5
5
  "description": "AI Agent CLI Security Sandbox",
6
6
  "keywords": [