@xcanwin/manyoyo 4.2.8 → 5.0.0
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 +19 -19
- package/bin/manyoyo.js +208 -103
- package/config.example.json +1 -0
- package/lib/container-run.js +1 -0
- package/lib/init-config.js +1 -1
- package/lib/web/server.js +33 -5
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
```bash
|
|
47
47
|
npm install -g @xcanwin/manyoyo # 安装
|
|
48
48
|
podman pull ubuntu:24.04 # 仅 Podman 需要
|
|
49
|
-
manyoyo
|
|
50
|
-
manyoyo
|
|
51
|
-
manyoyo -r claude # 使用 manyoyo.json 的 runs.claude 启动
|
|
49
|
+
manyoyo build --iv 1.8.0-common # 构建镜像
|
|
50
|
+
manyoyo init all # 从本机 Agent 配置迁移到 ~/.manyoyo
|
|
51
|
+
manyoyo run -r claude # 使用 manyoyo.json 的 runs.claude 启动
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
注意:YOLO/SOLO 会跳过权限确认,请确保在可控环境中使用。
|
|
@@ -97,13 +97,13 @@ npm install -g @xcanwin/manyoyo
|
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
99
|
# 构建 common 版本(推荐)
|
|
100
|
-
manyoyo
|
|
100
|
+
manyoyo build --iv 1.8.0-common
|
|
101
101
|
|
|
102
102
|
# 构建 full 版本
|
|
103
|
-
manyoyo
|
|
103
|
+
manyoyo build --iv 1.8.0-full
|
|
104
104
|
|
|
105
105
|
# 构建自定义版本
|
|
106
|
-
manyoyo
|
|
106
|
+
manyoyo build --iba TOOL=go,codex,java,gemini
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
- 首次构建会自动下载依赖到 `docker/cache/`,2天内再次构建会使用缓存,速度提升约 **5 倍**
|
|
@@ -112,25 +112,25 @@ manyoyo --ib --iba TOOL=go,codex,java,gemini
|
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
114
|
# 配置迁移(推荐首步)
|
|
115
|
-
manyoyo
|
|
115
|
+
manyoyo init all
|
|
116
116
|
|
|
117
117
|
# 启动常见智能体
|
|
118
|
-
manyoyo -y c # Claude Code(或 claude / cc)
|
|
119
|
-
manyoyo -y gm # Gemini(或 gemini / g)
|
|
120
|
-
manyoyo -y cx # Codex(或 codex)
|
|
121
|
-
manyoyo -y oc # OpenCode(或 opencode)
|
|
122
|
-
manyoyo
|
|
118
|
+
manyoyo run -y c # Claude Code(或 claude / cc)
|
|
119
|
+
manyoyo run -y gm # Gemini(或 gemini / g)
|
|
120
|
+
manyoyo run -y cx # Codex(或 codex)
|
|
121
|
+
manyoyo run -y oc # OpenCode(或 opencode)
|
|
122
|
+
manyoyo update # 更新 MANYOYO(全局 npm 安装场景)
|
|
123
123
|
|
|
124
124
|
# 容器管理
|
|
125
|
-
manyoyo
|
|
126
|
-
manyoyo -n my-dev -x /bin/bash
|
|
127
|
-
manyoyo
|
|
128
|
-
manyoyo
|
|
129
|
-
manyoyo
|
|
125
|
+
manyoyo ls
|
|
126
|
+
manyoyo run -n my-dev -x /bin/bash
|
|
127
|
+
manyoyo rm my-dev
|
|
128
|
+
manyoyo serve 3000
|
|
129
|
+
manyoyo serve 3000 -u admin -P 123456
|
|
130
130
|
|
|
131
131
|
# 调试配置与命令拼装
|
|
132
|
-
manyoyo
|
|
133
|
-
manyoyo
|
|
132
|
+
manyoyo config show
|
|
133
|
+
manyoyo config command
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
## 配置
|
package/bin/manyoyo.js
CHANGED
|
@@ -55,18 +55,14 @@ let IMAGE_VERSION = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
|
|
|
55
55
|
let EXEC_COMMAND = "";
|
|
56
56
|
let EXEC_COMMAND_PREFIX = "";
|
|
57
57
|
let EXEC_COMMAND_SUFFIX = "";
|
|
58
|
-
let SHOULD_REMOVE = false;
|
|
59
|
-
let IMAGE_BUILD_NEED = false;
|
|
60
58
|
let IMAGE_BUILD_ARGS = [];
|
|
61
59
|
let CONTAINER_ENVS = [];
|
|
62
60
|
let CONTAINER_VOLUMES = [];
|
|
63
|
-
let
|
|
61
|
+
let CONTAINER_PORTS = [];
|
|
62
|
+
const MANYOYO_NAME = detectCommandName();
|
|
64
63
|
let CONT_MODE_ARGS = [];
|
|
65
64
|
let QUIET = {};
|
|
66
|
-
let SHOW_COMMAND = false;
|
|
67
|
-
let YES_MODE = false;
|
|
68
65
|
let RM_ON_EXIT = false;
|
|
69
|
-
let SERVER_MODE = false;
|
|
70
66
|
let SERVER_HOST = '127.0.0.1';
|
|
71
67
|
let SERVER_PORT = 3000;
|
|
72
68
|
let SERVER_AUTH_USER = "";
|
|
@@ -127,7 +123,7 @@ function validateServerHost(host, rawServer) {
|
|
|
127
123
|
return value;
|
|
128
124
|
}
|
|
129
125
|
|
|
130
|
-
console.error(`${RED}⚠️ 错误:
|
|
126
|
+
console.error(`${RED}⚠️ 错误: serve 地址格式应为 端口 或 host:port (例如 3000 / 0.0.0.0:3000): ${rawServer}${NC}`);
|
|
131
127
|
process.exit(1);
|
|
132
128
|
}
|
|
133
129
|
|
|
@@ -160,13 +156,13 @@ function parseServerListen(rawServer) {
|
|
|
160
156
|
}
|
|
161
157
|
|
|
162
158
|
if (!/^\d+$/.test(portText)) {
|
|
163
|
-
console.error(`${RED}⚠️ 错误:
|
|
159
|
+
console.error(`${RED}⚠️ 错误: serve 端口必须是 1-65535 的整数: ${rawServer}${NC}`);
|
|
164
160
|
process.exit(1);
|
|
165
161
|
}
|
|
166
162
|
|
|
167
163
|
const port = Number(portText);
|
|
168
164
|
if (port < 1 || port > 65535) {
|
|
169
|
-
console.error(`${RED}⚠️ 错误:
|
|
165
|
+
console.error(`${RED}⚠️ 错误: serve 端口超出范围 (1-65535): ${rawServer}${NC}`);
|
|
170
166
|
process.exit(1);
|
|
171
167
|
}
|
|
172
168
|
|
|
@@ -188,7 +184,7 @@ function ensureWebServerAuthCredentials() {
|
|
|
188
184
|
}
|
|
189
185
|
|
|
190
186
|
/**
|
|
191
|
-
* 敏感信息脱敏(用于
|
|
187
|
+
* 敏感信息脱敏(用于 config show 输出)
|
|
192
188
|
* @param {Object} obj - 配置对象
|
|
193
189
|
* @returns {Object} 脱敏后的配置对象
|
|
194
190
|
*/
|
|
@@ -298,12 +294,12 @@ function getHelloTip(containerName, defaultCommand, runningCommand) {
|
|
|
298
294
|
console.log(`${BLUE}----------------------------------------${NC}`);
|
|
299
295
|
console.log(`📦 首次命令 : ${defaultCommand}`);
|
|
300
296
|
if (resumeArg) {
|
|
301
|
-
console.log(`⚫ 恢复首次命令会话: ${CYAN}${MANYOYO_NAME} -n ${containerName} -- ${resumeArg}${NC}`);
|
|
297
|
+
console.log(`⚫ 恢复首次命令会话: ${CYAN}${MANYOYO_NAME} run -n ${containerName} -- ${resumeArg}${NC}`);
|
|
302
298
|
}
|
|
303
|
-
console.log(`⚫ 执行首次命令 : ${GREEN}${MANYOYO_NAME} -n ${containerName}${NC}`);
|
|
304
|
-
console.log(`⚫ 执行指定命令 : ${GREEN}${MANYOYO_NAME} -n ${containerName} -x /bin/bash${NC}`);
|
|
299
|
+
console.log(`⚫ 执行首次命令 : ${GREEN}${MANYOYO_NAME} run -n ${containerName}${NC}`);
|
|
300
|
+
console.log(`⚫ 执行指定命令 : ${GREEN}${MANYOYO_NAME} run -n ${containerName} -x /bin/bash${NC}`);
|
|
305
301
|
console.log(`⚫ 执行指定命令 : ${GREEN}docker exec -it ${containerName} /bin/bash${NC}`);
|
|
306
|
-
console.log(`⚫ 删除容器 : ${MANYOYO_NAME}
|
|
302
|
+
console.log(`⚫ 删除容器 : ${MANYOYO_NAME} rm ${containerName}`);
|
|
307
303
|
console.log("");
|
|
308
304
|
}
|
|
309
305
|
}
|
|
@@ -488,6 +484,10 @@ function addVolume(volume) {
|
|
|
488
484
|
CONTAINER_VOLUMES.push("--volume", volume);
|
|
489
485
|
}
|
|
490
486
|
|
|
487
|
+
function addPort(port) {
|
|
488
|
+
CONTAINER_PORTS.push("--publish", String(port));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
491
|
function addImageBuildArg(value) {
|
|
492
492
|
IMAGE_BUILD_ARGS.push("--build-arg", value);
|
|
493
493
|
}
|
|
@@ -567,7 +567,7 @@ function showImagePullHint(err) {
|
|
|
567
567
|
}
|
|
568
568
|
const image = `${IMAGE_NAME}:${IMAGE_VERSION}`;
|
|
569
569
|
console.log(`${YELLOW}💡 提示: 本地未找到镜像 ${image},并且从 localhost 注册表拉取失败。${NC}`);
|
|
570
|
-
console.log(`${YELLOW} 你可以: (1) 更新 ~/.manyoyo/manyoyo.json 的 imageVersion。 (2) 或先执行 ${MANYOYO_NAME}
|
|
570
|
+
console.log(`${YELLOW} 你可以: (1) 更新 ~/.manyoyo/manyoyo.json 的 imageVersion。 (2) 或先执行 ${MANYOYO_NAME} build --iv <x.y.z-后缀> 构建镜像。${NC}`);
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
function runCmd(cmd, args, options = {}) {
|
|
@@ -758,11 +758,74 @@ function normalizeShellFullArgv(argv) {
|
|
|
758
758
|
}
|
|
759
759
|
}
|
|
760
760
|
|
|
761
|
+
function appendArrayOption(command, flags, description) {
|
|
762
|
+
return command.option(
|
|
763
|
+
flags,
|
|
764
|
+
description,
|
|
765
|
+
(value, previous) => [...(previous || []), value],
|
|
766
|
+
[]
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function applyRunStyleOptions(command, options = {}) {
|
|
771
|
+
const includeRmOnExit = options.includeRmOnExit !== false;
|
|
772
|
+
const includeServePreview = options.includeServePreview === true;
|
|
773
|
+
const includeWebAuthOptions = options.includeWebAuthOptions === true;
|
|
774
|
+
|
|
775
|
+
command
|
|
776
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
777
|
+
.option('--hp, --host-path <path>', '设置宿主机工作目录 (默认当前路径)')
|
|
778
|
+
.option('-n, --cont-name <name>', '设置容器名称')
|
|
779
|
+
.option('--cp, --cont-path <path>', '设置容器工作目录')
|
|
780
|
+
.option('-m, --cont-mode <mode>', '设置容器嵌套容器模式 (common, dind, sock)')
|
|
781
|
+
.option('--in, --image-name <name>', '指定镜像名称')
|
|
782
|
+
.option('--iv, --image-ver <version>', '指定镜像版本 (格式: x.y.z-后缀,如 1.7.4-common)');
|
|
783
|
+
|
|
784
|
+
appendArrayOption(command, '-e, --env <env>', '设置环境变量 XXX=YYY (可多次使用)');
|
|
785
|
+
appendArrayOption(command, '--ef, --env-file <file>', '设置环境变量通过文件 (仅支持绝对路径,如 /abs/path.env)');
|
|
786
|
+
appendArrayOption(command, '-v, --volume <volume>', '绑定挂载卷 XXX:YYY (可多次使用)');
|
|
787
|
+
appendArrayOption(command, '-p, --port <port>', '设置端口映射 XXX:YYY (可多次使用)');
|
|
788
|
+
|
|
789
|
+
command
|
|
790
|
+
.option('--sp, --shell-prefix <command>', '临时环境变量 (作为-s前缀)')
|
|
791
|
+
.option('-s, --shell <command>', '指定命令执行')
|
|
792
|
+
.option('--ss, --shell-suffix <command>', '指定命令后缀 (追加到-s之后,等价于 -- <args>)')
|
|
793
|
+
.option('-x, --shell-full <command...>', '指定完整命令执行 (代替--sp和-s和--命令)')
|
|
794
|
+
.option('-y, --yolo <cli>', '使AGENT无需确认 (claude/c, gemini/gm, codex/cx, opencode/oc)');
|
|
795
|
+
|
|
796
|
+
if (includeRmOnExit) {
|
|
797
|
+
command.option('--rm-on-exit', '退出后自动删除容器 (一次性模式)');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
appendArrayOption(command, '-q, --quiet <item>', '静默显示 (可多次使用: cnew,crm,tip,cmd,full)');
|
|
801
|
+
|
|
802
|
+
if (includeServePreview) {
|
|
803
|
+
command
|
|
804
|
+
.option('--serve [listen]', '按 serve 模式解析配置 (支持 port 或 host:port)')
|
|
805
|
+
.option('-u, --user <username>', '网页服务登录用户名 (默认 admin)')
|
|
806
|
+
.option('-P, --pass <password>', '网页服务登录密码 (默认自动生成随机密码)');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (includeWebAuthOptions) {
|
|
810
|
+
command
|
|
811
|
+
.option('-u, --user <username>', '网页服务登录用户名 (默认 admin)')
|
|
812
|
+
.option('-P, --pass <password>', '网页服务登录密码 (默认自动生成随机密码)');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return command;
|
|
816
|
+
}
|
|
817
|
+
|
|
761
818
|
async function setupCommander() {
|
|
762
819
|
// Load config file
|
|
763
820
|
const config = loadConfig();
|
|
764
821
|
|
|
765
822
|
const program = new Command();
|
|
823
|
+
let selectedAction = '';
|
|
824
|
+
let selectedOptions = {};
|
|
825
|
+
const selectAction = (action, options = {}) => {
|
|
826
|
+
selectedAction = action;
|
|
827
|
+
selectedOptions = options;
|
|
828
|
+
};
|
|
766
829
|
|
|
767
830
|
program
|
|
768
831
|
.name(MANYOYO_NAME)
|
|
@@ -774,59 +837,94 @@ async function setupCommander() {
|
|
|
774
837
|
~/.manyoyo/run/c.json 运行配置示例
|
|
775
838
|
|
|
776
839
|
路径规则:
|
|
777
|
-
-r name
|
|
778
|
-
--ef /abs/path.env → 绝对路径环境文件
|
|
779
|
-
--ss "<args>"
|
|
780
|
-
-- <args...>
|
|
840
|
+
run -r name → ~/.manyoyo/manyoyo.json 的 runs.name
|
|
841
|
+
run --ef /abs/path.env → 绝对路径环境文件
|
|
842
|
+
run --ss "<args>" → 显式设置命令后缀
|
|
843
|
+
run -- <args...> → 直接透传命令后缀(优先级最高)
|
|
781
844
|
|
|
782
845
|
示例:
|
|
783
|
-
${MANYOYO_NAME}
|
|
784
|
-
${MANYOYO_NAME}
|
|
785
|
-
${MANYOYO_NAME}
|
|
786
|
-
${MANYOYO_NAME} -r claude
|
|
787
|
-
${MANYOYO_NAME} -r codex --ss "resume --last"
|
|
788
|
-
${MANYOYO_NAME} -n test --ef /abs/path/myenv.env -y c 使用绝对路径环境变量文件
|
|
789
|
-
${MANYOYO_NAME} -n test -- -c
|
|
790
|
-
${MANYOYO_NAME} -x echo 123
|
|
791
|
-
${MANYOYO_NAME}
|
|
792
|
-
${MANYOYO_NAME}
|
|
793
|
-
${MANYOYO_NAME}
|
|
794
|
-
${MANYOYO_NAME} -n test -q tip -q cmd 多次使用静默选项
|
|
846
|
+
${MANYOYO_NAME} update 更新 MANYOYO 到最新版本
|
|
847
|
+
${MANYOYO_NAME} build --iv ${IMAGE_VERSION_HELP_EXAMPLE} 构建镜像
|
|
848
|
+
${MANYOYO_NAME} init all 从本机 Agent 配置初始化 ~/.manyoyo
|
|
849
|
+
${MANYOYO_NAME} run -r claude 使用 manyoyo.json 的 runs.claude 快速启动
|
|
850
|
+
${MANYOYO_NAME} run -r codex --ss "resume --last" 使用命令后缀
|
|
851
|
+
${MANYOYO_NAME} run -n test --ef /abs/path/myenv.env -y c 使用绝对路径环境变量文件
|
|
852
|
+
${MANYOYO_NAME} run -n test -- -c 恢复之前会话
|
|
853
|
+
${MANYOYO_NAME} run -x "echo 123" 指定命令执行
|
|
854
|
+
${MANYOYO_NAME} serve 3000 -u admin -P 123456 启动带登录认证的网页服务
|
|
855
|
+
${MANYOYO_NAME} serve 0.0.0.0:3000 监听全部网卡,便于局域网访问
|
|
856
|
+
${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
|
|
795
857
|
`);
|
|
796
858
|
|
|
797
|
-
|
|
798
|
-
|
|
859
|
+
const runCommand = program.command('run').description('启动或连接容器并执行命令');
|
|
860
|
+
applyRunStyleOptions(runCommand);
|
|
861
|
+
runCommand.action(options => selectAction('run', options));
|
|
862
|
+
|
|
863
|
+
const buildCommand = program.command('build').description('构建 manyoyo 沙箱镜像');
|
|
864
|
+
buildCommand
|
|
799
865
|
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
800
|
-
.option('--hp, --host-path <path>', '设置宿主机工作目录 (默认当前路径)')
|
|
801
|
-
.option('-n, --cont-name <name>', '设置容器名称')
|
|
802
|
-
.option('--cp, --cont-path <path>', '设置容器工作目录')
|
|
803
|
-
.option('-l, --cont-list', '列举容器')
|
|
804
|
-
.option('--crm, --cont-remove', '删除-n指定容器')
|
|
805
|
-
.option('-m, --cont-mode <mode>', '设置容器嵌套容器模式 (common, dind, sock)')
|
|
806
866
|
.option('--in, --image-name <name>', '指定镜像名称')
|
|
807
867
|
.option('--iv, --image-ver <version>', '指定镜像版本 (格式: x.y.z-后缀,如 1.7.4-common)')
|
|
808
|
-
.option('--
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
.option('
|
|
815
|
-
.
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
.
|
|
819
|
-
.
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
868
|
+
.option('--yes', '所有提示自动确认 (用于CI/脚本)');
|
|
869
|
+
appendArrayOption(buildCommand, '--iba, --image-build-arg <arg>', '构建镜像时传参给dockerfile (可多次使用)');
|
|
870
|
+
buildCommand.action(options => selectAction('build', options));
|
|
871
|
+
|
|
872
|
+
const removeCommand = program.command('rm <name>').description('删除指定容器');
|
|
873
|
+
removeCommand
|
|
874
|
+
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
875
|
+
.action((name, options) => selectAction('rm', { ...options, contName: name }));
|
|
876
|
+
|
|
877
|
+
program.command('ls')
|
|
878
|
+
.description('列举容器')
|
|
879
|
+
.action(() => selectAction('ls', { contList: true }));
|
|
880
|
+
|
|
881
|
+
const serveCommand = program.command('serve [listen]').description('启动网页交互服务 (默认 127.0.0.1:3000)');
|
|
882
|
+
applyRunStyleOptions(serveCommand, { includeRmOnExit: false, includeWebAuthOptions: true });
|
|
883
|
+
serveCommand.action((listen, options) => {
|
|
884
|
+
selectAction('serve', {
|
|
885
|
+
...options,
|
|
886
|
+
server: listen === undefined ? true : listen,
|
|
887
|
+
serverUser: options.user,
|
|
888
|
+
serverPass: options.pass
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
const configCommand = program.command('config').description('查看解析后的配置或命令');
|
|
893
|
+
const configShowCommand = configCommand.command('show').description('显示最终生效配置并退出');
|
|
894
|
+
applyRunStyleOptions(configShowCommand, { includeRmOnExit: false, includeServePreview: true });
|
|
895
|
+
configShowCommand.action(options => {
|
|
896
|
+
const finalOptions = {
|
|
897
|
+
...options,
|
|
898
|
+
showConfig: true
|
|
899
|
+
};
|
|
900
|
+
if (options.serve !== undefined) {
|
|
901
|
+
finalOptions.server = options.serve;
|
|
902
|
+
finalOptions.serverUser = options.user;
|
|
903
|
+
finalOptions.serverPass = options.pass;
|
|
904
|
+
}
|
|
905
|
+
selectAction('config-show', finalOptions);
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
const configRunCommand = configCommand.command('command').description('显示将执行的 docker run 命令并退出');
|
|
909
|
+
applyRunStyleOptions(configRunCommand, { includeRmOnExit: false });
|
|
910
|
+
configRunCommand.action(options => selectAction('config-command', options));
|
|
911
|
+
|
|
912
|
+
const initCommand = program.command('init [agents]').description('初始化 Agent 配置到 ~/.manyoyo');
|
|
913
|
+
initCommand
|
|
827
914
|
.option('--yes', '所有提示自动确认 (用于CI/脚本)')
|
|
828
|
-
.
|
|
829
|
-
|
|
915
|
+
.action((agents, options) => selectAction('init', { ...options, initConfig: agents === undefined ? 'all' : agents }));
|
|
916
|
+
|
|
917
|
+
program.command('update')
|
|
918
|
+
.description('更新 MANYOYO(若检测为本地 file 安装则跳过)')
|
|
919
|
+
.action(() => selectAction('update', { update: true }));
|
|
920
|
+
|
|
921
|
+
program.command('install <name>')
|
|
922
|
+
.description(`安装${MANYOYO_NAME}命令 (docker-cli-plugin)`)
|
|
923
|
+
.action(name => selectAction('install', { install: name }));
|
|
924
|
+
|
|
925
|
+
program.command('prune')
|
|
926
|
+
.description('清理悬空镜像和 <none> 镜像')
|
|
927
|
+
.action(() => selectAction('prune', { imageRemove: true }));
|
|
830
928
|
|
|
831
929
|
// Docker CLI plugin metadata check
|
|
832
930
|
if (maybeHandleDockerPluginMetadata(process.argv)) {
|
|
@@ -841,25 +939,30 @@ async function setupCommander() {
|
|
|
841
939
|
program.help();
|
|
842
940
|
}
|
|
843
941
|
|
|
844
|
-
const isInitConfigMode = process.argv.some(arg => arg === '--init-config' || arg.startsWith('--init-config='));
|
|
845
|
-
const isUpdateMode = process.argv.includes('--update');
|
|
846
|
-
// init-config/update 只处理本地文件或 npm,不依赖 docker/podman
|
|
847
|
-
if (!isInitConfigMode && !isUpdateMode) {
|
|
848
|
-
// Ensure docker/podman is available
|
|
849
|
-
ensureDocker();
|
|
850
|
-
}
|
|
851
|
-
|
|
852
942
|
// Pre-handle -x/--shell-full: treat all following args as a single command
|
|
853
943
|
normalizeShellFullArgv(process.argv);
|
|
854
944
|
|
|
855
945
|
// Parse arguments
|
|
856
946
|
program.allowUnknownOption(false);
|
|
857
|
-
program.
|
|
947
|
+
await program.parseAsync(process.argv);
|
|
858
948
|
|
|
859
|
-
|
|
949
|
+
if (!selectedAction) {
|
|
950
|
+
program.help();
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const options = selectedOptions;
|
|
954
|
+
const yesMode = Boolean(options.yes);
|
|
955
|
+
const isBuildMode = selectedAction === 'build';
|
|
956
|
+
const isRemoveMode = selectedAction === 'rm';
|
|
957
|
+
const isListMode = selectedAction === 'ls';
|
|
958
|
+
const isPruneMode = selectedAction === 'prune';
|
|
959
|
+
const isShowConfigMode = selectedAction === 'config-show';
|
|
960
|
+
const isShowCommandMode = selectedAction === 'config-command';
|
|
961
|
+
const isServerMode = options.server !== undefined;
|
|
860
962
|
|
|
861
|
-
|
|
862
|
-
|
|
963
|
+
const noDockerActions = new Set(['init', 'update', 'install', 'config-show']);
|
|
964
|
+
if (!noDockerActions.has(selectedAction)) {
|
|
965
|
+
ensureDocker();
|
|
863
966
|
}
|
|
864
967
|
|
|
865
968
|
if (options.update) {
|
|
@@ -869,7 +972,7 @@ async function setupCommander() {
|
|
|
869
972
|
|
|
870
973
|
if (options.initConfig !== undefined) {
|
|
871
974
|
await initAgentConfigs(options.initConfig, {
|
|
872
|
-
yesMode
|
|
975
|
+
yesMode,
|
|
873
976
|
askQuestion,
|
|
874
977
|
loadConfig,
|
|
875
978
|
supportedAgents: SUPPORTED_INIT_AGENTS,
|
|
@@ -936,6 +1039,9 @@ async function setupCommander() {
|
|
|
936
1039
|
const volumeList = mergeArrayConfig(config.volumes, runConfig.volumes, options.volume);
|
|
937
1040
|
volumeList.forEach(v => addVolume(v));
|
|
938
1041
|
|
|
1042
|
+
const portList = mergeArrayConfig(config.ports, runConfig.ports, options.port);
|
|
1043
|
+
portList.forEach(p => addPort(p));
|
|
1044
|
+
|
|
939
1045
|
const buildArgList = mergeArrayConfig(config.imageBuildArgs, runConfig.imageBuildArgs, options.imageBuildArg);
|
|
940
1046
|
buildArgList.forEach(arg => addImageBuildArg(arg));
|
|
941
1047
|
|
|
@@ -968,8 +1074,7 @@ async function setupCommander() {
|
|
|
968
1074
|
RM_ON_EXIT = true;
|
|
969
1075
|
}
|
|
970
1076
|
|
|
971
|
-
if (
|
|
972
|
-
SERVER_MODE = true;
|
|
1077
|
+
if (isServerMode) {
|
|
973
1078
|
const serverListen = parseServerListen(options.server);
|
|
974
1079
|
SERVER_HOST = serverListen.host;
|
|
975
1080
|
SERVER_PORT = serverListen.port;
|
|
@@ -986,11 +1091,11 @@ async function setupCommander() {
|
|
|
986
1091
|
SERVER_AUTH_PASS_AUTO = false;
|
|
987
1092
|
}
|
|
988
1093
|
|
|
989
|
-
if (
|
|
1094
|
+
if (isServerMode) {
|
|
990
1095
|
ensureWebServerAuthCredentials();
|
|
991
1096
|
}
|
|
992
1097
|
|
|
993
|
-
if (
|
|
1098
|
+
if (isShowConfigMode) {
|
|
994
1099
|
const finalConfig = {
|
|
995
1100
|
hostPath: HOST_PATH,
|
|
996
1101
|
containerName: CONTAINER_NAME,
|
|
@@ -1000,6 +1105,7 @@ async function setupCommander() {
|
|
|
1000
1105
|
envFile: envFileList,
|
|
1001
1106
|
env: envMap,
|
|
1002
1107
|
volumes: volumeList,
|
|
1108
|
+
ports: portList,
|
|
1003
1109
|
imageBuildArgs: buildArgList,
|
|
1004
1110
|
containerMode: contModeValue || "",
|
|
1005
1111
|
shellPrefix: EXEC_COMMAND_PREFIX.trim(),
|
|
@@ -1007,9 +1113,9 @@ async function setupCommander() {
|
|
|
1007
1113
|
shellSuffix: EXEC_COMMAND_SUFFIX || "",
|
|
1008
1114
|
yolo: yoloValue || "",
|
|
1009
1115
|
quiet: quietValue || [],
|
|
1010
|
-
server:
|
|
1011
|
-
serverHost:
|
|
1012
|
-
serverPort:
|
|
1116
|
+
server: isServerMode,
|
|
1117
|
+
serverHost: isServerMode ? SERVER_HOST : null,
|
|
1118
|
+
serverPort: isServerMode ? SERVER_PORT : null,
|
|
1013
1119
|
serverUser: SERVER_AUTH_USER || "",
|
|
1014
1120
|
serverPass: SERVER_AUTH_PASS || "",
|
|
1015
1121
|
exec: {
|
|
@@ -1024,20 +1130,20 @@ async function setupCommander() {
|
|
|
1024
1130
|
process.exit(0);
|
|
1025
1131
|
}
|
|
1026
1132
|
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
if (options.contList) { getContList(); process.exit(0); }
|
|
1032
|
-
if (options.contRemove) SHOULD_REMOVE = true;
|
|
1033
|
-
if (options.imageBuild) IMAGE_BUILD_NEED = true;
|
|
1034
|
-
if (options.imageRemove) { pruneDanglingImages(); process.exit(0); }
|
|
1035
|
-
if (options.install) { installManyoyo(options.install); process.exit(0); }
|
|
1133
|
+
if (isListMode) { getContList(); process.exit(0); }
|
|
1134
|
+
if (isPruneMode) { pruneDanglingImages(); process.exit(0); }
|
|
1135
|
+
if (selectedAction === 'install') { installManyoyo(options.install); process.exit(0); }
|
|
1036
1136
|
|
|
1037
|
-
return
|
|
1137
|
+
return {
|
|
1138
|
+
yesMode,
|
|
1139
|
+
isBuildMode,
|
|
1140
|
+
isRemoveMode,
|
|
1141
|
+
isShowCommandMode,
|
|
1142
|
+
isServerMode
|
|
1143
|
+
};
|
|
1038
1144
|
}
|
|
1039
1145
|
|
|
1040
|
-
function createRuntimeContext() {
|
|
1146
|
+
function createRuntimeContext(modeState = {}) {
|
|
1041
1147
|
return {
|
|
1042
1148
|
containerName: CONTAINER_NAME,
|
|
1043
1149
|
hostPath: HOST_PATH,
|
|
@@ -1050,10 +1156,11 @@ function createRuntimeContext() {
|
|
|
1050
1156
|
contModeArgs: CONT_MODE_ARGS,
|
|
1051
1157
|
containerEnvs: CONTAINER_ENVS,
|
|
1052
1158
|
containerVolumes: CONTAINER_VOLUMES,
|
|
1159
|
+
containerPorts: CONTAINER_PORTS,
|
|
1053
1160
|
quiet: QUIET,
|
|
1054
|
-
showCommand:
|
|
1161
|
+
showCommand: Boolean(modeState.isShowCommandMode),
|
|
1055
1162
|
rmOnExit: RM_ON_EXIT,
|
|
1056
|
-
serverMode:
|
|
1163
|
+
serverMode: Boolean(modeState.isServerMode),
|
|
1057
1164
|
serverHost: SERVER_HOST,
|
|
1058
1165
|
serverPort: SERVER_PORT,
|
|
1059
1166
|
serverAuthUser: SERVER_AUTH_USER,
|
|
@@ -1063,10 +1170,6 @@ function createRuntimeContext() {
|
|
|
1063
1170
|
}
|
|
1064
1171
|
|
|
1065
1172
|
function handleRemoveContainer(runtime) {
|
|
1066
|
-
if (!SHOULD_REMOVE) {
|
|
1067
|
-
return false;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
1173
|
try {
|
|
1071
1174
|
if (containerExists(runtime.containerName)) {
|
|
1072
1175
|
removeContainer(runtime.containerName);
|
|
@@ -1076,7 +1179,6 @@ function handleRemoveContainer(runtime) {
|
|
|
1076
1179
|
} catch (e) {
|
|
1077
1180
|
console.log(`${RED}⚠️ 错误: 未找到名为 ${runtime.containerName} 的容器。${NC}`);
|
|
1078
1181
|
}
|
|
1079
|
-
return true;
|
|
1080
1182
|
}
|
|
1081
1183
|
|
|
1082
1184
|
function validateHostPath(runtime) {
|
|
@@ -1180,6 +1282,7 @@ function buildDockerRunArgs(runtime) {
|
|
|
1180
1282
|
contModeArgs: runtime.contModeArgs,
|
|
1181
1283
|
containerEnvs: runtime.containerEnvs,
|
|
1182
1284
|
containerVolumes: runtime.containerVolumes,
|
|
1285
|
+
containerPorts: runtime.containerPorts,
|
|
1183
1286
|
defaultCommand: runtime.execCommand
|
|
1184
1287
|
});
|
|
1185
1288
|
}
|
|
@@ -1343,6 +1446,7 @@ async function runWebServerMode(runtime) {
|
|
|
1343
1446
|
contModeArgs: runtime.contModeArgs,
|
|
1344
1447
|
containerEnvs: runtime.containerEnvs,
|
|
1345
1448
|
containerVolumes: runtime.containerVolumes,
|
|
1449
|
+
containerPorts: runtime.containerPorts,
|
|
1346
1450
|
validateHostPath: () => validateHostPath(runtime),
|
|
1347
1451
|
formatDate,
|
|
1348
1452
|
isValidContainerName,
|
|
@@ -1367,8 +1471,8 @@ async function runWebServerMode(runtime) {
|
|
|
1367
1471
|
async function main() {
|
|
1368
1472
|
try {
|
|
1369
1473
|
// 1. Setup commander and parse arguments
|
|
1370
|
-
await setupCommander();
|
|
1371
|
-
const runtime = createRuntimeContext();
|
|
1474
|
+
const modeState = await setupCommander();
|
|
1475
|
+
const runtime = createRuntimeContext(modeState);
|
|
1372
1476
|
|
|
1373
1477
|
// 2. Start web server mode
|
|
1374
1478
|
if (runtime.serverMode) {
|
|
@@ -1377,7 +1481,7 @@ async function main() {
|
|
|
1377
1481
|
}
|
|
1378
1482
|
|
|
1379
1483
|
// 3. Handle image build operation
|
|
1380
|
-
if (
|
|
1484
|
+
if (modeState.isBuildMode) {
|
|
1381
1485
|
await buildImage({
|
|
1382
1486
|
imageBuildArgs: IMAGE_BUILD_ARGS,
|
|
1383
1487
|
imageName: runtime.imageName,
|
|
@@ -1386,7 +1490,7 @@ async function main() {
|
|
|
1386
1490
|
imageVersionBase: IMAGE_VERSION_BASE,
|
|
1387
1491
|
parseImageVersionTag,
|
|
1388
1492
|
manyoyoName: MANYOYO_NAME,
|
|
1389
|
-
yesMode:
|
|
1493
|
+
yesMode: Boolean(modeState.yesMode),
|
|
1390
1494
|
dockerCmd: DOCKER_CMD,
|
|
1391
1495
|
rootDir: path.join(__dirname, '..'),
|
|
1392
1496
|
loadConfig,
|
|
@@ -1399,7 +1503,8 @@ async function main() {
|
|
|
1399
1503
|
}
|
|
1400
1504
|
|
|
1401
1505
|
// 4. Handle remove container operation
|
|
1402
|
-
if (
|
|
1506
|
+
if (modeState.isRemoveMode) {
|
|
1507
|
+
handleRemoveContainer(runtime);
|
|
1403
1508
|
return;
|
|
1404
1509
|
}
|
|
1405
1510
|
|
package/config.example.json
CHANGED
package/lib/container-run.js
CHANGED
|
@@ -12,6 +12,7 @@ function buildContainerRunArgs(options) {
|
|
|
12
12
|
...(options.contModeArgs || []),
|
|
13
13
|
...(options.containerEnvs || []),
|
|
14
14
|
...(options.containerVolumes || []),
|
|
15
|
+
...(options.containerPorts || []),
|
|
15
16
|
'--volume', `${options.hostPath}:${options.containerPath}`,
|
|
16
17
|
'--workdir', options.containerPath,
|
|
17
18
|
'--label', `manyoyo.default_cmd=${sanitizeDefaultCommand(options.defaultCommand)}`,
|
package/lib/init-config.js
CHANGED
|
@@ -126,7 +126,7 @@ function normalizeInitConfigAgents(rawAgents, ctx) {
|
|
|
126
126
|
const mapped = aliasMap[token];
|
|
127
127
|
if (!mapped) {
|
|
128
128
|
const { RED, YELLOW, NC } = ctx.colors;
|
|
129
|
-
ctx.error(`${RED}⚠️ 错误:
|
|
129
|
+
ctx.error(`${RED}⚠️ 错误: init 不支持的 Agent: ${token}${NC}`);
|
|
130
130
|
ctx.error(`${YELLOW}支持: ${ctx.supportedAgents.join(', ')} 或 all${NC}`);
|
|
131
131
|
ctx.exit(1);
|
|
132
132
|
return [];
|
package/lib/web/server.js
CHANGED
|
@@ -51,7 +51,8 @@ const DEFAULT_WEB_CONFIG_TEMPLATE = `{
|
|
|
51
51
|
"yolo": "",
|
|
52
52
|
"env": {},
|
|
53
53
|
"envFile": [],
|
|
54
|
-
"volumes": []
|
|
54
|
+
"volumes": [],
|
|
55
|
+
"ports": []
|
|
55
56
|
}
|
|
56
57
|
`;
|
|
57
58
|
|
|
@@ -474,6 +475,9 @@ function validateWebConfigShape(configObject) {
|
|
|
474
475
|
if (hasOwn(config, 'volumes')) {
|
|
475
476
|
normalizeStringArray(config.volumes, 'volumes');
|
|
476
477
|
}
|
|
478
|
+
if (hasOwn(config, 'ports')) {
|
|
479
|
+
normalizeStringArray(config.ports, 'ports');
|
|
480
|
+
}
|
|
477
481
|
if (hasOwn(config, 'runs')) {
|
|
478
482
|
const runs = config.runs;
|
|
479
483
|
if (runs !== undefined && (typeof runs !== 'object' || runs === null || Array.isArray(runs))) {
|
|
@@ -555,7 +559,8 @@ function buildConfigDefaults(ctx, config) {
|
|
|
555
559
|
yolo: hasOwn(parsed, 'yolo') ? String(parsed.yolo || '') : '',
|
|
556
560
|
env: {},
|
|
557
561
|
envFile: [],
|
|
558
|
-
volumes: []
|
|
562
|
+
volumes: [],
|
|
563
|
+
ports: []
|
|
559
564
|
};
|
|
560
565
|
|
|
561
566
|
try {
|
|
@@ -573,6 +578,11 @@ function buildConfigDefaults(ctx, config) {
|
|
|
573
578
|
} catch (e) {
|
|
574
579
|
defaults.volumes = [];
|
|
575
580
|
}
|
|
581
|
+
try {
|
|
582
|
+
defaults.ports = normalizeStringArray(parsed.ports, 'ports');
|
|
583
|
+
} catch (e) {
|
|
584
|
+
defaults.ports = [];
|
|
585
|
+
}
|
|
576
586
|
|
|
577
587
|
return defaults;
|
|
578
588
|
}
|
|
@@ -587,6 +597,7 @@ function buildStaticContainerRuntime(ctx, containerName) {
|
|
|
587
597
|
contModeArgs: Array.isArray(ctx.contModeArgs) ? ctx.contModeArgs.slice() : [],
|
|
588
598
|
containerEnvs: Array.isArray(ctx.containerEnvs) ? ctx.containerEnvs.slice() : [],
|
|
589
599
|
containerVolumes: Array.isArray(ctx.containerVolumes) ? ctx.containerVolumes.slice() : [],
|
|
600
|
+
containerPorts: Array.isArray(ctx.containerPorts) ? ctx.containerPorts.slice() : [],
|
|
590
601
|
defaultCommand: buildDefaultCommand(ctx.execCommandPrefix, ctx.execCommand, ctx.execCommandSuffix) || '/bin/bash'
|
|
591
602
|
};
|
|
592
603
|
}
|
|
@@ -600,9 +611,11 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
600
611
|
const hasRequestEnv = hasOwn(requestOptions, 'env');
|
|
601
612
|
const hasRequestEnvFile = hasOwn(requestOptions, 'envFile');
|
|
602
613
|
const hasRequestVolumes = hasOwn(requestOptions, 'volumes');
|
|
614
|
+
const hasRequestPorts = hasOwn(requestOptions, 'ports');
|
|
603
615
|
const hasConfigEnv = hasOwn(config, 'env');
|
|
604
616
|
const hasConfigEnvFile = hasOwn(config, 'envFile');
|
|
605
617
|
const hasConfigVolumes = hasOwn(config, 'volumes');
|
|
618
|
+
const hasConfigPorts = hasOwn(config, 'ports');
|
|
606
619
|
|
|
607
620
|
const requestName = pickFirstString(requestOptions.containerName, body.name);
|
|
608
621
|
let containerName = pickFirstString(requestName, config.containerName);
|
|
@@ -683,6 +696,17 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
683
696
|
});
|
|
684
697
|
}
|
|
685
698
|
|
|
699
|
+
let containerPorts = Array.isArray(ctx.containerPorts) ? ctx.containerPorts.slice() : [];
|
|
700
|
+
if (hasRequestPorts || hasConfigPorts) {
|
|
701
|
+
const portList = hasRequestPorts
|
|
702
|
+
? normalizeStringArray(requestOptions.ports, 'createOptions.ports')
|
|
703
|
+
: normalizeStringArray(config.ports, 'config.ports');
|
|
704
|
+
containerPorts = [];
|
|
705
|
+
portList.forEach(port => {
|
|
706
|
+
containerPorts.push('--publish', port);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
686
710
|
return {
|
|
687
711
|
containerName,
|
|
688
712
|
hostPath,
|
|
@@ -692,6 +716,7 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
692
716
|
contModeArgs,
|
|
693
717
|
containerEnvs,
|
|
694
718
|
containerVolumes,
|
|
719
|
+
containerPorts,
|
|
695
720
|
defaultCommand: buildDefaultCommand(shellPrefix, shell, shellSuffix) || '/bin/bash',
|
|
696
721
|
applied: {
|
|
697
722
|
containerName,
|
|
@@ -705,7 +730,8 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
705
730
|
shellSuffix: shellSuffix || '',
|
|
706
731
|
yolo: yolo || '',
|
|
707
732
|
envCount: Math.floor(containerEnvs.length / 2),
|
|
708
|
-
volumeCount: Math.floor(containerVolumes.length / 2)
|
|
733
|
+
volumeCount: Math.floor(containerVolumes.length / 2),
|
|
734
|
+
portCount: Math.floor(containerPorts.length / 2)
|
|
709
735
|
}
|
|
710
736
|
};
|
|
711
737
|
}
|
|
@@ -755,6 +781,7 @@ async function ensureWebContainer(ctx, state, containerInput) {
|
|
|
755
781
|
contModeArgs: runtime.contModeArgs,
|
|
756
782
|
containerEnvs: runtime.containerEnvs,
|
|
757
783
|
containerVolumes: runtime.containerVolumes,
|
|
784
|
+
containerPorts: runtime.containerPorts,
|
|
758
785
|
defaultCommand: runtime.defaultCommand
|
|
759
786
|
});
|
|
760
787
|
|
|
@@ -1407,6 +1434,7 @@ async function startWebServer(options) {
|
|
|
1407
1434
|
contModeArgs: options.contModeArgs,
|
|
1408
1435
|
containerEnvs: options.containerEnvs,
|
|
1409
1436
|
containerVolumes: options.containerVolumes,
|
|
1437
|
+
containerPorts: options.containerPorts,
|
|
1410
1438
|
validateHostPath: options.validateHostPath,
|
|
1411
1439
|
formatDate: options.formatDate,
|
|
1412
1440
|
isValidContainerName: options.isValidContainerName,
|
|
@@ -1425,7 +1453,7 @@ async function startWebServer(options) {
|
|
|
1425
1453
|
};
|
|
1426
1454
|
|
|
1427
1455
|
if (!ctx.authUser || !ctx.authPass) {
|
|
1428
|
-
throw new Error('Web 认证配置缺失,请设置
|
|
1456
|
+
throw new Error('Web 认证配置缺失,请设置 serve -u / serve -P');
|
|
1429
1457
|
}
|
|
1430
1458
|
|
|
1431
1459
|
const state = {
|
|
@@ -1593,7 +1621,7 @@ async function startWebServer(options) {
|
|
|
1593
1621
|
if (ctx.authPassAuto) {
|
|
1594
1622
|
console.log(`${CYAN}🔐 登录密码(本次随机): ${YELLOW}${ctx.authPass}${NC}`);
|
|
1595
1623
|
} else {
|
|
1596
|
-
console.log(`${CYAN}🔐 登录密码: 使用你配置的
|
|
1624
|
+
console.log(`${CYAN}🔐 登录密码: 使用你配置的 serve -P / serverPass / MANYOYO_SERVER_PASS${NC}`);
|
|
1597
1625
|
}
|
|
1598
1626
|
resolve();
|
|
1599
1627
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"imageVersion": "1.8.0-common",
|
|
5
5
|
"description": "AI Agent CLI Security Sandbox for Docker and Podman",
|
|
6
6
|
"keywords": [
|
|
@@ -60,9 +60,14 @@
|
|
|
60
60
|
"ws": "^8.19.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"jest": "^
|
|
63
|
+
"jest": "^30.2.0",
|
|
64
64
|
"vitepress": "^2.0.0-alpha.16"
|
|
65
65
|
},
|
|
66
|
+
"overrides": {
|
|
67
|
+
"glob": "^13.0.6",
|
|
68
|
+
"minimatch": "^10.2.2",
|
|
69
|
+
"test-exclude": "^8.0.0"
|
|
70
|
+
},
|
|
66
71
|
"jest": {
|
|
67
72
|
"testMatch": [
|
|
68
73
|
"**/test/**/*.test.js"
|