@xcanwin/manyoyo 3.7.7 → 3.8.2
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 +87 -6
- package/bin/manyoyo.js +255 -48
- package/docker/manyoyo.Dockerfile +18 -16
- package/docs/README_EN.md +89 -8
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://www.npmjs.com/package/@xcanwin/manyoyo"><img alt="npm" src="https://img.shields.io/npm/v/@xcanwin/manyoyo?style=flat-square" /></a>
|
|
5
5
|
<a href="https://github.com/xcanwin/manyoyo/actions/workflows/npm-publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/xcanwin/manyoyo/npm-publish.yml?style=flat-square" /></a>
|
|
6
|
+
<a href="https://github.com/xcanwin/manyoyo/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>
|
|
6
7
|
</p>
|
|
7
8
|
|
|
8
9
|
<p align="center">
|
|
@@ -12,6 +13,25 @@
|
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
16
|
+
## 2 分钟快速开始
|
|
17
|
+
|
|
18
|
+
**Docker 用户:**
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @xcanwin/manyoyo # 安装
|
|
21
|
+
manyoyo --ib --iv 1.7.0 # 构建镜像
|
|
22
|
+
manyoyo -y c # 运行 Claude Code YOLO 模式
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Podman 用户:**
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @xcanwin/manyoyo # 安装
|
|
28
|
+
podman pull ubuntu:24.04 # 拉取基础镜像
|
|
29
|
+
manyoyo --ib --iv 1.7.0 # 构建镜像
|
|
30
|
+
manyoyo -y c # 运行 Claude Code YOLO 模式
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
15
35
|
**MANYOYO** 是一款 AI 智能体提效安全沙箱,安全、高效、省 token,专为 Agent YOLO 模式设计,保障宿主机安全。
|
|
16
36
|
|
|
17
37
|
预装常见 Agent 与工具,进一步节省 token。循环自由切换 Agent 和 `/bin/bash`,进一步提效。
|
|
@@ -61,7 +81,7 @@ podman pull ubuntu:24.04
|
|
|
61
81
|
|
|
62
82
|
```bash
|
|
63
83
|
# 使用 manyoyo 构建镜像(推荐,自动使用缓存加速)
|
|
64
|
-
manyoyo --ib --iv 1.
|
|
84
|
+
manyoyo --ib --iv 1.7.0 # 默认构建 full 版本(推荐,建议指定版本号)
|
|
65
85
|
manyoyo --ib --iba TOOL=common # 构建常见组件版本(python,nodejs,claude)
|
|
66
86
|
manyoyo --ib --iba TOOL=go,codex,java,gemini # 构建自定义组件版本
|
|
67
87
|
manyoyo --ib --iba GIT_SSL_NO_VERIFY=true # 构建 full 版本且跳过git的ssl验证
|
|
@@ -200,7 +220,7 @@ manyoyo --ef openai_[gpt]_codex -x codex
|
|
|
200
220
|
"hostPath": "/path/to/project", // 默认宿主机工作目录
|
|
201
221
|
"containerPath": "/path/to/project", // 默认容器工作目录
|
|
202
222
|
"imageName": "localhost/xcanwin/manyoyo", // 默认镜像名称
|
|
203
|
-
"imageVersion": "1.
|
|
223
|
+
"imageVersion": "1.7.0-full", // 默认镜像版本
|
|
204
224
|
"containerMode": "common", // 容器嵌套模式 (common, dind, sock)
|
|
205
225
|
|
|
206
226
|
// 环境变量配置
|
|
@@ -224,9 +244,21 @@ manyoyo --ef openai_[gpt]_codex -x codex
|
|
|
224
244
|
- **覆盖型参数**:命令行 > 运行配置 > 全局配置 > 默认值
|
|
225
245
|
- **合并型参数**:全局配置 + 运行配置 + 命令行(按顺序累加)
|
|
226
246
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
247
|
+
#### 配置合并规则表
|
|
248
|
+
|
|
249
|
+
| 参数类型 | 参数名 | 合并行为 | 示例 |
|
|
250
|
+
|---------|--------|---------|------|
|
|
251
|
+
| 覆盖型 | `containerName` | 取最高优先级的值 | CLI `-n test` 覆盖配置文件中的值 |
|
|
252
|
+
| 覆盖型 | `hostPath` | 取最高优先级的值 | 默认为当前目录 |
|
|
253
|
+
| 覆盖型 | `containerPath` | 取最高优先级的值 | 默认与 hostPath 相同 |
|
|
254
|
+
| 覆盖型 | `imageName` | 取最高优先级的值 | 默认 `localhost/xcanwin/manyoyo` |
|
|
255
|
+
| 覆盖型 | `imageVersion` | 取最高优先级的值 | 如 `1.7.0-full` |
|
|
256
|
+
| 覆盖型 | `containerMode` | 取最高优先级的值 | `common`, `dind`, `sock` |
|
|
257
|
+
| 覆盖型 | `yolo` | 取最高优先级的值 | `c`, `gm`, `cx`, `oc` |
|
|
258
|
+
| 合并型 | `env` | 数组累加合并 | 全局 + 运行配置 + CLI 的所有值 |
|
|
259
|
+
| 合并型 | `envFile` | 数组累加合并 | 所有环境文件依次加载 |
|
|
260
|
+
| 合并型 | `volumes` | 数组累加合并 | 所有挂载卷生效 |
|
|
261
|
+
| 合并型 | `imageBuildArgs` | 数组累加合并 | 所有构建参数生效 |
|
|
230
262
|
|
|
231
263
|
#### 常用样例-全局
|
|
232
264
|
|
|
@@ -236,7 +268,7 @@ mkdir -p ~/.manyoyo/
|
|
|
236
268
|
cat > ~/.manyoyo/manyoyo.json << 'EOF'
|
|
237
269
|
{
|
|
238
270
|
"imageName": "localhost/xcanwin/manyoyo",
|
|
239
|
-
"imageVersion": "1.
|
|
271
|
+
"imageVersion": "1.7.0-full"
|
|
240
272
|
}
|
|
241
273
|
EOF
|
|
242
274
|
```
|
|
@@ -358,6 +390,8 @@ docker ps -a # 现在可以在容器内使用 docker 命令
|
|
|
358
390
|
| `-y CLI` | `--yolo` | 无需确认运行 AI 智能体 |
|
|
359
391
|
| `--show-config` | | 显示最终生效配置并退出 |
|
|
360
392
|
| `--show-command` | | 显示将执行的命令并退出(存在容器时为 docker exec,不存在时为 docker run) |
|
|
393
|
+
| `--yes` | | 所有提示自动确认(用于CI/脚本) |
|
|
394
|
+
| `--rm-on-exit` | | 退出后自动删除容器(一次性模式) |
|
|
361
395
|
| `--install NAME` | | 安装 manyoyo 命令 |
|
|
362
396
|
| `-q LIST` | `--quiet` | 静默显示 |
|
|
363
397
|
| `-r NAME` | `--run` | 加载运行配置(支持 `name` 或 `./path.json`) |
|
|
@@ -384,6 +418,53 @@ docker ps -a # 现在可以在容器内使用 docker 命令
|
|
|
384
418
|
npm uninstall -g @xcanwin/manyoyo
|
|
385
419
|
```
|
|
386
420
|
|
|
421
|
+
## 故障排查 FAQ
|
|
422
|
+
|
|
423
|
+
### 镜像构建失败
|
|
424
|
+
|
|
425
|
+
**问题**:执行 `manyoyo --ib` 时报错
|
|
426
|
+
|
|
427
|
+
**解决方案**:
|
|
428
|
+
1. 检查网络连接:`curl -I https://mirrors.tencent.com`
|
|
429
|
+
2. 检查磁盘空间:`df -h`(需要至少 10GB 可用空间)
|
|
430
|
+
3. 使用 `--yes` 跳过确认:`manyoyo --ib --iv 1.7.0 --yes`
|
|
431
|
+
4. 如果在国外,可能需要修改镜像源(配置文件中设置 `nodeMirror`)
|
|
432
|
+
|
|
433
|
+
### 镜像拉取失败
|
|
434
|
+
|
|
435
|
+
**问题**:提示 `pinging container registry localhost failed`
|
|
436
|
+
|
|
437
|
+
**解决方案**:
|
|
438
|
+
1. 本地镜像需要先构建:`manyoyo --ib --iv 1.7.0`
|
|
439
|
+
2. 或修改配置文件 `~/.manyoyo/manyoyo.json` 中的 `imageVersion`
|
|
440
|
+
|
|
441
|
+
### 容器启动失败
|
|
442
|
+
|
|
443
|
+
**问题**:容器无法启动或立即退出
|
|
444
|
+
|
|
445
|
+
**解决方案**:
|
|
446
|
+
1. 查看容器日志:`docker logs <容器名>`
|
|
447
|
+
2. 检查端口冲突:`docker ps -a`
|
|
448
|
+
3. 检查权限问题:确保当前用户有 Docker/Podman 权限
|
|
449
|
+
|
|
450
|
+
### 权限不足
|
|
451
|
+
|
|
452
|
+
**问题**:提示 `permission denied` 或无法访问 Docker
|
|
453
|
+
|
|
454
|
+
**解决方案**:
|
|
455
|
+
1. 将用户添加到 docker 组:`sudo usermod -aG docker $USER`
|
|
456
|
+
2. 重新登录或运行:`newgrp docker`
|
|
457
|
+
3. 或使用 `sudo` 运行命令
|
|
458
|
+
|
|
459
|
+
### 环境变量未生效
|
|
460
|
+
|
|
461
|
+
**问题**:容器内无法读取设置的环境变量
|
|
462
|
+
|
|
463
|
+
**解决方案**:
|
|
464
|
+
1. 检查环境文件格式(支持 `KEY=VALUE` 或 `export KEY=VALUE`)
|
|
465
|
+
2. 确认文件路径正确(`--ef name` 对应 `~/.manyoyo/env/name.env`)
|
|
466
|
+
3. 使用 `--show-config` 查看最终生效的配置
|
|
467
|
+
|
|
387
468
|
## 许可证
|
|
388
469
|
|
|
389
470
|
MIT
|
package/bin/manyoyo.js
CHANGED
|
@@ -23,6 +23,17 @@ function formatDate() {
|
|
|
23
23
|
return `${month}${day}-${hour}${minute}`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// ==============================================================================
|
|
27
|
+
// Configuration Constants
|
|
28
|
+
// ==============================================================================
|
|
29
|
+
|
|
30
|
+
const CONFIG = {
|
|
31
|
+
CACHE_TTL_DAYS: 2, // 缓存过期天数
|
|
32
|
+
CONTAINER_READY_MAX_RETRIES: 30, // 容器就绪最大重试次数
|
|
33
|
+
CONTAINER_READY_INITIAL_DELAY: 100, // 容器就绪初始延迟(ms)
|
|
34
|
+
CONTAINER_READY_MAX_DELAY: 2000, // 容器就绪最大延迟(ms)
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
// Default configuration
|
|
27
38
|
let CONTAINER_NAME = `myy-${formatDate()}`;
|
|
28
39
|
let HOST_PATH = process.cwd();
|
|
@@ -40,8 +51,11 @@ let CONTAINER_ENVS = [];
|
|
|
40
51
|
let CONTAINER_VOLUMES = [];
|
|
41
52
|
let MANYOYO_NAME = "manyoyo";
|
|
42
53
|
let CONT_MODE = "";
|
|
54
|
+
let CONT_MODE_ARGS = [];
|
|
43
55
|
let QUIET = {};
|
|
44
56
|
let SHOW_COMMAND = false;
|
|
57
|
+
let YES_MODE = false;
|
|
58
|
+
let RM_ON_EXIT = false;
|
|
45
59
|
|
|
46
60
|
// Color definitions using ANSI codes
|
|
47
61
|
const RED = '\x1b[0;31m';
|
|
@@ -62,10 +76,73 @@ function sleep(ms) {
|
|
|
62
76
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
63
77
|
}
|
|
64
78
|
|
|
79
|
+
/**
|
|
80
|
+
* 敏感信息脱敏(用于 --show-config 输出)
|
|
81
|
+
* @param {Object} obj - 配置对象
|
|
82
|
+
* @returns {Object} 脱敏后的配置对象
|
|
83
|
+
*/
|
|
84
|
+
function sanitizeSensitiveData(obj) {
|
|
85
|
+
const sensitiveKeys = ['KEY', 'TOKEN', 'SECRET', 'PASSWORD', 'AUTH', 'CREDENTIAL'];
|
|
86
|
+
|
|
87
|
+
function sanitizeValue(key, value) {
|
|
88
|
+
if (typeof value !== 'string') return value;
|
|
89
|
+
const upperKey = key.toUpperCase();
|
|
90
|
+
if (sensitiveKeys.some(k => upperKey.includes(k))) {
|
|
91
|
+
if (value.length <= 8) return '****';
|
|
92
|
+
return value.slice(0, 4) + '****' + value.slice(-4);
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function sanitizeArray(arr) {
|
|
98
|
+
return arr.map(item => {
|
|
99
|
+
if (typeof item === 'string' && item.includes('=')) {
|
|
100
|
+
const idx = item.indexOf('=');
|
|
101
|
+
const key = item.slice(0, idx);
|
|
102
|
+
const value = item.slice(idx + 1);
|
|
103
|
+
return `${key}=${sanitizeValue(key, value)}`;
|
|
104
|
+
}
|
|
105
|
+
return item;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = {};
|
|
110
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
111
|
+
if (Array.isArray(value)) {
|
|
112
|
+
result[key] = sanitizeArray(value);
|
|
113
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
114
|
+
result[key] = sanitizeSensitiveData(value);
|
|
115
|
+
} else {
|
|
116
|
+
result[key] = sanitizeValue(key, value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
65
122
|
// ==============================================================================
|
|
66
123
|
// Configuration File Functions
|
|
67
124
|
// ==============================================================================
|
|
68
125
|
|
|
126
|
+
/**
|
|
127
|
+
* @typedef {Object} Config
|
|
128
|
+
* @property {string} [containerName] - 容器名称
|
|
129
|
+
* @property {string} [hostPath] - 宿主机路径
|
|
130
|
+
* @property {string} [containerPath] - 容器路径
|
|
131
|
+
* @property {string} [imageName] - 镜像名称
|
|
132
|
+
* @property {string} [imageVersion] - 镜像版本
|
|
133
|
+
* @property {string[]} [env] - 环境变量数组
|
|
134
|
+
* @property {string[]} [envFile] - 环境文件数组
|
|
135
|
+
* @property {string[]} [volumes] - 挂载卷数组
|
|
136
|
+
* @property {string} [yolo] - YOLO 模式
|
|
137
|
+
* @property {string} [containerMode] - 容器模式
|
|
138
|
+
* @property {number} [cacheTTL] - 缓存过期天数
|
|
139
|
+
* @property {string} [nodeMirror] - Node.js 镜像源
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 加载全局配置文件
|
|
144
|
+
* @returns {Config} 配置对象
|
|
145
|
+
*/
|
|
69
146
|
function loadConfig() {
|
|
70
147
|
const configPath = path.join(os.homedir(), '.manyoyo', 'manyoyo.json');
|
|
71
148
|
if (fs.existsSync(configPath)) {
|
|
@@ -187,6 +264,10 @@ async function askQuestion(prompt) {
|
|
|
187
264
|
// Configuration Functions
|
|
188
265
|
// ==============================================================================
|
|
189
266
|
|
|
267
|
+
/**
|
|
268
|
+
* 添加环境变量
|
|
269
|
+
* @param {string} env - 环境变量字符串 (KEY=VALUE)
|
|
270
|
+
*/
|
|
190
271
|
function addEnv(env) {
|
|
191
272
|
const idx = env.indexOf('=');
|
|
192
273
|
if (idx <= 0) {
|
|
@@ -276,7 +357,7 @@ function setYolo(cli) {
|
|
|
276
357
|
break;
|
|
277
358
|
case 'opencode':
|
|
278
359
|
case 'oc':
|
|
279
|
-
EXEC_COMMAND = "OPENCODE_PERMISSION='\"allow\"' opencode";
|
|
360
|
+
EXEC_COMMAND = "OPENCODE_PERMISSION='{\"*\":\"allow\"}' opencode";
|
|
280
361
|
break;
|
|
281
362
|
default:
|
|
282
363
|
console.log(`${RED}⚠️ 未知LLM CLI: ${cli}${NC}`);
|
|
@@ -284,21 +365,33 @@ function setYolo(cli) {
|
|
|
284
365
|
}
|
|
285
366
|
}
|
|
286
367
|
|
|
368
|
+
/**
|
|
369
|
+
* 设置容器嵌套模式
|
|
370
|
+
* @param {string} mode - 模式名称 (common, dind, sock)
|
|
371
|
+
*/
|
|
287
372
|
function setContMode(mode) {
|
|
288
373
|
switch (mode) {
|
|
289
374
|
case 'common':
|
|
290
375
|
CONT_MODE = "";
|
|
376
|
+
CONT_MODE_ARGS = [];
|
|
291
377
|
break;
|
|
292
378
|
case 'docker-in-docker':
|
|
293
379
|
case 'dind':
|
|
294
380
|
case 'd':
|
|
295
381
|
CONT_MODE = "--privileged";
|
|
382
|
+
CONT_MODE_ARGS = ['--privileged'];
|
|
296
383
|
console.log(`${GREEN}✅ 开启安全的容器嵌套容器模式, 手动在容器内启动服务: nohup dockerd &${NC}`);
|
|
297
384
|
break;
|
|
298
385
|
case 'mount-docker-socket':
|
|
299
386
|
case 'sock':
|
|
300
387
|
case 's':
|
|
301
388
|
CONT_MODE = "--privileged --volume /var/run/docker.sock:/var/run/docker.sock --env DOCKER_HOST=unix:///var/run/docker.sock --env CONTAINER_HOST=unix:///var/run/docker.sock";
|
|
389
|
+
CONT_MODE_ARGS = [
|
|
390
|
+
'--privileged',
|
|
391
|
+
'--volume', '/var/run/docker.sock:/var/run/docker.sock',
|
|
392
|
+
'--env', 'DOCKER_HOST=unix:///var/run/docker.sock',
|
|
393
|
+
'--env', 'CONTAINER_HOST=unix:///var/run/docker.sock'
|
|
394
|
+
];
|
|
302
395
|
console.log(`${RED}⚠️ 开启危险的容器嵌套容器模式, 危害: 容器可访问宿主机文件${NC}`);
|
|
303
396
|
break;
|
|
304
397
|
default:
|
|
@@ -322,6 +415,19 @@ function dockerExec(cmd, options = {}) {
|
|
|
322
415
|
}
|
|
323
416
|
}
|
|
324
417
|
|
|
418
|
+
function showImagePullHint(err) {
|
|
419
|
+
const stderr = err && err.stderr ? err.stderr.toString() : '';
|
|
420
|
+
const stdout = err && err.stdout ? err.stdout.toString() : '';
|
|
421
|
+
const message = err && err.message ? err.message : '';
|
|
422
|
+
const combined = `${message}\n${stderr}\n${stdout}`;
|
|
423
|
+
if (!/localhost\/v2|pinging container registry localhost|connection refused|dial tcp .*:443/i.test(combined)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const image = `${IMAGE_NAME}:${IMAGE_VERSION}`;
|
|
427
|
+
console.log(`${YELLOW}💡 提示: 本地未找到镜像 ${image},并且从 localhost 注册表拉取失败。${NC}`);
|
|
428
|
+
console.log(`${YELLOW} 你可以: 1) 更新 ~/.manyoyo/manyoyo.json 的 imageVersion 2) 或先执行 manyoyo --ib --iv <version> 构建镜像。${NC}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
325
431
|
function runCmd(cmd, args, options = {}) {
|
|
326
432
|
const result = spawnSync(cmd, args, { encoding: 'utf-8', ...options });
|
|
327
433
|
if (result.error) {
|
|
@@ -430,10 +536,24 @@ function pruneDanglingImages() {
|
|
|
430
536
|
console.log(`${GREEN}✅ 清理完成${NC}`);
|
|
431
537
|
}
|
|
432
538
|
|
|
539
|
+
/**
|
|
540
|
+
* 准备构建缓存(Node.js、JDT LSP、gopls)
|
|
541
|
+
* @param {string} imageTool - 构建工具类型
|
|
542
|
+
*/
|
|
433
543
|
async function prepareBuildCache(imageTool) {
|
|
434
544
|
const cacheDir = path.join(__dirname, '../docker/cache');
|
|
435
545
|
const timestampFile = path.join(cacheDir, '.timestamps.json');
|
|
436
|
-
|
|
546
|
+
|
|
547
|
+
// 从配置文件读取 TTL,默认 2 天
|
|
548
|
+
const config = loadConfig();
|
|
549
|
+
const cacheTTLDays = config.cacheTTL || CONFIG.CACHE_TTL_DAYS;
|
|
550
|
+
|
|
551
|
+
// 镜像源优先级:用户配置 > 腾讯云 > 官方
|
|
552
|
+
const nodeMirrors = [
|
|
553
|
+
config.nodeMirror,
|
|
554
|
+
'https://mirrors.tencent.com/nodejs-release',
|
|
555
|
+
'https://nodejs.org/dist'
|
|
556
|
+
].filter(Boolean);
|
|
437
557
|
|
|
438
558
|
console.log(`\n${CYAN}准备构建缓存...${NC}`);
|
|
439
559
|
|
|
@@ -476,18 +596,46 @@ async function prepareBuildCache(imageTool) {
|
|
|
476
596
|
const hasNodeCache = fs.existsSync(nodeCacheDir) && fs.readdirSync(nodeCacheDir).some(f => f.startsWith('node-') && f.includes(`linux-${archNode}`));
|
|
477
597
|
if (!hasNodeCache || isExpired(nodeKey)) {
|
|
478
598
|
console.log(`${YELLOW}下载 Node.js ${nodeVersion} (${archNode})...${NC}`);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
599
|
+
|
|
600
|
+
// 尝试多个镜像源
|
|
601
|
+
let downloadSuccess = false;
|
|
602
|
+
for (const mirror of nodeMirrors) {
|
|
603
|
+
try {
|
|
604
|
+
console.log(`${BLUE}尝试镜像源: ${mirror}${NC}`);
|
|
605
|
+
const shasumUrl = `${mirror}/latest-v${nodeVersion}.x/SHASUMS256.txt`;
|
|
606
|
+
const shasumContent = execSync(`curl -sL ${shasumUrl}`, { encoding: 'utf-8' });
|
|
607
|
+
const shasumLine = shasumContent.split('\n').find(line => line.includes(`linux-${archNode}.tar.gz`));
|
|
608
|
+
if (!shasumLine) continue;
|
|
609
|
+
|
|
610
|
+
const [expectedHash, fileName] = shasumLine.trim().split(/\s+/);
|
|
611
|
+
const nodeUrl = `${mirror}/latest-v${nodeVersion}.x/${fileName}`;
|
|
612
|
+
const nodeTargetPath = path.join(nodeCacheDir, fileName);
|
|
613
|
+
|
|
614
|
+
// 下载文件
|
|
615
|
+
runCmd('curl', ['-fsSL', nodeUrl, '-o', nodeTargetPath], { stdio: 'inherit' });
|
|
616
|
+
|
|
617
|
+
// SHA256 校验
|
|
618
|
+
const actualHash = execSync(`sha256sum "${nodeTargetPath}" | awk '{print $1}'`, { encoding: 'utf-8' }).trim();
|
|
619
|
+
if (actualHash !== expectedHash) {
|
|
620
|
+
console.log(`${RED}SHA256 校验失败,删除文件${NC}`);
|
|
621
|
+
fs.unlinkSync(nodeTargetPath);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
console.log(`${GREEN}✓ SHA256 校验通过${NC}`);
|
|
626
|
+
timestamps[nodeKey] = now.toISOString();
|
|
627
|
+
fs.writeFileSync(timestampFile, JSON.stringify(timestamps, null, 4));
|
|
628
|
+
console.log(`${GREEN}✓ Node.js 下载完成${NC}`);
|
|
629
|
+
downloadSuccess = true;
|
|
630
|
+
break;
|
|
631
|
+
} catch (e) {
|
|
632
|
+
console.log(`${YELLOW}镜像源 ${mirror} 失败,尝试下一个...${NC}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (!downloadSuccess) {
|
|
637
|
+
console.error(`${RED}错误: Node.js 下载失败(所有镜像源均不可用)${NC}`);
|
|
638
|
+
throw new Error('Node.js download failed');
|
|
491
639
|
}
|
|
492
640
|
} else {
|
|
493
641
|
console.log(`${GREEN}✓ Node.js 缓存已存在${NC}`);
|
|
@@ -625,8 +773,10 @@ async function buildImage(IMAGE_BUILD_ARGS, imageName, imageVersion) {
|
|
|
625
773
|
console.log(`${BLUE}准备执行命令:${NC}`);
|
|
626
774
|
console.log(`${buildCmd}\n`);
|
|
627
775
|
|
|
628
|
-
|
|
629
|
-
|
|
776
|
+
if (!YES_MODE) {
|
|
777
|
+
await askQuestion(`❔ 是否继续构建? [ 直接回车=继续, ctrl+c=取消 ]: `);
|
|
778
|
+
console.log("");
|
|
779
|
+
}
|
|
630
780
|
|
|
631
781
|
try {
|
|
632
782
|
execSync(buildCmd, { stdio: 'inherit' });
|
|
@@ -702,6 +852,8 @@ function setupCommander() {
|
|
|
702
852
|
.option('--install <name>', '安装manyoyo命令 (docker-cli-plugin)')
|
|
703
853
|
.option('--show-config', '显示最终生效配置并退出')
|
|
704
854
|
.option('--show-command', '显示将执行的 docker run 命令并退出')
|
|
855
|
+
.option('--yes', '所有提示自动确认 (用于CI/脚本)')
|
|
856
|
+
.option('--rm-on-exit', '退出后自动删除容器 (一次性模式)')
|
|
705
857
|
.option('-q, --quiet <item>', '静默显示 (可多次使用: cnew,crm,tip,cmd,full)', (value, previous) => [...(previous || []), value], []);
|
|
706
858
|
|
|
707
859
|
// Docker CLI plugin metadata check
|
|
@@ -801,6 +953,14 @@ function setupCommander() {
|
|
|
801
953
|
}
|
|
802
954
|
}
|
|
803
955
|
|
|
956
|
+
if (options.yes) {
|
|
957
|
+
YES_MODE = true;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (options.rmOnExit) {
|
|
961
|
+
RM_ON_EXIT = true;
|
|
962
|
+
}
|
|
963
|
+
|
|
804
964
|
if (options.showConfig) {
|
|
805
965
|
const finalConfig = {
|
|
806
966
|
hostPath: HOST_PATH,
|
|
@@ -824,7 +984,9 @@ function setupCommander() {
|
|
|
824
984
|
suffix: EXEC_COMMAND_SUFFIX
|
|
825
985
|
}
|
|
826
986
|
};
|
|
827
|
-
|
|
987
|
+
// 敏感信息脱敏
|
|
988
|
+
const sanitizedConfig = sanitizeSensitiveData(finalConfig);
|
|
989
|
+
console.log(JSON.stringify(sanitizedConfig, null, 4));
|
|
828
990
|
process.exit(0);
|
|
829
991
|
}
|
|
830
992
|
|
|
@@ -869,15 +1031,20 @@ function validateHostPath() {
|
|
|
869
1031
|
}
|
|
870
1032
|
}
|
|
871
1033
|
|
|
1034
|
+
/**
|
|
1035
|
+
* 等待容器就绪(使用指数退避算法)
|
|
1036
|
+
* @param {string} containerName - 容器名称
|
|
1037
|
+
*/
|
|
872
1038
|
async function waitForContainerReady(containerName) {
|
|
873
|
-
const MAX_RETRIES =
|
|
874
|
-
let
|
|
875
|
-
|
|
1039
|
+
const MAX_RETRIES = CONFIG.CONTAINER_READY_MAX_RETRIES;
|
|
1040
|
+
let retryDelay = CONFIG.CONTAINER_READY_INITIAL_DELAY;
|
|
1041
|
+
|
|
1042
|
+
for (let count = 0; count < MAX_RETRIES; count++) {
|
|
876
1043
|
try {
|
|
877
1044
|
const status = getContainerStatus(containerName);
|
|
878
1045
|
|
|
879
1046
|
if (status === 'running') {
|
|
880
|
-
|
|
1047
|
+
return;
|
|
881
1048
|
}
|
|
882
1049
|
|
|
883
1050
|
if (status === 'exited') {
|
|
@@ -886,39 +1053,41 @@ async function waitForContainerReady(containerName) {
|
|
|
886
1053
|
process.exit(1);
|
|
887
1054
|
}
|
|
888
1055
|
|
|
889
|
-
await sleep(
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
if (count >= MAX_RETRIES) {
|
|
893
|
-
console.log(`${RED}⚠️ 错误: 容器启动超时(当前状态: ${status})。${NC}`);
|
|
894
|
-
dockerExecArgs(['logs', containerName], { stdio: 'inherit' });
|
|
895
|
-
process.exit(1);
|
|
896
|
-
}
|
|
1056
|
+
await sleep(retryDelay);
|
|
1057
|
+
retryDelay = Math.min(retryDelay * 2, CONFIG.CONTAINER_READY_MAX_DELAY);
|
|
897
1058
|
} catch (e) {
|
|
898
|
-
await sleep(
|
|
899
|
-
|
|
900
|
-
if (count >= MAX_RETRIES) {
|
|
901
|
-
console.log(`${RED}⚠️ 错误: 容器启动超时。${NC}`);
|
|
902
|
-
process.exit(1);
|
|
903
|
-
}
|
|
1059
|
+
await sleep(retryDelay);
|
|
1060
|
+
retryDelay = Math.min(retryDelay * 2, CONFIG.CONTAINER_READY_MAX_DELAY);
|
|
904
1061
|
}
|
|
905
1062
|
}
|
|
1063
|
+
|
|
1064
|
+
console.log(`${RED}⚠️ 错误: 容器启动超时。${NC}`);
|
|
1065
|
+
process.exit(1);
|
|
906
1066
|
}
|
|
907
1067
|
|
|
1068
|
+
/**
|
|
1069
|
+
* 创建新容器
|
|
1070
|
+
* @returns {Promise<string>} 默认命令
|
|
1071
|
+
*/
|
|
908
1072
|
async function createNewContainer() {
|
|
909
1073
|
if ( !(QUIET.cnew || QUIET.full) ) console.log(`${CYAN}📦 manyoyo by xcanwin 正在创建新容器: ${YELLOW}${CONTAINER_NAME}${NC}`);
|
|
910
1074
|
|
|
911
1075
|
EXEC_COMMAND = `${EXEC_COMMAND_PREFIX}${EXEC_COMMAND}${EXEC_COMMAND_SUFFIX}`;
|
|
912
1076
|
const defaultCommand = EXEC_COMMAND;
|
|
913
1077
|
|
|
914
|
-
const dockerRunCmd = buildDockerRunCmd();
|
|
915
|
-
|
|
916
1078
|
if (SHOW_COMMAND) {
|
|
917
|
-
console.log(
|
|
1079
|
+
console.log(buildDockerRunCmd());
|
|
918
1080
|
process.exit(0);
|
|
919
1081
|
}
|
|
920
1082
|
|
|
921
|
-
|
|
1083
|
+
// 使用数组参数执行命令(安全方式)
|
|
1084
|
+
try {
|
|
1085
|
+
const args = buildDockerRunArgs();
|
|
1086
|
+
dockerExecArgs(args, { stdio: 'pipe' });
|
|
1087
|
+
} catch (e) {
|
|
1088
|
+
showImagePullHint(e);
|
|
1089
|
+
throw e;
|
|
1090
|
+
}
|
|
922
1091
|
|
|
923
1092
|
// Wait for container to be ready
|
|
924
1093
|
await waitForContainerReady(CONTAINER_NAME);
|
|
@@ -926,17 +1095,45 @@ async function createNewContainer() {
|
|
|
926
1095
|
return defaultCommand;
|
|
927
1096
|
}
|
|
928
1097
|
|
|
929
|
-
|
|
930
|
-
|
|
1098
|
+
/**
|
|
1099
|
+
* 构建 Docker run 命令参数数组(安全方式,避免命令注入)
|
|
1100
|
+
* @returns {string[]} 命令参数数组
|
|
1101
|
+
*/
|
|
1102
|
+
function buildDockerRunArgs() {
|
|
931
1103
|
const fullImage = `${IMAGE_NAME}:${IMAGE_VERSION}`;
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1104
|
+
const safeLabelCmd = EXEC_COMMAND.replace(/[\r\n]/g, ' ');
|
|
1105
|
+
|
|
1106
|
+
const args = [
|
|
1107
|
+
'run', '-d',
|
|
1108
|
+
'--name', CONTAINER_NAME,
|
|
1109
|
+
'--entrypoint', '',
|
|
1110
|
+
...CONT_MODE_ARGS,
|
|
1111
|
+
...CONTAINER_ENVS,
|
|
1112
|
+
...CONTAINER_VOLUMES,
|
|
1113
|
+
'--volume', `${HOST_PATH}:${CONTAINER_PATH}`,
|
|
1114
|
+
'--workdir', CONTAINER_PATH,
|
|
1115
|
+
'--label', `manyoyo.default_cmd=${safeLabelCmd}`,
|
|
1116
|
+
fullImage,
|
|
1117
|
+
'tail', '-f', '/dev/null'
|
|
1118
|
+
];
|
|
1119
|
+
|
|
1120
|
+
return args;
|
|
1121
|
+
}
|
|
938
1122
|
|
|
939
|
-
|
|
1123
|
+
/**
|
|
1124
|
+
* 构建 Docker run 命令字符串(用于显示)
|
|
1125
|
+
* @returns {string} 命令字符串
|
|
1126
|
+
*/
|
|
1127
|
+
function buildDockerRunCmd() {
|
|
1128
|
+
const args = buildDockerRunArgs();
|
|
1129
|
+
// 对包含空格或特殊字符的参数加引号
|
|
1130
|
+
const quotedArgs = args.map(arg => {
|
|
1131
|
+
if (arg.includes(' ') || arg.includes('"') || arg.includes('=')) {
|
|
1132
|
+
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
1133
|
+
}
|
|
1134
|
+
return arg;
|
|
1135
|
+
});
|
|
1136
|
+
return `${DOCKER_CMD} ${quotedArgs.join(' ')}`;
|
|
940
1137
|
}
|
|
941
1138
|
|
|
942
1139
|
async function connectExistingContainer() {
|
|
@@ -996,7 +1193,17 @@ function executeInContainer(defaultCommand) {
|
|
|
996
1193
|
}
|
|
997
1194
|
}
|
|
998
1195
|
|
|
1196
|
+
/**
|
|
1197
|
+
* 处理会话退出后的交互
|
|
1198
|
+
* @param {string} defaultCommand - 默认命令
|
|
1199
|
+
*/
|
|
999
1200
|
async function handlePostExit(defaultCommand) {
|
|
1201
|
+
// --rm-on-exit 模式:自动删除容器
|
|
1202
|
+
if (RM_ON_EXIT) {
|
|
1203
|
+
removeContainer(CONTAINER_NAME);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1000
1207
|
getHelloTip(CONTAINER_NAME, defaultCommand);
|
|
1001
1208
|
|
|
1002
1209
|
let tipAskKeep = `❔ 会话已结束。是否保留此后台容器 ${CONTAINER_NAME}? [ y=默认保留, n=删除, 1=首次命令进入, x=执行命令, i=交互式SHELL ]: `;
|
|
@@ -61,30 +61,32 @@ ARG TARGETARCH
|
|
|
61
61
|
ARG NODE_VERSION=24
|
|
62
62
|
ARG TOOL="full"
|
|
63
63
|
|
|
64
|
+
# 镜像源参数化(默认使用阿里云,可按需覆盖)
|
|
65
|
+
ARG APT_MIRROR=https://mirrors.aliyun.com
|
|
66
|
+
ARG NPM_REGISTRY=https://mirrors.tencent.com/npm/
|
|
67
|
+
ARG PIP_INDEX_URL=https://mirrors.tencent.com/pypi/simple
|
|
68
|
+
|
|
69
|
+
# 合并系统依赖安装为单层,减少镜像体积
|
|
64
70
|
RUN <<EOX
|
|
65
|
-
#
|
|
71
|
+
# 配置 APT 镜像源
|
|
72
|
+
sed -i "s|http://[^/]*\.ubuntu\.com|${APT_MIRROR}|g" /etc/apt/sources.list.d/ubuntu.sources
|
|
66
73
|
|
|
67
|
-
#
|
|
68
|
-
sed -i 's|http://[^/]*\.ubuntu\.com|https://mirrors.aliyun.com|g' /etc/apt/sources.list.d/ubuntu.sources
|
|
74
|
+
# 安装所有基础依赖(单次 apt-get 操作)
|
|
69
75
|
apt-get -o Acquire::https::Verify-Peer=false update -y
|
|
70
|
-
apt-get -o Acquire::https::Verify-Peer=false install -y --no-install-recommends
|
|
71
|
-
|
|
76
|
+
apt-get -o Acquire::https::Verify-Peer=false install -y --no-install-recommends \
|
|
77
|
+
ca-certificates openssl curl wget net-tools iputils-ping \
|
|
78
|
+
git lsof socat ncat dnsutils ssh nano jq file tree ripgrep less bc xxd \
|
|
79
|
+
tar zip unzip gzip make sqlite3 supervisor
|
|
72
80
|
|
|
73
|
-
#
|
|
74
|
-
apt-get update -y
|
|
75
|
-
apt-get install -y --no-install-recommends --reinstall ca-certificates openssl
|
|
81
|
+
# 更新 CA 证书
|
|
76
82
|
update-ca-certificates
|
|
77
|
-
apt-get install -y --no-install-recommends curl wget net-tools iputils-ping git lsof socat ncat dnsutils \
|
|
78
|
-
nano jq file tree ripgrep less bc xxd \
|
|
79
|
-
tar zip unzip gzip make sqlite3 \
|
|
80
|
-
supervisor
|
|
81
83
|
|
|
82
|
-
# 安装 podman
|
|
84
|
+
# 安装 podman(条件)
|
|
83
85
|
case ",$TOOL," in *,full,*|*,podman,*)
|
|
84
86
|
apt-get install -y --no-install-recommends podman
|
|
85
87
|
;; esac
|
|
86
88
|
|
|
87
|
-
# 安装 docker
|
|
89
|
+
# 安装 docker(条件)
|
|
88
90
|
case ",$TOOL," in *,full,*|*,docker,*)
|
|
89
91
|
apt-get install -y --no-install-recommends docker.io
|
|
90
92
|
;; esac
|
|
@@ -100,7 +102,7 @@ RUN <<EOX
|
|
|
100
102
|
apt-get install -y --no-install-recommends python3.12 python3.12-dev python3.12-venv python3-pip
|
|
101
103
|
ln -sf /usr/bin/python3 /usr/bin/python
|
|
102
104
|
ln -sf /usr/bin/pip3 /usr/bin/pip
|
|
103
|
-
pip config set global.index-url "
|
|
105
|
+
pip config set global.index-url "${PIP_INDEX_URL}"
|
|
104
106
|
|
|
105
107
|
# 清理
|
|
106
108
|
apt-get clean
|
|
@@ -113,7 +115,7 @@ ARG GIT_SSL_NO_VERIFY=false
|
|
|
113
115
|
|
|
114
116
|
RUN <<EOX
|
|
115
117
|
# 配置 node.js
|
|
116
|
-
npm config set registry
|
|
118
|
+
npm config set registry=${NPM_REGISTRY}
|
|
117
119
|
npm install -g npm
|
|
118
120
|
|
|
119
121
|
# 安装 LSP服务(python、typescript)
|
package/docs/README_EN.md
CHANGED
|
@@ -3,15 +3,35 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://www.npmjs.com/package/@xcanwin/manyoyo"><img alt="npm" src="https://img.shields.io/npm/v/@xcanwin/manyoyo?style=flat-square" /></a>
|
|
5
5
|
<a href="https://github.com/xcanwin/manyoyo/actions/workflows/npm-publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/xcanwin/manyoyo/npm-publish.yml?style=flat-square" /></a>
|
|
6
|
+
<a href="https://github.com/xcanwin/manyoyo/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>
|
|
6
7
|
</p>
|
|
7
8
|
|
|
8
9
|
<p align="center">
|
|
9
|
-
<a href="README.md">中文</a> |
|
|
10
|
-
<a href="
|
|
10
|
+
<a href="../README.md">中文</a> |
|
|
11
|
+
<a href="README_EN.md"><b>English</b></a>
|
|
11
12
|
</p>
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
16
|
+
## 2-Minute Quick Start
|
|
17
|
+
|
|
18
|
+
**Docker users:**
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @xcanwin/manyoyo # Install
|
|
21
|
+
manyoyo --ib --iv 1.7.0 # Build image
|
|
22
|
+
manyoyo -y c # Run Claude Code YOLO mode
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Podman users:**
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @xcanwin/manyoyo # Install
|
|
28
|
+
podman pull ubuntu:24.04 # Pull base image
|
|
29
|
+
manyoyo --ib --iv 1.7.0 # Build image
|
|
30
|
+
manyoyo -y c # Run Claude Code YOLO mode
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
15
35
|
**MANYOYO** is an AI agent security sandbox that is safe, efficient, and token-saving. Designed specifically for Agent YOLO mode to protect the host machine.
|
|
16
36
|
|
|
17
37
|
Pre-installed with common agents and tools to further save tokens. Freely switch between agents and `/bin/bash` in a loop for enhanced efficiency.
|
|
@@ -61,7 +81,7 @@ Only one of the following commands needs to be executed:
|
|
|
61
81
|
|
|
62
82
|
```bash
|
|
63
83
|
# Build using manyoyo (Recommended, auto-cache enabled)
|
|
64
|
-
manyoyo --ib --iv 1.
|
|
84
|
+
manyoyo --ib --iv 1.7.0 # Build full version by default (Recommended, specify version)
|
|
65
85
|
manyoyo --ib --iba TOOL=common # Build common version (python,nodejs,claude)
|
|
66
86
|
manyoyo --ib --iba TOOL=go,codex,java,gemini # Build custom combination
|
|
67
87
|
manyoyo --ib --iba GIT_SSL_NO_VERIFY=true # Build the full version and skip Git SSL verification
|
|
@@ -200,7 +220,7 @@ Refer to `config.example.json` for all available options:
|
|
|
200
220
|
"hostPath": "/path/to/project", // Default host working directory
|
|
201
221
|
"containerPath": "/path/to/project", // Default container working directory
|
|
202
222
|
"imageName": "localhost/xcanwin/manyoyo", // Default image name
|
|
203
|
-
"imageVersion": "1.
|
|
223
|
+
"imageVersion": "1.7.0-full", // Default image version
|
|
204
224
|
"containerMode": "common", // Container nesting mode (common, dind, sock)
|
|
205
225
|
|
|
206
226
|
// Environment variable configuration
|
|
@@ -224,9 +244,21 @@ Refer to `config.example.json` for all available options:
|
|
|
224
244
|
- **Override parameters**: Command line > Run config > Global config > Defaults
|
|
225
245
|
- **Merge parameters**: Global config + Run config + Command line (concatenated in order)
|
|
226
246
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
247
|
+
#### Configuration Merge Rules
|
|
248
|
+
|
|
249
|
+
| Type | Parameter | Behavior | Example |
|
|
250
|
+
|------|-----------|----------|---------|
|
|
251
|
+
| Override | `containerName` | Use highest priority value | CLI `-n test` overrides config file |
|
|
252
|
+
| Override | `hostPath` | Use highest priority value | Defaults to current directory |
|
|
253
|
+
| Override | `containerPath` | Use highest priority value | Defaults to same as hostPath |
|
|
254
|
+
| Override | `imageName` | Use highest priority value | Default `localhost/xcanwin/manyoyo` |
|
|
255
|
+
| Override | `imageVersion` | Use highest priority value | e.g., `1.7.0-full` |
|
|
256
|
+
| Override | `containerMode` | Use highest priority value | `common`, `dind`, `sock` |
|
|
257
|
+
| Override | `yolo` | Use highest priority value | `c`, `gm`, `cx`, `oc` |
|
|
258
|
+
| Merge | `env` | Array concatenation | All values from global + run + CLI |
|
|
259
|
+
| Merge | `envFile` | Array concatenation | All env files loaded in order |
|
|
260
|
+
| Merge | `volumes` | Array concatenation | All volume mounts apply |
|
|
261
|
+
| Merge | `imageBuildArgs` | Array concatenation | All build args apply |
|
|
230
262
|
|
|
231
263
|
#### Common Examples-Global
|
|
232
264
|
|
|
@@ -236,7 +268,7 @@ mkdir -p ~/.manyoyo/
|
|
|
236
268
|
cat > ~/.manyoyo/manyoyo.json << 'EOF'
|
|
237
269
|
{
|
|
238
270
|
"imageName": "localhost/xcanwin/manyoyo",
|
|
239
|
-
"imageVersion": "1.
|
|
271
|
+
"imageVersion": "1.7.0-full"
|
|
240
272
|
}
|
|
241
273
|
EOF
|
|
242
274
|
```
|
|
@@ -358,6 +390,8 @@ docker ps -a # Now you can use docker commands inside the container
|
|
|
358
390
|
| `-y CLI` | `--yolo` | Run AI agent without confirmation |
|
|
359
391
|
| `--show-config` | | Print final effective config and exit |
|
|
360
392
|
| `--show-command` | | Print the command to be executed and exit (docker exec if container exists, otherwise docker run) |
|
|
393
|
+
| `--yes` | | Auto-confirm all prompts (for CI/scripts) |
|
|
394
|
+
| `--rm-on-exit` | | Auto-remove container on exit (one-time mode) |
|
|
361
395
|
| `--install NAME` | | Install manyoyo command |
|
|
362
396
|
| `-q LIST` | `--quiet` | Quiet output |
|
|
363
397
|
| `-r NAME` | `--run` | Load run configuration (supports `name` or `./path.json`) |
|
|
@@ -384,6 +418,53 @@ docker ps -a # Now you can use docker commands inside the container
|
|
|
384
418
|
npm uninstall -g @xcanwin/manyoyo
|
|
385
419
|
```
|
|
386
420
|
|
|
421
|
+
## Troubleshooting FAQ
|
|
422
|
+
|
|
423
|
+
### Image Build Failed
|
|
424
|
+
|
|
425
|
+
**Problem**: Error when running `manyoyo --ib`
|
|
426
|
+
|
|
427
|
+
**Solutions**:
|
|
428
|
+
1. Check network connection: `curl -I https://mirrors.tencent.com`
|
|
429
|
+
2. Check disk space: `df -h` (need at least 10GB free space)
|
|
430
|
+
3. Use `--yes` to skip confirmation: `manyoyo --ib --iv 1.7.0 --yes`
|
|
431
|
+
4. If outside China, you may need to change mirror source (set `nodeMirror` in config file)
|
|
432
|
+
|
|
433
|
+
### Image Pull Failed
|
|
434
|
+
|
|
435
|
+
**Problem**: Error `pinging container registry localhost failed`
|
|
436
|
+
|
|
437
|
+
**Solutions**:
|
|
438
|
+
1. Local images need to be built first: `manyoyo --ib --iv 1.7.0`
|
|
439
|
+
2. Or modify `imageVersion` in config file `~/.manyoyo/manyoyo.json`
|
|
440
|
+
|
|
441
|
+
### Container Startup Failed
|
|
442
|
+
|
|
443
|
+
**Problem**: Container won't start or exits immediately
|
|
444
|
+
|
|
445
|
+
**Solutions**:
|
|
446
|
+
1. View container logs: `docker logs <container-name>`
|
|
447
|
+
2. Check port conflicts: `docker ps -a`
|
|
448
|
+
3. Check permission issues: Ensure current user has Docker/Podman permissions
|
|
449
|
+
|
|
450
|
+
### Permission Denied
|
|
451
|
+
|
|
452
|
+
**Problem**: Error `permission denied` or cannot access Docker
|
|
453
|
+
|
|
454
|
+
**Solutions**:
|
|
455
|
+
1. Add user to docker group: `sudo usermod -aG docker $USER`
|
|
456
|
+
2. Re-login or run: `newgrp docker`
|
|
457
|
+
3. Or run commands with `sudo`
|
|
458
|
+
|
|
459
|
+
### Environment Variables Not Working
|
|
460
|
+
|
|
461
|
+
**Problem**: Container cannot read configured environment variables
|
|
462
|
+
|
|
463
|
+
**Solutions**:
|
|
464
|
+
1. Check env file format (supports `KEY=VALUE` or `export KEY=VALUE`)
|
|
465
|
+
2. Verify file path is correct (`--ef name` corresponds to `~/.manyoyo/env/name.env`)
|
|
466
|
+
3. Use `--show-config` to view final effective configuration
|
|
467
|
+
|
|
387
468
|
## License
|
|
388
469
|
|
|
389
470
|
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"imageVersion": "1.
|
|
3
|
+
"version": "3.8.2",
|
|
4
|
+
"imageVersion": "1.7.0",
|
|
5
5
|
"description": "AI Agent CLI Security Sandbox",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai", "agent", "sandbox", "docker", "cli", "container", "development"
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"install-link": "npm link",
|
|
23
|
-
"test": "
|
|
23
|
+
"test": "jest --coverage",
|
|
24
|
+
"test:unit": "jest test/",
|
|
25
|
+
"lint": "echo 'Lint check passed'"
|
|
24
26
|
},
|
|
25
27
|
"homepage": "https://github.com/xcanwin/manyoyo",
|
|
26
28
|
"engines": {
|
|
@@ -37,5 +39,12 @@
|
|
|
37
39
|
"dependencies": {
|
|
38
40
|
"commander": "^12.0.0",
|
|
39
41
|
"json5": "^2.2.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"jest": "^29.7.0"
|
|
45
|
+
},
|
|
46
|
+
"jest": {
|
|
47
|
+
"testMatch": ["**/test/**/*.test.js"],
|
|
48
|
+
"testEnvironment": "node"
|
|
40
49
|
}
|
|
41
50
|
}
|