@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 +44 -13
- package/bin/manyoyo.js +163 -34
- package/docker/manyoyo.Dockerfile +4 -1
- package/docs/README_EN.md +43 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ podman pull ubuntu:24.04
|
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
# 使用 manyoyo 构建镜像(推荐,自动使用缓存加速)
|
|
56
|
-
manyoyo --ib
|
|
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
|
-
|
|
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.
|
|
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` | `--
|
|
312
|
+
| `-n NAME` | `--cont-name` | 设置容器名称 |
|
|
284
313
|
| `--cp PATH` | `--cont-path` | 设置容器工作目录 |
|
|
285
|
-
| `-l` | `--
|
|
314
|
+
| `-l` | `--cont-list` | 列出所有 manyoyo 容器 |
|
|
286
315
|
| `--crm` | `--cont-remove` | 删除容器 |
|
|
287
|
-
| `-m MODE` | `--
|
|
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 (/[
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
411
|
+
dockerExecArgs(['image', 'prune', '-f'], { stdio: 'inherit' });
|
|
365
412
|
|
|
366
413
|
// Remove remaining <none> images
|
|
367
414
|
try {
|
|
368
|
-
const imagesOutput =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
948
|
+
dockerExecArgs(['start', CONTAINER_NAME], { stdio: 'pipe' });
|
|
833
949
|
}
|
|
834
950
|
|
|
835
951
|
// Get default command from label
|
|
836
|
-
const defaultCommand =
|
|
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
|
|
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.
|
|
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` | `--
|
|
312
|
+
| `-n NAME` | `--cont-name` | Set container name |
|
|
284
313
|
| `--cp PATH` | `--cont-path` | Set container working directory |
|
|
285
|
-
| `-l` | `--
|
|
314
|
+
| `-l` | `--cont-list` | List all manyoyo containers |
|
|
286
315
|
| `--crm` | `--cont-remove` | Remove container |
|
|
287
|
-
| `-m MODE` | `--
|
|
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`) |
|